commit 72cf89bce8e4230dbc161dc5606f48ef9884ba70 Author: Thiago de Arruda Date: Fri Jan 31 10:39:15 2014 -0300 Import vim from changeset v5628:c9cad40b4181 - Cleanup source tree, leaving only files necessary for compilation/testing - Process files through unifdef to remove tons of FEAT_* macros - Process files through uncrustify to normalize source code formatting. - Port the build system to cmake diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..d87a317eea --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +build/ +*.rej +*.orig +*.mo +*.swp +*~ +*.pyc +src/po/vim.pot + +src/po/*.ck +src/testdir/mbyte.vim +src/testdir/mzscheme.vim +src/testdir/lua.vim +src/testdir/small.vim +src/testdir/tiny.vim +src/testdir/test*.out +src/testdir/test.log diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000000..adb9894a3c --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,25 @@ +cmake_minimum_required (VERSION 2.6) +project (NEOVIM) + +set(NEOVIM_VERSION_MAJOR 0) +set(NEOVIM_VERSION_MINOR 0) +set(NEOVIM_VERSION_PATCH 0) + +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +add_definitions(-DHAVE_CONFIG_H -Wall -std=gnu99) +if(CMAKE_BUILD_TYPE MATCHES Debug) + # cmake automatically appends -g to the compiler flags + set(DEBUG 1) +else() + set(DEBUG 0) +endif() + +# add dependencies to include/lib directories +link_directories ("${PROJECT_SOURCE_DIR}/.deps/usr/lib") +include_directories ("${PROJECT_SOURCE_DIR}/.deps/usr/include") + +include_directories ("${PROJECT_BINARY_DIR}/config") + +add_subdirectory(src) +add_subdirectory(config) diff --git a/Makefile b/Makefile new file mode 100644 index 0000000000..4bdfaa39aa --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +CMAKE_FLAGS := -DCMAKE_BUILD_TYPE=Debug + +test: build/src/vim + cd src/testdir && make + +build/src/vim: + cd build && make + +cmake: + rm -rf build + mkdir build + cd build && cmake $(CMAKE_FLAGS) ../ + +.PHONY: test cmake diff --git a/config/CMakeLists.txt b/config/CMakeLists.txt new file mode 100644 index 0000000000..3d9fd04c25 --- /dev/null +++ b/config/CMakeLists.txt @@ -0,0 +1,30 @@ +include(CheckTypeSize) +check_type_size("int" SIZEOF_INT) +check_type_size("long" SIZEOF_LONG) +check_type_size("time_t" SIZEOF_TIME_T) +check_type_size("off_t" SIZEOF_OFF_T) + +# generate configuration header and update include directories +configure_file ( + "${PROJECT_SOURCE_DIR}/config/config.h.in" + "${PROJECT_BINARY_DIR}/config/auto/config.h" + ) +# generate pathdef.c +find_program(WHOAMI_PROG whoami) +find_program(HOSTNAME_PROG hostname) + +if (EXISTS ${WHOAMI_PROG}) + execute_process(COMMAND ${WHOAMI_PROG} + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE USERNAME) +endif() +if (EXISTS ${HOSTNAME_PROG}) + execute_process(COMMAND ${HOSTNAME_PROG} + OUTPUT_STRIP_TRAILING_WHITESPACE + OUTPUT_VARIABLE HOSTNAME) +endif() + +configure_file ( + "${PROJECT_SOURCE_DIR}/config/pathdef.c.in" + "${PROJECT_BINARY_DIR}/config/auto/pathdef.c" + ESCAPE_QUOTES) diff --git a/config/config.h.in b/config/config.h.in new file mode 100644 index 0000000000..6606c78c87 --- /dev/null +++ b/config/config.h.in @@ -0,0 +1,214 @@ +#define NEOVIM_VERSION_MAJOR @NEOVIM_VERSION_MAJOR@ +#define NEOVIM_VERSION_MINOR @NEOVIM_VERSION_MINOR@ +#define NEOVIM_VERSION_PATCH @NEOVIM_VERSION_PATCH@ + +#if @DEBUG@ +#define DEBUG +#endif + +#define SIZEOF_INT @SIZEOF_INT@ +#define SIZEOF_LONG @SIZEOF_LONG@ +#define SIZEOF_TIME_T @SIZEOF_TIME_T@ +#define SIZEOF_OFF_T @SIZEOF_OFF_T@ + +#define _FILE_OFFSET_BITS 64 +#define HAVE_ATTRIBUTE_UNUSED 1 +#define HAVE_BCMP 1 +#define HAVE_BIND_TEXTDOMAIN_CODESET 1 +#define HAVE_DATE_TIME 1 +#define HAVE_DIRENT_H 1 +#define HAVE_DLFCN_H 1 +#define HAVE_DLOPEN 1 +#define HAVE_DLSYM 1 +#define HAVE_ERRNO_H 1 +#define HAVE_FCHDIR 1 +#define HAVE_FCHOWN 1 +#define HAVE_FCNTL_H 1 +#define HAVE_FD_CLOEXEC 1 +#define HAVE_FLOAT_FUNCS 1 +#define HAVE_FSEEKO 1 +#define HAVE_FSYNC 1 +#define HAVE_GETCWD 1 +#define HAVE_GETPWENT 1 +#define HAVE_GETPWNAM 1 +#define HAVE_GETPWUID 1 +#define HAVE_GETRLIMIT 1 +#define HAVE_GETTEXT 1 +#define HAVE_GETTIMEOFDAY 1 +#define HAVE_GETWD 1 +#define HAVE_ICONV 1 +#define HAVE_ICONV_H 1 +#define HAVE_INTTYPES_H 1 +#define HAVE_ISWUPPER 1 +#define HAVE_LANGINFO_H 1 +#define HAVE_LIBGEN_H 1 +#define HAVE_LIBINTL_H 1 +#define HAVE_LOCALE_H 1 +#define HAVE_LSTAT 1 +#define HAVE_MATH_H 1 +#define HAVE_MEMCMP 1 +#define HAVE_MEMSET 1 +#define HAVE_MKDTEMP 1 +#define HAVE_NANOSLEEP 1 +#define HAVE_NL_LANGINFO_CODESET 1 +#define HAVE_NL_MSG_CAT_CNTR 1 +#define HAVE_OPENDIR 1 +#define HAVE_OSPEED 1 +#define HAVE_POLL_H 1 +#define HAVE_PUTENV 1 +#define HAVE_PWD_H 1 +#define HAVE_QSORT 1 +#define HAVE_READLINK 1 +#define HAVE_RENAME 1 +#define HAVE_SELECT 1 +#define HAVE_SELINUX 1 +#define HAVE_SETENV 1 +#define HAVE_SETJMP_H 1 +#define HAVE_SETPGID 1 +#define HAVE_SETSID 1 +#define HAVE_SGTTY_H 1 +#define HAVE_SIGACTION 1 +#define HAVE_SIGALTSTACK 1 +#define HAVE_SIGCONTEXT 1 +#define HAVE_SIGSTACK 1 +#define HAVE_SIGVEC 1 +#define HAVE_ST_BLKSIZE 1 +#define HAVE_STDARG_H 1 +#define HAVE_STDINT_H 1 +#define HAVE_STDLIB_H 1 +#define HAVE_STRCASECMP 1 +#define HAVE_STRERROR 1 +#define HAVE_STRFTIME 1 +#define HAVE_STRING_H 1 +#define HAVE_STRINGS_H 1 +#define HAVE_STRNCASECMP 1 +#define HAVE_STROPTS_H 1 +#define HAVE_STRPBRK 1 +#define HAVE_STRTOL 1 +#define HAVE_SVR4_PTYS 1 +#define HAVE_SYSCONF 1 +#define HAVE_SYSINFO 1 +#define HAVE_SYSINFO_MEM_UNIT 1 +#define HAVE_SYS_IOCTL_H 1 +#define HAVE_SYS_PARAM_H 1 +#define HAVE_SYS_POLL_H 1 +#define HAVE_SYS_RESOURCE_H 1 +#define HAVE_SYS_SELECT_H 1 +#define HAVE_SYS_STATFS_H 1 +#define HAVE_SYS_SYSCTL_H 1 +#define HAVE_SYS_SYSINFO_H 1 +#define HAVE_SYS_TIME_H 1 +#define HAVE_SYS_TYPES_H 1 +#define HAVE_SYS_UTSNAME_H 1 +#define HAVE_SYS_WAIT_H 1 +#define HAVE_TERMCAP_H 1 +#define HAVE_TERMIO_H 1 +#define HAVE_TERMIOS_H 1 +#define HAVE_TGETENT 1 +#define HAVE_TOWLOWER 1 +#define HAVE_TOWUPPER 1 +#define HAVE_UNISTD_H 1 +#define HAVE_UP_BC_PC 1 +#define HAVE_USLEEP 1 +#define HAVE_UTIME 1 +#define HAVE_UTIME_H 1 +#define HAVE_UTIMES 1 +#define HAVE_WCHAR_H 1 +#define HAVE_WCTYPE_H 1 +#define RETSIGTYPE void +#define SIGRETURN return +#define SYS_SELECT_WITH_SYS_TIME 1 +#define TERMINFO 1 +#define TGETENT_ZERO_ERR 0 +#define TIME_WITH_SYS_TIME 1 +#define UNIX 1 +#define USEMAN_S 1 +#define USEMEMMOVE 1 + +#define FEAT_ARABIC +#define FEAT_AUTOCHDIR +#define FEAT_AUTOCMD +#define FEAT_BROWSE +#define FEAT_BROWSE_CMD +#define FEAT_BYTEOFF +#define FEAT_CINDENT +#define FEAT_CMDHIST +#define FEAT_CMDL_COMPL +#define FEAT_CMDL_INFO +#define FEAT_CMDWIN +#define FEAT_COMMENTS +#define FEAT_COMPL_FUNC +#define FEAT_CONCEAL +#define FEAT_CON_DIALOG +#define FEAT_CRYPT +#define FEAT_CSCOPE +#define FEAT_CURSORBIND +#define FEAT_DIFF +#define FEAT_DIGRAPHS +#define FEAT_EVAL +#define FEAT_EX_EXTRA +#define FEAT_FIND_ID +#define FEAT_FKMAP +#define FEAT_FLOAT +#define FEAT_FOLDING +#define FEAT_GETTEXT +#define FEAT_HANGULIN +#define FEAT_INS_EXPAND +#define FEAT_JUMPLIST +#define FEAT_KEYMAP +#define FEAT_LANGMAP +#define FEAT_LINEBREAK +#define FEAT_LISP +#define FEAT_LISTCMDS +#define FEAT_LOCALMAP +#define FEAT_MBYTE +#define FEAT_MENU +#define FEAT_MODIFY_FNAME +#define FEAT_MOUSE +#define FEAT_MOUSE_DEC +#define FEAT_MOUSE_NET +#define FEAT_MOUSE_SGR +#define FEAT_MOUSE_TTY +#define FEAT_MOUSE_URXVT +#define FEAT_MOUSE_XTERM +#define FEAT_MULTI_LANG +#define FEAT_PATH_EXTRA +#define FEAT_PERSISTENT_UNDO +#define FEAT_POSTSCRIPT +#define FEAT_PRINTER +#define FEAT_PROFILE +#define FEAT_QUICKFIX +#define FEAT_RELTIME +#define FEAT_RIGHTLEFT +#define FEAT_SCROLLBIND +#define FEAT_SEARCHPATH +#define FEAT_SEARCH_EXTRA +#define FEAT_SESSION +#define FEAT_SMARTINDENT +#define FEAT_SPELL +#define FEAT_STL_OPT +#define FEAT_SYN_HL +#define FEAT_TAG_BINS +#define FEAT_TAG_OLDSTATIC +#define FEAT_TERMRESPONSE +#define FEAT_TEXTOBJ +#define FEAT_TITLE +#define FEAT_USR_CMDS +#define FEAT_VERTSPLIT +#define FEAT_VIMINFO +#define FEAT_VIRTUALEDIT +#define FEAT_VISUAL +#define FEAT_VISUALEXTRA +#define FEAT_VREPLACE +#define FEAT_WAK +#define FEAT_WILDIGN +#define FEAT_WILDMENU +#define FEAT_WINDOWS +#define FEAT_WRITEBACK +#define FEAT_HUGE +#define FEAT_BIG +#define FEAT_NORMAL +#define FEAT_SMALL +#define FEAT_TINY +#define FEAT_WRITEBACKUP +#define VIM_BACKTICK /* internal backtick expansion */ diff --git a/config/pathdef.c.in b/config/pathdef.c.in new file mode 100644 index 0000000000..6ba5358689 --- /dev/null +++ b/config/pathdef.c.in @@ -0,0 +1,7 @@ +#include "${PROJECT_SOURCE_DIR}/src/vim.h" +char_u *default_vim_dir = (char_u *)"${CMAKE_INSTALL_PREFIX}/share/vim"; +char_u *default_vimruntime_dir = (char_u *)""; +char_u *all_cflags = (char_u *)"${COMPILER_FLAGS}"; +char_u *all_lflags = (char_u *)"${LINKER_FLAGS}"; +char_u *compiled_user = (char_u *)"${USERNAME}"; +char_u *compiled_sys = (char_u *)"${HOSTNAME}"; diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000000..65138a6d5d --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,18 @@ +file( GLOB NEOVIM_SOURCES *.c ) + +foreach(sfile ${NEOVIM_SOURCES}) + get_filename_component(f ${sfile} NAME) + if(${f} MATCHES "^(regexp_nfa.c|farsi.c|arabic.c)$") + list(APPEND to_remove ${sfile}) + endif() +endforeach() + +list(REMOVE_ITEM NEOVIM_SOURCES ${to_remove}) +list(APPEND NEOVIM_SOURCES "${PROJECT_BINARY_DIR}/config/auto/pathdef.c") + +file( GLOB IO_SOURCES io/*.c ) + +add_executable (vim ${NEOVIM_SOURCES} ${IO_SOURCES}) + +target_link_libraries (vim m termcap selinux) +include_directories ("${PROJECT_SOURCE_DIR}/src/proto") diff --git a/src/arabic.c b/src/arabic.c new file mode 100644 index 0000000000..c8d3fc2a69 --- /dev/null +++ b/src/arabic.c @@ -0,0 +1,1134 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * arabic.c: functions for Arabic language + * + * Included by main.c, when FEAT_ARABIC & FEAT_GUI is defined. + * + * -- + * + * Author: Nadim Shaikli & Isam Bayazidi + * + */ + +static int A_is_a __ARGS((int cur_c)); +static int A_is_s __ARGS((int cur_c)); +static int A_is_f __ARGS((int cur_c)); +static int chg_c_a2s __ARGS((int cur_c)); +static int chg_c_a2i __ARGS((int cur_c)); +static int chg_c_a2m __ARGS((int cur_c)); +static int chg_c_a2f __ARGS((int cur_c)); +static int chg_c_i2m __ARGS((int cur_c)); +static int chg_c_f2m __ARGS((int cur_c)); +static int chg_c_laa2i __ARGS((int hid_c)); +static int chg_c_laa2f __ARGS((int hid_c)); +static int half_shape __ARGS((int c)); +static int A_firstc_laa __ARGS((int c1, int c)); +static int A_is_harakat __ARGS((int c)); +static int A_is_iso __ARGS((int c)); +static int A_is_formb __ARGS((int c)); +static int A_is_ok __ARGS((int c)); +static int A_is_valid __ARGS((int c)); +static int A_is_special __ARGS((int c)); + + +/* + * Returns True if c is an ISO-8859-6 shaped ARABIC letter (user entered) + */ +static int A_is_a(cur_c) +int cur_c; +{ + switch (cur_c) { + case a_HAMZA: + case a_ALEF_MADDA: + case a_ALEF_HAMZA_ABOVE: + case a_WAW_HAMZA: + case a_ALEF_HAMZA_BELOW: + case a_YEH_HAMZA: + case a_ALEF: + case a_BEH: + case a_TEH_MARBUTA: + case a_TEH: + case a_THEH: + case a_JEEM: + case a_HAH: + case a_KHAH: + case a_DAL: + case a_THAL: + case a_REH: + case a_ZAIN: + case a_SEEN: + case a_SHEEN: + case a_SAD: + case a_DAD: + case a_TAH: + case a_ZAH: + case a_AIN: + case a_GHAIN: + case a_TATWEEL: + case a_FEH: + case a_QAF: + case a_KAF: + case a_LAM: + case a_MEEM: + case a_NOON: + case a_HEH: + case a_WAW: + case a_ALEF_MAKSURA: + case a_YEH: + return TRUE; + } + + return FALSE; +} + + +/* + * Returns True if c is an Isolated Form-B ARABIC letter + */ +static int A_is_s(cur_c) +int cur_c; +{ + switch (cur_c) { + case a_s_HAMZA: + case a_s_ALEF_MADDA: + case a_s_ALEF_HAMZA_ABOVE: + case a_s_WAW_HAMZA: + case a_s_ALEF_HAMZA_BELOW: + case a_s_YEH_HAMZA: + case a_s_ALEF: + case a_s_BEH: + case a_s_TEH_MARBUTA: + case a_s_TEH: + case a_s_THEH: + case a_s_JEEM: + case a_s_HAH: + case a_s_KHAH: + case a_s_DAL: + case a_s_THAL: + case a_s_REH: + case a_s_ZAIN: + case a_s_SEEN: + case a_s_SHEEN: + case a_s_SAD: + case a_s_DAD: + case a_s_TAH: + case a_s_ZAH: + case a_s_AIN: + case a_s_GHAIN: + case a_s_FEH: + case a_s_QAF: + case a_s_KAF: + case a_s_LAM: + case a_s_MEEM: + case a_s_NOON: + case a_s_HEH: + case a_s_WAW: + case a_s_ALEF_MAKSURA: + case a_s_YEH: + return TRUE; + } + + return FALSE; +} + + +/* + * Returns True if c is a Final shape of an ARABIC letter + */ +static int A_is_f(cur_c) +int cur_c; +{ + switch (cur_c) { + case a_f_ALEF_MADDA: + case a_f_ALEF_HAMZA_ABOVE: + case a_f_WAW_HAMZA: + case a_f_ALEF_HAMZA_BELOW: + case a_f_YEH_HAMZA: + case a_f_ALEF: + case a_f_BEH: + case a_f_TEH_MARBUTA: + case a_f_TEH: + case a_f_THEH: + case a_f_JEEM: + case a_f_HAH: + case a_f_KHAH: + case a_f_DAL: + case a_f_THAL: + case a_f_REH: + case a_f_ZAIN: + case a_f_SEEN: + case a_f_SHEEN: + case a_f_SAD: + case a_f_DAD: + case a_f_TAH: + case a_f_ZAH: + case a_f_AIN: + case a_f_GHAIN: + case a_f_FEH: + case a_f_QAF: + case a_f_KAF: + case a_f_LAM: + case a_f_MEEM: + case a_f_NOON: + case a_f_HEH: + case a_f_WAW: + case a_f_ALEF_MAKSURA: + case a_f_YEH: + case a_f_LAM_ALEF_MADDA_ABOVE: + case a_f_LAM_ALEF_HAMZA_ABOVE: + case a_f_LAM_ALEF_HAMZA_BELOW: + case a_f_LAM_ALEF: + return TRUE; + } + return FALSE; +} + + +/* + * Change shape - from ISO-8859-6/Isolated to Form-B Isolated + */ +static int chg_c_a2s(cur_c) +int cur_c; +{ + int tempc; + + switch (cur_c) { + case a_HAMZA: + tempc = a_s_HAMZA; + break; + case a_ALEF_MADDA: + tempc = a_s_ALEF_MADDA; + break; + case a_ALEF_HAMZA_ABOVE: + tempc = a_s_ALEF_HAMZA_ABOVE; + break; + case a_WAW_HAMZA: + tempc = a_s_WAW_HAMZA; + break; + case a_ALEF_HAMZA_BELOW: + tempc = a_s_ALEF_HAMZA_BELOW; + break; + case a_YEH_HAMZA: + tempc = a_s_YEH_HAMZA; + break; + case a_ALEF: + tempc = a_s_ALEF; + break; + case a_TEH_MARBUTA: + tempc = a_s_TEH_MARBUTA; + break; + case a_DAL: + tempc = a_s_DAL; + break; + case a_THAL: + tempc = a_s_THAL; + break; + case a_REH: + tempc = a_s_REH; + break; + case a_ZAIN: + tempc = a_s_ZAIN; + break; + case a_TATWEEL: /* exceptions */ + tempc = cur_c; + break; + case a_WAW: + tempc = a_s_WAW; + break; + case a_ALEF_MAKSURA: + tempc = a_s_ALEF_MAKSURA; + break; + case a_BEH: + tempc = a_s_BEH; + break; + case a_TEH: + tempc = a_s_TEH; + break; + case a_THEH: + tempc = a_s_THEH; + break; + case a_JEEM: + tempc = a_s_JEEM; + break; + case a_HAH: + tempc = a_s_HAH; + break; + case a_KHAH: + tempc = a_s_KHAH; + break; + case a_SEEN: + tempc = a_s_SEEN; + break; + case a_SHEEN: + tempc = a_s_SHEEN; + break; + case a_SAD: + tempc = a_s_SAD; + break; + case a_DAD: + tempc = a_s_DAD; + break; + case a_TAH: + tempc = a_s_TAH; + break; + case a_ZAH: + tempc = a_s_ZAH; + break; + case a_AIN: + tempc = a_s_AIN; + break; + case a_GHAIN: + tempc = a_s_GHAIN; + break; + case a_FEH: + tempc = a_s_FEH; + break; + case a_QAF: + tempc = a_s_QAF; + break; + case a_KAF: + tempc = a_s_KAF; + break; + case a_LAM: + tempc = a_s_LAM; + break; + case a_MEEM: + tempc = a_s_MEEM; + break; + case a_NOON: + tempc = a_s_NOON; + break; + case a_HEH: + tempc = a_s_HEH; + break; + case a_YEH: + tempc = a_s_YEH; + break; + default: + tempc = 0; + } + + return tempc; +} + + +/* + * Change shape - from ISO-8859-6/Isolated to Initial + */ +static int chg_c_a2i(cur_c) +int cur_c; +{ + int tempc; + + switch (cur_c) { + case a_YEH_HAMZA: + tempc = a_i_YEH_HAMZA; + break; + case a_HAMZA: /* exceptions */ + tempc = a_s_HAMZA; + break; + case a_ALEF_MADDA: /* exceptions */ + tempc = a_s_ALEF_MADDA; + break; + case a_ALEF_HAMZA_ABOVE: /* exceptions */ + tempc = a_s_ALEF_HAMZA_ABOVE; + break; + case a_WAW_HAMZA: /* exceptions */ + tempc = a_s_WAW_HAMZA; + break; + case a_ALEF_HAMZA_BELOW: /* exceptions */ + tempc = a_s_ALEF_HAMZA_BELOW; + break; + case a_ALEF: /* exceptions */ + tempc = a_s_ALEF; + break; + case a_TEH_MARBUTA: /* exceptions */ + tempc = a_s_TEH_MARBUTA; + break; + case a_DAL: /* exceptions */ + tempc = a_s_DAL; + break; + case a_THAL: /* exceptions */ + tempc = a_s_THAL; + break; + case a_REH: /* exceptions */ + tempc = a_s_REH; + break; + case a_ZAIN: /* exceptions */ + tempc = a_s_ZAIN; + break; + case a_TATWEEL: /* exceptions */ + tempc = cur_c; + break; + case a_WAW: /* exceptions */ + tempc = a_s_WAW; + break; + case a_ALEF_MAKSURA: /* exceptions */ + tempc = a_s_ALEF_MAKSURA; + break; + case a_BEH: + tempc = a_i_BEH; + break; + case a_TEH: + tempc = a_i_TEH; + break; + case a_THEH: + tempc = a_i_THEH; + break; + case a_JEEM: + tempc = a_i_JEEM; + break; + case a_HAH: + tempc = a_i_HAH; + break; + case a_KHAH: + tempc = a_i_KHAH; + break; + case a_SEEN: + tempc = a_i_SEEN; + break; + case a_SHEEN: + tempc = a_i_SHEEN; + break; + case a_SAD: + tempc = a_i_SAD; + break; + case a_DAD: + tempc = a_i_DAD; + break; + case a_TAH: + tempc = a_i_TAH; + break; + case a_ZAH: + tempc = a_i_ZAH; + break; + case a_AIN: + tempc = a_i_AIN; + break; + case a_GHAIN: + tempc = a_i_GHAIN; + break; + case a_FEH: + tempc = a_i_FEH; + break; + case a_QAF: + tempc = a_i_QAF; + break; + case a_KAF: + tempc = a_i_KAF; + break; + case a_LAM: + tempc = a_i_LAM; + break; + case a_MEEM: + tempc = a_i_MEEM; + break; + case a_NOON: + tempc = a_i_NOON; + break; + case a_HEH: + tempc = a_i_HEH; + break; + case a_YEH: + tempc = a_i_YEH; + break; + default: + tempc = 0; + } + + return tempc; +} + + +/* + * Change shape - from ISO-8859-6/Isolated to Medial + */ +static int chg_c_a2m(cur_c) +int cur_c; +{ + int tempc; + + switch (cur_c) { + case a_HAMZA: /* exception */ + tempc = a_s_HAMZA; + break; + case a_ALEF_MADDA: /* exception */ + tempc = a_f_ALEF_MADDA; + break; + case a_ALEF_HAMZA_ABOVE: /* exception */ + tempc = a_f_ALEF_HAMZA_ABOVE; + break; + case a_WAW_HAMZA: /* exception */ + tempc = a_f_WAW_HAMZA; + break; + case a_ALEF_HAMZA_BELOW: /* exception */ + tempc = a_f_ALEF_HAMZA_BELOW; + break; + case a_YEH_HAMZA: + tempc = a_m_YEH_HAMZA; + break; + case a_ALEF: /* exception */ + tempc = a_f_ALEF; + break; + case a_BEH: + tempc = a_m_BEH; + break; + case a_TEH_MARBUTA: /* exception */ + tempc = a_f_TEH_MARBUTA; + break; + case a_TEH: + tempc = a_m_TEH; + break; + case a_THEH: + tempc = a_m_THEH; + break; + case a_JEEM: + tempc = a_m_JEEM; + break; + case a_HAH: + tempc = a_m_HAH; + break; + case a_KHAH: + tempc = a_m_KHAH; + break; + case a_DAL: /* exception */ + tempc = a_f_DAL; + break; + case a_THAL: /* exception */ + tempc = a_f_THAL; + break; + case a_REH: /* exception */ + tempc = a_f_REH; + break; + case a_ZAIN: /* exception */ + tempc = a_f_ZAIN; + break; + case a_SEEN: + tempc = a_m_SEEN; + break; + case a_SHEEN: + tempc = a_m_SHEEN; + break; + case a_SAD: + tempc = a_m_SAD; + break; + case a_DAD: + tempc = a_m_DAD; + break; + case a_TAH: + tempc = a_m_TAH; + break; + case a_ZAH: + tempc = a_m_ZAH; + break; + case a_AIN: + tempc = a_m_AIN; + break; + case a_GHAIN: + tempc = a_m_GHAIN; + break; + case a_TATWEEL: /* exception */ + tempc = cur_c; + break; + case a_FEH: + tempc = a_m_FEH; + break; + case a_QAF: + tempc = a_m_QAF; + break; + case a_KAF: + tempc = a_m_KAF; + break; + case a_LAM: + tempc = a_m_LAM; + break; + case a_MEEM: + tempc = a_m_MEEM; + break; + case a_NOON: + tempc = a_m_NOON; + break; + case a_HEH: + tempc = a_m_HEH; + break; + case a_WAW: /* exception */ + tempc = a_f_WAW; + break; + case a_ALEF_MAKSURA: /* exception */ + tempc = a_f_ALEF_MAKSURA; + break; + case a_YEH: + tempc = a_m_YEH; + break; + default: + tempc = 0; + } + + return tempc; +} + + +/* + * Change shape - from ISO-8859-6/Isolated to final + */ +static int chg_c_a2f(cur_c) +int cur_c; +{ + int tempc; + + /* NOTE: these encodings need to be accounted for + + a_f_ALEF_MADDA; + a_f_ALEF_HAMZA_ABOVE; + a_f_ALEF_HAMZA_BELOW; + a_f_LAM_ALEF_MADDA_ABOVE; + a_f_LAM_ALEF_HAMZA_ABOVE; + a_f_LAM_ALEF_HAMZA_BELOW; + */ + + switch (cur_c) { + case a_HAMZA: /* exception */ + tempc = a_s_HAMZA; + break; + case a_ALEF_MADDA: + tempc = a_f_ALEF_MADDA; + break; + case a_ALEF_HAMZA_ABOVE: + tempc = a_f_ALEF_HAMZA_ABOVE; + break; + case a_WAW_HAMZA: + tempc = a_f_WAW_HAMZA; + break; + case a_ALEF_HAMZA_BELOW: + tempc = a_f_ALEF_HAMZA_BELOW; + break; + case a_YEH_HAMZA: + tempc = a_f_YEH_HAMZA; + break; + case a_ALEF: + tempc = a_f_ALEF; + break; + case a_BEH: + tempc = a_f_BEH; + break; + case a_TEH_MARBUTA: + tempc = a_f_TEH_MARBUTA; + break; + case a_TEH: + tempc = a_f_TEH; + break; + case a_THEH: + tempc = a_f_THEH; + break; + case a_JEEM: + tempc = a_f_JEEM; + break; + case a_HAH: + tempc = a_f_HAH; + break; + case a_KHAH: + tempc = a_f_KHAH; + break; + case a_DAL: + tempc = a_f_DAL; + break; + case a_THAL: + tempc = a_f_THAL; + break; + case a_REH: + tempc = a_f_REH; + break; + case a_ZAIN: + tempc = a_f_ZAIN; + break; + case a_SEEN: + tempc = a_f_SEEN; + break; + case a_SHEEN: + tempc = a_f_SHEEN; + break; + case a_SAD: + tempc = a_f_SAD; + break; + case a_DAD: + tempc = a_f_DAD; + break; + case a_TAH: + tempc = a_f_TAH; + break; + case a_ZAH: + tempc = a_f_ZAH; + break; + case a_AIN: + tempc = a_f_AIN; + break; + case a_GHAIN: + tempc = a_f_GHAIN; + break; + case a_TATWEEL: /* exception */ + tempc = cur_c; + break; + case a_FEH: + tempc = a_f_FEH; + break; + case a_QAF: + tempc = a_f_QAF; + break; + case a_KAF: + tempc = a_f_KAF; + break; + case a_LAM: + tempc = a_f_LAM; + break; + case a_MEEM: + tempc = a_f_MEEM; + break; + case a_NOON: + tempc = a_f_NOON; + break; + case a_HEH: + tempc = a_f_HEH; + break; + case a_WAW: + tempc = a_f_WAW; + break; + case a_ALEF_MAKSURA: + tempc = a_f_ALEF_MAKSURA; + break; + case a_YEH: + tempc = a_f_YEH; + break; + default: + tempc = 0; + } + + return tempc; +} + + +/* + * Change shape - from Initial to Medial + */ +static int chg_c_i2m(cur_c) +int cur_c; +{ + int tempc; + + switch (cur_c) { + case a_i_YEH_HAMZA: + tempc = a_m_YEH_HAMZA; + break; + case a_i_BEH: + tempc = a_m_BEH; + break; + case a_i_TEH: + tempc = a_m_TEH; + break; + case a_i_THEH: + tempc = a_m_THEH; + break; + case a_i_JEEM: + tempc = a_m_JEEM; + break; + case a_i_HAH: + tempc = a_m_HAH; + break; + case a_i_KHAH: + tempc = a_m_KHAH; + break; + case a_i_SEEN: + tempc = a_m_SEEN; + break; + case a_i_SHEEN: + tempc = a_m_SHEEN; + break; + case a_i_SAD: + tempc = a_m_SAD; + break; + case a_i_DAD: + tempc = a_m_DAD; + break; + case a_i_TAH: + tempc = a_m_TAH; + break; + case a_i_ZAH: + tempc = a_m_ZAH; + break; + case a_i_AIN: + tempc = a_m_AIN; + break; + case a_i_GHAIN: + tempc = a_m_GHAIN; + break; + case a_i_FEH: + tempc = a_m_FEH; + break; + case a_i_QAF: + tempc = a_m_QAF; + break; + case a_i_KAF: + tempc = a_m_KAF; + break; + case a_i_LAM: + tempc = a_m_LAM; + break; + case a_i_MEEM: + tempc = a_m_MEEM; + break; + case a_i_NOON: + tempc = a_m_NOON; + break; + case a_i_HEH: + tempc = a_m_HEH; + break; + case a_i_YEH: + tempc = a_m_YEH; + break; + default: + tempc = 0; + } + + return tempc; +} + + +/* + * Change shape - from Final to Medial + */ +static int chg_c_f2m(cur_c) +int cur_c; +{ + int tempc; + + switch (cur_c) { + /* NOTE: these encodings are multi-positional, no ? + case a_f_ALEF_MADDA: + case a_f_ALEF_HAMZA_ABOVE: + case a_f_ALEF_HAMZA_BELOW: + */ + case a_f_YEH_HAMZA: + tempc = a_m_YEH_HAMZA; + break; + case a_f_WAW_HAMZA: /* exceptions */ + case a_f_ALEF: + case a_f_TEH_MARBUTA: + case a_f_DAL: + case a_f_THAL: + case a_f_REH: + case a_f_ZAIN: + case a_f_WAW: + case a_f_ALEF_MAKSURA: + tempc = cur_c; + break; + case a_f_BEH: + tempc = a_m_BEH; + break; + case a_f_TEH: + tempc = a_m_TEH; + break; + case a_f_THEH: + tempc = a_m_THEH; + break; + case a_f_JEEM: + tempc = a_m_JEEM; + break; + case a_f_HAH: + tempc = a_m_HAH; + break; + case a_f_KHAH: + tempc = a_m_KHAH; + break; + case a_f_SEEN: + tempc = a_m_SEEN; + break; + case a_f_SHEEN: + tempc = a_m_SHEEN; + break; + case a_f_SAD: + tempc = a_m_SAD; + break; + case a_f_DAD: + tempc = a_m_DAD; + break; + case a_f_TAH: + tempc = a_m_TAH; + break; + case a_f_ZAH: + tempc = a_m_ZAH; + break; + case a_f_AIN: + tempc = a_m_AIN; + break; + case a_f_GHAIN: + tempc = a_m_GHAIN; + break; + case a_f_FEH: + tempc = a_m_FEH; + break; + case a_f_QAF: + tempc = a_m_QAF; + break; + case a_f_KAF: + tempc = a_m_KAF; + break; + case a_f_LAM: + tempc = a_m_LAM; + break; + case a_f_MEEM: + tempc = a_m_MEEM; + break; + case a_f_NOON: + tempc = a_m_NOON; + break; + case a_f_HEH: + tempc = a_m_HEH; + break; + case a_f_YEH: + tempc = a_m_YEH; + break; + /* NOTE: these encodings are multi-positional, no ? + case a_f_LAM_ALEF_MADDA_ABOVE: + case a_f_LAM_ALEF_HAMZA_ABOVE: + case a_f_LAM_ALEF_HAMZA_BELOW: + case a_f_LAM_ALEF: + */ + default: + tempc = 0; + } + + return tempc; +} + + +/* + * Change shape - from Combination (2 char) to an Isolated + */ +static int chg_c_laa2i(hid_c) +int hid_c; +{ + int tempc; + + switch (hid_c) { + case a_ALEF_MADDA: + tempc = a_s_LAM_ALEF_MADDA_ABOVE; + break; + case a_ALEF_HAMZA_ABOVE: + tempc = a_s_LAM_ALEF_HAMZA_ABOVE; + break; + case a_ALEF_HAMZA_BELOW: + tempc = a_s_LAM_ALEF_HAMZA_BELOW; + break; + case a_ALEF: + tempc = a_s_LAM_ALEF; + break; + default: + tempc = 0; + } + + return tempc; +} + + +/* + * Change shape - from Combination-Isolated to Final + */ +static int chg_c_laa2f(hid_c) +int hid_c; +{ + int tempc; + + switch (hid_c) { + case a_ALEF_MADDA: + tempc = a_f_LAM_ALEF_MADDA_ABOVE; + break; + case a_ALEF_HAMZA_ABOVE: + tempc = a_f_LAM_ALEF_HAMZA_ABOVE; + break; + case a_ALEF_HAMZA_BELOW: + tempc = a_f_LAM_ALEF_HAMZA_BELOW; + break; + case a_ALEF: + tempc = a_f_LAM_ALEF; + break; + default: + tempc = 0; + } + + return tempc; +} + +/* + * Do "half-shaping" on character "c". Return zero if no shaping. + */ +static int half_shape(c) +int c; +{ + if (A_is_a(c)) + return chg_c_a2i(c); + if (A_is_valid(c) && A_is_f(c)) + return chg_c_f2m(c); + return 0; +} + +/* + * Do Arabic shaping on character "c". Returns the shaped character. + * out: "ccp" points to the first byte of the character to be shaped. + * in/out: "c1p" points to the first composing char for "c". + * in: "prev_c" is the previous character (not shaped) + * in: "prev_c1" is the first composing char for the previous char + * (not shaped) + * in: "next_c" is the next character (not shaped). + */ +int arabic_shape(c, ccp, c1p, prev_c, prev_c1, next_c) +int c; +int *ccp; +int *c1p; +int prev_c; +int prev_c1; +int next_c; +{ + int curr_c; + int shape_c; + int curr_laa; + int prev_laa; + + /* Deal only with Arabic character, pass back all others */ + if (!A_is_ok(c)) + return c; + + /* half-shape current and previous character */ + shape_c = half_shape(prev_c); + + /* Save away current character */ + curr_c = c; + + curr_laa = A_firstc_laa(c, *c1p); + prev_laa = A_firstc_laa(prev_c, prev_c1); + + if (curr_laa) { + if (A_is_valid(prev_c) && !A_is_f(shape_c) + && !A_is_s(shape_c) && !prev_laa) + curr_c = chg_c_laa2f(curr_laa); + else + curr_c = chg_c_laa2i(curr_laa); + + /* Remove the composing character */ + *c1p = 0; + } else if (!A_is_valid(prev_c) && A_is_valid(next_c)) + curr_c = chg_c_a2i(c); + else if (!shape_c || A_is_f(shape_c) || A_is_s(shape_c) || prev_laa) + curr_c = A_is_valid(next_c) ? chg_c_a2i(c) : chg_c_a2s(c); + else if (A_is_valid(next_c)) + curr_c = A_is_iso(c) ? chg_c_a2m(c) : chg_c_i2m(c); + else if (A_is_valid(prev_c)) + curr_c = chg_c_a2f(c); + else + curr_c = chg_c_a2s(c); + + /* Sanity check -- curr_c should, in the future, never be 0. + * We should, in the future, insert a fatal error here. */ + if (curr_c == NUL) + curr_c = c; + + if (curr_c != c && ccp != NULL) { + char_u buf[MB_MAXBYTES + 1]; + + /* Update the first byte of the character. */ + (*mb_char2bytes)(curr_c, buf); + *ccp = buf[0]; + } + + /* Return the shaped character */ + return curr_c; +} + + +/* + * A_firstc_laa returns first character of LAA combination if it exists + */ +static int A_firstc_laa(c, c1) +int c; /* base character */ +int c1; /* first composing character */ +{ + if (c1 != NUL && c == a_LAM && !A_is_harakat(c1)) + return c1; + return 0; +} + + +/* + * A_is_harakat returns TRUE if 'c' is an Arabic Harakat character + * (harakat/tanween) + */ +static int A_is_harakat(c) +int c; +{ + return c >= a_FATHATAN && c <= a_SUKUN; +} + + +/* + * A_is_iso returns TRUE if 'c' is an Arabic ISO-8859-6 character + * (alphabet/number/punctuation) + */ +static int A_is_iso(c) +int c; +{ + return (c >= a_HAMZA && c <= a_GHAIN) + || (c >= a_TATWEEL && c <= a_HAMZA_BELOW) + || c == a_MINI_ALEF; +} + + +/* + * A_is_formb returns TRUE if 'c' is an Arabic 10646-1 FormB character + * (alphabet/number/punctuation) + */ +static int A_is_formb(c) +int c; +{ + return (c >= a_s_FATHATAN && c <= a_s_DAMMATAN) + || c == a_s_KASRATAN + || (c >= a_s_FATHA && c <= a_f_LAM_ALEF) + || c == a_BYTE_ORDER_MARK; +} + + +/* + * A_is_ok returns TRUE if 'c' is an Arabic 10646 (8859-6 or Form-B) + */ +static int A_is_ok(c) +int c; +{ + return A_is_iso(c) || A_is_formb(c); +} + + +/* + * A_is_valid returns TRUE if 'c' is an Arabic 10646 (8859-6 or Form-B) + * with some exceptions/exclusions + */ +static int A_is_valid(c) +int c; +{ + return A_is_ok(c) && !A_is_special(c); +} + + +/* + * A_is_special returns TRUE if 'c' is not a special Arabic character. + * Specials don't adhere to most of the rules. + */ +static int A_is_special(c) +int c; +{ + return c == a_HAMZA || c == a_s_HAMZA; +} diff --git a/src/arabic.h b/src/arabic.h new file mode 100644 index 0000000000..6b277351cf --- /dev/null +++ b/src/arabic.h @@ -0,0 +1,258 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * Arabic characters are categorized into following types: + * + * Isolated - iso-8859-6 form char denoted with a_* + * Initial - unicode form-B start char denoted with a_i_* + * Medial - unicode form-B middle char denoted with a_m_* + * Final - unicode form-B final char denoted with a_f_* + * Stand-Alone - unicode form-B isolated char denoted with a_s_* (NOT USED) + * + * -- + * + * Author: Nadim Shaikli & Isam Bayazidi + * - (based on Unicode) + * + */ + +/* + * Arabic ISO-10646-1 character set definition + */ + +/* + * Arabic ISO-8859-6 (subset of 10646; 0600 - 06FF) + */ +#define a_COMMA 0x060C +#define a_SEMICOLON 0x061B +#define a_QUESTION 0x061F +#define a_HAMZA 0x0621 +#define a_ALEF_MADDA 0x0622 +#define a_ALEF_HAMZA_ABOVE 0x0623 +#define a_WAW_HAMZA 0x0624 +#define a_ALEF_HAMZA_BELOW 0x0625 +#define a_YEH_HAMZA 0x0626 +#define a_ALEF 0x0627 +#define a_BEH 0x0628 +#define a_TEH_MARBUTA 0x0629 +#define a_TEH 0x062a +#define a_THEH 0x062b +#define a_JEEM 0x062c +#define a_HAH 0x062d +#define a_KHAH 0x062e +#define a_DAL 0x062f +#define a_THAL 0x0630 +#define a_REH 0x0631 +#define a_ZAIN 0x0632 +#define a_SEEN 0x0633 +#define a_SHEEN 0x0634 +#define a_SAD 0x0635 +#define a_DAD 0x0636 +#define a_TAH 0x0637 +#define a_ZAH 0x0638 +#define a_AIN 0x0639 +#define a_GHAIN 0x063a +#define a_TATWEEL 0x0640 +#define a_FEH 0x0641 +#define a_QAF 0x0642 +#define a_KAF 0x0643 +#define a_LAM 0x0644 +#define a_MEEM 0x0645 +#define a_NOON 0x0646 +#define a_HEH 0x0647 +#define a_WAW 0x0648 +#define a_ALEF_MAKSURA 0x0649 +#define a_YEH 0x064a + +#define a_FATHATAN 0x064b +#define a_DAMMATAN 0x064c +#define a_KASRATAN 0x064d +#define a_FATHA 0x064e +#define a_DAMMA 0x064f +#define a_KASRA 0x0650 +#define a_SHADDA 0x0651 +#define a_SUKUN 0x0652 + +#define a_MADDA_ABOVE 0x0653 +#define a_HAMZA_ABOVE 0x0654 +#define a_HAMZA_BELOW 0x0655 + +#define a_ZERO 0x0660 +#define a_ONE 0x0661 +#define a_TWO 0x0662 +#define a_THREE 0x0663 +#define a_FOUR 0x0664 +#define a_FIVE 0x0665 +#define a_SIX 0x0666 +#define a_SEVEN 0x0667 +#define a_EIGHT 0x0668 +#define a_NINE 0x0669 +#define a_PERCENT 0x066a +#define a_DECIMAL 0x066b +#define a_THOUSANDS 0x066c +#define a_STAR 0x066d +#define a_MINI_ALEF 0x0670 +/* Rest of 8859-6 does not relate to Arabic */ + +/* + * Arabic Presentation Form-B (subset of 10646; FE70 - FEFF) + * + * s -> isolated + * i -> initial + * m -> medial + * f -> final + * + */ +#define a_s_FATHATAN 0xfe70 +#define a_m_TATWEEL_FATHATAN 0xfe71 +#define a_s_DAMMATAN 0xfe72 + +#define a_s_KASRATAN 0xfe74 + +#define a_s_FATHA 0xfe76 +#define a_m_FATHA 0xfe77 +#define a_s_DAMMA 0xfe78 +#define a_m_DAMMA 0xfe79 +#define a_s_KASRA 0xfe7a +#define a_m_KASRA 0xfe7b +#define a_s_SHADDA 0xfe7c +#define a_m_SHADDA 0xfe7d +#define a_s_SUKUN 0xfe7e +#define a_m_SUKUN 0xfe7f + +#define a_s_HAMZA 0xfe80 +#define a_s_ALEF_MADDA 0xfe81 +#define a_f_ALEF_MADDA 0xfe82 +#define a_s_ALEF_HAMZA_ABOVE 0xfe83 +#define a_f_ALEF_HAMZA_ABOVE 0xfe84 +#define a_s_WAW_HAMZA 0xfe85 +#define a_f_WAW_HAMZA 0xfe86 +#define a_s_ALEF_HAMZA_BELOW 0xfe87 +#define a_f_ALEF_HAMZA_BELOW 0xfe88 +#define a_s_YEH_HAMZA 0xfe89 +#define a_f_YEH_HAMZA 0xfe8a +#define a_i_YEH_HAMZA 0xfe8b +#define a_m_YEH_HAMZA 0xfe8c +#define a_s_ALEF 0xfe8d +#define a_f_ALEF 0xfe8e +#define a_s_BEH 0xfe8f +#define a_f_BEH 0xfe90 +#define a_i_BEH 0xfe91 +#define a_m_BEH 0xfe92 +#define a_s_TEH_MARBUTA 0xfe93 +#define a_f_TEH_MARBUTA 0xfe94 +#define a_s_TEH 0xfe95 +#define a_f_TEH 0xfe96 +#define a_i_TEH 0xfe97 +#define a_m_TEH 0xfe98 +#define a_s_THEH 0xfe99 +#define a_f_THEH 0xfe9a +#define a_i_THEH 0xfe9b +#define a_m_THEH 0xfe9c +#define a_s_JEEM 0xfe9d +#define a_f_JEEM 0xfe9e +#define a_i_JEEM 0xfe9f +#define a_m_JEEM 0xfea0 +#define a_s_HAH 0xfea1 +#define a_f_HAH 0xfea2 +#define a_i_HAH 0xfea3 +#define a_m_HAH 0xfea4 +#define a_s_KHAH 0xfea5 +#define a_f_KHAH 0xfea6 +#define a_i_KHAH 0xfea7 +#define a_m_KHAH 0xfea8 +#define a_s_DAL 0xfea9 +#define a_f_DAL 0xfeaa +#define a_s_THAL 0xfeab +#define a_f_THAL 0xfeac +#define a_s_REH 0xfead +#define a_f_REH 0xfeae +#define a_s_ZAIN 0xfeaf +#define a_f_ZAIN 0xfeb0 +#define a_s_SEEN 0xfeb1 +#define a_f_SEEN 0xfeb2 +#define a_i_SEEN 0xfeb3 +#define a_m_SEEN 0xfeb4 +#define a_s_SHEEN 0xfeb5 +#define a_f_SHEEN 0xfeb6 +#define a_i_SHEEN 0xfeb7 +#define a_m_SHEEN 0xfeb8 +#define a_s_SAD 0xfeb9 +#define a_f_SAD 0xfeba +#define a_i_SAD 0xfebb +#define a_m_SAD 0xfebc +#define a_s_DAD 0xfebd +#define a_f_DAD 0xfebe +#define a_i_DAD 0xfebf +#define a_m_DAD 0xfec0 +#define a_s_TAH 0xfec1 +#define a_f_TAH 0xfec2 +#define a_i_TAH 0xfec3 +#define a_m_TAH 0xfec4 +#define a_s_ZAH 0xfec5 +#define a_f_ZAH 0xfec6 +#define a_i_ZAH 0xfec7 +#define a_m_ZAH 0xfec8 +#define a_s_AIN 0xfec9 +#define a_f_AIN 0xfeca +#define a_i_AIN 0xfecb +#define a_m_AIN 0xfecc +#define a_s_GHAIN 0xfecd +#define a_f_GHAIN 0xfece +#define a_i_GHAIN 0xfecf +#define a_m_GHAIN 0xfed0 +#define a_s_FEH 0xfed1 +#define a_f_FEH 0xfed2 +#define a_i_FEH 0xfed3 +#define a_m_FEH 0xfed4 +#define a_s_QAF 0xfed5 +#define a_f_QAF 0xfed6 +#define a_i_QAF 0xfed7 +#define a_m_QAF 0xfed8 +#define a_s_KAF 0xfed9 +#define a_f_KAF 0xfeda +#define a_i_KAF 0xfedb +#define a_m_KAF 0xfedc +#define a_s_LAM 0xfedd +#define a_f_LAM 0xfede +#define a_i_LAM 0xfedf +#define a_m_LAM 0xfee0 +#define a_s_MEEM 0xfee1 +#define a_f_MEEM 0xfee2 +#define a_i_MEEM 0xfee3 +#define a_m_MEEM 0xfee4 +#define a_s_NOON 0xfee5 +#define a_f_NOON 0xfee6 +#define a_i_NOON 0xfee7 +#define a_m_NOON 0xfee8 +#define a_s_HEH 0xfee9 +#define a_f_HEH 0xfeea +#define a_i_HEH 0xfeeb +#define a_m_HEH 0xfeec +#define a_s_WAW 0xfeed +#define a_f_WAW 0xfeee +#define a_s_ALEF_MAKSURA 0xfeef +#define a_f_ALEF_MAKSURA 0xfef0 +#define a_s_YEH 0xfef1 +#define a_f_YEH 0xfef2 +#define a_i_YEH 0xfef3 +#define a_m_YEH 0xfef4 +#define a_s_LAM_ALEF_MADDA_ABOVE 0xfef5 +#define a_f_LAM_ALEF_MADDA_ABOVE 0xfef6 +#define a_s_LAM_ALEF_HAMZA_ABOVE 0xfef7 +#define a_f_LAM_ALEF_HAMZA_ABOVE 0xfef8 +#define a_s_LAM_ALEF_HAMZA_BELOW 0xfef9 +#define a_f_LAM_ALEF_HAMZA_BELOW 0xfefa +#define a_s_LAM_ALEF 0xfefb +#define a_f_LAM_ALEF 0xfefc + +#define a_BYTE_ORDER_MARK 0xfeff + +/* Range of Arabic characters that might be shaped. */ +#define ARABIC_CHAR(c) ((c) >= a_HAMZA && (c) <= a_MINI_ALEF) diff --git a/src/ascii.h b/src/ascii.h new file mode 100644 index 0000000000..0904521ccf --- /dev/null +++ b/src/ascii.h @@ -0,0 +1,96 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * Definitions of various common control characters. + * For EBCDIC we have to use different values. + */ + + +/* IF_EB(ASCII_constant, EBCDIC_constant) */ +#define IF_EB(a, b) a + +#define CharOrd(x) ((x) < 'a' ? (x) - 'A' : (x) - 'a') +#define CharOrdLow(x) ((x) - 'a') +#define CharOrdUp(x) ((x) - 'A') +#define ROT13(c, a) (((((c) - (a)) + 13) % 26) + (a)) + +#define NUL '\000' +#define BELL '\007' +#define BS '\010' +#define TAB '\011' +#define NL '\012' +#define NL_STR (char_u *)"\012" +#define FF '\014' +#define CAR '\015' /* CR is used by Mac OS X */ +#define ESC '\033' +#define ESC_STR (char_u *)"\033" +#define ESC_STR_nc "\033" +#define DEL 0x7f +#define DEL_STR (char_u *)"\177" +#define CSI 0x9b /* Control Sequence Introducer */ +#define CSI_STR "\233" +#define DCS 0x90 /* Device Control String */ +#define STERM 0x9c /* String Terminator */ + +#define POUND 0xA3 + +#define Ctrl_chr(x) (TOUPPER_ASC(x) ^ 0x40) /* '?' -> DEL, '@' -> ^@, etc. */ +#define Meta(x) ((x) | 0x80) + +#define CTRL_F_STR "\006" +#define CTRL_H_STR "\010" +#define CTRL_V_STR "\026" + +#define Ctrl_AT 0 /* @ */ +#define Ctrl_A 1 +#define Ctrl_B 2 +#define Ctrl_C 3 +#define Ctrl_D 4 +#define Ctrl_E 5 +#define Ctrl_F 6 +#define Ctrl_G 7 +#define Ctrl_H 8 +#define Ctrl_I 9 +#define Ctrl_J 10 +#define Ctrl_K 11 +#define Ctrl_L 12 +#define Ctrl_M 13 +#define Ctrl_N 14 +#define Ctrl_O 15 +#define Ctrl_P 16 +#define Ctrl_Q 17 +#define Ctrl_R 18 +#define Ctrl_S 19 +#define Ctrl_T 20 +#define Ctrl_U 21 +#define Ctrl_V 22 +#define Ctrl_W 23 +#define Ctrl_X 24 +#define Ctrl_Y 25 +#define Ctrl_Z 26 +/* CTRL- [ Left Square Bracket == ESC*/ +#define Ctrl_BSL 28 /* \ BackSLash */ +#define Ctrl_RSB 29 /* ] Right Square Bracket */ +#define Ctrl_HAT 30 /* ^ */ +#define Ctrl__ 31 + + +/* + * Character that separates dir names in a path. + * For MS-DOS, WIN32 and OS/2 we use a backslash. A slash mostly works + * fine, but there are places where it doesn't (e.g. in a command name). + * For Acorn we use a dot. + */ +#ifdef BACKSLASH_IN_FILENAME +# define PATHSEP psepc +# define PATHSEPSTR pseps +#else +# define PATHSEP '/' +# define PATHSEPSTR "/" +#endif diff --git a/src/blowfish.c b/src/blowfish.c new file mode 100644 index 0000000000..2e4d8cf1af --- /dev/null +++ b/src/blowfish.c @@ -0,0 +1,637 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + * + * Blowfish encryption for Vim; in Blowfish output feedback mode. + * Contributed by Mohsin Ahmed, http://www.cs.albany.edu/~mosh + * Based on http://www.schneier.com/blowfish.html by Bruce Schneier. + */ + +#include "vim.h" + + +#define ARRAY_LENGTH(A) (sizeof(A)/sizeof(A[0])) + +#define BF_BLOCK 8 +#define BF_BLOCK_MASK 7 +#define BF_OFB_LEN (8*(BF_BLOCK)) + +typedef union { + UINT32_T ul[2]; + char_u uc[8]; +} block8; + + +static void bf_e_block __ARGS((UINT32_T *p_xl, UINT32_T *p_xr)); +static void bf_e_cblock __ARGS((char_u *block)); +static int bf_check_tables __ARGS((UINT32_T a_ipa[18], UINT32_T a_sbi[4][256], + UINT32_T val)); +static int bf_self_test __ARGS((void)); + +/* Blowfish code */ +static UINT32_T pax[18]; +static UINT32_T ipa[18] = { + 0x243f6a88u, 0x85a308d3u, 0x13198a2eu, + 0x03707344u, 0xa4093822u, 0x299f31d0u, + 0x082efa98u, 0xec4e6c89u, 0x452821e6u, + 0x38d01377u, 0xbe5466cfu, 0x34e90c6cu, + 0xc0ac29b7u, 0xc97c50ddu, 0x3f84d5b5u, + 0xb5470917u, 0x9216d5d9u, 0x8979fb1bu +}; + +static UINT32_T sbx[4][256]; +static UINT32_T sbi[4][256] = { + {0xd1310ba6u, 0x98dfb5acu, 0x2ffd72dbu, 0xd01adfb7u, + 0xb8e1afedu, 0x6a267e96u, 0xba7c9045u, 0xf12c7f99u, + 0x24a19947u, 0xb3916cf7u, 0x0801f2e2u, 0x858efc16u, + 0x636920d8u, 0x71574e69u, 0xa458fea3u, 0xf4933d7eu, + 0x0d95748fu, 0x728eb658u, 0x718bcd58u, 0x82154aeeu, + 0x7b54a41du, 0xc25a59b5u, 0x9c30d539u, 0x2af26013u, + 0xc5d1b023u, 0x286085f0u, 0xca417918u, 0xb8db38efu, + 0x8e79dcb0u, 0x603a180eu, 0x6c9e0e8bu, 0xb01e8a3eu, + 0xd71577c1u, 0xbd314b27u, 0x78af2fdau, 0x55605c60u, + 0xe65525f3u, 0xaa55ab94u, 0x57489862u, 0x63e81440u, + 0x55ca396au, 0x2aab10b6u, 0xb4cc5c34u, 0x1141e8ceu, + 0xa15486afu, 0x7c72e993u, 0xb3ee1411u, 0x636fbc2au, + 0x2ba9c55du, 0x741831f6u, 0xce5c3e16u, 0x9b87931eu, + 0xafd6ba33u, 0x6c24cf5cu, 0x7a325381u, 0x28958677u, + 0x3b8f4898u, 0x6b4bb9afu, 0xc4bfe81bu, 0x66282193u, + 0x61d809ccu, 0xfb21a991u, 0x487cac60u, 0x5dec8032u, + 0xef845d5du, 0xe98575b1u, 0xdc262302u, 0xeb651b88u, + 0x23893e81u, 0xd396acc5u, 0x0f6d6ff3u, 0x83f44239u, + 0x2e0b4482u, 0xa4842004u, 0x69c8f04au, 0x9e1f9b5eu, + 0x21c66842u, 0xf6e96c9au, 0x670c9c61u, 0xabd388f0u, + 0x6a51a0d2u, 0xd8542f68u, 0x960fa728u, 0xab5133a3u, + 0x6eef0b6cu, 0x137a3be4u, 0xba3bf050u, 0x7efb2a98u, + 0xa1f1651du, 0x39af0176u, 0x66ca593eu, 0x82430e88u, + 0x8cee8619u, 0x456f9fb4u, 0x7d84a5c3u, 0x3b8b5ebeu, + 0xe06f75d8u, 0x85c12073u, 0x401a449fu, 0x56c16aa6u, + 0x4ed3aa62u, 0x363f7706u, 0x1bfedf72u, 0x429b023du, + 0x37d0d724u, 0xd00a1248u, 0xdb0fead3u, 0x49f1c09bu, + 0x075372c9u, 0x80991b7bu, 0x25d479d8u, 0xf6e8def7u, + 0xe3fe501au, 0xb6794c3bu, 0x976ce0bdu, 0x04c006bau, + 0xc1a94fb6u, 0x409f60c4u, 0x5e5c9ec2u, 0x196a2463u, + 0x68fb6fafu, 0x3e6c53b5u, 0x1339b2ebu, 0x3b52ec6fu, + 0x6dfc511fu, 0x9b30952cu, 0xcc814544u, 0xaf5ebd09u, + 0xbee3d004u, 0xde334afdu, 0x660f2807u, 0x192e4bb3u, + 0xc0cba857u, 0x45c8740fu, 0xd20b5f39u, 0xb9d3fbdbu, + 0x5579c0bdu, 0x1a60320au, 0xd6a100c6u, 0x402c7279u, + 0x679f25feu, 0xfb1fa3ccu, 0x8ea5e9f8u, 0xdb3222f8u, + 0x3c7516dfu, 0xfd616b15u, 0x2f501ec8u, 0xad0552abu, + 0x323db5fau, 0xfd238760u, 0x53317b48u, 0x3e00df82u, + 0x9e5c57bbu, 0xca6f8ca0u, 0x1a87562eu, 0xdf1769dbu, + 0xd542a8f6u, 0x287effc3u, 0xac6732c6u, 0x8c4f5573u, + 0x695b27b0u, 0xbbca58c8u, 0xe1ffa35du, 0xb8f011a0u, + 0x10fa3d98u, 0xfd2183b8u, 0x4afcb56cu, 0x2dd1d35bu, + 0x9a53e479u, 0xb6f84565u, 0xd28e49bcu, 0x4bfb9790u, + 0xe1ddf2dau, 0xa4cb7e33u, 0x62fb1341u, 0xcee4c6e8u, + 0xef20cadau, 0x36774c01u, 0xd07e9efeu, 0x2bf11fb4u, + 0x95dbda4du, 0xae909198u, 0xeaad8e71u, 0x6b93d5a0u, + 0xd08ed1d0u, 0xafc725e0u, 0x8e3c5b2fu, 0x8e7594b7u, + 0x8ff6e2fbu, 0xf2122b64u, 0x8888b812u, 0x900df01cu, + 0x4fad5ea0u, 0x688fc31cu, 0xd1cff191u, 0xb3a8c1adu, + 0x2f2f2218u, 0xbe0e1777u, 0xea752dfeu, 0x8b021fa1u, + 0xe5a0cc0fu, 0xb56f74e8u, 0x18acf3d6u, 0xce89e299u, + 0xb4a84fe0u, 0xfd13e0b7u, 0x7cc43b81u, 0xd2ada8d9u, + 0x165fa266u, 0x80957705u, 0x93cc7314u, 0x211a1477u, + 0xe6ad2065u, 0x77b5fa86u, 0xc75442f5u, 0xfb9d35cfu, + 0xebcdaf0cu, 0x7b3e89a0u, 0xd6411bd3u, 0xae1e7e49u, + 0x00250e2du, 0x2071b35eu, 0x226800bbu, 0x57b8e0afu, + 0x2464369bu, 0xf009b91eu, 0x5563911du, 0x59dfa6aau, + 0x78c14389u, 0xd95a537fu, 0x207d5ba2u, 0x02e5b9c5u, + 0x83260376u, 0x6295cfa9u, 0x11c81968u, 0x4e734a41u, + 0xb3472dcau, 0x7b14a94au, 0x1b510052u, 0x9a532915u, + 0xd60f573fu, 0xbc9bc6e4u, 0x2b60a476u, 0x81e67400u, + 0x08ba6fb5u, 0x571be91fu, 0xf296ec6bu, 0x2a0dd915u, + 0xb6636521u, 0xe7b9f9b6u, 0xff34052eu, 0xc5855664u, + 0x53b02d5du, 0xa99f8fa1u, 0x08ba4799u, 0x6e85076au}, + {0x4b7a70e9u, 0xb5b32944u, 0xdb75092eu, 0xc4192623u, + 0xad6ea6b0u, 0x49a7df7du, 0x9cee60b8u, 0x8fedb266u, + 0xecaa8c71u, 0x699a17ffu, 0x5664526cu, 0xc2b19ee1u, + 0x193602a5u, 0x75094c29u, 0xa0591340u, 0xe4183a3eu, + 0x3f54989au, 0x5b429d65u, 0x6b8fe4d6u, 0x99f73fd6u, + 0xa1d29c07u, 0xefe830f5u, 0x4d2d38e6u, 0xf0255dc1u, + 0x4cdd2086u, 0x8470eb26u, 0x6382e9c6u, 0x021ecc5eu, + 0x09686b3fu, 0x3ebaefc9u, 0x3c971814u, 0x6b6a70a1u, + 0x687f3584u, 0x52a0e286u, 0xb79c5305u, 0xaa500737u, + 0x3e07841cu, 0x7fdeae5cu, 0x8e7d44ecu, 0x5716f2b8u, + 0xb03ada37u, 0xf0500c0du, 0xf01c1f04u, 0x0200b3ffu, + 0xae0cf51au, 0x3cb574b2u, 0x25837a58u, 0xdc0921bdu, + 0xd19113f9u, 0x7ca92ff6u, 0x94324773u, 0x22f54701u, + 0x3ae5e581u, 0x37c2dadcu, 0xc8b57634u, 0x9af3dda7u, + 0xa9446146u, 0x0fd0030eu, 0xecc8c73eu, 0xa4751e41u, + 0xe238cd99u, 0x3bea0e2fu, 0x3280bba1u, 0x183eb331u, + 0x4e548b38u, 0x4f6db908u, 0x6f420d03u, 0xf60a04bfu, + 0x2cb81290u, 0x24977c79u, 0x5679b072u, 0xbcaf89afu, + 0xde9a771fu, 0xd9930810u, 0xb38bae12u, 0xdccf3f2eu, + 0x5512721fu, 0x2e6b7124u, 0x501adde6u, 0x9f84cd87u, + 0x7a584718u, 0x7408da17u, 0xbc9f9abcu, 0xe94b7d8cu, + 0xec7aec3au, 0xdb851dfau, 0x63094366u, 0xc464c3d2u, + 0xef1c1847u, 0x3215d908u, 0xdd433b37u, 0x24c2ba16u, + 0x12a14d43u, 0x2a65c451u, 0x50940002u, 0x133ae4ddu, + 0x71dff89eu, 0x10314e55u, 0x81ac77d6u, 0x5f11199bu, + 0x043556f1u, 0xd7a3c76bu, 0x3c11183bu, 0x5924a509u, + 0xf28fe6edu, 0x97f1fbfau, 0x9ebabf2cu, 0x1e153c6eu, + 0x86e34570u, 0xeae96fb1u, 0x860e5e0au, 0x5a3e2ab3u, + 0x771fe71cu, 0x4e3d06fau, 0x2965dcb9u, 0x99e71d0fu, + 0x803e89d6u, 0x5266c825u, 0x2e4cc978u, 0x9c10b36au, + 0xc6150ebau, 0x94e2ea78u, 0xa5fc3c53u, 0x1e0a2df4u, + 0xf2f74ea7u, 0x361d2b3du, 0x1939260fu, 0x19c27960u, + 0x5223a708u, 0xf71312b6u, 0xebadfe6eu, 0xeac31f66u, + 0xe3bc4595u, 0xa67bc883u, 0xb17f37d1u, 0x018cff28u, + 0xc332ddefu, 0xbe6c5aa5u, 0x65582185u, 0x68ab9802u, + 0xeecea50fu, 0xdb2f953bu, 0x2aef7dadu, 0x5b6e2f84u, + 0x1521b628u, 0x29076170u, 0xecdd4775u, 0x619f1510u, + 0x13cca830u, 0xeb61bd96u, 0x0334fe1eu, 0xaa0363cfu, + 0xb5735c90u, 0x4c70a239u, 0xd59e9e0bu, 0xcbaade14u, + 0xeecc86bcu, 0x60622ca7u, 0x9cab5cabu, 0xb2f3846eu, + 0x648b1eafu, 0x19bdf0cau, 0xa02369b9u, 0x655abb50u, + 0x40685a32u, 0x3c2ab4b3u, 0x319ee9d5u, 0xc021b8f7u, + 0x9b540b19u, 0x875fa099u, 0x95f7997eu, 0x623d7da8u, + 0xf837889au, 0x97e32d77u, 0x11ed935fu, 0x16681281u, + 0x0e358829u, 0xc7e61fd6u, 0x96dedfa1u, 0x7858ba99u, + 0x57f584a5u, 0x1b227263u, 0x9b83c3ffu, 0x1ac24696u, + 0xcdb30aebu, 0x532e3054u, 0x8fd948e4u, 0x6dbc3128u, + 0x58ebf2efu, 0x34c6ffeau, 0xfe28ed61u, 0xee7c3c73u, + 0x5d4a14d9u, 0xe864b7e3u, 0x42105d14u, 0x203e13e0u, + 0x45eee2b6u, 0xa3aaabeau, 0xdb6c4f15u, 0xfacb4fd0u, + 0xc742f442u, 0xef6abbb5u, 0x654f3b1du, 0x41cd2105u, + 0xd81e799eu, 0x86854dc7u, 0xe44b476au, 0x3d816250u, + 0xcf62a1f2u, 0x5b8d2646u, 0xfc8883a0u, 0xc1c7b6a3u, + 0x7f1524c3u, 0x69cb7492u, 0x47848a0bu, 0x5692b285u, + 0x095bbf00u, 0xad19489du, 0x1462b174u, 0x23820e00u, + 0x58428d2au, 0x0c55f5eau, 0x1dadf43eu, 0x233f7061u, + 0x3372f092u, 0x8d937e41u, 0xd65fecf1u, 0x6c223bdbu, + 0x7cde3759u, 0xcbee7460u, 0x4085f2a7u, 0xce77326eu, + 0xa6078084u, 0x19f8509eu, 0xe8efd855u, 0x61d99735u, + 0xa969a7aau, 0xc50c06c2u, 0x5a04abfcu, 0x800bcadcu, + 0x9e447a2eu, 0xc3453484u, 0xfdd56705u, 0x0e1e9ec9u, + 0xdb73dbd3u, 0x105588cdu, 0x675fda79u, 0xe3674340u, + 0xc5c43465u, 0x713e38d8u, 0x3d28f89eu, 0xf16dff20u, + 0x153e21e7u, 0x8fb03d4au, 0xe6e39f2bu, 0xdb83adf7u}, + {0xe93d5a68u, 0x948140f7u, 0xf64c261cu, 0x94692934u, + 0x411520f7u, 0x7602d4f7u, 0xbcf46b2eu, 0xd4a20068u, + 0xd4082471u, 0x3320f46au, 0x43b7d4b7u, 0x500061afu, + 0x1e39f62eu, 0x97244546u, 0x14214f74u, 0xbf8b8840u, + 0x4d95fc1du, 0x96b591afu, 0x70f4ddd3u, 0x66a02f45u, + 0xbfbc09ecu, 0x03bd9785u, 0x7fac6dd0u, 0x31cb8504u, + 0x96eb27b3u, 0x55fd3941u, 0xda2547e6u, 0xabca0a9au, + 0x28507825u, 0x530429f4u, 0x0a2c86dau, 0xe9b66dfbu, + 0x68dc1462u, 0xd7486900u, 0x680ec0a4u, 0x27a18deeu, + 0x4f3ffea2u, 0xe887ad8cu, 0xb58ce006u, 0x7af4d6b6u, + 0xaace1e7cu, 0xd3375fecu, 0xce78a399u, 0x406b2a42u, + 0x20fe9e35u, 0xd9f385b9u, 0xee39d7abu, 0x3b124e8bu, + 0x1dc9faf7u, 0x4b6d1856u, 0x26a36631u, 0xeae397b2u, + 0x3a6efa74u, 0xdd5b4332u, 0x6841e7f7u, 0xca7820fbu, + 0xfb0af54eu, 0xd8feb397u, 0x454056acu, 0xba489527u, + 0x55533a3au, 0x20838d87u, 0xfe6ba9b7u, 0xd096954bu, + 0x55a867bcu, 0xa1159a58u, 0xcca92963u, 0x99e1db33u, + 0xa62a4a56u, 0x3f3125f9u, 0x5ef47e1cu, 0x9029317cu, + 0xfdf8e802u, 0x04272f70u, 0x80bb155cu, 0x05282ce3u, + 0x95c11548u, 0xe4c66d22u, 0x48c1133fu, 0xc70f86dcu, + 0x07f9c9eeu, 0x41041f0fu, 0x404779a4u, 0x5d886e17u, + 0x325f51ebu, 0xd59bc0d1u, 0xf2bcc18fu, 0x41113564u, + 0x257b7834u, 0x602a9c60u, 0xdff8e8a3u, 0x1f636c1bu, + 0x0e12b4c2u, 0x02e1329eu, 0xaf664fd1u, 0xcad18115u, + 0x6b2395e0u, 0x333e92e1u, 0x3b240b62u, 0xeebeb922u, + 0x85b2a20eu, 0xe6ba0d99u, 0xde720c8cu, 0x2da2f728u, + 0xd0127845u, 0x95b794fdu, 0x647d0862u, 0xe7ccf5f0u, + 0x5449a36fu, 0x877d48fau, 0xc39dfd27u, 0xf33e8d1eu, + 0x0a476341u, 0x992eff74u, 0x3a6f6eabu, 0xf4f8fd37u, + 0xa812dc60u, 0xa1ebddf8u, 0x991be14cu, 0xdb6e6b0du, + 0xc67b5510u, 0x6d672c37u, 0x2765d43bu, 0xdcd0e804u, + 0xf1290dc7u, 0xcc00ffa3u, 0xb5390f92u, 0x690fed0bu, + 0x667b9ffbu, 0xcedb7d9cu, 0xa091cf0bu, 0xd9155ea3u, + 0xbb132f88u, 0x515bad24u, 0x7b9479bfu, 0x763bd6ebu, + 0x37392eb3u, 0xcc115979u, 0x8026e297u, 0xf42e312du, + 0x6842ada7u, 0xc66a2b3bu, 0x12754cccu, 0x782ef11cu, + 0x6a124237u, 0xb79251e7u, 0x06a1bbe6u, 0x4bfb6350u, + 0x1a6b1018u, 0x11caedfau, 0x3d25bdd8u, 0xe2e1c3c9u, + 0x44421659u, 0x0a121386u, 0xd90cec6eu, 0xd5abea2au, + 0x64af674eu, 0xda86a85fu, 0xbebfe988u, 0x64e4c3feu, + 0x9dbc8057u, 0xf0f7c086u, 0x60787bf8u, 0x6003604du, + 0xd1fd8346u, 0xf6381fb0u, 0x7745ae04u, 0xd736fcccu, + 0x83426b33u, 0xf01eab71u, 0xb0804187u, 0x3c005e5fu, + 0x77a057beu, 0xbde8ae24u, 0x55464299u, 0xbf582e61u, + 0x4e58f48fu, 0xf2ddfda2u, 0xf474ef38u, 0x8789bdc2u, + 0x5366f9c3u, 0xc8b38e74u, 0xb475f255u, 0x46fcd9b9u, + 0x7aeb2661u, 0x8b1ddf84u, 0x846a0e79u, 0x915f95e2u, + 0x466e598eu, 0x20b45770u, 0x8cd55591u, 0xc902de4cu, + 0xb90bace1u, 0xbb8205d0u, 0x11a86248u, 0x7574a99eu, + 0xb77f19b6u, 0xe0a9dc09u, 0x662d09a1u, 0xc4324633u, + 0xe85a1f02u, 0x09f0be8cu, 0x4a99a025u, 0x1d6efe10u, + 0x1ab93d1du, 0x0ba5a4dfu, 0xa186f20fu, 0x2868f169u, + 0xdcb7da83u, 0x573906feu, 0xa1e2ce9bu, 0x4fcd7f52u, + 0x50115e01u, 0xa70683fau, 0xa002b5c4u, 0x0de6d027u, + 0x9af88c27u, 0x773f8641u, 0xc3604c06u, 0x61a806b5u, + 0xf0177a28u, 0xc0f586e0u, 0x006058aau, 0x30dc7d62u, + 0x11e69ed7u, 0x2338ea63u, 0x53c2dd94u, 0xc2c21634u, + 0xbbcbee56u, 0x90bcb6deu, 0xebfc7da1u, 0xce591d76u, + 0x6f05e409u, 0x4b7c0188u, 0x39720a3du, 0x7c927c24u, + 0x86e3725fu, 0x724d9db9u, 0x1ac15bb4u, 0xd39eb8fcu, + 0xed545578u, 0x08fca5b5u, 0xd83d7cd3u, 0x4dad0fc4u, + 0x1e50ef5eu, 0xb161e6f8u, 0xa28514d9u, 0x6c51133cu, + 0x6fd5c7e7u, 0x56e14ec4u, 0x362abfceu, 0xddc6c837u, + 0xd79a3234u, 0x92638212u, 0x670efa8eu, 0x406000e0u}, + {0x3a39ce37u, 0xd3faf5cfu, 0xabc27737u, 0x5ac52d1bu, + 0x5cb0679eu, 0x4fa33742u, 0xd3822740u, 0x99bc9bbeu, + 0xd5118e9du, 0xbf0f7315u, 0xd62d1c7eu, 0xc700c47bu, + 0xb78c1b6bu, 0x21a19045u, 0xb26eb1beu, 0x6a366eb4u, + 0x5748ab2fu, 0xbc946e79u, 0xc6a376d2u, 0x6549c2c8u, + 0x530ff8eeu, 0x468dde7du, 0xd5730a1du, 0x4cd04dc6u, + 0x2939bbdbu, 0xa9ba4650u, 0xac9526e8u, 0xbe5ee304u, + 0xa1fad5f0u, 0x6a2d519au, 0x63ef8ce2u, 0x9a86ee22u, + 0xc089c2b8u, 0x43242ef6u, 0xa51e03aau, 0x9cf2d0a4u, + 0x83c061bau, 0x9be96a4du, 0x8fe51550u, 0xba645bd6u, + 0x2826a2f9u, 0xa73a3ae1u, 0x4ba99586u, 0xef5562e9u, + 0xc72fefd3u, 0xf752f7dau, 0x3f046f69u, 0x77fa0a59u, + 0x80e4a915u, 0x87b08601u, 0x9b09e6adu, 0x3b3ee593u, + 0xe990fd5au, 0x9e34d797u, 0x2cf0b7d9u, 0x022b8b51u, + 0x96d5ac3au, 0x017da67du, 0xd1cf3ed6u, 0x7c7d2d28u, + 0x1f9f25cfu, 0xadf2b89bu, 0x5ad6b472u, 0x5a88f54cu, + 0xe029ac71u, 0xe019a5e6u, 0x47b0acfdu, 0xed93fa9bu, + 0xe8d3c48du, 0x283b57ccu, 0xf8d56629u, 0x79132e28u, + 0x785f0191u, 0xed756055u, 0xf7960e44u, 0xe3d35e8cu, + 0x15056dd4u, 0x88f46dbau, 0x03a16125u, 0x0564f0bdu, + 0xc3eb9e15u, 0x3c9057a2u, 0x97271aecu, 0xa93a072au, + 0x1b3f6d9bu, 0x1e6321f5u, 0xf59c66fbu, 0x26dcf319u, + 0x7533d928u, 0xb155fdf5u, 0x03563482u, 0x8aba3cbbu, + 0x28517711u, 0xc20ad9f8u, 0xabcc5167u, 0xccad925fu, + 0x4de81751u, 0x3830dc8eu, 0x379d5862u, 0x9320f991u, + 0xea7a90c2u, 0xfb3e7bceu, 0x5121ce64u, 0x774fbe32u, + 0xa8b6e37eu, 0xc3293d46u, 0x48de5369u, 0x6413e680u, + 0xa2ae0810u, 0xdd6db224u, 0x69852dfdu, 0x09072166u, + 0xb39a460au, 0x6445c0ddu, 0x586cdecfu, 0x1c20c8aeu, + 0x5bbef7ddu, 0x1b588d40u, 0xccd2017fu, 0x6bb4e3bbu, + 0xdda26a7eu, 0x3a59ff45u, 0x3e350a44u, 0xbcb4cdd5u, + 0x72eacea8u, 0xfa6484bbu, 0x8d6612aeu, 0xbf3c6f47u, + 0xd29be463u, 0x542f5d9eu, 0xaec2771bu, 0xf64e6370u, + 0x740e0d8du, 0xe75b1357u, 0xf8721671u, 0xaf537d5du, + 0x4040cb08u, 0x4eb4e2ccu, 0x34d2466au, 0x0115af84u, + 0xe1b00428u, 0x95983a1du, 0x06b89fb4u, 0xce6ea048u, + 0x6f3f3b82u, 0x3520ab82u, 0x011a1d4bu, 0x277227f8u, + 0x611560b1u, 0xe7933fdcu, 0xbb3a792bu, 0x344525bdu, + 0xa08839e1u, 0x51ce794bu, 0x2f32c9b7u, 0xa01fbac9u, + 0xe01cc87eu, 0xbcc7d1f6u, 0xcf0111c3u, 0xa1e8aac7u, + 0x1a908749u, 0xd44fbd9au, 0xd0dadecbu, 0xd50ada38u, + 0x0339c32au, 0xc6913667u, 0x8df9317cu, 0xe0b12b4fu, + 0xf79e59b7u, 0x43f5bb3au, 0xf2d519ffu, 0x27d9459cu, + 0xbf97222cu, 0x15e6fc2au, 0x0f91fc71u, 0x9b941525u, + 0xfae59361u, 0xceb69cebu, 0xc2a86459u, 0x12baa8d1u, + 0xb6c1075eu, 0xe3056a0cu, 0x10d25065u, 0xcb03a442u, + 0xe0ec6e0eu, 0x1698db3bu, 0x4c98a0beu, 0x3278e964u, + 0x9f1f9532u, 0xe0d392dfu, 0xd3a0342bu, 0x8971f21eu, + 0x1b0a7441u, 0x4ba3348cu, 0xc5be7120u, 0xc37632d8u, + 0xdf359f8du, 0x9b992f2eu, 0xe60b6f47u, 0x0fe3f11du, + 0xe54cda54u, 0x1edad891u, 0xce6279cfu, 0xcd3e7e6fu, + 0x1618b166u, 0xfd2c1d05u, 0x848fd2c5u, 0xf6fb2299u, + 0xf523f357u, 0xa6327623u, 0x93a83531u, 0x56cccd02u, + 0xacf08162u, 0x5a75ebb5u, 0x6e163697u, 0x88d273ccu, + 0xde966292u, 0x81b949d0u, 0x4c50901bu, 0x71c65614u, + 0xe6c6c7bdu, 0x327a140au, 0x45e1d006u, 0xc3f27b9au, + 0xc9aa53fdu, 0x62a80f00u, 0xbb25bfe2u, 0x35bdd2f6u, + 0x71126905u, 0xb2040222u, 0xb6cbcf7cu, 0xcd769c2bu, + 0x53113ec0u, 0x1640e3d3u, 0x38abbd60u, 0x2547adf0u, + 0xba38209cu, 0xf746ce76u, 0x77afa1c5u, 0x20756060u, + 0x85cbfe4eu, 0x8ae88dd8u, 0x7aaaf9b0u, 0x4cf9aa7eu, + 0x1948c25cu, 0x02fb8a8cu, 0x01c36ae4u, 0xd6ebe1f9u, + 0x90d4f869u, 0xa65cdea0u, 0x3f09252du, 0xc208e69fu, + 0xb74e6132u, 0xce77e25bu, 0x578fdfe3u, 0x3ac372e6u} +}; + + +#define F1(i) \ + xl ^= pax[i]; \ + xr ^= ((sbx[0][xl >> 24] + \ + sbx[1][(xl & 0xFF0000) >> 16]) ^ \ + sbx[2][(xl & 0xFF00) >> 8]) + \ + sbx[3][xl & 0xFF]; + +#define F2(i) \ + xr ^= pax[i]; \ + xl ^= ((sbx[0][xr >> 24] + \ + sbx[1][(xr & 0xFF0000) >> 16]) ^ \ + sbx[2][(xr & 0xFF00) >> 8]) + \ + sbx[3][xr & 0xFF]; + + +static void bf_e_block(p_xl, p_xr) +UINT32_T *p_xl; +UINT32_T *p_xr; +{ + UINT32_T temp, xl = *p_xl, xr = *p_xr; + + F1(0) F2(1) F1(2) F2(3) F1(4) F2(5) F1(6) F2(7) + F1(8) F2(9) F1(10) F2(11) F1(12) F2(13) F1(14) F2(15) + xl ^= pax[16]; + xr ^= pax[17]; + temp = xl; + xl = xr; + xr = temp; + *p_xl = xl; + *p_xr = xr; +} + + + +#ifdef WORDS_BIGENDIAN +# define htonl2(x) \ + x = ((((x) & 0xffL) << 24) | (((x) & 0xff00L) << 8) | \ + (((x) & 0xff0000L) >> 8) | (((x) & 0xff000000L) >> 24)) +#else +# define htonl2(x) +#endif + +static void bf_e_cblock(block) +char_u *block; +{ + block8 bk; + + memcpy(bk.uc, block, 8); + htonl2(bk.ul[0]); + htonl2(bk.ul[1]); + bf_e_block(&bk.ul[0], &bk.ul[1]); + htonl2(bk.ul[0]); + htonl2(bk.ul[1]); + memcpy(block, bk.uc, 8); +} + + +/* + * Initialize the crypt method using "password" as the encryption key and + * "salt[salt_len]" as the salt. + */ +void bf_key_init(password, salt, salt_len) +char_u *password; +char_u *salt; +int salt_len; +{ + int i, j, keypos = 0; + unsigned u; + UINT32_T val, data_l, data_r; + char_u *key; + int keylen; + + /* Process the key 1000 times. + * See http://en.wikipedia.org/wiki/Key_strengthening. */ + key = sha256_key(password, salt, salt_len); + for (i = 0; i < 1000; i++) + key = sha256_key(key, salt, salt_len); + + /* Convert the key from 64 hex chars to 32 binary chars. */ + keylen = (int)STRLEN(key) / 2; + if (keylen == 0) { + EMSG(_("E831: bf_key_init() called with empty password")); + return; + } + for (i = 0; i < keylen; i++) { + sscanf((char *)&key[i * 2], "%2x", &u); + key[i] = u; + } + + mch_memmove(sbx, sbi, 4 * 4 * 256); + + for (i = 0; i < 18; ++i) { + val = 0; + for (j = 0; j < 4; ++j) + val = (val << 8) | key[keypos++ % keylen]; + pax[i] = ipa[i] ^ val; + } + + data_l = data_r = 0; + for (i = 0; i < 18; i += 2) { + bf_e_block(&data_l, &data_r); + pax[i + 0] = data_l; + pax[i + 1] = data_r; + } + + for (i = 0; i < 4; ++i) { + for (j = 0; j < 256; j += 2) { + bf_e_block(&data_l, &data_r); + sbx[i][j + 0] = data_l; + sbx[i][j + 1] = data_r; + } + } +} + +/* + * BF Self test for corrupted tables or instructions + */ +static int bf_check_tables(a_ipa, a_sbi, val) +UINT32_T a_ipa[18]; +UINT32_T a_sbi[4][256]; +UINT32_T val; +{ + int i, j; + UINT32_T c = 0; + + for (i = 0; i < 18; i++) + c ^= a_ipa[i]; + for (i = 0; i < 4; i++) + for (j = 0; j < 256; j++) + c ^= a_sbi[i][j]; + return c == val; +} + +typedef struct { + char_u password[64]; + char_u salt[9]; + char_u plaintxt[9]; + char_u cryptxt[9]; + char_u badcryptxt[9]; /* cryptxt when big/little endian is wrong */ + UINT32_T keysum; +} struct_bf_test_data; + +/* + * Assert bf(password, plaintxt) is cryptxt. + * Assert csum(pax sbx(password)) is keysum. + */ +static struct_bf_test_data bf_test_data[] = { + { + "password", + "salt", + "plaintxt", + "\xad\x3d\xfa\x7f\xe8\xea\x40\xf6", /* cryptxt */ + "\x72\x50\x3b\x38\x10\x60\x22\xa7", /* badcryptxt */ + 0x56701b5du /* keysum */ + }, +}; + +/* + * Return FAIL when there is something wrong with blowfish encryption. + */ +static int bf_self_test() { + int i, bn; + int err = 0; + block8 bk; + UINT32_T ui = 0xffffffffUL; + + /* We can't simply use sizeof(UINT32_T), it would generate a compiler + * warning. */ + if (ui != 0xffffffffUL || ui + 1 != 0) { + err++; + EMSG(_("E820: sizeof(uint32_t) != 4")); + } + + if (!bf_check_tables(ipa, sbi, 0x6ffa520a)) + err++; + + bn = ARRAY_LENGTH(bf_test_data); + for (i = 0; i < bn; i++) { + bf_key_init((char_u *)(bf_test_data[i].password), + bf_test_data[i].salt, + (int)STRLEN(bf_test_data[i].salt)); + if (!bf_check_tables(pax, sbx, bf_test_data[i].keysum)) + err++; + + /* Don't modify bf_test_data[i].plaintxt, self test is idempotent. */ + memcpy(bk.uc, bf_test_data[i].plaintxt, 8); + bf_e_cblock(bk.uc); + if (memcmp(bk.uc, bf_test_data[i].cryptxt, 8) != 0) { + if (err == 0 && memcmp(bk.uc, bf_test_data[i].badcryptxt, 8) == 0) + EMSG(_("E817: Blowfish big/little endian use wrong")); + err++; + } + } + + return err > 0 ? FAIL : OK; +} + +/* Output feedback mode. */ +static int randbyte_offset = 0; +static int update_offset = 0; +static char_u ofb_buffer[BF_OFB_LEN]; /* 64 bytes */ + +/* + * Initialize with seed "iv[iv_len]". + */ +void bf_ofb_init(iv, iv_len) +char_u *iv; +int iv_len; +{ + int i, mi; + + randbyte_offset = update_offset = 0; + vim_memset(ofb_buffer, 0, BF_OFB_LEN); + if (iv_len > 0) { + mi = iv_len > BF_OFB_LEN ? iv_len : BF_OFB_LEN; + for (i = 0; i < mi; i++) + ofb_buffer[i % BF_OFB_LEN] ^= iv[i % iv_len]; + } +} + +#define BF_OFB_UPDATE(c) { \ + ofb_buffer[update_offset] ^= (char_u)c; \ + if (++update_offset == BF_OFB_LEN) \ + update_offset = 0; \ +} + +#define BF_RANBYTE(t) { \ + if ((randbyte_offset & BF_BLOCK_MASK) == 0) \ + bf_e_cblock(&ofb_buffer[randbyte_offset]); \ + t = ofb_buffer[randbyte_offset]; \ + if (++randbyte_offset == BF_OFB_LEN) \ + randbyte_offset = 0; \ +} + +/* + * Encrypt "from[len]" into "to[len]". + * "from" and "to" can be equal to encrypt in place. + */ +void bf_crypt_encode(from, len, to) +char_u *from; +size_t len; +char_u *to; +{ + size_t i; + int ztemp, t; + + for (i = 0; i < len; ++i) { + ztemp = from[i]; + BF_RANBYTE(t); + BF_OFB_UPDATE(ztemp); + to[i] = t ^ ztemp; + } +} + +/* + * Decrypt "ptr[len]" in place. + */ +void bf_crypt_decode(ptr, len) +char_u *ptr; +long len; +{ + char_u *p; + int t; + + for (p = ptr; p < ptr + len; ++p) { + BF_RANBYTE(t); + *p ^= t; + BF_OFB_UPDATE(*p); + } +} + +/* + * Initialize the encryption keys and the random header according to + * the given password. + */ +void bf_crypt_init_keys(passwd) +char_u *passwd; /* password string with which to modify keys */ +{ + char_u *p; + + for (p = passwd; *p != NUL; ++p) { + BF_OFB_UPDATE(*p); + } +} + +static int save_randbyte_offset; +static int save_update_offset; +static char_u save_ofb_buffer[BF_OFB_LEN]; +static UINT32_T save_pax[18]; +static UINT32_T save_sbx[4][256]; + +/* + * Save the current crypt state. Can only be used once before + * bf_crypt_restore(). + */ +void bf_crypt_save() { + save_randbyte_offset = randbyte_offset; + save_update_offset = update_offset; + mch_memmove(save_ofb_buffer, ofb_buffer, BF_OFB_LEN); + mch_memmove(save_pax, pax, 4 * 18); + mch_memmove(save_sbx, sbx, 4 * 4 * 256); +} + +/* + * Restore the current crypt state. Can only be used after + * bf_crypt_save(). + */ +void bf_crypt_restore() { + randbyte_offset = save_randbyte_offset; + update_offset = save_update_offset; + mch_memmove(ofb_buffer, save_ofb_buffer, BF_OFB_LEN); + mch_memmove(pax, save_pax, 4 * 18); + mch_memmove(sbx, save_sbx, 4 * 4 * 256); +} + +/* + * Run a test to check if the encryption works as expected. + * Give an error and return FAIL when not. + */ +int blowfish_self_test() { + if (sha256_self_test() == FAIL) { + EMSG(_("E818: sha256 test failed")); + return FAIL; + } + if (bf_self_test() == FAIL) { + EMSG(_("E819: Blowfish test failed")); + return FAIL; + } + return OK; +} + diff --git a/src/buffer.c b/src/buffer.c new file mode 100644 index 0000000000..bffae5388d --- /dev/null +++ b/src/buffer.c @@ -0,0 +1,4517 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * buffer.c: functions for dealing with the buffer structure + */ + +/* + * The buffer list is a double linked list of all buffers. + * Each buffer can be in one of these states: + * never loaded: BF_NEVERLOADED is set, only the file name is valid + * not loaded: b_ml.ml_mfp == NULL, no memfile allocated + * hidden: b_nwindows == 0, loaded but not displayed in a window + * normal: loaded and displayed in a window + * + * Instead of storing file names all over the place, each file name is + * stored in the buffer list. It can be referenced by a number. + * + * The current implementation remembers all file names ever used. + */ + +#include "vim.h" + +static char_u *buflist_match __ARGS((regprog_T *prog, buf_T *buf)); +# define HAVE_BUFLIST_MATCH +static char_u *fname_match __ARGS((regprog_T *prog, char_u *name)); +static void buflist_setfpos __ARGS((buf_T *buf, win_T *win, linenr_T lnum, + colnr_T col, + int copy_options)); +static wininfo_T *find_wininfo __ARGS((buf_T *buf, int skip_diff_buffer)); +#ifdef UNIX +static buf_T *buflist_findname_stat __ARGS((char_u *ffname, struct stat *st)); +static int otherfile_buf __ARGS((buf_T *buf, char_u *ffname, struct stat *stp)); +static int buf_same_ino __ARGS((buf_T *buf, struct stat *stp)); +#else +static int otherfile_buf __ARGS((buf_T *buf, char_u *ffname)); +#endif +static int ti_change __ARGS((char_u *str, char_u **last)); +static int append_arg_number __ARGS((win_T *wp, char_u *buf, int buflen, + int add_file)); +static void free_buffer __ARGS((buf_T *)); +static void free_buffer_stuff __ARGS((buf_T *buf, int free_options)); +static void clear_wininfo __ARGS((buf_T *buf)); + +#ifdef UNIX +# define dev_T dev_t +#else +# define dev_T unsigned +#endif + + +static char *msg_loclist = N_("[Location List]"); +static char *msg_qflist = N_("[Quickfix List]"); +static char *e_auabort = N_("E855: Autocommands caused command to abort"); + +/* + * Open current buffer, that is: open the memfile and read the file into + * memory. + * Return FAIL for failure, OK otherwise. + */ +int open_buffer(read_stdin, eap, flags) +int read_stdin; /* read file from stdin */ +exarg_T *eap; /* for forced 'ff' and 'fenc' or NULL */ +int flags; /* extra flags for readfile() */ +{ + int retval = OK; + buf_T *old_curbuf; + long old_tw = curbuf->b_p_tw; + + /* + * The 'readonly' flag is only set when BF_NEVERLOADED is being reset. + * When re-entering the same buffer, it should not change, because the + * user may have reset the flag by hand. + */ + if (readonlymode && curbuf->b_ffname != NULL + && (curbuf->b_flags & BF_NEVERLOADED)) + curbuf->b_p_ro = TRUE; + + if (ml_open(curbuf) == FAIL) { + /* + * There MUST be a memfile, otherwise we can't do anything + * If we can't create one for the current buffer, take another buffer + */ + close_buffer(NULL, curbuf, 0, FALSE); + for (curbuf = firstbuf; curbuf != NULL; curbuf = curbuf->b_next) + if (curbuf->b_ml.ml_mfp != NULL) + break; + /* + * if there is no memfile at all, exit + * This is OK, since there are no changes to lose. + */ + if (curbuf == NULL) { + EMSG(_("E82: Cannot allocate any buffer, exiting...")); + getout(2); + } + EMSG(_("E83: Cannot allocate buffer, using other one...")); + enter_buffer(curbuf); + if (old_tw != curbuf->b_p_tw) + check_colorcolumn(curwin); + return FAIL; + } + + /* The autocommands in readfile() may change the buffer, but only AFTER + * reading the file. */ + old_curbuf = curbuf; + modified_was_set = FALSE; + + /* mark cursor position as being invalid */ + curwin->w_valid = 0; + + if (curbuf->b_ffname != NULL + ) { + retval = readfile(curbuf->b_ffname, curbuf->b_fname, + (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, eap, + flags | READ_NEW); + /* Help buffer is filtered. */ + if (curbuf->b_help) + fix_help_buffer(); + } else if (read_stdin) { + int save_bin = curbuf->b_p_bin; + linenr_T line_count; + + /* + * First read the text in binary mode into the buffer. + * Then read from that same buffer and append at the end. This makes + * it possible to retry when 'fileformat' or 'fileencoding' was + * guessed wrong. + */ + curbuf->b_p_bin = TRUE; + retval = readfile(NULL, NULL, (linenr_T)0, + (linenr_T)0, (linenr_T)MAXLNUM, NULL, + flags | (READ_NEW + READ_STDIN)); + curbuf->b_p_bin = save_bin; + if (retval == OK) { + line_count = curbuf->b_ml.ml_line_count; + retval = readfile(NULL, NULL, (linenr_T)line_count, + (linenr_T)0, (linenr_T)MAXLNUM, eap, + flags | READ_BUFFER); + if (retval == OK) { + /* Delete the binary lines. */ + while (--line_count >= 0) + ml_delete((linenr_T)1, FALSE); + } else { + /* Delete the converted lines. */ + while (curbuf->b_ml.ml_line_count > line_count) + ml_delete(line_count, FALSE); + } + /* Put the cursor on the first line. */ + curwin->w_cursor.lnum = 1; + curwin->w_cursor.col = 0; + + /* Set or reset 'modified' before executing autocommands, so that + * it can be changed there. */ + if (!readonlymode && !bufempty()) + changed(); + else if (retval != FAIL) + unchanged(curbuf, FALSE); + apply_autocmds_retval(EVENT_STDINREADPOST, NULL, NULL, FALSE, + curbuf, &retval); + } + } + + /* if first time loading this buffer, init b_chartab[] */ + if (curbuf->b_flags & BF_NEVERLOADED) { + (void)buf_init_chartab(curbuf, FALSE); + parse_cino(curbuf); + } + + /* + * Set/reset the Changed flag first, autocmds may change the buffer. + * Apply the automatic commands, before processing the modelines. + * So the modelines have priority over auto commands. + */ + /* When reading stdin, the buffer contents always needs writing, so set + * the changed flag. Unless in readonly mode: "ls | gview -". + * When interrupted and 'cpoptions' contains 'i' set changed flag. */ + if ((got_int && vim_strchr(p_cpo, CPO_INTMOD) != NULL) + || modified_was_set /* ":set modified" used in autocmd */ + || (aborting() && vim_strchr(p_cpo, CPO_INTMOD) != NULL) + ) + changed(); + else if (retval != FAIL && !read_stdin) + unchanged(curbuf, FALSE); + save_file_ff(curbuf); /* keep this fileformat */ + + /* require "!" to overwrite the file, because it wasn't read completely */ + if (aborting()) + curbuf->b_flags |= BF_READERR; + + /* Need to update automatic folding. Do this before the autocommands, + * they may use the fold info. */ + foldUpdateAll(curwin); + + /* need to set w_topline, unless some autocommand already did that. */ + if (!(curwin->w_valid & VALID_TOPLINE)) { + curwin->w_topline = 1; + curwin->w_topfill = 0; + } + apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf, &retval); + + if (retval != FAIL) { + /* + * The autocommands may have changed the current buffer. Apply the + * modelines to the correct buffer, if it still exists and is loaded. + */ + if (buf_valid(old_curbuf) && old_curbuf->b_ml.ml_mfp != NULL) { + aco_save_T aco; + + /* Go to the buffer that was opened. */ + aucmd_prepbuf(&aco, old_curbuf); + do_modelines(0); + curbuf->b_flags &= ~(BF_CHECK_RO | BF_NEVERLOADED); + + apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf, + &retval); + + /* restore curwin/curbuf and a few other things */ + aucmd_restbuf(&aco); + } + } + + return retval; +} + +/* + * Return TRUE if "buf" points to a valid buffer (in the buffer list). + */ +int buf_valid(buf) +buf_T *buf; +{ + buf_T *bp; + + for (bp = firstbuf; bp != NULL; bp = bp->b_next) + if (bp == buf) + return TRUE; + return FALSE; +} + +/* + * Close the link to a buffer. + * "action" is used when there is no longer a window for the buffer. + * It can be: + * 0 buffer becomes hidden + * DOBUF_UNLOAD buffer is unloaded + * DOBUF_DELETE buffer is unloaded and removed from buffer list + * DOBUF_WIPE buffer is unloaded and really deleted + * When doing all but the first one on the current buffer, the caller should + * get a new buffer very soon! + * + * The 'bufhidden' option can force freeing and deleting. + * + * When "abort_if_last" is TRUE then do not close the buffer if autocommands + * cause there to be only one window with this buffer. e.g. when ":quit" is + * supposed to close the window but autocommands close all other windows. + */ +void close_buffer(win, buf, action, abort_if_last) +win_T *win; /* if not NULL, set b_last_cursor */ +buf_T *buf; +int action; +int abort_if_last UNUSED; +{ + int is_curbuf; + int nwindows; + int unload_buf = (action != 0); + int del_buf = (action == DOBUF_DEL || action == DOBUF_WIPE); + int wipe_buf = (action == DOBUF_WIPE); + + /* + * Force unloading or deleting when 'bufhidden' says so. + * The caller must take care of NOT deleting/freeing when 'bufhidden' is + * "hide" (otherwise we could never free or delete a buffer). + */ + if (buf->b_p_bh[0] == 'd') { /* 'bufhidden' == "delete" */ + del_buf = TRUE; + unload_buf = TRUE; + } else if (buf->b_p_bh[0] == 'w') { /* 'bufhidden' == "wipe" */ + del_buf = TRUE; + unload_buf = TRUE; + wipe_buf = TRUE; + } else if (buf->b_p_bh[0] == 'u') /* 'bufhidden' == "unload" */ + unload_buf = TRUE; + + if (win != NULL) { + /* Set b_last_cursor when closing the last window for the buffer. + * Remember the last cursor position and window options of the buffer. + * This used to be only for the current window, but then options like + * 'foldmethod' may be lost with a ":only" command. */ + if (buf->b_nwindows == 1) + set_last_cursor(win); + buflist_setfpos(buf, win, + win->w_cursor.lnum == 1 ? 0 : win->w_cursor.lnum, + win->w_cursor.col, TRUE); + } + + /* When the buffer is no longer in a window, trigger BufWinLeave */ + if (buf->b_nwindows == 1) { + buf->b_closing = TRUE; + apply_autocmds(EVENT_BUFWINLEAVE, buf->b_fname, buf->b_fname, + FALSE, buf); + if (!buf_valid(buf)) { + /* Autocommands deleted the buffer. */ +aucmd_abort: + EMSG(_(e_auabort)); + return; + } + buf->b_closing = FALSE; + if (abort_if_last && one_window()) + /* Autocommands made this the only window. */ + goto aucmd_abort; + + /* When the buffer becomes hidden, but is not unloaded, trigger + * BufHidden */ + if (!unload_buf) { + buf->b_closing = TRUE; + apply_autocmds(EVENT_BUFHIDDEN, buf->b_fname, buf->b_fname, + FALSE, buf); + if (!buf_valid(buf)) + /* Autocommands deleted the buffer. */ + goto aucmd_abort; + buf->b_closing = FALSE; + if (abort_if_last && one_window()) + /* Autocommands made this the only window. */ + goto aucmd_abort; + } + if (aborting()) /* autocmds may abort script processing */ + return; + } + nwindows = buf->b_nwindows; + + /* decrease the link count from windows (unless not in any window) */ + if (buf->b_nwindows > 0) + --buf->b_nwindows; + + /* Return when a window is displaying the buffer or when it's not + * unloaded. */ + if (buf->b_nwindows > 0 || !unload_buf) + return; + + /* Always remove the buffer when there is no file name. */ + if (buf->b_ffname == NULL) + del_buf = TRUE; + + /* + * Free all things allocated for this buffer. + * Also calls the "BufDelete" autocommands when del_buf is TRUE. + */ + /* Remember if we are closing the current buffer. Restore the number of + * windows, so that autocommands in buf_freeall() don't get confused. */ + is_curbuf = (buf == curbuf); + buf->b_nwindows = nwindows; + + buf_freeall(buf, (del_buf ? BFA_DEL : 0) + (wipe_buf ? BFA_WIPE : 0)); + if ( + win_valid(win) && + win->w_buffer == buf) + win->w_buffer = NULL; /* make sure we don't use the buffer now */ + + /* Autocommands may have deleted the buffer. */ + if (!buf_valid(buf)) + return; + if (aborting()) /* autocmds may abort script processing */ + return; + + /* Autocommands may have opened or closed windows for this buffer. + * Decrement the count for the close we do here. */ + if (buf->b_nwindows > 0) + --buf->b_nwindows; + + /* + * It's possible that autocommands change curbuf to the one being deleted. + * This might cause the previous curbuf to be deleted unexpectedly. But + * in some cases it's OK to delete the curbuf, because a new one is + * obtained anyway. Therefore only return if curbuf changed to the + * deleted buffer. + */ + if (buf == curbuf && !is_curbuf) + return; + + /* Change directories when the 'acd' option is set. */ + DO_AUTOCHDIR + + /* + * Remove the buffer from the list. + */ + if (wipe_buf) { + vim_free(buf->b_ffname); + vim_free(buf->b_sfname); + if (buf->b_prev == NULL) + firstbuf = buf->b_next; + else + buf->b_prev->b_next = buf->b_next; + if (buf->b_next == NULL) + lastbuf = buf->b_prev; + else + buf->b_next->b_prev = buf->b_prev; + free_buffer(buf); + } else { + if (del_buf) { + /* Free all internal variables and reset option values, to make + * ":bdel" compatible with Vim 5.7. */ + free_buffer_stuff(buf, TRUE); + + /* Make it look like a new buffer. */ + buf->b_flags = BF_CHECK_RO | BF_NEVERLOADED; + + /* Init the options when loaded again. */ + buf->b_p_initialized = FALSE; + } + buf_clear_file(buf); + if (del_buf) + buf->b_p_bl = FALSE; + } +} + +/* + * Make buffer not contain a file. + */ +void buf_clear_file(buf) +buf_T *buf; +{ + buf->b_ml.ml_line_count = 1; + unchanged(buf, TRUE); +#ifndef SHORT_FNAME + buf->b_shortname = FALSE; +#endif + buf->b_p_eol = TRUE; + buf->b_start_eol = TRUE; + buf->b_p_bomb = FALSE; + buf->b_start_bomb = FALSE; + buf->b_ml.ml_mfp = NULL; + buf->b_ml.ml_flags = ML_EMPTY; /* empty buffer */ +} + +/* + * buf_freeall() - free all things allocated for a buffer that are related to + * the file. flags: + * BFA_DEL buffer is going to be deleted + * BFA_WIPE buffer is going to be wiped out + * BFA_KEEP_UNDO do not free undo information + */ +void buf_freeall(buf, flags) +buf_T *buf; +int flags; +{ + int is_curbuf = (buf == curbuf); + + buf->b_closing = TRUE; + apply_autocmds(EVENT_BUFUNLOAD, buf->b_fname, buf->b_fname, FALSE, buf); + if (!buf_valid(buf)) /* autocommands may delete the buffer */ + return; + if ((flags & BFA_DEL) && buf->b_p_bl) { + apply_autocmds(EVENT_BUFDELETE, buf->b_fname, buf->b_fname, FALSE, buf); + if (!buf_valid(buf)) /* autocommands may delete the buffer */ + return; + } + if (flags & BFA_WIPE) { + apply_autocmds(EVENT_BUFWIPEOUT, buf->b_fname, buf->b_fname, + FALSE, buf); + if (!buf_valid(buf)) /* autocommands may delete the buffer */ + return; + } + buf->b_closing = FALSE; + if (aborting()) /* autocmds may abort script processing */ + return; + + /* + * It's possible that autocommands change curbuf to the one being deleted. + * This might cause curbuf to be deleted unexpectedly. But in some cases + * it's OK to delete the curbuf, because a new one is obtained anyway. + * Therefore only return if curbuf changed to the deleted buffer. + */ + if (buf == curbuf && !is_curbuf) + return; + diff_buf_delete(buf); /* Can't use 'diff' for unloaded buffer. */ + /* Remove any ownsyntax, unless exiting. */ + if (firstwin != NULL && curwin->w_buffer == buf) + reset_synblock(curwin); + + /* No folds in an empty buffer. */ + { + win_T *win; + tabpage_T *tp; + + FOR_ALL_TAB_WINDOWS(tp, win) + if (win->w_buffer == buf) + clearFolding(win); + } + + ml_close(buf, TRUE); /* close and delete the memline/memfile */ + buf->b_ml.ml_line_count = 0; /* no lines in buffer */ + if ((flags & BFA_KEEP_UNDO) == 0) { + u_blockfree(buf); /* free the memory allocated for undo */ + u_clearall(buf); /* reset all undo information */ + } + syntax_clear(&buf->b_s); /* reset syntax info */ + buf->b_flags &= ~BF_READERR; /* a read error is no longer relevant */ +} + +/* + * Free a buffer structure and the things it contains related to the buffer + * itself (not the file, that must have been done already). + */ +static void free_buffer(buf) +buf_T *buf; +{ + free_buffer_stuff(buf, TRUE); + unref_var_dict(buf->b_vars); + aubuflocal_remove(buf); + vim_free(buf); +} + +/* + * Free stuff in the buffer for ":bdel" and when wiping out the buffer. + */ +static void free_buffer_stuff(buf, free_options) +buf_T *buf; +int free_options; /* free options as well */ +{ + if (free_options) { + clear_wininfo(buf); /* including window-local options */ + free_buf_options(buf, TRUE); + ga_clear(&buf->b_s.b_langp); + } + vars_clear(&buf->b_vars->dv_hashtab); /* free all internal variables */ + hash_init(&buf->b_vars->dv_hashtab); + uc_clear(&buf->b_ucmds); /* clear local user commands */ + map_clear_int(buf, MAP_ALL_MODES, TRUE, FALSE); /* clear local mappings */ + map_clear_int(buf, MAP_ALL_MODES, TRUE, TRUE); /* clear local abbrevs */ + vim_free(buf->b_start_fenc); + buf->b_start_fenc = NULL; +} + +/* + * Free the b_wininfo list for buffer "buf". + */ +static void clear_wininfo(buf) +buf_T *buf; +{ + wininfo_T *wip; + + while (buf->b_wininfo != NULL) { + wip = buf->b_wininfo; + buf->b_wininfo = wip->wi_next; + if (wip->wi_optset) { + clear_winopt(&wip->wi_opt); + deleteFoldRecurse(&wip->wi_folds); + } + vim_free(wip); + } +} + +/* + * Go to another buffer. Handles the result of the ATTENTION dialog. + */ +void goto_buffer(eap, start, dir, count) +exarg_T *eap; +int start; +int dir; +int count; +{ +# if defined(FEAT_WINDOWS) && defined(HAS_SWAP_EXISTS_ACTION) + buf_T *old_curbuf = curbuf; + + swap_exists_action = SEA_DIALOG; +# endif + (void)do_buffer(*eap->cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, + start, dir, count, eap->forceit); +# if defined(FEAT_WINDOWS) && defined(HAS_SWAP_EXISTS_ACTION) + if (swap_exists_action == SEA_QUIT && *eap->cmd == 's') { + cleanup_T cs; + + /* Reset the error/interrupt/exception state here so that + * aborting() returns FALSE when closing a window. */ + enter_cleanup(&cs); + + /* Quitting means closing the split window, nothing else. */ + win_close(curwin, TRUE); + swap_exists_action = SEA_NONE; + swap_exists_did_quit = TRUE; + + /* Restore the error/interrupt/exception state if not discarded by a + * new aborting error, interrupt, or uncaught exception. */ + leave_cleanup(&cs); + } else + handle_swap_exists(old_curbuf); +# endif +} + +#if defined(HAS_SWAP_EXISTS_ACTION) || defined(PROTO) +/* + * Handle the situation of swap_exists_action being set. + * It is allowed for "old_curbuf" to be NULL or invalid. + */ +void handle_swap_exists(old_curbuf) +buf_T *old_curbuf; +{ + cleanup_T cs; + long old_tw = curbuf->b_p_tw; + + if (swap_exists_action == SEA_QUIT) { + /* Reset the error/interrupt/exception state here so that + * aborting() returns FALSE when closing a buffer. */ + enter_cleanup(&cs); + + /* User selected Quit at ATTENTION prompt. Go back to previous + * buffer. If that buffer is gone or the same as the current one, + * open a new, empty buffer. */ + swap_exists_action = SEA_NONE; /* don't want it again */ + swap_exists_did_quit = TRUE; + close_buffer(curwin, curbuf, DOBUF_UNLOAD, FALSE); + if (!buf_valid(old_curbuf) || old_curbuf == curbuf) + old_curbuf = buflist_new(NULL, NULL, 1L, BLN_CURBUF | BLN_LISTED); + if (old_curbuf != NULL) { + enter_buffer(old_curbuf); + if (old_tw != curbuf->b_p_tw) + check_colorcolumn(curwin); + } + /* If "old_curbuf" is NULL we are in big trouble here... */ + + /* Restore the error/interrupt/exception state if not discarded by a + * new aborting error, interrupt, or uncaught exception. */ + leave_cleanup(&cs); + } else if (swap_exists_action == SEA_RECOVER) { + /* Reset the error/interrupt/exception state here so that + * aborting() returns FALSE when closing a buffer. */ + enter_cleanup(&cs); + + /* User selected Recover at ATTENTION prompt. */ + msg_scroll = TRUE; + ml_recover(); + MSG_PUTS("\n"); /* don't overwrite the last message */ + cmdline_row = msg_row; + do_modelines(0); + + /* Restore the error/interrupt/exception state if not discarded by a + * new aborting error, interrupt, or uncaught exception. */ + leave_cleanup(&cs); + } + swap_exists_action = SEA_NONE; +} +#endif + +/* + * do_bufdel() - delete or unload buffer(s) + * + * addr_count == 0: ":bdel" - delete current buffer + * addr_count == 1: ":N bdel" or ":bdel N [N ..]" - first delete + * buffer "end_bnr", then any other arguments. + * addr_count == 2: ":N,N bdel" - delete buffers in range + * + * command can be DOBUF_UNLOAD (":bunload"), DOBUF_WIPE (":bwipeout") or + * DOBUF_DEL (":bdel") + * + * Returns error message or NULL + */ +char_u * do_bufdel(command, arg, addr_count, start_bnr, end_bnr, forceit) +int command; +char_u *arg; /* pointer to extra arguments */ +int addr_count; +int start_bnr; /* first buffer number in a range */ +int end_bnr; /* buffer nr or last buffer nr in a range */ +int forceit; +{ + int do_current = 0; /* delete current buffer? */ + int deleted = 0; /* number of buffers deleted */ + char_u *errormsg = NULL; /* return value */ + int bnr; /* buffer number */ + char_u *p; + + if (addr_count == 0) { + (void)do_buffer(command, DOBUF_CURRENT, FORWARD, 0, forceit); + } else { + if (addr_count == 2) { + if (*arg) /* both range and argument is not allowed */ + return (char_u *)_(e_trailing); + bnr = start_bnr; + } else /* addr_count == 1 */ + bnr = end_bnr; + + for (; !got_int; ui_breakcheck()) { + /* + * delete the current buffer last, otherwise when the + * current buffer is deleted, the next buffer becomes + * the current one and will be loaded, which may then + * also be deleted, etc. + */ + if (bnr == curbuf->b_fnum) + do_current = bnr; + else if (do_buffer(command, DOBUF_FIRST, FORWARD, (int)bnr, + forceit) == OK) + ++deleted; + + /* + * find next buffer number to delete/unload + */ + if (addr_count == 2) { + if (++bnr > end_bnr) + break; + } else { /* addr_count == 1 */ + arg = skipwhite(arg); + if (*arg == NUL) + break; + if (!VIM_ISDIGIT(*arg)) { + p = skiptowhite_esc(arg); + bnr = buflist_findpat(arg, p, command == DOBUF_WIPE, + FALSE, FALSE); + if (bnr < 0) /* failed */ + break; + arg = p; + } else + bnr = getdigits(&arg); + } + } + if (!got_int && do_current && do_buffer(command, DOBUF_FIRST, + FORWARD, do_current, forceit) == OK) + ++deleted; + + if (deleted == 0) { + if (command == DOBUF_UNLOAD) + STRCPY(IObuff, _("E515: No buffers were unloaded")); + else if (command == DOBUF_DEL) + STRCPY(IObuff, _("E516: No buffers were deleted")); + else + STRCPY(IObuff, _("E517: No buffers were wiped out")); + errormsg = IObuff; + } else if (deleted >= p_report) { + if (command == DOBUF_UNLOAD) { + if (deleted == 1) + MSG(_("1 buffer unloaded")); + else + smsg((char_u *)_("%d buffers unloaded"), deleted); + } else if (command == DOBUF_DEL) { + if (deleted == 1) + MSG(_("1 buffer deleted")); + else + smsg((char_u *)_("%d buffers deleted"), deleted); + } else { + if (deleted == 1) + MSG(_("1 buffer wiped out")); + else + smsg((char_u *)_("%d buffers wiped out"), deleted); + } + } + } + + + return errormsg; +} + +#if defined(FEAT_LISTCMDS) || defined(FEAT_PYTHON) \ + || defined(FEAT_PYTHON3) || defined(PROTO) + +static int empty_curbuf __ARGS((int close_others, int forceit, int action)); + +/* + * Make the current buffer empty. + * Used when it is wiped out and it's the last buffer. + */ +static int empty_curbuf(close_others, forceit, action) +int close_others; +int forceit; +int action; +{ + int retval; + buf_T *buf = curbuf; + + if (action == DOBUF_UNLOAD) { + EMSG(_("E90: Cannot unload last buffer")); + return FAIL; + } + + if (close_others) { + /* Close any other windows on this buffer, then make it empty. */ + close_windows(buf, TRUE); + } + + setpcmark(); + retval = do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, + forceit ? ECMD_FORCEIT : 0, curwin); + + /* + * do_ecmd() may create a new buffer, then we have to delete + * the old one. But do_ecmd() may have done that already, check + * if the buffer still exists. + */ + if (buf != curbuf && buf_valid(buf) && buf->b_nwindows == 0) + close_buffer(NULL, buf, action, FALSE); + if (!close_others) + need_fileinfo = FALSE; + return retval; +} +/* + * Implementation of the commands for the buffer list. + * + * action == DOBUF_GOTO go to specified buffer + * action == DOBUF_SPLIT split window and go to specified buffer + * action == DOBUF_UNLOAD unload specified buffer(s) + * action == DOBUF_DEL delete specified buffer(s) from buffer list + * action == DOBUF_WIPE delete specified buffer(s) really + * + * start == DOBUF_CURRENT go to "count" buffer from current buffer + * start == DOBUF_FIRST go to "count" buffer from first buffer + * start == DOBUF_LAST go to "count" buffer from last buffer + * start == DOBUF_MOD go to "count" modified buffer from current buffer + * + * Return FAIL or OK. + */ +int do_buffer(action, start, dir, count, forceit) +int action; +int start; +int dir; /* FORWARD or BACKWARD */ +int count; /* buffer number or number of buffers */ +int forceit; /* TRUE for :...! */ +{ + buf_T *buf; + buf_T *bp; + int unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL + || action == DOBUF_WIPE); + + switch (start) { + case DOBUF_FIRST: buf = firstbuf; break; + case DOBUF_LAST: buf = lastbuf; break; + default: buf = curbuf; break; + } + if (start == DOBUF_MOD) { /* find next modified buffer */ + while (count-- > 0) { + do { + buf = buf->b_next; + if (buf == NULL) + buf = firstbuf; + } while (buf != curbuf && !bufIsChanged(buf)); + } + if (!bufIsChanged(buf)) { + EMSG(_("E84: No modified buffer found")); + return FAIL; + } + } else if (start == DOBUF_FIRST && count) { /* find specified buffer number */ + while (buf != NULL && buf->b_fnum != count) + buf = buf->b_next; + } else { + bp = NULL; + while (count > 0 || (!unload && !buf->b_p_bl && bp != buf)) { + /* remember the buffer where we start, we come back there when all + * buffers are unlisted. */ + if (bp == NULL) + bp = buf; + if (dir == FORWARD) { + buf = buf->b_next; + if (buf == NULL) + buf = firstbuf; + } else { + buf = buf->b_prev; + if (buf == NULL) + buf = lastbuf; + } + /* don't count unlisted buffers */ + if (unload || buf->b_p_bl) { + --count; + bp = NULL; /* use this buffer as new starting point */ + } + if (bp == buf) { + /* back where we started, didn't find anything. */ + EMSG(_("E85: There is no listed buffer")); + return FAIL; + } + } + } + + if (buf == NULL) { /* could not find it */ + if (start == DOBUF_FIRST) { + /* don't warn when deleting */ + if (!unload) + EMSGN(_("E86: Buffer %ld does not exist"), count); + } else if (dir == FORWARD) + EMSG(_("E87: Cannot go beyond last buffer")); + else + EMSG(_("E88: Cannot go before first buffer")); + return FAIL; + } + + + /* + * delete buffer buf from memory and/or the list + */ + if (unload) { + int forward; + + /* When unloading or deleting a buffer that's already unloaded and + * unlisted: fail silently. */ + if (action != DOBUF_WIPE && buf->b_ml.ml_mfp == NULL && !buf->b_p_bl) + return FAIL; + + if (!forceit && bufIsChanged(buf)) { + if ((p_confirm || cmdmod.confirm) && p_write) { + dialog_changed(buf, FALSE); + if (!buf_valid(buf)) + /* Autocommand deleted buffer, oops! It's not changed + * now. */ + return FAIL; + /* If it's still changed fail silently, the dialog already + * mentioned why it fails. */ + if (bufIsChanged(buf)) + return FAIL; + } else { + EMSGN(_( + "E89: No write since last change for buffer %ld (add ! to override)"), + buf->b_fnum); + return FAIL; + } + } + + /* + * If deleting the last (listed) buffer, make it empty. + * The last (listed) buffer cannot be unloaded. + */ + for (bp = firstbuf; bp != NULL; bp = bp->b_next) + if (bp->b_p_bl && bp != buf) + break; + if (bp == NULL && buf == curbuf) + return empty_curbuf(TRUE, forceit, action); + + /* + * If the deleted buffer is the current one, close the current window + * (unless it's the only window). Repeat this so long as we end up in + * a window with this buffer. + */ + while (buf == curbuf + && !(curwin->w_closing || curwin->w_buffer->b_closing) + && (firstwin != lastwin || first_tabpage->tp_next != NULL)) { + if (win_close(curwin, FALSE) == FAIL) + break; + } + + /* + * If the buffer to be deleted is not the current one, delete it here. + */ + if (buf != curbuf) { + close_windows(buf, FALSE); + if (buf != curbuf && buf_valid(buf) && buf->b_nwindows <= 0) + close_buffer(NULL, buf, action, FALSE); + return OK; + } + + /* + * Deleting the current buffer: Need to find another buffer to go to. + * There should be another, otherwise it would have been handled + * above. However, autocommands may have deleted all buffers. + * First use au_new_curbuf, if it is valid. + * Then prefer the buffer we most recently visited. + * Else try to find one that is loaded, after the current buffer, + * then before the current buffer. + * Finally use any buffer. + */ + buf = NULL; /* selected buffer */ + bp = NULL; /* used when no loaded buffer found */ + if (au_new_curbuf != NULL && buf_valid(au_new_curbuf)) + buf = au_new_curbuf; + else if (curwin->w_jumplistlen > 0) { + int jumpidx; + + jumpidx = curwin->w_jumplistidx - 1; + if (jumpidx < 0) + jumpidx = curwin->w_jumplistlen - 1; + + forward = jumpidx; + while (jumpidx != curwin->w_jumplistidx) { + buf = buflist_findnr(curwin->w_jumplist[jumpidx].fmark.fnum); + if (buf != NULL) { + if (buf == curbuf || !buf->b_p_bl) + buf = NULL; /* skip current and unlisted bufs */ + else if (buf->b_ml.ml_mfp == NULL) { + /* skip unloaded buf, but may keep it for later */ + if (bp == NULL) + bp = buf; + buf = NULL; + } + } + if (buf != NULL) /* found a valid buffer: stop searching */ + break; + /* advance to older entry in jump list */ + if (!jumpidx && curwin->w_jumplistidx == curwin->w_jumplistlen) + break; + if (--jumpidx < 0) + jumpidx = curwin->w_jumplistlen - 1; + if (jumpidx == forward) /* List exhausted for sure */ + break; + } + } + + if (buf == NULL) { /* No previous buffer, Try 2'nd approach */ + forward = TRUE; + buf = curbuf->b_next; + for (;; ) { + if (buf == NULL) { + if (!forward) /* tried both directions */ + break; + buf = curbuf->b_prev; + forward = FALSE; + continue; + } + /* in non-help buffer, try to skip help buffers, and vv */ + if (buf->b_help == curbuf->b_help && buf->b_p_bl) { + if (buf->b_ml.ml_mfp != NULL) /* found loaded buffer */ + break; + if (bp == NULL) /* remember unloaded buf for later */ + bp = buf; + } + if (forward) + buf = buf->b_next; + else + buf = buf->b_prev; + } + } + if (buf == NULL) /* No loaded buffer, use unloaded one */ + buf = bp; + if (buf == NULL) { /* No loaded buffer, find listed one */ + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + if (buf->b_p_bl && buf != curbuf) + break; + } + if (buf == NULL) { /* Still no buffer, just take one */ + if (curbuf->b_next != NULL) + buf = curbuf->b_next; + else + buf = curbuf->b_prev; + } + } + + if (buf == NULL) { + /* Autocommands must have wiped out all other buffers. Only option + * now is to make the current buffer empty. */ + return empty_curbuf(FALSE, forceit, action); + } + + /* + * make buf current buffer + */ + if (action == DOBUF_SPLIT) { /* split window first */ + /* If 'switchbuf' contains "useopen": jump to first window containing + * "buf" if one exists */ + if ((swb_flags & SWB_USEOPEN) && buf_jump_open_win(buf)) + return OK; + /* If 'switchbuf' contains "usetab": jump to first window in any tab + * page containing "buf" if one exists */ + if ((swb_flags & SWB_USETAB) && buf_jump_open_tab(buf)) + return OK; + if (win_split(0, 0) == FAIL) + return FAIL; + } + + /* go to current buffer - nothing to do */ + if (buf == curbuf) + return OK; + + /* + * Check if the current buffer may be abandoned. + */ + if (action == DOBUF_GOTO && !can_abandon(curbuf, forceit)) { + if ((p_confirm || cmdmod.confirm) && p_write) { + dialog_changed(curbuf, FALSE); + if (!buf_valid(buf)) + /* Autocommand deleted buffer, oops! */ + return FAIL; + } + if (bufIsChanged(curbuf)) { + EMSG(_(e_nowrtmsg)); + return FAIL; + } + } + + /* Go to the other buffer. */ + set_curbuf(buf, action); + +#if defined(FEAT_LISTCMDS) \ + && (defined(FEAT_SCROLLBIND) || defined(FEAT_CURSORBIND)) + if (action == DOBUF_SPLIT) { + RESET_BINDING(curwin); /* reset 'scrollbind' and 'cursorbind' */ + } +#endif + + if (aborting()) /* autocmds may abort script processing */ + return FAIL; + + return OK; +} +#endif + +/* + * Set current buffer to "buf". Executes autocommands and closes current + * buffer. "action" tells how to close the current buffer: + * DOBUF_GOTO free or hide it + * DOBUF_SPLIT nothing + * DOBUF_UNLOAD unload it + * DOBUF_DEL delete it + * DOBUF_WIPE wipe it out + */ +void set_curbuf(buf, action) +buf_T *buf; +int action; +{ + buf_T *prevbuf; + int unload = (action == DOBUF_UNLOAD || action == DOBUF_DEL + || action == DOBUF_WIPE); + long old_tw = curbuf->b_p_tw; + + setpcmark(); + if (!cmdmod.keepalt) + curwin->w_alt_fnum = curbuf->b_fnum; /* remember alternate file */ + buflist_altfpos(curwin); /* remember curpos */ + + /* Don't restart Select mode after switching to another buffer. */ + VIsual_reselect = FALSE; + + /* close_windows() or apply_autocmds() may change curbuf */ + prevbuf = curbuf; + + apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, FALSE, curbuf); + if (buf_valid(prevbuf) && !aborting()) { + if (prevbuf == curwin->w_buffer) + reset_synblock(curwin); + if (unload) + close_windows(prevbuf, FALSE); + if (buf_valid(prevbuf) && !aborting()) { + win_T *previouswin = curwin; + if (prevbuf == curbuf) + u_sync(FALSE); + close_buffer(prevbuf == curwin->w_buffer ? curwin : NULL, prevbuf, + unload ? action : (action == DOBUF_GOTO + && !P_HID(prevbuf) + && !bufIsChanged( + prevbuf)) ? DOBUF_UNLOAD : 0, FALSE); + if (curwin != previouswin && win_valid(previouswin)) + /* autocommands changed curwin, Grr! */ + curwin = previouswin; + } + } + /* An autocommand may have deleted "buf", already entered it (e.g., when + * it did ":bunload") or aborted the script processing! + * If curwin->w_buffer is null, enter_buffer() will make it valid again */ + if ((buf_valid(buf) && buf != curbuf + && !aborting() + ) || curwin->w_buffer == NULL + ) { + enter_buffer(buf); + if (old_tw != curbuf->b_p_tw) + check_colorcolumn(curwin); + } +} + +/* + * Enter a new current buffer. + * Old curbuf must have been abandoned already! This also means "curbuf" may + * be pointing to freed memory. + */ +void enter_buffer(buf) +buf_T *buf; +{ + /* Copy buffer and window local option values. Not for a help buffer. */ + buf_copy_options(buf, BCO_ENTER | BCO_NOHELP); + if (!buf->b_help) + get_winopts(buf); + else + /* Remove all folds in the window. */ + clearFolding(curwin); + foldUpdateAll(curwin); /* update folds (later). */ + + /* Get the buffer in the current window. */ + curwin->w_buffer = buf; + curbuf = buf; + ++curbuf->b_nwindows; + + if (curwin->w_p_diff) + diff_buf_add(curbuf); + + curwin->w_s = &(buf->b_s); + + /* Cursor on first line by default. */ + curwin->w_cursor.lnum = 1; + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + curwin->w_set_curswant = TRUE; + curwin->w_topline_was_set = FALSE; + + /* mark cursor position as being invalid */ + curwin->w_valid = 0; + + /* Make sure the buffer is loaded. */ + if (curbuf->b_ml.ml_mfp == NULL) { /* need to load the file */ + /* If there is no filetype, allow for detecting one. Esp. useful for + * ":ball" used in a autocommand. If there already is a filetype we + * might prefer to keep it. */ + if (*curbuf->b_p_ft == NUL) + did_filetype = FALSE; + + open_buffer(FALSE, NULL, 0); + } else { + if (!msg_silent) + need_fileinfo = TRUE; /* display file info after redraw */ + (void)buf_check_timestamp(curbuf, FALSE); /* check if file changed */ + curwin->w_topline = 1; + curwin->w_topfill = 0; + apply_autocmds(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf); + apply_autocmds(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf); + } + + /* If autocommands did not change the cursor position, restore cursor lnum + * and possibly cursor col. */ + if (curwin->w_cursor.lnum == 1 && inindent(0)) + buflist_getfpos(); + + check_arg_idx(curwin); /* check for valid arg_idx */ + maketitle(); + /* when autocmds didn't change it */ + if (curwin->w_topline == 1 && !curwin->w_topline_was_set) + scroll_cursor_halfway(FALSE); /* redisplay at correct position */ + + + /* Change directories when the 'acd' option is set. */ + DO_AUTOCHDIR + + if (curbuf->b_kmap_state & KEYMAP_INIT) + (void)keymap_init(); + /* May need to set the spell language. Can only do this after the buffer + * has been properly setup. */ + if (!curbuf->b_help && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) + (void)did_set_spelllang(curwin); + + redraw_later(NOT_VALID); +} + +/* + * Change to the directory of the current buffer. + */ +void do_autochdir() { + if (curbuf->b_ffname != NULL && vim_chdirfile(curbuf->b_ffname) == OK) + shorten_fnames(TRUE); +} + +/* + * functions for dealing with the buffer list + */ + +/* + * Add a file name to the buffer list. Return a pointer to the buffer. + * If the same file name already exists return a pointer to that buffer. + * If it does not exist, or if fname == NULL, a new entry is created. + * If (flags & BLN_CURBUF) is TRUE, may use current buffer. + * If (flags & BLN_LISTED) is TRUE, add new buffer to buffer list. + * If (flags & BLN_DUMMY) is TRUE, don't count it as a real buffer. + * This is the ONLY way to create a new buffer. + */ +static int top_file_num = 1; /* highest file number */ + +buf_T * buflist_new(ffname, sfname, lnum, flags) +char_u *ffname; /* full path of fname or relative */ +char_u *sfname; /* short fname or NULL */ +linenr_T lnum; /* preferred cursor line */ +int flags; /* BLN_ defines */ +{ + buf_T *buf; +#ifdef UNIX + struct stat st; +#endif + + fname_expand(curbuf, &ffname, &sfname); /* will allocate ffname */ + + /* + * If file name already exists in the list, update the entry. + */ +#ifdef UNIX + /* On Unix we can use inode numbers when the file exists. Works better + * for hard links. */ + if (sfname == NULL || mch_stat((char *)sfname, &st) < 0) + st.st_dev = (dev_T)-1; +#endif + if (ffname != NULL && !(flags & BLN_DUMMY) && (buf = +#ifdef UNIX + buflist_findname_stat(ffname, + &st) +#else + buflist_findname(ffname) +#endif + ) != NULL) { + vim_free(ffname); + if (lnum != 0) + buflist_setfpos(buf, curwin, lnum, (colnr_T)0, FALSE); + /* copy the options now, if 'cpo' doesn't have 's' and not done + * already */ + buf_copy_options(buf, 0); + if ((flags & BLN_LISTED) && !buf->b_p_bl) { + buf->b_p_bl = TRUE; + if (!(flags & BLN_DUMMY)) + apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, buf); + } + return buf; + } + + /* + * If the current buffer has no name and no contents, use the current + * buffer. Otherwise: Need to allocate a new buffer structure. + * + * This is the ONLY place where a new buffer structure is allocated! + * (A spell file buffer is allocated in spell.c, but that's not a normal + * buffer.) + */ + buf = NULL; + if ((flags & BLN_CURBUF) + && curbuf != NULL + && curbuf->b_ffname == NULL + && curbuf->b_nwindows <= 1 + && (curbuf->b_ml.ml_mfp == NULL || bufempty())) { + buf = curbuf; + /* It's like this buffer is deleted. Watch out for autocommands that + * change curbuf! If that happens, allocate a new buffer anyway. */ + if (curbuf->b_p_bl) + apply_autocmds(EVENT_BUFDELETE, NULL, NULL, FALSE, curbuf); + if (buf == curbuf) + apply_autocmds(EVENT_BUFWIPEOUT, NULL, NULL, FALSE, curbuf); + if (aborting()) /* autocmds may abort script processing */ + return NULL; + if (buf == curbuf) { + /* Make sure 'bufhidden' and 'buftype' are empty */ + clear_string_option(&buf->b_p_bh); + clear_string_option(&buf->b_p_bt); + } + } + if (buf != curbuf || curbuf == NULL) { + buf = (buf_T *)alloc_clear((unsigned)sizeof(buf_T)); + if (buf == NULL) { + vim_free(ffname); + return NULL; + } + /* init b: variables */ + buf->b_vars = dict_alloc(); + if (buf->b_vars == NULL) { + vim_free(ffname); + vim_free(buf); + return NULL; + } + init_var_dict(buf->b_vars, &buf->b_bufvar, VAR_SCOPE); + } + + if (ffname != NULL) { + buf->b_ffname = ffname; + buf->b_sfname = vim_strsave(sfname); + } + + clear_wininfo(buf); + buf->b_wininfo = (wininfo_T *)alloc_clear((unsigned)sizeof(wininfo_T)); + + if ((ffname != NULL && (buf->b_ffname == NULL || buf->b_sfname == NULL)) + || buf->b_wininfo == NULL) { + vim_free(buf->b_ffname); + buf->b_ffname = NULL; + vim_free(buf->b_sfname); + buf->b_sfname = NULL; + if (buf != curbuf) + free_buffer(buf); + return NULL; + } + + if (buf == curbuf) { + /* free all things allocated for this buffer */ + buf_freeall(buf, 0); + if (buf != curbuf) /* autocommands deleted the buffer! */ + return NULL; + if (aborting()) /* autocmds may abort script processing */ + return NULL; + /* buf->b_nwindows = 0; why was this here? */ + free_buffer_stuff(buf, FALSE); /* delete local variables et al. */ + + /* Init the options. */ + buf->b_p_initialized = FALSE; + buf_copy_options(buf, BCO_ENTER); + + /* need to reload lmaps and set b:keymap_name */ + curbuf->b_kmap_state |= KEYMAP_INIT; + } else { + /* + * put new buffer at the end of the buffer list + */ + buf->b_next = NULL; + if (firstbuf == NULL) { /* buffer list is empty */ + buf->b_prev = NULL; + firstbuf = buf; + } else { /* append new buffer at end of list */ + lastbuf->b_next = buf; + buf->b_prev = lastbuf; + } + lastbuf = buf; + + buf->b_fnum = top_file_num++; + if (top_file_num < 0) { /* wrap around (may cause duplicates) */ + EMSG(_("W14: Warning: List of file names overflow")); + if (emsg_silent == 0) { + out_flush(); + ui_delay(3000L, TRUE); /* make sure it is noticed */ + } + top_file_num = 1; + } + + /* + * Always copy the options from the current buffer. + */ + buf_copy_options(buf, BCO_ALWAYS); + } + + buf->b_wininfo->wi_fpos.lnum = lnum; + buf->b_wininfo->wi_win = curwin; + + hash_init(&buf->b_s.b_keywtab); + hash_init(&buf->b_s.b_keywtab_ic); + + buf->b_fname = buf->b_sfname; +#ifdef UNIX + if (st.st_dev == (dev_T)-1) + buf->b_dev_valid = FALSE; + else { + buf->b_dev_valid = TRUE; + buf->b_dev = st.st_dev; + buf->b_ino = st.st_ino; + } +#endif + buf->b_u_synced = TRUE; + buf->b_flags = BF_CHECK_RO | BF_NEVERLOADED; + if (flags & BLN_DUMMY) + buf->b_flags |= BF_DUMMY; + buf_clear_file(buf); + clrallmarks(buf); /* clear marks */ + fmarks_check_names(buf); /* check file marks for this file */ + buf->b_p_bl = (flags & BLN_LISTED) ? TRUE : FALSE; /* init 'buflisted' */ + if (!(flags & BLN_DUMMY)) { + apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, buf); + if (flags & BLN_LISTED) + apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, buf); + if (aborting()) /* autocmds may abort script processing */ + return NULL; + } + + return buf; +} + +/* + * Free the memory for the options of a buffer. + * If "free_p_ff" is TRUE also free 'fileformat', 'buftype' and + * 'fileencoding'. + */ +void free_buf_options(buf, free_p_ff) +buf_T *buf; +int free_p_ff; +{ + if (free_p_ff) { + clear_string_option(&buf->b_p_fenc); + clear_string_option(&buf->b_p_ff); + clear_string_option(&buf->b_p_bh); + clear_string_option(&buf->b_p_bt); + } + clear_string_option(&buf->b_p_def); + clear_string_option(&buf->b_p_inc); + clear_string_option(&buf->b_p_inex); + clear_string_option(&buf->b_p_inde); + clear_string_option(&buf->b_p_indk); + clear_string_option(&buf->b_p_cm); + clear_string_option(&buf->b_p_fex); + clear_string_option(&buf->b_p_key); + clear_string_option(&buf->b_p_kp); + clear_string_option(&buf->b_p_mps); + clear_string_option(&buf->b_p_fo); + clear_string_option(&buf->b_p_flp); + clear_string_option(&buf->b_p_isk); + clear_string_option(&buf->b_p_keymap); + ga_clear(&buf->b_kmap_ga); + clear_string_option(&buf->b_p_com); + clear_string_option(&buf->b_p_cms); + clear_string_option(&buf->b_p_nf); + clear_string_option(&buf->b_p_syn); + clear_string_option(&buf->b_s.b_p_spc); + clear_string_option(&buf->b_s.b_p_spf); + vim_regfree(buf->b_s.b_cap_prog); + buf->b_s.b_cap_prog = NULL; + clear_string_option(&buf->b_s.b_p_spl); + clear_string_option(&buf->b_p_sua); + clear_string_option(&buf->b_p_ft); + clear_string_option(&buf->b_p_cink); + clear_string_option(&buf->b_p_cino); + clear_string_option(&buf->b_p_cinw); + clear_string_option(&buf->b_p_cpt); + clear_string_option(&buf->b_p_cfu); + clear_string_option(&buf->b_p_ofu); + clear_string_option(&buf->b_p_gp); + clear_string_option(&buf->b_p_mp); + clear_string_option(&buf->b_p_efm); + clear_string_option(&buf->b_p_ep); + clear_string_option(&buf->b_p_path); + clear_string_option(&buf->b_p_tags); + clear_string_option(&buf->b_p_dict); + clear_string_option(&buf->b_p_tsr); + clear_string_option(&buf->b_p_qe); + buf->b_p_ar = -1; + buf->b_p_ul = NO_LOCAL_UNDOLEVEL; +} + +/* + * get alternate file n + * set linenr to lnum or altfpos.lnum if lnum == 0 + * also set cursor column to altfpos.col if 'startofline' is not set. + * if (options & GETF_SETMARK) call setpcmark() + * if (options & GETF_ALT) we are jumping to an alternate file. + * if (options & GETF_SWITCH) respect 'switchbuf' settings when jumping + * + * return FAIL for failure, OK for success + */ +int buflist_getfile(n, lnum, options, forceit) +int n; +linenr_T lnum; +int options; +int forceit; +{ + buf_T *buf; + win_T *wp = NULL; + pos_T *fpos; + colnr_T col; + + buf = buflist_findnr(n); + if (buf == NULL) { + if ((options & GETF_ALT) && n == 0) + EMSG(_(e_noalt)); + else + EMSGN(_("E92: Buffer %ld not found"), n); + return FAIL; + } + + /* if alternate file is the current buffer, nothing to do */ + if (buf == curbuf) + return OK; + + if (text_locked()) { + text_locked_msg(); + return FAIL; + } + if (curbuf_locked()) + return FAIL; + + /* altfpos may be changed by getfile(), get it now */ + if (lnum == 0) { + fpos = buflist_findfpos(buf); + lnum = fpos->lnum; + col = fpos->col; + } else + col = 0; + + if (options & GETF_SWITCH) { + /* If 'switchbuf' contains "useopen": jump to first window containing + * "buf" if one exists */ + if (swb_flags & SWB_USEOPEN) + wp = buf_jump_open_win(buf); + /* If 'switchbuf' contains "usetab": jump to first window in any tab + * page containing "buf" if one exists */ + if (wp == NULL && (swb_flags & SWB_USETAB)) + wp = buf_jump_open_tab(buf); + /* If 'switchbuf' contains "split" or "newtab" and the current buffer + * isn't empty: open new window */ + if (wp == NULL && (swb_flags & (SWB_SPLIT | SWB_NEWTAB)) && !bufempty()) { + if (swb_flags & SWB_NEWTAB) /* Open in a new tab */ + tabpage_new(); + else if (win_split(0, 0) == FAIL) /* Open in a new window */ + return FAIL; + RESET_BINDING(curwin); + } + } + + ++RedrawingDisabled; + if (getfile(buf->b_fnum, NULL, NULL, (options & GETF_SETMARK), + lnum, forceit) <= 0) { + --RedrawingDisabled; + + /* cursor is at to BOL and w_cursor.lnum is checked due to getfile() */ + if (!p_sol && col != 0) { + curwin->w_cursor.col = col; + check_cursor_col(); + curwin->w_cursor.coladd = 0; + curwin->w_set_curswant = TRUE; + } + return OK; + } + --RedrawingDisabled; + return FAIL; +} + +/* + * go to the last know line number for the current buffer + */ +void buflist_getfpos() { + pos_T *fpos; + + fpos = buflist_findfpos(curbuf); + + curwin->w_cursor.lnum = fpos->lnum; + check_cursor_lnum(); + + if (p_sol) + curwin->w_cursor.col = 0; + else { + curwin->w_cursor.col = fpos->col; + check_cursor_col(); + curwin->w_cursor.coladd = 0; + curwin->w_set_curswant = TRUE; + } +} + +/* + * Find file in buffer list by name (it has to be for the current window). + * Returns NULL if not found. + */ +buf_T * buflist_findname_exp(fname) +char_u *fname; +{ + char_u *ffname; + buf_T *buf = NULL; + + /* First make the name into a full path name */ + ffname = FullName_save(fname, +#ifdef UNIX + TRUE /* force expansion, get rid of symbolic links */ +#else + FALSE +#endif + ); + if (ffname != NULL) { + buf = buflist_findname(ffname); + vim_free(ffname); + } + return buf; +} + +/* + * Find file in buffer list by name (it has to be for the current window). + * "ffname" must have a full path. + * Skips dummy buffers. + * Returns NULL if not found. + */ +buf_T * buflist_findname(ffname) +char_u *ffname; +{ +#ifdef UNIX + struct stat st; + + if (mch_stat((char *)ffname, &st) < 0) + st.st_dev = (dev_T)-1; + return buflist_findname_stat(ffname, &st); +} + +/* + * Same as buflist_findname(), but pass the stat structure to avoid getting it + * twice for the same file. + * Returns NULL if not found. + */ +static buf_T * buflist_findname_stat(ffname, stp) +char_u *ffname; +struct stat *stp; +{ +#endif + buf_T *buf; + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + if ((buf->b_flags & BF_DUMMY) == 0 && !otherfile_buf(buf, ffname +#ifdef UNIX + , stp +#endif + )) + return buf; + return NULL; +} + +#if defined(FEAT_LISTCMDS) || defined(FEAT_EVAL) || defined(FEAT_PERL) \ + || defined(PROTO) +/* + * Find file in buffer list by a regexp pattern. + * Return fnum of the found buffer. + * Return < 0 for error. + */ +int buflist_findpat(pattern, pattern_end, unlisted, diffmode, curtab_only) +char_u *pattern; +char_u *pattern_end; /* pointer to first char after pattern */ +int unlisted; /* find unlisted buffers */ +int diffmode UNUSED; /* find diff-mode buffers only */ +int curtab_only; /* find buffers in current tab only */ +{ + buf_T *buf; + regprog_T *prog; + int match = -1; + int find_listed; + char_u *pat; + char_u *patend; + int attempt; + char_u *p; + int toggledollar; + + if (pattern_end == pattern + 1 && (*pattern == '%' || *pattern == '#')) { + if (*pattern == '%') + match = curbuf->b_fnum; + else + match = curwin->w_alt_fnum; + if (diffmode && !diff_mode_buf(buflist_findnr(match))) + match = -1; + } + /* + * Try four ways of matching a listed buffer: + * attempt == 0: without '^' or '$' (at any position) + * attempt == 1: with '^' at start (only at position 0) + * attempt == 2: with '$' at end (only match at end) + * attempt == 3: with '^' at start and '$' at end (only full match) + * Repeat this for finding an unlisted buffer if there was no matching + * listed buffer. + */ + else { + pat = file_pat_to_reg_pat(pattern, pattern_end, NULL, FALSE); + if (pat == NULL) + return -1; + patend = pat + STRLEN(pat) - 1; + toggledollar = (patend > pat && *patend == '$'); + + /* First try finding a listed buffer. If not found and "unlisted" + * is TRUE, try finding an unlisted buffer. */ + find_listed = TRUE; + for (;; ) { + for (attempt = 0; attempt <= 3; ++attempt) { + /* may add '^' and '$' */ + if (toggledollar) + *patend = (attempt < 2) ? NUL : '$'; /* add/remove '$' */ + p = pat; + if (*p == '^' && !(attempt & 1)) /* add/remove '^' */ + ++p; + prog = vim_regcomp(p, p_magic ? RE_MAGIC : 0); + if (prog == NULL) { + vim_free(pat); + return -1; + } + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + if (buf->b_p_bl == find_listed + && (!diffmode || diff_mode_buf(buf)) + && buflist_match(prog, buf) != NULL) { + if (curtab_only) { + /* Ignore the match if the buffer is not open in + * the current tab. */ + win_T *wp; + + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp->w_buffer == buf) + break; + if (wp == NULL) + continue; + } + if (match >= 0) { /* already found a match */ + match = -2; + break; + } + match = buf->b_fnum; /* remember first match */ + } + + vim_regfree(prog); + if (match >= 0) /* found one match */ + break; + } + + /* Only search for unlisted buffers if there was no match with + * a listed buffer. */ + if (!unlisted || !find_listed || match != -1) + break; + find_listed = FALSE; + } + + vim_free(pat); + } + + if (match == -2) + EMSG2(_("E93: More than one match for %s"), pattern); + else if (match < 0) + EMSG2(_("E94: No matching buffer for %s"), pattern); + return match; +} +#endif + + +/* + * Find all buffer names that match. + * For command line expansion of ":buf" and ":sbuf". + * Return OK if matches found, FAIL otherwise. + */ +int ExpandBufnames(pat, num_file, file, options) +char_u *pat; +int *num_file; +char_u ***file; +int options; +{ + int count = 0; + buf_T *buf; + int round; + char_u *p; + int attempt; + regprog_T *prog; + char_u *patc; + + *num_file = 0; /* return values in case of FAIL */ + *file = NULL; + + /* Make a copy of "pat" and change "^" to "\(^\|[\/]\)". */ + if (*pat == '^') { + patc = alloc((unsigned)STRLEN(pat) + 11); + if (patc == NULL) + return FAIL; + STRCPY(patc, "\\(^\\|[\\/]\\)"); + STRCPY(patc + 11, pat + 1); + } else + patc = pat; + + /* + * attempt == 0: try match with '\<', match at start of word + * attempt == 1: try match without '\<', match anywhere + */ + for (attempt = 0; attempt <= 1; ++attempt) { + if (attempt > 0 && patc == pat) + break; /* there was no anchor, no need to try again */ + prog = vim_regcomp(patc + attempt * 11, RE_MAGIC); + if (prog == NULL) { + if (patc != pat) + vim_free(patc); + return FAIL; + } + + /* + * round == 1: Count the matches. + * round == 2: Build the array to keep the matches. + */ + for (round = 1; round <= 2; ++round) { + count = 0; + for (buf = firstbuf; buf != NULL; buf = buf->b_next) { + if (!buf->b_p_bl) /* skip unlisted buffers */ + continue; + p = buflist_match(prog, buf); + if (p != NULL) { + if (round == 1) + ++count; + else { + if (options & WILD_HOME_REPLACE) + p = home_replace_save(buf, p); + else + p = vim_strsave(p); + (*file)[count++] = p; + } + } + } + if (count == 0) /* no match found, break here */ + break; + if (round == 1) { + *file = (char_u **)alloc((unsigned)(count * sizeof(char_u *))); + if (*file == NULL) { + vim_regfree(prog); + if (patc != pat) + vim_free(patc); + return FAIL; + } + } + } + vim_regfree(prog); + if (count) /* match(es) found, break here */ + break; + } + + if (patc != pat) + vim_free(patc); + + *num_file = count; + return count == 0 ? FAIL : OK; +} + + +#ifdef HAVE_BUFLIST_MATCH +/* + * Check for a match on the file name for buffer "buf" with regprog "prog". + */ +static char_u * buflist_match(prog, buf) +regprog_T *prog; +buf_T *buf; +{ + char_u *match; + + /* First try the short file name, then the long file name. */ + match = fname_match(prog, buf->b_sfname); + if (match == NULL) + match = fname_match(prog, buf->b_ffname); + + return match; +} + +/* + * Try matching the regexp in "prog" with file name "name". + * Return "name" when there is a match, NULL when not. + */ +static char_u * fname_match(prog, name) +regprog_T *prog; +char_u *name; +{ + char_u *match = NULL; + char_u *p; + regmatch_T regmatch; + + if (name != NULL) { + regmatch.regprog = prog; + regmatch.rm_ic = p_fic; /* ignore case when 'fileignorecase' is set */ + if (vim_regexec(®match, name, (colnr_T)0)) + match = name; + else { + /* Replace $(HOME) with '~' and try matching again. */ + p = home_replace_save(NULL, name); + if (p != NULL && vim_regexec(®match, p, (colnr_T)0)) + match = name; + vim_free(p); + } + } + + return match; +} +#endif + +/* + * find file in buffer list by number + */ +buf_T * buflist_findnr(nr) +int nr; +{ + buf_T *buf; + + if (nr == 0) + nr = curwin->w_alt_fnum; + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + if (buf->b_fnum == nr) + return buf; + return NULL; +} + +/* + * Get name of file 'n' in the buffer list. + * When the file has no name an empty string is returned. + * home_replace() is used to shorten the file name (used for marks). + * Returns a pointer to allocated memory, of NULL when failed. + */ +char_u * buflist_nr2name(n, fullname, helptail) +int n; +int fullname; +int helptail; /* for help buffers return tail only */ +{ + buf_T *buf; + + buf = buflist_findnr(n); + if (buf == NULL) + return NULL; + return home_replace_save(helptail ? buf : NULL, + fullname ? buf->b_ffname : buf->b_fname); +} + +/* + * Set the "lnum" and "col" for the buffer "buf" and the current window. + * When "copy_options" is TRUE save the local window option values. + * When "lnum" is 0 only do the options. + */ +static void buflist_setfpos(buf, win, lnum, col, copy_options) +buf_T *buf; +win_T *win; +linenr_T lnum; +colnr_T col; +int copy_options; +{ + wininfo_T *wip; + + for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) + if (wip->wi_win == win) + break; + if (wip == NULL) { + /* allocate a new entry */ + wip = (wininfo_T *)alloc_clear((unsigned)sizeof(wininfo_T)); + if (wip == NULL) + return; + wip->wi_win = win; + if (lnum == 0) /* set lnum even when it's 0 */ + lnum = 1; + } else { + /* remove the entry from the list */ + if (wip->wi_prev) + wip->wi_prev->wi_next = wip->wi_next; + else + buf->b_wininfo = wip->wi_next; + if (wip->wi_next) + wip->wi_next->wi_prev = wip->wi_prev; + if (copy_options && wip->wi_optset) { + clear_winopt(&wip->wi_opt); + deleteFoldRecurse(&wip->wi_folds); + } + } + if (lnum != 0) { + wip->wi_fpos.lnum = lnum; + wip->wi_fpos.col = col; + } + if (copy_options) { + /* Save the window-specific option values. */ + copy_winopt(&win->w_onebuf_opt, &wip->wi_opt); + wip->wi_fold_manual = win->w_fold_manual; + cloneFoldGrowArray(&win->w_folds, &wip->wi_folds); + wip->wi_optset = TRUE; + } + + /* insert the entry in front of the list */ + wip->wi_next = buf->b_wininfo; + buf->b_wininfo = wip; + wip->wi_prev = NULL; + if (wip->wi_next) + wip->wi_next->wi_prev = wip; + + return; +} + +static int wininfo_other_tab_diff __ARGS((wininfo_T *wip)); + +/* + * Return TRUE when "wip" has 'diff' set and the diff is only for another tab + * page. That's because a diff is local to a tab page. + */ +static int wininfo_other_tab_diff(wip) +wininfo_T *wip; +{ + win_T *wp; + + if (wip->wi_opt.wo_diff) { + for (wp = firstwin; wp != NULL; wp = wp->w_next) + /* return FALSE when it's a window in the current tab page, thus + * the buffer was in diff mode here */ + if (wip->wi_win == wp) + return FALSE; + return TRUE; + } + return FALSE; +} + +/* + * Find info for the current window in buffer "buf". + * If not found, return the info for the most recently used window. + * When "skip_diff_buffer" is TRUE avoid windows with 'diff' set that is in + * another tab page. + * Returns NULL when there isn't any info. + */ +static wininfo_T * find_wininfo(buf, skip_diff_buffer) +buf_T *buf; +int skip_diff_buffer UNUSED; +{ + wininfo_T *wip; + + for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) + if (wip->wi_win == curwin + && (!skip_diff_buffer || !wininfo_other_tab_diff(wip)) + ) + break; + + /* If no wininfo for curwin, use the first in the list (that doesn't have + * 'diff' set and is in another tab page). */ + if (wip == NULL) { + if (skip_diff_buffer) { + for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) + if (!wininfo_other_tab_diff(wip)) + break; + } else + wip = buf->b_wininfo; + } + return wip; +} + +/* + * Reset the local window options to the values last used in this window. + * If the buffer wasn't used in this window before, use the values from + * the most recently used window. If the values were never set, use the + * global values for the window. + */ +void get_winopts(buf) +buf_T *buf; +{ + wininfo_T *wip; + + clear_winopt(&curwin->w_onebuf_opt); + clearFolding(curwin); + + wip = find_wininfo(buf, TRUE); + if (wip != NULL && wip->wi_optset) { + copy_winopt(&wip->wi_opt, &curwin->w_onebuf_opt); + curwin->w_fold_manual = wip->wi_fold_manual; + curwin->w_foldinvalid = TRUE; + cloneFoldGrowArray(&wip->wi_folds, &curwin->w_folds); + } else + copy_winopt(&curwin->w_allbuf_opt, &curwin->w_onebuf_opt); + + /* Set 'foldlevel' to 'foldlevelstart' if it's not negative. */ + if (p_fdls >= 0) + curwin->w_p_fdl = p_fdls; + check_colorcolumn(curwin); +} + +/* + * Find the position (lnum and col) for the buffer 'buf' for the current + * window. + * Returns a pointer to no_position if no position is found. + */ +pos_T * buflist_findfpos(buf) +buf_T *buf; +{ + wininfo_T *wip; + static pos_T no_position = INIT_POS_T(1, 0, 0); + + wip = find_wininfo(buf, FALSE); + if (wip != NULL) + return &(wip->wi_fpos); + else + return &no_position; +} + +/* + * Find the lnum for the buffer 'buf' for the current window. + */ +linenr_T buflist_findlnum(buf) +buf_T *buf; +{ + return buflist_findfpos(buf)->lnum; +} + +/* + * List all know file names (for :files and :buffers command). + */ +void buflist_list(eap) +exarg_T *eap; +{ + buf_T *buf; + int len; + int i; + + for (buf = firstbuf; buf != NULL && !got_int; buf = buf->b_next) { + /* skip unlisted buffers, unless ! was used */ + if (!buf->b_p_bl && !eap->forceit) + continue; + msg_putchar('\n'); + if (buf_spname(buf) != NULL) + vim_strncpy(NameBuff, buf_spname(buf), MAXPATHL - 1); + else + home_replace(buf, buf->b_fname, NameBuff, MAXPATHL, TRUE); + + len = vim_snprintf((char *)IObuff, IOSIZE - 20, "%3d%c%c%c%c%c \"%s\"", + buf->b_fnum, + buf->b_p_bl ? ' ' : 'u', + buf == curbuf ? '%' : + (curwin->w_alt_fnum == buf->b_fnum ? '#' : ' '), + buf->b_ml.ml_mfp == NULL ? ' ' : + (buf->b_nwindows == 0 ? 'h' : 'a'), + !buf->b_p_ma ? '-' : (buf->b_p_ro ? '=' : ' '), + (buf->b_flags & BF_READERR) ? 'x' + : (bufIsChanged(buf) ? '+' : ' '), + NameBuff); + + /* put "line 999" in column 40 or after the file name */ + i = 40 - vim_strsize(IObuff); + do { + IObuff[len++] = ' '; + } while (--i > 0 && len < IOSIZE - 18); + vim_snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), + _("line %ld"), buf == curbuf ? curwin->w_cursor.lnum + : (long)buflist_findlnum(buf)); + msg_outtrans(IObuff); + out_flush(); /* output one line at a time */ + ui_breakcheck(); + } +} + +/* + * Get file name and line number for file 'fnum'. + * Used by DoOneCmd() for translating '%' and '#'. + * Used by insert_reg() and cmdline_paste() for '#' register. + * Return FAIL if not found, OK for success. + */ +int buflist_name_nr(fnum, fname, lnum) +int fnum; +char_u **fname; +linenr_T *lnum; +{ + buf_T *buf; + + buf = buflist_findnr(fnum); + if (buf == NULL || buf->b_fname == NULL) + return FAIL; + + *fname = buf->b_fname; + *lnum = buflist_findlnum(buf); + + return OK; +} + +/* + * Set the file name for "buf"' to 'ffname', short file name to 'sfname'. + * The file name with the full path is also remembered, for when :cd is used. + * Returns FAIL for failure (file name already in use by other buffer) + * OK otherwise. + */ +int setfname(buf, ffname, sfname, message) +buf_T *buf; +char_u *ffname, *sfname; +int message; /* give message when buffer already exists */ +{ + buf_T *obuf = NULL; +#ifdef UNIX + struct stat st; +#endif + + if (ffname == NULL || *ffname == NUL) { + /* Removing the name. */ + vim_free(buf->b_ffname); + vim_free(buf->b_sfname); + buf->b_ffname = NULL; + buf->b_sfname = NULL; +#ifdef UNIX + st.st_dev = (dev_T)-1; +#endif + } else { + fname_expand(buf, &ffname, &sfname); /* will allocate ffname */ + if (ffname == NULL) /* out of memory */ + return FAIL; + + /* + * if the file name is already used in another buffer: + * - if the buffer is loaded, fail + * - if the buffer is not loaded, delete it from the list + */ +#ifdef UNIX + if (mch_stat((char *)ffname, &st) < 0) + st.st_dev = (dev_T)-1; +#endif + if (!(buf->b_flags & BF_DUMMY)) +#ifdef UNIX + obuf = buflist_findname_stat(ffname, &st); +#else + obuf = buflist_findname(ffname); +#endif + if (obuf != NULL && obuf != buf) { + if (obuf->b_ml.ml_mfp != NULL) { /* it's loaded, fail */ + if (message) + EMSG(_("E95: Buffer with this name already exists")); + vim_free(ffname); + return FAIL; + } + /* delete from the list */ + close_buffer(NULL, obuf, DOBUF_WIPE, FALSE); + } + sfname = vim_strsave(sfname); + if (ffname == NULL || sfname == NULL) { + vim_free(sfname); + vim_free(ffname); + return FAIL; + } +#ifdef USE_FNAME_CASE +# ifdef USE_LONG_FNAME + if (USE_LONG_FNAME) +# endif + fname_case(sfname, 0); /* set correct case for short file name */ +#endif + vim_free(buf->b_ffname); + vim_free(buf->b_sfname); + buf->b_ffname = ffname; + buf->b_sfname = sfname; + } + buf->b_fname = buf->b_sfname; +#ifdef UNIX + if (st.st_dev == (dev_T)-1) + buf->b_dev_valid = FALSE; + else { + buf->b_dev_valid = TRUE; + buf->b_dev = st.st_dev; + buf->b_ino = st.st_ino; + } +#endif + +#ifndef SHORT_FNAME + buf->b_shortname = FALSE; +#endif + + buf_name_changed(buf); + return OK; +} + +/* + * Crude way of changing the name of a buffer. Use with care! + * The name should be relative to the current directory. + */ +void buf_set_name(fnum, name) +int fnum; +char_u *name; +{ + buf_T *buf; + + buf = buflist_findnr(fnum); + if (buf != NULL) { + vim_free(buf->b_sfname); + vim_free(buf->b_ffname); + buf->b_ffname = vim_strsave(name); + buf->b_sfname = NULL; + /* Allocate ffname and expand into full path. Also resolves .lnk + * files on Win32. */ + fname_expand(buf, &buf->b_ffname, &buf->b_sfname); + buf->b_fname = buf->b_sfname; + } +} + +/* + * Take care of what needs to be done when the name of buffer "buf" has + * changed. + */ +void buf_name_changed(buf) +buf_T *buf; +{ + /* + * If the file name changed, also change the name of the swapfile + */ + if (buf->b_ml.ml_mfp != NULL) + ml_setname(buf); + + if (curwin->w_buffer == buf) + check_arg_idx(curwin); /* check file name for arg list */ + maketitle(); /* set window title */ + status_redraw_all(); /* status lines need to be redrawn */ + fmarks_check_names(buf); /* check named file marks */ + ml_timestamp(buf); /* reset timestamp */ +} + +/* + * set alternate file name for current window + * + * Used by do_one_cmd(), do_write() and do_ecmd(). + * Return the buffer. + */ +buf_T * setaltfname(ffname, sfname, lnum) +char_u *ffname; +char_u *sfname; +linenr_T lnum; +{ + buf_T *buf; + + /* Create a buffer. 'buflisted' is not set if it's a new buffer */ + buf = buflist_new(ffname, sfname, lnum, 0); + if (buf != NULL && !cmdmod.keepalt) + curwin->w_alt_fnum = buf->b_fnum; + return buf; +} + +/* + * Get alternate file name for current window. + * Return NULL if there isn't any, and give error message if requested. + */ +char_u * getaltfname(errmsg) +int errmsg; /* give error message */ +{ + char_u *fname; + linenr_T dummy; + + if (buflist_name_nr(0, &fname, &dummy) == FAIL) { + if (errmsg) + EMSG(_(e_noalt)); + return NULL; + } + return fname; +} + +/* + * Add a file name to the buflist and return its number. + * Uses same flags as buflist_new(), except BLN_DUMMY. + * + * used by qf_init(), main() and doarglist() + */ +int buflist_add(fname, flags) +char_u *fname; +int flags; +{ + buf_T *buf; + + buf = buflist_new(fname, NULL, (linenr_T)0, flags); + if (buf != NULL) + return buf->b_fnum; + return 0; +} + +#if defined(BACKSLASH_IN_FILENAME) || defined(PROTO) +/* + * Adjust slashes in file names. Called after 'shellslash' was set. + */ +void buflist_slash_adjust() { + buf_T *bp; + + for (bp = firstbuf; bp != NULL; bp = bp->b_next) { + if (bp->b_ffname != NULL) + slash_adjust(bp->b_ffname); + if (bp->b_sfname != NULL) + slash_adjust(bp->b_sfname); + } +} + +#endif + +/* + * Set alternate cursor position for the current buffer and window "win". + * Also save the local window option values. + */ +void buflist_altfpos(win) +win_T *win; +{ + buflist_setfpos(curbuf, win, win->w_cursor.lnum, win->w_cursor.col, TRUE); +} + +/* + * Return TRUE if 'ffname' is not the same file as current file. + * Fname must have a full path (expanded by mch_FullName()). + */ +int otherfile(ffname) +char_u *ffname; +{ + return otherfile_buf(curbuf, ffname +#ifdef UNIX + , NULL +#endif + ); +} + +static int otherfile_buf(buf, ffname +#ifdef UNIX + , stp +#endif + ) +buf_T *buf; +char_u *ffname; +#ifdef UNIX +struct stat *stp; +#endif +{ + /* no name is different */ + if (ffname == NULL || *ffname == NUL || buf->b_ffname == NULL) + return TRUE; + if (fnamecmp(ffname, buf->b_ffname) == 0) + return FALSE; +#ifdef UNIX + { + struct stat st; + + /* If no struct stat given, get it now */ + if (stp == NULL) { + if (!buf->b_dev_valid || mch_stat((char *)ffname, &st) < 0) + st.st_dev = (dev_T)-1; + stp = &st; + } + /* Use dev/ino to check if the files are the same, even when the names + * are different (possible with links). Still need to compare the + * name above, for when the file doesn't exist yet. + * Problem: The dev/ino changes when a file is deleted (and created + * again) and remains the same when renamed/moved. We don't want to + * mch_stat() each buffer each time, that would be too slow. Get the + * dev/ino again when they appear to match, but not when they appear + * to be different: Could skip a buffer when it's actually the same + * file. */ + if (buf_same_ino(buf, stp)) { + buf_setino(buf); + if (buf_same_ino(buf, stp)) + return FALSE; + } + } +#endif + return TRUE; +} + +#if defined(UNIX) || defined(PROTO) +/* + * Set inode and device number for a buffer. + * Must always be called when b_fname is changed!. + */ +void buf_setino(buf) +buf_T *buf; +{ + struct stat st; + + if (buf->b_fname != NULL && mch_stat((char *)buf->b_fname, &st) >= 0) { + buf->b_dev_valid = TRUE; + buf->b_dev = st.st_dev; + buf->b_ino = st.st_ino; + } else + buf->b_dev_valid = FALSE; +} + +/* + * Return TRUE if dev/ino in buffer "buf" matches with "stp". + */ +static int buf_same_ino(buf, stp) +buf_T *buf; +struct stat *stp; +{ + return buf->b_dev_valid + && stp->st_dev == buf->b_dev + && stp->st_ino == buf->b_ino; +} +#endif + +/* + * Print info about the current buffer. + */ +void fileinfo(fullname, shorthelp, dont_truncate) +int fullname; /* when non-zero print full path */ +int shorthelp; +int dont_truncate; +{ + char_u *name; + int n; + char_u *p; + char_u *buffer; + size_t len; + + buffer = alloc(IOSIZE); + if (buffer == NULL) + return; + + if (fullname > 1) { /* 2 CTRL-G: include buffer number */ + vim_snprintf((char *)buffer, IOSIZE, "buf %d: ", curbuf->b_fnum); + p = buffer + STRLEN(buffer); + } else + p = buffer; + + *p++ = '"'; + if (buf_spname(curbuf) != NULL) + vim_strncpy(p, buf_spname(curbuf), IOSIZE - (p - buffer) - 1); + else { + if (!fullname && curbuf->b_fname != NULL) + name = curbuf->b_fname; + else + name = curbuf->b_ffname; + home_replace(shorthelp ? curbuf : NULL, name, p, + (int)(IOSIZE - (p - buffer)), TRUE); + } + + vim_snprintf_add((char *)buffer, IOSIZE, "\"%s%s%s%s%s%s", + curbufIsChanged() ? (shortmess(SHM_MOD) + ? " [+]" : _(" [Modified]")) : " ", + (curbuf->b_flags & BF_NOTEDITED) + && !bt_dontwrite(curbuf) + ? _("[Not edited]") : "", + (curbuf->b_flags & BF_NEW) + && !bt_dontwrite(curbuf) + ? _("[New file]") : "", + (curbuf->b_flags & BF_READERR) ? _("[Read errors]") : "", + curbuf->b_p_ro ? (shortmess(SHM_RO) ? _("[RO]") + : _("[readonly]")) : "", + (curbufIsChanged() || (curbuf->b_flags & BF_WRITE_MASK) + || curbuf->b_p_ro) ? + " " : ""); + /* With 32 bit longs and more than 21,474,836 lines multiplying by 100 + * causes an overflow, thus for large numbers divide instead. */ + if (curwin->w_cursor.lnum > 1000000L) + n = (int)(((long)curwin->w_cursor.lnum) / + ((long)curbuf->b_ml.ml_line_count / 100L)); + else + n = (int)(((long)curwin->w_cursor.lnum * 100L) / + (long)curbuf->b_ml.ml_line_count); + if (curbuf->b_ml.ml_flags & ML_EMPTY) { + vim_snprintf_add((char *)buffer, IOSIZE, "%s", _(no_lines_msg)); + } else if (p_ru) { + /* Current line and column are already on the screen -- webb */ + if (curbuf->b_ml.ml_line_count == 1) + vim_snprintf_add((char *)buffer, IOSIZE, _("1 line --%d%%--"), n); + else + vim_snprintf_add((char *)buffer, IOSIZE, _("%ld lines --%d%%--"), + (long)curbuf->b_ml.ml_line_count, n); + } else { + vim_snprintf_add((char *)buffer, IOSIZE, + _("line %ld of %ld --%d%%-- col "), + (long)curwin->w_cursor.lnum, + (long)curbuf->b_ml.ml_line_count, + n); + validate_virtcol(); + len = STRLEN(buffer); + col_print(buffer + len, IOSIZE - len, + (int)curwin->w_cursor.col + 1, (int)curwin->w_virtcol + 1); + } + + (void)append_arg_number(curwin, buffer, IOSIZE, !shortmess(SHM_FILE)); + + if (dont_truncate) { + /* Temporarily set msg_scroll to avoid the message being truncated. + * First call msg_start() to get the message in the right place. */ + msg_start(); + n = msg_scroll; + msg_scroll = TRUE; + msg(buffer); + msg_scroll = n; + } else { + p = msg_trunc_attr(buffer, FALSE, 0); + if (restart_edit != 0 || (msg_scrolled && !need_wait_return)) + /* Need to repeat the message after redrawing when: + * - When restart_edit is set (otherwise there will be a delay + * before redrawing). + * - When the screen was scrolled but there is no wait-return + * prompt. */ + set_keep_msg(p, 0); + } + + vim_free(buffer); +} + +void col_print(buf, buflen, col, vcol) +char_u *buf; +size_t buflen; +int col; +int vcol; +{ + if (col == vcol) + vim_snprintf((char *)buf, buflen, "%d", col); + else + vim_snprintf((char *)buf, buflen, "%d-%d", col, vcol); +} + +/* + * put file name in title bar of window and in icon title + */ + +static char_u *lasttitle = NULL; +static char_u *lasticon = NULL; + +void maketitle() { + char_u *p; + char_u *t_str = NULL; + char_u *i_name; + char_u *i_str = NULL; + int maxlen = 0; + int len; + int mustset; + char_u buf[IOSIZE]; + int off; + + if (!redrawing()) { + /* Postpone updating the title when 'lazyredraw' is set. */ + need_maketitle = TRUE; + return; + } + + need_maketitle = FALSE; + if (!p_title && !p_icon && lasttitle == NULL && lasticon == NULL) + return; + + if (p_title) { + if (p_titlelen > 0) { + maxlen = p_titlelen * Columns / 100; + if (maxlen < 10) + maxlen = 10; + } + + t_str = buf; + if (*p_titlestring != NUL) { + if (stl_syntax & STL_IN_TITLE) { + int use_sandbox = FALSE; + int save_called_emsg = called_emsg; + + use_sandbox = was_set_insecurely((char_u *)"titlestring", 0); + called_emsg = FALSE; + build_stl_str_hl(curwin, t_str, sizeof(buf), + p_titlestring, use_sandbox, + 0, maxlen, NULL, NULL); + if (called_emsg) + set_string_option_direct((char_u *)"titlestring", -1, + (char_u *)"", OPT_FREE, SID_ERROR); + called_emsg |= save_called_emsg; + } else + t_str = p_titlestring; + } else { + /* format: "fname + (path) (1 of 2) - VIM" */ + +#define SPACE_FOR_FNAME (IOSIZE - 100) +#define SPACE_FOR_DIR (IOSIZE - 20) +#define SPACE_FOR_ARGNR (IOSIZE - 10) /* at least room for " - VIM" */ + if (curbuf->b_fname == NULL) + vim_strncpy(buf, (char_u *)_("[No Name]"), SPACE_FOR_FNAME); + else { + p = transstr(gettail(curbuf->b_fname)); + vim_strncpy(buf, p, SPACE_FOR_FNAME); + vim_free(p); + } + + switch (bufIsChanged(curbuf) + + (curbuf->b_p_ro * 2) + + (!curbuf->b_p_ma * 4)) { + case 1: STRCAT(buf, " +"); break; + case 2: STRCAT(buf, " ="); break; + case 3: STRCAT(buf, " =+"); break; + case 4: + case 6: STRCAT(buf, " -"); break; + case 5: + case 7: STRCAT(buf, " -+"); break; + } + + if (curbuf->b_fname != NULL) { + /* Get path of file, replace home dir with ~ */ + off = (int)STRLEN(buf); + buf[off++] = ' '; + buf[off++] = '('; + home_replace(curbuf, curbuf->b_ffname, + buf + off, SPACE_FOR_DIR - off, TRUE); +#ifdef BACKSLASH_IN_FILENAME + /* avoid "c:/name" to be reduced to "c" */ + if (isalpha(buf[off]) && buf[off + 1] == ':') + off += 2; +#endif + /* remove the file name */ + p = gettail_sep(buf + off); + if (p == buf + off) + /* must be a help buffer */ + vim_strncpy(buf + off, (char_u *)_("help"), + (size_t)(SPACE_FOR_DIR - off - 1)); + else + *p = NUL; + + /* Translate unprintable chars and concatenate. Keep some + * room for the server name. When there is no room (very long + * file name) use (...). */ + if (off < SPACE_FOR_DIR) { + p = transstr(buf + off); + vim_strncpy(buf + off, p, (size_t)(SPACE_FOR_DIR - off)); + vim_free(p); + } else { + vim_strncpy(buf + off, (char_u *)"...", + (size_t)(SPACE_FOR_ARGNR - off)); + } + STRCAT(buf, ")"); + } + + append_arg_number(curwin, buf, SPACE_FOR_ARGNR, FALSE); + + STRCAT(buf, " - VIM"); + + if (maxlen > 0) { + /* make it shorter by removing a bit in the middle */ + if (vim_strsize(buf) > maxlen) + trunc_string(buf, buf, maxlen, IOSIZE); + } + } + } + mustset = ti_change(t_str, &lasttitle); + + if (p_icon) { + i_str = buf; + if (*p_iconstring != NUL) { + if (stl_syntax & STL_IN_ICON) { + int use_sandbox = FALSE; + int save_called_emsg = called_emsg; + + use_sandbox = was_set_insecurely((char_u *)"iconstring", 0); + called_emsg = FALSE; + build_stl_str_hl(curwin, i_str, sizeof(buf), + p_iconstring, use_sandbox, + 0, 0, NULL, NULL); + if (called_emsg) + set_string_option_direct((char_u *)"iconstring", -1, + (char_u *)"", OPT_FREE, SID_ERROR); + called_emsg |= save_called_emsg; + } else + i_str = p_iconstring; + } else { + if (buf_spname(curbuf) != NULL) + i_name = buf_spname(curbuf); + else /* use file name only in icon */ + i_name = gettail(curbuf->b_ffname); + *i_str = NUL; + /* Truncate name at 100 bytes. */ + len = (int)STRLEN(i_name); + if (len > 100) { + len -= 100; + if (has_mbyte) + len += (*mb_tail_off)(i_name, i_name + len) + 1; + i_name += len; + } + STRCPY(i_str, i_name); + trans_characters(i_str, IOSIZE); + } + } + + mustset |= ti_change(i_str, &lasticon); + + if (mustset) + resettitle(); +} + +/* + * Used for title and icon: Check if "str" differs from "*last". Set "*last" + * from "str" if it does. + * Return TRUE when "*last" changed. + */ +static int ti_change(str, last) +char_u *str; +char_u **last; +{ + if ((str == NULL) != (*last == NULL) + || (str != NULL && *last != NULL && STRCMP(str, *last) != 0)) { + vim_free(*last); + if (str == NULL) + *last = NULL; + else + *last = vim_strsave(str); + return TRUE; + } + return FALSE; +} + +/* + * Put current window title back (used after calling a shell) + */ +void resettitle() { + mch_settitle(lasttitle, lasticon); +} + +# if defined(EXITFREE) || defined(PROTO) +void free_titles() { + vim_free(lasttitle); + vim_free(lasticon); +} + +# endif + + +/* + * Build a string from the status line items in "fmt". + * Return length of string in screen cells. + * + * Normally works for window "wp", except when working for 'tabline' then it + * is "curwin". + * + * Items are drawn interspersed with the text that surrounds it + * Specials: %-(xxx%) => group, %= => middle marker, %< => truncation + * Item: %-. All but are optional + * + * If maxwidth is not zero, the string will be filled at any middle marker + * or truncated if too long, fillchar is used for all whitespace. + */ +int build_stl_str_hl(wp, out, outlen, fmt, use_sandbox, fillchar, + maxwidth, hltab, tabtab) +win_T *wp; +char_u *out; /* buffer to write into != NameBuff */ +size_t outlen; /* length of out[] */ +char_u *fmt; +int use_sandbox UNUSED; /* "fmt" was set insecurely, use sandbox */ +int fillchar; +int maxwidth; +struct stl_hlrec *hltab; /* return: HL attributes (can be NULL) */ +struct stl_hlrec *tabtab; /* return: tab page nrs (can be NULL) */ +{ + char_u *p; + char_u *s; + char_u *t; + int byteval; + win_T *o_curwin; + buf_T *o_curbuf; + int empty_line; + colnr_T virtcol; + long l; + long n; + int prevchar_isflag; + int prevchar_isitem; + int itemisflag; + int fillable; + char_u *str; + long num; + int width; + int itemcnt; + int curitem; + int groupitem[STL_MAX_ITEM]; + int groupdepth; + struct stl_item { + char_u *start; + int minwid; + int maxwid; + enum { + Normal, + Empty, + Group, + Middle, + Highlight, + TabPage, + Trunc + } type; + } item[STL_MAX_ITEM]; + int minwid; + int maxwid; + int zeropad; + char_u base; + char_u opt; +#define TMPLEN 70 + char_u tmp[TMPLEN]; + char_u *usefmt = fmt; + struct stl_hlrec *sp; + + /* + * When the format starts with "%!" then evaluate it as an expression and + * use the result as the actual format string. + */ + if (fmt[0] == '%' && fmt[1] == '!') { + usefmt = eval_to_string_safe(fmt + 2, NULL, use_sandbox); + if (usefmt == NULL) + usefmt = fmt; + } + + if (fillchar == 0) + fillchar = ' '; + /* Can't handle a multi-byte fill character yet. */ + else if (mb_char2len(fillchar) > 1) + fillchar = '-'; + + /* Get line & check if empty (cursorpos will show "0-1"). Note that + * p will become invalid when getting another buffer line. */ + p = ml_get_buf(wp->w_buffer, wp->w_cursor.lnum, FALSE); + empty_line = (*p == NUL); + + /* Get the byte value now, in case we need it below. This is more + * efficient than making a copy of the line. */ + if (wp->w_cursor.col > (colnr_T)STRLEN(p)) + byteval = 0; + else + byteval = (*mb_ptr2char)(p + wp->w_cursor.col); + + groupdepth = 0; + p = out; + curitem = 0; + prevchar_isflag = TRUE; + prevchar_isitem = FALSE; + for (s = usefmt; *s; ) { + if (curitem == STL_MAX_ITEM) { + /* There are too many items. Add the error code to the statusline + * to give the user a hint about what went wrong. */ + if (p + 6 < out + outlen) { + mch_memmove(p, " E541", (size_t)5); + p += 5; + } + break; + } + + if (*s != NUL && *s != '%') + prevchar_isflag = prevchar_isitem = FALSE; + + /* + * Handle up to the next '%' or the end. + */ + while (*s != NUL && *s != '%' && p + 1 < out + outlen) + *p++ = *s++; + if (*s == NUL || p + 1 >= out + outlen) + break; + + /* + * Handle one '%' item. + */ + s++; + if (*s == NUL) /* ignore trailing % */ + break; + if (*s == '%') { + if (p + 1 >= out + outlen) + break; + *p++ = *s++; + prevchar_isflag = prevchar_isitem = FALSE; + continue; + } + if (*s == STL_MIDDLEMARK) { + s++; + if (groupdepth > 0) + continue; + item[curitem].type = Middle; + item[curitem++].start = p; + continue; + } + if (*s == STL_TRUNCMARK) { + s++; + item[curitem].type = Trunc; + item[curitem++].start = p; + continue; + } + if (*s == ')') { + s++; + if (groupdepth < 1) + continue; + groupdepth--; + + t = item[groupitem[groupdepth]].start; + *p = NUL; + l = vim_strsize(t); + if (curitem > groupitem[groupdepth] + 1 + && item[groupitem[groupdepth]].minwid == 0) { + /* remove group if all items are empty */ + for (n = groupitem[groupdepth] + 1; n < curitem; n++) + if (item[n].type == Normal) + break; + if (n == curitem) { + p = t; + l = 0; + } + } + if (l > item[groupitem[groupdepth]].maxwid) { + /* truncate, remove n bytes of text at the start */ + if (has_mbyte) { + /* Find the first character that should be included. */ + n = 0; + while (l >= item[groupitem[groupdepth]].maxwid) { + l -= ptr2cells(t + n); + n += (*mb_ptr2len)(t + n); + } + } else + n = (long)(p - t) - item[groupitem[groupdepth]].maxwid + 1; + + *t = '<'; + mch_memmove(t + 1, t + n, (size_t)(p - (t + n))); + p = p - n + 1; + /* Fill up space left over by half a double-wide char. */ + while (++l < item[groupitem[groupdepth]].minwid) + *p++ = fillchar; + + /* correct the start of the items for the truncation */ + for (l = groupitem[groupdepth] + 1; l < curitem; l++) { + item[l].start -= n; + if (item[l].start < t) + item[l].start = t; + } + } else if (abs(item[groupitem[groupdepth]].minwid) > l) { + /* fill */ + n = item[groupitem[groupdepth]].minwid; + if (n < 0) { + /* fill by appending characters */ + n = 0 - n; + while (l++ < n && p + 1 < out + outlen) + *p++ = fillchar; + } else { + /* fill by inserting characters */ + mch_memmove(t + n - l, t, (size_t)(p - t)); + l = n - l; + if (p + l >= out + outlen) + l = (long)((out + outlen) - p - 1); + p += l; + for (n = groupitem[groupdepth] + 1; n < curitem; n++) + item[n].start += l; + for (; l > 0; l--) + *t++ = fillchar; + } + } + continue; + } + minwid = 0; + maxwid = 9999; + zeropad = FALSE; + l = 1; + if (*s == '0') { + s++; + zeropad = TRUE; + } + if (*s == '-') { + s++; + l = -1; + } + if (VIM_ISDIGIT(*s)) { + minwid = (int)getdigits(&s); + if (minwid < 0) /* overflow */ + minwid = 0; + } + if (*s == STL_USER_HL) { + item[curitem].type = Highlight; + item[curitem].start = p; + item[curitem].minwid = minwid > 9 ? 1 : minwid; + s++; + curitem++; + continue; + } + if (*s == STL_TABPAGENR || *s == STL_TABCLOSENR) { + if (*s == STL_TABCLOSENR) { + if (minwid == 0) { + /* %X ends the close label, go back to the previously + * define tab label nr. */ + for (n = curitem - 1; n >= 0; --n) + if (item[n].type == TabPage && item[n].minwid >= 0) { + minwid = item[n].minwid; + break; + } + } else + /* close nrs are stored as negative values */ + minwid = -minwid; + } + item[curitem].type = TabPage; + item[curitem].start = p; + item[curitem].minwid = minwid; + s++; + curitem++; + continue; + } + if (*s == '.') { + s++; + if (VIM_ISDIGIT(*s)) { + maxwid = (int)getdigits(&s); + if (maxwid <= 0) /* overflow */ + maxwid = 50; + } + } + minwid = (minwid > 50 ? 50 : minwid) * l; + if (*s == '(') { + groupitem[groupdepth++] = curitem; + item[curitem].type = Group; + item[curitem].start = p; + item[curitem].minwid = minwid; + item[curitem].maxwid = maxwid; + s++; + curitem++; + continue; + } + if (vim_strchr(STL_ALL, *s) == NULL) { + s++; + continue; + } + opt = *s++; + + /* OK - now for the real work */ + base = 'D'; + itemisflag = FALSE; + fillable = TRUE; + num = -1; + str = NULL; + switch (opt) { + case STL_FILEPATH: + case STL_FULLPATH: + case STL_FILENAME: + fillable = FALSE; /* don't change ' ' to fillchar */ + if (buf_spname(wp->w_buffer) != NULL) + vim_strncpy(NameBuff, buf_spname(wp->w_buffer), MAXPATHL - 1); + else { + t = (opt == STL_FULLPATH) ? wp->w_buffer->b_ffname + : wp->w_buffer->b_fname; + home_replace(wp->w_buffer, t, NameBuff, MAXPATHL, TRUE); + } + trans_characters(NameBuff, MAXPATHL); + if (opt != STL_FILENAME) + str = NameBuff; + else + str = gettail(NameBuff); + break; + + case STL_VIM_EXPR: /* '{' */ + itemisflag = TRUE; + t = p; + while (*s != '}' && *s != NUL && p + 1 < out + outlen) + *p++ = *s++; + if (*s != '}') /* missing '}' or out of space */ + break; + s++; + *p = 0; + p = t; + + vim_snprintf((char *)tmp, sizeof(tmp), "%d", curbuf->b_fnum); + set_internal_string_var((char_u *)"actual_curbuf", tmp); + + o_curbuf = curbuf; + o_curwin = curwin; + curwin = wp; + curbuf = wp->w_buffer; + + str = eval_to_string_safe(p, &t, use_sandbox); + + curwin = o_curwin; + curbuf = o_curbuf; + do_unlet((char_u *)"g:actual_curbuf", TRUE); + + if (str != NULL && *str != 0) { + if (*skipdigits(str) == NUL) { + num = atoi((char *)str); + vim_free(str); + str = NULL; + itemisflag = FALSE; + } + } + break; + + case STL_LINE: + num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) + ? 0L : (long)(wp->w_cursor.lnum); + break; + + case STL_NUMLINES: + num = wp->w_buffer->b_ml.ml_line_count; + break; + + case STL_COLUMN: + num = !(State & INSERT) && empty_line + ? 0 : (int)wp->w_cursor.col + 1; + break; + + case STL_VIRTCOL: + case STL_VIRTCOL_ALT: + /* In list mode virtcol needs to be recomputed */ + virtcol = wp->w_virtcol; + if (wp->w_p_list && lcs_tab1 == NUL) { + wp->w_p_list = FALSE; + getvcol(wp, &wp->w_cursor, NULL, &virtcol, NULL); + wp->w_p_list = TRUE; + } + ++virtcol; + /* Don't display %V if it's the same as %c. */ + if (opt == STL_VIRTCOL_ALT + && (virtcol == (colnr_T)(!(State & INSERT) && empty_line + ? 0 : (int)wp->w_cursor.col + 1))) + break; + num = (long)virtcol; + break; + + case STL_PERCENTAGE: + num = (int)(((long)wp->w_cursor.lnum * 100L) / + (long)wp->w_buffer->b_ml.ml_line_count); + break; + + case STL_ALTPERCENT: + str = tmp; + get_rel_pos(wp, str, TMPLEN); + break; + + case STL_ARGLISTSTAT: + fillable = FALSE; + tmp[0] = 0; + if (append_arg_number(wp, tmp, (int)sizeof(tmp), FALSE)) + str = tmp; + break; + + case STL_KEYMAP: + fillable = FALSE; + if (get_keymap_str(wp, tmp, TMPLEN)) + str = tmp; + break; + case STL_PAGENUM: + num = printer_page_num; + break; + + case STL_BUFNO: + num = wp->w_buffer->b_fnum; + break; + + case STL_OFFSET_X: + base = 'X'; + case STL_OFFSET: + l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL); + num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) || l < 0 ? + 0L : l + 1 + (!(State & INSERT) && empty_line ? + 0 : (int)wp->w_cursor.col); + break; + + case STL_BYTEVAL_X: + base = 'X'; + case STL_BYTEVAL: + num = byteval; + if (num == NL) + num = 0; + else if (num == CAR && get_fileformat(wp->w_buffer) == EOL_MAC) + num = NL; + break; + + case STL_ROFLAG: + case STL_ROFLAG_ALT: + itemisflag = TRUE; + if (wp->w_buffer->b_p_ro) + str = (char_u *)((opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]")); + break; + + case STL_HELPFLAG: + case STL_HELPFLAG_ALT: + itemisflag = TRUE; + if (wp->w_buffer->b_help) + str = (char_u *)((opt == STL_HELPFLAG_ALT) ? ",HLP" + : _("[Help]")); + break; + + case STL_FILETYPE: + if (*wp->w_buffer->b_p_ft != NUL + && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 3) { + vim_snprintf((char *)tmp, sizeof(tmp), "[%s]", + wp->w_buffer->b_p_ft); + str = tmp; + } + break; + + case STL_FILETYPE_ALT: + itemisflag = TRUE; + if (*wp->w_buffer->b_p_ft != NUL + && STRLEN(wp->w_buffer->b_p_ft) < TMPLEN - 2) { + vim_snprintf((char *)tmp, sizeof(tmp), ",%s", + wp->w_buffer->b_p_ft); + for (t = tmp; *t != 0; t++) + *t = TOUPPER_LOC(*t); + str = tmp; + } + break; + + case STL_PREVIEWFLAG: + case STL_PREVIEWFLAG_ALT: + itemisflag = TRUE; + if (wp->w_p_pvw) + str = (char_u *)((opt == STL_PREVIEWFLAG_ALT) ? ",PRV" + : _("[Preview]")); + break; + + case STL_QUICKFIX: + if (bt_quickfix(wp->w_buffer)) + str = (char_u *)(wp->w_llist_ref + ? _(msg_loclist) + : _(msg_qflist)); + break; + + case STL_MODIFIED: + case STL_MODIFIED_ALT: + itemisflag = TRUE; + switch ((opt == STL_MODIFIED_ALT) + + bufIsChanged(wp->w_buffer) * 2 + + (!wp->w_buffer->b_p_ma) * 4) { + case 2: str = (char_u *)"[+]"; break; + case 3: str = (char_u *)",+"; break; + case 4: str = (char_u *)"[-]"; break; + case 5: str = (char_u *)",-"; break; + case 6: str = (char_u *)"[+-]"; break; + case 7: str = (char_u *)",+-"; break; + } + break; + + case STL_HIGHLIGHT: + t = s; + while (*s != '#' && *s != NUL) + ++s; + if (*s == '#') { + item[curitem].type = Highlight; + item[curitem].start = p; + item[curitem].minwid = -syn_namen2id(t, (int)(s - t)); + curitem++; + } + if (*s != NUL) + ++s; + continue; + } + + item[curitem].start = p; + item[curitem].type = Normal; + if (str != NULL && *str) { + t = str; + if (itemisflag) { + if ((t[0] && t[1]) + && ((!prevchar_isitem && *t == ',') + || (prevchar_isflag && *t == ' '))) + t++; + prevchar_isflag = TRUE; + } + l = vim_strsize(t); + if (l > 0) + prevchar_isitem = TRUE; + if (l > maxwid) { + while (l >= maxwid) + if (has_mbyte) { + l -= ptr2cells(t); + t += (*mb_ptr2len)(t); + } else + l -= byte2cells(*t++); + if (p + 1 >= out + outlen) + break; + *p++ = '<'; + } + if (minwid > 0) { + for (; l < minwid && p + 1 < out + outlen; l++) { + /* Don't put a "-" in front of a digit. */ + if (l + 1 == minwid && fillchar == '-' && VIM_ISDIGIT(*t)) + *p++ = ' '; + else + *p++ = fillchar; + } + minwid = 0; + } else + minwid *= -1; + while (*t && p + 1 < out + outlen) { + *p++ = *t++; + /* Change a space by fillchar, unless fillchar is '-' and a + * digit follows. */ + if (fillable && p[-1] == ' ' + && (!VIM_ISDIGIT(*t) || fillchar != '-')) + p[-1] = fillchar; + } + for (; l < minwid && p + 1 < out + outlen; l++) + *p++ = fillchar; + } else if (num >= 0) { + int nbase = (base == 'D' ? 10 : (base == 'O' ? 8 : 16)); + char_u nstr[20]; + + if (p + 20 >= out + outlen) + break; /* not sufficient space */ + prevchar_isitem = TRUE; + t = nstr; + if (opt == STL_VIRTCOL_ALT) { + *t++ = '-'; + minwid--; + } + *t++ = '%'; + if (zeropad) + *t++ = '0'; + *t++ = '*'; + *t++ = nbase == 16 ? base : (char_u)(nbase == 8 ? 'o' : 'd'); + *t = 0; + + for (n = num, l = 1; n >= nbase; n /= nbase) + l++; + if (opt == STL_VIRTCOL_ALT) + l++; + if (l > maxwid) { + l += 2; + n = l - maxwid; + while (l-- > maxwid) + num /= nbase; + *t++ = '>'; + *t++ = '%'; + *t = t[-3]; + *++t = 0; + vim_snprintf((char *)p, outlen - (p - out), (char *)nstr, + 0, num, n); + } else + vim_snprintf((char *)p, outlen - (p - out), (char *)nstr, + minwid, num); + p += STRLEN(p); + } else + item[curitem].type = Empty; + + if (opt == STL_VIM_EXPR) + vim_free(str); + + if (num >= 0 || (!itemisflag && str && *str)) + prevchar_isflag = FALSE; /* Item not NULL, but not a flag */ + curitem++; + } + *p = NUL; + itemcnt = curitem; + + if (usefmt != fmt) + vim_free(usefmt); + + width = vim_strsize(out); + if (maxwidth > 0 && width > maxwidth) { + /* Result is too long, must truncate somewhere. */ + l = 0; + if (itemcnt == 0) + s = out; + else { + for (; l < itemcnt; l++) + if (item[l].type == Trunc) { + /* Truncate at %< item. */ + s = item[l].start; + break; + } + if (l == itemcnt) { + /* No %< item, truncate first item. */ + s = item[0].start; + l = 0; + } + } + + if (width - vim_strsize(s) >= maxwidth) { + /* Truncation mark is beyond max length */ + if (has_mbyte) { + s = out; + width = 0; + for (;; ) { + width += ptr2cells(s); + if (width >= maxwidth) + break; + s += (*mb_ptr2len)(s); + } + /* Fill up for half a double-wide character. */ + while (++width < maxwidth) + *s++ = fillchar; + } else + s = out + maxwidth - 1; + for (l = 0; l < itemcnt; l++) + if (item[l].start > s) + break; + itemcnt = l; + *s++ = '>'; + *s = 0; + } else { + if (has_mbyte) { + n = 0; + while (width >= maxwidth) { + width -= ptr2cells(s + n); + n += (*mb_ptr2len)(s + n); + } + } else + n = width - maxwidth + 1; + p = s + n; + STRMOVE(s + 1, p); + *s = '<'; + + /* Fill up for half a double-wide character. */ + while (++width < maxwidth) { + s = s + STRLEN(s); + *s++ = fillchar; + *s = NUL; + } + + --n; /* count the '<' */ + for (; l < itemcnt; l++) { + if (item[l].start - n >= s) + item[l].start -= n; + else + item[l].start = s; + } + } + width = maxwidth; + } else if (width < maxwidth && STRLEN(out) + maxwidth - width + 1 < + outlen) { + /* Apply STL_MIDDLE if any */ + for (l = 0; l < itemcnt; l++) + if (item[l].type == Middle) + break; + if (l < itemcnt) { + p = item[l].start + maxwidth - width; + STRMOVE(p, item[l].start); + for (s = item[l].start; s < p; s++) + *s = fillchar; + for (l++; l < itemcnt; l++) + item[l].start += maxwidth - width; + width = maxwidth; + } + } + + /* Store the info about highlighting. */ + if (hltab != NULL) { + sp = hltab; + for (l = 0; l < itemcnt; l++) { + if (item[l].type == Highlight) { + sp->start = item[l].start; + sp->userhl = item[l].minwid; + sp++; + } + } + sp->start = NULL; + sp->userhl = 0; + } + + /* Store the info about tab pages labels. */ + if (tabtab != NULL) { + sp = tabtab; + for (l = 0; l < itemcnt; l++) { + if (item[l].type == TabPage) { + sp->start = item[l].start; + sp->userhl = item[l].minwid; + sp++; + } + } + sp->start = NULL; + sp->userhl = 0; + } + + return width; +} + +#if defined(FEAT_STL_OPT) || defined(FEAT_CMDL_INFO) \ + || defined(FEAT_GUI_TABLINE) || defined(PROTO) +/* + * Get relative cursor position in window into "buf[buflen]", in the form 99%, + * using "Top", "Bot" or "All" when appropriate. + */ +void get_rel_pos(wp, buf, buflen) +win_T *wp; +char_u *buf; +int buflen; +{ + long above; /* number of lines above window */ + long below; /* number of lines below window */ + + above = wp->w_topline - 1; + above += diff_check_fill(wp, wp->w_topline) - wp->w_topfill; + below = wp->w_buffer->b_ml.ml_line_count - wp->w_botline + 1; + if (below <= 0) + vim_strncpy(buf, (char_u *)(above == 0 ? _("All") : _("Bot")), + (size_t)(buflen - 1)); + else if (above <= 0) + vim_strncpy(buf, (char_u *)_("Top"), (size_t)(buflen - 1)); + else + vim_snprintf((char *)buf, (size_t)buflen, "%2d%%", above > 1000000L + ? (int)(above / ((above + below) / 100L)) + : (int)(above * 100L / (above + below))); +} +#endif + +/* + * Append (file 2 of 8) to "buf[buflen]", if editing more than one file. + * Return TRUE if it was appended. + */ +static int append_arg_number(wp, buf, buflen, add_file) +win_T *wp; +char_u *buf; +int buflen; +int add_file; /* Add "file" before the arg number */ +{ + char_u *p; + + if (ARGCOUNT <= 1) /* nothing to do */ + return FALSE; + + p = buf + STRLEN(buf); /* go to the end of the buffer */ + if (p - buf + 35 >= buflen) /* getting too long */ + return FALSE; + *p++ = ' '; + *p++ = '('; + if (add_file) { + STRCPY(p, "file "); + p += 5; + } + vim_snprintf((char *)p, (size_t)(buflen - (p - buf)), + wp->w_arg_idx_invalid ? "(%d) of %d)" + : "%d of %d)", wp->w_arg_idx + 1, ARGCOUNT); + return TRUE; +} + +/* + * If fname is not a full path, make it a full path. + * Returns pointer to allocated memory (NULL for failure). + */ +char_u * fix_fname(fname) +char_u *fname; +{ + /* + * Force expanding the path always for Unix, because symbolic links may + * mess up the full path name, even though it starts with a '/'. + * Also expand when there is ".." in the file name, try to remove it, + * because "c:/src/../README" is equal to "c:/README". + * Similarly "c:/src//file" is equal to "c:/src/file". + * For MS-Windows also expand names like "longna~1" to "longname". + */ +#ifdef UNIX + return FullName_save(fname, TRUE); +#else + if (!vim_isAbsName(fname) + || strstr((char *)fname, "..") != NULL + || strstr((char *)fname, "//") != NULL +# ifdef BACKSLASH_IN_FILENAME + || strstr((char *)fname, "\\\\") != NULL +# endif + ) + return FullName_save(fname, FALSE); + + fname = vim_strsave(fname); + +# ifdef USE_FNAME_CASE +# ifdef USE_LONG_FNAME + if (USE_LONG_FNAME) +# endif + { + if (fname != NULL) + fname_case(fname, 0); /* set correct case for file name */ + } +# endif + + return fname; +#endif +} + +/* + * Make "ffname" a full file name, set "sfname" to "ffname" if not NULL. + * "ffname" becomes a pointer to allocated memory (or NULL). + */ +void fname_expand(buf, ffname, sfname) +buf_T *buf UNUSED; +char_u **ffname; +char_u **sfname; +{ + if (*ffname == NULL) /* if no file name given, nothing to do */ + return; + if (*sfname == NULL) /* if no short file name given, use ffname */ + *sfname = *ffname; + *ffname = fix_fname(*ffname); /* expand to full path */ + +#ifdef FEAT_SHORTCUT + if (!buf->b_p_bin) { + char_u *rfname; + + /* If the file name is a shortcut file, use the file it links to. */ + rfname = mch_resolve_shortcut(*ffname); + if (rfname != NULL) { + vim_free(*ffname); + *ffname = rfname; + *sfname = rfname; + } + } +#endif +} + +/* + * Get the file name for an argument list entry. + */ +char_u * alist_name(aep) +aentry_T *aep; +{ + buf_T *bp; + + /* Use the name from the associated buffer if it exists. */ + bp = buflist_findnr(aep->ae_fnum); + if (bp == NULL || bp->b_fname == NULL) + return aep->ae_fname; + return bp->b_fname; +} + +/* + * do_arg_all(): Open up to 'count' windows, one for each argument. + */ +void do_arg_all(count, forceit, keep_tabs) +int count; +int forceit; /* hide buffers in current windows */ +int keep_tabs; /* keep current tabs, for ":tab drop file" */ +{ + int i; + win_T *wp, *wpnext; + char_u *opened; /* Array of weight for which args are open: + * 0: not opened + * 1: opened in other tab + * 2: opened in curtab + * 3: opened in curtab and curwin + */ + int opened_len; /* length of opened[] */ + int use_firstwin = FALSE; /* use first window for arglist */ + int split_ret = OK; + int p_ea_save; + alist_T *alist; /* argument list to be used */ + buf_T *buf; + tabpage_T *tpnext; + int had_tab = cmdmod.tab; + win_T *old_curwin, *last_curwin; + tabpage_T *old_curtab, *last_curtab; + win_T *new_curwin = NULL; + tabpage_T *new_curtab = NULL; + + if (ARGCOUNT <= 0) { + /* Don't give an error message. We don't want it when the ":all" + * command is in the .vimrc. */ + return; + } + setpcmark(); + + opened_len = ARGCOUNT; + opened = alloc_clear((unsigned)opened_len); + if (opened == NULL) + return; + + /* Autocommands may do anything to the argument list. Make sure it's not + * freed while we are working here by "locking" it. We still have to + * watch out for its size to be changed. */ + alist = curwin->w_alist; + ++alist->al_refcount; + + old_curwin = curwin; + old_curtab = curtab; + + + /* + * Try closing all windows that are not in the argument list. + * Also close windows that are not full width; + * When 'hidden' or "forceit" set the buffer becomes hidden. + * Windows that have a changed buffer and can't be hidden won't be closed. + * When the ":tab" modifier was used do this for all tab pages. + */ + if (had_tab > 0) + goto_tabpage_tp(first_tabpage, TRUE, TRUE); + for (;; ) { + tpnext = curtab->tp_next; + for (wp = firstwin; wp != NULL; wp = wpnext) { + wpnext = wp->w_next; + buf = wp->w_buffer; + if (buf->b_ffname == NULL + || (!keep_tabs && buf->b_nwindows > 1) + || wp->w_width != Columns + ) + i = opened_len; + else { + /* check if the buffer in this window is in the arglist */ + for (i = 0; i < opened_len; ++i) { + if (i < alist->al_ga.ga_len + && (AARGLIST(alist)[i].ae_fnum == buf->b_fnum + || fullpathcmp(alist_name(&AARGLIST(alist)[i]), + buf->b_ffname, TRUE) & FPC_SAME)) { + int weight = 1; + + if (old_curtab == curtab) { + ++weight; + if (old_curwin == wp) + ++weight; + } + + if (weight > (int)opened[i]) { + opened[i] = (char_u)weight; + if (i == 0) { + if (new_curwin != NULL) + new_curwin->w_arg_idx = opened_len; + new_curwin = wp; + new_curtab = curtab; + } + } else if (keep_tabs) + i = opened_len; + + if (wp->w_alist != alist) { + /* Use the current argument list for all windows + * containing a file from it. */ + alist_unlink(wp->w_alist); + wp->w_alist = alist; + ++wp->w_alist->al_refcount; + } + break; + } + } + } + wp->w_arg_idx = i; + + if (i == opened_len && !keep_tabs) { /* close this window */ + if (P_HID(buf) || forceit || buf->b_nwindows > 1 + || !bufIsChanged(buf)) { + /* If the buffer was changed, and we would like to hide it, + * try autowriting. */ + if (!P_HID(buf) && buf->b_nwindows <= 1 + && bufIsChanged(buf)) { + (void)autowrite(buf, FALSE); + /* check if autocommands removed the window */ + if (!win_valid(wp) || !buf_valid(buf)) { + wpnext = firstwin; /* start all over... */ + continue; + } + } + /* don't close last window */ + if (firstwin == lastwin + && (first_tabpage->tp_next == NULL || !had_tab)) + use_firstwin = TRUE; + else { + win_close(wp, !P_HID(buf) && !bufIsChanged(buf)); + /* check if autocommands removed the next window */ + if (!win_valid(wpnext)) + wpnext = firstwin; /* start all over... */ + } + } + } + } + + /* Without the ":tab" modifier only do the current tab page. */ + if (had_tab == 0 || tpnext == NULL) + break; + + /* check if autocommands removed the next tab page */ + if (!valid_tabpage(tpnext)) + tpnext = first_tabpage; /* start all over...*/ + goto_tabpage_tp(tpnext, TRUE, TRUE); + } + + /* + * Open a window for files in the argument list that don't have one. + * ARGCOUNT may change while doing this, because of autocommands. + */ + if (count > opened_len || count <= 0) + count = opened_len; + + /* Don't execute Win/Buf Enter/Leave autocommands here. */ + ++autocmd_no_enter; + ++autocmd_no_leave; + last_curwin = curwin; + last_curtab = curtab; + win_enter(lastwin, FALSE); + /* ":drop all" should re-use an empty window to avoid "--remote-tab" + * leaving an empty tab page when executed locally. */ + if (keep_tabs && bufempty() && curbuf->b_nwindows == 1 + && curbuf->b_ffname == NULL && !curbuf->b_changed) + use_firstwin = TRUE; + + for (i = 0; i < count && i < opened_len && !got_int; ++i) { + if (alist == &global_alist && i == global_alist.al_ga.ga_len - 1) + arg_had_last = TRUE; + if (opened[i] > 0) { + /* Move the already present window to below the current window */ + if (curwin->w_arg_idx != i) { + for (wpnext = firstwin; wpnext != NULL; wpnext = wpnext->w_next) { + if (wpnext->w_arg_idx == i) { + if (keep_tabs) { + new_curwin = wpnext; + new_curtab = curtab; + } else + win_move_after(wpnext, curwin); + break; + } + } + } + } else if (split_ret == OK) { + if (!use_firstwin) { /* split current window */ + p_ea_save = p_ea; + p_ea = TRUE; /* use space from all windows */ + split_ret = win_split(0, WSP_ROOM | WSP_BELOW); + p_ea = p_ea_save; + if (split_ret == FAIL) + continue; + } else /* first window: do autocmd for leaving this buffer */ + --autocmd_no_leave; + + /* + * edit file "i" + */ + curwin->w_arg_idx = i; + if (i == 0) { + new_curwin = curwin; + new_curtab = curtab; + } + (void)do_ecmd(0, alist_name(&AARGLIST(alist)[i]), NULL, NULL, + ECMD_ONE, + ((P_HID(curwin->w_buffer) + || bufIsChanged(curwin->w_buffer)) ? ECMD_HIDE : 0) + + ECMD_OLDBUF, curwin); + if (use_firstwin) + ++autocmd_no_leave; + use_firstwin = FALSE; + } + ui_breakcheck(); + + /* When ":tab" was used open a new tab for a new window repeatedly. */ + if (had_tab > 0 && tabpage_index(NULL) <= p_tpm) + cmdmod.tab = 9999; + } + + /* Remove the "lock" on the argument list. */ + alist_unlink(alist); + + --autocmd_no_enter; + /* restore last referenced tabpage's curwin */ + if (last_curtab != new_curtab) { + if (valid_tabpage(last_curtab)) + goto_tabpage_tp(last_curtab, TRUE, TRUE); + if (win_valid(last_curwin)) + win_enter(last_curwin, FALSE); + } + /* to window with first arg */ + if (valid_tabpage(new_curtab)) + goto_tabpage_tp(new_curtab, TRUE, TRUE); + if (win_valid(new_curwin)) + win_enter(new_curwin, FALSE); + + --autocmd_no_leave; + vim_free(opened); +} + +/* + * Open a window for a number of buffers. + */ +void ex_buffer_all(eap) +exarg_T *eap; +{ + buf_T *buf; + win_T *wp, *wpnext; + int split_ret = OK; + int p_ea_save; + int open_wins = 0; + int r; + int count; /* Maximum number of windows to open. */ + int all; /* When TRUE also load inactive buffers. */ + int had_tab = cmdmod.tab; + tabpage_T *tpnext; + + if (eap->addr_count == 0) /* make as many windows as possible */ + count = 9999; + else + count = eap->line2; /* make as many windows as specified */ + if (eap->cmdidx == CMD_unhide || eap->cmdidx == CMD_sunhide) + all = FALSE; + else + all = TRUE; + + setpcmark(); + + + /* + * Close superfluous windows (two windows for the same buffer). + * Also close windows that are not full-width. + */ + if (had_tab > 0) + goto_tabpage_tp(first_tabpage, TRUE, TRUE); + for (;; ) { + tpnext = curtab->tp_next; + for (wp = firstwin; wp != NULL; wp = wpnext) { + wpnext = wp->w_next; + if ((wp->w_buffer->b_nwindows > 1 + || ((cmdmod.split & WSP_VERT) + ? wp->w_height + wp->w_status_height < Rows - p_ch + - tabline_height() + : wp->w_width != Columns) + || (had_tab > 0 && wp != firstwin) + ) && firstwin != lastwin + && !(wp->w_closing || wp->w_buffer->b_closing) + ) { + win_close(wp, FALSE); + wpnext = firstwin; /* just in case an autocommand does + something strange with windows */ + tpnext = first_tabpage; /* start all over...*/ + open_wins = 0; + } else + ++open_wins; + } + + /* Without the ":tab" modifier only do the current tab page. */ + if (had_tab == 0 || tpnext == NULL) + break; + goto_tabpage_tp(tpnext, TRUE, TRUE); + } + + /* + * Go through the buffer list. When a buffer doesn't have a window yet, + * open one. Otherwise move the window to the right position. + * Watch out for autocommands that delete buffers or windows! + */ + /* Don't execute Win/Buf Enter/Leave autocommands here. */ + ++autocmd_no_enter; + win_enter(lastwin, FALSE); + ++autocmd_no_leave; + for (buf = firstbuf; buf != NULL && open_wins < count; buf = buf->b_next) { + /* Check if this buffer needs a window */ + if ((!all && buf->b_ml.ml_mfp == NULL) || !buf->b_p_bl) + continue; + + if (had_tab != 0) { + /* With the ":tab" modifier don't move the window. */ + if (buf->b_nwindows > 0) + wp = lastwin; /* buffer has a window, skip it */ + else + wp = NULL; + } else { + /* Check if this buffer already has a window */ + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp->w_buffer == buf) + break; + /* If the buffer already has a window, move it */ + if (wp != NULL) + win_move_after(wp, curwin); + } + + if (wp == NULL && split_ret == OK) { + /* Split the window and put the buffer in it */ + p_ea_save = p_ea; + p_ea = TRUE; /* use space from all windows */ + split_ret = win_split(0, WSP_ROOM | WSP_BELOW); + ++open_wins; + p_ea = p_ea_save; + if (split_ret == FAIL) + continue; + + /* Open the buffer in this window. */ +#if defined(HAS_SWAP_EXISTS_ACTION) + swap_exists_action = SEA_DIALOG; +#endif + set_curbuf(buf, DOBUF_GOTO); + if (!buf_valid(buf)) { /* autocommands deleted the buffer!!! */ +#if defined(HAS_SWAP_EXISTS_ACTION) + swap_exists_action = SEA_NONE; +# endif + break; + } +#if defined(HAS_SWAP_EXISTS_ACTION) + if (swap_exists_action == SEA_QUIT) { + cleanup_T cs; + + /* Reset the error/interrupt/exception state here so that + * aborting() returns FALSE when closing a window. */ + enter_cleanup(&cs); + + /* User selected Quit at ATTENTION prompt; close this window. */ + win_close(curwin, TRUE); + --open_wins; + swap_exists_action = SEA_NONE; + swap_exists_did_quit = TRUE; + + /* Restore the error/interrupt/exception state if not + * discarded by a new aborting error, interrupt, or uncaught + * exception. */ + leave_cleanup(&cs); + } else + handle_swap_exists(NULL); +#endif + } + + ui_breakcheck(); + if (got_int) { + (void)vgetc(); /* only break the file loading, not the rest */ + break; + } + /* Autocommands deleted the buffer or aborted script processing!!! */ + if (aborting()) + break; + /* When ":tab" was used open a new tab for a new window repeatedly. */ + if (had_tab > 0 && tabpage_index(NULL) <= p_tpm) + cmdmod.tab = 9999; + } + --autocmd_no_enter; + win_enter(firstwin, FALSE); /* back to first window */ + --autocmd_no_leave; + + /* + * Close superfluous windows. + */ + for (wp = lastwin; open_wins > count; ) { + r = (P_HID(wp->w_buffer) || !bufIsChanged(wp->w_buffer) + || autowrite(wp->w_buffer, FALSE) == OK); + if (!win_valid(wp)) { + /* BufWrite Autocommands made the window invalid, start over */ + wp = lastwin; + } else if (r) { + win_close(wp, !P_HID(wp->w_buffer)); + --open_wins; + wp = lastwin; + } else { + wp = wp->w_prev; + if (wp == NULL) + break; + } + } +} + + +static int chk_modeline __ARGS((linenr_T, int)); + +/* + * do_modelines() - process mode lines for the current file + * + * "flags" can be: + * OPT_WINONLY only set options local to window + * OPT_NOWIN don't set options local to window + * + * Returns immediately if the "ml" option isn't set. + */ +void do_modelines(flags) +int flags; +{ + linenr_T lnum; + int nmlines; + static int entered = 0; + + if (!curbuf->b_p_ml || (nmlines = (int)p_mls) == 0) + return; + + /* Disallow recursive entry here. Can happen when executing a modeline + * triggers an autocommand, which reloads modelines with a ":do". */ + if (entered) + return; + + ++entered; + for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count && lnum <= nmlines; + ++lnum) + if (chk_modeline(lnum, flags) == FAIL) + nmlines = 0; + + for (lnum = curbuf->b_ml.ml_line_count; lnum > 0 && lnum > nmlines + && lnum > curbuf->b_ml.ml_line_count - nmlines; --lnum) + if (chk_modeline(lnum, flags) == FAIL) + nmlines = 0; + --entered; +} + +#include "version.h" /* for version number */ + +/* + * chk_modeline() - check a single line for a mode string + * Return FAIL if an error encountered. + */ +static int chk_modeline(lnum, flags) +linenr_T lnum; +int flags; /* Same as for do_modelines(). */ +{ + char_u *s; + char_u *e; + char_u *linecopy; /* local copy of any modeline found */ + int prev; + int vers; + int end; + int retval = OK; + char_u *save_sourcing_name; + linenr_T save_sourcing_lnum; + scid_T save_SID; + + prev = -1; + for (s = ml_get(lnum); *s != NUL; ++s) { + if (prev == -1 || vim_isspace(prev)) { + if ((prev != -1 && STRNCMP(s, "ex:", (size_t)3) == 0) + || STRNCMP(s, "vi:", (size_t)3) == 0) + break; + /* Accept both "vim" and "Vim". */ + if ((s[0] == 'v' || s[0] == 'V') && s[1] == 'i' && s[2] == 'm') { + if (s[3] == '<' || s[3] == '=' || s[3] == '>') + e = s + 4; + else + e = s + 3; + vers = getdigits(&e); + if (*e == ':' + && (s[0] != 'V' + || STRNCMP(skipwhite(e + 1), "set", 3) == 0) + && (s[3] == ':' + || (VIM_VERSION_100 >= vers && isdigit(s[3])) + || (VIM_VERSION_100 < vers && s[3] == '<') + || (VIM_VERSION_100 > vers && s[3] == '>') + || (VIM_VERSION_100 == vers && s[3] == '='))) + break; + } + } + prev = *s; + } + + if (*s) { + do /* skip over "ex:", "vi:" or "vim:" */ + ++s; + while (s[-1] != ':'); + + s = linecopy = vim_strsave(s); /* copy the line, it will change */ + if (linecopy == NULL) + return FAIL; + + save_sourcing_lnum = sourcing_lnum; + save_sourcing_name = sourcing_name; + sourcing_lnum = lnum; /* prepare for emsg() */ + sourcing_name = (char_u *)"modelines"; + + end = FALSE; + while (end == FALSE) { + s = skipwhite(s); + if (*s == NUL) + break; + + /* + * Find end of set command: ':' or end of line. + * Skip over "\:", replacing it with ":". + */ + for (e = s; *e != ':' && *e != NUL; ++e) + if (e[0] == '\\' && e[1] == ':') + STRMOVE(e, e + 1); + if (*e == NUL) + end = TRUE; + + /* + * If there is a "set" command, require a terminating ':' and + * ignore the stuff after the ':'. + * "vi:set opt opt opt: foo" -- foo not interpreted + * "vi:opt opt opt: foo" -- foo interpreted + * Accept "se" for compatibility with Elvis. + */ + if (STRNCMP(s, "set ", (size_t)4) == 0 + || STRNCMP(s, "se ", (size_t)3) == 0) { + if (*e != ':') /* no terminating ':'? */ + break; + end = TRUE; + s = vim_strchr(s, ' ') + 1; + } + *e = NUL; /* truncate the set command */ + + if (*s != NUL) { /* skip over an empty "::" */ + save_SID = current_SID; + current_SID = SID_MODELINE; + retval = do_set(s, OPT_MODELINE | OPT_LOCAL | flags); + current_SID = save_SID; + if (retval == FAIL) /* stop if error found */ + break; + } + s = e + 1; /* advance to next part */ + } + + sourcing_lnum = save_sourcing_lnum; + sourcing_name = save_sourcing_name; + + vim_free(linecopy); + } + return retval; +} + +int read_viminfo_bufferlist(virp, writing) +vir_T *virp; +int writing; +{ + char_u *tab; + linenr_T lnum; + colnr_T col; + buf_T *buf; + char_u *sfname; + char_u *xline; + + /* Handle long line and escaped characters. */ + xline = viminfo_readstring(virp, 1, FALSE); + + /* don't read in if there are files on the command-line or if writing: */ + if (xline != NULL && !writing && ARGCOUNT == 0 + && find_viminfo_parameter('%') != NULL) { + /* Format is: Tab Tab . + * Watch out for a Tab in the file name, work from the end. */ + lnum = 0; + col = 0; + tab = vim_strrchr(xline, '\t'); + if (tab != NULL) { + *tab++ = '\0'; + col = (colnr_T)atoi((char *)tab); + tab = vim_strrchr(xline, '\t'); + if (tab != NULL) { + *tab++ = '\0'; + lnum = atol((char *)tab); + } + } + + /* Expand "~/" in the file name at "line + 1" to a full path. + * Then try shortening it by comparing with the current directory */ + expand_env(xline, NameBuff, MAXPATHL); + sfname = shorten_fname1(NameBuff); + + buf = buflist_new(NameBuff, sfname, (linenr_T)0, BLN_LISTED); + if (buf != NULL) { /* just in case... */ + buf->b_last_cursor.lnum = lnum; + buf->b_last_cursor.col = col; + buflist_setfpos(buf, curwin, lnum, col, FALSE); + } + } + vim_free(xline); + + return viminfo_readline(virp); +} + +void write_viminfo_bufferlist(fp) +FILE *fp; +{ + buf_T *buf; + win_T *win; + tabpage_T *tp; + char_u *line; + int max_buffers; + + if (find_viminfo_parameter('%') == NULL) + return; + + /* Without a number -1 is returned: do all buffers. */ + max_buffers = get_viminfo_parameter('%'); + + /* Allocate room for the file name, lnum and col. */ +#define LINE_BUF_LEN (MAXPATHL + 40) + line = alloc(LINE_BUF_LEN); + if (line == NULL) + return; + + FOR_ALL_TAB_WINDOWS(tp, win) + set_last_cursor(win); + + fputs(_("\n# Buffer list:\n"), fp); + for (buf = firstbuf; buf != NULL; buf = buf->b_next) { + if (buf->b_fname == NULL + || !buf->b_p_bl + || bt_quickfix(buf) + || removable(buf->b_ffname)) + continue; + + if (max_buffers-- == 0) + break; + putc('%', fp); + home_replace(NULL, buf->b_ffname, line, MAXPATHL, TRUE); + vim_snprintf_add((char *)line, LINE_BUF_LEN, "\t%ld\t%d", + (long)buf->b_last_cursor.lnum, + buf->b_last_cursor.col); + viminfo_writestring(fp, line); + } + vim_free(line); +} + + +/* + * Return special buffer name. + * Returns NULL when the buffer has a normal file name. + */ +char_u * buf_spname(buf) +buf_T *buf; +{ + if (bt_quickfix(buf)) { + win_T *win; + tabpage_T *tp; + + /* + * For location list window, w_llist_ref points to the location list. + * For quickfix window, w_llist_ref is NULL. + */ + if (find_win_for_buf(buf, &win, &tp) == OK && win->w_llist_ref != NULL) + return (char_u *)_(msg_loclist); + else + return (char_u *)_(msg_qflist); + } + /* There is no _file_ when 'buftype' is "nofile", b_sfname + * contains the name as specified by the user */ + if (bt_nofile(buf)) { + if (buf->b_sfname != NULL) + return buf->b_sfname; + return (char_u *)_("[Scratch]"); + } + if (buf->b_fname == NULL) + return (char_u *)_("[No Name]"); + return NULL; +} + +#if (defined(FEAT_QUICKFIX) && defined(FEAT_WINDOWS)) \ + || defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) \ + || defined(PROTO) +/* + * Find a window for buffer "buf". + * If found OK is returned and "wp" and "tp" are set to the window and tabpage. + * If not found FAIL is returned. + */ +int find_win_for_buf(buf, wp, tp) +buf_T *buf; +win_T **wp; +tabpage_T **tp; +{ + FOR_ALL_TAB_WINDOWS(*tp, *wp) + if ((*wp)->w_buffer == buf) + goto win_found; + return FAIL; +win_found: + return OK; +} +#endif + + +/* + * Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed. + */ +void set_buflisted(on) +int on; +{ + if (on != curbuf->b_p_bl) { + curbuf->b_p_bl = on; + if (on) + apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, curbuf); + else + apply_autocmds(EVENT_BUFDELETE, NULL, NULL, FALSE, curbuf); + } +} + +/* + * Read the file for "buf" again and check if the contents changed. + * Return TRUE if it changed or this could not be checked. + */ +int buf_contents_changed(buf) +buf_T *buf; +{ + buf_T *newbuf; + int differ = TRUE; + linenr_T lnum; + aco_save_T aco; + exarg_T ea; + + /* Allocate a buffer without putting it in the buffer list. */ + newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY); + if (newbuf == NULL) + return TRUE; + + /* Force the 'fileencoding' and 'fileformat' to be equal. */ + if (prep_exarg(&ea, buf) == FAIL) { + wipe_buffer(newbuf, FALSE); + return TRUE; + } + + /* set curwin/curbuf to buf and save a few things */ + aucmd_prepbuf(&aco, newbuf); + + if (ml_open(curbuf) == OK + && readfile(buf->b_ffname, buf->b_fname, + (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, + &ea, READ_NEW | READ_DUMMY) == OK) { + /* compare the two files line by line */ + if (buf->b_ml.ml_line_count == curbuf->b_ml.ml_line_count) { + differ = FALSE; + for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) + if (STRCMP(ml_get_buf(buf, lnum, FALSE), ml_get(lnum)) != 0) { + differ = TRUE; + break; + } + } + } + vim_free(ea.cmd); + + /* restore curwin/curbuf and a few other things */ + aucmd_restbuf(&aco); + + if (curbuf != newbuf) /* safety check */ + wipe_buffer(newbuf, FALSE); + + return differ; +} + +/* + * Wipe out a buffer and decrement the last buffer number if it was used for + * this buffer. Call this to wipe out a temp buffer that does not contain any + * marks. + */ +void wipe_buffer(buf, aucmd) +buf_T *buf; +int aucmd UNUSED; /* When TRUE trigger autocommands. */ +{ + if (buf->b_fnum == top_file_num - 1) + --top_file_num; + + if (!aucmd) /* Don't trigger BufDelete autocommands here. */ + block_autocmds(); + close_buffer(NULL, buf, DOBUF_WIPE, FALSE); + if (!aucmd) + unblock_autocmds(); +} diff --git a/src/charset.c b/src/charset.c new file mode 100644 index 0000000000..1eee20f66c --- /dev/null +++ b/src/charset.c @@ -0,0 +1,1653 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +#include "vim.h" + +static int win_chartabsize __ARGS((win_T *wp, char_u *p, colnr_T col)); + +# if defined(HAVE_WCHAR_H) +# include /* for towupper() and towlower() */ +# endif +static int win_nolbr_chartabsize __ARGS((win_T *wp, char_u *s, colnr_T col, + int *headp)); + +static unsigned nr2hex __ARGS((unsigned c)); + +static int chartab_initialized = FALSE; + +/* b_chartab[] is an array of 32 bytes, each bit representing one of the + * characters 0-255. */ +#define SET_CHARTAB(buf, c) (buf)->b_chartab[(unsigned)(c) >> \ + 3] |= (1 << ((c) & 0x7)) +#define RESET_CHARTAB(buf, c) (buf)->b_chartab[(unsigned)(c) >> \ + 3] &= ~(1 << ((c) & 0x7)) +#define GET_CHARTAB(buf, \ + c) ((buf)->b_chartab[(unsigned)(c) >> 3] & (1 << ((c) & 0x7))) + +/* + * Fill chartab[]. Also fills curbuf->b_chartab[] with flags for keyword + * characters for current buffer. + * + * Depends on the option settings 'iskeyword', 'isident', 'isfname', + * 'isprint' and 'encoding'. + * + * The index in chartab[] depends on 'encoding': + * - For non-multi-byte index with the byte (same as the character). + * - For DBCS index with the first byte. + * - For UTF-8 index with the character (when first byte is up to 0x80 it is + * the same as the character, if the first byte is 0x80 and above it depends + * on further bytes). + * + * The contents of chartab[]: + * - The lower two bits, masked by CT_CELL_MASK, give the number of display + * cells the character occupies (1 or 2). Not valid for UTF-8 above 0x80. + * - CT_PRINT_CHAR bit is set when the character is printable (no need to + * translate the character before displaying it). Note that only DBCS + * characters can have 2 display cells and still be printable. + * - CT_FNAME_CHAR bit is set when the character can be in a file name. + * - CT_ID_CHAR bit is set when the character can be in an identifier. + * + * Return FAIL if 'iskeyword', 'isident', 'isfname' or 'isprint' option has an + * error, OK otherwise. + */ +int init_chartab() { + return buf_init_chartab(curbuf, TRUE); +} + +int buf_init_chartab(buf, global) +buf_T *buf; +int global; /* FALSE: only set buf->b_chartab[] */ +{ + int c; + int c2; + char_u *p; + int i; + int tilde; + int do_isalpha; + + if (global) { + /* + * Set the default size for printable characters: + * From to '~' is 1 (printable), others are 2 (not printable). + * This also inits all 'isident' and 'isfname' flags to FALSE. + * + * EBCDIC: all chars below ' ' are not printable, all others are + * printable. + */ + c = 0; + while (c < ' ') + chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2; + while (c <= '~') + chartab[c++] = 1 + CT_PRINT_CHAR; + if (p_altkeymap) { + while (c < YE) + chartab[c++] = 1 + CT_PRINT_CHAR; + } + while (c < 256) { + /* UTF-8: bytes 0xa0 - 0xff are printable (latin1) */ + if (enc_utf8 && c >= 0xa0) + chartab[c++] = CT_PRINT_CHAR + 1; + /* euc-jp characters starting with 0x8e are single width */ + else if (enc_dbcs == DBCS_JPNU && c == 0x8e) + chartab[c++] = CT_PRINT_CHAR + 1; + /* other double-byte chars can be printable AND double-width */ + else if (enc_dbcs != 0 && MB_BYTE2LEN(c) == 2) + chartab[c++] = CT_PRINT_CHAR + 2; + else + /* the rest is unprintable by default */ + chartab[c++] = (dy_flags & DY_UHEX) ? 4 : 2; + } + + /* Assume that every multi-byte char is a filename character. */ + for (c = 1; c < 256; ++c) + if ((enc_dbcs != 0 && MB_BYTE2LEN(c) > 1) + || (enc_dbcs == DBCS_JPNU && c == 0x8e) + || (enc_utf8 && c >= 0xa0)) + chartab[c] |= CT_FNAME_CHAR; + } + + /* + * Init word char flags all to FALSE + */ + vim_memset(buf->b_chartab, 0, (size_t)32); + if (enc_dbcs != 0) + for (c = 0; c < 256; ++c) { + /* double-byte characters are probably word characters */ + if (MB_BYTE2LEN(c) == 2) + SET_CHARTAB(buf, c); + } + + /* + * In lisp mode the '-' character is included in keywords. + */ + if (buf->b_p_lisp) + SET_CHARTAB(buf, '-'); + + /* Walk through the 'isident', 'iskeyword', 'isfname' and 'isprint' + * options Each option is a list of characters, character numbers or + * ranges, separated by commas, e.g.: "200-210,x,#-178,-" + */ + for (i = global ? 0 : 3; i <= 3; ++i) { + if (i == 0) + p = p_isi; /* first round: 'isident' */ + else if (i == 1) + p = p_isp; /* second round: 'isprint' */ + else if (i == 2) + p = p_isf; /* third round: 'isfname' */ + else /* i == 3 */ + p = buf->b_p_isk; /* fourth round: 'iskeyword' */ + + while (*p) { + tilde = FALSE; + do_isalpha = FALSE; + if (*p == '^' && p[1] != NUL) { + tilde = TRUE; + ++p; + } + if (VIM_ISDIGIT(*p)) + c = getdigits(&p); + else if (has_mbyte) + c = mb_ptr2char_adv(&p); + else + c = *p++; + c2 = -1; + if (*p == '-' && p[1] != NUL) { + ++p; + if (VIM_ISDIGIT(*p)) + c2 = getdigits(&p); + else if (has_mbyte) + c2 = mb_ptr2char_adv(&p); + else + c2 = *p++; + } + if (c <= 0 || c >= 256 || (c2 < c && c2 != -1) || c2 >= 256 + || !(*p == NUL || *p == ',')) + return FAIL; + + if (c2 == -1) { /* not a range */ + /* + * A single '@' (not "@-@"): + * Decide on letters being ID/printable/keyword chars with + * standard function isalpha(). This takes care of locale for + * single-byte characters). + */ + if (c == '@') { + do_isalpha = TRUE; + c = 1; + c2 = 255; + } else + c2 = c; + } + while (c <= c2) { + /* Use the MB_ functions here, because isalpha() doesn't + * work properly when 'encoding' is "latin1" and the locale is + * "C". */ + if (!do_isalpha || MB_ISLOWER(c) || MB_ISUPPER(c) + || (p_altkeymap && (F_isalpha(c) || F_isdigit(c))) + ) { + if (i == 0) { /* (re)set ID flag */ + if (tilde) + chartab[c] &= ~CT_ID_CHAR; + else + chartab[c] |= CT_ID_CHAR; + } else if (i == 1) { /* (re)set printable */ + if ((c < ' ' + || c > '~' + || (p_altkeymap + && (F_isalpha(c) || F_isdigit(c))) + ) + /* For double-byte we keep the cell width, so + * that we can detect it from the first byte. */ + && !(enc_dbcs && MB_BYTE2LEN(c) == 2) + ) { + if (tilde) { + chartab[c] = (chartab[c] & ~CT_CELL_MASK) + + ((dy_flags & DY_UHEX) ? 4 : 2); + chartab[c] &= ~CT_PRINT_CHAR; + } else { + chartab[c] = (chartab[c] & ~CT_CELL_MASK) + 1; + chartab[c] |= CT_PRINT_CHAR; + } + } + } else if (i == 2) { /* (re)set fname flag */ + if (tilde) + chartab[c] &= ~CT_FNAME_CHAR; + else + chartab[c] |= CT_FNAME_CHAR; + } else { /* i == 3 */ /* (re)set keyword flag */ + if (tilde) + RESET_CHARTAB(buf, c); + else + SET_CHARTAB(buf, c); + } + } + ++c; + } + + c = *p; + p = skip_to_option_part(p); + if (c == ',' && *p == NUL) + /* Trailing comma is not allowed. */ + return FAIL; + } + } + chartab_initialized = TRUE; + return OK; +} + +/* + * Translate any special characters in buf[bufsize] in-place. + * The result is a string with only printable characters, but if there is not + * enough room, not all characters will be translated. + */ +void trans_characters(buf, bufsize) +char_u *buf; +int bufsize; +{ + int len; /* length of string needing translation */ + int room; /* room in buffer after string */ + char_u *trs; /* translated character */ + int trs_len; /* length of trs[] */ + + len = (int)STRLEN(buf); + room = bufsize - len; + while (*buf != 0) { + /* Assume a multi-byte character doesn't need translation. */ + if (has_mbyte && (trs_len = (*mb_ptr2len)(buf)) > 1) + len -= trs_len; + else { + trs = transchar_byte(*buf); + trs_len = (int)STRLEN(trs); + if (trs_len > 1) { + room -= trs_len - 1; + if (room <= 0) + return; + mch_memmove(buf + trs_len, buf + 1, (size_t)len); + } + mch_memmove(buf, trs, (size_t)trs_len); + --len; + } + buf += trs_len; + } +} + +#if defined(FEAT_EVAL) || defined(FEAT_TITLE) || defined(FEAT_INS_EXPAND) \ + || defined(PROTO) +/* + * Translate a string into allocated memory, replacing special chars with + * printable chars. Returns NULL when out of memory. + */ +char_u * transstr(s) +char_u *s; +{ + char_u *res; + char_u *p; + int l, len, c; + char_u hexbuf[11]; + + if (has_mbyte) { + /* Compute the length of the result, taking account of unprintable + * multi-byte characters. */ + len = 0; + p = s; + while (*p != NUL) { + if ((l = (*mb_ptr2len)(p)) > 1) { + c = (*mb_ptr2char)(p); + p += l; + if (vim_isprintc(c)) + len += l; + else { + transchar_hex(hexbuf, c); + len += (int)STRLEN(hexbuf); + } + } else { + l = byte2cells(*p++); + if (l > 0) + len += l; + else + len += 4; /* illegal byte sequence */ + } + } + res = alloc((unsigned)(len + 1)); + } else + res = alloc((unsigned)(vim_strsize(s) + 1)); + if (res != NULL) { + *res = NUL; + p = s; + while (*p != NUL) { + if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1) { + c = (*mb_ptr2char)(p); + if (vim_isprintc(c)) + STRNCAT(res, p, l); /* append printable multi-byte char */ + else + transchar_hex(res + STRLEN(res), c); + p += l; + } else + STRCAT(res, transchar_byte(*p++)); + } + } + return res; +} +#endif + +/* + * Convert the string "str[orglen]" to do ignore-case comparing. Uses the + * current locale. + * When "buf" is NULL returns an allocated string (NULL for out-of-memory). + * Otherwise puts the result in "buf[buflen]". + */ +char_u * str_foldcase(str, orglen, buf, buflen) +char_u *str; +int orglen; +char_u *buf; +int buflen; +{ + garray_T ga; + int i; + int len = orglen; + +#define GA_CHAR(i) ((char_u *)ga.ga_data)[i] +#define GA_PTR(i) ((char_u *)ga.ga_data + i) +#define STR_CHAR(i) (buf == NULL ? GA_CHAR(i) : buf[i]) +#define STR_PTR(i) (buf == NULL ? GA_PTR(i) : buf + i) + + /* Copy "str" into "buf" or allocated memory, unmodified. */ + if (buf == NULL) { + ga_init2(&ga, 1, 10); + if (ga_grow(&ga, len + 1) == FAIL) + return NULL; + mch_memmove(ga.ga_data, str, (size_t)len); + ga.ga_len = len; + } else { + if (len >= buflen) /* Ugly! */ + len = buflen - 1; + mch_memmove(buf, str, (size_t)len); + } + if (buf == NULL) + GA_CHAR(len) = NUL; + else + buf[len] = NUL; + + /* Make each character lower case. */ + i = 0; + while (STR_CHAR(i) != NUL) { + if (enc_utf8 || (has_mbyte && MB_BYTE2LEN(STR_CHAR(i)) > 1)) { + if (enc_utf8) { + int c = utf_ptr2char(STR_PTR(i)); + int olen = utf_ptr2len(STR_PTR(i)); + int lc = utf_tolower(c); + + /* Only replace the character when it is not an invalid + * sequence (ASCII character or more than one byte) and + * utf_tolower() doesn't return the original character. */ + if ((c < 0x80 || olen > 1) && c != lc) { + int nlen = utf_char2len(lc); + + /* If the byte length changes need to shift the following + * characters forward or backward. */ + if (olen != nlen) { + if (nlen > olen) { + if (buf == NULL + ? ga_grow(&ga, nlen - olen + 1) == FAIL + : len + nlen - olen >= buflen) { + /* out of memory, keep old char */ + lc = c; + nlen = olen; + } + } + if (olen != nlen) { + if (buf == NULL) { + STRMOVE(GA_PTR(i) + nlen, GA_PTR(i) + olen); + ga.ga_len += nlen - olen; + } else { + STRMOVE(buf + i + nlen, buf + i + olen); + len += nlen - olen; + } + } + } + (void)utf_char2bytes(lc, STR_PTR(i)); + } + } + /* skip to next multi-byte char */ + i += (*mb_ptr2len)(STR_PTR(i)); + } else { + if (buf == NULL) + GA_CHAR(i) = TOLOWER_LOC(GA_CHAR(i)); + else + buf[i] = TOLOWER_LOC(buf[i]); + ++i; + } + } + + if (buf == NULL) + return (char_u *)ga.ga_data; + return buf; +} + +/* + * Catch 22: chartab[] can't be initialized before the options are + * initialized, and initializing options may cause transchar() to be called! + * When chartab_initialized == FALSE don't use chartab[]. + * Does NOT work for multi-byte characters, c must be <= 255. + * Also doesn't work for the first byte of a multi-byte, "c" must be a + * character! + */ +static char_u transchar_buf[7]; + +char_u * transchar(c) +int c; +{ + int i; + + i = 0; + if (IS_SPECIAL(c)) { /* special key code, display as ~@ char */ + transchar_buf[0] = '~'; + transchar_buf[1] = '@'; + i = 2; + c = K_SECOND(c); + } + + if ((!chartab_initialized && ( + (c >= ' ' && c <= '~') + || F_ischar(c) + )) || (c < 256 && vim_isprintc_strict(c))) { + /* printable character */ + transchar_buf[i] = c; + transchar_buf[i + 1] = NUL; + } else + transchar_nonprint(transchar_buf + i, c); + return transchar_buf; +} + +/* + * Like transchar(), but called with a byte instead of a character. Checks + * for an illegal UTF-8 byte. + */ +char_u * transchar_byte(c) +int c; +{ + if (enc_utf8 && c >= 0x80) { + transchar_nonprint(transchar_buf, c); + return transchar_buf; + } + return transchar(c); +} + +/* + * Convert non-printable character to two or more printable characters in + * "buf[]". "buf" needs to be able to hold five bytes. + * Does NOT work for multi-byte characters, c must be <= 255. + */ +void transchar_nonprint(buf, c) +char_u *buf; +int c; +{ + if (c == NL) + c = NUL; /* we use newline in place of a NUL */ + else if (c == CAR && get_fileformat(curbuf) == EOL_MAC) + c = NL; /* we use CR in place of NL in this case */ + + if (dy_flags & DY_UHEX) /* 'display' has "uhex" */ + transchar_hex(buf, c); + + else if (c <= 0x7f) { /* 0x00 - 0x1f and 0x7f */ + buf[0] = '^'; + buf[1] = c ^ 0x40; /* DEL displayed as ^? */ + + buf[2] = NUL; + } else if (enc_utf8 && c >= 0x80) { + transchar_hex(buf, c); + } else if (c >= ' ' + 0x80 && c <= '~' + 0x80) { /* 0xa0 - 0xfe */ + buf[0] = '|'; + buf[1] = c - 0x80; + buf[2] = NUL; + } else { /* 0x80 - 0x9f and 0xff */ + /* + * TODO: EBCDIC I don't know what to do with this chars, so I display + * them as '~?' for now + */ + buf[0] = '~'; + buf[1] = (c - 0x80) ^ 0x40; /* 0xff displayed as ~? */ + buf[2] = NUL; + } +} + +void transchar_hex(buf, c) +char_u *buf; +int c; +{ + int i = 0; + + buf[0] = '<'; + if (c > 255) { + buf[++i] = nr2hex((unsigned)c >> 12); + buf[++i] = nr2hex((unsigned)c >> 8); + } + buf[++i] = nr2hex((unsigned)c >> 4); + buf[++i] = nr2hex((unsigned)c); + buf[++i] = '>'; + buf[++i] = NUL; +} + +/* + * Convert the lower 4 bits of byte "c" to its hex character. + * Lower case letters are used to avoid the confusion of being 0xf1 or + * function key 1. + */ +static unsigned nr2hex(c) +unsigned c; +{ + if ((c & 0xf) <= 9) + return (c & 0xf) + '0'; + return (c & 0xf) - 10 + 'a'; +} + +/* + * Return number of display cells occupied by byte "b". + * Caller must make sure 0 <= b <= 255. + * For multi-byte mode "b" must be the first byte of a character. + * A TAB is counted as two cells: "^I". + * For UTF-8 mode this will return 0 for bytes >= 0x80, because the number of + * cells depends on further bytes. + */ +int byte2cells(b) +int b; +{ + if (enc_utf8 && b >= 0x80) + return 0; + return chartab[b] & CT_CELL_MASK; +} + +/* + * Return number of display cells occupied by character "c". + * "c" can be a special key (negative number) in which case 3 or 4 is returned. + * A TAB is counted as two cells: "^I" or four: "<09>". + */ +int char2cells(c) +int c; +{ + if (IS_SPECIAL(c)) + return char2cells(K_SECOND(c)) + 2; + if (c >= 0x80) { + /* UTF-8: above 0x80 need to check the value */ + if (enc_utf8) + return utf_char2cells(c); + /* DBCS: double-byte means double-width, except for euc-jp with first + * byte 0x8e */ + if (enc_dbcs != 0 && c >= 0x100) { + if (enc_dbcs == DBCS_JPNU && ((unsigned)c >> 8) == 0x8e) + return 1; + return 2; + } + } + return chartab[c & 0xff] & CT_CELL_MASK; +} + +/* + * Return number of display cells occupied by character at "*p". + * A TAB is counted as two cells: "^I" or four: "<09>". + */ +int ptr2cells(p) +char_u *p; +{ + /* For UTF-8 we need to look at more bytes if the first byte is >= 0x80. */ + if (enc_utf8 && *p >= 0x80) + return utf_ptr2cells(p); + /* For DBCS we can tell the cell count from the first byte. */ + return chartab[*p] & CT_CELL_MASK; +} + +/* + * Return the number of character cells string "s" will take on the screen, + * counting TABs as two characters: "^I". + */ +int vim_strsize(s) +char_u *s; +{ + return vim_strnsize(s, (int)MAXCOL); +} + +/* + * Return the number of character cells string "s[len]" will take on the + * screen, counting TABs as two characters: "^I". + */ +int vim_strnsize(s, len) +char_u *s; +int len; +{ + int size = 0; + + while (*s != NUL && --len >= 0) { + if (has_mbyte) { + int l = (*mb_ptr2len)(s); + + size += ptr2cells(s); + s += l; + len -= l - 1; + } else + size += byte2cells(*s++); + } + return size; +} + +/* + * Return the number of characters 'c' will take on the screen, taking + * into account the size of a tab. + * Use a define to make it fast, this is used very often!!! + * Also see getvcol() below. + */ + +#define RET_WIN_BUF_CHARTABSIZE(wp, buf, p, col) \ + if (*(p) == TAB && (!(wp)->w_p_list || lcs_tab1)) \ + { \ + int ts; \ + ts = (buf)->b_p_ts; \ + return (int)(ts - (col % ts)); \ + } \ + else \ + return ptr2cells(p); + +#if defined(FEAT_VREPLACE) || defined(FEAT_EX_EXTRA) || defined(FEAT_GUI) \ + || defined(FEAT_VIRTUALEDIT) || defined(PROTO) +int chartabsize(p, col) +char_u *p; +colnr_T col; +{ + RET_WIN_BUF_CHARTABSIZE(curwin, curbuf, p, col) +} +#endif + +static int win_chartabsize(wp, p, col) +win_T *wp; +char_u *p; +colnr_T col; +{ + RET_WIN_BUF_CHARTABSIZE(wp, wp->w_buffer, p, col) +} + +/* + * Return the number of characters the string 's' will take on the screen, + * taking into account the size of a tab. + */ +int linetabsize(s) +char_u *s; +{ + return linetabsize_col(0, s); +} + +/* + * Like linetabsize(), but starting at column "startcol". + */ +int linetabsize_col(startcol, s) +int startcol; +char_u *s; +{ + colnr_T col = startcol; + + while (*s != NUL) + col += lbr_chartabsize_adv(&s, col); + return (int)col; +} + +/* + * Like linetabsize(), but for a given window instead of the current one. + */ +int win_linetabsize(wp, p, len) +win_T *wp; +char_u *p; +colnr_T len; +{ + colnr_T col = 0; + char_u *s; + + for (s = p; *s != NUL && (len == MAXCOL || s < p + len); mb_ptr_adv(s)) + col += win_lbr_chartabsize(wp, s, col, NULL); + return (int)col; +} + +/* + * Return TRUE if 'c' is a normal identifier character: + * Letters and characters from the 'isident' option. + */ +int vim_isIDc(c) +int c; +{ + return c > 0 && c < 0x100 && (chartab[c] & CT_ID_CHAR); +} + +/* + * return TRUE if 'c' is a keyword character: Letters and characters from + * 'iskeyword' option for current buffer. + * For multi-byte characters mb_get_class() is used (builtin rules). + */ +int vim_iswordc(c) +int c; +{ + return vim_iswordc_buf(c, curbuf); +} + +int vim_iswordc_buf(c, buf) +int c; +buf_T *buf; +{ + if (c >= 0x100) { + if (enc_dbcs != 0) + return dbcs_class((unsigned)c >> 8, (unsigned)(c & 0xff)) >= 2; + if (enc_utf8) + return utf_class(c) >= 2; + } + return c > 0 && c < 0x100 && GET_CHARTAB(buf, c) != 0; +} + +/* + * Just like vim_iswordc() but uses a pointer to the (multi-byte) character. + */ +int vim_iswordp(p) +char_u *p; +{ + if (has_mbyte && MB_BYTE2LEN(*p) > 1) + return mb_get_class(p) >= 2; + return GET_CHARTAB(curbuf, *p) != 0; +} + +int vim_iswordp_buf(p, buf) +char_u *p; +buf_T *buf; +{ + if (has_mbyte && MB_BYTE2LEN(*p) > 1) + return mb_get_class(p) >= 2; + return GET_CHARTAB(buf, *p) != 0; +} + +/* + * return TRUE if 'c' is a valid file-name character + * Assume characters above 0x100 are valid (multi-byte). + */ +int vim_isfilec(c) +int c; +{ + return c >= 0x100 || (c > 0 && (chartab[c] & CT_FNAME_CHAR)); +} + +/* + * return TRUE if 'c' is a valid file-name character or a wildcard character + * Assume characters above 0x100 are valid (multi-byte). + * Explicitly interpret ']' as a wildcard character as mch_has_wildcard("]") + * returns false. + */ +int vim_isfilec_or_wc(c) +int c; +{ + char_u buf[2]; + + buf[0] = (char_u)c; + buf[1] = NUL; + return vim_isfilec(c) || c == ']' || mch_has_wildcard(buf); +} + +/* + * return TRUE if 'c' is a printable character + * Assume characters above 0x100 are printable (multi-byte), except for + * Unicode. + */ +int vim_isprintc(c) +int c; +{ + if (enc_utf8 && c >= 0x100) + return utf_printable(c); + return c >= 0x100 || (c > 0 && (chartab[c] & CT_PRINT_CHAR)); +} + +/* + * Strict version of vim_isprintc(c), don't return TRUE if "c" is the head + * byte of a double-byte character. + */ +int vim_isprintc_strict(c) +int c; +{ + if (enc_dbcs != 0 && c < 0x100 && MB_BYTE2LEN(c) > 1) + return FALSE; + if (enc_utf8 && c >= 0x100) + return utf_printable(c); + return c >= 0x100 || (c > 0 && (chartab[c] & CT_PRINT_CHAR)); +} + +/* + * like chartabsize(), but also check for line breaks on the screen + */ +int lbr_chartabsize(s, col) +unsigned char *s; +colnr_T col; +{ + if (!curwin->w_p_lbr && *p_sbr == NUL) { + if (curwin->w_p_wrap) + return win_nolbr_chartabsize(curwin, s, col, NULL); + RET_WIN_BUF_CHARTABSIZE(curwin, curbuf, s, col) + } + return win_lbr_chartabsize(curwin, s, col, NULL); +} + +/* + * Call lbr_chartabsize() and advance the pointer. + */ +int lbr_chartabsize_adv(s, col) +char_u **s; +colnr_T col; +{ + int retval; + + retval = lbr_chartabsize(*s, col); + mb_ptr_adv(*s); + return retval; +} + +/* + * This function is used very often, keep it fast!!!! + * + * If "headp" not NULL, set *headp to the size of what we for 'showbreak' + * string at start of line. Warning: *headp is only set if it's a non-zero + * value, init to 0 before calling. + */ +int win_lbr_chartabsize(wp, s, col, headp) +win_T *wp; +char_u *s; +colnr_T col; +int *headp UNUSED; +{ + int c; + int size; + colnr_T col2; + colnr_T colmax; + int added; + int mb_added = 0; + int numberextra; + char_u *ps; + int tab_corr = (*s == TAB); + int n; + + /* + * No 'linebreak' and 'showbreak': return quickly. + */ + if (!wp->w_p_lbr && *p_sbr == NUL) { + if (wp->w_p_wrap) + return win_nolbr_chartabsize(wp, s, col, headp); + RET_WIN_BUF_CHARTABSIZE(wp, wp->w_buffer, s, col) + } + + /* + * First get normal size, without 'linebreak' + */ + size = win_chartabsize(wp, s, col); + c = *s; + + /* + * If 'linebreak' set check at a blank before a non-blank if the line + * needs a break here + */ + if (wp->w_p_lbr + && vim_isbreak(c) + && !vim_isbreak(s[1]) + && !wp->w_p_list + && wp->w_p_wrap + && wp->w_width != 0 + ) { + /* + * Count all characters from first non-blank after a blank up to next + * non-blank after a blank. + */ + numberextra = win_col_off(wp); + col2 = col; + colmax = (colnr_T)(W_WIDTH(wp) - numberextra); + if (col >= colmax) { + n = colmax + win_col_off2(wp); + if (n > 0) + colmax += (((col - colmax) / n) + 1) * n; + } + + for (;; ) { + ps = s; + mb_ptr_adv(s); + c = *s; + if (!(c != NUL + && (vim_isbreak(c) + || (!vim_isbreak(c) + && (col2 == col || !vim_isbreak(*ps)))))) + break; + + col2 += win_chartabsize(wp, s, col2); + if (col2 >= colmax) { /* doesn't fit */ + size = colmax - col; + tab_corr = FALSE; + break; + } + } + } else if (has_mbyte && size == 2 && MB_BYTE2LEN(*s) > 1 + && wp->w_p_wrap && in_win_border(wp, col)) { + ++size; /* Count the ">" in the last column. */ + mb_added = 1; + } + + /* + * May have to add something for 'showbreak' string at start of line + * Set *headp to the size of what we add. + */ + added = 0; + if (*p_sbr != NUL && wp->w_p_wrap && col != 0) { + numberextra = win_col_off(wp); + col += numberextra + mb_added; + if (col >= (colnr_T)W_WIDTH(wp)) { + col -= W_WIDTH(wp); + numberextra = W_WIDTH(wp) - (numberextra - win_col_off2(wp)); + if (numberextra > 0) + col = col % numberextra; + } + if (col == 0 || col + size > (colnr_T)W_WIDTH(wp)) { + added = vim_strsize(p_sbr); + if (tab_corr) + size += (added / wp->w_buffer->b_p_ts) * wp->w_buffer->b_p_ts; + else + size += added; + if (col != 0) + added = 0; + } + } + if (headp != NULL) + *headp = added + mb_added; + return size; +} + +/* + * Like win_lbr_chartabsize(), except that we know 'linebreak' is off and + * 'wrap' is on. This means we need to check for a double-byte character that + * doesn't fit at the end of the screen line. + */ +static int win_nolbr_chartabsize(wp, s, col, headp) +win_T *wp; +char_u *s; +colnr_T col; +int *headp; +{ + int n; + + if (*s == TAB && (!wp->w_p_list || lcs_tab1)) { + n = wp->w_buffer->b_p_ts; + return (int)(n - (col % n)); + } + n = ptr2cells(s); + /* Add one cell for a double-width character in the last column of the + * window, displayed with a ">". */ + if (n == 2 && MB_BYTE2LEN(*s) > 1 && in_win_border(wp, col)) { + if (headp != NULL) + *headp = 1; + return 3; + } + return n; +} + +/* + * Return TRUE if virtual column "vcol" is in the rightmost column of window + * "wp". + */ +int in_win_border(wp, vcol) +win_T *wp; +colnr_T vcol; +{ + int width1; /* width of first line (after line number) */ + int width2; /* width of further lines */ + + if (wp->w_width == 0) /* there is no border */ + return FALSE; + width1 = W_WIDTH(wp) - win_col_off(wp); + if ((int)vcol < width1 - 1) + return FALSE; + if ((int)vcol == width1 - 1) + return TRUE; + width2 = width1 + win_col_off2(wp); + if (width2 <= 0) + return FALSE; + return (vcol - width1) % width2 == width2 - 1; +} + +/* + * Get virtual column number of pos. + * start: on the first position of this character (TAB, ctrl) + * cursor: where the cursor is on this character (first char, except for TAB) + * end: on the last position of this character (TAB, ctrl) + * + * This is used very often, keep it fast! + */ +void getvcol(wp, pos, start, cursor, end) +win_T *wp; +pos_T *pos; +colnr_T *start; +colnr_T *cursor; +colnr_T *end; +{ + colnr_T vcol; + char_u *ptr; /* points to current char */ + char_u *posptr; /* points to char at pos->col */ + int incr; + int head; + int ts = wp->w_buffer->b_p_ts; + int c; + + vcol = 0; + ptr = ml_get_buf(wp->w_buffer, pos->lnum, FALSE); + if (pos->col == MAXCOL) + posptr = NULL; /* continue until the NUL */ + else + posptr = ptr + pos->col; + + /* + * This function is used very often, do some speed optimizations. + * When 'list', 'linebreak' and 'showbreak' are not set use a simple loop. + * Also use this when 'list' is set but tabs take their normal size. + */ + if ((!wp->w_p_list || lcs_tab1 != NUL) + && !wp->w_p_lbr && *p_sbr == NUL + ) { + for (;; ) { + head = 0; + c = *ptr; + /* make sure we don't go past the end of the line */ + if (c == NUL) { + incr = 1; /* NUL at end of line only takes one column */ + break; + } + /* A tab gets expanded, depending on the current column */ + if (c == TAB) + incr = ts - (vcol % ts); + else { + if (has_mbyte) { + /* For utf-8, if the byte is >= 0x80, need to look at + * further bytes to find the cell width. */ + if (enc_utf8 && c >= 0x80) + incr = utf_ptr2cells(ptr); + else + incr = CHARSIZE(c); + + /* If a double-cell char doesn't fit at the end of a line + * it wraps to the next line, it's like this char is three + * cells wide. */ + if (incr == 2 && wp->w_p_wrap && MB_BYTE2LEN(*ptr) > 1 + && in_win_border(wp, vcol)) { + ++incr; + head = 1; + } + } else + incr = CHARSIZE(c); + } + + if (posptr != NULL && ptr >= posptr) /* character at pos->col */ + break; + + vcol += incr; + mb_ptr_adv(ptr); + } + } else { + for (;; ) { + /* A tab gets expanded, depending on the current column */ + head = 0; + incr = win_lbr_chartabsize(wp, ptr, vcol, &head); + /* make sure we don't go past the end of the line */ + if (*ptr == NUL) { + incr = 1; /* NUL at end of line only takes one column */ + break; + } + + if (posptr != NULL && ptr >= posptr) /* character at pos->col */ + break; + + vcol += incr; + mb_ptr_adv(ptr); + } + } + if (start != NULL) + *start = vcol + head; + if (end != NULL) + *end = vcol + incr - 1; + if (cursor != NULL) { + if (*ptr == TAB + && (State & NORMAL) + && !wp->w_p_list + && !virtual_active() + && !(VIsual_active + && (*p_sel == 'e' || ltoreq(*pos, VIsual))) + ) + *cursor = vcol + incr - 1; /* cursor at end */ + else + *cursor = vcol + head; /* cursor at start */ + } +} + +/* + * Get virtual cursor column in the current window, pretending 'list' is off. + */ +colnr_T getvcol_nolist(posp) +pos_T *posp; +{ + int list_save = curwin->w_p_list; + colnr_T vcol; + + curwin->w_p_list = FALSE; + getvcol(curwin, posp, NULL, &vcol, NULL); + curwin->w_p_list = list_save; + return vcol; +} + +/* + * Get virtual column in virtual mode. + */ +void getvvcol(wp, pos, start, cursor, end) +win_T *wp; +pos_T *pos; +colnr_T *start; +colnr_T *cursor; +colnr_T *end; +{ + colnr_T col; + colnr_T coladd; + colnr_T endadd; + char_u *ptr; + + if (virtual_active()) { + /* For virtual mode, only want one value */ + getvcol(wp, pos, &col, NULL, NULL); + + coladd = pos->coladd; + endadd = 0; + /* Cannot put the cursor on part of a wide character. */ + ptr = ml_get_buf(wp->w_buffer, pos->lnum, FALSE); + if (pos->col < (colnr_T)STRLEN(ptr)) { + int c = (*mb_ptr2char)(ptr + pos->col); + + if (c != TAB && vim_isprintc(c)) { + endadd = (colnr_T)(char2cells(c) - 1); + if (coladd > endadd) /* past end of line */ + endadd = 0; + else + coladd = 0; + } + } + col += coladd; + if (start != NULL) + *start = col; + if (cursor != NULL) + *cursor = col; + if (end != NULL) + *end = col + endadd; + } else + getvcol(wp, pos, start, cursor, end); +} + +/* + * Get the leftmost and rightmost virtual column of pos1 and pos2. + * Used for Visual block mode. + */ +void getvcols(wp, pos1, pos2, left, right) +win_T *wp; +pos_T *pos1, *pos2; +colnr_T *left, *right; +{ + colnr_T from1, from2, to1, to2; + + if (ltp(pos1, pos2)) { + getvvcol(wp, pos1, &from1, NULL, &to1); + getvvcol(wp, pos2, &from2, NULL, &to2); + } else { + getvvcol(wp, pos2, &from1, NULL, &to1); + getvvcol(wp, pos1, &from2, NULL, &to2); + } + if (from2 < from1) + *left = from2; + else + *left = from1; + if (to2 > to1) { + if (*p_sel == 'e' && from2 - 1 >= to1) + *right = from2 - 1; + else + *right = to2; + } else + *right = to1; +} + +/* + * skipwhite: skip over ' ' and '\t'. + */ +char_u * skipwhite(q) +char_u *q; +{ + char_u *p = q; + + while (vim_iswhite(*p)) /* skip to next non-white */ + ++p; + return p; +} + +/* + * skip over digits + */ +char_u * skipdigits(q) +char_u *q; +{ + char_u *p = q; + + while (VIM_ISDIGIT(*p)) /* skip to next non-digit */ + ++p; + return p; +} + +/* + * skip over digits and hex characters + */ +char_u * skiphex(q) +char_u *q; +{ + char_u *p = q; + + while (vim_isxdigit(*p)) /* skip to next non-digit */ + ++p; + return p; +} + +/* + * skip to digit (or NUL after the string) + */ +char_u * skiptodigit(q) +char_u *q; +{ + char_u *p = q; + + while (*p != NUL && !VIM_ISDIGIT(*p)) /* skip to next digit */ + ++p; + return p; +} + +/* + * skip to hex character (or NUL after the string) + */ +char_u * skiptohex(q) +char_u *q; +{ + char_u *p = q; + + while (*p != NUL && !vim_isxdigit(*p)) /* skip to next digit */ + ++p; + return p; +} + +/* + * Variant of isdigit() that can handle characters > 0x100. + * We don't use isdigit() here, because on some systems it also considers + * superscript 1 to be a digit. + * Use the VIM_ISDIGIT() macro for simple arguments. + */ +int vim_isdigit(c) +int c; +{ + return c >= '0' && c <= '9'; +} + +/* + * Variant of isxdigit() that can handle characters > 0x100. + * We don't use isxdigit() here, because on some systems it also considers + * superscript 1 to be a digit. + */ +int vim_isxdigit(c) +int c; +{ + return (c >= '0' && c <= '9') + || (c >= 'a' && c <= 'f') + || (c >= 'A' && c <= 'F'); +} + +/* + * Vim's own character class functions. These exist because many library + * islower()/toupper() etc. do not work properly: they crash when used with + * invalid values or can't handle latin1 when the locale is C. + * Speed is most important here. + */ +#define LATIN1LOWER 'l' +#define LATIN1UPPER 'U' + +static char_u latin1flags[257] = + " UUUUUUUUUUUUUUUUUUUUUUUUUU llllllllllllllllllllllllll UUUUUUUUUUUUUUUUUUUUUUU UUUUUUUllllllllllllllllllllllll llllllll"; +static char_u latin1upper[257] = + " !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`ABCDEFGHIJKLMNOPQRSTUVWXYZ{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xd7\xd8\xd9\xda\xdb\xdc\xdd\xde\xdf\xc0\xc1\xc2\xc3\xc4\xc5\xc6\xc7\xc8\xc9\xca\xcb\xcc\xcd\xce\xcf\xd0\xd1\xd2\xd3\xd4\xd5\xd6\xf7\xd8\xd9\xda\xdb\xdc\xdd\xde\xff"; +static char_u latin1lower[257] = + " !\"#$%&'()*+,-./0123456789:;<=>?@abcdefghijklmnopqrstuvwxyz[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\x7f\x80\x81\x82\x83\x84\x85\x86\x87\x88\x89\x8a\x8b\x8c\x8d\x8e\x8f\x90\x91\x92\x93\x94\x95\x96\x97\x98\x99\x9a\x9b\x9c\x9d\x9e\x9f\xa0\xa1\xa2\xa3\xa4\xa5\xa6\xa7\xa8\xa9\xaa\xab\xac\xad\xae\xaf\xb0\xb1\xb2\xb3\xb4\xb5\xb6\xb7\xb8\xb9\xba\xbb\xbc\xbd\xbe\xbf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xd7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xdf\xe0\xe1\xe2\xe3\xe4\xe5\xe6\xe7\xe8\xe9\xea\xeb\xec\xed\xee\xef\xf0\xf1\xf2\xf3\xf4\xf5\xf6\xf7\xf8\xf9\xfa\xfb\xfc\xfd\xfe\xff"; + +int vim_islower(c) +int c; +{ + if (c <= '@') + return FALSE; + if (c >= 0x80) { + if (enc_utf8) + return utf_islower(c); + if (c >= 0x100) { +#ifdef HAVE_ISWLOWER + if (has_mbyte) + return iswlower(c); +#endif + /* islower() can't handle these chars and may crash */ + return FALSE; + } + if (enc_latin1like) + return (latin1flags[c] & LATIN1LOWER) == LATIN1LOWER; + } + return islower(c); +} + +int vim_isupper(c) +int c; +{ + if (c <= '@') + return FALSE; + if (c >= 0x80) { + if (enc_utf8) + return utf_isupper(c); + if (c >= 0x100) { +#ifdef HAVE_ISWUPPER + if (has_mbyte) + return iswupper(c); +#endif + /* islower() can't handle these chars and may crash */ + return FALSE; + } + if (enc_latin1like) + return (latin1flags[c] & LATIN1UPPER) == LATIN1UPPER; + } + return isupper(c); +} + +int vim_toupper(c) +int c; +{ + if (c <= '@') + return c; + if (c >= 0x80) { + if (enc_utf8) + return utf_toupper(c); + if (c >= 0x100) { +#ifdef HAVE_TOWUPPER + if (has_mbyte) + return towupper(c); +#endif + /* toupper() can't handle these chars and may crash */ + return c; + } + if (enc_latin1like) + return latin1upper[c]; + } + return TOUPPER_LOC(c); +} + +int vim_tolower(c) +int c; +{ + if (c <= '@') + return c; + if (c >= 0x80) { + if (enc_utf8) + return utf_tolower(c); + if (c >= 0x100) { +#ifdef HAVE_TOWLOWER + if (has_mbyte) + return towlower(c); +#endif + /* tolower() can't handle these chars and may crash */ + return c; + } + if (enc_latin1like) + return latin1lower[c]; + } + return TOLOWER_LOC(c); +} + +/* + * skiptowhite: skip over text until ' ' or '\t' or NUL. + */ +char_u * skiptowhite(p) +char_u *p; +{ + while (*p != ' ' && *p != '\t' && *p != NUL) + ++p; + return p; +} + +#if defined(FEAT_LISTCMDS) || defined(FEAT_SIGNS) || defined(FEAT_SNIFF) \ + || defined(PROTO) +/* + * skiptowhite_esc: Like skiptowhite(), but also skip escaped chars + */ +char_u * skiptowhite_esc(p) +char_u *p; +{ + while (*p != ' ' && *p != '\t' && *p != NUL) { + if ((*p == '\\' || *p == Ctrl_V) && *(p + 1) != NUL) + ++p; + ++p; + } + return p; +} +#endif + +/* + * Getdigits: Get a number from a string and skip over it. + * Note: the argument is a pointer to a char_u pointer! + */ +long getdigits(pp) +char_u **pp; +{ + char_u *p; + long retval; + + p = *pp; + retval = atol((char *)p); + if (*p == '-') /* skip negative sign */ + ++p; + p = skipdigits(p); /* skip to next non-digit */ + *pp = p; + return retval; +} + +/* + * Return TRUE if "lbuf" is empty or only contains blanks. + */ +int vim_isblankline(lbuf) +char_u *lbuf; +{ + char_u *p; + + p = skipwhite(lbuf); + return *p == NUL || *p == '\r' || *p == '\n'; +} + +/* + * Convert a string into a long and/or unsigned long, taking care of + * hexadecimal and octal numbers. Accepts a '-' sign. + * If "hexp" is not NULL, returns a flag to indicate the type of the number: + * 0 decimal + * '0' octal + * 'X' hex + * 'x' hex + * If "len" is not NULL, the length of the number in characters is returned. + * If "nptr" is not NULL, the signed result is returned in it. + * If "unptr" is not NULL, the unsigned result is returned in it. + * If "dooct" is non-zero recognize octal numbers, when > 1 always assume + * octal number. + * If "dohex" is non-zero recognize hex numbers, when > 1 always assume + * hex number. + */ +void vim_str2nr(start, hexp, len, dooct, dohex, nptr, unptr) +char_u *start; +int *hexp; /* return: type of number 0 = decimal, 'x' + or 'X' is hex, '0' = octal */ +int *len; /* return: detected length of number */ +int dooct; /* recognize octal number */ +int dohex; /* recognize hex number */ +long *nptr; /* return: signed result */ +unsigned long *unptr; /* return: unsigned result */ +{ + char_u *ptr = start; + int hex = 0; /* default is decimal */ + int negative = FALSE; + unsigned long un = 0; + int n; + + if (ptr[0] == '-') { + negative = TRUE; + ++ptr; + } + + /* Recognize hex and octal. */ + if (ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { + hex = ptr[1]; + if (dohex && (hex == 'X' || hex == 'x') && vim_isxdigit(ptr[2])) + ptr += 2; /* hexadecimal */ + else { + hex = 0; /* default is decimal */ + if (dooct) { + /* Don't interpret "0", "08" or "0129" as octal. */ + for (n = 1; VIM_ISDIGIT(ptr[n]); ++n) { + if (ptr[n] > '7') { + hex = 0; /* can't be octal */ + break; + } + if (ptr[n] >= '0') + hex = '0'; /* assume octal */ + } + } + } + } + + /* + * Do the string-to-numeric conversion "manually" to avoid sscanf quirks. + */ + if (hex == '0' || dooct > 1) { + /* octal */ + while ('0' <= *ptr && *ptr <= '7') { + un = 8 * un + (unsigned long)(*ptr - '0'); + ++ptr; + } + } else if (hex != 0 || dohex > 1) { + /* hex */ + while (vim_isxdigit(*ptr)) { + un = 16 * un + (unsigned long)hex2nr(*ptr); + ++ptr; + } + } else { + /* decimal */ + while (VIM_ISDIGIT(*ptr)) { + un = 10 * un + (unsigned long)(*ptr - '0'); + ++ptr; + } + } + + if (hexp != NULL) + *hexp = hex; + if (len != NULL) + *len = (int)(ptr - start); + if (nptr != NULL) { + if (negative) /* account for leading '-' for decimal numbers */ + *nptr = -(long)un; + else + *nptr = (long)un; + } + if (unptr != NULL) + *unptr = un; +} + +/* + * Return the value of a single hex character. + * Only valid when the argument is '0' - '9', 'A' - 'F' or 'a' - 'f'. + */ +int hex2nr(c) +int c; +{ + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + return c - '0'; +} + +#if defined(FEAT_TERMRESPONSE) \ + || (defined(FEAT_GUI_GTK) && defined(FEAT_WINDOWS)) || defined(PROTO) +/* + * Convert two hex characters to a byte. + * Return -1 if one of the characters is not hex. + */ +int hexhex2nr(p) +char_u *p; +{ + if (!vim_isxdigit(p[0]) || !vim_isxdigit(p[1])) + return -1; + return (hex2nr(p[0]) << 4) + hex2nr(p[1]); +} +#endif + +/* + * Return TRUE if "str" starts with a backslash that should be removed. + * For MS-DOS, WIN32 and OS/2 this is only done when the character after the + * backslash is not a normal file name character. + * '$' is a valid file name character, we don't remove the backslash before + * it. This means it is not possible to use an environment variable after a + * backslash. "C:\$VIM\doc" is taken literally, only "$VIM\doc" works. + * Although "\ name" is valid, the backslash in "Program\ files" must be + * removed. Assume a file name doesn't start with a space. + * For multi-byte names, never remove a backslash before a non-ascii + * character, assume that all multi-byte characters are valid file name + * characters. + */ +int rem_backslash(str) +char_u *str; +{ +#ifdef BACKSLASH_IN_FILENAME + return str[0] == '\\' + && str[1] < 0x80 + && (str[1] == ' ' + || (str[1] != NUL + && str[1] != '*' + && str[1] != '?' + && !vim_isfilec(str[1]))); +#else + return str[0] == '\\' && str[1] != NUL; +#endif +} + +/* + * Halve the number of backslashes in a file name argument. + * For MS-DOS we only do this if the character after the backslash + * is not a normal file character. + */ +void backslash_halve(p) +char_u *p; +{ + for (; *p; ++p) + if (rem_backslash(p)) + STRMOVE(p, p + 1); +} + +/* + * backslash_halve() plus save the result in allocated memory. + */ +char_u * backslash_halve_save(p) +char_u *p; +{ + char_u *res; + + res = vim_strsave(p); + if (res == NULL) + return p; + backslash_halve(res); + return res; +} + diff --git a/src/diff.c b/src/diff.c new file mode 100644 index 0000000000..ad31128575 --- /dev/null +++ b/src/diff.c @@ -0,0 +1,2296 @@ +/* vim:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * diff.c: code for diff'ing two, three or four buffers. + */ + +#include "vim.h" + + +static int diff_busy = FALSE; /* ex_diffgetput() is busy */ + +/* flags obtained from the 'diffopt' option */ +#define DIFF_FILLER 1 /* display filler lines */ +#define DIFF_ICASE 2 /* ignore case */ +#define DIFF_IWHITE 4 /* ignore change in white space */ +#define DIFF_HORIZONTAL 8 /* horizontal splits */ +#define DIFF_VERTICAL 16 /* vertical splits */ +static int diff_flags = DIFF_FILLER; + +#define LBUFLEN 50 /* length of line in diff file */ + +static int diff_a_works = MAYBE; /* TRUE when "diff -a" works, FALSE when it + doesn't work, MAYBE when not checked yet */ + +static int diff_buf_idx __ARGS((buf_T *buf)); +static int diff_buf_idx_tp __ARGS((buf_T *buf, tabpage_T *tp)); +static void diff_mark_adjust_tp __ARGS((tabpage_T *tp, int idx, linenr_T line1, + linenr_T line2, long amount, + long amount_after)); +static void diff_check_unchanged __ARGS((tabpage_T *tp, diff_T *dp)); +static int diff_check_sanity __ARGS((tabpage_T *tp, diff_T *dp)); +static void diff_redraw __ARGS((int dofold)); +static int diff_write __ARGS((buf_T *buf, char_u *fname)); +static void diff_file __ARGS((char_u *tmp_orig, char_u *tmp_new, + char_u *tmp_diff)); +static int diff_equal_entry __ARGS((diff_T *dp, int idx1, int idx2)); +static int diff_cmp __ARGS((char_u *s1, char_u *s2)); +static void diff_fold_update __ARGS((diff_T *dp, int skip_idx)); +static void diff_read __ARGS((int idx_orig, int idx_new, char_u *fname)); +static void diff_copy_entry __ARGS((diff_T *dprev, diff_T *dp, int idx_orig, + int idx_new)); +static diff_T *diff_alloc_new __ARGS((tabpage_T *tp, diff_T *dprev, diff_T *dp)); + +#ifndef USE_CR +# define tag_fgets vim_fgets +#endif + +/* + * Called when deleting or unloading a buffer: No longer make a diff with it. + */ +void diff_buf_delete(buf) +buf_T *buf; +{ + int i; + tabpage_T *tp; + + for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) { + i = diff_buf_idx_tp(buf, tp); + if (i != DB_COUNT) { + tp->tp_diffbuf[i] = NULL; + tp->tp_diff_invalid = TRUE; + if (tp == curtab) + diff_redraw(TRUE); + } + } +} + +/* + * Check if the current buffer should be added to or removed from the list of + * diff buffers. + */ +void diff_buf_adjust(win) +win_T *win; +{ + win_T *wp; + int i; + + if (!win->w_p_diff) { + /* When there is no window showing a diff for this buffer, remove + * it from the diffs. */ + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp->w_buffer == win->w_buffer && wp->w_p_diff) + break; + if (wp == NULL) { + i = diff_buf_idx(win->w_buffer); + if (i != DB_COUNT) { + curtab->tp_diffbuf[i] = NULL; + curtab->tp_diff_invalid = TRUE; + diff_redraw(TRUE); + } + } + } else + diff_buf_add(win->w_buffer); +} + +/* + * Add a buffer to make diffs for. + * Call this when a new buffer is being edited in the current window where + * 'diff' is set. + * Marks the current buffer as being part of the diff and requiring updating. + * This must be done before any autocmd, because a command may use info + * about the screen contents. + */ +void diff_buf_add(buf) +buf_T *buf; +{ + int i; + + if (diff_buf_idx(buf) != DB_COUNT) + return; /* It's already there. */ + + for (i = 0; i < DB_COUNT; ++i) + if (curtab->tp_diffbuf[i] == NULL) { + curtab->tp_diffbuf[i] = buf; + curtab->tp_diff_invalid = TRUE; + diff_redraw(TRUE); + return; + } + + EMSGN(_("E96: Can not diff more than %ld buffers"), DB_COUNT); +} + +/* + * Find buffer "buf" in the list of diff buffers for the current tab page. + * Return its index or DB_COUNT if not found. + */ +static int diff_buf_idx(buf) +buf_T *buf; +{ + int idx; + + for (idx = 0; idx < DB_COUNT; ++idx) + if (curtab->tp_diffbuf[idx] == buf) + break; + return idx; +} + +/* + * Find buffer "buf" in the list of diff buffers for tab page "tp". + * Return its index or DB_COUNT if not found. + */ +static int diff_buf_idx_tp(buf, tp) +buf_T *buf; +tabpage_T *tp; +{ + int idx; + + for (idx = 0; idx < DB_COUNT; ++idx) + if (tp->tp_diffbuf[idx] == buf) + break; + return idx; +} + +/* + * Mark the diff info involving buffer "buf" as invalid, it will be updated + * when info is requested. + */ +void diff_invalidate(buf) +buf_T *buf; +{ + tabpage_T *tp; + int i; + + for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) { + i = diff_buf_idx_tp(buf, tp); + if (i != DB_COUNT) { + tp->tp_diff_invalid = TRUE; + if (tp == curtab) + diff_redraw(TRUE); + } + } +} + +/* + * Called by mark_adjust(): update line numbers in "curbuf". + */ +void diff_mark_adjust(line1, line2, amount, amount_after) +linenr_T line1; +linenr_T line2; +long amount; +long amount_after; +{ + int idx; + tabpage_T *tp; + + /* Handle all tab pages that use the current buffer in a diff. */ + for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) { + idx = diff_buf_idx_tp(curbuf, tp); + if (idx != DB_COUNT) + diff_mark_adjust_tp(tp, idx, line1, line2, amount, amount_after); + } +} + +/* + * Update line numbers in tab page "tp" for "curbuf" with index "idx". + * This attempts to update the changes as much as possible: + * When inserting/deleting lines outside of existing change blocks, create a + * new change block and update the line numbers in following blocks. + * When inserting/deleting lines in existing change blocks, update them. + */ +static void diff_mark_adjust_tp(tp, idx, line1, line2, amount, amount_after) +tabpage_T *tp; +int idx; +linenr_T line1; +linenr_T line2; +long amount; +long amount_after; +{ + diff_T *dp; + diff_T *dprev; + diff_T *dnext; + int i; + int inserted, deleted; + int n, off; + linenr_T last; + linenr_T lnum_deleted = line1; /* lnum of remaining deletion */ + int check_unchanged; + + if (line2 == MAXLNUM) { + /* mark_adjust(99, MAXLNUM, 9, 0): insert lines */ + inserted = amount; + deleted = 0; + } else if (amount_after > 0) { + /* mark_adjust(99, 98, MAXLNUM, 9): a change that inserts lines*/ + inserted = amount_after; + deleted = 0; + } else { + /* mark_adjust(98, 99, MAXLNUM, -2): delete lines */ + inserted = 0; + deleted = -amount_after; + } + + dprev = NULL; + dp = tp->tp_first_diff; + for (;; ) { + /* If the change is after the previous diff block and before the next + * diff block, thus not touching an existing change, create a new diff + * block. Don't do this when ex_diffgetput() is busy. */ + if ((dp == NULL || dp->df_lnum[idx] - 1 > line2 + || (line2 == MAXLNUM && dp->df_lnum[idx] > line1)) + && (dprev == NULL + || dprev->df_lnum[idx] + dprev->df_count[idx] < line1) + && !diff_busy) { + dnext = diff_alloc_new(tp, dprev, dp); + if (dnext == NULL) + return; + + dnext->df_lnum[idx] = line1; + dnext->df_count[idx] = inserted; + for (i = 0; i < DB_COUNT; ++i) + if (tp->tp_diffbuf[i] != NULL && i != idx) { + if (dprev == NULL) + dnext->df_lnum[i] = line1; + else + dnext->df_lnum[i] = line1 + + (dprev->df_lnum[i] + dprev->df_count[i]) + - (dprev->df_lnum[idx] + dprev->df_count[idx]); + dnext->df_count[i] = deleted; + } + } + + /* if at end of the list, quit */ + if (dp == NULL) + break; + + /* + * Check for these situations: + * 1 2 3 + * 1 2 3 + * line1 2 3 4 5 + * 2 3 4 5 + * 2 3 4 5 + * line2 2 3 4 5 + * 3 5 6 + * 3 5 6 + */ + /* compute last line of this change */ + last = dp->df_lnum[idx] + dp->df_count[idx] - 1; + + /* 1. change completely above line1: nothing to do */ + if (last >= line1 - 1) { + /* 6. change below line2: only adjust for amount_after; also when + * "deleted" became zero when deleted all lines between two diffs */ + if (dp->df_lnum[idx] - (deleted + inserted != 0) > line2) { + if (amount_after == 0) + break; /* nothing left to change */ + dp->df_lnum[idx] += amount_after; + } else { + check_unchanged = FALSE; + + /* 2. 3. 4. 5.: inserted/deleted lines touching this diff. */ + if (deleted > 0) { + if (dp->df_lnum[idx] >= line1) { + off = dp->df_lnum[idx] - lnum_deleted; + if (last <= line2) { + /* 4. delete all lines of diff */ + if (dp->df_next != NULL + && dp->df_next->df_lnum[idx] - 1 <= line2) { + /* delete continues in next diff, only do + * lines until that one */ + n = dp->df_next->df_lnum[idx] - lnum_deleted; + deleted -= n; + n -= dp->df_count[idx]; + lnum_deleted = dp->df_next->df_lnum[idx]; + } else + n = deleted - dp->df_count[idx]; + dp->df_count[idx] = 0; + } else { + /* 5. delete lines at or just before top of diff */ + n = off; + dp->df_count[idx] -= line2 - dp->df_lnum[idx] + 1; + check_unchanged = TRUE; + } + dp->df_lnum[idx] = line1; + } else { + off = 0; + if (last < line2) { + /* 2. delete at end of of diff */ + dp->df_count[idx] -= last - lnum_deleted + 1; + if (dp->df_next != NULL + && dp->df_next->df_lnum[idx] - 1 <= line2) { + /* delete continues in next diff, only do + * lines until that one */ + n = dp->df_next->df_lnum[idx] - 1 - last; + deleted -= dp->df_next->df_lnum[idx] + - lnum_deleted; + lnum_deleted = dp->df_next->df_lnum[idx]; + } else + n = line2 - last; + check_unchanged = TRUE; + } else { + /* 3. delete lines inside the diff */ + n = 0; + dp->df_count[idx] -= deleted; + } + } + + for (i = 0; i < DB_COUNT; ++i) + if (tp->tp_diffbuf[i] != NULL && i != idx) { + dp->df_lnum[i] -= off; + dp->df_count[i] += n; + } + } else { + if (dp->df_lnum[idx] <= line1) { + /* inserted lines somewhere in this diff */ + dp->df_count[idx] += inserted; + check_unchanged = TRUE; + } else + /* inserted lines somewhere above this diff */ + dp->df_lnum[idx] += inserted; + } + + if (check_unchanged) + /* Check if inserted lines are equal, may reduce the + * size of the diff. TODO: also check for equal lines + * in the middle and perhaps split the block. */ + diff_check_unchanged(tp, dp); + } + } + + /* check if this block touches the previous one, may merge them. */ + if (dprev != NULL && dprev->df_lnum[idx] + dprev->df_count[idx] + == dp->df_lnum[idx]) { + for (i = 0; i < DB_COUNT; ++i) + if (tp->tp_diffbuf[i] != NULL) + dprev->df_count[i] += dp->df_count[i]; + dprev->df_next = dp->df_next; + vim_free(dp); + dp = dprev->df_next; + } else { + /* Advance to next entry. */ + dprev = dp; + dp = dp->df_next; + } + } + + dprev = NULL; + dp = tp->tp_first_diff; + while (dp != NULL) { + /* All counts are zero, remove this entry. */ + for (i = 0; i < DB_COUNT; ++i) + if (tp->tp_diffbuf[i] != NULL && dp->df_count[i] != 0) + break; + if (i == DB_COUNT) { + dnext = dp->df_next; + vim_free(dp); + dp = dnext; + if (dprev == NULL) + tp->tp_first_diff = dnext; + else + dprev->df_next = dnext; + } else { + /* Advance to next entry. */ + dprev = dp; + dp = dp->df_next; + } + + } + + if (tp == curtab) { + diff_redraw(TRUE); + + /* Need to recompute the scroll binding, may remove or add filler + * lines (e.g., when adding lines above w_topline). But it's slow when + * making many changes, postpone until redrawing. */ + diff_need_scrollbind = TRUE; + } +} + +/* + * Allocate a new diff block and link it between "dprev" and "dp". + */ +static diff_T * diff_alloc_new(tp, dprev, dp) +tabpage_T *tp; +diff_T *dprev; +diff_T *dp; +{ + diff_T *dnew; + + dnew = (diff_T *)alloc((unsigned)sizeof(diff_T)); + if (dnew != NULL) { + dnew->df_next = dp; + if (dprev == NULL) + tp->tp_first_diff = dnew; + else + dprev->df_next = dnew; + } + return dnew; +} + +/* + * Check if the diff block "dp" can be made smaller for lines at the start and + * end that are equal. Called after inserting lines. + * This may result in a change where all buffers have zero lines, the caller + * must take care of removing it. + */ +static void diff_check_unchanged(tp, dp) +tabpage_T *tp; +diff_T *dp; +{ + int i_org; + int i_new; + int off_org, off_new; + char_u *line_org; + int dir = FORWARD; + + /* Find the first buffers, use it as the original, compare the other + * buffer lines against this one. */ + for (i_org = 0; i_org < DB_COUNT; ++i_org) + if (tp->tp_diffbuf[i_org] != NULL) + break; + if (i_org == DB_COUNT) /* safety check */ + return; + + if (diff_check_sanity(tp, dp) == FAIL) + return; + + /* First check lines at the top, then at the bottom. */ + off_org = 0; + off_new = 0; + for (;; ) { + /* Repeat until a line is found which is different or the number of + * lines has become zero. */ + while (dp->df_count[i_org] > 0) { + /* Copy the line, the next ml_get() will invalidate it. */ + if (dir == BACKWARD) + off_org = dp->df_count[i_org] - 1; + line_org = vim_strsave(ml_get_buf(tp->tp_diffbuf[i_org], + dp->df_lnum[i_org] + off_org, FALSE)); + if (line_org == NULL) + return; + for (i_new = i_org + 1; i_new < DB_COUNT; ++i_new) { + if (tp->tp_diffbuf[i_new] == NULL) + continue; + if (dir == BACKWARD) + off_new = dp->df_count[i_new] - 1; + /* if other buffer doesn't have this line, it was inserted */ + if (off_new < 0 || off_new >= dp->df_count[i_new]) + break; + if (diff_cmp(line_org, ml_get_buf(tp->tp_diffbuf[i_new], + dp->df_lnum[i_new] + off_new, FALSE)) != 0) + break; + } + vim_free(line_org); + + /* Stop when a line isn't equal in all diff buffers. */ + if (i_new != DB_COUNT) + break; + + /* Line matched in all buffers, remove it from the diff. */ + for (i_new = i_org; i_new < DB_COUNT; ++i_new) + if (tp->tp_diffbuf[i_new] != NULL) { + if (dir == FORWARD) + ++dp->df_lnum[i_new]; + --dp->df_count[i_new]; + } + } + if (dir == BACKWARD) + break; + dir = BACKWARD; + } +} + +/* + * Check if a diff block doesn't contain invalid line numbers. + * This can happen when the diff program returns invalid results. + */ +static int diff_check_sanity(tp, dp) +tabpage_T *tp; +diff_T *dp; +{ + int i; + + for (i = 0; i < DB_COUNT; ++i) + if (tp->tp_diffbuf[i] != NULL) + if (dp->df_lnum[i] + dp->df_count[i] - 1 + > tp->tp_diffbuf[i]->b_ml.ml_line_count) + return FAIL; + return OK; +} + +/* + * Mark all diff buffers in the current tab page for redraw. + */ +static void diff_redraw(dofold) +int dofold; /* also recompute the folds */ +{ + win_T *wp; + int n; + + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp->w_p_diff) { + redraw_win_later(wp, SOME_VALID); + if (dofold && foldmethodIsDiff(wp)) + foldUpdateAll(wp); + /* A change may have made filler lines invalid, need to take care + * of that for other windows. */ + n = diff_check(wp, wp->w_topline); + if ((wp != curwin && wp->w_topfill > 0) || n > 0) { + if (wp->w_topfill > n) + wp->w_topfill = (n < 0 ? 0 : n); + else if (n > 0 && n > wp->w_topfill) + wp->w_topfill = n; + } + } +} + +/* + * Write buffer "buf" to file "name". + * Always use 'fileformat' set to "unix". + * Return FAIL for failure + */ +static int diff_write(buf, fname) +buf_T *buf; +char_u *fname; +{ + int r; + char_u *save_ff; + + save_ff = buf->b_p_ff; + buf->b_p_ff = vim_strsave((char_u *)FF_UNIX); + r = buf_write(buf, fname, NULL, (linenr_T)1, buf->b_ml.ml_line_count, + NULL, FALSE, FALSE, FALSE, TRUE); + free_string_option(buf->b_p_ff); + buf->b_p_ff = save_ff; + return r; +} + +/* + * Completely update the diffs for the buffers involved. + * This uses the ordinary "diff" command. + * The buffers are written to a file, also for unmodified buffers (the file + * could have been produced by autocommands, e.g. the netrw plugin). + */ +void ex_diffupdate(eap) +exarg_T *eap UNUSED; /* can be NULL */ +{ + buf_T *buf; + int idx_orig; + int idx_new; + char_u *tmp_orig; + char_u *tmp_new; + char_u *tmp_diff; + FILE *fd; + int ok; + int io_error = FALSE; + + /* Delete all diffblocks. */ + diff_clear(curtab); + curtab->tp_diff_invalid = FALSE; + + /* Use the first buffer as the original text. */ + for (idx_orig = 0; idx_orig < DB_COUNT; ++idx_orig) + if (curtab->tp_diffbuf[idx_orig] != NULL) + break; + if (idx_orig == DB_COUNT) + return; + + /* Only need to do something when there is another buffer. */ + for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new) + if (curtab->tp_diffbuf[idx_new] != NULL) + break; + if (idx_new == DB_COUNT) + return; + + /* We need three temp file names. */ + tmp_orig = vim_tempname('o'); + tmp_new = vim_tempname('n'); + tmp_diff = vim_tempname('d'); + if (tmp_orig == NULL || tmp_new == NULL || tmp_diff == NULL) + goto theend; + + /* + * Do a quick test if "diff" really works. Otherwise it looks like there + * are no differences. Can't use the return value, it's non-zero when + * there are differences. + * May try twice, first with "-a" and then without. + */ + for (;; ) { + ok = FALSE; + fd = mch_fopen((char *)tmp_orig, "w"); + if (fd == NULL) + io_error = TRUE; + else { + if (fwrite("line1\n", (size_t)6, (size_t)1, fd) != 1) + io_error = TRUE; + fclose(fd); + fd = mch_fopen((char *)tmp_new, "w"); + if (fd == NULL) + io_error = TRUE; + else { + if (fwrite("line2\n", (size_t)6, (size_t)1, fd) != 1) + io_error = TRUE; + fclose(fd); + diff_file(tmp_orig, tmp_new, tmp_diff); + fd = mch_fopen((char *)tmp_diff, "r"); + if (fd == NULL) + io_error = TRUE; + else { + char_u linebuf[LBUFLEN]; + + for (;; ) { + /* There must be a line that contains "1c1". */ + if (tag_fgets(linebuf, LBUFLEN, fd)) + break; + if (STRNCMP(linebuf, "1c1", 3) == 0) + ok = TRUE; + } + fclose(fd); + } + mch_remove(tmp_diff); + mch_remove(tmp_new); + } + mch_remove(tmp_orig); + } + + /* When using 'diffexpr' break here. */ + if (*p_dex != NUL) + break; + + + /* If we checked if "-a" works already, break here. */ + if (diff_a_works != MAYBE) + break; + diff_a_works = ok; + + /* If "-a" works break here, otherwise retry without "-a". */ + if (ok) + break; + } + if (!ok) { + if (io_error) + EMSG(_("E810: Cannot read or write temp files")); + EMSG(_("E97: Cannot create diffs")); + diff_a_works = MAYBE; + goto theend; + } + + /* :diffupdate! */ + if (eap != NULL && eap->forceit) + for (idx_new = idx_orig; idx_new < DB_COUNT; ++idx_new) { + buf = curtab->tp_diffbuf[idx_new]; + if (buf_valid(buf)) + buf_check_timestamp(buf, FALSE); + } + + /* Write the first buffer to a tempfile. */ + buf = curtab->tp_diffbuf[idx_orig]; + if (diff_write(buf, tmp_orig) == FAIL) + goto theend; + + /* Make a difference between the first buffer and every other. */ + for (idx_new = idx_orig + 1; idx_new < DB_COUNT; ++idx_new) { + buf = curtab->tp_diffbuf[idx_new]; + if (buf == NULL) + continue; + if (diff_write(buf, tmp_new) == FAIL) + continue; + diff_file(tmp_orig, tmp_new, tmp_diff); + + /* Read the diff output and add each entry to the diff list. */ + diff_read(idx_orig, idx_new, tmp_diff); + mch_remove(tmp_diff); + mch_remove(tmp_new); + } + mch_remove(tmp_orig); + + /* force updating cursor position on screen */ + curwin->w_valid_cursor.lnum = 0; + + diff_redraw(TRUE); + +theend: + vim_free(tmp_orig); + vim_free(tmp_new); + vim_free(tmp_diff); +} + +/* + * Make a diff between files "tmp_orig" and "tmp_new", results in "tmp_diff". + */ +static void diff_file(tmp_orig, tmp_new, tmp_diff) +char_u *tmp_orig; +char_u *tmp_new; +char_u *tmp_diff; +{ + char_u *cmd; + size_t len; + + if (*p_dex != NUL) + /* Use 'diffexpr' to generate the diff file. */ + eval_diff(tmp_orig, tmp_new, tmp_diff); + else { + len = STRLEN(tmp_orig) + STRLEN(tmp_new) + + STRLEN(tmp_diff) + STRLEN(p_srr) + 27; + cmd = alloc((unsigned)len); + if (cmd != NULL) { + /* We don't want $DIFF_OPTIONS to get in the way. */ + if (getenv("DIFF_OPTIONS")) + vim_setenv((char_u *)"DIFF_OPTIONS", (char_u *)""); + + /* Build the diff command and execute it. Always use -a, binary + * differences are of no use. Ignore errors, diff returns + * non-zero when differences have been found. */ + vim_snprintf((char *)cmd, len, "diff %s%s%s%s%s %s", + diff_a_works == FALSE ? "" : "-a ", + "", + (diff_flags & DIFF_IWHITE) ? "-b " : "", + (diff_flags & DIFF_ICASE) ? "-i " : "", + tmp_orig, tmp_new); + append_redir(cmd, (int)len, p_srr, tmp_diff); + block_autocmds(); /* Avoid ShellCmdPost stuff */ + (void)call_shell(cmd, SHELL_FILTER|SHELL_SILENT|SHELL_DOOUT); + unblock_autocmds(); + vim_free(cmd); + } + } +} + +/* + * Create a new version of a file from the current buffer and a diff file. + * The buffer is written to a file, also for unmodified buffers (the file + * could have been produced by autocommands, e.g. the netrw plugin). + */ +void ex_diffpatch(eap) +exarg_T *eap; +{ + char_u *tmp_orig; /* name of original temp file */ + char_u *tmp_new; /* name of patched temp file */ + char_u *buf = NULL; + size_t buflen; + win_T *old_curwin = curwin; + char_u *newname = NULL; /* name of patched file buffer */ +#ifdef UNIX + char_u dirbuf[MAXPATHL]; + char_u *fullname = NULL; +#endif + struct stat st; + + + /* We need two temp file names. */ + tmp_orig = vim_tempname('o'); + tmp_new = vim_tempname('n'); + if (tmp_orig == NULL || tmp_new == NULL) + goto theend; + + /* Write the current buffer to "tmp_orig". */ + if (buf_write(curbuf, tmp_orig, NULL, + (linenr_T)1, curbuf->b_ml.ml_line_count, + NULL, FALSE, FALSE, FALSE, TRUE) == FAIL) + goto theend; + +#ifdef UNIX + /* Get the absolute path of the patchfile, changing directory below. */ + fullname = FullName_save(eap->arg, FALSE); +#endif + buflen = STRLEN(tmp_orig) + ( +# ifdef UNIX + fullname != NULL ? STRLEN(fullname) : +# endif + STRLEN(eap->arg)) + STRLEN(tmp_new) + 16; + buf = alloc((unsigned)buflen); + if (buf == NULL) + goto theend; + +#ifdef UNIX + /* Temporarily chdir to /tmp, to avoid patching files in the current + * directory when the patch file contains more than one patch. When we + * have our own temp dir use that instead, it will be cleaned up when we + * exit (any .rej files created). Don't change directory if we can't + * return to the current. */ + if (mch_dirname(dirbuf, MAXPATHL) != OK || mch_chdir((char *)dirbuf) != 0) + dirbuf[0] = NUL; + else { +# ifdef TEMPDIRNAMES + if (vim_tempdir != NULL) + ignored = mch_chdir((char *)vim_tempdir); + else +# endif + ignored = mch_chdir("/tmp"); + shorten_fnames(TRUE); + } +#endif + + if (*p_pex != NUL) + /* Use 'patchexpr' to generate the new file. */ + eval_patch(tmp_orig, +# ifdef UNIX + fullname != NULL ? fullname : +# endif + eap->arg, tmp_new); + else { + /* Build the patch command and execute it. Ignore errors. Switch to + * cooked mode to allow the user to respond to prompts. */ + vim_snprintf((char *)buf, buflen, "patch -o %s %s < \"%s\"", + tmp_new, tmp_orig, +# ifdef UNIX + fullname != NULL ? fullname : +# endif + eap->arg); + block_autocmds(); /* Avoid ShellCmdPost stuff */ + (void)call_shell(buf, SHELL_FILTER | SHELL_COOKED); + unblock_autocmds(); + } + +#ifdef UNIX + if (dirbuf[0] != NUL) { + if (mch_chdir((char *)dirbuf) != 0) + EMSG(_(e_prev_dir)); + shorten_fnames(TRUE); + } +#endif + + /* patch probably has written over the screen */ + redraw_later(CLEAR); + + /* Delete any .orig or .rej file created. */ + STRCPY(buf, tmp_new); + STRCAT(buf, ".orig"); + mch_remove(buf); + STRCPY(buf, tmp_new); + STRCAT(buf, ".rej"); + mch_remove(buf); + + /* Only continue if the output file was created. */ + if (mch_stat((char *)tmp_new, &st) < 0 || st.st_size == 0) + EMSG(_("E816: Cannot read patch output")); + else { + if (curbuf->b_fname != NULL) { + newname = vim_strnsave(curbuf->b_fname, + (int)(STRLEN(curbuf->b_fname) + 4)); + if (newname != NULL) + STRCAT(newname, ".new"); + } + + /* don't use a new tab page, each tab page has its own diffs */ + cmdmod.tab = 0; + + if (win_split(0, (diff_flags & DIFF_VERTICAL) ? WSP_VERT : 0) != FAIL) { + /* Pretend it was a ":split fname" command */ + eap->cmdidx = CMD_split; + eap->arg = tmp_new; + do_exedit(eap, old_curwin); + + /* check that split worked and editing tmp_new */ + if (curwin != old_curwin && win_valid(old_curwin)) { + /* Set 'diff', 'scrollbind' on and 'wrap' off. */ + diff_win_options(curwin, TRUE); + diff_win_options(old_curwin, TRUE); + + if (newname != NULL) { + /* do a ":file filename.new" on the patched buffer */ + eap->arg = newname; + ex_file(eap); + + /* Do filetype detection with the new name. */ + if (au_has_group((char_u *)"filetypedetect")) + do_cmdline_cmd((char_u *)":doau filetypedetect BufRead"); + } + } + } + } + +theend: + if (tmp_orig != NULL) + mch_remove(tmp_orig); + vim_free(tmp_orig); + if (tmp_new != NULL) + mch_remove(tmp_new); + vim_free(tmp_new); + vim_free(newname); + vim_free(buf); +#ifdef UNIX + vim_free(fullname); +#endif +} + +/* + * Split the window and edit another file, setting options to show the diffs. + */ +void ex_diffsplit(eap) +exarg_T *eap; +{ + win_T *old_curwin = curwin; + + /* don't use a new tab page, each tab page has its own diffs */ + cmdmod.tab = 0; + + if (win_split(0, (diff_flags & DIFF_VERTICAL) ? WSP_VERT : 0) != FAIL) { + /* Pretend it was a ":split fname" command */ + eap->cmdidx = CMD_split; + curwin->w_p_diff = TRUE; + do_exedit(eap, old_curwin); + + if (curwin != old_curwin) { /* split must have worked */ + /* Set 'diff', 'scrollbind' on and 'wrap' off. */ + diff_win_options(curwin, TRUE); + diff_win_options(old_curwin, TRUE); + } + } +} + +/* + * Set options to show diffs for the current window. + */ +void ex_diffthis(eap) +exarg_T *eap UNUSED; +{ + /* Set 'diff', 'scrollbind' on and 'wrap' off. */ + diff_win_options(curwin, TRUE); +} + +/* + * Set options in window "wp" for diff mode. + */ +void diff_win_options(wp, addbuf) +win_T *wp; +int addbuf; /* Add buffer to diff. */ +{ + win_T *old_curwin = curwin; + + /* close the manually opened folds */ + curwin = wp; + newFoldLevel(); + curwin = old_curwin; + + wp->w_p_diff = TRUE; + + /* Use 'scrollbind' and 'cursorbind' when available */ + if (!wp->w_p_diff_saved) + wp->w_p_scb_save = wp->w_p_scb; + wp->w_p_scb = TRUE; + if (!wp->w_p_diff_saved) + wp->w_p_crb_save = wp->w_p_crb; + wp->w_p_crb = TRUE; + if (!wp->w_p_diff_saved) + wp->w_p_wrap_save = wp->w_p_wrap; + wp->w_p_wrap = FALSE; + curwin = wp; + curbuf = curwin->w_buffer; + if (!wp->w_p_diff_saved) + wp->w_p_fdm_save = vim_strsave(wp->w_p_fdm); + set_string_option_direct((char_u *)"fdm", -1, (char_u *)"diff", + OPT_LOCAL|OPT_FREE, 0); + curwin = old_curwin; + curbuf = curwin->w_buffer; + if (!wp->w_p_diff_saved) { + wp->w_p_fdc_save = wp->w_p_fdc; + wp->w_p_fen_save = wp->w_p_fen; + wp->w_p_fdl_save = wp->w_p_fdl; + } + wp->w_p_fdc = diff_foldcolumn; + wp->w_p_fen = TRUE; + wp->w_p_fdl = 0; + foldUpdateAll(wp); + /* make sure topline is not halfway a fold */ + changed_window_setting_win(wp); + if (vim_strchr(p_sbo, 'h') == NULL) + do_cmdline_cmd((char_u *)"set sbo+=hor"); + /* Saved the current values, to be restored in ex_diffoff(). */ + wp->w_p_diff_saved = TRUE; + + if (addbuf) + diff_buf_add(wp->w_buffer); + redraw_win_later(wp, NOT_VALID); +} + +/* + * Set options not to show diffs. For the current window or all windows. + * Only in the current tab page. + */ +void ex_diffoff(eap) +exarg_T *eap; +{ + win_T *wp; + win_T *old_curwin = curwin; + int diffwin = FALSE; + + for (wp = firstwin; wp != NULL; wp = wp->w_next) { + if (eap->forceit ? wp->w_p_diff : wp == curwin) { + /* Set 'diff', 'scrollbind' off and 'wrap' on. If option values + * were saved in diff_win_options() restore them. */ + wp->w_p_diff = FALSE; + + if (wp->w_p_scb) + wp->w_p_scb = wp->w_p_diff_saved ? wp->w_p_scb_save : FALSE; + if (wp->w_p_crb) + wp->w_p_crb = wp->w_p_diff_saved ? wp->w_p_crb_save : FALSE; + if (!wp->w_p_wrap) + wp->w_p_wrap = wp->w_p_diff_saved ? wp->w_p_wrap_save : TRUE; + curwin = wp; + curbuf = curwin->w_buffer; + if (wp->w_p_diff_saved) { + free_string_option(wp->w_p_fdm); + wp->w_p_fdm = wp->w_p_fdm_save; + wp->w_p_fdm_save = empty_option; + } else + set_string_option_direct((char_u *)"fdm", -1, + (char_u *)"manual", OPT_LOCAL|OPT_FREE, 0); + curwin = old_curwin; + curbuf = curwin->w_buffer; + if (wp->w_p_fdc == diff_foldcolumn) + wp->w_p_fdc = wp->w_p_diff_saved ? wp->w_p_fdc_save : 0; + if (wp->w_p_fdl == 0 && wp->w_p_diff_saved) + wp->w_p_fdl = wp->w_p_fdl_save; + + if (wp->w_p_fen) { + /* Only restore 'foldenable' when 'foldmethod' is not + * "manual", otherwise we continue to show the diff folds. */ + if (foldmethodIsManual(wp) || !wp->w_p_diff_saved) + wp->w_p_fen = FALSE; + else + wp->w_p_fen = wp->w_p_fen_save; + } + + foldUpdateAll(wp); + /* make sure topline is not halfway a fold */ + changed_window_setting_win(wp); + /* Note: 'sbo' is not restored, it's a global option. */ + diff_buf_adjust(wp); + + wp->w_p_diff_saved = FALSE; + } + diffwin |= wp->w_p_diff; + } + + /* Remove "hor" from from 'scrollopt' if there are no diff windows left. */ + if (!diffwin && vim_strchr(p_sbo, 'h') != NULL) + do_cmdline_cmd((char_u *)"set sbo-=hor"); +} + +/* + * Read the diff output and add each entry to the diff list. + */ +static void diff_read(idx_orig, idx_new, fname) +int idx_orig; /* idx of original file */ +int idx_new; /* idx of new file */ +char_u *fname; /* name of diff output file */ +{ + FILE *fd; + diff_T *dprev = NULL; + diff_T *dp = curtab->tp_first_diff; + diff_T *dn, *dpl; + long f1, l1, f2, l2; + char_u linebuf[LBUFLEN]; /* only need to hold the diff line */ + int difftype; + char_u *p; + long off; + int i; + linenr_T lnum_orig, lnum_new; + long count_orig, count_new; + int notset = TRUE; /* block "*dp" not set yet */ + + fd = mch_fopen((char *)fname, "r"); + if (fd == NULL) { + EMSG(_("E98: Cannot read diff output")); + return; + } + + for (;; ) { + if (tag_fgets(linebuf, LBUFLEN, fd)) + break; /* end of file */ + if (!isdigit(*linebuf)) + continue; /* not the start of a diff block */ + + /* This line must be one of three formats: + * {first}[,{last}]c{first}[,{last}] + * {first}a{first}[,{last}] + * {first}[,{last}]d{first} + */ + p = linebuf; + f1 = getdigits(&p); + if (*p == ',') { + ++p; + l1 = getdigits(&p); + } else + l1 = f1; + if (*p != 'a' && *p != 'c' && *p != 'd') + continue; /* invalid diff format */ + difftype = *p++; + f2 = getdigits(&p); + if (*p == ',') { + ++p; + l2 = getdigits(&p); + } else + l2 = f2; + if (l1 < f1 || l2 < f2) + continue; /* invalid line range */ + + if (difftype == 'a') { + lnum_orig = f1 + 1; + count_orig = 0; + } else { + lnum_orig = f1; + count_orig = l1 - f1 + 1; + } + if (difftype == 'd') { + lnum_new = f2 + 1; + count_new = 0; + } else { + lnum_new = f2; + count_new = l2 - f2 + 1; + } + + /* Go over blocks before the change, for which orig and new are equal. + * Copy blocks from orig to new. */ + while (dp != NULL + && lnum_orig > dp->df_lnum[idx_orig] + dp->df_count[idx_orig]) { + if (notset) + diff_copy_entry(dprev, dp, idx_orig, idx_new); + dprev = dp; + dp = dp->df_next; + notset = TRUE; + } + + if (dp != NULL + && lnum_orig <= dp->df_lnum[idx_orig] + dp->df_count[idx_orig] + && lnum_orig + count_orig >= dp->df_lnum[idx_orig]) { + /* New block overlaps with existing block(s). + * First find last block that overlaps. */ + for (dpl = dp; dpl->df_next != NULL; dpl = dpl->df_next) + if (lnum_orig + count_orig < dpl->df_next->df_lnum[idx_orig]) + break; + + /* If the newly found block starts before the old one, set the + * start back a number of lines. */ + off = dp->df_lnum[idx_orig] - lnum_orig; + if (off > 0) { + for (i = idx_orig; i < idx_new; ++i) + if (curtab->tp_diffbuf[i] != NULL) + dp->df_lnum[i] -= off; + dp->df_lnum[idx_new] = lnum_new; + dp->df_count[idx_new] = count_new; + } else if (notset) { + /* new block inside existing one, adjust new block */ + dp->df_lnum[idx_new] = lnum_new + off; + dp->df_count[idx_new] = count_new - off; + } else + /* second overlap of new block with existing block */ + dp->df_count[idx_new] += count_new - count_orig + + dpl->df_lnum[idx_orig] + + dpl->df_count[idx_orig] + - (dp->df_lnum[idx_orig] + + dp->df_count[idx_orig]); + + /* Adjust the size of the block to include all the lines to the + * end of the existing block or the new diff, whatever ends last. */ + off = (lnum_orig + count_orig) + - (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]); + if (off < 0) { + /* new change ends in existing block, adjust the end if not + * done already */ + if (notset) + dp->df_count[idx_new] += -off; + off = 0; + } + for (i = idx_orig; i < idx_new; ++i) + if (curtab->tp_diffbuf[i] != NULL) + dp->df_count[i] = dpl->df_lnum[i] + dpl->df_count[i] + - dp->df_lnum[i] + off; + + /* Delete the diff blocks that have been merged into one. */ + dn = dp->df_next; + dp->df_next = dpl->df_next; + while (dn != dp->df_next) { + dpl = dn->df_next; + vim_free(dn); + dn = dpl; + } + } else { + /* Allocate a new diffblock. */ + dp = diff_alloc_new(curtab, dprev, dp); + if (dp == NULL) + goto done; + + dp->df_lnum[idx_orig] = lnum_orig; + dp->df_count[idx_orig] = count_orig; + dp->df_lnum[idx_new] = lnum_new; + dp->df_count[idx_new] = count_new; + + /* Set values for other buffers, these must be equal to the + * original buffer, otherwise there would have been a change + * already. */ + for (i = idx_orig + 1; i < idx_new; ++i) + if (curtab->tp_diffbuf[i] != NULL) + diff_copy_entry(dprev, dp, idx_orig, i); + } + notset = FALSE; /* "*dp" has been set */ + } + + /* for remaining diff blocks orig and new are equal */ + while (dp != NULL) { + if (notset) + diff_copy_entry(dprev, dp, idx_orig, idx_new); + dprev = dp; + dp = dp->df_next; + notset = TRUE; + } + +done: + fclose(fd); +} + +/* + * Copy an entry at "dp" from "idx_orig" to "idx_new". + */ +static void diff_copy_entry(dprev, dp, idx_orig, idx_new) +diff_T *dprev; +diff_T *dp; +int idx_orig; +int idx_new; +{ + long off; + + if (dprev == NULL) + off = 0; + else + off = (dprev->df_lnum[idx_orig] + dprev->df_count[idx_orig]) + - (dprev->df_lnum[idx_new] + dprev->df_count[idx_new]); + dp->df_lnum[idx_new] = dp->df_lnum[idx_orig] - off; + dp->df_count[idx_new] = dp->df_count[idx_orig]; +} + +/* + * Clear the list of diffblocks for tab page "tp". + */ +void diff_clear(tp) +tabpage_T *tp; +{ + diff_T *p, *next_p; + + for (p = tp->tp_first_diff; p != NULL; p = next_p) { + next_p = p->df_next; + vim_free(p); + } + tp->tp_first_diff = NULL; +} + +/* + * Check diff status for line "lnum" in buffer "buf": + * Returns 0 for nothing special + * Returns -1 for a line that should be highlighted as changed. + * Returns -2 for a line that should be highlighted as added/deleted. + * Returns > 0 for inserting that many filler lines above it (never happens + * when 'diffopt' doesn't contain "filler"). + * This should only be used for windows where 'diff' is set. + */ +int diff_check(wp, lnum) +win_T *wp; +linenr_T lnum; +{ + int idx; /* index in tp_diffbuf[] for this buffer */ + diff_T *dp; + int maxcount; + int i; + buf_T *buf = wp->w_buffer; + int cmp; + + if (curtab->tp_diff_invalid) + ex_diffupdate(NULL); /* update after a big change */ + + if (curtab->tp_first_diff == NULL || !wp->w_p_diff) /* no diffs at all */ + return 0; + + /* safety check: "lnum" must be a buffer line */ + if (lnum < 1 || lnum > buf->b_ml.ml_line_count + 1) + return 0; + + idx = diff_buf_idx(buf); + if (idx == DB_COUNT) + return 0; /* no diffs for buffer "buf" */ + + /* A closed fold never has filler lines. */ + if (hasFoldingWin(wp, lnum, NULL, NULL, TRUE, NULL)) + return 0; + + /* search for a change that includes "lnum" in the list of diffblocks. */ + for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) + if (lnum <= dp->df_lnum[idx] + dp->df_count[idx]) + break; + if (dp == NULL || lnum < dp->df_lnum[idx]) + return 0; + + if (lnum < dp->df_lnum[idx] + dp->df_count[idx]) { + int zero = FALSE; + + /* Changed or inserted line. If the other buffers have a count of + * zero, the lines were inserted. If the other buffers have the same + * count, check if the lines are identical. */ + cmp = FALSE; + for (i = 0; i < DB_COUNT; ++i) + if (i != idx && curtab->tp_diffbuf[i] != NULL) { + if (dp->df_count[i] == 0) + zero = TRUE; + else { + if (dp->df_count[i] != dp->df_count[idx]) + return -1; /* nr of lines changed. */ + cmp = TRUE; + } + } + if (cmp) { + /* Compare all lines. If they are equal the lines were inserted + * in some buffers, deleted in others, but not changed. */ + for (i = 0; i < DB_COUNT; ++i) + if (i != idx && curtab->tp_diffbuf[i] != NULL && dp->df_count[i] != 0) + if (!diff_equal_entry(dp, idx, i)) + return -1; + } + /* If there is no buffer with zero lines then there is no difference + * any longer. Happens when making a change (or undo) that removes + * the difference. Can't remove the entry here, we might be halfway + * updating the window. Just report the text as unchanged. Other + * windows might still show the change though. */ + if (zero == FALSE) + return 0; + return -2; + } + + /* If 'diffopt' doesn't contain "filler", return 0. */ + if (!(diff_flags & DIFF_FILLER)) + return 0; + + /* Insert filler lines above the line just below the change. Will return + * 0 when this buf had the max count. */ + maxcount = 0; + for (i = 0; i < DB_COUNT; ++i) + if (curtab->tp_diffbuf[i] != NULL && dp->df_count[i] > maxcount) + maxcount = dp->df_count[i]; + return maxcount - dp->df_count[idx]; +} + +/* + * Compare two entries in diff "*dp" and return TRUE if they are equal. + */ +static int diff_equal_entry(dp, idx1, idx2) +diff_T *dp; +int idx1; +int idx2; +{ + int i; + char_u *line; + int cmp; + + if (dp->df_count[idx1] != dp->df_count[idx2]) + return FALSE; + if (diff_check_sanity(curtab, dp) == FAIL) + return FALSE; + for (i = 0; i < dp->df_count[idx1]; ++i) { + line = vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx1], + dp->df_lnum[idx1] + i, FALSE)); + if (line == NULL) + return FALSE; + cmp = diff_cmp(line, ml_get_buf(curtab->tp_diffbuf[idx2], + dp->df_lnum[idx2] + i, FALSE)); + vim_free(line); + if (cmp != 0) + return FALSE; + } + return TRUE; +} + +/* + * Compare strings "s1" and "s2" according to 'diffopt'. + * Return non-zero when they are different. + */ +static int diff_cmp(s1, s2) +char_u *s1; +char_u *s2; +{ + char_u *p1, *p2; + int l; + + if ((diff_flags & (DIFF_ICASE | DIFF_IWHITE)) == 0) + return STRCMP(s1, s2); + if ((diff_flags & DIFF_ICASE) && !(diff_flags & DIFF_IWHITE)) + return MB_STRICMP(s1, s2); + + /* Ignore white space changes and possibly ignore case. */ + p1 = s1; + p2 = s2; + while (*p1 != NUL && *p2 != NUL) { + if (vim_iswhite(*p1) && vim_iswhite(*p2)) { + p1 = skipwhite(p1); + p2 = skipwhite(p2); + } else { + l = (*mb_ptr2len)(p1); + if (l != (*mb_ptr2len)(p2)) + break; + if (l > 1) { + if (STRNCMP(p1, p2, l) != 0 + && (!enc_utf8 + || !(diff_flags & DIFF_ICASE) + || utf_fold(utf_ptr2char(p1)) + != utf_fold(utf_ptr2char(p2)))) + break; + p1 += l; + p2 += l; + } else { + if (*p1 != *p2 && (!(diff_flags & DIFF_ICASE) + || TOLOWER_LOC(*p1) != TOLOWER_LOC(*p2))) + break; + ++p1; + ++p2; + } + } + } + + /* Ignore trailing white space. */ + p1 = skipwhite(p1); + p2 = skipwhite(p2); + if (*p1 != NUL || *p2 != NUL) + return 1; + return 0; +} + +/* + * Return the number of filler lines above "lnum". + */ +int diff_check_fill(wp, lnum) +win_T *wp; +linenr_T lnum; +{ + int n; + + /* be quick when there are no filler lines */ + if (!(diff_flags & DIFF_FILLER)) + return 0; + n = diff_check(wp, lnum); + if (n <= 0) + return 0; + return n; +} + +/* + * Set the topline of "towin" to match the position in "fromwin", so that they + * show the same diff'ed lines. + */ +void diff_set_topline(fromwin, towin) +win_T *fromwin; +win_T *towin; +{ + buf_T *frombuf = fromwin->w_buffer; + linenr_T lnum = fromwin->w_topline; + int fromidx; + int toidx; + diff_T *dp; + int max_count; + int i; + + fromidx = diff_buf_idx(frombuf); + if (fromidx == DB_COUNT) + return; /* safety check */ + + if (curtab->tp_diff_invalid) + ex_diffupdate(NULL); /* update after a big change */ + + towin->w_topfill = 0; + + /* search for a change that includes "lnum" in the list of diffblocks. */ + for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) + if (lnum <= dp->df_lnum[fromidx] + dp->df_count[fromidx]) + break; + if (dp == NULL) { + /* After last change, compute topline relative to end of file; no + * filler lines. */ + towin->w_topline = towin->w_buffer->b_ml.ml_line_count + - (frombuf->b_ml.ml_line_count - lnum); + } else { + /* Find index for "towin". */ + toidx = diff_buf_idx(towin->w_buffer); + if (toidx == DB_COUNT) + return; /* safety check */ + + towin->w_topline = lnum + (dp->df_lnum[toidx] - dp->df_lnum[fromidx]); + if (lnum >= dp->df_lnum[fromidx]) { + /* Inside a change: compute filler lines. With three or more + * buffers we need to know the largest count. */ + max_count = 0; + for (i = 0; i < DB_COUNT; ++i) + if (curtab->tp_diffbuf[i] != NULL + && max_count < dp->df_count[i]) + max_count = dp->df_count[i]; + + if (dp->df_count[toidx] == dp->df_count[fromidx]) { + /* same number of lines: use same filler count */ + towin->w_topfill = fromwin->w_topfill; + } else if (dp->df_count[toidx] > dp->df_count[fromidx]) { + if (lnum == dp->df_lnum[fromidx] + dp->df_count[fromidx]) { + /* more lines in towin and fromwin doesn't show diff + * lines, only filler lines */ + if (max_count - fromwin->w_topfill >= dp->df_count[toidx]) { + /* towin also only shows filler lines */ + towin->w_topline = dp->df_lnum[toidx] + + dp->df_count[toidx]; + towin->w_topfill = fromwin->w_topfill; + } else + /* towin still has some diff lines to show */ + towin->w_topline = dp->df_lnum[toidx] + + max_count - fromwin->w_topfill; + } + } else if (towin->w_topline >= dp->df_lnum[toidx] + + dp->df_count[toidx]) { + /* less lines in towin and no diff lines to show: compute + * filler lines */ + towin->w_topline = dp->df_lnum[toidx] + dp->df_count[toidx]; + if (diff_flags & DIFF_FILLER) { + if (lnum == dp->df_lnum[fromidx] + dp->df_count[fromidx]) + /* fromwin is also out of diff lines */ + towin->w_topfill = fromwin->w_topfill; + else + /* fromwin has some diff lines */ + towin->w_topfill = dp->df_lnum[fromidx] + + max_count - lnum; + } + } + } + } + + /* safety check (if diff info gets outdated strange things may happen) */ + towin->w_botfill = FALSE; + if (towin->w_topline > towin->w_buffer->b_ml.ml_line_count) { + towin->w_topline = towin->w_buffer->b_ml.ml_line_count; + towin->w_botfill = TRUE; + } + if (towin->w_topline < 1) { + towin->w_topline = 1; + towin->w_topfill = 0; + } + + /* When w_topline changes need to recompute w_botline and cursor position */ + invalidate_botline_win(towin); + changed_line_abv_curs_win(towin); + + check_topfill(towin, FALSE); + (void)hasFoldingWin(towin, towin->w_topline, &towin->w_topline, + NULL, TRUE, NULL); +} + +/* + * This is called when 'diffopt' is changed. + */ +int diffopt_changed() { + char_u *p; + int diff_context_new = 6; + int diff_flags_new = 0; + int diff_foldcolumn_new = 2; + tabpage_T *tp; + + p = p_dip; + while (*p != NUL) { + if (STRNCMP(p, "filler", 6) == 0) { + p += 6; + diff_flags_new |= DIFF_FILLER; + } else if (STRNCMP(p, "context:", 8) == 0 && VIM_ISDIGIT(p[8])) { + p += 8; + diff_context_new = getdigits(&p); + } else if (STRNCMP(p, "icase", 5) == 0) { + p += 5; + diff_flags_new |= DIFF_ICASE; + } else if (STRNCMP(p, "iwhite", 6) == 0) { + p += 6; + diff_flags_new |= DIFF_IWHITE; + } else if (STRNCMP(p, "horizontal", 10) == 0) { + p += 10; + diff_flags_new |= DIFF_HORIZONTAL; + } else if (STRNCMP(p, "vertical", 8) == 0) { + p += 8; + diff_flags_new |= DIFF_VERTICAL; + } else if (STRNCMP(p, "foldcolumn:", 11) == 0 && VIM_ISDIGIT(p[11])) { + p += 11; + diff_foldcolumn_new = getdigits(&p); + } + if (*p != ',' && *p != NUL) + return FAIL; + if (*p == ',') + ++p; + } + + /* Can't have both "horizontal" and "vertical". */ + if ((diff_flags_new & DIFF_HORIZONTAL) && (diff_flags_new & DIFF_VERTICAL)) + return FAIL; + + /* If "icase" or "iwhite" was added or removed, need to update the diff. */ + if (diff_flags != diff_flags_new) + for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) + tp->tp_diff_invalid = TRUE; + + diff_flags = diff_flags_new; + diff_context = diff_context_new; + diff_foldcolumn = diff_foldcolumn_new; + + diff_redraw(TRUE); + + /* recompute the scroll binding with the new option value, may + * remove or add filler lines */ + check_scrollbind((linenr_T)0, 0L); + + return OK; +} + +/* + * Return TRUE if 'diffopt' contains "horizontal". + */ +int diffopt_horizontal() { + return (diff_flags & DIFF_HORIZONTAL) != 0; +} + +/* + * Find the difference within a changed line. + * Returns TRUE if the line was added, no other buffer has it. + */ +int diff_find_change(wp, lnum, startp, endp) +win_T *wp; +linenr_T lnum; +int *startp; /* first char of the change */ +int *endp; /* last char of the change */ +{ + char_u *line_org; + char_u *line_new; + int i; + int si_org, si_new; + int ei_org, ei_new; + diff_T *dp; + int idx; + int off; + int added = TRUE; + + /* Make a copy of the line, the next ml_get() will invalidate it. */ + line_org = vim_strsave(ml_get_buf(wp->w_buffer, lnum, FALSE)); + if (line_org == NULL) + return FALSE; + + idx = diff_buf_idx(wp->w_buffer); + if (idx == DB_COUNT) { /* cannot happen */ + vim_free(line_org); + return FALSE; + } + + /* search for a change that includes "lnum" in the list of diffblocks. */ + for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) + if (lnum <= dp->df_lnum[idx] + dp->df_count[idx]) + break; + if (dp == NULL || diff_check_sanity(curtab, dp) == FAIL) { + vim_free(line_org); + return FALSE; + } + + off = lnum - dp->df_lnum[idx]; + + for (i = 0; i < DB_COUNT; ++i) + if (curtab->tp_diffbuf[i] != NULL && i != idx) { + /* Skip lines that are not in the other change (filler lines). */ + if (off >= dp->df_count[i]) + continue; + added = FALSE; + line_new = ml_get_buf(curtab->tp_diffbuf[i], + dp->df_lnum[i] + off, FALSE); + + /* Search for start of difference */ + si_org = si_new = 0; + while (line_org[si_org] != NUL) { + if ((diff_flags & DIFF_IWHITE) + && vim_iswhite(line_org[si_org]) + && vim_iswhite(line_new[si_new])) { + si_org = (int)(skipwhite(line_org + si_org) - line_org); + si_new = (int)(skipwhite(line_new + si_new) - line_new); + } else { + if (line_org[si_org] != line_new[si_new]) + break; + ++si_org; + ++si_new; + } + } + if (has_mbyte) { + /* Move back to first byte of character in both lines (may + * have "nn^" in line_org and "n^ in line_new). */ + si_org -= (*mb_head_off)(line_org, line_org + si_org); + si_new -= (*mb_head_off)(line_new, line_new + si_new); + } + if (*startp > si_org) + *startp = si_org; + + /* Search for end of difference, if any. */ + if (line_org[si_org] != NUL || line_new[si_new] != NUL) { + ei_org = (int)STRLEN(line_org); + ei_new = (int)STRLEN(line_new); + while (ei_org >= *startp && ei_new >= si_new + && ei_org >= 0 && ei_new >= 0) { + if ((diff_flags & DIFF_IWHITE) + && vim_iswhite(line_org[ei_org]) + && vim_iswhite(line_new[ei_new])) { + while (ei_org >= *startp + && vim_iswhite(line_org[ei_org])) + --ei_org; + while (ei_new >= si_new + && vim_iswhite(line_new[ei_new])) + --ei_new; + } else { + if (line_org[ei_org] != line_new[ei_new]) + break; + --ei_org; + --ei_new; + } + } + if (*endp < ei_org) + *endp = ei_org; + } + } + + vim_free(line_org); + return added; +} + +/* + * Return TRUE if line "lnum" is not close to a diff block, this line should + * be in a fold. + * Return FALSE if there are no diff blocks at all in this window. + */ +int diff_infold(wp, lnum) +win_T *wp; +linenr_T lnum; +{ + int i; + int idx = -1; + int other = FALSE; + diff_T *dp; + + /* Return if 'diff' isn't set. */ + if (!wp->w_p_diff) + return FALSE; + + for (i = 0; i < DB_COUNT; ++i) { + if (curtab->tp_diffbuf[i] == wp->w_buffer) + idx = i; + else if (curtab->tp_diffbuf[i] != NULL) + other = TRUE; + } + + /* return here if there are no diffs in the window */ + if (idx == -1 || !other) + return FALSE; + + if (curtab->tp_diff_invalid) + ex_diffupdate(NULL); /* update after a big change */ + + /* Return if there are no diff blocks. All lines will be folded. */ + if (curtab->tp_first_diff == NULL) + return TRUE; + + for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { + /* If this change is below the line there can't be any further match. */ + if (dp->df_lnum[idx] - diff_context > lnum) + break; + /* If this change ends before the line we have a match. */ + if (dp->df_lnum[idx] + dp->df_count[idx] + diff_context > lnum) + return FALSE; + } + return TRUE; +} + +/* + * "dp" and "do" commands. + */ +void nv_diffgetput(put) +int put; +{ + exarg_T ea; + + ea.arg = (char_u *)""; + if (put) + ea.cmdidx = CMD_diffput; + else + ea.cmdidx = CMD_diffget; + ea.addr_count = 0; + ea.line1 = curwin->w_cursor.lnum; + ea.line2 = curwin->w_cursor.lnum; + ex_diffgetput(&ea); +} + +/* + * ":diffget" + * ":diffput" + */ +void ex_diffgetput(eap) +exarg_T *eap; +{ + linenr_T lnum; + int count; + linenr_T off = 0; + diff_T *dp; + diff_T *dprev; + diff_T *dfree; + int idx_cur; + int idx_other; + int idx_from; + int idx_to; + int i; + int added; + char_u *p; + aco_save_T aco; + buf_T *buf; + int start_skip, end_skip; + int new_count; + int buf_empty; + int found_not_ma = FALSE; + + /* Find the current buffer in the list of diff buffers. */ + idx_cur = diff_buf_idx(curbuf); + if (idx_cur == DB_COUNT) { + EMSG(_("E99: Current buffer is not in diff mode")); + return; + } + + if (*eap->arg == NUL) { + /* No argument: Find the other buffer in the list of diff buffers. */ + for (idx_other = 0; idx_other < DB_COUNT; ++idx_other) + if (curtab->tp_diffbuf[idx_other] != curbuf + && curtab->tp_diffbuf[idx_other] != NULL) { + if (eap->cmdidx != CMD_diffput + || curtab->tp_diffbuf[idx_other]->b_p_ma) + break; + found_not_ma = TRUE; + } + if (idx_other == DB_COUNT) { + if (found_not_ma) + EMSG(_("E793: No other buffer in diff mode is modifiable")); + else + EMSG(_("E100: No other buffer in diff mode")); + return; + } + + /* Check that there isn't a third buffer in the list */ + for (i = idx_other + 1; i < DB_COUNT; ++i) + if (curtab->tp_diffbuf[i] != curbuf + && curtab->tp_diffbuf[i] != NULL + && (eap->cmdidx != CMD_diffput || curtab->tp_diffbuf[i]->b_p_ma)) { + EMSG(_( + "E101: More than two buffers in diff mode, don't know which one to use")); + return; + } + } else { + /* Buffer number or pattern given. Ignore trailing white space. */ + p = eap->arg + STRLEN(eap->arg); + while (p > eap->arg && vim_iswhite(p[-1])) + --p; + for (i = 0; vim_isdigit(eap->arg[i]) && eap->arg + i < p; ++i) + ; + if (eap->arg + i == p) /* digits only */ + i = atol((char *)eap->arg); + else { + i = buflist_findpat(eap->arg, p, FALSE, TRUE, FALSE); + if (i < 0) + return; /* error message already given */ + } + buf = buflist_findnr(i); + if (buf == NULL) { + EMSG2(_("E102: Can't find buffer \"%s\""), eap->arg); + return; + } + if (buf == curbuf) + return; /* nothing to do */ + idx_other = diff_buf_idx(buf); + if (idx_other == DB_COUNT) { + EMSG2(_("E103: Buffer \"%s\" is not in diff mode"), eap->arg); + return; + } + } + + diff_busy = TRUE; + + /* When no range given include the line above or below the cursor. */ + if (eap->addr_count == 0) { + /* Make it possible that ":diffget" on the last line gets line below + * the cursor line when there is no difference above the cursor. */ + if (eap->cmdidx == CMD_diffget + && eap->line1 == curbuf->b_ml.ml_line_count + && diff_check(curwin, eap->line1) == 0 + && (eap->line1 == 1 || diff_check(curwin, eap->line1 - 1) == 0)) + ++eap->line2; + else if (eap->line1 > 0) + --eap->line1; + } + + if (eap->cmdidx == CMD_diffget) { + idx_from = idx_other; + idx_to = idx_cur; + } else { + idx_from = idx_cur; + idx_to = idx_other; + /* Need to make the other buffer the current buffer to be able to make + * changes in it. */ + /* set curwin/curbuf to buf and save a few things */ + aucmd_prepbuf(&aco, curtab->tp_diffbuf[idx_other]); + } + + /* May give the warning for a changed buffer here, which can trigger the + * FileChangedRO autocommand, which may do nasty things and mess + * everything up. */ + if (!curbuf->b_changed) { + change_warning(0); + if (diff_buf_idx(curbuf) != idx_to) { + EMSG(_("E787: Buffer changed unexpectedly")); + return; + } + } + + dprev = NULL; + for (dp = curtab->tp_first_diff; dp != NULL; ) { + if (dp->df_lnum[idx_cur] > eap->line2 + off) + break; /* past the range that was specified */ + + dfree = NULL; + lnum = dp->df_lnum[idx_to]; + count = dp->df_count[idx_to]; + if (dp->df_lnum[idx_cur] + dp->df_count[idx_cur] > eap->line1 + off + && u_save(lnum - 1, lnum + count) != FAIL) { + /* Inside the specified range and saving for undo worked. */ + start_skip = 0; + end_skip = 0; + if (eap->addr_count > 0) { + /* A range was specified: check if lines need to be skipped. */ + start_skip = eap->line1 + off - dp->df_lnum[idx_cur]; + if (start_skip > 0) { + /* range starts below start of current diff block */ + if (start_skip > count) { + lnum += count; + count = 0; + } else { + count -= start_skip; + lnum += start_skip; + } + } else + start_skip = 0; + + end_skip = dp->df_lnum[idx_cur] + dp->df_count[idx_cur] - 1 + - (eap->line2 + off); + if (end_skip > 0) { + /* range ends above end of current/from diff block */ + if (idx_cur == idx_from) { /* :diffput */ + i = dp->df_count[idx_cur] - start_skip - end_skip; + if (count > i) + count = i; + } else { /* :diffget */ + count -= end_skip; + end_skip = dp->df_count[idx_from] - start_skip - count; + if (end_skip < 0) + end_skip = 0; + } + } else + end_skip = 0; + } + + buf_empty = FALSE; + added = 0; + for (i = 0; i < count; ++i) { + /* remember deleting the last line of the buffer */ + buf_empty = curbuf->b_ml.ml_line_count == 1; + ml_delete(lnum, FALSE); + --added; + } + for (i = 0; i < dp->df_count[idx_from] - start_skip - end_skip; ++i) { + linenr_T nr; + + nr = dp->df_lnum[idx_from] + start_skip + i; + if (nr > curtab->tp_diffbuf[idx_from]->b_ml.ml_line_count) + break; + p = vim_strsave(ml_get_buf(curtab->tp_diffbuf[idx_from], + nr, FALSE)); + if (p != NULL) { + ml_append(lnum + i - 1, p, 0, FALSE); + vim_free(p); + ++added; + if (buf_empty && curbuf->b_ml.ml_line_count == 2) { + /* Added the first line into an empty buffer, need to + * delete the dummy empty line. */ + buf_empty = FALSE; + ml_delete((linenr_T)2, FALSE); + } + } + } + new_count = dp->df_count[idx_to] + added; + dp->df_count[idx_to] = new_count; + + if (start_skip == 0 && end_skip == 0) { + /* Check if there are any other buffers and if the diff is + * equal in them. */ + for (i = 0; i < DB_COUNT; ++i) + if (curtab->tp_diffbuf[i] != NULL && i != idx_from + && i != idx_to + && !diff_equal_entry(dp, idx_from, i)) + break; + if (i == DB_COUNT) { + /* delete the diff entry, the buffers are now equal here */ + dfree = dp; + dp = dp->df_next; + if (dprev == NULL) + curtab->tp_first_diff = dp; + else + dprev->df_next = dp; + } + } + + /* Adjust marks. This will change the following entries! */ + if (added != 0) { + mark_adjust(lnum, lnum + count - 1, (long)MAXLNUM, (long)added); + if (curwin->w_cursor.lnum >= lnum) { + /* Adjust the cursor position if it's in/after the changed + * lines. */ + if (curwin->w_cursor.lnum >= lnum + count) + curwin->w_cursor.lnum += added; + else if (added < 0) + curwin->w_cursor.lnum = lnum; + } + } + changed_lines(lnum, 0, lnum + count, (long)added); + + if (dfree != NULL) { + /* Diff is deleted, update folds in other windows. */ + diff_fold_update(dfree, idx_to); + vim_free(dfree); + } else + /* mark_adjust() may have changed the count in a wrong way */ + dp->df_count[idx_to] = new_count; + + /* When changing the current buffer, keep track of line numbers */ + if (idx_cur == idx_to) + off += added; + } + + /* If before the range or not deleted, go to next diff. */ + if (dfree == NULL) { + dprev = dp; + dp = dp->df_next; + } + } + + /* restore curwin/curbuf and a few other things */ + if (eap->cmdidx != CMD_diffget) { + /* Syncing undo only works for the current buffer, but we change + * another buffer. Sync undo if the command was typed. This isn't + * 100% right when ":diffput" is used in a function or mapping. */ + if (KeyTyped) + u_sync(FALSE); + aucmd_restbuf(&aco); + } + + diff_busy = FALSE; + + /* Check that the cursor is on a valid character and update it's position. + * When there were filler lines the topline has become invalid. */ + check_cursor(); + changed_line_abv_curs(); + + /* Also need to redraw the other buffers. */ + diff_redraw(FALSE); +} + +/* + * Update folds for all diff buffers for entry "dp". + * Skip buffer with index "skip_idx". + * When there are no diffs, all folds are removed. + */ +static void diff_fold_update(dp, skip_idx) +diff_T *dp; +int skip_idx; +{ + int i; + win_T *wp; + + for (wp = firstwin; wp != NULL; wp = wp->w_next) + for (i = 0; i < DB_COUNT; ++i) + if (curtab->tp_diffbuf[i] == wp->w_buffer && i != skip_idx) + foldUpdate(wp, dp->df_lnum[i], + dp->df_lnum[i] + dp->df_count[i]); +} + +/* + * Return TRUE if buffer "buf" is in diff-mode. + */ +int diff_mode_buf(buf) +buf_T *buf; +{ + tabpage_T *tp; + + for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) + if (diff_buf_idx_tp(buf, tp) != DB_COUNT) + return TRUE; + return FALSE; +} + +/* + * Move "count" times in direction "dir" to the next diff block. + * Return FAIL if there isn't such a diff block. + */ +int diff_move_to(dir, count) +int dir; +long count; +{ + int idx; + linenr_T lnum = curwin->w_cursor.lnum; + diff_T *dp; + + idx = diff_buf_idx(curbuf); + if (idx == DB_COUNT || curtab->tp_first_diff == NULL) + return FAIL; + + if (curtab->tp_diff_invalid) + ex_diffupdate(NULL); /* update after a big change */ + + if (curtab->tp_first_diff == NULL) /* no diffs today */ + return FAIL; + + while (--count >= 0) { + /* Check if already before first diff. */ + if (dir == BACKWARD && lnum <= curtab->tp_first_diff->df_lnum[idx]) + break; + + for (dp = curtab->tp_first_diff;; dp = dp->df_next) { + if (dp == NULL) + break; + if ((dir == FORWARD && lnum < dp->df_lnum[idx]) + || (dir == BACKWARD + && (dp->df_next == NULL + || lnum <= dp->df_next->df_lnum[idx]))) { + lnum = dp->df_lnum[idx]; + break; + } + } + } + + /* don't end up past the end of the file */ + if (lnum > curbuf->b_ml.ml_line_count) + lnum = curbuf->b_ml.ml_line_count; + + /* When the cursor didn't move at all we fail. */ + if (lnum == curwin->w_cursor.lnum) + return FAIL; + + setpcmark(); + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + + return OK; +} + +linenr_T diff_get_corresponding_line(buf1, lnum1, buf2, lnum3) +buf_T *buf1; +linenr_T lnum1; +buf_T *buf2; +linenr_T lnum3; +{ + int idx1; + int idx2; + diff_T *dp; + int baseline = 0; + linenr_T lnum2; + + idx1 = diff_buf_idx(buf1); + idx2 = diff_buf_idx(buf2); + if (idx1 == DB_COUNT || idx2 == DB_COUNT || curtab->tp_first_diff == NULL) + return lnum1; + + if (curtab->tp_diff_invalid) + ex_diffupdate(NULL); /* update after a big change */ + + if (curtab->tp_first_diff == NULL) /* no diffs today */ + return lnum1; + + for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) { + if (dp->df_lnum[idx1] > lnum1) { + lnum2 = lnum1 - baseline; + /* don't end up past the end of the file */ + if (lnum2 > buf2->b_ml.ml_line_count) + lnum2 = buf2->b_ml.ml_line_count; + + return lnum2; + } else if ((dp->df_lnum[idx1] + dp->df_count[idx1]) > lnum1) { + /* Inside the diffblock */ + baseline = lnum1 - dp->df_lnum[idx1]; + if (baseline > dp->df_count[idx2]) + baseline = dp->df_count[idx2]; + + return dp->df_lnum[idx2] + baseline; + } else if ( (dp->df_lnum[idx1] == lnum1) + && (dp->df_count[idx1] == 0) + && (dp->df_lnum[idx2] <= lnum3) + && ((dp->df_lnum[idx2] + dp->df_count[idx2]) > lnum3)) + /* + * Special case: if the cursor is just after a zero-count + * block (i.e. all filler) and the target cursor is already + * inside the corresponding block, leave the target cursor + * unmoved. This makes repeated CTRL-W W operations work + * as expected. + */ + return lnum3; + baseline = (dp->df_lnum[idx1] + dp->df_count[idx1]) + - (dp->df_lnum[idx2] + dp->df_count[idx2]); + } + + /* If we get here then the cursor is after the last diff */ + lnum2 = lnum1 - baseline; + /* don't end up past the end of the file */ + if (lnum2 > buf2->b_ml.ml_line_count) + lnum2 = buf2->b_ml.ml_line_count; + + return lnum2; +} + +/* + * For line "lnum" in the current window find the equivalent lnum in window + * "wp", compensating for inserted/deleted lines. + */ +linenr_T diff_lnum_win(lnum, wp) +linenr_T lnum; +win_T *wp; +{ + diff_T *dp; + int idx; + int i; + linenr_T n; + + idx = diff_buf_idx(curbuf); + if (idx == DB_COUNT) /* safety check */ + return (linenr_T)0; + + if (curtab->tp_diff_invalid) + ex_diffupdate(NULL); /* update after a big change */ + + /* search for a change that includes "lnum" in the list of diffblocks. */ + for (dp = curtab->tp_first_diff; dp != NULL; dp = dp->df_next) + if (lnum <= dp->df_lnum[idx] + dp->df_count[idx]) + break; + + /* When after the last change, compute relative to the last line number. */ + if (dp == NULL) + return wp->w_buffer->b_ml.ml_line_count + - (curbuf->b_ml.ml_line_count - lnum); + + /* Find index for "wp". */ + i = diff_buf_idx(wp->w_buffer); + if (i == DB_COUNT) /* safety check */ + return (linenr_T)0; + + n = lnum + (dp->df_lnum[i] - dp->df_lnum[idx]); + if (n > dp->df_lnum[i] + dp->df_count[i]) + n = dp->df_lnum[i] + dp->df_count[i]; + return n; +} + diff --git a/src/digraph.c b/src/digraph.c new file mode 100644 index 0000000000..4584973526 --- /dev/null +++ b/src/digraph.c @@ -0,0 +1,2074 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * digraph.c: code for digraphs + */ + +#include "vim.h" + + +typedef int result_T; + +typedef struct digraph { + char_u char1; + char_u char2; + result_T result; +} digr_T; + +static int getexactdigraph __ARGS((int, int, int)); +static void printdigraph __ARGS((digr_T *)); + +/* digraphs added by the user */ +static garray_T user_digraphs = {0, 0, (int)sizeof(digr_T), 10, NULL}; + +/* + * Note: Characters marked with XX are not included literally, because some + * compilers cannot handle them (Amiga SAS/C is the most picky one). + */ +static digr_T digraphdefault[] = +# ifdef HPUX_DIGRAPHS + +/* + * different HPUX digraphs + */ +{{'A', '`', 161}, /* ¡ */ + {'A', '^', 162}, /* ¢ */ + {'E', '`', 163}, /* £ */ + {'E', '^', 164}, /* ¤ */ + {'E', '"', 165}, /* ¥ */ + {'I', '^', 166}, /* ¦ */ + {'I', '"', 167}, /* § */ + {'\'', '\'', 168}, /* ¨ */ + {'`', '`', 169}, /* © */ + {'^', '^', 170}, /* ª */ + {'"', '"', 171}, /* « */ + {'~', '~', 172}, /* ¬ */ + {'U', '`', 173}, /* ­ */ + {'U', '^', 174}, /* ® */ + {'L', '=', 175}, /* ¯ */ + {'~', '_', 176}, /* ° */ + {'Y', '\'', 177}, /* ± */ + {'y', '\'', 178}, /* ² */ + {'~', 'o', 179}, /* ³ */ + {'C', ',', 180}, /* ´ */ + {'c', ',', 181}, /* µ */ + {'N', '~', 182}, /* ¶ */ + {'n', '~', 183}, /* · */ + {'~', '!', 184}, /* ¸ */ + {'~', '?', 185}, /* ¹ */ + {'o', 'x', 186}, /* º */ + {'L', '-', 187}, /* » */ + {'Y', '=', 188}, /* ¼ */ + {'p', 'p', 189}, /* ½ */ + {'f', 'l', 190}, /* ¾ */ + {'c', '|', 191}, /* ¿ */ + {'a', '^', 192}, /* À */ + {'e', '^', 193}, /* Á */ + {'o', '^', 194}, /* Â */ + {'u', '^', 195}, /* Ã */ + {'a', '\'', 196}, /* Ä */ + {'e', '\'', 197}, /* Å */ + {'o', '\'', 198}, /* Æ */ + {'u', '\'', 199}, /* Ç */ + {'a', '`', 200}, /* È */ + {'e', '`', 201}, /* É */ + {'o', '`', 202}, /* Ê */ + {'u', '`', 203}, /* Ë */ + {'a', '"', 204}, /* Ì */ + {'e', '"', 205}, /* Í */ + {'o', '"', 206}, /* Î */ + {'u', '"', 207}, /* Ï */ + {'A', 'o', 208}, /* Ð */ + {'i', '^', 209}, /* Ñ */ + {'O', '/', 210}, /* Ò */ + {'A', 'E', 211}, /* Ó */ + {'a', 'o', 212}, /* Ô */ + {'i', '\'', 213}, /* Õ */ + {'o', '/', 214}, /* Ö */ + {'a', 'e', 215}, /* × */ + {'A', '"', 216}, /* Ø */ + {'i', '`', 217}, /* Ù */ + {'O', '"', 218}, /* Ú */ + {'U', '"', 219}, /* Û */ + {'E', '\'', 220}, /* Ü */ + {'i', '"', 221}, /* Ý */ + {'s', 's', 222}, /* Þ */ + {'O', '^', 223}, /* ß */ + {'A', '\'', 224}, /* à */ + {'A', '~', 225}, /* á */ + {'a', '~', 226}, /* â */ + {'D', '-', 227}, /* ã */ + {'d', '-', 228}, /* ä */ + {'I', '\'', 229}, /* å */ + {'I', '`', 230}, /* æ */ + {'O', '\'', 231}, /* ç */ + {'O', '`', 232}, /* è */ + {'O', '~', 233}, /* é */ + {'o', '~', 234}, /* ê */ + {'S', '~', 235}, /* ë */ + {'s', '~', 236}, /* ì */ + {'U', '\'', 237}, /* í */ + {'Y', '"', 238}, /* î */ + {'y', '"', 239}, /* ï */ + {'p', '-', 240}, /* ð */ + {'p', '~', 241}, /* ñ */ + {'~', '.', 242}, /* ò */ + {'j', 'u', 243}, /* ó */ + {'P', 'p', 244}, /* ô */ + {'3', '4', 245}, /* õ */ + {'-', '-', 246}, /* ö */ + {'1', '4', 247}, /* ÷ */ + {'1', '2', 248}, /* ø */ + {'a', '_', 249}, /* ù */ + {'o', '_', 250}, /* ú */ + {'<', '<', 251}, /* û */ + {'x', 'x', 252}, /* ü */ + {'>', '>', 253}, /* ý */ + {'+', '-', 254}, /* þ */ + {'n', 'u', 255}, /* x XX */ + {NUL, NUL, NUL}}; + +# else /* !HPUX_DIGRAPHS */ + + +# ifdef OLD_DIGRAPHS + +/* + * digraphs compatible with Vim 5.x + */ +{{'~', '!', 161}, /* ¡ */ + {'c', '|', 162}, /* ¢ */ + {'$', '$', 163}, /* £ */ + {'o', 'x', 164}, /* ¤ - currency symbol in ISO 8859-1 */ + {'e', '=', 164}, /* ¤ - euro symbol in ISO 8859-15 */ + {'Y', '-', 165}, /* ¥ */ + {'|', '|', 166}, /* ¦ */ + {'p', 'a', 167}, /* § */ + {'"', '"', 168}, /* ¨ */ + {'c', 'O', 169}, /* © */ + {'a', '-', 170}, /* ª */ + {'<', '<', 171}, /* « */ + {'-', ',', 172}, /* ¬ */ + {'-', '-', 173}, /* ­ */ + {'r', 'O', 174}, /* ® */ + {'-', '=', 175}, /* ¯ */ + {'~', 'o', 176}, /* ° */ + {'+', '-', 177}, /* ± */ + {'2', '2', 178}, /* ² */ + {'3', '3', 179}, /* ³ */ + {'\'', '\'', 180}, /* ´ */ + {'j', 'u', 181}, /* µ */ + {'p', 'p', 182}, /* ¶ */ + {'~', '.', 183}, /* · */ + {',', ',', 184}, /* ¸ */ + {'1', '1', 185}, /* ¹ */ + {'o', '-', 186}, /* º */ + {'>', '>', 187}, /* » */ + {'1', '4', 188}, /* ¼ */ + {'1', '2', 189}, /* ½ */ + {'3', '4', 190}, /* ¾ */ + {'~', '?', 191}, /* ¿ */ + {'A', '`', 192}, /* À */ + {'A', '\'', 193}, /* Á */ + {'A', '^', 194}, /* Â */ + {'A', '~', 195}, /* Ã */ + {'A', '"', 196}, /* Ä */ + {'A', '@', 197}, /* Å */ + {'A', 'A', 197}, /* Å */ + {'A', 'E', 198}, /* Æ */ + {'C', ',', 199}, /* Ç */ + {'E', '`', 200}, /* È */ + {'E', '\'', 201}, /* É */ + {'E', '^', 202}, /* Ê */ + {'E', '"', 203}, /* Ë */ + {'I', '`', 204}, /* Ì */ + {'I', '\'', 205}, /* Í */ + {'I', '^', 206}, /* Î */ + {'I', '"', 207}, /* Ï */ + {'D', '-', 208}, /* Ð */ + {'N', '~', 209}, /* Ñ */ + {'O', '`', 210}, /* Ò */ + {'O', '\'', 211}, /* Ó */ + {'O', '^', 212}, /* Ô */ + {'O', '~', 213}, /* Õ */ + {'O', '"', 214}, /* Ö */ + {'/', '\\', 215}, /* × - multiplication symbol in ISO 8859-1 */ + {'O', 'E', 215}, /* × - OE in ISO 8859-15 */ + {'O', '/', 216}, /* Ø */ + {'U', '`', 217}, /* Ù */ + {'U', '\'', 218}, /* Ú */ + {'U', '^', 219}, /* Û */ + {'U', '"', 220}, /* Ü */ + {'Y', '\'', 221}, /* Ý */ + {'I', 'p', 222}, /* Þ */ + {'s', 's', 223}, /* ß */ + {'a', '`', 224}, /* à */ + {'a', '\'', 225}, /* á */ + {'a', '^', 226}, /* â */ + {'a', '~', 227}, /* ã */ + {'a', '"', 228}, /* ä */ + {'a', '@', 229}, /* å */ + {'a', 'a', 229}, /* å */ + {'a', 'e', 230}, /* æ */ + {'c', ',', 231}, /* ç */ + {'e', '`', 232}, /* è */ + {'e', '\'', 233}, /* é */ + {'e', '^', 234}, /* ê */ + {'e', '"', 235}, /* ë */ + {'i', '`', 236}, /* ì */ + {'i', '\'', 237}, /* í */ + {'i', '^', 238}, /* î */ + {'i', '"', 239}, /* ï */ + {'d', '-', 240}, /* ð */ + {'n', '~', 241}, /* ñ */ + {'o', '`', 242}, /* ò */ + {'o', '\'', 243}, /* ó */ + {'o', '^', 244}, /* ô */ + {'o', '~', 245}, /* õ */ + {'o', '"', 246}, /* ö */ + {':', '-', 247}, /* ÷ - division symbol in ISO 8859-1 */ + {'o', 'e', 247}, /* ÷ - oe in ISO 8859-15 */ + {'o', '/', 248}, /* ø */ + {'u', '`', 249}, /* ù */ + {'u', '\'', 250}, /* ú */ + {'u', '^', 251}, /* û */ + {'u', '"', 252}, /* ü */ + {'y', '\'', 253}, /* ý */ + {'i', 'p', 254}, /* þ */ + {'y', '"', 255}, /* x XX */ + {NUL, NUL, NUL}}; +# else /* OLD_DIGRAPHS */ + +/* + * digraphs for Unicode from RFC1345 + * (also work for ISO-8859-1 aka latin1) + */ +{ + {'N', 'U', 0x0a}, /* LF for NUL */ + {'S', 'H', 0x01}, + {'S', 'X', 0x02}, + {'E', 'X', 0x03}, + {'E', 'T', 0x04}, + {'E', 'Q', 0x05}, + {'A', 'K', 0x06}, + {'B', 'L', 0x07}, + {'B', 'S', 0x08}, + {'H', 'T', 0x09}, + {'L', 'F', 0x0a}, + {'V', 'T', 0x0b}, + {'F', 'F', 0x0c}, + {'C', 'R', 0x0d}, + {'S', 'O', 0x0e}, + {'S', 'I', 0x0f}, + {'D', 'L', 0x10}, + {'D', '1', 0x11}, + {'D', '2', 0x12}, + {'D', '3', 0x13}, + {'D', '4', 0x14}, + {'N', 'K', 0x15}, + {'S', 'Y', 0x16}, + {'E', 'B', 0x17}, + {'C', 'N', 0x18}, + {'E', 'M', 0x19}, + {'S', 'B', 0x1a}, + {'E', 'C', 0x1b}, + {'F', 'S', 0x1c}, + {'G', 'S', 0x1d}, + {'R', 'S', 0x1e}, + {'U', 'S', 0x1f}, + {'S', 'P', 0x20}, + {'N', 'b', 0x23}, + {'D', 'O', 0x24}, + {'A', 't', 0x40}, + {'<', '(', 0x5b}, + {'/', '/', 0x5c}, + {')', '>', 0x5d}, + {'\'', '>', 0x5e}, + {'\'', '!', 0x60}, + {'(', '!', 0x7b}, + {'!', '!', 0x7c}, + {'!', ')', 0x7d}, + {'\'', '?', 0x7e}, + {'D', 'T', 0x7f}, + {'P', 'A', 0x80}, + {'H', 'O', 0x81}, + {'B', 'H', 0x82}, + {'N', 'H', 0x83}, + {'I', 'N', 0x84}, + {'N', 'L', 0x85}, + {'S', 'A', 0x86}, + {'E', 'S', 0x87}, + {'H', 'S', 0x88}, + {'H', 'J', 0x89}, + {'V', 'S', 0x8a}, + {'P', 'D', 0x8b}, + {'P', 'U', 0x8c}, + {'R', 'I', 0x8d}, + {'S', '2', 0x8e}, + {'S', '3', 0x8f}, + {'D', 'C', 0x90}, + {'P', '1', 0x91}, + {'P', '2', 0x92}, + {'T', 'S', 0x93}, + {'C', 'C', 0x94}, + {'M', 'W', 0x95}, + {'S', 'G', 0x96}, + {'E', 'G', 0x97}, + {'S', 'S', 0x98}, + {'G', 'C', 0x99}, + {'S', 'C', 0x9a}, + {'C', 'I', 0x9b}, + {'S', 'T', 0x9c}, + {'O', 'C', 0x9d}, + {'P', 'M', 0x9e}, + {'A', 'C', 0x9f}, + {'N', 'S', 0xa0}, + {'!', 'I', 0xa1}, + {'C', 't', 0xa2}, + {'P', 'd', 0xa3}, + {'C', 'u', 0xa4}, + {'Y', 'e', 0xa5}, + {'B', 'B', 0xa6}, + {'S', 'E', 0xa7}, + {'\'', ':', 0xa8}, + {'C', 'o', 0xa9}, + {'-', 'a', 0xaa}, + {'<', '<', 0xab}, + {'N', 'O', 0xac}, + {'-', '-', 0xad}, + {'R', 'g', 0xae}, + {'\'', 'm', 0xaf}, + {'D', 'G', 0xb0}, + {'+', '-', 0xb1}, + {'2', 'S', 0xb2}, + {'3', 'S', 0xb3}, + {'\'', '\'', 0xb4}, + {'M', 'y', 0xb5}, + {'P', 'I', 0xb6}, + {'.', 'M', 0xb7}, + {'\'', ',', 0xb8}, + {'1', 'S', 0xb9}, + {'-', 'o', 0xba}, + {'>', '>', 0xbb}, + {'1', '4', 0xbc}, + {'1', '2', 0xbd}, + {'3', '4', 0xbe}, + {'?', 'I', 0xbf}, + {'A', '!', 0xc0}, + {'A', '\'', 0xc1}, + {'A', '>', 0xc2}, + {'A', '?', 0xc3}, + {'A', ':', 0xc4}, + {'A', 'A', 0xc5}, + {'A', 'E', 0xc6}, + {'C', ',', 0xc7}, + {'E', '!', 0xc8}, + {'E', '\'', 0xc9}, + {'E', '>', 0xca}, + {'E', ':', 0xcb}, + {'I', '!', 0xcc}, + {'I', '\'', 0xcd}, + {'I', '>', 0xce}, + {'I', ':', 0xcf}, + {'D', '-', 0xd0}, + {'N', '?', 0xd1}, + {'O', '!', 0xd2}, + {'O', '\'', 0xd3}, + {'O', '>', 0xd4}, + {'O', '?', 0xd5}, + {'O', ':', 0xd6}, + {'*', 'X', 0xd7}, + {'O', '/', 0xd8}, + {'U', '!', 0xd9}, + {'U', '\'', 0xda}, + {'U', '>', 0xdb}, + {'U', ':', 0xdc}, + {'Y', '\'', 0xdd}, + {'T', 'H', 0xde}, + {'s', 's', 0xdf}, + {'a', '!', 0xe0}, + {'a', '\'', 0xe1}, + {'a', '>', 0xe2}, + {'a', '?', 0xe3}, + {'a', ':', 0xe4}, + {'a', 'a', 0xe5}, + {'a', 'e', 0xe6}, + {'c', ',', 0xe7}, + {'e', '!', 0xe8}, + {'e', '\'', 0xe9}, + {'e', '>', 0xea}, + {'e', ':', 0xeb}, + {'i', '!', 0xec}, + {'i', '\'', 0xed}, + {'i', '>', 0xee}, + {'i', ':', 0xef}, + {'d', '-', 0xf0}, + {'n', '?', 0xf1}, + {'o', '!', 0xf2}, + {'o', '\'', 0xf3}, + {'o', '>', 0xf4}, + {'o', '?', 0xf5}, + {'o', ':', 0xf6}, + {'-', ':', 0xf7}, + {'o', '/', 0xf8}, + {'u', '!', 0xf9}, + {'u', '\'', 0xfa}, + {'u', '>', 0xfb}, + {'u', ':', 0xfc}, + {'y', '\'', 0xfd}, + {'t', 'h', 0xfe}, + {'y', ':', 0xff}, + +# define USE_UNICODE_DIGRAPHS + + {'A', '-', 0x0100}, + {'a', '-', 0x0101}, + {'A', '(', 0x0102}, + {'a', '(', 0x0103}, + {'A', ';', 0x0104}, + {'a', ';', 0x0105}, + {'C', '\'', 0x0106}, + {'c', '\'', 0x0107}, + {'C', '>', 0x0108}, + {'c', '>', 0x0109}, + {'C', '.', 0x010a}, + {'c', '.', 0x010b}, + {'C', '<', 0x010c}, + {'c', '<', 0x010d}, + {'D', '<', 0x010e}, + {'d', '<', 0x010f}, + {'D', '/', 0x0110}, + {'d', '/', 0x0111}, + {'E', '-', 0x0112}, + {'e', '-', 0x0113}, + {'E', '(', 0x0114}, + {'e', '(', 0x0115}, + {'E', '.', 0x0116}, + {'e', '.', 0x0117}, + {'E', ';', 0x0118}, + {'e', ';', 0x0119}, + {'E', '<', 0x011a}, + {'e', '<', 0x011b}, + {'G', '>', 0x011c}, + {'g', '>', 0x011d}, + {'G', '(', 0x011e}, + {'g', '(', 0x011f}, + {'G', '.', 0x0120}, + {'g', '.', 0x0121}, + {'G', ',', 0x0122}, + {'g', ',', 0x0123}, + {'H', '>', 0x0124}, + {'h', '>', 0x0125}, + {'H', '/', 0x0126}, + {'h', '/', 0x0127}, + {'I', '?', 0x0128}, + {'i', '?', 0x0129}, + {'I', '-', 0x012a}, + {'i', '-', 0x012b}, + {'I', '(', 0x012c}, + {'i', '(', 0x012d}, + {'I', ';', 0x012e}, + {'i', ';', 0x012f}, + {'I', '.', 0x0130}, + {'i', '.', 0x0131}, + {'I', 'J', 0x0132}, + {'i', 'j', 0x0133}, + {'J', '>', 0x0134}, + {'j', '>', 0x0135}, + {'K', ',', 0x0136}, + {'k', ',', 0x0137}, + {'k', 'k', 0x0138}, + {'L', '\'', 0x0139}, + {'l', '\'', 0x013a}, + {'L', ',', 0x013b}, + {'l', ',', 0x013c}, + {'L', '<', 0x013d}, + {'l', '<', 0x013e}, + {'L', '.', 0x013f}, + {'l', '.', 0x0140}, + {'L', '/', 0x0141}, + {'l', '/', 0x0142}, + {'N', '\'', 0x0143}, + {'n', '\'', 0x0144}, + {'N', ',', 0x0145}, + {'n', ',', 0x0146}, + {'N', '<', 0x0147}, + {'n', '<', 0x0148}, + {'\'', 'n', 0x0149}, + {'N', 'G', 0x014a}, + {'n', 'g', 0x014b}, + {'O', '-', 0x014c}, + {'o', '-', 0x014d}, + {'O', '(', 0x014e}, + {'o', '(', 0x014f}, + {'O', '"', 0x0150}, + {'o', '"', 0x0151}, + {'O', 'E', 0x0152}, + {'o', 'e', 0x0153}, + {'R', '\'', 0x0154}, + {'r', '\'', 0x0155}, + {'R', ',', 0x0156}, + {'r', ',', 0x0157}, + {'R', '<', 0x0158}, + {'r', '<', 0x0159}, + {'S', '\'', 0x015a}, + {'s', '\'', 0x015b}, + {'S', '>', 0x015c}, + {'s', '>', 0x015d}, + {'S', ',', 0x015e}, + {'s', ',', 0x015f}, + {'S', '<', 0x0160}, + {'s', '<', 0x0161}, + {'T', ',', 0x0162}, + {'t', ',', 0x0163}, + {'T', '<', 0x0164}, + {'t', '<', 0x0165}, + {'T', '/', 0x0166}, + {'t', '/', 0x0167}, + {'U', '?', 0x0168}, + {'u', '?', 0x0169}, + {'U', '-', 0x016a}, + {'u', '-', 0x016b}, + {'U', '(', 0x016c}, + {'u', '(', 0x016d}, + {'U', '0', 0x016e}, + {'u', '0', 0x016f}, + {'U', '"', 0x0170}, + {'u', '"', 0x0171}, + {'U', ';', 0x0172}, + {'u', ';', 0x0173}, + {'W', '>', 0x0174}, + {'w', '>', 0x0175}, + {'Y', '>', 0x0176}, + {'y', '>', 0x0177}, + {'Y', ':', 0x0178}, + {'Z', '\'', 0x0179}, + {'z', '\'', 0x017a}, + {'Z', '.', 0x017b}, + {'z', '.', 0x017c}, + {'Z', '<', 0x017d}, + {'z', '<', 0x017e}, + {'O', '9', 0x01a0}, + {'o', '9', 0x01a1}, + {'O', 'I', 0x01a2}, + {'o', 'i', 0x01a3}, + {'y', 'r', 0x01a6}, + {'U', '9', 0x01af}, + {'u', '9', 0x01b0}, + {'Z', '/', 0x01b5}, + {'z', '/', 0x01b6}, + {'E', 'D', 0x01b7}, + {'A', '<', 0x01cd}, + {'a', '<', 0x01ce}, + {'I', '<', 0x01cf}, + {'i', '<', 0x01d0}, + {'O', '<', 0x01d1}, + {'o', '<', 0x01d2}, + {'U', '<', 0x01d3}, + {'u', '<', 0x01d4}, + {'A', '1', 0x01de}, + {'a', '1', 0x01df}, + {'A', '7', 0x01e0}, + {'a', '7', 0x01e1}, + {'A', '3', 0x01e2}, + {'a', '3', 0x01e3}, + {'G', '/', 0x01e4}, + {'g', '/', 0x01e5}, + {'G', '<', 0x01e6}, + {'g', '<', 0x01e7}, + {'K', '<', 0x01e8}, + {'k', '<', 0x01e9}, + {'O', ';', 0x01ea}, + {'o', ';', 0x01eb}, + {'O', '1', 0x01ec}, + {'o', '1', 0x01ed}, + {'E', 'Z', 0x01ee}, + {'e', 'z', 0x01ef}, + {'j', '<', 0x01f0}, + {'G', '\'', 0x01f4}, + {'g', '\'', 0x01f5}, + {';', 'S', 0x02bf}, + {'\'', '<', 0x02c7}, + {'\'', '(', 0x02d8}, + {'\'', '.', 0x02d9}, + {'\'', '0', 0x02da}, + {'\'', ';', 0x02db}, + {'\'', '"', 0x02dd}, + {'A', '%', 0x0386}, + {'E', '%', 0x0388}, + {'Y', '%', 0x0389}, + {'I', '%', 0x038a}, + {'O', '%', 0x038c}, + {'U', '%', 0x038e}, + {'W', '%', 0x038f}, + {'i', '3', 0x0390}, + {'A', '*', 0x0391}, + {'B', '*', 0x0392}, + {'G', '*', 0x0393}, + {'D', '*', 0x0394}, + {'E', '*', 0x0395}, + {'Z', '*', 0x0396}, + {'Y', '*', 0x0397}, + {'H', '*', 0x0398}, + {'I', '*', 0x0399}, + {'K', '*', 0x039a}, + {'L', '*', 0x039b}, + {'M', '*', 0x039c}, + {'N', '*', 0x039d}, + {'C', '*', 0x039e}, + {'O', '*', 0x039f}, + {'P', '*', 0x03a0}, + {'R', '*', 0x03a1}, + {'S', '*', 0x03a3}, + {'T', '*', 0x03a4}, + {'U', '*', 0x03a5}, + {'F', '*', 0x03a6}, + {'X', '*', 0x03a7}, + {'Q', '*', 0x03a8}, + {'W', '*', 0x03a9}, + {'J', '*', 0x03aa}, + {'V', '*', 0x03ab}, + {'a', '%', 0x03ac}, + {'e', '%', 0x03ad}, + {'y', '%', 0x03ae}, + {'i', '%', 0x03af}, + {'u', '3', 0x03b0}, + {'a', '*', 0x03b1}, + {'b', '*', 0x03b2}, + {'g', '*', 0x03b3}, + {'d', '*', 0x03b4}, + {'e', '*', 0x03b5}, + {'z', '*', 0x03b6}, + {'y', '*', 0x03b7}, + {'h', '*', 0x03b8}, + {'i', '*', 0x03b9}, + {'k', '*', 0x03ba}, + {'l', '*', 0x03bb}, + {'m', '*', 0x03bc}, + {'n', '*', 0x03bd}, + {'c', '*', 0x03be}, + {'o', '*', 0x03bf}, + {'p', '*', 0x03c0}, + {'r', '*', 0x03c1}, + {'*', 's', 0x03c2}, + {'s', '*', 0x03c3}, + {'t', '*', 0x03c4}, + {'u', '*', 0x03c5}, + {'f', '*', 0x03c6}, + {'x', '*', 0x03c7}, + {'q', '*', 0x03c8}, + {'w', '*', 0x03c9}, + {'j', '*', 0x03ca}, + {'v', '*', 0x03cb}, + {'o', '%', 0x03cc}, + {'u', '%', 0x03cd}, + {'w', '%', 0x03ce}, + {'\'', 'G', 0x03d8}, + {',', 'G', 0x03d9}, + {'T', '3', 0x03da}, + {'t', '3', 0x03db}, + {'M', '3', 0x03dc}, + {'m', '3', 0x03dd}, + {'K', '3', 0x03de}, + {'k', '3', 0x03df}, + {'P', '3', 0x03e0}, + {'p', '3', 0x03e1}, + {'\'', '%', 0x03f4}, + {'j', '3', 0x03f5}, + {'I', 'O', 0x0401}, + {'D', '%', 0x0402}, + {'G', '%', 0x0403}, + {'I', 'E', 0x0404}, + {'D', 'S', 0x0405}, + {'I', 'I', 0x0406}, + {'Y', 'I', 0x0407}, + {'J', '%', 0x0408}, + {'L', 'J', 0x0409}, + {'N', 'J', 0x040a}, + {'T', 's', 0x040b}, + {'K', 'J', 0x040c}, + {'V', '%', 0x040e}, + {'D', 'Z', 0x040f}, + {'A', '=', 0x0410}, + {'B', '=', 0x0411}, + {'V', '=', 0x0412}, + {'G', '=', 0x0413}, + {'D', '=', 0x0414}, + {'E', '=', 0x0415}, + {'Z', '%', 0x0416}, + {'Z', '=', 0x0417}, + {'I', '=', 0x0418}, + {'J', '=', 0x0419}, + {'K', '=', 0x041a}, + {'L', '=', 0x041b}, + {'M', '=', 0x041c}, + {'N', '=', 0x041d}, + {'O', '=', 0x041e}, + {'P', '=', 0x041f}, + {'R', '=', 0x0420}, + {'S', '=', 0x0421}, + {'T', '=', 0x0422}, + {'U', '=', 0x0423}, + {'F', '=', 0x0424}, + {'H', '=', 0x0425}, + {'C', '=', 0x0426}, + {'C', '%', 0x0427}, + {'S', '%', 0x0428}, + {'S', 'c', 0x0429}, + {'=', '"', 0x042a}, + {'Y', '=', 0x042b}, + {'%', '"', 0x042c}, + {'J', 'E', 0x042d}, + {'J', 'U', 0x042e}, + {'J', 'A', 0x042f}, + {'a', '=', 0x0430}, + {'b', '=', 0x0431}, + {'v', '=', 0x0432}, + {'g', '=', 0x0433}, + {'d', '=', 0x0434}, + {'e', '=', 0x0435}, + {'z', '%', 0x0436}, + {'z', '=', 0x0437}, + {'i', '=', 0x0438}, + {'j', '=', 0x0439}, + {'k', '=', 0x043a}, + {'l', '=', 0x043b}, + {'m', '=', 0x043c}, + {'n', '=', 0x043d}, + {'o', '=', 0x043e}, + {'p', '=', 0x043f}, + {'r', '=', 0x0440}, + {'s', '=', 0x0441}, + {'t', '=', 0x0442}, + {'u', '=', 0x0443}, + {'f', '=', 0x0444}, + {'h', '=', 0x0445}, + {'c', '=', 0x0446}, + {'c', '%', 0x0447}, + {'s', '%', 0x0448}, + {'s', 'c', 0x0449}, + {'=', '\'', 0x044a}, + {'y', '=', 0x044b}, + {'%', '\'', 0x044c}, + {'j', 'e', 0x044d}, + {'j', 'u', 0x044e}, + {'j', 'a', 0x044f}, + {'i', 'o', 0x0451}, + {'d', '%', 0x0452}, + {'g', '%', 0x0453}, + {'i', 'e', 0x0454}, + {'d', 's', 0x0455}, + {'i', 'i', 0x0456}, + {'y', 'i', 0x0457}, + {'j', '%', 0x0458}, + {'l', 'j', 0x0459}, + {'n', 'j', 0x045a}, + {'t', 's', 0x045b}, + {'k', 'j', 0x045c}, + {'v', '%', 0x045e}, + {'d', 'z', 0x045f}, + {'Y', '3', 0x0462}, + {'y', '3', 0x0463}, + {'O', '3', 0x046a}, + {'o', '3', 0x046b}, + {'F', '3', 0x0472}, + {'f', '3', 0x0473}, + {'V', '3', 0x0474}, + {'v', '3', 0x0475}, + {'C', '3', 0x0480}, + {'c', '3', 0x0481}, + {'G', '3', 0x0490}, + {'g', '3', 0x0491}, + {'A', '+', 0x05d0}, + {'B', '+', 0x05d1}, + {'G', '+', 0x05d2}, + {'D', '+', 0x05d3}, + {'H', '+', 0x05d4}, + {'W', '+', 0x05d5}, + {'Z', '+', 0x05d6}, + {'X', '+', 0x05d7}, + {'T', 'j', 0x05d8}, + {'J', '+', 0x05d9}, + {'K', '%', 0x05da}, + {'K', '+', 0x05db}, + {'L', '+', 0x05dc}, + {'M', '%', 0x05dd}, + {'M', '+', 0x05de}, + {'N', '%', 0x05df}, + {'N', '+', 0x05e0}, + {'S', '+', 0x05e1}, + {'E', '+', 0x05e2}, + {'P', '%', 0x05e3}, + {'P', '+', 0x05e4}, + {'Z', 'j', 0x05e5}, + {'Z', 'J', 0x05e6}, + {'Q', '+', 0x05e7}, + {'R', '+', 0x05e8}, + {'S', 'h', 0x05e9}, + {'T', '+', 0x05ea}, + {',', '+', 0x060c}, + {';', '+', 0x061b}, + {'?', '+', 0x061f}, + {'H', '\'', 0x0621}, + {'a', 'M', 0x0622}, + {'a', 'H', 0x0623}, + {'w', 'H', 0x0624}, + {'a', 'h', 0x0625}, + {'y', 'H', 0x0626}, + {'a', '+', 0x0627}, + {'b', '+', 0x0628}, + {'t', 'm', 0x0629}, + {'t', '+', 0x062a}, + {'t', 'k', 0x062b}, + {'g', '+', 0x062c}, + {'h', 'k', 0x062d}, + {'x', '+', 0x062e}, + {'d', '+', 0x062f}, + {'d', 'k', 0x0630}, + {'r', '+', 0x0631}, + {'z', '+', 0x0632}, + {'s', '+', 0x0633}, + {'s', 'n', 0x0634}, + {'c', '+', 0x0635}, + {'d', 'd', 0x0636}, + {'t', 'j', 0x0637}, + {'z', 'H', 0x0638}, + {'e', '+', 0x0639}, + {'i', '+', 0x063a}, + {'+', '+', 0x0640}, + {'f', '+', 0x0641}, + {'q', '+', 0x0642}, + {'k', '+', 0x0643}, + {'l', '+', 0x0644}, + {'m', '+', 0x0645}, + {'n', '+', 0x0646}, + {'h', '+', 0x0647}, + {'w', '+', 0x0648}, + {'j', '+', 0x0649}, + {'y', '+', 0x064a}, + {':', '+', 0x064b}, + {'"', '+', 0x064c}, + {'=', '+', 0x064d}, + {'/', '+', 0x064e}, + {'\'', '+', 0x064f}, + {'1', '+', 0x0650}, + {'3', '+', 0x0651}, + {'0', '+', 0x0652}, + {'a', 'S', 0x0670}, + {'p', '+', 0x067e}, + {'v', '+', 0x06a4}, + {'g', 'f', 0x06af}, + {'0', 'a', 0x06f0}, + {'1', 'a', 0x06f1}, + {'2', 'a', 0x06f2}, + {'3', 'a', 0x06f3}, + {'4', 'a', 0x06f4}, + {'5', 'a', 0x06f5}, + {'6', 'a', 0x06f6}, + {'7', 'a', 0x06f7}, + {'8', 'a', 0x06f8}, + {'9', 'a', 0x06f9}, + {'B', '.', 0x1e02}, + {'b', '.', 0x1e03}, + {'B', '_', 0x1e06}, + {'b', '_', 0x1e07}, + {'D', '.', 0x1e0a}, + {'d', '.', 0x1e0b}, + {'D', '_', 0x1e0e}, + {'d', '_', 0x1e0f}, + {'D', ',', 0x1e10}, + {'d', ',', 0x1e11}, + {'F', '.', 0x1e1e}, + {'f', '.', 0x1e1f}, + {'G', '-', 0x1e20}, + {'g', '-', 0x1e21}, + {'H', '.', 0x1e22}, + {'h', '.', 0x1e23}, + {'H', ':', 0x1e26}, + {'h', ':', 0x1e27}, + {'H', ',', 0x1e28}, + {'h', ',', 0x1e29}, + {'K', '\'', 0x1e30}, + {'k', '\'', 0x1e31}, + {'K', '_', 0x1e34}, + {'k', '_', 0x1e35}, + {'L', '_', 0x1e3a}, + {'l', '_', 0x1e3b}, + {'M', '\'', 0x1e3e}, + {'m', '\'', 0x1e3f}, + {'M', '.', 0x1e40}, + {'m', '.', 0x1e41}, + {'N', '.', 0x1e44}, + {'n', '.', 0x1e45}, + {'N', '_', 0x1e48}, + {'n', '_', 0x1e49}, + {'P', '\'', 0x1e54}, + {'p', '\'', 0x1e55}, + {'P', '.', 0x1e56}, + {'p', '.', 0x1e57}, + {'R', '.', 0x1e58}, + {'r', '.', 0x1e59}, + {'R', '_', 0x1e5e}, + {'r', '_', 0x1e5f}, + {'S', '.', 0x1e60}, + {'s', '.', 0x1e61}, + {'T', '.', 0x1e6a}, + {'t', '.', 0x1e6b}, + {'T', '_', 0x1e6e}, + {'t', '_', 0x1e6f}, + {'V', '?', 0x1e7c}, + {'v', '?', 0x1e7d}, + {'W', '!', 0x1e80}, + {'w', '!', 0x1e81}, + {'W', '\'', 0x1e82}, + {'w', '\'', 0x1e83}, + {'W', ':', 0x1e84}, + {'w', ':', 0x1e85}, + {'W', '.', 0x1e86}, + {'w', '.', 0x1e87}, + {'X', '.', 0x1e8a}, + {'x', '.', 0x1e8b}, + {'X', ':', 0x1e8c}, + {'x', ':', 0x1e8d}, + {'Y', '.', 0x1e8e}, + {'y', '.', 0x1e8f}, + {'Z', '>', 0x1e90}, + {'z', '>', 0x1e91}, + {'Z', '_', 0x1e94}, + {'z', '_', 0x1e95}, + {'h', '_', 0x1e96}, + {'t', ':', 0x1e97}, + {'w', '0', 0x1e98}, + {'y', '0', 0x1e99}, + {'A', '2', 0x1ea2}, + {'a', '2', 0x1ea3}, + {'E', '2', 0x1eba}, + {'e', '2', 0x1ebb}, + {'E', '?', 0x1ebc}, + {'e', '?', 0x1ebd}, + {'I', '2', 0x1ec8}, + {'i', '2', 0x1ec9}, + {'O', '2', 0x1ece}, + {'o', '2', 0x1ecf}, + {'U', '2', 0x1ee6}, + {'u', '2', 0x1ee7}, + {'Y', '!', 0x1ef2}, + {'y', '!', 0x1ef3}, + {'Y', '2', 0x1ef6}, + {'y', '2', 0x1ef7}, + {'Y', '?', 0x1ef8}, + {'y', '?', 0x1ef9}, + {';', '\'', 0x1f00}, + {',', '\'', 0x1f01}, + {';', '!', 0x1f02}, + {',', '!', 0x1f03}, + {'?', ';', 0x1f04}, + {'?', ',', 0x1f05}, + {'!', ':', 0x1f06}, + {'?', ':', 0x1f07}, + {'1', 'N', 0x2002}, + {'1', 'M', 0x2003}, + {'3', 'M', 0x2004}, + {'4', 'M', 0x2005}, + {'6', 'M', 0x2006}, + {'1', 'T', 0x2009}, + {'1', 'H', 0x200a}, + {'-', '1', 0x2010}, + {'-', 'N', 0x2013}, + {'-', 'M', 0x2014}, + {'-', '3', 0x2015}, + {'!', '2', 0x2016}, + {'=', '2', 0x2017}, + {'\'', '6', 0x2018}, + {'\'', '9', 0x2019}, + {'.', '9', 0x201a}, + {'9', '\'', 0x201b}, + {'"', '6', 0x201c}, + {'"', '9', 0x201d}, + {':', '9', 0x201e}, + {'9', '"', 0x201f}, + {'/', '-', 0x2020}, + {'/', '=', 0x2021}, + {'.', '.', 0x2025}, + {'%', '0', 0x2030}, + {'1', '\'', 0x2032}, + {'2', '\'', 0x2033}, + {'3', '\'', 0x2034}, + {'1', '"', 0x2035}, + {'2', '"', 0x2036}, + {'3', '"', 0x2037}, + {'C', 'a', 0x2038}, + {'<', '1', 0x2039}, + {'>', '1', 0x203a}, + {':', 'X', 0x203b}, + {'\'', '-', 0x203e}, + {'/', 'f', 0x2044}, + {'0', 'S', 0x2070}, + {'4', 'S', 0x2074}, + {'5', 'S', 0x2075}, + {'6', 'S', 0x2076}, + {'7', 'S', 0x2077}, + {'8', 'S', 0x2078}, + {'9', 'S', 0x2079}, + {'+', 'S', 0x207a}, + {'-', 'S', 0x207b}, + {'=', 'S', 0x207c}, + {'(', 'S', 0x207d}, + {')', 'S', 0x207e}, + {'n', 'S', 0x207f}, + {'0', 's', 0x2080}, + {'1', 's', 0x2081}, + {'2', 's', 0x2082}, + {'3', 's', 0x2083}, + {'4', 's', 0x2084}, + {'5', 's', 0x2085}, + {'6', 's', 0x2086}, + {'7', 's', 0x2087}, + {'8', 's', 0x2088}, + {'9', 's', 0x2089}, + {'+', 's', 0x208a}, + {'-', 's', 0x208b}, + {'=', 's', 0x208c}, + {'(', 's', 0x208d}, + {')', 's', 0x208e}, + {'L', 'i', 0x20a4}, + {'P', 't', 0x20a7}, + {'W', '=', 0x20a9}, + {'=', 'e', 0x20ac}, /* euro */ + {'E', 'u', 0x20ac}, /* euro */ + {'o', 'C', 0x2103}, + {'c', 'o', 0x2105}, + {'o', 'F', 0x2109}, + {'N', '0', 0x2116}, + {'P', 'O', 0x2117}, + {'R', 'x', 0x211e}, + {'S', 'M', 0x2120}, + {'T', 'M', 0x2122}, + {'O', 'm', 0x2126}, + {'A', 'O', 0x212b}, + {'1', '3', 0x2153}, + {'2', '3', 0x2154}, + {'1', '5', 0x2155}, + {'2', '5', 0x2156}, + {'3', '5', 0x2157}, + {'4', '5', 0x2158}, + {'1', '6', 0x2159}, + {'5', '6', 0x215a}, + {'1', '8', 0x215b}, + {'3', '8', 0x215c}, + {'5', '8', 0x215d}, + {'7', '8', 0x215e}, + {'1', 'R', 0x2160}, + {'2', 'R', 0x2161}, + {'3', 'R', 0x2162}, + {'4', 'R', 0x2163}, + {'5', 'R', 0x2164}, + {'6', 'R', 0x2165}, + {'7', 'R', 0x2166}, + {'8', 'R', 0x2167}, + {'9', 'R', 0x2168}, + {'a', 'R', 0x2169}, + {'b', 'R', 0x216a}, + {'c', 'R', 0x216b}, + {'1', 'r', 0x2170}, + {'2', 'r', 0x2171}, + {'3', 'r', 0x2172}, + {'4', 'r', 0x2173}, + {'5', 'r', 0x2174}, + {'6', 'r', 0x2175}, + {'7', 'r', 0x2176}, + {'8', 'r', 0x2177}, + {'9', 'r', 0x2178}, + {'a', 'r', 0x2179}, + {'b', 'r', 0x217a}, + {'c', 'r', 0x217b}, + {'<', '-', 0x2190}, + {'-', '!', 0x2191}, + {'-', '>', 0x2192}, + {'-', 'v', 0x2193}, + {'<', '>', 0x2194}, + {'U', 'D', 0x2195}, + {'<', '=', 0x21d0}, + {'=', '>', 0x21d2}, + {'=', '=', 0x21d4}, + {'F', 'A', 0x2200}, + {'d', 'P', 0x2202}, + {'T', 'E', 0x2203}, + {'/', '0', 0x2205}, + {'D', 'E', 0x2206}, + {'N', 'B', 0x2207}, + {'(', '-', 0x2208}, + {'-', ')', 0x220b}, + {'*', 'P', 0x220f}, + {'+', 'Z', 0x2211}, + {'-', '2', 0x2212}, + {'-', '+', 0x2213}, + {'*', '-', 0x2217}, + {'O', 'b', 0x2218}, + {'S', 'b', 0x2219}, + {'R', 'T', 0x221a}, + {'0', '(', 0x221d}, + {'0', '0', 0x221e}, + {'-', 'L', 0x221f}, + {'-', 'V', 0x2220}, + {'P', 'P', 0x2225}, + {'A', 'N', 0x2227}, + {'O', 'R', 0x2228}, + {'(', 'U', 0x2229}, + {')', 'U', 0x222a}, + {'I', 'n', 0x222b}, + {'D', 'I', 0x222c}, + {'I', 'o', 0x222e}, + {'.', ':', 0x2234}, + {':', '.', 0x2235}, + {':', 'R', 0x2236}, + {':', ':', 0x2237}, + {'?', '1', 0x223c}, + {'C', 'G', 0x223e}, + {'?', '-', 0x2243}, + {'?', '=', 0x2245}, + {'?', '2', 0x2248}, + {'=', '?', 0x224c}, + {'H', 'I', 0x2253}, + {'!', '=', 0x2260}, + {'=', '3', 0x2261}, + {'=', '<', 0x2264}, + {'>', '=', 0x2265}, + {'<', '*', 0x226a}, + {'*', '>', 0x226b}, + {'!', '<', 0x226e}, + {'!', '>', 0x226f}, + {'(', 'C', 0x2282}, + {')', 'C', 0x2283}, + {'(', '_', 0x2286}, + {')', '_', 0x2287}, + {'0', '.', 0x2299}, + {'0', '2', 0x229a}, + {'-', 'T', 0x22a5}, + {'.', 'P', 0x22c5}, + {':', '3', 0x22ee}, + {'.', '3', 0x22ef}, + {'E', 'h', 0x2302}, + {'<', '7', 0x2308}, + {'>', '7', 0x2309}, + {'7', '<', 0x230a}, + {'7', '>', 0x230b}, + {'N', 'I', 0x2310}, + {'(', 'A', 0x2312}, + {'T', 'R', 0x2315}, + {'I', 'u', 0x2320}, + {'I', 'l', 0x2321}, + {'<', '/', 0x2329}, + {'/', '>', 0x232a}, + {'V', 's', 0x2423}, + {'1', 'h', 0x2440}, + {'3', 'h', 0x2441}, + {'2', 'h', 0x2442}, + {'4', 'h', 0x2443}, + {'1', 'j', 0x2446}, + {'2', 'j', 0x2447}, + {'3', 'j', 0x2448}, + {'4', 'j', 0x2449}, + {'1', '.', 0x2488}, + {'2', '.', 0x2489}, + {'3', '.', 0x248a}, + {'4', '.', 0x248b}, + {'5', '.', 0x248c}, + {'6', '.', 0x248d}, + {'7', '.', 0x248e}, + {'8', '.', 0x248f}, + {'9', '.', 0x2490}, + {'h', 'h', 0x2500}, + {'H', 'H', 0x2501}, + {'v', 'v', 0x2502}, + {'V', 'V', 0x2503}, + {'3', '-', 0x2504}, + {'3', '_', 0x2505}, + {'3', '!', 0x2506}, + {'3', '/', 0x2507}, + {'4', '-', 0x2508}, + {'4', '_', 0x2509}, + {'4', '!', 0x250a}, + {'4', '/', 0x250b}, + {'d', 'r', 0x250c}, + {'d', 'R', 0x250d}, + {'D', 'r', 0x250e}, + {'D', 'R', 0x250f}, + {'d', 'l', 0x2510}, + {'d', 'L', 0x2511}, + {'D', 'l', 0x2512}, + {'L', 'D', 0x2513}, + {'u', 'r', 0x2514}, + {'u', 'R', 0x2515}, + {'U', 'r', 0x2516}, + {'U', 'R', 0x2517}, + {'u', 'l', 0x2518}, + {'u', 'L', 0x2519}, + {'U', 'l', 0x251a}, + {'U', 'L', 0x251b}, + {'v', 'r', 0x251c}, + {'v', 'R', 0x251d}, + {'V', 'r', 0x2520}, + {'V', 'R', 0x2523}, + {'v', 'l', 0x2524}, + {'v', 'L', 0x2525}, + {'V', 'l', 0x2528}, + {'V', 'L', 0x252b}, + {'d', 'h', 0x252c}, + {'d', 'H', 0x252f}, + {'D', 'h', 0x2530}, + {'D', 'H', 0x2533}, + {'u', 'h', 0x2534}, + {'u', 'H', 0x2537}, + {'U', 'h', 0x2538}, + {'U', 'H', 0x253b}, + {'v', 'h', 0x253c}, + {'v', 'H', 0x253f}, + {'V', 'h', 0x2542}, + {'V', 'H', 0x254b}, + {'F', 'D', 0x2571}, + {'B', 'D', 0x2572}, + {'T', 'B', 0x2580}, + {'L', 'B', 0x2584}, + {'F', 'B', 0x2588}, + {'l', 'B', 0x258c}, + {'R', 'B', 0x2590}, + {'.', 'S', 0x2591}, + {':', 'S', 0x2592}, + {'?', 'S', 0x2593}, + {'f', 'S', 0x25a0}, + {'O', 'S', 0x25a1}, + {'R', 'O', 0x25a2}, + {'R', 'r', 0x25a3}, + {'R', 'F', 0x25a4}, + {'R', 'Y', 0x25a5}, + {'R', 'H', 0x25a6}, + {'R', 'Z', 0x25a7}, + {'R', 'K', 0x25a8}, + {'R', 'X', 0x25a9}, + {'s', 'B', 0x25aa}, + {'S', 'R', 0x25ac}, + {'O', 'r', 0x25ad}, + {'U', 'T', 0x25b2}, + {'u', 'T', 0x25b3}, + {'P', 'R', 0x25b6}, + {'T', 'r', 0x25b7}, + {'D', 't', 0x25bc}, + {'d', 'T', 0x25bd}, + {'P', 'L', 0x25c0}, + {'T', 'l', 0x25c1}, + {'D', 'b', 0x25c6}, + {'D', 'w', 0x25c7}, + {'L', 'Z', 0x25ca}, + {'0', 'm', 0x25cb}, + {'0', 'o', 0x25ce}, + {'0', 'M', 0x25cf}, + {'0', 'L', 0x25d0}, + {'0', 'R', 0x25d1}, + {'S', 'n', 0x25d8}, + {'I', 'c', 0x25d9}, + {'F', 'd', 0x25e2}, + {'B', 'd', 0x25e3}, + {'*', '2', 0x2605}, + {'*', '1', 0x2606}, + {'<', 'H', 0x261c}, + {'>', 'H', 0x261e}, + {'0', 'u', 0x263a}, + {'0', 'U', 0x263b}, + {'S', 'U', 0x263c}, + {'F', 'm', 0x2640}, + {'M', 'l', 0x2642}, + {'c', 'S', 0x2660}, + {'c', 'H', 0x2661}, + {'c', 'D', 0x2662}, + {'c', 'C', 0x2663}, + {'M', 'd', 0x2669}, + {'M', '8', 0x266a}, + {'M', '2', 0x266b}, + {'M', 'b', 0x266d}, + {'M', 'x', 0x266e}, + {'M', 'X', 0x266f}, + {'O', 'K', 0x2713}, + {'X', 'X', 0x2717}, + {'-', 'X', 0x2720}, + {'I', 'S', 0x3000}, + {',', '_', 0x3001}, + {'.', '_', 0x3002}, + {'+', '"', 0x3003}, + {'+', '_', 0x3004}, + {'*', '_', 0x3005}, + {';', '_', 0x3006}, + {'0', '_', 0x3007}, + {'<', '+', 0x300a}, + {'>', '+', 0x300b}, + {'<', '\'', 0x300c}, + {'>', '\'', 0x300d}, + {'<', '"', 0x300e}, + {'>', '"', 0x300f}, + {'(', '"', 0x3010}, + {')', '"', 0x3011}, + {'=', 'T', 0x3012}, + {'=', '_', 0x3013}, + {'(', '\'', 0x3014}, + {')', '\'', 0x3015}, + {'(', 'I', 0x3016}, + {')', 'I', 0x3017}, + {'-', '?', 0x301c}, + {'A', '5', 0x3041}, + {'a', '5', 0x3042}, + {'I', '5', 0x3043}, + {'i', '5', 0x3044}, + {'U', '5', 0x3045}, + {'u', '5', 0x3046}, + {'E', '5', 0x3047}, + {'e', '5', 0x3048}, + {'O', '5', 0x3049}, + {'o', '5', 0x304a}, + {'k', 'a', 0x304b}, + {'g', 'a', 0x304c}, + {'k', 'i', 0x304d}, + {'g', 'i', 0x304e}, + {'k', 'u', 0x304f}, + {'g', 'u', 0x3050}, + {'k', 'e', 0x3051}, + {'g', 'e', 0x3052}, + {'k', 'o', 0x3053}, + {'g', 'o', 0x3054}, + {'s', 'a', 0x3055}, + {'z', 'a', 0x3056}, + {'s', 'i', 0x3057}, + {'z', 'i', 0x3058}, + {'s', 'u', 0x3059}, + {'z', 'u', 0x305a}, + {'s', 'e', 0x305b}, + {'z', 'e', 0x305c}, + {'s', 'o', 0x305d}, + {'z', 'o', 0x305e}, + {'t', 'a', 0x305f}, + {'d', 'a', 0x3060}, + {'t', 'i', 0x3061}, + {'d', 'i', 0x3062}, + {'t', 'U', 0x3063}, + {'t', 'u', 0x3064}, + {'d', 'u', 0x3065}, + {'t', 'e', 0x3066}, + {'d', 'e', 0x3067}, + {'t', 'o', 0x3068}, + {'d', 'o', 0x3069}, + {'n', 'a', 0x306a}, + {'n', 'i', 0x306b}, + {'n', 'u', 0x306c}, + {'n', 'e', 0x306d}, + {'n', 'o', 0x306e}, + {'h', 'a', 0x306f}, + {'b', 'a', 0x3070}, + {'p', 'a', 0x3071}, + {'h', 'i', 0x3072}, + {'b', 'i', 0x3073}, + {'p', 'i', 0x3074}, + {'h', 'u', 0x3075}, + {'b', 'u', 0x3076}, + {'p', 'u', 0x3077}, + {'h', 'e', 0x3078}, + {'b', 'e', 0x3079}, + {'p', 'e', 0x307a}, + {'h', 'o', 0x307b}, + {'b', 'o', 0x307c}, + {'p', 'o', 0x307d}, + {'m', 'a', 0x307e}, + {'m', 'i', 0x307f}, + {'m', 'u', 0x3080}, + {'m', 'e', 0x3081}, + {'m', 'o', 0x3082}, + {'y', 'A', 0x3083}, + {'y', 'a', 0x3084}, + {'y', 'U', 0x3085}, + {'y', 'u', 0x3086}, + {'y', 'O', 0x3087}, + {'y', 'o', 0x3088}, + {'r', 'a', 0x3089}, + {'r', 'i', 0x308a}, + {'r', 'u', 0x308b}, + {'r', 'e', 0x308c}, + {'r', 'o', 0x308d}, + {'w', 'A', 0x308e}, + {'w', 'a', 0x308f}, + {'w', 'i', 0x3090}, + {'w', 'e', 0x3091}, + {'w', 'o', 0x3092}, + {'n', '5', 0x3093}, + {'v', 'u', 0x3094}, + {'"', '5', 0x309b}, + {'0', '5', 0x309c}, + {'*', '5', 0x309d}, + {'+', '5', 0x309e}, + {'a', '6', 0x30a1}, + {'A', '6', 0x30a2}, + {'i', '6', 0x30a3}, + {'I', '6', 0x30a4}, + {'u', '6', 0x30a5}, + {'U', '6', 0x30a6}, + {'e', '6', 0x30a7}, + {'E', '6', 0x30a8}, + {'o', '6', 0x30a9}, + {'O', '6', 0x30aa}, + {'K', 'a', 0x30ab}, + {'G', 'a', 0x30ac}, + {'K', 'i', 0x30ad}, + {'G', 'i', 0x30ae}, + {'K', 'u', 0x30af}, + {'G', 'u', 0x30b0}, + {'K', 'e', 0x30b1}, + {'G', 'e', 0x30b2}, + {'K', 'o', 0x30b3}, + {'G', 'o', 0x30b4}, + {'S', 'a', 0x30b5}, + {'Z', 'a', 0x30b6}, + {'S', 'i', 0x30b7}, + {'Z', 'i', 0x30b8}, + {'S', 'u', 0x30b9}, + {'Z', 'u', 0x30ba}, + {'S', 'e', 0x30bb}, + {'Z', 'e', 0x30bc}, + {'S', 'o', 0x30bd}, + {'Z', 'o', 0x30be}, + {'T', 'a', 0x30bf}, + {'D', 'a', 0x30c0}, + {'T', 'i', 0x30c1}, + {'D', 'i', 0x30c2}, + {'T', 'U', 0x30c3}, + {'T', 'u', 0x30c4}, + {'D', 'u', 0x30c5}, + {'T', 'e', 0x30c6}, + {'D', 'e', 0x30c7}, + {'T', 'o', 0x30c8}, + {'D', 'o', 0x30c9}, + {'N', 'a', 0x30ca}, + {'N', 'i', 0x30cb}, + {'N', 'u', 0x30cc}, + {'N', 'e', 0x30cd}, + {'N', 'o', 0x30ce}, + {'H', 'a', 0x30cf}, + {'B', 'a', 0x30d0}, + {'P', 'a', 0x30d1}, + {'H', 'i', 0x30d2}, + {'B', 'i', 0x30d3}, + {'P', 'i', 0x30d4}, + {'H', 'u', 0x30d5}, + {'B', 'u', 0x30d6}, + {'P', 'u', 0x30d7}, + {'H', 'e', 0x30d8}, + {'B', 'e', 0x30d9}, + {'P', 'e', 0x30da}, + {'H', 'o', 0x30db}, + {'B', 'o', 0x30dc}, + {'P', 'o', 0x30dd}, + {'M', 'a', 0x30de}, + {'M', 'i', 0x30df}, + {'M', 'u', 0x30e0}, + {'M', 'e', 0x30e1}, + {'M', 'o', 0x30e2}, + {'Y', 'A', 0x30e3}, + {'Y', 'a', 0x30e4}, + {'Y', 'U', 0x30e5}, + {'Y', 'u', 0x30e6}, + {'Y', 'O', 0x30e7}, + {'Y', 'o', 0x30e8}, + {'R', 'a', 0x30e9}, + {'R', 'i', 0x30ea}, + {'R', 'u', 0x30eb}, + {'R', 'e', 0x30ec}, + {'R', 'o', 0x30ed}, + {'W', 'A', 0x30ee}, + {'W', 'a', 0x30ef}, + {'W', 'i', 0x30f0}, + {'W', 'e', 0x30f1}, + {'W', 'o', 0x30f2}, + {'N', '6', 0x30f3}, + {'V', 'u', 0x30f4}, + {'K', 'A', 0x30f5}, + {'K', 'E', 0x30f6}, + {'V', 'a', 0x30f7}, + {'V', 'i', 0x30f8}, + {'V', 'e', 0x30f9}, + {'V', 'o', 0x30fa}, + {'.', '6', 0x30fb}, + {'-', '6', 0x30fc}, + {'*', '6', 0x30fd}, + {'+', '6', 0x30fe}, + {'b', '4', 0x3105}, + {'p', '4', 0x3106}, + {'m', '4', 0x3107}, + {'f', '4', 0x3108}, + {'d', '4', 0x3109}, + {'t', '4', 0x310a}, + {'n', '4', 0x310b}, + {'l', '4', 0x310c}, + {'g', '4', 0x310d}, + {'k', '4', 0x310e}, + {'h', '4', 0x310f}, + {'j', '4', 0x3110}, + {'q', '4', 0x3111}, + {'x', '4', 0x3112}, + {'z', 'h', 0x3113}, + {'c', 'h', 0x3114}, + {'s', 'h', 0x3115}, + {'r', '4', 0x3116}, + {'z', '4', 0x3117}, + {'c', '4', 0x3118}, + {'s', '4', 0x3119}, + {'a', '4', 0x311a}, + {'o', '4', 0x311b}, + {'e', '4', 0x311c}, + {'a', 'i', 0x311e}, + {'e', 'i', 0x311f}, + {'a', 'u', 0x3120}, + {'o', 'u', 0x3121}, + {'a', 'n', 0x3122}, + {'e', 'n', 0x3123}, + {'a', 'N', 0x3124}, + {'e', 'N', 0x3125}, + {'e', 'r', 0x3126}, + {'i', '4', 0x3127}, + {'u', '4', 0x3128}, + {'i', 'u', 0x3129}, + {'v', '4', 0x312a}, + {'n', 'G', 0x312b}, + {'g', 'n', 0x312c}, + {'1', 'c', 0x3220}, + {'2', 'c', 0x3221}, + {'3', 'c', 0x3222}, + {'4', 'c', 0x3223}, + {'5', 'c', 0x3224}, + {'6', 'c', 0x3225}, + {'7', 'c', 0x3226}, + {'8', 'c', 0x3227}, + {'9', 'c', 0x3228}, + /* code points 0xe000 - 0xefff excluded, they have no assigned + * characters, only used in proposals. */ + {'f', 'f', 0xfb00}, + {'f', 'i', 0xfb01}, + {'f', 'l', 0xfb02}, + {'f', 't', 0xfb05}, + {'s', 't', 0xfb06}, + + /* Vim 5.x compatible digraphs that don't conflict with the above */ + {'~', '!', 161}, /* ¡ */ + {'c', '|', 162}, /* ¢ */ + {'$', '$', 163}, /* £ */ + {'o', 'x', 164}, /* ¤ - currency symbol in ISO 8859-1 */ + {'Y', '-', 165}, /* ¥ */ + {'|', '|', 166}, /* ¦ */ + {'c', 'O', 169}, /* © */ + {'-', ',', 172}, /* ¬ */ + {'-', '=', 175}, /* ¯ */ + {'~', 'o', 176}, /* ° */ + {'2', '2', 178}, /* ² */ + {'3', '3', 179}, /* ³ */ + {'p', 'p', 182}, /* ¶ */ + {'~', '.', 183}, /* · */ + {'1', '1', 185}, /* ¹ */ + {'~', '?', 191}, /* ¿ */ + {'A', '`', 192}, /* À */ + {'A', '^', 194}, /* Â */ + {'A', '~', 195}, /* Ã */ + {'A', '"', 196}, /* Ä */ + {'A', '@', 197}, /* Å */ + {'E', '`', 200}, /* È */ + {'E', '^', 202}, /* Ê */ + {'E', '"', 203}, /* Ë */ + {'I', '`', 204}, /* Ì */ + {'I', '^', 206}, /* Î */ + {'I', '"', 207}, /* Ï */ + {'N', '~', 209}, /* Ñ */ + {'O', '`', 210}, /* Ò */ + {'O', '^', 212}, /* Ô */ + {'O', '~', 213}, /* Õ */ + {'/', '\\', 215}, /* × - multiplication symbol in ISO 8859-1 */ + {'U', '`', 217}, /* Ù */ + {'U', '^', 219}, /* Û */ + {'I', 'p', 222}, /* Þ */ + {'a', '`', 224}, /* à */ + {'a', '^', 226}, /* â */ + {'a', '~', 227}, /* ã */ + {'a', '"', 228}, /* ä */ + {'a', '@', 229}, /* å */ + {'e', '`', 232}, /* è */ + {'e', '^', 234}, /* ê */ + {'e', '"', 235}, /* ë */ + {'i', '`', 236}, /* ì */ + {'i', '^', 238}, /* î */ + {'n', '~', 241}, /* ñ */ + {'o', '`', 242}, /* ò */ + {'o', '^', 244}, /* ô */ + {'o', '~', 245}, /* õ */ + {'u', '`', 249}, /* ù */ + {'u', '^', 251}, /* û */ + {'y', '"', 255}, /* x XX */ + + {NUL, NUL, NUL} +}; + +# endif /* OLD_DIGRAPHS */ + +# endif /* !HPUX_DIGRAPHS */ + +/* + * handle digraphs after typing a character + */ +int do_digraph(c) +int c; +{ + static int backspaced; /* character before K_BS */ + static int lastchar; /* last typed character */ + + if (c == -1) { /* init values */ + backspaced = -1; + } else if (p_dg) { + if (backspaced >= 0) + c = getdigraph(backspaced, c, FALSE); + backspaced = -1; + if ((c == K_BS || c == Ctrl_H) && lastchar >= 0) + backspaced = lastchar; + } + lastchar = c; + return c; +} + +/* + * Get a digraph. Used after typing CTRL-K on the command line or in normal + * mode. + * Returns composed character, or NUL when ESC was used. + */ +int get_digraph(cmdline) +int cmdline; /* TRUE when called from the cmdline */ +{ + int c, cc; + + ++no_mapping; + ++allow_keys; + c = plain_vgetc(); + --no_mapping; + --allow_keys; + if (c != ESC) { /* ESC cancels CTRL-K */ + if (IS_SPECIAL(c)) /* insert special key code */ + return c; + if (cmdline) { + if (char2cells(c) == 1 + && cmdline_star == 0 + ) + putcmdline(c, TRUE); + } else + add_to_showcmd(c); + ++no_mapping; + ++allow_keys; + cc = plain_vgetc(); + --no_mapping; + --allow_keys; + if (cc != ESC) /* ESC cancels CTRL-K */ + return getdigraph(c, cc, TRUE); + } + return NUL; +} + +/* + * Lookup the pair "char1", "char2" in the digraph tables. + * If no match, return "char2". + * If "meta_char" is TRUE and "char1" is a space, return "char2" | 0x80. + */ +static int getexactdigraph(char1, char2, meta_char) +int char1; +int char2; +int meta_char; +{ + int i; + int retval = 0; + digr_T *dp; + + if (IS_SPECIAL(char1) || IS_SPECIAL(char2)) + return char2; + + /* + * Search user digraphs first. + */ + dp = (digr_T *)user_digraphs.ga_data; + for (i = 0; i < user_digraphs.ga_len; ++i) { + if ((int)dp->char1 == char1 && (int)dp->char2 == char2) { + retval = dp->result; + break; + } + ++dp; + } + + /* + * Search default digraphs. + */ + if (retval == 0) { + dp = digraphdefault; + for (i = 0; dp->char1 != 0; ++i) { + if ((int)dp->char1 == char1 && (int)dp->char2 == char2) { + retval = dp->result; + break; + } + ++dp; + } + } +# ifdef USE_UNICODE_DIGRAPHS + if (retval != 0 && !enc_utf8) { + char_u buf[6], *to; + vimconv_T vc; + + /* + * Convert the Unicode digraph to 'encoding'. + */ + i = utf_char2bytes(retval, buf); + retval = 0; + vc.vc_type = CONV_NONE; + if (convert_setup(&vc, (char_u *)"utf-8", p_enc) == OK) { + vc.vc_fail = TRUE; + to = string_convert(&vc, buf, &i); + if (to != NULL) { + retval = (*mb_ptr2char)(to); + vim_free(to); + } + (void)convert_setup(&vc, NULL, NULL); + } + } +# endif + + /* Ignore multi-byte characters when not in multi-byte mode. */ + if (!has_mbyte && retval > 0xff) + retval = 0; + + if (retval == 0) { /* digraph deleted or not found */ + if (char1 == ' ' && meta_char) /* --> meta-char */ + return char2 | 0x80; + return char2; + } + return retval; +} + +/* + * Get digraph. + * Allow for both char1-char2 and char2-char1 + */ +int getdigraph(char1, char2, meta_char) +int char1; +int char2; +int meta_char; +{ + int retval; + + if (((retval = getexactdigraph(char1, char2, meta_char)) == char2) + && (char1 != char2) + && ((retval = getexactdigraph(char2, char1, meta_char)) == char1)) + return char2; + return retval; +} + +/* + * Add the digraphs in the argument to the digraph table. + * format: {c1}{c2} char {c1}{c2} char ... + */ +void putdigraph(str) +char_u *str; +{ + int char1, char2, n; + int i; + digr_T *dp; + + while (*str != NUL) { + str = skipwhite(str); + if (*str == NUL) + return; + char1 = *str++; + char2 = *str++; + if (char2 == 0) { + EMSG(_(e_invarg)); + return; + } + if (char1 == ESC || char2 == ESC) { + EMSG(_("E104: Escape not allowed in digraph")); + return; + } + str = skipwhite(str); + if (!VIM_ISDIGIT(*str)) { + EMSG(_(e_number_exp)); + return; + } + n = getdigits(&str); + + /* If the digraph already exists, replace the result. */ + dp = (digr_T *)user_digraphs.ga_data; + for (i = 0; i < user_digraphs.ga_len; ++i) { + if ((int)dp->char1 == char1 && (int)dp->char2 == char2) { + dp->result = n; + break; + } + ++dp; + } + + /* Add a new digraph to the table. */ + if (i == user_digraphs.ga_len) { + if (ga_grow(&user_digraphs, 1) == OK) { + dp = (digr_T *)user_digraphs.ga_data + user_digraphs.ga_len; + dp->char1 = char1; + dp->char2 = char2; + dp->result = n; + ++user_digraphs.ga_len; + } + } + } +} + +void listdigraphs() { + int i; + digr_T *dp; + + msg_putchar('\n'); + + dp = digraphdefault; + for (i = 0; dp->char1 != NUL && !got_int; ++i) { +#if defined(USE_UNICODE_DIGRAPHS) && defined(FEAT_MBYTE) + digr_T tmp; + + /* May need to convert the result to 'encoding'. */ + tmp.char1 = dp->char1; + tmp.char2 = dp->char2; + tmp.result = getexactdigraph(tmp.char1, tmp.char2, FALSE); + if (tmp.result != 0 && tmp.result != tmp.char2 + && (has_mbyte || tmp.result <= 255)) + printdigraph(&tmp); +#else + + if (getexactdigraph(dp->char1, dp->char2, FALSE) == dp->result + && (has_mbyte || dp->result <= 255) + ) + printdigraph(dp); +#endif + ++dp; + ui_breakcheck(); + } + + dp = (digr_T *)user_digraphs.ga_data; + for (i = 0; i < user_digraphs.ga_len && !got_int; ++i) { + printdigraph(dp); + ui_breakcheck(); + ++dp; + } + must_redraw = CLEAR; /* clear screen, because some digraphs may be + wrong, in which case we messed up ScreenLines */ +} + +static void printdigraph(dp) +digr_T *dp; +{ + char_u buf[30]; + char_u *p; + + int list_width; + + if ((dy_flags & DY_UHEX) + || has_mbyte + ) + list_width = 13; + else + list_width = 11; + + if (dp->result != 0) { + if (msg_col > Columns - list_width) + msg_putchar('\n'); + if (msg_col) + while (msg_col % list_width != 0) + msg_putchar(' '); + + p = buf; + *p++ = dp->char1; + *p++ = dp->char2; + *p++ = ' '; + if (has_mbyte) { + /* add a space to draw a composing char on */ + if (enc_utf8 && utf_iscomposing(dp->result)) + *p++ = ' '; + p += (*mb_char2bytes)(dp->result, p); + } else + *p++ = (char_u)dp->result; + if (char2cells(dp->result) == 1) + *p++ = ' '; + vim_snprintf((char *)p, sizeof(buf) - (p - buf), " %3d", dp->result); + msg_outtrans(buf); + } +} + + + +/* structure used for b_kmap_ga.ga_data */ +typedef struct { + char_u *from; + char_u *to; +} kmap_T; + +#define KMAP_MAXLEN 20 /* maximum length of "from" or "to" */ + +static void keymap_unload __ARGS((void)); + +/* + * Set up key mapping tables for the 'keymap' option. + * Returns NULL if OK, an error message for failure. This only needs to be + * used when setting the option, not later when the value has already been + * checked. + */ +char_u * keymap_init() { + curbuf->b_kmap_state &= ~KEYMAP_INIT; + + if (*curbuf->b_p_keymap == NUL) { + /* Stop any active keymap and clear the table. Also remove + * b:keymap_name, as no keymap is active now. */ + keymap_unload(); + do_cmdline_cmd((char_u *)"unlet! b:keymap_name"); + } else { + char_u *buf; + size_t buflen; + + /* Source the keymap file. It will contain a ":loadkeymap" command + * which will call ex_loadkeymap() below. */ + buflen = STRLEN(curbuf->b_p_keymap) + + STRLEN(p_enc) + + 14; + buf = alloc((unsigned)buflen); + if (buf == NULL) + return e_outofmem; + + /* try finding "keymap/'keymap'_'encoding'.vim" in 'runtimepath' */ + vim_snprintf((char *)buf, buflen, "keymap/%s_%s.vim", + curbuf->b_p_keymap, p_enc); + if (source_runtime(buf, FALSE) == FAIL) { + /* try finding "keymap/'keymap'.vim" in 'runtimepath' */ + vim_snprintf((char *)buf, buflen, "keymap/%s.vim", + curbuf->b_p_keymap); + if (source_runtime(buf, FALSE) == FAIL) { + vim_free(buf); + return (char_u *)N_("E544: Keymap file not found"); + } + } + vim_free(buf); + } + + return NULL; +} + +/* + * ":loadkeymap" command: load the following lines as the keymap. + */ +void ex_loadkeymap(eap) +exarg_T *eap; +{ + char_u *line; + char_u *p; + char_u *s; + kmap_T *kp; +#define KMAP_LLEN 200 /* max length of "to" and "from" together */ + char_u buf[KMAP_LLEN + 11]; + int i; + char_u *save_cpo = p_cpo; + + if (!getline_equal(eap->getline, eap->cookie, getsourceline)) { + EMSG(_("E105: Using :loadkeymap not in a sourced file")); + return; + } + + /* + * Stop any active keymap and clear the table. + */ + keymap_unload(); + + curbuf->b_kmap_state = 0; + ga_init2(&curbuf->b_kmap_ga, (int)sizeof(kmap_T), 20); + + /* Set 'cpoptions' to "C" to avoid line continuation. */ + p_cpo = (char_u *)"C"; + + /* + * Get each line of the sourced file, break at the end. + */ + for (;; ) { + line = eap->getline(0, eap->cookie, 0); + if (line == NULL) + break; + + p = skipwhite(line); + if (*p != '"' && *p != NUL && ga_grow(&curbuf->b_kmap_ga, 1) == OK) { + kp = (kmap_T *)curbuf->b_kmap_ga.ga_data + curbuf->b_kmap_ga.ga_len; + s = skiptowhite(p); + kp->from = vim_strnsave(p, (int)(s - p)); + p = skipwhite(s); + s = skiptowhite(p); + kp->to = vim_strnsave(p, (int)(s - p)); + + if (kp->from == NULL || kp->to == NULL + || STRLEN(kp->from) + STRLEN(kp->to) >= KMAP_LLEN + || *kp->from == NUL || *kp->to == NUL) { + if (kp->to != NULL && *kp->to == NUL) + EMSG(_("E791: Empty keymap entry")); + vim_free(kp->from); + vim_free(kp->to); + } else + ++curbuf->b_kmap_ga.ga_len; + } + vim_free(line); + } + + /* + * setup ":lnoremap" to map the keys + */ + for (i = 0; i < curbuf->b_kmap_ga.ga_len; ++i) { + vim_snprintf((char *)buf, sizeof(buf), " %s %s", + ((kmap_T *)curbuf->b_kmap_ga.ga_data)[i].from, + ((kmap_T *)curbuf->b_kmap_ga.ga_data)[i].to); + (void)do_map(2, buf, LANGMAP, FALSE); + } + + p_cpo = save_cpo; + + curbuf->b_kmap_state |= KEYMAP_LOADED; + status_redraw_curbuf(); +} + +/* + * Stop using 'keymap'. + */ +static void keymap_unload() { + char_u buf[KMAP_MAXLEN + 10]; + int i; + char_u *save_cpo = p_cpo; + kmap_T *kp; + + if (!(curbuf->b_kmap_state & KEYMAP_LOADED)) + return; + + /* Set 'cpoptions' to "C" to avoid line continuation. */ + p_cpo = (char_u *)"C"; + + /* clear the ":lmap"s */ + kp = (kmap_T *)curbuf->b_kmap_ga.ga_data; + for (i = 0; i < curbuf->b_kmap_ga.ga_len; ++i) { + vim_snprintf((char *)buf, sizeof(buf), " %s", kp[i].from); + (void)do_map(1, buf, LANGMAP, FALSE); + vim_free(kp[i].from); + vim_free(kp[i].to); + } + + p_cpo = save_cpo; + + ga_clear(&curbuf->b_kmap_ga); + curbuf->b_kmap_state &= ~KEYMAP_LOADED; + status_redraw_curbuf(); +} + diff --git a/src/edit.c b/src/edit.c new file mode 100644 index 0000000000..4d245fbfaf --- /dev/null +++ b/src/edit.c @@ -0,0 +1,8419 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * edit.c: functions for Insert mode + */ + +#include "vim.h" + +/* + * definitions used for CTRL-X submode + */ +#define CTRL_X_WANT_IDENT 0x100 + +#define CTRL_X_NOT_DEFINED_YET 1 +#define CTRL_X_SCROLL 2 +#define CTRL_X_WHOLE_LINE 3 +#define CTRL_X_FILES 4 +#define CTRL_X_TAGS (5 + CTRL_X_WANT_IDENT) +#define CTRL_X_PATH_PATTERNS (6 + CTRL_X_WANT_IDENT) +#define CTRL_X_PATH_DEFINES (7 + CTRL_X_WANT_IDENT) +#define CTRL_X_FINISHED 8 +#define CTRL_X_DICTIONARY (9 + CTRL_X_WANT_IDENT) +#define CTRL_X_THESAURUS (10 + CTRL_X_WANT_IDENT) +#define CTRL_X_CMDLINE 11 +#define CTRL_X_FUNCTION 12 +#define CTRL_X_OMNI 13 +#define CTRL_X_SPELL 14 +#define CTRL_X_LOCAL_MSG 15 /* only used in "ctrl_x_msgs" */ + +#define CTRL_X_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT] + +static char *ctrl_x_msgs[] = +{ + N_(" Keyword completion (^N^P)"), /* ctrl_x_mode == 0, ^P/^N compl. */ + N_(" ^X mode (^]^D^E^F^I^K^L^N^O^Ps^U^V^Y)"), + NULL, + N_(" Whole line completion (^L^N^P)"), + N_(" File name completion (^F^N^P)"), + N_(" Tag completion (^]^N^P)"), + N_(" Path pattern completion (^N^P)"), + N_(" Definition completion (^D^N^P)"), + NULL, + N_(" Dictionary completion (^K^N^P)"), + N_(" Thesaurus completion (^T^N^P)"), + N_(" Command-line completion (^V^N^P)"), + N_(" User defined completion (^U^N^P)"), + N_(" Omni completion (^O^N^P)"), + N_(" Spelling suggestion (s^N^P)"), + N_(" Keyword Local completion (^N^P)"), +}; + +static char e_hitend[] = N_("Hit end of paragraph"); +static char e_complwin[] = N_("E839: Completion function changed window"); +static char e_compldel[] = N_("E840: Completion function deleted text"); + +/* + * Structure used to store one match for insert completion. + */ +typedef struct compl_S compl_T; +struct compl_S { + compl_T *cp_next; + compl_T *cp_prev; + char_u *cp_str; /* matched text */ + char cp_icase; /* TRUE or FALSE: ignore case */ + char_u *(cp_text[CPT_COUNT]); /* text for the menu */ + char_u *cp_fname; /* file containing the match, allocated when + * cp_flags has FREE_FNAME */ + int cp_flags; /* ORIGINAL_TEXT, CONT_S_IPOS or FREE_FNAME */ + int cp_number; /* sequence number */ +}; + +#define ORIGINAL_TEXT (1) /* the original text when the expansion begun */ +#define FREE_FNAME (2) + +/* + * All the current matches are stored in a list. + * "compl_first_match" points to the start of the list. + * "compl_curr_match" points to the currently selected entry. + * "compl_shown_match" is different from compl_curr_match during + * ins_compl_get_exp(). + */ +static compl_T *compl_first_match = NULL; +static compl_T *compl_curr_match = NULL; +static compl_T *compl_shown_match = NULL; + +/* After using a cursor key selects a match in the popup menu, + * otherwise it inserts a line break. */ +static int compl_enter_selects = FALSE; + +/* When "compl_leader" is not NULL only matches that start with this string + * are used. */ +static char_u *compl_leader = NULL; + +static int compl_get_longest = FALSE; /* put longest common string + in compl_leader */ + +static int compl_used_match; /* Selected one of the matches. When + FALSE the match was edited or using + the longest common string. */ + +static int compl_was_interrupted = FALSE; /* didn't finish finding + completions. */ + +static int compl_restarting = FALSE; /* don't insert match */ + +/* When the first completion is done "compl_started" is set. When it's + * FALSE the word to be completed must be located. */ +static int compl_started = FALSE; + +/* Set when doing something for completion that may call edit() recursively, + * which is not allowed. */ +static int compl_busy = FALSE; + +static int compl_matches = 0; +static char_u *compl_pattern = NULL; +static int compl_direction = FORWARD; +static int compl_shows_dir = FORWARD; +static int compl_pending = 0; /* > 1 for postponed CTRL-N */ +static pos_T compl_startpos; +static colnr_T compl_col = 0; /* column where the text starts + * that is being completed */ +static char_u *compl_orig_text = NULL; /* text as it was before + * completion started */ +static int compl_cont_mode = 0; +static expand_T compl_xp; + +static int compl_opt_refresh_always = FALSE; + +static void ins_ctrl_x __ARGS((void)); +static int has_compl_option __ARGS((int dict_opt)); +static int ins_compl_accept_char __ARGS((int c)); +static int ins_compl_add __ARGS((char_u *str, int len, int icase, char_u *fname, + char_u **cptext, int cdir, int flags, + int adup)); +static int ins_compl_equal __ARGS((compl_T *match, char_u *str, int len)); +static void ins_compl_longest_match __ARGS((compl_T *match)); +static void ins_compl_add_matches __ARGS((int num_matches, char_u **matches, + int icase)); +static int ins_compl_make_cyclic __ARGS((void)); +static void ins_compl_upd_pum __ARGS((void)); +static void ins_compl_del_pum __ARGS((void)); +static int pum_wanted __ARGS((void)); +static int pum_enough_matches __ARGS((void)); +static void ins_compl_dictionaries __ARGS((char_u *dict, char_u *pat, int flags, + int thesaurus)); +static void ins_compl_files __ARGS((int count, char_u **files, int thesaurus, + int flags, regmatch_T *regmatch, char_u * + buf, + int *dir)); +static char_u *find_line_end __ARGS((char_u *ptr)); +static void ins_compl_free __ARGS((void)); +static void ins_compl_clear __ARGS((void)); +static int ins_compl_bs __ARGS((void)); +static int ins_compl_need_restart __ARGS((void)); +static void ins_compl_new_leader __ARGS((void)); +static void ins_compl_addleader __ARGS((int c)); +static int ins_compl_len __ARGS((void)); +static void ins_compl_restart __ARGS((void)); +static void ins_compl_set_original_text __ARGS((char_u *str)); +static void ins_compl_addfrommatch __ARGS((void)); +static int ins_compl_prep __ARGS((int c)); +static void ins_compl_fixRedoBufForLeader __ARGS((char_u *ptr_arg)); +static buf_T *ins_compl_next_buf __ARGS((buf_T *buf, int flag)); +static void ins_compl_add_list __ARGS((list_T *list)); +static void ins_compl_add_dict __ARGS((dict_T *dict)); +static int ins_compl_get_exp __ARGS((pos_T *ini)); +static void ins_compl_delete __ARGS((void)); +static void ins_compl_insert __ARGS((void)); +static int ins_compl_next __ARGS((int allow_get_expansion, int count, + int insert_match)); +static int ins_compl_key2dir __ARGS((int c)); +static int ins_compl_pum_key __ARGS((int c)); +static int ins_compl_key2count __ARGS((int c)); +static int ins_compl_use_match __ARGS((int c)); +static int ins_complete __ARGS((int c)); +static unsigned quote_meta __ARGS((char_u *dest, char_u *str, int len)); + +#define BACKSPACE_CHAR 1 +#define BACKSPACE_WORD 2 +#define BACKSPACE_WORD_NOT_SPACE 3 +#define BACKSPACE_LINE 4 + +static void ins_redraw __ARGS((int ready)); +static void ins_ctrl_v __ARGS((void)); +static void undisplay_dollar __ARGS((void)); +static void insert_special __ARGS((int, int, int)); +static void internal_format __ARGS((int textwidth, int second_indent, int flags, + int format_only, + int c)); +static void check_auto_format __ARGS((int)); +static void redo_literal __ARGS((int c)); +static void start_arrow __ARGS((pos_T *end_insert_pos)); +static void check_spell_redraw __ARGS((void)); +static void spell_back_to_badword __ARGS((void)); +static int spell_bad_len = 0; /* length of located bad word */ +static void stop_insert __ARGS((pos_T *end_insert_pos, int esc, int nomove)); +static int echeck_abbr __ARGS((int)); +static int replace_pop __ARGS((void)); +static void replace_join __ARGS((int off)); +static void replace_pop_ins __ARGS((void)); +static void mb_replace_pop_ins __ARGS((int cc)); +static void replace_flush __ARGS((void)); +static void replace_do_bs __ARGS((int limit_col)); +static int del_char_after_col __ARGS((int limit_col)); +static int cindent_on __ARGS((void)); +static void ins_reg __ARGS((void)); +static void ins_ctrl_g __ARGS((void)); +static void ins_ctrl_hat __ARGS((void)); +static int ins_esc __ARGS((long *count, int cmdchar, int nomove)); +static void ins_ctrl_ __ARGS((void)); +static int ins_start_select __ARGS((int c)); +static void ins_insert __ARGS((int replaceState)); +static void ins_ctrl_o __ARGS((void)); +static void ins_shift __ARGS((int c, int lastc)); +static void ins_del __ARGS((void)); +static int ins_bs __ARGS((int c, int mode, int *inserted_space_p)); +static void ins_mouse __ARGS((int c)); +static void ins_mousescroll __ARGS((int dir)); +static void ins_left __ARGS((void)); +static void ins_home __ARGS((int c)); +static void ins_end __ARGS((int c)); +static void ins_s_left __ARGS((void)); +static void ins_right __ARGS((void)); +static void ins_s_right __ARGS((void)); +static void ins_up __ARGS((int startcol)); +static void ins_pageup __ARGS((void)); +static void ins_down __ARGS((int startcol)); +static void ins_pagedown __ARGS((void)); +static int ins_tab __ARGS((void)); +static int ins_eol __ARGS((int c)); +static int ins_digraph __ARGS((void)); +static int ins_ctrl_ey __ARGS((int tc)); +static void ins_try_si __ARGS((int c)); +static colnr_T get_nolist_virtcol __ARGS((void)); +static char_u *do_insert_char_pre __ARGS((int c)); + +static colnr_T Insstart_textlen; /* length of line when insert started */ +static colnr_T Insstart_blank_vcol; /* vcol for first inserted blank */ + +static char_u *last_insert = NULL; /* the text of the previous insert, + K_SPECIAL and CSI are escaped */ +static int last_insert_skip; /* nr of chars in front of previous insert */ +static int new_insert_skip; /* nr of chars in front of current insert */ +static int did_restart_edit; /* "restart_edit" when calling edit() */ + +static int can_cindent; /* may do cindenting on this line */ + +static int old_indent = 0; /* for ^^D command in insert mode */ + +static int revins_on; /* reverse insert mode on */ +static int revins_chars; /* how much to skip after edit */ +static int revins_legal; /* was the last char 'legal'? */ +static int revins_scol; /* start column of revins session */ + +static int ins_need_undo; /* call u_save() before inserting a + char. Set when edit() is called. + after that arrow_used is used. */ + +static int did_add_space = FALSE; /* auto_format() added an extra space + under the cursor */ + +/* + * edit(): Start inserting text. + * + * "cmdchar" can be: + * 'i' normal insert command + * 'a' normal append command + * 'R' replace command + * 'r' "r" command: insert one . Note: count can be > 1, for redo, + * but still only one is inserted. The is not used for redo. + * 'g' "gI" command. + * 'V' "gR" command for Virtual Replace mode. + * 'v' "gr" command for single character Virtual Replace mode. + * + * This function is not called recursively. For CTRL-O commands, it returns + * and lets the caller handle the Normal-mode command. + * + * Return TRUE if a CTRL-O command caused the return (insert mode pending). + */ +int edit(cmdchar, startln, count) +int cmdchar; +int startln; /* if set, insert at start of line */ +long count; +{ + int c = 0; + char_u *ptr; + int lastc; + int mincol; + static linenr_T o_lnum = 0; + int i; + int did_backspace = TRUE; /* previous char was backspace */ + int line_is_white = FALSE; /* line is empty before insert */ + linenr_T old_topline = 0; /* topline before insertion */ + int old_topfill = -1; + int inserted_space = FALSE; /* just inserted a space */ + int replaceState = REPLACE; + int nomove = FALSE; /* don't move cursor on return */ + + /* Remember whether editing was restarted after CTRL-O. */ + did_restart_edit = restart_edit; + + /* sleep before redrawing, needed for "CTRL-O :" that results in an + * error message */ + check_for_delay(TRUE); + +#ifdef HAVE_SANDBOX + /* Don't allow inserting in the sandbox. */ + if (sandbox != 0) { + EMSG(_(e_sandbox)); + return FALSE; + } +#endif + /* Don't allow changes in the buffer while editing the cmdline. The + * caller of getcmdline() may get confused. */ + if (textlock != 0) { + EMSG(_(e_secure)); + return FALSE; + } + + /* Don't allow recursive insert mode when busy with completion. */ + if (compl_started || compl_busy || pum_visible()) { + EMSG(_(e_secure)); + return FALSE; + } + ins_compl_clear(); /* clear stuff for CTRL-X mode */ + + /* + * Trigger InsertEnter autocommands. Do not do this for "r" or "grx". + */ + if (cmdchar != 'r' && cmdchar != 'v') { + pos_T save_cursor = curwin->w_cursor; + + if (cmdchar == 'R') + ptr = (char_u *)"r"; + else if (cmdchar == 'V') + ptr = (char_u *)"v"; + else + ptr = (char_u *)"i"; + set_vim_var_string(VV_INSERTMODE, ptr, 1); + set_vim_var_string(VV_CHAR, NULL, -1); /* clear v:char */ + apply_autocmds(EVENT_INSERTENTER, NULL, NULL, FALSE, curbuf); + + /* Make sure the cursor didn't move. Do call check_cursor_col() in + * case the text was modified. Since Insert mode was not started yet + * a call to check_cursor_col() may move the cursor, especially with + * the "A" command, thus set State to avoid that. Also check that the + * line number is still valid (lines may have been deleted). + * Do not restore if v:char was set to a non-empty string. */ + if (!equalpos(curwin->w_cursor, save_cursor) + && *get_vim_var_str(VV_CHAR) == NUL + && save_cursor.lnum <= curbuf->b_ml.ml_line_count) { + int save_state = State; + + curwin->w_cursor = save_cursor; + State = INSERT; + check_cursor_col(); + State = save_state; + } + } + + /* Check if the cursor line needs redrawing before changing State. If + * 'concealcursor' is "n" it needs to be redrawn without concealing. */ + conceal_check_cursur_line(); + + /* + * When doing a paste with the middle mouse button, Insstart is set to + * where the paste started. + */ + if (where_paste_started.lnum != 0) + Insstart = where_paste_started; + else { + Insstart = curwin->w_cursor; + if (startln) + Insstart.col = 0; + } + Insstart_textlen = (colnr_T)linetabsize(ml_get_curline()); + Insstart_blank_vcol = MAXCOL; + if (!did_ai) + ai_col = 0; + + if (cmdchar != NUL && restart_edit == 0) { + ResetRedobuff(); + AppendNumberToRedobuff(count); + if (cmdchar == 'V' || cmdchar == 'v') { + /* "gR" or "gr" command */ + AppendCharToRedobuff('g'); + AppendCharToRedobuff((cmdchar == 'v') ? 'r' : 'R'); + } else { + AppendCharToRedobuff(cmdchar); + if (cmdchar == 'g') /* "gI" command */ + AppendCharToRedobuff('I'); + else if (cmdchar == 'r') /* "r" command */ + count = 1; /* insert only one */ + } + } + + if (cmdchar == 'R') { + if (p_fkmap && p_ri) { + beep_flush(); + EMSG(farsi_text_3); /* encoded in Farsi */ + State = INSERT; + } else + State = REPLACE; + } else if (cmdchar == 'V' || cmdchar == 'v') { + State = VREPLACE; + replaceState = VREPLACE; + orig_line_count = curbuf->b_ml.ml_line_count; + vr_lines_changed = 1; + } else + State = INSERT; + + stop_insert_mode = FALSE; + + /* + * Need to recompute the cursor position, it might move when the cursor is + * on a TAB or special character. + */ + curs_columns(TRUE); + + /* + * Enable langmap or IME, indicated by 'iminsert'. + * Note that IME may enabled/disabled without us noticing here, thus the + * 'iminsert' value may not reflect what is actually used. It is updated + * when hitting . + */ + if (curbuf->b_p_iminsert == B_IMODE_LMAP) + State |= LANGMAP; +#ifdef USE_IM_CONTROL + im_set_active(curbuf->b_p_iminsert == B_IMODE_IM); +#endif + + setmouse(); + clear_showcmd(); + /* there is no reverse replace mode */ + revins_on = (State == INSERT && p_ri); + if (revins_on) + undisplay_dollar(); + revins_chars = 0; + revins_legal = 0; + revins_scol = -1; + + /* + * Handle restarting Insert mode. + * Don't do this for "CTRL-O ." (repeat an insert): we get here with + * restart_edit non-zero, and something in the stuff buffer. + */ + if (restart_edit != 0 && stuff_empty()) { + /* + * After a paste we consider text typed to be part of the insert for + * the pasted text. You can backspace over the pasted text too. + */ + if (where_paste_started.lnum) + arrow_used = FALSE; + else + arrow_used = TRUE; + restart_edit = 0; + + /* + * If the cursor was after the end-of-line before the CTRL-O and it is + * now at the end-of-line, put it after the end-of-line (this is not + * correct in very rare cases). + * Also do this if curswant is greater than the current virtual + * column. Eg after "^O$" or "^O80|". + */ + validate_virtcol(); + update_curswant(); + if (((ins_at_eol && curwin->w_cursor.lnum == o_lnum) + || curwin->w_curswant > curwin->w_virtcol) + && *(ptr = ml_get_curline() + curwin->w_cursor.col) != NUL) { + if (ptr[1] == NUL) + ++curwin->w_cursor.col; + else if (has_mbyte) { + i = (*mb_ptr2len)(ptr); + if (ptr[i] == NUL) + curwin->w_cursor.col += i; + } + } + ins_at_eol = FALSE; + } else + arrow_used = FALSE; + + /* we are in insert mode now, don't need to start it anymore */ + need_start_insertmode = FALSE; + + /* Need to save the line for undo before inserting the first char. */ + ins_need_undo = TRUE; + + where_paste_started.lnum = 0; + can_cindent = TRUE; + /* The cursor line is not in a closed fold, unless 'insertmode' is set or + * restarting. */ + if (!p_im && did_restart_edit == 0) + foldOpenCursor(); + + /* + * If 'showmode' is set, show the current (insert/replace/..) mode. + * A warning message for changing a readonly file is given here, before + * actually changing anything. It's put after the mode, if any. + */ + i = 0; + if (p_smd && msg_silent == 0) + i = showmode(); + + if (!p_im && did_restart_edit == 0) + change_warning(i == 0 ? 0 : i + 1); + +#ifdef CURSOR_SHAPE + ui_cursor_shape(); /* may show different cursor shape */ +#endif + do_digraph(-1); /* clear digraphs */ + + /* + * Get the current length of the redo buffer, those characters have to be + * skipped if we want to get to the inserted characters. + */ + ptr = get_inserted(); + if (ptr == NULL) + new_insert_skip = 0; + else { + new_insert_skip = (int)STRLEN(ptr); + vim_free(ptr); + } + + old_indent = 0; + + /* + * Main loop in Insert mode: repeat until Insert mode is left. + */ + for (;; ) { + if (!revins_legal) + revins_scol = -1; /* reset on illegal motions */ + else + revins_legal = 0; + if (arrow_used) /* don't repeat insert when arrow key used */ + count = 0; + + if (stop_insert_mode) { + /* ":stopinsert" used or 'insertmode' reset */ + count = 0; + goto doESCkey; + } + + /* set curwin->w_curswant for next K_DOWN or K_UP */ + if (!arrow_used) + curwin->w_set_curswant = TRUE; + + /* If there is no typeahead may check for timestamps (e.g., for when a + * menu invoked a shell command). */ + if (stuff_empty()) { + did_check_timestamps = FALSE; + if (need_check_timestamps) + check_timestamps(FALSE); + } + + /* + * When emsg() was called msg_scroll will have been set. + */ + msg_scroll = FALSE; + + + /* Open fold at the cursor line, according to 'foldopen'. */ + if (fdo_flags & FDO_INSERT) + foldOpenCursor(); + /* Close folds where the cursor isn't, according to 'foldclose' */ + if (!char_avail()) + foldCheckClose(); + + /* + * If we inserted a character at the last position of the last line in + * the window, scroll the window one line up. This avoids an extra + * redraw. + * This is detected when the cursor column is smaller after inserting + * something. + * Don't do this when the topline changed already, it has + * already been adjusted (by insertchar() calling open_line())). + */ + if (curbuf->b_mod_set + && curwin->w_p_wrap + && !did_backspace + && curwin->w_topline == old_topline + && curwin->w_topfill == old_topfill + ) { + mincol = curwin->w_wcol; + validate_cursor_col(); + + if ((int)curwin->w_wcol < mincol - curbuf->b_p_ts + && curwin->w_wrow == W_WINROW(curwin) + + curwin->w_height - 1 - p_so + && (curwin->w_cursor.lnum != curwin->w_topline + || curwin->w_topfill > 0 + )) { + if (curwin->w_topfill > 0) + --curwin->w_topfill; + else if (hasFolding(curwin->w_topline, NULL, &old_topline)) + set_topline(curwin, old_topline + 1); + else + set_topline(curwin, curwin->w_topline + 1); + } + } + + /* May need to adjust w_topline to show the cursor. */ + update_topline(); + + did_backspace = FALSE; + + validate_cursor(); /* may set must_redraw */ + + /* + * Redraw the display when no characters are waiting. + * Also shows mode, ruler and positions cursor. + */ + ins_redraw(TRUE); + + if (curwin->w_p_scb) + do_check_scrollbind(TRUE); + + if (curwin->w_p_crb) + do_check_cursorbind(); + update_curswant(); + old_topline = curwin->w_topline; + old_topfill = curwin->w_topfill; + +#ifdef USE_ON_FLY_SCROLL + dont_scroll = FALSE; /* allow scrolling here */ +#endif + + /* + * Get a character for Insert mode. Ignore K_IGNORE. + */ + lastc = c; /* remember previous char for CTRL-D */ + do { + c = safe_vgetc(); + } while (c == K_IGNORE); + + /* Don't want K_CURSORHOLD for the second key, e.g., after CTRL-V. */ + did_cursorhold = TRUE; + + if (p_hkmap && KeyTyped) + c = hkmap(c); /* Hebrew mode mapping */ + if (p_fkmap && KeyTyped) + c = fkmap(c); /* Farsi mode mapping */ + + /* + * Special handling of keys while the popup menu is visible or wanted + * and the cursor is still in the completed word. Only when there is + * a match, skip this when no matches were found. + */ + if (compl_started + && pum_wanted() + && curwin->w_cursor.col >= compl_col + && (compl_shown_match == NULL + || compl_shown_match != compl_shown_match->cp_next)) { + /* BS: Delete one character from "compl_leader". */ + if ((c == K_BS || c == Ctrl_H) + && curwin->w_cursor.col > compl_col + && (c = ins_compl_bs()) == NUL) + continue; + + /* When no match was selected or it was edited. */ + if (!compl_used_match) { + /* CTRL-L: Add one character from the current match to + * "compl_leader". Except when at the original match and + * there is nothing to add, CTRL-L works like CTRL-P then. */ + if (c == Ctrl_L + && (ctrl_x_mode != CTRL_X_WHOLE_LINE + || (int)STRLEN(compl_shown_match->cp_str) + > curwin->w_cursor.col - compl_col)) { + ins_compl_addfrommatch(); + continue; + } + + /* A non-white character that fits in with the current + * completion: Add to "compl_leader". */ + if (ins_compl_accept_char(c)) { + /* Trigger InsertCharPre. */ + char_u *str = do_insert_char_pre(c); + char_u *p; + + if (str != NULL) { + for (p = str; *p != NUL; mb_ptr_adv(p)) + ins_compl_addleader(PTR2CHAR(p)); + vim_free(str); + } else + ins_compl_addleader(c); + continue; + } + + /* Pressing CTRL-Y selects the current match. When + * compl_enter_selects is set the Enter key does the same. */ + if (c == Ctrl_Y || (compl_enter_selects + && (c == CAR || c == K_KENTER || c == NL))) { + ins_compl_delete(); + ins_compl_insert(); + } + } + } + + /* Prepare for or stop CTRL-X mode. This doesn't do completion, but + * it does fix up the text when finishing completion. */ + compl_get_longest = FALSE; + if (ins_compl_prep(c)) + continue; + + /* CTRL-\ CTRL-N goes to Normal mode, + * CTRL-\ CTRL-G goes to mode selected with 'insertmode', + * CTRL-\ CTRL-O is like CTRL-O but without moving the cursor. */ + if (c == Ctrl_BSL) { + /* may need to redraw when no more chars available now */ + ins_redraw(FALSE); + ++no_mapping; + ++allow_keys; + c = plain_vgetc(); + --no_mapping; + --allow_keys; + if (c != Ctrl_N && c != Ctrl_G && c != Ctrl_O) { + /* it's something else */ + vungetc(c); + c = Ctrl_BSL; + } else if (c == Ctrl_G && p_im) + continue; + else { + if (c == Ctrl_O) { + ins_ctrl_o(); + ins_at_eol = FALSE; /* cursor keeps its column */ + nomove = TRUE; + } + count = 0; + goto doESCkey; + } + } + + c = do_digraph(c); + + if ((c == Ctrl_V || c == Ctrl_Q) && ctrl_x_mode == CTRL_X_CMDLINE) + goto docomplete; + if (c == Ctrl_V || c == Ctrl_Q) { + ins_ctrl_v(); + c = Ctrl_V; /* pretend CTRL-V is last typed character */ + continue; + } + + if (cindent_on() + && ctrl_x_mode == 0 + ) { + /* A key name preceded by a bang means this key is not to be + * inserted. Skip ahead to the re-indenting below. + * A key name preceded by a star means that indenting has to be + * done before inserting the key. */ + line_is_white = inindent(0); + if (in_cinkeys(c, '!', line_is_white)) + goto force_cindent; + if (can_cindent && in_cinkeys(c, '*', line_is_white) + && stop_arrow() == OK) + do_c_expr_indent(); + } + + if (curwin->w_p_rl) + switch (c) { + case K_LEFT: c = K_RIGHT; break; + case K_S_LEFT: c = K_S_RIGHT; break; + case K_C_LEFT: c = K_C_RIGHT; break; + case K_RIGHT: c = K_LEFT; break; + case K_S_RIGHT: c = K_S_LEFT; break; + case K_C_RIGHT: c = K_C_LEFT; break; + } + + /* + * If 'keymodel' contains "startsel", may start selection. If it + * does, a CTRL-O and c will be stuffed, we need to get these + * characters. + */ + if (ins_start_select(c)) + continue; + + /* + * The big switch to handle a character in insert mode. + */ + switch (c) { + case ESC: /* End input mode */ + if (echeck_abbr(ESC + ABBR_OFF)) + break; + /*FALLTHROUGH*/ + + case Ctrl_C: /* End input mode */ + if (c == Ctrl_C && cmdwin_type != 0) { + /* Close the cmdline window. */ + cmdwin_result = K_IGNORE; + got_int = FALSE; /* don't stop executing autocommands et al. */ + nomove = TRUE; + goto doESCkey; + } + +#ifdef UNIX +do_intr: +#endif + /* when 'insertmode' set, and not halfway a mapping, don't leave + * Insert mode */ + if (goto_im()) { + if (got_int) { + (void)vgetc(); /* flush all buffers */ + got_int = FALSE; + } else + vim_beep(); + break; + } +doESCkey: + /* + * This is the ONLY return from edit()! + */ + /* Always update o_lnum, so that a "CTRL-O ." that adds a line + * still puts the cursor back after the inserted text. */ + if (ins_at_eol && gchar_cursor() == NUL) + o_lnum = curwin->w_cursor.lnum; + + if (ins_esc(&count, cmdchar, nomove)) { + if (cmdchar != 'r' && cmdchar != 'v') + apply_autocmds(EVENT_INSERTLEAVE, NULL, NULL, + FALSE, curbuf); + did_cursorhold = FALSE; + return c == Ctrl_O; + } + continue; + + case Ctrl_Z: /* suspend when 'insertmode' set */ + if (!p_im) + goto normalchar; /* insert CTRL-Z as normal char */ + stuffReadbuff((char_u *)":st\r"); + c = Ctrl_O; + /*FALLTHROUGH*/ + + case Ctrl_O: /* execute one command */ + if (ctrl_x_mode == CTRL_X_OMNI) + goto docomplete; + if (echeck_abbr(Ctrl_O + ABBR_OFF)) + break; + ins_ctrl_o(); + + /* don't move the cursor left when 'virtualedit' has "onemore". */ + if (ve_flags & VE_ONEMORE) { + ins_at_eol = FALSE; + nomove = TRUE; + } + count = 0; + goto doESCkey; + + case K_INS: /* toggle insert/replace mode */ + case K_KINS: + ins_insert(replaceState); + break; + + case K_SELECT: /* end of Select mode mapping - ignore */ + break; + + + case K_HELP: /* Help key works like */ + case K_F1: + case K_XF1: + stuffcharReadbuff(K_HELP); + if (p_im) + need_start_insertmode = TRUE; + goto doESCkey; + + + case K_ZERO: /* Insert the previously inserted text. */ + case NUL: + case Ctrl_A: + /* For ^@ the trailing ESC will end the insert, unless there is an + * error. */ + if (stuff_inserted(NUL, 1L, (c == Ctrl_A)) == FAIL + && c != Ctrl_A && !p_im) + goto doESCkey; /* quit insert mode */ + inserted_space = FALSE; + break; + + case Ctrl_R: /* insert the contents of a register */ + ins_reg(); + auto_format(FALSE, TRUE); + inserted_space = FALSE; + break; + + case Ctrl_G: /* commands starting with CTRL-G */ + ins_ctrl_g(); + break; + + case Ctrl_HAT: /* switch input mode and/or langmap */ + ins_ctrl_hat(); + break; + + case Ctrl__: /* switch between languages */ + if (!p_ari) + goto normalchar; + ins_ctrl_(); + break; + + case Ctrl_D: /* Make indent one shiftwidth smaller. */ + if (ctrl_x_mode == CTRL_X_PATH_DEFINES) + goto docomplete; + /* FALLTHROUGH */ + + case Ctrl_T: /* Make indent one shiftwidth greater. */ + if (c == Ctrl_T && ctrl_x_mode == CTRL_X_THESAURUS) { + if (has_compl_option(FALSE)) + goto docomplete; + break; + } + ins_shift(c, lastc); + auto_format(FALSE, TRUE); + inserted_space = FALSE; + break; + + case K_DEL: /* delete character under the cursor */ + case K_KDEL: + ins_del(); + auto_format(FALSE, TRUE); + break; + + case K_BS: /* delete character before the cursor */ + case Ctrl_H: + did_backspace = ins_bs(c, BACKSPACE_CHAR, &inserted_space); + auto_format(FALSE, TRUE); + break; + + case Ctrl_W: /* delete word before the cursor */ + did_backspace = ins_bs(c, BACKSPACE_WORD, &inserted_space); + auto_format(FALSE, TRUE); + break; + + case Ctrl_U: /* delete all inserted text in current line */ + /* CTRL-X CTRL-U completes with 'completefunc'. */ + if (ctrl_x_mode == CTRL_X_FUNCTION) + goto docomplete; + did_backspace = ins_bs(c, BACKSPACE_LINE, &inserted_space); + auto_format(FALSE, TRUE); + inserted_space = FALSE; + break; + + case K_LEFTMOUSE: /* mouse keys */ + case K_LEFTMOUSE_NM: + case K_LEFTDRAG: + case K_LEFTRELEASE: + case K_LEFTRELEASE_NM: + case K_MIDDLEMOUSE: + case K_MIDDLEDRAG: + case K_MIDDLERELEASE: + case K_RIGHTMOUSE: + case K_RIGHTDRAG: + case K_RIGHTRELEASE: + case K_X1MOUSE: + case K_X1DRAG: + case K_X1RELEASE: + case K_X2MOUSE: + case K_X2DRAG: + case K_X2RELEASE: + ins_mouse(c); + break; + + case K_MOUSEDOWN: /* Default action for scroll wheel up: scroll up */ + ins_mousescroll(MSCR_DOWN); + break; + + case K_MOUSEUP: /* Default action for scroll wheel down: scroll down */ + ins_mousescroll(MSCR_UP); + break; + + case K_MOUSELEFT: /* Scroll wheel left */ + ins_mousescroll(MSCR_LEFT); + break; + + case K_MOUSERIGHT: /* Scroll wheel right */ + ins_mousescroll(MSCR_RIGHT); + break; + + case K_IGNORE: /* Something mapped to nothing */ + break; + + case K_CURSORHOLD: /* Didn't type something for a while. */ + apply_autocmds(EVENT_CURSORHOLDI, NULL, NULL, FALSE, curbuf); + did_cursorhold = TRUE; + break; + + + + case K_HOME: /* */ + case K_KHOME: + case K_S_HOME: + case K_C_HOME: + ins_home(c); + break; + + case K_END: /* */ + case K_KEND: + case K_S_END: + case K_C_END: + ins_end(c); + break; + + case K_LEFT: /* */ + if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) + ins_s_left(); + else + ins_left(); + break; + + case K_S_LEFT: /* */ + case K_C_LEFT: + ins_s_left(); + break; + + case K_RIGHT: /* */ + if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) + ins_s_right(); + else + ins_right(); + break; + + case K_S_RIGHT: /* */ + case K_C_RIGHT: + ins_s_right(); + break; + + case K_UP: /* */ + if (pum_visible()) + goto docomplete; + if (mod_mask & MOD_MASK_SHIFT) + ins_pageup(); + else + ins_up(FALSE); + break; + + case K_S_UP: /* */ + case K_PAGEUP: + case K_KPAGEUP: + if (pum_visible()) + goto docomplete; + ins_pageup(); + break; + + case K_DOWN: /* */ + if (pum_visible()) + goto docomplete; + if (mod_mask & MOD_MASK_SHIFT) + ins_pagedown(); + else + ins_down(FALSE); + break; + + case K_S_DOWN: /* */ + case K_PAGEDOWN: + case K_KPAGEDOWN: + if (pum_visible()) + goto docomplete; + ins_pagedown(); + break; + + + case K_S_TAB: /* When not mapped, use like a normal TAB */ + c = TAB; + /* FALLTHROUGH */ + + case TAB: /* TAB or Complete patterns along path */ + if (ctrl_x_mode == CTRL_X_PATH_PATTERNS) + goto docomplete; + inserted_space = FALSE; + if (ins_tab()) + goto normalchar; /* insert TAB as a normal char */ + auto_format(FALSE, TRUE); + break; + + case K_KENTER: /* */ + c = CAR; + /* FALLTHROUGH */ + case CAR: + case NL: + /* In a quickfix window a jumps to the error under the + * cursor. */ + if (bt_quickfix(curbuf) && c == CAR) { + if (curwin->w_llist_ref == NULL) /* quickfix window */ + do_cmdline_cmd((char_u *)".cc"); + else /* location list window */ + do_cmdline_cmd((char_u *)".ll"); + break; + } + if (cmdwin_type != 0) { + /* Execute the command in the cmdline window. */ + cmdwin_result = CAR; + goto doESCkey; + } + if (ins_eol(c) && !p_im) + goto doESCkey; /* out of memory */ + auto_format(FALSE, FALSE); + inserted_space = FALSE; + break; + + case Ctrl_K: /* digraph or keyword completion */ + if (ctrl_x_mode == CTRL_X_DICTIONARY) { + if (has_compl_option(TRUE)) + goto docomplete; + break; + } + c = ins_digraph(); + if (c == NUL) + break; + goto normalchar; + + case Ctrl_X: /* Enter CTRL-X mode */ + ins_ctrl_x(); + break; + + case Ctrl_RSB: /* Tag name completion after ^X */ + if (ctrl_x_mode != CTRL_X_TAGS) + goto normalchar; + goto docomplete; + + case Ctrl_F: /* File name completion after ^X */ + if (ctrl_x_mode != CTRL_X_FILES) + goto normalchar; + goto docomplete; + + case 's': /* Spelling completion after ^X */ + case Ctrl_S: + if (ctrl_x_mode != CTRL_X_SPELL) + goto normalchar; + goto docomplete; + + case Ctrl_L: /* Whole line completion after ^X */ + if (ctrl_x_mode != CTRL_X_WHOLE_LINE) { + /* CTRL-L with 'insertmode' set: Leave Insert mode */ + if (p_im) { + if (echeck_abbr(Ctrl_L + ABBR_OFF)) + break; + goto doESCkey; + } + goto normalchar; + } + /* FALLTHROUGH */ + + case Ctrl_P: /* Do previous/next pattern completion */ + case Ctrl_N: + /* if 'complete' is empty then plain ^P is no longer special, + * but it is under other ^X modes */ + if (*curbuf->b_p_cpt == NUL + && ctrl_x_mode != 0 + && !(compl_cont_status & CONT_LOCAL)) + goto normalchar; + +docomplete: + compl_busy = TRUE; + if (ins_complete(c) == FAIL) + compl_cont_status = 0; + compl_busy = FALSE; + break; + + case Ctrl_Y: /* copy from previous line or scroll down */ + case Ctrl_E: /* copy from next line or scroll up */ + c = ins_ctrl_ey(c); + break; + + default: +#ifdef UNIX + if (c == intr_char) /* special interrupt char */ + goto do_intr; +#endif + +normalchar: + /* + * Insert a normal character. + */ + if (!p_paste) { + /* Trigger InsertCharPre. */ + char_u *str = do_insert_char_pre(c); + char_u *p; + + if (str != NULL) { + if (*str != NUL && stop_arrow() != FAIL) { + /* Insert the new value of v:char literally. */ + for (p = str; *p != NUL; mb_ptr_adv(p)) { + c = PTR2CHAR(p); + if (c == CAR || c == K_KENTER || c == NL) + ins_eol(c); + else + ins_char(c); + } + AppendToRedobuffLit(str, -1); + } + vim_free(str); + c = NUL; + } + + /* If the new value is already inserted or an empty string + * then don't insert any character. */ + if (c == NUL) + break; + } + /* Try to perform smart-indenting. */ + ins_try_si(c); + + if (c == ' ') { + inserted_space = TRUE; + if (inindent(0)) + can_cindent = FALSE; + if (Insstart_blank_vcol == MAXCOL + && curwin->w_cursor.lnum == Insstart.lnum) + Insstart_blank_vcol = get_nolist_virtcol(); + } + + /* Insert a normal character and check for abbreviations on a + * special character. Let CTRL-] expand abbreviations without + * inserting it. */ + if (vim_iswordc(c) || (!echeck_abbr( + /* Add ABBR_OFF for characters above 0x100, this is + * what check_abbr() expects. */ + (has_mbyte && c >= 0x100) ? (c + ABBR_OFF) : + c) && c != Ctrl_RSB)) { + insert_special(c, FALSE, FALSE); + revins_legal++; + revins_chars++; + } + + auto_format(FALSE, TRUE); + + /* When inserting a character the cursor line must never be in a + * closed fold. */ + foldOpenCursor(); + break; + } /* end of switch (c) */ + + /* If typed something may trigger CursorHoldI again. */ + if (c != K_CURSORHOLD) + did_cursorhold = FALSE; + + /* If the cursor was moved we didn't just insert a space */ + if (arrow_used) + inserted_space = FALSE; + + if (can_cindent && cindent_on() + && ctrl_x_mode == 0 + ) { +force_cindent: + /* + * Indent now if a key was typed that is in 'cinkeys'. + */ + if (in_cinkeys(c, ' ', line_is_white)) { + if (stop_arrow() == OK) + /* re-indent the current line */ + do_c_expr_indent(); + } + } + + } /* for (;;) */ + /* NOTREACHED */ +} + +/* + * Redraw for Insert mode. + * This is postponed until getting the next character to make '$' in the 'cpo' + * option work correctly. + * Only redraw when there are no characters available. This speeds up + * inserting sequences of characters (e.g., for CTRL-R). + */ +static void ins_redraw(ready) +int ready UNUSED; /* not busy with something */ +{ + linenr_T conceal_old_cursor_line = 0; + linenr_T conceal_new_cursor_line = 0; + int conceal_update_lines = FALSE; + + if (char_avail()) + return; + + /* Trigger CursorMoved if the cursor moved. Not when the popup menu is + * visible, the command might delete it. */ + if (ready && ( + has_cursormovedI() + || + curwin->w_p_cole > 0 + ) + && !equalpos(last_cursormoved, curwin->w_cursor) + && !pum_visible() + ) { + /* Need to update the screen first, to make sure syntax + * highlighting is correct after making a change (e.g., inserting + * a "(". The autocommand may also require a redraw, so it's done + * again below, unfortunately. */ + if (syntax_present(curwin) && must_redraw) + update_screen(0); + if (has_cursormovedI()) + apply_autocmds(EVENT_CURSORMOVEDI, NULL, NULL, FALSE, curbuf); + if (curwin->w_p_cole > 0) { + conceal_old_cursor_line = last_cursormoved.lnum; + conceal_new_cursor_line = curwin->w_cursor.lnum; + conceal_update_lines = TRUE; + } + last_cursormoved = curwin->w_cursor; + } + + /* Trigger TextChangedI if b_changedtick differs. */ + if (ready && has_textchangedI() + && last_changedtick != curbuf->b_changedtick + && !pum_visible() + ) { + if (last_changedtick_buf == curbuf) + apply_autocmds(EVENT_TEXTCHANGEDI, NULL, NULL, FALSE, curbuf); + last_changedtick_buf = curbuf; + last_changedtick = curbuf->b_changedtick; + } + + if (must_redraw) + update_screen(0); + else if (clear_cmdline || redraw_cmdline) + showmode(); /* clear cmdline and show mode */ + if ((conceal_update_lines + && (conceal_old_cursor_line != conceal_new_cursor_line + || conceal_cursor_line(curwin))) + || need_cursor_line_redraw) { + if (conceal_old_cursor_line != conceal_new_cursor_line) + update_single_line(curwin, conceal_old_cursor_line); + update_single_line(curwin, conceal_new_cursor_line == 0 + ? curwin->w_cursor.lnum : conceal_new_cursor_line); + curwin->w_valid &= ~VALID_CROW; + } + showruler(FALSE); + setcursor(); + emsg_on_display = FALSE; /* may remove error message now */ +} + +/* + * Handle a CTRL-V or CTRL-Q typed in Insert mode. + */ +static void ins_ctrl_v() { + int c; + int did_putchar = FALSE; + + /* may need to redraw when no more chars available now */ + ins_redraw(FALSE); + + if (redrawing() && !char_avail()) { + edit_putchar('^', TRUE); + did_putchar = TRUE; + } + AppendToRedobuff((char_u *)CTRL_V_STR); /* CTRL-V */ + + add_to_showcmd_c(Ctrl_V); + + c = get_literal(); + if (did_putchar) + /* when the line fits in 'columns' the '^' is at the start of the next + * line and will not removed by the redraw */ + edit_unputchar(); + clear_showcmd(); + insert_special(c, FALSE, TRUE); + revins_chars++; + revins_legal++; +} + +/* + * Put a character directly onto the screen. It's not stored in a buffer. + * Used while handling CTRL-K, CTRL-V, etc. in Insert mode. + */ +static int pc_status; +#define PC_STATUS_UNSET 0 /* pc_bytes was not set */ +#define PC_STATUS_RIGHT 1 /* right halve of double-wide char */ +#define PC_STATUS_LEFT 2 /* left halve of double-wide char */ +#define PC_STATUS_SET 3 /* pc_bytes was filled */ +static char_u pc_bytes[MB_MAXBYTES + 1]; /* saved bytes */ +static int pc_attr; +static int pc_row; +static int pc_col; + +void edit_putchar(c, highlight) +int c; +int highlight; +{ + int attr; + + if (ScreenLines != NULL) { + update_topline(); /* just in case w_topline isn't valid */ + validate_cursor(); + if (highlight) + attr = hl_attr(HLF_8); + else + attr = 0; + pc_row = W_WINROW(curwin) + curwin->w_wrow; + pc_col = W_WINCOL(curwin); + pc_status = PC_STATUS_UNSET; + if (curwin->w_p_rl) { + pc_col += W_WIDTH(curwin) - 1 - curwin->w_wcol; + if (has_mbyte) { + int fix_col = mb_fix_col(pc_col, pc_row); + + if (fix_col != pc_col) { + screen_putchar(' ', pc_row, fix_col, attr); + --curwin->w_wcol; + pc_status = PC_STATUS_RIGHT; + } + } + } else { + pc_col += curwin->w_wcol; + if (mb_lefthalve(pc_row, pc_col)) + pc_status = PC_STATUS_LEFT; + } + + /* save the character to be able to put it back */ + if (pc_status == PC_STATUS_UNSET) { + screen_getbytes(pc_row, pc_col, pc_bytes, &pc_attr); + pc_status = PC_STATUS_SET; + } + screen_putchar(c, pc_row, pc_col, attr); + } +} + +/* + * Undo the previous edit_putchar(). + */ +void edit_unputchar() { + if (pc_status != PC_STATUS_UNSET && pc_row >= msg_scrolled) { + if (pc_status == PC_STATUS_RIGHT) + ++curwin->w_wcol; + if (pc_status == PC_STATUS_RIGHT || pc_status == PC_STATUS_LEFT) + redrawWinline(curwin->w_cursor.lnum, FALSE); + else + screen_puts(pc_bytes, pc_row - msg_scrolled, pc_col, pc_attr); + } +} + +/* + * Called when p_dollar is set: display a '$' at the end of the changed text + * Only works when cursor is in the line that changes. + */ +void display_dollar(col) +colnr_T col; +{ + colnr_T save_col; + + if (!redrawing()) + return; + + cursor_off(); + save_col = curwin->w_cursor.col; + curwin->w_cursor.col = col; + if (has_mbyte) { + char_u *p; + + /* If on the last byte of a multi-byte move to the first byte. */ + p = ml_get_curline(); + curwin->w_cursor.col -= (*mb_head_off)(p, p + col); + } + curs_columns(FALSE); /* recompute w_wrow and w_wcol */ + if (curwin->w_wcol < W_WIDTH(curwin)) { + edit_putchar('$', FALSE); + dollar_vcol = curwin->w_virtcol; + } + curwin->w_cursor.col = save_col; +} + +/* + * Call this function before moving the cursor from the normal insert position + * in insert mode. + */ +static void undisplay_dollar() { + if (dollar_vcol >= 0) { + dollar_vcol = -1; + redrawWinline(curwin->w_cursor.lnum, FALSE); + } +} + +/* + * Insert an indent (for or CTRL-T) or delete an indent (for CTRL-D). + * Keep the cursor on the same character. + * type == INDENT_INC increase indent (for CTRL-T or ) + * type == INDENT_DEC decrease indent (for CTRL-D) + * type == INDENT_SET set indent to "amount" + * if round is TRUE, round the indent to 'shiftwidth' (only with _INC and _Dec). + */ +void change_indent(type, amount, round, replaced, call_changed_bytes) +int type; +int amount; +int round; +int replaced; /* replaced character, put on replace stack */ +int call_changed_bytes; /* call changed_bytes() */ +{ + int vcol; + int last_vcol; + int insstart_less; /* reduction for Insstart.col */ + int new_cursor_col; + int i; + char_u *ptr; + int save_p_list; + int start_col; + colnr_T vc; + colnr_T orig_col = 0; /* init for GCC */ + char_u *new_line, *orig_line = NULL; /* init for GCC */ + + /* VREPLACE mode needs to know what the line was like before changing */ + if (State & VREPLACE_FLAG) { + orig_line = vim_strsave(ml_get_curline()); /* Deal with NULL below */ + orig_col = curwin->w_cursor.col; + } + + /* for the following tricks we don't want list mode */ + save_p_list = curwin->w_p_list; + curwin->w_p_list = FALSE; + vc = getvcol_nolist(&curwin->w_cursor); + vcol = vc; + + /* + * For Replace mode we need to fix the replace stack later, which is only + * possible when the cursor is in the indent. Remember the number of + * characters before the cursor if it's possible. + */ + start_col = curwin->w_cursor.col; + + /* determine offset from first non-blank */ + new_cursor_col = curwin->w_cursor.col; + beginline(BL_WHITE); + new_cursor_col -= curwin->w_cursor.col; + + insstart_less = curwin->w_cursor.col; + + /* + * If the cursor is in the indent, compute how many screen columns the + * cursor is to the left of the first non-blank. + */ + if (new_cursor_col < 0) + vcol = get_indent() - vcol; + + if (new_cursor_col > 0) /* can't fix replace stack */ + start_col = -1; + + /* + * Set the new indent. The cursor will be put on the first non-blank. + */ + if (type == INDENT_SET) + (void)set_indent(amount, call_changed_bytes ? SIN_CHANGED : 0); + else { + int save_State = State; + + /* Avoid being called recursively. */ + if (State & VREPLACE_FLAG) + State = INSERT; + shift_line(type == INDENT_DEC, round, 1, call_changed_bytes); + State = save_State; + } + insstart_less -= curwin->w_cursor.col; + + /* + * Try to put cursor on same character. + * If the cursor is at or after the first non-blank in the line, + * compute the cursor column relative to the column of the first + * non-blank character. + * If we are not in insert mode, leave the cursor on the first non-blank. + * If the cursor is before the first non-blank, position it relative + * to the first non-blank, counted in screen columns. + */ + if (new_cursor_col >= 0) { + /* + * When changing the indent while the cursor is touching it, reset + * Insstart_col to 0. + */ + if (new_cursor_col == 0) + insstart_less = MAXCOL; + new_cursor_col += curwin->w_cursor.col; + } else if (!(State & INSERT)) + new_cursor_col = curwin->w_cursor.col; + else { + /* + * Compute the screen column where the cursor should be. + */ + vcol = get_indent() - vcol; + curwin->w_virtcol = (colnr_T)((vcol < 0) ? 0 : vcol); + + /* + * Advance the cursor until we reach the right screen column. + */ + vcol = last_vcol = 0; + new_cursor_col = -1; + ptr = ml_get_curline(); + while (vcol <= (int)curwin->w_virtcol) { + last_vcol = vcol; + if (has_mbyte && new_cursor_col >= 0) + new_cursor_col += (*mb_ptr2len)(ptr + new_cursor_col); + else + ++new_cursor_col; + vcol += lbr_chartabsize(ptr + new_cursor_col, (colnr_T)vcol); + } + vcol = last_vcol; + + /* + * May need to insert spaces to be able to position the cursor on + * the right screen column. + */ + if (vcol != (int)curwin->w_virtcol) { + curwin->w_cursor.col = (colnr_T)new_cursor_col; + i = (int)curwin->w_virtcol - vcol; + ptr = alloc((unsigned)(i + 1)); + if (ptr != NULL) { + new_cursor_col += i; + ptr[i] = NUL; + while (--i >= 0) + ptr[i] = ' '; + ins_str(ptr); + vim_free(ptr); + } + } + + /* + * When changing the indent while the cursor is in it, reset + * Insstart_col to 0. + */ + insstart_less = MAXCOL; + } + + curwin->w_p_list = save_p_list; + + if (new_cursor_col <= 0) + curwin->w_cursor.col = 0; + else + curwin->w_cursor.col = (colnr_T)new_cursor_col; + curwin->w_set_curswant = TRUE; + changed_cline_bef_curs(); + + /* + * May have to adjust the start of the insert. + */ + if (State & INSERT) { + if (curwin->w_cursor.lnum == Insstart.lnum && Insstart.col != 0) { + if ((int)Insstart.col <= insstart_less) + Insstart.col = 0; + else + Insstart.col -= insstart_less; + } + if ((int)ai_col <= insstart_less) + ai_col = 0; + else + ai_col -= insstart_less; + } + + /* + * For REPLACE mode, may have to fix the replace stack, if it's possible. + * If the number of characters before the cursor decreased, need to pop a + * few characters from the replace stack. + * If the number of characters before the cursor increased, need to push a + * few NULs onto the replace stack. + */ + if (REPLACE_NORMAL(State) && start_col >= 0) { + while (start_col > (int)curwin->w_cursor.col) { + replace_join(0); /* remove a NUL from the replace stack */ + --start_col; + } + while (start_col < (int)curwin->w_cursor.col || replaced) { + replace_push(NUL); + if (replaced) { + replace_push(replaced); + replaced = NUL; + } + ++start_col; + } + } + + /* + * For VREPLACE mode, we also have to fix the replace stack. In this case + * it is always possible because we backspace over the whole line and then + * put it back again the way we wanted it. + */ + if (State & VREPLACE_FLAG) { + /* If orig_line didn't allocate, just return. At least we did the job, + * even if you can't backspace. */ + if (orig_line == NULL) + return; + + /* Save new line */ + new_line = vim_strsave(ml_get_curline()); + if (new_line == NULL) + return; + + /* We only put back the new line up to the cursor */ + new_line[curwin->w_cursor.col] = NUL; + + /* Put back original line */ + ml_replace(curwin->w_cursor.lnum, orig_line, FALSE); + curwin->w_cursor.col = orig_col; + + /* Backspace from cursor to start of line */ + backspace_until_column(0); + + /* Insert new stuff into line again */ + ins_bytes(new_line); + + vim_free(new_line); + } +} + +/* + * Truncate the space at the end of a line. This is to be used only in an + * insert mode. It handles fixing the replace stack for REPLACE and VREPLACE + * modes. + */ +void truncate_spaces(line) +char_u *line; +{ + int i; + + /* find start of trailing white space */ + for (i = (int)STRLEN(line) - 1; i >= 0 && vim_iswhite(line[i]); i--) { + if (State & REPLACE_FLAG) + replace_join(0); /* remove a NUL from the replace stack */ + } + line[i + 1] = NUL; +} + +#if defined(FEAT_VREPLACE) || defined(FEAT_INS_EXPAND) \ + || defined(FEAT_COMMENTS) || defined(PROTO) +/* + * Backspace the cursor until the given column. Handles REPLACE and VREPLACE + * modes correctly. May also be used when not in insert mode at all. + * Will attempt not to go before "col" even when there is a composing + * character. + */ +void backspace_until_column(col) +int col; +{ + while ((int)curwin->w_cursor.col > col) { + curwin->w_cursor.col--; + if (State & REPLACE_FLAG) + replace_do_bs(col); + else if (!del_char_after_col(col)) + break; + } +} +#endif + +/* + * Like del_char(), but make sure not to go before column "limit_col". + * Only matters when there are composing characters. + * Return TRUE when something was deleted. + */ +static int del_char_after_col(limit_col) +int limit_col UNUSED; +{ + if (enc_utf8 && limit_col >= 0) { + colnr_T ecol = curwin->w_cursor.col + 1; + + /* Make sure the cursor is at the start of a character, but + * skip forward again when going too far back because of a + * composing character. */ + mb_adjust_cursor(); + while (curwin->w_cursor.col < (colnr_T)limit_col) { + int l = utf_ptr2len(ml_get_cursor()); + + if (l == 0) /* end of line */ + break; + curwin->w_cursor.col += l; + } + if (*ml_get_cursor() == NUL || curwin->w_cursor.col == ecol) + return FALSE; + del_bytes((long)((int)ecol - curwin->w_cursor.col), FALSE, TRUE); + } else + (void)del_char(FALSE); + return TRUE; +} + +/* + * CTRL-X pressed in Insert mode. + */ +static void ins_ctrl_x() { + /* CTRL-X after CTRL-X CTRL-V doesn't do anything, so that CTRL-X + * CTRL-V works like CTRL-N */ + if (ctrl_x_mode != CTRL_X_CMDLINE) { + /* if the next ^X<> won't ADD nothing, then reset + * compl_cont_status */ + if (compl_cont_status & CONT_N_ADDS) + compl_cont_status |= CONT_INTRPT; + else + compl_cont_status = 0; + /* We're not sure which CTRL-X mode it will be yet */ + ctrl_x_mode = CTRL_X_NOT_DEFINED_YET; + edit_submode = (char_u *)_(CTRL_X_MSG(ctrl_x_mode)); + edit_submode_pre = NULL; + showmode(); + } +} + +/* + * Return TRUE if the 'dict' or 'tsr' option can be used. + */ +static int has_compl_option(dict_opt) +int dict_opt; +{ + if (dict_opt ? (*curbuf->b_p_dict == NUL && *p_dict == NUL + && !curwin->w_p_spell + ) + : (*curbuf->b_p_tsr == NUL && *p_tsr == NUL)) { + ctrl_x_mode = 0; + edit_submode = NULL; + msg_attr(dict_opt ? (char_u *)_("'dictionary' option is empty") + : (char_u *)_("'thesaurus' option is empty"), + hl_attr(HLF_E)); + if (emsg_silent == 0) { + vim_beep(); + setcursor(); + out_flush(); + ui_delay(2000L, FALSE); + } + return FALSE; + } + return TRUE; +} + +/* + * Is the character 'c' a valid key to go to or keep us in CTRL-X mode? + * This depends on the current mode. + */ +int vim_is_ctrl_x_key(c) +int c; +{ + /* Always allow ^R - let it's results then be checked */ + if (c == Ctrl_R) + return TRUE; + + /* Accept and if the popup menu is visible. */ + if (ins_compl_pum_key(c)) + return TRUE; + + switch (ctrl_x_mode) { + case 0: /* Not in any CTRL-X mode */ + return c == Ctrl_N || c == Ctrl_P || c == Ctrl_X; + case CTRL_X_NOT_DEFINED_YET: + return c == Ctrl_X || c == Ctrl_Y || c == Ctrl_E + || c == Ctrl_L || c == Ctrl_F || c == Ctrl_RSB + || c == Ctrl_I || c == Ctrl_D || c == Ctrl_P + || c == Ctrl_N || c == Ctrl_T || c == Ctrl_V + || c == Ctrl_Q || c == Ctrl_U || c == Ctrl_O + || c == Ctrl_S || c == Ctrl_K || c == 's'; + case CTRL_X_SCROLL: + return c == Ctrl_Y || c == Ctrl_E; + case CTRL_X_WHOLE_LINE: + return c == Ctrl_L || c == Ctrl_P || c == Ctrl_N; + case CTRL_X_FILES: + return c == Ctrl_F || c == Ctrl_P || c == Ctrl_N; + case CTRL_X_DICTIONARY: + return c == Ctrl_K || c == Ctrl_P || c == Ctrl_N; + case CTRL_X_THESAURUS: + return c == Ctrl_T || c == Ctrl_P || c == Ctrl_N; + case CTRL_X_TAGS: + return c == Ctrl_RSB || c == Ctrl_P || c == Ctrl_N; + case CTRL_X_PATH_PATTERNS: + return c == Ctrl_P || c == Ctrl_N; + case CTRL_X_PATH_DEFINES: + return c == Ctrl_D || c == Ctrl_P || c == Ctrl_N; + case CTRL_X_CMDLINE: + return c == Ctrl_V || c == Ctrl_Q || c == Ctrl_P || c == Ctrl_N + || c == Ctrl_X; + case CTRL_X_FUNCTION: + return c == Ctrl_U || c == Ctrl_P || c == Ctrl_N; + case CTRL_X_OMNI: + return c == Ctrl_O || c == Ctrl_P || c == Ctrl_N; + case CTRL_X_SPELL: + return c == Ctrl_S || c == Ctrl_P || c == Ctrl_N; + } + EMSG(_(e_internal)); + return FALSE; +} + +/* + * Return TRUE when character "c" is part of the item currently being + * completed. Used to decide whether to abandon complete mode when the menu + * is visible. + */ +static int ins_compl_accept_char(c) +int c; +{ + if (ctrl_x_mode & CTRL_X_WANT_IDENT) + /* When expanding an identifier only accept identifier chars. */ + return vim_isIDc(c); + + switch (ctrl_x_mode) { + case CTRL_X_FILES: + /* When expanding file name only accept file name chars. But not + * path separators, so that "proto/" expands files in + * "proto", not "proto/" as a whole */ + return vim_isfilec(c) && !vim_ispathsep(c); + + case CTRL_X_CMDLINE: + case CTRL_X_OMNI: + /* Command line and Omni completion can work with just about any + * printable character, but do stop at white space. */ + return vim_isprintc(c) && !vim_iswhite(c); + + case CTRL_X_WHOLE_LINE: + /* For while line completion a space can be part of the line. */ + return vim_isprintc(c); + } + return vim_iswordc(c); +} + +/* + * This is like ins_compl_add(), but if 'ic' and 'inf' are set, then the + * case of the originally typed text is used, and the case of the completed + * text is inferred, ie this tries to work out what case you probably wanted + * the rest of the word to be in -- webb + */ +int ins_compl_add_infercase(str, len, icase, fname, dir, flags) +char_u *str; +int len; +int icase; +char_u *fname; +int dir; +int flags; +{ + char_u *p; + int i, c; + int actual_len; /* Take multi-byte characters */ + int actual_compl_length; /* into account. */ + int min_len; + int *wca; /* Wide character array. */ + int has_lower = FALSE; + int was_letter = FALSE; + + if (p_ic && curbuf->b_p_inf && len > 0) { + /* Infer case of completed part. */ + + /* Find actual length of completion. */ + if (has_mbyte) { + p = str; + actual_len = 0; + while (*p != NUL) { + mb_ptr_adv(p); + ++actual_len; + } + } else + actual_len = len; + + /* Find actual length of original text. */ + if (has_mbyte) { + p = compl_orig_text; + actual_compl_length = 0; + while (*p != NUL) { + mb_ptr_adv(p); + ++actual_compl_length; + } + } else + actual_compl_length = compl_length; + + /* "actual_len" may be smaller than "actual_compl_length" when using + * thesaurus, only use the minimum when comparing. */ + min_len = actual_len < actual_compl_length + ? actual_len : actual_compl_length; + + /* Allocate wide character array for the completion and fill it. */ + wca = (int *)alloc((unsigned)(actual_len * sizeof(int))); + if (wca != NULL) { + p = str; + for (i = 0; i < actual_len; ++i) + if (has_mbyte) + wca[i] = mb_ptr2char_adv(&p); + else + wca[i] = *(p++); + + /* Rule 1: Were any chars converted to lower? */ + p = compl_orig_text; + for (i = 0; i < min_len; ++i) { + if (has_mbyte) + c = mb_ptr2char_adv(&p); + else + c = *(p++); + if (MB_ISLOWER(c)) { + has_lower = TRUE; + if (MB_ISUPPER(wca[i])) { + /* Rule 1 is satisfied. */ + for (i = actual_compl_length; i < actual_len; ++i) + wca[i] = MB_TOLOWER(wca[i]); + break; + } + } + } + + /* + * Rule 2: No lower case, 2nd consecutive letter converted to + * upper case. + */ + if (!has_lower) { + p = compl_orig_text; + for (i = 0; i < min_len; ++i) { + if (has_mbyte) + c = mb_ptr2char_adv(&p); + else + c = *(p++); + if (was_letter && MB_ISUPPER(c) && MB_ISLOWER(wca[i])) { + /* Rule 2 is satisfied. */ + for (i = actual_compl_length; i < actual_len; ++i) + wca[i] = MB_TOUPPER(wca[i]); + break; + } + was_letter = MB_ISLOWER(c) || MB_ISUPPER(c); + } + } + + /* Copy the original case of the part we typed. */ + p = compl_orig_text; + for (i = 0; i < min_len; ++i) { + if (has_mbyte) + c = mb_ptr2char_adv(&p); + else + c = *(p++); + if (MB_ISLOWER(c)) + wca[i] = MB_TOLOWER(wca[i]); + else if (MB_ISUPPER(c)) + wca[i] = MB_TOUPPER(wca[i]); + } + + /* + * Generate encoding specific output from wide character array. + * Multi-byte characters can occupy up to five bytes more than + * ASCII characters, and we also need one byte for NUL, so stay + * six bytes away from the edge of IObuff. + */ + p = IObuff; + i = 0; + while (i < actual_len && (p - IObuff + 6) < IOSIZE) + if (has_mbyte) + p += (*mb_char2bytes)(wca[i++], p); + else + *(p++) = wca[i++]; + *p = NUL; + + vim_free(wca); + } + + return ins_compl_add(IObuff, len, icase, fname, NULL, dir, + flags, FALSE); + } + return ins_compl_add(str, len, icase, fname, NULL, dir, flags, FALSE); +} + +/* + * Add a match to the list of matches. + * If the given string is already in the list of completions, then return + * NOTDONE, otherwise add it to the list and return OK. If there is an error, + * maybe because alloc() returns NULL, then FAIL is returned. + */ +static int ins_compl_add(str, len, icase, fname, cptext, cdir, flags, adup) +char_u *str; +int len; +int icase; +char_u *fname; +char_u **cptext; /* extra text for popup menu or NULL */ +int cdir; +int flags; +int adup; /* accept duplicate match */ +{ + compl_T *match; + int dir = (cdir == 0 ? compl_direction : cdir); + + ui_breakcheck(); + if (got_int) + return FAIL; + if (len < 0) + len = (int)STRLEN(str); + + /* + * If the same match is already present, don't add it. + */ + if (compl_first_match != NULL && !adup) { + match = compl_first_match; + do { + if ( !(match->cp_flags & ORIGINAL_TEXT) + && STRNCMP(match->cp_str, str, len) == 0 + && match->cp_str[len] == NUL) + return NOTDONE; + match = match->cp_next; + } while (match != NULL && match != compl_first_match); + } + + /* Remove any popup menu before changing the list of matches. */ + ins_compl_del_pum(); + + /* + * Allocate a new match structure. + * Copy the values to the new match structure. + */ + match = (compl_T *)alloc_clear((unsigned)sizeof(compl_T)); + if (match == NULL) + return FAIL; + match->cp_number = -1; + if (flags & ORIGINAL_TEXT) + match->cp_number = 0; + if ((match->cp_str = vim_strnsave(str, len)) == NULL) { + vim_free(match); + return FAIL; + } + match->cp_icase = icase; + + /* match-fname is: + * - compl_curr_match->cp_fname if it is a string equal to fname. + * - a copy of fname, FREE_FNAME is set to free later THE allocated mem. + * - NULL otherwise. --Acevedo */ + if (fname != NULL + && compl_curr_match != NULL + && compl_curr_match->cp_fname != NULL + && STRCMP(fname, compl_curr_match->cp_fname) == 0) + match->cp_fname = compl_curr_match->cp_fname; + else if (fname != NULL) { + match->cp_fname = vim_strsave(fname); + flags |= FREE_FNAME; + } else + match->cp_fname = NULL; + match->cp_flags = flags; + + if (cptext != NULL) { + int i; + + for (i = 0; i < CPT_COUNT; ++i) + if (cptext[i] != NULL && *cptext[i] != NUL) + match->cp_text[i] = vim_strsave(cptext[i]); + } + + /* + * Link the new match structure in the list of matches. + */ + if (compl_first_match == NULL) + match->cp_next = match->cp_prev = NULL; + else if (dir == FORWARD) { + match->cp_next = compl_curr_match->cp_next; + match->cp_prev = compl_curr_match; + } else { /* BACKWARD */ + match->cp_next = compl_curr_match; + match->cp_prev = compl_curr_match->cp_prev; + } + if (match->cp_next) + match->cp_next->cp_prev = match; + if (match->cp_prev) + match->cp_prev->cp_next = match; + else /* if there's nothing before, it is the first match */ + compl_first_match = match; + compl_curr_match = match; + + /* + * Find the longest common string if still doing that. + */ + if (compl_get_longest && (flags & ORIGINAL_TEXT) == 0) + ins_compl_longest_match(match); + + return OK; +} + +/* + * Return TRUE if "str[len]" matches with match->cp_str, considering + * match->cp_icase. + */ +static int ins_compl_equal(match, str, len) +compl_T *match; +char_u *str; +int len; +{ + if (match->cp_icase) + return STRNICMP(match->cp_str, str, (size_t)len) == 0; + return STRNCMP(match->cp_str, str, (size_t)len) == 0; +} + +/* + * Reduce the longest common string for match "match". + */ +static void ins_compl_longest_match(match) +compl_T *match; +{ + char_u *p, *s; + int c1, c2; + int had_match; + + if (compl_leader == NULL) { + /* First match, use it as a whole. */ + compl_leader = vim_strsave(match->cp_str); + if (compl_leader != NULL) { + had_match = (curwin->w_cursor.col > compl_col); + ins_compl_delete(); + ins_bytes(compl_leader + ins_compl_len()); + ins_redraw(FALSE); + + /* When the match isn't there (to avoid matching itself) remove it + * again after redrawing. */ + if (!had_match) + ins_compl_delete(); + compl_used_match = FALSE; + } + } else { + /* Reduce the text if this match differs from compl_leader. */ + p = compl_leader; + s = match->cp_str; + while (*p != NUL) { + if (has_mbyte) { + c1 = mb_ptr2char(p); + c2 = mb_ptr2char(s); + } else { + c1 = *p; + c2 = *s; + } + if (match->cp_icase ? (MB_TOLOWER(c1) != MB_TOLOWER(c2)) + : (c1 != c2)) + break; + if (has_mbyte) { + mb_ptr_adv(p); + mb_ptr_adv(s); + } else { + ++p; + ++s; + } + } + + if (*p != NUL) { + /* Leader was shortened, need to change the inserted text. */ + *p = NUL; + had_match = (curwin->w_cursor.col > compl_col); + ins_compl_delete(); + ins_bytes(compl_leader + ins_compl_len()); + ins_redraw(FALSE); + + /* When the match isn't there (to avoid matching itself) remove it + * again after redrawing. */ + if (!had_match) + ins_compl_delete(); + } + + compl_used_match = FALSE; + } +} + +/* + * Add an array of matches to the list of matches. + * Frees matches[]. + */ +static void ins_compl_add_matches(num_matches, matches, icase) +int num_matches; +char_u **matches; +int icase; +{ + int i; + int add_r = OK; + int dir = compl_direction; + + for (i = 0; i < num_matches && add_r != FAIL; i++) + if ((add_r = ins_compl_add(matches[i], -1, icase, + NULL, NULL, dir, 0, FALSE)) == OK) + /* if dir was BACKWARD then honor it just once */ + dir = FORWARD; + FreeWild(num_matches, matches); +} + +/* Make the completion list cyclic. + * Return the number of matches (excluding the original). + */ +static int ins_compl_make_cyclic() { + compl_T *match; + int count = 0; + + if (compl_first_match != NULL) { + /* + * Find the end of the list. + */ + match = compl_first_match; + /* there's always an entry for the compl_orig_text, it doesn't count. */ + while (match->cp_next != NULL && match->cp_next != compl_first_match) { + match = match->cp_next; + ++count; + } + match->cp_next = compl_first_match; + compl_first_match->cp_prev = match; + } + return count; +} + +/* + * Start completion for the complete() function. + * "startcol" is where the matched text starts (1 is first column). + * "list" is the list of matches. + */ +void set_completion(startcol, list) +colnr_T startcol; +list_T *list; +{ + /* If already doing completions stop it. */ + if (ctrl_x_mode != 0) + ins_compl_prep(' '); + ins_compl_clear(); + + if (stop_arrow() == FAIL) + return; + + compl_direction = FORWARD; + if (startcol > curwin->w_cursor.col) + startcol = curwin->w_cursor.col; + compl_col = startcol; + compl_length = (int)curwin->w_cursor.col - (int)startcol; + /* compl_pattern doesn't need to be set */ + compl_orig_text = vim_strnsave(ml_get_curline() + compl_col, compl_length); + if (compl_orig_text == NULL || ins_compl_add(compl_orig_text, + -1, p_ic, NULL, NULL, 0, ORIGINAL_TEXT, FALSE) != OK) + return; + + /* Handle like dictionary completion. */ + ctrl_x_mode = CTRL_X_WHOLE_LINE; + + ins_compl_add_list(list); + compl_matches = ins_compl_make_cyclic(); + compl_started = TRUE; + compl_used_match = TRUE; + compl_cont_status = 0; + + compl_curr_match = compl_first_match; + ins_complete(Ctrl_N); + out_flush(); +} + + +/* "compl_match_array" points the currently displayed list of entries in the + * popup menu. It is NULL when there is no popup menu. */ +static pumitem_T *compl_match_array = NULL; +static int compl_match_arraysize; + +/* + * Update the screen and when there is any scrolling remove the popup menu. + */ +static void ins_compl_upd_pum() { + int h; + + if (compl_match_array != NULL) { + h = curwin->w_cline_height; + update_screen(0); + if (h != curwin->w_cline_height) + ins_compl_del_pum(); + } +} + +/* + * Remove any popup menu. + */ +static void ins_compl_del_pum() { + if (compl_match_array != NULL) { + pum_undisplay(); + vim_free(compl_match_array); + compl_match_array = NULL; + } +} + +/* + * Return TRUE if the popup menu should be displayed. + */ +static int pum_wanted() { + /* 'completeopt' must contain "menu" or "menuone" */ + if (vim_strchr(p_cot, 'm') == NULL) + return FALSE; + + /* The display looks bad on a B&W display. */ + if (t_colors < 8 + ) + return FALSE; + return TRUE; +} + +/* + * Return TRUE if there are two or more matches to be shown in the popup menu. + * One if 'completopt' contains "menuone". + */ +static int pum_enough_matches() { + compl_T *compl; + int i; + + /* Don't display the popup menu if there are no matches or there is only + * one (ignoring the original text). */ + compl = compl_first_match; + i = 0; + do { + if (compl == NULL + || ((compl->cp_flags & ORIGINAL_TEXT) == 0 && ++i == 2)) + break; + compl = compl->cp_next; + } while (compl != compl_first_match); + + if (strstr((char *)p_cot, "menuone") != NULL) + return i >= 1; + return i >= 2; +} + +/* + * Show the popup menu for the list of matches. + * Also adjusts "compl_shown_match" to an entry that is actually displayed. + */ +void ins_compl_show_pum() { + compl_T *compl; + compl_T *shown_compl = NULL; + int did_find_shown_match = FALSE; + int shown_match_ok = FALSE; + int i; + int cur = -1; + colnr_T col; + int lead_len = 0; + + if (!pum_wanted() || !pum_enough_matches()) + return; + + /* Dirty hard-coded hack: remove any matchparen highlighting. */ + do_cmdline_cmd((char_u *)"if exists('g:loaded_matchparen')|3match none|endif"); + + /* Update the screen before drawing the popup menu over it. */ + update_screen(0); + + if (compl_match_array == NULL) { + /* Need to build the popup menu list. */ + compl_match_arraysize = 0; + compl = compl_first_match; + if (compl_leader != NULL) + lead_len = (int)STRLEN(compl_leader); + do { + if ((compl->cp_flags & ORIGINAL_TEXT) == 0 + && (compl_leader == NULL + || ins_compl_equal(compl, compl_leader, lead_len))) + ++compl_match_arraysize; + compl = compl->cp_next; + } while (compl != NULL && compl != compl_first_match); + if (compl_match_arraysize == 0) + return; + compl_match_array = (pumitem_T *)alloc_clear( + (unsigned)(sizeof(pumitem_T) + * compl_match_arraysize)); + if (compl_match_array != NULL) { + /* If the current match is the original text don't find the first + * match after it, don't highlight anything. */ + if (compl_shown_match->cp_flags & ORIGINAL_TEXT) + shown_match_ok = TRUE; + + i = 0; + compl = compl_first_match; + do { + if ((compl->cp_flags & ORIGINAL_TEXT) == 0 + && (compl_leader == NULL + || ins_compl_equal(compl, compl_leader, lead_len))) { + if (!shown_match_ok) { + if (compl == compl_shown_match || did_find_shown_match) { + /* This item is the shown match or this is the + * first displayed item after the shown match. */ + compl_shown_match = compl; + did_find_shown_match = TRUE; + shown_match_ok = TRUE; + } else + /* Remember this displayed match for when the + * shown match is just below it. */ + shown_compl = compl; + cur = i; + } + + if (compl->cp_text[CPT_ABBR] != NULL) + compl_match_array[i].pum_text = + compl->cp_text[CPT_ABBR]; + else + compl_match_array[i].pum_text = compl->cp_str; + compl_match_array[i].pum_kind = compl->cp_text[CPT_KIND]; + compl_match_array[i].pum_info = compl->cp_text[CPT_INFO]; + if (compl->cp_text[CPT_MENU] != NULL) + compl_match_array[i++].pum_extra = + compl->cp_text[CPT_MENU]; + else + compl_match_array[i++].pum_extra = compl->cp_fname; + } + + if (compl == compl_shown_match) { + did_find_shown_match = TRUE; + + /* When the original text is the shown match don't set + * compl_shown_match. */ + if (compl->cp_flags & ORIGINAL_TEXT) + shown_match_ok = TRUE; + + if (!shown_match_ok && shown_compl != NULL) { + /* The shown match isn't displayed, set it to the + * previously displayed match. */ + compl_shown_match = shown_compl; + shown_match_ok = TRUE; + } + } + compl = compl->cp_next; + } while (compl != NULL && compl != compl_first_match); + + if (!shown_match_ok) /* no displayed match at all */ + cur = -1; + } + } else { + /* popup menu already exists, only need to find the current item.*/ + for (i = 0; i < compl_match_arraysize; ++i) + if (compl_match_array[i].pum_text == compl_shown_match->cp_str + || compl_match_array[i].pum_text + == compl_shown_match->cp_text[CPT_ABBR]) { + cur = i; + break; + } + } + + if (compl_match_array != NULL) { + /* Compute the screen column of the start of the completed text. + * Use the cursor to get all wrapping and other settings right. */ + col = curwin->w_cursor.col; + curwin->w_cursor.col = compl_col; + pum_display(compl_match_array, compl_match_arraysize, cur); + curwin->w_cursor.col = col; + } +} + +#define DICT_FIRST (1) /* use just first element in "dict" */ +#define DICT_EXACT (2) /* "dict" is the exact name of a file */ + +/* + * Add any identifiers that match the given pattern in the list of dictionary + * files "dict_start" to the list of completions. + */ +static void ins_compl_dictionaries(dict_start, pat, flags, thesaurus) +char_u *dict_start; +char_u *pat; +int flags; /* DICT_FIRST and/or DICT_EXACT */ +int thesaurus; /* Thesaurus completion */ +{ + char_u *dict = dict_start; + char_u *ptr; + char_u *buf; + regmatch_T regmatch; + char_u **files; + int count; + int save_p_scs; + int dir = compl_direction; + + if (*dict == NUL) { + /* When 'dictionary' is empty and spell checking is enabled use + * "spell". */ + if (!thesaurus && curwin->w_p_spell) + dict = (char_u *)"spell"; + else + return; + } + + buf = alloc(LSIZE); + if (buf == NULL) + return; + regmatch.regprog = NULL; /* so that we can goto theend */ + + /* If 'infercase' is set, don't use 'smartcase' here */ + save_p_scs = p_scs; + if (curbuf->b_p_inf) + p_scs = FALSE; + + /* When invoked to match whole lines for CTRL-X CTRL-L adjust the pattern + * to only match at the start of a line. Otherwise just match the + * pattern. Also need to double backslashes. */ + if (ctrl_x_mode == CTRL_X_WHOLE_LINE) { + char_u *pat_esc = vim_strsave_escaped(pat, (char_u *)"\\"); + size_t len; + + if (pat_esc == NULL) + goto theend; + len = STRLEN(pat_esc) + 10; + ptr = alloc((unsigned)len); + if (ptr == NULL) { + vim_free(pat_esc); + goto theend; + } + vim_snprintf((char *)ptr, len, "^\\s*\\zs\\V%s", pat_esc); + regmatch.regprog = vim_regcomp(ptr, RE_MAGIC); + vim_free(pat_esc); + vim_free(ptr); + } else { + regmatch.regprog = vim_regcomp(pat, p_magic ? RE_MAGIC : 0); + if (regmatch.regprog == NULL) + goto theend; + } + + /* ignore case depends on 'ignorecase', 'smartcase' and "pat" */ + regmatch.rm_ic = ignorecase(pat); + while (*dict != NUL && !got_int && !compl_interrupted) { + /* copy one dictionary file name into buf */ + if (flags == DICT_EXACT) { + count = 1; + files = &dict; + } else { + /* Expand wildcards in the dictionary name, but do not allow + * backticks (for security, the 'dict' option may have been set in + * a modeline). */ + copy_option_part(&dict, buf, LSIZE, ","); + if (!thesaurus && STRCMP(buf, "spell") == 0) + count = -1; + else if (vim_strchr(buf, '`') != NULL + || expand_wildcards(1, &buf, &count, &files, + EW_FILE|EW_SILENT) != OK) + count = 0; + } + + if (count == -1) { + /* Complete from active spelling. Skip "\<" in the pattern, we + * don't use it as a RE. */ + if (pat[0] == '\\' && pat[1] == '<') + ptr = pat + 2; + else + ptr = pat; + spell_dump_compl(ptr, regmatch.rm_ic, &dir, 0); + } else if (count > 0) { /* avoid warning for using "files" uninit */ + ins_compl_files(count, files, thesaurus, flags, + ®match, buf, &dir); + if (flags != DICT_EXACT) + FreeWild(count, files); + } + if (flags != 0) + break; + } + +theend: + p_scs = save_p_scs; + vim_regfree(regmatch.regprog); + vim_free(buf); +} + +static void ins_compl_files(count, files, thesaurus, flags, regmatch, buf, dir) +int count; +char_u **files; +int thesaurus; +int flags; +regmatch_T *regmatch; +char_u *buf; +int *dir; +{ + char_u *ptr; + int i; + FILE *fp; + int add_r; + + for (i = 0; i < count && !got_int && !compl_interrupted; i++) { + fp = mch_fopen((char *)files[i], "r"); /* open dictionary file */ + if (flags != DICT_EXACT) { + vim_snprintf((char *)IObuff, IOSIZE, + _("Scanning dictionary: %s"), (char *)files[i]); + (void)msg_trunc_attr(IObuff, TRUE, hl_attr(HLF_R)); + } + + if (fp != NULL) { + /* + * Read dictionary file line by line. + * Check each line for a match. + */ + while (!got_int && !compl_interrupted + && !vim_fgets(buf, LSIZE, fp)) { + ptr = buf; + while (vim_regexec(regmatch, buf, (colnr_T)(ptr - buf))) { + ptr = regmatch->startp[0]; + if (ctrl_x_mode == CTRL_X_WHOLE_LINE) + ptr = find_line_end(ptr); + else + ptr = find_word_end(ptr); + add_r = ins_compl_add_infercase(regmatch->startp[0], + (int)(ptr - regmatch->startp[0]), + p_ic, files[i], *dir, 0); + if (thesaurus) { + char_u *wstart; + + /* + * Add the other matches on the line + */ + ptr = buf; + while (!got_int) { + /* Find start of the next word. Skip white + * space and punctuation. */ + ptr = find_word_start(ptr); + if (*ptr == NUL || *ptr == NL) + break; + wstart = ptr; + + /* Find end of the word. */ + if (has_mbyte) + /* Japanese words may have characters in + * different classes, only separate words + * with single-byte non-word characters. */ + while (*ptr != NUL) { + int l = (*mb_ptr2len)(ptr); + + if (l < 2 && !vim_iswordc(*ptr)) + break; + ptr += l; + } + else + ptr = find_word_end(ptr); + + /* Add the word. Skip the regexp match. */ + if (wstart != regmatch->startp[0]) + add_r = ins_compl_add_infercase(wstart, + (int)(ptr - wstart), + p_ic, files[i], *dir, 0); + } + } + if (add_r == OK) + /* if dir was BACKWARD then honor it just once */ + *dir = FORWARD; + else if (add_r == FAIL) + break; + /* avoid expensive call to vim_regexec() when at end + * of line */ + if (*ptr == '\n' || got_int) + break; + } + line_breakcheck(); + ins_compl_check_keys(50); + } + fclose(fp); + } + } +} + +/* + * Find the start of the next word. + * Returns a pointer to the first char of the word. Also stops at a NUL. + */ +char_u * find_word_start(ptr) +char_u *ptr; +{ + if (has_mbyte) + while (*ptr != NUL && *ptr != '\n' && mb_get_class(ptr) <= 1) + ptr += (*mb_ptr2len)(ptr); + else + while (*ptr != NUL && *ptr != '\n' && !vim_iswordc(*ptr)) + ++ptr; + return ptr; +} + +/* + * Find the end of the word. Assumes it starts inside a word. + * Returns a pointer to just after the word. + */ +char_u * find_word_end(ptr) +char_u *ptr; +{ + int start_class; + + if (has_mbyte) { + start_class = mb_get_class(ptr); + if (start_class > 1) + while (*ptr != NUL) { + ptr += (*mb_ptr2len)(ptr); + if (mb_get_class(ptr) != start_class) + break; + } + } else + while (vim_iswordc(*ptr)) + ++ptr; + return ptr; +} + +/* + * Find the end of the line, omitting CR and NL at the end. + * Returns a pointer to just after the line. + */ +static char_u * find_line_end(ptr) +char_u *ptr; +{ + char_u *s; + + s = ptr + STRLEN(ptr); + while (s > ptr && (s[-1] == CAR || s[-1] == NL)) + --s; + return s; +} + +/* + * Free the list of completions + */ +static void ins_compl_free() { + compl_T *match; + int i; + + vim_free(compl_pattern); + compl_pattern = NULL; + vim_free(compl_leader); + compl_leader = NULL; + + if (compl_first_match == NULL) + return; + + ins_compl_del_pum(); + pum_clear(); + + compl_curr_match = compl_first_match; + do { + match = compl_curr_match; + compl_curr_match = compl_curr_match->cp_next; + vim_free(match->cp_str); + /* several entries may use the same fname, free it just once. */ + if (match->cp_flags & FREE_FNAME) + vim_free(match->cp_fname); + for (i = 0; i < CPT_COUNT; ++i) + vim_free(match->cp_text[i]); + vim_free(match); + } while (compl_curr_match != NULL && compl_curr_match != compl_first_match); + compl_first_match = compl_curr_match = NULL; + compl_shown_match = NULL; +} + +static void ins_compl_clear() { + compl_cont_status = 0; + compl_started = FALSE; + compl_matches = 0; + vim_free(compl_pattern); + compl_pattern = NULL; + vim_free(compl_leader); + compl_leader = NULL; + edit_submode_extra = NULL; + vim_free(compl_orig_text); + compl_orig_text = NULL; + compl_enter_selects = FALSE; +} + +/* + * Return TRUE when Insert completion is active. + */ +int ins_compl_active() { + return compl_started; +} + +/* + * Delete one character before the cursor and show the subset of the matches + * that match the word that is now before the cursor. + * Returns the character to be used, NUL if the work is done and another char + * to be got from the user. + */ +static int ins_compl_bs() { + char_u *line; + char_u *p; + + line = ml_get_curline(); + p = line + curwin->w_cursor.col; + mb_ptr_back(line, p); + + /* Stop completion when the whole word was deleted. For Omni completion + * allow the word to be deleted, we won't match everything. */ + if ((int)(p - line) - (int)compl_col < 0 + || ((int)(p - line) - (int)compl_col == 0 + && (ctrl_x_mode & CTRL_X_OMNI) == 0)) + return K_BS; + + /* Deleted more than what was used to find matches or didn't finish + * finding all matches: need to look for matches all over again. */ + if (curwin->w_cursor.col <= compl_col + compl_length + || ins_compl_need_restart()) + ins_compl_restart(); + + vim_free(compl_leader); + compl_leader = vim_strnsave(line + compl_col, (int)(p - line) - compl_col); + if (compl_leader != NULL) { + ins_compl_new_leader(); + if (compl_shown_match != NULL) + /* Make sure current match is not a hidden item. */ + compl_curr_match = compl_shown_match; + return NUL; + } + return K_BS; +} + +/* + * Return TRUE when we need to find matches again, ins_compl_restart() is to + * be called. + */ +static int ins_compl_need_restart() { + /* Return TRUE if we didn't complete finding matches or when the + * 'completefunc' returned "always" in the "refresh" dictionary item. */ + return compl_was_interrupted + || ((ctrl_x_mode == CTRL_X_FUNCTION || ctrl_x_mode == CTRL_X_OMNI) + && compl_opt_refresh_always); +} + +/* + * Called after changing "compl_leader". + * Show the popup menu with a different set of matches. + * May also search for matches again if the previous search was interrupted. + */ +static void ins_compl_new_leader() { + ins_compl_del_pum(); + ins_compl_delete(); + ins_bytes(compl_leader + ins_compl_len()); + compl_used_match = FALSE; + + if (compl_started) + ins_compl_set_original_text(compl_leader); + else { + spell_bad_len = 0; /* need to redetect bad word */ + /* + * Matches were cleared, need to search for them now. First display + * the changed text before the cursor. Set "compl_restarting" to + * avoid that the first match is inserted. + */ + update_screen(0); + compl_restarting = TRUE; + if (ins_complete(Ctrl_N) == FAIL) + compl_cont_status = 0; + compl_restarting = FALSE; + } + + compl_enter_selects = !compl_used_match; + + /* Show the popup menu with a different set of matches. */ + ins_compl_show_pum(); + + /* Don't let Enter select the original text when there is no popup menu. */ + if (compl_match_array == NULL) + compl_enter_selects = FALSE; +} + +/* + * Return the length of the completion, from the completion start column to + * the cursor column. Making sure it never goes below zero. + */ +static int ins_compl_len() { + int off = (int)curwin->w_cursor.col - (int)compl_col; + + if (off < 0) + return 0; + return off; +} + +/* + * Append one character to the match leader. May reduce the number of + * matches. + */ +static void ins_compl_addleader(c) +int c; +{ + int cc; + + if (has_mbyte && (cc = (*mb_char2len)(c)) > 1) { + char_u buf[MB_MAXBYTES + 1]; + + (*mb_char2bytes)(c, buf); + buf[cc] = NUL; + ins_char_bytes(buf, cc); + if (compl_opt_refresh_always) + AppendToRedobuff(buf); + } else { + ins_char(c); + if (compl_opt_refresh_always) + AppendCharToRedobuff(c); + } + + /* If we didn't complete finding matches we must search again. */ + if (ins_compl_need_restart()) + ins_compl_restart(); + + /* When 'always' is set, don't reset compl_leader. While completing, + * cursor doesn't point original position, changing compl_leader would + * break redo. */ + if (!compl_opt_refresh_always) { + vim_free(compl_leader); + compl_leader = vim_strnsave(ml_get_curline() + compl_col, + (int)(curwin->w_cursor.col - compl_col)); + if (compl_leader != NULL) + ins_compl_new_leader(); + } +} + +/* + * Setup for finding completions again without leaving CTRL-X mode. Used when + * BS or a key was typed while still searching for matches. + */ +static void ins_compl_restart() { + ins_compl_free(); + compl_started = FALSE; + compl_matches = 0; + compl_cont_status = 0; + compl_cont_mode = 0; +} + +/* + * Set the first match, the original text. + */ +static void ins_compl_set_original_text(str) +char_u *str; +{ + char_u *p; + + /* Replace the original text entry. */ + if (compl_first_match->cp_flags & ORIGINAL_TEXT) { /* safety check */ + p = vim_strsave(str); + if (p != NULL) { + vim_free(compl_first_match->cp_str); + compl_first_match->cp_str = p; + } + } +} + +/* + * Append one character to the match leader. May reduce the number of + * matches. + */ +static void ins_compl_addfrommatch() { + char_u *p; + int len = (int)curwin->w_cursor.col - (int)compl_col; + int c; + compl_T *cp; + + p = compl_shown_match->cp_str; + if ((int)STRLEN(p) <= len) { /* the match is too short */ + /* When still at the original match use the first entry that matches + * the leader. */ + if (compl_shown_match->cp_flags & ORIGINAL_TEXT) { + p = NULL; + for (cp = compl_shown_match->cp_next; cp != NULL + && cp != compl_first_match; cp = cp->cp_next) { + if (compl_leader == NULL + || ins_compl_equal(cp, compl_leader, + (int)STRLEN(compl_leader))) { + p = cp->cp_str; + break; + } + } + if (p == NULL || (int)STRLEN(p) <= len) + return; + } else + return; + } + p += len; + c = PTR2CHAR(p); + ins_compl_addleader(c); +} + +/* + * Prepare for Insert mode completion, or stop it. + * Called just after typing a character in Insert mode. + * Returns TRUE when the character is not to be inserted; + */ +static int ins_compl_prep(c) +int c; +{ + char_u *ptr; + int want_cindent; + int retval = FALSE; + + /* Forget any previous 'special' messages if this is actually + * a ^X mode key - bar ^R, in which case we wait to see what it gives us. + */ + if (c != Ctrl_R && vim_is_ctrl_x_key(c)) + edit_submode_extra = NULL; + + /* Ignore end of Select mode mapping and mouse scroll buttons. */ + if (c == K_SELECT || c == K_MOUSEDOWN || c == K_MOUSEUP + || c == K_MOUSELEFT || c == K_MOUSERIGHT) + return retval; + + /* Set "compl_get_longest" when finding the first matches. */ + if (ctrl_x_mode == CTRL_X_NOT_DEFINED_YET + || (ctrl_x_mode == 0 && !compl_started)) { + compl_get_longest = (vim_strchr(p_cot, 'l') != NULL); + compl_used_match = TRUE; + } + + if (ctrl_x_mode == CTRL_X_NOT_DEFINED_YET) { + /* + * We have just typed CTRL-X and aren't quite sure which CTRL-X mode + * it will be yet. Now we decide. + */ + switch (c) { + case Ctrl_E: + case Ctrl_Y: + ctrl_x_mode = CTRL_X_SCROLL; + if (!(State & REPLACE_FLAG)) + edit_submode = (char_u *)_(" (insert) Scroll (^E/^Y)"); + else + edit_submode = (char_u *)_(" (replace) Scroll (^E/^Y)"); + edit_submode_pre = NULL; + showmode(); + break; + case Ctrl_L: + ctrl_x_mode = CTRL_X_WHOLE_LINE; + break; + case Ctrl_F: + ctrl_x_mode = CTRL_X_FILES; + break; + case Ctrl_K: + ctrl_x_mode = CTRL_X_DICTIONARY; + break; + case Ctrl_R: + /* Simply allow ^R to happen without affecting ^X mode */ + break; + case Ctrl_T: + ctrl_x_mode = CTRL_X_THESAURUS; + break; + case Ctrl_U: + ctrl_x_mode = CTRL_X_FUNCTION; + break; + case Ctrl_O: + ctrl_x_mode = CTRL_X_OMNI; + break; + case 's': + case Ctrl_S: + ctrl_x_mode = CTRL_X_SPELL; + ++emsg_off; /* Avoid getting the E756 error twice. */ + spell_back_to_badword(); + --emsg_off; + break; + case Ctrl_RSB: + ctrl_x_mode = CTRL_X_TAGS; + break; + case Ctrl_I: + case K_S_TAB: + ctrl_x_mode = CTRL_X_PATH_PATTERNS; + break; + case Ctrl_D: + ctrl_x_mode = CTRL_X_PATH_DEFINES; + break; + case Ctrl_V: + case Ctrl_Q: + ctrl_x_mode = CTRL_X_CMDLINE; + break; + case Ctrl_P: + case Ctrl_N: + /* ^X^P means LOCAL expansion if nothing interrupted (eg we + * just started ^X mode, or there were enough ^X's to cancel + * the previous mode, say ^X^F^X^X^P or ^P^X^X^X^P, see below) + * do normal expansion when interrupting a different mode (say + * ^X^F^X^P or ^P^X^X^P, see below) + * nothing changes if interrupting mode 0, (eg, the flag + * doesn't change when going to ADDING mode -- Acevedo */ + if (!(compl_cont_status & CONT_INTRPT)) + compl_cont_status |= CONT_LOCAL; + else if (compl_cont_mode != 0) + compl_cont_status &= ~CONT_LOCAL; + /* FALLTHROUGH */ + default: + /* If we have typed at least 2 ^X's... for modes != 0, we set + * compl_cont_status = 0 (eg, as if we had just started ^X + * mode). + * For mode 0, we set "compl_cont_mode" to an impossible + * value, in both cases ^X^X can be used to restart the same + * mode (avoiding ADDING mode). + * Undocumented feature: In a mode != 0 ^X^P and ^X^X^P start + * 'complete' and local ^P expansions respectively. + * In mode 0 an extra ^X is needed since ^X^P goes to ADDING + * mode -- Acevedo */ + if (c == Ctrl_X) { + if (compl_cont_mode != 0) + compl_cont_status = 0; + else + compl_cont_mode = CTRL_X_NOT_DEFINED_YET; + } + ctrl_x_mode = 0; + edit_submode = NULL; + showmode(); + break; + } + } else if (ctrl_x_mode != 0) { + /* We're already in CTRL-X mode, do we stay in it? */ + if (!vim_is_ctrl_x_key(c)) { + if (ctrl_x_mode == CTRL_X_SCROLL) + ctrl_x_mode = 0; + else + ctrl_x_mode = CTRL_X_FINISHED; + edit_submode = NULL; + } + showmode(); + } + + if (compl_started || ctrl_x_mode == CTRL_X_FINISHED) { + /* Show error message from attempted keyword completion (probably + * 'Pattern not found') until another key is hit, then go back to + * showing what mode we are in. */ + showmode(); + if ((ctrl_x_mode == 0 && c != Ctrl_N && c != Ctrl_P && c != Ctrl_R + && !ins_compl_pum_key(c)) + || ctrl_x_mode == CTRL_X_FINISHED) { + /* Get here when we have finished typing a sequence of ^N and + * ^P or other completion characters in CTRL-X mode. Free up + * memory that was used, and make sure we can redo the insert. */ + if (compl_curr_match != NULL || compl_leader != NULL || c == Ctrl_E) { + /* + * If any of the original typed text has been changed, eg when + * ignorecase is set, we must add back-spaces to the redo + * buffer. We add as few as necessary to delete just the part + * of the original text that has changed. + * When using the longest match, edited the match or used + * CTRL-E then don't use the current match. + */ + if (compl_curr_match != NULL && compl_used_match && c != Ctrl_E) + ptr = compl_curr_match->cp_str; + else + ptr = NULL; + ins_compl_fixRedoBufForLeader(ptr); + } + + want_cindent = (can_cindent && cindent_on()); + /* + * When completing whole lines: fix indent for 'cindent'. + * Otherwise, break line if it's too long. + */ + if (compl_cont_mode == CTRL_X_WHOLE_LINE) { + /* re-indent the current line */ + if (want_cindent) { + do_c_expr_indent(); + want_cindent = FALSE; /* don't do it again */ + } + } else { + int prev_col = curwin->w_cursor.col; + + /* put the cursor on the last char, for 'tw' formatting */ + if (prev_col > 0) + dec_cursor(); + if (stop_arrow() == OK) + insertchar(NUL, 0, -1); + if (prev_col > 0 + && ml_get_curline()[curwin->w_cursor.col] != NUL) + inc_cursor(); + } + + /* If the popup menu is displayed pressing CTRL-Y means accepting + * the selection without inserting anything. When + * compl_enter_selects is set the Enter key does the same. */ + if ((c == Ctrl_Y || (compl_enter_selects + && (c == CAR || c == K_KENTER || c == NL))) + && pum_visible()) + retval = TRUE; + + /* CTRL-E means completion is Ended, go back to the typed text. */ + if (c == Ctrl_E) { + ins_compl_delete(); + if (compl_leader != NULL) + ins_bytes(compl_leader + ins_compl_len()); + else if (compl_first_match != NULL) + ins_bytes(compl_orig_text + ins_compl_len()); + retval = TRUE; + } + + auto_format(FALSE, TRUE); + + ins_compl_free(); + compl_started = FALSE; + compl_matches = 0; + msg_clr_cmdline(); /* necessary for "noshowmode" */ + ctrl_x_mode = 0; + compl_enter_selects = FALSE; + if (edit_submode != NULL) { + edit_submode = NULL; + showmode(); + } + + /* + * Indent now if a key was typed that is in 'cinkeys'. + */ + if (want_cindent && in_cinkeys(KEY_COMPLETE, ' ', inindent(0))) + do_c_expr_indent(); + /* Trigger the CompleteDone event to give scripts a chance to act + * upon the completion. */ + apply_autocmds(EVENT_COMPLETEDONE, NULL, NULL, FALSE, curbuf); + } + } else if (ctrl_x_mode == CTRL_X_LOCAL_MSG) + /* Trigger the CompleteDone event to give scripts a chance to act + * upon the (possibly failed) completion. */ + apply_autocmds(EVENT_COMPLETEDONE, NULL, NULL, FALSE, curbuf); + + /* reset continue_* if we left expansion-mode, if we stay they'll be + * (re)set properly in ins_complete() */ + if (!vim_is_ctrl_x_key(c)) { + compl_cont_status = 0; + compl_cont_mode = 0; + } + + return retval; +} + +/* + * Fix the redo buffer for the completion leader replacing some of the typed + * text. This inserts backspaces and appends the changed text. + * "ptr" is the known leader text or NUL. + */ +static void ins_compl_fixRedoBufForLeader(ptr_arg) +char_u *ptr_arg; +{ + int len; + char_u *p; + char_u *ptr = ptr_arg; + + if (ptr == NULL) { + if (compl_leader != NULL) + ptr = compl_leader; + else + return; /* nothing to do */ + } + if (compl_orig_text != NULL) { + p = compl_orig_text; + for (len = 0; p[len] != NUL && p[len] == ptr[len]; ++len) + ; + if (len > 0) + len -= (*mb_head_off)(p, p + len); + for (p += len; *p != NUL; mb_ptr_adv(p)) + AppendCharToRedobuff(K_BS); + } else + len = 0; + if (ptr != NULL) + AppendToRedobuffLit(ptr + len, -1); +} + +/* + * Loops through the list of windows, loaded-buffers or non-loaded-buffers + * (depending on flag) starting from buf and looking for a non-scanned + * buffer (other than curbuf). curbuf is special, if it is called with + * buf=curbuf then it has to be the first call for a given flag/expansion. + * + * Returns the buffer to scan, if any, otherwise returns curbuf -- Acevedo + */ +static buf_T * ins_compl_next_buf(buf, flag) +buf_T *buf; +int flag; +{ + static win_T *wp; + + if (flag == 'w') { /* just windows */ + if (buf == curbuf) /* first call for this flag/expansion */ + wp = curwin; + while ((wp = (wp->w_next != NULL ? wp->w_next : firstwin)) != curwin + && wp->w_buffer->b_scanned) + ; + buf = wp->w_buffer; + } else + /* 'b' (just loaded buffers), 'u' (just non-loaded buffers) or 'U' + * (unlisted buffers) + * When completing whole lines skip unloaded buffers. */ + while ((buf = (buf->b_next != NULL ? buf->b_next : firstbuf)) != curbuf + && ((flag == 'U' + ? buf->b_p_bl + : (!buf->b_p_bl + || (buf->b_ml.ml_mfp == NULL) != (flag == 'u'))) + || buf->b_scanned)) + ; + return buf; +} + +static void expand_by_function __ARGS((int type, char_u *base)); + +/* + * Execute user defined complete function 'completefunc' or 'omnifunc', and + * get matches in "matches". + */ +static void expand_by_function(type, base) +int type; /* CTRL_X_OMNI or CTRL_X_FUNCTION */ +char_u *base; +{ + list_T *matchlist = NULL; + dict_T *matchdict = NULL; + char_u *args[2]; + char_u *funcname; + pos_T pos; + win_T *curwin_save; + buf_T *curbuf_save; + typval_T rettv; + + funcname = (type == CTRL_X_FUNCTION) ? curbuf->b_p_cfu : curbuf->b_p_ofu; + if (*funcname == NUL) + return; + + /* Call 'completefunc' to obtain the list of matches. */ + args[0] = (char_u *)"0"; + args[1] = base; + + pos = curwin->w_cursor; + curwin_save = curwin; + curbuf_save = curbuf; + + /* Call a function, which returns a list or dict. */ + if (call_vim_function(funcname, 2, args, FALSE, FALSE, &rettv) == OK) { + switch (rettv.v_type) { + case VAR_LIST: + matchlist = rettv.vval.v_list; + break; + case VAR_DICT: + matchdict = rettv.vval.v_dict; + break; + default: + /* TODO: Give error message? */ + clear_tv(&rettv); + break; + } + } + + if (curwin_save != curwin || curbuf_save != curbuf) { + EMSG(_(e_complwin)); + goto theend; + } + curwin->w_cursor = pos; /* restore the cursor position */ + check_cursor(); + if (!equalpos(curwin->w_cursor, pos)) { + EMSG(_(e_compldel)); + goto theend; + } + + if (matchlist != NULL) + ins_compl_add_list(matchlist); + else if (matchdict != NULL) + ins_compl_add_dict(matchdict); + +theend: + if (matchdict != NULL) + dict_unref(matchdict); + if (matchlist != NULL) + list_unref(matchlist); +} + +/* + * Add completions from a list. + */ +static void ins_compl_add_list(list) +list_T *list; +{ + listitem_T *li; + int dir = compl_direction; + + /* Go through the List with matches and add each of them. */ + for (li = list->lv_first; li != NULL; li = li->li_next) { + if (ins_compl_add_tv(&li->li_tv, dir) == OK) + /* if dir was BACKWARD then honor it just once */ + dir = FORWARD; + else if (did_emsg) + break; + } +} + +/* + * Add completions from a dict. + */ +static void ins_compl_add_dict(dict) +dict_T *dict; +{ + dictitem_T *di_refresh; + dictitem_T *di_words; + + /* Check for optional "refresh" item. */ + compl_opt_refresh_always = FALSE; + di_refresh = dict_find(dict, (char_u *)"refresh", 7); + if (di_refresh != NULL && di_refresh->di_tv.v_type == VAR_STRING) { + char_u *v = di_refresh->di_tv.vval.v_string; + + if (v != NULL && STRCMP(v, (char_u *)"always") == 0) + compl_opt_refresh_always = TRUE; + } + + /* Add completions from a "words" list. */ + di_words = dict_find(dict, (char_u *)"words", 5); + if (di_words != NULL && di_words->di_tv.v_type == VAR_LIST) + ins_compl_add_list(di_words->di_tv.vval.v_list); +} + +/* + * Add a match to the list of matches from a typeval_T. + * If the given string is already in the list of completions, then return + * NOTDONE, otherwise add it to the list and return OK. If there is an error, + * maybe because alloc() returns NULL, then FAIL is returned. + */ +int ins_compl_add_tv(tv, dir) +typval_T *tv; +int dir; +{ + char_u *word; + int icase = FALSE; + int adup = FALSE; + int aempty = FALSE; + char_u *(cptext[CPT_COUNT]); + + if (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL) { + word = get_dict_string(tv->vval.v_dict, (char_u *)"word", FALSE); + cptext[CPT_ABBR] = get_dict_string(tv->vval.v_dict, + (char_u *)"abbr", FALSE); + cptext[CPT_MENU] = get_dict_string(tv->vval.v_dict, + (char_u *)"menu", FALSE); + cptext[CPT_KIND] = get_dict_string(tv->vval.v_dict, + (char_u *)"kind", FALSE); + cptext[CPT_INFO] = get_dict_string(tv->vval.v_dict, + (char_u *)"info", FALSE); + if (get_dict_string(tv->vval.v_dict, (char_u *)"icase", FALSE) != NULL) + icase = get_dict_number(tv->vval.v_dict, (char_u *)"icase"); + if (get_dict_string(tv->vval.v_dict, (char_u *)"dup", FALSE) != NULL) + adup = get_dict_number(tv->vval.v_dict, (char_u *)"dup"); + if (get_dict_string(tv->vval.v_dict, (char_u *)"empty", FALSE) != NULL) + aempty = get_dict_number(tv->vval.v_dict, (char_u *)"empty"); + } else { + word = get_tv_string_chk(tv); + vim_memset(cptext, 0, sizeof(cptext)); + } + if (word == NULL || (!aempty && *word == NUL)) + return FAIL; + return ins_compl_add(word, -1, icase, NULL, cptext, dir, 0, adup); +} + +/* + * Get the next expansion(s), using "compl_pattern". + * The search starts at position "ini" in curbuf and in the direction + * compl_direction. + * When "compl_started" is FALSE start at that position, otherwise continue + * where we stopped searching before. + * This may return before finding all the matches. + * Return the total number of matches or -1 if still unknown -- Acevedo + */ +static int ins_compl_get_exp(ini) +pos_T *ini; +{ + static pos_T first_match_pos; + static pos_T last_match_pos; + static char_u *e_cpt = (char_u *)""; /* curr. entry in 'complete' */ + static int found_all = FALSE; /* Found all matches of a + certain type. */ + static buf_T *ins_buf = NULL; /* buffer being scanned */ + + pos_T *pos; + char_u **matches; + int save_p_scs; + int save_p_ws; + int save_p_ic; + int i; + int num_matches; + int len; + int found_new_match; + int type = ctrl_x_mode; + char_u *ptr; + char_u *dict = NULL; + int dict_f = 0; + compl_T *old_match; + int set_match_pos; + + if (!compl_started) { + for (ins_buf = firstbuf; ins_buf != NULL; ins_buf = ins_buf->b_next) + ins_buf->b_scanned = 0; + found_all = FALSE; + ins_buf = curbuf; + e_cpt = (compl_cont_status & CONT_LOCAL) + ? (char_u *)"." : curbuf->b_p_cpt; + last_match_pos = first_match_pos = *ini; + } + + old_match = compl_curr_match; /* remember the last current match */ + pos = (compl_direction == FORWARD) ? &last_match_pos : &first_match_pos; + /* For ^N/^P loop over all the flags/windows/buffers in 'complete' */ + for (;; ) { + found_new_match = FAIL; + set_match_pos = FALSE; + + /* For ^N/^P pick a new entry from e_cpt if compl_started is off, + * or if found_all says this entry is done. For ^X^L only use the + * entries from 'complete' that look in loaded buffers. */ + if ((ctrl_x_mode == 0 || ctrl_x_mode == CTRL_X_WHOLE_LINE) + && (!compl_started || found_all)) { + found_all = FALSE; + while (*e_cpt == ',' || *e_cpt == ' ') + e_cpt++; + if (*e_cpt == '.' && !curbuf->b_scanned) { + ins_buf = curbuf; + first_match_pos = *ini; + /* So that ^N can match word immediately after cursor */ + if (ctrl_x_mode == 0) + dec(&first_match_pos); + last_match_pos = first_match_pos; + type = 0; + + /* Remember the first match so that the loop stops when we + * wrap and come back there a second time. */ + set_match_pos = TRUE; + } else if (vim_strchr((char_u *)"buwU", *e_cpt) != NULL + && (ins_buf = + ins_compl_next_buf(ins_buf, *e_cpt)) != curbuf) { + /* Scan a buffer, but not the current one. */ + if (ins_buf->b_ml.ml_mfp != NULL) { /* loaded buffer */ + compl_started = TRUE; + first_match_pos.col = last_match_pos.col = 0; + first_match_pos.lnum = ins_buf->b_ml.ml_line_count + 1; + last_match_pos.lnum = 0; + type = 0; + } else { /* unloaded buffer, scan like dictionary */ + found_all = TRUE; + if (ins_buf->b_fname == NULL) + continue; + type = CTRL_X_DICTIONARY; + dict = ins_buf->b_fname; + dict_f = DICT_EXACT; + } + vim_snprintf((char *)IObuff, IOSIZE, _("Scanning: %s"), + ins_buf->b_fname == NULL + ? buf_spname(ins_buf) + : ins_buf->b_sfname == NULL + ? ins_buf->b_fname + : ins_buf->b_sfname); + (void)msg_trunc_attr(IObuff, TRUE, hl_attr(HLF_R)); + } else if (*e_cpt == NUL) + break; + else { + if (ctrl_x_mode == CTRL_X_WHOLE_LINE) + type = -1; + else if (*e_cpt == 'k' || *e_cpt == 's') { + if (*e_cpt == 'k') + type = CTRL_X_DICTIONARY; + else + type = CTRL_X_THESAURUS; + if (*++e_cpt != ',' && *e_cpt != NUL) { + dict = e_cpt; + dict_f = DICT_FIRST; + } + } else if (*e_cpt == 'i') + type = CTRL_X_PATH_PATTERNS; + else if (*e_cpt == 'd') + type = CTRL_X_PATH_DEFINES; + else if (*e_cpt == ']' || *e_cpt == 't') { + type = CTRL_X_TAGS; + vim_snprintf((char *)IObuff, IOSIZE, _("Scanning tags.")); + (void)msg_trunc_attr(IObuff, TRUE, hl_attr(HLF_R)); + } else + type = -1; + + /* in any case e_cpt is advanced to the next entry */ + (void)copy_option_part(&e_cpt, IObuff, IOSIZE, ","); + + found_all = TRUE; + if (type == -1) + continue; + } + } + + switch (type) { + case -1: + break; + case CTRL_X_PATH_PATTERNS: + case CTRL_X_PATH_DEFINES: + find_pattern_in_path(compl_pattern, compl_direction, + (int)STRLEN(compl_pattern), FALSE, FALSE, + (type == CTRL_X_PATH_DEFINES + && !(compl_cont_status & CONT_SOL)) + ? FIND_DEFINE : FIND_ANY, 1L, ACTION_EXPAND, + (linenr_T)1, (linenr_T)MAXLNUM); + break; + + case CTRL_X_DICTIONARY: + case CTRL_X_THESAURUS: + ins_compl_dictionaries( + dict != NULL ? dict + : (type == CTRL_X_THESAURUS + ? (*curbuf->b_p_tsr == NUL + ? p_tsr + : curbuf->b_p_tsr) + : (*curbuf->b_p_dict == NUL + ? p_dict + : curbuf->b_p_dict)), + compl_pattern, + dict != NULL ? dict_f + : 0, type == CTRL_X_THESAURUS); + dict = NULL; + break; + + case CTRL_X_TAGS: + /* set p_ic according to p_ic, p_scs and pat for find_tags(). */ + save_p_ic = p_ic; + p_ic = ignorecase(compl_pattern); + + /* Find up to TAG_MANY matches. Avoids that an enormous number + * of matches is found when compl_pattern is empty */ + if (find_tags(compl_pattern, &num_matches, &matches, + TAG_REGEXP | TAG_NAMES | TAG_NOIC | + TAG_INS_COMP | (ctrl_x_mode ? TAG_VERBOSE : 0), + TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) { + ins_compl_add_matches(num_matches, matches, p_ic); + } + p_ic = save_p_ic; + break; + + case CTRL_X_FILES: + if (expand_wildcards(1, &compl_pattern, &num_matches, &matches, + EW_FILE|EW_DIR|EW_ADDSLASH|EW_SILENT) == OK) { + + /* May change home directory back to "~". */ + tilde_replace(compl_pattern, num_matches, matches); + ins_compl_add_matches(num_matches, matches, p_fic || p_wic); + } + break; + + case CTRL_X_CMDLINE: + if (expand_cmdline(&compl_xp, compl_pattern, + (int)STRLEN(compl_pattern), + &num_matches, &matches) == EXPAND_OK) + ins_compl_add_matches(num_matches, matches, FALSE); + break; + + case CTRL_X_FUNCTION: + case CTRL_X_OMNI: + expand_by_function(type, compl_pattern); + break; + + case CTRL_X_SPELL: + num_matches = expand_spelling(first_match_pos.lnum, + compl_pattern, &matches); + if (num_matches > 0) + ins_compl_add_matches(num_matches, matches, p_ic); + break; + + default: /* normal ^P/^N and ^X^L */ + /* + * If 'infercase' is set, don't use 'smartcase' here + */ + save_p_scs = p_scs; + if (ins_buf->b_p_inf) + p_scs = FALSE; + + /* Buffers other than curbuf are scanned from the beginning or the + * end but never from the middle, thus setting nowrapscan in this + * buffers is a good idea, on the other hand, we always set + * wrapscan for curbuf to avoid missing matches -- Acevedo,Webb */ + save_p_ws = p_ws; + if (ins_buf != curbuf) + p_ws = FALSE; + else if (*e_cpt == '.') + p_ws = TRUE; + for (;; ) { + int flags = 0; + + ++msg_silent; /* Don't want messages for wrapscan. */ + + /* ctrl_x_mode == CTRL_X_WHOLE_LINE || word-wise search that + * has added a word that was at the beginning of the line */ + if ( ctrl_x_mode == CTRL_X_WHOLE_LINE + || (compl_cont_status & CONT_SOL)) + found_new_match = search_for_exact_line(ins_buf, pos, + compl_direction, compl_pattern); + else + found_new_match = searchit(NULL, ins_buf, pos, + compl_direction, + compl_pattern, 1L, SEARCH_KEEP + SEARCH_NFMSG, + RE_LAST, (linenr_T)0, NULL); + --msg_silent; + if (!compl_started || set_match_pos) { + /* set "compl_started" even on fail */ + compl_started = TRUE; + first_match_pos = *pos; + last_match_pos = *pos; + set_match_pos = FALSE; + } else if (first_match_pos.lnum == last_match_pos.lnum + && first_match_pos.col == last_match_pos.col) + found_new_match = FAIL; + if (found_new_match == FAIL) { + if (ins_buf == curbuf) + found_all = TRUE; + break; + } + + /* when ADDING, the text before the cursor matches, skip it */ + if ( (compl_cont_status & CONT_ADDING) && ins_buf == curbuf + && ini->lnum == pos->lnum + && ini->col == pos->col) + continue; + ptr = ml_get_buf(ins_buf, pos->lnum, FALSE) + pos->col; + if (ctrl_x_mode == CTRL_X_WHOLE_LINE) { + if (compl_cont_status & CONT_ADDING) { + if (pos->lnum >= ins_buf->b_ml.ml_line_count) + continue; + ptr = ml_get_buf(ins_buf, pos->lnum + 1, FALSE); + if (!p_paste) + ptr = skipwhite(ptr); + } + len = (int)STRLEN(ptr); + } else { + char_u *tmp_ptr = ptr; + + if (compl_cont_status & CONT_ADDING) { + tmp_ptr += compl_length; + /* Skip if already inside a word. */ + if (vim_iswordp(tmp_ptr)) + continue; + /* Find start of next word. */ + tmp_ptr = find_word_start(tmp_ptr); + } + /* Find end of this word. */ + tmp_ptr = find_word_end(tmp_ptr); + len = (int)(tmp_ptr - ptr); + + if ((compl_cont_status & CONT_ADDING) + && len == compl_length) { + if (pos->lnum < ins_buf->b_ml.ml_line_count) { + /* Try next line, if any. the new word will be + * "join" as if the normal command "J" was used. + * IOSIZE is always greater than + * compl_length, so the next STRNCPY always + * works -- Acevedo */ + STRNCPY(IObuff, ptr, len); + ptr = ml_get_buf(ins_buf, pos->lnum + 1, FALSE); + tmp_ptr = ptr = skipwhite(ptr); + /* Find start of next word. */ + tmp_ptr = find_word_start(tmp_ptr); + /* Find end of next word. */ + tmp_ptr = find_word_end(tmp_ptr); + if (tmp_ptr > ptr) { + if (*ptr != ')' && IObuff[len - 1] != TAB) { + if (IObuff[len - 1] != ' ') + IObuff[len++] = ' '; + /* IObuf =~ "\k.* ", thus len >= 2 */ + if (p_js + && (IObuff[len - 2] == '.' + || (vim_strchr(p_cpo, CPO_JOINSP) + == NULL + && (IObuff[len - 2] == '?' + || IObuff[len - 2] == '!')))) + IObuff[len++] = ' '; + } + /* copy as much as possible of the new word */ + if (tmp_ptr - ptr >= IOSIZE - len) + tmp_ptr = ptr + IOSIZE - len - 1; + STRNCPY(IObuff + len, ptr, tmp_ptr - ptr); + len += (int)(tmp_ptr - ptr); + flags |= CONT_S_IPOS; + } + IObuff[len] = NUL; + ptr = IObuff; + } + if (len == compl_length) + continue; + } + } + if (ins_compl_add_infercase(ptr, len, p_ic, + ins_buf == curbuf ? NULL : ins_buf->b_sfname, + 0, flags) != NOTDONE) { + found_new_match = OK; + break; + } + } + p_scs = save_p_scs; + p_ws = save_p_ws; + } + + /* check if compl_curr_match has changed, (e.g. other type of + * expansion added something) */ + if (type != 0 && compl_curr_match != old_match) + found_new_match = OK; + + /* break the loop for specialized modes (use 'complete' just for the + * generic ctrl_x_mode == 0) or when we've found a new match */ + if ((ctrl_x_mode != 0 && ctrl_x_mode != CTRL_X_WHOLE_LINE) + || found_new_match != FAIL) { + if (got_int) + break; + /* Fill the popup menu as soon as possible. */ + if (type != -1) + ins_compl_check_keys(0); + + if ((ctrl_x_mode != 0 && ctrl_x_mode != CTRL_X_WHOLE_LINE) + || compl_interrupted) + break; + compl_started = TRUE; + } else { + /* Mark a buffer scanned when it has been scanned completely */ + if (type == 0 || type == CTRL_X_PATH_PATTERNS) + ins_buf->b_scanned = TRUE; + + compl_started = FALSE; + } + } + compl_started = TRUE; + + if ((ctrl_x_mode == 0 || ctrl_x_mode == CTRL_X_WHOLE_LINE) + && *e_cpt == NUL) /* Got to end of 'complete' */ + found_new_match = FAIL; + + i = -1; /* total of matches, unknown */ + if (found_new_match == FAIL + || (ctrl_x_mode != 0 && ctrl_x_mode != CTRL_X_WHOLE_LINE)) + i = ins_compl_make_cyclic(); + + /* If several matches were added (FORWARD) or the search failed and has + * just been made cyclic then we have to move compl_curr_match to the next + * or previous entry (if any) -- Acevedo */ + compl_curr_match = compl_direction == FORWARD ? old_match->cp_next + : old_match->cp_prev; + if (compl_curr_match == NULL) + compl_curr_match = old_match; + return i; +} + +/* Delete the old text being completed. */ +static void ins_compl_delete() { + int i; + + /* + * In insert mode: Delete the typed part. + * In replace mode: Put the old characters back, if any. + */ + i = compl_col + (compl_cont_status & CONT_ADDING ? compl_length : 0); + backspace_until_column(i); + changed_cline_bef_curs(); +} + +/* Insert the new text being completed. */ +static void ins_compl_insert() { + ins_bytes(compl_shown_match->cp_str + ins_compl_len()); + if (compl_shown_match->cp_flags & ORIGINAL_TEXT) + compl_used_match = FALSE; + else + compl_used_match = TRUE; +} + +/* + * Fill in the next completion in the current direction. + * If "allow_get_expansion" is TRUE, then we may call ins_compl_get_exp() to + * get more completions. If it is FALSE, then we just do nothing when there + * are no more completions in a given direction. The latter case is used when + * we are still in the middle of finding completions, to allow browsing + * through the ones found so far. + * Return the total number of matches, or -1 if still unknown -- webb. + * + * compl_curr_match is currently being used by ins_compl_get_exp(), so we use + * compl_shown_match here. + * + * Note that this function may be called recursively once only. First with + * "allow_get_expansion" TRUE, which calls ins_compl_get_exp(), which in turn + * calls this function with "allow_get_expansion" FALSE. + */ +static int ins_compl_next(allow_get_expansion, count, insert_match) +int allow_get_expansion; +int count; /* repeat completion this many times; should + be at least 1 */ +int insert_match; /* Insert the newly selected match */ +{ + int num_matches = -1; + int i; + int todo = count; + compl_T *found_compl = NULL; + int found_end = FALSE; + int advance; + + /* When user complete function return -1 for findstart which is next + * time of 'always', compl_shown_match become NULL. */ + if (compl_shown_match == NULL) + return -1; + + if (compl_leader != NULL + && (compl_shown_match->cp_flags & ORIGINAL_TEXT) == 0) { + /* Set "compl_shown_match" to the actually shown match, it may differ + * when "compl_leader" is used to omit some of the matches. */ + while (!ins_compl_equal(compl_shown_match, + compl_leader, (int)STRLEN(compl_leader)) + && compl_shown_match->cp_next != NULL + && compl_shown_match->cp_next != compl_first_match) + compl_shown_match = compl_shown_match->cp_next; + + /* If we didn't find it searching forward, and compl_shows_dir is + * backward, find the last match. */ + if (compl_shows_dir == BACKWARD + && !ins_compl_equal(compl_shown_match, + compl_leader, (int)STRLEN(compl_leader)) + && (compl_shown_match->cp_next == NULL + || compl_shown_match->cp_next == compl_first_match)) { + while (!ins_compl_equal(compl_shown_match, + compl_leader, (int)STRLEN(compl_leader)) + && compl_shown_match->cp_prev != NULL + && compl_shown_match->cp_prev != compl_first_match) + compl_shown_match = compl_shown_match->cp_prev; + } + } + + if (allow_get_expansion && insert_match + && (!(compl_get_longest || compl_restarting) || compl_used_match)) + /* Delete old text to be replaced */ + ins_compl_delete(); + + /* When finding the longest common text we stick at the original text, + * don't let CTRL-N or CTRL-P move to the first match. */ + advance = count != 1 || !allow_get_expansion || !compl_get_longest; + + /* When restarting the search don't insert the first match either. */ + if (compl_restarting) { + advance = FALSE; + compl_restarting = FALSE; + } + + /* Repeat this for when or is typed. But don't wrap + * around. */ + while (--todo >= 0) { + if (compl_shows_dir == FORWARD && compl_shown_match->cp_next != NULL) { + compl_shown_match = compl_shown_match->cp_next; + found_end = (compl_first_match != NULL + && (compl_shown_match->cp_next == compl_first_match + || compl_shown_match == compl_first_match)); + } else if (compl_shows_dir == BACKWARD + && compl_shown_match->cp_prev != NULL) { + found_end = (compl_shown_match == compl_first_match); + compl_shown_match = compl_shown_match->cp_prev; + found_end |= (compl_shown_match == compl_first_match); + } else { + if (!allow_get_expansion) { + if (advance) { + if (compl_shows_dir == BACKWARD) + compl_pending -= todo + 1; + else + compl_pending += todo + 1; + } + return -1; + } + + if (advance) { + if (compl_shows_dir == BACKWARD) + --compl_pending; + else + ++compl_pending; + } + + /* Find matches. */ + num_matches = ins_compl_get_exp(&compl_startpos); + + /* handle any pending completions */ + while (compl_pending != 0 && compl_direction == compl_shows_dir + && advance) { + if (compl_pending > 0 && compl_shown_match->cp_next != NULL) { + compl_shown_match = compl_shown_match->cp_next; + --compl_pending; + } + if (compl_pending < 0 && compl_shown_match->cp_prev != NULL) { + compl_shown_match = compl_shown_match->cp_prev; + ++compl_pending; + } else + break; + } + found_end = FALSE; + } + if ((compl_shown_match->cp_flags & ORIGINAL_TEXT) == 0 + && compl_leader != NULL + && !ins_compl_equal(compl_shown_match, + compl_leader, (int)STRLEN(compl_leader))) + ++todo; + else + /* Remember a matching item. */ + found_compl = compl_shown_match; + + /* Stop at the end of the list when we found a usable match. */ + if (found_end) { + if (found_compl != NULL) { + compl_shown_match = found_compl; + break; + } + todo = 1; /* use first usable match after wrapping around */ + } + } + + /* Insert the text of the new completion, or the compl_leader. */ + if (insert_match) { + if (!compl_get_longest || compl_used_match) + ins_compl_insert(); + else + ins_bytes(compl_leader + ins_compl_len()); + } else + compl_used_match = FALSE; + + if (!allow_get_expansion) { + /* may undisplay the popup menu first */ + ins_compl_upd_pum(); + + /* redraw to show the user what was inserted */ + update_screen(0); + + /* display the updated popup menu */ + ins_compl_show_pum(); + + /* Delete old text to be replaced, since we're still searching and + * don't want to match ourselves! */ + ins_compl_delete(); + } + + /* Enter will select a match when the match wasn't inserted and the popup + * menu is visible. */ + compl_enter_selects = !insert_match && compl_match_array != NULL; + + /* + * Show the file name for the match (if any) + * Truncate the file name to avoid a wait for return. + */ + if (compl_shown_match->cp_fname != NULL) { + STRCPY(IObuff, "match in file "); + i = (vim_strsize(compl_shown_match->cp_fname) + 16) - sc_col; + if (i <= 0) + i = 0; + else + STRCAT(IObuff, "<"); + STRCAT(IObuff, compl_shown_match->cp_fname + i); + msg(IObuff); + redraw_cmdline = FALSE; /* don't overwrite! */ + } + + return num_matches; +} + +/* + * Call this while finding completions, to check whether the user has hit a key + * that should change the currently displayed completion, or exit completion + * mode. Also, when compl_pending is not zero, show a completion as soon as + * possible. -- webb + * "frequency" specifies out of how many calls we actually check. + */ +void ins_compl_check_keys(frequency) +int frequency; +{ + static int count = 0; + + int c; + + /* Don't check when reading keys from a script. That would break the test + * scripts */ + if (using_script()) + return; + + /* Only do this at regular intervals */ + if (++count < frequency) + return; + count = 0; + + /* Check for a typed key. Do use mappings, otherwise vim_is_ctrl_x_key() + * can't do its work correctly. */ + c = vpeekc_any(); + if (c != NUL) { + if (vim_is_ctrl_x_key(c) && c != Ctrl_X && c != Ctrl_R) { + c = safe_vgetc(); /* Eat the character */ + compl_shows_dir = ins_compl_key2dir(c); + (void)ins_compl_next(FALSE, ins_compl_key2count(c), + c != K_UP && c != K_DOWN); + } else { + /* Need to get the character to have KeyTyped set. We'll put it + * back with vungetc() below. But skip K_IGNORE. */ + c = safe_vgetc(); + if (c != K_IGNORE) { + /* Don't interrupt completion when the character wasn't typed, + * e.g., when doing @q to replay keys. */ + if (c != Ctrl_R && KeyTyped) + compl_interrupted = TRUE; + + vungetc(c); + } + } + } + if (compl_pending != 0 && !got_int) { + int todo = compl_pending > 0 ? compl_pending : -compl_pending; + + compl_pending = 0; + (void)ins_compl_next(FALSE, todo, TRUE); + } +} + +/* + * Decide the direction of Insert mode complete from the key typed. + * Returns BACKWARD or FORWARD. + */ +static int ins_compl_key2dir(c) +int c; +{ + if (c == Ctrl_P || c == Ctrl_L + || (pum_visible() && (c == K_PAGEUP || c == K_KPAGEUP + || c == K_S_UP || c == K_UP))) + return BACKWARD; + return FORWARD; +} + +/* + * Return TRUE for keys that are used for completion only when the popup menu + * is visible. + */ +static int ins_compl_pum_key(c) +int c; +{ + return pum_visible() && (c == K_PAGEUP || c == K_KPAGEUP || c == K_S_UP + || c == K_PAGEDOWN || c == K_KPAGEDOWN || c == + K_S_DOWN + || c == K_UP || c == K_DOWN); +} + +/* + * Decide the number of completions to move forward. + * Returns 1 for most keys, height of the popup menu for page-up/down keys. + */ +static int ins_compl_key2count(c) +int c; +{ + int h; + + if (ins_compl_pum_key(c) && c != K_UP && c != K_DOWN) { + h = pum_get_height(); + if (h > 3) + h -= 2; /* keep some context */ + return h; + } + return 1; +} + +/* + * Return TRUE if completion with "c" should insert the match, FALSE if only + * to change the currently selected completion. + */ +static int ins_compl_use_match(c) +int c; +{ + switch (c) { + case K_UP: + case K_DOWN: + case K_PAGEDOWN: + case K_KPAGEDOWN: + case K_S_DOWN: + case K_PAGEUP: + case K_KPAGEUP: + case K_S_UP: + return FALSE; + } + return TRUE; +} + +/* + * Do Insert mode completion. + * Called when character "c" was typed, which has a meaning for completion. + * Returns OK if completion was done, FAIL if something failed (out of mem). + */ +static int ins_complete(c) +int c; +{ + char_u *line; + int startcol = 0; /* column where searched text starts */ + colnr_T curs_col; /* cursor column */ + int n; + int save_w_wrow; + + compl_direction = ins_compl_key2dir(c); + if (!compl_started) { + /* First time we hit ^N or ^P (in a row, I mean) */ + + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + if (stop_arrow() == FAIL) + return FAIL; + + line = ml_get(curwin->w_cursor.lnum); + curs_col = curwin->w_cursor.col; + compl_pending = 0; + + /* If this same ctrl_x_mode has been interrupted use the text from + * "compl_startpos" to the cursor as a pattern to add a new word + * instead of expand the one before the cursor, in word-wise if + * "compl_startpos" is not in the same line as the cursor then fix it + * (the line has been split because it was longer than 'tw'). if SOL + * is set then skip the previous pattern, a word at the beginning of + * the line has been inserted, we'll look for that -- Acevedo. */ + if ((compl_cont_status & CONT_INTRPT) == CONT_INTRPT + && compl_cont_mode == ctrl_x_mode) { + /* + * it is a continued search + */ + compl_cont_status &= ~CONT_INTRPT; /* remove INTRPT */ + if (ctrl_x_mode == 0 || ctrl_x_mode == CTRL_X_PATH_PATTERNS + || ctrl_x_mode == CTRL_X_PATH_DEFINES) { + if (compl_startpos.lnum != curwin->w_cursor.lnum) { + /* line (probably) wrapped, set compl_startpos to the + * first non_blank in the line, if it is not a wordchar + * include it to get a better pattern, but then we don't + * want the "\\<" prefix, check it bellow */ + compl_col = (colnr_T)(skipwhite(line) - line); + compl_startpos.col = compl_col; + compl_startpos.lnum = curwin->w_cursor.lnum; + compl_cont_status &= ~CONT_SOL; /* clear SOL if present */ + } else { + /* S_IPOS was set when we inserted a word that was at the + * beginning of the line, which means that we'll go to SOL + * mode but first we need to redefine compl_startpos */ + if (compl_cont_status & CONT_S_IPOS) { + compl_cont_status |= CONT_SOL; + compl_startpos.col = (colnr_T)(skipwhite( + line + compl_length + + compl_startpos.col) - line); + } + compl_col = compl_startpos.col; + } + compl_length = curwin->w_cursor.col - (int)compl_col; + /* IObuff is used to add a "word from the next line" would we + * have enough space? just being paranoid */ +#define MIN_SPACE 75 + if (compl_length > (IOSIZE - MIN_SPACE)) { + compl_cont_status &= ~CONT_SOL; + compl_length = (IOSIZE - MIN_SPACE); + compl_col = curwin->w_cursor.col - compl_length; + } + compl_cont_status |= CONT_ADDING | CONT_N_ADDS; + if (compl_length < 1) + compl_cont_status &= CONT_LOCAL; + } else if (ctrl_x_mode == CTRL_X_WHOLE_LINE) + compl_cont_status = CONT_ADDING | CONT_N_ADDS; + else + compl_cont_status = 0; + } else + compl_cont_status &= CONT_LOCAL; + + if (!(compl_cont_status & CONT_ADDING)) { /* normal expansion */ + compl_cont_mode = ctrl_x_mode; + if (ctrl_x_mode != 0) /* Remove LOCAL if ctrl_x_mode != 0 */ + compl_cont_status = 0; + compl_cont_status |= CONT_N_ADDS; + compl_startpos = curwin->w_cursor; + startcol = (int)curs_col; + compl_col = 0; + } + + /* Work out completion pattern and original text -- webb */ + if (ctrl_x_mode == 0 || (ctrl_x_mode & CTRL_X_WANT_IDENT)) { + if ((compl_cont_status & CONT_SOL) + || ctrl_x_mode == CTRL_X_PATH_DEFINES) { + if (!(compl_cont_status & CONT_ADDING)) { + while (--startcol >= 0 && vim_isIDc(line[startcol])) + ; + compl_col += ++startcol; + compl_length = curs_col - startcol; + } + if (p_ic) + compl_pattern = str_foldcase(line + compl_col, + compl_length, NULL, 0); + else + compl_pattern = vim_strnsave(line + compl_col, + compl_length); + if (compl_pattern == NULL) + return FAIL; + } else if (compl_cont_status & CONT_ADDING) { + char_u *prefix = (char_u *)"\\<"; + + /* we need up to 2 extra chars for the prefix */ + compl_pattern = alloc(quote_meta(NULL, line + compl_col, + compl_length) + 2); + if (compl_pattern == NULL) + return FAIL; + if (!vim_iswordp(line + compl_col) + || (compl_col > 0 + && ( + vim_iswordp(mb_prevptr(line, line + compl_col)) + ))) + prefix = (char_u *)""; + STRCPY((char *)compl_pattern, prefix); + (void)quote_meta(compl_pattern + STRLEN(prefix), + line + compl_col, compl_length); + } else if (--startcol < 0 || + !vim_iswordp(mb_prevptr(line, line + startcol + 1)) + ) { + /* Match any word of at least two chars */ + compl_pattern = vim_strsave((char_u *)"\\<\\k\\k"); + if (compl_pattern == NULL) + return FAIL; + compl_col += curs_col; + compl_length = 0; + } else { + /* Search the point of change class of multibyte character + * or not a word single byte character backward. */ + if (has_mbyte) { + int base_class; + int head_off; + + startcol -= (*mb_head_off)(line, line + startcol); + base_class = mb_get_class(line + startcol); + while (--startcol >= 0) { + head_off = (*mb_head_off)(line, line + startcol); + if (base_class != mb_get_class(line + startcol + - head_off)) + break; + startcol -= head_off; + } + } else + while (--startcol >= 0 && vim_iswordc(line[startcol])) + ; + compl_col += ++startcol; + compl_length = (int)curs_col - startcol; + if (compl_length == 1) { + /* Only match word with at least two chars -- webb + * there's no need to call quote_meta, + * alloc(7) is enough -- Acevedo + */ + compl_pattern = alloc(7); + if (compl_pattern == NULL) + return FAIL; + STRCPY((char *)compl_pattern, "\\<"); + (void)quote_meta(compl_pattern + 2, line + compl_col, 1); + STRCAT((char *)compl_pattern, "\\k"); + } else { + compl_pattern = alloc(quote_meta(NULL, line + compl_col, + compl_length) + 2); + if (compl_pattern == NULL) + return FAIL; + STRCPY((char *)compl_pattern, "\\<"); + (void)quote_meta(compl_pattern + 2, line + compl_col, + compl_length); + } + } + } else if (ctrl_x_mode == CTRL_X_WHOLE_LINE) { + compl_col = (colnr_T)(skipwhite(line) - line); + compl_length = (int)curs_col - (int)compl_col; + if (compl_length < 0) /* cursor in indent: empty pattern */ + compl_length = 0; + if (p_ic) + compl_pattern = str_foldcase(line + compl_col, compl_length, + NULL, 0); + else + compl_pattern = vim_strnsave(line + compl_col, compl_length); + if (compl_pattern == NULL) + return FAIL; + } else if (ctrl_x_mode == CTRL_X_FILES) { + /* Go back to just before the first filename character. */ + if (startcol > 0) { + char_u *p = line + startcol; + + mb_ptr_back(line, p); + while (p > line && vim_isfilec(PTR2CHAR(p))) + mb_ptr_back(line, p); + if (p == line && vim_isfilec(PTR2CHAR(p))) + startcol = 0; + else + startcol = (int)(p - line) + 1; + } + + compl_col += startcol; + compl_length = (int)curs_col - startcol; + compl_pattern = addstar(line + compl_col, compl_length, + EXPAND_FILES); + if (compl_pattern == NULL) + return FAIL; + } else if (ctrl_x_mode == CTRL_X_CMDLINE) { + compl_pattern = vim_strnsave(line, curs_col); + if (compl_pattern == NULL) + return FAIL; + set_cmd_context(&compl_xp, compl_pattern, + (int)STRLEN(compl_pattern), curs_col); + if (compl_xp.xp_context == EXPAND_UNSUCCESSFUL + || compl_xp.xp_context == EXPAND_NOTHING) + /* No completion possible, use an empty pattern to get a + * "pattern not found" message. */ + compl_col = curs_col; + else + compl_col = (int)(compl_xp.xp_pattern - compl_pattern); + compl_length = curs_col - compl_col; + } else if (ctrl_x_mode == CTRL_X_FUNCTION || ctrl_x_mode == + CTRL_X_OMNI) { + /* + * Call user defined function 'completefunc' with "a:findstart" + * set to 1 to obtain the length of text to use for completion. + */ + char_u *args[2]; + int col; + char_u *funcname; + pos_T pos; + win_T *curwin_save; + buf_T *curbuf_save; + + /* Call 'completefunc' or 'omnifunc' and get pattern length as a + * string */ + funcname = ctrl_x_mode == CTRL_X_FUNCTION + ? curbuf->b_p_cfu : curbuf->b_p_ofu; + if (*funcname == NUL) { + EMSG2(_(e_notset), ctrl_x_mode == CTRL_X_FUNCTION + ? "completefunc" : "omnifunc"); + return FAIL; + } + + args[0] = (char_u *)"1"; + args[1] = NULL; + pos = curwin->w_cursor; + curwin_save = curwin; + curbuf_save = curbuf; + col = call_func_retnr(funcname, 2, args, FALSE); + if (curwin_save != curwin || curbuf_save != curbuf) { + EMSG(_(e_complwin)); + return FAIL; + } + curwin->w_cursor = pos; /* restore the cursor position */ + check_cursor(); + if (!equalpos(curwin->w_cursor, pos)) { + EMSG(_(e_compldel)); + return FAIL; + } + + /* Return value -2 means the user complete function wants to + * cancel the complete without an error. + * Return value -3 does the same as -2 and leaves CTRL-X mode.*/ + if (col == -2) + return FAIL; + if (col == -3) { + ctrl_x_mode = 0; + edit_submode = NULL; + msg_clr_cmdline(); + return FAIL; + } + + /* + * Reset extended parameters of completion, when start new + * completion. + */ + compl_opt_refresh_always = FALSE; + + if (col < 0) + col = curs_col; + compl_col = col; + if (compl_col > curs_col) + compl_col = curs_col; + + /* Setup variables for completion. Need to obtain "line" again, + * it may have become invalid. */ + line = ml_get(curwin->w_cursor.lnum); + compl_length = curs_col - compl_col; + compl_pattern = vim_strnsave(line + compl_col, compl_length); + if (compl_pattern == NULL) + return FAIL; + } else if (ctrl_x_mode == CTRL_X_SPELL) { + if (spell_bad_len > 0) + compl_col = curs_col - spell_bad_len; + else + compl_col = spell_word_start(startcol); + if (compl_col >= (colnr_T)startcol) { + compl_length = 0; + compl_col = curs_col; + } else { + spell_expand_check_cap(compl_col); + compl_length = (int)curs_col - compl_col; + } + /* Need to obtain "line" again, it may have become invalid. */ + line = ml_get(curwin->w_cursor.lnum); + compl_pattern = vim_strnsave(line + compl_col, compl_length); + if (compl_pattern == NULL) + return FAIL; + } else { + EMSG2(_(e_intern2), "ins_complete()"); + return FAIL; + } + + if (compl_cont_status & CONT_ADDING) { + edit_submode_pre = (char_u *)_(" Adding"); + if (ctrl_x_mode == CTRL_X_WHOLE_LINE) { + /* Insert a new line, keep indentation but ignore 'comments' */ + char_u *old = curbuf->b_p_com; + + curbuf->b_p_com = (char_u *)""; + compl_startpos.lnum = curwin->w_cursor.lnum; + compl_startpos.col = compl_col; + ins_eol('\r'); + curbuf->b_p_com = old; + compl_length = 0; + compl_col = curwin->w_cursor.col; + } + } else { + edit_submode_pre = NULL; + compl_startpos.col = compl_col; + } + + if (compl_cont_status & CONT_LOCAL) + edit_submode = (char_u *)_(ctrl_x_msgs[CTRL_X_LOCAL_MSG]); + else + edit_submode = (char_u *)_(CTRL_X_MSG(ctrl_x_mode)); + + /* If any of the original typed text has been changed we need to fix + * the redo buffer. */ + ins_compl_fixRedoBufForLeader(NULL); + + /* Always add completion for the original text. */ + vim_free(compl_orig_text); + compl_orig_text = vim_strnsave(line + compl_col, compl_length); + if (compl_orig_text == NULL || ins_compl_add(compl_orig_text, + -1, p_ic, NULL, NULL, 0, ORIGINAL_TEXT, FALSE) != OK) { + vim_free(compl_pattern); + compl_pattern = NULL; + vim_free(compl_orig_text); + compl_orig_text = NULL; + return FAIL; + } + + /* showmode might reset the internal line pointers, so it must + * be called before line = ml_get(), or when this address is no + * longer needed. -- Acevedo. + */ + edit_submode_extra = (char_u *)_("-- Searching..."); + edit_submode_highl = HLF_COUNT; + showmode(); + edit_submode_extra = NULL; + out_flush(); + } + + compl_shown_match = compl_curr_match; + compl_shows_dir = compl_direction; + + /* + * Find next match (and following matches). + */ + save_w_wrow = curwin->w_wrow; + n = ins_compl_next(TRUE, ins_compl_key2count(c), ins_compl_use_match(c)); + + /* may undisplay the popup menu */ + ins_compl_upd_pum(); + + if (n > 1) /* all matches have been found */ + compl_matches = n; + compl_curr_match = compl_shown_match; + compl_direction = compl_shows_dir; + + /* Eat the ESC that vgetc() returns after a CTRL-C to avoid leaving Insert + * mode. */ + if (got_int && !global_busy) { + (void)vgetc(); + got_int = FALSE; + } + + /* we found no match if the list has only the "compl_orig_text"-entry */ + if (compl_first_match == compl_first_match->cp_next) { + edit_submode_extra = (compl_cont_status & CONT_ADDING) + && compl_length > 1 + ? (char_u *)_(e_hitend) : (char_u *)_(e_patnotf); + edit_submode_highl = HLF_E; + /* remove N_ADDS flag, so next ^X<> won't try to go to ADDING mode, + * because we couldn't expand anything at first place, but if we used + * ^P, ^N, ^X^I or ^X^D we might want to add-expand a single-char-word + * (such as M in M'exico) if not tried already. -- Acevedo */ + if ( compl_length > 1 + || (compl_cont_status & CONT_ADDING) + || (ctrl_x_mode != 0 + && ctrl_x_mode != CTRL_X_PATH_PATTERNS + && ctrl_x_mode != CTRL_X_PATH_DEFINES)) + compl_cont_status &= ~CONT_N_ADDS; + } + + if (compl_curr_match->cp_flags & CONT_S_IPOS) + compl_cont_status |= CONT_S_IPOS; + else + compl_cont_status &= ~CONT_S_IPOS; + + if (edit_submode_extra == NULL) { + if (compl_curr_match->cp_flags & ORIGINAL_TEXT) { + edit_submode_extra = (char_u *)_("Back at original"); + edit_submode_highl = HLF_W; + } else if (compl_cont_status & CONT_S_IPOS) { + edit_submode_extra = (char_u *)_("Word from other line"); + edit_submode_highl = HLF_COUNT; + } else if (compl_curr_match->cp_next == compl_curr_match->cp_prev) { + edit_submode_extra = (char_u *)_("The only match"); + edit_submode_highl = HLF_COUNT; + } else { + /* Update completion sequence number when needed. */ + if (compl_curr_match->cp_number == -1) { + int number = 0; + compl_T *match; + + if (compl_direction == FORWARD) { + /* search backwards for the first valid (!= -1) number. + * This should normally succeed already at the first loop + * cycle, so it's fast! */ + for (match = compl_curr_match->cp_prev; match != NULL + && match != compl_first_match; + match = match->cp_prev) + if (match->cp_number != -1) { + number = match->cp_number; + break; + } + if (match != NULL) + /* go up and assign all numbers which are not assigned + * yet */ + for (match = match->cp_next; + match != NULL && match->cp_number == -1; + match = match->cp_next) + match->cp_number = ++number; + } else { /* BACKWARD */ + /* search forwards (upwards) for the first valid (!= -1) + * number. This should normally succeed already at the + * first loop cycle, so it's fast! */ + for (match = compl_curr_match->cp_next; match != NULL + && match != compl_first_match; + match = match->cp_next) + if (match->cp_number != -1) { + number = match->cp_number; + break; + } + if (match != NULL) + /* go down and assign all numbers which are not + * assigned yet */ + for (match = match->cp_prev; match + && match->cp_number == -1; + match = match->cp_prev) + match->cp_number = ++number; + } + } + + /* The match should always have a sequence number now, this is + * just a safety check. */ + if (compl_curr_match->cp_number != -1) { + /* Space for 10 text chars. + 2x10-digit no.s = 31. + * Translations may need more than twice that. */ + static char_u match_ref[81]; + + if (compl_matches > 0) + vim_snprintf((char *)match_ref, sizeof(match_ref), + _("match %d of %d"), + compl_curr_match->cp_number, compl_matches); + else + vim_snprintf((char *)match_ref, sizeof(match_ref), + _("match %d"), + compl_curr_match->cp_number); + edit_submode_extra = match_ref; + edit_submode_highl = HLF_R; + if (dollar_vcol >= 0) + curs_columns(FALSE); + } + } + } + + /* Show a message about what (completion) mode we're in. */ + showmode(); + if (edit_submode_extra != NULL) { + if (!p_smd) + msg_attr(edit_submode_extra, + edit_submode_highl < HLF_COUNT + ? hl_attr(edit_submode_highl) : 0); + } else + msg_clr_cmdline(); /* necessary for "noshowmode" */ + + /* Show the popup menu, unless we got interrupted. */ + if (!compl_interrupted) { + /* RedrawingDisabled may be set when invoked through complete(). */ + n = RedrawingDisabled; + RedrawingDisabled = 0; + + /* If the cursor moved we need to remove the pum first. */ + setcursor(); + if (save_w_wrow != curwin->w_wrow) + ins_compl_del_pum(); + + ins_compl_show_pum(); + setcursor(); + RedrawingDisabled = n; + } + compl_was_interrupted = compl_interrupted; + compl_interrupted = FALSE; + + return OK; +} + +/* + * Looks in the first "len" chars. of "src" for search-metachars. + * If dest is not NULL the chars. are copied there quoting (with + * a backslash) the metachars, and dest would be NUL terminated. + * Returns the length (needed) of dest + */ +static unsigned quote_meta(dest, src, len) +char_u *dest; +char_u *src; +int len; +{ + unsigned m = (unsigned)len + 1; /* one extra for the NUL */ + + for (; --len >= 0; src++) { + switch (*src) { + case '.': + case '*': + case '[': + if (ctrl_x_mode == CTRL_X_DICTIONARY + || ctrl_x_mode == CTRL_X_THESAURUS) + break; + case '~': + if (!p_magic) /* quote these only if magic is set */ + break; + case '\\': + if (ctrl_x_mode == CTRL_X_DICTIONARY + || ctrl_x_mode == CTRL_X_THESAURUS) + break; + case '^': /* currently it's not needed. */ + case '$': + m++; + if (dest != NULL) + *dest++ = '\\'; + break; + } + if (dest != NULL) + *dest++ = *src; + /* Copy remaining bytes of a multibyte character. */ + if (has_mbyte) { + int i, mb_len; + + mb_len = (*mb_ptr2len)(src) - 1; + if (mb_len > 0 && len >= mb_len) + for (i = 0; i < mb_len; ++i) { + --len; + ++src; + if (dest != NULL) + *dest++ = *src; + } + } + } + if (dest != NULL) + *dest = NUL; + + return m; +} + +/* + * Next character is interpreted literally. + * A one, two or three digit decimal number is interpreted as its byte value. + * If one or two digits are entered, the next character is given to vungetc(). + * For Unicode a character > 255 may be returned. + */ +int get_literal() { + int cc; + int nc; + int i; + int hex = FALSE; + int octal = FALSE; + int unicode = 0; + + if (got_int) + return Ctrl_C; + +#ifdef USE_ON_FLY_SCROLL + dont_scroll = TRUE; /* disallow scrolling here */ +#endif + ++no_mapping; /* don't map the next key hits */ + cc = 0; + i = 0; + for (;; ) { + nc = plain_vgetc(); + if (!(State & CMDLINE) + && MB_BYTE2LEN_CHECK(nc) == 1 + ) + add_to_showcmd(nc); + if (nc == 'x' || nc == 'X') + hex = TRUE; + else if (nc == 'o' || nc == 'O') + octal = TRUE; + else if (nc == 'u' || nc == 'U') + unicode = nc; + else { + if (hex + || unicode != 0 + ) { + if (!vim_isxdigit(nc)) + break; + cc = cc * 16 + hex2nr(nc); + } else if (octal) { + if (nc < '0' || nc > '7') + break; + cc = cc * 8 + nc - '0'; + } else { + if (!VIM_ISDIGIT(nc)) + break; + cc = cc * 10 + nc - '0'; + } + + ++i; + } + + if (cc > 255 + && unicode == 0 + ) + cc = 255; /* limit range to 0-255 */ + nc = 0; + + if (hex) { /* hex: up to two chars */ + if (i >= 2) + break; + } else if (unicode) { /* Unicode: up to four or eight chars */ + if ((unicode == 'u' && i >= 4) || (unicode == 'U' && i >= 8)) + break; + } else if (i >= 3) /* decimal or octal: up to three chars */ + break; + } + if (i == 0) { /* no number entered */ + if (nc == K_ZERO) { /* NUL is stored as NL */ + cc = '\n'; + nc = 0; + } else { + cc = nc; + nc = 0; + } + } + + if (cc == 0) /* NUL is stored as NL */ + cc = '\n'; + if (enc_dbcs && (cc & 0xff) == 0) + cc = '?'; /* don't accept an illegal DBCS char, the NUL in the + second byte will cause trouble! */ + + --no_mapping; + if (nc) + vungetc(nc); + got_int = FALSE; /* CTRL-C typed after CTRL-V is not an interrupt */ + return cc; +} + +/* + * Insert character, taking care of special keys and mod_mask + */ +static void insert_special(c, allow_modmask, ctrlv) +int c; +int allow_modmask; +int ctrlv; /* c was typed after CTRL-V */ +{ + char_u *p; + int len; + + /* + * Special function key, translate into "". Up to the last '>' is + * inserted with ins_str(), so as not to replace characters in replace + * mode. + * Only use mod_mask for special keys, to avoid things like , + * unless 'allow_modmask' is TRUE. + */ + if (IS_SPECIAL(c) || (mod_mask && allow_modmask)) { + p = get_special_key_name(c, mod_mask); + len = (int)STRLEN(p); + c = p[len - 1]; + if (len > 2) { + if (stop_arrow() == FAIL) + return; + p[len - 1] = NUL; + ins_str(p); + AppendToRedobuffLit(p, -1); + ctrlv = FALSE; + } + } + if (stop_arrow() == OK) + insertchar(c, ctrlv ? INSCHAR_CTRLV : 0, -1); +} + +/* + * Special characters in this context are those that need processing other + * than the simple insertion that can be performed here. This includes ESC + * which terminates the insert, and CR/NL which need special processing to + * open up a new line. This routine tries to optimize insertions performed by + * the "redo", "undo" or "put" commands, so it needs to know when it should + * stop and defer processing to the "normal" mechanism. + * '0' and '^' are special, because they can be followed by CTRL-D. + */ +# define ISSPECIAL(c) ((c) < ' ' || (c) >= DEL || (c) == '0' || (c) == '^') + +# define WHITECHAR(cc) (vim_iswhite(cc) && \ + (!enc_utf8 || \ + !utf_iscomposing(utf_ptr2char(ml_get_cursor() + 1)))) + +/* + * "flags": INSCHAR_FORMAT - force formatting + * INSCHAR_CTRLV - char typed just after CTRL-V + * INSCHAR_NO_FEX - don't use 'formatexpr' + * + * NOTE: passes the flags value straight through to internal_format() which, + * beside INSCHAR_FORMAT (above), is also looking for these: + * INSCHAR_DO_COM - format comments + * INSCHAR_COM_LIST - format comments with num list or 2nd line indent + */ +void insertchar(c, flags, second_indent) +int c; /* character to insert or NUL */ +int flags; /* INSCHAR_FORMAT, etc. */ +int second_indent; /* indent for second line if >= 0 */ +{ + int textwidth; + char_u *p; + int fo_ins_blank; + + textwidth = comp_textwidth(flags & INSCHAR_FORMAT); + fo_ins_blank = has_format_option(FO_INS_BLANK); + + /* + * Try to break the line in two or more pieces when: + * - Always do this if we have been called to do formatting only. + * - Always do this when 'formatoptions' has the 'a' flag and the line + * ends in white space. + * - Otherwise: + * - Don't do this if inserting a blank + * - Don't do this if an existing character is being replaced, unless + * we're in VREPLACE mode. + * - Do this if the cursor is not on the line where insert started + * or - 'formatoptions' doesn't have 'l' or the line was not too long + * before the insert. + * - 'formatoptions' doesn't have 'b' or a blank was inserted at or + * before 'textwidth' + */ + if (textwidth > 0 + && ((flags & INSCHAR_FORMAT) + || (!vim_iswhite(c) + && !((State & REPLACE_FLAG) + && !(State & VREPLACE_FLAG) + && *ml_get_cursor() != NUL) + && (curwin->w_cursor.lnum != Insstart.lnum + || ((!has_format_option(FO_INS_LONG) + || Insstart_textlen <= (colnr_T)textwidth) + && (!fo_ins_blank + || Insstart_blank_vcol <= (colnr_T)textwidth + )))))) { + /* Format with 'formatexpr' when it's set. Use internal formatting + * when 'formatexpr' isn't set or it returns non-zero. */ + int do_internal = TRUE; + + if (*curbuf->b_p_fex != NUL && (flags & INSCHAR_NO_FEX) == 0) { + do_internal = (fex_format(curwin->w_cursor.lnum, 1L, c) != 0); + /* It may be required to save for undo again, e.g. when setline() + * was called. */ + ins_need_undo = TRUE; + } + if (do_internal) + internal_format(textwidth, second_indent, flags, c == NUL, c); + } + + if (c == NUL) /* only formatting was wanted */ + return; + + /* Check whether this character should end a comment. */ + if (did_ai && (int)c == end_comment_pending) { + char_u *line; + char_u lead_end[COM_MAX_LEN]; /* end-comment string */ + int middle_len, end_len; + int i; + + /* + * Need to remove existing (middle) comment leader and insert end + * comment leader. First, check what comment leader we can find. + */ + i = get_leader_len(line = ml_get_curline(), &p, FALSE, TRUE); + if (i > 0 && vim_strchr(p, COM_MIDDLE) != NULL) { /* Just checking */ + /* Skip middle-comment string */ + while (*p && p[-1] != ':') /* find end of middle flags */ + ++p; + middle_len = copy_option_part(&p, lead_end, COM_MAX_LEN, ","); + /* Don't count trailing white space for middle_len */ + while (middle_len > 0 && vim_iswhite(lead_end[middle_len - 1])) + --middle_len; + + /* Find the end-comment string */ + while (*p && p[-1] != ':') /* find end of end flags */ + ++p; + end_len = copy_option_part(&p, lead_end, COM_MAX_LEN, ","); + + /* Skip white space before the cursor */ + i = curwin->w_cursor.col; + while (--i >= 0 && vim_iswhite(line[i])) + ; + i++; + + /* Skip to before the middle leader */ + i -= middle_len; + + /* Check some expected things before we go on */ + if (i >= 0 && lead_end[end_len - 1] == end_comment_pending) { + /* Backspace over all the stuff we want to replace */ + backspace_until_column(i); + + /* + * Insert the end-comment string, except for the last + * character, which will get inserted as normal later. + */ + ins_bytes_len(lead_end, end_len - 1); + } + } + } + end_comment_pending = NUL; + + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + + /* + * If there's any pending input, grab up to INPUT_BUFLEN at once. + * This speeds up normal text input considerably. + * Don't do this when 'cindent' or 'indentexpr' is set, because we might + * need to re-indent at a ':', or any other character (but not what + * 'paste' is set).. + * Don't do this when there an InsertCharPre autocommand is defined, + * because we need to fire the event for every character. + */ +#ifdef USE_ON_FLY_SCROLL + dont_scroll = FALSE; /* allow scrolling here */ +#endif + + if ( !ISSPECIAL(c) + && (!has_mbyte || (*mb_char2len)(c) == 1) + && vpeekc() != NUL + && !(State & REPLACE_FLAG) + && !cindent_on() + && !p_ri + && !has_insertcharpre() + ) { +#define INPUT_BUFLEN 100 + char_u buf[INPUT_BUFLEN + 1]; + int i; + colnr_T virtcol = 0; + + buf[0] = c; + i = 1; + if (textwidth > 0) + virtcol = get_nolist_virtcol(); + /* + * Stop the string when: + * - no more chars available + * - finding a special character (command key) + * - buffer is full + * - running into the 'textwidth' boundary + * - need to check for abbreviation: A non-word char after a word-char + */ + while ( (c = vpeekc()) != NUL + && !ISSPECIAL(c) + && (!has_mbyte || MB_BYTE2LEN_CHECK(c) == 1) + && i < INPUT_BUFLEN + && (textwidth == 0 + || (virtcol += byte2cells(buf[i - 1])) < (colnr_T)textwidth) + && !(!no_abbr && !vim_iswordc(c) && vim_iswordc(buf[i - 1]))) { + c = vgetc(); + if (p_hkmap && KeyTyped) + c = hkmap(c); /* Hebrew mode mapping */ + if (p_fkmap && KeyTyped) + c = fkmap(c); /* Farsi mode mapping */ + buf[i++] = c; + } + + do_digraph(-1); /* clear digraphs */ + do_digraph(buf[i-1]); /* may be the start of a digraph */ + buf[i] = NUL; + ins_str(buf); + if (flags & INSCHAR_CTRLV) { + redo_literal(*buf); + i = 1; + } else + i = 0; + if (buf[i] != NUL) + AppendToRedobuffLit(buf + i, -1); + } else { + int cc; + + if (has_mbyte && (cc = (*mb_char2len)(c)) > 1) { + char_u buf[MB_MAXBYTES + 1]; + + (*mb_char2bytes)(c, buf); + buf[cc] = NUL; + ins_char_bytes(buf, cc); + AppendCharToRedobuff(c); + } else { + ins_char(c); + if (flags & INSCHAR_CTRLV) + redo_literal(c); + else + AppendCharToRedobuff(c); + } + } +} + +/* + * Format text at the current insert position. + * + * If the INSCHAR_COM_LIST flag is present, then the value of second_indent + * will be the comment leader length sent to open_line(). + */ +static void internal_format(textwidth, second_indent, flags, format_only, c) +int textwidth; +int second_indent; +int flags; +int format_only; +int c; /* character to be inserted (can be NUL) */ +{ + int cc; + int save_char = NUL; + int haveto_redraw = FALSE; + int fo_ins_blank = has_format_option(FO_INS_BLANK); + int fo_multibyte = has_format_option(FO_MBYTE_BREAK); + int fo_white_par = has_format_option(FO_WHITE_PAR); + int first_line = TRUE; + colnr_T leader_len; + int no_leader = FALSE; + int do_comments = (flags & INSCHAR_DO_COM); + + /* + * When 'ai' is off we don't want a space under the cursor to be + * deleted. Replace it with an 'x' temporarily. + */ + if (!curbuf->b_p_ai + && !(State & VREPLACE_FLAG) + ) { + cc = gchar_cursor(); + if (vim_iswhite(cc)) { + save_char = cc; + pchar_cursor('x'); + } + } + + /* + * Repeat breaking lines, until the current line is not too long. + */ + while (!got_int) { + int startcol; /* Cursor column at entry */ + int wantcol; /* column at textwidth border */ + int foundcol; /* column for start of spaces */ + int end_foundcol = 0; /* column for start of word */ + colnr_T len; + colnr_T virtcol; + int orig_col = 0; + char_u *saved_text = NULL; + colnr_T col; + colnr_T end_col; + + virtcol = get_nolist_virtcol() + + char2cells(c != NUL ? c : gchar_cursor()); + if (virtcol <= (colnr_T)textwidth) + break; + + if (no_leader) + do_comments = FALSE; + else if (!(flags & INSCHAR_FORMAT) + && has_format_option(FO_WRAP_COMS)) + do_comments = TRUE; + + /* Don't break until after the comment leader */ + if (do_comments) + leader_len = get_leader_len(ml_get_curline(), NULL, FALSE, TRUE); + else + leader_len = 0; + + /* If the line doesn't start with a comment leader, then don't + * start one in a following broken line. Avoids that a %word + * moved to the start of the next line causes all following lines + * to start with %. */ + if (leader_len == 0) + no_leader = TRUE; + if (!(flags & INSCHAR_FORMAT) + && leader_len == 0 + && !has_format_option(FO_WRAP)) + + break; + if ((startcol = curwin->w_cursor.col) == 0) + break; + + /* find column of textwidth border */ + coladvance((colnr_T)textwidth); + wantcol = curwin->w_cursor.col; + + curwin->w_cursor.col = startcol; + foundcol = 0; + + /* + * Find position to break at. + * Stop at first entered white when 'formatoptions' has 'v' + */ + while ((!fo_ins_blank && !has_format_option(FO_INS_VI)) + || (flags & INSCHAR_FORMAT) + || curwin->w_cursor.lnum != Insstart.lnum + || curwin->w_cursor.col >= Insstart.col) { + if (curwin->w_cursor.col == startcol && c != NUL) + cc = c; + else + cc = gchar_cursor(); + if (WHITECHAR(cc)) { + /* remember position of blank just before text */ + end_col = curwin->w_cursor.col; + + /* find start of sequence of blanks */ + while (curwin->w_cursor.col > 0 && WHITECHAR(cc)) { + dec_cursor(); + cc = gchar_cursor(); + } + if (curwin->w_cursor.col == 0 && WHITECHAR(cc)) + break; /* only spaces in front of text */ + /* Don't break until after the comment leader */ + if (curwin->w_cursor.col < leader_len) + break; + if (has_format_option(FO_ONE_LETTER)) { + /* do not break after one-letter words */ + if (curwin->w_cursor.col == 0) + break; /* one-letter word at begin */ + /* do not break "#a b" when 'tw' is 2 */ + if (curwin->w_cursor.col <= leader_len) + break; + col = curwin->w_cursor.col; + dec_cursor(); + cc = gchar_cursor(); + + if (WHITECHAR(cc)) + continue; /* one-letter, continue */ + curwin->w_cursor.col = col; + } + + inc_cursor(); + + end_foundcol = end_col + 1; + foundcol = curwin->w_cursor.col; + if (curwin->w_cursor.col <= (colnr_T)wantcol) + break; + } else if (cc >= 0x100 && fo_multibyte) { + /* Break after or before a multi-byte character. */ + if (curwin->w_cursor.col != startcol) { + /* Don't break until after the comment leader */ + if (curwin->w_cursor.col < leader_len) + break; + col = curwin->w_cursor.col; + inc_cursor(); + /* Don't change end_foundcol if already set. */ + if (foundcol != curwin->w_cursor.col) { + foundcol = curwin->w_cursor.col; + end_foundcol = foundcol; + if (curwin->w_cursor.col <= (colnr_T)wantcol) + break; + } + curwin->w_cursor.col = col; + } + + if (curwin->w_cursor.col == 0) + break; + + col = curwin->w_cursor.col; + + dec_cursor(); + cc = gchar_cursor(); + + if (WHITECHAR(cc)) + continue; /* break with space */ + /* Don't break until after the comment leader */ + if (curwin->w_cursor.col < leader_len) + break; + + curwin->w_cursor.col = col; + + foundcol = curwin->w_cursor.col; + end_foundcol = foundcol; + if (curwin->w_cursor.col <= (colnr_T)wantcol) + break; + } + if (curwin->w_cursor.col == 0) + break; + dec_cursor(); + } + + if (foundcol == 0) { /* no spaces, cannot break line */ + curwin->w_cursor.col = startcol; + break; + } + + /* Going to break the line, remove any "$" now. */ + undisplay_dollar(); + + /* + * Offset between cursor position and line break is used by replace + * stack functions. VREPLACE does not use this, and backspaces + * over the text instead. + */ + if (State & VREPLACE_FLAG) + orig_col = startcol; /* Will start backspacing from here */ + else + replace_offset = startcol - end_foundcol; + + /* + * adjust startcol for spaces that will be deleted and + * characters that will remain on top line + */ + curwin->w_cursor.col = foundcol; + while ((cc = gchar_cursor(), WHITECHAR(cc)) + && (!fo_white_par || curwin->w_cursor.col < startcol)) + inc_cursor(); + startcol -= curwin->w_cursor.col; + if (startcol < 0) + startcol = 0; + + if (State & VREPLACE_FLAG) { + /* + * In VREPLACE mode, we will backspace over the text to be + * wrapped, so save a copy now to put on the next line. + */ + saved_text = vim_strsave(ml_get_cursor()); + curwin->w_cursor.col = orig_col; + if (saved_text == NULL) + break; /* Can't do it, out of memory */ + saved_text[startcol] = NUL; + + /* Backspace over characters that will move to the next line */ + if (!fo_white_par) + backspace_until_column(foundcol); + } else { + /* put cursor after pos. to break line */ + if (!fo_white_par) + curwin->w_cursor.col = foundcol; + } + + /* + * Split the line just before the margin. + * Only insert/delete lines, but don't really redraw the window. + */ + open_line(FORWARD, OPENLINE_DELSPACES + OPENLINE_MARKFIX + + (fo_white_par ? OPENLINE_KEEPTRAIL : 0) + + (do_comments ? OPENLINE_DO_COM : 0) + + ((flags & INSCHAR_COM_LIST) ? OPENLINE_COM_LIST : 0) + , ((flags & INSCHAR_COM_LIST) ? second_indent : old_indent)); + if (!(flags & INSCHAR_COM_LIST)) + old_indent = 0; + + replace_offset = 0; + if (first_line) { + if (!(flags & INSCHAR_COM_LIST)) { + /* + * This section is for auto-wrap of numeric lists. When not + * in insert mode (i.e. format_lines()), the INSCHAR_COM_LIST + * flag will be set and open_line() will handle it (as seen + * above). The code here (and in get_number_indent()) will + * recognize comments if needed... + */ + if (second_indent < 0 && has_format_option(FO_Q_NUMBER)) + second_indent = + get_number_indent(curwin->w_cursor.lnum - 1); + if (second_indent >= 0) { + if (State & VREPLACE_FLAG) + change_indent(INDENT_SET, second_indent, + FALSE, NUL, TRUE); + else if (leader_len > 0 && second_indent - leader_len > 0) { + int i; + int padding = second_indent - leader_len; + + /* We started at the first_line of a numbered list + * that has a comment. the open_line() function has + * inserted the proper comment leader and positioned + * the cursor at the end of the split line. Now we + * add the additional whitespace needed after the + * comment leader for the numbered list. */ + for (i = 0; i < padding; i++) + ins_str((char_u *)" "); + changed_bytes(curwin->w_cursor.lnum, leader_len); + } else { + (void)set_indent(second_indent, SIN_CHANGED); + } + } + } + first_line = FALSE; + } + + if (State & VREPLACE_FLAG) { + /* + * In VREPLACE mode we have backspaced over the text to be + * moved, now we re-insert it into the new line. + */ + ins_bytes(saved_text); + vim_free(saved_text); + } else { + /* + * Check if cursor is not past the NUL off the line, cindent + * may have added or removed indent. + */ + curwin->w_cursor.col += startcol; + len = (colnr_T)STRLEN(ml_get_curline()); + if (curwin->w_cursor.col > len) + curwin->w_cursor.col = len; + } + + haveto_redraw = TRUE; + can_cindent = TRUE; + /* moved the cursor, don't autoindent or cindent now */ + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + line_breakcheck(); + } + + if (save_char != NUL) /* put back space after cursor */ + pchar_cursor(save_char); + + if (!format_only && haveto_redraw) { + update_topline(); + redraw_curbuf_later(VALID); + } +} + +/* + * Called after inserting or deleting text: When 'formatoptions' includes the + * 'a' flag format from the current line until the end of the paragraph. + * Keep the cursor at the same position relative to the text. + * The caller must have saved the cursor line for undo, following ones will be + * saved here. + */ +void auto_format(trailblank, prev_line) +int trailblank; /* when TRUE also format with trailing blank */ +int prev_line; /* may start in previous line */ +{ + pos_T pos; + colnr_T len; + char_u *old; + char_u *new, *pnew; + int wasatend; + int cc; + + if (!has_format_option(FO_AUTO)) + return; + + pos = curwin->w_cursor; + old = ml_get_curline(); + + /* may remove added space */ + check_auto_format(FALSE); + + /* Don't format in Insert mode when the cursor is on a trailing blank, the + * user might insert normal text next. Also skip formatting when "1" is + * in 'formatoptions' and there is a single character before the cursor. + * Otherwise the line would be broken and when typing another non-white + * next they are not joined back together. */ + wasatend = (pos.col == (colnr_T)STRLEN(old)); + if (*old != NUL && !trailblank && wasatend) { + dec_cursor(); + cc = gchar_cursor(); + if (!WHITECHAR(cc) && curwin->w_cursor.col > 0 + && has_format_option(FO_ONE_LETTER)) + dec_cursor(); + cc = gchar_cursor(); + if (WHITECHAR(cc)) { + curwin->w_cursor = pos; + return; + } + curwin->w_cursor = pos; + } + + /* With the 'c' flag in 'formatoptions' and 't' missing: only format + * comments. */ + if (has_format_option(FO_WRAP_COMS) && !has_format_option(FO_WRAP) + && get_leader_len(old, NULL, FALSE, TRUE) == 0) + return; + + /* + * May start formatting in a previous line, so that after "x" a word is + * moved to the previous line if it fits there now. Only when this is not + * the start of a paragraph. + */ + if (prev_line && !paragraph_start(curwin->w_cursor.lnum)) { + --curwin->w_cursor.lnum; + if (u_save_cursor() == FAIL) + return; + } + + /* + * Do the formatting and restore the cursor position. "saved_cursor" will + * be adjusted for the text formatting. + */ + saved_cursor = pos; + format_lines((linenr_T)-1, FALSE); + curwin->w_cursor = saved_cursor; + saved_cursor.lnum = 0; + + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { + /* "cannot happen" */ + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + coladvance((colnr_T)MAXCOL); + } else + check_cursor_col(); + + /* Insert mode: If the cursor is now after the end of the line while it + * previously wasn't, the line was broken. Because of the rule above we + * need to add a space when 'w' is in 'formatoptions' to keep a paragraph + * formatted. */ + if (!wasatend && has_format_option(FO_WHITE_PAR)) { + new = ml_get_curline(); + len = (colnr_T)STRLEN(new); + if (curwin->w_cursor.col == len) { + pnew = vim_strnsave(new, len + 2); + pnew[len] = ' '; + pnew[len + 1] = NUL; + ml_replace(curwin->w_cursor.lnum, pnew, FALSE); + /* remove the space later */ + did_add_space = TRUE; + } else + /* may remove added space */ + check_auto_format(FALSE); + } + + check_cursor(); +} + +/* + * When an extra space was added to continue a paragraph for auto-formatting, + * delete it now. The space must be under the cursor, just after the insert + * position. + */ +static void check_auto_format(end_insert) +int end_insert; /* TRUE when ending Insert mode */ +{ + int c = ' '; + int cc; + + if (did_add_space) { + cc = gchar_cursor(); + if (!WHITECHAR(cc)) + /* Somehow the space was removed already. */ + did_add_space = FALSE; + else { + if (!end_insert) { + inc_cursor(); + c = gchar_cursor(); + dec_cursor(); + } + if (c != NUL) { + /* The space is no longer at the end of the line, delete it. */ + del_char(FALSE); + did_add_space = FALSE; + } + } + } +} + +/* + * Find out textwidth to be used for formatting: + * if 'textwidth' option is set, use it + * else if 'wrapmargin' option is set, use W_WIDTH(curwin) - 'wrapmargin' + * if invalid value, use 0. + * Set default to window width (maximum 79) for "gq" operator. + */ +int comp_textwidth(ff) +int ff; /* force formatting (for "gq" command) */ +{ + int textwidth; + + textwidth = curbuf->b_p_tw; + if (textwidth == 0 && curbuf->b_p_wm) { + /* The width is the window width minus 'wrapmargin' minus all the + * things that add to the margin. */ + textwidth = W_WIDTH(curwin) - curbuf->b_p_wm; + if (cmdwin_type != 0) + textwidth -= 1; + textwidth -= curwin->w_p_fdc; + if (curwin->w_p_nu || curwin->w_p_rnu) + textwidth -= 8; + } + if (textwidth < 0) + textwidth = 0; + if (ff && textwidth == 0) { + textwidth = W_WIDTH(curwin) - 1; + if (textwidth > 79) + textwidth = 79; + } + return textwidth; +} + +/* + * Put a character in the redo buffer, for when just after a CTRL-V. + */ +static void redo_literal(c) +int c; +{ + char_u buf[10]; + + /* Only digits need special treatment. Translate them into a string of + * three digits. */ + if (VIM_ISDIGIT(c)) { + vim_snprintf((char *)buf, sizeof(buf), "%03d", c); + AppendToRedobuff(buf); + } else + AppendCharToRedobuff(c); +} + +/* + * start_arrow() is called when an arrow key is used in insert mode. + * For undo/redo it resembles hitting the key. + */ +static void start_arrow(end_insert_pos) +pos_T *end_insert_pos; /* can be NULL */ +{ + if (!arrow_used) { /* something has been inserted */ + AppendToRedobuff(ESC_STR); + stop_insert(end_insert_pos, FALSE, FALSE); + arrow_used = TRUE; /* this means we stopped the current insert */ + } + check_spell_redraw(); +} + +/* + * If we skipped highlighting word at cursor, do it now. + * It may be skipped again, thus reset spell_redraw_lnum first. + */ +static void check_spell_redraw() { + if (spell_redraw_lnum != 0) { + linenr_T lnum = spell_redraw_lnum; + + spell_redraw_lnum = 0; + redrawWinline(lnum, FALSE); + } +} + +/* + * Called when starting CTRL_X_SPELL mode: Move backwards to a previous badly + * spelled word, if there is one. + */ +static void spell_back_to_badword() { + pos_T tpos = curwin->w_cursor; + + spell_bad_len = spell_move_to(curwin, BACKWARD, TRUE, TRUE, NULL); + if (curwin->w_cursor.col != tpos.col) + start_arrow(&tpos); +} + +/* + * stop_arrow() is called before a change is made in insert mode. + * If an arrow key has been used, start a new insertion. + * Returns FAIL if undo is impossible, shouldn't insert then. + */ +int stop_arrow() { + if (arrow_used) { + if (u_save_cursor() == OK) { + arrow_used = FALSE; + ins_need_undo = FALSE; + } + Insstart = curwin->w_cursor; /* new insertion starts here */ + Insstart_textlen = (colnr_T)linetabsize(ml_get_curline()); + ai_col = 0; + if (State & VREPLACE_FLAG) { + orig_line_count = curbuf->b_ml.ml_line_count; + vr_lines_changed = 1; + } + ResetRedobuff(); + AppendToRedobuff((char_u *)"1i"); /* pretend we start an insertion */ + new_insert_skip = 2; + } else if (ins_need_undo) { + if (u_save_cursor() == OK) + ins_need_undo = FALSE; + } + + /* Always open fold at the cursor line when inserting something. */ + foldOpenCursor(); + + return arrow_used || ins_need_undo ? FAIL : OK; +} + +/* + * Do a few things to stop inserting. + * "end_insert_pos" is where insert ended. It is NULL when we already jumped + * to another window/buffer. + */ +static void stop_insert(end_insert_pos, esc, nomove) +pos_T *end_insert_pos; +int esc; /* called by ins_esc() */ +int nomove; /* , don't move cursor */ +{ + int cc; + char_u *ptr; + + stop_redo_ins(); + replace_flush(); /* abandon replace stack */ + + /* + * Save the inserted text for later redo with ^@ and CTRL-A. + * Don't do it when "restart_edit" was set and nothing was inserted, + * otherwise CTRL-O w and then will clear "last_insert". + */ + ptr = get_inserted(); + if (did_restart_edit == 0 || (ptr != NULL + && (int)STRLEN(ptr) > new_insert_skip)) { + vim_free(last_insert); + last_insert = ptr; + last_insert_skip = new_insert_skip; + } else + vim_free(ptr); + + if (!arrow_used && end_insert_pos != NULL) { + /* Auto-format now. It may seem strange to do this when stopping an + * insertion (or moving the cursor), but it's required when appending + * a line and having it end in a space. But only do it when something + * was actually inserted, otherwise undo won't work. */ + if (!ins_need_undo && has_format_option(FO_AUTO)) { + pos_T tpos = curwin->w_cursor; + + /* When the cursor is at the end of the line after a space the + * formatting will move it to the following word. Avoid that by + * moving the cursor onto the space. */ + cc = 'x'; + if (curwin->w_cursor.col > 0 && gchar_cursor() == NUL) { + dec_cursor(); + cc = gchar_cursor(); + if (!vim_iswhite(cc)) + curwin->w_cursor = tpos; + } + + auto_format(TRUE, FALSE); + + if (vim_iswhite(cc)) { + if (gchar_cursor() != NUL) + inc_cursor(); + /* If the cursor is still at the same character, also keep + * the "coladd". */ + if (gchar_cursor() == NUL + && curwin->w_cursor.lnum == tpos.lnum + && curwin->w_cursor.col == tpos.col) + curwin->w_cursor.coladd = tpos.coladd; + } + } + + /* If a space was inserted for auto-formatting, remove it now. */ + check_auto_format(TRUE); + + /* If we just did an auto-indent, remove the white space from the end + * of the line, and put the cursor back. + * Do this when ESC was used or moving the cursor up/down. + * Check for the old position still being valid, just in case the text + * got changed unexpectedly. */ + if (!nomove && did_ai && (esc || (vim_strchr(p_cpo, CPO_INDENT) == NULL + && curwin->w_cursor.lnum != + end_insert_pos->lnum)) + && end_insert_pos->lnum <= curbuf->b_ml.ml_line_count) { + pos_T tpos = curwin->w_cursor; + + curwin->w_cursor = *end_insert_pos; + check_cursor_col(); /* make sure it is not past the line */ + for (;; ) { + if (gchar_cursor() == NUL && curwin->w_cursor.col > 0) + --curwin->w_cursor.col; + cc = gchar_cursor(); + if (!vim_iswhite(cc)) + break; + if (del_char(TRUE) == FAIL) + break; /* should not happen */ + } + if (curwin->w_cursor.lnum != tpos.lnum) + curwin->w_cursor = tpos; + else if (cc != NUL) + ++curwin->w_cursor.col; /* put cursor back on the NUL */ + + /* may have started Visual mode, adjust the position for + * deleted characters. */ + if (VIsual_active && VIsual.lnum == curwin->w_cursor.lnum) { + int len = (int)STRLEN(ml_get_curline()); + + if (VIsual.col > len) { + VIsual.col = len; + VIsual.coladd = 0; + } + } + } + } + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + + /* Set '[ and '] to the inserted text. When end_insert_pos is NULL we are + * now in a different buffer. */ + if (end_insert_pos != NULL) { + curbuf->b_op_start = Insstart; + curbuf->b_op_end = *end_insert_pos; + } +} + +/* + * Set the last inserted text to a single character. + * Used for the replace command. + */ +void set_last_insert(c) +int c; +{ + char_u *s; + + vim_free(last_insert); + last_insert = alloc(MB_MAXBYTES * 3 + 5); + if (last_insert != NULL) { + s = last_insert; + /* Use the CTRL-V only when entering a special char */ + if (c < ' ' || c == DEL) + *s++ = Ctrl_V; + s = add_char2buf(c, s); + *s++ = ESC; + *s++ = NUL; + last_insert_skip = 0; + } +} + +#if defined(EXITFREE) || defined(PROTO) +void free_last_insert() { + vim_free(last_insert); + last_insert = NULL; + vim_free(compl_orig_text); + compl_orig_text = NULL; +} + +#endif + +/* + * Add character "c" to buffer "s". Escape the special meaning of K_SPECIAL + * and CSI. Handle multi-byte characters. + * Returns a pointer to after the added bytes. + */ +char_u * add_char2buf(c, s) +int c; +char_u *s; +{ + char_u temp[MB_MAXBYTES + 1]; + int i; + int len; + + len = (*mb_char2bytes)(c, temp); + for (i = 0; i < len; ++i) { + c = temp[i]; + /* Need to escape K_SPECIAL and CSI like in the typeahead buffer. */ + if (c == K_SPECIAL) { + *s++ = K_SPECIAL; + *s++ = KS_SPECIAL; + *s++ = KE_FILLER; + } else + *s++ = c; + } + return s; +} + +/* + * move cursor to start of line + * if flags & BL_WHITE move to first non-white + * if flags & BL_SOL move to first non-white if startofline is set, + * otherwise keep "curswant" column + * if flags & BL_FIX don't leave the cursor on a NUL. + */ +void beginline(flags) +int flags; +{ + if ((flags & BL_SOL) && !p_sol) + coladvance(curwin->w_curswant); + else { + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + + if (flags & (BL_WHITE | BL_SOL)) { + char_u *ptr; + + for (ptr = ml_get_curline(); vim_iswhite(*ptr) + && !((flags & BL_FIX) && ptr[1] == NUL); ++ptr) + ++curwin->w_cursor.col; + } + curwin->w_set_curswant = TRUE; + } +} + +/* + * oneright oneleft cursor_down cursor_up + * + * Move one char {right,left,down,up}. + * Doesn't move onto the NUL past the end of the line, unless it is allowed. + * Return OK when successful, FAIL when we hit a line of file boundary. + */ + +int oneright() { + char_u *ptr; + int l; + + if (virtual_active()) { + pos_T prevpos = curwin->w_cursor; + + /* Adjust for multi-wide char (excluding TAB) */ + ptr = ml_get_cursor(); + coladvance(getviscol() + ((*ptr != TAB && vim_isprintc( + (*mb_ptr2char)(ptr) + )) + ? ptr2cells(ptr) : 1)); + curwin->w_set_curswant = TRUE; + /* Return OK if the cursor moved, FAIL otherwise (at window edge). */ + return (prevpos.col != curwin->w_cursor.col + || prevpos.coladd != curwin->w_cursor.coladd) ? OK : FAIL; + } + + ptr = ml_get_cursor(); + if (*ptr == NUL) + return FAIL; /* already at the very end */ + + if (has_mbyte) + l = (*mb_ptr2len)(ptr); + else + l = 1; + + /* move "l" bytes right, but don't end up on the NUL, unless 'virtualedit' + * contains "onemore". */ + if (ptr[l] == NUL + && (ve_flags & VE_ONEMORE) == 0 + ) + return FAIL; + curwin->w_cursor.col += l; + + curwin->w_set_curswant = TRUE; + return OK; +} + +int oneleft() { + if (virtual_active()) { + int width; + int v = getviscol(); + + if (v == 0) + return FAIL; + + /* We might get stuck on 'showbreak', skip over it. */ + width = 1; + for (;; ) { + coladvance(v - width); + /* getviscol() is slow, skip it when 'showbreak' is empty and + * there are no multi-byte characters */ + if ((*p_sbr == NUL + && !has_mbyte + ) || getviscol() < v) + break; + ++width; + } + + if (curwin->w_cursor.coladd == 1) { + char_u *ptr; + + /* Adjust for multi-wide char (not a TAB) */ + ptr = ml_get_cursor(); + if (*ptr != TAB && vim_isprintc( + (*mb_ptr2char)(ptr) + ) && ptr2cells(ptr) > 1) + curwin->w_cursor.coladd = 0; + } + + curwin->w_set_curswant = TRUE; + return OK; + } + + if (curwin->w_cursor.col == 0) + return FAIL; + + curwin->w_set_curswant = TRUE; + --curwin->w_cursor.col; + + /* if the character on the left of the current cursor is a multi-byte + * character, move to its first byte */ + if (has_mbyte) + mb_adjust_cursor(); + return OK; +} + +int cursor_up(n, upd_topline) +long n; +int upd_topline; /* When TRUE: update topline */ +{ + linenr_T lnum; + + if (n > 0) { + lnum = curwin->w_cursor.lnum; + /* This fails if the cursor is already in the first line or the count + * is larger than the line number and '-' is in 'cpoptions' */ + if (lnum <= 1 || (n >= lnum && vim_strchr(p_cpo, CPO_MINUS) != NULL)) + return FAIL; + if (n >= lnum) + lnum = 1; + else if (hasAnyFolding(curwin)) { + /* + * Count each sequence of folded lines as one logical line. + */ + /* go to the start of the current fold */ + (void)hasFolding(lnum, &lnum, NULL); + + while (n--) { + /* move up one line */ + --lnum; + if (lnum <= 1) + break; + /* If we entered a fold, move to the beginning, unless in + * Insert mode or when 'foldopen' contains "all": it will open + * in a moment. */ + if (n > 0 || !((State & INSERT) || (fdo_flags & FDO_ALL))) + (void)hasFolding(lnum, &lnum, NULL); + } + if (lnum < 1) + lnum = 1; + } else + lnum -= n; + curwin->w_cursor.lnum = lnum; + } + + /* try to advance to the column we want to be at */ + coladvance(curwin->w_curswant); + + if (upd_topline) + update_topline(); /* make sure curwin->w_topline is valid */ + + return OK; +} + +/* + * Cursor down a number of logical lines. + */ +int cursor_down(n, upd_topline) +long n; +int upd_topline; /* When TRUE: update topline */ +{ + linenr_T lnum; + + if (n > 0) { + lnum = curwin->w_cursor.lnum; + /* Move to last line of fold, will fail if it's the end-of-file. */ + (void)hasFolding(lnum, NULL, &lnum); + /* This fails if the cursor is already in the last line or would move + * beyond the last line and '-' is in 'cpoptions' */ + if (lnum >= curbuf->b_ml.ml_line_count + || (lnum + n > curbuf->b_ml.ml_line_count + && vim_strchr(p_cpo, CPO_MINUS) != NULL)) + return FAIL; + if (lnum + n >= curbuf->b_ml.ml_line_count) + lnum = curbuf->b_ml.ml_line_count; + else if (hasAnyFolding(curwin)) { + linenr_T last; + + /* count each sequence of folded lines as one logical line */ + while (n--) { + if (hasFolding(lnum, NULL, &last)) + lnum = last + 1; + else + ++lnum; + if (lnum >= curbuf->b_ml.ml_line_count) + break; + } + if (lnum > curbuf->b_ml.ml_line_count) + lnum = curbuf->b_ml.ml_line_count; + } else + lnum += n; + curwin->w_cursor.lnum = lnum; + } + + /* try to advance to the column we want to be at */ + coladvance(curwin->w_curswant); + + if (upd_topline) + update_topline(); /* make sure curwin->w_topline is valid */ + + return OK; +} + +/* + * Stuff the last inserted text in the read buffer. + * Last_insert actually is a copy of the redo buffer, so we + * first have to remove the command. + */ +int stuff_inserted(c, count, no_esc) +int c; /* Command character to be inserted */ +long count; /* Repeat this many times */ +int no_esc; /* Don't add an ESC at the end */ +{ + char_u *esc_ptr; + char_u *ptr; + char_u *last_ptr; + char_u last = NUL; + + ptr = get_last_insert(); + if (ptr == NULL) { + EMSG(_(e_noinstext)); + return FAIL; + } + + /* may want to stuff the command character, to start Insert mode */ + if (c != NUL) + stuffcharReadbuff(c); + if ((esc_ptr = (char_u *)vim_strrchr(ptr, ESC)) != NULL) + *esc_ptr = NUL; /* remove the ESC */ + + /* when the last char is either "0" or "^" it will be quoted if no ESC + * comes after it OR if it will inserted more than once and "ptr" + * starts with ^D. -- Acevedo + */ + last_ptr = (esc_ptr ? esc_ptr : ptr + STRLEN(ptr)) - 1; + if (last_ptr >= ptr && (*last_ptr == '0' || *last_ptr == '^') + && (no_esc || (*ptr == Ctrl_D && count > 1))) { + last = *last_ptr; + *last_ptr = NUL; + } + + do { + stuffReadbuff(ptr); + /* a trailing "0" is inserted as "048", "^" as "^" */ + if (last) + stuffReadbuff((char_u *)(last == '0' + ? IF_EB("\026\060\064\070", CTRL_V_STR "xf0") + : IF_EB("\026^", CTRL_V_STR "^"))); + } while (--count > 0); + + if (last) + *last_ptr = last; + + if (esc_ptr != NULL) + *esc_ptr = ESC; /* put the ESC back */ + + /* may want to stuff a trailing ESC, to get out of Insert mode */ + if (!no_esc) + stuffcharReadbuff(ESC); + + return OK; +} + +char_u * get_last_insert() { + if (last_insert == NULL) + return NULL; + return last_insert + last_insert_skip; +} + +/* + * Get last inserted string, and remove trailing . + * Returns pointer to allocated memory (must be freed) or NULL. + */ +char_u * get_last_insert_save() { + char_u *s; + int len; + + if (last_insert == NULL) + return NULL; + s = vim_strsave(last_insert + last_insert_skip); + if (s != NULL) { + len = (int)STRLEN(s); + if (len > 0 && s[len - 1] == ESC) /* remove trailing ESC */ + s[len - 1] = NUL; + } + return s; +} + +/* + * Check the word in front of the cursor for an abbreviation. + * Called when the non-id character "c" has been entered. + * When an abbreviation is recognized it is removed from the text and + * the replacement string is inserted in typebuf.tb_buf[], followed by "c". + */ +static int echeck_abbr(c) +int c; +{ + /* Don't check for abbreviation in paste mode, when disabled and just + * after moving around with cursor keys. */ + if (p_paste || no_abbr || arrow_used) + return FALSE; + + return check_abbr(c, ml_get_curline(), curwin->w_cursor.col, + curwin->w_cursor.lnum == Insstart.lnum ? Insstart.col : 0); +} + +/* + * replace-stack functions + * + * When replacing characters, the replaced characters are remembered for each + * new character. This is used to re-insert the old text when backspacing. + * + * There is a NUL headed list of characters for each character that is + * currently in the file after the insertion point. When BS is used, one NUL + * headed list is put back for the deleted character. + * + * For a newline, there are two NUL headed lists. One contains the characters + * that the NL replaced. The extra one stores the characters after the cursor + * that were deleted (always white space). + * + * Replace_offset is normally 0, in which case replace_push will add a new + * character at the end of the stack. If replace_offset is not 0, that many + * characters will be left on the stack above the newly inserted character. + */ + +static char_u *replace_stack = NULL; +static long replace_stack_nr = 0; /* next entry in replace stack */ +static long replace_stack_len = 0; /* max. number of entries */ + +void replace_push(c) +int c; /* character that is replaced (NUL is none) */ +{ + char_u *p; + + if (replace_stack_nr < replace_offset) /* nothing to do */ + return; + if (replace_stack_len <= replace_stack_nr) { + replace_stack_len += 50; + p = lalloc(sizeof(char_u) * replace_stack_len, TRUE); + if (p == NULL) { /* out of memory */ + replace_stack_len -= 50; + return; + } + if (replace_stack != NULL) { + mch_memmove(p, replace_stack, + (size_t)(replace_stack_nr * sizeof(char_u))); + vim_free(replace_stack); + } + replace_stack = p; + } + p = replace_stack + replace_stack_nr - replace_offset; + if (replace_offset) + mch_memmove(p + 1, p, (size_t)(replace_offset * sizeof(char_u))); + *p = c; + ++replace_stack_nr; +} + +/* + * Push a character onto the replace stack. Handles a multi-byte character in + * reverse byte order, so that the first byte is popped off first. + * Return the number of bytes done (includes composing characters). + */ +int replace_push_mb(p) +char_u *p; +{ + int l = (*mb_ptr2len)(p); + int j; + + for (j = l - 1; j >= 0; --j) + replace_push(p[j]); + return l; +} + +/* + * Pop one item from the replace stack. + * return -1 if stack empty + * return replaced character or NUL otherwise + */ +static int replace_pop() { + if (replace_stack_nr == 0) + return -1; + return (int)replace_stack[--replace_stack_nr]; +} + +/* + * Join the top two items on the replace stack. This removes to "off"'th NUL + * encountered. + */ +static void replace_join(off) +int off; /* offset for which NUL to remove */ +{ + int i; + + for (i = replace_stack_nr; --i >= 0; ) + if (replace_stack[i] == NUL && off-- <= 0) { + --replace_stack_nr; + mch_memmove(replace_stack + i, replace_stack + i + 1, + (size_t)(replace_stack_nr - i)); + return; + } +} + +/* + * Pop bytes from the replace stack until a NUL is found, and insert them + * before the cursor. Can only be used in REPLACE or VREPLACE mode. + */ +static void replace_pop_ins() { + int cc; + int oldState = State; + + State = NORMAL; /* don't want REPLACE here */ + while ((cc = replace_pop()) > 0) { + mb_replace_pop_ins(cc); + dec_cursor(); + } + State = oldState; +} + +/* + * Insert bytes popped from the replace stack. "cc" is the first byte. If it + * indicates a multi-byte char, pop the other bytes too. + */ +static void mb_replace_pop_ins(cc) +int cc; +{ + int n; + char_u buf[MB_MAXBYTES + 1]; + int i; + int c; + + if (has_mbyte && (n = MB_BYTE2LEN(cc)) > 1) { + buf[0] = cc; + for (i = 1; i < n; ++i) + buf[i] = replace_pop(); + ins_bytes_len(buf, n); + } else + ins_char(cc); + + if (enc_utf8) + /* Handle composing chars. */ + for (;; ) { + c = replace_pop(); + if (c == -1) /* stack empty */ + break; + if ((n = MB_BYTE2LEN(c)) == 1) { + /* Not a multi-byte char, put it back. */ + replace_push(c); + break; + } else { + buf[0] = c; + for (i = 1; i < n; ++i) + buf[i] = replace_pop(); + if (utf_iscomposing(utf_ptr2char(buf))) + ins_bytes_len(buf, n); + else { + /* Not a composing char, put it back. */ + for (i = n - 1; i >= 0; --i) + replace_push(buf[i]); + break; + } + } + } +} + +/* + * make the replace stack empty + * (called when exiting replace mode) + */ +static void replace_flush() { + vim_free(replace_stack); + replace_stack = NULL; + replace_stack_len = 0; + replace_stack_nr = 0; +} + +/* + * Handle doing a BS for one character. + * cc < 0: replace stack empty, just move cursor + * cc == 0: character was inserted, delete it + * cc > 0: character was replaced, put cc (first byte of original char) back + * and check for more characters to be put back + * When "limit_col" is >= 0, don't delete before this column. Matters when + * using composing characters, use del_char_after_col() instead of del_char(). + */ +static void replace_do_bs(limit_col) +int limit_col; +{ + int cc; + int orig_len = 0; + int ins_len; + int orig_vcols = 0; + colnr_T start_vcol; + char_u *p; + int i; + int vcol; + + cc = replace_pop(); + if (cc > 0) { + if (State & VREPLACE_FLAG) { + /* Get the number of screen cells used by the character we are + * going to delete. */ + getvcol(curwin, &curwin->w_cursor, NULL, &start_vcol, NULL); + orig_vcols = chartabsize(ml_get_cursor(), start_vcol); + } + if (has_mbyte) { + (void)del_char_after_col(limit_col); + if (State & VREPLACE_FLAG) + orig_len = (int)STRLEN(ml_get_cursor()); + replace_push(cc); + } else { + pchar_cursor(cc); + if (State & VREPLACE_FLAG) + orig_len = (int)STRLEN(ml_get_cursor()) - 1; + } + replace_pop_ins(); + + if (State & VREPLACE_FLAG) { + /* Get the number of screen cells used by the inserted characters */ + p = ml_get_cursor(); + ins_len = (int)STRLEN(p) - orig_len; + vcol = start_vcol; + for (i = 0; i < ins_len; ++i) { + vcol += chartabsize(p + i, vcol); + i += (*mb_ptr2len)(p) - 1; + } + vcol -= start_vcol; + + /* Delete spaces that were inserted after the cursor to keep the + * text aligned. */ + curwin->w_cursor.col += ins_len; + while (vcol > orig_vcols && gchar_cursor() == ' ') { + del_char(FALSE); + ++orig_vcols; + } + curwin->w_cursor.col -= ins_len; + } + + /* mark the buffer as changed and prepare for displaying */ + changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); + } else if (cc == 0) + (void)del_char_after_col(limit_col); +} + +/* + * Return TRUE if C-indenting is on. + */ +static int cindent_on() { + return !p_paste && (curbuf->b_p_cin + || *curbuf->b_p_inde != NUL + ); +} + +/* + * Re-indent the current line, based on the current contents of it and the + * surrounding lines. Fixing the cursor position seems really easy -- I'm very + * confused what all the part that handles Control-T is doing that I'm not. + * "get_the_indent" should be get_c_indent, get_expr_indent or get_lisp_indent. + */ + +void fixthisline(get_the_indent) +int (*get_the_indent)__ARGS((void)); +{ + change_indent(INDENT_SET, get_the_indent(), FALSE, 0, TRUE); + if (linewhite(curwin->w_cursor.lnum)) + did_ai = TRUE; /* delete the indent if the line stays empty */ +} + +void fix_indent() { + if (p_paste) + return; + if (curbuf->b_p_lisp && curbuf->b_p_ai) + fixthisline(get_lisp_indent); + else if (cindent_on()) + do_c_expr_indent(); +} + +/* + * return TRUE if 'cinkeys' contains the key "keytyped", + * when == '*': Only if key is preceded with '*' (indent before insert) + * when == '!': Only if key is preceded with '!' (don't insert) + * when == ' ': Only if key is not preceded with '*'(indent afterwards) + * + * "keytyped" can have a few special values: + * KEY_OPEN_FORW + * KEY_OPEN_BACK + * KEY_COMPLETE just finished completion. + * + * If line_is_empty is TRUE accept keys with '0' before them. + */ +int in_cinkeys(keytyped, when, line_is_empty) +int keytyped; +int when; +int line_is_empty; +{ + char_u *look; + int try_match; + int try_match_word; + char_u *p; + char_u *line; + int icase; + int i; + + if (keytyped == NUL) + /* Can happen with CTRL-Y and CTRL-E on a short line. */ + return FALSE; + + if (*curbuf->b_p_inde != NUL) + look = curbuf->b_p_indk; /* 'indentexpr' set: use 'indentkeys' */ + else + look = curbuf->b_p_cink; /* 'indentexpr' empty: use 'cinkeys' */ + while (*look) { + /* + * Find out if we want to try a match with this key, depending on + * 'when' and a '*' or '!' before the key. + */ + switch (when) { + case '*': try_match = (*look == '*'); break; + case '!': try_match = (*look == '!'); break; + default: try_match = (*look != '*'); break; + } + if (*look == '*' || *look == '!') + ++look; + + /* + * If there is a '0', only accept a match if the line is empty. + * But may still match when typing last char of a word. + */ + if (*look == '0') { + try_match_word = try_match; + if (!line_is_empty) + try_match = FALSE; + ++look; + } else + try_match_word = FALSE; + + /* + * does it look like a control character? + */ + if (*look == '^' + && look[1] >= '?' && look[1] <= '_' + ) { + if (try_match && keytyped == Ctrl_chr(look[1])) + return TRUE; + look += 2; + } + /* + * 'o' means "o" command, open forward. + * 'O' means "O" command, open backward. + */ + else if (*look == 'o') { + if (try_match && keytyped == KEY_OPEN_FORW) + return TRUE; + ++look; + } else if (*look == 'O') { + if (try_match && keytyped == KEY_OPEN_BACK) + return TRUE; + ++look; + } + /* + * 'e' means to check for "else" at start of line and just before the + * cursor. + */ + else if (*look == 'e') { + if (try_match && keytyped == 'e' && curwin->w_cursor.col >= 4) { + p = ml_get_curline(); + if (skipwhite(p) == p + curwin->w_cursor.col - 4 && + STRNCMP(p + curwin->w_cursor.col - 4, "else", 4) == 0) + return TRUE; + } + ++look; + } + /* + * ':' only causes an indent if it is at the end of a label or case + * statement, or when it was before typing the ':' (to fix + * class::method for C++). + */ + else if (*look == ':') { + if (try_match && keytyped == ':') { + p = ml_get_curline(); + if (cin_iscase(p, FALSE) || cin_isscopedecl(p) || cin_islabel()) + return TRUE; + /* Need to get the line again after cin_islabel(). */ + p = ml_get_curline(); + if (curwin->w_cursor.col > 2 + && p[curwin->w_cursor.col - 1] == ':' + && p[curwin->w_cursor.col - 2] == ':') { + p[curwin->w_cursor.col - 1] = ' '; + i = (cin_iscase(p, FALSE) || cin_isscopedecl(p) + || cin_islabel()); + p = ml_get_curline(); + p[curwin->w_cursor.col - 1] = ':'; + if (i) + return TRUE; + } + } + ++look; + } + /* + * Is it a key in <>, maybe? + */ + else if (*look == '<') { + if (try_match) { + /* + * make up some named keys , , , <0>, <>>, <<>, <*>, + * <:> and so that people can re-indent on o, O, e, 0, <, + * >, *, : and ! keys if they really really want to. + */ + if (vim_strchr((char_u *)"<>!*oOe0:", look[1]) != NULL + && keytyped == look[1]) + return TRUE; + + if (keytyped == get_special_key_code(look + 1)) + return TRUE; + } + while (*look && *look != '>') + look++; + while (*look == '>') + look++; + } + /* + * Is it a word: "=word"? + */ + else if (*look == '=' && look[1] != ',' && look[1] != NUL) { + ++look; + if (*look == '~') { + icase = TRUE; + ++look; + } else + icase = FALSE; + p = vim_strchr(look, ','); + if (p == NULL) + p = look + STRLEN(look); + if ((try_match || try_match_word) + && curwin->w_cursor.col >= (colnr_T)(p - look)) { + int match = FALSE; + + if (keytyped == KEY_COMPLETE) { + char_u *s; + + /* Just completed a word, check if it starts with "look". + * search back for the start of a word. */ + line = ml_get_curline(); + if (has_mbyte) { + char_u *n; + + for (s = line + curwin->w_cursor.col; s > line; s = n) { + n = mb_prevptr(line, s); + if (!vim_iswordp(n)) + break; + } + } else + for (s = line + curwin->w_cursor.col; s > line; --s) + if (!vim_iswordc(s[-1])) + break; + if (s + (p - look) <= line + curwin->w_cursor.col + && (icase + ? MB_STRNICMP(s, look, p - look) + : STRNCMP(s, look, p - look)) == 0) + match = TRUE; + } else + /* TODO: multi-byte */ + if (keytyped == (int)p[-1] || (icase && keytyped < 256 + && TOLOWER_LOC(keytyped) == + TOLOWER_LOC((int)p[-1]))) { + line = ml_get_cursor(); + if ((curwin->w_cursor.col == (colnr_T)(p - look) + || !vim_iswordc(line[-(p - look) - 1])) + && (icase + ? MB_STRNICMP(line - (p - look), look, p - look) + : STRNCMP(line - (p - look), look, p - look)) + == 0) + match = TRUE; + } + if (match && try_match_word && !try_match) { + /* "0=word": Check if there are only blanks before the + * word. */ + line = ml_get_curline(); + if ((int)(skipwhite(line) - line) != + (int)(curwin->w_cursor.col - (p - look))) + match = FALSE; + } + if (match) + return TRUE; + } + look = p; + } + /* + * ok, it's a boring generic character. + */ + else { + if (try_match && *look == keytyped) + return TRUE; + ++look; + } + + /* + * Skip over ", ". + */ + look = skip_to_option_part(look); + } + return FALSE; +} + +/* + * Map Hebrew keyboard when in hkmap mode. + */ +int hkmap(c) +int c; +{ + if (p_hkmapp) { /* phonetic mapping, by Ilya Dogolazky */ + enum {hALEF=0, BET, GIMEL, DALET, HEI, VAV, ZAIN, HET, TET, IUD, + KAFsofit, hKAF, LAMED, MEMsofit, MEM, NUNsofit, NUN, SAMEH, AIN, + PEIsofit, PEI, ZADIsofit, ZADI, KOF, RESH, hSHIN, TAV}; + static char_u map[26] = + {(char_u)hALEF /*a*/, (char_u)BET /*b*/, (char_u)hKAF /*c*/, + (char_u)DALET /*d*/, (char_u)-1 /*e*/, (char_u)PEIsofit /*f*/, + (char_u)GIMEL /*g*/, (char_u)HEI /*h*/, (char_u)IUD /*i*/, + (char_u)HET /*j*/, (char_u)KOF /*k*/, (char_u)LAMED /*l*/, + (char_u)MEM /*m*/, (char_u)NUN /*n*/, (char_u)SAMEH /*o*/, + (char_u)PEI /*p*/, (char_u)-1 /*q*/, (char_u)RESH /*r*/, + (char_u)ZAIN /*s*/, (char_u)TAV /*t*/, (char_u)TET /*u*/, + (char_u)VAV /*v*/, (char_u)hSHIN /*w*/, (char_u)-1 /*x*/, + (char_u)AIN /*y*/, (char_u)ZADI /*z*/}; + + if (c == 'N' || c == 'M' || c == 'P' || c == 'C' || c == 'Z') + return (int)(map[CharOrd(c)] - 1 + p_aleph); + /* '-1'='sofit' */ + else if (c == 'x') + return 'X'; + else if (c == 'q') + return '\''; /* {geresh}={'} */ + else if (c == 246) + return ' '; /* \"o --> ' ' for a german keyboard */ + else if (c == 228) + return ' '; /* \"a --> ' ' -- / -- */ + else if (c == 252) + return ' '; /* \"u --> ' ' -- / -- */ + /* NOTE: islower() does not do the right thing for us on Linux so we + * do this the same was as 5.7 and previous, so it works correctly on + * all systems. Specifically, the e.g. Delete and Arrow keys are + * munged and won't work if e.g. searching for Hebrew text. + */ + else if (c >= 'a' && c <= 'z') + return (int)(map[CharOrdLow(c)] + p_aleph); + else + return c; + } else { + switch (c) { + case '`': return ';'; + case '/': return '.'; + case '\'': return ','; + case 'q': return '/'; + case 'w': return '\''; + + /* Hebrew letters - set offset from 'a' */ + case ',': c = '{'; break; + case '.': c = 'v'; break; + case ';': c = 't'; break; + default: { + static char str[] = "zqbcxlsjphmkwonu ydafe rig"; + + if (c < 'a' || c > 'z') + return c; + c = str[CharOrdLow(c)]; + break; + } + } + + return (int)(CharOrdLow(c) + p_aleph); + } +} + +static void ins_reg() { + int need_redraw = FALSE; + int regname; + int literally = 0; + int vis_active = VIsual_active; + + /* + * If we are going to wait for a character, show a '"'. + */ + pc_status = PC_STATUS_UNSET; + if (redrawing() && !char_avail()) { + /* may need to redraw when no more chars available now */ + ins_redraw(FALSE); + + edit_putchar('"', TRUE); + add_to_showcmd_c(Ctrl_R); + } + +#ifdef USE_ON_FLY_SCROLL + dont_scroll = TRUE; /* disallow scrolling here */ +#endif + + /* + * Don't map the register name. This also prevents the mode message to be + * deleted when ESC is hit. + */ + ++no_mapping; + regname = plain_vgetc(); + LANGMAP_ADJUST(regname, TRUE); + if (regname == Ctrl_R || regname == Ctrl_O || regname == Ctrl_P) { + /* Get a third key for literal register insertion */ + literally = regname; + add_to_showcmd_c(literally); + regname = plain_vgetc(); + LANGMAP_ADJUST(regname, TRUE); + } + --no_mapping; + + /* Don't call u_sync() while typing the expression or giving an error + * message for it. Only call it explicitly. */ + ++no_u_sync; + if (regname == '=') { +# ifdef USE_IM_CONTROL + int im_on = im_get_status(); +# endif + /* Sync undo when evaluating the expression calls setline() or + * append(), so that it can be undone separately. */ + u_sync_once = 2; + + regname = get_expr_register(); +# ifdef USE_IM_CONTROL + /* Restore the Input Method. */ + if (im_on) + im_set_active(TRUE); +# endif + } + if (regname == NUL || !valid_yank_reg(regname, FALSE)) { + vim_beep(); + need_redraw = TRUE; /* remove the '"' */ + } else { + if (literally == Ctrl_O || literally == Ctrl_P) { + /* Append the command to the redo buffer. */ + AppendCharToRedobuff(Ctrl_R); + AppendCharToRedobuff(literally); + AppendCharToRedobuff(regname); + + do_put(regname, BACKWARD, 1L, + (literally == Ctrl_P ? PUT_FIXINDENT : 0) | PUT_CURSEND); + } else if (insert_reg(regname, literally) == FAIL) { + vim_beep(); + need_redraw = TRUE; /* remove the '"' */ + } else if (stop_insert_mode) + /* When the '=' register was used and a function was invoked that + * did ":stopinsert" then stuff_empty() returns FALSE but we won't + * insert anything, need to remove the '"' */ + need_redraw = TRUE; + + } + --no_u_sync; + if (u_sync_once == 1) + ins_need_undo = TRUE; + u_sync_once = 0; + clear_showcmd(); + + /* If the inserted register is empty, we need to remove the '"' */ + if (need_redraw || stuff_empty()) + edit_unputchar(); + + /* Disallow starting Visual mode here, would get a weird mode. */ + if (!vis_active && VIsual_active) + end_visual_mode(); +} + +/* + * CTRL-G commands in Insert mode. + */ +static void ins_ctrl_g() { + int c; + + /* Right after CTRL-X the cursor will be after the ruler. */ + setcursor(); + + /* + * Don't map the second key. This also prevents the mode message to be + * deleted when ESC is hit. + */ + ++no_mapping; + c = plain_vgetc(); + --no_mapping; + switch (c) { + /* CTRL-G k and CTRL-G : cursor up to Insstart.col */ + case K_UP: + case Ctrl_K: + case 'k': ins_up(TRUE); + break; + + /* CTRL-G j and CTRL-G : cursor down to Insstart.col */ + case K_DOWN: + case Ctrl_J: + case 'j': ins_down(TRUE); + break; + + /* CTRL-G u: start new undoable edit */ + case 'u': u_sync(TRUE); + ins_need_undo = TRUE; + + /* Need to reset Insstart, esp. because a BS that joins + * a line to the previous one must save for undo. */ + Insstart = curwin->w_cursor; + break; + + /* Unknown CTRL-G command, reserved for future expansion. */ + default: vim_beep(); + } +} + +/* + * CTRL-^ in Insert mode. + */ +static void ins_ctrl_hat() { + if (map_to_exists_mode((char_u *)"", LANGMAP, FALSE)) { + /* ":lmap" mappings exists, Toggle use of ":lmap" mappings. */ + if (State & LANGMAP) { + curbuf->b_p_iminsert = B_IMODE_NONE; + State &= ~LANGMAP; + } else { + curbuf->b_p_iminsert = B_IMODE_LMAP; + State |= LANGMAP; +#ifdef USE_IM_CONTROL + im_set_active(FALSE); +#endif + } + } +#ifdef USE_IM_CONTROL + else { + /* There are no ":lmap" mappings, toggle IM */ + if (im_get_status()) { + curbuf->b_p_iminsert = B_IMODE_NONE; + im_set_active(FALSE); + } else { + curbuf->b_p_iminsert = B_IMODE_IM; + State &= ~LANGMAP; + im_set_active(TRUE); + } + } +#endif + set_iminsert_global(); + showmode(); + /* Show/unshow value of 'keymap' in status lines. */ + status_redraw_curbuf(); +} + +/* + * Handle ESC in insert mode. + * Returns TRUE when leaving insert mode, FALSE when going to repeat the + * insert. + */ +static int ins_esc(count, cmdchar, nomove) +long *count; +int cmdchar; +int nomove; /* don't move cursor */ +{ + int temp; + static int disabled_redraw = FALSE; + + check_spell_redraw(); +# if defined(ESC_CHG_TO_ENG_MODE) + hangul_input_state_set(0); +# endif + if (composing_hangul) { + push_raw_key(composing_hangul_buffer, 2); + composing_hangul = 0; + } + + temp = curwin->w_cursor.col; + if (disabled_redraw) { + --RedrawingDisabled; + disabled_redraw = FALSE; + } + if (!arrow_used) { + /* + * Don't append the ESC for "r" and "grx". + * When 'insertmode' is set only CTRL-L stops Insert mode. Needed for + * when "count" is non-zero. + */ + if (cmdchar != 'r' && cmdchar != 'v') + AppendToRedobuff(p_im ? (char_u *)"\014" : ESC_STR); + + /* + * Repeating insert may take a long time. Check for + * interrupt now and then. + */ + if (*count > 0) { + line_breakcheck(); + if (got_int) + *count = 0; + } + + if (--*count > 0) { /* repeat what was typed */ + /* Vi repeats the insert without replacing characters. */ + if (vim_strchr(p_cpo, CPO_REPLCNT) != NULL) + State &= ~REPLACE_FLAG; + + (void)start_redo_ins(); + if (cmdchar == 'r' || cmdchar == 'v') + stuffReadbuff(ESC_STR); /* no ESC in redo buffer */ + ++RedrawingDisabled; + disabled_redraw = TRUE; + return FALSE; /* repeat the insert */ + } + stop_insert(&curwin->w_cursor, TRUE, nomove); + undisplay_dollar(); + } + + /* When an autoindent was removed, curswant stays after the + * indent */ + if (restart_edit == NUL && (colnr_T)temp == curwin->w_cursor.col) + curwin->w_set_curswant = TRUE; + + /* Remember the last Insert position in the '^ mark. */ + if (!cmdmod.keepjumps) + curbuf->b_last_insert = curwin->w_cursor; + + /* + * The cursor should end up on the last inserted character. + * Don't do it for CTRL-O, unless past the end of the line. + */ + if (!nomove + && (curwin->w_cursor.col != 0 + || curwin->w_cursor.coladd > 0 + ) + && (restart_edit == NUL + || (gchar_cursor() == NUL + && !VIsual_active + )) + && !revins_on + ) { + if (curwin->w_cursor.coladd > 0 || ve_flags == VE_ALL) { + oneleft(); + if (restart_edit != NUL) + ++curwin->w_cursor.coladd; + } else { + --curwin->w_cursor.col; + /* Correct cursor for multi-byte character. */ + if (has_mbyte) + mb_adjust_cursor(); + } + } + +#ifdef USE_IM_CONTROL + /* Disable IM to allow typing English directly for Normal mode commands. + * When ":lmap" is enabled don't change 'iminsert' (IM can be enabled as + * well). */ + if (!(State & LANGMAP)) + im_save_status(&curbuf->b_p_iminsert); + im_set_active(FALSE); +#endif + + State = NORMAL; + /* need to position cursor again (e.g. when on a TAB ) */ + changed_cline_bef_curs(); + + setmouse(); +#ifdef CURSOR_SHAPE + ui_cursor_shape(); /* may show different cursor shape */ +#endif + + /* + * When recording or for CTRL-O, need to display the new mode. + * Otherwise remove the mode message. + */ + if (Recording || restart_edit != NUL) + showmode(); + else if (p_smd) + MSG(""); + + return TRUE; /* exit Insert mode */ +} + +/* + * Toggle language: hkmap and revins_on. + * Move to end of reverse inserted text. + */ +static void ins_ctrl_() { + if (revins_on && revins_chars && revins_scol >= 0) { + while (gchar_cursor() != NUL && revins_chars--) + ++curwin->w_cursor.col; + } + p_ri = !p_ri; + revins_on = (State == INSERT && p_ri); + if (revins_on) { + revins_scol = curwin->w_cursor.col; + revins_legal++; + revins_chars = 0; + undisplay_dollar(); + } else + revins_scol = -1; + if (p_altkeymap) { + /* + * to be consistent also for redo command, using '.' + * set arrow_used to true and stop it - causing to redo + * characters entered in one mode (normal/reverse insert). + */ + arrow_used = TRUE; + (void)stop_arrow(); + p_fkmap = curwin->w_p_rl ^ p_ri; + if (p_fkmap && p_ri) + State = INSERT; + } else + p_hkmap = curwin->w_p_rl ^ p_ri; /* be consistent! */ + showmode(); +} + +/* + * If 'keymodel' contains "startsel", may start selection. + * Returns TRUE when a CTRL-O and other keys stuffed. + */ +static int ins_start_select(c) +int c; +{ + if (km_startsel) + switch (c) { + case K_KHOME: + case K_KEND: + case K_PAGEUP: + case K_KPAGEUP: + case K_PAGEDOWN: + case K_KPAGEDOWN: + if (!(mod_mask & MOD_MASK_SHIFT)) + break; + /* FALLTHROUGH */ + case K_S_LEFT: + case K_S_RIGHT: + case K_S_UP: + case K_S_DOWN: + case K_S_END: + case K_S_HOME: + /* Start selection right away, the cursor can move with + * CTRL-O when beyond the end of the line. */ + start_selection(); + + /* Execute the key in (insert) Select mode. */ + stuffcharReadbuff(Ctrl_O); + if (mod_mask) { + char_u buf[4]; + + buf[0] = K_SPECIAL; + buf[1] = KS_MODIFIER; + buf[2] = mod_mask; + buf[3] = NUL; + stuffReadbuff(buf); + } + stuffcharReadbuff(c); + return TRUE; + } + return FALSE; +} + +/* + * key in Insert mode: toggle insert/replace mode. + */ +static void ins_insert(replaceState) +int replaceState; +{ + if (p_fkmap && p_ri) { + beep_flush(); + EMSG(farsi_text_3); /* encoded in Farsi */ + return; + } + + set_vim_var_string(VV_INSERTMODE, + (char_u *)((State & REPLACE_FLAG) ? "i" : + replaceState == VREPLACE ? "v" : + "r"), 1); + apply_autocmds(EVENT_INSERTCHANGE, NULL, NULL, FALSE, curbuf); + if (State & REPLACE_FLAG) + State = INSERT | (State & LANGMAP); + else + State = replaceState | (State & LANGMAP); + AppendCharToRedobuff(K_INS); + showmode(); +#ifdef CURSOR_SHAPE + ui_cursor_shape(); /* may show different cursor shape */ +#endif +} + +/* + * Pressed CTRL-O in Insert mode. + */ +static void ins_ctrl_o() { + if (State & VREPLACE_FLAG) + restart_edit = 'V'; + else if (State & REPLACE_FLAG) + restart_edit = 'R'; + else + restart_edit = 'I'; + if (virtual_active()) + ins_at_eol = FALSE; /* cursor always keeps its column */ + else + ins_at_eol = (gchar_cursor() == NUL); +} + +/* + * If the cursor is on an indent, ^T/^D insert/delete one + * shiftwidth. Otherwise ^T/^D behave like a "<<" or ">>". + * Always round the indent to 'shiftwidth', this is compatible + * with vi. But vi only supports ^T and ^D after an + * autoindent, we support it everywhere. + */ +static void ins_shift(c, lastc) +int c; +int lastc; +{ + if (stop_arrow() == FAIL) + return; + AppendCharToRedobuff(c); + + /* + * 0^D and ^^D: remove all indent. + */ + if (c == Ctrl_D && (lastc == '0' || lastc == '^') + && curwin->w_cursor.col > 0) { + --curwin->w_cursor.col; + (void)del_char(FALSE); /* delete the '^' or '0' */ + /* In Replace mode, restore the characters that '^' or '0' replaced. */ + if (State & REPLACE_FLAG) + replace_pop_ins(); + if (lastc == '^') + old_indent = get_indent(); /* remember curr. indent */ + change_indent(INDENT_SET, 0, TRUE, 0, TRUE); + } else + change_indent(c == Ctrl_D ? INDENT_DEC : INDENT_INC, 0, TRUE, 0, TRUE); + + if (did_ai && *skipwhite(ml_get_curline()) != NUL) + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + can_cindent = FALSE; /* no cindenting after ^D or ^T */ +} + +static void ins_del() { + int temp; + + if (stop_arrow() == FAIL) + return; + if (gchar_cursor() == NUL) { /* delete newline */ + temp = curwin->w_cursor.col; + if (!can_bs(BS_EOL) /* only if "eol" included */ + || do_join(2, FALSE, TRUE, FALSE) == FAIL) + vim_beep(); + else + curwin->w_cursor.col = temp; + } else if (del_char(FALSE) == FAIL) /* delete char under cursor */ + vim_beep(); + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + AppendCharToRedobuff(K_DEL); +} + +static void ins_bs_one __ARGS((colnr_T *vcolp)); + +/* + * Delete one character for ins_bs(). + */ +static void ins_bs_one(vcolp) +colnr_T *vcolp; +{ + dec_cursor(); + getvcol(curwin, &curwin->w_cursor, vcolp, NULL, NULL); + if (State & REPLACE_FLAG) { + /* Don't delete characters before the insert point when in + * Replace mode */ + if (curwin->w_cursor.lnum != Insstart.lnum + || curwin->w_cursor.col >= Insstart.col) + replace_do_bs(-1); + } else + (void)del_char(FALSE); +} + +/* + * Handle Backspace, delete-word and delete-line in Insert mode. + * Return TRUE when backspace was actually used. + */ +static int ins_bs(c, mode, inserted_space_p) +int c; +int mode; +int *inserted_space_p; +{ + linenr_T lnum; + int cc; + int temp = 0; /* init for GCC */ + colnr_T save_col; + colnr_T mincol; + int did_backspace = FALSE; + int in_indent; + int oldState; + int cpc[MAX_MCO]; /* composing characters */ + + /* + * can't delete anything in an empty file + * can't backup past first character in buffer + * can't backup past starting point unless 'backspace' > 1 + * can backup to a previous line if 'backspace' == 0 + */ + if ( bufempty() + || ( + !revins_on && + ((curwin->w_cursor.lnum == 1 && curwin->w_cursor.col == 0) + || (!can_bs(BS_START) + && (arrow_used + || (curwin->w_cursor.lnum == Insstart.lnum + && curwin->w_cursor.col <= Insstart.col))) + || (!can_bs(BS_INDENT) && !arrow_used && ai_col > 0 + && curwin->w_cursor.col <= ai_col) + || (!can_bs(BS_EOL) && curwin->w_cursor.col == 0)))) { + vim_beep(); + return FALSE; + } + + if (stop_arrow() == FAIL) + return FALSE; + in_indent = inindent(0); + if (in_indent) + can_cindent = FALSE; + end_comment_pending = NUL; /* After BS, don't auto-end comment */ + if (revins_on) /* put cursor after last inserted char */ + inc_cursor(); + + /* Virtualedit: + * BACKSPACE_CHAR eats a virtual space + * BACKSPACE_WORD eats all coladd + * BACKSPACE_LINE eats all coladd and keeps going + */ + if (curwin->w_cursor.coladd > 0) { + if (mode == BACKSPACE_CHAR) { + --curwin->w_cursor.coladd; + return TRUE; + } + if (mode == BACKSPACE_WORD) { + curwin->w_cursor.coladd = 0; + return TRUE; + } + curwin->w_cursor.coladd = 0; + } + + /* + * delete newline! + */ + if (curwin->w_cursor.col == 0) { + lnum = Insstart.lnum; + if (curwin->w_cursor.lnum == Insstart.lnum + || revins_on + ) { + if (u_save((linenr_T)(curwin->w_cursor.lnum - 2), + (linenr_T)(curwin->w_cursor.lnum + 1)) == FAIL) + return FALSE; + --Insstart.lnum; + Insstart.col = MAXCOL; + } + /* + * In replace mode: + * cc < 0: NL was inserted, delete it + * cc >= 0: NL was replaced, put original characters back + */ + cc = -1; + if (State & REPLACE_FLAG) + cc = replace_pop(); /* returns -1 if NL was inserted */ + /* + * In replace mode, in the line we started replacing, we only move the + * cursor. + */ + if ((State & REPLACE_FLAG) && curwin->w_cursor.lnum <= lnum) { + dec_cursor(); + } else { + if (!(State & VREPLACE_FLAG) + || curwin->w_cursor.lnum > orig_line_count) { + temp = gchar_cursor(); /* remember current char */ + --curwin->w_cursor.lnum; + + /* When "aw" is in 'formatoptions' we must delete the space at + * the end of the line, otherwise the line will be broken + * again when auto-formatting. */ + if (has_format_option(FO_AUTO) + && has_format_option(FO_WHITE_PAR)) { + char_u *ptr = ml_get_buf(curbuf, curwin->w_cursor.lnum, + TRUE); + int len; + + len = (int)STRLEN(ptr); + if (len > 0 && ptr[len - 1] == ' ') + ptr[len - 1] = NUL; + } + + (void)do_join(2, FALSE, FALSE, FALSE); + if (temp == NUL && gchar_cursor() != NUL) + inc_cursor(); + } else + dec_cursor(); + + /* + * In REPLACE mode we have to put back the text that was replaced + * by the NL. On the replace stack is first a NUL-terminated + * sequence of characters that were deleted and then the + * characters that NL replaced. + */ + if (State & REPLACE_FLAG) { + /* + * Do the next ins_char() in NORMAL state, to + * prevent ins_char() from replacing characters and + * avoiding showmatch(). + */ + oldState = State; + State = NORMAL; + /* + * restore characters (blanks) deleted after cursor + */ + while (cc > 0) { + save_col = curwin->w_cursor.col; + mb_replace_pop_ins(cc); + curwin->w_cursor.col = save_col; + cc = replace_pop(); + } + /* restore the characters that NL replaced */ + replace_pop_ins(); + State = oldState; + } + } + did_ai = FALSE; + } else { + /* + * Delete character(s) before the cursor. + */ + if (revins_on) /* put cursor on last inserted char */ + dec_cursor(); + mincol = 0; + /* keep indent */ + if (mode == BACKSPACE_LINE + && (curbuf->b_p_ai + || cindent_on() + ) + && !revins_on + ) { + save_col = curwin->w_cursor.col; + beginline(BL_WHITE); + if (curwin->w_cursor.col < save_col) + mincol = curwin->w_cursor.col; + curwin->w_cursor.col = save_col; + } + + /* + * Handle deleting one 'shiftwidth' or 'softtabstop'. + */ + if ( mode == BACKSPACE_CHAR + && ((p_sta && in_indent) + || (get_sts_value() != 0 + && curwin->w_cursor.col > 0 + && (*(ml_get_cursor() - 1) == TAB + || (*(ml_get_cursor() - 1) == ' ' + && (!*inserted_space_p + || arrow_used)))))) { + int ts; + colnr_T vcol; + colnr_T want_vcol; + colnr_T start_vcol; + + *inserted_space_p = FALSE; + if (p_sta && in_indent) + ts = (int)get_sw_value(curbuf); + else + ts = (int)get_sts_value(); + /* Compute the virtual column where we want to be. Since + * 'showbreak' may get in the way, need to get the last column of + * the previous character. */ + getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL); + start_vcol = vcol; + dec_cursor(); + getvcol(curwin, &curwin->w_cursor, NULL, NULL, &want_vcol); + inc_cursor(); + want_vcol = (want_vcol / ts) * ts; + + /* delete characters until we are at or before want_vcol */ + while (vcol > want_vcol + && (cc = *(ml_get_cursor() - 1), vim_iswhite(cc))) + ins_bs_one(&vcol); + + /* insert extra spaces until we are at want_vcol */ + while (vcol < want_vcol) { + /* Remember the first char we inserted */ + if (curwin->w_cursor.lnum == Insstart.lnum + && curwin->w_cursor.col < Insstart.col) + Insstart.col = curwin->w_cursor.col; + + if (State & VREPLACE_FLAG) + ins_char(' '); + else { + ins_str((char_u *)" "); + if ((State & REPLACE_FLAG)) + replace_push(NUL); + } + getvcol(curwin, &curwin->w_cursor, &vcol, NULL, NULL); + } + + /* If we are now back where we started delete one character. Can + * happen when using 'sts' and 'linebreak'. */ + if (vcol >= start_vcol) + ins_bs_one(&vcol); + } + /* + * Delete upto starting point, start of line or previous word. + */ + else do { + if (!revins_on) /* put cursor on char to be deleted */ + dec_cursor(); + + /* start of word? */ + if (mode == BACKSPACE_WORD && !vim_isspace(gchar_cursor())) { + mode = BACKSPACE_WORD_NOT_SPACE; + temp = vim_iswordc(gchar_cursor()); + } + /* end of word? */ + else if (mode == BACKSPACE_WORD_NOT_SPACE + && (vim_isspace(cc = gchar_cursor()) + || vim_iswordc(cc) != temp)) { + if (!revins_on) + inc_cursor(); + else if (State & REPLACE_FLAG) + dec_cursor(); + break; + } + if (State & REPLACE_FLAG) + replace_do_bs(-1); + else { + if (enc_utf8 && p_deco) + (void)utfc_ptr2char(ml_get_cursor(), cpc); + (void)del_char(FALSE); + /* + * If there are combining characters and 'delcombine' is set + * move the cursor back. Don't back up before the base + * character. + */ + if (enc_utf8 && p_deco && cpc[0] != NUL) + inc_cursor(); + if (revins_chars) { + revins_chars--; + revins_legal++; + } + if (revins_on && gchar_cursor() == NUL) + break; + } + /* Just a single backspace?: */ + if (mode == BACKSPACE_CHAR) + break; + } while ( + revins_on || + (curwin->w_cursor.col > mincol + && (curwin->w_cursor.lnum != Insstart.lnum + || curwin->w_cursor.col != Insstart.col))); + did_backspace = TRUE; + } + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + if (curwin->w_cursor.col <= 1) + did_ai = FALSE; + /* + * It's a little strange to put backspaces into the redo + * buffer, but it makes auto-indent a lot easier to deal + * with. + */ + AppendCharToRedobuff(c); + + /* If deleted before the insertion point, adjust it */ + if (curwin->w_cursor.lnum == Insstart.lnum + && curwin->w_cursor.col < Insstart.col) + Insstart.col = curwin->w_cursor.col; + + /* vi behaviour: the cursor moves backward but the character that + * was there remains visible + * Vim behaviour: the cursor moves backward and the character that + * was there is erased from the screen. + * We can emulate the vi behaviour by pretending there is a dollar + * displayed even when there isn't. + * --pkv Sun Jan 19 01:56:40 EST 2003 */ + if (vim_strchr(p_cpo, CPO_BACKSPACE) != NULL && dollar_vcol == -1) + dollar_vcol = curwin->w_virtcol; + + /* When deleting a char the cursor line must never be in a closed fold. + * E.g., when 'foldmethod' is indent and deleting the first non-white + * char before a Tab. */ + if (did_backspace) + foldOpenCursor(); + + return did_backspace; +} + +static void ins_mouse(c) +int c; +{ + pos_T tpos; + win_T *old_curwin = curwin; + + if (!mouse_has(MOUSE_INSERT)) + return; + + undisplay_dollar(); + tpos = curwin->w_cursor; + if (do_mouse(NULL, c, BACKWARD, 1L, 0)) { + win_T *new_curwin = curwin; + + if (curwin != old_curwin && win_valid(old_curwin)) { + /* Mouse took us to another window. We need to go back to the + * previous one to stop insert there properly. */ + curwin = old_curwin; + curbuf = curwin->w_buffer; + } + start_arrow(curwin == old_curwin ? &tpos : NULL); + if (curwin != new_curwin && win_valid(new_curwin)) { + curwin = new_curwin; + curbuf = curwin->w_buffer; + } + can_cindent = TRUE; + } + + /* redraw status lines (in case another window became active) */ + redraw_statuslines(); +} + +static void ins_mousescroll(dir) +int dir; +{ + pos_T tpos; + win_T *old_curwin = curwin; + int did_scroll = FALSE; + + tpos = curwin->w_cursor; + + if (mouse_row >= 0 && mouse_col >= 0) { + int row, col; + + row = mouse_row; + col = mouse_col; + + /* find the window at the pointer coordinates */ + curwin = mouse_find_win(&row, &col); + curbuf = curwin->w_buffer; + } + if (curwin == old_curwin) + undisplay_dollar(); + + /* Don't scroll the window in which completion is being done. */ + if (!pum_visible() + || curwin != old_curwin + ) { + if (dir == MSCR_DOWN || dir == MSCR_UP) { + if (mod_mask & (MOD_MASK_SHIFT | MOD_MASK_CTRL)) + scroll_redraw(dir, + (long)(curwin->w_botline - curwin->w_topline)); + else + scroll_redraw(dir, 3L); + } + did_scroll = TRUE; + } + + curwin->w_redr_status = TRUE; + + curwin = old_curwin; + curbuf = curwin->w_buffer; + + /* The popup menu may overlay the window, need to redraw it. + * TODO: Would be more efficient to only redraw the windows that are + * overlapped by the popup menu. */ + if (pum_visible() && did_scroll) { + redraw_all_later(NOT_VALID); + ins_compl_show_pum(); + } + + if (!equalpos(curwin->w_cursor, tpos)) { + start_arrow(&tpos); + can_cindent = TRUE; + } +} + + + +static void ins_left() { + pos_T tpos; + + if ((fdo_flags & FDO_HOR) && KeyTyped) + foldOpenCursor(); + undisplay_dollar(); + tpos = curwin->w_cursor; + if (oneleft() == OK) { + start_arrow(&tpos); + /* If exit reversed string, position is fixed */ + if (revins_scol != -1 && (int)curwin->w_cursor.col >= revins_scol) + revins_legal++; + revins_chars++; + } + /* + * if 'whichwrap' set for cursor in insert mode may go to + * previous line + */ + else if (vim_strchr(p_ww, '[') != NULL && curwin->w_cursor.lnum > 1) { + start_arrow(&tpos); + --(curwin->w_cursor.lnum); + coladvance((colnr_T)MAXCOL); + curwin->w_set_curswant = TRUE; /* so we stay at the end */ + } else + vim_beep(); +} + +static void ins_home(c) +int c; +{ + pos_T tpos; + + if ((fdo_flags & FDO_HOR) && KeyTyped) + foldOpenCursor(); + undisplay_dollar(); + tpos = curwin->w_cursor; + if (c == K_C_HOME) + curwin->w_cursor.lnum = 1; + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + curwin->w_curswant = 0; + start_arrow(&tpos); +} + +static void ins_end(c) +int c; +{ + pos_T tpos; + + if ((fdo_flags & FDO_HOR) && KeyTyped) + foldOpenCursor(); + undisplay_dollar(); + tpos = curwin->w_cursor; + if (c == K_C_END) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + coladvance((colnr_T)MAXCOL); + curwin->w_curswant = MAXCOL; + + start_arrow(&tpos); +} + +static void ins_s_left() { + if ((fdo_flags & FDO_HOR) && KeyTyped) + foldOpenCursor(); + undisplay_dollar(); + if (curwin->w_cursor.lnum > 1 || curwin->w_cursor.col > 0) { + start_arrow(&curwin->w_cursor); + (void)bck_word(1L, FALSE, FALSE); + curwin->w_set_curswant = TRUE; + } else + vim_beep(); +} + +static void ins_right() { + if ((fdo_flags & FDO_HOR) && KeyTyped) + foldOpenCursor(); + undisplay_dollar(); + if (gchar_cursor() != NUL + || virtual_active() + ) { + start_arrow(&curwin->w_cursor); + curwin->w_set_curswant = TRUE; + if (virtual_active()) + oneright(); + else { + if (has_mbyte) + curwin->w_cursor.col += (*mb_ptr2len)(ml_get_cursor()); + else + ++curwin->w_cursor.col; + } + + revins_legal++; + if (revins_chars) + revins_chars--; + } + /* if 'whichwrap' set for cursor in insert mode, may move the + * cursor to the next line */ + else if (vim_strchr(p_ww, ']') != NULL + && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { + start_arrow(&curwin->w_cursor); + curwin->w_set_curswant = TRUE; + ++curwin->w_cursor.lnum; + curwin->w_cursor.col = 0; + } else + vim_beep(); +} + +static void ins_s_right() { + if ((fdo_flags & FDO_HOR) && KeyTyped) + foldOpenCursor(); + undisplay_dollar(); + if (curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count + || gchar_cursor() != NUL) { + start_arrow(&curwin->w_cursor); + (void)fwd_word(1L, FALSE, 0); + curwin->w_set_curswant = TRUE; + } else + vim_beep(); +} + +static void ins_up(startcol) +int startcol; /* when TRUE move to Insstart.col */ +{ + pos_T tpos; + linenr_T old_topline = curwin->w_topline; + int old_topfill = curwin->w_topfill; + + undisplay_dollar(); + tpos = curwin->w_cursor; + if (cursor_up(1L, TRUE) == OK) { + if (startcol) + coladvance(getvcol_nolist(&Insstart)); + if (old_topline != curwin->w_topline + || old_topfill != curwin->w_topfill + ) + redraw_later(VALID); + start_arrow(&tpos); + can_cindent = TRUE; + } else + vim_beep(); +} + +static void ins_pageup() { + pos_T tpos; + + undisplay_dollar(); + + if (mod_mask & MOD_MASK_CTRL) { + /* : tab page back */ + if (first_tabpage->tp_next != NULL) { + start_arrow(&curwin->w_cursor); + goto_tabpage(-1); + } + return; + } + + tpos = curwin->w_cursor; + if (onepage(BACKWARD, 1L) == OK) { + start_arrow(&tpos); + can_cindent = TRUE; + } else + vim_beep(); +} + +static void ins_down(startcol) +int startcol; /* when TRUE move to Insstart.col */ +{ + pos_T tpos; + linenr_T old_topline = curwin->w_topline; + int old_topfill = curwin->w_topfill; + + undisplay_dollar(); + tpos = curwin->w_cursor; + if (cursor_down(1L, TRUE) == OK) { + if (startcol) + coladvance(getvcol_nolist(&Insstart)); + if (old_topline != curwin->w_topline + || old_topfill != curwin->w_topfill + ) + redraw_later(VALID); + start_arrow(&tpos); + can_cindent = TRUE; + } else + vim_beep(); +} + +static void ins_pagedown() { + pos_T tpos; + + undisplay_dollar(); + + if (mod_mask & MOD_MASK_CTRL) { + /* : tab page forward */ + if (first_tabpage->tp_next != NULL) { + start_arrow(&curwin->w_cursor); + goto_tabpage(0); + } + return; + } + + tpos = curwin->w_cursor; + if (onepage(FORWARD, 1L) == OK) { + start_arrow(&tpos); + can_cindent = TRUE; + } else + vim_beep(); +} + +/* + * Handle TAB in Insert or Replace mode. + * Return TRUE when the TAB needs to be inserted like a normal character. + */ +static int ins_tab() { + int ind; + int i; + int temp; + + if (Insstart_blank_vcol == MAXCOL && curwin->w_cursor.lnum == Insstart.lnum) + Insstart_blank_vcol = get_nolist_virtcol(); + if (echeck_abbr(TAB + ABBR_OFF)) + return FALSE; + + ind = inindent(0); + if (ind) + can_cindent = FALSE; + + /* + * When nothing special, insert TAB like a normal character + */ + if (!curbuf->b_p_et + && !(p_sta && ind && curbuf->b_p_ts != get_sw_value(curbuf)) + && get_sts_value() == 0) + return TRUE; + + if (stop_arrow() == FAIL) + return TRUE; + + did_ai = FALSE; + did_si = FALSE; + can_si = FALSE; + can_si_back = FALSE; + AppendToRedobuff((char_u *)"\t"); + + if (p_sta && ind) /* insert tab in indent, use 'shiftwidth' */ + temp = (int)get_sw_value(curbuf); + else if (curbuf->b_p_sts != 0) /* use 'softtabstop' when set */ + temp = (int)get_sts_value(); + else /* otherwise use 'tabstop' */ + temp = (int)curbuf->b_p_ts; + temp -= get_nolist_virtcol() % temp; + + /* + * Insert the first space with ins_char(). It will delete one char in + * replace mode. Insert the rest with ins_str(); it will not delete any + * chars. For VREPLACE mode, we use ins_char() for all characters. + */ + ins_char(' '); + while (--temp > 0) { + if (State & VREPLACE_FLAG) + ins_char(' '); + else { + ins_str((char_u *)" "); + if (State & REPLACE_FLAG) /* no char replaced */ + replace_push(NUL); + } + } + + /* + * When 'expandtab' not set: Replace spaces by TABs where possible. + */ + if (!curbuf->b_p_et && (get_sts_value() || (p_sta && ind))) { + char_u *ptr; + char_u *saved_line = NULL; /* init for GCC */ + pos_T pos; + pos_T fpos; + pos_T *cursor; + colnr_T want_vcol, vcol; + int change_col = -1; + int save_list = curwin->w_p_list; + + /* + * Get the current line. For VREPLACE mode, don't make real changes + * yet, just work on a copy of the line. + */ + if (State & VREPLACE_FLAG) { + pos = curwin->w_cursor; + cursor = &pos; + saved_line = vim_strsave(ml_get_curline()); + if (saved_line == NULL) + return FALSE; + ptr = saved_line + pos.col; + } else { + ptr = ml_get_cursor(); + cursor = &curwin->w_cursor; + } + + /* When 'L' is not in 'cpoptions' a tab always takes up 'ts' spaces. */ + if (vim_strchr(p_cpo, CPO_LISTWM) == NULL) + curwin->w_p_list = FALSE; + + /* Find first white before the cursor */ + fpos = curwin->w_cursor; + while (fpos.col > 0 && vim_iswhite(ptr[-1])) { + --fpos.col; + --ptr; + } + + /* In Replace mode, don't change characters before the insert point. */ + if ((State & REPLACE_FLAG) + && fpos.lnum == Insstart.lnum + && fpos.col < Insstart.col) { + ptr += Insstart.col - fpos.col; + fpos.col = Insstart.col; + } + + /* compute virtual column numbers of first white and cursor */ + getvcol(curwin, &fpos, &vcol, NULL, NULL); + getvcol(curwin, cursor, &want_vcol, NULL, NULL); + + /* Use as many TABs as possible. Beware of 'showbreak' and + * 'linebreak' adding extra virtual columns. */ + while (vim_iswhite(*ptr)) { + i = lbr_chartabsize((char_u *)"\t", vcol); + if (vcol + i > want_vcol) + break; + if (*ptr != TAB) { + *ptr = TAB; + if (change_col < 0) { + change_col = fpos.col; /* Column of first change */ + /* May have to adjust Insstart */ + if (fpos.lnum == Insstart.lnum && fpos.col < Insstart.col) + Insstart.col = fpos.col; + } + } + ++fpos.col; + ++ptr; + vcol += i; + } + + if (change_col >= 0) { + int repl_off = 0; + + /* Skip over the spaces we need. */ + while (vcol < want_vcol && *ptr == ' ') { + vcol += lbr_chartabsize(ptr, vcol); + ++ptr; + ++repl_off; + } + if (vcol > want_vcol) { + /* Must have a char with 'showbreak' just before it. */ + --ptr; + --repl_off; + } + fpos.col += repl_off; + + /* Delete following spaces. */ + i = cursor->col - fpos.col; + if (i > 0) { + STRMOVE(ptr, ptr + i); + /* correct replace stack. */ + if ((State & REPLACE_FLAG) + && !(State & VREPLACE_FLAG) + ) + for (temp = i; --temp >= 0; ) + replace_join(repl_off); + } + cursor->col -= i; + + /* + * In VREPLACE mode, we haven't changed anything yet. Do it now by + * backspacing over the changed spacing and then inserting the new + * spacing. + */ + if (State & VREPLACE_FLAG) { + /* Backspace from real cursor to change_col */ + backspace_until_column(change_col); + + /* Insert each char in saved_line from changed_col to + * ptr-cursor */ + ins_bytes_len(saved_line + change_col, + cursor->col - change_col); + } + } + + if (State & VREPLACE_FLAG) + vim_free(saved_line); + curwin->w_p_list = save_list; + } + + return FALSE; +} + +/* + * Handle CR or NL in insert mode. + * Return TRUE when out of memory or can't undo. + */ +static int ins_eol(c) +int c; +{ + int i; + + if (echeck_abbr(c + ABBR_OFF)) + return FALSE; + if (stop_arrow() == FAIL) + return TRUE; + undisplay_dollar(); + + /* + * Strange Vi behaviour: In Replace mode, typing a NL will not delete the + * character under the cursor. Only push a NUL on the replace stack, + * nothing to put back when the NL is deleted. + */ + if ((State & REPLACE_FLAG) + && !(State & VREPLACE_FLAG) + ) + replace_push(NUL); + + /* + * In VREPLACE mode, a NL replaces the rest of the line, and starts + * replacing the next line, so we push all of the characters left on the + * line onto the replace stack. This is not done here though, it is done + * in open_line(). + */ + + /* Put cursor on NUL if on the last char and coladd is 1 (happens after + * CTRL-O). */ + if (virtual_active() && curwin->w_cursor.coladd > 0) + coladvance(getviscol()); + + if (p_altkeymap && p_fkmap) + fkmap(NL); + /* NL in reverse insert will always start in the end of + * current line. */ + if (revins_on) + curwin->w_cursor.col += (colnr_T)STRLEN(ml_get_cursor()); + + AppendToRedobuff(NL_STR); + i = open_line(FORWARD, + has_format_option(FO_RET_COMS) ? OPENLINE_DO_COM : + 0, old_indent); + old_indent = 0; + can_cindent = TRUE; + /* When inserting a line the cursor line must never be in a closed fold. */ + foldOpenCursor(); + + return !i; +} + +/* + * Handle digraph in insert mode. + * Returns character still to be inserted, or NUL when nothing remaining to be + * done. + */ +static int ins_digraph() { + int c; + int cc; + int did_putchar = FALSE; + + pc_status = PC_STATUS_UNSET; + if (redrawing() && !char_avail()) { + /* may need to redraw when no more chars available now */ + ins_redraw(FALSE); + + edit_putchar('?', TRUE); + did_putchar = TRUE; + add_to_showcmd_c(Ctrl_K); + } + +#ifdef USE_ON_FLY_SCROLL + dont_scroll = TRUE; /* disallow scrolling here */ +#endif + + /* don't map the digraph chars. This also prevents the + * mode message to be deleted when ESC is hit */ + ++no_mapping; + ++allow_keys; + c = plain_vgetc(); + --no_mapping; + --allow_keys; + if (did_putchar) + /* when the line fits in 'columns' the '?' is at the start of the next + * line and will not be removed by the redraw */ + edit_unputchar(); + + if (IS_SPECIAL(c) || mod_mask) { /* special key */ + clear_showcmd(); + insert_special(c, TRUE, FALSE); + return NUL; + } + if (c != ESC) { + did_putchar = FALSE; + if (redrawing() && !char_avail()) { + /* may need to redraw when no more chars available now */ + ins_redraw(FALSE); + + if (char2cells(c) == 1) { + ins_redraw(FALSE); + edit_putchar(c, TRUE); + did_putchar = TRUE; + } + add_to_showcmd_c(c); + } + ++no_mapping; + ++allow_keys; + cc = plain_vgetc(); + --no_mapping; + --allow_keys; + if (did_putchar) + /* when the line fits in 'columns' the '?' is at the start of the + * next line and will not be removed by a redraw */ + edit_unputchar(); + if (cc != ESC) { + AppendToRedobuff((char_u *)CTRL_V_STR); + c = getdigraph(c, cc, TRUE); + clear_showcmd(); + return c; + } + } + clear_showcmd(); + return NUL; +} + +/* + * Handle CTRL-E and CTRL-Y in Insert mode: copy char from other line. + * Returns the char to be inserted, or NUL if none found. + */ +int ins_copychar(lnum) +linenr_T lnum; +{ + int c; + int temp; + char_u *ptr, *prev_ptr; + + if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) { + vim_beep(); + return NUL; + } + + /* try to advance to the cursor column */ + temp = 0; + ptr = ml_get(lnum); + prev_ptr = ptr; + validate_virtcol(); + while ((colnr_T)temp < curwin->w_virtcol && *ptr != NUL) { + prev_ptr = ptr; + temp += lbr_chartabsize_adv(&ptr, (colnr_T)temp); + } + if ((colnr_T)temp > curwin->w_virtcol) + ptr = prev_ptr; + + c = (*mb_ptr2char)(ptr); + if (c == NUL) + vim_beep(); + return c; +} + +/* + * CTRL-Y or CTRL-E typed in Insert mode. + */ +static int ins_ctrl_ey(tc) +int tc; +{ + int c = tc; + + if (ctrl_x_mode == CTRL_X_SCROLL) { + if (c == Ctrl_Y) + scrolldown_clamp(); + else + scrollup_clamp(); + redraw_later(VALID); + } else { + c = ins_copychar(curwin->w_cursor.lnum + (c == Ctrl_Y ? -1 : 1)); + if (c != NUL) { + long tw_save; + + /* The character must be taken literally, insert like it + * was typed after a CTRL-V, and pretend 'textwidth' + * wasn't set. Digits, 'o' and 'x' are special after a + * CTRL-V, don't use it for these. */ + if (c < 256 && !isalnum(c)) + AppendToRedobuff((char_u *)CTRL_V_STR); /* CTRL-V */ + tw_save = curbuf->b_p_tw; + curbuf->b_p_tw = -1; + insert_special(c, TRUE, FALSE); + curbuf->b_p_tw = tw_save; + revins_chars++; + revins_legal++; + c = Ctrl_V; /* pretend CTRL-V is last character */ + auto_format(FALSE, TRUE); + } + } + return c; +} + +/* + * Try to do some very smart auto-indenting. + * Used when inserting a "normal" character. + */ +static void ins_try_si(c) +int c; +{ + pos_T *pos, old_pos; + char_u *ptr; + int i; + int temp; + + /* + * do some very smart indenting when entering '{' or '}' + */ + if (((did_si || can_si_back) && c == '{') || (can_si && c == '}')) { + /* + * for '}' set indent equal to indent of line containing matching '{' + */ + if (c == '}' && (pos = findmatch(NULL, '{')) != NULL) { + old_pos = curwin->w_cursor; + /* + * If the matching '{' has a ')' immediately before it (ignoring + * white-space), then line up with the start of the line + * containing the matching '(' if there is one. This handles the + * case where an "if (..\n..) {" statement continues over multiple + * lines -- webb + */ + ptr = ml_get(pos->lnum); + i = pos->col; + if (i > 0) /* skip blanks before '{' */ + while (--i > 0 && vim_iswhite(ptr[i])) + ; + curwin->w_cursor.lnum = pos->lnum; + curwin->w_cursor.col = i; + if (ptr[i] == ')' && (pos = findmatch(NULL, '(')) != NULL) + curwin->w_cursor = *pos; + i = get_indent(); + curwin->w_cursor = old_pos; + if (State & VREPLACE_FLAG) + change_indent(INDENT_SET, i, FALSE, NUL, TRUE); + else + (void)set_indent(i, SIN_CHANGED); + } else if (curwin->w_cursor.col > 0) { + /* + * when inserting '{' after "O" reduce indent, but not + * more than indent of previous line + */ + temp = TRUE; + if (c == '{' && can_si_back && curwin->w_cursor.lnum > 1) { + old_pos = curwin->w_cursor; + i = get_indent(); + while (curwin->w_cursor.lnum > 1) { + ptr = skipwhite(ml_get(--(curwin->w_cursor.lnum))); + + /* ignore empty lines and lines starting with '#'. */ + if (*ptr != '#' && *ptr != NUL) + break; + } + if (get_indent() >= i) + temp = FALSE; + curwin->w_cursor = old_pos; + } + if (temp) + shift_line(TRUE, FALSE, 1, TRUE); + } + } + + /* + * set indent of '#' always to 0 + */ + if (curwin->w_cursor.col > 0 && can_si && c == '#') { + /* remember current indent for next line */ + old_indent = get_indent(); + (void)set_indent(0, SIN_CHANGED); + } + + /* Adjust ai_col, the char at this position can be deleted. */ + if (ai_col > curwin->w_cursor.col) + ai_col = curwin->w_cursor.col; +} + +/* + * Get the value that w_virtcol would have when 'list' is off. + * Unless 'cpo' contains the 'L' flag. + */ +static colnr_T get_nolist_virtcol() { + if (curwin->w_p_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) + return getvcol_nolist(&curwin->w_cursor); + validate_virtcol(); + return curwin->w_virtcol; +} + +/* + * Handle the InsertCharPre autocommand. + * "c" is the character that was typed. + * Return a pointer to allocated memory with the replacement string. + * Return NULL to continue inserting "c". + */ +static char_u * do_insert_char_pre(c) +int c; +{ + char_u *res; + char_u buf[MB_MAXBYTES + 1]; + + /* Return quickly when there is nothing to do. */ + if (!has_insertcharpre()) + return NULL; + + if (has_mbyte) + buf[(*mb_char2bytes)(c, buf)] = NUL; + else { + buf[0] = c; + buf[1] = NUL; + } + + /* Lock the text to avoid weird things from happening. */ + ++textlock; + set_vim_var_string(VV_CHAR, buf, -1); /* set v:char */ + + res = NULL; + if (apply_autocmds(EVENT_INSERTCHARPRE, NULL, NULL, FALSE, curbuf)) { + /* Get the value of v:char. It may be empty or more than one + * character. Only use it when changed, otherwise continue with the + * original character to avoid breaking autoindent. */ + if (STRCMP(buf, get_vim_var_str(VV_CHAR)) != 0) + res = vim_strsave(get_vim_var_str(VV_CHAR)); + } + + set_vim_var_string(VV_CHAR, NULL, -1); /* clear v:char */ + --textlock; + + return res; +} diff --git a/src/eval.c b/src/eval.c new file mode 100644 index 0000000000..bcf9c75730 --- /dev/null +++ b/src/eval.c @@ -0,0 +1,20483 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * eval.c: Expression evaluation. + */ + +#include "vim.h" + + + + + +#if defined(FEAT_FLOAT) && defined(HAVE_MATH_H) +# include +#endif + +#define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */ + +#define DO_NOT_FREE_CNT 99999 /* refcount for dict or list that should not + be freed. */ + +/* + * In a hashtab item "hi_key" points to "di_key" in a dictitem. + * This avoids adding a pointer to the hashtab item. + * DI2HIKEY() converts a dictitem pointer to a hashitem key pointer. + * HIKEY2DI() converts a hashitem key pointer to a dictitem pointer. + * HI2DI() converts a hashitem pointer to a dictitem pointer. + */ +static dictitem_T dumdi; +#define DI2HIKEY(di) ((di)->di_key) +#define HIKEY2DI(p) ((dictitem_T *)(p - (dumdi.di_key - (char_u *)&dumdi))) +#define HI2DI(hi) HIKEY2DI((hi)->hi_key) + +/* + * Structure returned by get_lval() and used by set_var_lval(). + * For a plain name: + * "name" points to the variable name. + * "exp_name" is NULL. + * "tv" is NULL + * For a magic braces name: + * "name" points to the expanded variable name. + * "exp_name" is non-NULL, to be freed later. + * "tv" is NULL + * For an index in a list: + * "name" points to the (expanded) variable name. + * "exp_name" NULL or non-NULL, to be freed later. + * "tv" points to the (first) list item value + * "li" points to the (first) list item + * "range", "n1", "n2" and "empty2" indicate what items are used. + * For an existing Dict item: + * "name" points to the (expanded) variable name. + * "exp_name" NULL or non-NULL, to be freed later. + * "tv" points to the dict item value + * "newkey" is NULL + * For a non-existing Dict item: + * "name" points to the (expanded) variable name. + * "exp_name" NULL or non-NULL, to be freed later. + * "tv" points to the Dictionary typval_T + * "newkey" is the key for the new item. + */ +typedef struct lval_S { + char_u *ll_name; /* start of variable name (can be NULL) */ + char_u *ll_exp_name; /* NULL or expanded name in allocated memory. */ + typval_T *ll_tv; /* Typeval of item being used. If "newkey" + isn't NULL it's the Dict to which to add + the item. */ + listitem_T *ll_li; /* The list item or NULL. */ + list_T *ll_list; /* The list or NULL. */ + int ll_range; /* TRUE when a [i:j] range was used */ + long ll_n1; /* First index for list */ + long ll_n2; /* Second index for list range */ + int ll_empty2; /* Second index is empty: [i:] */ + dict_T *ll_dict; /* The Dictionary or NULL */ + dictitem_T *ll_di; /* The dictitem or NULL */ + char_u *ll_newkey; /* New key for Dict in alloc. mem or NULL. */ +} lval_T; + + +static char *e_letunexp = N_("E18: Unexpected characters in :let"); +static char *e_listidx = N_("E684: list index out of range: %ld"); +static char *e_undefvar = N_("E121: Undefined variable: %s"); +static char *e_missbrac = N_("E111: Missing ']'"); +static char *e_listarg = N_("E686: Argument of %s must be a List"); +static char *e_listdictarg = N_( + "E712: Argument of %s must be a List or Dictionary"); +static char *e_emptykey = N_("E713: Cannot use empty key for Dictionary"); +static char *e_listreq = N_("E714: List required"); +static char *e_dictreq = N_("E715: Dictionary required"); +static char *e_toomanyarg = N_("E118: Too many arguments for function: %s"); +static char *e_dictkey = N_("E716: Key not present in Dictionary: %s"); +static char *e_funcexts = N_( + "E122: Function %s already exists, add ! to replace it"); +static char *e_funcdict = N_("E717: Dictionary entry already exists"); +static char *e_funcref = N_("E718: Funcref required"); +static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary"); +static char *e_letwrong = N_("E734: Wrong variable type for %s="); +static char *e_nofunc = N_("E130: Unknown function: %s"); +static char *e_illvar = N_("E461: Illegal variable name: %s"); +static char *e_float_as_string = N_("E806: using Float as a String"); + +static dictitem_T globvars_var; /* variable used for g: */ +#define globvarht globvardict.dv_hashtab + +/* + * Old Vim variables such as "v:version" are also available without the "v:". + * Also in functions. We need a special hashtable for them. + */ +static hashtab_T compat_hashtab; + +/* + * When recursively copying lists and dicts we need to remember which ones we + * have done to avoid endless recursiveness. This unique ID is used for that. + * The last bit is used for previous_funccal, ignored when comparing. + */ +static int current_copyID = 0; +#define COPYID_INC 2 +#define COPYID_MASK (~0x1) + +/* + * Array to hold the hashtab with variables local to each sourced script. + * Each item holds a variable (nameless) that points to the dict_T. + */ +typedef struct { + dictitem_T sv_var; + dict_T sv_dict; +} scriptvar_T; + +static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL}; +#define SCRIPT_SV(id) (((scriptvar_T **)ga_scripts.ga_data)[(id) - 1]) +#define SCRIPT_VARS(id) (SCRIPT_SV(id)->sv_dict.dv_hashtab) + +static int echo_attr = 0; /* attributes used for ":echo" */ + +/* Values for trans_function_name() argument: */ +#define TFN_INT 1 /* internal function name OK */ +#define TFN_QUIET 2 /* no error messages */ +#define TFN_NO_AUTOLOAD 4 /* do not use script autoloading */ + +/* Values for get_lval() flags argument: */ +#define GLV_QUIET TFN_QUIET /* no error messages */ +#define GLV_NO_AUTOLOAD TFN_NO_AUTOLOAD /* do not use script autoloading */ + +/* + * Structure to hold info for a user function. + */ +typedef struct ufunc ufunc_T; + +struct ufunc { + int uf_varargs; /* variable nr of arguments */ + int uf_flags; + int uf_calls; /* nr of active calls */ + garray_T uf_args; /* arguments */ + garray_T uf_lines; /* function lines */ + int uf_profiling; /* TRUE when func is being profiled */ + /* profiling the function as a whole */ + int uf_tm_count; /* nr of calls */ + proftime_T uf_tm_total; /* time spent in function + children */ + proftime_T uf_tm_self; /* time spent in function itself */ + proftime_T uf_tm_children; /* time spent in children this call */ + /* profiling the function per line */ + int *uf_tml_count; /* nr of times line was executed */ + proftime_T *uf_tml_total; /* time spent in a line + children */ + proftime_T *uf_tml_self; /* time spent in a line itself */ + proftime_T uf_tml_start; /* start time for current line */ + proftime_T uf_tml_children; /* time spent in children for this line */ + proftime_T uf_tml_wait; /* start wait time for current line */ + int uf_tml_idx; /* index of line being timed; -1 if none */ + int uf_tml_execed; /* line being timed was executed */ + scid_T uf_script_ID; /* ID of script where function was defined, + used for s: variables */ + int uf_refcount; /* for numbered function: reference count */ + char_u uf_name[1]; /* name of function (actually longer); can + start with 123_ ( is K_SPECIAL + KS_EXTRA KE_SNR) */ +}; + +/* function flags */ +#define FC_ABORT 1 /* abort function on error */ +#define FC_RANGE 2 /* function accepts range */ +#define FC_DICT 4 /* Dict function, uses "self" */ + +/* + * All user-defined functions are found in this hashtable. + */ +static hashtab_T func_hashtab; + +/* The names of packages that once were loaded are remembered. */ +static garray_T ga_loaded = {0, 0, sizeof(char_u *), 4, NULL}; + +/* list heads for garbage collection */ +static dict_T *first_dict = NULL; /* list of all dicts */ +static list_T *first_list = NULL; /* list of all lists */ + +/* From user function to hashitem and back. */ +static ufunc_T dumuf; +#define UF2HIKEY(fp) ((fp)->uf_name) +#define HIKEY2UF(p) ((ufunc_T *)(p - (dumuf.uf_name - (char_u *)&dumuf))) +#define HI2UF(hi) HIKEY2UF((hi)->hi_key) + +#define FUNCARG(fp, j) ((char_u **)(fp->uf_args.ga_data))[j] +#define FUNCLINE(fp, j) ((char_u **)(fp->uf_lines.ga_data))[j] + +#define MAX_FUNC_ARGS 20 /* maximum number of function arguments */ +#define VAR_SHORT_LEN 20 /* short variable name length */ +#define FIXVAR_CNT 12 /* number of fixed variables */ + +/* structure to hold info for a function that is currently being executed. */ +typedef struct funccall_S funccall_T; + +struct funccall_S { + ufunc_T *func; /* function being called */ + int linenr; /* next line to be executed */ + int returned; /* ":return" used */ + struct /* fixed variables for arguments */ + { + dictitem_T var; /* variable (without room for name) */ + char_u room[VAR_SHORT_LEN]; /* room for the name */ + } fixvar[FIXVAR_CNT]; + dict_T l_vars; /* l: local function variables */ + dictitem_T l_vars_var; /* variable for l: scope */ + dict_T l_avars; /* a: argument variables */ + dictitem_T l_avars_var; /* variable for a: scope */ + list_T l_varlist; /* list for a:000 */ + listitem_T l_listitems[MAX_FUNC_ARGS]; /* listitems for a:000 */ + typval_T *rettv; /* return value */ + linenr_T breakpoint; /* next line with breakpoint or zero */ + int dbg_tick; /* debug_tick when breakpoint was set */ + int level; /* top nesting level of executed function */ + proftime_T prof_child; /* time spent in a child */ + funccall_T *caller; /* calling function or NULL */ +}; + +/* + * Info used by a ":for" loop. + */ +typedef struct { + int fi_semicolon; /* TRUE if ending in '; var]' */ + int fi_varcount; /* nr of variables in the list */ + listwatch_T fi_lw; /* keep an eye on the item used. */ + list_T *fi_list; /* list being used */ +} forinfo_T; + +/* + * Struct used by trans_function_name() + */ +typedef struct { + dict_T *fd_dict; /* Dictionary used */ + char_u *fd_newkey; /* new key in "dict" in allocated memory */ + dictitem_T *fd_di; /* Dictionary item used */ +} funcdict_T; + + +/* + * Array to hold the value of v: variables. + * The value is in a dictitem, so that it can also be used in the v: scope. + * The reason to use this table anyway is for very quick access to the + * variables with the VV_ defines. + */ +#include "version.h" + +/* values for vv_flags: */ +#define VV_COMPAT 1 /* compatible, also used without "v:" */ +#define VV_RO 2 /* read-only */ +#define VV_RO_SBX 4 /* read-only in the sandbox */ + +#define VV_NAME(s, t) s, {{t, 0, {0}}, 0, {0}}, {0} + +static struct vimvar { + char *vv_name; /* name of variable, without v: */ + dictitem_T vv_di; /* value and name for key */ + char vv_filler[16]; /* space for LONGEST name below!!! */ + char vv_flags; /* VV_COMPAT, VV_RO, VV_RO_SBX */ +} vimvars[VV_LEN] = +{ + /* + * The order here must match the VV_ defines in vim.h! + * Initializing a union does not work, leave tv.vval empty to get zero's. + */ + {VV_NAME("count", VAR_NUMBER), VV_COMPAT+VV_RO}, + {VV_NAME("count1", VAR_NUMBER), VV_RO}, + {VV_NAME("prevcount", VAR_NUMBER), VV_RO}, + {VV_NAME("errmsg", VAR_STRING), VV_COMPAT}, + {VV_NAME("warningmsg", VAR_STRING), 0}, + {VV_NAME("statusmsg", VAR_STRING), 0}, + {VV_NAME("shell_error", VAR_NUMBER), VV_COMPAT+VV_RO}, + {VV_NAME("this_session", VAR_STRING), VV_COMPAT}, + {VV_NAME("version", VAR_NUMBER), VV_COMPAT+VV_RO}, + {VV_NAME("lnum", VAR_NUMBER), VV_RO_SBX}, + {VV_NAME("termresponse", VAR_STRING), VV_RO}, + {VV_NAME("fname", VAR_STRING), VV_RO}, + {VV_NAME("lang", VAR_STRING), VV_RO}, + {VV_NAME("lc_time", VAR_STRING), VV_RO}, + {VV_NAME("ctype", VAR_STRING), VV_RO}, + {VV_NAME("charconvert_from", VAR_STRING), VV_RO}, + {VV_NAME("charconvert_to", VAR_STRING), VV_RO}, + {VV_NAME("fname_in", VAR_STRING), VV_RO}, + {VV_NAME("fname_out", VAR_STRING), VV_RO}, + {VV_NAME("fname_new", VAR_STRING), VV_RO}, + {VV_NAME("fname_diff", VAR_STRING), VV_RO}, + {VV_NAME("cmdarg", VAR_STRING), VV_RO}, + {VV_NAME("foldstart", VAR_NUMBER), VV_RO_SBX}, + {VV_NAME("foldend", VAR_NUMBER), VV_RO_SBX}, + {VV_NAME("folddashes", VAR_STRING), VV_RO_SBX}, + {VV_NAME("foldlevel", VAR_NUMBER), VV_RO_SBX}, + {VV_NAME("progname", VAR_STRING), VV_RO}, + {VV_NAME("servername", VAR_STRING), VV_RO}, + {VV_NAME("dying", VAR_NUMBER), VV_RO}, + {VV_NAME("exception", VAR_STRING), VV_RO}, + {VV_NAME("throwpoint", VAR_STRING), VV_RO}, + {VV_NAME("register", VAR_STRING), VV_RO}, + {VV_NAME("cmdbang", VAR_NUMBER), VV_RO}, + {VV_NAME("insertmode", VAR_STRING), VV_RO}, + {VV_NAME("val", VAR_UNKNOWN), VV_RO}, + {VV_NAME("key", VAR_UNKNOWN), VV_RO}, + {VV_NAME("profiling", VAR_NUMBER), VV_RO}, + {VV_NAME("fcs_reason", VAR_STRING), VV_RO}, + {VV_NAME("fcs_choice", VAR_STRING), 0}, + {VV_NAME("beval_bufnr", VAR_NUMBER), VV_RO}, + {VV_NAME("beval_winnr", VAR_NUMBER), VV_RO}, + {VV_NAME("beval_lnum", VAR_NUMBER), VV_RO}, + {VV_NAME("beval_col", VAR_NUMBER), VV_RO}, + {VV_NAME("beval_text", VAR_STRING), VV_RO}, + {VV_NAME("scrollstart", VAR_STRING), 0}, + {VV_NAME("swapname", VAR_STRING), VV_RO}, + {VV_NAME("swapchoice", VAR_STRING), 0}, + {VV_NAME("swapcommand", VAR_STRING), VV_RO}, + {VV_NAME("char", VAR_STRING), 0}, + {VV_NAME("mouse_win", VAR_NUMBER), 0}, + {VV_NAME("mouse_lnum", VAR_NUMBER), 0}, + {VV_NAME("mouse_col", VAR_NUMBER), 0}, + {VV_NAME("operator", VAR_STRING), VV_RO}, + {VV_NAME("searchforward", VAR_NUMBER), 0}, + {VV_NAME("hlsearch", VAR_NUMBER), 0}, + {VV_NAME("oldfiles", VAR_LIST), 0}, + {VV_NAME("windowid", VAR_NUMBER), VV_RO}, +}; + +/* shorthand */ +#define vv_type vv_di.di_tv.v_type +#define vv_nr vv_di.di_tv.vval.v_number +#define vv_float vv_di.di_tv.vval.v_float +#define vv_str vv_di.di_tv.vval.v_string +#define vv_list vv_di.di_tv.vval.v_list +#define vv_tv vv_di.di_tv + +static dictitem_T vimvars_var; /* variable used for v: */ +#define vimvarht vimvardict.dv_hashtab + +static void prepare_vimvar __ARGS((int idx, typval_T *save_tv)); +static void restore_vimvar __ARGS((int idx, typval_T *save_tv)); +static int ex_let_vars __ARGS((char_u *arg, typval_T *tv, int copy, + int semicolon, int var_count, + char_u *nextchars)); +static char_u *skip_var_list __ARGS((char_u *arg, int *var_count, + int *semicolon)); +static char_u *skip_var_one __ARGS((char_u *arg)); +static void list_hashtable_vars __ARGS((hashtab_T *ht, char_u *prefix, + int empty, + int *first)); +static void list_glob_vars __ARGS((int *first)); +static void list_buf_vars __ARGS((int *first)); +static void list_win_vars __ARGS((int *first)); +static void list_tab_vars __ARGS((int *first)); +static void list_vim_vars __ARGS((int *first)); +static void list_script_vars __ARGS((int *first)); +static void list_func_vars __ARGS((int *first)); +static char_u *list_arg_vars __ARGS((exarg_T *eap, char_u *arg, int *first)); +static char_u *ex_let_one __ARGS((char_u *arg, typval_T *tv, int copy, char_u * + endchars, + char_u *op)); +static int check_changedtick __ARGS((char_u *arg)); +static char_u *get_lval __ARGS((char_u *name, typval_T *rettv, lval_T *lp, + int unlet, int skip, int flags, + int fne_flags)); +static void clear_lval __ARGS((lval_T *lp)); +static void set_var_lval __ARGS((lval_T *lp, char_u *endp, typval_T *rettv, + int copy, + char_u *op)); +static int tv_op __ARGS((typval_T *tv1, typval_T *tv2, char_u *op)); +static void list_fix_watch __ARGS((list_T *l, listitem_T *item)); +static void ex_unletlock __ARGS((exarg_T *eap, char_u *argstart, int deep)); +static int do_unlet_var __ARGS((lval_T *lp, char_u *name_end, int forceit)); +static int do_lock_var __ARGS((lval_T *lp, char_u *name_end, int deep, int lock)); +static void item_lock __ARGS((typval_T *tv, int deep, int lock)); +static int tv_islocked __ARGS((typval_T *tv)); + +static int eval0 __ARGS((char_u *arg, typval_T *rettv, char_u **nextcmd, + int evaluate)); +static int eval1 __ARGS((char_u **arg, typval_T *rettv, int evaluate)); +static int eval2 __ARGS((char_u **arg, typval_T *rettv, int evaluate)); +static int eval3 __ARGS((char_u **arg, typval_T *rettv, int evaluate)); +static int eval4 __ARGS((char_u **arg, typval_T *rettv, int evaluate)); +static int eval5 __ARGS((char_u **arg, typval_T *rettv, int evaluate)); +static int eval6 __ARGS((char_u **arg, typval_T *rettv, int evaluate, + int want_string)); +static int eval7 __ARGS((char_u **arg, typval_T *rettv, int evaluate, + int want_string)); + +static int eval_index __ARGS((char_u **arg, typval_T *rettv, int evaluate, + int verbose)); +static int get_option_tv __ARGS((char_u **arg, typval_T *rettv, int evaluate)); +static int get_string_tv __ARGS((char_u **arg, typval_T *rettv, int evaluate)); +static int get_lit_string_tv __ARGS((char_u **arg, typval_T *rettv, + int evaluate)); +static int get_list_tv __ARGS((char_u **arg, typval_T *rettv, int evaluate)); +static int rettv_list_alloc __ARGS((typval_T *rettv)); +static long list_len __ARGS((list_T *l)); +static int list_equal __ARGS((list_T *l1, list_T *l2, int ic, int recursive)); +static int dict_equal __ARGS((dict_T *d1, dict_T *d2, int ic, int recursive)); +static int tv_equal __ARGS((typval_T *tv1, typval_T *tv2, int ic, int recursive)); +static long list_find_nr __ARGS((list_T *l, long idx, int *errorp)); +static long list_idx_of_item __ARGS((list_T *l, listitem_T *item)); +static int list_append_number __ARGS((list_T *l, varnumber_T n)); +static int list_extend __ARGS((list_T *l1, list_T *l2, listitem_T *bef)); +static int list_concat __ARGS((list_T *l1, list_T *l2, typval_T *tv)); +static list_T *list_copy __ARGS((list_T *orig, int deep, int copyID)); +static char_u *list2string __ARGS((typval_T *tv, int copyID)); +static int list_join_inner __ARGS((garray_T *gap, list_T *l, char_u *sep, + int echo_style, int copyID, + garray_T *join_gap)); +static int list_join __ARGS((garray_T *gap, list_T *l, char_u *sep, int echo, + int copyID)); +static int free_unref_items __ARGS((int copyID)); +static int rettv_dict_alloc __ARGS((typval_T *rettv)); +static dictitem_T *dictitem_copy __ARGS((dictitem_T *org)); +static void dictitem_remove __ARGS((dict_T *dict, dictitem_T *item)); +static dict_T *dict_copy __ARGS((dict_T *orig, int deep, int copyID)); +static long dict_len __ARGS((dict_T *d)); +static char_u *dict2string __ARGS((typval_T *tv, int copyID)); +static int get_dict_tv __ARGS((char_u **arg, typval_T *rettv, int evaluate)); +static char_u *echo_string __ARGS((typval_T *tv, char_u **tofree, char_u * + numbuf, + int copyID)); +static char_u *tv2string __ARGS((typval_T *tv, char_u **tofree, char_u *numbuf, + int copyID)); +static char_u *string_quote __ARGS((char_u *str, int function)); +static int string2float __ARGS((char_u *text, float_T *value)); +static int get_env_tv __ARGS((char_u **arg, typval_T *rettv, int evaluate)); +static int find_internal_func __ARGS((char_u *name)); +static char_u *deref_func_name __ARGS((char_u *name, int *lenp, int no_autoload)); +static int get_func_tv __ARGS((char_u *name, int len, typval_T *rettv, char_u * + *arg, linenr_T firstline, linenr_T lastline, + int *doesrange, int evaluate, + dict_T *selfdict)); +static int call_func __ARGS((char_u *funcname, int len, typval_T *rettv, + int argcount, typval_T *argvars, + linenr_T firstline, linenr_T lastline, + int *doesrange, int evaluate, + dict_T *selfdict)); +static void emsg_funcname __ARGS((char *ermsg, char_u *name)); +static int non_zero_arg __ARGS((typval_T *argvars)); + +static void f_abs __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_acos __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_add __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_and __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_append __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_argc __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_argidx __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_argv __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_asin __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_atan __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_atan2 __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_browse __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_browsedir __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_bufexists __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_buflisted __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_bufloaded __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_bufname __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_bufnr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_bufwinnr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_byte2line __ARGS((typval_T *argvars, typval_T *rettv)); +static void byteidx __ARGS((typval_T *argvars, typval_T *rettv, int comp)); +static void f_byteidx __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_byteidxcomp __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_call __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_ceil __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_changenr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_char2nr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_cindent __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_clearmatches __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_col __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_complete __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_complete_add __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_complete_check __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_confirm __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_copy __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_cos __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_cosh __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_count __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_cscope_connection __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_cursor __ARGS((typval_T *argsvars, typval_T *rettv)); +static void f_deepcopy __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_delete __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_did_filetype __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_diff_filler __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_diff_hlID __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_empty __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_escape __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_eval __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_eventhandler __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_executable __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_exists __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_exp __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_expand __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_extend __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_feedkeys __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_filereadable __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_filewritable __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_filter __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_finddir __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_findfile __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_float2nr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_floor __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_fmod __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_fnameescape __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_fnamemodify __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_foldclosed __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_foldclosedend __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_foldlevel __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_foldtext __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_foldtextresult __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_foreground __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_function __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_garbagecollect __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_get __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getbufline __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getbufvar __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getchar __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getcharmod __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getcmdline __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getcmdpos __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getcmdtype __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getcwd __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getfontname __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getfperm __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getfsize __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getftime __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getftype __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getline __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getmatches __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getpid __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getpos __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getqflist __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getreg __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getregtype __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_gettabvar __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_gettabwinvar __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getwinposx __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getwinposy __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_getwinvar __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_glob __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_globpath __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_has __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_has_key __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_haslocaldir __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_hasmapto __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_histadd __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_histdel __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_histget __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_histnr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_hlID __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_hlexists __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_hostname __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_iconv __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_indent __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_index __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_input __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_inputdialog __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_inputlist __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_inputrestore __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_inputsave __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_inputsecret __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_insert __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_invert __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_isdirectory __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_islocked __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_items __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_join __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_keys __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_last_buffer_nr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_len __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_libcall __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_libcallnr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_line __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_line2byte __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_lispindent __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_localtime __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_log __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_log10 __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_map __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_maparg __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_mapcheck __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_match __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_matchadd __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_matcharg __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_matchdelete __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_matchend __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_matchlist __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_matchstr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_max __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_min __ARGS((typval_T *argvars, typval_T *rettv)); +#ifdef vim_mkdir +static void f_mkdir __ARGS((typval_T *argvars, typval_T *rettv)); +#endif +static void f_mode __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_nextnonblank __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_nr2char __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_or __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_pathshorten __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_pow __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_prevnonblank __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_printf __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_pumvisible __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_range __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_readfile __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_reltime __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_reltimestr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_remote_expr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_remote_foreground __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_remote_peek __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_remote_read __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_remote_send __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_remove __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_rename __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_repeat __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_resolve __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_reverse __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_round __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_screenattr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_screenchar __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_screencol __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_screenrow __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_search __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_searchdecl __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_searchpair __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_searchpairpos __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_searchpos __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_server2client __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_serverlist __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_setbufvar __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_setcmdpos __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_setline __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_setloclist __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_setmatches __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_setpos __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_setqflist __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_setreg __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_settabvar __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_settabwinvar __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_setwinvar __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_sha256 __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_shellescape __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_shiftwidth __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_simplify __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_sin __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_sinh __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_sort __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_soundfold __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_spellbadword __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_spellsuggest __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_split __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_sqrt __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_str2float __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_str2nr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_strchars __ARGS((typval_T *argvars, typval_T *rettv)); +#ifdef HAVE_STRFTIME +static void f_strftime __ARGS((typval_T *argvars, typval_T *rettv)); +#endif +static void f_stridx __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_string __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_strlen __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_strpart __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_strridx __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_strtrans __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_strdisplaywidth __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_strwidth __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_submatch __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_substitute __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_synID __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_synIDattr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_synIDtrans __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_synstack __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_synconcealed __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_system __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_tabpagebuflist __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_tabpagenr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_tabpagewinnr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_taglist __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_tagfiles __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_tempname __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_test __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_tan __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_tanh __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_tolower __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_toupper __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_tr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_trunc __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_type __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_undofile __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_undotree __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_values __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_virtcol __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_visualmode __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_wildmenumode __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_winbufnr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_wincol __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_winheight __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_winline __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_winnr __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_winrestcmd __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_winrestview __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_winsaveview __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_winwidth __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_writefile __ARGS((typval_T *argvars, typval_T *rettv)); +static void f_xor __ARGS((typval_T *argvars, typval_T *rettv)); + +static int list2fpos __ARGS((typval_T *arg, pos_T *posp, int *fnump)); +static pos_T *var2fpos __ARGS((typval_T *varp, int dollar_lnum, int *fnum)); +static int get_env_len __ARGS((char_u **arg)); +static int get_id_len __ARGS((char_u **arg)); +static int get_name_len __ARGS((char_u **arg, char_u **alias, int evaluate, + int verbose)); +static char_u *find_name_end __ARGS((char_u *arg, char_u **expr_start, char_u * + *expr_end, + int flags)); +#define FNE_INCL_BR 1 /* find_name_end(): include [] in name */ +#define FNE_CHECK_START 2 /* find_name_end(): check name starts with + valid character */ +static char_u * +make_expanded_name __ARGS((char_u *in_start, char_u *expr_start, char_u * + expr_end, + char_u *in_end)); +static int eval_isnamec __ARGS((int c)); +static int eval_isnamec1 __ARGS((int c)); +static int get_var_tv __ARGS((char_u *name, int len, typval_T *rettv, + int verbose, + int no_autoload)); +static int handle_subscript __ARGS((char_u **arg, typval_T *rettv, int evaluate, + int verbose)); +static typval_T *alloc_tv __ARGS((void)); +static typval_T *alloc_string_tv __ARGS((char_u *string)); +static void init_tv __ARGS((typval_T *varp)); +static long get_tv_number __ARGS((typval_T *varp)); +static linenr_T get_tv_lnum __ARGS((typval_T *argvars)); +static linenr_T get_tv_lnum_buf __ARGS((typval_T *argvars, buf_T *buf)); +static char_u *get_tv_string __ARGS((typval_T *varp)); +static char_u *get_tv_string_buf __ARGS((typval_T *varp, char_u *buf)); +static char_u *get_tv_string_buf_chk __ARGS((typval_T *varp, char_u *buf)); +static dictitem_T *find_var __ARGS((char_u *name, hashtab_T **htp, + int no_autoload)); +static dictitem_T *find_var_in_ht __ARGS((hashtab_T *ht, int htname, char_u * + varname, + int no_autoload)); +static hashtab_T *find_var_ht __ARGS((char_u *name, char_u **varname)); +static void vars_clear_ext __ARGS((hashtab_T *ht, int free_val)); +static void delete_var __ARGS((hashtab_T *ht, hashitem_T *hi)); +static void list_one_var __ARGS((dictitem_T *v, char_u *prefix, int *first)); +static void list_one_var_a __ARGS((char_u *prefix, char_u *name, int type, + char_u *string, + int *first)); +static void set_var __ARGS((char_u *name, typval_T *varp, int copy)); +static int var_check_ro __ARGS((int flags, char_u *name)); +static int var_check_fixed __ARGS((int flags, char_u *name)); +static int var_check_func_name __ARGS((char_u *name, int new_var)); +static int valid_varname __ARGS((char_u *varname)); +static int tv_check_lock __ARGS((int lock, char_u *name)); +static int item_copy __ARGS((typval_T *from, typval_T *to, int deep, int copyID)); +static char_u *find_option_end __ARGS((char_u **arg, int *opt_flags)); +static char_u *trans_function_name __ARGS((char_u **pp, int skip, int flags, + funcdict_T *fd)); +static int eval_fname_script __ARGS((char_u *p)); +static int eval_fname_sid __ARGS((char_u *p)); +static void list_func_head __ARGS((ufunc_T *fp, int indent)); +static ufunc_T *find_func __ARGS((char_u *name)); +static int function_exists __ARGS((char_u *name)); +static int builtin_function __ARGS((char_u *name)); +static void func_do_profile __ARGS((ufunc_T *fp)); +static void prof_sort_list __ARGS((FILE *fd, ufunc_T **sorttab, int st_len, + char *title, + int prefer_self)); +static void prof_func_line __ARGS((FILE *fd, int count, proftime_T *total, + proftime_T *self, + int prefer_self)); +static int +prof_total_cmp __ARGS((const void *s1, const void *s2)); +static int +prof_self_cmp __ARGS((const void *s1, const void *s2)); +static int script_autoload __ARGS((char_u *name, int reload)); +static char_u *autoload_name __ARGS((char_u *name)); +static void cat_func_name __ARGS((char_u *buf, ufunc_T *fp)); +static void func_free __ARGS((ufunc_T *fp)); +static void call_user_func __ARGS((ufunc_T *fp, int argcount, typval_T *argvars, + typval_T *rettv, linenr_T firstline, + linenr_T lastline, + dict_T *selfdict)); +static int can_free_funccal __ARGS((funccall_T *fc, int copyID)); +static void free_funccal __ARGS((funccall_T *fc, int free_val)); +static void add_nr_var __ARGS((dict_T *dp, dictitem_T *v, char *name, + varnumber_T nr)); +static win_T *find_win_by_nr __ARGS((typval_T *vp, tabpage_T *tp)); +static void getwinvar __ARGS((typval_T *argvars, typval_T *rettv, int off)); +static int searchpair_cmn __ARGS((typval_T *argvars, pos_T *match_pos)); +static int search_cmn __ARGS((typval_T *argvars, pos_T *match_pos, int *flagsp)); +static void setwinvar __ARGS((typval_T *argvars, typval_T *rettv, int off)); + + + +/* + * Initialize the global and v: variables. + */ +void eval_init() { + int i; + struct vimvar *p; + + init_var_dict(&globvardict, &globvars_var, VAR_DEF_SCOPE); + init_var_dict(&vimvardict, &vimvars_var, VAR_SCOPE); + vimvardict.dv_lock = VAR_FIXED; + hash_init(&compat_hashtab); + hash_init(&func_hashtab); + + for (i = 0; i < VV_LEN; ++i) { + p = &vimvars[i]; + STRCPY(p->vv_di.di_key, p->vv_name); + if (p->vv_flags & VV_RO) + p->vv_di.di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + else if (p->vv_flags & VV_RO_SBX) + p->vv_di.di_flags = DI_FLAGS_RO_SBX | DI_FLAGS_FIX; + else + p->vv_di.di_flags = DI_FLAGS_FIX; + + /* add to v: scope dict, unless the value is not always available */ + if (p->vv_type != VAR_UNKNOWN) + hash_add(&vimvarht, p->vv_di.di_key); + if (p->vv_flags & VV_COMPAT) + /* add to compat scope dict */ + hash_add(&compat_hashtab, p->vv_di.di_key); + } + set_vim_var_nr(VV_SEARCHFORWARD, 1L); + set_vim_var_nr(VV_HLSEARCH, 1L); + set_reg_var(0); /* default for v:register is not 0 but '"' */ + +} + +#if defined(EXITFREE) || defined(PROTO) +void eval_clear() { + int i; + struct vimvar *p; + + for (i = 0; i < VV_LEN; ++i) { + p = &vimvars[i]; + if (p->vv_di.di_tv.v_type == VAR_STRING) { + vim_free(p->vv_str); + p->vv_str = NULL; + } else if (p->vv_di.di_tv.v_type == VAR_LIST) { + list_unref(p->vv_list); + p->vv_list = NULL; + } + } + hash_clear(&vimvarht); + hash_init(&vimvarht); /* garbage_collect() will access it */ + hash_clear(&compat_hashtab); + + free_scriptnames(); + free_locales(); + + /* global variables */ + vars_clear(&globvarht); + + /* autoloaded script names */ + ga_clear_strings(&ga_loaded); + + /* Script-local variables. First clear all the variables and in a second + * loop free the scriptvar_T, because a variable in one script might hold + * a reference to the whole scope of another script. */ + for (i = 1; i <= ga_scripts.ga_len; ++i) + vars_clear(&SCRIPT_VARS(i)); + for (i = 1; i <= ga_scripts.ga_len; ++i) + vim_free(SCRIPT_SV(i)); + ga_clear(&ga_scripts); + + /* unreferenced lists and dicts */ + (void)garbage_collect(); + + /* functions */ + free_all_functions(); + hash_clear(&func_hashtab); +} + +#endif + +/* + * Return the name of the executed function. + */ +char_u * func_name(cookie) +void *cookie; +{ + return ((funccall_T *)cookie)->func->uf_name; +} + +/* + * Return the address holding the next breakpoint line for a funccall cookie. + */ +linenr_T * func_breakpoint(cookie) +void *cookie; +{ + return &((funccall_T *)cookie)->breakpoint; +} + +/* + * Return the address holding the debug tick for a funccall cookie. + */ +int * func_dbg_tick(cookie) +void *cookie; +{ + return &((funccall_T *)cookie)->dbg_tick; +} + +/* + * Return the nesting level for a funccall cookie. + */ +int func_level(cookie) +void *cookie; +{ + return ((funccall_T *)cookie)->level; +} + +/* pointer to funccal for currently active function */ +funccall_T *current_funccal = NULL; + +/* pointer to list of previously used funccal, still around because some + * item in it is still being used. */ +funccall_T *previous_funccal = NULL; + +/* + * Return TRUE when a function was ended by a ":return" command. + */ +int current_func_returned() { + return current_funccal->returned; +} + +/* + * Set an internal variable to a string value. Creates the variable if it does + * not already exist. + */ +void set_internal_string_var(name, value) +char_u *name; +char_u *value; +{ + char_u *val; + typval_T *tvp; + + val = vim_strsave(value); + if (val != NULL) { + tvp = alloc_string_tv(val); + if (tvp != NULL) { + set_var(name, tvp, FALSE); + free_tv(tvp); + } + } +} + +static lval_T *redir_lval = NULL; +static garray_T redir_ga; /* only valid when redir_lval is not NULL */ +static char_u *redir_endp = NULL; +static char_u *redir_varname = NULL; + +/* + * Start recording command output to a variable + * Returns OK if successfully completed the setup. FAIL otherwise. + */ +int var_redir_start(name, append) +char_u *name; +int append; /* append to an existing variable */ +{ + int save_emsg; + int err; + typval_T tv; + + /* Catch a bad name early. */ + if (!eval_isnamec1(*name)) { + EMSG(_(e_invarg)); + return FAIL; + } + + /* Make a copy of the name, it is used in redir_lval until redir ends. */ + redir_varname = vim_strsave(name); + if (redir_varname == NULL) + return FAIL; + + redir_lval = (lval_T *)alloc_clear((unsigned)sizeof(lval_T)); + if (redir_lval == NULL) { + var_redir_stop(); + return FAIL; + } + + /* The output is stored in growarray "redir_ga" until redirection ends. */ + ga_init2(&redir_ga, (int)sizeof(char), 500); + + /* Parse the variable name (can be a dict or list entry). */ + redir_endp = get_lval(redir_varname, NULL, redir_lval, FALSE, FALSE, 0, + FNE_CHECK_START); + if (redir_endp == NULL || redir_lval->ll_name == NULL || *redir_endp != + NUL) { + clear_lval(redir_lval); + if (redir_endp != NULL && *redir_endp != NUL) + /* Trailing characters are present after the variable name */ + EMSG(_(e_trailing)); + else + EMSG(_(e_invarg)); + redir_endp = NULL; /* don't store a value, only cleanup */ + var_redir_stop(); + return FAIL; + } + + /* check if we can write to the variable: set it to or append an empty + * string */ + save_emsg = did_emsg; + did_emsg = FALSE; + tv.v_type = VAR_STRING; + tv.vval.v_string = (char_u *)""; + if (append) + set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"."); + else + set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"="); + clear_lval(redir_lval); + err = did_emsg; + did_emsg |= save_emsg; + if (err) { + redir_endp = NULL; /* don't store a value, only cleanup */ + var_redir_stop(); + return FAIL; + } + + return OK; +} + +/* + * Append "value[value_len]" to the variable set by var_redir_start(). + * The actual appending is postponed until redirection ends, because the value + * appended may in fact be the string we write to, changing it may cause freed + * memory to be used: + * :redir => foo + * :let foo + * :redir END + */ +void var_redir_str(value, value_len) +char_u *value; +int value_len; +{ + int len; + + if (redir_lval == NULL) + return; + + if (value_len == -1) + len = (int)STRLEN(value); /* Append the entire string */ + else + len = value_len; /* Append only "value_len" characters */ + + if (ga_grow(&redir_ga, len) == OK) { + mch_memmove((char *)redir_ga.ga_data + redir_ga.ga_len, value, len); + redir_ga.ga_len += len; + } else + var_redir_stop(); +} + +/* + * Stop redirecting command output to a variable. + * Frees the allocated memory. + */ +void var_redir_stop() { + typval_T tv; + + if (redir_lval != NULL) { + /* If there was no error: assign the text to the variable. */ + if (redir_endp != NULL) { + ga_append(&redir_ga, NUL); /* Append the trailing NUL. */ + tv.v_type = VAR_STRING; + tv.vval.v_string = redir_ga.ga_data; + /* Call get_lval() again, if it's inside a Dict or List it may + * have changed. */ + redir_endp = get_lval(redir_varname, NULL, redir_lval, + FALSE, FALSE, 0, FNE_CHECK_START); + if (redir_endp != NULL && redir_lval->ll_name != NULL) + set_var_lval(redir_lval, redir_endp, &tv, FALSE, (char_u *)"."); + clear_lval(redir_lval); + } + + /* free the collected output */ + vim_free(redir_ga.ga_data); + redir_ga.ga_data = NULL; + + vim_free(redir_lval); + redir_lval = NULL; + } + vim_free(redir_varname); + redir_varname = NULL; +} + +int eval_charconvert(enc_from, enc_to, fname_from, fname_to) +char_u *enc_from; +char_u *enc_to; +char_u *fname_from; +char_u *fname_to; +{ + int err = FALSE; + + set_vim_var_string(VV_CC_FROM, enc_from, -1); + set_vim_var_string(VV_CC_TO, enc_to, -1); + set_vim_var_string(VV_FNAME_IN, fname_from, -1); + set_vim_var_string(VV_FNAME_OUT, fname_to, -1); + if (eval_to_bool(p_ccv, &err, NULL, FALSE)) + err = TRUE; + set_vim_var_string(VV_CC_FROM, NULL, -1); + set_vim_var_string(VV_CC_TO, NULL, -1); + set_vim_var_string(VV_FNAME_IN, NULL, -1); + set_vim_var_string(VV_FNAME_OUT, NULL, -1); + + if (err) + return FAIL; + return OK; +} + +int eval_printexpr(fname, args) +char_u *fname; +char_u *args; +{ + int err = FALSE; + + set_vim_var_string(VV_FNAME_IN, fname, -1); + set_vim_var_string(VV_CMDARG, args, -1); + if (eval_to_bool(p_pexpr, &err, NULL, FALSE)) + err = TRUE; + set_vim_var_string(VV_FNAME_IN, NULL, -1); + set_vim_var_string(VV_CMDARG, NULL, -1); + + if (err) { + mch_remove(fname); + return FAIL; + } + return OK; +} + +void eval_diff(origfile, newfile, outfile) +char_u *origfile; +char_u *newfile; +char_u *outfile; +{ + int err = FALSE; + + set_vim_var_string(VV_FNAME_IN, origfile, -1); + set_vim_var_string(VV_FNAME_NEW, newfile, -1); + set_vim_var_string(VV_FNAME_OUT, outfile, -1); + (void)eval_to_bool(p_dex, &err, NULL, FALSE); + set_vim_var_string(VV_FNAME_IN, NULL, -1); + set_vim_var_string(VV_FNAME_NEW, NULL, -1); + set_vim_var_string(VV_FNAME_OUT, NULL, -1); +} + +void eval_patch(origfile, difffile, outfile) +char_u *origfile; +char_u *difffile; +char_u *outfile; +{ + int err; + + set_vim_var_string(VV_FNAME_IN, origfile, -1); + set_vim_var_string(VV_FNAME_DIFF, difffile, -1); + set_vim_var_string(VV_FNAME_OUT, outfile, -1); + (void)eval_to_bool(p_pex, &err, NULL, FALSE); + set_vim_var_string(VV_FNAME_IN, NULL, -1); + set_vim_var_string(VV_FNAME_DIFF, NULL, -1); + set_vim_var_string(VV_FNAME_OUT, NULL, -1); +} + +/* + * Top level evaluation function, returning a boolean. + * Sets "error" to TRUE if there was an error. + * Return TRUE or FALSE. + */ +int eval_to_bool(arg, error, nextcmd, skip) +char_u *arg; +int *error; +char_u **nextcmd; +int skip; /* only parse, don't execute */ +{ + typval_T tv; + int retval = FALSE; + + if (skip) + ++emsg_skip; + if (eval0(arg, &tv, nextcmd, !skip) == FAIL) + *error = TRUE; + else { + *error = FALSE; + if (!skip) { + retval = (get_tv_number_chk(&tv, error) != 0); + clear_tv(&tv); + } + } + if (skip) + --emsg_skip; + + return retval; +} + +/* + * Top level evaluation function, returning a string. If "skip" is TRUE, + * only parsing to "nextcmd" is done, without reporting errors. Return + * pointer to allocated memory, or NULL for failure or when "skip" is TRUE. + */ +char_u * eval_to_string_skip(arg, nextcmd, skip) +char_u *arg; +char_u **nextcmd; +int skip; /* only parse, don't execute */ +{ + typval_T tv; + char_u *retval; + + if (skip) + ++emsg_skip; + if (eval0(arg, &tv, nextcmd, !skip) == FAIL || skip) + retval = NULL; + else { + retval = vim_strsave(get_tv_string(&tv)); + clear_tv(&tv); + } + if (skip) + --emsg_skip; + + return retval; +} + +/* + * Skip over an expression at "*pp". + * Return FAIL for an error, OK otherwise. + */ +int skip_expr(pp) +char_u **pp; +{ + typval_T rettv; + + *pp = skipwhite(*pp); + return eval1(pp, &rettv, FALSE); +} + +/* + * Top level evaluation function, returning a string. + * When "convert" is TRUE convert a List into a sequence of lines and convert + * a Float to a String. + * Return pointer to allocated memory, or NULL for failure. + */ +char_u * eval_to_string(arg, nextcmd, convert) +char_u *arg; +char_u **nextcmd; +int convert; +{ + typval_T tv; + char_u *retval; + garray_T ga; + char_u numbuf[NUMBUFLEN]; + + if (eval0(arg, &tv, nextcmd, TRUE) == FAIL) + retval = NULL; + else { + if (convert && tv.v_type == VAR_LIST) { + ga_init2(&ga, (int)sizeof(char), 80); + if (tv.vval.v_list != NULL) { + list_join(&ga, tv.vval.v_list, (char_u *)"\n", TRUE, 0); + if (tv.vval.v_list->lv_len > 0) + ga_append(&ga, NL); + } + ga_append(&ga, NUL); + retval = (char_u *)ga.ga_data; + } else if (convert && tv.v_type == VAR_FLOAT) { + vim_snprintf((char *)numbuf, NUMBUFLEN, "%g", tv.vval.v_float); + retval = vim_strsave(numbuf); + } else + retval = vim_strsave(get_tv_string(&tv)); + clear_tv(&tv); + } + + return retval; +} + +/* + * Call eval_to_string() without using current local variables and using + * textlock. When "use_sandbox" is TRUE use the sandbox. + */ +char_u * eval_to_string_safe(arg, nextcmd, use_sandbox) +char_u *arg; +char_u **nextcmd; +int use_sandbox; +{ + char_u *retval; + void *save_funccalp; + + save_funccalp = save_funccal(); + if (use_sandbox) + ++sandbox; + ++textlock; + retval = eval_to_string(arg, nextcmd, FALSE); + if (use_sandbox) + --sandbox; + --textlock; + restore_funccal(save_funccalp); + return retval; +} + +/* + * Top level evaluation function, returning a number. + * Evaluates "expr" silently. + * Returns -1 for an error. + */ +int eval_to_number(expr) +char_u *expr; +{ + typval_T rettv; + int retval; + char_u *p = skipwhite(expr); + + ++emsg_off; + + if (eval1(&p, &rettv, TRUE) == FAIL) + retval = -1; + else { + retval = get_tv_number_chk(&rettv, NULL); + clear_tv(&rettv); + } + --emsg_off; + + return retval; +} + +/* + * Prepare v: variable "idx" to be used. + * Save the current typeval in "save_tv". + * When not used yet add the variable to the v: hashtable. + */ +static void prepare_vimvar(idx, save_tv) +int idx; +typval_T *save_tv; +{ + *save_tv = vimvars[idx].vv_tv; + if (vimvars[idx].vv_type == VAR_UNKNOWN) + hash_add(&vimvarht, vimvars[idx].vv_di.di_key); +} + +/* + * Restore v: variable "idx" to typeval "save_tv". + * When no longer defined, remove the variable from the v: hashtable. + */ +static void restore_vimvar(idx, save_tv) +int idx; +typval_T *save_tv; +{ + hashitem_T *hi; + + vimvars[idx].vv_tv = *save_tv; + if (vimvars[idx].vv_type == VAR_UNKNOWN) { + hi = hash_find(&vimvarht, vimvars[idx].vv_di.di_key); + if (HASHITEM_EMPTY(hi)) + EMSG2(_(e_intern2), "restore_vimvar()"); + else + hash_remove(&vimvarht, hi); + } +} + +/* + * Evaluate an expression to a list with suggestions. + * For the "expr:" part of 'spellsuggest'. + * Returns NULL when there is an error. + */ +list_T * eval_spell_expr(badword, expr) +char_u *badword; +char_u *expr; +{ + typval_T save_val; + typval_T rettv; + list_T *list = NULL; + char_u *p = skipwhite(expr); + + /* Set "v:val" to the bad word. */ + prepare_vimvar(VV_VAL, &save_val); + vimvars[VV_VAL].vv_type = VAR_STRING; + vimvars[VV_VAL].vv_str = badword; + if (p_verbose == 0) + ++emsg_off; + + if (eval1(&p, &rettv, TRUE) == OK) { + if (rettv.v_type != VAR_LIST) + clear_tv(&rettv); + else + list = rettv.vval.v_list; + } + + if (p_verbose == 0) + --emsg_off; + restore_vimvar(VV_VAL, &save_val); + + return list; +} + +/* + * "list" is supposed to contain two items: a word and a number. Return the + * word in "pp" and the number as the return value. + * Return -1 if anything isn't right. + * Used to get the good word and score from the eval_spell_expr() result. + */ +int get_spellword(list, pp) +list_T *list; +char_u **pp; +{ + listitem_T *li; + + li = list->lv_first; + if (li == NULL) + return -1; + *pp = get_tv_string(&li->li_tv); + + li = li->li_next; + if (li == NULL) + return -1; + return get_tv_number(&li->li_tv); +} + +/* + * Top level evaluation function. + * Returns an allocated typval_T with the result. + * Returns NULL when there is an error. + */ +typval_T * eval_expr(arg, nextcmd) +char_u *arg; +char_u **nextcmd; +{ + typval_T *tv; + + tv = (typval_T *)alloc(sizeof(typval_T)); + if (tv != NULL && eval0(arg, tv, nextcmd, TRUE) == FAIL) { + vim_free(tv); + tv = NULL; + } + + return tv; +} + + +/* + * Call some vimL function and return the result in "*rettv". + * Uses argv[argc] for the function arguments. Only Number and String + * arguments are currently supported. + * Returns OK or FAIL. + */ +int call_vim_function(func, argc, argv, safe, str_arg_only, rettv) +char_u *func; +int argc; +char_u **argv; +int safe; /* use the sandbox */ +int str_arg_only; /* all arguments are strings */ +typval_T *rettv; +{ + typval_T *argvars; + long n; + int len; + int i; + int doesrange; + void *save_funccalp = NULL; + int ret; + + argvars = (typval_T *)alloc((unsigned)((argc + 1) * sizeof(typval_T))); + if (argvars == NULL) + return FAIL; + + for (i = 0; i < argc; i++) { + /* Pass a NULL or empty argument as an empty string */ + if (argv[i] == NULL || *argv[i] == NUL) { + argvars[i].v_type = VAR_STRING; + argvars[i].vval.v_string = (char_u *)""; + continue; + } + + if (str_arg_only) + len = 0; + else + /* Recognize a number argument, the others must be strings. */ + vim_str2nr(argv[i], NULL, &len, TRUE, TRUE, &n, NULL); + if (len != 0 && len == (int)STRLEN(argv[i])) { + argvars[i].v_type = VAR_NUMBER; + argvars[i].vval.v_number = n; + } else { + argvars[i].v_type = VAR_STRING; + argvars[i].vval.v_string = argv[i]; + } + } + + if (safe) { + save_funccalp = save_funccal(); + ++sandbox; + } + + rettv->v_type = VAR_UNKNOWN; /* clear_tv() uses this */ + ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &doesrange, TRUE, NULL); + if (safe) { + --sandbox; + restore_funccal(save_funccalp); + } + vim_free(argvars); + + if (ret == FAIL) + clear_tv(rettv); + + return ret; +} + +/* + * Call vimL function "func" and return the result as a number. + * Returns -1 when calling the function fails. + * Uses argv[argc] for the function arguments. + */ +long call_func_retnr(func, argc, argv, safe) +char_u *func; +int argc; +char_u **argv; +int safe; /* use the sandbox */ +{ + typval_T rettv; + long retval; + + /* All arguments are passed as strings, no conversion to number. */ + if (call_vim_function(func, argc, argv, safe, TRUE, &rettv) == FAIL) + return -1; + + retval = get_tv_number_chk(&rettv, NULL); + clear_tv(&rettv); + return retval; +} + +#if (defined(FEAT_USR_CMDS) && defined(FEAT_CMDL_COMPL)) \ + || defined(FEAT_COMPL_FUNC) || defined(PROTO) + +/* + * Call vimL function "func" and return the result as a string. + * Returns NULL when calling the function fails. + * Uses argv[argc] for the function arguments. + */ +void * call_func_retstr(func, argc, argv, safe) +char_u *func; +int argc; +char_u **argv; +int safe; /* use the sandbox */ +{ + typval_T rettv; + char_u *retval; + + /* All arguments are passed as strings, no conversion to number. */ + if (call_vim_function(func, argc, argv, safe, TRUE, &rettv) == FAIL) + return NULL; + + retval = vim_strsave(get_tv_string(&rettv)); + clear_tv(&rettv); + return retval; +} + +/* + * Call vimL function "func" and return the result as a List. + * Uses argv[argc] for the function arguments. + * Returns NULL when there is something wrong. + */ +void * call_func_retlist(func, argc, argv, safe) +char_u *func; +int argc; +char_u **argv; +int safe; /* use the sandbox */ +{ + typval_T rettv; + + /* All arguments are passed as strings, no conversion to number. */ + if (call_vim_function(func, argc, argv, safe, TRUE, &rettv) == FAIL) + return NULL; + + if (rettv.v_type != VAR_LIST) { + clear_tv(&rettv); + return NULL; + } + + return rettv.vval.v_list; +} +#endif + +/* + * Save the current function call pointer, and set it to NULL. + * Used when executing autocommands and for ":source". + */ +void * save_funccal() { + funccall_T *fc = current_funccal; + + current_funccal = NULL; + return (void *)fc; +} + +void restore_funccal(vfc) +void *vfc; +{ + funccall_T *fc = (funccall_T *)vfc; + + current_funccal = fc; +} + +/* + * Prepare profiling for entering a child or something else that is not + * counted for the script/function itself. + * Should always be called in pair with prof_child_exit(). + */ +void prof_child_enter(tm) +proftime_T *tm; /* place to store waittime */ +{ + funccall_T *fc = current_funccal; + + if (fc != NULL && fc->func->uf_profiling) + profile_start(&fc->prof_child); + script_prof_save(tm); +} + +/* + * Take care of time spent in a child. + * Should always be called after prof_child_enter(). + */ +void prof_child_exit(tm) +proftime_T *tm; /* where waittime was stored */ +{ + funccall_T *fc = current_funccal; + + if (fc != NULL && fc->func->uf_profiling) { + profile_end(&fc->prof_child); + profile_sub_wait(tm, &fc->prof_child); /* don't count waiting time */ + profile_add(&fc->func->uf_tm_children, &fc->prof_child); + profile_add(&fc->func->uf_tml_children, &fc->prof_child); + } + script_prof_restore(tm); +} + + +/* + * Evaluate 'foldexpr'. Returns the foldlevel, and any character preceding + * it in "*cp". Doesn't give error messages. + */ +int eval_foldexpr(arg, cp) +char_u *arg; +int *cp; +{ + typval_T tv; + int retval; + char_u *s; + int use_sandbox = was_set_insecurely((char_u *)"foldexpr", + OPT_LOCAL); + + ++emsg_off; + if (use_sandbox) + ++sandbox; + ++textlock; + *cp = NUL; + if (eval0(arg, &tv, NULL, TRUE) == FAIL) + retval = 0; + else { + /* If the result is a number, just return the number. */ + if (tv.v_type == VAR_NUMBER) + retval = tv.vval.v_number; + else if (tv.v_type != VAR_STRING || tv.vval.v_string == NULL) + retval = 0; + else { + /* If the result is a string, check if there is a non-digit before + * the number. */ + s = tv.vval.v_string; + if (!VIM_ISDIGIT(*s) && *s != '-') + *cp = *s++; + retval = atol((char *)s); + } + clear_tv(&tv); + } + --emsg_off; + if (use_sandbox) + --sandbox; + --textlock; + + return retval; +} + +/* + * ":let" list all variable values + * ":let var1 var2" list variable values + * ":let var = expr" assignment command. + * ":let var += expr" assignment command. + * ":let var -= expr" assignment command. + * ":let var .= expr" assignment command. + * ":let [var1, var2] = expr" unpack list. + */ +void ex_let(eap) +exarg_T *eap; +{ + char_u *arg = eap->arg; + char_u *expr = NULL; + typval_T rettv; + int i; + int var_count = 0; + int semicolon = 0; + char_u op[2]; + char_u *argend; + int first = TRUE; + + argend = skip_var_list(arg, &var_count, &semicolon); + if (argend == NULL) + return; + if (argend > arg && argend[-1] == '.') /* for var.='str' */ + --argend; + expr = vim_strchr(argend, '='); + if (expr == NULL) { + /* + * ":let" without "=": list variables + */ + if (*arg == '[') + EMSG(_(e_invarg)); + else if (!ends_excmd(*arg)) + /* ":let var1 var2" */ + arg = list_arg_vars(eap, arg, &first); + else if (!eap->skip) { + /* ":let" */ + list_glob_vars(&first); + list_buf_vars(&first); + list_win_vars(&first); + list_tab_vars(&first); + list_script_vars(&first); + list_func_vars(&first); + list_vim_vars(&first); + } + eap->nextcmd = check_nextcmd(arg); + } else { + op[0] = '='; + op[1] = NUL; + if (expr > argend) { + if (vim_strchr((char_u *)"+-.", expr[-1]) != NULL) + op[0] = expr[-1]; /* +=, -= or .= */ + } + expr = skipwhite(expr + 1); + + if (eap->skip) + ++emsg_skip; + i = eval0(expr, &rettv, &eap->nextcmd, !eap->skip); + if (eap->skip) { + if (i != FAIL) + clear_tv(&rettv); + --emsg_skip; + } else if (i != FAIL) { + (void)ex_let_vars(eap->arg, &rettv, FALSE, semicolon, var_count, + op); + clear_tv(&rettv); + } + } +} + +/* + * Assign the typevalue "tv" to the variable or variables at "arg_start". + * Handles both "var" with any type and "[var, var; var]" with a list type. + * When "nextchars" is not NULL it points to a string with characters that + * must appear after the variable(s). Use "+", "-" or "." for add, subtract + * or concatenate. + * Returns OK or FAIL; + */ +static int ex_let_vars(arg_start, tv, copy, semicolon, var_count, nextchars) +char_u *arg_start; +typval_T *tv; +int copy; /* copy values from "tv", don't move */ +int semicolon; /* from skip_var_list() */ +int var_count; /* from skip_var_list() */ +char_u *nextchars; +{ + char_u *arg = arg_start; + list_T *l; + int i; + listitem_T *item; + typval_T ltv; + + if (*arg != '[') { + /* + * ":let var = expr" or ":for var in list" + */ + if (ex_let_one(arg, tv, copy, nextchars, nextchars) == NULL) + return FAIL; + return OK; + } + + /* + * ":let [v1, v2] = list" or ":for [v1, v2] in listlist" + */ + if (tv->v_type != VAR_LIST || (l = tv->vval.v_list) == NULL) { + EMSG(_(e_listreq)); + return FAIL; + } + + i = list_len(l); + if (semicolon == 0 && var_count < i) { + EMSG(_("E687: Less targets than List items")); + return FAIL; + } + if (var_count - semicolon > i) { + EMSG(_("E688: More targets than List items")); + return FAIL; + } + + item = l->lv_first; + while (*arg != ']') { + arg = skipwhite(arg + 1); + arg = ex_let_one(arg, &item->li_tv, TRUE, (char_u *)",;]", nextchars); + item = item->li_next; + if (arg == NULL) + return FAIL; + + arg = skipwhite(arg); + if (*arg == ';') { + /* Put the rest of the list (may be empty) in the var after ';'. + * Create a new list for this. */ + l = list_alloc(); + if (l == NULL) + return FAIL; + while (item != NULL) { + list_append_tv(l, &item->li_tv); + item = item->li_next; + } + + ltv.v_type = VAR_LIST; + ltv.v_lock = 0; + ltv.vval.v_list = l; + l->lv_refcount = 1; + + arg = ex_let_one(skipwhite(arg + 1), <v, FALSE, + (char_u *)"]", nextchars); + clear_tv(<v); + if (arg == NULL) + return FAIL; + break; + } else if (*arg != ',' && *arg != ']') { + EMSG2(_(e_intern2), "ex_let_vars()"); + return FAIL; + } + } + + return OK; +} + +/* + * Skip over assignable variable "var" or list of variables "[var, var]". + * Used for ":let varvar = expr" and ":for varvar in expr". + * For "[var, var]" increment "*var_count" for each variable. + * for "[var, var; var]" set "semicolon". + * Return NULL for an error. + */ +static char_u * skip_var_list(arg, var_count, semicolon) +char_u *arg; +int *var_count; +int *semicolon; +{ + char_u *p, *s; + + if (*arg == '[') { + /* "[var, var]": find the matching ']'. */ + p = arg; + for (;; ) { + p = skipwhite(p + 1); /* skip whites after '[', ';' or ',' */ + s = skip_var_one(p); + if (s == p) { + EMSG2(_(e_invarg2), p); + return NULL; + } + ++*var_count; + + p = skipwhite(s); + if (*p == ']') + break; + else if (*p == ';') { + if (*semicolon == 1) { + EMSG(_("Double ; in list of variables")); + return NULL; + } + *semicolon = 1; + } else if (*p != ',') { + EMSG2(_(e_invarg2), p); + return NULL; + } + } + return p + 1; + } else + return skip_var_one(arg); +} + +/* + * Skip one (assignable) variable name, including @r, $VAR, &option, d.key, + * l[idx]. + */ +static char_u * skip_var_one(arg) +char_u *arg; +{ + if (*arg == '@' && arg[1] != NUL) + return arg + 2; + return find_name_end(*arg == '$' || *arg == '&' ? arg + 1 : arg, + NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); +} + +/* + * List variables for hashtab "ht" with prefix "prefix". + * If "empty" is TRUE also list NULL strings as empty strings. + */ +static void list_hashtable_vars(ht, prefix, empty, first) +hashtab_T *ht; +char_u *prefix; +int empty; +int *first; +{ + hashitem_T *hi; + dictitem_T *di; + int todo; + + todo = (int)ht->ht_used; + for (hi = ht->ht_array; todo > 0 && !got_int; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + di = HI2DI(hi); + if (empty || di->di_tv.v_type != VAR_STRING + || di->di_tv.vval.v_string != NULL) + list_one_var(di, prefix, first); + } + } +} + +/* + * List global variables. + */ +static void list_glob_vars(first) +int *first; +{ + list_hashtable_vars(&globvarht, (char_u *)"", TRUE, first); +} + +/* + * List buffer variables. + */ +static void list_buf_vars(first) +int *first; +{ + char_u numbuf[NUMBUFLEN]; + + list_hashtable_vars(&curbuf->b_vars->dv_hashtab, (char_u *)"b:", + TRUE, first); + + sprintf((char *)numbuf, "%ld", (long)curbuf->b_changedtick); + list_one_var_a((char_u *)"b:", (char_u *)"changedtick", VAR_NUMBER, + numbuf, first); +} + +/* + * List window variables. + */ +static void list_win_vars(first) +int *first; +{ + list_hashtable_vars(&curwin->w_vars->dv_hashtab, + (char_u *)"w:", TRUE, first); +} + +/* + * List tab page variables. + */ +static void list_tab_vars(first) +int *first; +{ + list_hashtable_vars(&curtab->tp_vars->dv_hashtab, + (char_u *)"t:", TRUE, first); +} + +/* + * List Vim variables. + */ +static void list_vim_vars(first) +int *first; +{ + list_hashtable_vars(&vimvarht, (char_u *)"v:", FALSE, first); +} + +/* + * List script-local variables, if there is a script. + */ +static void list_script_vars(first) +int *first; +{ + if (current_SID > 0 && current_SID <= ga_scripts.ga_len) + list_hashtable_vars(&SCRIPT_VARS(current_SID), + (char_u *)"s:", FALSE, first); +} + +/* + * List function variables, if there is a function. + */ +static void list_func_vars(first) +int *first; +{ + if (current_funccal != NULL) + list_hashtable_vars(¤t_funccal->l_vars.dv_hashtab, + (char_u *)"l:", FALSE, first); +} + +/* + * List variables in "arg". + */ +static char_u * list_arg_vars(eap, arg, first) +exarg_T *eap; +char_u *arg; +int *first; +{ + int error = FALSE; + int len; + char_u *name; + char_u *name_start; + char_u *arg_subsc; + char_u *tofree; + typval_T tv; + + while (!ends_excmd(*arg) && !got_int) { + if (error || eap->skip) { + arg = find_name_end(arg, NULL, NULL, FNE_INCL_BR | FNE_CHECK_START); + if (!vim_iswhite(*arg) && !ends_excmd(*arg)) { + emsg_severe = TRUE; + EMSG(_(e_trailing)); + break; + } + } else { + /* get_name_len() takes care of expanding curly braces */ + name_start = name = arg; + len = get_name_len(&arg, &tofree, TRUE, TRUE); + if (len <= 0) { + /* This is mainly to keep test 49 working: when expanding + * curly braces fails overrule the exception error message. */ + if (len < 0 && !aborting()) { + emsg_severe = TRUE; + EMSG2(_(e_invarg2), arg); + break; + } + error = TRUE; + } else { + if (tofree != NULL) + name = tofree; + if (get_var_tv(name, len, &tv, TRUE, FALSE) == FAIL) + error = TRUE; + else { + /* handle d.key, l[idx], f(expr) */ + arg_subsc = arg; + if (handle_subscript(&arg, &tv, TRUE, TRUE) == FAIL) + error = TRUE; + else { + if (arg == arg_subsc && len == 2 && name[1] == ':') { + switch (*name) { + case 'g': list_glob_vars(first); break; + case 'b': list_buf_vars(first); break; + case 'w': list_win_vars(first); break; + case 't': list_tab_vars(first); break; + case 'v': list_vim_vars(first); break; + case 's': list_script_vars(first); break; + case 'l': list_func_vars(first); break; + default: + EMSG2(_("E738: Can't list variables for %s"), name); + } + } else { + char_u numbuf[NUMBUFLEN]; + char_u *tf; + int c; + char_u *s; + + s = echo_string(&tv, &tf, numbuf, 0); + c = *arg; + *arg = NUL; + list_one_var_a((char_u *)"", + arg == arg_subsc ? name : name_start, + tv.v_type, + s == NULL ? (char_u *)"" : s, + first); + *arg = c; + vim_free(tf); + } + clear_tv(&tv); + } + } + } + + vim_free(tofree); + } + + arg = skipwhite(arg); + } + + return arg; +} + +/* + * Set one item of ":let var = expr" or ":let [v1, v2] = list" to its value. + * Returns a pointer to the char just after the var name. + * Returns NULL if there is an error. + */ +static char_u * ex_let_one(arg, tv, copy, endchars, op) +char_u *arg; /* points to variable name */ +typval_T *tv; /* value to assign to variable */ +int copy; /* copy value from "tv" */ +char_u *endchars; /* valid chars after variable name or NULL */ +char_u *op; /* "+", "-", "." or NULL*/ +{ + int c1; + char_u *name; + char_u *p; + char_u *arg_end = NULL; + int len; + int opt_flags; + char_u *tofree = NULL; + + /* + * ":let $VAR = expr": Set environment variable. + */ + if (*arg == '$') { + /* Find the end of the name. */ + ++arg; + name = arg; + len = get_env_len(&arg); + if (len == 0) + EMSG2(_(e_invarg2), name - 1); + else { + if (op != NULL && (*op == '+' || *op == '-')) + EMSG2(_(e_letwrong), op); + else if (endchars != NULL + && vim_strchr(endchars, *skipwhite(arg)) == NULL) + EMSG(_(e_letunexp)); + else if (!check_secure()) { + c1 = name[len]; + name[len] = NUL; + p = get_tv_string_chk(tv); + if (p != NULL && op != NULL && *op == '.') { + int mustfree = FALSE; + char_u *s = vim_getenv(name, &mustfree); + + if (s != NULL) { + p = tofree = concat_str(s, p); + if (mustfree) + vim_free(s); + } + } + if (p != NULL) { + vim_setenv(name, p); + if (STRICMP(name, "HOME") == 0) + init_homedir(); + else if (didset_vim && STRICMP(name, "VIM") == 0) + didset_vim = FALSE; + else if (didset_vimruntime + && STRICMP(name, "VIMRUNTIME") == 0) + didset_vimruntime = FALSE; + arg_end = arg; + } + name[len] = c1; + vim_free(tofree); + } + } + } + /* + * ":let &option = expr": Set option value. + * ":let &l:option = expr": Set local option value. + * ":let &g:option = expr": Set global option value. + */ + else if (*arg == '&') { + /* Find the end of the name. */ + p = find_option_end(&arg, &opt_flags); + if (p == NULL || (endchars != NULL + && vim_strchr(endchars, *skipwhite(p)) == NULL)) + EMSG(_(e_letunexp)); + else { + long n; + int opt_type; + long numval; + char_u *stringval = NULL; + char_u *s; + + c1 = *p; + *p = NUL; + + n = get_tv_number(tv); + s = get_tv_string_chk(tv); /* != NULL if number or string */ + if (s != NULL && op != NULL && *op != '=') { + opt_type = get_option_value(arg, &numval, + &stringval, opt_flags); + if ((opt_type == 1 && *op == '.') + || (opt_type == 0 && *op != '.')) + EMSG2(_(e_letwrong), op); + else { + if (opt_type == 1) { /* number */ + if (*op == '+') + n = numval + n; + else + n = numval - n; + } else if (opt_type == 0 && stringval != NULL) { /* string */ + s = concat_str(stringval, s); + vim_free(stringval); + stringval = s; + } + } + } + if (s != NULL) { + set_option_value(arg, n, s, opt_flags); + arg_end = p; + } + *p = c1; + vim_free(stringval); + } + } + /* + * ":let @r = expr": Set register contents. + */ + else if (*arg == '@') { + ++arg; + if (op != NULL && (*op == '+' || *op == '-')) + EMSG2(_(e_letwrong), op); + else if (endchars != NULL + && vim_strchr(endchars, *skipwhite(arg + 1)) == NULL) + EMSG(_(e_letunexp)); + else { + char_u *ptofree = NULL; + char_u *s; + + p = get_tv_string_chk(tv); + if (p != NULL && op != NULL && *op == '.') { + s = get_reg_contents(*arg == '@' ? '"' : *arg, TRUE, TRUE); + if (s != NULL) { + p = ptofree = concat_str(s, p); + vim_free(s); + } + } + if (p != NULL) { + write_reg_contents(*arg == '@' ? '"' : *arg, p, -1, FALSE); + arg_end = arg + 1; + } + vim_free(ptofree); + } + } + /* + * ":let var = expr": Set internal variable. + * ":let {expr} = expr": Idem, name made with curly braces + */ + else if (eval_isnamec1(*arg) || *arg == '{') { + lval_T lv; + + p = get_lval(arg, tv, &lv, FALSE, FALSE, 0, FNE_CHECK_START); + if (p != NULL && lv.ll_name != NULL) { + if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) + EMSG(_(e_letunexp)); + else { + set_var_lval(&lv, p, tv, copy, op); + arg_end = p; + } + } + clear_lval(&lv); + } else + EMSG2(_(e_invarg2), arg); + + return arg_end; +} + +/* + * If "arg" is equal to "b:changedtick" give an error and return TRUE. + */ +static int check_changedtick(arg) +char_u *arg; +{ + if (STRNCMP(arg, "b:changedtick", 13) == 0 && !eval_isnamec(arg[13])) { + EMSG2(_(e_readonlyvar), arg); + return TRUE; + } + return FALSE; +} + +/* + * Get an lval: variable, Dict item or List item that can be assigned a value + * to: "name", "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", + * "name.key", "name.key[expr]" etc. + * Indexing only works if "name" is an existing List or Dictionary. + * "name" points to the start of the name. + * If "rettv" is not NULL it points to the value to be assigned. + * "unlet" is TRUE for ":unlet": slightly different behavior when something is + * wrong; must end in space or cmd separator. + * + * flags: + * GLV_QUIET: do not give error messages + * GLV_NO_AUTOLOAD: do not use script autoloading + * + * Returns a pointer to just after the name, including indexes. + * When an evaluation error occurs "lp->ll_name" is NULL; + * Returns NULL for a parsing error. Still need to free items in "lp"! + */ +static char_u * get_lval(name, rettv, lp, unlet, skip, flags, fne_flags) +char_u *name; +typval_T *rettv; +lval_T *lp; +int unlet; +int skip; +int flags; /* GLV_ values */ +int fne_flags; /* flags for find_name_end() */ +{ + char_u *p; + char_u *expr_start, *expr_end; + int cc; + dictitem_T *v; + typval_T var1; + typval_T var2; + int empty1 = FALSE; + listitem_T *ni; + char_u *key = NULL; + int len; + hashtab_T *ht; + int quiet = flags & GLV_QUIET; + + /* Clear everything in "lp". */ + vim_memset(lp, 0, sizeof(lval_T)); + + if (skip) { + /* When skipping just find the end of the name. */ + lp->ll_name = name; + return find_name_end(name, NULL, NULL, FNE_INCL_BR | fne_flags); + } + + /* Find the end of the name. */ + p = find_name_end(name, &expr_start, &expr_end, fne_flags); + if (expr_start != NULL) { + /* Don't expand the name when we already know there is an error. */ + if (unlet && !vim_iswhite(*p) && !ends_excmd(*p) + && *p != '[' && *p != '.') { + EMSG(_(e_trailing)); + return NULL; + } + + lp->ll_exp_name = make_expanded_name(name, expr_start, expr_end, p); + if (lp->ll_exp_name == NULL) { + /* Report an invalid expression in braces, unless the + * expression evaluation has been cancelled due to an + * aborting error, an interrupt, or an exception. */ + if (!aborting() && !quiet) { + emsg_severe = TRUE; + EMSG2(_(e_invarg2), name); + return NULL; + } + } + lp->ll_name = lp->ll_exp_name; + } else + lp->ll_name = name; + + /* Without [idx] or .key we are done. */ + if ((*p != '[' && *p != '.') || lp->ll_name == NULL) + return p; + + cc = *p; + *p = NUL; + v = find_var(lp->ll_name, &ht, flags & GLV_NO_AUTOLOAD); + if (v == NULL && !quiet) + EMSG2(_(e_undefvar), lp->ll_name); + *p = cc; + if (v == NULL) + return NULL; + + /* + * Loop until no more [idx] or .key is following. + */ + lp->ll_tv = &v->di_tv; + while (*p == '[' || (*p == '.' && lp->ll_tv->v_type == VAR_DICT)) { + if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL) + && !(lp->ll_tv->v_type == VAR_DICT + && lp->ll_tv->vval.v_dict != NULL)) { + if (!quiet) + EMSG(_("E689: Can only index a List or Dictionary")); + return NULL; + } + if (lp->ll_range) { + if (!quiet) + EMSG(_("E708: [:] must come last")); + return NULL; + } + + len = -1; + if (*p == '.') { + key = p + 1; + for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; ++len) + ; + if (len == 0) { + if (!quiet) + EMSG(_(e_emptykey)); + return NULL; + } + p = key + len; + } else { + /* Get the index [expr] or the first index [expr: ]. */ + p = skipwhite(p + 1); + if (*p == ':') + empty1 = TRUE; + else { + empty1 = FALSE; + if (eval1(&p, &var1, TRUE) == FAIL) /* recursive! */ + return NULL; + if (get_tv_string_chk(&var1) == NULL) { + /* not a number or string */ + clear_tv(&var1); + return NULL; + } + } + + /* Optionally get the second index [ :expr]. */ + if (*p == ':') { + if (lp->ll_tv->v_type == VAR_DICT) { + if (!quiet) + EMSG(_(e_dictrange)); + if (!empty1) + clear_tv(&var1); + return NULL; + } + if (rettv != NULL && (rettv->v_type != VAR_LIST + || rettv->vval.v_list == NULL)) { + if (!quiet) + EMSG(_("E709: [:] requires a List value")); + if (!empty1) + clear_tv(&var1); + return NULL; + } + p = skipwhite(p + 1); + if (*p == ']') + lp->ll_empty2 = TRUE; + else { + lp->ll_empty2 = FALSE; + if (eval1(&p, &var2, TRUE) == FAIL) { /* recursive! */ + if (!empty1) + clear_tv(&var1); + return NULL; + } + if (get_tv_string_chk(&var2) == NULL) { + /* not a number or string */ + if (!empty1) + clear_tv(&var1); + clear_tv(&var2); + return NULL; + } + } + lp->ll_range = TRUE; + } else + lp->ll_range = FALSE; + + if (*p != ']') { + if (!quiet) + EMSG(_(e_missbrac)); + if (!empty1) + clear_tv(&var1); + if (lp->ll_range && !lp->ll_empty2) + clear_tv(&var2); + return NULL; + } + + /* Skip to past ']'. */ + ++p; + } + + if (lp->ll_tv->v_type == VAR_DICT) { + if (len == -1) { + /* "[key]": get key from "var1" */ + key = get_tv_string(&var1); /* is number or string */ + if (*key == NUL) { + if (!quiet) + EMSG(_(e_emptykey)); + clear_tv(&var1); + return NULL; + } + } + lp->ll_list = NULL; + lp->ll_dict = lp->ll_tv->vval.v_dict; + lp->ll_di = dict_find(lp->ll_dict, key, len); + + /* When assigning to a scope dictionary check that a function and + * variable name is valid (only variable name unless it is l: or + * g: dictionary). Disallow overwriting a builtin function. */ + if (rettv != NULL && lp->ll_dict->dv_scope != 0) { + int prevval; + int wrong; + + if (len != -1) { + prevval = key[len]; + key[len] = NUL; + } else + prevval = 0; /* avoid compiler warning */ + wrong = (lp->ll_dict->dv_scope == VAR_DEF_SCOPE + && rettv->v_type == VAR_FUNC + && var_check_func_name(key, lp->ll_di == NULL)) + || !valid_varname(key); + if (len != -1) + key[len] = prevval; + if (wrong) + return NULL; + } + + if (lp->ll_di == NULL) { + /* Can't add "v:" variable. */ + if (lp->ll_dict == &vimvardict) { + EMSG2(_(e_illvar), name); + return NULL; + } + + /* Key does not exist in dict: may need to add it. */ + if (*p == '[' || *p == '.' || unlet) { + if (!quiet) + EMSG2(_(e_dictkey), key); + if (len == -1) + clear_tv(&var1); + return NULL; + } + if (len == -1) + lp->ll_newkey = vim_strsave(key); + else + lp->ll_newkey = vim_strnsave(key, len); + if (len == -1) + clear_tv(&var1); + if (lp->ll_newkey == NULL) + p = NULL; + break; + } + /* existing variable, need to check if it can be changed */ + else if (var_check_ro(lp->ll_di->di_flags, name)) + return NULL; + + if (len == -1) + clear_tv(&var1); + lp->ll_tv = &lp->ll_di->di_tv; + } else { + /* + * Get the number and item for the only or first index of the List. + */ + if (empty1) + lp->ll_n1 = 0; + else { + lp->ll_n1 = get_tv_number(&var1); /* is number or string */ + clear_tv(&var1); + } + lp->ll_dict = NULL; + lp->ll_list = lp->ll_tv->vval.v_list; + lp->ll_li = list_find(lp->ll_list, lp->ll_n1); + if (lp->ll_li == NULL) { + if (lp->ll_n1 < 0) { + lp->ll_n1 = 0; + lp->ll_li = list_find(lp->ll_list, lp->ll_n1); + } + } + if (lp->ll_li == NULL) { + if (lp->ll_range && !lp->ll_empty2) + clear_tv(&var2); + if (!quiet) + EMSGN(_(e_listidx), lp->ll_n1); + return NULL; + } + + /* + * May need to find the item or absolute index for the second + * index of a range. + * When no index given: "lp->ll_empty2" is TRUE. + * Otherwise "lp->ll_n2" is set to the second index. + */ + if (lp->ll_range && !lp->ll_empty2) { + lp->ll_n2 = get_tv_number(&var2); /* is number or string */ + clear_tv(&var2); + if (lp->ll_n2 < 0) { + ni = list_find(lp->ll_list, lp->ll_n2); + if (ni == NULL) { + if (!quiet) + EMSGN(_(e_listidx), lp->ll_n2); + return NULL; + } + lp->ll_n2 = list_idx_of_item(lp->ll_list, ni); + } + + /* Check that lp->ll_n2 isn't before lp->ll_n1. */ + if (lp->ll_n1 < 0) + lp->ll_n1 = list_idx_of_item(lp->ll_list, lp->ll_li); + if (lp->ll_n2 < lp->ll_n1) { + if (!quiet) + EMSGN(_(e_listidx), lp->ll_n2); + return NULL; + } + } + + lp->ll_tv = &lp->ll_li->li_tv; + } + } + + return p; +} + +/* + * Clear lval "lp" that was filled by get_lval(). + */ +static void clear_lval(lp) +lval_T *lp; +{ + vim_free(lp->ll_exp_name); + vim_free(lp->ll_newkey); +} + +/* + * Set a variable that was parsed by get_lval() to "rettv". + * "endp" points to just after the parsed name. + * "op" is NULL, "+" for "+=", "-" for "-=", "." for ".=" or "=" for "=". + */ +static void set_var_lval(lp, endp, rettv, copy, op) +lval_T *lp; +char_u *endp; +typval_T *rettv; +int copy; +char_u *op; +{ + int cc; + listitem_T *ri; + dictitem_T *di; + + if (lp->ll_tv == NULL) { + if (!check_changedtick(lp->ll_name)) { + cc = *endp; + *endp = NUL; + if (op != NULL && *op != '=') { + typval_T tv; + + /* handle +=, -= and .= */ + if (get_var_tv(lp->ll_name, (int)STRLEN(lp->ll_name), + &tv, TRUE, FALSE) == OK) { + if (tv_op(&tv, rettv, op) == OK) + set_var(lp->ll_name, &tv, FALSE); + clear_tv(&tv); + } + } else + set_var(lp->ll_name, rettv, copy); + *endp = cc; + } + } else if (tv_check_lock(lp->ll_newkey == NULL + ? lp->ll_tv->v_lock + : lp->ll_tv->vval.v_dict->dv_lock, lp->ll_name)) + ; + else if (lp->ll_range) { + /* + * Assign the List values to the list items. + */ + for (ri = rettv->vval.v_list->lv_first; ri != NULL; ) { + if (op != NULL && *op != '=') + tv_op(&lp->ll_li->li_tv, &ri->li_tv, op); + else { + clear_tv(&lp->ll_li->li_tv); + copy_tv(&ri->li_tv, &lp->ll_li->li_tv); + } + ri = ri->li_next; + if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) + break; + if (lp->ll_li->li_next == NULL) { + /* Need to add an empty item. */ + if (list_append_number(lp->ll_list, 0) == FAIL) { + ri = NULL; + break; + } + } + lp->ll_li = lp->ll_li->li_next; + ++lp->ll_n1; + } + if (ri != NULL) + EMSG(_("E710: List value has more items than target")); + else if (lp->ll_empty2 + ? (lp->ll_li != NULL && lp->ll_li->li_next != NULL) + : lp->ll_n1 != lp->ll_n2) + EMSG(_("E711: List value has not enough items")); + } else { + /* + * Assign to a List or Dictionary item. + */ + if (lp->ll_newkey != NULL) { + if (op != NULL && *op != '=') { + EMSG2(_(e_letwrong), op); + return; + } + + /* Need to add an item to the Dictionary. */ + di = dictitem_alloc(lp->ll_newkey); + if (di == NULL) + return; + if (dict_add(lp->ll_tv->vval.v_dict, di) == FAIL) { + vim_free(di); + return; + } + lp->ll_tv = &di->di_tv; + } else if (op != NULL && *op != '=') { + tv_op(lp->ll_tv, rettv, op); + return; + } else + clear_tv(lp->ll_tv); + + /* + * Assign the value to the variable or list item. + */ + if (copy) + copy_tv(rettv, lp->ll_tv); + else { + *lp->ll_tv = *rettv; + lp->ll_tv->v_lock = 0; + init_tv(rettv); + } + } +} + +/* + * Handle "tv1 += tv2", "tv1 -= tv2" and "tv1 .= tv2" + * Returns OK or FAIL. + */ +static int tv_op(tv1, tv2, op) +typval_T *tv1; +typval_T *tv2; +char_u *op; +{ + long n; + char_u numbuf[NUMBUFLEN]; + char_u *s; + + /* Can't do anything with a Funcref or a Dict on the right. */ + if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT) { + switch (tv1->v_type) { + case VAR_DICT: + case VAR_FUNC: + break; + + case VAR_LIST: + if (*op != '+' || tv2->v_type != VAR_LIST) + break; + /* List += List */ + if (tv1->vval.v_list != NULL && tv2->vval.v_list != NULL) + list_extend(tv1->vval.v_list, tv2->vval.v_list, NULL); + return OK; + + case VAR_NUMBER: + case VAR_STRING: + if (tv2->v_type == VAR_LIST) + break; + if (*op == '+' || *op == '-') { + /* nr += nr or nr -= nr*/ + n = get_tv_number(tv1); + if (tv2->v_type == VAR_FLOAT) { + float_T f = n; + + if (*op == '+') + f += tv2->vval.v_float; + else + f -= tv2->vval.v_float; + clear_tv(tv1); + tv1->v_type = VAR_FLOAT; + tv1->vval.v_float = f; + } else { + if (*op == '+') + n += get_tv_number(tv2); + else + n -= get_tv_number(tv2); + clear_tv(tv1); + tv1->v_type = VAR_NUMBER; + tv1->vval.v_number = n; + } + } else { + if (tv2->v_type == VAR_FLOAT) + break; + + /* str .= str */ + s = get_tv_string(tv1); + s = concat_str(s, get_tv_string_buf(tv2, numbuf)); + clear_tv(tv1); + tv1->v_type = VAR_STRING; + tv1->vval.v_string = s; + } + return OK; + + case VAR_FLOAT: + { + float_T f; + + if (*op == '.' || (tv2->v_type != VAR_FLOAT + && tv2->v_type != VAR_NUMBER + && tv2->v_type != VAR_STRING)) + break; + if (tv2->v_type == VAR_FLOAT) + f = tv2->vval.v_float; + else + f = get_tv_number(tv2); + if (*op == '+') + tv1->vval.v_float += f; + else + tv1->vval.v_float -= f; + } + return OK; + } + } + + EMSG2(_(e_letwrong), op); + return FAIL; +} + +/* + * Add a watcher to a list. + */ +void list_add_watch(l, lw) +list_T *l; +listwatch_T *lw; +{ + lw->lw_next = l->lv_watch; + l->lv_watch = lw; +} + +/* + * Remove a watcher from a list. + * No warning when it isn't found... + */ +void list_rem_watch(l, lwrem) +list_T *l; +listwatch_T *lwrem; +{ + listwatch_T *lw, **lwp; + + lwp = &l->lv_watch; + for (lw = l->lv_watch; lw != NULL; lw = lw->lw_next) { + if (lw == lwrem) { + *lwp = lw->lw_next; + break; + } + lwp = &lw->lw_next; + } +} + +/* + * Just before removing an item from a list: advance watchers to the next + * item. + */ +static void list_fix_watch(l, item) +list_T *l; +listitem_T *item; +{ + listwatch_T *lw; + + for (lw = l->lv_watch; lw != NULL; lw = lw->lw_next) + if (lw->lw_item == item) + lw->lw_item = item->li_next; +} + +/* + * Evaluate the expression used in a ":for var in expr" command. + * "arg" points to "var". + * Set "*errp" to TRUE for an error, FALSE otherwise; + * Return a pointer that holds the info. Null when there is an error. + */ +void * eval_for_line(arg, errp, nextcmdp, skip) +char_u *arg; +int *errp; +char_u **nextcmdp; +int skip; +{ + forinfo_T *fi; + char_u *expr; + typval_T tv; + list_T *l; + + *errp = TRUE; /* default: there is an error */ + + fi = (forinfo_T *)alloc_clear(sizeof(forinfo_T)); + if (fi == NULL) + return NULL; + + expr = skip_var_list(arg, &fi->fi_varcount, &fi->fi_semicolon); + if (expr == NULL) + return fi; + + expr = skipwhite(expr); + if (expr[0] != 'i' || expr[1] != 'n' || !vim_iswhite(expr[2])) { + EMSG(_("E690: Missing \"in\" after :for")); + return fi; + } + + if (skip) + ++emsg_skip; + if (eval0(skipwhite(expr + 2), &tv, nextcmdp, !skip) == OK) { + *errp = FALSE; + if (!skip) { + l = tv.vval.v_list; + if (tv.v_type != VAR_LIST || l == NULL) { + EMSG(_(e_listreq)); + clear_tv(&tv); + } else { + /* No need to increment the refcount, it's already set for the + * list being used in "tv". */ + fi->fi_list = l; + list_add_watch(l, &fi->fi_lw); + fi->fi_lw.lw_item = l->lv_first; + } + } + } + if (skip) + --emsg_skip; + + return fi; +} + +/* + * Use the first item in a ":for" list. Advance to the next. + * Assign the values to the variable (list). "arg" points to the first one. + * Return TRUE when a valid item was found, FALSE when at end of list or + * something wrong. + */ +int next_for_item(fi_void, arg) +void *fi_void; +char_u *arg; +{ + forinfo_T *fi = (forinfo_T *)fi_void; + int result; + listitem_T *item; + + item = fi->fi_lw.lw_item; + if (item == NULL) + result = FALSE; + else { + fi->fi_lw.lw_item = item->li_next; + result = (ex_let_vars(arg, &item->li_tv, TRUE, + fi->fi_semicolon, fi->fi_varcount, NULL) == OK); + } + return result; +} + +/* + * Free the structure used to store info used by ":for". + */ +void free_for_info(fi_void) +void *fi_void; +{ + forinfo_T *fi = (forinfo_T *)fi_void; + + if (fi != NULL && fi->fi_list != NULL) { + list_rem_watch(fi->fi_list, &fi->fi_lw); + list_unref(fi->fi_list); + } + vim_free(fi); +} + + +void set_context_for_expression(xp, arg, cmdidx) +expand_T *xp; +char_u *arg; +cmdidx_T cmdidx; +{ + int got_eq = FALSE; + int c; + char_u *p; + + if (cmdidx == CMD_let) { + xp->xp_context = EXPAND_USER_VARS; + if (vim_strpbrk(arg, (char_u *)"\"'+-*/%.=!?~|&$([<>,#") == NULL) { + /* ":let var1 var2 ...": find last space. */ + for (p = arg + STRLEN(arg); p >= arg; ) { + xp->xp_pattern = p; + mb_ptr_back(arg, p); + if (vim_iswhite(*p)) + break; + } + return; + } + } else + xp->xp_context = cmdidx == CMD_call ? EXPAND_FUNCTIONS + : EXPAND_EXPRESSION; + while ((xp->xp_pattern = vim_strpbrk(arg, + (char_u *)"\"'+-*/%.=!?~|&$([<>,#")) != NULL) { + c = *xp->xp_pattern; + if (c == '&') { + c = xp->xp_pattern[1]; + if (c == '&') { + ++xp->xp_pattern; + xp->xp_context = cmdidx != CMD_let || got_eq + ? EXPAND_EXPRESSION : EXPAND_NOTHING; + } else if (c != ' ') { + xp->xp_context = EXPAND_SETTINGS; + if ((c == 'l' || c == 'g') && xp->xp_pattern[2] == ':') + xp->xp_pattern += 2; + + } + } else if (c == '$') { + /* environment variable */ + xp->xp_context = EXPAND_ENV_VARS; + } else if (c == '=') { + got_eq = TRUE; + xp->xp_context = EXPAND_EXPRESSION; + } else if (c == '<' + && xp->xp_context == EXPAND_FUNCTIONS + && vim_strchr(xp->xp_pattern, '(') == NULL) { + /* Function name can start with "" */ + break; + } else if (cmdidx != CMD_let || got_eq) { + if (c == '"') { /* string */ + while ((c = *++xp->xp_pattern) != NUL && c != '"') + if (c == '\\' && xp->xp_pattern[1] != NUL) + ++xp->xp_pattern; + xp->xp_context = EXPAND_NOTHING; + } else if (c == '\'') { /* literal string */ + /* Trick: '' is like stopping and starting a literal string. */ + while ((c = *++xp->xp_pattern) != NUL && c != '\'') + /* skip */; + xp->xp_context = EXPAND_NOTHING; + } else if (c == '|') { + if (xp->xp_pattern[1] == '|') { + ++xp->xp_pattern; + xp->xp_context = EXPAND_EXPRESSION; + } else + xp->xp_context = EXPAND_COMMANDS; + } else + xp->xp_context = EXPAND_EXPRESSION; + } else + /* Doesn't look like something valid, expand as an expression + * anyway. */ + xp->xp_context = EXPAND_EXPRESSION; + arg = xp->xp_pattern; + if (*arg != NUL) + while ((c = *++arg) != NUL && (c == ' ' || c == '\t')) + /* skip */; + } + xp->xp_pattern = arg; +} + + +/* + * ":1,25call func(arg1, arg2)" function call. + */ +void ex_call(eap) +exarg_T *eap; +{ + char_u *arg = eap->arg; + char_u *startarg; + char_u *name; + char_u *tofree; + int len; + typval_T rettv; + linenr_T lnum; + int doesrange; + int failed = FALSE; + funcdict_T fudi; + + if (eap->skip) { + /* trans_function_name() doesn't work well when skipping, use eval0() + * instead to skip to any following command, e.g. for: + * :if 0 | call dict.foo().bar() | endif */ + ++emsg_skip; + if (eval0(eap->arg, &rettv, &eap->nextcmd, FALSE) != FAIL) + clear_tv(&rettv); + --emsg_skip; + return; + } + + tofree = trans_function_name(&arg, eap->skip, TFN_INT, &fudi); + if (fudi.fd_newkey != NULL) { + /* Still need to give an error message for missing key. */ + EMSG2(_(e_dictkey), fudi.fd_newkey); + vim_free(fudi.fd_newkey); + } + if (tofree == NULL) + return; + + /* Increase refcount on dictionary, it could get deleted when evaluating + * the arguments. */ + if (fudi.fd_dict != NULL) + ++fudi.fd_dict->dv_refcount; + + /* If it is the name of a variable of type VAR_FUNC use its contents. */ + len = (int)STRLEN(tofree); + name = deref_func_name(tofree, &len, FALSE); + + /* Skip white space to allow ":call func ()". Not good, but required for + * backward compatibility. */ + startarg = skipwhite(arg); + rettv.v_type = VAR_UNKNOWN; /* clear_tv() uses this */ + + if (*startarg != '(') { + EMSG2(_("E107: Missing parentheses: %s"), eap->arg); + goto end; + } + + /* + * When skipping, evaluate the function once, to find the end of the + * arguments. + * When the function takes a range, this is discovered after the first + * call, and the loop is broken. + */ + if (eap->skip) { + ++emsg_skip; + lnum = eap->line2; /* do it once, also with an invalid range */ + } else + lnum = eap->line1; + for (; lnum <= eap->line2; ++lnum) { + if (!eap->skip && eap->addr_count > 0) { + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + } + arg = startarg; + if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg, + eap->line1, eap->line2, &doesrange, + !eap->skip, fudi.fd_dict) == FAIL) { + failed = TRUE; + break; + } + + /* Handle a function returning a Funcref, Dictionary or List. */ + if (handle_subscript(&arg, &rettv, !eap->skip, TRUE) == FAIL) { + failed = TRUE; + break; + } + + clear_tv(&rettv); + if (doesrange || eap->skip) + break; + + /* Stop when immediately aborting on error, or when an interrupt + * occurred or an exception was thrown but not caught. + * get_func_tv() returned OK, so that the check for trailing + * characters below is executed. */ + if (aborting()) + break; + } + if (eap->skip) + --emsg_skip; + + if (!failed) { + /* Check for trailing illegal characters and a following command. */ + if (!ends_excmd(*arg)) { + emsg_severe = TRUE; + EMSG(_(e_trailing)); + } else + eap->nextcmd = check_nextcmd(arg); + } + +end: + dict_unref(fudi.fd_dict); + vim_free(tofree); +} + +/* + * ":unlet[!] var1 ... " command. + */ +void ex_unlet(eap) +exarg_T *eap; +{ + ex_unletlock(eap, eap->arg, 0); +} + +/* + * ":lockvar" and ":unlockvar" commands + */ +void ex_lockvar(eap) +exarg_T *eap; +{ + char_u *arg = eap->arg; + int deep = 2; + + if (eap->forceit) + deep = -1; + else if (vim_isdigit(*arg)) { + deep = getdigits(&arg); + arg = skipwhite(arg); + } + + ex_unletlock(eap, arg, deep); +} + +/* + * ":unlet", ":lockvar" and ":unlockvar" are quite similar. + */ +static void ex_unletlock(eap, argstart, deep) +exarg_T *eap; +char_u *argstart; +int deep; +{ + char_u *arg = argstart; + char_u *name_end; + int error = FALSE; + lval_T lv; + + do { + /* Parse the name and find the end. */ + name_end = get_lval(arg, NULL, &lv, TRUE, eap->skip || error, 0, + FNE_CHECK_START); + if (lv.ll_name == NULL) + error = TRUE; /* error but continue parsing */ + if (name_end == NULL || (!vim_iswhite(*name_end) + && !ends_excmd(*name_end))) { + if (name_end != NULL) { + emsg_severe = TRUE; + EMSG(_(e_trailing)); + } + if (!(eap->skip || error)) + clear_lval(&lv); + break; + } + + if (!error && !eap->skip) { + if (eap->cmdidx == CMD_unlet) { + if (do_unlet_var(&lv, name_end, eap->forceit) == FAIL) + error = TRUE; + } else { + if (do_lock_var(&lv, name_end, deep, + eap->cmdidx == CMD_lockvar) == FAIL) + error = TRUE; + } + } + + if (!eap->skip) + clear_lval(&lv); + + arg = skipwhite(name_end); + } while (!ends_excmd(*arg)); + + eap->nextcmd = check_nextcmd(arg); +} + +static int do_unlet_var(lp, name_end, forceit) +lval_T *lp; +char_u *name_end; +int forceit; +{ + int ret = OK; + int cc; + + if (lp->ll_tv == NULL) { + cc = *name_end; + *name_end = NUL; + + /* Normal name or expanded name. */ + if (check_changedtick(lp->ll_name)) + ret = FAIL; + else if (do_unlet(lp->ll_name, forceit) == FAIL) + ret = FAIL; + *name_end = cc; + } else if (tv_check_lock(lp->ll_tv->v_lock, lp->ll_name)) + return FAIL; + else if (lp->ll_range) { + listitem_T *li; + + /* Delete a range of List items. */ + while (lp->ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { + li = lp->ll_li->li_next; + listitem_remove(lp->ll_list, lp->ll_li); + lp->ll_li = li; + ++lp->ll_n1; + } + } else { + if (lp->ll_list != NULL) + /* unlet a List item. */ + listitem_remove(lp->ll_list, lp->ll_li); + else + /* unlet a Dictionary item. */ + dictitem_remove(lp->ll_dict, lp->ll_di); + } + + return ret; +} + +/* + * "unlet" a variable. Return OK if it existed, FAIL if not. + * When "forceit" is TRUE don't complain if the variable doesn't exist. + */ +int do_unlet(name, forceit) +char_u *name; +int forceit; +{ + hashtab_T *ht; + hashitem_T *hi; + char_u *varname; + dictitem_T *di; + + ht = find_var_ht(name, &varname); + if (ht != NULL && *varname != NUL) { + hi = hash_find(ht, varname); + if (!HASHITEM_EMPTY(hi)) { + di = HI2DI(hi); + if (var_check_fixed(di->di_flags, name) + || var_check_ro(di->di_flags, name)) + return FAIL; + delete_var(ht, hi); + return OK; + } + } + if (forceit) + return OK; + EMSG2(_("E108: No such variable: \"%s\""), name); + return FAIL; +} + +/* + * Lock or unlock variable indicated by "lp". + * "deep" is the levels to go (-1 for unlimited); + * "lock" is TRUE for ":lockvar", FALSE for ":unlockvar". + */ +static int do_lock_var(lp, name_end, deep, lock) +lval_T *lp; +char_u *name_end; +int deep; +int lock; +{ + int ret = OK; + int cc; + dictitem_T *di; + + if (deep == 0) /* nothing to do */ + return OK; + + if (lp->ll_tv == NULL) { + cc = *name_end; + *name_end = NUL; + + /* Normal name or expanded name. */ + if (check_changedtick(lp->ll_name)) + ret = FAIL; + else { + di = find_var(lp->ll_name, NULL, TRUE); + if (di == NULL) + ret = FAIL; + else { + if (lock) + di->di_flags |= DI_FLAGS_LOCK; + else + di->di_flags &= ~DI_FLAGS_LOCK; + item_lock(&di->di_tv, deep, lock); + } + } + *name_end = cc; + } else if (lp->ll_range) { + listitem_T *li = lp->ll_li; + + /* (un)lock a range of List items. */ + while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { + item_lock(&li->li_tv, deep, lock); + li = li->li_next; + ++lp->ll_n1; + } + } else if (lp->ll_list != NULL) + /* (un)lock a List item. */ + item_lock(&lp->ll_li->li_tv, deep, lock); + else + /* un(lock) a Dictionary item. */ + item_lock(&lp->ll_di->di_tv, deep, lock); + + return ret; +} + +/* + * Lock or unlock an item. "deep" is nr of levels to go. + */ +static void item_lock(tv, deep, lock) +typval_T *tv; +int deep; +int lock; +{ + static int recurse = 0; + list_T *l; + listitem_T *li; + dict_T *d; + hashitem_T *hi; + int todo; + + if (recurse >= DICT_MAXNEST) { + EMSG(_("E743: variable nested too deep for (un)lock")); + return; + } + if (deep == 0) + return; + ++recurse; + + /* lock/unlock the item itself */ + if (lock) + tv->v_lock |= VAR_LOCKED; + else + tv->v_lock &= ~VAR_LOCKED; + + switch (tv->v_type) { + case VAR_LIST: + if ((l = tv->vval.v_list) != NULL) { + if (lock) + l->lv_lock |= VAR_LOCKED; + else + l->lv_lock &= ~VAR_LOCKED; + if (deep < 0 || deep > 1) + /* recursive: lock/unlock the items the List contains */ + for (li = l->lv_first; li != NULL; li = li->li_next) + item_lock(&li->li_tv, deep - 1, lock); + } + break; + case VAR_DICT: + if ((d = tv->vval.v_dict) != NULL) { + if (lock) + d->dv_lock |= VAR_LOCKED; + else + d->dv_lock &= ~VAR_LOCKED; + if (deep < 0 || deep > 1) { + /* recursive: lock/unlock the items the List contains */ + todo = (int)d->dv_hashtab.ht_used; + for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + item_lock(&HI2DI(hi)->di_tv, deep - 1, lock); + } + } + } + } + } + --recurse; +} + +/* + * Return TRUE if typeval "tv" is locked: Either that value is locked itself + * or it refers to a List or Dictionary that is locked. + */ +static int tv_islocked(tv) +typval_T *tv; +{ + return (tv->v_lock & VAR_LOCKED) + || (tv->v_type == VAR_LIST + && tv->vval.v_list != NULL + && (tv->vval.v_list->lv_lock & VAR_LOCKED)) + || (tv->v_type == VAR_DICT + && tv->vval.v_dict != NULL + && (tv->vval.v_dict->dv_lock & VAR_LOCKED)); +} + +/* + * Delete all "menutrans_" variables. + */ +void del_menutrans_vars() { + hashitem_T *hi; + int todo; + + hash_lock(&globvarht); + todo = (int)globvarht.ht_used; + for (hi = globvarht.ht_array; todo > 0 && !got_int; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + if (STRNCMP(HI2DI(hi)->di_key, "menutrans_", 10) == 0) + delete_var(&globvarht, hi); + } + } + hash_unlock(&globvarht); +} + +/* + * Local string buffer for the next two functions to store a variable name + * with its prefix. Allocated in cat_prefix_varname(), freed later in + * get_user_var_name(). + */ + +static char_u *cat_prefix_varname __ARGS((int prefix, char_u *name)); + +static char_u *varnamebuf = NULL; +static int varnamebuflen = 0; + +/* + * Function to concatenate a prefix and a variable name. + */ +static char_u * cat_prefix_varname(prefix, name) +int prefix; +char_u *name; +{ + int len; + + len = (int)STRLEN(name) + 3; + if (len > varnamebuflen) { + vim_free(varnamebuf); + len += 10; /* some additional space */ + varnamebuf = alloc(len); + if (varnamebuf == NULL) { + varnamebuflen = 0; + return NULL; + } + varnamebuflen = len; + } + *varnamebuf = prefix; + varnamebuf[1] = ':'; + STRCPY(varnamebuf + 2, name); + return varnamebuf; +} + +/* + * Function given to ExpandGeneric() to obtain the list of user defined + * (global/buffer/window/built-in) variable names. + */ +char_u * get_user_var_name(xp, idx) +expand_T *xp; +int idx; +{ + static long_u gdone; + static long_u bdone; + static long_u wdone; + static long_u tdone; + static int vidx; + static hashitem_T *hi; + hashtab_T *ht; + + if (idx == 0) { + gdone = bdone = wdone = vidx = 0; + tdone = 0; + } + + /* Global variables */ + if (gdone < globvarht.ht_used) { + if (gdone++ == 0) + hi = globvarht.ht_array; + else + ++hi; + while (HASHITEM_EMPTY(hi)) + ++hi; + if (STRNCMP("g:", xp->xp_pattern, 2) == 0) + return cat_prefix_varname('g', hi->hi_key); + return hi->hi_key; + } + + /* b: variables */ + ht = &curbuf->b_vars->dv_hashtab; + if (bdone < ht->ht_used) { + if (bdone++ == 0) + hi = ht->ht_array; + else + ++hi; + while (HASHITEM_EMPTY(hi)) + ++hi; + return cat_prefix_varname('b', hi->hi_key); + } + if (bdone == ht->ht_used) { + ++bdone; + return (char_u *)"b:changedtick"; + } + + /* w: variables */ + ht = &curwin->w_vars->dv_hashtab; + if (wdone < ht->ht_used) { + if (wdone++ == 0) + hi = ht->ht_array; + else + ++hi; + while (HASHITEM_EMPTY(hi)) + ++hi; + return cat_prefix_varname('w', hi->hi_key); + } + + /* t: variables */ + ht = &curtab->tp_vars->dv_hashtab; + if (tdone < ht->ht_used) { + if (tdone++ == 0) + hi = ht->ht_array; + else + ++hi; + while (HASHITEM_EMPTY(hi)) + ++hi; + return cat_prefix_varname('t', hi->hi_key); + } + + /* v: variables */ + if (vidx < VV_LEN) + return cat_prefix_varname('v', (char_u *)vimvars[vidx++].vv_name); + + vim_free(varnamebuf); + varnamebuf = NULL; + varnamebuflen = 0; + return NULL; +} + + +/* + * types for expressions. + */ +typedef enum { + TYPE_UNKNOWN = 0 + , TYPE_EQUAL /* == */ + , TYPE_NEQUAL /* != */ + , TYPE_GREATER /* > */ + , TYPE_GEQUAL /* >= */ + , TYPE_SMALLER /* < */ + , TYPE_SEQUAL /* <= */ + , TYPE_MATCH /* =~ */ + , TYPE_NOMATCH /* !~ */ +} exptype_T; + +/* + * The "evaluate" argument: When FALSE, the argument is only parsed but not + * executed. The function may return OK, but the rettv will be of type + * VAR_UNKNOWN. The function still returns FAIL for a syntax error. + */ + +/* + * Handle zero level expression. + * This calls eval1() and handles error message and nextcmd. + * Put the result in "rettv" when returning OK and "evaluate" is TRUE. + * Note: "rettv.v_lock" is not set. + * Return OK or FAIL. + */ +static int eval0(arg, rettv, nextcmd, evaluate) +char_u *arg; +typval_T *rettv; +char_u **nextcmd; +int evaluate; +{ + int ret; + char_u *p; + + p = skipwhite(arg); + ret = eval1(&p, rettv, evaluate); + if (ret == FAIL || !ends_excmd(*p)) { + if (ret != FAIL) + clear_tv(rettv); + /* + * Report the invalid expression unless the expression evaluation has + * been cancelled due to an aborting error, an interrupt, or an + * exception. + */ + if (!aborting()) + EMSG2(_(e_invexpr2), arg); + ret = FAIL; + } + if (nextcmd != NULL) + *nextcmd = check_nextcmd(p); + + return ret; +} + +/* + * Handle top level expression: + * expr2 ? expr1 : expr1 + * + * "arg" must point to the first non-white of the expression. + * "arg" is advanced to the next non-white after the recognized expression. + * + * Note: "rettv.v_lock" is not set. + * + * Return OK or FAIL. + */ +static int eval1(arg, rettv, evaluate) +char_u **arg; +typval_T *rettv; +int evaluate; +{ + int result; + typval_T var2; + + /* + * Get the first variable. + */ + if (eval2(arg, rettv, evaluate) == FAIL) + return FAIL; + + if ((*arg)[0] == '?') { + result = FALSE; + if (evaluate) { + int error = FALSE; + + if (get_tv_number_chk(rettv, &error) != 0) + result = TRUE; + clear_tv(rettv); + if (error) + return FAIL; + } + + /* + * Get the second variable. + */ + *arg = skipwhite(*arg + 1); + if (eval1(arg, rettv, evaluate && result) == FAIL) /* recursive! */ + return FAIL; + + /* + * Check for the ":". + */ + if ((*arg)[0] != ':') { + EMSG(_("E109: Missing ':' after '?'")); + if (evaluate && result) + clear_tv(rettv); + return FAIL; + } + + /* + * Get the third variable. + */ + *arg = skipwhite(*arg + 1); + if (eval1(arg, &var2, evaluate && !result) == FAIL) { /* recursive! */ + if (evaluate && result) + clear_tv(rettv); + return FAIL; + } + if (evaluate && !result) + *rettv = var2; + } + + return OK; +} + +/* + * Handle first level expression: + * expr2 || expr2 || expr2 logical OR + * + * "arg" must point to the first non-white of the expression. + * "arg" is advanced to the next non-white after the recognized expression. + * + * Return OK or FAIL. + */ +static int eval2(arg, rettv, evaluate) +char_u **arg; +typval_T *rettv; +int evaluate; +{ + typval_T var2; + long result; + int first; + int error = FALSE; + + /* + * Get the first variable. + */ + if (eval3(arg, rettv, evaluate) == FAIL) + return FAIL; + + /* + * Repeat until there is no following "||". + */ + first = TRUE; + result = FALSE; + while ((*arg)[0] == '|' && (*arg)[1] == '|') { + if (evaluate && first) { + if (get_tv_number_chk(rettv, &error) != 0) + result = TRUE; + clear_tv(rettv); + if (error) + return FAIL; + first = FALSE; + } + + /* + * Get the second variable. + */ + *arg = skipwhite(*arg + 2); + if (eval3(arg, &var2, evaluate && !result) == FAIL) + return FAIL; + + /* + * Compute the result. + */ + if (evaluate && !result) { + if (get_tv_number_chk(&var2, &error) != 0) + result = TRUE; + clear_tv(&var2); + if (error) + return FAIL; + } + if (evaluate) { + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = result; + } + } + + return OK; +} + +/* + * Handle second level expression: + * expr3 && expr3 && expr3 logical AND + * + * "arg" must point to the first non-white of the expression. + * "arg" is advanced to the next non-white after the recognized expression. + * + * Return OK or FAIL. + */ +static int eval3(arg, rettv, evaluate) +char_u **arg; +typval_T *rettv; +int evaluate; +{ + typval_T var2; + long result; + int first; + int error = FALSE; + + /* + * Get the first variable. + */ + if (eval4(arg, rettv, evaluate) == FAIL) + return FAIL; + + /* + * Repeat until there is no following "&&". + */ + first = TRUE; + result = TRUE; + while ((*arg)[0] == '&' && (*arg)[1] == '&') { + if (evaluate && first) { + if (get_tv_number_chk(rettv, &error) == 0) + result = FALSE; + clear_tv(rettv); + if (error) + return FAIL; + first = FALSE; + } + + /* + * Get the second variable. + */ + *arg = skipwhite(*arg + 2); + if (eval4(arg, &var2, evaluate && result) == FAIL) + return FAIL; + + /* + * Compute the result. + */ + if (evaluate && result) { + if (get_tv_number_chk(&var2, &error) == 0) + result = FALSE; + clear_tv(&var2); + if (error) + return FAIL; + } + if (evaluate) { + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = result; + } + } + + return OK; +} + +/* + * Handle third level expression: + * var1 == var2 + * var1 =~ var2 + * var1 != var2 + * var1 !~ var2 + * var1 > var2 + * var1 >= var2 + * var1 < var2 + * var1 <= var2 + * var1 is var2 + * var1 isnot var2 + * + * "arg" must point to the first non-white of the expression. + * "arg" is advanced to the next non-white after the recognized expression. + * + * Return OK or FAIL. + */ +static int eval4(arg, rettv, evaluate) +char_u **arg; +typval_T *rettv; +int evaluate; +{ + typval_T var2; + char_u *p; + int i; + exptype_T type = TYPE_UNKNOWN; + int type_is = FALSE; /* TRUE for "is" and "isnot" */ + int len = 2; + long n1, n2; + char_u *s1, *s2; + char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; + regmatch_T regmatch; + int ic; + char_u *save_cpo; + + /* + * Get the first variable. + */ + if (eval5(arg, rettv, evaluate) == FAIL) + return FAIL; + + p = *arg; + switch (p[0]) { + case '=': if (p[1] == '=') + type = TYPE_EQUAL; + else if (p[1] == '~') + type = TYPE_MATCH; + break; + case '!': if (p[1] == '=') + type = TYPE_NEQUAL; + else if (p[1] == '~') + type = TYPE_NOMATCH; + break; + case '>': if (p[1] != '=') { + type = TYPE_GREATER; + len = 1; + } else + type = TYPE_GEQUAL; + break; + case '<': if (p[1] != '=') { + type = TYPE_SMALLER; + len = 1; + } else + type = TYPE_SEQUAL; + break; + case 'i': if (p[1] == 's') { + if (p[2] == 'n' && p[3] == 'o' && p[4] == 't') + len = 5; + if (!vim_isIDc(p[len])) { + type = len == 2 ? TYPE_EQUAL : TYPE_NEQUAL; + type_is = TRUE; + } + } + break; + } + + /* + * If there is a comparative operator, use it. + */ + if (type != TYPE_UNKNOWN) { + /* extra question mark appended: ignore case */ + if (p[len] == '?') { + ic = TRUE; + ++len; + } + /* extra '#' appended: match case */ + else if (p[len] == '#') { + ic = FALSE; + ++len; + } + /* nothing appended: use 'ignorecase' */ + else + ic = p_ic; + + /* + * Get the second variable. + */ + *arg = skipwhite(p + len); + if (eval5(arg, &var2, evaluate) == FAIL) { + clear_tv(rettv); + return FAIL; + } + + if (evaluate) { + if (type_is && rettv->v_type != var2.v_type) { + /* For "is" a different type always means FALSE, for "notis" + * it means TRUE. */ + n1 = (type == TYPE_NEQUAL); + } else if (rettv->v_type == VAR_LIST || var2.v_type == VAR_LIST) { + if (type_is) { + n1 = (rettv->v_type == var2.v_type + && rettv->vval.v_list == var2.vval.v_list); + if (type == TYPE_NEQUAL) + n1 = !n1; + } else if (rettv->v_type != var2.v_type + || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) { + if (rettv->v_type != var2.v_type) + EMSG(_("E691: Can only compare List with List")); + else + EMSG(_("E692: Invalid operation for Lists")); + clear_tv(rettv); + clear_tv(&var2); + return FAIL; + } else { + /* Compare two Lists for being equal or unequal. */ + n1 = list_equal(rettv->vval.v_list, var2.vval.v_list, + ic, FALSE); + if (type == TYPE_NEQUAL) + n1 = !n1; + } + } else if (rettv->v_type == VAR_DICT || var2.v_type == VAR_DICT) { + if (type_is) { + n1 = (rettv->v_type == var2.v_type + && rettv->vval.v_dict == var2.vval.v_dict); + if (type == TYPE_NEQUAL) + n1 = !n1; + } else if (rettv->v_type != var2.v_type + || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) { + if (rettv->v_type != var2.v_type) + EMSG(_("E735: Can only compare Dictionary with Dictionary")); + else + EMSG(_("E736: Invalid operation for Dictionary")); + clear_tv(rettv); + clear_tv(&var2); + return FAIL; + } else { + /* Compare two Dictionaries for being equal or unequal. */ + n1 = dict_equal(rettv->vval.v_dict, var2.vval.v_dict, + ic, FALSE); + if (type == TYPE_NEQUAL) + n1 = !n1; + } + } else if (rettv->v_type == VAR_FUNC || var2.v_type == VAR_FUNC) { + if (rettv->v_type != var2.v_type + || (type != TYPE_EQUAL && type != TYPE_NEQUAL)) { + if (rettv->v_type != var2.v_type) + EMSG(_("E693: Can only compare Funcref with Funcref")); + else + EMSG(_("E694: Invalid operation for Funcrefs")); + clear_tv(rettv); + clear_tv(&var2); + return FAIL; + } else { + /* Compare two Funcrefs for being equal or unequal. */ + if (rettv->vval.v_string == NULL + || var2.vval.v_string == NULL) + n1 = FALSE; + else + n1 = STRCMP(rettv->vval.v_string, + var2.vval.v_string) == 0; + if (type == TYPE_NEQUAL) + n1 = !n1; + } + } + /* + * If one of the two variables is a float, compare as a float. + * When using "=~" or "!~", always compare as string. + */ + else if ((rettv->v_type == VAR_FLOAT || var2.v_type == VAR_FLOAT) + && type != TYPE_MATCH && type != TYPE_NOMATCH) { + float_T f1, f2; + + if (rettv->v_type == VAR_FLOAT) + f1 = rettv->vval.v_float; + else + f1 = get_tv_number(rettv); + if (var2.v_type == VAR_FLOAT) + f2 = var2.vval.v_float; + else + f2 = get_tv_number(&var2); + n1 = FALSE; + switch (type) { + case TYPE_EQUAL: n1 = (f1 == f2); break; + case TYPE_NEQUAL: n1 = (f1 != f2); break; + case TYPE_GREATER: n1 = (f1 > f2); break; + case TYPE_GEQUAL: n1 = (f1 >= f2); break; + case TYPE_SMALLER: n1 = (f1 < f2); break; + case TYPE_SEQUAL: n1 = (f1 <= f2); break; + case TYPE_UNKNOWN: + case TYPE_MATCH: + case TYPE_NOMATCH: break; /* avoid gcc warning */ + } + } + /* + * If one of the two variables is a number, compare as a number. + * When using "=~" or "!~", always compare as string. + */ + else if ((rettv->v_type == VAR_NUMBER || var2.v_type == VAR_NUMBER) + && type != TYPE_MATCH && type != TYPE_NOMATCH) { + n1 = get_tv_number(rettv); + n2 = get_tv_number(&var2); + switch (type) { + case TYPE_EQUAL: n1 = (n1 == n2); break; + case TYPE_NEQUAL: n1 = (n1 != n2); break; + case TYPE_GREATER: n1 = (n1 > n2); break; + case TYPE_GEQUAL: n1 = (n1 >= n2); break; + case TYPE_SMALLER: n1 = (n1 < n2); break; + case TYPE_SEQUAL: n1 = (n1 <= n2); break; + case TYPE_UNKNOWN: + case TYPE_MATCH: + case TYPE_NOMATCH: break; /* avoid gcc warning */ + } + } else { + s1 = get_tv_string_buf(rettv, buf1); + s2 = get_tv_string_buf(&var2, buf2); + if (type != TYPE_MATCH && type != TYPE_NOMATCH) + i = ic ? MB_STRICMP(s1, s2) : STRCMP(s1, s2); + else + i = 0; + n1 = FALSE; + switch (type) { + case TYPE_EQUAL: n1 = (i == 0); break; + case TYPE_NEQUAL: n1 = (i != 0); break; + case TYPE_GREATER: n1 = (i > 0); break; + case TYPE_GEQUAL: n1 = (i >= 0); break; + case TYPE_SMALLER: n1 = (i < 0); break; + case TYPE_SEQUAL: n1 = (i <= 0); break; + + case TYPE_MATCH: + case TYPE_NOMATCH: + /* avoid 'l' flag in 'cpoptions' */ + save_cpo = p_cpo; + p_cpo = (char_u *)""; + regmatch.regprog = vim_regcomp(s2, + RE_MAGIC + RE_STRING); + regmatch.rm_ic = ic; + if (regmatch.regprog != NULL) { + n1 = vim_regexec_nl(®match, s1, (colnr_T)0); + vim_regfree(regmatch.regprog); + if (type == TYPE_NOMATCH) + n1 = !n1; + } + p_cpo = save_cpo; + break; + + case TYPE_UNKNOWN: break; /* avoid gcc warning */ + } + } + clear_tv(rettv); + clear_tv(&var2); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = n1; + } + } + + return OK; +} + +/* + * Handle fourth level expression: + * + number addition + * - number subtraction + * . string concatenation + * + * "arg" must point to the first non-white of the expression. + * "arg" is advanced to the next non-white after the recognized expression. + * + * Return OK or FAIL. + */ +static int eval5(arg, rettv, evaluate) +char_u **arg; +typval_T *rettv; +int evaluate; +{ + typval_T var2; + typval_T var3; + int op; + long n1, n2; + float_T f1 = 0, f2 = 0; + char_u *s1, *s2; + char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; + char_u *p; + + /* + * Get the first variable. + */ + if (eval6(arg, rettv, evaluate, FALSE) == FAIL) + return FAIL; + + /* + * Repeat computing, until no '+', '-' or '.' is following. + */ + for (;; ) { + op = **arg; + if (op != '+' && op != '-' && op != '.') + break; + + if ((op != '+' || rettv->v_type != VAR_LIST) + && (op == '.' || rettv->v_type != VAR_FLOAT) + ) { + /* For "list + ...", an illegal use of the first operand as + * a number cannot be determined before evaluating the 2nd + * operand: if this is also a list, all is ok. + * For "something . ...", "something - ..." or "non-list + ...", + * we know that the first operand needs to be a string or number + * without evaluating the 2nd operand. So check before to avoid + * side effects after an error. */ + if (evaluate && get_tv_string_chk(rettv) == NULL) { + clear_tv(rettv); + return FAIL; + } + } + + /* + * Get the second variable. + */ + *arg = skipwhite(*arg + 1); + if (eval6(arg, &var2, evaluate, op == '.') == FAIL) { + clear_tv(rettv); + return FAIL; + } + + if (evaluate) { + /* + * Compute the result. + */ + if (op == '.') { + s1 = get_tv_string_buf(rettv, buf1); /* already checked */ + s2 = get_tv_string_buf_chk(&var2, buf2); + if (s2 == NULL) { /* type error ? */ + clear_tv(rettv); + clear_tv(&var2); + return FAIL; + } + p = concat_str(s1, s2); + clear_tv(rettv); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = p; + } else if (op == '+' && rettv->v_type == VAR_LIST + && var2.v_type == VAR_LIST) { + /* concatenate Lists */ + if (list_concat(rettv->vval.v_list, var2.vval.v_list, + &var3) == FAIL) { + clear_tv(rettv); + clear_tv(&var2); + return FAIL; + } + clear_tv(rettv); + *rettv = var3; + } else { + int error = FALSE; + + if (rettv->v_type == VAR_FLOAT) { + f1 = rettv->vval.v_float; + n1 = 0; + } else { + n1 = get_tv_number_chk(rettv, &error); + if (error) { + /* This can only happen for "list + non-list". For + * "non-list + ..." or "something - ...", we returned + * before evaluating the 2nd operand. */ + clear_tv(rettv); + return FAIL; + } + if (var2.v_type == VAR_FLOAT) + f1 = n1; + } + if (var2.v_type == VAR_FLOAT) { + f2 = var2.vval.v_float; + n2 = 0; + } else { + n2 = get_tv_number_chk(&var2, &error); + if (error) { + clear_tv(rettv); + clear_tv(&var2); + return FAIL; + } + if (rettv->v_type == VAR_FLOAT) + f2 = n2; + } + clear_tv(rettv); + + /* If there is a float on either side the result is a float. */ + if (rettv->v_type == VAR_FLOAT || var2.v_type == VAR_FLOAT) { + if (op == '+') + f1 = f1 + f2; + else + f1 = f1 - f2; + rettv->v_type = VAR_FLOAT; + rettv->vval.v_float = f1; + } else { + if (op == '+') + n1 = n1 + n2; + else + n1 = n1 - n2; + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = n1; + } + } + clear_tv(&var2); + } + } + return OK; +} + +/* + * Handle fifth level expression: + * * number multiplication + * / number division + * % number modulo + * + * "arg" must point to the first non-white of the expression. + * "arg" is advanced to the next non-white after the recognized expression. + * + * Return OK or FAIL. + */ +static int eval6(arg, rettv, evaluate, want_string) +char_u **arg; +typval_T *rettv; +int evaluate; +int want_string; /* after "." operator */ +{ + typval_T var2; + int op; + long n1, n2; + int use_float = FALSE; + float_T f1 = 0, f2; + int error = FALSE; + + /* + * Get the first variable. + */ + if (eval7(arg, rettv, evaluate, want_string) == FAIL) + return FAIL; + + /* + * Repeat computing, until no '*', '/' or '%' is following. + */ + for (;; ) { + op = **arg; + if (op != '*' && op != '/' && op != '%') + break; + + if (evaluate) { + if (rettv->v_type == VAR_FLOAT) { + f1 = rettv->vval.v_float; + use_float = TRUE; + n1 = 0; + } else + n1 = get_tv_number_chk(rettv, &error); + clear_tv(rettv); + if (error) + return FAIL; + } else + n1 = 0; + + /* + * Get the second variable. + */ + *arg = skipwhite(*arg + 1); + if (eval7(arg, &var2, evaluate, FALSE) == FAIL) + return FAIL; + + if (evaluate) { + if (var2.v_type == VAR_FLOAT) { + if (!use_float) { + f1 = n1; + use_float = TRUE; + } + f2 = var2.vval.v_float; + n2 = 0; + } else { + n2 = get_tv_number_chk(&var2, &error); + clear_tv(&var2); + if (error) + return FAIL; + if (use_float) + f2 = n2; + } + + /* + * Compute the result. + * When either side is a float the result is a float. + */ + if (use_float) { + if (op == '*') + f1 = f1 * f2; + else if (op == '/') { + /* We rely on the floating point library to handle divide + * by zero to result in "inf" and not a crash. */ + f1 = f1 / f2; + } else { + EMSG(_("E804: Cannot use '%' with Float")); + return FAIL; + } + rettv->v_type = VAR_FLOAT; + rettv->vval.v_float = f1; + } else { + if (op == '*') + n1 = n1 * n2; + else if (op == '/') { + if (n2 == 0) { /* give an error message? */ + if (n1 == 0) + n1 = -0x7fffffffL - 1L; /* similar to NaN */ + else if (n1 < 0) + n1 = -0x7fffffffL; + else + n1 = 0x7fffffffL; + } else + n1 = n1 / n2; + } else { + if (n2 == 0) /* give an error message? */ + n1 = 0; + else + n1 = n1 % n2; + } + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = n1; + } + } + } + + return OK; +} + +/* + * Handle sixth level expression: + * number number constant + * "string" string constant + * 'string' literal string constant + * &option-name option value + * @r register contents + * identifier variable value + * function() function call + * $VAR environment variable + * (expression) nested expression + * [expr, expr] List + * {key: val, key: val} Dictionary + * + * Also handle: + * ! in front logical NOT + * - in front unary minus + * + in front unary plus (ignored) + * trailing [] subscript in String or List + * trailing .name entry in Dictionary + * + * "arg" must point to the first non-white of the expression. + * "arg" is advanced to the next non-white after the recognized expression. + * + * Return OK or FAIL. + */ +static int eval7(arg, rettv, evaluate, want_string) +char_u **arg; +typval_T *rettv; +int evaluate; +int want_string UNUSED; /* after "." operator */ +{ + long n; + int len; + char_u *s; + char_u *start_leader, *end_leader; + int ret = OK; + char_u *alias; + + /* + * Initialise variable so that clear_tv() can't mistake this for a + * string and free a string that isn't there. + */ + rettv->v_type = VAR_UNKNOWN; + + /* + * Skip '!' and '-' characters. They are handled later. + */ + start_leader = *arg; + while (**arg == '!' || **arg == '-' || **arg == '+') + *arg = skipwhite(*arg + 1); + end_leader = *arg; + + switch (**arg) { + /* + * Number constant. + */ + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + { + char_u *p = skipdigits(*arg + 1); + int get_float = FALSE; + + /* We accept a float when the format matches + * "[0-9]\+\.[0-9]\+\([eE][+-]\?[0-9]\+\)\?". This is very + * strict to avoid backwards compatibility problems. + * Don't look for a float after the "." operator, so that + * ":let vers = 1.2.3" doesn't fail. */ + if (!want_string && p[0] == '.' && vim_isdigit(p[1])) { + get_float = TRUE; + p = skipdigits(p + 2); + if (*p == 'e' || *p == 'E') { + ++p; + if (*p == '-' || *p == '+') + ++p; + if (!vim_isdigit(*p)) + get_float = FALSE; + else + p = skipdigits(p + 1); + } + if (ASCII_ISALPHA(*p) || *p == '.') + get_float = FALSE; + } + if (get_float) { + float_T f; + + *arg += string2float(*arg, &f); + if (evaluate) { + rettv->v_type = VAR_FLOAT; + rettv->vval.v_float = f; + } + } else { + vim_str2nr(*arg, NULL, &len, TRUE, TRUE, &n, NULL); + *arg += len; + if (evaluate) { + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = n; + } + } + break; + } + + /* + * String constant: "string". + */ + case '"': ret = get_string_tv(arg, rettv, evaluate); + break; + + /* + * Literal string constant: 'str''ing'. + */ + case '\'': ret = get_lit_string_tv(arg, rettv, evaluate); + break; + + /* + * List: [expr, expr] + */ + case '[': ret = get_list_tv(arg, rettv, evaluate); + break; + + /* + * Dictionary: {key: val, key: val} + */ + case '{': ret = get_dict_tv(arg, rettv, evaluate); + break; + + /* + * Option value: &name + */ + case '&': ret = get_option_tv(arg, rettv, evaluate); + break; + + /* + * Environment variable: $VAR. + */ + case '$': ret = get_env_tv(arg, rettv, evaluate); + break; + + /* + * Register contents: @r. + */ + case '@': ++*arg; + if (evaluate) { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = get_reg_contents(**arg, TRUE, TRUE); + } + if (**arg != NUL) + ++*arg; + break; + + /* + * nested expression: (expression). + */ + case '(': *arg = skipwhite(*arg + 1); + ret = eval1(arg, rettv, evaluate); /* recursive! */ + if (**arg == ')') + ++*arg; + else if (ret == OK) { + EMSG(_("E110: Missing ')'")); + clear_tv(rettv); + ret = FAIL; + } + break; + + default: ret = NOTDONE; + break; + } + + if (ret == NOTDONE) { + /* + * Must be a variable or function name. + * Can also be a curly-braces kind of name: {expr}. + */ + s = *arg; + len = get_name_len(arg, &alias, evaluate, TRUE); + if (alias != NULL) + s = alias; + + if (len <= 0) + ret = FAIL; + else { + if (**arg == '(') { /* recursive! */ + /* If "s" is the name of a variable of type VAR_FUNC + * use its contents. */ + s = deref_func_name(s, &len, FALSE); + + /* Invoke the function. */ + ret = get_func_tv(s, len, rettv, arg, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &len, evaluate, NULL); + + /* If evaluate is FALSE rettv->v_type was not set in + * get_func_tv, but it's needed in handle_subscript() to parse + * what follows. So set it here. */ + if (rettv->v_type == VAR_UNKNOWN && !evaluate && **arg == '(') { + rettv->vval.v_string = vim_strsave((char_u *)""); + rettv->v_type = VAR_FUNC; + } + + /* Stop the expression evaluation when immediately + * aborting on error, or when an interrupt occurred or + * an exception was thrown but not caught. */ + if (aborting()) { + if (ret == OK) + clear_tv(rettv); + ret = FAIL; + } + } else if (evaluate) + ret = get_var_tv(s, len, rettv, TRUE, FALSE); + else + ret = OK; + } + vim_free(alias); + } + + *arg = skipwhite(*arg); + + /* Handle following '[', '(' and '.' for expr[expr], expr.name, + * expr(expr). */ + if (ret == OK) + ret = handle_subscript(arg, rettv, evaluate, TRUE); + + /* + * Apply logical NOT and unary '-', from right to left, ignore '+'. + */ + if (ret == OK && evaluate && end_leader > start_leader) { + int error = FALSE; + int val = 0; + float_T f = 0.0; + + if (rettv->v_type == VAR_FLOAT) + f = rettv->vval.v_float; + else + val = get_tv_number_chk(rettv, &error); + if (error) { + clear_tv(rettv); + ret = FAIL; + } else { + while (end_leader > start_leader) { + --end_leader; + if (*end_leader == '!') { + if (rettv->v_type == VAR_FLOAT) + f = !f; + else + val = !val; + } else if (*end_leader == '-') { + if (rettv->v_type == VAR_FLOAT) + f = -f; + else + val = -val; + } + } + if (rettv->v_type == VAR_FLOAT) { + clear_tv(rettv); + rettv->vval.v_float = f; + } else { + clear_tv(rettv); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = val; + } + } + } + + return ret; +} + +/* + * Evaluate an "[expr]" or "[expr:expr]" index. Also "dict.key". + * "*arg" points to the '[' or '.'. + * Returns FAIL or OK. "*arg" is advanced to after the ']'. + */ +static int eval_index(arg, rettv, evaluate, verbose) +char_u **arg; +typval_T *rettv; +int evaluate; +int verbose; /* give error messages */ +{ + int empty1 = FALSE, empty2 = FALSE; + typval_T var1, var2; + long n1, n2 = 0; + long len = -1; + int range = FALSE; + char_u *s; + char_u *key = NULL; + + if (rettv->v_type == VAR_FUNC) { + if (verbose) + EMSG(_("E695: Cannot index a Funcref")); + return FAIL; + } else if (rettv->v_type == VAR_FLOAT) { + if (verbose) + EMSG(_(e_float_as_string)); + return FAIL; + } + + if (**arg == '.') { + /* + * dict.name + */ + key = *arg + 1; + for (len = 0; ASCII_ISALNUM(key[len]) || key[len] == '_'; ++len) + ; + if (len == 0) + return FAIL; + *arg = skipwhite(key + len); + } else { + /* + * something[idx] + * + * Get the (first) variable from inside the []. + */ + *arg = skipwhite(*arg + 1); + if (**arg == ':') + empty1 = TRUE; + else if (eval1(arg, &var1, evaluate) == FAIL) /* recursive! */ + return FAIL; + else if (evaluate && get_tv_string_chk(&var1) == NULL) { + /* not a number or string */ + clear_tv(&var1); + return FAIL; + } + + /* + * Get the second variable from inside the [:]. + */ + if (**arg == ':') { + range = TRUE; + *arg = skipwhite(*arg + 1); + if (**arg == ']') + empty2 = TRUE; + else if (eval1(arg, &var2, evaluate) == FAIL) { /* recursive! */ + if (!empty1) + clear_tv(&var1); + return FAIL; + } else if (evaluate && get_tv_string_chk(&var2) == NULL) { + /* not a number or string */ + if (!empty1) + clear_tv(&var1); + clear_tv(&var2); + return FAIL; + } + } + + /* Check for the ']'. */ + if (**arg != ']') { + if (verbose) + EMSG(_(e_missbrac)); + clear_tv(&var1); + if (range) + clear_tv(&var2); + return FAIL; + } + *arg = skipwhite(*arg + 1); /* skip the ']' */ + } + + if (evaluate) { + n1 = 0; + if (!empty1 && rettv->v_type != VAR_DICT) { + n1 = get_tv_number(&var1); + clear_tv(&var1); + } + if (range) { + if (empty2) + n2 = -1; + else { + n2 = get_tv_number(&var2); + clear_tv(&var2); + } + } + + switch (rettv->v_type) { + case VAR_NUMBER: + case VAR_STRING: + s = get_tv_string(rettv); + len = (long)STRLEN(s); + if (range) { + /* The resulting variable is a substring. If the indexes + * are out of range the result is empty. */ + if (n1 < 0) { + n1 = len + n1; + if (n1 < 0) + n1 = 0; + } + if (n2 < 0) + n2 = len + n2; + else if (n2 >= len) + n2 = len; + if (n1 >= len || n2 < 0 || n1 > n2) + s = NULL; + else + s = vim_strnsave(s + n1, (int)(n2 - n1 + 1)); + } else { + /* The resulting variable is a string of a single + * character. If the index is too big or negative the + * result is empty. */ + if (n1 >= len || n1 < 0) + s = NULL; + else + s = vim_strnsave(s + n1, 1); + } + clear_tv(rettv); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = s; + break; + + case VAR_LIST: + len = list_len(rettv->vval.v_list); + if (n1 < 0) + n1 = len + n1; + if (!empty1 && (n1 < 0 || n1 >= len)) { + /* For a range we allow invalid values and return an empty + * list. A list index out of range is an error. */ + if (!range) { + if (verbose) + EMSGN(_(e_listidx), n1); + return FAIL; + } + n1 = len; + } + if (range) { + list_T *l; + listitem_T *item; + + if (n2 < 0) + n2 = len + n2; + else if (n2 >= len) + n2 = len - 1; + if (!empty2 && (n2 < 0 || n2 + 1 < n1)) + n2 = -1; + l = list_alloc(); + if (l == NULL) + return FAIL; + for (item = list_find(rettv->vval.v_list, n1); + n1 <= n2; ++n1) { + if (list_append_tv(l, &item->li_tv) == FAIL) { + list_free(l, TRUE); + return FAIL; + } + item = item->li_next; + } + clear_tv(rettv); + rettv->v_type = VAR_LIST; + rettv->vval.v_list = l; + ++l->lv_refcount; + } else { + copy_tv(&list_find(rettv->vval.v_list, n1)->li_tv, &var1); + clear_tv(rettv); + *rettv = var1; + } + break; + + case VAR_DICT: + if (range) { + if (verbose) + EMSG(_(e_dictrange)); + if (len == -1) + clear_tv(&var1); + return FAIL; + } + { + dictitem_T *item; + + if (len == -1) { + key = get_tv_string(&var1); + if (*key == NUL) { + if (verbose) + EMSG(_(e_emptykey)); + clear_tv(&var1); + return FAIL; + } + } + + item = dict_find(rettv->vval.v_dict, key, (int)len); + + if (item == NULL && verbose) + EMSG2(_(e_dictkey), key); + if (len == -1) + clear_tv(&var1); + if (item == NULL) + return FAIL; + + copy_tv(&item->di_tv, &var1); + clear_tv(rettv); + *rettv = var1; + } + break; + } + } + + return OK; +} + +/* + * Get an option value. + * "arg" points to the '&' or '+' before the option name. + * "arg" is advanced to character after the option name. + * Return OK or FAIL. + */ +static int get_option_tv(arg, rettv, evaluate) +char_u **arg; +typval_T *rettv; /* when NULL, only check if option exists */ +int evaluate; +{ + char_u *option_end; + long numval; + char_u *stringval; + int opt_type; + int c; + int working = (**arg == '+'); /* has("+option") */ + int ret = OK; + int opt_flags; + + /* + * Isolate the option name and find its value. + */ + option_end = find_option_end(arg, &opt_flags); + if (option_end == NULL) { + if (rettv != NULL) + EMSG2(_("E112: Option name missing: %s"), *arg); + return FAIL; + } + + if (!evaluate) { + *arg = option_end; + return OK; + } + + c = *option_end; + *option_end = NUL; + opt_type = get_option_value(*arg, &numval, + rettv == NULL ? NULL : &stringval, opt_flags); + + if (opt_type == -3) { /* invalid name */ + if (rettv != NULL) + EMSG2(_("E113: Unknown option: %s"), *arg); + ret = FAIL; + } else if (rettv != NULL) { + if (opt_type == -2) { /* hidden string option */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + } else if (opt_type == -1) { /* hidden number option */ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + } else if (opt_type == 1) { /* number option */ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = numval; + } else { /* string option */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = stringval; + } + } else if (working && (opt_type == -2 || opt_type == -1)) + ret = FAIL; + + *option_end = c; /* put back for error messages */ + *arg = option_end; + + return ret; +} + +/* + * Allocate a variable for a string constant. + * Return OK or FAIL. + */ +static int get_string_tv(arg, rettv, evaluate) +char_u **arg; +typval_T *rettv; +int evaluate; +{ + char_u *p; + char_u *name; + int extra = 0; + + /* + * Find the end of the string, skipping backslashed characters. + */ + for (p = *arg + 1; *p != NUL && *p != '"'; mb_ptr_adv(p)) { + if (*p == '\\' && p[1] != NUL) { + ++p; + /* A "\" form occupies at least 4 characters, and produces up + * to 6 characters: reserve space for 2 extra */ + if (*p == '<') + extra += 2; + } + } + + if (*p != '"') { + EMSG2(_("E114: Missing quote: %s"), *arg); + return FAIL; + } + + /* If only parsing, set *arg and return here */ + if (!evaluate) { + *arg = p + 1; + return OK; + } + + /* + * Copy the string into allocated memory, handling backslashed + * characters. + */ + name = alloc((unsigned)(p - *arg + extra)); + if (name == NULL) + return FAIL; + rettv->v_type = VAR_STRING; + rettv->vval.v_string = name; + + for (p = *arg + 1; *p != NUL && *p != '"'; ) { + if (*p == '\\') { + switch (*++p) { + case 'b': *name++ = BS; ++p; break; + case 'e': *name++ = ESC; ++p; break; + case 'f': *name++ = FF; ++p; break; + case 'n': *name++ = NL; ++p; break; + case 'r': *name++ = CAR; ++p; break; + case 't': *name++ = TAB; ++p; break; + + case 'X': /* hex: "\x1", "\x12" */ + case 'x': + case 'u': /* Unicode: "\u0023" */ + case 'U': + if (vim_isxdigit(p[1])) { + int n, nr; + int c = toupper(*p); + + if (c == 'X') + n = 2; + else + n = 4; + nr = 0; + while (--n >= 0 && vim_isxdigit(p[1])) { + ++p; + nr = (nr << 4) + hex2nr(*p); + } + ++p; + /* For "\u" store the number according to + * 'encoding'. */ + if (c != 'X') + name += (*mb_char2bytes)(nr, name); + else + *name++ = nr; + } + break; + + /* octal: "\1", "\12", "\123" */ + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': *name = *p++ - '0'; + if (*p >= '0' && *p <= '7') { + *name = (*name << 3) + *p++ - '0'; + if (*p >= '0' && *p <= '7') + *name = (*name << 3) + *p++ - '0'; + } + ++name; + break; + + /* Special key, e.g.: "\" */ + case '<': extra = trans_special(&p, name, TRUE); + if (extra != 0) { + name += extra; + break; + } + /* FALLTHROUGH */ + + default: MB_COPY_CHAR(p, name); + break; + } + } else + MB_COPY_CHAR(p, name); + + } + *name = NUL; + *arg = p + 1; + + return OK; +} + +/* + * Allocate a variable for a 'str''ing' constant. + * Return OK or FAIL. + */ +static int get_lit_string_tv(arg, rettv, evaluate) +char_u **arg; +typval_T *rettv; +int evaluate; +{ + char_u *p; + char_u *str; + int reduce = 0; + + /* + * Find the end of the string, skipping ''. + */ + for (p = *arg + 1; *p != NUL; mb_ptr_adv(p)) { + if (*p == '\'') { + if (p[1] != '\'') + break; + ++reduce; + ++p; + } + } + + if (*p != '\'') { + EMSG2(_("E115: Missing quote: %s"), *arg); + return FAIL; + } + + /* If only parsing return after setting "*arg" */ + if (!evaluate) { + *arg = p + 1; + return OK; + } + + /* + * Copy the string into allocated memory, handling '' to ' reduction. + */ + str = alloc((unsigned)((p - *arg) - reduce)); + if (str == NULL) + return FAIL; + rettv->v_type = VAR_STRING; + rettv->vval.v_string = str; + + for (p = *arg + 1; *p != NUL; ) { + if (*p == '\'') { + if (p[1] != '\'') + break; + ++p; + } + MB_COPY_CHAR(p, str); + } + *str = NUL; + *arg = p + 1; + + return OK; +} + +/* + * Allocate a variable for a List and fill it from "*arg". + * Return OK or FAIL. + */ +static int get_list_tv(arg, rettv, evaluate) +char_u **arg; +typval_T *rettv; +int evaluate; +{ + list_T *l = NULL; + typval_T tv; + listitem_T *item; + + if (evaluate) { + l = list_alloc(); + if (l == NULL) + return FAIL; + } + + *arg = skipwhite(*arg + 1); + while (**arg != ']' && **arg != NUL) { + if (eval1(arg, &tv, evaluate) == FAIL) /* recursive! */ + goto failret; + if (evaluate) { + item = listitem_alloc(); + if (item != NULL) { + item->li_tv = tv; + item->li_tv.v_lock = 0; + list_append(l, item); + } else + clear_tv(&tv); + } + + if (**arg == ']') + break; + if (**arg != ',') { + EMSG2(_("E696: Missing comma in List: %s"), *arg); + goto failret; + } + *arg = skipwhite(*arg + 1); + } + + if (**arg != ']') { + EMSG2(_("E697: Missing end of List ']': %s"), *arg); +failret: + if (evaluate) + list_free(l, TRUE); + return FAIL; + } + + *arg = skipwhite(*arg + 1); + if (evaluate) { + rettv->v_type = VAR_LIST; + rettv->vval.v_list = l; + ++l->lv_refcount; + } + + return OK; +} + +/* + * Allocate an empty header for a list. + * Caller should take care of the reference count. + */ +list_T * list_alloc() { + list_T *l; + + l = (list_T *)alloc_clear(sizeof(list_T)); + if (l != NULL) { + /* Prepend the list to the list of lists for garbage collection. */ + if (first_list != NULL) + first_list->lv_used_prev = l; + l->lv_used_prev = NULL; + l->lv_used_next = first_list; + first_list = l; + } + return l; +} + +/* + * Allocate an empty list for a return value. + * Returns OK or FAIL. + */ +static int rettv_list_alloc(rettv) +typval_T *rettv; +{ + list_T *l = list_alloc(); + + if (l == NULL) + return FAIL; + + rettv->vval.v_list = l; + rettv->v_type = VAR_LIST; + ++l->lv_refcount; + return OK; +} + +/* + * Unreference a list: decrement the reference count and free it when it + * becomes zero. + */ +void list_unref(l) +list_T *l; +{ + if (l != NULL && --l->lv_refcount <= 0) + list_free(l, TRUE); +} + +/* + * Free a list, including all items it points to. + * Ignores the reference count. + */ +void list_free(l, recurse) +list_T *l; +int recurse; /* Free Lists and Dictionaries recursively. */ +{ + listitem_T *item; + + /* Remove the list from the list of lists for garbage collection. */ + if (l->lv_used_prev == NULL) + first_list = l->lv_used_next; + else + l->lv_used_prev->lv_used_next = l->lv_used_next; + if (l->lv_used_next != NULL) + l->lv_used_next->lv_used_prev = l->lv_used_prev; + + for (item = l->lv_first; item != NULL; item = l->lv_first) { + /* Remove the item before deleting it. */ + l->lv_first = item->li_next; + if (recurse || (item->li_tv.v_type != VAR_LIST + && item->li_tv.v_type != VAR_DICT)) + clear_tv(&item->li_tv); + vim_free(item); + } + vim_free(l); +} + +/* + * Allocate a list item. + */ +listitem_T * listitem_alloc() { + return (listitem_T *)alloc(sizeof(listitem_T)); +} + +/* + * Free a list item. Also clears the value. Does not notify watchers. + */ +void listitem_free(item) +listitem_T *item; +{ + clear_tv(&item->li_tv); + vim_free(item); +} + +/* + * Remove a list item from a List and free it. Also clears the value. + */ +void listitem_remove(l, item) +list_T *l; +listitem_T *item; +{ + list_remove(l, item, item); + listitem_free(item); +} + +/* + * Get the number of items in a list. + */ +static long list_len(l) +list_T *l; +{ + if (l == NULL) + return 0L; + return l->lv_len; +} + +/* + * Return TRUE when two lists have exactly the same values. + */ +static int list_equal(l1, l2, ic, recursive) +list_T *l1; +list_T *l2; +int ic; /* ignore case for strings */ +int recursive; /* TRUE when used recursively */ +{ + listitem_T *item1, *item2; + + if (l1 == NULL || l2 == NULL) + return FALSE; + if (l1 == l2) + return TRUE; + if (list_len(l1) != list_len(l2)) + return FALSE; + + for (item1 = l1->lv_first, item2 = l2->lv_first; + item1 != NULL && item2 != NULL; + item1 = item1->li_next, item2 = item2->li_next) + if (!tv_equal(&item1->li_tv, &item2->li_tv, ic, recursive)) + return FALSE; + return item1 == NULL && item2 == NULL; +} + +#if defined(FEAT_RUBY) || defined(FEAT_PYTHON) || defined(FEAT_PYTHON3) \ + || defined(FEAT_MZSCHEME) || defined(FEAT_LUA) || defined(PROTO) +/* + * Return the dictitem that an entry in a hashtable points to. + */ +dictitem_T * dict_lookup(hi) +hashitem_T *hi; +{ + return HI2DI(hi); +} +#endif + +/* + * Return TRUE when two dictionaries have exactly the same key/values. + */ +static int dict_equal(d1, d2, ic, recursive) +dict_T *d1; +dict_T *d2; +int ic; /* ignore case for strings */ +int recursive; /* TRUE when used recursively */ +{ + hashitem_T *hi; + dictitem_T *item2; + int todo; + + if (d1 == NULL || d2 == NULL) + return FALSE; + if (d1 == d2) + return TRUE; + if (dict_len(d1) != dict_len(d2)) + return FALSE; + + todo = (int)d1->dv_hashtab.ht_used; + for (hi = d1->dv_hashtab.ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + item2 = dict_find(d2, hi->hi_key, -1); + if (item2 == NULL) + return FALSE; + if (!tv_equal(&HI2DI(hi)->di_tv, &item2->di_tv, ic, recursive)) + return FALSE; + --todo; + } + } + return TRUE; +} + +static int tv_equal_recurse_limit; + +/* + * Return TRUE if "tv1" and "tv2" have the same value. + * Compares the items just like "==" would compare them, but strings and + * numbers are different. Floats and numbers are also different. + */ +static int tv_equal(tv1, tv2, ic, recursive) +typval_T *tv1; +typval_T *tv2; +int ic; /* ignore case */ +int recursive; /* TRUE when used recursively */ +{ + char_u buf1[NUMBUFLEN], buf2[NUMBUFLEN]; + char_u *s1, *s2; + static int recursive_cnt = 0; /* catch recursive loops */ + int r; + + if (tv1->v_type != tv2->v_type) + return FALSE; + + /* Catch lists and dicts that have an endless loop by limiting + * recursiveness to a limit. We guess they are equal then. + * A fixed limit has the problem of still taking an awful long time. + * Reduce the limit every time running into it. That should work fine for + * deeply linked structures that are not recursively linked and catch + * recursiveness quickly. */ + if (!recursive) + tv_equal_recurse_limit = 1000; + if (recursive_cnt >= tv_equal_recurse_limit) { + --tv_equal_recurse_limit; + return TRUE; + } + + switch (tv1->v_type) { + case VAR_LIST: + ++recursive_cnt; + r = list_equal(tv1->vval.v_list, tv2->vval.v_list, ic, TRUE); + --recursive_cnt; + return r; + + case VAR_DICT: + ++recursive_cnt; + r = dict_equal(tv1->vval.v_dict, tv2->vval.v_dict, ic, TRUE); + --recursive_cnt; + return r; + + case VAR_FUNC: + return tv1->vval.v_string != NULL + && tv2->vval.v_string != NULL + && STRCMP(tv1->vval.v_string, tv2->vval.v_string) == 0; + + case VAR_NUMBER: + return tv1->vval.v_number == tv2->vval.v_number; + + case VAR_FLOAT: + return tv1->vval.v_float == tv2->vval.v_float; + + case VAR_STRING: + s1 = get_tv_string_buf(tv1, buf1); + s2 = get_tv_string_buf(tv2, buf2); + return (ic ? MB_STRICMP(s1, s2) : STRCMP(s1, s2)) == 0; + } + + EMSG2(_(e_intern2), "tv_equal()"); + return TRUE; +} + +/* + * Locate item with index "n" in list "l" and return it. + * A negative index is counted from the end; -1 is the last item. + * Returns NULL when "n" is out of range. + */ +listitem_T * list_find(l, n) +list_T *l; +long n; +{ + listitem_T *item; + long idx; + + if (l == NULL) + return NULL; + + /* Negative index is relative to the end. */ + if (n < 0) + n = l->lv_len + n; + + /* Check for index out of range. */ + if (n < 0 || n >= l->lv_len) + return NULL; + + /* When there is a cached index may start search from there. */ + if (l->lv_idx_item != NULL) { + if (n < l->lv_idx / 2) { + /* closest to the start of the list */ + item = l->lv_first; + idx = 0; + } else if (n > (l->lv_idx + l->lv_len) / 2) { + /* closest to the end of the list */ + item = l->lv_last; + idx = l->lv_len - 1; + } else { + /* closest to the cached index */ + item = l->lv_idx_item; + idx = l->lv_idx; + } + } else { + if (n < l->lv_len / 2) { + /* closest to the start of the list */ + item = l->lv_first; + idx = 0; + } else { + /* closest to the end of the list */ + item = l->lv_last; + idx = l->lv_len - 1; + } + } + + while (n > idx) { + /* search forward */ + item = item->li_next; + ++idx; + } + while (n < idx) { + /* search backward */ + item = item->li_prev; + --idx; + } + + /* cache the used index */ + l->lv_idx = idx; + l->lv_idx_item = item; + + return item; +} + +/* + * Get list item "l[idx]" as a number. + */ +static long list_find_nr(l, idx, errorp) +list_T *l; +long idx; +int *errorp; /* set to TRUE when something wrong */ +{ + listitem_T *li; + + li = list_find(l, idx); + if (li == NULL) { + if (errorp != NULL) + *errorp = TRUE; + return -1L; + } + return get_tv_number_chk(&li->li_tv, errorp); +} + +/* + * Get list item "l[idx - 1]" as a string. Returns NULL for failure. + */ +char_u * list_find_str(l, idx) +list_T *l; +long idx; +{ + listitem_T *li; + + li = list_find(l, idx - 1); + if (li == NULL) { + EMSGN(_(e_listidx), idx); + return NULL; + } + return get_tv_string(&li->li_tv); +} + +/* + * Locate "item" list "l" and return its index. + * Returns -1 when "item" is not in the list. + */ +static long list_idx_of_item(l, item) +list_T *l; +listitem_T *item; +{ + long idx = 0; + listitem_T *li; + + if (l == NULL) + return -1; + idx = 0; + for (li = l->lv_first; li != NULL && li != item; li = li->li_next) + ++idx; + if (li == NULL) + return -1; + return idx; +} + +/* + * Append item "item" to the end of list "l". + */ +void list_append(l, item) +list_T *l; +listitem_T *item; +{ + if (l->lv_last == NULL) { + /* empty list */ + l->lv_first = item; + l->lv_last = item; + item->li_prev = NULL; + } else { + l->lv_last->li_next = item; + item->li_prev = l->lv_last; + l->lv_last = item; + } + ++l->lv_len; + item->li_next = NULL; +} + +/* + * Append typval_T "tv" to the end of list "l". + * Return FAIL when out of memory. + */ +int list_append_tv(l, tv) +list_T *l; +typval_T *tv; +{ + listitem_T *li = listitem_alloc(); + + if (li == NULL) + return FAIL; + copy_tv(tv, &li->li_tv); + list_append(l, li); + return OK; +} + +/* + * Add a dictionary to a list. Used by getqflist(). + * Return FAIL when out of memory. + */ +int list_append_dict(list, dict) +list_T *list; +dict_T *dict; +{ + listitem_T *li = listitem_alloc(); + + if (li == NULL) + return FAIL; + li->li_tv.v_type = VAR_DICT; + li->li_tv.v_lock = 0; + li->li_tv.vval.v_dict = dict; + list_append(list, li); + ++dict->dv_refcount; + return OK; +} + +/* + * Make a copy of "str" and append it as an item to list "l". + * When "len" >= 0 use "str[len]". + * Returns FAIL when out of memory. + */ +int list_append_string(l, str, len) +list_T *l; +char_u *str; +int len; +{ + listitem_T *li = listitem_alloc(); + + if (li == NULL) + return FAIL; + list_append(l, li); + li->li_tv.v_type = VAR_STRING; + li->li_tv.v_lock = 0; + if (str == NULL) + li->li_tv.vval.v_string = NULL; + else if ((li->li_tv.vval.v_string = (len >= 0 ? vim_strnsave(str, len) + : vim_strsave(str))) == NULL) + return FAIL; + return OK; +} + +/* + * Append "n" to list "l". + * Returns FAIL when out of memory. + */ +static int list_append_number(l, n) +list_T *l; +varnumber_T n; +{ + listitem_T *li; + + li = listitem_alloc(); + if (li == NULL) + return FAIL; + li->li_tv.v_type = VAR_NUMBER; + li->li_tv.v_lock = 0; + li->li_tv.vval.v_number = n; + list_append(l, li); + return OK; +} + +/* + * Insert typval_T "tv" in list "l" before "item". + * If "item" is NULL append at the end. + * Return FAIL when out of memory. + */ +int list_insert_tv(l, tv, item) +list_T *l; +typval_T *tv; +listitem_T *item; +{ + listitem_T *ni = listitem_alloc(); + + if (ni == NULL) + return FAIL; + copy_tv(tv, &ni->li_tv); + list_insert(l, ni, item); + return OK; +} + +void list_insert(l, ni, item) +list_T *l; +listitem_T *ni; +listitem_T *item; +{ + if (item == NULL) + /* Append new item at end of list. */ + list_append(l, ni); + else { + /* Insert new item before existing item. */ + ni->li_prev = item->li_prev; + ni->li_next = item; + if (item->li_prev == NULL) { + l->lv_first = ni; + ++l->lv_idx; + } else { + item->li_prev->li_next = ni; + l->lv_idx_item = NULL; + } + item->li_prev = ni; + ++l->lv_len; + } +} + +/* + * Extend "l1" with "l2". + * If "bef" is NULL append at the end, otherwise insert before this item. + * Returns FAIL when out of memory. + */ +static int list_extend(l1, l2, bef) +list_T *l1; +list_T *l2; +listitem_T *bef; +{ + listitem_T *item; + int todo = l2->lv_len; + + /* We also quit the loop when we have inserted the original item count of + * the list, avoid a hang when we extend a list with itself. */ + for (item = l2->lv_first; item != NULL && --todo >= 0; item = item->li_next) + if (list_insert_tv(l1, &item->li_tv, bef) == FAIL) + return FAIL; + return OK; +} + +/* + * Concatenate lists "l1" and "l2" into a new list, stored in "tv". + * Return FAIL when out of memory. + */ +static int list_concat(l1, l2, tv) +list_T *l1; +list_T *l2; +typval_T *tv; +{ + list_T *l; + + if (l1 == NULL || l2 == NULL) + return FAIL; + + /* make a copy of the first list. */ + l = list_copy(l1, FALSE, 0); + if (l == NULL) + return FAIL; + tv->v_type = VAR_LIST; + tv->vval.v_list = l; + + /* append all items from the second list */ + return list_extend(l, l2, NULL); +} + +/* + * Make a copy of list "orig". Shallow if "deep" is FALSE. + * The refcount of the new list is set to 1. + * See item_copy() for "copyID". + * Returns NULL when out of memory. + */ +static list_T * list_copy(orig, deep, copyID) +list_T *orig; +int deep; +int copyID; +{ + list_T *copy; + listitem_T *item; + listitem_T *ni; + + if (orig == NULL) + return NULL; + + copy = list_alloc(); + if (copy != NULL) { + if (copyID != 0) { + /* Do this before adding the items, because one of the items may + * refer back to this list. */ + orig->lv_copyID = copyID; + orig->lv_copylist = copy; + } + for (item = orig->lv_first; item != NULL && !got_int; + item = item->li_next) { + ni = listitem_alloc(); + if (ni == NULL) + break; + if (deep) { + if (item_copy(&item->li_tv, &ni->li_tv, deep, copyID) == FAIL) { + vim_free(ni); + break; + } + } else + copy_tv(&item->li_tv, &ni->li_tv); + list_append(copy, ni); + } + ++copy->lv_refcount; + if (item != NULL) { + list_unref(copy); + copy = NULL; + } + } + + return copy; +} + +/* + * Remove items "item" to "item2" from list "l". + * Does not free the listitem or the value! + */ +void list_remove(l, item, item2) +list_T *l; +listitem_T *item; +listitem_T *item2; +{ + listitem_T *ip; + + /* notify watchers */ + for (ip = item; ip != NULL; ip = ip->li_next) { + --l->lv_len; + list_fix_watch(l, ip); + if (ip == item2) + break; + } + + if (item2->li_next == NULL) + l->lv_last = item->li_prev; + else + item2->li_next->li_prev = item->li_prev; + if (item->li_prev == NULL) + l->lv_first = item2->li_next; + else + item->li_prev->li_next = item2->li_next; + l->lv_idx_item = NULL; +} + +/* + * Return an allocated string with the string representation of a list. + * May return NULL. + */ +static char_u * list2string(tv, copyID) +typval_T *tv; +int copyID; +{ + garray_T ga; + + if (tv->vval.v_list == NULL) + return NULL; + ga_init2(&ga, (int)sizeof(char), 80); + ga_append(&ga, '['); + if (list_join(&ga, tv->vval.v_list, (char_u *)", ", FALSE, copyID) == FAIL) { + vim_free(ga.ga_data); + return NULL; + } + ga_append(&ga, ']'); + ga_append(&ga, NUL); + return (char_u *)ga.ga_data; +} + +typedef struct join_S { + char_u *s; + char_u *tofree; +} join_T; + +static int list_join_inner(gap, l, sep, echo_style, copyID, join_gap) +garray_T *gap; /* to store the result in */ +list_T *l; +char_u *sep; +int echo_style; +int copyID; +garray_T *join_gap; /* to keep each list item string */ +{ + int i; + join_T *p; + int len; + int sumlen = 0; + int first = TRUE; + char_u *tofree; + char_u numbuf[NUMBUFLEN]; + listitem_T *item; + char_u *s; + + /* Stringify each item in the list. */ + for (item = l->lv_first; item != NULL && !got_int; item = item->li_next) { + if (echo_style) + s = echo_string(&item->li_tv, &tofree, numbuf, copyID); + else + s = tv2string(&item->li_tv, &tofree, numbuf, copyID); + if (s == NULL) + return FAIL; + + len = (int)STRLEN(s); + sumlen += len; + + ga_grow(join_gap, 1); + p = ((join_T *)join_gap->ga_data) + (join_gap->ga_len++); + if (tofree != NULL || s != numbuf) { + p->s = s; + p->tofree = tofree; + } else { + p->s = vim_strnsave(s, len); + p->tofree = p->s; + } + + line_breakcheck(); + } + + /* Allocate result buffer with its total size, avoid re-allocation and + * multiple copy operations. Add 2 for a tailing ']' and NUL. */ + if (join_gap->ga_len >= 2) + sumlen += (int)STRLEN(sep) * (join_gap->ga_len - 1); + if (ga_grow(gap, sumlen + 2) == FAIL) + return FAIL; + + for (i = 0; i < join_gap->ga_len && !got_int; ++i) { + if (first) + first = FALSE; + else + ga_concat(gap, sep); + p = ((join_T *)join_gap->ga_data) + i; + + if (p->s != NULL) + ga_concat(gap, p->s); + line_breakcheck(); + } + + return OK; +} + +/* + * Join list "l" into a string in "*gap", using separator "sep". + * When "echo_style" is TRUE use String as echoed, otherwise as inside a List. + * Return FAIL or OK. + */ +static int list_join(gap, l, sep, echo_style, copyID) +garray_T *gap; +list_T *l; +char_u *sep; +int echo_style; +int copyID; +{ + garray_T join_ga; + int retval; + join_T *p; + int i; + + ga_init2(&join_ga, (int)sizeof(join_T), l->lv_len); + retval = list_join_inner(gap, l, sep, echo_style, copyID, &join_ga); + + /* Dispose each item in join_ga. */ + if (join_ga.ga_data != NULL) { + p = (join_T *)join_ga.ga_data; + for (i = 0; i < join_ga.ga_len; ++i) { + vim_free(p->tofree); + ++p; + } + ga_clear(&join_ga); + } + + return retval; +} + +/* + * Garbage collection for lists and dictionaries. + * + * We use reference counts to be able to free most items right away when they + * are no longer used. But for composite items it's possible that it becomes + * unused while the reference count is > 0: When there is a recursive + * reference. Example: + * :let l = [1, 2, 3] + * :let d = {9: l} + * :let l[1] = d + * + * Since this is quite unusual we handle this with garbage collection: every + * once in a while find out which lists and dicts are not referenced from any + * variable. + * + * Here is a good reference text about garbage collection (refers to Python + * but it applies to all reference-counting mechanisms): + * http://python.ca/nas/python/gc/ + */ + +/* + * Do garbage collection for lists and dicts. + * Return TRUE if some memory was freed. + */ +int garbage_collect() { + int copyID; + buf_T *buf; + win_T *wp; + int i; + funccall_T *fc, **pfc; + int did_free; + int did_free_funccal = FALSE; + tabpage_T *tp; + + /* Only do this once. */ + want_garbage_collect = FALSE; + may_garbage_collect = FALSE; + garbage_collect_at_exit = FALSE; + + /* We advance by two because we add one for items referenced through + * previous_funccal. */ + current_copyID += COPYID_INC; + copyID = current_copyID; + + /* + * 1. Go through all accessible variables and mark all lists and dicts + * with copyID. + */ + + /* Don't free variables in the previous_funccal list unless they are only + * referenced through previous_funccal. This must be first, because if + * the item is referenced elsewhere the funccal must not be freed. */ + for (fc = previous_funccal; fc != NULL; fc = fc->caller) { + set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1); + set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1); + } + + /* script-local variables */ + for (i = 1; i <= ga_scripts.ga_len; ++i) + set_ref_in_ht(&SCRIPT_VARS(i), copyID); + + /* buffer-local variables */ + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + set_ref_in_item(&buf->b_bufvar.di_tv, copyID); + + /* window-local variables */ + FOR_ALL_TAB_WINDOWS(tp, wp) + set_ref_in_item(&wp->w_winvar.di_tv, copyID); + if (aucmd_win != NULL) + set_ref_in_item(&aucmd_win->w_winvar.di_tv, copyID); + + /* tabpage-local variables */ + for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) + set_ref_in_item(&tp->tp_winvar.di_tv, copyID); + + /* global variables */ + set_ref_in_ht(&globvarht, copyID); + + /* function-local variables */ + for (fc = current_funccal; fc != NULL; fc = fc->caller) { + set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID); + set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID); + } + + /* v: vars */ + set_ref_in_ht(&vimvarht, copyID); + + + + + /* + * 2. Free lists and dictionaries that are not referenced. + */ + did_free = free_unref_items(copyID); + + /* + * 3. Check if any funccal can be freed now. + */ + for (pfc = &previous_funccal; *pfc != NULL; ) { + if (can_free_funccal(*pfc, copyID)) { + fc = *pfc; + *pfc = fc->caller; + free_funccal(fc, TRUE); + did_free = TRUE; + did_free_funccal = TRUE; + } else + pfc = &(*pfc)->caller; + } + if (did_free_funccal) + /* When a funccal was freed some more items might be garbage + * collected, so run again. */ + (void)garbage_collect(); + + return did_free; +} + +/* + * Free lists and dictionaries that are no longer referenced. + */ +static int free_unref_items(copyID) +int copyID; +{ + dict_T *dd; + list_T *ll; + int did_free = FALSE; + + /* + * Go through the list of dicts and free items without the copyID. + */ + for (dd = first_dict; dd != NULL; ) + if ((dd->dv_copyID & COPYID_MASK) != (copyID & COPYID_MASK)) { + /* Free the Dictionary and ordinary items it contains, but don't + * recurse into Lists and Dictionaries, they will be in the list + * of dicts or list of lists. */ + dict_free(dd, FALSE); + did_free = TRUE; + + /* restart, next dict may also have been freed */ + dd = first_dict; + } else + dd = dd->dv_used_next; + + /* + * Go through the list of lists and free items without the copyID. + * But don't free a list that has a watcher (used in a for loop), these + * are not referenced anywhere. + */ + for (ll = first_list; ll != NULL; ) + if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) + && ll->lv_watch == NULL) { + /* Free the List and ordinary items it contains, but don't recurse + * into Lists and Dictionaries, they will be in the list of dicts + * or list of lists. */ + list_free(ll, FALSE); + did_free = TRUE; + + /* restart, next list may also have been freed */ + ll = first_list; + } else + ll = ll->lv_used_next; + + return did_free; +} + +/* + * Mark all lists and dicts referenced through hashtab "ht" with "copyID". + */ +void set_ref_in_ht(ht, copyID) +hashtab_T *ht; +int copyID; +{ + int todo; + hashitem_T *hi; + + todo = (int)ht->ht_used; + for (hi = ht->ht_array; todo > 0; ++hi) + if (!HASHITEM_EMPTY(hi)) { + --todo; + set_ref_in_item(&HI2DI(hi)->di_tv, copyID); + } +} + +/* + * Mark all lists and dicts referenced through list "l" with "copyID". + */ +void set_ref_in_list(l, copyID) +list_T *l; +int copyID; +{ + listitem_T *li; + + for (li = l->lv_first; li != NULL; li = li->li_next) + set_ref_in_item(&li->li_tv, copyID); +} + +/* + * Mark all lists and dicts referenced through typval "tv" with "copyID". + */ +void set_ref_in_item(tv, copyID) +typval_T *tv; +int copyID; +{ + dict_T *dd; + list_T *ll; + + switch (tv->v_type) { + case VAR_DICT: + dd = tv->vval.v_dict; + if (dd != NULL && dd->dv_copyID != copyID) { + /* Didn't see this dict yet. */ + dd->dv_copyID = copyID; + set_ref_in_ht(&dd->dv_hashtab, copyID); + } + break; + + case VAR_LIST: + ll = tv->vval.v_list; + if (ll != NULL && ll->lv_copyID != copyID) { + /* Didn't see this list yet. */ + ll->lv_copyID = copyID; + set_ref_in_list(ll, copyID); + } + break; + } + return; +} + +/* + * Allocate an empty header for a dictionary. + */ +dict_T * dict_alloc() { + dict_T *d; + + d = (dict_T *)alloc(sizeof(dict_T)); + if (d != NULL) { + /* Add the dict to the list of dicts for garbage collection. */ + if (first_dict != NULL) + first_dict->dv_used_prev = d; + d->dv_used_next = first_dict; + d->dv_used_prev = NULL; + first_dict = d; + + hash_init(&d->dv_hashtab); + d->dv_lock = 0; + d->dv_scope = 0; + d->dv_refcount = 0; + d->dv_copyID = 0; + } + return d; +} + +/* + * Allocate an empty dict for a return value. + * Returns OK or FAIL. + */ +static int rettv_dict_alloc(rettv) +typval_T *rettv; +{ + dict_T *d = dict_alloc(); + + if (d == NULL) + return FAIL; + + rettv->vval.v_dict = d; + rettv->v_type = VAR_DICT; + ++d->dv_refcount; + return OK; +} + + +/* + * Unreference a Dictionary: decrement the reference count and free it when it + * becomes zero. + */ +void dict_unref(d) +dict_T *d; +{ + if (d != NULL && --d->dv_refcount <= 0) + dict_free(d, TRUE); +} + +/* + * Free a Dictionary, including all items it contains. + * Ignores the reference count. + */ +void dict_free(d, recurse) +dict_T *d; +int recurse; /* Free Lists and Dictionaries recursively. */ +{ + int todo; + hashitem_T *hi; + dictitem_T *di; + + /* Remove the dict from the list of dicts for garbage collection. */ + if (d->dv_used_prev == NULL) + first_dict = d->dv_used_next; + else + d->dv_used_prev->dv_used_next = d->dv_used_next; + if (d->dv_used_next != NULL) + d->dv_used_next->dv_used_prev = d->dv_used_prev; + + /* Lock the hashtab, we don't want it to resize while freeing items. */ + hash_lock(&d->dv_hashtab); + todo = (int)d->dv_hashtab.ht_used; + for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + /* Remove the item before deleting it, just in case there is + * something recursive causing trouble. */ + di = HI2DI(hi); + hash_remove(&d->dv_hashtab, hi); + if (recurse || (di->di_tv.v_type != VAR_LIST + && di->di_tv.v_type != VAR_DICT)) + clear_tv(&di->di_tv); + vim_free(di); + --todo; + } + } + hash_clear(&d->dv_hashtab); + vim_free(d); +} + +/* + * Allocate a Dictionary item. + * The "key" is copied to the new item. + * Note that the value of the item "di_tv" still needs to be initialized! + * Returns NULL when out of memory. + */ +dictitem_T * dictitem_alloc(key) +char_u *key; +{ + dictitem_T *di; + + di = (dictitem_T *)alloc((unsigned)(sizeof(dictitem_T) + STRLEN(key))); + if (di != NULL) { + STRCPY(di->di_key, key); + di->di_flags = 0; + } + return di; +} + +/* + * Make a copy of a Dictionary item. + */ +static dictitem_T * dictitem_copy(org) +dictitem_T *org; +{ + dictitem_T *di; + + di = (dictitem_T *)alloc((unsigned)(sizeof(dictitem_T) + + STRLEN(org->di_key))); + if (di != NULL) { + STRCPY(di->di_key, org->di_key); + di->di_flags = 0; + copy_tv(&org->di_tv, &di->di_tv); + } + return di; +} + +/* + * Remove item "item" from Dictionary "dict" and free it. + */ +static void dictitem_remove(dict, item) +dict_T *dict; +dictitem_T *item; +{ + hashitem_T *hi; + + hi = hash_find(&dict->dv_hashtab, item->di_key); + if (HASHITEM_EMPTY(hi)) + EMSG2(_(e_intern2), "dictitem_remove()"); + else + hash_remove(&dict->dv_hashtab, hi); + dictitem_free(item); +} + +/* + * Free a dict item. Also clears the value. + */ +void dictitem_free(item) +dictitem_T *item; +{ + clear_tv(&item->di_tv); + vim_free(item); +} + +/* + * Make a copy of dict "d". Shallow if "deep" is FALSE. + * The refcount of the new dict is set to 1. + * See item_copy() for "copyID". + * Returns NULL when out of memory. + */ +static dict_T * dict_copy(orig, deep, copyID) +dict_T *orig; +int deep; +int copyID; +{ + dict_T *copy; + dictitem_T *di; + int todo; + hashitem_T *hi; + + if (orig == NULL) + return NULL; + + copy = dict_alloc(); + if (copy != NULL) { + if (copyID != 0) { + orig->dv_copyID = copyID; + orig->dv_copydict = copy; + } + todo = (int)orig->dv_hashtab.ht_used; + for (hi = orig->dv_hashtab.ht_array; todo > 0 && !got_int; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + + di = dictitem_alloc(hi->hi_key); + if (di == NULL) + break; + if (deep) { + if (item_copy(&HI2DI(hi)->di_tv, &di->di_tv, deep, + copyID) == FAIL) { + vim_free(di); + break; + } + } else + copy_tv(&HI2DI(hi)->di_tv, &di->di_tv); + if (dict_add(copy, di) == FAIL) { + dictitem_free(di); + break; + } + } + } + + ++copy->dv_refcount; + if (todo > 0) { + dict_unref(copy); + copy = NULL; + } + } + + return copy; +} + +/* + * Add item "item" to Dictionary "d". + * Returns FAIL when out of memory and when key already exists. + */ +int dict_add(d, item) +dict_T *d; +dictitem_T *item; +{ + return hash_add(&d->dv_hashtab, item->di_key); +} + +/* + * Add a number or string entry to dictionary "d". + * When "str" is NULL use number "nr", otherwise use "str". + * Returns FAIL when out of memory and when key already exists. + */ +int dict_add_nr_str(d, key, nr, str) +dict_T *d; +char *key; +long nr; +char_u *str; +{ + dictitem_T *item; + + item = dictitem_alloc((char_u *)key); + if (item == NULL) + return FAIL; + item->di_tv.v_lock = 0; + if (str == NULL) { + item->di_tv.v_type = VAR_NUMBER; + item->di_tv.vval.v_number = nr; + } else { + item->di_tv.v_type = VAR_STRING; + item->di_tv.vval.v_string = vim_strsave(str); + } + if (dict_add(d, item) == FAIL) { + dictitem_free(item); + return FAIL; + } + return OK; +} + +/* + * Add a list entry to dictionary "d". + * Returns FAIL when out of memory and when key already exists. + */ +int dict_add_list(d, key, list) +dict_T *d; +char *key; +list_T *list; +{ + dictitem_T *item; + + item = dictitem_alloc((char_u *)key); + if (item == NULL) + return FAIL; + item->di_tv.v_lock = 0; + item->di_tv.v_type = VAR_LIST; + item->di_tv.vval.v_list = list; + if (dict_add(d, item) == FAIL) { + dictitem_free(item); + return FAIL; + } + ++list->lv_refcount; + return OK; +} + +/* + * Get the number of items in a Dictionary. + */ +static long dict_len(d) +dict_T *d; +{ + if (d == NULL) + return 0L; + return (long)d->dv_hashtab.ht_used; +} + +/* + * Find item "key[len]" in Dictionary "d". + * If "len" is negative use strlen(key). + * Returns NULL when not found. + */ +dictitem_T * dict_find(d, key, len) +dict_T *d; +char_u *key; +int len; +{ +#define AKEYLEN 200 + char_u buf[AKEYLEN]; + char_u *akey; + char_u *tofree = NULL; + hashitem_T *hi; + + if (len < 0) + akey = key; + else if (len >= AKEYLEN) { + tofree = akey = vim_strnsave(key, len); + if (akey == NULL) + return NULL; + } else { + /* Avoid a malloc/free by using buf[]. */ + vim_strncpy(buf, key, len); + akey = buf; + } + + hi = hash_find(&d->dv_hashtab, akey); + vim_free(tofree); + if (HASHITEM_EMPTY(hi)) + return NULL; + return HI2DI(hi); +} + +/* + * Get a string item from a dictionary. + * When "save" is TRUE allocate memory for it. + * Returns NULL if the entry doesn't exist or out of memory. + */ +char_u * get_dict_string(d, key, save) +dict_T *d; +char_u *key; +int save; +{ + dictitem_T *di; + char_u *s; + + di = dict_find(d, key, -1); + if (di == NULL) + return NULL; + s = get_tv_string(&di->di_tv); + if (save && s != NULL) + s = vim_strsave(s); + return s; +} + +/* + * Get a number item from a dictionary. + * Returns 0 if the entry doesn't exist or out of memory. + */ +long get_dict_number(d, key) +dict_T *d; +char_u *key; +{ + dictitem_T *di; + + di = dict_find(d, key, -1); + if (di == NULL) + return 0; + return get_tv_number(&di->di_tv); +} + +/* + * Return an allocated string with the string representation of a Dictionary. + * May return NULL. + */ +static char_u * dict2string(tv, copyID) +typval_T *tv; +int copyID; +{ + garray_T ga; + int first = TRUE; + char_u *tofree; + char_u numbuf[NUMBUFLEN]; + hashitem_T *hi; + char_u *s; + dict_T *d; + int todo; + + if ((d = tv->vval.v_dict) == NULL) + return NULL; + ga_init2(&ga, (int)sizeof(char), 80); + ga_append(&ga, '{'); + + todo = (int)d->dv_hashtab.ht_used; + for (hi = d->dv_hashtab.ht_array; todo > 0 && !got_int; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + + if (first) + first = FALSE; + else + ga_concat(&ga, (char_u *)", "); + + tofree = string_quote(hi->hi_key, FALSE); + if (tofree != NULL) { + ga_concat(&ga, tofree); + vim_free(tofree); + } + ga_concat(&ga, (char_u *)": "); + s = tv2string(&HI2DI(hi)->di_tv, &tofree, numbuf, copyID); + if (s != NULL) + ga_concat(&ga, s); + vim_free(tofree); + if (s == NULL) + break; + } + } + if (todo > 0) { + vim_free(ga.ga_data); + return NULL; + } + + ga_append(&ga, '}'); + ga_append(&ga, NUL); + return (char_u *)ga.ga_data; +} + +/* + * Allocate a variable for a Dictionary and fill it from "*arg". + * Return OK or FAIL. Returns NOTDONE for {expr}. + */ +static int get_dict_tv(arg, rettv, evaluate) +char_u **arg; +typval_T *rettv; +int evaluate; +{ + dict_T *d = NULL; + typval_T tvkey; + typval_T tv; + char_u *key = NULL; + dictitem_T *item; + char_u *start = skipwhite(*arg + 1); + char_u buf[NUMBUFLEN]; + + /* + * First check if it's not a curly-braces thing: {expr}. + * Must do this without evaluating, otherwise a function may be called + * twice. Unfortunately this means we need to call eval1() twice for the + * first item. + * But {} is an empty Dictionary. + */ + if (*start != '}') { + if (eval1(&start, &tv, FALSE) == FAIL) /* recursive! */ + return FAIL; + if (*start == '}') + return NOTDONE; + } + + if (evaluate) { + d = dict_alloc(); + if (d == NULL) + return FAIL; + } + tvkey.v_type = VAR_UNKNOWN; + tv.v_type = VAR_UNKNOWN; + + *arg = skipwhite(*arg + 1); + while (**arg != '}' && **arg != NUL) { + if (eval1(arg, &tvkey, evaluate) == FAIL) /* recursive! */ + goto failret; + if (**arg != ':') { + EMSG2(_("E720: Missing colon in Dictionary: %s"), *arg); + clear_tv(&tvkey); + goto failret; + } + if (evaluate) { + key = get_tv_string_buf_chk(&tvkey, buf); + if (key == NULL || *key == NUL) { + /* "key" is NULL when get_tv_string_buf_chk() gave an errmsg */ + if (key != NULL) + EMSG(_(e_emptykey)); + clear_tv(&tvkey); + goto failret; + } + } + + *arg = skipwhite(*arg + 1); + if (eval1(arg, &tv, evaluate) == FAIL) { /* recursive! */ + if (evaluate) + clear_tv(&tvkey); + goto failret; + } + if (evaluate) { + item = dict_find(d, key, -1); + if (item != NULL) { + EMSG2(_("E721: Duplicate key in Dictionary: \"%s\""), key); + clear_tv(&tvkey); + clear_tv(&tv); + goto failret; + } + item = dictitem_alloc(key); + clear_tv(&tvkey); + if (item != NULL) { + item->di_tv = tv; + item->di_tv.v_lock = 0; + if (dict_add(d, item) == FAIL) + dictitem_free(item); + } + } + + if (**arg == '}') + break; + if (**arg != ',') { + EMSG2(_("E722: Missing comma in Dictionary: %s"), *arg); + goto failret; + } + *arg = skipwhite(*arg + 1); + } + + if (**arg != '}') { + EMSG2(_("E723: Missing end of Dictionary '}': %s"), *arg); +failret: + if (evaluate) + dict_free(d, TRUE); + return FAIL; + } + + *arg = skipwhite(*arg + 1); + if (evaluate) { + rettv->v_type = VAR_DICT; + rettv->vval.v_dict = d; + ++d->dv_refcount; + } + + return OK; +} + +/* + * Return a string with the string representation of a variable. + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * "numbuf" is used for a number. + * Does not put quotes around strings, as ":echo" displays values. + * When "copyID" is not NULL replace recursive lists and dicts with "...". + * May return NULL. + */ +static char_u * echo_string(tv, tofree, numbuf, copyID) +typval_T *tv; +char_u **tofree; +char_u *numbuf; +int copyID; +{ + static int recurse = 0; + char_u *r = NULL; + + if (recurse >= DICT_MAXNEST) { + EMSG(_("E724: variable nested too deep for displaying")); + *tofree = NULL; + return NULL; + } + ++recurse; + + switch (tv->v_type) { + case VAR_FUNC: + *tofree = NULL; + r = tv->vval.v_string; + break; + + case VAR_LIST: + if (tv->vval.v_list == NULL) { + *tofree = NULL; + r = NULL; + } else if (copyID != 0 && tv->vval.v_list->lv_copyID == copyID) { + *tofree = NULL; + r = (char_u *)"[...]"; + } else { + tv->vval.v_list->lv_copyID = copyID; + *tofree = list2string(tv, copyID); + r = *tofree; + } + break; + + case VAR_DICT: + if (tv->vval.v_dict == NULL) { + *tofree = NULL; + r = NULL; + } else if (copyID != 0 && tv->vval.v_dict->dv_copyID == copyID) { + *tofree = NULL; + r = (char_u *)"{...}"; + } else { + tv->vval.v_dict->dv_copyID = copyID; + *tofree = dict2string(tv, copyID); + r = *tofree; + } + break; + + case VAR_STRING: + case VAR_NUMBER: + *tofree = NULL; + r = get_tv_string_buf(tv, numbuf); + break; + + case VAR_FLOAT: + *tofree = NULL; + vim_snprintf((char *)numbuf, NUMBUFLEN, "%g", tv->vval.v_float); + r = numbuf; + break; + + default: + EMSG2(_(e_intern2), "echo_string()"); + *tofree = NULL; + } + + --recurse; + return r; +} + +/* + * Return a string with the string representation of a variable. + * If the memory is allocated "tofree" is set to it, otherwise NULL. + * "numbuf" is used for a number. + * Puts quotes around strings, so that they can be parsed back by eval(). + * May return NULL. + */ +static char_u * tv2string(tv, tofree, numbuf, copyID) +typval_T *tv; +char_u **tofree; +char_u *numbuf; +int copyID; +{ + switch (tv->v_type) { + case VAR_FUNC: + *tofree = string_quote(tv->vval.v_string, TRUE); + return *tofree; + case VAR_STRING: + *tofree = string_quote(tv->vval.v_string, FALSE); + return *tofree; + case VAR_FLOAT: + *tofree = NULL; + vim_snprintf((char *)numbuf, NUMBUFLEN - 1, "%g", tv->vval.v_float); + return numbuf; + case VAR_NUMBER: + case VAR_LIST: + case VAR_DICT: + break; + default: + EMSG2(_(e_intern2), "tv2string()"); + } + return echo_string(tv, tofree, numbuf, copyID); +} + +/* + * Return string "str" in ' quotes, doubling ' characters. + * If "str" is NULL an empty string is assumed. + * If "function" is TRUE make it function('string'). + */ +static char_u * string_quote(str, function) +char_u *str; +int function; +{ + unsigned len; + char_u *p, *r, *s; + + len = (function ? 13 : 3); + if (str != NULL) { + len += (unsigned)STRLEN(str); + for (p = str; *p != NUL; mb_ptr_adv(p)) + if (*p == '\'') + ++len; + } + s = r = alloc(len); + if (r != NULL) { + if (function) { + STRCPY(r, "function('"); + r += 10; + } else + *r++ = '\''; + if (str != NULL) + for (p = str; *p != NUL; ) { + if (*p == '\'') + *r++ = '\''; + MB_COPY_CHAR(p, r); + } + *r++ = '\''; + if (function) + *r++ = ')'; + *r++ = NUL; + } + return s; +} + +/* + * Convert the string "text" to a floating point number. + * This uses strtod(). setlocale(LC_NUMERIC, "C") has been used to make sure + * this always uses a decimal point. + * Returns the length of the text that was consumed. + */ +static int string2float(text, value) +char_u *text; +float_T *value; /* result stored here */ +{ + char *s = (char *)text; + float_T f; + + f = strtod(s, &s); + *value = f; + return (int)((char_u *)s - text); +} + +/* + * Get the value of an environment variable. + * "arg" is pointing to the '$'. It is advanced to after the name. + * If the environment variable was not set, silently assume it is empty. + * Always return OK. + */ +static int get_env_tv(arg, rettv, evaluate) +char_u **arg; +typval_T *rettv; +int evaluate; +{ + char_u *string = NULL; + int len; + int cc; + char_u *name; + int mustfree = FALSE; + + ++*arg; + name = *arg; + len = get_env_len(arg); + if (evaluate) { + if (len != 0) { + cc = name[len]; + name[len] = NUL; + /* first try vim_getenv(), fast for normal environment vars */ + string = vim_getenv(name, &mustfree); + if (string != NULL && *string != NUL) { + if (!mustfree) + string = vim_strsave(string); + } else { + if (mustfree) + vim_free(string); + + /* next try expanding things like $VIM and ${HOME} */ + string = expand_env_save(name - 1); + if (string != NULL && *string == '$') { + vim_free(string); + string = NULL; + } + } + name[len] = cc; + } + rettv->v_type = VAR_STRING; + rettv->vval.v_string = string; + } + + return OK; +} + +/* + * Array with names and number of arguments of all internal functions + * MUST BE KEPT SORTED IN strcmp() ORDER FOR BINARY SEARCH! + */ +static struct fst { + char *f_name; /* function name */ + char f_min_argc; /* minimal number of arguments */ + char f_max_argc; /* maximal number of arguments */ + void (*f_func)__ARGS((typval_T *args, typval_T *rvar)); + /* implementation of function */ +} functions[] = +{ + {"abs", 1, 1, f_abs}, + {"acos", 1, 1, f_acos}, /* WJMc */ + {"add", 2, 2, f_add}, + {"and", 2, 2, f_and}, + {"append", 2, 2, f_append}, + {"argc", 0, 0, f_argc}, + {"argidx", 0, 0, f_argidx}, + {"argv", 0, 1, f_argv}, + {"asin", 1, 1, f_asin}, /* WJMc */ + {"atan", 1, 1, f_atan}, + {"atan2", 2, 2, f_atan2}, + {"browse", 4, 4, f_browse}, + {"browsedir", 2, 2, f_browsedir}, + {"bufexists", 1, 1, f_bufexists}, + {"buffer_exists", 1, 1, f_bufexists}, /* obsolete */ + {"buffer_name", 1, 1, f_bufname}, /* obsolete */ + {"buffer_number", 1, 1, f_bufnr}, /* obsolete */ + {"buflisted", 1, 1, f_buflisted}, + {"bufloaded", 1, 1, f_bufloaded}, + {"bufname", 1, 1, f_bufname}, + {"bufnr", 1, 2, f_bufnr}, + {"bufwinnr", 1, 1, f_bufwinnr}, + {"byte2line", 1, 1, f_byte2line}, + {"byteidx", 2, 2, f_byteidx}, + {"byteidxcomp", 2, 2, f_byteidxcomp}, + {"call", 2, 3, f_call}, + {"ceil", 1, 1, f_ceil}, + {"changenr", 0, 0, f_changenr}, + {"char2nr", 1, 2, f_char2nr}, + {"cindent", 1, 1, f_cindent}, + {"clearmatches", 0, 0, f_clearmatches}, + {"col", 1, 1, f_col}, + {"complete", 2, 2, f_complete}, + {"complete_add", 1, 1, f_complete_add}, + {"complete_check", 0, 0, f_complete_check}, + {"confirm", 1, 4, f_confirm}, + {"copy", 1, 1, f_copy}, + {"cos", 1, 1, f_cos}, + {"cosh", 1, 1, f_cosh}, + {"count", 2, 4, f_count}, + {"cscope_connection",0,3, f_cscope_connection}, + {"cursor", 1, 3, f_cursor}, + {"deepcopy", 1, 2, f_deepcopy}, + {"delete", 1, 1, f_delete}, + {"did_filetype", 0, 0, f_did_filetype}, + {"diff_filler", 1, 1, f_diff_filler}, + {"diff_hlID", 2, 2, f_diff_hlID}, + {"empty", 1, 1, f_empty}, + {"escape", 2, 2, f_escape}, + {"eval", 1, 1, f_eval}, + {"eventhandler", 0, 0, f_eventhandler}, + {"executable", 1, 1, f_executable}, + {"exists", 1, 1, f_exists}, + {"exp", 1, 1, f_exp}, + {"expand", 1, 3, f_expand}, + {"extend", 2, 3, f_extend}, + {"feedkeys", 1, 2, f_feedkeys}, + {"file_readable", 1, 1, f_filereadable}, /* obsolete */ + {"filereadable", 1, 1, f_filereadable}, + {"filewritable", 1, 1, f_filewritable}, + {"filter", 2, 2, f_filter}, + {"finddir", 1, 3, f_finddir}, + {"findfile", 1, 3, f_findfile}, + {"float2nr", 1, 1, f_float2nr}, + {"floor", 1, 1, f_floor}, + {"fmod", 2, 2, f_fmod}, + {"fnameescape", 1, 1, f_fnameescape}, + {"fnamemodify", 2, 2, f_fnamemodify}, + {"foldclosed", 1, 1, f_foldclosed}, + {"foldclosedend", 1, 1, f_foldclosedend}, + {"foldlevel", 1, 1, f_foldlevel}, + {"foldtext", 0, 0, f_foldtext}, + {"foldtextresult", 1, 1, f_foldtextresult}, + {"foreground", 0, 0, f_foreground}, + {"function", 1, 1, f_function}, + {"garbagecollect", 0, 1, f_garbagecollect}, + {"get", 2, 3, f_get}, + {"getbufline", 2, 3, f_getbufline}, + {"getbufvar", 2, 3, f_getbufvar}, + {"getchar", 0, 1, f_getchar}, + {"getcharmod", 0, 0, f_getcharmod}, + {"getcmdline", 0, 0, f_getcmdline}, + {"getcmdpos", 0, 0, f_getcmdpos}, + {"getcmdtype", 0, 0, f_getcmdtype}, + {"getcwd", 0, 0, f_getcwd}, + {"getfontname", 0, 1, f_getfontname}, + {"getfperm", 1, 1, f_getfperm}, + {"getfsize", 1, 1, f_getfsize}, + {"getftime", 1, 1, f_getftime}, + {"getftype", 1, 1, f_getftype}, + {"getline", 1, 2, f_getline}, + {"getloclist", 1, 1, f_getqflist}, + {"getmatches", 0, 0, f_getmatches}, + {"getpid", 0, 0, f_getpid}, + {"getpos", 1, 1, f_getpos}, + {"getqflist", 0, 0, f_getqflist}, + {"getreg", 0, 2, f_getreg}, + {"getregtype", 0, 1, f_getregtype}, + {"gettabvar", 2, 3, f_gettabvar}, + {"gettabwinvar", 3, 4, f_gettabwinvar}, + {"getwinposx", 0, 0, f_getwinposx}, + {"getwinposy", 0, 0, f_getwinposy}, + {"getwinvar", 2, 3, f_getwinvar}, + {"glob", 1, 3, f_glob}, + {"globpath", 2, 3, f_globpath}, + {"has", 1, 1, f_has}, + {"has_key", 2, 2, f_has_key}, + {"haslocaldir", 0, 0, f_haslocaldir}, + {"hasmapto", 1, 3, f_hasmapto}, + {"highlightID", 1, 1, f_hlID}, /* obsolete */ + {"highlight_exists",1, 1, f_hlexists}, /* obsolete */ + {"histadd", 2, 2, f_histadd}, + {"histdel", 1, 2, f_histdel}, + {"histget", 1, 2, f_histget}, + {"histnr", 1, 1, f_histnr}, + {"hlID", 1, 1, f_hlID}, + {"hlexists", 1, 1, f_hlexists}, + {"hostname", 0, 0, f_hostname}, + {"iconv", 3, 3, f_iconv}, + {"indent", 1, 1, f_indent}, + {"index", 2, 4, f_index}, + {"input", 1, 3, f_input}, + {"inputdialog", 1, 3, f_inputdialog}, + {"inputlist", 1, 1, f_inputlist}, + {"inputrestore", 0, 0, f_inputrestore}, + {"inputsave", 0, 0, f_inputsave}, + {"inputsecret", 1, 2, f_inputsecret}, + {"insert", 2, 3, f_insert}, + {"invert", 1, 1, f_invert}, + {"isdirectory", 1, 1, f_isdirectory}, + {"islocked", 1, 1, f_islocked}, + {"items", 1, 1, f_items}, + {"join", 1, 2, f_join}, + {"keys", 1, 1, f_keys}, + {"last_buffer_nr", 0, 0, f_last_buffer_nr}, /* obsolete */ + {"len", 1, 1, f_len}, + {"libcall", 3, 3, f_libcall}, + {"libcallnr", 3, 3, f_libcallnr}, + {"line", 1, 1, f_line}, + {"line2byte", 1, 1, f_line2byte}, + {"lispindent", 1, 1, f_lispindent}, + {"localtime", 0, 0, f_localtime}, + {"log", 1, 1, f_log}, + {"log10", 1, 1, f_log10}, + {"map", 2, 2, f_map}, + {"maparg", 1, 4, f_maparg}, + {"mapcheck", 1, 3, f_mapcheck}, + {"match", 2, 4, f_match}, + {"matchadd", 2, 4, f_matchadd}, + {"matcharg", 1, 1, f_matcharg}, + {"matchdelete", 1, 1, f_matchdelete}, + {"matchend", 2, 4, f_matchend}, + {"matchlist", 2, 4, f_matchlist}, + {"matchstr", 2, 4, f_matchstr}, + {"max", 1, 1, f_max}, + {"min", 1, 1, f_min}, +#ifdef vim_mkdir + {"mkdir", 1, 3, f_mkdir}, +#endif + {"mode", 0, 1, f_mode}, + {"nextnonblank", 1, 1, f_nextnonblank}, + {"nr2char", 1, 2, f_nr2char}, + {"or", 2, 2, f_or}, + {"pathshorten", 1, 1, f_pathshorten}, + {"pow", 2, 2, f_pow}, + {"prevnonblank", 1, 1, f_prevnonblank}, + {"printf", 2, 19, f_printf}, + {"pumvisible", 0, 0, f_pumvisible}, + {"range", 1, 3, f_range}, + {"readfile", 1, 3, f_readfile}, + {"reltime", 0, 2, f_reltime}, + {"reltimestr", 1, 1, f_reltimestr}, + {"remote_expr", 2, 3, f_remote_expr}, + {"remote_foreground", 1, 1, f_remote_foreground}, + {"remote_peek", 1, 2, f_remote_peek}, + {"remote_read", 1, 1, f_remote_read}, + {"remote_send", 2, 3, f_remote_send}, + {"remove", 2, 3, f_remove}, + {"rename", 2, 2, f_rename}, + {"repeat", 2, 2, f_repeat}, + {"resolve", 1, 1, f_resolve}, + {"reverse", 1, 1, f_reverse}, + {"round", 1, 1, f_round}, + {"screenattr", 2, 2, f_screenattr}, + {"screenchar", 2, 2, f_screenchar}, + {"screencol", 0, 0, f_screencol}, + {"screenrow", 0, 0, f_screenrow}, + {"search", 1, 4, f_search}, + {"searchdecl", 1, 3, f_searchdecl}, + {"searchpair", 3, 7, f_searchpair}, + {"searchpairpos", 3, 7, f_searchpairpos}, + {"searchpos", 1, 4, f_searchpos}, + {"server2client", 2, 2, f_server2client}, + {"serverlist", 0, 0, f_serverlist}, + {"setbufvar", 3, 3, f_setbufvar}, + {"setcmdpos", 1, 1, f_setcmdpos}, + {"setline", 2, 2, f_setline}, + {"setloclist", 2, 3, f_setloclist}, + {"setmatches", 1, 1, f_setmatches}, + {"setpos", 2, 2, f_setpos}, + {"setqflist", 1, 2, f_setqflist}, + {"setreg", 2, 3, f_setreg}, + {"settabvar", 3, 3, f_settabvar}, + {"settabwinvar", 4, 4, f_settabwinvar}, + {"setwinvar", 3, 3, f_setwinvar}, + {"sha256", 1, 1, f_sha256}, + {"shellescape", 1, 2, f_shellescape}, + {"shiftwidth", 0, 0, f_shiftwidth}, + {"simplify", 1, 1, f_simplify}, + {"sin", 1, 1, f_sin}, + {"sinh", 1, 1, f_sinh}, + {"sort", 1, 3, f_sort}, + {"soundfold", 1, 1, f_soundfold}, + {"spellbadword", 0, 1, f_spellbadword}, + {"spellsuggest", 1, 3, f_spellsuggest}, + {"split", 1, 3, f_split}, + {"sqrt", 1, 1, f_sqrt}, + {"str2float", 1, 1, f_str2float}, + {"str2nr", 1, 2, f_str2nr}, + {"strchars", 1, 1, f_strchars}, + {"strdisplaywidth", 1, 2, f_strdisplaywidth}, +#ifdef HAVE_STRFTIME + {"strftime", 1, 2, f_strftime}, +#endif + {"stridx", 2, 3, f_stridx}, + {"string", 1, 1, f_string}, + {"strlen", 1, 1, f_strlen}, + {"strpart", 2, 3, f_strpart}, + {"strridx", 2, 3, f_strridx}, + {"strtrans", 1, 1, f_strtrans}, + {"strwidth", 1, 1, f_strwidth}, + {"submatch", 1, 1, f_submatch}, + {"substitute", 4, 4, f_substitute}, + {"synID", 3, 3, f_synID}, + {"synIDattr", 2, 3, f_synIDattr}, + {"synIDtrans", 1, 1, f_synIDtrans}, + {"synconcealed", 2, 2, f_synconcealed}, + {"synstack", 2, 2, f_synstack}, + {"system", 1, 2, f_system}, + {"tabpagebuflist", 0, 1, f_tabpagebuflist}, + {"tabpagenr", 0, 1, f_tabpagenr}, + {"tabpagewinnr", 1, 2, f_tabpagewinnr}, + {"tagfiles", 0, 0, f_tagfiles}, + {"taglist", 1, 1, f_taglist}, + {"tan", 1, 1, f_tan}, + {"tanh", 1, 1, f_tanh}, + {"tempname", 0, 0, f_tempname}, + {"test", 1, 1, f_test}, + {"tolower", 1, 1, f_tolower}, + {"toupper", 1, 1, f_toupper}, + {"tr", 3, 3, f_tr}, + {"trunc", 1, 1, f_trunc}, + {"type", 1, 1, f_type}, + {"undofile", 1, 1, f_undofile}, + {"undotree", 0, 0, f_undotree}, + {"values", 1, 1, f_values}, + {"virtcol", 1, 1, f_virtcol}, + {"visualmode", 0, 1, f_visualmode}, + {"wildmenumode", 0, 0, f_wildmenumode}, + {"winbufnr", 1, 1, f_winbufnr}, + {"wincol", 0, 0, f_wincol}, + {"winheight", 1, 1, f_winheight}, + {"winline", 0, 0, f_winline}, + {"winnr", 0, 1, f_winnr}, + {"winrestcmd", 0, 0, f_winrestcmd}, + {"winrestview", 1, 1, f_winrestview}, + {"winsaveview", 0, 0, f_winsaveview}, + {"winwidth", 1, 1, f_winwidth}, + {"writefile", 2, 3, f_writefile}, + {"xor", 2, 2, f_xor}, +}; + + +/* + * Function given to ExpandGeneric() to obtain the list of internal + * or user defined function names. + */ +char_u * get_function_name(xp, idx) +expand_T *xp; +int idx; +{ + static int intidx = -1; + char_u *name; + + if (idx == 0) + intidx = -1; + if (intidx < 0) { + name = get_user_func_name(xp, idx); + if (name != NULL) + return name; + } + if (++intidx < (int)(sizeof(functions) / sizeof(struct fst))) { + STRCPY(IObuff, functions[intidx].f_name); + STRCAT(IObuff, "("); + if (functions[intidx].f_max_argc == 0) + STRCAT(IObuff, ")"); + return IObuff; + } + + return NULL; +} + +/* + * Function given to ExpandGeneric() to obtain the list of internal or + * user defined variable or function names. + */ +char_u * get_expr_name(xp, idx) +expand_T *xp; +int idx; +{ + static int intidx = -1; + char_u *name; + + if (idx == 0) + intidx = -1; + if (intidx < 0) { + name = get_function_name(xp, idx); + if (name != NULL) + return name; + } + return get_user_var_name(xp, ++intidx); +} + + + + +/* + * Find internal function in table above. + * Return index, or -1 if not found + */ +static int find_internal_func(name) +char_u *name; /* name of the function */ +{ + int first = 0; + int last = (int)(sizeof(functions) / sizeof(struct fst)) - 1; + int cmp; + int x; + + /* + * Find the function name in the table. Binary search. + */ + while (first <= last) { + x = first + ((unsigned)(last - first) >> 1); + cmp = STRCMP(name, functions[x].f_name); + if (cmp < 0) + last = x - 1; + else if (cmp > 0) + first = x + 1; + else + return x; + } + return -1; +} + +/* + * Check if "name" is a variable of type VAR_FUNC. If so, return the function + * name it contains, otherwise return "name". + */ +static char_u * deref_func_name(name, lenp, no_autoload) +char_u *name; +int *lenp; +int no_autoload; +{ + dictitem_T *v; + int cc; + + cc = name[*lenp]; + name[*lenp] = NUL; + v = find_var(name, NULL, no_autoload); + name[*lenp] = cc; + if (v != NULL && v->di_tv.v_type == VAR_FUNC) { + if (v->di_tv.vval.v_string == NULL) { + *lenp = 0; + return (char_u *)""; /* just in case */ + } + *lenp = (int)STRLEN(v->di_tv.vval.v_string); + return v->di_tv.vval.v_string; + } + + return name; +} + +/* + * Allocate a variable for the result of a function. + * Return OK or FAIL. + */ +static int get_func_tv(name, len, rettv, arg, firstline, lastline, doesrange, + evaluate, selfdict) +char_u *name; /* name of the function */ +int len; /* length of "name" */ +typval_T *rettv; +char_u **arg; /* argument, pointing to the '(' */ +linenr_T firstline; /* first line of range */ +linenr_T lastline; /* last line of range */ +int *doesrange; /* return: function handled range */ +int evaluate; +dict_T *selfdict; /* Dictionary for "self" */ +{ + char_u *argp; + int ret = OK; + typval_T argvars[MAX_FUNC_ARGS + 1]; /* vars for arguments */ + int argcount = 0; /* number of arguments found */ + + /* + * Get the arguments. + */ + argp = *arg; + while (argcount < MAX_FUNC_ARGS) { + argp = skipwhite(argp + 1); /* skip the '(' or ',' */ + if (*argp == ')' || *argp == ',' || *argp == NUL) + break; + if (eval1(&argp, &argvars[argcount], evaluate) == FAIL) { + ret = FAIL; + break; + } + ++argcount; + if (*argp != ',') + break; + } + if (*argp == ')') + ++argp; + else + ret = FAIL; + + if (ret == OK) + ret = call_func(name, len, rettv, argcount, argvars, + firstline, lastline, doesrange, evaluate, selfdict); + else if (!aborting()) { + if (argcount == MAX_FUNC_ARGS) + emsg_funcname(N_("E740: Too many arguments for function %s"), name); + else + emsg_funcname(N_("E116: Invalid arguments for function %s"), name); + } + + while (--argcount >= 0) + clear_tv(&argvars[argcount]); + + *arg = skipwhite(argp); + return ret; +} + + +/* + * Call a function with its resolved parameters + * Return FAIL when the function can't be called, OK otherwise. + * Also returns OK when an error was encountered while executing the function. + */ +static int call_func(funcname, len, rettv, argcount, argvars, firstline, + lastline, + doesrange, evaluate, + selfdict) +char_u *funcname; /* name of the function */ +int len; /* length of "name" */ +typval_T *rettv; /* return value goes here */ +int argcount; /* number of "argvars" */ +typval_T *argvars; /* vars for arguments, must have "argcount" + PLUS ONE elements! */ +linenr_T firstline; /* first line of range */ +linenr_T lastline; /* last line of range */ +int *doesrange; /* return: function handled range */ +int evaluate; +dict_T *selfdict; /* Dictionary for "self" */ +{ + int ret = FAIL; +#define ERROR_UNKNOWN 0 +#define ERROR_TOOMANY 1 +#define ERROR_TOOFEW 2 +#define ERROR_SCRIPT 3 +#define ERROR_DICT 4 +#define ERROR_NONE 5 +#define ERROR_OTHER 6 + int error = ERROR_NONE; + int i; + int llen; + ufunc_T *fp; +#define FLEN_FIXED 40 + char_u fname_buf[FLEN_FIXED + 1]; + char_u *fname; + char_u *name; + + /* Make a copy of the name, if it comes from a funcref variable it could + * be changed or deleted in the called function. */ + name = vim_strnsave(funcname, len); + if (name == NULL) + return ret; + + /* + * In a script change name() and s:name() to K_SNR 123_name(). + * Change 123_name() to K_SNR 123_name(). + * Use fname_buf[] when it fits, otherwise allocate memory (slow). + */ + llen = eval_fname_script(name); + if (llen > 0) { + fname_buf[0] = K_SPECIAL; + fname_buf[1] = KS_EXTRA; + fname_buf[2] = (int)KE_SNR; + i = 3; + if (eval_fname_sid(name)) { /* "" or "s:" */ + if (current_SID <= 0) + error = ERROR_SCRIPT; + else { + sprintf((char *)fname_buf + 3, "%ld_", (long)current_SID); + i = (int)STRLEN(fname_buf); + } + } + if (i + STRLEN(name + llen) < FLEN_FIXED) { + STRCPY(fname_buf + i, name + llen); + fname = fname_buf; + } else { + fname = alloc((unsigned)(i + STRLEN(name + llen) + 1)); + if (fname == NULL) + error = ERROR_OTHER; + else { + mch_memmove(fname, fname_buf, (size_t)i); + STRCPY(fname + i, name + llen); + } + } + } else + fname = name; + + *doesrange = FALSE; + + + /* execute the function if no errors detected and executing */ + if (evaluate && error == ERROR_NONE) { + rettv->v_type = VAR_NUMBER; /* default rettv is number zero */ + rettv->vval.v_number = 0; + error = ERROR_UNKNOWN; + + if (!builtin_function(fname)) { + /* + * User defined function. + */ + fp = find_func(fname); + + /* Trigger FuncUndefined event, may load the function. */ + if (fp == NULL + && apply_autocmds(EVENT_FUNCUNDEFINED, + fname, fname, TRUE, NULL) + && !aborting()) { + /* executed an autocommand, search for the function again */ + fp = find_func(fname); + } + /* Try loading a package. */ + if (fp == NULL && script_autoload(fname, TRUE) && !aborting()) { + /* loaded a package, search for the function again */ + fp = find_func(fname); + } + + if (fp != NULL) { + if (fp->uf_flags & FC_RANGE) + *doesrange = TRUE; + if (argcount < fp->uf_args.ga_len) + error = ERROR_TOOFEW; + else if (!fp->uf_varargs && argcount > fp->uf_args.ga_len) + error = ERROR_TOOMANY; + else if ((fp->uf_flags & FC_DICT) && selfdict == NULL) + error = ERROR_DICT; + else { + /* + * Call the user function. + * Save and restore search patterns, script variables and + * redo buffer. + */ + save_search_patterns(); + saveRedobuff(); + ++fp->uf_calls; + call_user_func(fp, argcount, argvars, rettv, + firstline, lastline, + (fp->uf_flags & FC_DICT) ? selfdict : NULL); + if (--fp->uf_calls <= 0 && isdigit(*fp->uf_name) + && fp->uf_refcount <= 0) + /* Function was unreferenced while being used, free it + * now. */ + func_free(fp); + restoreRedobuff(); + restore_search_patterns(); + error = ERROR_NONE; + } + } + } else { + /* + * Find the function name in the table, call its implementation. + */ + i = find_internal_func(fname); + if (i >= 0) { + if (argcount < functions[i].f_min_argc) + error = ERROR_TOOFEW; + else if (argcount > functions[i].f_max_argc) + error = ERROR_TOOMANY; + else { + argvars[argcount].v_type = VAR_UNKNOWN; + functions[i].f_func(argvars, rettv); + error = ERROR_NONE; + } + } + } + /* + * The function call (or "FuncUndefined" autocommand sequence) might + * have been aborted by an error, an interrupt, or an explicitly thrown + * exception that has not been caught so far. This situation can be + * tested for by calling aborting(). For an error in an internal + * function or for the "E132" error in call_user_func(), however, the + * throw point at which the "force_abort" flag (temporarily reset by + * emsg()) is normally updated has not been reached yet. We need to + * update that flag first to make aborting() reliable. + */ + update_force_abort(); + } + if (error == ERROR_NONE) + ret = OK; + + /* + * Report an error unless the argument evaluation or function call has been + * cancelled due to an aborting error, an interrupt, or an exception. + */ + if (!aborting()) { + switch (error) { + case ERROR_UNKNOWN: + emsg_funcname(N_("E117: Unknown function: %s"), name); + break; + case ERROR_TOOMANY: + emsg_funcname(e_toomanyarg, name); + break; + case ERROR_TOOFEW: + emsg_funcname(N_("E119: Not enough arguments for function: %s"), + name); + break; + case ERROR_SCRIPT: + emsg_funcname(N_("E120: Using not in a script context: %s"), + name); + break; + case ERROR_DICT: + emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"), + name); + break; + } + } + + if (fname != name && fname != fname_buf) + vim_free(fname); + vim_free(name); + + return ret; +} + +/* + * Give an error message with a function name. Handle things. + * "ermsg" is to be passed without translation, use N_() instead of _(). + */ +static void emsg_funcname(ermsg, name) +char *ermsg; +char_u *name; +{ + char_u *p; + + if (*name == K_SPECIAL) + p = concat_str((char_u *)"", name + 3); + else + p = name; + EMSG2(_(ermsg), p); + if (p != name) + vim_free(p); +} + +/* + * Return TRUE for a non-zero Number and a non-empty String. + */ +static int non_zero_arg(argvars) +typval_T *argvars; +{ + return (argvars[0].v_type == VAR_NUMBER + && argvars[0].vval.v_number != 0) + || (argvars[0].v_type == VAR_STRING + && argvars[0].vval.v_string != NULL + && *argvars[0].vval.v_string != NUL); +} + +/********************************************* + * Implementation of the built-in functions + */ + +static int get_float_arg __ARGS((typval_T *argvars, float_T *f)); + +/* + * Get the float value of "argvars[0]" into "f". + * Returns FAIL when the argument is not a Number or Float. + */ +static int get_float_arg(argvars, f) +typval_T *argvars; +float_T *f; +{ + if (argvars[0].v_type == VAR_FLOAT) { + *f = argvars[0].vval.v_float; + return OK; + } + if (argvars[0].v_type == VAR_NUMBER) { + *f = (float_T)argvars[0].vval.v_number; + return OK; + } + EMSG(_("E808: Number or Float required")); + return FAIL; +} + +/* + * "abs(expr)" function + */ +static void f_abs(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + if (argvars[0].v_type == VAR_FLOAT) { + rettv->v_type = VAR_FLOAT; + rettv->vval.v_float = fabs(argvars[0].vval.v_float); + } else { + varnumber_T n; + int error = FALSE; + + n = get_tv_number_chk(&argvars[0], &error); + if (error) + rettv->vval.v_number = -1; + else if (n > 0) + rettv->vval.v_number = n; + else + rettv->vval.v_number = -n; + } +} + +/* + * "acos()" function + */ +static void f_acos(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = acos(f); + else + rettv->vval.v_float = 0.0; +} + +/* + * "add(list, item)" function + */ +static void f_add(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + list_T *l; + + rettv->vval.v_number = 1; /* Default: Failed */ + if (argvars[0].v_type == VAR_LIST) { + if ((l = argvars[0].vval.v_list) != NULL + && !tv_check_lock(l->lv_lock, (char_u *)_("add() argument")) + && list_append_tv(l, &argvars[1]) == OK) + copy_tv(&argvars[0], rettv); + } else + EMSG(_(e_listreq)); +} + +/* + * "and(expr, expr)" function + */ +static void f_and(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->vval.v_number = get_tv_number_chk(&argvars[0], NULL) + & get_tv_number_chk(&argvars[1], NULL); +} + +/* + * "append(lnum, string/list)" function + */ +static void f_append(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + long lnum; + char_u *line; + list_T *l = NULL; + listitem_T *li = NULL; + typval_T *tv; + long added = 0; + + /* When coming here from Insert mode, sync undo, so that this can be + * undone separately from what was previously inserted. */ + if (u_sync_once == 2) { + u_sync_once = 1; /* notify that u_sync() was called */ + u_sync(TRUE); + } + + lnum = get_tv_lnum(argvars); + if (lnum >= 0 + && lnum <= curbuf->b_ml.ml_line_count + && u_save(lnum, lnum + 1) == OK) { + if (argvars[1].v_type == VAR_LIST) { + l = argvars[1].vval.v_list; + if (l == NULL) + return; + li = l->lv_first; + } + for (;; ) { + if (l == NULL) + tv = &argvars[1]; /* append a string */ + else if (li == NULL) + break; /* end of list */ + else + tv = &li->li_tv; /* append item from list */ + line = get_tv_string_chk(tv); + if (line == NULL) { /* type error */ + rettv->vval.v_number = 1; /* Failed */ + break; + } + ml_append(lnum + added, line, (colnr_T)0, FALSE); + ++added; + if (l == NULL) + break; + li = li->li_next; + } + + appended_lines_mark(lnum, added); + if (curwin->w_cursor.lnum > lnum) + curwin->w_cursor.lnum += added; + } else + rettv->vval.v_number = 1; /* Failed */ +} + +/* + * "argc()" function + */ +static void f_argc(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = ARGCOUNT; +} + +/* + * "argidx()" function + */ +static void f_argidx(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = curwin->w_arg_idx; +} + +/* + * "argv(nr)" function + */ +static void f_argv(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + int idx; + + if (argvars[0].v_type != VAR_UNKNOWN) { + idx = get_tv_number_chk(&argvars[0], NULL); + if (idx >= 0 && idx < ARGCOUNT) + rettv->vval.v_string = vim_strsave(alist_name(&ARGLIST[idx])); + else + rettv->vval.v_string = NULL; + rettv->v_type = VAR_STRING; + } else if (rettv_list_alloc(rettv) == OK) + for (idx = 0; idx < ARGCOUNT; ++idx) + list_append_string(rettv->vval.v_list, + alist_name(&ARGLIST[idx]), -1); +} + +/* + * "asin()" function + */ +static void f_asin(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = asin(f); + else + rettv->vval.v_float = 0.0; +} + +/* + * "atan()" function + */ +static void f_atan(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = atan(f); + else + rettv->vval.v_float = 0.0; +} + +/* + * "atan2()" function + */ +static void f_atan2(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T fx, fy; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &fx) == OK + && get_float_arg(&argvars[1], &fy) == OK) + rettv->vval.v_float = atan2(fx, fy); + else + rettv->vval.v_float = 0.0; +} + +/* + * "browse(save, title, initdir, default)" function + */ +static void f_browse(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_string = NULL; + rettv->v_type = VAR_STRING; +} + +/* + * "browsedir(title, initdir)" function + */ +static void f_browsedir(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_string = NULL; + rettv->v_type = VAR_STRING; +} + +static buf_T *find_buffer __ARGS((typval_T *avar)); + +/* + * Find a buffer by number or exact name. + */ +static buf_T * find_buffer(avar) +typval_T *avar; +{ + buf_T *buf = NULL; + + if (avar->v_type == VAR_NUMBER) + buf = buflist_findnr((int)avar->vval.v_number); + else if (avar->v_type == VAR_STRING && avar->vval.v_string != NULL) { + buf = buflist_findname_exp(avar->vval.v_string); + if (buf == NULL) { + /* No full path name match, try a match with a URL or a "nofile" + * buffer, these don't use the full path. */ + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + if (buf->b_fname != NULL + && (path_with_url(buf->b_fname) + || bt_nofile(buf) + ) + && STRCMP(buf->b_fname, avar->vval.v_string) == 0) + break; + } + } + return buf; +} + +/* + * "bufexists(expr)" function + */ +static void f_bufexists(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->vval.v_number = (find_buffer(&argvars[0]) != NULL); +} + +/* + * "buflisted(expr)" function + */ +static void f_buflisted(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + buf_T *buf; + + buf = find_buffer(&argvars[0]); + rettv->vval.v_number = (buf != NULL && buf->b_p_bl); +} + +/* + * "bufloaded(expr)" function + */ +static void f_bufloaded(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + buf_T *buf; + + buf = find_buffer(&argvars[0]); + rettv->vval.v_number = (buf != NULL && buf->b_ml.ml_mfp != NULL); +} + +static buf_T *get_buf_tv __ARGS((typval_T *tv, int curtab_only)); + +/* + * Get buffer by number or pattern. + */ +static buf_T * get_buf_tv(tv, curtab_only) +typval_T *tv; +int curtab_only; +{ + char_u *name = tv->vval.v_string; + int save_magic; + char_u *save_cpo; + buf_T *buf; + + if (tv->v_type == VAR_NUMBER) + return buflist_findnr((int)tv->vval.v_number); + if (tv->v_type != VAR_STRING) + return NULL; + if (name == NULL || *name == NUL) + return curbuf; + if (name[0] == '$' && name[1] == NUL) + return lastbuf; + + /* Ignore 'magic' and 'cpoptions' here to make scripts portable */ + save_magic = p_magic; + p_magic = TRUE; + save_cpo = p_cpo; + p_cpo = (char_u *)""; + + buf = buflist_findnr(buflist_findpat(name, name + STRLEN(name), + TRUE, FALSE, curtab_only)); + + p_magic = save_magic; + p_cpo = save_cpo; + + /* If not found, try expanding the name, like done for bufexists(). */ + if (buf == NULL) + buf = find_buffer(tv); + + return buf; +} + +/* + * "bufname(expr)" function + */ +static void f_bufname(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + buf_T *buf; + + (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ + ++emsg_off; + buf = get_buf_tv(&argvars[0], FALSE); + rettv->v_type = VAR_STRING; + if (buf != NULL && buf->b_fname != NULL) + rettv->vval.v_string = vim_strsave(buf->b_fname); + else + rettv->vval.v_string = NULL; + --emsg_off; +} + +/* + * "bufnr(expr)" function + */ +static void f_bufnr(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + buf_T *buf; + int error = FALSE; + char_u *name; + + (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ + ++emsg_off; + buf = get_buf_tv(&argvars[0], FALSE); + --emsg_off; + + /* If the buffer isn't found and the second argument is not zero create a + * new buffer. */ + if (buf == NULL + && argvars[1].v_type != VAR_UNKNOWN + && get_tv_number_chk(&argvars[1], &error) != 0 + && !error + && (name = get_tv_string_chk(&argvars[0])) != NULL + && !error) + buf = buflist_new(name, NULL, (linenr_T)1, 0); + + if (buf != NULL) + rettv->vval.v_number = buf->b_fnum; + else + rettv->vval.v_number = -1; +} + +/* + * "bufwinnr(nr)" function + */ +static void f_bufwinnr(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + win_T *wp; + int winnr = 0; + buf_T *buf; + + (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ + ++emsg_off; + buf = get_buf_tv(&argvars[0], TRUE); + for (wp = firstwin; wp; wp = wp->w_next) { + ++winnr; + if (wp->w_buffer == buf) + break; + } + rettv->vval.v_number = (wp != NULL ? winnr : -1); + --emsg_off; +} + +/* + * "byte2line(byte)" function + */ +static void f_byte2line(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + long boff = 0; + + boff = get_tv_number(&argvars[0]) - 1; /* boff gets -1 on type error */ + if (boff < 0) + rettv->vval.v_number = -1; + else + rettv->vval.v_number = ml_find_line_or_offset(curbuf, + (linenr_T)0, &boff); +} + +static void byteidx(argvars, rettv, comp) +typval_T *argvars; +typval_T *rettv; +int comp; +{ + char_u *t; + char_u *str; + long idx; + + str = get_tv_string_chk(&argvars[0]); + idx = get_tv_number_chk(&argvars[1], NULL); + rettv->vval.v_number = -1; + if (str == NULL || idx < 0) + return; + + t = str; + for (; idx > 0; idx--) { + if (*t == NUL) /* EOL reached */ + return; + if (enc_utf8 && comp) + t += utf_ptr2len(t); + else + t += (*mb_ptr2len)(t); + } + rettv->vval.v_number = (varnumber_T)(t - str); +} + +/* + * "byteidx()" function + */ +static void f_byteidx(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + byteidx(argvars, rettv, FALSE); +} + +/* + * "byteidxcomp()" function + */ +static void f_byteidxcomp(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + byteidx(argvars, rettv, TRUE); +} + +int func_call(name, args, selfdict, rettv) +char_u *name; +typval_T *args; +dict_T *selfdict; +typval_T *rettv; +{ + listitem_T *item; + typval_T argv[MAX_FUNC_ARGS + 1]; + int argc = 0; + int dummy; + int r = 0; + + for (item = args->vval.v_list->lv_first; item != NULL; + item = item->li_next) { + if (argc == MAX_FUNC_ARGS) { + EMSG(_("E699: Too many arguments")); + break; + } + /* Make a copy of each argument. This is needed to be able to set + * v_lock to VAR_FIXED in the copy without changing the original list. + */ + copy_tv(&item->li_tv, &argv[argc++]); + } + + if (item == NULL) + r = call_func(name, (int)STRLEN(name), rettv, argc, argv, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, TRUE, selfdict); + + /* Free the arguments. */ + while (argc > 0) + clear_tv(&argv[--argc]); + + return r; +} + +/* + * "call(func, arglist)" function + */ +static void f_call(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *func; + dict_T *selfdict = NULL; + + if (argvars[1].v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; + } + if (argvars[1].vval.v_list == NULL) + return; + + if (argvars[0].v_type == VAR_FUNC) + func = argvars[0].vval.v_string; + else + func = get_tv_string(&argvars[0]); + if (*func == NUL) + return; /* type error or empty name */ + + if (argvars[2].v_type != VAR_UNKNOWN) { + if (argvars[2].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + selfdict = argvars[2].vval.v_dict; + } + + (void)func_call(func, &argvars[1], selfdict, rettv); +} + +/* + * "ceil({float})" function + */ +static void f_ceil(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = ceil(f); + else + rettv->vval.v_float = 0.0; +} + +/* + * "changenr()" function + */ +static void f_changenr(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = curbuf->b_u_seq_cur; +} + +/* + * "char2nr(string)" function + */ +static void f_char2nr(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + if (has_mbyte) { + int utf8 = 0; + + if (argvars[1].v_type != VAR_UNKNOWN) + utf8 = get_tv_number_chk(&argvars[1], NULL); + + if (utf8) + rettv->vval.v_number = (*utf_ptr2char)(get_tv_string(&argvars[0])); + else + rettv->vval.v_number = (*mb_ptr2char)(get_tv_string(&argvars[0])); + } else + rettv->vval.v_number = get_tv_string(&argvars[0])[0]; +} + +/* + * "cindent(lnum)" function + */ +static void f_cindent(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + pos_T pos; + linenr_T lnum; + + pos = curwin->w_cursor; + lnum = get_tv_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { + curwin->w_cursor.lnum = lnum; + rettv->vval.v_number = get_c_indent(); + curwin->w_cursor = pos; + } else + rettv->vval.v_number = -1; +} + +/* + * "clearmatches()" function + */ +static void f_clearmatches(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + clear_matches(curwin); +} + +/* + * "col(string)" function + */ +static void f_col(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + colnr_T col = 0; + pos_T *fp; + int fnum = curbuf->b_fnum; + + fp = var2fpos(&argvars[0], FALSE, &fnum); + if (fp != NULL && fnum == curbuf->b_fnum) { + if (fp->col == MAXCOL) { + /* '> can be MAXCOL, get the length of the line then */ + if (fp->lnum <= curbuf->b_ml.ml_line_count) + col = (colnr_T)STRLEN(ml_get(fp->lnum)) + 1; + else + col = MAXCOL; + } else { + col = fp->col + 1; + /* col(".") when the cursor is on the NUL at the end of the line + * because of "coladd" can be seen as an extra column. */ + if (virtual_active() && fp == &curwin->w_cursor) { + char_u *p = ml_get_cursor(); + + if (curwin->w_cursor.coladd >= (colnr_T)chartabsize(p, + curwin->w_virtcol - curwin->w_cursor.coladd)) { + int l; + + if (*p != NUL && p[(l = (*mb_ptr2len)(p))] == NUL) + col += l; + } + } + } + } + rettv->vval.v_number = col; +} + +/* + * "complete()" function + */ +static void f_complete(argvars, rettv) +typval_T *argvars; +typval_T *rettv UNUSED; +{ + int startcol; + + if ((State & INSERT) == 0) { + EMSG(_("E785: complete() can only be used in Insert mode")); + return; + } + + /* Check for undo allowed here, because if something was already inserted + * the line was already saved for undo and this check isn't done. */ + if (!undo_allowed()) + return; + + if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL) { + EMSG(_(e_invarg)); + return; + } + + startcol = get_tv_number_chk(&argvars[0], NULL); + if (startcol <= 0) + return; + + set_completion(startcol - 1, argvars[1].vval.v_list); +} + +/* + * "complete_add()" function + */ +static void f_complete_add(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->vval.v_number = ins_compl_add_tv(&argvars[0], 0); +} + +/* + * "complete_check()" function + */ +static void f_complete_check(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + int saved = RedrawingDisabled; + + RedrawingDisabled = 0; + ins_compl_check_keys(0); + rettv->vval.v_number = compl_interrupted; + RedrawingDisabled = saved; +} + +/* + * "confirm(message, buttons[, default [, type]])" function + */ +static void f_confirm(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + char_u *message; + char_u *buttons = NULL; + char_u buf[NUMBUFLEN]; + char_u buf2[NUMBUFLEN]; + int def = 1; + int type = VIM_GENERIC; + char_u *typestr; + int error = FALSE; + + message = get_tv_string_chk(&argvars[0]); + if (message == NULL) + error = TRUE; + if (argvars[1].v_type != VAR_UNKNOWN) { + buttons = get_tv_string_buf_chk(&argvars[1], buf); + if (buttons == NULL) + error = TRUE; + if (argvars[2].v_type != VAR_UNKNOWN) { + def = get_tv_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + typestr = get_tv_string_buf_chk(&argvars[3], buf2); + if (typestr == NULL) + error = TRUE; + else { + switch (TOUPPER_ASC(*typestr)) { + case 'E': type = VIM_ERROR; break; + case 'Q': type = VIM_QUESTION; break; + case 'I': type = VIM_INFO; break; + case 'W': type = VIM_WARNING; break; + case 'G': type = VIM_GENERIC; break; + } + } + } + } + } + + if (buttons == NULL || *buttons == NUL) + buttons = (char_u *)_("&Ok"); + + if (!error) + rettv->vval.v_number = do_dialog(type, NULL, message, buttons, + def, NULL, FALSE); +} + +/* + * "copy()" function + */ +static void f_copy(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + item_copy(&argvars[0], rettv, FALSE, 0); +} + +/* + * "cos()" function + */ +static void f_cos(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = cos(f); + else + rettv->vval.v_float = 0.0; +} + +/* + * "cosh()" function + */ +static void f_cosh(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = cosh(f); + else + rettv->vval.v_float = 0.0; +} + +/* + * "count()" function + */ +static void f_count(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + long n = 0; + int ic = FALSE; + + if (argvars[0].v_type == VAR_LIST) { + listitem_T *li; + list_T *l; + long idx; + + if ((l = argvars[0].vval.v_list) != NULL) { + li = l->lv_first; + if (argvars[2].v_type != VAR_UNKNOWN) { + int error = FALSE; + + ic = get_tv_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + idx = get_tv_number_chk(&argvars[3], &error); + if (!error) { + li = list_find(l, idx); + if (li == NULL) + EMSGN(_(e_listidx), idx); + } + } + if (error) + li = NULL; + } + + for (; li != NULL; li = li->li_next) + if (tv_equal(&li->li_tv, &argvars[1], ic, FALSE)) + ++n; + } + } else if (argvars[0].v_type == VAR_DICT) { + int todo; + dict_T *d; + hashitem_T *hi; + + if ((d = argvars[0].vval.v_dict) != NULL) { + int error = FALSE; + + if (argvars[2].v_type != VAR_UNKNOWN) { + ic = get_tv_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) + EMSG(_(e_invarg)); + } + + todo = error ? 0 : (int)d->dv_hashtab.ht_used; + for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + if (tv_equal(&HI2DI(hi)->di_tv, &argvars[1], ic, FALSE)) + ++n; + } + } + } + } else + EMSG2(_(e_listdictarg), "count()"); + rettv->vval.v_number = n; +} + +/* + * "cscope_connection([{num} , {dbpath} [, {prepend}]])" function + * + * Checks the existence of a cscope connection. + */ +static void f_cscope_connection(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + int num = 0; + char_u *dbpath = NULL; + char_u *prepend = NULL; + char_u buf[NUMBUFLEN]; + + if (argvars[0].v_type != VAR_UNKNOWN + && argvars[1].v_type != VAR_UNKNOWN) { + num = (int)get_tv_number(&argvars[0]); + dbpath = get_tv_string(&argvars[1]); + if (argvars[2].v_type != VAR_UNKNOWN) + prepend = get_tv_string_buf(&argvars[2], buf); + } + + rettv->vval.v_number = cs_connection(num, dbpath, prepend); +} + +/* + * "cursor(lnum, col)" function + * + * Moves the cursor to the specified line and column. + * Returns 0 when the position could be set, -1 otherwise. + */ +static void f_cursor(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + long line, col; + long coladd = 0; + + rettv->vval.v_number = -1; + if (argvars[1].v_type == VAR_UNKNOWN) { + pos_T pos; + + if (list2fpos(argvars, &pos, NULL) == FAIL) + return; + line = pos.lnum; + col = pos.col; + coladd = pos.coladd; + } else { + line = get_tv_lnum(argvars); + col = get_tv_number_chk(&argvars[1], NULL); + if (argvars[2].v_type != VAR_UNKNOWN) + coladd = get_tv_number_chk(&argvars[2], NULL); + } + if (line < 0 || col < 0 + || coladd < 0 + ) + return; /* type error; errmsg already given */ + if (line > 0) + curwin->w_cursor.lnum = line; + if (col > 0) + curwin->w_cursor.col = col - 1; + curwin->w_cursor.coladd = coladd; + + /* Make sure the cursor is in a valid position. */ + check_cursor(); + /* Correct cursor for multi-byte character. */ + if (has_mbyte) + mb_adjust_cursor(); + + curwin->w_set_curswant = TRUE; + rettv->vval.v_number = 0; +} + +/* + * "deepcopy()" function + */ +static void f_deepcopy(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + int noref = 0; + + if (argvars[1].v_type != VAR_UNKNOWN) + noref = get_tv_number_chk(&argvars[1], NULL); + if (noref < 0 || noref > 1) + EMSG(_(e_invarg)); + else { + current_copyID += COPYID_INC; + item_copy(&argvars[0], rettv, TRUE, noref == 0 ? current_copyID : 0); + } +} + +/* + * "delete()" function + */ +static void f_delete(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + if (check_restricted() || check_secure()) + rettv->vval.v_number = -1; + else + rettv->vval.v_number = mch_remove(get_tv_string(&argvars[0])); +} + +/* + * "did_filetype()" function + */ +static void f_did_filetype(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + rettv->vval.v_number = did_filetype; +} + +/* + * "diff_filler()" function + */ +static void f_diff_filler(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + rettv->vval.v_number = diff_check_fill(curwin, get_tv_lnum(argvars)); +} + +/* + * "diff_hlID()" function + */ +static void f_diff_hlID(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + linenr_T lnum = get_tv_lnum(argvars); + static linenr_T prev_lnum = 0; + static int changedtick = 0; + static int fnum = 0; + static int change_start = 0; + static int change_end = 0; + static hlf_T hlID = (hlf_T)0; + int filler_lines; + int col; + + if (lnum < 0) /* ignore type error in {lnum} arg */ + lnum = 0; + if (lnum != prev_lnum + || changedtick != curbuf->b_changedtick + || fnum != curbuf->b_fnum) { + /* New line, buffer, change: need to get the values. */ + filler_lines = diff_check(curwin, lnum); + if (filler_lines < 0) { + if (filler_lines == -1) { + change_start = MAXCOL; + change_end = -1; + if (diff_find_change(curwin, lnum, &change_start, &change_end)) + hlID = HLF_ADD; /* added line */ + else + hlID = HLF_CHD; /* changed line */ + } else + hlID = HLF_ADD; /* added line */ + } else + hlID = (hlf_T)0; + prev_lnum = lnum; + changedtick = curbuf->b_changedtick; + fnum = curbuf->b_fnum; + } + + if (hlID == HLF_CHD || hlID == HLF_TXD) { + col = get_tv_number(&argvars[1]) - 1; /* ignore type error in {col} */ + if (col >= change_start && col <= change_end) + hlID = HLF_TXD; /* changed text */ + else + hlID = HLF_CHD; /* changed line */ + } + rettv->vval.v_number = hlID == (hlf_T)0 ? 0 : (int)hlID; +} + +/* + * "empty({expr})" function + */ +static void f_empty(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + int n; + + switch (argvars[0].v_type) { + case VAR_STRING: + case VAR_FUNC: + n = argvars[0].vval.v_string == NULL + || *argvars[0].vval.v_string == NUL; + break; + case VAR_NUMBER: + n = argvars[0].vval.v_number == 0; + break; + case VAR_FLOAT: + n = argvars[0].vval.v_float == 0.0; + break; + case VAR_LIST: + n = argvars[0].vval.v_list == NULL + || argvars[0].vval.v_list->lv_first == NULL; + break; + case VAR_DICT: + n = argvars[0].vval.v_dict == NULL + || argvars[0].vval.v_dict->dv_hashtab.ht_used == 0; + break; + default: + EMSG2(_(e_intern2), "f_empty()"); + n = 0; + } + + rettv->vval.v_number = n; +} + +/* + * "escape({string}, {chars})" function + */ +static void f_escape(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u buf[NUMBUFLEN]; + + rettv->vval.v_string = vim_strsave_escaped(get_tv_string(&argvars[0]), + get_tv_string_buf(&argvars[1], buf)); + rettv->v_type = VAR_STRING; +} + +/* + * "eval()" function + */ +static void f_eval(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *s; + + s = get_tv_string_chk(&argvars[0]); + if (s != NULL) + s = skipwhite(s); + + if (s == NULL || eval1(&s, rettv, TRUE) == FAIL) { + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; + } else if (*s != NUL) + EMSG(_(e_trailing)); +} + +/* + * "eventhandler()" function + */ +static void f_eventhandler(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = vgetc_busy; +} + +/* + * "executable()" function + */ +static void f_executable(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->vval.v_number = mch_can_exe(get_tv_string(&argvars[0])); +} + +/* + * "exists()" function + */ +static void f_exists(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *p; + char_u *name; + int n = FALSE; + int len = 0; + + p = get_tv_string(&argvars[0]); + if (*p == '$') { /* environment variable */ + /* first try "normal" environment variables (fast) */ + if (mch_getenv(p + 1) != NULL) + n = TRUE; + else { + /* try expanding things like $VIM and ${HOME} */ + p = expand_env_save(p); + if (p != NULL && *p != '$') + n = TRUE; + vim_free(p); + } + } else if (*p == '&' || *p == '+') { /* option */ + n = (get_option_tv(&p, NULL, TRUE) == OK); + if (*skipwhite(p) != NUL) + n = FALSE; /* trailing garbage */ + } else if (*p == '*') { /* internal or user defined function */ + n = function_exists(p + 1); + } else if (*p == ':') { + n = cmd_exists(p + 1); + } else if (*p == '#') { + if (p[1] == '#') + n = autocmd_supported(p + 2); + else + n = au_exists(p + 1); + } else { /* internal variable */ + char_u *tofree; + typval_T tv; + + /* get_name_len() takes care of expanding curly braces */ + name = p; + len = get_name_len(&p, &tofree, TRUE, FALSE); + if (len > 0) { + if (tofree != NULL) + name = tofree; + n = (get_var_tv(name, len, &tv, FALSE, TRUE) == OK); + if (n) { + /* handle d.key, l[idx], f(expr) */ + n = (handle_subscript(&p, &tv, TRUE, FALSE) == OK); + if (n) + clear_tv(&tv); + } + } + if (*p != NUL) + n = FALSE; + + vim_free(tofree); + } + + rettv->vval.v_number = n; +} + +/* + * "exp()" function + */ +static void f_exp(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = exp(f); + else + rettv->vval.v_float = 0.0; +} + +/* + * "expand()" function + */ +static void f_expand(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *s; + int len; + char_u *errormsg; + int options = WILD_SILENT|WILD_USE_NL|WILD_LIST_NOTFOUND; + expand_T xpc; + int error = FALSE; + char_u *result; + + rettv->v_type = VAR_STRING; + if (argvars[1].v_type != VAR_UNKNOWN + && argvars[2].v_type != VAR_UNKNOWN + && get_tv_number_chk(&argvars[2], &error) + && !error) { + rettv->v_type = VAR_LIST; + rettv->vval.v_list = NULL; + } + + s = get_tv_string(&argvars[0]); + if (*s == '%' || *s == '#' || *s == '<') { + ++emsg_off; + result = eval_vars(s, s, &len, NULL, &errormsg, NULL); + --emsg_off; + if (rettv->v_type == VAR_LIST) { + if (rettv_list_alloc(rettv) != FAIL && result != NULL) + list_append_string(rettv->vval.v_list, result, -1); + else + vim_free(result); + } else + rettv->vval.v_string = result; + } else { + /* When the optional second argument is non-zero, don't remove matches + * for 'wildignore' and don't put matches for 'suffixes' at the end. */ + if (argvars[1].v_type != VAR_UNKNOWN + && get_tv_number_chk(&argvars[1], &error)) + options |= WILD_KEEP_ALL; + if (!error) { + ExpandInit(&xpc); + xpc.xp_context = EXPAND_FILES; + if (p_wic) + options += WILD_ICASE; + if (rettv->v_type == VAR_STRING) + rettv->vval.v_string = ExpandOne(&xpc, s, NULL, + options, WILD_ALL); + else if (rettv_list_alloc(rettv) != FAIL) { + int i; + + ExpandOne(&xpc, s, NULL, options, WILD_ALL_KEEP); + for (i = 0; i < xpc.xp_numfiles; i++) + list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); + ExpandCleanup(&xpc); + } + } else + rettv->vval.v_string = NULL; + } +} + +/* + * Go over all entries in "d2" and add them to "d1". + * When "action" is "error" then a duplicate key is an error. + * When "action" is "force" then a duplicate key is overwritten. + * Otherwise duplicate keys are ignored ("action" is "keep"). + */ +void dict_extend(d1, d2, action) +dict_T *d1; +dict_T *d2; +char_u *action; +{ + dictitem_T *di1; + hashitem_T *hi2; + int todo; + + todo = (int)d2->dv_hashtab.ht_used; + for (hi2 = d2->dv_hashtab.ht_array; todo > 0; ++hi2) { + if (!HASHITEM_EMPTY(hi2)) { + --todo; + di1 = dict_find(d1, hi2->hi_key, -1); + if (d1->dv_scope != 0) { + /* Disallow replacing a builtin function in l: and g:. + * Check the key to be valid when adding to any + * scope. */ + if (d1->dv_scope == VAR_DEF_SCOPE + && HI2DI(hi2)->di_tv.v_type == VAR_FUNC + && var_check_func_name(hi2->hi_key, + di1 == NULL)) + break; + if (!valid_varname(hi2->hi_key)) + break; + } + if (di1 == NULL) { + di1 = dictitem_copy(HI2DI(hi2)); + if (di1 != NULL && dict_add(d1, di1) == FAIL) + dictitem_free(di1); + } else if (*action == 'e') { + EMSG2(_("E737: Key already exists: %s"), hi2->hi_key); + break; + } else if (*action == 'f' && HI2DI(hi2) != di1) { + clear_tv(&di1->di_tv); + copy_tv(&HI2DI(hi2)->di_tv, &di1->di_tv); + } + } + } +} + +/* + * "extend(list, list [, idx])" function + * "extend(dict, dict [, action])" function + */ +static void f_extend(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char *arg_errmsg = N_("extend() argument"); + + if (argvars[0].v_type == VAR_LIST && argvars[1].v_type == VAR_LIST) { + list_T *l1, *l2; + listitem_T *item; + long before; + int error = FALSE; + + l1 = argvars[0].vval.v_list; + l2 = argvars[1].vval.v_list; + if (l1 != NULL && !tv_check_lock(l1->lv_lock, (char_u *)_(arg_errmsg)) + && l2 != NULL) { + if (argvars[2].v_type != VAR_UNKNOWN) { + before = get_tv_number_chk(&argvars[2], &error); + if (error) + return; /* type error; errmsg already given */ + + if (before == l1->lv_len) + item = NULL; + else { + item = list_find(l1, before); + if (item == NULL) { + EMSGN(_(e_listidx), before); + return; + } + } + } else + item = NULL; + list_extend(l1, l2, item); + + copy_tv(&argvars[0], rettv); + } + } else if (argvars[0].v_type == VAR_DICT && argvars[1].v_type == + VAR_DICT) { + dict_T *d1, *d2; + char_u *action; + int i; + + d1 = argvars[0].vval.v_dict; + d2 = argvars[1].vval.v_dict; + if (d1 != NULL && !tv_check_lock(d1->dv_lock, (char_u *)_(arg_errmsg)) + && d2 != NULL) { + /* Check the third argument. */ + if (argvars[2].v_type != VAR_UNKNOWN) { + static char *(av[]) = {"keep", "force", "error"}; + + action = get_tv_string_chk(&argvars[2]); + if (action == NULL) + return; /* type error; errmsg already given */ + for (i = 0; i < 3; ++i) + if (STRCMP(action, av[i]) == 0) + break; + if (i == 3) { + EMSG2(_(e_invarg2), action); + return; + } + } else + action = (char_u *)"force"; + + dict_extend(d1, d2, action); + + copy_tv(&argvars[0], rettv); + } + } else + EMSG2(_(e_listdictarg), "extend()"); +} + +/* + * "feedkeys()" function + */ +static void f_feedkeys(argvars, rettv) +typval_T *argvars; +typval_T *rettv UNUSED; +{ + int remap = TRUE; + char_u *keys, *flags; + char_u nbuf[NUMBUFLEN]; + int typed = FALSE; + char_u *keys_esc; + + /* This is not allowed in the sandbox. If the commands would still be + * executed in the sandbox it would be OK, but it probably happens later, + * when "sandbox" is no longer set. */ + if (check_secure()) + return; + + keys = get_tv_string(&argvars[0]); + if (*keys != NUL) { + if (argvars[1].v_type != VAR_UNKNOWN) { + flags = get_tv_string_buf(&argvars[1], nbuf); + for (; *flags != NUL; ++flags) { + switch (*flags) { + case 'n': remap = FALSE; break; + case 'm': remap = TRUE; break; + case 't': typed = TRUE; break; + } + } + } + + /* Need to escape K_SPECIAL and CSI before putting the string in the + * typeahead buffer. */ + keys_esc = vim_strsave_escape_csi(keys); + if (keys_esc != NULL) { + ins_typebuf(keys_esc, (remap ? REMAP_YES : REMAP_NONE), + typebuf.tb_len, !typed, FALSE); + vim_free(keys_esc); + if (vgetc_busy) + typebuf_was_filled = TRUE; + } + } +} + +/* + * "filereadable()" function + */ +static void f_filereadable(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + int fd; + char_u *p; + int n; + +#ifndef O_NONBLOCK +# define O_NONBLOCK 0 +#endif + p = get_tv_string(&argvars[0]); + if (*p && !mch_isdir(p) && (fd = mch_open((char *)p, + O_RDONLY | O_NONBLOCK, 0)) >= 0) { + n = TRUE; + close(fd); + } else + n = FALSE; + + rettv->vval.v_number = n; +} + +/* + * Return 0 for not writable, 1 for writable file, 2 for a dir which we have + * rights to write into. + */ +static void f_filewritable(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->vval.v_number = filewritable(get_tv_string(&argvars[0])); +} + +static void findfilendir __ARGS((typval_T *argvars, typval_T *rettv, + int find_what)); + +static void findfilendir(argvars, rettv, find_what) +typval_T *argvars UNUSED; +typval_T *rettv; +int find_what UNUSED; +{ + char_u *fname; + char_u *fresult = NULL; + char_u *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path; + char_u *p; + char_u pathbuf[NUMBUFLEN]; + int count = 1; + int first = TRUE; + int error = FALSE; + + rettv->vval.v_string = NULL; + rettv->v_type = VAR_STRING; + + fname = get_tv_string(&argvars[0]); + + if (argvars[1].v_type != VAR_UNKNOWN) { + p = get_tv_string_buf_chk(&argvars[1], pathbuf); + if (p == NULL) + error = TRUE; + else { + if (*p != NUL) + path = p; + + if (argvars[2].v_type != VAR_UNKNOWN) + count = get_tv_number_chk(&argvars[2], &error); + } + } + + if (count < 0 && rettv_list_alloc(rettv) == FAIL) + error = TRUE; + + if (*fname != NUL && !error) { + do { + if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) + vim_free(fresult); + fresult = find_file_in_path_option(first ? fname : NULL, + first ? (int)STRLEN(fname) : 0, + 0, first, path, + find_what, + curbuf->b_ffname, + find_what == FINDFILE_DIR + ? (char_u *)"" : curbuf->b_p_sua); + first = FALSE; + + if (fresult != NULL && rettv->v_type == VAR_LIST) + list_append_string(rettv->vval.v_list, fresult, -1); + + } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL); + } + + if (rettv->v_type == VAR_STRING) + rettv->vval.v_string = fresult; +} + +static void filter_map __ARGS((typval_T *argvars, typval_T *rettv, int map)); +static int filter_map_one __ARGS((typval_T *tv, char_u *expr, int map, + int *remp)); + +/* + * Implementation of map() and filter(). + */ +static void filter_map(argvars, rettv, map) +typval_T *argvars; +typval_T *rettv; +int map; +{ + char_u buf[NUMBUFLEN]; + char_u *expr; + listitem_T *li, *nli; + list_T *l = NULL; + dictitem_T *di; + hashtab_T *ht; + hashitem_T *hi; + dict_T *d = NULL; + typval_T save_val; + typval_T save_key; + int rem; + int todo; + char_u *ermsg = (char_u *)(map ? "map()" : "filter()"); + char *arg_errmsg = (map ? N_("map() argument") + : N_("filter() argument")); + int save_did_emsg; + int idx = 0; + + if (argvars[0].v_type == VAR_LIST) { + if ((l = argvars[0].vval.v_list) == NULL + || tv_check_lock(l->lv_lock, (char_u *)_(arg_errmsg))) + return; + } else if (argvars[0].v_type == VAR_DICT) { + if ((d = argvars[0].vval.v_dict) == NULL + || tv_check_lock(d->dv_lock, (char_u *)_(arg_errmsg))) + return; + } else { + EMSG2(_(e_listdictarg), ermsg); + return; + } + + expr = get_tv_string_buf_chk(&argvars[1], buf); + /* On type errors, the preceding call has already displayed an error + * message. Avoid a misleading error message for an empty string that + * was not passed as argument. */ + if (expr != NULL) { + prepare_vimvar(VV_VAL, &save_val); + expr = skipwhite(expr); + + /* We reset "did_emsg" to be able to detect whether an error + * occurred during evaluation of the expression. */ + save_did_emsg = did_emsg; + did_emsg = FALSE; + + prepare_vimvar(VV_KEY, &save_key); + if (argvars[0].v_type == VAR_DICT) { + vimvars[VV_KEY].vv_type = VAR_STRING; + + ht = &d->dv_hashtab; + hash_lock(ht); + todo = (int)ht->ht_used; + for (hi = ht->ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + di = HI2DI(hi); + if (tv_check_lock(di->di_tv.v_lock, + (char_u *)_(arg_errmsg))) + break; + vimvars[VV_KEY].vv_str = vim_strsave(di->di_key); + if (filter_map_one(&di->di_tv, expr, map, &rem) == FAIL + || did_emsg) + break; + if (!map && rem) + dictitem_remove(d, di); + clear_tv(&vimvars[VV_KEY].vv_tv); + } + } + hash_unlock(ht); + } else { + vimvars[VV_KEY].vv_type = VAR_NUMBER; + + for (li = l->lv_first; li != NULL; li = nli) { + if (tv_check_lock(li->li_tv.v_lock, (char_u *)_(arg_errmsg))) + break; + nli = li->li_next; + vimvars[VV_KEY].vv_nr = idx; + if (filter_map_one(&li->li_tv, expr, map, &rem) == FAIL + || did_emsg) + break; + if (!map && rem) + listitem_remove(l, li); + ++idx; + } + } + + restore_vimvar(VV_KEY, &save_key); + restore_vimvar(VV_VAL, &save_val); + + did_emsg |= save_did_emsg; + } + + copy_tv(&argvars[0], rettv); +} + +static int filter_map_one(tv, expr, map, remp) +typval_T *tv; +char_u *expr; +int map; +int *remp; +{ + typval_T rettv; + char_u *s; + int retval = FAIL; + + copy_tv(tv, &vimvars[VV_VAL].vv_tv); + s = expr; + if (eval1(&s, &rettv, TRUE) == FAIL) + goto theend; + if (*s != NUL) { /* check for trailing chars after expr */ + EMSG2(_(e_invexpr2), s); + goto theend; + } + if (map) { + /* map(): replace the list item value */ + clear_tv(tv); + rettv.v_lock = 0; + *tv = rettv; + } else { + int error = FALSE; + + /* filter(): when expr is zero remove the item */ + *remp = (get_tv_number_chk(&rettv, &error) == 0); + clear_tv(&rettv); + /* On type error, nothing has been removed; return FAIL to stop the + * loop. The error message was given by get_tv_number_chk(). */ + if (error) + goto theend; + } + retval = OK; +theend: + clear_tv(&vimvars[VV_VAL].vv_tv); + return retval; +} + +/* + * "filter()" function + */ +static void f_filter(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + filter_map(argvars, rettv, FALSE); +} + +/* + * "finddir({fname}[, {path}[, {count}]])" function + */ +static void f_finddir(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + findfilendir(argvars, rettv, FINDFILE_DIR); +} + +/* + * "findfile({fname}[, {path}[, {count}]])" function + */ +static void f_findfile(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + findfilendir(argvars, rettv, FINDFILE_FILE); +} + +/* + * "float2nr({float})" function + */ +static void f_float2nr(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + if (get_float_arg(argvars, &f) == OK) { + if (f < -0x7fffffff) + rettv->vval.v_number = -0x7fffffff; + else if (f > 0x7fffffff) + rettv->vval.v_number = 0x7fffffff; + else + rettv->vval.v_number = (varnumber_T)f; + } +} + +/* + * "floor({float})" function + */ +static void f_floor(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = floor(f); + else + rettv->vval.v_float = 0.0; +} + +/* + * "fmod()" function + */ +static void f_fmod(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T fx, fy; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &fx) == OK + && get_float_arg(&argvars[1], &fy) == OK) + rettv->vval.v_float = fmod(fx, fy); + else + rettv->vval.v_float = 0.0; +} + +/* + * "fnameescape({string})" function + */ +static void f_fnameescape(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->vval.v_string = vim_strsave_fnameescape( + get_tv_string(&argvars[0]), FALSE); + rettv->v_type = VAR_STRING; +} + +/* + * "fnamemodify({fname}, {mods})" function + */ +static void f_fnamemodify(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *fname; + char_u *mods; + int usedlen = 0; + int len; + char_u *fbuf = NULL; + char_u buf[NUMBUFLEN]; + + fname = get_tv_string_chk(&argvars[0]); + mods = get_tv_string_buf_chk(&argvars[1], buf); + if (fname == NULL || mods == NULL) + fname = NULL; + else { + len = (int)STRLEN(fname); + (void)modify_fname(mods, &usedlen, &fname, &fbuf, &len); + } + + rettv->v_type = VAR_STRING; + if (fname == NULL) + rettv->vval.v_string = NULL; + else + rettv->vval.v_string = vim_strnsave(fname, len); + vim_free(fbuf); +} + +static void foldclosed_both __ARGS((typval_T *argvars, typval_T *rettv, int end)); + +/* + * "foldclosed()" function + */ +static void foldclosed_both(argvars, rettv, end) +typval_T *argvars UNUSED; +typval_T *rettv; +int end UNUSED; +{ + linenr_T lnum; + linenr_T first, last; + + lnum = get_tv_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { + if (hasFoldingWin(curwin, lnum, &first, &last, FALSE, NULL)) { + if (end) + rettv->vval.v_number = (varnumber_T)last; + else + rettv->vval.v_number = (varnumber_T)first; + return; + } + } + rettv->vval.v_number = -1; +} + +/* + * "foldclosed()" function + */ +static void f_foldclosed(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + foldclosed_both(argvars, rettv, FALSE); +} + +/* + * "foldclosedend()" function + */ +static void f_foldclosedend(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + foldclosed_both(argvars, rettv, TRUE); +} + +/* + * "foldlevel()" function + */ +static void f_foldlevel(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + linenr_T lnum; + + lnum = get_tv_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) + rettv->vval.v_number = foldLevel(lnum); +} + +/* + * "foldtext()" function + */ +static void f_foldtext(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + linenr_T lnum; + char_u *s; + char_u *r; + int len; + char *txt; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if ((linenr_T)vimvars[VV_FOLDSTART].vv_nr > 0 + && (linenr_T)vimvars[VV_FOLDEND].vv_nr + <= curbuf->b_ml.ml_line_count + && vimvars[VV_FOLDDASHES].vv_str != NULL) { + /* Find first non-empty line in the fold. */ + lnum = (linenr_T)vimvars[VV_FOLDSTART].vv_nr; + while (lnum < (linenr_T)vimvars[VV_FOLDEND].vv_nr) { + if (!linewhite(lnum)) + break; + ++lnum; + } + + /* Find interesting text in this line. */ + s = skipwhite(ml_get(lnum)); + /* skip C comment-start */ + if (s[0] == '/' && (s[1] == '*' || s[1] == '/')) { + s = skipwhite(s + 2); + if (*skipwhite(s) == NUL + && lnum + 1 < (linenr_T)vimvars[VV_FOLDEND].vv_nr) { + s = skipwhite(ml_get(lnum + 1)); + if (*s == '*') + s = skipwhite(s + 1); + } + } + txt = _("+-%s%3ld lines: "); + r = alloc((unsigned)(STRLEN(txt) + + STRLEN(vimvars[VV_FOLDDASHES].vv_str) /* for %s */ + + 20 /* for %3ld */ + + STRLEN(s))); /* concatenated */ + if (r != NULL) { + sprintf((char *)r, txt, vimvars[VV_FOLDDASHES].vv_str, + (long)((linenr_T)vimvars[VV_FOLDEND].vv_nr + - (linenr_T)vimvars[VV_FOLDSTART].vv_nr + 1)); + len = (int)STRLEN(r); + STRCAT(r, s); + /* remove 'foldmarker' and 'commentstring' */ + foldtext_cleanup(r + len); + rettv->vval.v_string = r; + } + } +} + +/* + * "foldtextresult(lnum)" function + */ +static void f_foldtextresult(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + linenr_T lnum; + char_u *text; + char_u buf[51]; + foldinfo_T foldinfo; + int fold_count; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + lnum = get_tv_lnum(argvars); + /* treat illegal types and illegal string values for {lnum} the same */ + if (lnum < 0) + lnum = 0; + fold_count = foldedCount(curwin, lnum, &foldinfo); + if (fold_count > 0) { + text = get_foldtext(curwin, lnum, lnum + fold_count - 1, + &foldinfo, buf); + if (text == buf) + text = vim_strsave(text); + rettv->vval.v_string = text; + } +} + +/* + * "foreground()" function + */ +static void f_foreground(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ +} + +/* + * "function()" function + */ +static void f_function(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *s; + + s = get_tv_string(&argvars[0]); + if (s == NULL || *s == NUL || VIM_ISDIGIT(*s)) + EMSG2(_(e_invarg2), s); + /* Don't check an autoload name for existence here. */ + else if (vim_strchr(s, AUTOLOAD_CHAR) == NULL && !function_exists(s)) + EMSG2(_("E700: Unknown function: %s"), s); + else { + if (STRNCMP(s, "s:", 2) == 0 || STRNCMP(s, "", 5) == 0) { + char sid_buf[25]; + int off = *s == 's' ? 2 : 5; + + /* Expand s: and into nr_, so that the function can + * also be called from another script. Using trans_function_name() + * would also work, but some plugins depend on the name being + * printable text. */ + sprintf(sid_buf, "%ld_", (long)current_SID); + rettv->vval.v_string = + alloc((int)(STRLEN(sid_buf) + STRLEN(s + off) + 1)); + if (rettv->vval.v_string != NULL) { + STRCPY(rettv->vval.v_string, sid_buf); + STRCAT(rettv->vval.v_string, s + off); + } + } else + rettv->vval.v_string = vim_strsave(s); + rettv->v_type = VAR_FUNC; + } +} + +/* + * "garbagecollect()" function + */ +static void f_garbagecollect(argvars, rettv) +typval_T *argvars; +typval_T *rettv UNUSED; +{ + /* This is postponed until we are back at the toplevel, because we may be + * using Lists and Dicts internally. E.g.: ":echo [garbagecollect()]". */ + want_garbage_collect = TRUE; + + if (argvars[0].v_type != VAR_UNKNOWN && get_tv_number(&argvars[0]) == 1) + garbage_collect_at_exit = TRUE; +} + +/* + * "get()" function + */ +static void f_get(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + listitem_T *li; + list_T *l; + dictitem_T *di; + dict_T *d; + typval_T *tv = NULL; + + if (argvars[0].v_type == VAR_LIST) { + if ((l = argvars[0].vval.v_list) != NULL) { + int error = FALSE; + + li = list_find(l, get_tv_number_chk(&argvars[1], &error)); + if (!error && li != NULL) + tv = &li->li_tv; + } + } else if (argvars[0].v_type == VAR_DICT) { + if ((d = argvars[0].vval.v_dict) != NULL) { + di = dict_find(d, get_tv_string(&argvars[1]), -1); + if (di != NULL) + tv = &di->di_tv; + } + } else + EMSG2(_(e_listdictarg), "get()"); + + if (tv == NULL) { + if (argvars[2].v_type != VAR_UNKNOWN) + copy_tv(&argvars[2], rettv); + } else + copy_tv(tv, rettv); +} + +static void get_buffer_lines __ARGS((buf_T *buf, linenr_T start, linenr_T end, + int retlist, + typval_T *rettv)); + +/* + * Get line or list of lines from buffer "buf" into "rettv". + * Return a range (from start to end) of lines in rettv from the specified + * buffer. + * If 'retlist' is TRUE, then the lines are returned as a Vim List. + */ +static void get_buffer_lines(buf, start, end, retlist, rettv) +buf_T *buf; +linenr_T start; +linenr_T end; +int retlist; +typval_T *rettv; +{ + char_u *p; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (retlist && rettv_list_alloc(rettv) == FAIL) + return; + + if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0) + return; + + if (!retlist) { + if (start >= 1 && start <= buf->b_ml.ml_line_count) + p = ml_get_buf(buf, start, FALSE); + else + p = (char_u *)""; + rettv->vval.v_string = vim_strsave(p); + } else { + if (end < start) + return; + + if (start < 1) + start = 1; + if (end > buf->b_ml.ml_line_count) + end = buf->b_ml.ml_line_count; + while (start <= end) + if (list_append_string(rettv->vval.v_list, + ml_get_buf(buf, start++, FALSE), -1) == FAIL) + break; + } +} + +/* + * "getbufline()" function + */ +static void f_getbufline(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + linenr_T lnum; + linenr_T end; + buf_T *buf; + + (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ + ++emsg_off; + buf = get_buf_tv(&argvars[0], FALSE); + --emsg_off; + + lnum = get_tv_lnum_buf(&argvars[1], buf); + if (argvars[2].v_type == VAR_UNKNOWN) + end = lnum; + else + end = get_tv_lnum_buf(&argvars[2], buf); + + get_buffer_lines(buf, lnum, end, TRUE, rettv); +} + +/* + * "getbufvar()" function + */ +static void f_getbufvar(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + buf_T *buf; + buf_T *save_curbuf; + char_u *varname; + dictitem_T *v; + int done = FALSE; + + (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ + varname = get_tv_string_chk(&argvars[1]); + ++emsg_off; + buf = get_buf_tv(&argvars[0], FALSE); + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + if (buf != NULL && varname != NULL) { + /* set curbuf to be our buf, temporarily */ + save_curbuf = curbuf; + curbuf = buf; + + if (*varname == '&') { /* buffer-local-option */ + if (get_option_tv(&varname, rettv, TRUE) == OK) + done = TRUE; + } else if (STRCMP(varname, "changedtick") == 0) { + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = curbuf->b_changedtick; + done = TRUE; + } else { + /* Look up the variable. */ + /* Let getbufvar({nr}, "") return the "b:" dictionary. */ + v = find_var_in_ht(&curbuf->b_vars->dv_hashtab, + 'b', varname, FALSE); + if (v != NULL) { + copy_tv(&v->di_tv, rettv); + done = TRUE; + } + } + + /* restore previous notion of curbuf */ + curbuf = save_curbuf; + } + + if (!done && argvars[2].v_type != VAR_UNKNOWN) + /* use the default value */ + copy_tv(&argvars[2], rettv); + + --emsg_off; +} + +/* + * "getchar()" function + */ +static void f_getchar(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + varnumber_T n; + int error = FALSE; + + /* Position the cursor. Needed after a message that ends in a space. */ + windgoto(msg_row, msg_col); + + ++no_mapping; + ++allow_keys; + for (;; ) { + if (argvars[0].v_type == VAR_UNKNOWN) + /* getchar(): blocking wait. */ + n = safe_vgetc(); + else if (get_tv_number_chk(&argvars[0], &error) == 1) + /* getchar(1): only check if char avail */ + n = vpeekc(); + else if (error || vpeekc() == NUL) + /* illegal argument or getchar(0) and no char avail: return zero */ + n = 0; + else + /* getchar(0) and char avail: return char */ + n = safe_vgetc(); + if (n == K_IGNORE) + continue; + break; + } + --no_mapping; + --allow_keys; + + vimvars[VV_MOUSE_WIN].vv_nr = 0; + vimvars[VV_MOUSE_LNUM].vv_nr = 0; + vimvars[VV_MOUSE_COL].vv_nr = 0; + + rettv->vval.v_number = n; + if (IS_SPECIAL(n) || mod_mask != 0) { + char_u temp[10]; /* modifier: 3, mbyte-char: 6, NUL: 1 */ + int i = 0; + + /* Turn a special key into three bytes, plus modifier. */ + if (mod_mask != 0) { + temp[i++] = K_SPECIAL; + temp[i++] = KS_MODIFIER; + temp[i++] = mod_mask; + } + if (IS_SPECIAL(n)) { + temp[i++] = K_SPECIAL; + temp[i++] = K_SECOND(n); + temp[i++] = K_THIRD(n); + } else if (has_mbyte) + i += (*mb_char2bytes)(n, temp + i); + else + temp[i++] = n; + temp[i++] = NUL; + rettv->v_type = VAR_STRING; + rettv->vval.v_string = vim_strsave(temp); + + if (is_mouse_key(n)) { + int row = mouse_row; + int col = mouse_col; + win_T *win; + linenr_T lnum; + win_T *wp; + int winnr = 1; + + if (row >= 0 && col >= 0) { + /* Find the window at the mouse coordinates and compute the + * text position. */ + win = mouse_find_win(&row, &col); + (void)mouse_comp_pos(win, &row, &col, &lnum); + for (wp = firstwin; wp != win; wp = wp->w_next) + ++winnr; + vimvars[VV_MOUSE_WIN].vv_nr = winnr; + vimvars[VV_MOUSE_LNUM].vv_nr = lnum; + vimvars[VV_MOUSE_COL].vv_nr = col + 1; + } + } + } +} + +/* + * "getcharmod()" function + */ +static void f_getcharmod(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = mod_mask; +} + +/* + * "getcmdline()" function + */ +static void f_getcmdline(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = get_cmdline_str(); +} + +/* + * "getcmdpos()" function + */ +static void f_getcmdpos(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = get_cmdline_pos() + 1; +} + +/* + * "getcmdtype()" function + */ +static void f_getcmdtype(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = alloc(2); + if (rettv->vval.v_string != NULL) { + rettv->vval.v_string[0] = get_cmdline_type(); + rettv->vval.v_string[1] = NUL; + } +} + +/* + * "getcwd()" function + */ +static void f_getcwd(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + char_u *cwd; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + cwd = alloc(MAXPATHL); + if (cwd != NULL) { + if (mch_dirname(cwd, MAXPATHL) != FAIL) { + rettv->vval.v_string = vim_strsave(cwd); +#ifdef BACKSLASH_IN_FILENAME + if (rettv->vval.v_string != NULL) + slash_adjust(rettv->vval.v_string); +#endif + } + vim_free(cwd); + } +} + +/* + * "getfontname()" function + */ +static void f_getfontname(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; +} + +/* + * "getfperm({fname})" function + */ +static void f_getfperm(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *fname; + struct stat st; + char_u *perm = NULL; + char_u flags[] = "rwx"; + int i; + + fname = get_tv_string(&argvars[0]); + + rettv->v_type = VAR_STRING; + if (mch_stat((char *)fname, &st) >= 0) { + perm = vim_strsave((char_u *)"---------"); + if (perm != NULL) { + for (i = 0; i < 9; i++) { + if (st.st_mode & (1 << (8 - i))) + perm[i] = flags[i % 3]; + } + } + } + rettv->vval.v_string = perm; +} + +/* + * "getfsize({fname})" function + */ +static void f_getfsize(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *fname; + struct stat st; + + fname = get_tv_string(&argvars[0]); + + rettv->v_type = VAR_NUMBER; + + if (mch_stat((char *)fname, &st) >= 0) { + if (mch_isdir(fname)) + rettv->vval.v_number = 0; + else { + rettv->vval.v_number = (varnumber_T)st.st_size; + + /* non-perfect check for overflow */ + if ((off_t)rettv->vval.v_number != (off_t)st.st_size) + rettv->vval.v_number = -2; + } + } else + rettv->vval.v_number = -1; +} + +/* + * "getftime({fname})" function + */ +static void f_getftime(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *fname; + struct stat st; + + fname = get_tv_string(&argvars[0]); + + if (mch_stat((char *)fname, &st) >= 0) + rettv->vval.v_number = (varnumber_T)st.st_mtime; + else + rettv->vval.v_number = -1; +} + +/* + * "getftype({fname})" function + */ +static void f_getftype(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *fname; + struct stat st; + char_u *type = NULL; + char *t; + + fname = get_tv_string(&argvars[0]); + + rettv->v_type = VAR_STRING; + if (mch_lstat((char *)fname, &st) >= 0) { +#ifdef S_ISREG + if (S_ISREG(st.st_mode)) + t = "file"; + else if (S_ISDIR(st.st_mode)) + t = "dir"; +# ifdef S_ISLNK + else if (S_ISLNK(st.st_mode)) + t = "link"; +# endif +# ifdef S_ISBLK + else if (S_ISBLK(st.st_mode)) + t = "bdev"; +# endif +# ifdef S_ISCHR + else if (S_ISCHR(st.st_mode)) + t = "cdev"; +# endif +# ifdef S_ISFIFO + else if (S_ISFIFO(st.st_mode)) + t = "fifo"; +# endif +# ifdef S_ISSOCK + else if (S_ISSOCK(st.st_mode)) + t = "fifo"; +# endif + else + t = "other"; +#else +# ifdef S_IFMT + switch (st.st_mode & S_IFMT) { + case S_IFREG: t = "file"; break; + case S_IFDIR: t = "dir"; break; +# ifdef S_IFLNK + case S_IFLNK: t = "link"; break; +# endif +# ifdef S_IFBLK + case S_IFBLK: t = "bdev"; break; +# endif +# ifdef S_IFCHR + case S_IFCHR: t = "cdev"; break; +# endif +# ifdef S_IFIFO + case S_IFIFO: t = "fifo"; break; +# endif +# ifdef S_IFSOCK + case S_IFSOCK: t = "socket"; break; +# endif + default: t = "other"; + } +# else + if (mch_isdir(fname)) + t = "dir"; + else + t = "file"; +# endif +#endif + type = vim_strsave((char_u *)t); + } + rettv->vval.v_string = type; +} + +/* + * "getline(lnum, [end])" function + */ +static void f_getline(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + linenr_T lnum; + linenr_T end; + int retlist; + + lnum = get_tv_lnum(argvars); + if (argvars[1].v_type == VAR_UNKNOWN) { + end = 0; + retlist = FALSE; + } else { + end = get_tv_lnum(&argvars[1]); + retlist = TRUE; + } + + get_buffer_lines(curbuf, lnum, end, retlist, rettv); +} + +/* + * "getmatches()" function + */ +static void f_getmatches(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + dict_T *dict; + matchitem_T *cur = curwin->w_match_head; + + if (rettv_list_alloc(rettv) == OK) { + while (cur != NULL) { + dict = dict_alloc(); + if (dict == NULL) + return; + dict_add_nr_str(dict, "group", 0L, syn_id2name(cur->hlg_id)); + dict_add_nr_str(dict, "pattern", 0L, cur->pattern); + dict_add_nr_str(dict, "priority", (long)cur->priority, NULL); + dict_add_nr_str(dict, "id", (long)cur->id, NULL); + list_append_dict(rettv->vval.v_list, dict); + cur = cur->next; + } + } +} + +/* + * "getpid()" function + */ +static void f_getpid(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = mch_get_pid(); +} + +/* + * "getpos(string)" function + */ +static void f_getpos(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + pos_T *fp; + list_T *l; + int fnum = -1; + + if (rettv_list_alloc(rettv) == OK) { + l = rettv->vval.v_list; + fp = var2fpos(&argvars[0], TRUE, &fnum); + if (fnum != -1) + list_append_number(l, (varnumber_T)fnum); + else + list_append_number(l, (varnumber_T)0); + list_append_number(l, (fp != NULL) ? (varnumber_T)fp->lnum + : (varnumber_T)0); + list_append_number(l, (fp != NULL) + ? (varnumber_T)(fp->col == MAXCOL ? MAXCOL : fp->col + 1) + : (varnumber_T)0); + list_append_number(l, + (fp != NULL) ? (varnumber_T)fp->coladd : + (varnumber_T)0); + } else + rettv->vval.v_number = FALSE; +} + +/* + * "getqflist()" and "getloclist()" functions + */ +static void f_getqflist(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + win_T *wp; + + if (rettv_list_alloc(rettv) == OK) { + wp = NULL; + if (argvars[0].v_type != VAR_UNKNOWN) { /* getloclist() */ + wp = find_win_by_nr(&argvars[0], NULL); + if (wp == NULL) + return; + } + + (void)get_errorlist(wp, rettv->vval.v_list); + } +} + +/* + * "getreg()" function + */ +static void f_getreg(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *strregname; + int regname; + int arg2 = FALSE; + int error = FALSE; + + if (argvars[0].v_type != VAR_UNKNOWN) { + strregname = get_tv_string_chk(&argvars[0]); + error = strregname == NULL; + if (argvars[1].v_type != VAR_UNKNOWN) + arg2 = get_tv_number_chk(&argvars[1], &error); + } else + strregname = vimvars[VV_REG].vv_str; + regname = (strregname == NULL ? '"' : *strregname); + if (regname == 0) + regname = '"'; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = error ? NULL : + get_reg_contents(regname, TRUE, arg2); +} + +/* + * "getregtype()" function + */ +static void f_getregtype(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *strregname; + int regname; + char_u buf[NUMBUFLEN + 2]; + long reglen = 0; + + if (argvars[0].v_type != VAR_UNKNOWN) { + strregname = get_tv_string_chk(&argvars[0]); + if (strregname == NULL) { /* type error; errmsg already given */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + return; + } + } else + /* Default to v:register */ + strregname = vimvars[VV_REG].vv_str; + + regname = (strregname == NULL ? '"' : *strregname); + if (regname == 0) + regname = '"'; + + buf[0] = NUL; + buf[1] = NUL; + switch (get_reg_type(regname, ®len)) { + case MLINE: buf[0] = 'V'; break; + case MCHAR: buf[0] = 'v'; break; + case MBLOCK: + buf[0] = Ctrl_V; + sprintf((char *)buf + 1, "%ld", reglen + 1); + break; + } + rettv->v_type = VAR_STRING; + rettv->vval.v_string = vim_strsave(buf); +} + +/* + * "gettabvar()" function + */ +static void f_gettabvar(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + tabpage_T *tp; + dictitem_T *v; + char_u *varname; + int done = FALSE; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + varname = get_tv_string_chk(&argvars[1]); + tp = find_tabpage((int)get_tv_number_chk(&argvars[0], NULL)); + if (tp != NULL && varname != NULL) { + /* look up the variable */ + v = find_var_in_ht(&tp->tp_vars->dv_hashtab, 0, varname, FALSE); + if (v != NULL) { + copy_tv(&v->di_tv, rettv); + done = TRUE; + } + } + + if (!done && argvars[2].v_type != VAR_UNKNOWN) + copy_tv(&argvars[2], rettv); +} + +/* + * "gettabwinvar()" function + */ +static void f_gettabwinvar(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + getwinvar(argvars, rettv, 1); +} + +/* + * "getwinposx()" function + */ +static void f_getwinposx(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = -1; +} + +/* + * "getwinposy()" function + */ +static void f_getwinposy(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = -1; +} + +/* + * Find window specified by "vp" in tabpage "tp". + */ +static win_T * find_win_by_nr(vp, tp) +typval_T *vp; +tabpage_T *tp UNUSED; /* NULL for current tab page */ +{ + win_T *wp; + int nr; + + nr = get_tv_number_chk(vp, NULL); + + if (nr < 0) + return NULL; + if (nr == 0) + return curwin; + + for (wp = (tp == NULL || tp == curtab) ? firstwin : tp->tp_firstwin; + wp != NULL; wp = wp->w_next) + if (--nr <= 0) + break; + return wp; +} + +/* + * "getwinvar()" function + */ +static void f_getwinvar(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + getwinvar(argvars, rettv, 0); +} + +/* + * getwinvar() and gettabwinvar() + */ +static void getwinvar(argvars, rettv, off) +typval_T *argvars; +typval_T *rettv; +int off; /* 1 for gettabwinvar() */ +{ + win_T *win, *oldcurwin; + char_u *varname; + dictitem_T *v; + tabpage_T *tp = NULL; + tabpage_T *oldtabpage; + int done = FALSE; + + if (off == 1) + tp = find_tabpage((int)get_tv_number_chk(&argvars[0], NULL)); + else + tp = curtab; + win = find_win_by_nr(&argvars[off], tp); + varname = get_tv_string_chk(&argvars[off + 1]); + ++emsg_off; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + if (win != NULL && varname != NULL) { + /* Set curwin to be our win, temporarily. Also set the tabpage, + * otherwise the window is not valid. */ + switch_win(&oldcurwin, &oldtabpage, win, tp, TRUE); + + if (*varname == '&') { /* window-local-option */ + if (get_option_tv(&varname, rettv, 1) == OK) + done = TRUE; + } else { + /* Look up the variable. */ + /* Let getwinvar({nr}, "") return the "w:" dictionary. */ + v = find_var_in_ht(&win->w_vars->dv_hashtab, 'w', varname, FALSE); + if (v != NULL) { + copy_tv(&v->di_tv, rettv); + done = TRUE; + } + } + + /* restore previous notion of curwin */ + restore_win(oldcurwin, oldtabpage, TRUE); + } + + if (!done && argvars[off + 2].v_type != VAR_UNKNOWN) + /* use the default return value */ + copy_tv(&argvars[off + 2], rettv); + + --emsg_off; +} + +/* + * "glob()" function + */ +static void f_glob(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + int options = WILD_SILENT|WILD_USE_NL; + expand_T xpc; + int error = FALSE; + + /* When the optional second argument is non-zero, don't remove matches + * for 'wildignore' and don't put matches for 'suffixes' at the end. */ + rettv->v_type = VAR_STRING; + if (argvars[1].v_type != VAR_UNKNOWN) { + if (get_tv_number_chk(&argvars[1], &error)) + options |= WILD_KEEP_ALL; + if (argvars[2].v_type != VAR_UNKNOWN + && get_tv_number_chk(&argvars[2], &error)) { + rettv->v_type = VAR_LIST; + rettv->vval.v_list = NULL; + } + } + if (!error) { + ExpandInit(&xpc); + xpc.xp_context = EXPAND_FILES; + if (p_wic) + options += WILD_ICASE; + if (rettv->v_type == VAR_STRING) + rettv->vval.v_string = ExpandOne(&xpc, get_tv_string(&argvars[0]), + NULL, options, WILD_ALL); + else if (rettv_list_alloc(rettv) != FAIL) { + int i; + + ExpandOne(&xpc, get_tv_string(&argvars[0]), + NULL, options, WILD_ALL_KEEP); + for (i = 0; i < xpc.xp_numfiles; i++) + list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1); + + ExpandCleanup(&xpc); + } + } else + rettv->vval.v_string = NULL; +} + +/* + * "globpath()" function + */ +static void f_globpath(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + int flags = 0; + char_u buf1[NUMBUFLEN]; + char_u *file = get_tv_string_buf_chk(&argvars[1], buf1); + int error = FALSE; + + /* When the optional second argument is non-zero, don't remove matches + * for 'wildignore' and don't put matches for 'suffixes' at the end. */ + if (argvars[2].v_type != VAR_UNKNOWN + && get_tv_number_chk(&argvars[2], &error)) + flags |= WILD_KEEP_ALL; + rettv->v_type = VAR_STRING; + if (file == NULL || error) + rettv->vval.v_string = NULL; + else + rettv->vval.v_string = globpath(get_tv_string(&argvars[0]), file, + flags); +} + +/* + * "has()" function + */ +static void f_has(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + int i; + char_u *name; + int n = FALSE; + static char *(has_list[]) = + { +#ifdef UNIX + "unix", +#endif +#if defined(WIN64) || defined(_WIN64) + "win64", +#endif +#ifndef CASE_INSENSITIVE_FILENAME + "fname_case", +#endif +#ifdef HAVE_ACL + "acl", +#endif + "arabic", + "autocmd", +#if defined(SOME_BUILTIN_TCAPS) || defined(ALL_BUILTIN_TCAPS) + "builtin_terms", +# ifdef ALL_BUILTIN_TCAPS + "all_builtin_terms", +# endif +#endif +#if defined(FEAT_BROWSE) && (defined(USE_FILE_CHOOSER) \ + || defined(FEAT_GUI_W32) \ + || defined(FEAT_GUI_MOTIF)) + "browsefilter", +#endif + "byte_offset", + "cindent", + "cmdline_compl", + "cmdline_hist", + "comments", + "conceal", + "cryptv", + "cscope", + "cursorbind", +#ifdef CURSOR_SHAPE + "cursorshape", +#endif +#ifdef DEBUG + "debug", +#endif + "dialog_con", + "diff", + "digraphs", + "eval", /* always present, of course! */ + "ex_extra", + "extra_search", + "farsi", + "file_in_path", + "find_in_path", + "float", + "folding", +#if !defined(USE_SYSTEM) && defined(UNIX) + "fork", +#endif + "gettext", + "hangul_input", +#if defined(HAVE_ICONV_H) && defined(USE_ICONV) + "iconv", +#endif + "insert_expand", + "jumplist", + "keymap", + "langmap", +#ifdef FEAT_LIBCALL + "libcall", +#endif + "linebreak", + "lispindent", + "listcmds", + "localmap", + "menu", + "mksession", + "modify_fname", + "mouse", +#if defined(UNIX) || defined(VMS) + "mouse_dec", +# ifdef FEAT_MOUSE_JSB + "mouse_jsbterm", +# endif + "mouse_netterm", + "mouse_sgr", + "mouse_urxvt", + "mouse_xterm", +#endif + "multi_byte", + "multi_lang", +#ifdef FEAT_OLE + "ole", +#endif + "path_extra", + "persistent_undo", + "postscript", + "printer", + "profile", + "reltime", + "quickfix", + "rightleft", + "scrollbind", + "showcmd", + "cmdline_info", + "smartindent", +#ifdef STARTUPTIME + "startuptime", +#endif + "statusline", + "spell", + "syntax", +#if defined(USE_SYSTEM) || !defined(UNIX) + "system", +#endif + "tag_binary", + "tag_old_static", +#ifdef FEAT_TAG_ANYWHITE + "tag_any_white", +#endif +#ifdef TERMINFO + "terminfo", +#endif + "termresponse", + "textobjects", +#ifdef HAVE_TGETENT + "tgetent", +#endif + "title", + "user-commands", /* was accidentally included in 5.4 */ + "user_commands", + "viminfo", + "vertsplit", + "virtualedit", + "visual", + "visualextra", + "vreplace", + "wildignore", + "wildmenu", + "windows", + "winaltkeys", + "writebackup", +#ifdef FEAT_XPM_W32 + "xpm", + "xpm_w32", /* for backward compatibility */ +#else +#endif +#ifdef FEAT_XTERM_SAVE + "xterm_save", +#endif + NULL + }; + + name = get_tv_string(&argvars[0]); + for (i = 0; has_list[i] != NULL; ++i) + if (STRICMP(name, has_list[i]) == 0) { + n = TRUE; + break; + } + + if (n == FALSE) { + if (STRNICMP(name, "patch", 5) == 0) + n = has_patch(atoi((char *)name + 5)); + else if (STRICMP(name, "vim_starting") == 0) + n = (starting != 0); + else if (STRICMP(name, "multi_byte_encoding") == 0) + n = has_mbyte; +#ifdef DYNAMIC_TCL + else if (STRICMP(name, "tcl") == 0) + n = tcl_enabled(FALSE); +#endif +#if defined(USE_ICONV) && defined(DYNAMIC_ICONV) + else if (STRICMP(name, "iconv") == 0) + n = iconv_enabled(FALSE); +#endif +#ifdef DYNAMIC_MZSCHEME + else if (STRICMP(name, "mzscheme") == 0) + n = mzscheme_enabled(FALSE); +#endif + else if (STRICMP(name, "syntax_items") == 0) + n = syntax_present(curwin); + } + + rettv->vval.v_number = n; +} + +/* + * "has_key()" function + */ +static void f_has_key(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + if (argvars[0].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + if (argvars[0].vval.v_dict == NULL) + return; + + rettv->vval.v_number = dict_find(argvars[0].vval.v_dict, + get_tv_string(&argvars[1]), -1) != NULL; +} + +/* + * "haslocaldir()" function + */ +static void f_haslocaldir(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = (curwin->w_localdir != NULL); +} + +/* + * "hasmapto()" function + */ +static void f_hasmapto(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *name; + char_u *mode; + char_u buf[NUMBUFLEN]; + int abbr = FALSE; + + name = get_tv_string(&argvars[0]); + if (argvars[1].v_type == VAR_UNKNOWN) + mode = (char_u *)"nvo"; + else { + mode = get_tv_string_buf(&argvars[1], buf); + if (argvars[2].v_type != VAR_UNKNOWN) + abbr = get_tv_number(&argvars[2]); + } + + if (map_to_exists(name, mode, abbr)) + rettv->vval.v_number = TRUE; + else + rettv->vval.v_number = FALSE; +} + +/* + * "histadd()" function + */ +static void f_histadd(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + int histype; + char_u *str; + char_u buf[NUMBUFLEN]; + + rettv->vval.v_number = FALSE; + if (check_restricted() || check_secure()) + return; + str = get_tv_string_chk(&argvars[0]); /* NULL on type error */ + histype = str != NULL ? get_histtype(str) : -1; + if (histype >= 0) { + str = get_tv_string_buf(&argvars[1], buf); + if (*str != NUL) { + init_history(); + add_to_history(histype, str, FALSE, NUL); + rettv->vval.v_number = TRUE; + return; + } + } +} + +/* + * "histdel()" function + */ +static void f_histdel(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + int n; + char_u buf[NUMBUFLEN]; + char_u *str; + + str = get_tv_string_chk(&argvars[0]); /* NULL on type error */ + if (str == NULL) + n = 0; + else if (argvars[1].v_type == VAR_UNKNOWN) + /* only one argument: clear entire history */ + n = clr_history(get_histtype(str)); + else if (argvars[1].v_type == VAR_NUMBER) + /* index given: remove that entry */ + n = del_history_idx(get_histtype(str), + (int)get_tv_number(&argvars[1])); + else + /* string given: remove all matching entries */ + n = del_history_entry(get_histtype(str), + get_tv_string_buf(&argvars[1], buf)); + rettv->vval.v_number = n; +} + +/* + * "histget()" function + */ +static void f_histget(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + int type; + int idx; + char_u *str; + + str = get_tv_string_chk(&argvars[0]); /* NULL on type error */ + if (str == NULL) + rettv->vval.v_string = NULL; + else { + type = get_histtype(str); + if (argvars[1].v_type == VAR_UNKNOWN) + idx = get_history_idx(type); + else + idx = (int)get_tv_number_chk(&argvars[1], NULL); + /* -1 on type error */ + rettv->vval.v_string = vim_strsave(get_history_entry(type, idx)); + } + rettv->v_type = VAR_STRING; +} + +/* + * "histnr()" function + */ +static void f_histnr(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + int i; + + char_u *history = get_tv_string_chk(&argvars[0]); + + i = history == NULL ? HIST_CMD - 1 : get_histtype(history); + if (i >= HIST_CMD && i < HIST_COUNT) + i = get_history_idx(i); + else + i = -1; + rettv->vval.v_number = i; +} + +/* + * "highlightID(name)" function + */ +static void f_hlID(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->vval.v_number = syn_name2id(get_tv_string(&argvars[0])); +} + +/* + * "highlight_exists()" function + */ +static void f_hlexists(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->vval.v_number = highlight_exists(get_tv_string(&argvars[0])); +} + +/* + * "hostname()" function + */ +static void f_hostname(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + char_u hostname[256]; + + mch_get_host_name(hostname, 256); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = vim_strsave(hostname); +} + +/* + * iconv() function + */ +static void f_iconv(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + char_u buf1[NUMBUFLEN]; + char_u buf2[NUMBUFLEN]; + char_u *from, *to, *str; + vimconv_T vimconv; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + str = get_tv_string(&argvars[0]); + from = enc_canonize(enc_skip(get_tv_string_buf(&argvars[1], buf1))); + to = enc_canonize(enc_skip(get_tv_string_buf(&argvars[2], buf2))); + vimconv.vc_type = CONV_NONE; + convert_setup(&vimconv, from, to); + + /* If the encodings are equal, no conversion needed. */ + if (vimconv.vc_type == CONV_NONE) + rettv->vval.v_string = vim_strsave(str); + else + rettv->vval.v_string = string_convert(&vimconv, str, NULL); + + convert_setup(&vimconv, NULL, NULL); + vim_free(from); + vim_free(to); +} + +/* + * "indent()" function + */ +static void f_indent(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + linenr_T lnum; + + lnum = get_tv_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) + rettv->vval.v_number = get_indent_lnum(lnum); + else + rettv->vval.v_number = -1; +} + +/* + * "index()" function + */ +static void f_index(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + list_T *l; + listitem_T *item; + long idx = 0; + int ic = FALSE; + + rettv->vval.v_number = -1; + if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; + } + l = argvars[0].vval.v_list; + if (l != NULL) { + item = l->lv_first; + if (argvars[2].v_type != VAR_UNKNOWN) { + int error = FALSE; + + /* Start at specified item. Use the cached index that list_find() + * sets, so that a negative number also works. */ + item = list_find(l, get_tv_number_chk(&argvars[2], &error)); + idx = l->lv_idx; + if (argvars[3].v_type != VAR_UNKNOWN) + ic = get_tv_number_chk(&argvars[3], &error); + if (error) + item = NULL; + } + + for (; item != NULL; item = item->li_next, ++idx) + if (tv_equal(&item->li_tv, &argvars[1], ic, FALSE)) { + rettv->vval.v_number = idx; + break; + } + } +} + +static int inputsecret_flag = 0; + +static void get_user_input __ARGS((typval_T *argvars, typval_T *rettv, + int inputdialog)); + +/* + * This function is used by f_input() and f_inputdialog() functions. The third + * argument to f_input() specifies the type of completion to use at the + * prompt. The third argument to f_inputdialog() specifies the value to return + * when the user cancels the prompt. + */ +static void get_user_input(argvars, rettv, inputdialog) +typval_T *argvars; +typval_T *rettv; +int inputdialog; +{ + char_u *prompt = get_tv_string_chk(&argvars[0]); + char_u *p = NULL; + int c; + char_u buf[NUMBUFLEN]; + int cmd_silent_save = cmd_silent; + char_u *defstr = (char_u *)""; + int xp_type = EXPAND_NOTHING; + char_u *xp_arg = NULL; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + +#ifdef NO_CONSOLE_INPUT + /* While starting up, there is no place to enter text. */ + if (no_console_input()) + return; +#endif + + cmd_silent = FALSE; /* Want to see the prompt. */ + if (prompt != NULL) { + /* Only the part of the message after the last NL is considered as + * prompt for the command line */ + p = vim_strrchr(prompt, '\n'); + if (p == NULL) + p = prompt; + else { + ++p; + c = *p; + *p = NUL; + msg_start(); + msg_clr_eos(); + msg_puts_attr(prompt, echo_attr); + msg_didout = FALSE; + msg_starthere(); + *p = c; + } + cmdline_row = msg_row; + + if (argvars[1].v_type != VAR_UNKNOWN) { + defstr = get_tv_string_buf_chk(&argvars[1], buf); + if (defstr != NULL) + stuffReadbuffSpec(defstr); + + if (!inputdialog && argvars[2].v_type != VAR_UNKNOWN) { + char_u *xp_name; + int xp_namelen; + long argt; + + /* input() with a third argument: completion */ + rettv->vval.v_string = NULL; + + xp_name = get_tv_string_buf_chk(&argvars[2], buf); + if (xp_name == NULL) + return; + + xp_namelen = (int)STRLEN(xp_name); + + if (parse_compl_arg(xp_name, xp_namelen, &xp_type, &argt, + &xp_arg) == FAIL) + return; + } + } + + if (defstr != NULL) { + int save_ex_normal_busy = ex_normal_busy; + ex_normal_busy = 0; + rettv->vval.v_string = + getcmdline_prompt(inputsecret_flag ? NUL : '@', p, echo_attr, + xp_type, xp_arg); + ex_normal_busy = save_ex_normal_busy; + } + if (inputdialog && rettv->vval.v_string == NULL + && argvars[1].v_type != VAR_UNKNOWN + && argvars[2].v_type != VAR_UNKNOWN) + rettv->vval.v_string = vim_strsave(get_tv_string_buf( + &argvars[2], buf)); + + vim_free(xp_arg); + + /* since the user typed this, no need to wait for return */ + need_wait_return = FALSE; + msg_didout = FALSE; + } + cmd_silent = cmd_silent_save; +} + +/* + * "input()" function + * Also handles inputsecret() when inputsecret is set. + */ +static void f_input(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + get_user_input(argvars, rettv, FALSE); +} + +/* + * "inputdialog()" function + */ +static void f_inputdialog(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + get_user_input(argvars, rettv, TRUE); +} + +/* + * "inputlist()" function + */ +static void f_inputlist(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + listitem_T *li; + int selected; + int mouse_used; + +#ifdef NO_CONSOLE_INPUT + /* While starting up, there is no place to enter text. */ + if (no_console_input()) + return; +#endif + if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL) { + EMSG2(_(e_listarg), "inputlist()"); + return; + } + + msg_start(); + msg_row = Rows - 1; /* for when 'cmdheight' > 1 */ + lines_left = Rows; /* avoid more prompt */ + msg_scroll = TRUE; + msg_clr_eos(); + + for (li = argvars[0].vval.v_list->lv_first; li != NULL; li = li->li_next) { + msg_puts(get_tv_string(&li->li_tv)); + msg_putchar('\n'); + } + + /* Ask for choice. */ + selected = prompt_for_number(&mouse_used); + if (mouse_used) + selected -= lines_left; + + rettv->vval.v_number = selected; +} + + +static garray_T ga_userinput = {0, 0, sizeof(tasave_T), 4, NULL}; + +/* + * "inputrestore()" function + */ +static void f_inputrestore(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + if (ga_userinput.ga_len > 0) { + --ga_userinput.ga_len; + restore_typeahead((tasave_T *)(ga_userinput.ga_data) + + ga_userinput.ga_len); + /* default return is zero == OK */ + } else if (p_verbose > 1) { + verb_msg((char_u *)_("called inputrestore() more often than inputsave()")); + rettv->vval.v_number = 1; /* Failed */ + } +} + +/* + * "inputsave()" function + */ +static void f_inputsave(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + /* Add an entry to the stack of typeahead storage. */ + if (ga_grow(&ga_userinput, 1) == OK) { + save_typeahead((tasave_T *)(ga_userinput.ga_data) + + ga_userinput.ga_len); + ++ga_userinput.ga_len; + /* default return is zero == OK */ + } else + rettv->vval.v_number = 1; /* Failed */ +} + +/* + * "inputsecret()" function + */ +static void f_inputsecret(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + ++cmdline_star; + ++inputsecret_flag; + f_input(argvars, rettv); + --cmdline_star; + --inputsecret_flag; +} + +/* + * "insert()" function + */ +static void f_insert(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + long before = 0; + listitem_T *item; + list_T *l; + int error = FALSE; + + if (argvars[0].v_type != VAR_LIST) + EMSG2(_(e_listarg), "insert()"); + else if ((l = argvars[0].vval.v_list) != NULL + && !tv_check_lock(l->lv_lock, (char_u *)_("insert() argument"))) { + if (argvars[2].v_type != VAR_UNKNOWN) + before = get_tv_number_chk(&argvars[2], &error); + if (error) + return; /* type error; errmsg already given */ + + if (before == l->lv_len) + item = NULL; + else { + item = list_find(l, before); + if (item == NULL) { + EMSGN(_(e_listidx), before); + l = NULL; + } + } + if (l != NULL) { + list_insert_tv(l, &argvars[1], item); + copy_tv(&argvars[0], rettv); + } + } +} + +/* + * "invert(expr)" function + */ +static void f_invert(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->vval.v_number = ~get_tv_number_chk(&argvars[0], NULL); +} + +/* + * "isdirectory()" function + */ +static void f_isdirectory(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->vval.v_number = mch_isdir(get_tv_string(&argvars[0])); +} + +/* + * "islocked()" function + */ +static void f_islocked(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + lval_T lv; + char_u *end; + dictitem_T *di; + + rettv->vval.v_number = -1; + end = get_lval(get_tv_string(&argvars[0]), NULL, &lv, FALSE, FALSE, + GLV_NO_AUTOLOAD, FNE_CHECK_START); + if (end != NULL && lv.ll_name != NULL) { + if (*end != NUL) + EMSG(_(e_trailing)); + else { + if (lv.ll_tv == NULL) { + if (check_changedtick(lv.ll_name)) + rettv->vval.v_number = 1; /* always locked */ + else { + di = find_var(lv.ll_name, NULL, TRUE); + if (di != NULL) { + /* Consider a variable locked when: + * 1. the variable itself is locked + * 2. the value of the variable is locked. + * 3. the List or Dict value is locked. + */ + rettv->vval.v_number = ((di->di_flags & DI_FLAGS_LOCK) + || tv_islocked(&di->di_tv)); + } + } + } else if (lv.ll_range) + EMSG(_("E786: Range not allowed")); + else if (lv.ll_newkey != NULL) + EMSG2(_(e_dictkey), lv.ll_newkey); + else if (lv.ll_list != NULL) + /* List item. */ + rettv->vval.v_number = tv_islocked(&lv.ll_li->li_tv); + else + /* Dictionary item. */ + rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv); + } + } + + clear_lval(&lv); +} + +static void dict_list __ARGS((typval_T *argvars, typval_T *rettv, int what)); + +/* + * Turn a dict into a list: + * "what" == 0: list of keys + * "what" == 1: list of values + * "what" == 2: list of items + */ +static void dict_list(argvars, rettv, what) +typval_T *argvars; +typval_T *rettv; +int what; +{ + list_T *l2; + dictitem_T *di; + hashitem_T *hi; + listitem_T *li; + listitem_T *li2; + dict_T *d; + int todo; + + if (argvars[0].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + if ((d = argvars[0].vval.v_dict) == NULL) + return; + + if (rettv_list_alloc(rettv) == FAIL) + return; + + todo = (int)d->dv_hashtab.ht_used; + for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + di = HI2DI(hi); + + li = listitem_alloc(); + if (li == NULL) + break; + list_append(rettv->vval.v_list, li); + + if (what == 0) { + /* keys() */ + li->li_tv.v_type = VAR_STRING; + li->li_tv.v_lock = 0; + li->li_tv.vval.v_string = vim_strsave(di->di_key); + } else if (what == 1) { + /* values() */ + copy_tv(&di->di_tv, &li->li_tv); + } else { + /* items() */ + l2 = list_alloc(); + li->li_tv.v_type = VAR_LIST; + li->li_tv.v_lock = 0; + li->li_tv.vval.v_list = l2; + if (l2 == NULL) + break; + ++l2->lv_refcount; + + li2 = listitem_alloc(); + if (li2 == NULL) + break; + list_append(l2, li2); + li2->li_tv.v_type = VAR_STRING; + li2->li_tv.v_lock = 0; + li2->li_tv.vval.v_string = vim_strsave(di->di_key); + + li2 = listitem_alloc(); + if (li2 == NULL) + break; + list_append(l2, li2); + copy_tv(&di->di_tv, &li2->li_tv); + } + } + } +} + +/* + * "items(dict)" function + */ +static void f_items(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + dict_list(argvars, rettv, 2); +} + +/* + * "join()" function + */ +static void f_join(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + garray_T ga; + char_u *sep; + + if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; + } + if (argvars[0].vval.v_list == NULL) + return; + if (argvars[1].v_type == VAR_UNKNOWN) + sep = (char_u *)" "; + else + sep = get_tv_string_chk(&argvars[1]); + + rettv->v_type = VAR_STRING; + + if (sep != NULL) { + ga_init2(&ga, (int)sizeof(char), 80); + list_join(&ga, argvars[0].vval.v_list, sep, TRUE, 0); + ga_append(&ga, NUL); + rettv->vval.v_string = (char_u *)ga.ga_data; + } else + rettv->vval.v_string = NULL; +} + +/* + * "keys()" function + */ +static void f_keys(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + dict_list(argvars, rettv, 0); +} + +/* + * "last_buffer_nr()" function. + */ +static void f_last_buffer_nr(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + int n = 0; + buf_T *buf; + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + if (n < buf->b_fnum) + n = buf->b_fnum; + + rettv->vval.v_number = n; +} + +/* + * "len()" function + */ +static void f_len(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + switch (argvars[0].v_type) { + case VAR_STRING: + case VAR_NUMBER: + rettv->vval.v_number = (varnumber_T)STRLEN( + get_tv_string(&argvars[0])); + break; + case VAR_LIST: + rettv->vval.v_number = list_len(argvars[0].vval.v_list); + break; + case VAR_DICT: + rettv->vval.v_number = dict_len(argvars[0].vval.v_dict); + break; + default: + EMSG(_("E701: Invalid type for len()")); + break; + } +} + +static void libcall_common __ARGS((typval_T *argvars, typval_T *rettv, int type)); + +static void libcall_common(argvars, rettv, type) +typval_T *argvars; +typval_T *rettv; +int type; +{ +#ifdef FEAT_LIBCALL + char_u *string_in; + char_u **string_result; + int nr_result; +#endif + + rettv->v_type = type; + if (type != VAR_NUMBER) + rettv->vval.v_string = NULL; + + if (check_restricted() || check_secure()) + return; + +#ifdef FEAT_LIBCALL + /* The first two args must be strings, otherwise its meaningless */ + if (argvars[0].v_type == VAR_STRING && argvars[1].v_type == VAR_STRING) { + string_in = NULL; + if (argvars[2].v_type == VAR_STRING) + string_in = argvars[2].vval.v_string; + if (type == VAR_NUMBER) + string_result = NULL; + else + string_result = &rettv->vval.v_string; + if (mch_libcall(argvars[0].vval.v_string, + argvars[1].vval.v_string, + string_in, + argvars[2].vval.v_number, + string_result, + &nr_result) == OK + && type == VAR_NUMBER) + rettv->vval.v_number = nr_result; + } +#endif +} + +/* + * "libcall()" function + */ +static void f_libcall(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + libcall_common(argvars, rettv, VAR_STRING); +} + +/* + * "libcallnr()" function + */ +static void f_libcallnr(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + libcall_common(argvars, rettv, VAR_NUMBER); +} + +/* + * "line(string)" function + */ +static void f_line(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + linenr_T lnum = 0; + pos_T *fp; + int fnum; + + fp = var2fpos(&argvars[0], TRUE, &fnum); + if (fp != NULL) + lnum = fp->lnum; + rettv->vval.v_number = lnum; +} + +/* + * "line2byte(lnum)" function + */ +static void f_line2byte(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + linenr_T lnum; + + lnum = get_tv_lnum(argvars); + if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) + rettv->vval.v_number = -1; + else + rettv->vval.v_number = ml_find_line_or_offset(curbuf, lnum, NULL); + if (rettv->vval.v_number >= 0) + ++rettv->vval.v_number; +} + +/* + * "lispindent(lnum)" function + */ +static void f_lispindent(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + pos_T pos; + linenr_T lnum; + + pos = curwin->w_cursor; + lnum = get_tv_lnum(argvars); + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count) { + curwin->w_cursor.lnum = lnum; + rettv->vval.v_number = get_lisp_indent(); + curwin->w_cursor = pos; + } else + rettv->vval.v_number = -1; +} + +/* + * "localtime()" function + */ +static void f_localtime(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = (varnumber_T)time(NULL); +} + +static void get_maparg __ARGS((typval_T *argvars, typval_T *rettv, int exact)); + +static void get_maparg(argvars, rettv, exact) +typval_T *argvars; +typval_T *rettv; +int exact; +{ + char_u *keys; + char_u *which; + char_u buf[NUMBUFLEN]; + char_u *keys_buf = NULL; + char_u *rhs; + int mode; + int abbr = FALSE; + int get_dict = FALSE; + mapblock_T *mp; + int buffer_local; + + /* return empty string for failure */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + keys = get_tv_string(&argvars[0]); + if (*keys == NUL) + return; + + if (argvars[1].v_type != VAR_UNKNOWN) { + which = get_tv_string_buf_chk(&argvars[1], buf); + if (argvars[2].v_type != VAR_UNKNOWN) { + abbr = get_tv_number(&argvars[2]); + if (argvars[3].v_type != VAR_UNKNOWN) + get_dict = get_tv_number(&argvars[3]); + } + } else + which = (char_u *)""; + if (which == NULL) + return; + + mode = get_map_mode(&which, 0); + + keys = replace_termcodes(keys, &keys_buf, TRUE, TRUE, FALSE); + rhs = check_map(keys, mode, exact, FALSE, abbr, &mp, &buffer_local); + vim_free(keys_buf); + + if (!get_dict) { + /* Return a string. */ + if (rhs != NULL) + rettv->vval.v_string = str2special_save(rhs, FALSE); + + } else if (rettv_dict_alloc(rettv) != FAIL && rhs != NULL) { + /* Return a dictionary. */ + char_u *lhs = str2special_save(mp->m_keys, TRUE); + char_u *mapmode = map_mode_to_chars(mp->m_mode); + dict_T *dict = rettv->vval.v_dict; + + dict_add_nr_str(dict, "lhs", 0L, lhs); + dict_add_nr_str(dict, "rhs", 0L, mp->m_orig_str); + dict_add_nr_str(dict, "noremap", mp->m_noremap ? 1L : 0L, NULL); + dict_add_nr_str(dict, "expr", mp->m_expr ? 1L : 0L, NULL); + dict_add_nr_str(dict, "silent", mp->m_silent ? 1L : 0L, NULL); + dict_add_nr_str(dict, "sid", (long)mp->m_script_ID, NULL); + dict_add_nr_str(dict, "buffer", (long)buffer_local, NULL); + dict_add_nr_str(dict, "nowait", mp->m_nowait ? 1L : 0L, NULL); + dict_add_nr_str(dict, "mode", 0L, mapmode); + + vim_free(lhs); + vim_free(mapmode); + } +} + +/* + * "log()" function + */ +static void f_log(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = log(f); + else + rettv->vval.v_float = 0.0; +} + +/* + * "log10()" function + */ +static void f_log10(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = log10(f); + else + rettv->vval.v_float = 0.0; +} + + +/* + * "map()" function + */ +static void f_map(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + filter_map(argvars, rettv, TRUE); +} + +/* + * "maparg()" function + */ +static void f_maparg(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + get_maparg(argvars, rettv, TRUE); +} + +/* + * "mapcheck()" function + */ +static void f_mapcheck(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + get_maparg(argvars, rettv, FALSE); +} + +static void find_some_match __ARGS((typval_T *argvars, typval_T *rettv, + int start)); + +static void find_some_match(argvars, rettv, type) +typval_T *argvars; +typval_T *rettv; +int type; +{ + char_u *str = NULL; + char_u *expr = NULL; + char_u *pat; + regmatch_T regmatch; + char_u patbuf[NUMBUFLEN]; + char_u strbuf[NUMBUFLEN]; + char_u *save_cpo; + long start = 0; + long nth = 1; + colnr_T startcol = 0; + int match = 0; + list_T *l = NULL; + listitem_T *li = NULL; + long idx = 0; + char_u *tofree = NULL; + + /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ + save_cpo = p_cpo; + p_cpo = (char_u *)""; + + rettv->vval.v_number = -1; + if (type == 3) { + /* return empty list when there are no matches */ + if (rettv_list_alloc(rettv) == FAIL) + goto theend; + } else if (type == 2) { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + } + + if (argvars[0].v_type == VAR_LIST) { + if ((l = argvars[0].vval.v_list) == NULL) + goto theend; + li = l->lv_first; + } else + expr = str = get_tv_string(&argvars[0]); + + pat = get_tv_string_buf_chk(&argvars[1], patbuf); + if (pat == NULL) + goto theend; + + if (argvars[2].v_type != VAR_UNKNOWN) { + int error = FALSE; + + start = get_tv_number_chk(&argvars[2], &error); + if (error) + goto theend; + if (l != NULL) { + li = list_find(l, start); + if (li == NULL) + goto theend; + idx = l->lv_idx; /* use the cached index */ + } else { + if (start < 0) + start = 0; + if (start > (long)STRLEN(str)) + goto theend; + /* When "count" argument is there ignore matches before "start", + * otherwise skip part of the string. Differs when pattern is "^" + * or "\<". */ + if (argvars[3].v_type != VAR_UNKNOWN) + startcol = start; + else + str += start; + } + + if (argvars[3].v_type != VAR_UNKNOWN) + nth = get_tv_number_chk(&argvars[3], &error); + if (error) + goto theend; + } + + regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); + if (regmatch.regprog != NULL) { + regmatch.rm_ic = p_ic; + + for (;; ) { + if (l != NULL) { + if (li == NULL) { + match = FALSE; + break; + } + vim_free(tofree); + str = echo_string(&li->li_tv, &tofree, strbuf, 0); + if (str == NULL) + break; + } + + match = vim_regexec_nl(®match, str, (colnr_T)startcol); + + if (match && --nth <= 0) + break; + if (l == NULL && !match) + break; + + /* Advance to just after the match. */ + if (l != NULL) { + li = li->li_next; + ++idx; + } else { + startcol = (colnr_T)(regmatch.startp[0] + + (*mb_ptr2len)(regmatch.startp[0]) - str); + } + } + + if (match) { + if (type == 3) { + int i; + + /* return list with matched string and submatches */ + for (i = 0; i < NSUBEXP; ++i) { + if (regmatch.endp[i] == NULL) { + if (list_append_string(rettv->vval.v_list, + (char_u *)"", 0) == FAIL) + break; + } else if (list_append_string(rettv->vval.v_list, + regmatch.startp[i], + (int)(regmatch.endp[i] - regmatch.startp[i])) + == FAIL) + break; + } + } else if (type == 2) { + /* return matched string */ + if (l != NULL) + copy_tv(&li->li_tv, rettv); + else + rettv->vval.v_string = vim_strnsave(regmatch.startp[0], + (int)(regmatch.endp[0] - regmatch.startp[0])); + } else if (l != NULL) + rettv->vval.v_number = idx; + else { + if (type != 0) + rettv->vval.v_number = + (varnumber_T)(regmatch.startp[0] - str); + else + rettv->vval.v_number = + (varnumber_T)(regmatch.endp[0] - str); + rettv->vval.v_number += (varnumber_T)(str - expr); + } + } + vim_regfree(regmatch.regprog); + } + +theend: + vim_free(tofree); + p_cpo = save_cpo; +} + +/* + * "match()" function + */ +static void f_match(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + find_some_match(argvars, rettv, 1); +} + +/* + * "matchadd()" function + */ +static void f_matchadd(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + char_u buf[NUMBUFLEN]; + char_u *grp = get_tv_string_buf_chk(&argvars[0], buf); /* group */ + char_u *pat = get_tv_string_buf_chk(&argvars[1], buf); /* pattern */ + int prio = 10; /* default priority */ + int id = -1; + int error = FALSE; + + rettv->vval.v_number = -1; + + if (grp == NULL || pat == NULL) + return; + if (argvars[2].v_type != VAR_UNKNOWN) { + prio = get_tv_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) + id = get_tv_number_chk(&argvars[3], &error); + } + if (error == TRUE) + return; + if (id >= 1 && id <= 3) { + EMSGN("E798: ID is reserved for \":match\": %ld", id); + return; + } + + rettv->vval.v_number = match_add(curwin, grp, pat, prio, id); +} + +/* + * "matcharg()" function + */ +static void f_matcharg(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + if (rettv_list_alloc(rettv) == OK) { + int id = get_tv_number(&argvars[0]); + matchitem_T *m; + + if (id >= 1 && id <= 3) { + if ((m = (matchitem_T *)get_match(curwin, id)) != NULL) { + list_append_string(rettv->vval.v_list, + syn_id2name(m->hlg_id), -1); + list_append_string(rettv->vval.v_list, m->pattern, -1); + } else { + list_append_string(rettv->vval.v_list, NULL, -1); + list_append_string(rettv->vval.v_list, NULL, -1); + } + } + } +} + +/* + * "matchdelete()" function + */ +static void f_matchdelete(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + rettv->vval.v_number = match_delete(curwin, + (int)get_tv_number(&argvars[0]), TRUE); +} + +/* + * "matchend()" function + */ +static void f_matchend(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + find_some_match(argvars, rettv, 0); +} + +/* + * "matchlist()" function + */ +static void f_matchlist(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + find_some_match(argvars, rettv, 3); +} + +/* + * "matchstr()" function + */ +static void f_matchstr(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + find_some_match(argvars, rettv, 2); +} + +static void max_min __ARGS((typval_T *argvars, typval_T *rettv, int domax)); + +static void max_min(argvars, rettv, domax) +typval_T *argvars; +typval_T *rettv; +int domax; +{ + long n = 0; + long i; + int error = FALSE; + + if (argvars[0].v_type == VAR_LIST) { + list_T *l; + listitem_T *li; + + l = argvars[0].vval.v_list; + if (l != NULL) { + li = l->lv_first; + if (li != NULL) { + n = get_tv_number_chk(&li->li_tv, &error); + for (;; ) { + li = li->li_next; + if (li == NULL) + break; + i = get_tv_number_chk(&li->li_tv, &error); + if (domax ? i > n : i < n) + n = i; + } + } + } + } else if (argvars[0].v_type == VAR_DICT) { + dict_T *d; + int first = TRUE; + hashitem_T *hi; + int todo; + + d = argvars[0].vval.v_dict; + if (d != NULL) { + todo = (int)d->dv_hashtab.ht_used; + for (hi = d->dv_hashtab.ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + i = get_tv_number_chk(&HI2DI(hi)->di_tv, &error); + if (first) { + n = i; + first = FALSE; + } else if (domax ? i > n : i < n) + n = i; + } + } + } + } else + EMSG(_(e_listdictarg)); + rettv->vval.v_number = error ? 0 : n; +} + +/* + * "max()" function + */ +static void f_max(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + max_min(argvars, rettv, TRUE); +} + +/* + * "min()" function + */ +static void f_min(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + max_min(argvars, rettv, FALSE); +} + +static int mkdir_recurse __ARGS((char_u *dir, int prot)); + +/* + * Create the directory in which "dir" is located, and higher levels when + * needed. + */ +static int mkdir_recurse(dir, prot) +char_u *dir; +int prot; +{ + char_u *p; + char_u *updir; + int r = FAIL; + + /* Get end of directory name in "dir". + * We're done when it's "/" or "c:/". */ + p = gettail_sep(dir); + if (p <= get_past_head(dir)) + return OK; + + /* If the directory exists we're done. Otherwise: create it.*/ + updir = vim_strnsave(dir, (int)(p - dir)); + if (updir == NULL) + return FAIL; + if (mch_isdir(updir)) + r = OK; + else if (mkdir_recurse(updir, prot) == OK) + r = vim_mkdir_emsg(updir, prot); + vim_free(updir); + return r; +} + +#ifdef vim_mkdir +/* + * "mkdir()" function + */ +static void f_mkdir(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *dir; + char_u buf[NUMBUFLEN]; + int prot = 0755; + + rettv->vval.v_number = FAIL; + if (check_restricted() || check_secure()) + return; + + dir = get_tv_string_buf(&argvars[0], buf); + if (*dir == NUL) + rettv->vval.v_number = FAIL; + else { + if (*gettail(dir) == NUL) + /* remove trailing slashes */ + *gettail_sep(dir) = NUL; + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (argvars[2].v_type != VAR_UNKNOWN) + prot = get_tv_number_chk(&argvars[2], NULL); + if (prot != -1 && STRCMP(get_tv_string(&argvars[1]), "p") == 0) + mkdir_recurse(dir, prot); + } + rettv->vval.v_number = prot == -1 ? FAIL : vim_mkdir_emsg(dir, prot); + } +} +#endif + +/* + * "mode()" function + */ +static void f_mode(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u buf[3]; + + buf[1] = NUL; + buf[2] = NUL; + + if (VIsual_active) { + if (VIsual_select) + buf[0] = VIsual_mode + 's' - 'v'; + else + buf[0] = VIsual_mode; + } else if (State == HITRETURN || State == ASKMORE || State == SETWSIZE + || State == CONFIRM) { + buf[0] = 'r'; + if (State == ASKMORE) + buf[1] = 'm'; + else if (State == CONFIRM) + buf[1] = '?'; + } else if (State == EXTERNCMD) + buf[0] = '!'; + else if (State & INSERT) { + if (State & VREPLACE_FLAG) { + buf[0] = 'R'; + buf[1] = 'v'; + } else if (State & REPLACE_FLAG) + buf[0] = 'R'; + else + buf[0] = 'i'; + } else if (State & CMDLINE) { + buf[0] = 'c'; + if (exmode_active) + buf[1] = 'v'; + } else if (exmode_active) { + buf[0] = 'c'; + buf[1] = 'e'; + } else { + buf[0] = 'n'; + if (finish_op) + buf[1] = 'o'; + } + + /* Clear out the minor mode when the argument is not a non-zero number or + * non-empty string. */ + if (!non_zero_arg(&argvars[0])) + buf[1] = NUL; + + rettv->vval.v_string = vim_strsave(buf); + rettv->v_type = VAR_STRING; +} + + +/* + * "nextnonblank()" function + */ +static void f_nextnonblank(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + linenr_T lnum; + + for (lnum = get_tv_lnum(argvars);; ++lnum) { + if (lnum < 0 || lnum > curbuf->b_ml.ml_line_count) { + lnum = 0; + break; + } + if (*skipwhite(ml_get(lnum)) != NUL) + break; + } + rettv->vval.v_number = lnum; +} + +/* + * "nr2char()" function + */ +static void f_nr2char(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u buf[NUMBUFLEN]; + + if (has_mbyte) { + int utf8 = 0; + + if (argvars[1].v_type != VAR_UNKNOWN) + utf8 = get_tv_number_chk(&argvars[1], NULL); + if (utf8) + buf[(*utf_char2bytes)((int)get_tv_number(&argvars[0]), buf)] = NUL; + else + buf[(*mb_char2bytes)((int)get_tv_number(&argvars[0]), buf)] = NUL; + } else { + buf[0] = (char_u)get_tv_number(&argvars[0]); + buf[1] = NUL; + } + rettv->v_type = VAR_STRING; + rettv->vval.v_string = vim_strsave(buf); +} + +/* + * "or(expr, expr)" function + */ +static void f_or(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->vval.v_number = get_tv_number_chk(&argvars[0], NULL) + | get_tv_number_chk(&argvars[1], NULL); +} + +/* + * "pathshorten()" function + */ +static void f_pathshorten(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *p; + + rettv->v_type = VAR_STRING; + p = get_tv_string_chk(&argvars[0]); + if (p == NULL) + rettv->vval.v_string = NULL; + else { + p = vim_strsave(p); + rettv->vval.v_string = p; + if (p != NULL) + shorten_dir(p); + } +} + +/* + * "pow()" function + */ +static void f_pow(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T fx, fy; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &fx) == OK + && get_float_arg(&argvars[1], &fy) == OK) + rettv->vval.v_float = pow(fx, fy); + else + rettv->vval.v_float = 0.0; +} + +/* + * "prevnonblank()" function + */ +static void f_prevnonblank(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + linenr_T lnum; + + lnum = get_tv_lnum(argvars); + if (lnum < 1 || lnum > curbuf->b_ml.ml_line_count) + lnum = 0; + else + while (lnum >= 1 && *skipwhite(ml_get(lnum)) == NUL) + --lnum; + rettv->vval.v_number = lnum; +} + +#ifdef HAVE_STDARG_H +/* This dummy va_list is here because: + * - passing a NULL pointer doesn't work when va_list isn't a pointer + * - locally in the function results in a "used before set" warning + * - using va_start() to initialize it gives "function with fixed args" error */ +static va_list ap; +#endif + +/* + * "printf()" function + */ +static void f_printf(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; +#ifdef HAVE_STDARG_H /* only very old compilers can't do this */ + { + char_u buf[NUMBUFLEN]; + int len; + char_u *s; + int saved_did_emsg = did_emsg; + char *fmt; + + /* Get the required length, allocate the buffer and do it for real. */ + did_emsg = FALSE; + fmt = (char *)get_tv_string_buf(&argvars[0], buf); + len = vim_vsnprintf(NULL, 0, fmt, ap, argvars + 1); + if (!did_emsg) { + s = alloc(len + 1); + if (s != NULL) { + rettv->vval.v_string = s; + (void)vim_vsnprintf((char *)s, len + 1, fmt, ap, argvars + 1); + } + } + did_emsg |= saved_did_emsg; + } +#endif +} + +/* + * "pumvisible()" function + */ +static void f_pumvisible(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + if (pum_visible()) + rettv->vval.v_number = 1; +} + + + +/* + * "range()" function + */ +static void f_range(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + long start; + long end; + long stride = 1; + long i; + int error = FALSE; + + start = get_tv_number_chk(&argvars[0], &error); + if (argvars[1].v_type == VAR_UNKNOWN) { + end = start - 1; + start = 0; + } else { + end = get_tv_number_chk(&argvars[1], &error); + if (argvars[2].v_type != VAR_UNKNOWN) + stride = get_tv_number_chk(&argvars[2], &error); + } + + if (error) + return; /* type error; errmsg already given */ + if (stride == 0) + EMSG(_("E726: Stride is zero")); + else if (stride > 0 ? end + 1 < start : end - 1 > start) + EMSG(_("E727: Start past end")); + else { + if (rettv_list_alloc(rettv) == OK) + for (i = start; stride > 0 ? i <= end : i >= end; i += stride) + if (list_append_number(rettv->vval.v_list, + (varnumber_T)i) == FAIL) + break; + } +} + +/* + * "readfile()" function + */ +static void f_readfile(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + int binary = FALSE; + int failed = FALSE; + char_u *fname; + FILE *fd; + char_u buf[(IOSIZE/256)*256]; /* rounded to avoid odd + 1 */ + int io_size = sizeof(buf); + int readlen; /* size of last fread() */ + char_u *prev = NULL; /* previously read bytes, if any */ + long prevlen = 0; /* length of data in prev */ + long prevsize = 0; /* size of prev buffer */ + long maxline = MAXLNUM; + long cnt = 0; + char_u *p; /* position in buf */ + char_u *start; /* start of current line */ + + if (argvars[1].v_type != VAR_UNKNOWN) { + if (STRCMP(get_tv_string(&argvars[1]), "b") == 0) + binary = TRUE; + if (argvars[2].v_type != VAR_UNKNOWN) + maxline = get_tv_number(&argvars[2]); + } + + if (rettv_list_alloc(rettv) == FAIL) + return; + + /* Always open the file in binary mode, library functions have a mind of + * their own about CR-LF conversion. */ + fname = get_tv_string(&argvars[0]); + if (*fname == NUL || (fd = mch_fopen((char *)fname, READBIN)) == NULL) { + EMSG2(_(e_notopen), *fname == NUL ? (char_u *)_("") : fname); + return; + } + + while (cnt < maxline || maxline < 0) { + readlen = (int)fread(buf, 1, io_size, fd); + + /* This for loop processes what was read, but is also entered at end + * of file so that either: + * - an incomplete line gets written + * - a "binary" file gets an empty line at the end if it ends in a + * newline. */ + for (p = buf, start = buf; + p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); + ++p) { + if (*p == '\n' || readlen <= 0) { + listitem_T *li; + char_u *s = NULL; + long_u len = p - start; + + /* Finished a line. Remove CRs before NL. */ + if (readlen > 0 && !binary) { + while (len > 0 && start[len - 1] == '\r') + --len; + /* removal may cross back to the "prev" string */ + if (len == 0) + while (prevlen > 0 && prev[prevlen - 1] == '\r') + --prevlen; + } + if (prevlen == 0) + s = vim_strnsave(start, (int)len); + else { + /* Change "prev" buffer to be the right size. This way + * the bytes are only copied once, and very long lines are + * allocated only once. */ + if ((s = vim_realloc(prev, prevlen + len + 1)) != NULL) { + mch_memmove(s + prevlen, start, len); + s[prevlen + len] = NUL; + prev = NULL; /* the list will own the string */ + prevlen = prevsize = 0; + } + } + if (s == NULL) { + do_outofmem_msg((long_u) prevlen + len + 1); + failed = TRUE; + break; + } + + if ((li = listitem_alloc()) == NULL) { + vim_free(s); + failed = TRUE; + break; + } + li->li_tv.v_type = VAR_STRING; + li->li_tv.v_lock = 0; + li->li_tv.vval.v_string = s; + list_append(rettv->vval.v_list, li); + + start = p + 1; /* step over newline */ + if ((++cnt >= maxline && maxline >= 0) || readlen <= 0) + break; + } else if (*p == NUL) + *p = '\n'; + /* Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this + * when finding the BF and check the previous two bytes. */ + else if (*p == 0xbf && enc_utf8 && !binary) { + /* Find the two bytes before the 0xbf. If p is at buf, or buf + * + 1, these may be in the "prev" string. */ + char_u back1 = p >= buf + 1 ? p[-1] + : prevlen >= 1 ? prev[prevlen - 1] : NUL; + char_u back2 = p >= buf + 2 ? p[-2] + : p == buf + 1 && prevlen >= 1 ? prev[prevlen - 1] + : prevlen >= 2 ? prev[prevlen - 2] : NUL; + + if (back2 == 0xef && back1 == 0xbb) { + char_u *dest = p - 2; + + /* Usually a BOM is at the beginning of a file, and so at + * the beginning of a line; then we can just step over it. + */ + if (start == dest) + start = p + 1; + else { + /* have to shuffle buf to close gap */ + int adjust_prevlen = 0; + + if (dest < buf) { + adjust_prevlen = (int)(buf - dest); /* must be 1 or 2 */ + dest = buf; + } + if (readlen > p - buf + 1) + mch_memmove(dest, p + 1, readlen - (p - buf) - 1); + readlen -= 3 - adjust_prevlen; + prevlen -= adjust_prevlen; + p = dest - 1; + } + } + } + } /* for */ + + if (failed || (cnt >= maxline && maxline >= 0) || readlen <= 0) + break; + if (start < p) { + /* There's part of a line in buf, store it in "prev". */ + if (p - start + prevlen >= prevsize) { + /* need bigger "prev" buffer */ + char_u *newprev; + + /* A common use case is ordinary text files and "prev" gets a + * fragment of a line, so the first allocation is made + * small, to avoid repeatedly 'allocing' large and + * 'reallocing' small. */ + if (prevsize == 0) + prevsize = (long)(p - start); + else { + long grow50pc = (prevsize * 3) / 2; + long growmin = (long)((p - start) * 2 + prevlen); + prevsize = grow50pc > growmin ? grow50pc : growmin; + } + newprev = prev == NULL ? alloc(prevsize) + : vim_realloc(prev, prevsize); + if (newprev == NULL) { + do_outofmem_msg((long_u)prevsize); + failed = TRUE; + break; + } + prev = newprev; + } + /* Add the line part to end of "prev". */ + mch_memmove(prev + prevlen, start, p - start); + prevlen += (long)(p - start); + } + } /* while */ + + /* + * For a negative line count use only the lines at the end of the file, + * free the rest. + */ + if (!failed && maxline < 0) + while (cnt > -maxline) { + listitem_remove(rettv->vval.v_list, rettv->vval.v_list->lv_first); + --cnt; + } + + if (failed) { + list_free(rettv->vval.v_list, TRUE); + /* readfile doc says an empty list is returned on error */ + rettv->vval.v_list = list_alloc(); + } + + vim_free(prev); + fclose(fd); +} + +static int list2proftime __ARGS((typval_T *arg, proftime_T *tm)); + +/* + * Convert a List to proftime_T. + * Return FAIL when there is something wrong. + */ +static int list2proftime(arg, tm) +typval_T *arg; +proftime_T *tm; +{ + long n1, n2; + int error = FALSE; + + if (arg->v_type != VAR_LIST || arg->vval.v_list == NULL + || arg->vval.v_list->lv_len != 2) + return FAIL; + n1 = list_find_nr(arg->vval.v_list, 0L, &error); + n2 = list_find_nr(arg->vval.v_list, 1L, &error); + tm->tv_sec = n1; + tm->tv_usec = n2; + return error ? FAIL : OK; +} + +/* + * "reltime()" function + */ +static void f_reltime(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + proftime_T res; + proftime_T start; + + if (argvars[0].v_type == VAR_UNKNOWN) { + /* No arguments: get current time. */ + profile_start(&res); + } else if (argvars[1].v_type == VAR_UNKNOWN) { + if (list2proftime(&argvars[0], &res) == FAIL) + return; + profile_end(&res); + } else { + /* Two arguments: compute the difference. */ + if (list2proftime(&argvars[0], &start) == FAIL + || list2proftime(&argvars[1], &res) == FAIL) + return; + profile_sub(&res, &start); + } + + if (rettv_list_alloc(rettv) == OK) { + long n1, n2; + + n1 = res.tv_sec; + n2 = res.tv_usec; + list_append_number(rettv->vval.v_list, (varnumber_T)n1); + list_append_number(rettv->vval.v_list, (varnumber_T)n2); + } +} + +/* + * "reltimestr()" function + */ +static void f_reltimestr(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + proftime_T tm; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (list2proftime(&argvars[0], &tm) == OK) + rettv->vval.v_string = vim_strsave((char_u *)profile_msg(&tm)); +} + + + +/* + * "remote_expr()" function + */ +static void f_remote_expr(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; +} + +/* + * "remote_foreground()" function + */ +static void f_remote_foreground(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ +} + +static void f_remote_peek(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = -1; +} + +static void f_remote_read(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + char_u *r = NULL; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = r; +} + +/* + * "remote_send()" function + */ +static void f_remote_send(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; +} + +/* + * "remove()" function + */ +static void f_remove(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + list_T *l; + listitem_T *item, *item2; + listitem_T *li; + long idx; + long end; + char_u *key; + dict_T *d; + dictitem_T *di; + char *arg_errmsg = N_("remove() argument"); + + if (argvars[0].v_type == VAR_DICT) { + if (argvars[2].v_type != VAR_UNKNOWN) + EMSG2(_(e_toomanyarg), "remove()"); + else if ((d = argvars[0].vval.v_dict) != NULL + && !tv_check_lock(d->dv_lock, (char_u *)_(arg_errmsg))) { + key = get_tv_string_chk(&argvars[1]); + if (key != NULL) { + di = dict_find(d, key, -1); + if (di == NULL) + EMSG2(_(e_dictkey), key); + else { + *rettv = di->di_tv; + init_tv(&di->di_tv); + dictitem_remove(d, di); + } + } + } + } else if (argvars[0].v_type != VAR_LIST) + EMSG2(_(e_listdictarg), "remove()"); + else if ((l = argvars[0].vval.v_list) != NULL + && !tv_check_lock(l->lv_lock, (char_u *)_(arg_errmsg))) { + int error = FALSE; + + idx = get_tv_number_chk(&argvars[1], &error); + if (error) + ; /* type error: do nothing, errmsg already given */ + else if ((item = list_find(l, idx)) == NULL) + EMSGN(_(e_listidx), idx); + else { + if (argvars[2].v_type == VAR_UNKNOWN) { + /* Remove one item, return its value. */ + list_remove(l, item, item); + *rettv = item->li_tv; + vim_free(item); + } else { + /* Remove range of items, return list with values. */ + end = get_tv_number_chk(&argvars[2], &error); + if (error) + ; /* type error: do nothing */ + else if ((item2 = list_find(l, end)) == NULL) + EMSGN(_(e_listidx), end); + else { + int cnt = 0; + + for (li = item; li != NULL; li = li->li_next) { + ++cnt; + if (li == item2) + break; + } + if (li == NULL) /* didn't find "item2" after "item" */ + EMSG(_(e_invrange)); + else { + list_remove(l, item, item2); + if (rettv_list_alloc(rettv) == OK) { + l = rettv->vval.v_list; + l->lv_first = item; + l->lv_last = item2; + item->li_prev = NULL; + item2->li_next = NULL; + l->lv_len = cnt; + } + } + } + } + } + } +} + +/* + * "rename({from}, {to})" function + */ +static void f_rename(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u buf[NUMBUFLEN]; + + if (check_restricted() || check_secure()) + rettv->vval.v_number = -1; + else + rettv->vval.v_number = vim_rename(get_tv_string(&argvars[0]), + get_tv_string_buf(&argvars[1], buf)); +} + +/* + * "repeat()" function + */ +static void f_repeat(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *p; + int n; + int slen; + int len; + char_u *r; + int i; + + n = get_tv_number(&argvars[1]); + if (argvars[0].v_type == VAR_LIST) { + if (rettv_list_alloc(rettv) == OK && argvars[0].vval.v_list != NULL) + while (n-- > 0) + if (list_extend(rettv->vval.v_list, + argvars[0].vval.v_list, NULL) == FAIL) + break; + } else { + p = get_tv_string(&argvars[0]); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + slen = (int)STRLEN(p); + len = slen * n; + if (len <= 0) + return; + + r = alloc(len + 1); + if (r != NULL) { + for (i = 0; i < n; i++) + mch_memmove(r + i * slen, p, (size_t)slen); + r[len] = NUL; + } + + rettv->vval.v_string = r; + } +} + +/* + * "resolve()" function + */ +static void f_resolve(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *p; +#ifdef HAVE_READLINK + char_u *buf = NULL; +#endif + + p = get_tv_string(&argvars[0]); +#ifdef FEAT_SHORTCUT + { + char_u *v = NULL; + + v = mch_resolve_shortcut(p); + if (v != NULL) + rettv->vval.v_string = v; + else + rettv->vval.v_string = vim_strsave(p); + } +#else +# ifdef HAVE_READLINK + { + char_u *cpy; + int len; + char_u *remain = NULL; + char_u *q; + int is_relative_to_current = FALSE; + int has_trailing_pathsep = FALSE; + int limit = 100; + + p = vim_strsave(p); + + if (p[0] == '.' && (vim_ispathsep(p[1]) + || (p[1] == '.' && (vim_ispathsep(p[2]))))) + is_relative_to_current = TRUE; + + len = STRLEN(p); + if (len > 0 && after_pathsep(p, p + len)) { + has_trailing_pathsep = TRUE; + p[len - 1] = NUL; /* the trailing slash breaks readlink() */ + } + + q = getnextcomp(p); + if (*q != NUL) { + /* Separate the first path component in "p", and keep the + * remainder (beginning with the path separator). */ + remain = vim_strsave(q - 1); + q[-1] = NUL; + } + + buf = alloc(MAXPATHL + 1); + if (buf == NULL) + goto fail; + + for (;; ) { + for (;; ) { + len = readlink((char *)p, (char *)buf, MAXPATHL); + if (len <= 0) + break; + buf[len] = NUL; + + if (limit-- == 0) { + vim_free(p); + vim_free(remain); + EMSG(_("E655: Too many symbolic links (cycle?)")); + rettv->vval.v_string = NULL; + goto fail; + } + + /* Ensure that the result will have a trailing path separator + * if the argument has one. */ + if (remain == NULL && has_trailing_pathsep) + add_pathsep(buf); + + /* Separate the first path component in the link value and + * concatenate the remainders. */ + q = getnextcomp(vim_ispathsep(*buf) ? buf + 1 : buf); + if (*q != NUL) { + if (remain == NULL) + remain = vim_strsave(q - 1); + else { + cpy = concat_str(q - 1, remain); + if (cpy != NULL) { + vim_free(remain); + remain = cpy; + } + } + q[-1] = NUL; + } + + q = gettail(p); + if (q > p && *q == NUL) { + /* Ignore trailing path separator. */ + q[-1] = NUL; + q = gettail(p); + } + if (q > p && !mch_isFullName(buf)) { + /* symlink is relative to directory of argument */ + cpy = alloc((unsigned)(STRLEN(p) + STRLEN(buf) + 1)); + if (cpy != NULL) { + STRCPY(cpy, p); + STRCPY(gettail(cpy), buf); + vim_free(p); + p = cpy; + } + } else { + vim_free(p); + p = vim_strsave(buf); + } + } + + if (remain == NULL) + break; + + /* Append the first path component of "remain" to "p". */ + q = getnextcomp(remain + 1); + len = q - remain - (*q != NUL); + cpy = vim_strnsave(p, STRLEN(p) + len); + if (cpy != NULL) { + STRNCAT(cpy, remain, len); + vim_free(p); + p = cpy; + } + /* Shorten "remain". */ + if (*q != NUL) + STRMOVE(remain, q - 1); + else { + vim_free(remain); + remain = NULL; + } + } + + /* If the result is a relative path name, make it explicitly relative to + * the current directory if and only if the argument had this form. */ + if (!vim_ispathsep(*p)) { + if (is_relative_to_current + && *p != NUL + && !(p[0] == '.' + && (p[1] == NUL + || vim_ispathsep(p[1]) + || (p[1] == '.' + && (p[2] == NUL + || vim_ispathsep(p[2])))))) { + /* Prepend "./". */ + cpy = concat_str((char_u *)"./", p); + if (cpy != NULL) { + vim_free(p); + p = cpy; + } + } else if (!is_relative_to_current) { + /* Strip leading "./". */ + q = p; + while (q[0] == '.' && vim_ispathsep(q[1])) + q += 2; + if (q > p) + STRMOVE(p, p + 2); + } + } + + /* Ensure that the result will have no trailing path separator + * if the argument had none. But keep "/" or "//". */ + if (!has_trailing_pathsep) { + q = p + STRLEN(p); + if (after_pathsep(p, q)) + *gettail_sep(p) = NUL; + } + + rettv->vval.v_string = p; + } +# else + rettv->vval.v_string = vim_strsave(p); +# endif +#endif + + simplify_filename(rettv->vval.v_string); + +#ifdef HAVE_READLINK +fail: + vim_free(buf); +#endif + rettv->v_type = VAR_STRING; +} + +/* + * "reverse({list})" function + */ +static void f_reverse(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + list_T *l; + listitem_T *li, *ni; + + if (argvars[0].v_type != VAR_LIST) + EMSG2(_(e_listarg), "reverse()"); + else if ((l = argvars[0].vval.v_list) != NULL + && !tv_check_lock(l->lv_lock, (char_u *)_("reverse() argument"))) { + li = l->lv_last; + l->lv_first = l->lv_last = NULL; + l->lv_len = 0; + while (li != NULL) { + ni = li->li_prev; + list_append(l, li); + li = ni; + } + rettv->vval.v_list = l; + rettv->v_type = VAR_LIST; + ++l->lv_refcount; + l->lv_idx = l->lv_len - l->lv_idx - 1; + } +} + +#define SP_NOMOVE 0x01 /* don't move cursor */ +#define SP_REPEAT 0x02 /* repeat to find outer pair */ +#define SP_RETCOUNT 0x04 /* return matchcount */ +#define SP_SETPCMARK 0x08 /* set previous context mark */ +#define SP_START 0x10 /* accept match at start position */ +#define SP_SUBPAT 0x20 /* return nr of matching sub-pattern */ +#define SP_END 0x40 /* leave cursor at end of match */ + +static int get_search_arg __ARGS((typval_T *varp, int *flagsp)); + +/* + * Get flags for a search function. + * Possibly sets "p_ws". + * Returns BACKWARD, FORWARD or zero (for an error). + */ +static int get_search_arg(varp, flagsp) +typval_T *varp; +int *flagsp; +{ + int dir = FORWARD; + char_u *flags; + char_u nbuf[NUMBUFLEN]; + int mask; + + if (varp->v_type != VAR_UNKNOWN) { + flags = get_tv_string_buf_chk(varp, nbuf); + if (flags == NULL) + return 0; /* type error; errmsg already given */ + while (*flags != NUL) { + switch (*flags) { + case 'b': dir = BACKWARD; break; + case 'w': p_ws = TRUE; break; + case 'W': p_ws = FALSE; break; + default: mask = 0; + if (flagsp != NULL) + switch (*flags) { + case 'c': mask = SP_START; break; + case 'e': mask = SP_END; break; + case 'm': mask = SP_RETCOUNT; break; + case 'n': mask = SP_NOMOVE; break; + case 'p': mask = SP_SUBPAT; break; + case 'r': mask = SP_REPEAT; break; + case 's': mask = SP_SETPCMARK; break; + } + if (mask == 0) { + EMSG2(_(e_invarg2), flags); + dir = 0; + } else + *flagsp |= mask; + } + if (dir == 0) + break; + ++flags; + } + } + return dir; +} + +/* + * Shared by search() and searchpos() functions + */ +static int search_cmn(argvars, match_pos, flagsp) +typval_T *argvars; +pos_T *match_pos; +int *flagsp; +{ + int flags; + char_u *pat; + pos_T pos; + pos_T save_cursor; + int save_p_ws = p_ws; + int dir; + int retval = 0; /* default: FAIL */ + long lnum_stop = 0; + proftime_T tm; + long time_limit = 0; + int options = SEARCH_KEEP; + int subpatnum; + + pat = get_tv_string(&argvars[0]); + dir = get_search_arg(&argvars[1], flagsp); /* may set p_ws */ + if (dir == 0) + goto theend; + flags = *flagsp; + if (flags & SP_START) + options |= SEARCH_START; + if (flags & SP_END) + options |= SEARCH_END; + + /* Optional arguments: line number to stop searching and timeout. */ + if (argvars[1].v_type != VAR_UNKNOWN && argvars[2].v_type != VAR_UNKNOWN) { + lnum_stop = get_tv_number_chk(&argvars[2], NULL); + if (lnum_stop < 0) + goto theend; + if (argvars[3].v_type != VAR_UNKNOWN) { + time_limit = get_tv_number_chk(&argvars[3], NULL); + if (time_limit < 0) + goto theend; + } + } + + /* Set the time limit, if there is one. */ + profile_setlimit(time_limit, &tm); + + /* + * This function does not accept SP_REPEAT and SP_RETCOUNT flags. + * Check to make sure only those flags are set. + * Also, Only the SP_NOMOVE or the SP_SETPCMARK flag can be set. Both + * flags cannot be set. Check for that condition also. + */ + if (((flags & (SP_REPEAT | SP_RETCOUNT)) != 0) + || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) { + EMSG2(_(e_invarg2), get_tv_string(&argvars[1])); + goto theend; + } + + pos = save_cursor = curwin->w_cursor; + subpatnum = searchit(curwin, curbuf, &pos, dir, pat, 1L, + options, RE_SEARCH, (linenr_T)lnum_stop, &tm); + if (subpatnum != FAIL) { + if (flags & SP_SUBPAT) + retval = subpatnum; + else + retval = pos.lnum; + if (flags & SP_SETPCMARK) + setpcmark(); + curwin->w_cursor = pos; + if (match_pos != NULL) { + /* Store the match cursor position */ + match_pos->lnum = pos.lnum; + match_pos->col = pos.col + 1; + } + /* "/$" will put the cursor after the end of the line, may need to + * correct that here */ + check_cursor(); + } + + /* If 'n' flag is used: restore cursor position. */ + if (flags & SP_NOMOVE) + curwin->w_cursor = save_cursor; + else + curwin->w_set_curswant = TRUE; +theend: + p_ws = save_p_ws; + + return retval; +} + + +/* + * round() is not in C90, use ceil() or floor() instead. + */ +float_T vim_round(f) +float_T f; +{ + return f > 0 ? floor(f + 0.5) : ceil(f - 0.5); +} + +/* + * "round({float})" function + */ +static void f_round(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = vim_round(f); + else + rettv->vval.v_float = 0.0; +} + +/* + * "screenattr()" function + */ +static void f_screenattr(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + int row; + int col; + int c; + + row = get_tv_number_chk(&argvars[0], NULL) - 1; + col = get_tv_number_chk(&argvars[1], NULL) - 1; + if (row < 0 || row >= screen_Rows + || col < 0 || col >= screen_Columns) + c = -1; + else + c = ScreenAttrs[LineOffset[row] + col]; + rettv->vval.v_number = c; +} + +/* + * "screenchar()" function + */ +static void f_screenchar(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + int row; + int col; + int off; + int c; + + row = get_tv_number_chk(&argvars[0], NULL) - 1; + col = get_tv_number_chk(&argvars[1], NULL) - 1; + if (row < 0 || row >= screen_Rows + || col < 0 || col >= screen_Columns) + c = -1; + else { + off = LineOffset[row] + col; + if (enc_utf8 && ScreenLinesUC[off] != 0) + c = ScreenLinesUC[off]; + else + c = ScreenLines[off]; + } + rettv->vval.v_number = c; +} + +/* + * "screencol()" function + * + * First column is 1 to be consistent with virtcol(). + */ +static void f_screencol(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = screen_screencol() + 1; +} + +/* + * "screenrow()" function + */ +static void f_screenrow(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = screen_screenrow() + 1; +} + +/* + * "search()" function + */ +static void f_search(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + int flags = 0; + + rettv->vval.v_number = search_cmn(argvars, NULL, &flags); +} + +/* + * "searchdecl()" function + */ +static void f_searchdecl(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + int locally = 1; + int thisblock = 0; + int error = FALSE; + char_u *name; + + rettv->vval.v_number = 1; /* default: FAIL */ + + name = get_tv_string_chk(&argvars[0]); + if (argvars[1].v_type != VAR_UNKNOWN) { + locally = get_tv_number_chk(&argvars[1], &error) == 0; + if (!error && argvars[2].v_type != VAR_UNKNOWN) + thisblock = get_tv_number_chk(&argvars[2], &error) != 0; + } + if (!error && name != NULL) + rettv->vval.v_number = find_decl(name, (int)STRLEN(name), + locally, thisblock, SEARCH_KEEP) == FAIL; +} + +/* + * Used by searchpair() and searchpairpos() + */ +static int searchpair_cmn(argvars, match_pos) +typval_T *argvars; +pos_T *match_pos; +{ + char_u *spat, *mpat, *epat; + char_u *skip; + int save_p_ws = p_ws; + int dir; + int flags = 0; + char_u nbuf1[NUMBUFLEN]; + char_u nbuf2[NUMBUFLEN]; + char_u nbuf3[NUMBUFLEN]; + int retval = 0; /* default: FAIL */ + long lnum_stop = 0; + long time_limit = 0; + + /* Get the three pattern arguments: start, middle, end. */ + spat = get_tv_string_chk(&argvars[0]); + mpat = get_tv_string_buf_chk(&argvars[1], nbuf1); + epat = get_tv_string_buf_chk(&argvars[2], nbuf2); + if (spat == NULL || mpat == NULL || epat == NULL) + goto theend; /* type error */ + + /* Handle the optional fourth argument: flags */ + dir = get_search_arg(&argvars[3], &flags); /* may set p_ws */ + if (dir == 0) + goto theend; + + /* Don't accept SP_END or SP_SUBPAT. + * Only one of the SP_NOMOVE or SP_SETPCMARK flags can be set. + */ + if ((flags & (SP_END | SP_SUBPAT)) != 0 + || ((flags & SP_NOMOVE) && (flags & SP_SETPCMARK))) { + EMSG2(_(e_invarg2), get_tv_string(&argvars[3])); + goto theend; + } + + /* Using 'r' implies 'W', otherwise it doesn't work. */ + if (flags & SP_REPEAT) + p_ws = FALSE; + + /* Optional fifth argument: skip expression */ + if (argvars[3].v_type == VAR_UNKNOWN + || argvars[4].v_type == VAR_UNKNOWN) + skip = (char_u *)""; + else { + skip = get_tv_string_buf_chk(&argvars[4], nbuf3); + if (argvars[5].v_type != VAR_UNKNOWN) { + lnum_stop = get_tv_number_chk(&argvars[5], NULL); + if (lnum_stop < 0) + goto theend; + if (argvars[6].v_type != VAR_UNKNOWN) { + time_limit = get_tv_number_chk(&argvars[6], NULL); + if (time_limit < 0) + goto theend; + } + } + } + if (skip == NULL) + goto theend; /* type error */ + + retval = do_searchpair(spat, mpat, epat, dir, skip, flags, + match_pos, lnum_stop, time_limit); + +theend: + p_ws = save_p_ws; + + return retval; +} + +/* + * "searchpair()" function + */ +static void f_searchpair(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->vval.v_number = searchpair_cmn(argvars, NULL); +} + +/* + * "searchpairpos()" function + */ +static void f_searchpairpos(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + pos_T match_pos; + int lnum = 0; + int col = 0; + + if (rettv_list_alloc(rettv) == FAIL) + return; + + if (searchpair_cmn(argvars, &match_pos) > 0) { + lnum = match_pos.lnum; + col = match_pos.col; + } + + list_append_number(rettv->vval.v_list, (varnumber_T)lnum); + list_append_number(rettv->vval.v_list, (varnumber_T)col); +} + +/* + * Search for a start/middle/end thing. + * Used by searchpair(), see its documentation for the details. + * Returns 0 or -1 for no match, + */ +long do_searchpair(spat, mpat, epat, dir, skip, flags, match_pos, + lnum_stop, time_limit) +char_u *spat; /* start pattern */ +char_u *mpat; /* middle pattern */ +char_u *epat; /* end pattern */ +int dir; /* BACKWARD or FORWARD */ +char_u *skip; /* skip expression */ +int flags; /* SP_SETPCMARK and other SP_ values */ +pos_T *match_pos; +linenr_T lnum_stop; /* stop at this line if not zero */ +long time_limit UNUSED; /* stop after this many msec */ +{ + char_u *save_cpo; + char_u *pat, *pat2 = NULL, *pat3 = NULL; + long retval = 0; + pos_T pos; + pos_T firstpos; + pos_T foundpos; + pos_T save_cursor; + pos_T save_pos; + int n; + int r; + int nest = 1; + int err; + int options = SEARCH_KEEP; + proftime_T tm; + + /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ + save_cpo = p_cpo; + p_cpo = empty_option; + + /* Set the time limit, if there is one. */ + profile_setlimit(time_limit, &tm); + + /* Make two search patterns: start/end (pat2, for in nested pairs) and + * start/middle/end (pat3, for the top pair). */ + pat2 = alloc((unsigned)(STRLEN(spat) + STRLEN(epat) + 15)); + pat3 = alloc((unsigned)(STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 23)); + if (pat2 == NULL || pat3 == NULL) + goto theend; + sprintf((char *)pat2, "\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat); + if (*mpat == NUL) + STRCPY(pat3, pat2); + else + sprintf((char *)pat3, "\\(%s\\m\\)\\|\\(%s\\m\\)\\|\\(%s\\m\\)", + spat, epat, mpat); + if (flags & SP_START) + options |= SEARCH_START; + + save_cursor = curwin->w_cursor; + pos = curwin->w_cursor; + clearpos(&firstpos); + clearpos(&foundpos); + pat = pat3; + for (;; ) { + n = searchit(curwin, curbuf, &pos, dir, pat, 1L, + options, RE_SEARCH, lnum_stop, &tm); + if (n == FAIL || (firstpos.lnum != 0 && equalpos(pos, firstpos))) + /* didn't find it or found the first match again: FAIL */ + break; + + if (firstpos.lnum == 0) + firstpos = pos; + if (equalpos(pos, foundpos)) { + /* Found the same position again. Can happen with a pattern that + * has "\zs" at the end and searching backwards. Advance one + * character and try again. */ + if (dir == BACKWARD) + decl(&pos); + else + incl(&pos); + } + foundpos = pos; + + /* clear the start flag to avoid getting stuck here */ + options &= ~SEARCH_START; + + /* If the skip pattern matches, ignore this match. */ + if (*skip != NUL) { + save_pos = curwin->w_cursor; + curwin->w_cursor = pos; + r = eval_to_bool(skip, &err, NULL, FALSE); + curwin->w_cursor = save_pos; + if (err) { + /* Evaluating {skip} caused an error, break here. */ + curwin->w_cursor = save_cursor; + retval = -1; + break; + } + if (r) + continue; + } + + if ((dir == BACKWARD && n == 3) || (dir == FORWARD && n == 2)) { + /* Found end when searching backwards or start when searching + * forward: nested pair. */ + ++nest; + pat = pat2; /* nested, don't search for middle */ + } else { + /* Found end when searching forward or start when searching + * backward: end of (nested) pair; or found middle in outer pair. */ + if (--nest == 1) + pat = pat3; /* outer level, search for middle */ + } + + if (nest == 0) { + /* Found the match: return matchcount or line number. */ + if (flags & SP_RETCOUNT) + ++retval; + else + retval = pos.lnum; + if (flags & SP_SETPCMARK) + setpcmark(); + curwin->w_cursor = pos; + if (!(flags & SP_REPEAT)) + break; + nest = 1; /* search for next unmatched */ + } + } + + if (match_pos != NULL) { + /* Store the match cursor position */ + match_pos->lnum = curwin->w_cursor.lnum; + match_pos->col = curwin->w_cursor.col + 1; + } + + /* If 'n' flag is used or search failed: restore cursor position. */ + if ((flags & SP_NOMOVE) || retval == 0) + curwin->w_cursor = save_cursor; + +theend: + vim_free(pat2); + vim_free(pat3); + if (p_cpo == empty_option) + p_cpo = save_cpo; + else + /* Darn, evaluating the {skip} expression changed the value. */ + free_string_option(save_cpo); + + return retval; +} + +/* + * "searchpos()" function + */ +static void f_searchpos(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + pos_T match_pos; + int lnum = 0; + int col = 0; + int n; + int flags = 0; + + if (rettv_list_alloc(rettv) == FAIL) + return; + + n = search_cmn(argvars, &match_pos, &flags); + if (n > 0) { + lnum = match_pos.lnum; + col = match_pos.col; + } + + list_append_number(rettv->vval.v_list, (varnumber_T)lnum); + list_append_number(rettv->vval.v_list, (varnumber_T)col); + if (flags & SP_SUBPAT) + list_append_number(rettv->vval.v_list, (varnumber_T)n); +} + + +static void f_server2client(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = -1; +} + +static void f_serverlist(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + char_u *r = NULL; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = r; +} + +/* + * "setbufvar()" function + */ +static void f_setbufvar(argvars, rettv) +typval_T *argvars; +typval_T *rettv UNUSED; +{ + buf_T *buf; + aco_save_T aco; + char_u *varname, *bufvarname; + typval_T *varp; + char_u nbuf[NUMBUFLEN]; + + if (check_restricted() || check_secure()) + return; + (void)get_tv_number(&argvars[0]); /* issue errmsg if type error */ + varname = get_tv_string_chk(&argvars[1]); + buf = get_buf_tv(&argvars[0], FALSE); + varp = &argvars[2]; + + if (buf != NULL && varname != NULL && varp != NULL) { + /* set curbuf to be our buf, temporarily */ + aucmd_prepbuf(&aco, buf); + + if (*varname == '&') { + long numval; + char_u *strval; + int error = FALSE; + + ++varname; + numval = get_tv_number_chk(varp, &error); + strval = get_tv_string_buf_chk(varp, nbuf); + if (!error && strval != NULL) + set_option_value(varname, numval, strval, OPT_LOCAL); + } else { + bufvarname = alloc((unsigned)STRLEN(varname) + 3); + if (bufvarname != NULL) { + STRCPY(bufvarname, "b:"); + STRCPY(bufvarname + 2, varname); + set_var(bufvarname, varp, TRUE); + vim_free(bufvarname); + } + } + + /* reset notion of buffer */ + aucmd_restbuf(&aco); + } +} + +/* + * "setcmdpos()" function + */ +static void f_setcmdpos(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + int pos = (int)get_tv_number(&argvars[0]) - 1; + + if (pos >= 0) + rettv->vval.v_number = set_cmdline_pos(pos); +} + +/* + * "setline()" function + */ +static void f_setline(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + linenr_T lnum; + char_u *line = NULL; + list_T *l = NULL; + listitem_T *li = NULL; + long added = 0; + linenr_T lcount = curbuf->b_ml.ml_line_count; + + lnum = get_tv_lnum(&argvars[0]); + if (argvars[1].v_type == VAR_LIST) { + l = argvars[1].vval.v_list; + li = l->lv_first; + } else + line = get_tv_string_chk(&argvars[1]); + + /* default result is zero == OK */ + for (;; ) { + if (l != NULL) { + /* list argument, get next string */ + if (li == NULL) + break; + line = get_tv_string_chk(&li->li_tv); + li = li->li_next; + } + + rettv->vval.v_number = 1; /* FAIL */ + if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) + break; + + /* When coming here from Insert mode, sync undo, so that this can be + * undone separately from what was previously inserted. */ + if (u_sync_once == 2) { + u_sync_once = 1; /* notify that u_sync() was called */ + u_sync(TRUE); + } + + if (lnum <= curbuf->b_ml.ml_line_count) { + /* existing line, replace it */ + if (u_savesub(lnum) == OK && ml_replace(lnum, line, TRUE) == OK) { + changed_bytes(lnum, 0); + if (lnum == curwin->w_cursor.lnum) + check_cursor_col(); + rettv->vval.v_number = 0; /* OK */ + } + } else if (added > 0 || u_save(lnum - 1, lnum) == OK) { + /* lnum is one past the last line, append the line */ + ++added; + if (ml_append(lnum - 1, line, (colnr_T)0, FALSE) == OK) + rettv->vval.v_number = 0; /* OK */ + } + + if (l == NULL) /* only one string argument */ + break; + ++lnum; + } + + if (added > 0) + appended_lines_mark(lcount, added); +} + +static void set_qf_ll_list __ARGS((win_T *wp, typval_T *list_arg, typval_T * + action_arg, + typval_T *rettv)); + +/* + * Used by "setqflist()" and "setloclist()" functions + */ +static void set_qf_ll_list(wp, list_arg, action_arg, rettv) +win_T *wp UNUSED; +typval_T *list_arg UNUSED; +typval_T *action_arg UNUSED; +typval_T *rettv; +{ + char_u *act; + int action = ' '; + + rettv->vval.v_number = -1; + + if (list_arg->v_type != VAR_LIST) + EMSG(_(e_listreq)); + else { + list_T *l = list_arg->vval.v_list; + + if (action_arg->v_type == VAR_STRING) { + act = get_tv_string_chk(action_arg); + if (act == NULL) + return; /* type error; errmsg already given */ + if (*act == 'a' || *act == 'r') + action = *act; + } + + if (l != NULL && set_errorlist(wp, l, action, + (char_u *)(wp == NULL ? "setqflist()" : "setloclist()")) == OK) + rettv->vval.v_number = 0; + } +} + +/* + * "setloclist()" function + */ +static void f_setloclist(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + win_T *win; + + rettv->vval.v_number = -1; + + win = find_win_by_nr(&argvars[0], NULL); + if (win != NULL) + set_qf_ll_list(win, &argvars[1], &argvars[2], rettv); +} + +/* + * "setmatches()" function + */ +static void f_setmatches(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + list_T *l; + listitem_T *li; + dict_T *d; + + rettv->vval.v_number = -1; + if (argvars[0].v_type != VAR_LIST) { + EMSG(_(e_listreq)); + return; + } + if ((l = argvars[0].vval.v_list) != NULL) { + + /* To some extent make sure that we are dealing with a list from + * "getmatches()". */ + li = l->lv_first; + while (li != NULL) { + if (li->li_tv.v_type != VAR_DICT + || (d = li->li_tv.vval.v_dict) == NULL) { + EMSG(_(e_invarg)); + return; + } + if (!(dict_find(d, (char_u *)"group", -1) != NULL + && dict_find(d, (char_u *)"pattern", -1) != NULL + && dict_find(d, (char_u *)"priority", -1) != NULL + && dict_find(d, (char_u *)"id", -1) != NULL)) { + EMSG(_(e_invarg)); + return; + } + li = li->li_next; + } + + clear_matches(curwin); + li = l->lv_first; + while (li != NULL) { + d = li->li_tv.vval.v_dict; + match_add(curwin, get_dict_string(d, (char_u *)"group", FALSE), + get_dict_string(d, (char_u *)"pattern", FALSE), + (int)get_dict_number(d, (char_u *)"priority"), + (int)get_dict_number(d, (char_u *)"id")); + li = li->li_next; + } + rettv->vval.v_number = 0; + } +} + +/* + * "setpos()" function + */ +static void f_setpos(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + pos_T pos; + int fnum; + char_u *name; + + rettv->vval.v_number = -1; + name = get_tv_string_chk(argvars); + if (name != NULL) { + if (list2fpos(&argvars[1], &pos, &fnum) == OK) { + if (--pos.col < 0) + pos.col = 0; + if (name[0] == '.' && name[1] == NUL) { + /* set cursor */ + if (fnum == curbuf->b_fnum) { + curwin->w_cursor = pos; + check_cursor(); + rettv->vval.v_number = 0; + } else + EMSG(_(e_invarg)); + } else if (name[0] == '\'' && name[1] != NUL && name[2] == NUL) { + /* set mark */ + if (setmark_pos(name[1], &pos, fnum) == OK) + rettv->vval.v_number = 0; + } else + EMSG(_(e_invarg)); + } + } +} + +/* + * "setqflist()" function + */ +static void f_setqflist(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + set_qf_ll_list(NULL, &argvars[0], &argvars[1], rettv); +} + +/* + * "setreg()" function + */ +static void f_setreg(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + int regname; + char_u *strregname; + char_u *stropt; + char_u *strval; + int append; + char_u yank_type; + long block_len; + + block_len = -1; + yank_type = MAUTO; + append = FALSE; + + strregname = get_tv_string_chk(argvars); + rettv->vval.v_number = 1; /* FAIL is default */ + + if (strregname == NULL) + return; /* type error; errmsg already given */ + regname = *strregname; + if (regname == 0 || regname == '@') + regname = '"'; + else if (regname == '=') + return; + + if (argvars[2].v_type != VAR_UNKNOWN) { + stropt = get_tv_string_chk(&argvars[2]); + if (stropt == NULL) + return; /* type error */ + for (; *stropt != NUL; ++stropt) + switch (*stropt) { + case 'a': case 'A': /* append */ + append = TRUE; + break; + case 'v': case 'c': /* character-wise selection */ + yank_type = MCHAR; + break; + case 'V': case 'l': /* line-wise selection */ + yank_type = MLINE; + break; + case 'b': case Ctrl_V: /* block-wise selection */ + yank_type = MBLOCK; + if (VIM_ISDIGIT(stropt[1])) { + ++stropt; + block_len = getdigits(&stropt) - 1; + --stropt; + } + break; + } + } + + strval = get_tv_string_chk(&argvars[1]); + if (strval != NULL) + write_reg_contents_ex(regname, strval, -1, + append, yank_type, block_len); + rettv->vval.v_number = 0; +} + +/* + * "settabvar()" function + */ +static void f_settabvar(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + tabpage_T *save_curtab; + tabpage_T *tp; + char_u *varname, *tabvarname; + typval_T *varp; + + rettv->vval.v_number = 0; + + if (check_restricted() || check_secure()) + return; + + tp = find_tabpage((int)get_tv_number_chk(&argvars[0], NULL)); + varname = get_tv_string_chk(&argvars[1]); + varp = &argvars[2]; + + if (varname != NULL && varp != NULL + && tp != NULL + ) { + save_curtab = curtab; + goto_tabpage_tp(tp, FALSE, FALSE); + + tabvarname = alloc((unsigned)STRLEN(varname) + 3); + if (tabvarname != NULL) { + STRCPY(tabvarname, "t:"); + STRCPY(tabvarname + 2, varname); + set_var(tabvarname, varp, TRUE); + vim_free(tabvarname); + } + + /* Restore current tabpage */ + if (valid_tabpage(save_curtab)) + goto_tabpage_tp(save_curtab, FALSE, FALSE); + } +} + +/* + * "settabwinvar()" function + */ +static void f_settabwinvar(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + setwinvar(argvars, rettv, 1); +} + +/* + * "setwinvar()" function + */ +static void f_setwinvar(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + setwinvar(argvars, rettv, 0); +} + +/* + * "setwinvar()" and "settabwinvar()" functions + */ + +static void setwinvar(argvars, rettv, off) +typval_T *argvars; +typval_T *rettv UNUSED; +int off; +{ + win_T *win; + win_T *save_curwin; + tabpage_T *save_curtab; + char_u *varname, *winvarname; + typval_T *varp; + char_u nbuf[NUMBUFLEN]; + tabpage_T *tp = NULL; + + if (check_restricted() || check_secure()) + return; + + if (off == 1) + tp = find_tabpage((int)get_tv_number_chk(&argvars[0], NULL)); + else + tp = curtab; + win = find_win_by_nr(&argvars[off], tp); + varname = get_tv_string_chk(&argvars[off + 1]); + varp = &argvars[off + 2]; + + if (win != NULL && varname != NULL && varp != NULL) { + if (switch_win(&save_curwin, &save_curtab, win, tp, TRUE) == FAIL) + return; + + if (*varname == '&') { + long numval; + char_u *strval; + int error = FALSE; + + ++varname; + numval = get_tv_number_chk(varp, &error); + strval = get_tv_string_buf_chk(varp, nbuf); + if (!error && strval != NULL) + set_option_value(varname, numval, strval, OPT_LOCAL); + } else { + winvarname = alloc((unsigned)STRLEN(varname) + 3); + if (winvarname != NULL) { + STRCPY(winvarname, "w:"); + STRCPY(winvarname + 2, varname); + set_var(winvarname, varp, TRUE); + vim_free(winvarname); + } + } + + restore_win(save_curwin, save_curtab, TRUE); + } +} + +/* + * "sha256({string})" function + */ +static void f_sha256(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *p; + + p = get_tv_string(&argvars[0]); + rettv->vval.v_string = vim_strsave( + sha256_bytes(p, (int)STRLEN(p), NULL, 0)); + rettv->v_type = VAR_STRING; +} + +/* + * "shellescape({string})" function + */ +static void f_shellescape(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->vval.v_string = vim_strsave_shellescape( + get_tv_string(&argvars[0]), non_zero_arg(&argvars[1])); + rettv->v_type = VAR_STRING; +} + +/* + * shiftwidth() function + */ +static void f_shiftwidth(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->vval.v_number = get_sw_value(curbuf); +} + +/* + * "simplify()" function + */ +static void f_simplify(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *p; + + p = get_tv_string(&argvars[0]); + rettv->vval.v_string = vim_strsave(p); + simplify_filename(rettv->vval.v_string); /* simplify in place */ + rettv->v_type = VAR_STRING; +} + +/* + * "sin()" function + */ +static void f_sin(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = sin(f); + else + rettv->vval.v_float = 0.0; +} + +/* + * "sinh()" function + */ +static void f_sinh(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = sinh(f); + else + rettv->vval.v_float = 0.0; +} + +static int +item_compare __ARGS((const void *s1, const void *s2)); +static int +item_compare2 __ARGS((const void *s1, const void *s2)); + +static int item_compare_ic; +static char_u *item_compare_func; +static dict_T *item_compare_selfdict; +static int item_compare_func_err; +#define ITEM_COMPARE_FAIL 999 + +/* + * Compare functions for f_sort() below. + */ +static int item_compare(s1, s2) +const void *s1; +const void *s2; +{ + char_u *p1, *p2; + char_u *tofree1, *tofree2; + int res; + char_u numbuf1[NUMBUFLEN]; + char_u numbuf2[NUMBUFLEN]; + + p1 = tv2string(&(*(listitem_T **)s1)->li_tv, &tofree1, numbuf1, 0); + p2 = tv2string(&(*(listitem_T **)s2)->li_tv, &tofree2, numbuf2, 0); + if (p1 == NULL) + p1 = (char_u *)""; + if (p2 == NULL) + p2 = (char_u *)""; + if (item_compare_ic) + res = STRICMP(p1, p2); + else + res = STRCMP(p1, p2); + vim_free(tofree1); + vim_free(tofree2); + return res; +} + +static int item_compare2(s1, s2) +const void *s1; +const void *s2; +{ + int res; + typval_T rettv; + typval_T argv[3]; + int dummy; + + /* shortcut after failure in previous call; compare all items equal */ + if (item_compare_func_err) + return 0; + + /* copy the values. This is needed to be able to set v_lock to VAR_FIXED + * in the copy without changing the original list items. */ + copy_tv(&(*(listitem_T **)s1)->li_tv, &argv[0]); + copy_tv(&(*(listitem_T **)s2)->li_tv, &argv[1]); + + rettv.v_type = VAR_UNKNOWN; /* clear_tv() uses this */ + res = call_func(item_compare_func, (int)STRLEN(item_compare_func), + &rettv, 2, argv, 0L, 0L, &dummy, TRUE, + item_compare_selfdict); + clear_tv(&argv[0]); + clear_tv(&argv[1]); + + if (res == FAIL) + res = ITEM_COMPARE_FAIL; + else + res = get_tv_number_chk(&rettv, &item_compare_func_err); + if (item_compare_func_err) + res = ITEM_COMPARE_FAIL; /* return value has wrong type */ + clear_tv(&rettv); + return res; +} + +/* + * "sort({list})" function + */ +static void f_sort(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + list_T *l; + listitem_T *li; + listitem_T **ptrs; + long len; + long i; + + if (argvars[0].v_type != VAR_LIST) + EMSG2(_(e_listarg), "sort()"); + else { + l = argvars[0].vval.v_list; + if (l == NULL || tv_check_lock(l->lv_lock, + (char_u *)_("sort() argument"))) + return; + rettv->vval.v_list = l; + rettv->v_type = VAR_LIST; + ++l->lv_refcount; + + len = list_len(l); + if (len <= 1) + return; /* short list sorts pretty quickly */ + + item_compare_ic = FALSE; + item_compare_func = NULL; + item_compare_selfdict = NULL; + if (argvars[1].v_type != VAR_UNKNOWN) { + /* optional second argument: {func} */ + if (argvars[1].v_type == VAR_FUNC) + item_compare_func = argvars[1].vval.v_string; + else { + int error = FALSE; + + i = get_tv_number_chk(&argvars[1], &error); + if (error) + return; /* type error; errmsg already given */ + if (i == 1) + item_compare_ic = TRUE; + else + item_compare_func = get_tv_string(&argvars[1]); + } + + if (argvars[2].v_type != VAR_UNKNOWN) { + /* optional third argument: {dict} */ + if (argvars[2].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + item_compare_selfdict = argvars[2].vval.v_dict; + } + } + + /* Make an array with each entry pointing to an item in the List. */ + ptrs = (listitem_T **)alloc((int)(len * sizeof(listitem_T *))); + if (ptrs == NULL) + return; + i = 0; + for (li = l->lv_first; li != NULL; li = li->li_next) + ptrs[i++] = li; + + item_compare_func_err = FALSE; + /* test the compare function */ + if (item_compare_func != NULL + && item_compare2((void *)&ptrs[0], (void *)&ptrs[1]) + == ITEM_COMPARE_FAIL) + EMSG(_("E702: Sort compare function failed")); + else { + /* Sort the array with item pointers. */ + qsort((void *)ptrs, (size_t)len, sizeof(listitem_T *), + item_compare_func == NULL ? item_compare : item_compare2); + + if (!item_compare_func_err) { + /* Clear the List and append the items in the sorted order. */ + l->lv_first = l->lv_last = l->lv_idx_item = NULL; + l->lv_len = 0; + for (i = 0; i < len; ++i) + list_append(l, ptrs[i]); + } + } + + vim_free(ptrs); + } +} + +/* + * "soundfold({word})" function + */ +static void f_soundfold(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *s; + + rettv->v_type = VAR_STRING; + s = get_tv_string(&argvars[0]); + rettv->vval.v_string = eval_soundfold(s); +} + +/* + * "spellbadword()" function + */ +static void f_spellbadword(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + char_u *word = (char_u *)""; + hlf_T attr = HLF_COUNT; + int len = 0; + + if (rettv_list_alloc(rettv) == FAIL) + return; + + if (argvars[0].v_type == VAR_UNKNOWN) { + /* Find the start and length of the badly spelled word. */ + len = spell_move_to(curwin, FORWARD, TRUE, TRUE, &attr); + if (len != 0) + word = ml_get_cursor(); + } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) { + char_u *str = get_tv_string_chk(&argvars[0]); + int capcol = -1; + + if (str != NULL) { + /* Check the argument for spelling. */ + while (*str != NUL) { + len = spell_check(curwin, str, &attr, &capcol, FALSE); + if (attr != HLF_COUNT) { + word = str; + break; + } + str += len; + } + } + } + + list_append_string(rettv->vval.v_list, word, len); + list_append_string(rettv->vval.v_list, (char_u *)( + attr == HLF_SPB ? "bad" : + attr == HLF_SPR ? "rare" : + attr == HLF_SPL ? "local" : + attr == HLF_SPC ? "caps" : + ""), -1); +} + +/* + * "spellsuggest()" function + */ +static void f_spellsuggest(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + char_u *str; + int typeerr = FALSE; + int maxcount; + garray_T ga; + int i; + listitem_T *li; + int need_capital = FALSE; + + if (rettv_list_alloc(rettv) == FAIL) + return; + + if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) { + str = get_tv_string(&argvars[0]); + if (argvars[1].v_type != VAR_UNKNOWN) { + maxcount = get_tv_number_chk(&argvars[1], &typeerr); + if (maxcount <= 0) + return; + if (argvars[2].v_type != VAR_UNKNOWN) { + need_capital = get_tv_number_chk(&argvars[2], &typeerr); + if (typeerr) + return; + } + } else + maxcount = 25; + + spell_suggest_list(&ga, str, maxcount, need_capital, FALSE); + + for (i = 0; i < ga.ga_len; ++i) { + str = ((char_u **)ga.ga_data)[i]; + + li = listitem_alloc(); + if (li == NULL) + vim_free(str); + else { + li->li_tv.v_type = VAR_STRING; + li->li_tv.v_lock = 0; + li->li_tv.vval.v_string = str; + list_append(rettv->vval.v_list, li); + } + } + ga_clear(&ga); + } +} + +static void f_split(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *str; + char_u *end; + char_u *pat = NULL; + regmatch_T regmatch; + char_u patbuf[NUMBUFLEN]; + char_u *save_cpo; + int match; + colnr_T col = 0; + int keepempty = FALSE; + int typeerr = FALSE; + + /* Make 'cpoptions' empty, the 'l' flag should not be used here. */ + save_cpo = p_cpo; + p_cpo = (char_u *)""; + + str = get_tv_string(&argvars[0]); + if (argvars[1].v_type != VAR_UNKNOWN) { + pat = get_tv_string_buf_chk(&argvars[1], patbuf); + if (pat == NULL) + typeerr = TRUE; + if (argvars[2].v_type != VAR_UNKNOWN) + keepempty = get_tv_number_chk(&argvars[2], &typeerr); + } + if (pat == NULL || *pat == NUL) + pat = (char_u *)"[\\x01- ]\\+"; + + if (rettv_list_alloc(rettv) == FAIL) + return; + if (typeerr) + return; + + regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); + if (regmatch.regprog != NULL) { + regmatch.rm_ic = FALSE; + while (*str != NUL || keepempty) { + if (*str == NUL) + match = FALSE; /* empty item at the end */ + else + match = vim_regexec_nl(®match, str, col); + if (match) + end = regmatch.startp[0]; + else + end = str + STRLEN(str); + if (keepempty || end > str || (rettv->vval.v_list->lv_len > 0 + && *str != NUL && match && end < + regmatch.endp[0])) { + if (list_append_string(rettv->vval.v_list, str, + (int)(end - str)) == FAIL) + break; + } + if (!match) + break; + /* Advance to just after the match. */ + if (regmatch.endp[0] > str) + col = 0; + else { + /* Don't get stuck at the same match. */ + col = (*mb_ptr2len)(regmatch.endp[0]); + } + str = regmatch.endp[0]; + } + + vim_regfree(regmatch.regprog); + } + + p_cpo = save_cpo; +} + +/* + * "sqrt()" function + */ +static void f_sqrt(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = sqrt(f); + else + rettv->vval.v_float = 0.0; +} + +/* + * "str2float()" function + */ +static void f_str2float(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *p = skipwhite(get_tv_string(&argvars[0])); + + if (*p == '+') + p = skipwhite(p + 1); + (void)string2float(p, &rettv->vval.v_float); + rettv->v_type = VAR_FLOAT; +} + +/* + * "str2nr()" function + */ +static void f_str2nr(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + int base = 10; + char_u *p; + long n; + + if (argvars[1].v_type != VAR_UNKNOWN) { + base = get_tv_number(&argvars[1]); + if (base != 8 && base != 10 && base != 16) { + EMSG(_(e_invarg)); + return; + } + } + + p = skipwhite(get_tv_string(&argvars[0])); + if (*p == '+') + p = skipwhite(p + 1); + vim_str2nr(p, NULL, NULL, base == 8 ? 2 : 0, base == 16 ? 2 : 0, &n, NULL); + rettv->vval.v_number = n; +} + +#ifdef HAVE_STRFTIME +/* + * "strftime({format}[, {time}])" function + */ +static void f_strftime(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u result_buf[256]; + struct tm *curtime; + time_t seconds; + char_u *p; + + rettv->v_type = VAR_STRING; + + p = get_tv_string(&argvars[0]); + if (argvars[1].v_type == VAR_UNKNOWN) + seconds = time(NULL); + else + seconds = (time_t)get_tv_number(&argvars[1]); + curtime = localtime(&seconds); + /* MSVC returns NULL for an invalid value of seconds. */ + if (curtime == NULL) + rettv->vval.v_string = vim_strsave((char_u *)_("(Invalid)")); + else { + vimconv_T conv; + char_u *enc; + + conv.vc_type = CONV_NONE; + enc = enc_locale(); + convert_setup(&conv, p_enc, enc); + if (conv.vc_type != CONV_NONE) + p = string_convert(&conv, p, NULL); + if (p != NULL) + (void)strftime((char *)result_buf, sizeof(result_buf), + (char *)p, curtime); + else + result_buf[0] = NUL; + + if (conv.vc_type != CONV_NONE) + vim_free(p); + convert_setup(&conv, enc, p_enc); + if (conv.vc_type != CONV_NONE) + rettv->vval.v_string = string_convert(&conv, result_buf, NULL); + else + rettv->vval.v_string = vim_strsave(result_buf); + + /* Release conversion descriptors */ + convert_setup(&conv, NULL, NULL); + vim_free(enc); + } +} +#endif + +/* + * "stridx()" function + */ +static void f_stridx(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u buf[NUMBUFLEN]; + char_u *needle; + char_u *haystack; + char_u *save_haystack; + char_u *pos; + int start_idx; + + needle = get_tv_string_chk(&argvars[1]); + save_haystack = haystack = get_tv_string_buf_chk(&argvars[0], buf); + rettv->vval.v_number = -1; + if (needle == NULL || haystack == NULL) + return; /* type error; errmsg already given */ + + if (argvars[2].v_type != VAR_UNKNOWN) { + int error = FALSE; + + start_idx = get_tv_number_chk(&argvars[2], &error); + if (error || start_idx >= (int)STRLEN(haystack)) + return; + if (start_idx >= 0) + haystack += start_idx; + } + + pos = (char_u *)strstr((char *)haystack, (char *)needle); + if (pos != NULL) + rettv->vval.v_number = (varnumber_T)(pos - save_haystack); +} + +/* + * "string()" function + */ +static void f_string(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *tofree; + char_u numbuf[NUMBUFLEN]; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = tv2string(&argvars[0], &tofree, numbuf, 0); + /* Make a copy if we have a value but it's not in allocated memory. */ + if (rettv->vval.v_string != NULL && tofree == NULL) + rettv->vval.v_string = vim_strsave(rettv->vval.v_string); +} + +/* + * "strlen()" function + */ +static void f_strlen(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->vval.v_number = (varnumber_T)(STRLEN( + get_tv_string(&argvars[0]))); +} + +/* + * "strchars()" function + */ +static void f_strchars(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *s = get_tv_string(&argvars[0]); + varnumber_T len = 0; + + while (*s != NUL) { + mb_cptr2char_adv(&s); + ++len; + } + rettv->vval.v_number = len; +} + +/* + * "strdisplaywidth()" function + */ +static void f_strdisplaywidth(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *s = get_tv_string(&argvars[0]); + int col = 0; + + if (argvars[1].v_type != VAR_UNKNOWN) + col = get_tv_number(&argvars[1]); + + rettv->vval.v_number = (varnumber_T)(linetabsize_col(col, s) - col); +} + +/* + * "strwidth()" function + */ +static void f_strwidth(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *s = get_tv_string(&argvars[0]); + + rettv->vval.v_number = (varnumber_T)( + mb_string2cells(s, -1) + ); +} + +/* + * "strpart()" function + */ +static void f_strpart(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *p; + int n; + int len; + int slen; + int error = FALSE; + + p = get_tv_string(&argvars[0]); + slen = (int)STRLEN(p); + + n = get_tv_number_chk(&argvars[1], &error); + if (error) + len = 0; + else if (argvars[2].v_type != VAR_UNKNOWN) + len = get_tv_number(&argvars[2]); + else + len = slen - n; /* default len: all bytes that are available. */ + + /* + * Only return the overlap between the specified part and the actual + * string. + */ + if (n < 0) { + len += n; + n = 0; + } else if (n > slen) + n = slen; + if (len < 0) + len = 0; + else if (n + len > slen) + len = slen - n; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = vim_strnsave(p + n, len); +} + +/* + * "strridx()" function + */ +static void f_strridx(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u buf[NUMBUFLEN]; + char_u *needle; + char_u *haystack; + char_u *rest; + char_u *lastmatch = NULL; + int haystack_len, end_idx; + + needle = get_tv_string_chk(&argvars[1]); + haystack = get_tv_string_buf_chk(&argvars[0], buf); + + rettv->vval.v_number = -1; + if (needle == NULL || haystack == NULL) + return; /* type error; errmsg already given */ + + haystack_len = (int)STRLEN(haystack); + if (argvars[2].v_type != VAR_UNKNOWN) { + /* Third argument: upper limit for index */ + end_idx = get_tv_number_chk(&argvars[2], NULL); + if (end_idx < 0) + return; /* can never find a match */ + } else + end_idx = haystack_len; + + if (*needle == NUL) { + /* Empty string matches past the end. */ + lastmatch = haystack + end_idx; + } else { + for (rest = haystack; *rest != '\0'; ++rest) { + rest = (char_u *)strstr((char *)rest, (char *)needle); + if (rest == NULL || rest > haystack + end_idx) + break; + lastmatch = rest; + } + } + + if (lastmatch == NULL) + rettv->vval.v_number = -1; + else + rettv->vval.v_number = (varnumber_T)(lastmatch - haystack); +} + +/* + * "strtrans()" function + */ +static void f_strtrans(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = transstr(get_tv_string(&argvars[0])); +} + +/* + * "submatch()" function + */ +static void f_submatch(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = + reg_submatch((int)get_tv_number_chk(&argvars[0], NULL)); +} + +/* + * "substitute()" function + */ +static void f_substitute(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u patbuf[NUMBUFLEN]; + char_u subbuf[NUMBUFLEN]; + char_u flagsbuf[NUMBUFLEN]; + + char_u *str = get_tv_string_chk(&argvars[0]); + char_u *pat = get_tv_string_buf_chk(&argvars[1], patbuf); + char_u *sub = get_tv_string_buf_chk(&argvars[2], subbuf); + char_u *flg = get_tv_string_buf_chk(&argvars[3], flagsbuf); + + rettv->v_type = VAR_STRING; + if (str == NULL || pat == NULL || sub == NULL || flg == NULL) + rettv->vval.v_string = NULL; + else + rettv->vval.v_string = do_string_sub(str, pat, sub, flg); +} + +/* + * "synID(lnum, col, trans)" function + */ +static void f_synID(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + int id = 0; + long lnum; + long col; + int trans; + int transerr = FALSE; + + lnum = get_tv_lnum(argvars); /* -1 on type error */ + col = get_tv_number(&argvars[1]) - 1; /* -1 on type error */ + trans = get_tv_number_chk(&argvars[2], &transerr); + + if (!transerr && lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count + && col >= 0 && col < (long)STRLEN(ml_get(lnum))) + id = syn_get_id(curwin, lnum, (colnr_T)col, trans, NULL, FALSE); + + rettv->vval.v_number = id; +} + +/* + * "synIDattr(id, what [, mode])" function + */ +static void f_synIDattr(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + char_u *p = NULL; + int id; + char_u *what; + char_u *mode; + char_u modebuf[NUMBUFLEN]; + int modec; + + id = get_tv_number(&argvars[0]); + what = get_tv_string(&argvars[1]); + if (argvars[2].v_type != VAR_UNKNOWN) { + mode = get_tv_string_buf(&argvars[2], modebuf); + modec = TOLOWER_ASC(mode[0]); + if (modec != 't' && modec != 'c' && modec != 'g') + modec = 0; /* replace invalid with current */ + } else { + if (t_colors > 1) + modec = 'c'; + else + modec = 't'; + } + + + switch (TOLOWER_ASC(what[0])) { + case 'b': + if (TOLOWER_ASC(what[1]) == 'g') /* bg[#] */ + p = highlight_color(id, what, modec); + else /* bold */ + p = highlight_has_attr(id, HL_BOLD, modec); + break; + + case 'f': /* fg[#] or font */ + p = highlight_color(id, what, modec); + break; + + case 'i': + if (TOLOWER_ASC(what[1]) == 'n') /* inverse */ + p = highlight_has_attr(id, HL_INVERSE, modec); + else /* italic */ + p = highlight_has_attr(id, HL_ITALIC, modec); + break; + + case 'n': /* name */ + p = get_highlight_name(NULL, id - 1); + break; + + case 'r': /* reverse */ + p = highlight_has_attr(id, HL_INVERSE, modec); + break; + + case 's': + if (TOLOWER_ASC(what[1]) == 'p') /* sp[#] */ + p = highlight_color(id, what, modec); + else /* standout */ + p = highlight_has_attr(id, HL_STANDOUT, modec); + break; + + case 'u': + if (STRLEN(what) <= 5 || TOLOWER_ASC(what[5]) != 'c') + /* underline */ + p = highlight_has_attr(id, HL_UNDERLINE, modec); + else + /* undercurl */ + p = highlight_has_attr(id, HL_UNDERCURL, modec); + break; + } + + if (p != NULL) + p = vim_strsave(p); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = p; +} + +/* + * "synIDtrans(id)" function + */ +static void f_synIDtrans(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + int id; + + id = get_tv_number(&argvars[0]); + + if (id > 0) + id = syn_get_final_id(id); + else + id = 0; + + rettv->vval.v_number = id; +} + +/* + * "synconcealed(lnum, col)" function + */ +static void f_synconcealed(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + long lnum; + long col; + int syntax_flags = 0; + int cchar; + int matchid = 0; + char_u str[NUMBUFLEN]; + + rettv->v_type = VAR_LIST; + rettv->vval.v_list = NULL; + + lnum = get_tv_lnum(argvars); /* -1 on type error */ + col = get_tv_number(&argvars[1]) - 1; /* -1 on type error */ + + vim_memset(str, NUL, sizeof(str)); + + if (rettv_list_alloc(rettv) != FAIL) { + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count + && col >= 0 && col <= (long)STRLEN(ml_get(lnum)) + && curwin->w_p_cole > 0) { + (void)syn_get_id(curwin, lnum, col, FALSE, NULL, FALSE); + syntax_flags = get_syntax_info(&matchid); + + /* get the conceal character */ + if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3) { + cchar = syn_get_sub_char(); + if (cchar == NUL && curwin->w_p_cole == 1 && lcs_conceal != NUL) + cchar = lcs_conceal; + if (cchar != NUL) { + if (has_mbyte) + (*mb_char2bytes)(cchar, str); + else + str[0] = cchar; + } + } + } + + list_append_number(rettv->vval.v_list, + (syntax_flags & HL_CONCEAL) != 0); + /* -1 to auto-determine strlen */ + list_append_string(rettv->vval.v_list, str, -1); + list_append_number(rettv->vval.v_list, matchid); + } +} + +/* + * "synstack(lnum, col)" function + */ +static void f_synstack(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + long lnum; + long col; + int i; + int id; + + rettv->v_type = VAR_LIST; + rettv->vval.v_list = NULL; + + lnum = get_tv_lnum(argvars); /* -1 on type error */ + col = get_tv_number(&argvars[1]) - 1; /* -1 on type error */ + + if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count + && col >= 0 && col <= (long)STRLEN(ml_get(lnum)) + && rettv_list_alloc(rettv) != FAIL) { + (void)syn_get_id(curwin, lnum, (colnr_T)col, FALSE, NULL, TRUE); + for (i = 0;; ++i) { + id = syn_get_stack_item(i); + if (id < 0) + break; + if (list_append_number(rettv->vval.v_list, id) == FAIL) + break; + } + } +} + +/* + * "system()" function + */ +static void f_system(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *res = NULL; + char_u *p; + char_u *infile = NULL; + char_u buf[NUMBUFLEN]; + int err = FALSE; + FILE *fd; + + if (check_restricted() || check_secure()) + goto done; + + if (argvars[1].v_type != VAR_UNKNOWN) { + /* + * Write the string to a temp file, to be used for input of the shell + * command. + */ + if ((infile = vim_tempname('i')) == NULL) { + EMSG(_(e_notmp)); + goto done; + } + + fd = mch_fopen((char *)infile, WRITEBIN); + if (fd == NULL) { + EMSG2(_(e_notopen), infile); + goto done; + } + p = get_tv_string_buf_chk(&argvars[1], buf); + if (p == NULL) { + fclose(fd); + goto done; /* type error; errmsg already given */ + } + if (fwrite(p, STRLEN(p), 1, fd) != 1) + err = TRUE; + if (fclose(fd) != 0) + err = TRUE; + if (err) { + EMSG(_("E677: Error writing temp file")); + goto done; + } + } + + res = get_cmd_output(get_tv_string(&argvars[0]), infile, + SHELL_SILENT | SHELL_COOKED); + +#ifdef USE_CR + /* translate into */ + if (res != NULL) { + char_u *s; + + for (s = res; *s; ++s) { + if (*s == CAR) + *s = NL; + } + } +#else +# ifdef USE_CRNL + /* translate into */ + if (res != NULL) { + char_u *s, *d; + + d = res; + for (s = res; *s; ++s) { + if (s[0] == CAR && s[1] == NL) + ++s; + *d++ = *s; + } + *d = NUL; + } +# endif +#endif + +done: + if (infile != NULL) { + mch_remove(infile); + vim_free(infile); + } + rettv->v_type = VAR_STRING; + rettv->vval.v_string = res; +} + +/* + * "tabpagebuflist()" function + */ +static void f_tabpagebuflist(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + tabpage_T *tp; + win_T *wp = NULL; + + if (argvars[0].v_type == VAR_UNKNOWN) + wp = firstwin; + else { + tp = find_tabpage((int)get_tv_number(&argvars[0])); + if (tp != NULL) + wp = (tp == curtab) ? firstwin : tp->tp_firstwin; + } + if (wp != NULL && rettv_list_alloc(rettv) != FAIL) { + for (; wp != NULL; wp = wp->w_next) + if (list_append_number(rettv->vval.v_list, + wp->w_buffer->b_fnum) == FAIL) + break; + } +} + + +/* + * "tabpagenr()" function + */ +static void f_tabpagenr(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + int nr = 1; + char_u *arg; + + if (argvars[0].v_type != VAR_UNKNOWN) { + arg = get_tv_string_chk(&argvars[0]); + nr = 0; + if (arg != NULL) { + if (STRCMP(arg, "$") == 0) + nr = tabpage_index(NULL) - 1; + else + EMSG2(_(e_invexpr2), arg); + } + } else + nr = tabpage_index(curtab); + rettv->vval.v_number = nr; +} + + +static int get_winnr __ARGS((tabpage_T *tp, typval_T *argvar)); + +/* + * Common code for tabpagewinnr() and winnr(). + */ +static int get_winnr(tp, argvar) +tabpage_T *tp; +typval_T *argvar; +{ + win_T *twin; + int nr = 1; + win_T *wp; + char_u *arg; + + twin = (tp == curtab) ? curwin : tp->tp_curwin; + if (argvar->v_type != VAR_UNKNOWN) { + arg = get_tv_string_chk(argvar); + if (arg == NULL) + nr = 0; /* type error; errmsg already given */ + else if (STRCMP(arg, "$") == 0) + twin = (tp == curtab) ? lastwin : tp->tp_lastwin; + else if (STRCMP(arg, "#") == 0) { + twin = (tp == curtab) ? prevwin : tp->tp_prevwin; + if (twin == NULL) + nr = 0; + } else { + EMSG2(_(e_invexpr2), arg); + nr = 0; + } + } + + if (nr > 0) + for (wp = (tp == curtab) ? firstwin : tp->tp_firstwin; + wp != twin; wp = wp->w_next) { + if (wp == NULL) { + /* didn't find it in this tabpage */ + nr = 0; + break; + } + ++nr; + } + return nr; +} + +/* + * "tabpagewinnr()" function + */ +static void f_tabpagewinnr(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + int nr = 1; + tabpage_T *tp; + + tp = find_tabpage((int)get_tv_number(&argvars[0])); + if (tp == NULL) + nr = 0; + else + nr = get_winnr(tp, &argvars[1]); + rettv->vval.v_number = nr; +} + + +/* + * "tagfiles()" function + */ +static void f_tagfiles(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + char_u *fname; + tagname_T tn; + int first; + + if (rettv_list_alloc(rettv) == FAIL) + return; + fname = alloc(MAXPATHL); + if (fname == NULL) + return; + + for (first = TRUE;; first = FALSE) + if (get_tagfname(&tn, first, fname) == FAIL + || list_append_string(rettv->vval.v_list, fname, -1) == FAIL) + break; + tagname_free(&tn); + vim_free(fname); +} + +/* + * "taglist()" function + */ +static void f_taglist(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *tag_pattern; + + tag_pattern = get_tv_string(&argvars[0]); + + rettv->vval.v_number = FALSE; + if (*tag_pattern == NUL) + return; + + if (rettv_list_alloc(rettv) == OK) + (void)get_tags(rettv->vval.v_list, tag_pattern); +} + +/* + * "tempname()" function + */ +static void f_tempname(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + static int x = 'A'; + + rettv->v_type = VAR_STRING; + rettv->vval.v_string = vim_tempname(x); + + /* Advance 'x' to use A-Z and 0-9, so that there are at least 34 different + * names. Skip 'I' and 'O', they are used for shell redirection. */ + do { + if (x == 'Z') + x = '0'; + else if (x == '9') + x = 'A'; + else { + ++x; + } + } while (x == 'I' || x == 'O'); +} + +/* + * "test(list)" function: Just checking the walls... + */ +static void f_test(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + /* Used for unit testing. Change the code below to your liking. */ +} + +/* + * "tan()" function + */ +static void f_tan(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = tan(f); + else + rettv->vval.v_float = 0.0; +} + +/* + * "tanh()" function + */ +static void f_tanh(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + rettv->vval.v_float = tanh(f); + else + rettv->vval.v_float = 0.0; +} + +/* + * "tolower(string)" function + */ +static void f_tolower(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *p; + + p = vim_strsave(get_tv_string(&argvars[0])); + rettv->v_type = VAR_STRING; + rettv->vval.v_string = p; + + if (p != NULL) + while (*p != NUL) { + int l; + + if (enc_utf8) { + int c, lc; + + c = utf_ptr2char(p); + lc = utf_tolower(c); + l = utf_ptr2len(p); + /* TODO: reallocate string when byte count changes. */ + if (utf_char2len(lc) == l) + utf_char2bytes(lc, p); + p += l; + } else if (has_mbyte && (l = (*mb_ptr2len)(p)) > 1) + p += l; /* skip multi-byte character */ + else { + *p = TOLOWER_LOC(*p); /* note that tolower() can be a macro */ + ++p; + } + } +} + +/* + * "toupper(string)" function + */ +static void f_toupper(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = strup_save(get_tv_string(&argvars[0])); +} + +/* + * "tr(string, fromstr, tostr)" function + */ +static void f_tr(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + char_u *in_str; + char_u *fromstr; + char_u *tostr; + char_u *p; + int inlen; + int fromlen; + int tolen; + int idx; + char_u *cpstr; + int cplen; + int first = TRUE; + char_u buf[NUMBUFLEN]; + char_u buf2[NUMBUFLEN]; + garray_T ga; + + in_str = get_tv_string(&argvars[0]); + fromstr = get_tv_string_buf_chk(&argvars[1], buf); + tostr = get_tv_string_buf_chk(&argvars[2], buf2); + + /* Default return value: empty string. */ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + if (fromstr == NULL || tostr == NULL) + return; /* type error; errmsg already given */ + ga_init2(&ga, (int)sizeof(char), 80); + + if (!has_mbyte) + /* not multi-byte: fromstr and tostr must be the same length */ + if (STRLEN(fromstr) != STRLEN(tostr)) { +error: + EMSG2(_(e_invarg2), fromstr); + ga_clear(&ga); + return; + } + + /* fromstr and tostr have to contain the same number of chars */ + while (*in_str != NUL) { + if (has_mbyte) { + inlen = (*mb_ptr2len)(in_str); + cpstr = in_str; + cplen = inlen; + idx = 0; + for (p = fromstr; *p != NUL; p += fromlen) { + fromlen = (*mb_ptr2len)(p); + if (fromlen == inlen && STRNCMP(in_str, p, inlen) == 0) { + for (p = tostr; *p != NUL; p += tolen) { + tolen = (*mb_ptr2len)(p); + if (idx-- == 0) { + cplen = tolen; + cpstr = p; + break; + } + } + if (*p == NUL) /* tostr is shorter than fromstr */ + goto error; + break; + } + ++idx; + } + + if (first && cpstr == in_str) { + /* Check that fromstr and tostr have the same number of + * (multi-byte) characters. Done only once when a character + * of in_str doesn't appear in fromstr. */ + first = FALSE; + for (p = tostr; *p != NUL; p += tolen) { + tolen = (*mb_ptr2len)(p); + --idx; + } + if (idx != 0) + goto error; + } + + ga_grow(&ga, cplen); + mch_memmove((char *)ga.ga_data + ga.ga_len, cpstr, (size_t)cplen); + ga.ga_len += cplen; + + in_str += inlen; + } else { + /* When not using multi-byte chars we can do it faster. */ + p = vim_strchr(fromstr, *in_str); + if (p != NULL) + ga_append(&ga, tostr[p - fromstr]); + else + ga_append(&ga, *in_str); + ++in_str; + } + } + + /* add a terminating NUL */ + ga_grow(&ga, 1); + ga_append(&ga, NUL); + + rettv->vval.v_string = ga.ga_data; +} + +/* + * "trunc({float})" function + */ +static void f_trunc(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + float_T f; + + rettv->v_type = VAR_FLOAT; + if (get_float_arg(argvars, &f) == OK) + /* trunc() is not in C90, use floor() or ceil() instead. */ + rettv->vval.v_float = f > 0 ? floor(f) : ceil(f); + else + rettv->vval.v_float = 0.0; +} + +/* + * "type(expr)" function + */ +static void f_type(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + int n; + + switch (argvars[0].v_type) { + case VAR_NUMBER: n = 0; break; + case VAR_STRING: n = 1; break; + case VAR_FUNC: n = 2; break; + case VAR_LIST: n = 3; break; + case VAR_DICT: n = 4; break; + case VAR_FLOAT: n = 5; break; + default: EMSG2(_(e_intern2), "f_type()"); n = 0; break; + } + rettv->vval.v_number = n; +} + +/* + * "undofile(name)" function + */ +static void f_undofile(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + rettv->v_type = VAR_STRING; + { + char_u *fname = get_tv_string(&argvars[0]); + + if (*fname == NUL) { + /* If there is no file name there will be no undo file. */ + rettv->vval.v_string = NULL; + } else { + char_u *ffname = FullName_save(fname, FALSE); + + if (ffname != NULL) + rettv->vval.v_string = u_get_undo_file_name(ffname, FALSE); + vim_free(ffname); + } + } +} + +/* + * "undotree()" function + */ +static void f_undotree(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + if (rettv_dict_alloc(rettv) == OK) { + dict_T *dict = rettv->vval.v_dict; + list_T *list; + + dict_add_nr_str(dict, "synced", (long)curbuf->b_u_synced, NULL); + dict_add_nr_str(dict, "seq_last", curbuf->b_u_seq_last, NULL); + dict_add_nr_str(dict, "save_last", + (long)curbuf->b_u_save_nr_last, NULL); + dict_add_nr_str(dict, "seq_cur", curbuf->b_u_seq_cur, NULL); + dict_add_nr_str(dict, "time_cur", (long)curbuf->b_u_time_cur, NULL); + dict_add_nr_str(dict, "save_cur", (long)curbuf->b_u_save_nr_cur, NULL); + + list = list_alloc(); + if (list != NULL) { + u_eval_tree(curbuf->b_u_oldhead, list); + dict_add_list(dict, "entries", list); + } + } +} + +/* + * "values(dict)" function + */ +static void f_values(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + dict_list(argvars, rettv, 1); +} + +/* + * "virtcol(string)" function + */ +static void f_virtcol(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + colnr_T vcol = 0; + pos_T *fp; + int fnum = curbuf->b_fnum; + + fp = var2fpos(&argvars[0], FALSE, &fnum); + if (fp != NULL && fp->lnum <= curbuf->b_ml.ml_line_count + && fnum == curbuf->b_fnum) { + getvvcol(curwin, fp, NULL, NULL, &vcol); + ++vcol; + } + + rettv->vval.v_number = vcol; +} + +/* + * "visualmode()" function + */ +static void f_visualmode(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + char_u str[2]; + + rettv->v_type = VAR_STRING; + str[0] = curbuf->b_visual_mode_eval; + str[1] = NUL; + rettv->vval.v_string = vim_strsave(str); + + /* A non-zero number or non-empty string argument: reset mode. */ + if (non_zero_arg(&argvars[0])) + curbuf->b_visual_mode_eval = NUL; +} + +/* + * "wildmenumode()" function + */ +static void f_wildmenumode(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv UNUSED; +{ + if (wild_menu_showing) + rettv->vval.v_number = 1; +} + +/* + * "winbufnr(nr)" function + */ +static void f_winbufnr(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + win_T *wp; + + wp = find_win_by_nr(&argvars[0], NULL); + if (wp == NULL) + rettv->vval.v_number = -1; + else + rettv->vval.v_number = wp->w_buffer->b_fnum; +} + +/* + * "wincol()" function + */ +static void f_wincol(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + validate_cursor(); + rettv->vval.v_number = curwin->w_wcol + 1; +} + +/* + * "winheight(nr)" function + */ +static void f_winheight(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + win_T *wp; + + wp = find_win_by_nr(&argvars[0], NULL); + if (wp == NULL) + rettv->vval.v_number = -1; + else + rettv->vval.v_number = wp->w_height; +} + +/* + * "winline()" function + */ +static void f_winline(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + validate_cursor(); + rettv->vval.v_number = curwin->w_wrow + 1; +} + +/* + * "winnr()" function + */ +static void f_winnr(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + int nr = 1; + + nr = get_winnr(curtab, &argvars[0]); + rettv->vval.v_number = nr; +} + +/* + * "winrestcmd()" function + */ +static void f_winrestcmd(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + win_T *wp; + int winnr = 1; + garray_T ga; + char_u buf[50]; + + ga_init2(&ga, (int)sizeof(char), 70); + for (wp = firstwin; wp != NULL; wp = wp->w_next) { + sprintf((char *)buf, "%dresize %d|", winnr, wp->w_height); + ga_concat(&ga, buf); + sprintf((char *)buf, "vert %dresize %d|", winnr, wp->w_width); + ga_concat(&ga, buf); + ++winnr; + } + ga_append(&ga, NUL); + + rettv->vval.v_string = ga.ga_data; + rettv->v_type = VAR_STRING; +} + +/* + * "winrestview()" function + */ +static void f_winrestview(argvars, rettv) +typval_T *argvars; +typval_T *rettv UNUSED; +{ + dict_T *dict; + + if (argvars[0].v_type != VAR_DICT + || (dict = argvars[0].vval.v_dict) == NULL) + EMSG(_(e_invarg)); + else { + curwin->w_cursor.lnum = get_dict_number(dict, (char_u *)"lnum"); + curwin->w_cursor.col = get_dict_number(dict, (char_u *)"col"); + curwin->w_cursor.coladd = get_dict_number(dict, (char_u *)"coladd"); + curwin->w_curswant = get_dict_number(dict, (char_u *)"curswant"); + curwin->w_set_curswant = FALSE; + + set_topline(curwin, get_dict_number(dict, (char_u *)"topline")); + curwin->w_topfill = get_dict_number(dict, (char_u *)"topfill"); + curwin->w_leftcol = get_dict_number(dict, (char_u *)"leftcol"); + curwin->w_skipcol = get_dict_number(dict, (char_u *)"skipcol"); + + check_cursor(); + win_new_height(curwin, curwin->w_height); + win_new_width(curwin, W_WIDTH(curwin)); + changed_window_setting(); + + if (curwin->w_topline == 0) + curwin->w_topline = 1; + if (curwin->w_topline > curbuf->b_ml.ml_line_count) + curwin->w_topline = curbuf->b_ml.ml_line_count; + check_topfill(curwin, TRUE); + } +} + +/* + * "winsaveview()" function + */ +static void f_winsaveview(argvars, rettv) +typval_T *argvars UNUSED; +typval_T *rettv; +{ + dict_T *dict; + + if (rettv_dict_alloc(rettv) == FAIL) + return; + dict = rettv->vval.v_dict; + + dict_add_nr_str(dict, "lnum", (long)curwin->w_cursor.lnum, NULL); + dict_add_nr_str(dict, "col", (long)curwin->w_cursor.col, NULL); + dict_add_nr_str(dict, "coladd", (long)curwin->w_cursor.coladd, NULL); + update_curswant(); + dict_add_nr_str(dict, "curswant", (long)curwin->w_curswant, NULL); + + dict_add_nr_str(dict, "topline", (long)curwin->w_topline, NULL); + dict_add_nr_str(dict, "topfill", (long)curwin->w_topfill, NULL); + dict_add_nr_str(dict, "leftcol", (long)curwin->w_leftcol, NULL); + dict_add_nr_str(dict, "skipcol", (long)curwin->w_skipcol, NULL); +} + +/* + * "winwidth(nr)" function + */ +static void f_winwidth(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + win_T *wp; + + wp = find_win_by_nr(&argvars[0], NULL); + if (wp == NULL) + rettv->vval.v_number = -1; + else + rettv->vval.v_number = wp->w_width; +} + +/* + * "writefile()" function + */ +static void f_writefile(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + int binary = FALSE; + char_u *fname; + FILE *fd; + listitem_T *li; + char_u *s; + int ret = 0; + int c; + + if (check_restricted() || check_secure()) + return; + + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "writefile()"); + return; + } + if (argvars[0].vval.v_list == NULL) + return; + + if (argvars[2].v_type != VAR_UNKNOWN + && STRCMP(get_tv_string(&argvars[2]), "b") == 0) + binary = TRUE; + + /* Always open the file in binary mode, library functions have a mind of + * their own about CR-LF conversion. */ + fname = get_tv_string(&argvars[1]); + if (*fname == NUL || (fd = mch_fopen((char *)fname, WRITEBIN)) == NULL) { + EMSG2(_(e_notcreate), *fname == NUL ? (char_u *)_("") : fname); + ret = -1; + } else { + for (li = argvars[0].vval.v_list->lv_first; li != NULL; + li = li->li_next) { + for (s = get_tv_string(&li->li_tv); *s != NUL; ++s) { + if (*s == '\n') + c = putc(NUL, fd); + else + c = putc(*s, fd); + if (c == EOF) { + ret = -1; + break; + } + } + if (!binary || li->li_next != NULL) + if (putc('\n', fd) == EOF) { + ret = -1; + break; + } + if (ret < 0) { + EMSG(_(e_write)); + break; + } + } + fclose(fd); + } + + rettv->vval.v_number = ret; +} + +/* + * "xor(expr, expr)" function + */ +static void f_xor(argvars, rettv) +typval_T *argvars; +typval_T *rettv; +{ + rettv->vval.v_number = get_tv_number_chk(&argvars[0], NULL) + ^ get_tv_number_chk(&argvars[1], NULL); +} + + +/* + * Translate a String variable into a position. + * Returns NULL when there is an error. + */ +static pos_T * var2fpos(varp, dollar_lnum, fnum) +typval_T *varp; +int dollar_lnum; /* TRUE when $ is last line */ +int *fnum; /* set to fnum for '0, 'A, etc. */ +{ + char_u *name; + static pos_T pos; + pos_T *pp; + + /* Argument can be [lnum, col, coladd]. */ + if (varp->v_type == VAR_LIST) { + list_T *l; + int len; + int error = FALSE; + listitem_T *li; + + l = varp->vval.v_list; + if (l == NULL) + return NULL; + + /* Get the line number */ + pos.lnum = list_find_nr(l, 0L, &error); + if (error || pos.lnum <= 0 || pos.lnum > curbuf->b_ml.ml_line_count) + return NULL; /* invalid line number */ + + /* Get the column number */ + pos.col = list_find_nr(l, 1L, &error); + if (error) + return NULL; + len = (long)STRLEN(ml_get(pos.lnum)); + + /* We accept "$" for the column number: last column. */ + li = list_find(l, 1L); + if (li != NULL && li->li_tv.v_type == VAR_STRING + && li->li_tv.vval.v_string != NULL + && STRCMP(li->li_tv.vval.v_string, "$") == 0) + pos.col = len + 1; + + /* Accept a position up to the NUL after the line. */ + if (pos.col == 0 || (int)pos.col > len + 1) + return NULL; /* invalid column number */ + --pos.col; + + /* Get the virtual offset. Defaults to zero. */ + pos.coladd = list_find_nr(l, 2L, &error); + if (error) + pos.coladd = 0; + + return &pos; + } + + name = get_tv_string_chk(varp); + if (name == NULL) + return NULL; + if (name[0] == '.') /* cursor */ + return &curwin->w_cursor; + if (name[0] == 'v' && name[1] == NUL) { /* Visual start */ + if (VIsual_active) + return &VIsual; + return &curwin->w_cursor; + } + if (name[0] == '\'') { /* mark */ + pp = getmark_buf_fnum(curbuf, name[1], FALSE, fnum); + if (pp == NULL || pp == (pos_T *)-1 || pp->lnum <= 0) + return NULL; + return pp; + } + + pos.coladd = 0; + + if (name[0] == 'w' && dollar_lnum) { + pos.col = 0; + if (name[1] == '0') { /* "w0": first visible line */ + update_topline(); + pos.lnum = curwin->w_topline; + return &pos; + } else if (name[1] == '$') { /* "w$": last visible line */ + validate_botline(); + pos.lnum = curwin->w_botline - 1; + return &pos; + } + } else if (name[0] == '$') { /* last column or line */ + if (dollar_lnum) { + pos.lnum = curbuf->b_ml.ml_line_count; + pos.col = 0; + } else { + pos.lnum = curwin->w_cursor.lnum; + pos.col = (colnr_T)STRLEN(ml_get_curline()); + } + return &pos; + } + return NULL; +} + +/* + * Convert list in "arg" into a position and optional file number. + * When "fnump" is NULL there is no file number, only 3 items. + * Note that the column is passed on as-is, the caller may want to decrement + * it to use 1 for the first column. + * Return FAIL when conversion is not possible, doesn't check the position for + * validity. + */ +static int list2fpos(arg, posp, fnump) +typval_T *arg; +pos_T *posp; +int *fnump; +{ + list_T *l = arg->vval.v_list; + long i = 0; + long n; + + /* List must be: [fnum, lnum, col, coladd], where "fnum" is only there + * when "fnump" isn't NULL and "coladd" is optional. */ + if (arg->v_type != VAR_LIST + || l == NULL + || l->lv_len < (fnump == NULL ? 2 : 3) + || l->lv_len > (fnump == NULL ? 3 : 4)) + return FAIL; + + if (fnump != NULL) { + n = list_find_nr(l, i++, NULL); /* fnum */ + if (n < 0) + return FAIL; + if (n == 0) + n = curbuf->b_fnum; /* current buffer */ + *fnump = n; + } + + n = list_find_nr(l, i++, NULL); /* lnum */ + if (n < 0) + return FAIL; + posp->lnum = n; + + n = list_find_nr(l, i++, NULL); /* col */ + if (n < 0) + return FAIL; + posp->col = n; + + n = list_find_nr(l, i, NULL); + if (n < 0) + posp->coladd = 0; + else + posp->coladd = n; + + return OK; +} + +/* + * Get the length of an environment variable name. + * Advance "arg" to the first character after the name. + * Return 0 for error. + */ +static int get_env_len(arg) +char_u **arg; +{ + char_u *p; + int len; + + for (p = *arg; vim_isIDc(*p); ++p) + ; + if (p == *arg) /* no name found */ + return 0; + + len = (int)(p - *arg); + *arg = p; + return len; +} + +/* + * Get the length of the name of a function or internal variable. + * "arg" is advanced to the first non-white character after the name. + * Return 0 if something is wrong. + */ +static int get_id_len(arg) +char_u **arg; +{ + char_u *p; + int len; + + /* Find the end of the name. */ + for (p = *arg; eval_isnamec(*p); ++p) + ; + if (p == *arg) /* no name found */ + return 0; + + len = (int)(p - *arg); + *arg = skipwhite(p); + + return len; +} + +/* + * Get the length of the name of a variable or function. + * Only the name is recognized, does not handle ".key" or "[idx]". + * "arg" is advanced to the first non-white character after the name. + * Return -1 if curly braces expansion failed. + * Return 0 if something else is wrong. + * If the name contains 'magic' {}'s, expand them and return the + * expanded name in an allocated string via 'alias' - caller must free. + */ +static int get_name_len(arg, alias, evaluate, verbose) +char_u **arg; +char_u **alias; +int evaluate; +int verbose; +{ + int len; + char_u *p; + char_u *expr_start; + char_u *expr_end; + + *alias = NULL; /* default to no alias */ + + if ((*arg)[0] == K_SPECIAL && (*arg)[1] == KS_EXTRA + && (*arg)[2] == (int)KE_SNR) { + /* hard coded , already translated */ + *arg += 3; + return get_id_len(arg) + 3; + } + len = eval_fname_script(*arg); + if (len > 0) { + /* literal "", "s:" or "" */ + *arg += len; + } + + /* + * Find the end of the name; check for {} construction. + */ + p = find_name_end(*arg, &expr_start, &expr_end, + len > 0 ? 0 : FNE_CHECK_START); + if (expr_start != NULL) { + char_u *temp_string; + + if (!evaluate) { + len += (int)(p - *arg); + *arg = skipwhite(p); + return len; + } + + /* + * Include any etc in the expanded string: + * Thus the -len here. + */ + temp_string = make_expanded_name(*arg - len, expr_start, expr_end, p); + if (temp_string == NULL) + return -1; + *alias = temp_string; + *arg = skipwhite(p); + return (int)STRLEN(temp_string); + } + + len += get_id_len(arg); + if (len == 0 && verbose) + EMSG2(_(e_invexpr2), *arg); + + return len; +} + +/* + * Find the end of a variable or function name, taking care of magic braces. + * If "expr_start" is not NULL then "expr_start" and "expr_end" are set to the + * start and end of the first magic braces item. + * "flags" can have FNE_INCL_BR and FNE_CHECK_START. + * Return a pointer to just after the name. Equal to "arg" if there is no + * valid name. + */ +static char_u * find_name_end(arg, expr_start, expr_end, flags) +char_u *arg; +char_u **expr_start; +char_u **expr_end; +int flags; +{ + int mb_nest = 0; + int br_nest = 0; + char_u *p; + + if (expr_start != NULL) { + *expr_start = NULL; + *expr_end = NULL; + } + + /* Quick check for valid starting character. */ + if ((flags & FNE_CHECK_START) && !eval_isnamec1(*arg) && *arg != '{') + return arg; + + for (p = arg; *p != NUL + && (eval_isnamec(*p) + || *p == '{' + || ((flags & FNE_INCL_BR) && (*p == '[' || *p == '.')) + || mb_nest != 0 + || br_nest != 0); mb_ptr_adv(p)) { + if (*p == '\'') { + /* skip over 'string' to avoid counting [ and ] inside it. */ + for (p = p + 1; *p != NUL && *p != '\''; mb_ptr_adv(p)) + ; + if (*p == NUL) + break; + } else if (*p == '"') { + /* skip over "str\"ing" to avoid counting [ and ] inside it. */ + for (p = p + 1; *p != NUL && *p != '"'; mb_ptr_adv(p)) + if (*p == '\\' && p[1] != NUL) + ++p; + if (*p == NUL) + break; + } + + if (mb_nest == 0) { + if (*p == '[') + ++br_nest; + else if (*p == ']') + --br_nest; + } + + if (br_nest == 0) { + if (*p == '{') { + mb_nest++; + if (expr_start != NULL && *expr_start == NULL) + *expr_start = p; + } else if (*p == '}') { + mb_nest--; + if (expr_start != NULL && mb_nest == 0 && *expr_end == NULL) + *expr_end = p; + } + } + } + + return p; +} + +/* + * Expands out the 'magic' {}'s in a variable/function name. + * Note that this can call itself recursively, to deal with + * constructs like foo{bar}{baz}{bam} + * The four pointer arguments point to "foo{expre}ss{ion}bar" + * "in_start" ^ + * "expr_start" ^ + * "expr_end" ^ + * "in_end" ^ + * + * Returns a new allocated string, which the caller must free. + * Returns NULL for failure. + */ +static char_u * make_expanded_name(in_start, expr_start, expr_end, in_end) +char_u *in_start; +char_u *expr_start; +char_u *expr_end; +char_u *in_end; +{ + char_u c1; + char_u *retval = NULL; + char_u *temp_result; + char_u *nextcmd = NULL; + + if (expr_end == NULL || in_end == NULL) + return NULL; + *expr_start = NUL; + *expr_end = NUL; + c1 = *in_end; + *in_end = NUL; + + temp_result = eval_to_string(expr_start + 1, &nextcmd, FALSE); + if (temp_result != NULL && nextcmd == NULL) { + retval = alloc((unsigned)(STRLEN(temp_result) + (expr_start - in_start) + + (in_end - expr_end) + 1)); + if (retval != NULL) { + STRCPY(retval, in_start); + STRCAT(retval, temp_result); + STRCAT(retval, expr_end + 1); + } + } + vim_free(temp_result); + + *in_end = c1; /* put char back for error messages */ + *expr_start = '{'; + *expr_end = '}'; + + if (retval != NULL) { + temp_result = find_name_end(retval, &expr_start, &expr_end, 0); + if (expr_start != NULL) { + /* Further expansion! */ + temp_result = make_expanded_name(retval, expr_start, + expr_end, temp_result); + vim_free(retval); + retval = temp_result; + } + } + + return retval; +} + +/* + * Return TRUE if character "c" can be used in a variable or function name. + * Does not include '{' or '}' for magic braces. + */ +static int eval_isnamec(c) +int c; +{ + return ASCII_ISALNUM(c) || c == '_' || c == ':' || c == AUTOLOAD_CHAR; +} + +/* + * Return TRUE if character "c" can be used as the first character in a + * variable or function name (excluding '{' and '}'). + */ +static int eval_isnamec1(c) +int c; +{ + return ASCII_ISALPHA(c) || c == '_'; +} + +/* + * Set number v: variable to "val". + */ +void set_vim_var_nr(idx, val) +int idx; +long val; +{ + vimvars[idx].vv_nr = val; +} + +/* + * Get number v: variable value. + */ +long get_vim_var_nr(idx) +int idx; +{ + return vimvars[idx].vv_nr; +} + +/* + * Get string v: variable value. Uses a static buffer, can only be used once. + */ +char_u * get_vim_var_str(idx) +int idx; +{ + return get_tv_string(&vimvars[idx].vv_tv); +} + +/* + * Get List v: variable value. Caller must take care of reference count when + * needed. + */ +list_T * get_vim_var_list(idx) +int idx; +{ + return vimvars[idx].vv_list; +} + +/* + * Set v:char to character "c". + */ +void set_vim_var_char(c) +int c; +{ + char_u buf[MB_MAXBYTES + 1]; + + if (has_mbyte) + buf[(*mb_char2bytes)(c, buf)] = NUL; + else { + buf[0] = c; + buf[1] = NUL; + } + set_vim_var_string(VV_CHAR, buf, -1); +} + +/* + * Set v:count to "count" and v:count1 to "count1". + * When "set_prevcount" is TRUE first set v:prevcount from v:count. + */ +void set_vcount(count, count1, set_prevcount) +long count; +long count1; +int set_prevcount; +{ + if (set_prevcount) + vimvars[VV_PREVCOUNT].vv_nr = vimvars[VV_COUNT].vv_nr; + vimvars[VV_COUNT].vv_nr = count; + vimvars[VV_COUNT1].vv_nr = count1; +} + +/* + * Set string v: variable to a copy of "val". + */ +void set_vim_var_string(idx, val, len) +int idx; +char_u *val; +int len; /* length of "val" to use or -1 (whole string) */ +{ + /* Need to do this (at least) once, since we can't initialize a union. + * Will always be invoked when "v:progname" is set. */ + vimvars[VV_VERSION].vv_nr = VIM_VERSION_100; + + vim_free(vimvars[idx].vv_str); + if (val == NULL) + vimvars[idx].vv_str = NULL; + else if (len == -1) + vimvars[idx].vv_str = vim_strsave(val); + else + vimvars[idx].vv_str = vim_strnsave(val, len); +} + +/* + * Set List v: variable to "val". + */ +void set_vim_var_list(idx, val) +int idx; +list_T *val; +{ + list_unref(vimvars[idx].vv_list); + vimvars[idx].vv_list = val; + if (val != NULL) + ++val->lv_refcount; +} + +/* + * Set v:register if needed. + */ +void set_reg_var(c) +int c; +{ + char_u regname; + + if (c == 0 || c == ' ') + regname = '"'; + else + regname = c; + /* Avoid free/alloc when the value is already right. */ + if (vimvars[VV_REG].vv_str == NULL || vimvars[VV_REG].vv_str[0] != c) + set_vim_var_string(VV_REG, ®name, 1); +} + +/* + * Get or set v:exception. If "oldval" == NULL, return the current value. + * Otherwise, restore the value to "oldval" and return NULL. + * Must always be called in pairs to save and restore v:exception! Does not + * take care of memory allocations. + */ +char_u * v_exception(oldval) +char_u *oldval; +{ + if (oldval == NULL) + return vimvars[VV_EXCEPTION].vv_str; + + vimvars[VV_EXCEPTION].vv_str = oldval; + return NULL; +} + +/* + * Get or set v:throwpoint. If "oldval" == NULL, return the current value. + * Otherwise, restore the value to "oldval" and return NULL. + * Must always be called in pairs to save and restore v:throwpoint! Does not + * take care of memory allocations. + */ +char_u * v_throwpoint(oldval) +char_u *oldval; +{ + if (oldval == NULL) + return vimvars[VV_THROWPOINT].vv_str; + + vimvars[VV_THROWPOINT].vv_str = oldval; + return NULL; +} + +/* + * Set v:cmdarg. + * If "eap" != NULL, use "eap" to generate the value and return the old value. + * If "oldarg" != NULL, restore the value to "oldarg" and return NULL. + * Must always be called in pairs! + */ +char_u * set_cmdarg(eap, oldarg) +exarg_T *eap; +char_u *oldarg; +{ + char_u *oldval; + char_u *newval; + unsigned len; + + oldval = vimvars[VV_CMDARG].vv_str; + if (eap == NULL) { + vim_free(oldval); + vimvars[VV_CMDARG].vv_str = oldarg; + return NULL; + } + + if (eap->force_bin == FORCE_BIN) + len = 6; + else if (eap->force_bin == FORCE_NOBIN) + len = 8; + else + len = 0; + + if (eap->read_edit) + len += 7; + + if (eap->force_ff != 0) + len += (unsigned)STRLEN(eap->cmd + eap->force_ff) + 6; + if (eap->force_enc != 0) + len += (unsigned)STRLEN(eap->cmd + eap->force_enc) + 7; + if (eap->bad_char != 0) + len += 7 + 4; /* " ++bad=" + "keep" or "drop" */ + + newval = alloc(len + 1); + if (newval == NULL) + return NULL; + + if (eap->force_bin == FORCE_BIN) + sprintf((char *)newval, " ++bin"); + else if (eap->force_bin == FORCE_NOBIN) + sprintf((char *)newval, " ++nobin"); + else + *newval = NUL; + + if (eap->read_edit) + STRCAT(newval, " ++edit"); + + if (eap->force_ff != 0) + sprintf((char *)newval + STRLEN(newval), " ++ff=%s", + eap->cmd + eap->force_ff); + if (eap->force_enc != 0) + sprintf((char *)newval + STRLEN(newval), " ++enc=%s", + eap->cmd + eap->force_enc); + if (eap->bad_char == BAD_KEEP) + STRCPY(newval + STRLEN(newval), " ++bad=keep"); + else if (eap->bad_char == BAD_DROP) + STRCPY(newval + STRLEN(newval), " ++bad=drop"); + else if (eap->bad_char != 0) + sprintf((char *)newval + STRLEN(newval), " ++bad=%c", eap->bad_char); + vimvars[VV_CMDARG].vv_str = newval; + return oldval; +} + +/* + * Get the value of internal variable "name". + * Return OK or FAIL. + */ +static int get_var_tv(name, len, rettv, verbose, no_autoload) +char_u *name; +int len; /* length of "name" */ +typval_T *rettv; /* NULL when only checking existence */ +int verbose; /* may give error message */ +int no_autoload; /* do not use script autoloading */ +{ + int ret = OK; + typval_T *tv = NULL; + typval_T atv; + dictitem_T *v; + int cc; + + /* truncate the name, so that we can use strcmp() */ + cc = name[len]; + name[len] = NUL; + + /* + * Check for "b:changedtick". + */ + if (STRCMP(name, "b:changedtick") == 0) { + atv.v_type = VAR_NUMBER; + atv.vval.v_number = curbuf->b_changedtick; + tv = &atv; + } + /* + * Check for user-defined variables. + */ + else { + v = find_var(name, NULL, no_autoload); + if (v != NULL) + tv = &v->di_tv; + } + + if (tv == NULL) { + if (rettv != NULL && verbose) + EMSG2(_(e_undefvar), name); + ret = FAIL; + } else if (rettv != NULL) + copy_tv(tv, rettv); + + name[len] = cc; + + return ret; +} + +/* + * Handle expr[expr], expr[expr:expr] subscript and .name lookup. + * Also handle function call with Funcref variable: func(expr) + * Can all be combined: dict.func(expr)[idx]['func'](expr) + */ +static int handle_subscript(arg, rettv, evaluate, verbose) +char_u **arg; +typval_T *rettv; +int evaluate; /* do more than finding the end */ +int verbose; /* give error messages */ +{ + int ret = OK; + dict_T *selfdict = NULL; + char_u *s; + int len; + typval_T functv; + + while (ret == OK + && (**arg == '[' + || (**arg == '.' && rettv->v_type == VAR_DICT) + || (**arg == '(' && (!evaluate || rettv->v_type == VAR_FUNC))) + && !vim_iswhite(*(*arg - 1))) { + if (**arg == '(') { + /* need to copy the funcref so that we can clear rettv */ + if (evaluate) { + functv = *rettv; + rettv->v_type = VAR_UNKNOWN; + + /* Invoke the function. Recursive! */ + s = functv.vval.v_string; + } else + s = (char_u *)""; + ret = get_func_tv(s, (int)STRLEN(s), rettv, arg, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &len, evaluate, selfdict); + + /* Clear the funcref afterwards, so that deleting it while + * evaluating the arguments is possible (see test55). */ + if (evaluate) + clear_tv(&functv); + + /* Stop the expression evaluation when immediately aborting on + * error, or when an interrupt occurred or an exception was thrown + * but not caught. */ + if (aborting()) { + if (ret == OK) + clear_tv(rettv); + ret = FAIL; + } + dict_unref(selfdict); + selfdict = NULL; + } else { /* **arg == '[' || **arg == '.' */ + dict_unref(selfdict); + if (rettv->v_type == VAR_DICT) { + selfdict = rettv->vval.v_dict; + if (selfdict != NULL) + ++selfdict->dv_refcount; + } else + selfdict = NULL; + if (eval_index(arg, rettv, evaluate, verbose) == FAIL) { + clear_tv(rettv); + ret = FAIL; + } + } + } + dict_unref(selfdict); + return ret; +} + +/* + * Allocate memory for a variable type-value, and make it empty (0 or NULL + * value). + */ +static typval_T * alloc_tv() { + return (typval_T *)alloc_clear((unsigned)sizeof(typval_T)); +} + +/* + * Allocate memory for a variable type-value, and assign a string to it. + * The string "s" must have been allocated, it is consumed. + * Return NULL for out of memory, the variable otherwise. + */ +static typval_T * alloc_string_tv(s) +char_u *s; +{ + typval_T *rettv; + + rettv = alloc_tv(); + if (rettv != NULL) { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = s; + } else + vim_free(s); + return rettv; +} + +/* + * Free the memory for a variable type-value. + */ +void free_tv(varp) +typval_T *varp; +{ + if (varp != NULL) { + switch (varp->v_type) { + case VAR_FUNC: + func_unref(varp->vval.v_string); + /*FALLTHROUGH*/ + case VAR_STRING: + vim_free(varp->vval.v_string); + break; + case VAR_LIST: + list_unref(varp->vval.v_list); + break; + case VAR_DICT: + dict_unref(varp->vval.v_dict); + break; + case VAR_NUMBER: + case VAR_FLOAT: + case VAR_UNKNOWN: + break; + default: + EMSG2(_(e_intern2), "free_tv()"); + break; + } + vim_free(varp); + } +} + +/* + * Free the memory for a variable value and set the value to NULL or 0. + */ +void clear_tv(varp) +typval_T *varp; +{ + if (varp != NULL) { + switch (varp->v_type) { + case VAR_FUNC: + func_unref(varp->vval.v_string); + /*FALLTHROUGH*/ + case VAR_STRING: + vim_free(varp->vval.v_string); + varp->vval.v_string = NULL; + break; + case VAR_LIST: + list_unref(varp->vval.v_list); + varp->vval.v_list = NULL; + break; + case VAR_DICT: + dict_unref(varp->vval.v_dict); + varp->vval.v_dict = NULL; + break; + case VAR_NUMBER: + varp->vval.v_number = 0; + break; + case VAR_FLOAT: + varp->vval.v_float = 0.0; + break; + case VAR_UNKNOWN: + break; + default: + EMSG2(_(e_intern2), "clear_tv()"); + } + varp->v_lock = 0; + } +} + +/* + * Set the value of a variable to NULL without freeing items. + */ +static void init_tv(varp) +typval_T *varp; +{ + if (varp != NULL) + vim_memset(varp, 0, sizeof(typval_T)); +} + +/* + * Get the number value of a variable. + * If it is a String variable, uses vim_str2nr(). + * For incompatible types, return 0. + * get_tv_number_chk() is similar to get_tv_number(), but informs the + * caller of incompatible types: it sets *denote to TRUE if "denote" + * is not NULL or returns -1 otherwise. + */ +static long get_tv_number(varp) +typval_T *varp; +{ + int error = FALSE; + + return get_tv_number_chk(varp, &error); /* return 0L on error */ +} + +long get_tv_number_chk(varp, denote) +typval_T *varp; +int *denote; +{ + long n = 0L; + + switch (varp->v_type) { + case VAR_NUMBER: + return (long)(varp->vval.v_number); + case VAR_FLOAT: + EMSG(_("E805: Using a Float as a Number")); + break; + case VAR_FUNC: + EMSG(_("E703: Using a Funcref as a Number")); + break; + case VAR_STRING: + if (varp->vval.v_string != NULL) + vim_str2nr(varp->vval.v_string, NULL, NULL, + TRUE, TRUE, &n, NULL); + return n; + case VAR_LIST: + EMSG(_("E745: Using a List as a Number")); + break; + case VAR_DICT: + EMSG(_("E728: Using a Dictionary as a Number")); + break; + default: + EMSG2(_(e_intern2), "get_tv_number()"); + break; + } + if (denote == NULL) /* useful for values that must be unsigned */ + n = -1; + else + *denote = TRUE; + return n; +} + +/* + * Get the lnum from the first argument. + * Also accepts ".", "$", etc., but that only works for the current buffer. + * Returns -1 on error. + */ +static linenr_T get_tv_lnum(argvars) +typval_T *argvars; +{ + typval_T rettv; + linenr_T lnum; + + lnum = get_tv_number_chk(&argvars[0], NULL); + if (lnum == 0) { /* no valid number, try using line() */ + rettv.v_type = VAR_NUMBER; + f_line(argvars, &rettv); + lnum = rettv.vval.v_number; + clear_tv(&rettv); + } + return lnum; +} + +/* + * Get the lnum from the first argument. + * Also accepts "$", then "buf" is used. + * Returns 0 on error. + */ +static linenr_T get_tv_lnum_buf(argvars, buf) +typval_T *argvars; +buf_T *buf; +{ + if (argvars[0].v_type == VAR_STRING + && argvars[0].vval.v_string != NULL + && argvars[0].vval.v_string[0] == '$' + && buf != NULL) + return buf->b_ml.ml_line_count; + return get_tv_number_chk(&argvars[0], NULL); +} + +/* + * Get the string value of a variable. + * If it is a Number variable, the number is converted into a string. + * get_tv_string() uses a single, static buffer. YOU CAN ONLY USE IT ONCE! + * get_tv_string_buf() uses a given buffer. + * If the String variable has never been set, return an empty string. + * Never returns NULL; + * get_tv_string_chk() and get_tv_string_buf_chk() are similar, but return + * NULL on error. + */ +static char_u * get_tv_string(varp) +typval_T *varp; +{ + static char_u mybuf[NUMBUFLEN]; + + return get_tv_string_buf(varp, mybuf); +} + +static char_u * get_tv_string_buf(varp, buf) +typval_T *varp; +char_u *buf; +{ + char_u *res = get_tv_string_buf_chk(varp, buf); + + return res != NULL ? res : (char_u *)""; +} + +char_u * get_tv_string_chk(varp) +typval_T *varp; +{ + static char_u mybuf[NUMBUFLEN]; + + return get_tv_string_buf_chk(varp, mybuf); +} + +static char_u * get_tv_string_buf_chk(varp, buf) +typval_T *varp; +char_u *buf; +{ + switch (varp->v_type) { + case VAR_NUMBER: + sprintf((char *)buf, "%ld", (long)varp->vval.v_number); + return buf; + case VAR_FUNC: + EMSG(_("E729: using Funcref as a String")); + break; + case VAR_LIST: + EMSG(_("E730: using List as a String")); + break; + case VAR_DICT: + EMSG(_("E731: using Dictionary as a String")); + break; + case VAR_FLOAT: + EMSG(_(e_float_as_string)); + break; + case VAR_STRING: + if (varp->vval.v_string != NULL) + return varp->vval.v_string; + return (char_u *)""; + default: + EMSG2(_(e_intern2), "get_tv_string_buf()"); + break; + } + return NULL; +} + +/* + * Find variable "name" in the list of variables. + * Return a pointer to it if found, NULL if not found. + * Careful: "a:0" variables don't have a name. + * When "htp" is not NULL we are writing to the variable, set "htp" to the + * hashtab_T used. + */ +static dictitem_T * find_var(name, htp, no_autoload) +char_u *name; +hashtab_T **htp; +int no_autoload; +{ + char_u *varname; + hashtab_T *ht; + + ht = find_var_ht(name, &varname); + if (htp != NULL) + *htp = ht; + if (ht == NULL) + return NULL; + return find_var_in_ht(ht, *name, varname, no_autoload || htp != NULL); +} + +/* + * Find variable "varname" in hashtab "ht" with name "htname". + * Returns NULL if not found. + */ +static dictitem_T * find_var_in_ht(ht, htname, varname, no_autoload) +hashtab_T *ht; +int htname; +char_u *varname; +int no_autoload; +{ + hashitem_T *hi; + + if (*varname == NUL) { + /* Must be something like "s:", otherwise "ht" would be NULL. */ + switch (htname) { + case 's': return &SCRIPT_SV(current_SID)->sv_var; + case 'g': return &globvars_var; + case 'v': return &vimvars_var; + case 'b': return &curbuf->b_bufvar; + case 'w': return &curwin->w_winvar; + case 't': return &curtab->tp_winvar; + case 'l': return current_funccal == NULL + ? NULL : ¤t_funccal->l_vars_var; + case 'a': return current_funccal == NULL + ? NULL : ¤t_funccal->l_avars_var; + } + return NULL; + } + + hi = hash_find(ht, varname); + if (HASHITEM_EMPTY(hi)) { + /* For global variables we may try auto-loading the script. If it + * worked find the variable again. Don't auto-load a script if it was + * loaded already, otherwise it would be loaded every time when + * checking if a function name is a Funcref variable. */ + if (ht == &globvarht && !no_autoload) { + /* Note: script_autoload() may make "hi" invalid. It must either + * be obtained again or not used. */ + if (!script_autoload(varname, FALSE) || aborting()) + return NULL; + hi = hash_find(ht, varname); + } + if (HASHITEM_EMPTY(hi)) + return NULL; + } + return HI2DI(hi); +} + +/* + * Find the hashtab used for a variable name. + * Set "varname" to the start of name without ':'. + */ +static hashtab_T * find_var_ht(name, varname) +char_u *name; +char_u **varname; +{ + hashitem_T *hi; + + if (name[1] != ':') { + /* The name must not start with a colon or #. */ + if (name[0] == ':' || name[0] == AUTOLOAD_CHAR) + return NULL; + *varname = name; + + /* "version" is "v:version" in all scopes */ + hi = hash_find(&compat_hashtab, name); + if (!HASHITEM_EMPTY(hi)) + return &compat_hashtab; + + if (current_funccal == NULL) + return &globvarht; /* global variable */ + return ¤t_funccal->l_vars.dv_hashtab; /* l: variable */ + } + *varname = name + 2; + if (*name == 'g') /* global variable */ + return &globvarht; + /* There must be no ':' or '#' in the rest of the name, unless g: is used + */ + if (vim_strchr(name + 2, ':') != NULL + || vim_strchr(name + 2, AUTOLOAD_CHAR) != NULL) + return NULL; + if (*name == 'b') /* buffer variable */ + return &curbuf->b_vars->dv_hashtab; + if (*name == 'w') /* window variable */ + return &curwin->w_vars->dv_hashtab; + if (*name == 't') /* tab page variable */ + return &curtab->tp_vars->dv_hashtab; + if (*name == 'v') /* v: variable */ + return &vimvarht; + if (*name == 'a' && current_funccal != NULL) /* function argument */ + return ¤t_funccal->l_avars.dv_hashtab; + if (*name == 'l' && current_funccal != NULL) /* local function variable */ + return ¤t_funccal->l_vars.dv_hashtab; + if (*name == 's' /* script variable */ + && current_SID > 0 && current_SID <= ga_scripts.ga_len) + return &SCRIPT_VARS(current_SID); + return NULL; +} + +/* + * Get the string value of a (global/local) variable. + * Note: see get_tv_string() for how long the pointer remains valid. + * Returns NULL when it doesn't exist. + */ +char_u * get_var_value(name) +char_u *name; +{ + dictitem_T *v; + + v = find_var(name, NULL, FALSE); + if (v == NULL) + return NULL; + return get_tv_string(&v->di_tv); +} + +/* + * Allocate a new hashtab for a sourced script. It will be used while + * sourcing this script and when executing functions defined in the script. + */ +void new_script_vars(id) +scid_T id; +{ + int i; + hashtab_T *ht; + scriptvar_T *sv; + + if (ga_grow(&ga_scripts, (int)(id - ga_scripts.ga_len)) == OK) { + /* Re-allocating ga_data means that an ht_array pointing to + * ht_smallarray becomes invalid. We can recognize this: ht_mask is + * at its init value. Also reset "v_dict", it's always the same. */ + for (i = 1; i <= ga_scripts.ga_len; ++i) { + ht = &SCRIPT_VARS(i); + if (ht->ht_mask == HT_INIT_SIZE - 1) + ht->ht_array = ht->ht_smallarray; + sv = SCRIPT_SV(i); + sv->sv_var.di_tv.vval.v_dict = &sv->sv_dict; + } + + while (ga_scripts.ga_len < id) { + sv = SCRIPT_SV(ga_scripts.ga_len + 1) = + (scriptvar_T *)alloc_clear(sizeof(scriptvar_T)); + init_var_dict(&sv->sv_dict, &sv->sv_var, VAR_SCOPE); + ++ga_scripts.ga_len; + } + } +} + +/* + * Initialize dictionary "dict" as a scope and set variable "dict_var" to + * point to it. + */ +void init_var_dict(dict, dict_var, scope) +dict_T *dict; +dictitem_T *dict_var; +int scope; +{ + hash_init(&dict->dv_hashtab); + dict->dv_lock = 0; + dict->dv_scope = scope; + dict->dv_refcount = DO_NOT_FREE_CNT; + dict->dv_copyID = 0; + dict_var->di_tv.vval.v_dict = dict; + dict_var->di_tv.v_type = VAR_DICT; + dict_var->di_tv.v_lock = VAR_FIXED; + dict_var->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + dict_var->di_key[0] = NUL; +} + +/* + * Unreference a dictionary initialized by init_var_dict(). + */ +void unref_var_dict(dict) +dict_T *dict; +{ + /* Now the dict needs to be freed if no one else is using it, go back to + * normal reference counting. */ + dict->dv_refcount -= DO_NOT_FREE_CNT - 1; + dict_unref(dict); +} + +/* + * Clean up a list of internal variables. + * Frees all allocated variables and the value they contain. + * Clears hashtab "ht", does not free it. + */ +void vars_clear(ht) +hashtab_T *ht; +{ + vars_clear_ext(ht, TRUE); +} + +/* + * Like vars_clear(), but only free the value if "free_val" is TRUE. + */ +static void vars_clear_ext(ht, free_val) +hashtab_T *ht; +int free_val; +{ + int todo; + hashitem_T *hi; + dictitem_T *v; + + hash_lock(ht); + todo = (int)ht->ht_used; + for (hi = ht->ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + + /* Free the variable. Don't remove it from the hashtab, + * ht_array might change then. hash_clear() takes care of it + * later. */ + v = HI2DI(hi); + if (free_val) + clear_tv(&v->di_tv); + if ((v->di_flags & DI_FLAGS_FIX) == 0) + vim_free(v); + } + } + hash_clear(ht); + ht->ht_used = 0; +} + +/* + * Delete a variable from hashtab "ht" at item "hi". + * Clear the variable value and free the dictitem. + */ +static void delete_var(ht, hi) +hashtab_T *ht; +hashitem_T *hi; +{ + dictitem_T *di = HI2DI(hi); + + hash_remove(ht, hi); + clear_tv(&di->di_tv); + vim_free(di); +} + +/* + * List the value of one internal variable. + */ +static void list_one_var(v, prefix, first) +dictitem_T *v; +char_u *prefix; +int *first; +{ + char_u *tofree; + char_u *s; + char_u numbuf[NUMBUFLEN]; + + current_copyID += COPYID_INC; + s = echo_string(&v->di_tv, &tofree, numbuf, current_copyID); + list_one_var_a(prefix, v->di_key, v->di_tv.v_type, + s == NULL ? (char_u *)"" : s, first); + vim_free(tofree); +} + +static void list_one_var_a(prefix, name, type, string, first) +char_u *prefix; +char_u *name; +int type; +char_u *string; +int *first; /* when TRUE clear rest of screen and set to FALSE */ +{ + /* don't use msg() or msg_attr() to avoid overwriting "v:statusmsg" */ + msg_start(); + msg_puts(prefix); + if (name != NULL) /* "a:" vars don't have a name stored */ + msg_puts(name); + msg_putchar(' '); + msg_advance(22); + if (type == VAR_NUMBER) + msg_putchar('#'); + else if (type == VAR_FUNC) + msg_putchar('*'); + else if (type == VAR_LIST) { + msg_putchar('['); + if (*string == '[') + ++string; + } else if (type == VAR_DICT) { + msg_putchar('{'); + if (*string == '{') + ++string; + } else + msg_putchar(' '); + + msg_outtrans(string); + + if (type == VAR_FUNC) + msg_puts((char_u *)"()"); + if (*first) { + msg_clr_eos(); + *first = FALSE; + } +} + +/* + * Set variable "name" to value in "tv". + * If the variable already exists, the value is updated. + * Otherwise the variable is created. + */ +static void set_var(name, tv, copy) +char_u *name; +typval_T *tv; +int copy; /* make copy of value in "tv" */ +{ + dictitem_T *v; + char_u *varname; + hashtab_T *ht; + + ht = find_var_ht(name, &varname); + if (ht == NULL || *varname == NUL) { + EMSG2(_(e_illvar), name); + return; + } + v = find_var_in_ht(ht, 0, varname, TRUE); + + if (tv->v_type == VAR_FUNC && var_check_func_name(name, v == NULL)) + return; + + if (v != NULL) { + /* existing variable, need to clear the value */ + if (var_check_ro(v->di_flags, name) + || tv_check_lock(v->di_tv.v_lock, name)) + return; + if (v->di_tv.v_type != tv->v_type + && !((v->di_tv.v_type == VAR_STRING + || v->di_tv.v_type == VAR_NUMBER) + && (tv->v_type == VAR_STRING + || tv->v_type == VAR_NUMBER)) + && !((v->di_tv.v_type == VAR_NUMBER + || v->di_tv.v_type == VAR_FLOAT) + && (tv->v_type == VAR_NUMBER + || tv->v_type == VAR_FLOAT)) + ) { + EMSG2(_("E706: Variable type mismatch for: %s"), name); + return; + } + + /* + * Handle setting internal v: variables separately: we don't change + * the type. + */ + if (ht == &vimvarht) { + if (v->di_tv.v_type == VAR_STRING) { + vim_free(v->di_tv.vval.v_string); + if (copy || tv->v_type != VAR_STRING) + v->di_tv.vval.v_string = vim_strsave(get_tv_string(tv)); + else { + /* Take over the string to avoid an extra alloc/free. */ + v->di_tv.vval.v_string = tv->vval.v_string; + tv->vval.v_string = NULL; + } + } else if (v->di_tv.v_type != VAR_NUMBER) + EMSG2(_(e_intern2), "set_var()"); + else { + v->di_tv.vval.v_number = get_tv_number(tv); + if (STRCMP(varname, "searchforward") == 0) + set_search_direction(v->di_tv.vval.v_number ? '/' : '?'); + else if (STRCMP(varname, "hlsearch") == 0) { + no_hlsearch = !v->di_tv.vval.v_number; + redraw_all_later(SOME_VALID); + } + } + return; + } + + clear_tv(&v->di_tv); + } else { /* add a new variable */ + /* Can't add "v:" variable. */ + if (ht == &vimvarht) { + EMSG2(_(e_illvar), name); + return; + } + + /* Make sure the variable name is valid. */ + if (!valid_varname(varname)) + return; + + v = (dictitem_T *)alloc((unsigned)(sizeof(dictitem_T) + + STRLEN(varname))); + if (v == NULL) + return; + STRCPY(v->di_key, varname); + if (hash_add(ht, DI2HIKEY(v)) == FAIL) { + vim_free(v); + return; + } + v->di_flags = 0; + } + + if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) + copy_tv(tv, &v->di_tv); + else { + v->di_tv = *tv; + v->di_tv.v_lock = 0; + init_tv(tv); + } +} + +/* + * Return TRUE if di_flags "flags" indicates variable "name" is read-only. + * Also give an error message. + */ +static int var_check_ro(flags, name) +int flags; +char_u *name; +{ + if (flags & DI_FLAGS_RO) { + EMSG2(_(e_readonlyvar), name); + return TRUE; + } + if ((flags & DI_FLAGS_RO_SBX) && sandbox) { + EMSG2(_(e_readonlysbx), name); + return TRUE; + } + return FALSE; +} + +/* + * Return TRUE if di_flags "flags" indicates variable "name" is fixed. + * Also give an error message. + */ +static int var_check_fixed(flags, name) +int flags; +char_u *name; +{ + if (flags & DI_FLAGS_FIX) { + EMSG2(_("E795: Cannot delete variable %s"), name); + return TRUE; + } + return FALSE; +} + +/* + * Check if a funcref is assigned to a valid variable name. + * Return TRUE and give an error if not. + */ +static int var_check_func_name(name, new_var) +char_u *name; /* points to start of variable name */ +int new_var; /* TRUE when creating the variable */ +{ + if (!(vim_strchr((char_u *)"wbs", name[0]) != NULL && name[1] == ':') + && !ASCII_ISUPPER((name[0] != NUL && name[1] == ':') + ? name[2] : name[0])) { + EMSG2(_("E704: Funcref variable name must start with a capital: %s"), + name); + return TRUE; + } + /* Don't allow hiding a function. When "v" is not NULL we might be + * assigning another function to the same var, the type is checked + * below. */ + if (new_var && function_exists(name)) { + EMSG2(_("E705: Variable name conflicts with existing function: %s"), + name); + return TRUE; + } + return FALSE; +} + +/* + * Check if a variable name is valid. + * Return FALSE and give an error if not. + */ +static int valid_varname(varname) +char_u *varname; +{ + char_u *p; + + for (p = varname; *p != NUL; ++p) + if (!eval_isnamec1(*p) && (p == varname || !VIM_ISDIGIT(*p)) + && *p != AUTOLOAD_CHAR) { + EMSG2(_(e_illvar), varname); + return FALSE; + } + return TRUE; +} + +/* + * Return TRUE if typeval "tv" is set to be locked (immutable). + * Also give an error message, using "name". + */ +static int tv_check_lock(lock, name) +int lock; +char_u *name; +{ + if (lock & VAR_LOCKED) { + EMSG2(_("E741: Value is locked: %s"), + name == NULL ? (char_u *)_("Unknown") : name); + return TRUE; + } + if (lock & VAR_FIXED) { + EMSG2(_("E742: Cannot change value of %s"), + name == NULL ? (char_u *)_("Unknown") : name); + return TRUE; + } + return FALSE; +} + +/* + * Copy the values from typval_T "from" to typval_T "to". + * When needed allocates string or increases reference count. + * Does not make a copy of a list or dict but copies the reference! + * It is OK for "from" and "to" to point to the same item. This is used to + * make a copy later. + */ +void copy_tv(from, to) +typval_T *from; +typval_T *to; +{ + to->v_type = from->v_type; + to->v_lock = 0; + switch (from->v_type) { + case VAR_NUMBER: + to->vval.v_number = from->vval.v_number; + break; + case VAR_FLOAT: + to->vval.v_float = from->vval.v_float; + break; + case VAR_STRING: + case VAR_FUNC: + if (from->vval.v_string == NULL) + to->vval.v_string = NULL; + else { + to->vval.v_string = vim_strsave(from->vval.v_string); + if (from->v_type == VAR_FUNC) + func_ref(to->vval.v_string); + } + break; + case VAR_LIST: + if (from->vval.v_list == NULL) + to->vval.v_list = NULL; + else { + to->vval.v_list = from->vval.v_list; + ++to->vval.v_list->lv_refcount; + } + break; + case VAR_DICT: + if (from->vval.v_dict == NULL) + to->vval.v_dict = NULL; + else { + to->vval.v_dict = from->vval.v_dict; + ++to->vval.v_dict->dv_refcount; + } + break; + default: + EMSG2(_(e_intern2), "copy_tv()"); + break; + } +} + +/* + * Make a copy of an item. + * Lists and Dictionaries are also copied. A deep copy if "deep" is set. + * For deepcopy() "copyID" is zero for a full copy or the ID for when a + * reference to an already copied list/dict can be used. + * Returns FAIL or OK. + */ +static int item_copy(from, to, deep, copyID) +typval_T *from; +typval_T *to; +int deep; +int copyID; +{ + static int recurse = 0; + int ret = OK; + + if (recurse >= DICT_MAXNEST) { + EMSG(_("E698: variable nested too deep for making a copy")); + return FAIL; + } + ++recurse; + + switch (from->v_type) { + case VAR_NUMBER: + case VAR_FLOAT: + case VAR_STRING: + case VAR_FUNC: + copy_tv(from, to); + break; + case VAR_LIST: + to->v_type = VAR_LIST; + to->v_lock = 0; + if (from->vval.v_list == NULL) + to->vval.v_list = NULL; + else if (copyID != 0 && from->vval.v_list->lv_copyID == copyID) { + /* use the copy made earlier */ + to->vval.v_list = from->vval.v_list->lv_copylist; + ++to->vval.v_list->lv_refcount; + } else + to->vval.v_list = list_copy(from->vval.v_list, deep, copyID); + if (to->vval.v_list == NULL) + ret = FAIL; + break; + case VAR_DICT: + to->v_type = VAR_DICT; + to->v_lock = 0; + if (from->vval.v_dict == NULL) + to->vval.v_dict = NULL; + else if (copyID != 0 && from->vval.v_dict->dv_copyID == copyID) { + /* use the copy made earlier */ + to->vval.v_dict = from->vval.v_dict->dv_copydict; + ++to->vval.v_dict->dv_refcount; + } else + to->vval.v_dict = dict_copy(from->vval.v_dict, deep, copyID); + if (to->vval.v_dict == NULL) + ret = FAIL; + break; + default: + EMSG2(_(e_intern2), "item_copy()"); + ret = FAIL; + } + --recurse; + return ret; +} + +/* + * ":echo expr1 ..." print each argument separated with a space, add a + * newline at the end. + * ":echon expr1 ..." print each argument plain. + */ +void ex_echo(eap) +exarg_T *eap; +{ + char_u *arg = eap->arg; + typval_T rettv; + char_u *tofree; + char_u *p; + int needclr = TRUE; + int atstart = TRUE; + char_u numbuf[NUMBUFLEN]; + + if (eap->skip) + ++emsg_skip; + while (*arg != NUL && *arg != '|' && *arg != '\n' && !got_int) { + /* If eval1() causes an error message the text from the command may + * still need to be cleared. E.g., "echo 22,44". */ + need_clr_eos = needclr; + + p = arg; + if (eval1(&arg, &rettv, !eap->skip) == FAIL) { + /* + * Report the invalid expression unless the expression evaluation + * has been cancelled due to an aborting error, an interrupt, or an + * exception. + */ + if (!aborting()) + EMSG2(_(e_invexpr2), p); + need_clr_eos = FALSE; + break; + } + need_clr_eos = FALSE; + + if (!eap->skip) { + if (atstart) { + atstart = FALSE; + /* Call msg_start() after eval1(), evaluating the expression + * may cause a message to appear. */ + if (eap->cmdidx == CMD_echo) { + /* Mark the saved text as finishing the line, so that what + * follows is displayed on a new line when scrolling back + * at the more prompt. */ + msg_sb_eol(); + msg_start(); + } + } else if (eap->cmdidx == CMD_echo) + msg_puts_attr((char_u *)" ", echo_attr); + current_copyID += COPYID_INC; + p = echo_string(&rettv, &tofree, numbuf, current_copyID); + if (p != NULL) + for (; *p != NUL && !got_int; ++p) { + if (*p == '\n' || *p == '\r' || *p == TAB) { + if (*p != TAB && needclr) { + /* remove any text still there from the command */ + msg_clr_eos(); + needclr = FALSE; + } + msg_putchar_attr(*p, echo_attr); + } else { + if (has_mbyte) { + int i = (*mb_ptr2len)(p); + + (void)msg_outtrans_len_attr(p, i, echo_attr); + p += i - 1; + } else + (void)msg_outtrans_len_attr(p, 1, echo_attr); + } + } + vim_free(tofree); + } + clear_tv(&rettv); + arg = skipwhite(arg); + } + eap->nextcmd = check_nextcmd(arg); + + if (eap->skip) + --emsg_skip; + else { + /* remove text that may still be there from the command */ + if (needclr) + msg_clr_eos(); + if (eap->cmdidx == CMD_echo) + msg_end(); + } +} + +/* + * ":echohl {name}". + */ +void ex_echohl(eap) +exarg_T *eap; +{ + int id; + + id = syn_name2id(eap->arg); + if (id == 0) + echo_attr = 0; + else + echo_attr = syn_id2attr(id); +} + +/* + * ":execute expr1 ..." execute the result of an expression. + * ":echomsg expr1 ..." Print a message + * ":echoerr expr1 ..." Print an error + * Each gets spaces around each argument and a newline at the end for + * echo commands + */ +void ex_execute(eap) +exarg_T *eap; +{ + char_u *arg = eap->arg; + typval_T rettv; + int ret = OK; + char_u *p; + garray_T ga; + int len; + int save_did_emsg; + + ga_init2(&ga, 1, 80); + + if (eap->skip) + ++emsg_skip; + while (*arg != NUL && *arg != '|' && *arg != '\n') { + p = arg; + if (eval1(&arg, &rettv, !eap->skip) == FAIL) { + /* + * Report the invalid expression unless the expression evaluation + * has been cancelled due to an aborting error, an interrupt, or an + * exception. + */ + if (!aborting()) + EMSG2(_(e_invexpr2), p); + ret = FAIL; + break; + } + + if (!eap->skip) { + p = get_tv_string(&rettv); + len = (int)STRLEN(p); + if (ga_grow(&ga, len + 2) == FAIL) { + clear_tv(&rettv); + ret = FAIL; + break; + } + if (ga.ga_len) + ((char_u *)(ga.ga_data))[ga.ga_len++] = ' '; + STRCPY((char_u *)(ga.ga_data) + ga.ga_len, p); + ga.ga_len += len; + } + + clear_tv(&rettv); + arg = skipwhite(arg); + } + + if (ret != FAIL && ga.ga_data != NULL) { + if (eap->cmdidx == CMD_echomsg) { + MSG_ATTR(ga.ga_data, echo_attr); + out_flush(); + } else if (eap->cmdidx == CMD_echoerr) { + /* We don't want to abort following commands, restore did_emsg. */ + save_did_emsg = did_emsg; + EMSG((char_u *)ga.ga_data); + if (!force_abort) + did_emsg = save_did_emsg; + } else if (eap->cmdidx == CMD_execute) + do_cmdline((char_u *)ga.ga_data, + eap->getline, eap->cookie, DOCMD_NOWAIT|DOCMD_VERBOSE); + } + + ga_clear(&ga); + + if (eap->skip) + --emsg_skip; + + eap->nextcmd = check_nextcmd(arg); +} + +/* + * Skip over the name of an option: "&option", "&g:option" or "&l:option". + * "arg" points to the "&" or '+' when called, to "option" when returning. + * Returns NULL when no option name found. Otherwise pointer to the char + * after the option name. + */ +static char_u * find_option_end(arg, opt_flags) +char_u **arg; +int *opt_flags; +{ + char_u *p = *arg; + + ++p; + if (*p == 'g' && p[1] == ':') { + *opt_flags = OPT_GLOBAL; + p += 2; + } else if (*p == 'l' && p[1] == ':') { + *opt_flags = OPT_LOCAL; + p += 2; + } else + *opt_flags = 0; + + if (!ASCII_ISALPHA(*p)) + return NULL; + *arg = p; + + if (p[0] == 't' && p[1] == '_' && p[2] != NUL && p[3] != NUL) + p += 4; /* termcap option */ + else + while (ASCII_ISALPHA(*p)) + ++p; + return p; +} + +/* + * ":function" + */ +void ex_function(eap) +exarg_T *eap; +{ + char_u *theline; + int i; + int j; + int c; + int saved_did_emsg; + int saved_wait_return = need_wait_return; + char_u *name = NULL; + char_u *p; + char_u *arg; + char_u *line_arg = NULL; + garray_T newargs; + garray_T newlines; + int varargs = FALSE; + int mustend = FALSE; + int flags = 0; + ufunc_T *fp; + int indent; + int nesting; + char_u *skip_until = NULL; + dictitem_T *v; + funcdict_T fudi; + static int func_nr = 0; /* number for nameless function */ + int paren; + hashtab_T *ht; + int todo; + hashitem_T *hi; + int sourcing_lnum_off; + + /* + * ":function" without argument: list functions. + */ + if (ends_excmd(*eap->arg)) { + if (!eap->skip) { + todo = (int)func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + fp = HI2UF(hi); + if (!isdigit(*fp->uf_name)) + list_func_head(fp, FALSE); + } + } + } + eap->nextcmd = check_nextcmd(eap->arg); + return; + } + + /* + * ":function /pat": list functions matching pattern. + */ + if (*eap->arg == '/') { + p = skip_regexp(eap->arg + 1, '/', TRUE, NULL); + if (!eap->skip) { + regmatch_T regmatch; + + c = *p; + *p = NUL; + regmatch.regprog = vim_regcomp(eap->arg + 1, RE_MAGIC); + *p = c; + if (regmatch.regprog != NULL) { + regmatch.rm_ic = p_ic; + + todo = (int)func_hashtab.ht_used; + for (hi = func_hashtab.ht_array; todo > 0 && !got_int; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + fp = HI2UF(hi); + if (!isdigit(*fp->uf_name) + && vim_regexec(®match, fp->uf_name, 0)) + list_func_head(fp, FALSE); + } + } + vim_regfree(regmatch.regprog); + } + } + if (*p == '/') + ++p; + eap->nextcmd = check_nextcmd(p); + return; + } + + /* + * Get the function name. There are these situations: + * func normal function name + * "name" == func, "fudi.fd_dict" == NULL + * dict.func new dictionary entry + * "name" == NULL, "fudi.fd_dict" set, + * "fudi.fd_di" == NULL, "fudi.fd_newkey" == func + * dict.func existing dict entry with a Funcref + * "name" == func, "fudi.fd_dict" set, + * "fudi.fd_di" set, "fudi.fd_newkey" == NULL + * dict.func existing dict entry that's not a Funcref + * "name" == NULL, "fudi.fd_dict" set, + * "fudi.fd_di" set, "fudi.fd_newkey" == NULL + */ + p = eap->arg; + name = trans_function_name(&p, eap->skip, 0, &fudi); + paren = (vim_strchr(p, '(') != NULL); + if (name == NULL && (fudi.fd_dict == NULL || !paren) && !eap->skip) { + /* + * Return on an invalid expression in braces, unless the expression + * evaluation has been cancelled due to an aborting error, an + * interrupt, or an exception. + */ + if (!aborting()) { + if (!eap->skip && fudi.fd_newkey != NULL) + EMSG2(_(e_dictkey), fudi.fd_newkey); + vim_free(fudi.fd_newkey); + return; + } else + eap->skip = TRUE; + } + + /* An error in a function call during evaluation of an expression in magic + * braces should not cause the function not to be defined. */ + saved_did_emsg = did_emsg; + did_emsg = FALSE; + + /* + * ":function func" with only function name: list function. + */ + if (!paren) { + if (!ends_excmd(*skipwhite(p))) { + EMSG(_(e_trailing)); + goto ret_free; + } + eap->nextcmd = check_nextcmd(p); + if (eap->nextcmd != NULL) + *p = NUL; + if (!eap->skip && !got_int) { + fp = find_func(name); + if (fp != NULL) { + list_func_head(fp, TRUE); + for (j = 0; j < fp->uf_lines.ga_len && !got_int; ++j) { + if (FUNCLINE(fp, j) == NULL) + continue; + msg_putchar('\n'); + msg_outnum((long)(j + 1)); + if (j < 9) + msg_putchar(' '); + if (j < 99) + msg_putchar(' '); + msg_prt_line(FUNCLINE(fp, j), FALSE); + out_flush(); /* show a line at a time */ + ui_breakcheck(); + } + if (!got_int) { + msg_putchar('\n'); + msg_puts((char_u *)" endfunction"); + } + } else + emsg_funcname(N_("E123: Undefined function: %s"), name); + } + goto ret_free; + } + + /* + * ":function name(arg1, arg2)" Define function. + */ + p = skipwhite(p); + if (*p != '(') { + if (!eap->skip) { + EMSG2(_("E124: Missing '(': %s"), eap->arg); + goto ret_free; + } + /* attempt to continue by skipping some text */ + if (vim_strchr(p, '(') != NULL) + p = vim_strchr(p, '('); + } + p = skipwhite(p + 1); + + ga_init2(&newargs, (int)sizeof(char_u *), 3); + ga_init2(&newlines, (int)sizeof(char_u *), 3); + + if (!eap->skip) { + /* Check the name of the function. Unless it's a dictionary function + * (that we are overwriting). */ + if (name != NULL) + arg = name; + else + arg = fudi.fd_newkey; + if (arg != NULL && (fudi.fd_di == NULL + || fudi.fd_di->di_tv.v_type != VAR_FUNC)) { + if (*arg == K_SPECIAL) + j = 3; + else + j = 0; + while (arg[j] != NUL && (j == 0 ? eval_isnamec1(arg[j]) + : eval_isnamec(arg[j]))) + ++j; + if (arg[j] != NUL) + emsg_funcname((char *)e_invarg2, arg); + } + /* Disallow using the g: dict. */ + if (fudi.fd_dict != NULL && fudi.fd_dict->dv_scope == VAR_DEF_SCOPE) + EMSG(_("E862: Cannot use g: here")); + } + + /* + * Isolate the arguments: "arg1, arg2, ...)" + */ + while (*p != ')') { + if (p[0] == '.' && p[1] == '.' && p[2] == '.') { + varargs = TRUE; + p += 3; + mustend = TRUE; + } else { + arg = p; + while (ASCII_ISALNUM(*p) || *p == '_') + ++p; + if (arg == p || isdigit(*arg) + || (p - arg == 9 && STRNCMP(arg, "firstline", 9) == 0) + || (p - arg == 8 && STRNCMP(arg, "lastline", 8) == 0)) { + if (!eap->skip) + EMSG2(_("E125: Illegal argument: %s"), arg); + break; + } + if (ga_grow(&newargs, 1) == FAIL) + goto erret; + c = *p; + *p = NUL; + arg = vim_strsave(arg); + if (arg == NULL) + goto erret; + + /* Check for duplicate argument name. */ + for (i = 0; i < newargs.ga_len; ++i) + if (STRCMP(((char_u **)(newargs.ga_data))[i], arg) == 0) { + EMSG2(_("E853: Duplicate argument name: %s"), arg); + goto erret; + } + + ((char_u **)(newargs.ga_data))[newargs.ga_len] = arg; + *p = c; + newargs.ga_len++; + if (*p == ',') + ++p; + else + mustend = TRUE; + } + p = skipwhite(p); + if (mustend && *p != ')') { + if (!eap->skip) + EMSG2(_(e_invarg2), eap->arg); + break; + } + } + ++p; /* skip the ')' */ + + /* find extra arguments "range", "dict" and "abort" */ + for (;; ) { + p = skipwhite(p); + if (STRNCMP(p, "range", 5) == 0) { + flags |= FC_RANGE; + p += 5; + } else if (STRNCMP(p, "dict", 4) == 0) { + flags |= FC_DICT; + p += 4; + } else if (STRNCMP(p, "abort", 5) == 0) { + flags |= FC_ABORT; + p += 5; + } else + break; + } + + /* When there is a line break use what follows for the function body. + * Makes 'exe "func Test()\n...\nendfunc"' work. */ + if (*p == '\n') + line_arg = p + 1; + else if (*p != NUL && *p != '"' && !eap->skip && !did_emsg) + EMSG(_(e_trailing)); + + /* + * Read the body of the function, until ":endfunction" is found. + */ + if (KeyTyped) { + /* Check if the function already exists, don't let the user type the + * whole function before telling him it doesn't work! For a script we + * need to skip the body to be able to find what follows. */ + if (!eap->skip && !eap->forceit) { + if (fudi.fd_dict != NULL && fudi.fd_newkey == NULL) + EMSG(_(e_funcdict)); + else if (name != NULL && find_func(name) != NULL) + emsg_funcname(e_funcexts, name); + } + + if (!eap->skip && did_emsg) + goto erret; + + msg_putchar('\n'); /* don't overwrite the function name */ + cmdline_row = msg_row; + } + + indent = 2; + nesting = 0; + for (;; ) { + if (KeyTyped) { + msg_scroll = TRUE; + saved_wait_return = FALSE; + } + need_wait_return = FALSE; + sourcing_lnum_off = sourcing_lnum; + + if (line_arg != NULL) { + /* Use eap->arg, split up in parts by line breaks. */ + theline = line_arg; + p = vim_strchr(theline, '\n'); + if (p == NULL) + line_arg += STRLEN(line_arg); + else { + *p = NUL; + line_arg = p + 1; + } + } else if (eap->getline == NULL) + theline = getcmdline(':', 0L, indent); + else + theline = eap->getline(':', eap->cookie, indent); + if (KeyTyped) + lines_left = Rows - 1; + if (theline == NULL) { + EMSG(_("E126: Missing :endfunction")); + goto erret; + } + + /* Detect line continuation: sourcing_lnum increased more than one. */ + if (sourcing_lnum > sourcing_lnum_off + 1) + sourcing_lnum_off = sourcing_lnum - sourcing_lnum_off - 1; + else + sourcing_lnum_off = 0; + + if (skip_until != NULL) { + /* between ":append" and "." and between ":python < 2 && STRNCMP(p, "end", 3) == 0) + indent -= 2; + else if (STRNCMP(p, "if", 2) == 0 + || STRNCMP(p, "wh", 2) == 0 + || STRNCMP(p, "for", 3) == 0 + || STRNCMP(p, "try", 3) == 0) + indent += 2; + + /* Check for defining a function inside this function. */ + if (checkforcmd(&p, "function", 2)) { + if (*p == '!') + p = skipwhite(p + 1); + p += eval_fname_script(p); + if (ASCII_ISALPHA(*p)) { + vim_free(trans_function_name(&p, TRUE, 0, NULL)); + if (*skipwhite(p) == '(') { + ++nesting; + indent += 2; + } + } + } + + /* Check for ":append" or ":insert". */ + p = skip_range(p, NULL); + if ((p[0] == 'a' && (!ASCII_ISALPHA(p[1]) || p[1] == 'p')) + || (p[0] == 'i' + && (!ASCII_ISALPHA(p[1]) || (p[1] == 'n' + && (!ASCII_ISALPHA(p[2]) || + (p[2] == 's')))))) + skip_until = vim_strsave((char_u *)"."); + + /* Check for ":python < 0) + ((char_u **)(newlines.ga_data))[newlines.ga_len++] = NULL; + + /* Check for end of eap->arg. */ + if (line_arg != NULL && *line_arg == NUL) + line_arg = NULL; + } + + /* Don't define the function when skipping commands or when an error was + * detected. */ + if (eap->skip || did_emsg) + goto erret; + + /* + * If there are no errors, add the function + */ + if (fudi.fd_dict == NULL) { + v = find_var(name, &ht, FALSE); + if (v != NULL && v->di_tv.v_type == VAR_FUNC) { + emsg_funcname(N_("E707: Function name conflicts with variable: %s"), + name); + goto erret; + } + + fp = find_func(name); + if (fp != NULL) { + if (!eap->forceit) { + emsg_funcname(e_funcexts, name); + goto erret; + } + if (fp->uf_calls > 0) { + emsg_funcname(N_("E127: Cannot redefine function %s: It is in use"), + name); + goto erret; + } + /* redefine existing function */ + ga_clear_strings(&(fp->uf_args)); + ga_clear_strings(&(fp->uf_lines)); + vim_free(name); + name = NULL; + } + } else { + char numbuf[20]; + + fp = NULL; + if (fudi.fd_newkey == NULL && !eap->forceit) { + EMSG(_(e_funcdict)); + goto erret; + } + if (fudi.fd_di == NULL) { + /* Can't add a function to a locked dictionary */ + if (tv_check_lock(fudi.fd_dict->dv_lock, eap->arg)) + goto erret; + } + /* Can't change an existing function if it is locked */ + else if (tv_check_lock(fudi.fd_di->di_tv.v_lock, eap->arg)) + goto erret; + + /* Give the function a sequential number. Can only be used with a + * Funcref! */ + vim_free(name); + sprintf(numbuf, "%d", ++func_nr); + name = vim_strsave((char_u *)numbuf); + if (name == NULL) + goto erret; + } + + if (fp == NULL) { + if (fudi.fd_dict == NULL && vim_strchr(name, AUTOLOAD_CHAR) != NULL) { + int slen, plen; + char_u *scriptname; + + /* Check that the autoload name matches the script name. */ + j = FAIL; + if (sourcing_name != NULL) { + scriptname = autoload_name(name); + if (scriptname != NULL) { + p = vim_strchr(scriptname, '/'); + plen = (int)STRLEN(p); + slen = (int)STRLEN(sourcing_name); + if (slen > plen && fnamecmp(p, + sourcing_name + slen - plen) == 0) + j = OK; + vim_free(scriptname); + } + } + if (j == FAIL) { + EMSG2(_( + "E746: Function name does not match script file name: %s"), + name); + goto erret; + } + } + + fp = (ufunc_T *)alloc((unsigned)(sizeof(ufunc_T) + STRLEN(name))); + if (fp == NULL) + goto erret; + + if (fudi.fd_dict != NULL) { + if (fudi.fd_di == NULL) { + /* add new dict entry */ + fudi.fd_di = dictitem_alloc(fudi.fd_newkey); + if (fudi.fd_di == NULL) { + vim_free(fp); + goto erret; + } + if (dict_add(fudi.fd_dict, fudi.fd_di) == FAIL) { + vim_free(fudi.fd_di); + vim_free(fp); + goto erret; + } + } else + /* overwrite existing dict entry */ + clear_tv(&fudi.fd_di->di_tv); + fudi.fd_di->di_tv.v_type = VAR_FUNC; + fudi.fd_di->di_tv.v_lock = 0; + fudi.fd_di->di_tv.vval.v_string = vim_strsave(name); + fp->uf_refcount = 1; + + /* behave like "dict" was used */ + flags |= FC_DICT; + } + + /* insert the new function in the function list */ + STRCPY(fp->uf_name, name); + hash_add(&func_hashtab, UF2HIKEY(fp)); + } + fp->uf_args = newargs; + fp->uf_lines = newlines; + fp->uf_tml_count = NULL; + fp->uf_tml_total = NULL; + fp->uf_tml_self = NULL; + fp->uf_profiling = FALSE; + if (prof_def_func()) + func_do_profile(fp); + fp->uf_varargs = varargs; + fp->uf_flags = flags; + fp->uf_calls = 0; + fp->uf_script_ID = current_SID; + goto ret_free; + +erret: + ga_clear_strings(&newargs); + ga_clear_strings(&newlines); +ret_free: + vim_free(skip_until); + vim_free(fudi.fd_newkey); + vim_free(name); + did_emsg |= saved_did_emsg; + need_wait_return |= saved_wait_return; +} + +/* + * Get a function name, translating "" and "". + * Also handles a Funcref in a List or Dictionary. + * Returns the function name in allocated memory, or NULL for failure. + * flags: + * TFN_INT: internal function name OK + * TFN_QUIET: be quiet + * TFN_NO_AUTOLOAD: do not use script autoloading + * Advances "pp" to just after the function name (if no error). + */ +static char_u * trans_function_name(pp, skip, flags, fdp) +char_u **pp; +int skip; /* only find the end, don't evaluate */ +int flags; +funcdict_T *fdp; /* return: info about dictionary used */ +{ + char_u *name = NULL; + char_u *start; + char_u *end; + int lead; + char_u sid_buf[20]; + int len; + lval_T lv; + + if (fdp != NULL) + vim_memset(fdp, 0, sizeof(funcdict_T)); + start = *pp; + + /* Check for hard coded : already translated function ID (from a user + * command). */ + if ((*pp)[0] == K_SPECIAL && (*pp)[1] == KS_EXTRA + && (*pp)[2] == (int)KE_SNR) { + *pp += 3; + len = get_id_len(pp) + 3; + return vim_strnsave(start, len); + } + + /* A name starting with "" or "" is local to a script. But + * don't skip over "s:", get_lval() needs it for "s:dict.func". */ + lead = eval_fname_script(start); + if (lead > 2) + start += lead; + + /* Note that TFN_ flags use the same values as GLV_ flags. */ + end = get_lval(start, NULL, &lv, FALSE, skip, flags, + lead > 2 ? 0 : FNE_CHECK_START); + if (end == start) { + if (!skip) + EMSG(_("E129: Function name required")); + goto theend; + } + if (end == NULL || (lv.ll_tv != NULL && (lead > 2 || lv.ll_range))) { + /* + * Report an invalid expression in braces, unless the expression + * evaluation has been cancelled due to an aborting error, an + * interrupt, or an exception. + */ + if (!aborting()) { + if (end != NULL) + EMSG2(_(e_invarg2), start); + } else + *pp = find_name_end(start, NULL, NULL, FNE_INCL_BR); + goto theend; + } + + if (lv.ll_tv != NULL) { + if (fdp != NULL) { + fdp->fd_dict = lv.ll_dict; + fdp->fd_newkey = lv.ll_newkey; + lv.ll_newkey = NULL; + fdp->fd_di = lv.ll_di; + } + if (lv.ll_tv->v_type == VAR_FUNC && lv.ll_tv->vval.v_string != NULL) { + name = vim_strsave(lv.ll_tv->vval.v_string); + *pp = end; + } else { + if (!skip && !(flags & TFN_QUIET) && (fdp == NULL + || lv.ll_dict == NULL || + fdp->fd_newkey == NULL)) + EMSG(_(e_funcref)); + else + *pp = end; + name = NULL; + } + goto theend; + } + + if (lv.ll_name == NULL) { + /* Error found, but continue after the function name. */ + *pp = end; + goto theend; + } + + /* Check if the name is a Funcref. If so, use the value. */ + if (lv.ll_exp_name != NULL) { + len = (int)STRLEN(lv.ll_exp_name); + name = deref_func_name(lv.ll_exp_name, &len, flags & TFN_NO_AUTOLOAD); + if (name == lv.ll_exp_name) + name = NULL; + } else { + len = (int)(end - *pp); + name = deref_func_name(*pp, &len, flags & TFN_NO_AUTOLOAD); + if (name == *pp) + name = NULL; + } + if (name != NULL) { + name = vim_strsave(name); + *pp = end; + goto theend; + } + + if (lv.ll_exp_name != NULL) { + len = (int)STRLEN(lv.ll_exp_name); + if (lead <= 2 && lv.ll_name == lv.ll_exp_name + && STRNCMP(lv.ll_name, "s:", 2) == 0) { + /* When there was "s:" already or the name expanded to get a + * leading "s:" then remove it. */ + lv.ll_name += 2; + len -= 2; + lead = 2; + } + } else { + if (lead == 2) /* skip over "s:" */ + lv.ll_name += 2; + len = (int)(end - lv.ll_name); + } + + /* + * Copy the function name to allocated memory. + * Accept name() inside a script, translate into 123_name(). + * Accept 123_name() outside a script. + */ + if (skip) + lead = 0; /* do nothing */ + else if (lead > 0) { + lead = 3; + if ((lv.ll_exp_name != NULL && eval_fname_sid(lv.ll_exp_name)) + || eval_fname_sid(*pp)) { + /* It's "s:" or "" */ + if (current_SID <= 0) { + EMSG(_(e_usingsid)); + goto theend; + } + sprintf((char *)sid_buf, "%ld_", (long)current_SID); + lead += (int)STRLEN(sid_buf); + } + } else if (!(flags & TFN_INT) && builtin_function(lv.ll_name)) { + EMSG2(_( + "E128: Function name must start with a capital or contain a colon: %s"), + lv.ll_name); + goto theend; + } + name = alloc((unsigned)(len + lead + 1)); + if (name != NULL) { + if (lead > 0) { + name[0] = K_SPECIAL; + name[1] = KS_EXTRA; + name[2] = (int)KE_SNR; + if (lead > 3) /* If it's "" */ + STRCPY(name + 3, sid_buf); + } + mch_memmove(name + lead, lv.ll_name, (size_t)len); + name[len + lead] = NUL; + } + *pp = end; + +theend: + clear_lval(&lv); + return name; +} + +/* + * Return 5 if "p" starts with "" or "" (ignoring case). + * Return 2 if "p" starts with "s:". + * Return 0 otherwise. + */ +static int eval_fname_script(p) +char_u *p; +{ + if (p[0] == '<' && (STRNICMP(p + 1, "SID>", 4) == 0 + || STRNICMP(p + 1, "SNR>", 4) == 0)) + return 5; + if (p[0] == 's' && p[1] == ':') + return 2; + return 0; +} + +/* + * Return TRUE if "p" starts with "" or "s:". + * Only works if eval_fname_script() returned non-zero for "p"! + */ +static int eval_fname_sid(p) +char_u *p; +{ + return *p == 's' || TOUPPER_ASC(p[2]) == 'I'; +} + +/* + * List the head of the function: "name(arg1, arg2)". + */ +static void list_func_head(fp, indent) +ufunc_T *fp; +int indent; +{ + int j; + + msg_start(); + if (indent) + MSG_PUTS(" "); + MSG_PUTS("function "); + if (fp->uf_name[0] == K_SPECIAL) { + MSG_PUTS_ATTR("", hl_attr(HLF_8)); + msg_puts(fp->uf_name + 3); + } else + msg_puts(fp->uf_name); + msg_putchar('('); + for (j = 0; j < fp->uf_args.ga_len; ++j) { + if (j) + MSG_PUTS(", "); + msg_puts(FUNCARG(fp, j)); + } + if (fp->uf_varargs) { + if (j) + MSG_PUTS(", "); + MSG_PUTS("..."); + } + msg_putchar(')'); + if (fp->uf_flags & FC_ABORT) + MSG_PUTS(" abort"); + if (fp->uf_flags & FC_RANGE) + MSG_PUTS(" range"); + if (fp->uf_flags & FC_DICT) + MSG_PUTS(" dict"); + msg_clr_eos(); + if (p_verbose > 0) + last_set_msg(fp->uf_script_ID); +} + +/* + * Find a function by name, return pointer to it in ufuncs. + * Return NULL for unknown function. + */ +static ufunc_T * find_func(name) +char_u *name; +{ + hashitem_T *hi; + + hi = hash_find(&func_hashtab, name); + if (!HASHITEM_EMPTY(hi)) + return HI2UF(hi); + return NULL; +} + +#if defined(EXITFREE) || defined(PROTO) +void free_all_functions() { + hashitem_T *hi; + + /* Need to start all over every time, because func_free() may change the + * hash table. */ + while (func_hashtab.ht_used > 0) + for (hi = func_hashtab.ht_array;; ++hi) + if (!HASHITEM_EMPTY(hi)) { + func_free(HI2UF(hi)); + break; + } +} + +#endif + +int translated_function_exists(name) +char_u *name; +{ + if (builtin_function(name)) + return find_internal_func(name) >= 0; + return find_func(name) != NULL; +} + +/* + * Return TRUE if a function "name" exists. + */ +static int function_exists(name) +char_u *name; +{ + char_u *nm = name; + char_u *p; + int n = FALSE; + + p = trans_function_name(&nm, FALSE, TFN_INT|TFN_QUIET|TFN_NO_AUTOLOAD, + NULL); + nm = skipwhite(nm); + + /* Only accept "funcname", "funcname ", "funcname (..." and + * "funcname(...", not "funcname!...". */ + if (p != NULL && (*nm == NUL || *nm == '(')) + n = translated_function_exists(p); + vim_free(p); + return n; +} + +char_u * get_expanded_name(name, check) +char_u *name; +int check; +{ + char_u *nm = name; + char_u *p; + + p = trans_function_name(&nm, FALSE, TFN_INT|TFN_QUIET, NULL); + + if (p != NULL && *nm == NUL) + if (!check || translated_function_exists(p)) + return p; + + vim_free(p); + return NULL; +} + +/* + * Return TRUE if "name" looks like a builtin function name: starts with a + * lower case letter and doesn't contain a ':' or AUTOLOAD_CHAR. + */ +static int builtin_function(name) +char_u *name; +{ + return ASCII_ISLOWER(name[0]) && vim_strchr(name, ':') == NULL + && vim_strchr(name, AUTOLOAD_CHAR) == NULL; +} + +/* + * Start profiling function "fp". + */ +static void func_do_profile(fp) +ufunc_T *fp; +{ + int len = fp->uf_lines.ga_len; + + if (len == 0) + len = 1; /* avoid getting error for allocating zero bytes */ + fp->uf_tm_count = 0; + profile_zero(&fp->uf_tm_self); + profile_zero(&fp->uf_tm_total); + if (fp->uf_tml_count == NULL) + fp->uf_tml_count = (int *)alloc_clear((unsigned) (sizeof(int) * len)); + if (fp->uf_tml_total == NULL) + fp->uf_tml_total = (proftime_T *)alloc_clear((unsigned) + (sizeof(proftime_T) * len)); + if (fp->uf_tml_self == NULL) + fp->uf_tml_self = (proftime_T *)alloc_clear((unsigned) + (sizeof(proftime_T) * len)); + fp->uf_tml_idx = -1; + if (fp->uf_tml_count == NULL || fp->uf_tml_total == NULL + || fp->uf_tml_self == NULL) + return; /* out of memory */ + + fp->uf_profiling = TRUE; +} + +/* + * Dump the profiling results for all functions in file "fd". + */ +void func_dump_profile(fd) +FILE *fd; +{ + hashitem_T *hi; + int todo; + ufunc_T *fp; + int i; + ufunc_T **sorttab; + int st_len = 0; + + todo = (int)func_hashtab.ht_used; + if (todo == 0) + return; /* nothing to dump */ + + sorttab = (ufunc_T **)alloc((unsigned)(sizeof(ufunc_T) * todo)); + + for (hi = func_hashtab.ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + fp = HI2UF(hi); + if (fp->uf_profiling) { + if (sorttab != NULL) + sorttab[st_len++] = fp; + + if (fp->uf_name[0] == K_SPECIAL) + fprintf(fd, "FUNCTION %s()\n", fp->uf_name + 3); + else + fprintf(fd, "FUNCTION %s()\n", fp->uf_name); + if (fp->uf_tm_count == 1) + fprintf(fd, "Called 1 time\n"); + else + fprintf(fd, "Called %d times\n", fp->uf_tm_count); + fprintf(fd, "Total time: %s\n", profile_msg(&fp->uf_tm_total)); + fprintf(fd, " Self time: %s\n", profile_msg(&fp->uf_tm_self)); + fprintf(fd, "\n"); + fprintf(fd, "count total (s) self (s)\n"); + + for (i = 0; i < fp->uf_lines.ga_len; ++i) { + if (FUNCLINE(fp, i) == NULL) + continue; + prof_func_line(fd, fp->uf_tml_count[i], + &fp->uf_tml_total[i], &fp->uf_tml_self[i], TRUE); + fprintf(fd, "%s\n", FUNCLINE(fp, i)); + } + fprintf(fd, "\n"); + } + } + } + + if (sorttab != NULL && st_len > 0) { + qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *), + prof_total_cmp); + prof_sort_list(fd, sorttab, st_len, "TOTAL", FALSE); + qsort((void *)sorttab, (size_t)st_len, sizeof(ufunc_T *), + prof_self_cmp); + prof_sort_list(fd, sorttab, st_len, "SELF", TRUE); + } + + vim_free(sorttab); +} + +static void prof_sort_list(fd, sorttab, st_len, title, prefer_self) +FILE *fd; +ufunc_T **sorttab; +int st_len; +char *title; +int prefer_self; /* when equal print only self time */ +{ + int i; + ufunc_T *fp; + + fprintf(fd, "FUNCTIONS SORTED ON %s TIME\n", title); + fprintf(fd, "count total (s) self (s) function\n"); + for (i = 0; i < 20 && i < st_len; ++i) { + fp = sorttab[i]; + prof_func_line(fd, fp->uf_tm_count, &fp->uf_tm_total, &fp->uf_tm_self, + prefer_self); + if (fp->uf_name[0] == K_SPECIAL) + fprintf(fd, " %s()\n", fp->uf_name + 3); + else + fprintf(fd, " %s()\n", fp->uf_name); + } + fprintf(fd, "\n"); +} + +/* + * Print the count and times for one function or function line. + */ +static void prof_func_line(fd, count, total, self, prefer_self) +FILE *fd; +int count; +proftime_T *total; +proftime_T *self; +int prefer_self; /* when equal print only self time */ +{ + if (count > 0) { + fprintf(fd, "%5d ", count); + if (prefer_self && profile_equal(total, self)) + fprintf(fd, " "); + else + fprintf(fd, "%s ", profile_msg(total)); + if (!prefer_self && profile_equal(total, self)) + fprintf(fd, " "); + else + fprintf(fd, "%s ", profile_msg(self)); + } else + fprintf(fd, " "); +} + +/* + * Compare function for total time sorting. + */ +static int prof_total_cmp(s1, s2) +const void *s1; +const void *s2; +{ + ufunc_T *p1, *p2; + + p1 = *(ufunc_T **)s1; + p2 = *(ufunc_T **)s2; + return profile_cmp(&p1->uf_tm_total, &p2->uf_tm_total); +} + +/* + * Compare function for self time sorting. + */ +static int prof_self_cmp(s1, s2) +const void *s1; +const void *s2; +{ + ufunc_T *p1, *p2; + + p1 = *(ufunc_T **)s1; + p2 = *(ufunc_T **)s2; + return profile_cmp(&p1->uf_tm_self, &p2->uf_tm_self); +} + + +/* + * If "name" has a package name try autoloading the script for it. + * Return TRUE if a package was loaded. + */ +static int script_autoload(name, reload) +char_u *name; +int reload; /* load script again when already loaded */ +{ + char_u *p; + char_u *scriptname, *tofree; + int ret = FALSE; + int i; + + /* If there is no '#' after name[0] there is no package name. */ + p = vim_strchr(name, AUTOLOAD_CHAR); + if (p == NULL || p == name) + return FALSE; + + tofree = scriptname = autoload_name(name); + + /* Find the name in the list of previously loaded package names. Skip + * "autoload/", it's always the same. */ + for (i = 0; i < ga_loaded.ga_len; ++i) + if (STRCMP(((char_u **)ga_loaded.ga_data)[i] + 9, scriptname + 9) == 0) + break; + if (!reload && i < ga_loaded.ga_len) + ret = FALSE; /* was loaded already */ + else { + /* Remember the name if it wasn't loaded already. */ + if (i == ga_loaded.ga_len && ga_grow(&ga_loaded, 1) == OK) { + ((char_u **)ga_loaded.ga_data)[ga_loaded.ga_len++] = scriptname; + tofree = NULL; + } + + /* Try loading the package from $VIMRUNTIME/autoload/.vim */ + if (source_runtime(scriptname, FALSE) == OK) + ret = TRUE; + } + + vim_free(tofree); + return ret; +} + +/* + * Return the autoload script name for a function or variable name. + * Returns NULL when out of memory. + */ +static char_u * autoload_name(name) +char_u *name; +{ + char_u *p; + char_u *scriptname; + + /* Get the script file name: replace '#' with '/', append ".vim". */ + scriptname = alloc((unsigned)(STRLEN(name) + 14)); + if (scriptname == NULL) + return FALSE; + STRCPY(scriptname, "autoload/"); + STRCAT(scriptname, name); + *vim_strrchr(scriptname, AUTOLOAD_CHAR) = NUL; + STRCAT(scriptname, ".vim"); + while ((p = vim_strchr(scriptname, AUTOLOAD_CHAR)) != NULL) + *p = '/'; + return scriptname; +} + + +/* + * Function given to ExpandGeneric() to obtain the list of user defined + * function names. + */ +char_u * get_user_func_name(xp, idx) +expand_T *xp; +int idx; +{ + static long_u done; + static hashitem_T *hi; + ufunc_T *fp; + + if (idx == 0) { + done = 0; + hi = func_hashtab.ht_array; + } + if (done < func_hashtab.ht_used) { + if (done++ > 0) + ++hi; + while (HASHITEM_EMPTY(hi)) + ++hi; + fp = HI2UF(hi); + + if (fp->uf_flags & FC_DICT) + return (char_u *)""; /* don't show dict functions */ + + if (STRLEN(fp->uf_name) + 4 >= IOSIZE) + return fp->uf_name; /* prevents overflow */ + + cat_func_name(IObuff, fp); + if (xp->xp_context != EXPAND_USER_FUNC) { + STRCAT(IObuff, "("); + if (!fp->uf_varargs && fp->uf_args.ga_len == 0) + STRCAT(IObuff, ")"); + } + return IObuff; + } + return NULL; +} + + +/* + * Copy the function name of "fp" to buffer "buf". + * "buf" must be able to hold the function name plus three bytes. + * Takes care of script-local function names. + */ +static void cat_func_name(buf, fp) +char_u *buf; +ufunc_T *fp; +{ + if (fp->uf_name[0] == K_SPECIAL) { + STRCPY(buf, ""); + STRCAT(buf, fp->uf_name + 3); + } else + STRCPY(buf, fp->uf_name); +} + +/* + * ":delfunction {name}" + */ +void ex_delfunction(eap) +exarg_T *eap; +{ + ufunc_T *fp = NULL; + char_u *p; + char_u *name; + funcdict_T fudi; + + p = eap->arg; + name = trans_function_name(&p, eap->skip, 0, &fudi); + vim_free(fudi.fd_newkey); + if (name == NULL) { + if (fudi.fd_dict != NULL && !eap->skip) + EMSG(_(e_funcref)); + return; + } + if (!ends_excmd(*skipwhite(p))) { + vim_free(name); + EMSG(_(e_trailing)); + return; + } + eap->nextcmd = check_nextcmd(p); + if (eap->nextcmd != NULL) + *p = NUL; + + if (!eap->skip) + fp = find_func(name); + vim_free(name); + + if (!eap->skip) { + if (fp == NULL) { + EMSG2(_(e_nofunc), eap->arg); + return; + } + if (fp->uf_calls > 0) { + EMSG2(_("E131: Cannot delete function %s: It is in use"), eap->arg); + return; + } + + if (fudi.fd_dict != NULL) { + /* Delete the dict item that refers to the function, it will + * invoke func_unref() and possibly delete the function. */ + dictitem_remove(fudi.fd_dict, fudi.fd_di); + } else + func_free(fp); + } +} + +/* + * Free a function and remove it from the list of functions. + */ +static void func_free(fp) +ufunc_T *fp; +{ + hashitem_T *hi; + + /* clear this function */ + ga_clear_strings(&(fp->uf_args)); + ga_clear_strings(&(fp->uf_lines)); + vim_free(fp->uf_tml_count); + vim_free(fp->uf_tml_total); + vim_free(fp->uf_tml_self); + + /* remove the function from the function hashtable */ + hi = hash_find(&func_hashtab, UF2HIKEY(fp)); + if (HASHITEM_EMPTY(hi)) + EMSG2(_(e_intern2), "func_free()"); + else + hash_remove(&func_hashtab, hi); + + vim_free(fp); +} + +/* + * Unreference a Function: decrement the reference count and free it when it + * becomes zero. Only for numbered functions. + */ +void func_unref(name) +char_u *name; +{ + ufunc_T *fp; + + if (name != NULL && isdigit(*name)) { + fp = find_func(name); + if (fp == NULL) + EMSG2(_(e_intern2), "func_unref()"); + else if (--fp->uf_refcount <= 0) { + /* Only delete it when it's not being used. Otherwise it's done + * when "uf_calls" becomes zero. */ + if (fp->uf_calls == 0) + func_free(fp); + } + } +} + +/* + * Count a reference to a Function. + */ +void func_ref(name) +char_u *name; +{ + ufunc_T *fp; + + if (name != NULL && isdigit(*name)) { + fp = find_func(name); + if (fp == NULL) + EMSG2(_(e_intern2), "func_ref()"); + else + ++fp->uf_refcount; + } +} + +/* + * Call a user function. + */ +static void call_user_func(fp, argcount, argvars, rettv, firstline, lastline, + selfdict) +ufunc_T *fp; /* pointer to function */ +int argcount; /* nr of args */ +typval_T *argvars; /* arguments */ +typval_T *rettv; /* return value */ +linenr_T firstline; /* first line of range */ +linenr_T lastline; /* last line of range */ +dict_T *selfdict; /* Dictionary for "self" */ +{ + char_u *save_sourcing_name; + linenr_T save_sourcing_lnum; + scid_T save_current_SID; + funccall_T *fc; + int save_did_emsg; + static int depth = 0; + dictitem_T *v; + int fixvar_idx = 0; /* index in fixvar[] */ + int i; + int ai; + char_u numbuf[NUMBUFLEN]; + char_u *name; + proftime_T wait_start; + proftime_T call_start; + + /* If depth of calling is getting too high, don't execute the function */ + if (depth >= p_mfd) { + EMSG(_("E132: Function call depth is higher than 'maxfuncdepth'")); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = -1; + return; + } + ++depth; + + line_breakcheck(); /* check for CTRL-C hit */ + + fc = (funccall_T *)alloc(sizeof(funccall_T)); + fc->caller = current_funccal; + current_funccal = fc; + fc->func = fp; + fc->rettv = rettv; + rettv->vval.v_number = 0; + fc->linenr = 0; + fc->returned = FALSE; + fc->level = ex_nesting_level; + /* Check if this function has a breakpoint. */ + fc->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, (linenr_T)0); + fc->dbg_tick = debug_tick; + + /* + * Note about using fc->fixvar[]: This is an array of FIXVAR_CNT variables + * with names up to VAR_SHORT_LEN long. This avoids having to alloc/free + * each argument variable and saves a lot of time. + */ + /* + * Init l: variables. + */ + init_var_dict(&fc->l_vars, &fc->l_vars_var, VAR_DEF_SCOPE); + if (selfdict != NULL) { + /* Set l:self to "selfdict". Use "name" to avoid a warning from + * some compiler that checks the destination size. */ + v = &fc->fixvar[fixvar_idx++].var; + name = v->di_key; + STRCPY(name, "self"); + v->di_flags = DI_FLAGS_RO + DI_FLAGS_FIX; + hash_add(&fc->l_vars.dv_hashtab, DI2HIKEY(v)); + v->di_tv.v_type = VAR_DICT; + v->di_tv.v_lock = 0; + v->di_tv.vval.v_dict = selfdict; + ++selfdict->dv_refcount; + } + + /* + * Init a: variables. + * Set a:0 to "argcount". + * Set a:000 to a list with room for the "..." arguments. + */ + init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE); + add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "0", + (varnumber_T)(argcount - fp->uf_args.ga_len)); + /* Use "name" to avoid a warning from some compiler that checks the + * destination size. */ + v = &fc->fixvar[fixvar_idx++].var; + name = v->di_key; + STRCPY(name, "000"); + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v)); + v->di_tv.v_type = VAR_LIST; + v->di_tv.v_lock = VAR_FIXED; + v->di_tv.vval.v_list = &fc->l_varlist; + vim_memset(&fc->l_varlist, 0, sizeof(list_T)); + fc->l_varlist.lv_refcount = DO_NOT_FREE_CNT; + fc->l_varlist.lv_lock = VAR_FIXED; + + /* + * Set a:firstline to "firstline" and a:lastline to "lastline". + * Set a:name to named arguments. + * Set a:N to the "..." arguments. + */ + add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "firstline", + (varnumber_T)firstline); + add_nr_var(&fc->l_avars, &fc->fixvar[fixvar_idx++].var, "lastline", + (varnumber_T)lastline); + for (i = 0; i < argcount; ++i) { + ai = i - fp->uf_args.ga_len; + if (ai < 0) + /* named argument a:name */ + name = FUNCARG(fp, i); + else { + /* "..." argument a:1, a:2, etc. */ + sprintf((char *)numbuf, "%d", ai + 1); + name = numbuf; + } + if (fixvar_idx < FIXVAR_CNT && STRLEN(name) <= VAR_SHORT_LEN) { + v = &fc->fixvar[fixvar_idx++].var; + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + } else { + v = (dictitem_T *)alloc((unsigned)(sizeof(dictitem_T) + + STRLEN(name))); + if (v == NULL) + break; + v->di_flags = DI_FLAGS_RO; + } + STRCPY(v->di_key, name); + hash_add(&fc->l_avars.dv_hashtab, DI2HIKEY(v)); + + /* Note: the values are copied directly to avoid alloc/free. + * "argvars" must have VAR_FIXED for v_lock. */ + v->di_tv = argvars[i]; + v->di_tv.v_lock = VAR_FIXED; + + if (ai >= 0 && ai < MAX_FUNC_ARGS) { + list_append(&fc->l_varlist, &fc->l_listitems[ai]); + fc->l_listitems[ai].li_tv = argvars[i]; + fc->l_listitems[ai].li_tv.v_lock = VAR_FIXED; + } + } + + /* Don't redraw while executing the function. */ + ++RedrawingDisabled; + save_sourcing_name = sourcing_name; + save_sourcing_lnum = sourcing_lnum; + sourcing_lnum = 1; + sourcing_name = alloc((unsigned)((save_sourcing_name == NULL ? 0 + : STRLEN(save_sourcing_name)) + + STRLEN(fp->uf_name) + 13)); + if (sourcing_name != NULL) { + if (save_sourcing_name != NULL + && STRNCMP(save_sourcing_name, "function ", 9) == 0) + sprintf((char *)sourcing_name, "%s..", save_sourcing_name); + else + STRCPY(sourcing_name, "function "); + cat_func_name(sourcing_name + STRLEN(sourcing_name), fp); + + if (p_verbose >= 12) { + ++no_wait_return; + verbose_enter_scroll(); + + smsg((char_u *)_("calling %s"), sourcing_name); + if (p_verbose >= 14) { + char_u buf[MSG_BUF_LEN]; + char_u numbuf2[NUMBUFLEN]; + char_u *tofree; + char_u *s; + + msg_puts((char_u *)"("); + for (i = 0; i < argcount; ++i) { + if (i > 0) + msg_puts((char_u *)", "); + if (argvars[i].v_type == VAR_NUMBER) + msg_outnum((long)argvars[i].vval.v_number); + else { + s = tv2string(&argvars[i], &tofree, numbuf2, 0); + if (s != NULL) { + if (vim_strsize(s) > MSG_BUF_CLEN) { + trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); + s = buf; + } + msg_puts(s); + vim_free(tofree); + } + } + } + msg_puts((char_u *)")"); + } + msg_puts((char_u *)"\n"); /* don't overwrite this either */ + + verbose_leave_scroll(); + --no_wait_return; + } + } + if (do_profiling == PROF_YES) { + if (!fp->uf_profiling && has_profiling(FALSE, fp->uf_name, NULL)) + func_do_profile(fp); + if (fp->uf_profiling + || (fc->caller != NULL && fc->caller->func->uf_profiling)) { + ++fp->uf_tm_count; + profile_start(&call_start); + profile_zero(&fp->uf_tm_children); + } + script_prof_save(&wait_start); + } + + save_current_SID = current_SID; + current_SID = fp->uf_script_ID; + save_did_emsg = did_emsg; + did_emsg = FALSE; + + /* call do_cmdline() to execute the lines */ + do_cmdline(NULL, get_func_line, (void *)fc, + DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); + + --RedrawingDisabled; + + /* when the function was aborted because of an error, return -1 */ + if ((did_emsg && + (fp->uf_flags & FC_ABORT)) || rettv->v_type == VAR_UNKNOWN) { + clear_tv(rettv); + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = -1; + } + + if (do_profiling == PROF_YES && (fp->uf_profiling + || (fc->caller != NULL && + fc->caller->func->uf_profiling))) { + profile_end(&call_start); + profile_sub_wait(&wait_start, &call_start); + profile_add(&fp->uf_tm_total, &call_start); + profile_self(&fp->uf_tm_self, &call_start, &fp->uf_tm_children); + if (fc->caller != NULL && fc->caller->func->uf_profiling) { + profile_add(&fc->caller->func->uf_tm_children, &call_start); + profile_add(&fc->caller->func->uf_tml_children, &call_start); + } + } + + /* when being verbose, mention the return value */ + if (p_verbose >= 12) { + ++no_wait_return; + verbose_enter_scroll(); + + if (aborting()) + smsg((char_u *)_("%s aborted"), sourcing_name); + else if (fc->rettv->v_type == VAR_NUMBER) + smsg((char_u *)_("%s returning #%ld"), sourcing_name, + (long)fc->rettv->vval.v_number); + else { + char_u buf[MSG_BUF_LEN]; + char_u numbuf2[NUMBUFLEN]; + char_u *tofree; + char_u *s; + + /* The value may be very long. Skip the middle part, so that we + * have some idea how it starts and ends. smsg() would always + * truncate it at the end. */ + s = tv2string(fc->rettv, &tofree, numbuf2, 0); + if (s != NULL) { + if (vim_strsize(s) > MSG_BUF_CLEN) { + trunc_string(s, buf, MSG_BUF_CLEN, MSG_BUF_LEN); + s = buf; + } + smsg((char_u *)_("%s returning %s"), sourcing_name, s); + vim_free(tofree); + } + } + msg_puts((char_u *)"\n"); /* don't overwrite this either */ + + verbose_leave_scroll(); + --no_wait_return; + } + + vim_free(sourcing_name); + sourcing_name = save_sourcing_name; + sourcing_lnum = save_sourcing_lnum; + current_SID = save_current_SID; + if (do_profiling == PROF_YES) + script_prof_restore(&wait_start); + + if (p_verbose >= 12 && sourcing_name != NULL) { + ++no_wait_return; + verbose_enter_scroll(); + + smsg((char_u *)_("continuing in %s"), sourcing_name); + msg_puts((char_u *)"\n"); /* don't overwrite this either */ + + verbose_leave_scroll(); + --no_wait_return; + } + + did_emsg |= save_did_emsg; + current_funccal = fc->caller; + --depth; + + /* If the a:000 list and the l: and a: dicts are not referenced we can + * free the funccall_T and what's in it. */ + if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT + && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT + && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT) { + free_funccal(fc, FALSE); + } else { + hashitem_T *hi; + listitem_T *li; + int todo; + + /* "fc" is still in use. This can happen when returning "a:000" or + * assigning "l:" to a global variable. + * Link "fc" in the list for garbage collection later. */ + fc->caller = previous_funccal; + previous_funccal = fc; + + /* Make a copy of the a: variables, since we didn't do that above. */ + todo = (int)fc->l_avars.dv_hashtab.ht_used; + for (hi = fc->l_avars.dv_hashtab.ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + v = HI2DI(hi); + copy_tv(&v->di_tv, &v->di_tv); + } + } + + /* Make a copy of the a:000 items, since we didn't do that above. */ + for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) + copy_tv(&li->li_tv, &li->li_tv); + } +} + +/* + * Return TRUE if items in "fc" do not have "copyID". That means they are not + * referenced from anywhere that is in use. + */ +static int can_free_funccal(fc, copyID) +funccall_T *fc; +int copyID; +{ + return fc->l_varlist.lv_copyID != copyID + && fc->l_vars.dv_copyID != copyID + && fc->l_avars.dv_copyID != copyID; +} + +/* + * Free "fc" and what it contains. + */ +static void free_funccal(fc, free_val) +funccall_T *fc; +int free_val; /* a: vars were allocated */ +{ + listitem_T *li; + + /* The a: variables typevals may not have been allocated, only free the + * allocated variables. */ + vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); + + /* free all l: variables */ + vars_clear(&fc->l_vars.dv_hashtab); + + /* Free the a:000 variables if they were allocated. */ + if (free_val) + for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) + clear_tv(&li->li_tv); + + vim_free(fc); +} + +/* + * Add a number variable "name" to dict "dp" with value "nr". + */ +static void add_nr_var(dp, v, name, nr) +dict_T *dp; +dictitem_T *v; +char *name; +varnumber_T nr; +{ + STRCPY(v->di_key, name); + v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + hash_add(&dp->dv_hashtab, DI2HIKEY(v)); + v->di_tv.v_type = VAR_NUMBER; + v->di_tv.v_lock = VAR_FIXED; + v->di_tv.vval.v_number = nr; +} + +/* + * ":return [expr]" + */ +void ex_return(eap) +exarg_T *eap; +{ + char_u *arg = eap->arg; + typval_T rettv; + int returning = FALSE; + + if (current_funccal == NULL) { + EMSG(_("E133: :return not inside a function")); + return; + } + + if (eap->skip) + ++emsg_skip; + + eap->nextcmd = NULL; + if ((*arg != NUL && *arg != '|' && *arg != '\n') + && eval0(arg, &rettv, &eap->nextcmd, !eap->skip) != FAIL) { + if (!eap->skip) + returning = do_return(eap, FALSE, TRUE, &rettv); + else + clear_tv(&rettv); + } + /* It's safer to return also on error. */ + else if (!eap->skip) { + /* + * Return unless the expression evaluation has been cancelled due to an + * aborting error, an interrupt, or an exception. + */ + if (!aborting()) + returning = do_return(eap, FALSE, TRUE, NULL); + } + + /* When skipping or the return gets pending, advance to the next command + * in this line (!returning). Otherwise, ignore the rest of the line. + * Following lines will be ignored by get_func_line(). */ + if (returning) + eap->nextcmd = NULL; + else if (eap->nextcmd == NULL) /* no argument */ + eap->nextcmd = check_nextcmd(arg); + + if (eap->skip) + --emsg_skip; +} + +/* + * Return from a function. Possibly makes the return pending. Also called + * for a pending return at the ":endtry" or after returning from an extra + * do_cmdline(). "reanimate" is used in the latter case. "is_cmd" is set + * when called due to a ":return" command. "rettv" may point to a typval_T + * with the return rettv. Returns TRUE when the return can be carried out, + * FALSE when the return gets pending. + */ +int do_return(eap, reanimate, is_cmd, rettv) +exarg_T *eap; +int reanimate; +int is_cmd; +void *rettv; +{ + int idx; + struct condstack *cstack = eap->cstack; + + if (reanimate) + /* Undo the return. */ + current_funccal->returned = FALSE; + + /* + * Cleanup (and inactivate) conditionals, but stop when a try conditional + * not in its finally clause (which then is to be executed next) is found. + * In this case, make the ":return" pending for execution at the ":endtry". + * Otherwise, return normally. + */ + idx = cleanup_conditionals(eap->cstack, 0, TRUE); + if (idx >= 0) { + cstack->cs_pending[idx] = CSTP_RETURN; + + if (!is_cmd && !reanimate) + /* A pending return again gets pending. "rettv" points to an + * allocated variable with the rettv of the original ":return"'s + * argument if present or is NULL else. */ + cstack->cs_rettv[idx] = rettv; + else { + /* When undoing a return in order to make it pending, get the stored + * return rettv. */ + if (reanimate) + rettv = current_funccal->rettv; + + if (rettv != NULL) { + /* Store the value of the pending return. */ + if ((cstack->cs_rettv[idx] = alloc_tv()) != NULL) + *(typval_T *)cstack->cs_rettv[idx] = *(typval_T *)rettv; + else + EMSG(_(e_outofmem)); + } else + cstack->cs_rettv[idx] = NULL; + + if (reanimate) { + /* The pending return value could be overwritten by a ":return" + * without argument in a finally clause; reset the default + * return value. */ + current_funccal->rettv->v_type = VAR_NUMBER; + current_funccal->rettv->vval.v_number = 0; + } + } + report_make_pending(CSTP_RETURN, rettv); + } else { + current_funccal->returned = TRUE; + + /* If the return is carried out now, store the return value. For + * a return immediately after reanimation, the value is already + * there. */ + if (!reanimate && rettv != NULL) { + clear_tv(current_funccal->rettv); + *current_funccal->rettv = *(typval_T *)rettv; + if (!is_cmd) + vim_free(rettv); + } + } + + return idx < 0; +} + +/* + * Free the variable with a pending return value. + */ +void discard_pending_return(rettv) +void *rettv; +{ + free_tv((typval_T *)rettv); +} + +/* + * Generate a return command for producing the value of "rettv". The result + * is an allocated string. Used by report_pending() for verbose messages. + */ +char_u * get_return_cmd(rettv) +void *rettv; +{ + char_u *s = NULL; + char_u *tofree = NULL; + char_u numbuf[NUMBUFLEN]; + + if (rettv != NULL) + s = echo_string((typval_T *)rettv, &tofree, numbuf, 0); + if (s == NULL) + s = (char_u *)""; + + STRCPY(IObuff, ":return "); + STRNCPY(IObuff + 8, s, IOSIZE - 8); + if (STRLEN(s) + 8 >= IOSIZE) + STRCPY(IObuff + IOSIZE - 4, "..."); + vim_free(tofree); + return vim_strsave(IObuff); +} + +/* + * Get next function line. + * Called by do_cmdline() to get the next line. + * Returns allocated string, or NULL for end of function. + */ +char_u * get_func_line(c, cookie, indent) +int c UNUSED; +void *cookie; +int indent UNUSED; +{ + funccall_T *fcp = (funccall_T *)cookie; + ufunc_T *fp = fcp->func; + char_u *retval; + garray_T *gap; /* growarray with function lines */ + + /* If breakpoints have been added/deleted need to check for it. */ + if (fcp->dbg_tick != debug_tick) { + fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, + sourcing_lnum); + fcp->dbg_tick = debug_tick; + } + if (do_profiling == PROF_YES) + func_line_end(cookie); + + gap = &fp->uf_lines; + if (((fp->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) + || fcp->returned) + retval = NULL; + else { + /* Skip NULL lines (continuation lines). */ + while (fcp->linenr < gap->ga_len + && ((char_u **)(gap->ga_data))[fcp->linenr] == NULL) + ++fcp->linenr; + if (fcp->linenr >= gap->ga_len) + retval = NULL; + else { + retval = vim_strsave(((char_u **)(gap->ga_data))[fcp->linenr++]); + sourcing_lnum = fcp->linenr; + if (do_profiling == PROF_YES) + func_line_start(cookie); + } + } + + /* Did we encounter a breakpoint? */ + if (fcp->breakpoint != 0 && fcp->breakpoint <= sourcing_lnum) { + dbg_breakpoint(fp->uf_name, sourcing_lnum); + /* Find next breakpoint. */ + fcp->breakpoint = dbg_find_breakpoint(FALSE, fp->uf_name, + sourcing_lnum); + fcp->dbg_tick = debug_tick; + } + + return retval; +} + +/* + * Called when starting to read a function line. + * "sourcing_lnum" must be correct! + * When skipping lines it may not actually be executed, but we won't find out + * until later and we need to store the time now. + */ +void func_line_start(cookie) +void *cookie; +{ + funccall_T *fcp = (funccall_T *)cookie; + ufunc_T *fp = fcp->func; + + if (fp->uf_profiling && sourcing_lnum >= 1 + && sourcing_lnum <= fp->uf_lines.ga_len) { + fp->uf_tml_idx = sourcing_lnum - 1; + /* Skip continuation lines. */ + while (fp->uf_tml_idx > 0 && FUNCLINE(fp, fp->uf_tml_idx) == NULL) + --fp->uf_tml_idx; + fp->uf_tml_execed = FALSE; + profile_start(&fp->uf_tml_start); + profile_zero(&fp->uf_tml_children); + profile_get_wait(&fp->uf_tml_wait); + } +} + +/* + * Called when actually executing a function line. + */ +void func_line_exec(cookie) +void *cookie; +{ + funccall_T *fcp = (funccall_T *)cookie; + ufunc_T *fp = fcp->func; + + if (fp->uf_profiling && fp->uf_tml_idx >= 0) + fp->uf_tml_execed = TRUE; +} + +/* + * Called when done with a function line. + */ +void func_line_end(cookie) +void *cookie; +{ + funccall_T *fcp = (funccall_T *)cookie; + ufunc_T *fp = fcp->func; + + if (fp->uf_profiling && fp->uf_tml_idx >= 0) { + if (fp->uf_tml_execed) { + ++fp->uf_tml_count[fp->uf_tml_idx]; + profile_end(&fp->uf_tml_start); + profile_sub_wait(&fp->uf_tml_wait, &fp->uf_tml_start); + profile_add(&fp->uf_tml_total[fp->uf_tml_idx], &fp->uf_tml_start); + profile_self(&fp->uf_tml_self[fp->uf_tml_idx], &fp->uf_tml_start, + &fp->uf_tml_children); + } + fp->uf_tml_idx = -1; + } +} + +/* + * Return TRUE if the currently active function should be ended, because a + * return was encountered or an error occurred. Used inside a ":while". + */ +int func_has_ended(cookie) +void *cookie; +{ + funccall_T *fcp = (funccall_T *)cookie; + + /* Ignore the "abort" flag if the abortion behavior has been changed due to + * an error inside a try conditional. */ + return ((fcp->func->uf_flags & FC_ABORT) && did_emsg && !aborted_in_try()) + || fcp->returned; +} + +/* + * return TRUE if cookie indicates a function which "abort"s on errors. + */ +int func_has_abort(cookie) +void *cookie; +{ + return ((funccall_T *)cookie)->func->uf_flags & FC_ABORT; +} + +typedef enum { + VAR_FLAVOUR_DEFAULT, /* doesn't start with uppercase */ + VAR_FLAVOUR_SESSION, /* starts with uppercase, some lower */ + VAR_FLAVOUR_VIMINFO /* all uppercase */ +} var_flavour_T; + +static var_flavour_T var_flavour __ARGS((char_u *varname)); + +static var_flavour_T var_flavour(varname) +char_u *varname; +{ + char_u *p = varname; + + if (ASCII_ISUPPER(*p)) { + while (*(++p)) + if (ASCII_ISLOWER(*p)) + return VAR_FLAVOUR_SESSION; + return VAR_FLAVOUR_VIMINFO; + } else + return VAR_FLAVOUR_DEFAULT; +} + +/* + * Restore global vars that start with a capital from the viminfo file + */ +int read_viminfo_varlist(virp, writing) +vir_T *virp; +int writing; +{ + char_u *tab; + int type = VAR_NUMBER; + typval_T tv; + + if (!writing && (find_viminfo_parameter('!') != NULL)) { + tab = vim_strchr(virp->vir_line + 1, '\t'); + if (tab != NULL) { + *tab++ = '\0'; /* isolate the variable name */ + switch (*tab) { + case 'S': type = VAR_STRING; break; + case 'F': type = VAR_FLOAT; break; + case 'D': type = VAR_DICT; break; + case 'L': type = VAR_LIST; break; + } + + tab = vim_strchr(tab, '\t'); + if (tab != NULL) { + tv.v_type = type; + if (type == VAR_STRING || type == VAR_DICT || type == VAR_LIST) + tv.vval.v_string = viminfo_readstring(virp, + (int)(tab - virp->vir_line + 1), TRUE); + else if (type == VAR_FLOAT) + (void)string2float(tab + 1, &tv.vval.v_float); + else + tv.vval.v_number = atol((char *)tab + 1); + if (type == VAR_DICT || type == VAR_LIST) { + typval_T *etv = eval_expr(tv.vval.v_string, NULL); + + if (etv == NULL) + /* Failed to parse back the dict or list, use it as a + * string. */ + tv.v_type = VAR_STRING; + else { + vim_free(tv.vval.v_string); + tv = *etv; + vim_free(etv); + } + } + + set_var(virp->vir_line + 1, &tv, FALSE); + + if (tv.v_type == VAR_STRING) + vim_free(tv.vval.v_string); + else if (tv.v_type == VAR_DICT || tv.v_type == VAR_LIST) + clear_tv(&tv); + } + } + } + + return viminfo_readline(virp); +} + +/* + * Write global vars that start with a capital to the viminfo file + */ +void write_viminfo_varlist(fp) +FILE *fp; +{ + hashitem_T *hi; + dictitem_T *this_var; + int todo; + char *s; + char_u *p; + char_u *tofree; + char_u numbuf[NUMBUFLEN]; + + if (find_viminfo_parameter('!') == NULL) + return; + + fputs(_("\n# global variables:\n"), fp); + + todo = (int)globvarht.ht_used; + for (hi = globvarht.ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + this_var = HI2DI(hi); + if (var_flavour(this_var->di_key) == VAR_FLAVOUR_VIMINFO) { + switch (this_var->di_tv.v_type) { + case VAR_STRING: s = "STR"; break; + case VAR_NUMBER: s = "NUM"; break; + case VAR_FLOAT: s = "FLO"; break; + case VAR_DICT: s = "DIC"; break; + case VAR_LIST: s = "LIS"; break; + default: continue; + } + fprintf(fp, "!%s\t%s\t", this_var->di_key, s); + p = echo_string(&this_var->di_tv, &tofree, numbuf, 0); + if (p != NULL) + viminfo_writestring(fp, p); + vim_free(tofree); + } + } + } +} + +int store_session_globals(fd) +FILE *fd; +{ + hashitem_T *hi; + dictitem_T *this_var; + int todo; + char_u *p, *t; + + todo = (int)globvarht.ht_used; + for (hi = globvarht.ht_array; todo > 0; ++hi) { + if (!HASHITEM_EMPTY(hi)) { + --todo; + this_var = HI2DI(hi); + if ((this_var->di_tv.v_type == VAR_NUMBER + || this_var->di_tv.v_type == VAR_STRING) + && var_flavour(this_var->di_key) == VAR_FLAVOUR_SESSION) { + /* Escape special characters with a backslash. Turn a LF and + * CR into \n and \r. */ + p = vim_strsave_escaped(get_tv_string(&this_var->di_tv), + (char_u *)"\\\"\n\r"); + if (p == NULL) /* out of memory */ + break; + for (t = p; *t != NUL; ++t) + if (*t == '\n') + *t = 'n'; + else if (*t == '\r') + *t = 'r'; + if ((fprintf(fd, "let %s = %c%s%c", + this_var->di_key, + (this_var->di_tv.v_type == VAR_STRING) ? '"' + : ' ', + p, + (this_var->di_tv.v_type == VAR_STRING) ? '"' + : ' ') < 0) + || put_eol(fd) == FAIL) { + vim_free(p); + return FAIL; + } + vim_free(p); + } else if (this_var->di_tv.v_type == VAR_FLOAT + && var_flavour(this_var->di_key) == VAR_FLAVOUR_SESSION) { + float_T f = this_var->di_tv.vval.v_float; + int sign = ' '; + + if (f < 0) { + f = -f; + sign = '-'; + } + if ((fprintf(fd, "let %s = %c%f", + this_var->di_key, sign, f) < 0) + || put_eol(fd) == FAIL) + return FAIL; + } + } + } + return OK; +} + +/* + * Display script name where an item was last set. + * Should only be invoked when 'verbose' is non-zero. + */ +void last_set_msg(scriptID) +scid_T scriptID; +{ + char_u *p; + + if (scriptID != 0) { + p = home_replace_save(NULL, get_scriptname(scriptID)); + if (p != NULL) { + verbose_enter(); + MSG_PUTS(_("\n\tLast set from ")); + MSG_PUTS(p); + vim_free(p); + verbose_leave(); + } + } +} + +/* + * List v:oldfiles in a nice way. + */ +void ex_oldfiles(eap) +exarg_T *eap UNUSED; +{ + list_T *l = vimvars[VV_OLDFILES].vv_list; + listitem_T *li; + int nr = 0; + + if (l == NULL) + msg((char_u *)_("No old files")); + else { + msg_start(); + msg_scroll = TRUE; + for (li = l->lv_first; li != NULL && !got_int; li = li->li_next) { + msg_outnum((long)++nr); + MSG_PUTS(": "); + msg_outtrans(get_tv_string(&li->li_tv)); + msg_putchar('\n'); + out_flush(); /* output one line at a time */ + ui_breakcheck(); + } + /* Assume "got_int" was set to truncate the listing. */ + got_int = FALSE; + + } +} + + + + + +/* + * Adjust a filename, according to a string of modifiers. + * *fnamep must be NUL terminated when called. When returning, the length is + * determined by *fnamelen. + * Returns VALID_ flags or -1 for failure. + * When there is an error, *fnamep is set to NULL. + */ +int modify_fname(src, usedlen, fnamep, bufp, fnamelen) +char_u *src; /* string with modifiers */ +int *usedlen; /* characters after src that are used */ +char_u **fnamep; /* file name so far */ +char_u **bufp; /* buffer for allocated file name or NULL */ +int *fnamelen; /* length of fnamep */ +{ + int valid = 0; + char_u *tail; + char_u *s, *p, *pbuf; + char_u dirname[MAXPATHL]; + int c; + int has_fullname = 0; + +repeat: + /* ":p" - full path/file_name */ + if (src[*usedlen] == ':' && src[*usedlen + 1] == 'p') { + has_fullname = 1; + + valid |= VALID_PATH; + *usedlen += 2; + + /* Expand "~/path" for all systems and "~user/path" for Unix and VMS */ + if ((*fnamep)[0] == '~' +#if !defined(UNIX) && !(defined(VMS) && defined(USER_HOME)) + && ((*fnamep)[1] == '/' +# ifdef BACKSLASH_IN_FILENAME + || (*fnamep)[1] == '\\' +# endif + || (*fnamep)[1] == NUL) + +#endif + ) { + *fnamep = expand_env_save(*fnamep); + vim_free(*bufp); /* free any allocated file name */ + *bufp = *fnamep; + if (*fnamep == NULL) + return -1; + } + + /* When "/." or "/.." is used: force expansion to get rid of it. */ + for (p = *fnamep; *p != NUL; mb_ptr_adv(p)) { + if (vim_ispathsep(*p) + && p[1] == '.' + && (p[2] == NUL + || vim_ispathsep(p[2]) + || (p[2] == '.' + && (p[3] == NUL || vim_ispathsep(p[3]))))) + break; + } + + /* FullName_save() is slow, don't use it when not needed. */ + if (*p != NUL || !vim_isAbsName(*fnamep)) { + *fnamep = FullName_save(*fnamep, *p != NUL); + vim_free(*bufp); /* free any allocated file name */ + *bufp = *fnamep; + if (*fnamep == NULL) + return -1; + } + + /* Append a path separator to a directory. */ + if (mch_isdir(*fnamep)) { + /* Make room for one or two extra characters. */ + *fnamep = vim_strnsave(*fnamep, (int)STRLEN(*fnamep) + 2); + vim_free(*bufp); /* free any allocated file name */ + *bufp = *fnamep; + if (*fnamep == NULL) + return -1; + add_pathsep(*fnamep); + } + } + + /* ":." - path relative to the current directory */ + /* ":~" - path relative to the home directory */ + /* ":8" - shortname path - postponed till after */ + while (src[*usedlen] == ':' + && ((c = src[*usedlen + 1]) == '.' || c == '~' || c == '8')) { + *usedlen += 2; + if (c == '8') { + continue; + } + pbuf = NULL; + /* Need full path first (use expand_env() to remove a "~/") */ + if (!has_fullname) { + if (c == '.' && **fnamep == '~') + p = pbuf = expand_env_save(*fnamep); + else + p = pbuf = FullName_save(*fnamep, FALSE); + } else + p = *fnamep; + + has_fullname = 0; + + if (p != NULL) { + if (c == '.') { + mch_dirname(dirname, MAXPATHL); + s = shorten_fname(p, dirname); + if (s != NULL) { + *fnamep = s; + if (pbuf != NULL) { + vim_free(*bufp); /* free any allocated file name */ + *bufp = pbuf; + pbuf = NULL; + } + } + } else { + home_replace(NULL, p, dirname, MAXPATHL, TRUE); + /* Only replace it when it starts with '~' */ + if (*dirname == '~') { + s = vim_strsave(dirname); + if (s != NULL) { + *fnamep = s; + vim_free(*bufp); + *bufp = s; + } + } + } + vim_free(pbuf); + } + } + + tail = gettail(*fnamep); + *fnamelen = (int)STRLEN(*fnamep); + + /* ":h" - head, remove "/file_name", can be repeated */ + /* Don't remove the first "/" or "c:\" */ + while (src[*usedlen] == ':' && src[*usedlen + 1] == 'h') { + valid |= VALID_HEAD; + *usedlen += 2; + s = get_past_head(*fnamep); + while (tail > s && after_pathsep(s, tail)) + mb_ptr_back(*fnamep, tail); + *fnamelen = (int)(tail - *fnamep); + if (*fnamelen == 0) { + /* Result is empty. Turn it into "." to make ":cd %:h" work. */ + p = vim_strsave((char_u *)"."); + if (p == NULL) + return -1; + vim_free(*bufp); + *bufp = *fnamep = tail = p; + *fnamelen = 1; + } else { + while (tail > s && !after_pathsep(s, tail)) + mb_ptr_back(*fnamep, tail); + } + } + + /* ":8" - shortname */ + if (src[*usedlen] == ':' && src[*usedlen + 1] == '8') { + *usedlen += 2; + } + + + /* ":t" - tail, just the basename */ + if (src[*usedlen] == ':' && src[*usedlen + 1] == 't') { + *usedlen += 2; + *fnamelen -= (int)(tail - *fnamep); + *fnamep = tail; + } + + /* ":e" - extension, can be repeated */ + /* ":r" - root, without extension, can be repeated */ + while (src[*usedlen] == ':' + && (src[*usedlen + 1] == 'e' || src[*usedlen + 1] == 'r')) { + /* find a '.' in the tail: + * - for second :e: before the current fname + * - otherwise: The last '.' + */ + if (src[*usedlen + 1] == 'e' && *fnamep > tail) + s = *fnamep - 2; + else + s = *fnamep + *fnamelen - 1; + for (; s > tail; --s) + if (s[0] == '.') + break; + if (src[*usedlen + 1] == 'e') { /* :e */ + if (s > tail) { + *fnamelen += (int)(*fnamep - (s + 1)); + *fnamep = s + 1; + } else if (*fnamep <= tail) + *fnamelen = 0; + } else { /* :r */ + if (s > tail) /* remove one extension */ + *fnamelen = (int)(s - *fnamep); + } + *usedlen += 2; + } + + /* ":s?pat?foo?" - substitute */ + /* ":gs?pat?foo?" - global substitute */ + if (src[*usedlen] == ':' + && (src[*usedlen + 1] == 's' + || (src[*usedlen + 1] == 'g' && src[*usedlen + 2] == 's'))) { + char_u *str; + char_u *pat; + char_u *sub; + int sep; + char_u *flags; + int didit = FALSE; + + flags = (char_u *)""; + s = src + *usedlen + 2; + if (src[*usedlen + 1] == 'g') { + flags = (char_u *)"g"; + ++s; + } + + sep = *s++; + if (sep) { + /* find end of pattern */ + p = vim_strchr(s, sep); + if (p != NULL) { + pat = vim_strnsave(s, (int)(p - s)); + if (pat != NULL) { + s = p + 1; + /* find end of substitution */ + p = vim_strchr(s, sep); + if (p != NULL) { + sub = vim_strnsave(s, (int)(p - s)); + str = vim_strnsave(*fnamep, *fnamelen); + if (sub != NULL && str != NULL) { + *usedlen = (int)(p + 1 - src); + s = do_string_sub(str, pat, sub, flags); + if (s != NULL) { + *fnamep = s; + *fnamelen = (int)STRLEN(s); + vim_free(*bufp); + *bufp = s; + didit = TRUE; + } + } + vim_free(sub); + vim_free(str); + } + vim_free(pat); + } + } + /* after using ":s", repeat all the modifiers */ + if (didit) + goto repeat; + } + } + + return valid; +} + +/* + * Perform a substitution on "str" with pattern "pat" and substitute "sub". + * "flags" can be "g" to do a global substitute. + * Returns an allocated string, NULL for error. + */ +char_u * do_string_sub(str, pat, sub, flags) +char_u *str; +char_u *pat; +char_u *sub; +char_u *flags; +{ + int sublen; + regmatch_T regmatch; + int i; + int do_all; + char_u *tail; + garray_T ga; + char_u *ret; + char_u *save_cpo; + char_u *zero_width = NULL; + + /* Make 'cpoptions' empty, so that the 'l' flag doesn't work here */ + save_cpo = p_cpo; + p_cpo = empty_option; + + ga_init2(&ga, 1, 200); + + do_all = (flags[0] == 'g'); + + regmatch.rm_ic = p_ic; + regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); + if (regmatch.regprog != NULL) { + tail = str; + while (vim_regexec_nl(®match, str, (colnr_T)(tail - str))) { + /* Skip empty match except for first match. */ + if (regmatch.startp[0] == regmatch.endp[0]) { + if (zero_width == regmatch.startp[0]) { + /* avoid getting stuck on a match with an empty string */ + *((char_u *)ga.ga_data + ga.ga_len) = *tail++; + ++ga.ga_len; + continue; + } + zero_width = regmatch.startp[0]; + } + + /* + * Get some space for a temporary buffer to do the substitution + * into. It will contain: + * - The text up to where the match is. + * - The substituted text. + * - The text after the match. + */ + sublen = vim_regsub(®match, sub, tail, FALSE, TRUE, FALSE); + if (ga_grow(&ga, (int)(STRLEN(tail) + sublen - + (regmatch.endp[0] - regmatch.startp[0]))) == + FAIL) { + ga_clear(&ga); + break; + } + + /* copy the text up to where the match is */ + i = (int)(regmatch.startp[0] - tail); + mch_memmove((char_u *)ga.ga_data + ga.ga_len, tail, (size_t)i); + /* add the substituted text */ + (void)vim_regsub(®match, sub, (char_u *)ga.ga_data + + ga.ga_len + i, TRUE, TRUE, FALSE); + ga.ga_len += i + sublen - 1; + tail = regmatch.endp[0]; + if (*tail == NUL) + break; + if (!do_all) + break; + } + + if (ga.ga_data != NULL) + STRCPY((char *)ga.ga_data + ga.ga_len, tail); + + vim_regfree(regmatch.regprog); + } + + ret = vim_strsave(ga.ga_data == NULL ? str : (char_u *)ga.ga_data); + ga_clear(&ga); + if (p_cpo == empty_option) + p_cpo = save_cpo; + else + /* Darn, evaluating {sub} expression changed the value. */ + free_string_option(save_cpo); + + return ret; +} + diff --git a/src/ex_cmds.c b/src/ex_cmds.c new file mode 100644 index 0000000000..d791d777dc --- /dev/null +++ b/src/ex_cmds.c @@ -0,0 +1,5679 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * ex_cmds.c: some functions for command line commands + */ + +#include "vim.h" +#include "version.h" + +static int linelen __ARGS((int *has_tab)); +static void do_filter __ARGS((linenr_T line1, linenr_T line2, exarg_T *eap, + char_u *cmd, int do_in, + int do_out)); +static char_u *viminfo_filename __ARGS((char_u *)); +static void do_viminfo __ARGS((FILE *fp_in, FILE *fp_out, int flags)); +static int viminfo_encoding __ARGS((vir_T *virp)); +static int read_viminfo_up_to_marks __ARGS((vir_T *virp, int forceit, + int writing)); + +static int check_readonly __ARGS((int *forceit, buf_T *buf)); +static void delbuf_msg __ARGS((char_u *name)); +static int +help_compare __ARGS((const void *s1, const void *s2)); + +/* + * ":ascii" and "ga". + */ +void do_ascii(eap) +exarg_T *eap UNUSED; +{ + int c; + int cval; + char buf1[20]; + char buf2[20]; + char_u buf3[7]; + int cc[MAX_MCO]; + int ci = 0; + int len; + + if (enc_utf8) + c = utfc_ptr2char(ml_get_cursor(), cc); + else + c = gchar_cursor(); + if (c == NUL) { + MSG("NUL"); + return; + } + + IObuff[0] = NUL; + if (!has_mbyte || (enc_dbcs != 0 && c < 0x100) || c < 0x80) { + if (c == NL) /* NUL is stored as NL */ + c = NUL; + if (c == CAR && get_fileformat(curbuf) == EOL_MAC) + cval = NL; /* NL is stored as CR */ + else + cval = c; + if (vim_isprintc_strict(c) && (c < ' ' + || c > '~' + )) { + transchar_nonprint(buf3, c); + vim_snprintf(buf1, sizeof(buf1), " <%s>", (char *)buf3); + } else + buf1[0] = NUL; + if (c >= 0x80) + vim_snprintf(buf2, sizeof(buf2), " ", + (char *)transchar(c & 0x7f)); + else + buf2[0] = NUL; + vim_snprintf((char *)IObuff, IOSIZE, + _("<%s>%s%s %d, Hex %02x, Octal %03o"), + transchar(c), buf1, buf2, cval, cval, cval); + if (enc_utf8) + c = cc[ci++]; + else + c = 0; + } + + /* Repeat for combining characters. */ + while (has_mbyte && (c >= 0x100 || (enc_utf8 && c >= 0x80))) { + len = (int)STRLEN(IObuff); + /* This assumes every multi-byte char is printable... */ + if (len > 0) + IObuff[len++] = ' '; + IObuff[len++] = '<'; + if (enc_utf8 && utf_iscomposing(c) +# ifdef USE_GUI + && !gui.in_use +# endif + ) + IObuff[len++] = ' '; /* draw composing char on top of a space */ + len += (*mb_char2bytes)(c, IObuff + len); + vim_snprintf((char *)IObuff + len, IOSIZE - len, + c < 0x10000 ? _("> %d, Hex %04x, Octal %o") + : _("> %d, Hex %08x, Octal %o"), c, c, c); + if (ci == MAX_MCO) + break; + if (enc_utf8) + c = cc[ci++]; + else + c = 0; + } + + msg(IObuff); +} + +/* + * ":left", ":center" and ":right": align text. + */ +void ex_align(eap) +exarg_T *eap; +{ + pos_T save_curpos; + int len; + int indent = 0; + int new_indent; + int has_tab; + int width; + + if (curwin->w_p_rl) { + /* switch left and right aligning */ + if (eap->cmdidx == CMD_right) + eap->cmdidx = CMD_left; + else if (eap->cmdidx == CMD_left) + eap->cmdidx = CMD_right; + } + + width = atoi((char *)eap->arg); + save_curpos = curwin->w_cursor; + if (eap->cmdidx == CMD_left) { /* width is used for new indent */ + if (width >= 0) + indent = width; + } else { + /* + * if 'textwidth' set, use it + * else if 'wrapmargin' set, use it + * if invalid value, use 80 + */ + if (width <= 0) + width = curbuf->b_p_tw; + if (width == 0 && curbuf->b_p_wm > 0) + width = W_WIDTH(curwin) - curbuf->b_p_wm; + if (width <= 0) + width = 80; + } + + if (u_save((linenr_T)(eap->line1 - 1), (linenr_T)(eap->line2 + 1)) == FAIL) + return; + + for (curwin->w_cursor.lnum = eap->line1; + curwin->w_cursor.lnum <= eap->line2; ++curwin->w_cursor.lnum) { + if (eap->cmdidx == CMD_left) /* left align */ + new_indent = indent; + else { + has_tab = FALSE; /* avoid uninit warnings */ + len = linelen(eap->cmdidx == CMD_right ? &has_tab + : NULL) - get_indent(); + + if (len <= 0) /* skip blank lines */ + continue; + + if (eap->cmdidx == CMD_center) + new_indent = (width - len) / 2; + else { + new_indent = width - len; /* right align */ + + /* + * Make sure that embedded TABs don't make the text go too far + * to the right. + */ + if (has_tab) + while (new_indent > 0) { + (void)set_indent(new_indent, 0); + if (linelen(NULL) <= width) { + /* + * Now try to move the line as much as possible to + * the right. Stop when it moves too far. + */ + do + (void)set_indent(++new_indent, 0); + while (linelen(NULL) <= width); + --new_indent; + break; + } + --new_indent; + } + } + } + if (new_indent < 0) + new_indent = 0; + (void)set_indent(new_indent, 0); /* set indent */ + } + changed_lines(eap->line1, 0, eap->line2 + 1, 0L); + curwin->w_cursor = save_curpos; + beginline(BL_WHITE | BL_FIX); +} + +/* + * Get the length of the current line, excluding trailing white space. + */ +static int linelen(has_tab) +int *has_tab; +{ + char_u *line; + char_u *first; + char_u *last; + int save; + int len; + + /* find the first non-blank character */ + line = ml_get_curline(); + first = skipwhite(line); + + /* find the character after the last non-blank character */ + for (last = first + STRLEN(first); + last > first && vim_iswhite(last[-1]); --last) + ; + save = *last; + *last = NUL; + len = linetabsize(line); /* get line length */ + if (has_tab != NULL) /* check for embedded TAB */ + *has_tab = (vim_strrchr(first, TAB) != NULL); + *last = save; + + return len; +} + +/* Buffer for two lines used during sorting. They are allocated to + * contain the longest line being sorted. */ +static char_u *sortbuf1; +static char_u *sortbuf2; + +static int sort_ic; /* ignore case */ +static int sort_nr; /* sort on number */ +static int sort_rx; /* sort on regex instead of skipping it */ + +static int sort_abort; /* flag to indicate if sorting has been interrupted */ + +/* Struct to store info to be sorted. */ +typedef struct { + linenr_T lnum; /* line number */ + long start_col_nr; /* starting column number or number */ + long end_col_nr; /* ending column number */ +} sorti_T; + +static int +sort_compare __ARGS((const void *s1, const void *s2)); + +static int sort_compare(s1, s2) +const void *s1; +const void *s2; +{ + sorti_T l1 = *(sorti_T *)s1; + sorti_T l2 = *(sorti_T *)s2; + int result = 0; + + /* If the user interrupts, there's no way to stop qsort() immediately, but + * if we return 0 every time, qsort will assume it's done sorting and + * exit. */ + if (sort_abort) + return 0; + fast_breakcheck(); + if (got_int) + sort_abort = TRUE; + + /* When sorting numbers "start_col_nr" is the number, not the column + * number. */ + if (sort_nr) + result = l1.start_col_nr == l2.start_col_nr ? 0 + : l1.start_col_nr > l2.start_col_nr ? 1 : -1; + else { + /* We need to copy one line into "sortbuf1", because there is no + * guarantee that the first pointer becomes invalid when obtaining the + * second one. */ + STRNCPY(sortbuf1, ml_get(l1.lnum) + l1.start_col_nr, + l1.end_col_nr - l1.start_col_nr + 1); + sortbuf1[l1.end_col_nr - l1.start_col_nr] = 0; + STRNCPY(sortbuf2, ml_get(l2.lnum) + l2.start_col_nr, + l2.end_col_nr - l2.start_col_nr + 1); + sortbuf2[l2.end_col_nr - l2.start_col_nr] = 0; + + result = sort_ic ? STRICMP(sortbuf1, sortbuf2) + : STRCMP(sortbuf1, sortbuf2); + } + + /* If two lines have the same value, preserve the original line order. */ + if (result == 0) + return (int)(l1.lnum - l2.lnum); + return result; +} + +/* + * ":sort". + */ +void ex_sort(eap) +exarg_T *eap; +{ + regmatch_T regmatch; + int len; + linenr_T lnum; + long maxlen = 0; + sorti_T *nrs; + size_t count = (size_t)(eap->line2 - eap->line1 + 1); + size_t i; + char_u *p; + char_u *s; + char_u *s2; + char_u c; /* temporary character storage */ + int unique = FALSE; + long deleted; + colnr_T start_col; + colnr_T end_col; + int sort_oct; /* sort on octal number */ + int sort_hex; /* sort on hex number */ + + /* Sorting one line is really quick! */ + if (count <= 1) + return; + + if (u_save((linenr_T)(eap->line1 - 1), (linenr_T)(eap->line2 + 1)) == FAIL) + return; + sortbuf1 = NULL; + sortbuf2 = NULL; + regmatch.regprog = NULL; + nrs = (sorti_T *)lalloc((long_u)(count * sizeof(sorti_T)), TRUE); + if (nrs == NULL) + goto sortend; + + sort_abort = sort_ic = sort_rx = sort_nr = sort_oct = sort_hex = 0; + + for (p = eap->arg; *p != NUL; ++p) { + if (vim_iswhite(*p)) + ; + else if (*p == 'i') + sort_ic = TRUE; + else if (*p == 'r') + sort_rx = TRUE; + else if (*p == 'n') + sort_nr = 2; + else if (*p == 'o') + sort_oct = 2; + else if (*p == 'x') + sort_hex = 2; + else if (*p == 'u') + unique = TRUE; + else if (*p == '"') /* comment start */ + break; + else if (check_nextcmd(p) != NULL) { + eap->nextcmd = check_nextcmd(p); + break; + } else if (!ASCII_ISALPHA(*p) && regmatch.regprog == NULL) { + s = skip_regexp(p + 1, *p, TRUE, NULL); + if (*s != *p) { + EMSG(_(e_invalpat)); + goto sortend; + } + *s = NUL; + /* Use last search pattern if sort pattern is empty. */ + if (s == p + 1) { + if (last_search_pat() == NULL) { + EMSG(_(e_noprevre)); + goto sortend; + } + regmatch.regprog = vim_regcomp(last_search_pat(), RE_MAGIC); + } else + regmatch.regprog = vim_regcomp(p + 1, RE_MAGIC); + if (regmatch.regprog == NULL) + goto sortend; + p = s; /* continue after the regexp */ + regmatch.rm_ic = p_ic; + } else { + EMSG2(_(e_invarg2), p); + goto sortend; + } + } + + /* Can only have one of 'n', 'o' and 'x'. */ + if (sort_nr + sort_oct + sort_hex > 2) { + EMSG(_(e_invarg)); + goto sortend; + } + + /* From here on "sort_nr" is used as a flag for any number sorting. */ + sort_nr += sort_oct + sort_hex; + + /* + * Make an array with all line numbers. This avoids having to copy all + * the lines into allocated memory. + * When sorting on strings "start_col_nr" is the offset in the line, for + * numbers sorting it's the number to sort on. This means the pattern + * matching and number conversion only has to be done once per line. + * Also get the longest line length for allocating "sortbuf". + */ + for (lnum = eap->line1; lnum <= eap->line2; ++lnum) { + s = ml_get(lnum); + len = (int)STRLEN(s); + if (maxlen < len) + maxlen = len; + + start_col = 0; + end_col = len; + if (regmatch.regprog != NULL && vim_regexec(®match, s, 0)) { + if (sort_rx) { + start_col = (colnr_T)(regmatch.startp[0] - s); + end_col = (colnr_T)(regmatch.endp[0] - s); + } else + start_col = (colnr_T)(regmatch.endp[0] - s); + } else if (regmatch.regprog != NULL) + end_col = 0; + + if (sort_nr) { + /* Make sure vim_str2nr doesn't read any digits past the end + * of the match, by temporarily terminating the string there */ + s2 = s + end_col; + c = *s2; + *s2 = NUL; + /* Sorting on number: Store the number itself. */ + p = s + start_col; + if (sort_hex) + s = skiptohex(p); + else + s = skiptodigit(p); + if (s > p && s[-1] == '-') + --s; /* include preceding negative sign */ + if (*s == NUL) + /* empty line should sort before any number */ + nrs[lnum - eap->line1].start_col_nr = -MAXLNUM; + else + vim_str2nr(s, NULL, NULL, sort_oct, sort_hex, + &nrs[lnum - eap->line1].start_col_nr, NULL); + *s2 = c; + } else { + /* Store the column to sort at. */ + nrs[lnum - eap->line1].start_col_nr = start_col; + nrs[lnum - eap->line1].end_col_nr = end_col; + } + + nrs[lnum - eap->line1].lnum = lnum; + + if (regmatch.regprog != NULL) + fast_breakcheck(); + if (got_int) + goto sortend; + } + + /* Allocate a buffer that can hold the longest line. */ + sortbuf1 = alloc((unsigned)maxlen + 1); + if (sortbuf1 == NULL) + goto sortend; + sortbuf2 = alloc((unsigned)maxlen + 1); + if (sortbuf2 == NULL) + goto sortend; + + /* Sort the array of line numbers. Note: can't be interrupted! */ + qsort((void *)nrs, count, sizeof(sorti_T), sort_compare); + + if (sort_abort) + goto sortend; + + /* Insert the lines in the sorted order below the last one. */ + lnum = eap->line2; + for (i = 0; i < count; ++i) { + s = ml_get(nrs[eap->forceit ? count - i - 1 : i].lnum); + if (!unique || i == 0 + || (sort_ic ? STRICMP(s, sortbuf1) : STRCMP(s, sortbuf1)) != 0) { + if (ml_append(lnum++, s, (colnr_T)0, FALSE) == FAIL) + break; + if (unique) + STRCPY(sortbuf1, s); + } + fast_breakcheck(); + if (got_int) + goto sortend; + } + + /* delete the original lines if appending worked */ + if (i == count) + for (i = 0; i < count; ++i) + ml_delete(eap->line1, FALSE); + else + count = 0; + + /* Adjust marks for deleted (or added) lines and prepare for displaying. */ + deleted = (long)(count - (lnum - eap->line2)); + if (deleted > 0) + mark_adjust(eap->line2 - deleted, eap->line2, (long)MAXLNUM, -deleted); + else if (deleted < 0) + mark_adjust(eap->line2, MAXLNUM, -deleted, 0L); + changed_lines(eap->line1, 0, eap->line2 + 1, -deleted); + + curwin->w_cursor.lnum = eap->line1; + beginline(BL_WHITE | BL_FIX); + +sortend: + vim_free(nrs); + vim_free(sortbuf1); + vim_free(sortbuf2); + vim_regfree(regmatch.regprog); + if (got_int) + EMSG(_(e_interr)); +} + +/* + * ":retab". + */ +void ex_retab(eap) +exarg_T *eap; +{ + linenr_T lnum; + int got_tab = FALSE; + long num_spaces = 0; + long num_tabs; + long len; + long col; + long vcol; + long start_col = 0; /* For start of white-space string */ + long start_vcol = 0; /* For start of white-space string */ + int temp; + long old_len; + char_u *ptr; + char_u *new_line = (char_u *)1; /* init to non-NULL */ + int did_undo; /* called u_save for current line */ + int new_ts; + int save_list; + linenr_T first_line = 0; /* first changed line */ + linenr_T last_line = 0; /* last changed line */ + + save_list = curwin->w_p_list; + curwin->w_p_list = 0; /* don't want list mode here */ + + new_ts = getdigits(&(eap->arg)); + if (new_ts < 0) { + EMSG(_(e_positive)); + return; + } + if (new_ts == 0) + new_ts = curbuf->b_p_ts; + for (lnum = eap->line1; !got_int && lnum <= eap->line2; ++lnum) { + ptr = ml_get(lnum); + col = 0; + vcol = 0; + did_undo = FALSE; + for (;; ) { + if (vim_iswhite(ptr[col])) { + if (!got_tab && num_spaces == 0) { + /* First consecutive white-space */ + start_vcol = vcol; + start_col = col; + } + if (ptr[col] == ' ') + num_spaces++; + else + got_tab = TRUE; + } else { + if (got_tab || (eap->forceit && num_spaces > 1)) { + /* Retabulate this string of white-space */ + + /* len is virtual length of white string */ + len = num_spaces = vcol - start_vcol; + num_tabs = 0; + if (!curbuf->b_p_et) { + temp = new_ts - (start_vcol % new_ts); + if (num_spaces >= temp) { + num_spaces -= temp; + num_tabs++; + } + num_tabs += num_spaces / new_ts; + num_spaces -= (num_spaces / new_ts) * new_ts; + } + if (curbuf->b_p_et || got_tab || + (num_spaces + num_tabs < len)) { + if (did_undo == FALSE) { + did_undo = TRUE; + if (u_save((linenr_T)(lnum - 1), + (linenr_T)(lnum + 1)) == FAIL) { + new_line = NULL; /* flag out-of-memory */ + break; + } + } + + /* len is actual number of white characters used */ + len = num_spaces + num_tabs; + old_len = (long)STRLEN(ptr); + new_line = lalloc(old_len - col + start_col + len + 1, + TRUE); + if (new_line == NULL) + break; + if (start_col > 0) + mch_memmove(new_line, ptr, (size_t)start_col); + mch_memmove(new_line + start_col + len, + ptr + col, (size_t)(old_len - col + 1)); + ptr = new_line + start_col; + for (col = 0; col < len; col++) + ptr[col] = (col < num_tabs) ? '\t' : ' '; + ml_replace(lnum, new_line, FALSE); + if (first_line == 0) + first_line = lnum; + last_line = lnum; + ptr = new_line; + col = start_col + len; + } + } + got_tab = FALSE; + num_spaces = 0; + } + if (ptr[col] == NUL) + break; + vcol += chartabsize(ptr + col, (colnr_T)vcol); + if (has_mbyte) + col += (*mb_ptr2len)(ptr + col); + else + ++col; + } + if (new_line == NULL) /* out of memory */ + break; + line_breakcheck(); + } + if (got_int) + EMSG(_(e_interr)); + + if (curbuf->b_p_ts != new_ts) + redraw_curbuf_later(NOT_VALID); + if (first_line != 0) + changed_lines(first_line, 0, last_line + 1, 0L); + + curwin->w_p_list = save_list; /* restore 'list' */ + + curbuf->b_p_ts = new_ts; + coladvance(curwin->w_curswant); + + u_clearline(); +} + +/* + * :move command - move lines line1-line2 to line dest + * + * return FAIL for failure, OK otherwise + */ +int do_move(line1, line2, dest) +linenr_T line1; +linenr_T line2; +linenr_T dest; +{ + char_u *str; + linenr_T l; + linenr_T extra; /* Num lines added before line1 */ + linenr_T num_lines; /* Num lines moved */ + linenr_T last_line; /* Last line in file after adding new text */ + + if (dest >= line1 && dest < line2) { + EMSG(_("E134: Move lines into themselves")); + return FAIL; + } + + num_lines = line2 - line1 + 1; + + /* + * First we copy the old text to its new location -- webb + * Also copy the flag that ":global" command uses. + */ + if (u_save(dest, dest + 1) == FAIL) + return FAIL; + for (extra = 0, l = line1; l <= line2; l++) { + str = vim_strsave(ml_get(l + extra)); + if (str != NULL) { + ml_append(dest + l - line1, str, (colnr_T)0, FALSE); + vim_free(str); + if (dest < line1) + extra++; + } + } + + /* + * Now we must be careful adjusting our marks so that we don't overlap our + * mark_adjust() calls. + * + * We adjust the marks within the old text so that they refer to the + * last lines of the file (temporarily), because we know no other marks + * will be set there since these line numbers did not exist until we added + * our new lines. + * + * Then we adjust the marks on lines between the old and new text positions + * (either forwards or backwards). + * + * And Finally we adjust the marks we put at the end of the file back to + * their final destination at the new text position -- webb + */ + last_line = curbuf->b_ml.ml_line_count; + mark_adjust(line1, line2, last_line - line2, 0L); + changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines); + if (dest >= line2) { + mark_adjust(line2 + 1, dest, -num_lines, 0L); + curbuf->b_op_start.lnum = dest - num_lines + 1; + curbuf->b_op_end.lnum = dest; + } else { + mark_adjust(dest + 1, line1 - 1, num_lines, 0L); + curbuf->b_op_start.lnum = dest + 1; + curbuf->b_op_end.lnum = dest + num_lines; + } + curbuf->b_op_start.col = curbuf->b_op_end.col = 0; + mark_adjust(last_line - num_lines + 1, last_line, + -(last_line - dest - extra), 0L); + changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra); + + /* + * Now we delete the original text -- webb + */ + if (u_save(line1 + extra - 1, line2 + extra + 1) == FAIL) + return FAIL; + + for (l = line1; l <= line2; l++) + ml_delete(line1 + extra, TRUE); + + if (!global_busy && num_lines > p_report) { + if (num_lines == 1) + MSG(_("1 line moved")); + else + smsg((char_u *)_("%ld lines moved"), num_lines); + } + + /* + * Leave the cursor on the last of the moved lines. + */ + if (dest >= line1) + curwin->w_cursor.lnum = dest; + else + curwin->w_cursor.lnum = dest + (line2 - line1) + 1; + + if (line1 < dest) { + dest += num_lines + 1; + last_line = curbuf->b_ml.ml_line_count; + if (dest > last_line + 1) + dest = last_line + 1; + changed_lines(line1, 0, dest, 0L); + } else + changed_lines(dest + 1, 0, line1 + num_lines, 0L); + + return OK; +} + +/* + * ":copy" + */ +void ex_copy(line1, line2, n) +linenr_T line1; +linenr_T line2; +linenr_T n; +{ + linenr_T count; + char_u *p; + + count = line2 - line1 + 1; + curbuf->b_op_start.lnum = n + 1; + curbuf->b_op_end.lnum = n + count; + curbuf->b_op_start.col = curbuf->b_op_end.col = 0; + + /* + * there are three situations: + * 1. destination is above line1 + * 2. destination is between line1 and line2 + * 3. destination is below line2 + * + * n = destination (when starting) + * curwin->w_cursor.lnum = destination (while copying) + * line1 = start of source (while copying) + * line2 = end of source (while copying) + */ + if (u_save(n, n + 1) == FAIL) + return; + + curwin->w_cursor.lnum = n; + while (line1 <= line2) { + /* need to use vim_strsave() because the line will be unlocked within + * ml_append() */ + p = vim_strsave(ml_get(line1)); + if (p != NULL) { + ml_append(curwin->w_cursor.lnum, p, (colnr_T)0, FALSE); + vim_free(p); + } + /* situation 2: skip already copied lines */ + if (line1 == n) + line1 = curwin->w_cursor.lnum; + ++line1; + if (curwin->w_cursor.lnum < line1) + ++line1; + if (curwin->w_cursor.lnum < line2) + ++line2; + ++curwin->w_cursor.lnum; + } + + appended_lines_mark(n, count); + + msgmore((long)count); +} + +static char_u *prevcmd = NULL; /* the previous command */ + +#if defined(EXITFREE) || defined(PROTO) +void free_prev_shellcmd() { + vim_free(prevcmd); +} + +#endif + +/* + * Handle the ":!cmd" command. Also for ":r !cmd" and ":w !cmd" + * Bangs in the argument are replaced with the previously entered command. + * Remember the argument. + */ +void do_bang(addr_count, eap, forceit, do_in, do_out) +int addr_count; +exarg_T *eap; +int forceit; +int do_in, do_out; +{ + char_u *arg = eap->arg; /* command */ + linenr_T line1 = eap->line1; /* start of range */ + linenr_T line2 = eap->line2; /* end of range */ + char_u *newcmd = NULL; /* the new command */ + int free_newcmd = FALSE; /* need to free() newcmd */ + int ins_prevcmd; + char_u *t; + char_u *p; + char_u *trailarg; + int len; + int scroll_save = msg_scroll; + + /* + * Disallow shell commands for "rvim". + * Disallow shell commands from .exrc and .vimrc in current directory for + * security reasons. + */ + if (check_restricted() || check_secure()) + return; + + if (addr_count == 0) { /* :! */ + msg_scroll = FALSE; /* don't scroll here */ + autowrite_all(); + msg_scroll = scroll_save; + } + + /* + * Try to find an embedded bang, like in :! ! [args] + * (:!! is indicated by the 'forceit' variable) + */ + ins_prevcmd = forceit; + trailarg = arg; + do { + len = (int)STRLEN(trailarg) + 1; + if (newcmd != NULL) + len += (int)STRLEN(newcmd); + if (ins_prevcmd) { + if (prevcmd == NULL) { + EMSG(_(e_noprev)); + vim_free(newcmd); + return; + } + len += (int)STRLEN(prevcmd); + } + if ((t = alloc((unsigned)len)) == NULL) { + vim_free(newcmd); + return; + } + *t = NUL; + if (newcmd != NULL) + STRCAT(t, newcmd); + if (ins_prevcmd) + STRCAT(t, prevcmd); + p = t + STRLEN(t); + STRCAT(t, trailarg); + vim_free(newcmd); + newcmd = t; + + /* + * Scan the rest of the argument for '!', which is replaced by the + * previous command. "\!" is replaced by "!" (this is vi compatible). + */ + trailarg = NULL; + while (*p) { + if (*p == '!') { + if (p > newcmd && p[-1] == '\\') + STRMOVE(p - 1, p); + else { + trailarg = p; + *trailarg++ = NUL; + ins_prevcmd = TRUE; + break; + } + } + ++p; + } + } while (trailarg != NULL); + + vim_free(prevcmd); + prevcmd = newcmd; + + if (bangredo) { /* put cmd in redo buffer for ! command */ + AppendToRedobuffLit(prevcmd, -1); + AppendToRedobuff((char_u *)"\n"); + bangredo = FALSE; + } + /* + * Add quotes around the command, for shells that need them. + */ + if (*p_shq != NUL) { + newcmd = alloc((unsigned)(STRLEN(prevcmd) + 2 * STRLEN(p_shq) + 1)); + if (newcmd == NULL) + return; + STRCPY(newcmd, p_shq); + STRCAT(newcmd, prevcmd); + STRCAT(newcmd, p_shq); + free_newcmd = TRUE; + } + if (addr_count == 0) { /* :! */ + /* echo the command */ + msg_start(); + msg_putchar(':'); + msg_putchar('!'); + msg_outtrans(newcmd); + msg_clr_eos(); + windgoto(msg_row, msg_col); + + do_shell(newcmd, 0); + } else { /* :range! */ + /* Careful: This may recursively call do_bang() again! (because of + * autocommands) */ + do_filter(line1, line2, eap, newcmd, do_in, do_out); + apply_autocmds(EVENT_SHELLFILTERPOST, NULL, NULL, FALSE, curbuf); + } + if (free_newcmd) + vim_free(newcmd); +} + +/* + * do_filter: filter lines through a command given by the user + * + * We mostly use temp files and the call_shell() routine here. This would + * normally be done using pipes on a UNIX machine, but this is more portable + * to non-unix machines. The call_shell() routine needs to be able + * to deal with redirection somehow, and should handle things like looking + * at the PATH env. variable, and adding reasonable extensions to the + * command name given by the user. All reasonable versions of call_shell() + * do this. + * Alternatively, if on Unix and redirecting input or output, but not both, + * and the 'shelltemp' option isn't set, use pipes. + * We use input redirection if do_in is TRUE. + * We use output redirection if do_out is TRUE. + */ +static void do_filter(line1, line2, eap, cmd, do_in, do_out) +linenr_T line1, line2; +exarg_T *eap; /* for forced 'ff' and 'fenc' */ +char_u *cmd; +int do_in, do_out; +{ + char_u *itmp = NULL; + char_u *otmp = NULL; + linenr_T linecount; + linenr_T read_linecount; + pos_T cursor_save; + char_u *cmd_buf; + buf_T *old_curbuf = curbuf; + int shell_flags = 0; + + if (*cmd == NUL) /* no filter command */ + return; + + + cursor_save = curwin->w_cursor; + linecount = line2 - line1 + 1; + curwin->w_cursor.lnum = line1; + curwin->w_cursor.col = 0; + changed_line_abv_curs(); + invalidate_botline(); + + /* + * When using temp files: + * 1. * Form temp file names + * 2. * Write the lines to a temp file + * 3. Run the filter command on the temp file + * 4. * Read the output of the command into the buffer + * 5. * Delete the original lines to be filtered + * 6. * Remove the temp files + * + * When writing the input with a pipe or when catching the output with a + * pipe only need to do 3. + */ + + if (do_out) + shell_flags |= SHELL_DOOUT; + + if ((do_in && (itmp = vim_tempname('i')) == NULL) + || (do_out && (otmp = vim_tempname('o')) == NULL)) { + EMSG(_(e_notmp)); + goto filterend; + } + + /* + * The writing and reading of temp files will not be shown. + * Vi also doesn't do this and the messages are not very informative. + */ + ++no_wait_return; /* don't call wait_return() while busy */ + if (itmp != NULL && buf_write(curbuf, itmp, NULL, line1, line2, eap, + FALSE, FALSE, FALSE, TRUE) == FAIL) { + msg_putchar('\n'); /* keep message from buf_write() */ + --no_wait_return; + if (!aborting()) + (void)EMSG2(_(e_notcreate), itmp); /* will call wait_return */ + goto filterend; + } + if (curbuf != old_curbuf) + goto filterend; + + if (!do_out) + msg_putchar('\n'); + + /* Create the shell command in allocated memory. */ + cmd_buf = make_filter_cmd(cmd, itmp, otmp); + if (cmd_buf == NULL) + goto filterend; + + windgoto((int)Rows - 1, 0); + cursor_on(); + + /* + * When not redirecting the output the command can write anything to the + * screen. If 'shellredir' is equal to ">", screen may be messed up by + * stderr output of external command. Clear the screen later. + * If do_in is FALSE, this could be something like ":r !cat", which may + * also mess up the screen, clear it later. + */ + if (!do_out || STRCMP(p_srr, ">") == 0 || !do_in) + redraw_later_clear(); + + if (do_out) { + if (u_save((linenr_T)(line2), (linenr_T)(line2 + 1)) == FAIL) { + vim_free(cmd_buf); + goto error; + } + redraw_curbuf_later(VALID); + } + read_linecount = curbuf->b_ml.ml_line_count; + + /* + * When call_shell() fails wait_return() is called to give the user a + * chance to read the error messages. Otherwise errors are ignored, so you + * can see the error messages from the command that appear on stdout; use + * 'u' to fix the text + * Switch to cooked mode when not redirecting stdin, avoids that something + * like ":r !cat" hangs. + * Pass on the SHELL_DOOUT flag when the output is being redirected. + */ + if (call_shell(cmd_buf, SHELL_FILTER | SHELL_COOKED | shell_flags)) { + redraw_later_clear(); + wait_return(FALSE); + } + vim_free(cmd_buf); + + did_check_timestamps = FALSE; + need_check_timestamps = TRUE; + + /* When interrupting the shell command, it may still have produced some + * useful output. Reset got_int here, so that readfile() won't cancel + * reading. */ + ui_breakcheck(); + got_int = FALSE; + + if (do_out) { + if (otmp != NULL) { + if (readfile(otmp, NULL, line2, (linenr_T)0, (linenr_T)MAXLNUM, + eap, READ_FILTER) == FAIL) { + if (!aborting()) { + msg_putchar('\n'); + EMSG2(_(e_notread), otmp); + } + goto error; + } + if (curbuf != old_curbuf) + goto filterend; + } + + read_linecount = curbuf->b_ml.ml_line_count - read_linecount; + + if (shell_flags & SHELL_READ) { + curbuf->b_op_start.lnum = line2 + 1; + curbuf->b_op_end.lnum = curwin->w_cursor.lnum; + appended_lines_mark(line2, read_linecount); + } + + if (do_in) { + if (cmdmod.keepmarks || vim_strchr(p_cpo, CPO_REMMARK) == NULL) { + if (read_linecount >= linecount) + /* move all marks from old lines to new lines */ + mark_adjust(line1, line2, linecount, 0L); + else { + /* move marks from old lines to new lines, delete marks + * that are in deleted lines */ + mark_adjust(line1, line1 + read_linecount - 1, + linecount, 0L); + mark_adjust(line1 + read_linecount, line2, MAXLNUM, 0L); + } + } + + /* + * Put cursor on first filtered line for ":range!cmd". + * Adjust '[ and '] (set by buf_write()). + */ + curwin->w_cursor.lnum = line1; + del_lines(linecount, TRUE); + curbuf->b_op_start.lnum -= linecount; /* adjust '[ */ + curbuf->b_op_end.lnum -= linecount; /* adjust '] */ + write_lnum_adjust(-linecount); /* adjust last line + for next write */ + foldUpdate(curwin, curbuf->b_op_start.lnum, curbuf->b_op_end.lnum); + } else { + /* + * Put cursor on last new line for ":r !cmd". + */ + linecount = curbuf->b_op_end.lnum - curbuf->b_op_start.lnum + 1; + curwin->w_cursor.lnum = curbuf->b_op_end.lnum; + } + + beginline(BL_WHITE | BL_FIX); /* cursor on first non-blank */ + --no_wait_return; + + if (linecount > p_report) { + if (do_in) { + vim_snprintf((char *)msg_buf, sizeof(msg_buf), + _("%ld lines filtered"), (long)linecount); + if (msg(msg_buf) && !msg_scroll) + /* save message to display it after redraw */ + set_keep_msg(msg_buf, 0); + } else + msgmore((long)linecount); + } + } else { +error: + /* put cursor back in same position for ":w !cmd" */ + curwin->w_cursor = cursor_save; + --no_wait_return; + wait_return(FALSE); + } + +filterend: + + if (curbuf != old_curbuf) { + --no_wait_return; + EMSG(_("E135: *Filter* Autocommands must not change current buffer")); + } + if (itmp != NULL) + mch_remove(itmp); + if (otmp != NULL) + mch_remove(otmp); + vim_free(itmp); + vim_free(otmp); +} + +/* + * Call a shell to execute a command. + * When "cmd" is NULL start an interactive shell. + */ +void do_shell(cmd, flags) +char_u *cmd; +int flags; /* may be SHELL_DOOUT when output is redirected */ +{ + buf_T *buf; + int save_nwr; + + /* + * Disallow shell commands for "rvim". + * Disallow shell commands from .exrc and .vimrc in current directory for + * security reasons. + */ + if (check_restricted() || check_secure()) { + msg_end(); + return; + } + + + /* + * For autocommands we want to get the output on the current screen, to + * avoid having to type return below. + */ + msg_putchar('\r'); /* put cursor at start of line */ + if (!autocmd_busy) { + stoptermcap(); + } + msg_putchar('\n'); /* may shift screen one line up */ + + /* warning message before calling the shell */ + if (p_warn + && !autocmd_busy + && msg_silent == 0) + for (buf = firstbuf; buf; buf = buf->b_next) + if (bufIsChanged(buf)) { + MSG_PUTS(_("[No write since last change]\n")); + break; + } + + /* This windgoto is required for when the '\n' resulted in a "delete line + * 1" command to the terminal. */ + if (!swapping_screen()) + windgoto(msg_row, msg_col); + cursor_on(); + (void)call_shell(cmd, SHELL_COOKED | flags); + did_check_timestamps = FALSE; + need_check_timestamps = TRUE; + + /* + * put the message cursor at the end of the screen, avoids wait_return() + * to overwrite the text that the external command showed + */ + if (!swapping_screen()) { + msg_row = Rows - 1; + msg_col = 0; + } + + if (autocmd_busy) { + if (msg_silent == 0) + redraw_later_clear(); + } else { + /* + * For ":sh" there is no need to call wait_return(), just redraw. + * Also for the Win32 GUI (the output is in a console window). + * Otherwise there is probably text on the screen that the user wants + * to read before redrawing, so call wait_return(). + */ + if (cmd == NULL + ) { + if (msg_silent == 0) + redraw_later_clear(); + need_wait_return = FALSE; + } else { + /* + * If we switch screens when starttermcap() is called, we really + * want to wait for "hit return to continue". + */ + save_nwr = no_wait_return; + if (swapping_screen()) + no_wait_return = FALSE; + wait_return(msg_silent == 0); + no_wait_return = save_nwr; + } + + starttermcap(); /* start termcap if not done by wait_return() */ + + /* + * In an Amiga window redrawing is caused by asking the window size. + * If we got an interrupt this will not work. The chance that the + * window size is wrong is very small, but we need to redraw the + * screen. Don't do this if ':' hit in wait_return(). THIS IS UGLY + * but it saves an extra redraw. + */ + } + + /* display any error messages now */ + display_errors(); + + apply_autocmds(EVENT_SHELLCMDPOST, NULL, NULL, FALSE, curbuf); +} + +/* + * Create a shell command from a command string, input redirection file and + * output redirection file. + * Returns an allocated string with the shell command, or NULL for failure. + */ +char_u * make_filter_cmd(cmd, itmp, otmp) +char_u *cmd; /* command */ +char_u *itmp; /* NULL or name of input file */ +char_u *otmp; /* NULL or name of output file */ +{ + char_u *buf; + long_u len; + + len = (long_u)STRLEN(cmd) + 3; /* "()" + NUL */ + if (itmp != NULL) + len += (long_u)STRLEN(itmp) + 9; /* " { < " + " } " */ + if (otmp != NULL) + len += (long_u)STRLEN(otmp) + (long_u)STRLEN(p_srr) + 2; /* " " */ + buf = lalloc(len, TRUE); + if (buf == NULL) + return NULL; + +#if (defined(UNIX) && !defined(ARCHIE)) || defined(OS2) + /* + * Put braces around the command (for concatenated commands) when + * redirecting input and/or output. + */ + if (itmp != NULL || otmp != NULL) + vim_snprintf((char *)buf, len, "(%s)", (char *)cmd); + else + STRCPY(buf, cmd); + if (itmp != NULL) { + STRCAT(buf, " < "); + STRCAT(buf, itmp); + } +#else + /* + * for shells that don't understand braces around commands, at least allow + * the use of commands in a pipe. + */ + STRCPY(buf, cmd); + if (itmp != NULL) { + char_u *p; + + /* + * If there is a pipe, we have to put the '<' in front of it. + * Don't do this when 'shellquote' is not empty, otherwise the + * redirection would be inside the quotes. + */ + if (*p_shq == NUL) { + p = vim_strchr(buf, '|'); + if (p != NULL) + *p = NUL; + } + STRCAT(buf, " <"); /* " < " causes problems on Amiga */ + STRCAT(buf, itmp); + if (*p_shq == NUL) { + p = vim_strchr(cmd, '|'); + if (p != NULL) { + STRCAT(buf, " "); /* insert a space before the '|' for DOS */ + STRCAT(buf, p); + } + } + } +#endif + if (otmp != NULL) + append_redir(buf, (int)len, p_srr, otmp); + + return buf; +} + +/* + * Append output redirection for file "fname" to the end of string buffer + * "buf[buflen]" + * Works with the 'shellredir' and 'shellpipe' options. + * The caller should make sure that there is enough room: + * STRLEN(opt) + STRLEN(fname) + 3 + */ +void append_redir(buf, buflen, opt, fname) +char_u *buf; +int buflen; +char_u *opt; +char_u *fname; +{ + char_u *p; + char_u *end; + + end = buf + STRLEN(buf); + /* find "%s" */ + for (p = opt; (p = vim_strchr(p, '%')) != NULL; ++p) { + if (p[1] == 's') /* found %s */ + break; + if (p[1] == '%') /* skip %% */ + ++p; + } + if (p != NULL) { + *end = ' '; /* not really needed? Not with sh, ksh or bash */ + vim_snprintf((char *)end + 1, (size_t)(buflen - (end + 1 - buf)), + (char *)opt, (char *)fname); + } else + vim_snprintf((char *)end, (size_t)(buflen - (end - buf)), + " %s %s", + (char *)opt, (char *)fname); +} + + +static int no_viminfo __ARGS((void)); +static int viminfo_errcnt; + +static int no_viminfo() { + /* "vim -i NONE" does not read or write a viminfo file */ + return use_viminfo != NULL && STRCMP(use_viminfo, "NONE") == 0; +} + +/* + * Report an error for reading a viminfo file. + * Count the number of errors. When there are more than 10, return TRUE. + */ +int viminfo_error(errnum, message, line) +char *errnum; +char *message; +char_u *line; +{ + vim_snprintf((char *)IObuff, IOSIZE, _("%sviminfo: %s in line: "), + errnum, message); + STRNCAT(IObuff, line, IOSIZE - STRLEN(IObuff) - 1); + if (IObuff[STRLEN(IObuff) - 1] == '\n') + IObuff[STRLEN(IObuff) - 1] = NUL; + emsg(IObuff); + if (++viminfo_errcnt >= 10) { + EMSG(_("E136: viminfo: Too many errors, skipping rest of file")); + return TRUE; + } + return FALSE; +} + +/* + * read_viminfo() -- Read the viminfo file. Registers etc. which are already + * set are not over-written unless "flags" includes VIF_FORCEIT. -- webb + */ +int read_viminfo(file, flags) +char_u *file; /* file name or NULL to use default name */ +int flags; /* VIF_WANT_INFO et al. */ +{ + FILE *fp; + char_u *fname; + + if (no_viminfo()) + return FAIL; + + fname = viminfo_filename(file); /* get file name in allocated buffer */ + if (fname == NULL) + return FAIL; + fp = mch_fopen((char *)fname, READBIN); + + if (p_verbose > 0) { + verbose_enter(); + smsg((char_u *)_("Reading viminfo file \"%s\"%s%s%s"), + fname, + (flags & VIF_WANT_INFO) ? _(" info") : "", + (flags & VIF_WANT_MARKS) ? _(" marks") : "", + (flags & VIF_GET_OLDFILES) ? _(" oldfiles") : "", + fp == NULL ? _(" FAILED") : ""); + verbose_leave(); + } + + vim_free(fname); + if (fp == NULL) + return FAIL; + + viminfo_errcnt = 0; + do_viminfo(fp, NULL, flags); + + fclose(fp); + return OK; +} + +/* + * Write the viminfo file. The old one is read in first so that effectively a + * merge of current info and old info is done. This allows multiple vims to + * run simultaneously, without losing any marks etc. + * If "forceit" is TRUE, then the old file is not read in, and only internal + * info is written to the file. + */ +void write_viminfo(file, forceit) +char_u *file; +int forceit; +{ + char_u *fname; + FILE *fp_in = NULL; /* input viminfo file, if any */ + FILE *fp_out = NULL; /* output viminfo file */ + char_u *tempname = NULL; /* name of temp viminfo file */ + struct stat st_new; /* mch_stat() of potential new file */ + char_u *wp; +#if defined(UNIX) || defined(VMS) + mode_t umask_save; +#endif +#ifdef UNIX + int shortname = FALSE; /* use 8.3 file name */ + struct stat st_old; /* mch_stat() of existing viminfo file */ +#endif + + if (no_viminfo()) + return; + + fname = viminfo_filename(file); /* may set to default if NULL */ + if (fname == NULL) + return; + + fp_in = mch_fopen((char *)fname, READBIN); + if (fp_in == NULL) { + /* if it does exist, but we can't read it, don't try writing */ + if (mch_stat((char *)fname, &st_new) == 0) + goto end; +#if defined(UNIX) || defined(VMS) + /* + * For Unix we create the .viminfo non-accessible for others, + * because it may contain text from non-accessible documents. + */ + umask_save = umask(077); +#endif + fp_out = mch_fopen((char *)fname, WRITEBIN); +#if defined(UNIX) || defined(VMS) + (void)umask(umask_save); +#endif + } else { + /* + * There is an existing viminfo file. Create a temporary file to + * write the new viminfo into, in the same directory as the + * existing viminfo file, which will be renamed later. + */ +#ifdef UNIX + /* + * For Unix we check the owner of the file. It's not very nice to + * overwrite a user's viminfo file after a "su root", with a + * viminfo file that the user can't read. + */ + st_old.st_dev = (dev_t)0; + st_old.st_ino = 0; + st_old.st_mode = 0600; + if (mch_stat((char *)fname, &st_old) == 0 + && getuid() != ROOT_UID + && !(st_old.st_uid == getuid() + ? (st_old.st_mode & 0200) + : (st_old.st_gid == getgid() + ? (st_old.st_mode & 0020) + : (st_old.st_mode & 0002)))) { + int tt = msg_didany; + + /* avoid a wait_return for this message, it's annoying */ + EMSG2(_("E137: Viminfo file is not writable: %s"), fname); + msg_didany = tt; + fclose(fp_in); + goto end; + } +#endif + + /* + * Make tempname. + * May try twice: Once normal and once with shortname set, just in + * case somebody puts his viminfo file in an 8.3 filesystem. + */ + for (;; ) { + tempname = buf_modname( +#ifdef UNIX + shortname, +#else +# ifdef SHORT_FNAME + TRUE, +# else + FALSE, +# endif +#endif + fname, + (char_u *)".tmp", + FALSE); + if (tempname == NULL) /* out of memory */ + break; + + /* + * Check if tempfile already exists. Never overwrite an + * existing file! + */ + if (mch_stat((char *)tempname, &st_new) == 0) { +#ifdef UNIX + /* + * Check if tempfile is same as original file. May happen + * when modname() gave the same file back. E.g. silly + * link, or file name-length reached. Try again with + * shortname set. + */ + if (!shortname && st_new.st_dev == st_old.st_dev + && st_new.st_ino == st_old.st_ino) { + vim_free(tempname); + tempname = NULL; + shortname = TRUE; + continue; + } +#endif + /* + * Try another name. Change one character, just before + * the extension. This should also work for an 8.3 + * file name, when after adding the extension it still is + * the same file as the original. + */ + wp = tempname + STRLEN(tempname) - 5; + if (wp < gettail(tempname)) /* empty file name? */ + wp = gettail(tempname); + for (*wp = 'z'; mch_stat((char *)tempname, &st_new) == 0; + --*wp) { + /* + * They all exist? Must be something wrong! Don't + * write the viminfo file then. + */ + if (*wp == 'a') { + vim_free(tempname); + tempname = NULL; + break; + } + } + } + break; + } + + if (tempname != NULL) { + int fd; + + /* Use mch_open() to be able to use O_NOFOLLOW and set file + * protection: + * Unix: same as original file, but strip s-bit. Reset umask to + * avoid it getting in the way. + * Others: r&w for user only. */ +# ifdef UNIX + umask_save = umask(0); + fd = mch_open((char *)tempname, + O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, + (int)((st_old.st_mode & 0777) | 0600)); + (void)umask(umask_save); +# else + fd = mch_open((char *)tempname, + O_CREAT|O_EXTRA|O_EXCL|O_WRONLY|O_NOFOLLOW, 0600); +# endif + if (fd < 0) + fp_out = NULL; + else + fp_out = fdopen(fd, WRITEBIN); + + /* + * If we can't create in the same directory, try creating a + * "normal" temp file. + */ + if (fp_out == NULL) { + vim_free(tempname); + if ((tempname = vim_tempname('o')) != NULL) + fp_out = mch_fopen((char *)tempname, WRITEBIN); + } + +#if defined(UNIX) && defined(HAVE_FCHOWN) + /* + * Make sure the owner can read/write it. This only works for + * root. + */ + if (fp_out != NULL) + ignored = fchown(fileno(fp_out), st_old.st_uid, st_old.st_gid); +#endif + } + } + + /* + * Check if the new viminfo file can be written to. + */ + if (fp_out == NULL) { + EMSG2(_("E138: Can't write viminfo file %s!"), + (fp_in == NULL || tempname == NULL) ? fname : tempname); + if (fp_in != NULL) + fclose(fp_in); + goto end; + } + + if (p_verbose > 0) { + verbose_enter(); + smsg((char_u *)_("Writing viminfo file \"%s\""), fname); + verbose_leave(); + } + + viminfo_errcnt = 0; + do_viminfo(fp_in, fp_out, forceit ? 0 : (VIF_WANT_INFO | VIF_WANT_MARKS)); + + fclose(fp_out); /* errors are ignored !? */ + if (fp_in != NULL) { + fclose(fp_in); + + /* + * In case of an error keep the original viminfo file. + * Otherwise rename the newly written file. + */ + if (viminfo_errcnt || vim_rename(tempname, fname) == -1) + mch_remove(tempname); + + } + +end: + vim_free(fname); + vim_free(tempname); +} + +/* + * Get the viminfo file name to use. + * If "file" is given and not empty, use it (has already been expanded by + * cmdline functions). + * Otherwise use "-i file_name", value from 'viminfo' or the default, and + * expand environment variables. + * Returns an allocated string. NULL when out of memory. + */ +static char_u * viminfo_filename(file) +char_u *file; +{ + if (file == NULL || *file == NUL) { + if (use_viminfo != NULL) + file = use_viminfo; + else if ((file = find_viminfo_parameter('n')) == NULL || *file == NUL) { +#ifdef VIMINFO_FILE2 + /* don't use $HOME when not defined (turned into "c:/"!). */ + if (mch_getenv((char_u *)"HOME") == NULL) { + /* don't use $VIM when not available. */ + expand_env((char_u *)"$VIM", NameBuff, MAXPATHL); + if (STRCMP("$VIM", NameBuff) != 0) /* $VIM was expanded */ + file = (char_u *)VIMINFO_FILE2; + else + file = (char_u *)VIMINFO_FILE; + } else +#endif + file = (char_u *)VIMINFO_FILE; + } + expand_env(file, NameBuff, MAXPATHL); + file = NameBuff; + } + return vim_strsave(file); +} + +/* + * do_viminfo() -- Should only be called from read_viminfo() & write_viminfo(). + */ +static void do_viminfo(fp_in, fp_out, flags) +FILE *fp_in; +FILE *fp_out; +int flags; +{ + int count = 0; + int eof = FALSE; + vir_T vir; + int merge = FALSE; + + if ((vir.vir_line = alloc(LSIZE)) == NULL) + return; + vir.vir_fd = fp_in; + vir.vir_conv.vc_type = CONV_NONE; + + if (fp_in != NULL) { + if (flags & VIF_WANT_INFO) { + eof = read_viminfo_up_to_marks(&vir, + flags & VIF_FORCEIT, fp_out != NULL); + merge = TRUE; + } else if (flags != 0) + /* Skip info, find start of marks */ + while (!(eof = viminfo_readline(&vir)) + && vir.vir_line[0] != '>') + ; + } + if (fp_out != NULL) { + /* Write the info: */ + fprintf(fp_out, _("# This viminfo file was generated by Vim %s.\n"), + VIM_VERSION_MEDIUM); + fputs(_("# You may edit it if you're careful!\n\n"), fp_out); + fputs(_("# Value of 'encoding' when this file was written\n"), fp_out); + fprintf(fp_out, "*encoding=%s\n\n", p_enc); + write_viminfo_search_pattern(fp_out); + write_viminfo_sub_string(fp_out); + write_viminfo_history(fp_out, merge); + write_viminfo_registers(fp_out); + write_viminfo_varlist(fp_out); + write_viminfo_filemarks(fp_out); + write_viminfo_bufferlist(fp_out); + count = write_viminfo_marks(fp_out); + } + if (fp_in != NULL + && (flags & (VIF_WANT_MARKS | VIF_GET_OLDFILES | VIF_FORCEIT))) + copy_viminfo_marks(&vir, fp_out, count, eof, flags); + + vim_free(vir.vir_line); + if (vir.vir_conv.vc_type != CONV_NONE) + convert_setup(&vir.vir_conv, NULL, NULL); +} + +/* + * read_viminfo_up_to_marks() -- Only called from do_viminfo(). Reads in the + * first part of the viminfo file which contains everything but the marks that + * are local to a file. Returns TRUE when end-of-file is reached. -- webb + */ +static int read_viminfo_up_to_marks(virp, forceit, writing) +vir_T *virp; +int forceit; +int writing; +{ + int eof; + buf_T *buf; + + prepare_viminfo_history(forceit ? 9999 : 0, writing); + eof = viminfo_readline(virp); + while (!eof && virp->vir_line[0] != '>') { + switch (virp->vir_line[0]) { + /* Characters reserved for future expansion, ignored now */ + case '+': /* "+40 /path/dir file", for running vim without args */ + case '|': /* to be defined */ + case '^': /* to be defined */ + case '<': /* long line - ignored */ + /* A comment or empty line. */ + case NUL: + case '\r': + case '\n': + case '#': + eof = viminfo_readline(virp); + break; + case '*': /* "*encoding=value" */ + eof = viminfo_encoding(virp); + break; + case '!': /* global variable */ + eof = read_viminfo_varlist(virp, writing); + break; + case '%': /* entry for buffer list */ + eof = read_viminfo_bufferlist(virp, writing); + break; + case '"': + eof = read_viminfo_register(virp, forceit); + break; + case '/': /* Search string */ + case '&': /* Substitute search string */ + case '~': /* Last search string, followed by '/' or '&' */ + eof = read_viminfo_search_pattern(virp, forceit); + break; + case '$': + eof = read_viminfo_sub_string(virp, forceit); + break; + case ':': + case '?': + case '=': + case '@': + eof = read_viminfo_history(virp, writing); + break; + case '-': + case '\'': + eof = read_viminfo_filemark(virp, forceit); + break; + default: + if (viminfo_error("E575: ", _("Illegal starting char"), + virp->vir_line)) + eof = TRUE; + else + eof = viminfo_readline(virp); + break; + } + } + + /* Finish reading history items. */ + if (!writing) + finish_viminfo_history(); + + /* Change file names to buffer numbers for fmarks. */ + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + fmarks_check_names(buf); + + return eof; +} + +/* + * Compare the 'encoding' value in the viminfo file with the current value of + * 'encoding'. If different and the 'c' flag is in 'viminfo', setup for + * conversion of text with iconv() in viminfo_readstring(). + */ +static int viminfo_encoding(virp) +vir_T *virp; +{ + char_u *p; + int i; + + if (get_viminfo_parameter('c') != 0) { + p = vim_strchr(virp->vir_line, '='); + if (p != NULL) { + /* remove trailing newline */ + ++p; + for (i = 0; vim_isprintc(p[i]); ++i) + ; + p[i] = NUL; + + convert_setup(&virp->vir_conv, p, p_enc); + } + } + return viminfo_readline(virp); +} + +/* + * Read a line from the viminfo file. + * Returns TRUE for end-of-file; + */ +int viminfo_readline(virp) +vir_T *virp; +{ + return vim_fgets(virp->vir_line, LSIZE, virp->vir_fd); +} + +/* + * check string read from viminfo file + * remove '\n' at the end of the line + * - replace CTRL-V CTRL-V with CTRL-V + * - replace CTRL-V 'n' with '\n' + * + * Check for a long line as written by viminfo_writestring(). + * + * Return the string in allocated memory (NULL when out of memory). + */ +char_u * viminfo_readstring(virp, off, convert) +vir_T *virp; +int off; /* offset for virp->vir_line */ +int convert UNUSED; /* convert the string */ +{ + char_u *retval; + char_u *s, *d; + long len; + + if (virp->vir_line[off] == Ctrl_V && vim_isdigit(virp->vir_line[off + 1])) { + len = atol((char *)virp->vir_line + off + 1); + retval = lalloc(len, TRUE); + if (retval == NULL) { + /* Line too long? File messed up? Skip next line. */ + (void)vim_fgets(virp->vir_line, 10, virp->vir_fd); + return NULL; + } + (void)vim_fgets(retval, (int)len, virp->vir_fd); + s = retval + 1; /* Skip the leading '<' */ + } else { + retval = vim_strsave(virp->vir_line + off); + if (retval == NULL) + return NULL; + s = retval; + } + + /* Change CTRL-V CTRL-V to CTRL-V and CTRL-V n to \n in-place. */ + d = retval; + while (*s != NUL && *s != '\n') { + if (s[0] == Ctrl_V && s[1] != NUL) { + if (s[1] == 'n') + *d++ = '\n'; + else + *d++ = Ctrl_V; + s += 2; + } else + *d++ = *s++; + } + *d = NUL; + + if (convert && virp->vir_conv.vc_type != CONV_NONE && *retval != NUL) { + d = string_convert(&virp->vir_conv, retval, NULL); + if (d != NULL) { + vim_free(retval); + retval = d; + } + } + + return retval; +} + +/* + * write string to viminfo file + * - replace CTRL-V with CTRL-V CTRL-V + * - replace '\n' with CTRL-V 'n' + * - add a '\n' at the end + * + * For a long line: + * - write " CTRL-V \n " in first line + * - write " < \n " in second line + */ +void viminfo_writestring(fd, p) +FILE *fd; +char_u *p; +{ + int c; + char_u *s; + int len = 0; + + for (s = p; *s != NUL; ++s) { + if (*s == Ctrl_V || *s == '\n') + ++len; + ++len; + } + + /* If the string will be too long, write its length and put it in the next + * line. Take into account that some room is needed for what comes before + * the string (e.g., variable name). Add something to the length for the + * '<', NL and trailing NUL. */ + if (len > LSIZE / 2) + fprintf(fd, IF_EB("\026%d\n<", CTRL_V_STR "%d\n<"), len + 3); + + while ((c = *p++) != NUL) { + if (c == Ctrl_V || c == '\n') { + putc(Ctrl_V, fd); + if (c == '\n') + c = 'n'; + } + putc(c, fd); + } + putc('\n', fd); +} + +/* + * Implementation of ":fixdel", also used by get_stty(). + * resulting + * ^? ^H + * not ^? ^? + */ +void do_fixdel(eap) +exarg_T *eap UNUSED; +{ + char_u *p; + + p = find_termcode((char_u *)"kb"); + add_termcode((char_u *)"kD", p != NULL + && *p == DEL ? (char_u *)CTRL_H_STR : DEL_STR, FALSE); +} + +void print_line_no_prefix(lnum, use_number, list) +linenr_T lnum; +int use_number; +int list; +{ + char_u numbuf[30]; + + if (curwin->w_p_nu || use_number) { + vim_snprintf((char *)numbuf, sizeof(numbuf), + "%*ld ", number_width(curwin), (long)lnum); + msg_puts_attr(numbuf, hl_attr(HLF_N)); /* Highlight line nrs */ + } + msg_prt_line(ml_get(lnum), list); +} + +/* + * Print a text line. Also in silent mode ("ex -s"). + */ +void print_line(lnum, use_number, list) +linenr_T lnum; +int use_number; +int list; +{ + int save_silent = silent_mode; + + msg_start(); + silent_mode = FALSE; + info_message = TRUE; /* use mch_msg(), not mch_errmsg() */ + print_line_no_prefix(lnum, use_number, list); + if (save_silent) { + msg_putchar('\n'); + cursor_on(); /* msg_start() switches it off */ + out_flush(); + silent_mode = save_silent; + } + info_message = FALSE; +} + +int rename_buffer(new_fname) +char_u *new_fname; +{ + char_u *fname, *sfname, *xfname; + buf_T *buf; + + buf = curbuf; + apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, FALSE, curbuf); + /* buffer changed, don't change name now */ + if (buf != curbuf) + return FAIL; + if (aborting()) /* autocmds may abort script processing */ + return FAIL; + /* + * The name of the current buffer will be changed. + * A new (unlisted) buffer entry needs to be made to hold the old file + * name, which will become the alternate file name. + * But don't set the alternate file name if the buffer didn't have a + * name. + */ + fname = curbuf->b_ffname; + sfname = curbuf->b_sfname; + xfname = curbuf->b_fname; + curbuf->b_ffname = NULL; + curbuf->b_sfname = NULL; + if (setfname(curbuf, new_fname, NULL, TRUE) == FAIL) { + curbuf->b_ffname = fname; + curbuf->b_sfname = sfname; + return FAIL; + } + curbuf->b_flags |= BF_NOTEDITED; + if (xfname != NULL && *xfname != NUL) { + buf = buflist_new(fname, xfname, curwin->w_cursor.lnum, 0); + if (buf != NULL && !cmdmod.keepalt) + curwin->w_alt_fnum = buf->b_fnum; + } + vim_free(fname); + vim_free(sfname); + apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, FALSE, curbuf); + /* Change directories when the 'acd' option is set. */ + DO_AUTOCHDIR + return OK; +} + +/* + * ":file[!] [fname]". + */ +void ex_file(eap) +exarg_T *eap; +{ + /* ":0file" removes the file name. Check for illegal uses ":3file", + * "0file name", etc. */ + if (eap->addr_count > 0 + && (*eap->arg != NUL + || eap->line2 > 0 + || eap->addr_count > 1)) { + EMSG(_(e_invarg)); + return; + } + + if (*eap->arg != NUL || eap->addr_count == 1) { + if (rename_buffer(eap->arg) == FAIL) + return; + } + /* print full file name if :cd used */ + fileinfo(FALSE, FALSE, eap->forceit); +} + +/* + * ":update". + */ +void ex_update(eap) +exarg_T *eap; +{ + if (curbufIsChanged()) + (void)do_write(eap); +} + +/* + * ":write" and ":saveas". + */ +void ex_write(eap) +exarg_T *eap; +{ + if (eap->usefilter) /* input lines to shell command */ + do_bang(1, eap, FALSE, TRUE, FALSE); + else + (void)do_write(eap); +} + +/* + * write current buffer to file 'eap->arg' + * if 'eap->append' is TRUE, append to the file + * + * if *eap->arg == NUL write to current file + * + * return FAIL for failure, OK otherwise + */ +int do_write(eap) +exarg_T *eap; +{ + int other; + char_u *fname = NULL; /* init to shut up gcc */ + char_u *ffname; + int retval = FAIL; + char_u *free_fname = NULL; + buf_T *alt_buf = NULL; + + if (not_writing()) /* check 'write' option */ + return FAIL; + + ffname = eap->arg; + if (*ffname == NUL) { + if (eap->cmdidx == CMD_saveas) { + EMSG(_(e_argreq)); + goto theend; + } + other = FALSE; + } else { + fname = ffname; + free_fname = fix_fname(ffname); + /* + * When out-of-memory, keep unexpanded file name, because we MUST be + * able to write the file in this situation. + */ + if (free_fname != NULL) + ffname = free_fname; + other = otherfile(ffname); + } + + /* + * If we have a new file, put its name in the list of alternate file names. + */ + if (other) { + if (vim_strchr(p_cpo, CPO_ALTWRITE) != NULL + || eap->cmdidx == CMD_saveas) + alt_buf = setaltfname(ffname, fname, (linenr_T)1); + else + alt_buf = buflist_findname(ffname); + if (alt_buf != NULL && alt_buf->b_ml.ml_mfp != NULL) { + /* Overwriting a file that is loaded in another buffer is not a + * good idea. */ + EMSG(_(e_bufloaded)); + goto theend; + } + } + + /* + * Writing to the current file is not allowed in readonly mode + * and a file name is required. + * "nofile" and "nowrite" buffers cannot be written implicitly either. + */ + if (!other && ( + bt_dontwrite_msg(curbuf) || + check_fname() == FAIL || check_readonly(&eap->forceit, curbuf))) + goto theend; + + if (!other) { + ffname = curbuf->b_ffname; + fname = curbuf->b_fname; + /* + * Not writing the whole file is only allowed with '!'. + */ + if ( (eap->line1 != 1 + || eap->line2 != curbuf->b_ml.ml_line_count) + && !eap->forceit + && !eap->append + && !p_wa) { + if (p_confirm || cmdmod.confirm) { + if (vim_dialog_yesno(VIM_QUESTION, NULL, + (char_u *)_("Write partial file?"), 2) != VIM_YES) + goto theend; + eap->forceit = TRUE; + } else { + EMSG(_("E140: Use ! to write partial buffer")); + goto theend; + } + } + } + + if (check_overwrite(eap, curbuf, fname, ffname, other) == OK) { + if (eap->cmdidx == CMD_saveas && alt_buf != NULL) { + buf_T *was_curbuf = curbuf; + + apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, FALSE, curbuf); + apply_autocmds(EVENT_BUFFILEPRE, NULL, NULL, FALSE, alt_buf); + if (curbuf != was_curbuf || aborting()) { + /* buffer changed, don't change name now */ + retval = FAIL; + goto theend; + } + /* Exchange the file names for the current and the alternate + * buffer. This makes it look like we are now editing the buffer + * under the new name. Must be done before buf_write(), because + * if there is no file name and 'cpo' contains 'F', it will set + * the file name. */ + fname = alt_buf->b_fname; + alt_buf->b_fname = curbuf->b_fname; + curbuf->b_fname = fname; + fname = alt_buf->b_ffname; + alt_buf->b_ffname = curbuf->b_ffname; + curbuf->b_ffname = fname; + fname = alt_buf->b_sfname; + alt_buf->b_sfname = curbuf->b_sfname; + curbuf->b_sfname = fname; + buf_name_changed(curbuf); + apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, FALSE, curbuf); + apply_autocmds(EVENT_BUFFILEPOST, NULL, NULL, FALSE, alt_buf); + if (!alt_buf->b_p_bl) { + alt_buf->b_p_bl = TRUE; + apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, alt_buf); + } + if (curbuf != was_curbuf || aborting()) { + /* buffer changed, don't write the file */ + retval = FAIL; + goto theend; + } + + /* If 'filetype' was empty try detecting it now. */ + if (*curbuf->b_p_ft == NUL) { + if (au_has_group((char_u *)"filetypedetect")) + (void)do_doautocmd((char_u *)"filetypedetect BufRead", + TRUE); + do_modelines(0); + } + + /* Autocommands may have changed buffer names, esp. when + * 'autochdir' is set. */ + fname = curbuf->b_sfname; + } + + retval = buf_write(curbuf, ffname, fname, eap->line1, eap->line2, + eap, eap->append, eap->forceit, TRUE, FALSE); + + /* After ":saveas fname" reset 'readonly'. */ + if (eap->cmdidx == CMD_saveas) { + if (retval == OK) { + curbuf->b_p_ro = FALSE; + redraw_tabline = TRUE; + } + /* Change directories when the 'acd' option is set. */ + DO_AUTOCHDIR + } + } + +theend: + vim_free(free_fname); + return retval; +} + +/* + * Check if it is allowed to overwrite a file. If b_flags has BF_NOTEDITED, + * BF_NEW or BF_READERR, check for overwriting current file. + * May set eap->forceit if a dialog says it's OK to overwrite. + * Return OK if it's OK, FAIL if it is not. + */ +int check_overwrite(eap, buf, fname, ffname, other) +exarg_T *eap; +buf_T *buf; +char_u *fname; /* file name to be used (can differ from + buf->ffname) */ +char_u *ffname; /* full path version of fname */ +int other; /* writing under other name */ +{ + /* + * write to other file or b_flags set or not writing the whole file: + * overwriting only allowed with '!' + */ + if ( (other + || (buf->b_flags & BF_NOTEDITED) + || ((buf->b_flags & BF_NEW) + && vim_strchr(p_cpo, CPO_OVERNEW) == NULL) + || (buf->b_flags & BF_READERR)) + && !p_wa + && !bt_nofile(buf) + && vim_fexists(ffname)) { + if (!eap->forceit && !eap->append) { +#ifdef UNIX + /* with UNIX it is possible to open a directory */ + if (mch_isdir(ffname)) { + EMSG2(_(e_isadir2), ffname); + return FAIL; + } +#endif + if (p_confirm || cmdmod.confirm) { + char_u buff[DIALOG_MSG_SIZE]; + + dialog_msg(buff, _("Overwrite existing file \"%s\"?"), fname); + if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2) != VIM_YES) + return FAIL; + eap->forceit = TRUE; + } else { + EMSG(_(e_exists)); + return FAIL; + } + } + + /* For ":w! filename" check that no swap file exists for "filename". */ + if (other && !emsg_silent) { + char_u *dir; + char_u *p; + int r; + char_u *swapname; + + /* We only try the first entry in 'directory', without checking if + * it's writable. If the "." directory is not writable the write + * will probably fail anyway. + * Use 'shortname' of the current buffer, since there is no buffer + * for the written file. */ + if (*p_dir == NUL) { + dir = alloc(5); + if (dir == NULL) + return FAIL; + STRCPY(dir, "."); + } else { + dir = alloc(MAXPATHL); + if (dir == NULL) + return FAIL; + p = p_dir; + copy_option_part(&p, dir, MAXPATHL, ","); + } + swapname = makeswapname(fname, ffname, curbuf, dir); + vim_free(dir); + r = vim_fexists(swapname); + if (r) { + if (p_confirm || cmdmod.confirm) { + char_u buff[DIALOG_MSG_SIZE]; + + dialog_msg(buff, + _("Swap file \"%s\" exists, overwrite anyway?"), + swapname); + if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2) + != VIM_YES) { + vim_free(swapname); + return FAIL; + } + eap->forceit = TRUE; + } else { + EMSG2(_("E768: Swap file exists: %s (:silent! overrides)"), + swapname); + vim_free(swapname); + return FAIL; + } + } + vim_free(swapname); + } + } + return OK; +} + +/* + * Handle ":wnext", ":wNext" and ":wprevious" commands. + */ +void ex_wnext(eap) +exarg_T *eap; +{ + int i; + + if (eap->cmd[1] == 'n') + i = curwin->w_arg_idx + (int)eap->line2; + else + i = curwin->w_arg_idx - (int)eap->line2; + eap->line1 = 1; + eap->line2 = curbuf->b_ml.ml_line_count; + if (do_write(eap) != FAIL) + do_argfile(eap, i); +} + +/* + * ":wall", ":wqall" and ":xall": Write all changed files (and exit). + */ +void do_wqall(eap) +exarg_T *eap; +{ + buf_T *buf; + int error = 0; + int save_forceit = eap->forceit; + + if (eap->cmdidx == CMD_xall || eap->cmdidx == CMD_wqall) + exiting = TRUE; + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) { + if (bufIsChanged(buf)) { + /* + * Check if there is a reason the buffer cannot be written: + * 1. if the 'write' option is set + * 2. if there is no file name (even after browsing) + * 3. if the 'readonly' is set (even after a dialog) + * 4. if overwriting is allowed (even after a dialog) + */ + if (not_writing()) { + ++error; + break; + } + if (buf->b_ffname == NULL) { + EMSGN(_("E141: No file name for buffer %ld"), (long)buf->b_fnum); + ++error; + } else if (check_readonly(&eap->forceit, buf) + || check_overwrite(eap, buf, buf->b_fname, buf->b_ffname, + FALSE) == FAIL) { + ++error; + } else { + if (buf_write_all(buf, eap->forceit) == FAIL) + ++error; + /* an autocommand may have deleted the buffer */ + if (!buf_valid(buf)) + buf = firstbuf; + } + eap->forceit = save_forceit; /* check_overwrite() may set it */ + } + } + if (exiting) { + if (!error) + getout(0); /* exit Vim */ + not_exiting(); + } +} + +/* + * Check the 'write' option. + * Return TRUE and give a message when it's not st. + */ +int not_writing() { + if (p_write) + return FALSE; + EMSG(_("E142: File not written: Writing is disabled by 'write' option")); + return TRUE; +} + +/* + * Check if a buffer is read-only (either 'readonly' option is set or file is + * read-only). Ask for overruling in a dialog. Return TRUE and give an error + * message when the buffer is readonly. + */ +static int check_readonly(forceit, buf) +int *forceit; +buf_T *buf; +{ + struct stat st; + + /* Handle a file being readonly when the 'readonly' option is set or when + * the file exists and permissions are read-only. + * We will send 0777 to check_file_readonly(), as the "perm" variable is + * important for device checks but not here. */ + if (!*forceit && (buf->b_p_ro + || (mch_stat((char *)buf->b_ffname, &st) >= 0 + && check_file_readonly(buf->b_ffname, 0777)))) { + if ((p_confirm || cmdmod.confirm) && buf->b_fname != NULL) { + char_u buff[DIALOG_MSG_SIZE]; + + if (buf->b_p_ro) + dialog_msg(buff, + _( + "'readonly' option is set for \"%s\".\nDo you wish to write anyway?"), + buf->b_fname); + else + dialog_msg(buff, + _( + "File permissions of \"%s\" are read-only.\nIt may still be possible to write it.\nDo you wish to try?"), + buf->b_fname); + + if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 2) == VIM_YES) { + /* Set forceit, to force the writing of a readonly file */ + *forceit = TRUE; + return FALSE; + } else + return TRUE; + } else if (buf->b_p_ro) + EMSG(_(e_readonly)); + else + EMSG2(_("E505: \"%s\" is read-only (add ! to override)"), + buf->b_fname); + return TRUE; + } + + return FALSE; +} + +/* + * Try to abandon current file and edit a new or existing file. + * 'fnum' is the number of the file, if zero use ffname/sfname. + * + * Return 1 for "normal" error, 2 for "not written" error, 0 for success + * -1 for successfully opening another file. + * 'lnum' is the line number for the cursor in the new file (if non-zero). + */ +int getfile(fnum, ffname, sfname, setpm, lnum, forceit) +int fnum; +char_u *ffname; +char_u *sfname; +int setpm; +linenr_T lnum; +int forceit; +{ + int other; + int retval; + char_u *free_me = NULL; + + if (text_locked()) + return 1; + if (curbuf_locked()) + return 1; + + if (fnum == 0) { + /* make ffname full path, set sfname */ + fname_expand(curbuf, &ffname, &sfname); + other = otherfile(ffname); + free_me = ffname; /* has been allocated, free() later */ + } else + other = (fnum != curbuf->b_fnum); + + if (other) + ++no_wait_return; /* don't wait for autowrite message */ + if (other && !forceit && curbuf->b_nwindows == 1 && !P_HID(curbuf) + && curbufIsChanged() && autowrite(curbuf, forceit) == FAIL) { + if (p_confirm && p_write) + dialog_changed(curbuf, FALSE); + if (curbufIsChanged()) { + if (other) + --no_wait_return; + EMSG(_(e_nowrtmsg)); + retval = 2; /* file has been changed */ + goto theend; + } + } + if (other) + --no_wait_return; + if (setpm) + setpcmark(); + if (!other) { + if (lnum != 0) + curwin->w_cursor.lnum = lnum; + check_cursor_lnum(); + beginline(BL_SOL | BL_FIX); + retval = 0; /* it's in the same file */ + } else if (do_ecmd(fnum, ffname, sfname, NULL, lnum, + (P_HID(curbuf) ? ECMD_HIDE : 0) + (forceit ? ECMD_FORCEIT : 0), + curwin) == OK) + retval = -1; /* opened another file */ + else + retval = 1; /* error encountered */ + +theend: + vim_free(free_me); + return retval; +} + +/* + * start editing a new file + * + * fnum: file number; if zero use ffname/sfname + * ffname: the file name + * - full path if sfname used, + * - any file name if sfname is NULL + * - empty string to re-edit with the same file name (but may be + * in a different directory) + * - NULL to start an empty buffer + * sfname: the short file name (or NULL) + * eap: contains the command to be executed after loading the file and + * forced 'ff' and 'fenc' + * newlnum: if > 0: put cursor on this line number (if possible) + * if ECMD_LASTL: use last position in loaded file + * if ECMD_LAST: use last position in all files + * if ECMD_ONE: use first line + * flags: + * ECMD_HIDE: if TRUE don't free the current buffer + * ECMD_SET_HELP: set b_help flag of (new) buffer before opening file + * ECMD_OLDBUF: use existing buffer if it exists + * ECMD_FORCEIT: ! used for Ex command + * ECMD_ADDBUF: don't edit, just add to buffer list + * oldwin: Should be "curwin" when editing a new buffer in the current + * window, NULL when splitting the window first. When not NULL info + * of the previous buffer for "oldwin" is stored. + * + * return FAIL for failure, OK otherwise + */ +int do_ecmd(fnum, ffname, sfname, eap, newlnum, flags, oldwin) +int fnum; +char_u *ffname; +char_u *sfname; +exarg_T *eap; /* can be NULL! */ +linenr_T newlnum; +int flags; +win_T *oldwin; +{ + int other_file; /* TRUE if editing another file */ + int oldbuf; /* TRUE if using existing buffer */ + int auto_buf = FALSE; /* TRUE if autocommands brought us + into the buffer unexpectedly */ + char_u *new_name = NULL; + int did_set_swapcommand = FALSE; + buf_T *buf; + buf_T *old_curbuf = curbuf; + char_u *free_fname = NULL; + int retval = FAIL; + long n; + linenr_T lnum; + linenr_T topline = 0; + int newcol = -1; + int solcol = -1; + pos_T *pos; + char_u *command = NULL; + int did_get_winopts = FALSE; + int readfile_flags = 0; + + if (eap != NULL) + command = eap->do_ecmd_cmd; + + if (fnum != 0) { + if (fnum == curbuf->b_fnum) /* file is already being edited */ + return OK; /* nothing to do */ + other_file = TRUE; + } else { + /* if no short name given, use ffname for short name */ + if (sfname == NULL) + sfname = ffname; +#ifdef USE_FNAME_CASE +# ifdef USE_LONG_FNAME + if (USE_LONG_FNAME) +# endif + if (sfname != NULL) + fname_case(sfname, 0); /* set correct case for sfname */ +#endif + + if ((flags & ECMD_ADDBUF) && (ffname == NULL || *ffname == NUL)) + goto theend; + + if (ffname == NULL) + other_file = TRUE; + /* there is no file name */ + else if (*ffname == NUL && curbuf->b_ffname == NULL) + other_file = FALSE; + else { + if (*ffname == NUL) { /* re-edit with same file name */ + ffname = curbuf->b_ffname; + sfname = curbuf->b_fname; + } + free_fname = fix_fname(ffname); /* may expand to full path name */ + if (free_fname != NULL) + ffname = free_fname; + other_file = otherfile(ffname); + } + } + + /* + * if the file was changed we may not be allowed to abandon it + * - if we are going to re-edit the same file + * - or if we are the only window on this file and if ECMD_HIDE is FALSE + */ + if ( ((!other_file && !(flags & ECMD_OLDBUF)) + || (curbuf->b_nwindows == 1 + && !(flags & (ECMD_HIDE | ECMD_ADDBUF)))) + && check_changed(curbuf, (p_awa ? CCGD_AW : 0) + | (other_file ? 0 : CCGD_MULTWIN) + | ((flags & ECMD_FORCEIT) ? CCGD_FORCEIT : 0) + | (eap == NULL ? 0 : CCGD_EXCMD))) { + if (fnum == 0 && other_file && ffname != NULL) + (void)setaltfname(ffname, sfname, newlnum < 0 ? 0 : newlnum); + goto theend; + } + + /* + * End Visual mode before switching to another buffer, so the text can be + * copied into the GUI selection buffer. + */ + reset_VIsual(); + + if ((command != NULL || newlnum > (linenr_T)0) + && *get_vim_var_str(VV_SWAPCOMMAND) == NUL) { + int len; + char_u *p; + + /* Set v:swapcommand for the SwapExists autocommands. */ + if (command != NULL) + len = (int)STRLEN(command) + 3; + else + len = 30; + p = alloc((unsigned)len); + if (p != NULL) { + if (command != NULL) + vim_snprintf((char *)p, len, ":%s\r", command); + else + vim_snprintf((char *)p, len, "%ldG", (long)newlnum); + set_vim_var_string(VV_SWAPCOMMAND, p, -1); + did_set_swapcommand = TRUE; + vim_free(p); + } + } + + /* + * If we are starting to edit another file, open a (new) buffer. + * Otherwise we re-use the current buffer. + */ + if (other_file) { + if (!(flags & ECMD_ADDBUF)) { + if (!cmdmod.keepalt) + curwin->w_alt_fnum = curbuf->b_fnum; + if (oldwin != NULL) + buflist_altfpos(oldwin); + } + + if (fnum) + buf = buflist_findnr(fnum); + else { + if (flags & ECMD_ADDBUF) { + linenr_T tlnum = 1L; + + if (command != NULL) { + tlnum = atol((char *)command); + if (tlnum <= 0) + tlnum = 1L; + } + (void)buflist_new(ffname, sfname, tlnum, BLN_LISTED); + goto theend; + } + buf = buflist_new(ffname, sfname, 0L, + BLN_CURBUF | ((flags & ECMD_SET_HELP) ? 0 : BLN_LISTED)); + } + if (buf == NULL) + goto theend; + if (buf->b_ml.ml_mfp == NULL) { /* no memfile yet */ + oldbuf = FALSE; + buf->b_nwindows = 0; + } else { /* existing memfile */ + oldbuf = TRUE; + (void)buf_check_timestamp(buf, FALSE); + /* Check if autocommands made buffer invalid or changed the current + * buffer. */ + if (!buf_valid(buf) + || curbuf != old_curbuf + ) + goto theend; + if (aborting()) /* autocmds may abort script processing */ + goto theend; + } + + /* May jump to last used line number for a loaded buffer or when asked + * for explicitly */ + if ((oldbuf && newlnum == ECMD_LASTL) || newlnum == ECMD_LAST) { + pos = buflist_findfpos(buf); + newlnum = pos->lnum; + solcol = pos->col; + } + + /* + * Make the (new) buffer the one used by the current window. + * If the old buffer becomes unused, free it if ECMD_HIDE is FALSE. + * If the current buffer was empty and has no file name, curbuf + * is returned by buflist_new(). + */ + if (buf != curbuf) { + /* + * Be careful: The autocommands may delete any buffer and change + * the current buffer. + * - If the buffer we are going to edit is deleted, give up. + * - If the current buffer is deleted, prefer to load the new + * buffer when loading a buffer is required. This avoids + * loading another buffer which then must be closed again. + * - If we ended up in the new buffer already, need to skip a few + * things, set auto_buf. + */ + if (buf->b_fname != NULL) + new_name = vim_strsave(buf->b_fname); + au_new_curbuf = buf; + apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, FALSE, curbuf); + if (!buf_valid(buf)) { /* new buffer has been deleted */ + delbuf_msg(new_name); /* frees new_name */ + goto theend; + } + if (aborting()) { /* autocmds may abort script processing */ + vim_free(new_name); + goto theend; + } + if (buf == curbuf) /* already in new buffer */ + auto_buf = TRUE; + else { + if (curbuf == old_curbuf) + buf_copy_options(buf, BCO_ENTER); + + /* close the link to the current buffer */ + u_sync(FALSE); + close_buffer(oldwin, curbuf, + (flags & ECMD_HIDE) ? 0 : DOBUF_UNLOAD, FALSE); + + /* Autocommands may open a new window and leave oldwin open + * which leads to crashes since the above call sets + * oldwin->w_buffer to NULL. */ + if (curwin != oldwin && oldwin != aucmd_win + && win_valid(oldwin) && oldwin->w_buffer == NULL) + win_close(oldwin, FALSE); + + if (aborting()) { /* autocmds may abort script processing */ + vim_free(new_name); + goto theend; + } + /* Be careful again, like above. */ + if (!buf_valid(buf)) { /* new buffer has been deleted */ + delbuf_msg(new_name); /* frees new_name */ + goto theend; + } + if (buf == curbuf) /* already in new buffer */ + auto_buf = TRUE; + else { + /* + * We could instead free the synblock + * and re-attach to buffer, perhaps. + */ + if (curwin->w_s == &(curwin->w_buffer->b_s)) + curwin->w_s = &(buf->b_s); + curwin->w_buffer = buf; + curbuf = buf; + ++curbuf->b_nwindows; + + /* Set 'fileformat', 'binary' and 'fenc' when forced. */ + if (!oldbuf && eap != NULL) { + set_file_options(TRUE, eap); + set_forced_fenc(eap); + } + } + + /* May get the window options from the last time this buffer + * was in this window (or another window). If not used + * before, reset the local window options to the global + * values. Also restores old folding stuff. */ + get_winopts(curbuf); + did_get_winopts = TRUE; + + } + vim_free(new_name); + au_new_curbuf = NULL; + } else + ++curbuf->b_nwindows; + + curwin->w_pcmark.lnum = 1; + curwin->w_pcmark.col = 0; + } else { /* !other_file */ + if ( + (flags & ECMD_ADDBUF) || + check_fname() == FAIL) + goto theend; + oldbuf = (flags & ECMD_OLDBUF); + } + + if ((flags & ECMD_SET_HELP) || keep_help_flag) { + char_u *p; + + curbuf->b_help = TRUE; + set_string_option_direct((char_u *)"buftype", -1, + (char_u *)"help", OPT_FREE|OPT_LOCAL, 0); + + /* + * Always set these options after jumping to a help tag, because the + * user may have an autocommand that gets in the way. + * Accept all ASCII chars for keywords, except ' ', '*', '"', '|', and + * latin1 word characters (for translated help files). + * Only set it when needed, buf_init_chartab() is some work. + */ + p = + (char_u *)"!-~,^*,^|,^\",192-255"; + if (STRCMP(curbuf->b_p_isk, p) != 0) { + set_string_option_direct((char_u *)"isk", -1, p, + OPT_FREE|OPT_LOCAL, 0); + check_buf_options(curbuf); + (void)buf_init_chartab(curbuf, FALSE); + } + + curbuf->b_p_ts = 8; /* 'tabstop' is 8 */ + curwin->w_p_list = FALSE; /* no list mode */ + + curbuf->b_p_ma = FALSE; /* not modifiable */ + curbuf->b_p_bin = FALSE; /* reset 'bin' before reading file */ + curwin->w_p_nu = 0; /* no line numbers */ + curwin->w_p_rnu = 0; /* no relative line numbers */ + RESET_BINDING(curwin); /* no scroll or cursor binding */ + curwin->w_p_arab = FALSE; /* no arabic mode */ + curwin->w_p_rl = FALSE; /* help window is left-to-right */ + curwin->w_p_fen = FALSE; /* No folding in the help window */ + curwin->w_p_diff = FALSE; /* No 'diff' */ + curwin->w_p_spell = FALSE; /* No spell checking */ + + buf = curbuf; + set_buflisted(FALSE); + } else { + buf = curbuf; + /* Don't make a buffer listed if it's a help buffer. Useful when + * using CTRL-O to go back to a help file. */ + if (!curbuf->b_help) + set_buflisted(TRUE); + } + + /* If autocommands change buffers under our fingers, forget about + * editing the file. */ + if (buf != curbuf) + goto theend; + if (aborting()) /* autocmds may abort script processing */ + goto theend; + + /* Since we are starting to edit a file, consider the filetype to be + * unset. Helps for when an autocommand changes files and expects syntax + * highlighting to work in the other file. */ + did_filetype = FALSE; + + /* + * other_file oldbuf + * FALSE FALSE re-edit same file, buffer is re-used + * FALSE TRUE re-edit same file, nothing changes + * TRUE FALSE start editing new file, new buffer + * TRUE TRUE start editing in existing buffer (nothing to do) + */ + if (!other_file && !oldbuf) { /* re-use the buffer */ + set_last_cursor(curwin); /* may set b_last_cursor */ + if (newlnum == ECMD_LAST || newlnum == ECMD_LASTL) { + newlnum = curwin->w_cursor.lnum; + solcol = curwin->w_cursor.col; + } + buf = curbuf; + if (buf->b_fname != NULL) + new_name = vim_strsave(buf->b_fname); + else + new_name = NULL; + if (p_ur < 0 || curbuf->b_ml.ml_line_count <= p_ur) { + /* Save all the text, so that the reload can be undone. + * Sync first so that this is a separate undo-able action. */ + u_sync(FALSE); + if (u_savecommon(0, curbuf->b_ml.ml_line_count + 1, 0, TRUE) + == FAIL) + goto theend; + u_unchanged(curbuf); + buf_freeall(curbuf, BFA_KEEP_UNDO); + + /* tell readfile() not to clear or reload undo info */ + readfile_flags = READ_KEEP_UNDO; + } else + buf_freeall(curbuf, 0); /* free all things for buffer */ + /* If autocommands deleted the buffer we were going to re-edit, give + * up and jump to the end. */ + if (!buf_valid(buf)) { + delbuf_msg(new_name); /* frees new_name */ + goto theend; + } + vim_free(new_name); + + /* If autocommands change buffers under our fingers, forget about + * re-editing the file. Should do the buf_clear_file(), but perhaps + * the autocommands changed the buffer... */ + if (buf != curbuf) + goto theend; + if (aborting()) /* autocmds may abort script processing */ + goto theend; + buf_clear_file(curbuf); + curbuf->b_op_start.lnum = 0; /* clear '[ and '] marks */ + curbuf->b_op_end.lnum = 0; + } + + /* + * If we get here we are sure to start editing + */ + /* don't redraw until the cursor is in the right line */ + ++RedrawingDisabled; + + /* Assume success now */ + retval = OK; + + /* + * Reset cursor position, could be used by autocommands. + */ + check_cursor(); + + /* + * Check if we are editing the w_arg_idx file in the argument list. + */ + check_arg_idx(curwin); + + if (!auto_buf) { + /* + * Set cursor and init window before reading the file and executing + * autocommands. This allows for the autocommands to position the + * cursor. + */ + curwin_init(); + + /* It's possible that all lines in the buffer changed. Need to update + * automatic folding for all windows where it's used. */ + { + win_T *win; + tabpage_T *tp; + + FOR_ALL_TAB_WINDOWS(tp, win) + if (win->w_buffer == curbuf) + foldUpdateAll(win); + } + + /* Change directories when the 'acd' option is set. */ + DO_AUTOCHDIR + + /* + * Careful: open_buffer() and apply_autocmds() may change the current + * buffer and window. + */ + lnum = curwin->w_cursor.lnum; + topline = curwin->w_topline; + if (!oldbuf) { /* need to read the file */ +#if defined(HAS_SWAP_EXISTS_ACTION) + swap_exists_action = SEA_DIALOG; +#endif + curbuf->b_flags |= BF_CHECK_RO; /* set/reset 'ro' flag */ + + /* + * Open the buffer and read the file. + */ + if (should_abort(open_buffer(FALSE, eap, readfile_flags))) + retval = FAIL; + +#if defined(HAS_SWAP_EXISTS_ACTION) + if (swap_exists_action == SEA_QUIT) + retval = FAIL; + handle_swap_exists(old_curbuf); +#endif + } else { + /* Read the modelines, but only to set window-local options. Any + * buffer-local options have already been set and may have been + * changed by the user. */ + do_modelines(OPT_WINONLY); + + apply_autocmds_retval(EVENT_BUFENTER, NULL, NULL, FALSE, curbuf, + &retval); + apply_autocmds_retval(EVENT_BUFWINENTER, NULL, NULL, FALSE, curbuf, + &retval); + } + check_arg_idx(curwin); + + /* + * If autocommands change the cursor position or topline, we should + * keep it. + */ + if (curwin->w_cursor.lnum != lnum) { + newlnum = curwin->w_cursor.lnum; + newcol = curwin->w_cursor.col; + } + if (curwin->w_topline == topline) + topline = 0; + + /* Even when cursor didn't move we need to recompute topline. */ + changed_line_abv_curs(); + + maketitle(); + } + + /* Tell the diff stuff that this buffer is new and/or needs updating. + * Also needed when re-editing the same buffer, because unloading will + * have removed it as a diff buffer. */ + if (curwin->w_p_diff) { + diff_buf_add(curbuf); + diff_invalidate(curbuf); + } + + /* If the window options were changed may need to set the spell language. + * Can only do this after the buffer has been properly setup. */ + if (did_get_winopts && curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) + (void)did_set_spelllang(curwin); + + if (command == NULL) { + if (newcol >= 0) { /* position set by autocommands */ + curwin->w_cursor.lnum = newlnum; + curwin->w_cursor.col = newcol; + check_cursor(); + } else if (newlnum > 0) { /* line number from caller or old position */ + curwin->w_cursor.lnum = newlnum; + check_cursor_lnum(); + if (solcol >= 0 && !p_sol) { + /* 'sol' is off: Use last known column. */ + curwin->w_cursor.col = solcol; + check_cursor_col(); + curwin->w_cursor.coladd = 0; + curwin->w_set_curswant = TRUE; + } else + beginline(BL_SOL | BL_FIX); + } else { /* no line number, go to last line in Ex mode */ + if (exmode_active) + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + beginline(BL_WHITE | BL_FIX); + } + } + + /* Check if cursors in other windows on the same buffer are still valid */ + check_lnums(FALSE); + + /* + * Did not read the file, need to show some info about the file. + * Do this after setting the cursor. + */ + if (oldbuf + && !auto_buf + ) { + int msg_scroll_save = msg_scroll; + + /* Obey the 'O' flag in 'cpoptions': overwrite any previous file + * message. */ + if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) + msg_scroll = FALSE; + if (!msg_scroll) /* wait a bit when overwriting an error msg */ + check_for_delay(FALSE); + msg_start(); + msg_scroll = msg_scroll_save; + msg_scrolled_ign = TRUE; + + fileinfo(FALSE, TRUE, FALSE); + + msg_scrolled_ign = FALSE; + } + + if (command != NULL) + do_cmdline(command, NULL, NULL, DOCMD_VERBOSE); + + if (curbuf->b_kmap_state & KEYMAP_INIT) + (void)keymap_init(); + + --RedrawingDisabled; + if (!skip_redraw) { + n = p_so; + if (topline == 0 && command == NULL) + p_so = 999; /* force cursor halfway the window */ + update_topline(); + curwin->w_scbind_pos = curwin->w_topline; + p_so = n; + redraw_curbuf_later(NOT_VALID); /* redraw this buffer later */ + } + + if (p_im) + need_start_insertmode = TRUE; + + /* Change directories when the 'acd' option is set. */ + DO_AUTOCHDIR + + +theend: + if (did_set_swapcommand) + set_vim_var_string(VV_SWAPCOMMAND, NULL, -1); + vim_free(free_fname); + return retval; +} + +static void delbuf_msg(name) +char_u *name; +{ + EMSG2(_("E143: Autocommands unexpectedly deleted new buffer %s"), + name == NULL ? (char_u *)"" : name); + vim_free(name); + au_new_curbuf = NULL; +} + +static int append_indent = 0; /* autoindent for first line */ + +/* + * ":insert" and ":append", also used by ":change" + */ +void ex_append(eap) +exarg_T *eap; +{ + char_u *theline; + int did_undo = FALSE; + linenr_T lnum = eap->line2; + int indent = 0; + char_u *p; + int vcol; + int empty = (curbuf->b_ml.ml_flags & ML_EMPTY); + + /* the ! flag toggles autoindent */ + if (eap->forceit) + curbuf->b_p_ai = !curbuf->b_p_ai; + + /* First autoindent comes from the line we start on */ + if (eap->cmdidx != CMD_change && curbuf->b_p_ai && lnum > 0) + append_indent = get_indent_lnum(lnum); + + if (eap->cmdidx != CMD_append) + --lnum; + + /* when the buffer is empty append to line 0 and delete the dummy line */ + if (empty && lnum == 1) + lnum = 0; + + State = INSERT; /* behave like in Insert mode */ + if (curbuf->b_p_iminsert == B_IMODE_LMAP) + State |= LANGMAP; + + for (;; ) { + msg_scroll = TRUE; + need_wait_return = FALSE; + if (curbuf->b_p_ai) { + if (append_indent >= 0) { + indent = append_indent; + append_indent = -1; + } else if (lnum > 0) + indent = get_indent_lnum(lnum); + } + ex_keep_indent = FALSE; + if (eap->getline == NULL) { + /* No getline() function, use the lines that follow. This ends + * when there is no more. */ + if (eap->nextcmd == NULL || *eap->nextcmd == NUL) + break; + p = vim_strchr(eap->nextcmd, NL); + if (p == NULL) + p = eap->nextcmd + STRLEN(eap->nextcmd); + theline = vim_strnsave(eap->nextcmd, (int)(p - eap->nextcmd)); + if (*p != NUL) + ++p; + eap->nextcmd = p; + } else + theline = eap->getline( + eap->cstack->cs_looplevel > 0 ? -1 : + NUL, eap->cookie, indent); + lines_left = Rows - 1; + if (theline == NULL) + break; + + /* Using ^ CTRL-D in getexmodeline() makes us repeat the indent. */ + if (ex_keep_indent) + append_indent = indent; + + /* Look for the "." after automatic indent. */ + vcol = 0; + for (p = theline; indent > vcol; ++p) { + if (*p == ' ') + ++vcol; + else if (*p == TAB) + vcol += 8 - vcol % 8; + else + break; + } + if ((p[0] == '.' && p[1] == NUL) + || (!did_undo && u_save(lnum, lnum + 1 + (empty ? 1 : 0)) + == FAIL)) { + vim_free(theline); + break; + } + + /* don't use autoindent if nothing was typed. */ + if (p[0] == NUL) + theline[0] = NUL; + + did_undo = TRUE; + ml_append(lnum, theline, (colnr_T)0, FALSE); + appended_lines_mark(lnum, 1L); + + vim_free(theline); + ++lnum; + + if (empty) { + ml_delete(2L, FALSE); + empty = FALSE; + } + } + State = NORMAL; + + if (eap->forceit) + curbuf->b_p_ai = !curbuf->b_p_ai; + + /* "start" is set to eap->line2+1 unless that position is invalid (when + * eap->line2 pointed to the end of the buffer and nothing was appended) + * "end" is set to lnum when something has been appended, otherwise + * it is the same than "start" -- Acevedo */ + curbuf->b_op_start.lnum = (eap->line2 < curbuf->b_ml.ml_line_count) ? + eap->line2 + 1 : curbuf->b_ml.ml_line_count; + if (eap->cmdidx != CMD_append) + --curbuf->b_op_start.lnum; + curbuf->b_op_end.lnum = (eap->line2 < lnum) + ? lnum : curbuf->b_op_start.lnum; + curbuf->b_op_start.col = curbuf->b_op_end.col = 0; + curwin->w_cursor.lnum = lnum; + check_cursor_lnum(); + beginline(BL_SOL | BL_FIX); + + need_wait_return = FALSE; /* don't use wait_return() now */ + ex_no_reprint = TRUE; +} + +/* + * ":change" + */ +void ex_change(eap) +exarg_T *eap; +{ + linenr_T lnum; + + if (eap->line2 >= eap->line1 + && u_save(eap->line1 - 1, eap->line2 + 1) == FAIL) + return; + + /* the ! flag toggles autoindent */ + if (eap->forceit ? !curbuf->b_p_ai : curbuf->b_p_ai) + append_indent = get_indent_lnum(eap->line1); + + for (lnum = eap->line2; lnum >= eap->line1; --lnum) { + if (curbuf->b_ml.ml_flags & ML_EMPTY) /* nothing to delete */ + break; + ml_delete(eap->line1, FALSE); + } + + /* make sure the cursor is not beyond the end of the file now */ + check_cursor_lnum(); + deleted_lines_mark(eap->line1, (long)(eap->line2 - lnum)); + + /* ":append" on the line above the deleted lines. */ + eap->line2 = eap->line1; + ex_append(eap); +} + +void ex_z(eap) +exarg_T *eap; +{ + char_u *x; + int bigness; + char_u *kind; + int minus = 0; + linenr_T start, end, curs, i; + int j; + linenr_T lnum = eap->line2; + + /* Vi compatible: ":z!" uses display height, without a count uses + * 'scroll' */ + if (eap->forceit) + bigness = curwin->w_height; + else if (firstwin == lastwin) + bigness = curwin->w_p_scr * 2; + else + bigness = curwin->w_height - 3; + if (bigness < 1) + bigness = 1; + + x = eap->arg; + kind = x; + if (*kind == '-' || *kind == '+' || *kind == '=' + || *kind == '^' || *kind == '.') + ++x; + while (*x == '-' || *x == '+') + ++x; + + if (*x != 0) { + if (!VIM_ISDIGIT(*x)) { + EMSG(_("E144: non-numeric argument to :z")); + return; + } else { + bigness = atoi((char *)x); + p_window = bigness; + if (*kind == '=') + bigness += 2; + } + } + + /* the number of '-' and '+' multiplies the distance */ + if (*kind == '-' || *kind == '+') + for (x = kind + 1; *x == *kind; ++x) + ; + + switch (*kind) { + case '-': + start = lnum - bigness * (linenr_T)(x - kind) + 1; + end = start + bigness - 1; + curs = end; + break; + + case '=': + start = lnum - (bigness + 1) / 2 + 1; + end = lnum + (bigness + 1) / 2 - 1; + curs = lnum; + minus = 1; + break; + + case '^': + start = lnum - bigness * 2; + end = lnum - bigness; + curs = lnum - bigness; + break; + + case '.': + start = lnum - (bigness + 1) / 2 + 1; + end = lnum + (bigness + 1) / 2 - 1; + curs = end; + break; + + default: /* '+' */ + start = lnum; + if (*kind == '+') + start += bigness * (linenr_T)(x - kind - 1) + 1; + else if (eap->addr_count == 0) + ++start; + end = start + bigness - 1; + curs = end; + break; + } + + if (start < 1) + start = 1; + + if (end > curbuf->b_ml.ml_line_count) + end = curbuf->b_ml.ml_line_count; + + if (curs > curbuf->b_ml.ml_line_count) + curs = curbuf->b_ml.ml_line_count; + + for (i = start; i <= end; i++) { + if (minus && i == lnum) { + msg_putchar('\n'); + + for (j = 1; j < Columns; j++) + msg_putchar('-'); + } + + print_line(i, eap->flags & EXFLAG_NR, eap->flags & EXFLAG_LIST); + + if (minus && i == lnum) { + msg_putchar('\n'); + + for (j = 1; j < Columns; j++) + msg_putchar('-'); + } + } + + curwin->w_cursor.lnum = curs; + ex_no_reprint = TRUE; +} + +/* + * Check if the restricted flag is set. + * If so, give an error message and return TRUE. + * Otherwise, return FALSE. + */ +int check_restricted() { + if (restricted) { + EMSG(_("E145: Shell commands not allowed in rvim")); + return TRUE; + } + return FALSE; +} + +/* + * Check if the secure flag is set (.exrc or .vimrc in current directory). + * If so, give an error message and return TRUE. + * Otherwise, return FALSE. + */ +int check_secure() { + if (secure) { + secure = 2; + EMSG(_(e_curdir)); + return TRUE; + } +#ifdef HAVE_SANDBOX + /* + * In the sandbox more things are not allowed, including the things + * disallowed in secure mode. + */ + if (sandbox != 0) { + EMSG(_(e_sandbox)); + return TRUE; + } +#endif + return FALSE; +} + +static char_u *old_sub = NULL; /* previous substitute pattern */ +static int global_need_beginline; /* call beginline() after ":g" */ + +/* do_sub() + * + * Perform a substitution from line eap->line1 to line eap->line2 using the + * command pointed to by eap->arg which should be of the form: + * + * /pattern/substitution/{flags} + * + * The usual escapes are supported as described in the regexp docs. + */ +void do_sub(eap) +exarg_T *eap; +{ + linenr_T lnum; + long i = 0; + regmmatch_T regmatch; + static int do_all = FALSE; /* do multiple substitutions per line */ + static int do_ask = FALSE; /* ask for confirmation */ + static int do_count = FALSE; /* count only */ + static int do_error = TRUE; /* if false, ignore errors */ + static int do_print = FALSE; /* print last line with subs. */ + static int do_list = FALSE; /* list last line with subs. */ + static int do_number = FALSE; /* list last line with line nr*/ + static int do_ic = 0; /* ignore case flag */ + char_u *pat = NULL, *sub = NULL; /* init for GCC */ + int delimiter; + int sublen; + int got_quit = FALSE; + int got_match = FALSE; + int temp; + int which_pat; + char_u *cmd; + int save_State; + linenr_T first_line = 0; /* first changed line */ + linenr_T last_line= 0; /* below last changed line AFTER the + * change */ + linenr_T old_line_count = curbuf->b_ml.ml_line_count; + linenr_T line2; + long nmatch; /* number of lines in match */ + char_u *sub_firstline; /* allocated copy of first sub line */ + int endcolumn = FALSE; /* cursor in last column when done */ + pos_T old_cursor = curwin->w_cursor; + int start_nsubs; + int save_ma = 0; + + cmd = eap->arg; + if (!global_busy) { + sub_nsubs = 0; + sub_nlines = 0; + } + start_nsubs = sub_nsubs; + + if (eap->cmdidx == CMD_tilde) + which_pat = RE_LAST; /* use last used regexp */ + else + which_pat = RE_SUBST; /* use last substitute regexp */ + + /* new pattern and substitution */ + if (eap->cmd[0] == 's' && *cmd != NUL && !vim_iswhite(*cmd) + && vim_strchr((char_u *)"0123456789cegriIp|\"", *cmd) == NULL) { + /* don't accept alphanumeric for separator */ + if (isalpha(*cmd)) { + EMSG(_("E146: Regular expressions can't be delimited by letters")); + return; + } + /* + * undocumented vi feature: + * "\/sub/" and "\?sub?" use last used search pattern (almost like + * //sub/r). "\&sub&" use last substitute pattern (like //sub/). + */ + if (*cmd == '\\') { + ++cmd; + if (vim_strchr((char_u *)"/?&", *cmd) == NULL) { + EMSG(_(e_backslash)); + return; + } + if (*cmd != '&') + which_pat = RE_SEARCH; /* use last '/' pattern */ + pat = (char_u *)""; /* empty search pattern */ + delimiter = *cmd++; /* remember delimiter character */ + } else { /* find the end of the regexp */ + if (p_altkeymap && curwin->w_p_rl) + lrF_sub(cmd); + which_pat = RE_LAST; /* use last used regexp */ + delimiter = *cmd++; /* remember delimiter character */ + pat = cmd; /* remember start of search pat */ + cmd = skip_regexp(cmd, delimiter, p_magic, &eap->arg); + if (cmd[0] == delimiter) /* end delimiter found */ + *cmd++ = NUL; /* replace it with a NUL */ + } + + /* + * Small incompatibility: vi sees '\n' as end of the command, but in + * Vim we want to use '\n' to find/substitute a NUL. + */ + sub = cmd; /* remember the start of the substitution */ + + while (cmd[0]) { + if (cmd[0] == delimiter) { /* end delimiter found */ + *cmd++ = NUL; /* replace it with a NUL */ + break; + } + if (cmd[0] == '\\' && cmd[1] != 0) /* skip escaped characters */ + ++cmd; + mb_ptr_adv(cmd); + } + + if (!eap->skip) { + /* In POSIX vi ":s/pat/%/" uses the previous subst. string. */ + if (STRCMP(sub, "%") == 0 + && vim_strchr(p_cpo, CPO_SUBPERCENT) != NULL) { + if (old_sub == NULL) { /* there is no previous command */ + EMSG(_(e_nopresub)); + return; + } + sub = old_sub; + } else { + vim_free(old_sub); + old_sub = vim_strsave(sub); + } + } + } else if (!eap->skip) { /* use previous pattern and substitution */ + if (old_sub == NULL) { /* there is no previous command */ + EMSG(_(e_nopresub)); + return; + } + pat = NULL; /* search_regcomp() will use previous pattern */ + sub = old_sub; + + /* Vi compatibility quirk: repeating with ":s" keeps the cursor in the + * last column after using "$". */ + endcolumn = (curwin->w_curswant == MAXCOL); + } + + /* + * Find trailing options. When '&' is used, keep old options. + */ + if (*cmd == '&') + ++cmd; + else { + if (!p_ed) { + if (p_gd) /* default is global on */ + do_all = TRUE; + else + do_all = FALSE; + do_ask = FALSE; + } + do_error = TRUE; + do_print = FALSE; + do_count = FALSE; + do_number = FALSE; + do_ic = 0; + } + while (*cmd) { + /* + * Note that 'g' and 'c' are always inverted, also when p_ed is off. + * 'r' is never inverted. + */ + if (*cmd == 'g') + do_all = !do_all; + else if (*cmd == 'c') + do_ask = !do_ask; + else if (*cmd == 'n') + do_count = TRUE; + else if (*cmd == 'e') + do_error = !do_error; + else if (*cmd == 'r') /* use last used regexp */ + which_pat = RE_LAST; + else if (*cmd == 'p') + do_print = TRUE; + else if (*cmd == '#') { + do_print = TRUE; + do_number = TRUE; + } else if (*cmd == 'l') { + do_print = TRUE; + do_list = TRUE; + } else if (*cmd == 'i') /* ignore case */ + do_ic = 'i'; + else if (*cmd == 'I') /* don't ignore case */ + do_ic = 'I'; + else + break; + ++cmd; + } + if (do_count) + do_ask = FALSE; + + /* + * check for a trailing count + */ + cmd = skipwhite(cmd); + if (VIM_ISDIGIT(*cmd)) { + i = getdigits(&cmd); + if (i <= 0 && !eap->skip && do_error) { + EMSG(_(e_zerocount)); + return; + } + eap->line1 = eap->line2; + eap->line2 += i - 1; + if (eap->line2 > curbuf->b_ml.ml_line_count) + eap->line2 = curbuf->b_ml.ml_line_count; + } + + /* + * check for trailing command or garbage + */ + cmd = skipwhite(cmd); + if (*cmd && *cmd != '"') { /* if not end-of-line or comment */ + eap->nextcmd = check_nextcmd(cmd); + if (eap->nextcmd == NULL) { + EMSG(_(e_trailing)); + return; + } + } + + if (eap->skip) /* not executing commands, only parsing */ + return; + + if (!do_count && !curbuf->b_p_ma) { + /* Substitution is not allowed in non-'modifiable' buffer */ + EMSG(_(e_modifiable)); + return; + } + + if (search_regcomp(pat, RE_SUBST, which_pat, SEARCH_HIS, + ®match) == FAIL) { + if (do_error) + EMSG(_(e_invcmd)); + return; + } + + /* the 'i' or 'I' flag overrules 'ignorecase' and 'smartcase' */ + if (do_ic == 'i') + regmatch.rmm_ic = TRUE; + else if (do_ic == 'I') + regmatch.rmm_ic = FALSE; + + sub_firstline = NULL; + + /* + * ~ in the substitute pattern is replaced with the old pattern. + * We do it here once to avoid it to be replaced over and over again. + * But don't do it when it starts with "\=", then it's an expression. + */ + if (!(sub[0] == '\\' && sub[1] == '=')) + sub = regtilde(sub, p_magic); + + /* + * Check for a match on each line. + */ + line2 = eap->line2; + for (lnum = eap->line1; lnum <= line2 && !(got_quit + || aborting() + ); ++lnum) { + nmatch = vim_regexec_multi(®match, curwin, curbuf, lnum, + (colnr_T)0, NULL); + if (nmatch) { + colnr_T copycol; + colnr_T matchcol; + colnr_T prev_matchcol = MAXCOL; + char_u *new_end, *new_start = NULL; + unsigned new_start_len = 0; + char_u *p1; + int did_sub = FALSE; + int lastone; + int len, copy_len, needed_len; + long nmatch_tl = 0; /* nr of lines matched below lnum */ + int do_again; /* do it again after joining lines */ + int skip_match = FALSE; + linenr_T sub_firstlnum; /* nr of first sub line */ + + /* + * The new text is build up step by step, to avoid too much + * copying. There are these pieces: + * sub_firstline The old text, unmodified. + * copycol Column in the old text where we started + * looking for a match; from here old text still + * needs to be copied to the new text. + * matchcol Column number of the old text where to look + * for the next match. It's just after the + * previous match or one further. + * prev_matchcol Column just after the previous match (if any). + * Mostly equal to matchcol, except for the first + * match and after skipping an empty match. + * regmatch.*pos Where the pattern matched in the old text. + * new_start The new text, all that has been produced so + * far. + * new_end The new text, where to append new text. + * + * lnum The line number where we found the start of + * the match. Can be below the line we searched + * when there is a \n before a \zs in the + * pattern. + * sub_firstlnum The line number in the buffer where to look + * for a match. Can be different from "lnum" + * when the pattern or substitute string contains + * line breaks. + * + * Special situations: + * - When the substitute string contains a line break, the part up + * to the line break is inserted in the text, but the copy of + * the original line is kept. "sub_firstlnum" is adjusted for + * the inserted lines. + * - When the matched pattern contains a line break, the old line + * is taken from the line at the end of the pattern. The lines + * in the match are deleted later, "sub_firstlnum" is adjusted + * accordingly. + * + * The new text is built up in new_start[]. It has some extra + * room to avoid using alloc()/free() too often. new_start_len is + * the length of the allocated memory at new_start. + * + * Make a copy of the old line, so it won't be taken away when + * updating the screen or handling a multi-line match. The "old_" + * pointers point into this copy. + */ + sub_firstlnum = lnum; + copycol = 0; + matchcol = 0; + + /* At first match, remember current cursor position. */ + if (!got_match) { + setpcmark(); + got_match = TRUE; + } + + /* + * Loop until nothing more to replace in this line. + * 1. Handle match with empty string. + * 2. If do_ask is set, ask for confirmation. + * 3. substitute the string. + * 4. if do_all is set, find next match + * 5. break if there isn't another match in this line + */ + for (;; ) { + /* Advance "lnum" to the line where the match starts. The + * match does not start in the first line when there is a line + * break before \zs. */ + if (regmatch.startpos[0].lnum > 0) { + lnum += regmatch.startpos[0].lnum; + sub_firstlnum += regmatch.startpos[0].lnum; + nmatch -= regmatch.startpos[0].lnum; + vim_free(sub_firstline); + sub_firstline = NULL; + } + + if (sub_firstline == NULL) { + sub_firstline = vim_strsave(ml_get(sub_firstlnum)); + if (sub_firstline == NULL) { + vim_free(new_start); + goto outofmem; + } + } + + /* Save the line number of the last change for the final + * cursor position (just like Vi). */ + curwin->w_cursor.lnum = lnum; + do_again = FALSE; + + /* + * 1. Match empty string does not count, except for first + * match. This reproduces the strange vi behaviour. + * This also catches endless loops. + */ + if (matchcol == prev_matchcol + && regmatch.endpos[0].lnum == 0 + && matchcol == regmatch.endpos[0].col) { + if (sub_firstline[matchcol] == NUL) + /* We already were at the end of the line. Don't look + * for a match in this line again. */ + skip_match = TRUE; + else { + /* search for a match at next column */ + if (has_mbyte) + matchcol += mb_ptr2len(sub_firstline + matchcol); + else + ++matchcol; + } + goto skip; + } + + /* Normally we continue searching for a match just after the + * previous match. */ + matchcol = regmatch.endpos[0].col; + prev_matchcol = matchcol; + + /* + * 2. If do_count is set only increase the counter. + * If do_ask is set, ask for confirmation. + */ + if (do_count) { + /* For a multi-line match, put matchcol at the NUL at + * the end of the line and set nmatch to one, so that + * we continue looking for a match on the next line. + * Avoids that ":s/\nB\@=//gc" get stuck. */ + if (nmatch > 1) { + matchcol = (colnr_T)STRLEN(sub_firstline); + nmatch = 1; + skip_match = TRUE; + } + sub_nsubs++; + did_sub = TRUE; + /* Skip the substitution, unless an expression is used, + * then it is evaluated in the sandbox. */ + if (!(sub[0] == '\\' && sub[1] == '=')) + goto skip; + } + + if (do_ask) { + int typed = 0; + + /* change State to CONFIRM, so that the mouse works + * properly */ + save_State = State; + State = CONFIRM; + setmouse(); /* disable mouse in xterm */ + curwin->w_cursor.col = regmatch.startpos[0].col; + + /* When 'cpoptions' contains "u" don't sync undo when + * asking for confirmation. */ + if (vim_strchr(p_cpo, CPO_UNDO) != NULL) + ++no_u_sync; + + /* + * Loop until 'y', 'n', 'q', CTRL-E or CTRL-Y typed. + */ + while (do_ask) { + if (exmode_active) { + char_u *resp; + colnr_T sc, ec; + + print_line_no_prefix(lnum, do_number, do_list); + + getvcol(curwin, &curwin->w_cursor, &sc, NULL, NULL); + curwin->w_cursor.col = regmatch.endpos[0].col - 1; + getvcol(curwin, &curwin->w_cursor, NULL, NULL, &ec); + if (do_number || curwin->w_p_nu) { + int numw = number_width(curwin) + 1; + sc += numw; + ec += numw; + } + msg_start(); + for (i = 0; i < (long)sc; ++i) + msg_putchar(' '); + for (; i <= (long)ec; ++i) + msg_putchar('^'); + + resp = getexmodeline('?', NULL, 0); + if (resp != NULL) { + typed = *resp; + vim_free(resp); + } + } else { + char_u *orig_line = NULL; + int len_change = 0; + int save_p_fen = curwin->w_p_fen; + + curwin->w_p_fen = FALSE; + /* Invert the matched string. + * Remove the inversion afterwards. */ + temp = RedrawingDisabled; + RedrawingDisabled = 0; + + if (new_start != NULL) { + /* There already was a substitution, we would + * like to show this to the user. We cannot + * really update the line, it would change + * what matches. Temporarily replace the line + * and change it back afterwards. */ + orig_line = vim_strsave(ml_get(lnum)); + if (orig_line != NULL) { + char_u *new_line = concat_str(new_start, + sub_firstline + copycol); + + if (new_line == NULL) { + vim_free(orig_line); + orig_line = NULL; + } else { + /* Position the cursor relative to the + * end of the line, the previous + * substitute may have inserted or + * deleted characters before the + * cursor. */ + len_change = (int)STRLEN(new_line) + - (int)STRLEN(orig_line); + curwin->w_cursor.col += len_change; + ml_replace(lnum, new_line, FALSE); + } + } + } + + search_match_lines = regmatch.endpos[0].lnum + - regmatch.startpos[0].lnum; + search_match_endcol = regmatch.endpos[0].col + + len_change; + highlight_match = TRUE; + + update_topline(); + validate_cursor(); + update_screen(SOME_VALID); + highlight_match = FALSE; + redraw_later(SOME_VALID); + + curwin->w_p_fen = save_p_fen; + if (msg_row == Rows - 1) + msg_didout = FALSE; /* avoid a scroll-up */ + msg_starthere(); + i = msg_scroll; + msg_scroll = 0; /* truncate msg when + needed */ + msg_no_more = TRUE; + /* write message same highlighting as for + * wait_return */ + smsg_attr(hl_attr(HLF_R), + (char_u *)_("replace with %s (y/n/a/q/l/^E/^Y)?"), sub); + msg_no_more = FALSE; + msg_scroll = i; + showruler(TRUE); + windgoto(msg_row, msg_col); + RedrawingDisabled = temp; + +#ifdef USE_ON_FLY_SCROLL + dont_scroll = FALSE; /* allow scrolling here */ +#endif + ++no_mapping; /* don't map this key */ + ++allow_keys; /* allow special keys */ + typed = plain_vgetc(); + --allow_keys; + --no_mapping; + + /* clear the question */ + msg_didout = FALSE; /* don't scroll up */ + msg_col = 0; + gotocmdline(TRUE); + + /* restore the line */ + if (orig_line != NULL) + ml_replace(lnum, orig_line, FALSE); + } + + need_wait_return = FALSE; /* no hit-return prompt */ + if (typed == 'q' || typed == ESC || typed == Ctrl_C +#ifdef UNIX + || typed == intr_char +#endif + ) { + got_quit = TRUE; + break; + } + if (typed == 'n') + break; + if (typed == 'y') + break; + if (typed == 'l') { + /* last: replace and then stop */ + do_all = FALSE; + line2 = lnum; + break; + } + if (typed == 'a') { + do_ask = FALSE; + break; + } + if (typed == Ctrl_E) + scrollup_clamp(); + else if (typed == Ctrl_Y) + scrolldown_clamp(); + } + State = save_State; + setmouse(); + if (vim_strchr(p_cpo, CPO_UNDO) != NULL) + --no_u_sync; + + if (typed == 'n') { + /* For a multi-line match, put matchcol at the NUL at + * the end of the line and set nmatch to one, so that + * we continue looking for a match on the next line. + * Avoids that ":%s/\nB\@=//gc" and ":%s/\n/,\r/gc" + * get stuck when pressing 'n'. */ + if (nmatch > 1) { + matchcol = (colnr_T)STRLEN(sub_firstline); + skip_match = TRUE; + } + goto skip; + } + if (got_quit) + goto skip; + } + + /* Move the cursor to the start of the match, so that we can + * use "\=col("."). */ + curwin->w_cursor.col = regmatch.startpos[0].col; + + /* + * 3. substitute the string. + */ + if (do_count) { + /* prevent accidentally changing the buffer by a function */ + save_ma = curbuf->b_p_ma; + curbuf->b_p_ma = FALSE; + sandbox++; + } + /* get length of substitution part */ + sublen = vim_regsub_multi(®match, + sub_firstlnum - regmatch.startpos[0].lnum, + sub, sub_firstline, FALSE, p_magic, TRUE); + if (do_count) { + curbuf->b_p_ma = save_ma; + sandbox--; + goto skip; + } + + /* When the match included the "$" of the last line it may + * go beyond the last line of the buffer. */ + if (nmatch > curbuf->b_ml.ml_line_count - sub_firstlnum + 1) { + nmatch = curbuf->b_ml.ml_line_count - sub_firstlnum + 1; + skip_match = TRUE; + } + + /* Need room for: + * - result so far in new_start (not for first sub in line) + * - original text up to match + * - length of substituted part + * - original text after match + */ + if (nmatch == 1) + p1 = sub_firstline; + else { + p1 = ml_get(sub_firstlnum + nmatch - 1); + nmatch_tl += nmatch - 1; + } + copy_len = regmatch.startpos[0].col - copycol; + needed_len = copy_len + ((unsigned)STRLEN(p1) + - regmatch.endpos[0].col) + sublen + 1; + if (new_start == NULL) { + /* + * Get some space for a temporary buffer to do the + * substitution into (and some extra space to avoid + * too many calls to alloc()/free()). + */ + new_start_len = needed_len + 50; + if ((new_start = alloc_check(new_start_len)) == NULL) + goto outofmem; + *new_start = NUL; + new_end = new_start; + } else { + /* + * Check if the temporary buffer is long enough to do the + * substitution into. If not, make it larger (with a bit + * extra to avoid too many calls to alloc()/free()). + */ + len = (unsigned)STRLEN(new_start); + needed_len += len; + if (needed_len > (int)new_start_len) { + new_start_len = needed_len + 50; + if ((p1 = alloc_check(new_start_len)) == NULL) { + vim_free(new_start); + goto outofmem; + } + mch_memmove(p1, new_start, (size_t)(len + 1)); + vim_free(new_start); + new_start = p1; + } + new_end = new_start + len; + } + + /* + * copy the text up to the part that matched + */ + mch_memmove(new_end, sub_firstline + copycol, (size_t)copy_len); + new_end += copy_len; + + (void)vim_regsub_multi(®match, + sub_firstlnum - regmatch.startpos[0].lnum, + sub, new_end, TRUE, p_magic, TRUE); + sub_nsubs++; + did_sub = TRUE; + + /* Move the cursor to the start of the line, to avoid that it + * is beyond the end of the line after the substitution. */ + curwin->w_cursor.col = 0; + + /* For a multi-line match, make a copy of the last matched + * line and continue in that one. */ + if (nmatch > 1) { + sub_firstlnum += nmatch - 1; + vim_free(sub_firstline); + sub_firstline = vim_strsave(ml_get(sub_firstlnum)); + /* When going beyond the last line, stop substituting. */ + if (sub_firstlnum <= line2) + do_again = TRUE; + else + do_all = FALSE; + } + + /* Remember next character to be copied. */ + copycol = regmatch.endpos[0].col; + + if (skip_match) { + /* Already hit end of the buffer, sub_firstlnum is one + * less than what it ought to be. */ + vim_free(sub_firstline); + sub_firstline = vim_strsave((char_u *)""); + copycol = 0; + } + + /* + * Now the trick is to replace CTRL-M chars with a real line + * break. This would make it impossible to insert a CTRL-M in + * the text. The line break can be avoided by preceding the + * CTRL-M with a backslash. To be able to insert a backslash, + * they must be doubled in the string and are halved here. + * That is Vi compatible. + */ + for (p1 = new_end; *p1; ++p1) { + if (p1[0] == '\\' && p1[1] != NUL) /* remove backslash */ + STRMOVE(p1, p1 + 1); + else if (*p1 == CAR) { + if (u_inssub(lnum) == OK) { /* prepare for undo */ + *p1 = NUL; /* truncate up to the CR */ + ml_append(lnum - 1, new_start, + (colnr_T)(p1 - new_start + 1), FALSE); + mark_adjust(lnum + 1, (linenr_T)MAXLNUM, 1L, 0L); + if (do_ask) + appended_lines(lnum - 1, 1L); + else { + if (first_line == 0) + first_line = lnum; + last_line = lnum + 1; + } + /* All line numbers increase. */ + ++sub_firstlnum; + ++lnum; + ++line2; + /* move the cursor to the new line, like Vi */ + ++curwin->w_cursor.lnum; + /* copy the rest */ + STRMOVE(new_start, p1 + 1); + p1 = new_start - 1; + } + } else if (has_mbyte) + p1 += (*mb_ptr2len)(p1) - 1; + } + + /* + * 4. If do_all is set, find next match. + * Prevent endless loop with patterns that match empty + * strings, e.g. :s/$/pat/g or :s/[a-z]* /(&)/g. + * But ":s/\n/#/" is OK. + */ +skip: + /* We already know that we did the last subst when we are at + * the end of the line, except that a pattern like + * "bar\|\nfoo" may match at the NUL. "lnum" can be below + * "line2" when there is a \zs in the pattern after a line + * break. */ + lastone = (skip_match + || got_int + || got_quit + || lnum > line2 + || !(do_all || do_again) + || (sub_firstline[matchcol] == NUL && nmatch <= 1 + && !re_multiline(regmatch.regprog))); + nmatch = -1; + + /* + * Replace the line in the buffer when needed. This is + * skipped when there are more matches. + * The check for nmatch_tl is needed for when multi-line + * matching must replace the lines before trying to do another + * match, otherwise "\@<=" won't work. + * When the match starts below where we start searching also + * need to replace the line first (using \zs after \n). + */ + if (lastone + || nmatch_tl > 0 + || (nmatch = vim_regexec_multi(®match, curwin, + curbuf, sub_firstlnum, + matchcol, NULL)) == 0 + || regmatch.startpos[0].lnum > 0) { + if (new_start != NULL) { + /* + * Copy the rest of the line, that didn't match. + * "matchcol" has to be adjusted, we use the end of + * the line as reference, because the substitute may + * have changed the number of characters. Same for + * "prev_matchcol". + */ + STRCAT(new_start, sub_firstline + copycol); + matchcol = (colnr_T)STRLEN(sub_firstline) - matchcol; + prev_matchcol = (colnr_T)STRLEN(sub_firstline) + - prev_matchcol; + + if (u_savesub(lnum) != OK) + break; + ml_replace(lnum, new_start, TRUE); + + if (nmatch_tl > 0) { + /* + * Matched lines have now been substituted and are + * useless, delete them. The part after the match + * has been appended to new_start, we don't need + * it in the buffer. + */ + ++lnum; + if (u_savedel(lnum, nmatch_tl) != OK) + break; + for (i = 0; i < nmatch_tl; ++i) + ml_delete(lnum, (int)FALSE); + mark_adjust(lnum, lnum + nmatch_tl - 1, + (long)MAXLNUM, -nmatch_tl); + if (do_ask) + deleted_lines(lnum, nmatch_tl); + --lnum; + line2 -= nmatch_tl; /* nr of lines decreases */ + nmatch_tl = 0; + } + + /* When asking, undo is saved each time, must also set + * changed flag each time. */ + if (do_ask) + changed_bytes(lnum, 0); + else { + if (first_line == 0) + first_line = lnum; + last_line = lnum + 1; + } + + sub_firstlnum = lnum; + vim_free(sub_firstline); /* free the temp buffer */ + sub_firstline = new_start; + new_start = NULL; + matchcol = (colnr_T)STRLEN(sub_firstline) - matchcol; + prev_matchcol = (colnr_T)STRLEN(sub_firstline) + - prev_matchcol; + copycol = 0; + } + if (nmatch == -1 && !lastone) + nmatch = vim_regexec_multi(®match, curwin, curbuf, + sub_firstlnum, matchcol, NULL); + + /* + * 5. break if there isn't another match in this line + */ + if (nmatch <= 0) { + /* If the match found didn't start where we were + * searching, do the next search in the line where we + * found the match. */ + if (nmatch == -1) + lnum -= regmatch.startpos[0].lnum; + break; + } + } + + line_breakcheck(); + } + + if (did_sub) + ++sub_nlines; + vim_free(new_start); /* for when substitute was cancelled */ + vim_free(sub_firstline); /* free the copy of the original line */ + sub_firstline = NULL; + } + + line_breakcheck(); + } + + if (first_line != 0) { + /* Need to subtract the number of added lines from "last_line" to get + * the line number before the change (same as adding the number of + * deleted lines). */ + i = curbuf->b_ml.ml_line_count - old_line_count; + changed_lines(first_line, 0, last_line - i, i); + } + +outofmem: + vim_free(sub_firstline); /* may have to free allocated copy of the line */ + + /* ":s/pat//n" doesn't move the cursor */ + if (do_count) + curwin->w_cursor = old_cursor; + + if (sub_nsubs > start_nsubs) { + /* Set the '[ and '] marks. */ + curbuf->b_op_start.lnum = eap->line1; + curbuf->b_op_end.lnum = line2; + curbuf->b_op_start.col = curbuf->b_op_end.col = 0; + + if (!global_busy) { + if (!do_ask) { /* when interactive leave cursor on the match */ + if (endcolumn) + coladvance((colnr_T)MAXCOL); + else + beginline(BL_WHITE | BL_FIX); + } + if (!do_sub_msg(do_count) && do_ask) + MSG(""); + } else + global_need_beginline = TRUE; + if (do_print) + print_line(curwin->w_cursor.lnum, do_number, do_list); + } else if (!global_busy) { + if (got_int) /* interrupted */ + EMSG(_(e_interr)); + else if (got_match) /* did find something but nothing substituted */ + MSG(""); + else if (do_error) /* nothing found */ + EMSG2(_(e_patnotf2), get_search_pat()); + } + + if (do_ask && hasAnyFolding(curwin)) + /* Cursor position may require updating */ + changed_window_setting(); + + vim_regfree(regmatch.regprog); +} + +/* + * Give message for number of substitutions. + * Can also be used after a ":global" command. + * Return TRUE if a message was given. + */ +int do_sub_msg(count_only) +int count_only; /* used 'n' flag for ":s" */ +{ + /* + * Only report substitutions when: + * - more than 'report' substitutions + * - command was typed by user, or number of changed lines > 'report' + * - giving messages is not disabled by 'lazyredraw' + */ + if (((sub_nsubs > p_report && (KeyTyped || sub_nlines > 1 || p_report < 1)) + || count_only) + && messaging()) { + if (got_int) + STRCPY(msg_buf, _("(Interrupted) ")); + else + *msg_buf = NUL; + if (sub_nsubs == 1) + vim_snprintf_add((char *)msg_buf, sizeof(msg_buf), + "%s", count_only ? _("1 match") : _("1 substitution")); + else + vim_snprintf_add((char *)msg_buf, sizeof(msg_buf), + count_only ? _("%ld matches") : _("%ld substitutions"), + sub_nsubs); + if (sub_nlines == 1) + vim_snprintf_add((char *)msg_buf, sizeof(msg_buf), + "%s", _(" on 1 line")); + else + vim_snprintf_add((char *)msg_buf, sizeof(msg_buf), + _(" on %ld lines"), (long)sub_nlines); + if (msg(msg_buf)) + /* save message to display it after redraw */ + set_keep_msg(msg_buf, 0); + return TRUE; + } + if (got_int) { + EMSG(_(e_interr)); + return TRUE; + } + return FALSE; +} + +/* + * Execute a global command of the form: + * + * g/pattern/X : execute X on all lines where pattern matches + * v/pattern/X : execute X on all lines where pattern does not match + * + * where 'X' is an EX command + * + * The command character (as well as the trailing slash) is optional, and + * is assumed to be 'p' if missing. + * + * This is implemented in two passes: first we scan the file for the pattern and + * set a mark for each line that (not) matches. Secondly we execute the command + * for each line that has a mark. This is required because after deleting + * lines we do not know where to search for the next match. + */ +void ex_global(eap) +exarg_T *eap; +{ + linenr_T lnum; /* line number according to old situation */ + int ndone = 0; + int type; /* first char of cmd: 'v' or 'g' */ + char_u *cmd; /* command argument */ + + char_u delim; /* delimiter, normally '/' */ + char_u *pat; + regmmatch_T regmatch; + int match; + int which_pat; + + if (global_busy) { + EMSG(_("E147: Cannot do :global recursive")); /* will increment global_busy */ + return; + } + + if (eap->forceit) /* ":global!" is like ":vglobal" */ + type = 'v'; + else + type = *eap->cmd; + cmd = eap->arg; + which_pat = RE_LAST; /* default: use last used regexp */ + + /* + * undocumented vi feature: + * "\/" and "\?": use previous search pattern. + * "\&": use previous substitute pattern. + */ + if (*cmd == '\\') { + ++cmd; + if (vim_strchr((char_u *)"/?&", *cmd) == NULL) { + EMSG(_(e_backslash)); + return; + } + if (*cmd == '&') + which_pat = RE_SUBST; /* use previous substitute pattern */ + else + which_pat = RE_SEARCH; /* use previous search pattern */ + ++cmd; + pat = (char_u *)""; + } else if (*cmd == NUL) { + EMSG(_("E148: Regular expression missing from global")); + return; + } else { + delim = *cmd; /* get the delimiter */ + if (delim) + ++cmd; /* skip delimiter if there is one */ + pat = cmd; /* remember start of pattern */ + cmd = skip_regexp(cmd, delim, p_magic, &eap->arg); + if (cmd[0] == delim) /* end delimiter found */ + *cmd++ = NUL; /* replace it with a NUL */ + } + + if (p_altkeymap && curwin->w_p_rl) + lrFswap(pat,0); + + if (search_regcomp(pat, RE_BOTH, which_pat, SEARCH_HIS, ®match) == FAIL) { + EMSG(_(e_invcmd)); + return; + } + + /* + * pass 1: set marks for each (not) matching line + */ + for (lnum = eap->line1; lnum <= eap->line2 && !got_int; ++lnum) { + /* a match on this line? */ + match = vim_regexec_multi(®match, curwin, curbuf, lnum, + (colnr_T)0, NULL); + if ((type == 'g' && match) || (type == 'v' && !match)) { + ml_setmarked(lnum); + ndone++; + } + line_breakcheck(); + } + + /* + * pass 2: execute the command for each line that has been marked + */ + if (got_int) + MSG(_(e_interr)); + else if (ndone == 0) { + if (type == 'v') + smsg((char_u *)_("Pattern found in every line: %s"), pat); + else + smsg((char_u *)_("Pattern not found: %s"), pat); + } else + global_exe(cmd); + + ml_clearmarked(); /* clear rest of the marks */ + vim_regfree(regmatch.regprog); +} + +/* + * Execute "cmd" on lines marked with ml_setmarked(). + */ +void global_exe(cmd) +char_u *cmd; +{ + linenr_T old_lcount; /* b_ml.ml_line_count before the command */ + buf_T *old_buf = curbuf; /* remember what buffer we started in */ + linenr_T lnum; /* line number according to old situation */ + + /* + * Set current position only once for a global command. + * If global_busy is set, setpcmark() will not do anything. + * If there is an error, global_busy will be incremented. + */ + setpcmark(); + + /* When the command writes a message, don't overwrite the command. */ + msg_didout = TRUE; + + sub_nsubs = 0; + sub_nlines = 0; + global_need_beginline = FALSE; + global_busy = 1; + old_lcount = curbuf->b_ml.ml_line_count; + while (!got_int && (lnum = ml_firstmarked()) != 0 && global_busy == 1) { + curwin->w_cursor.lnum = lnum; + curwin->w_cursor.col = 0; + if (*cmd == NUL || *cmd == '\n') + do_cmdline((char_u *)"p", NULL, NULL, DOCMD_NOWAIT); + else + do_cmdline(cmd, NULL, NULL, DOCMD_NOWAIT); + ui_breakcheck(); + } + + global_busy = 0; + if (global_need_beginline) + beginline(BL_WHITE | BL_FIX); + else + check_cursor(); /* cursor may be beyond the end of the line */ + + /* the cursor may not have moved in the text but a change in a previous + * line may move it on the screen */ + changed_line_abv_curs(); + + /* If it looks like no message was written, allow overwriting the + * command with the report for number of changes. */ + if (msg_col == 0 && msg_scrolled == 0) + msg_didout = FALSE; + + /* If substitutes done, report number of substitutes, otherwise report + * number of extra or deleted lines. + * Don't report extra or deleted lines in the edge case where the buffer + * we are in after execution is different from the buffer we started in. */ + if (!do_sub_msg(FALSE) && curbuf == old_buf) + msgmore(curbuf->b_ml.ml_line_count - old_lcount); +} + +int read_viminfo_sub_string(virp, force) +vir_T *virp; +int force; +{ + if (force) + vim_free(old_sub); + if (force || old_sub == NULL) + old_sub = viminfo_readstring(virp, 1, TRUE); + return viminfo_readline(virp); +} + +void write_viminfo_sub_string(fp) +FILE *fp; +{ + if (get_viminfo_parameter('/') != 0 && old_sub != NULL) { + fputs(_("\n# Last Substitute String:\n$"), fp); + viminfo_writestring(fp, old_sub); + } +} + +#if defined(EXITFREE) || defined(PROTO) +void free_old_sub() { + vim_free(old_sub); +} + +#endif + +/* + * Set up for a tagpreview. + * Return TRUE when it was created. + */ +int prepare_tagpreview(undo_sync) +int undo_sync; /* sync undo when leaving the window */ +{ + win_T *wp; + + + /* + * If there is already a preview window open, use that one. + */ + if (!curwin->w_p_pvw) { + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp->w_p_pvw) + break; + if (wp != NULL) + win_enter(wp, undo_sync); + else { + /* + * There is no preview window open yet. Create one. + */ + if (win_split(g_do_tagpreview > 0 ? g_do_tagpreview : 0, 0) + == FAIL) + return FALSE; + curwin->w_p_pvw = TRUE; + curwin->w_p_wfh = TRUE; + RESET_BINDING(curwin); /* don't take over 'scrollbind' + and 'cursorbind' */ + curwin->w_p_diff = FALSE; /* no 'diff' */ + curwin->w_p_fdc = 0; /* no 'foldcolumn' */ + return TRUE; + } + } + return FALSE; +} + + + +/* + * ":help": open a read-only window on a help file + */ +void ex_help(eap) +exarg_T *eap; +{ + char_u *arg; + char_u *tag; + FILE *helpfd; /* file descriptor of help file */ + int n; + int i; + win_T *wp; + int num_matches; + char_u **matches; + char_u *p; + int empty_fnum = 0; + int alt_fnum = 0; + buf_T *buf; + int len; + char_u *lang; + int old_KeyTyped = KeyTyped; + + if (eap != NULL) { + /* + * A ":help" command ends at the first LF, or at a '|' that is + * followed by some text. Set nextcmd to the following command. + */ + for (arg = eap->arg; *arg; ++arg) { + if (*arg == '\n' || *arg == '\r' + || (*arg == '|' && arg[1] != NUL && arg[1] != '|')) { + *arg++ = NUL; + eap->nextcmd = arg; + break; + } + } + arg = eap->arg; + + if (eap->forceit && *arg == NUL && !curbuf->b_help) { + EMSG(_("E478: Don't panic!")); + return; + } + + if (eap->skip) /* not executing commands */ + return; + } else + arg = (char_u *)""; + + /* remove trailing blanks */ + p = arg + STRLEN(arg) - 1; + while (p > arg && vim_iswhite(*p) && p[-1] != '\\') + *p-- = NUL; + + /* Check for a specified language */ + lang = check_help_lang(arg); + + /* When no argument given go to the index. */ + if (*arg == NUL) + arg = (char_u *)"help.txt"; + + /* + * Check if there is a match for the argument. + */ + n = find_help_tags(arg, &num_matches, &matches, + eap != NULL && eap->forceit); + + i = 0; + if (n != FAIL && lang != NULL) + /* Find first item with the requested language. */ + for (i = 0; i < num_matches; ++i) { + len = (int)STRLEN(matches[i]); + if (len > 3 && matches[i][len - 3] == '@' + && STRICMP(matches[i] + len - 2, lang) == 0) + break; + } + if (i >= num_matches || n == FAIL) { + if (lang != NULL) + EMSG3(_("E661: Sorry, no '%s' help for %s"), lang, arg); + else + EMSG2(_("E149: Sorry, no help for %s"), arg); + if (n != FAIL) + FreeWild(num_matches, matches); + return; + } + + /* The first match (in the requested language) is the best match. */ + tag = vim_strsave(matches[i]); + FreeWild(num_matches, matches); + + + /* + * Re-use an existing help window or open a new one. + * Always open a new one for ":tab help". + */ + if (!curwin->w_buffer->b_help + || cmdmod.tab != 0 + ) { + if (cmdmod.tab != 0) + wp = NULL; + else + for (wp = firstwin; wp != NULL; wp = wp->w_next) + if (wp->w_buffer != NULL && wp->w_buffer->b_help) + break; + if (wp != NULL && wp->w_buffer->b_nwindows > 0) + win_enter(wp, TRUE); + else { + /* + * There is no help window yet. + * Try to open the file specified by the "helpfile" option. + */ + if ((helpfd = mch_fopen((char *)p_hf, READBIN)) == NULL) { + smsg((char_u *)_("Sorry, help file \"%s\" not found"), p_hf); + goto erret; + } + fclose(helpfd); + + /* Split off help window; put it at far top if no position + * specified, the current window is vertically split and + * narrow. */ + n = WSP_HELP; + if (cmdmod.split == 0 && curwin->w_width != Columns + && curwin->w_width < 80) + n |= WSP_TOP; + if (win_split(0, n) == FAIL) + goto erret; + + if (curwin->w_height < p_hh) + win_setheight((int)p_hh); + + /* + * Open help file (do_ecmd() will set b_help flag, readfile() will + * set b_p_ro flag). + * Set the alternate file to the previously edited file. + */ + alt_fnum = curbuf->b_fnum; + (void)do_ecmd(0, NULL, NULL, NULL, ECMD_LASTL, + ECMD_HIDE + ECMD_SET_HELP, + NULL /* buffer is still open, don't store info */ + ); + if (!cmdmod.keepalt) + curwin->w_alt_fnum = alt_fnum; + empty_fnum = curbuf->b_fnum; + } + } + + if (!p_im) + restart_edit = 0; /* don't want insert mode in help file */ + + /* Restore KeyTyped, setting 'filetype=help' may reset it. + * It is needed for do_tag top open folds under the cursor. */ + KeyTyped = old_KeyTyped; + + if (tag != NULL) + do_tag(tag, DT_HELP, 1, FALSE, TRUE); + + /* Delete the empty buffer if we're not using it. Careful: autocommands + * may have jumped to another window, check that the buffer is not in a + * window. */ + if (empty_fnum != 0 && curbuf->b_fnum != empty_fnum) { + buf = buflist_findnr(empty_fnum); + if (buf != NULL && buf->b_nwindows == 0) + wipe_buffer(buf, TRUE); + } + + /* keep the previous alternate file */ + if (alt_fnum != 0 && curwin->w_alt_fnum == empty_fnum && !cmdmod.keepalt) + curwin->w_alt_fnum = alt_fnum; + +erret: + vim_free(tag); +} + + +/* + * In an argument search for a language specifiers in the form "@xx". + * Changes the "@" to NUL if found, and returns a pointer to "xx". + * Returns NULL if not found. + */ +char_u * check_help_lang(arg) +char_u *arg; +{ + int len = (int)STRLEN(arg); + + if (len >= 3 && arg[len - 3] == '@' && ASCII_ISALPHA(arg[len - 2]) + && ASCII_ISALPHA(arg[len - 1])) { + arg[len - 3] = NUL; /* remove the '@' */ + return arg + len - 2; + } + return NULL; +} + +/* + * Return a heuristic indicating how well the given string matches. The + * smaller the number, the better the match. This is the order of priorities, + * from best match to worst match: + * - Match with least alpha-numeric characters is better. + * - Match with least total characters is better. + * - Match towards the start is better. + * - Match starting with "+" is worse (feature instead of command) + * Assumption is made that the matched_string passed has already been found to + * match some string for which help is requested. webb. + */ +int help_heuristic(matched_string, offset, wrong_case) +char_u *matched_string; +int offset; /* offset for match */ +int wrong_case; /* no matching case */ +{ + int num_letters; + char_u *p; + + num_letters = 0; + for (p = matched_string; *p; p++) + if (ASCII_ISALNUM(*p)) + num_letters++; + + /* + * Multiply the number of letters by 100 to give it a much bigger + * weighting than the number of characters. + * If there only is a match while ignoring case, add 5000. + * If the match starts in the middle of a word, add 10000 to put it + * somewhere in the last half. + * If the match is more than 2 chars from the start, multiply by 200 to + * put it after matches at the start. + */ + if (ASCII_ISALNUM(matched_string[offset]) && offset > 0 + && ASCII_ISALNUM(matched_string[offset - 1])) + offset += 10000; + else if (offset > 2) + offset *= 200; + if (wrong_case) + offset += 5000; + /* Features are less interesting than the subjects themselves, but "+" + * alone is not a feature. */ + if (matched_string[0] == '+' && matched_string[1] != NUL) + offset += 100; + return (int)(100 * num_letters + STRLEN(matched_string) + offset); +} + +/* + * Compare functions for qsort() below, that checks the help heuristics number + * that has been put after the tagname by find_tags(). + */ +static int help_compare(s1, s2) +const void *s1; +const void *s2; +{ + char *p1; + char *p2; + + p1 = *(char **)s1 + strlen(*(char **)s1) + 1; + p2 = *(char **)s2 + strlen(*(char **)s2) + 1; + return strcmp(p1, p2); +} + +/* + * Find all help tags matching "arg", sort them and return in matches[], with + * the number of matches in num_matches. + * The matches will be sorted with a "best" match algorithm. + * When "keep_lang" is TRUE try keeping the language of the current buffer. + */ +int find_help_tags(arg, num_matches, matches, keep_lang) +char_u *arg; +int *num_matches; +char_u ***matches; +int keep_lang; +{ + char_u *s, *d; + int i; + static char *(mtable[]) = {"*", "g*", "[*", "]*", ":*", + "/*", "/\\*", "\"*", "**", + "cpo-*", "/\\(\\)", "/\\%(\\)", + "?", ":?", "?", "g?", "g?g?", "g??", "z?", + "/\\?", "/\\z(\\)", "\\=", ":s\\=", + "[count]", "[quotex]", "[range]", + "[pattern]", "\\|", "\\%$"}; + static char *(rtable[]) = {"star", "gstar", "[star", "]star", ":star", + "/star", "/\\\\star", "quotestar", "starstar", + "cpo-star", "/\\\\(\\\\)", "/\\\\%(\\\\)", + "?", ":?", "?", "g?", "g?g?", "g??", "z?", + "/\\\\?", "/\\\\z(\\\\)", "\\\\=", ":s\\\\=", + "\\[count]", "\\[quotex]", "\\[range]", + "\\[pattern]", "\\\\bar", "/\\\\%\\$"}; + int flags; + + d = IObuff; /* assume IObuff is long enough! */ + + /* + * Recognize a few exceptions to the rule. Some strings that contain '*' + * with "star". Otherwise '*' is recognized as a wildcard. + */ + for (i = (int)(sizeof(mtable) / sizeof(char *)); --i >= 0; ) + if (STRCMP(arg, mtable[i]) == 0) { + STRCPY(d, rtable[i]); + break; + } + + if (i < 0) { /* no match in table */ + /* Replace "\S" with "/\\S", etc. Otherwise every tag is matched. + * Also replace "\%^" and "\%(", they match every tag too. + * Also "\zs", "\z1", etc. + * Also "\@<", "\@=", "\@<=", etc. + * And also "\_$" and "\_^". */ + if (arg[0] == '\\' + && ((arg[1] != NUL && arg[2] == NUL) + || (vim_strchr((char_u *)"%_z@", arg[1]) != NULL + && arg[2] != NUL))) { + STRCPY(d, "/\\\\"); + STRCPY(d + 3, arg + 1); + /* Check for "/\\_$", should be "/\\_\$" */ + if (d[3] == '_' && d[4] == '$') + STRCPY(d + 4, "\\$"); + } else { + /* Replace: + * "[:...:]" with "\[:...:]" + * "[++...]" with "\[++...]" + * "\{" with "\\{" + */ + if ((arg[0] == '[' && (arg[1] == ':' + || (arg[1] == '+' && arg[2] == '+'))) + || (arg[0] == '\\' && arg[1] == '{')) + *d++ = '\\'; + + for (s = arg; *s; ++s) { + /* + * Replace "|" with "bar" and '"' with "quote" to match the name of + * the tags for these commands. + * Replace "*" with ".*" and "?" with "." to match command line + * completion. + * Insert a backslash before '~', '$' and '.' to avoid their + * special meaning. + */ + if (d - IObuff > IOSIZE - 10) /* getting too long!? */ + break; + switch (*s) { + case '|': STRCPY(d, "bar"); + d += 3; + continue; + case '"': STRCPY(d, "quote"); + d += 5; + continue; + case '*': *d++ = '.'; + break; + case '?': *d++ = '.'; + continue; + case '$': + case '.': + case '~': *d++ = '\\'; + break; + } + + /* + * Replace "^x" by "CTRL-X". Don't do this for "^_" to make + * ":help i_^_CTRL-D" work. + * Insert '-' before and after "CTRL-X" when applicable. + */ + if (*s < ' ' || (*s == '^' && s[1] && (ASCII_ISALPHA(s[1]) + || vim_strchr((char_u *) + "?@[\\]^", + s[1]) != NULL))) { + if (d > IObuff && d[-1] != '_' && d[-1] != '\\') + *d++ = '_'; /* prepend a '_' to make x_CTRL-x */ + STRCPY(d, "CTRL-"); + d += 5; + if (*s < ' ') { + *d++ = *s + '@'; + if (d[-1] == '\\') + *d++ = '\\'; /* double a backslash */ + } else + *d++ = *++s; + if (s[1] != NUL && s[1] != '_') + *d++ = '_'; /* append a '_' */ + continue; + } else if (*s == '^') /* "^" or "CTRL-^" or "^_" */ + *d++ = '\\'; + + /* + * Insert a backslash before a backslash after a slash, for search + * pattern tags: "/\|" --> "/\\|". + */ + else if (s[0] == '\\' && s[1] != '\\' + && *arg == '/' && s == arg + 1) + *d++ = '\\'; + + /* "CTRL-\_" -> "CTRL-\\_" to avoid the special meaning of "\_" in + * "CTRL-\_CTRL-N" */ + if (STRNICMP(s, "CTRL-\\_", 7) == 0) { + STRCPY(d, "CTRL-\\\\"); + d += 7; + s += 6; + } + + *d++ = *s; + + /* + * If tag starts with ', toss everything after a second '. Fixes + * CTRL-] on 'option'. (would include the trailing '.'). + */ + if (*s == '\'' && s > arg && *arg == '\'') + break; + } + *d = NUL; + + if (*IObuff == '`') { + if (d > IObuff + 2 && d[-1] == '`') { + /* remove the backticks from `command` */ + mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff)); + d[-2] = NUL; + } else if (d > IObuff + 3 && d[-2] == '`' && d[-1] == ',') { + /* remove the backticks and comma from `command`, */ + mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff)); + d[-3] = NUL; + } else if (d > IObuff + 4 && d[-3] == '`' + && d[-2] == '\\' && d[-1] == '.') { + /* remove the backticks and dot from `command`\. */ + mch_memmove(IObuff, IObuff + 1, STRLEN(IObuff)); + d[-4] = NUL; + } + } + } + } + + *matches = (char_u **)""; + *num_matches = 0; + flags = TAG_HELP | TAG_REGEXP | TAG_NAMES | TAG_VERBOSE; + if (keep_lang) + flags |= TAG_KEEP_LANG; + if (find_tags(IObuff, num_matches, matches, flags, (int)MAXCOL, NULL) == OK + && *num_matches > 0) { + /* Sort the matches found on the heuristic number that is after the + * tag name. */ + qsort((void *)*matches, (size_t)*num_matches, + sizeof(char_u *), help_compare); + /* Delete more than TAG_MANY to reduce the size of the listing. */ + while (*num_matches > TAG_MANY) + vim_free((*matches)[--*num_matches]); + } + return OK; +} + +/* + * After reading a help file: May cleanup a help buffer when syntax + * highlighting is not used. + */ +void fix_help_buffer() { + linenr_T lnum; + char_u *line; + int in_example = FALSE; + int len; + char_u *fname; + char_u *p; + char_u *rt; + int mustfree; + + /* set filetype to "help". */ + set_option_value((char_u *)"ft", 0L, (char_u *)"help", OPT_LOCAL); + + if (!syntax_present(curwin)) { + for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) { + line = ml_get_buf(curbuf, lnum, FALSE); + len = (int)STRLEN(line); + if (in_example && len > 0 && !vim_iswhite(line[0])) { + /* End of example: non-white or '<' in first column. */ + if (line[0] == '<') { + /* blank-out a '<' in the first column */ + line = ml_get_buf(curbuf, lnum, TRUE); + line[0] = ' '; + } + in_example = FALSE; + } + if (!in_example && len > 0) { + if (line[len - 1] == '>' && (len == 1 || line[len - 2] == ' ')) { + /* blank-out a '>' in the last column (start of example) */ + line = ml_get_buf(curbuf, lnum, TRUE); + line[len - 1] = ' '; + in_example = TRUE; + } else if (line[len - 1] == '~') { + /* blank-out a '~' at the end of line (header marker) */ + line = ml_get_buf(curbuf, lnum, TRUE); + line[len - 1] = ' '; + } + } + } + } + + /* + * In the "help.txt" and "help.abx" file, add the locally added help + * files. This uses the very first line in the help file. + */ + fname = gettail(curbuf->b_fname); + if (fnamecmp(fname, "help.txt") == 0 + || (fnamencmp(fname, "help.", 5) == 0 + && ASCII_ISALPHA(fname[5]) + && ASCII_ISALPHA(fname[6]) + && TOLOWER_ASC(fname[7]) == 'x' + && fname[8] == NUL) + ) { + for (lnum = 1; lnum < curbuf->b_ml.ml_line_count; ++lnum) { + line = ml_get_buf(curbuf, lnum, FALSE); + if (strstr((char *)line, "*local-additions*") == NULL) + continue; + + /* Go through all directories in 'runtimepath', skipping + * $VIMRUNTIME. */ + p = p_rtp; + while (*p != NUL) { + copy_option_part(&p, NameBuff, MAXPATHL, ","); + mustfree = FALSE; + rt = vim_getenv((char_u *)"VIMRUNTIME", &mustfree); + if (fullpathcmp(rt, NameBuff, FALSE) != FPC_SAME) { + int fcount; + char_u **fnames; + FILE *fd; + char_u *s; + int fi; + vimconv_T vc; + char_u *cp; + + /* Find all "doc/ *.txt" files in this directory. */ + add_pathsep(NameBuff); + STRCAT(NameBuff, "doc/*.??[tx]"); + if (gen_expand_wildcards(1, &NameBuff, &fcount, + &fnames, EW_FILE|EW_SILENT) == OK + && fcount > 0) { + int i1; + int i2; + char_u *f1; + char_u *f2; + char_u *t1; + char_u *e1; + char_u *e2; + + /* If foo.abx is found use it instead of foo.txt in + * the same directory. */ + for (i1 = 0; i1 < fcount; ++i1) { + for (i2 = 0; i2 < fcount; ++i2) { + if (i1 == i2) + continue; + if (fnames[i1] == NULL || fnames[i2] == NULL) + continue; + f1 = fnames[i1]; + f2 = fnames[i2]; + t1 = gettail(f1); + if (fnamencmp(f1, f2, t1 - f1) != 0) + continue; + e1 = vim_strrchr(t1, '.'); + e2 = vim_strrchr(gettail(f2), '.'); + if (e1 == NUL || e2 == NUL) + continue; + if (fnamecmp(e1, ".txt") != 0 + && fnamecmp(e1, fname + 4) != 0) { + /* Not .txt and not .abx, remove it. */ + vim_free(fnames[i1]); + fnames[i1] = NULL; + continue; + } + if (fnamencmp(f1, f2, e1 - f1) != 0) + continue; + if (fnamecmp(e1, ".txt") == 0 + && fnamecmp(e2, fname + 4) == 0) { + /* use .abx instead of .txt */ + vim_free(fnames[i1]); + fnames[i1] = NULL; + } + } + } + for (fi = 0; fi < fcount; ++fi) { + if (fnames[fi] == NULL) + continue; + fd = mch_fopen((char *)fnames[fi], "r"); + if (fd != NULL) { + vim_fgets(IObuff, IOSIZE, fd); + if (IObuff[0] == '*' + && (s = vim_strchr(IObuff + 1, '*')) + != NULL) { + int this_utf = MAYBE; + /* Change tag definition to a + * reference and remove /. */ + IObuff[0] = '|'; + *s = '|'; + while (*s != NUL) { + if (*s == '\r' || *s == '\n') + *s = NUL; + /* The text is utf-8 when a byte + * above 127 is found and no + * illegal byte sequence is found. + */ + if (*s >= 0x80 && this_utf != FALSE) { + int l; + + this_utf = TRUE; + l = utf_ptr2len(s); + if (l == 1) + this_utf = FALSE; + s += l - 1; + } + ++s; + } + /* The help file is latin1 or utf-8; + * conversion to the current + * 'encoding' may be required. */ + vc.vc_type = CONV_NONE; + convert_setup(&vc, (char_u *)( + this_utf == TRUE ? "utf-8" + : "latin1"), p_enc); + if (vc.vc_type == CONV_NONE) + /* No conversion needed. */ + cp = IObuff; + else { + /* Do the conversion. If it fails + * use the unconverted text. */ + cp = string_convert(&vc, IObuff, + NULL); + if (cp == NULL) + cp = IObuff; + } + convert_setup(&vc, NULL, NULL); + + ml_append(lnum, cp, (colnr_T)0, FALSE); + if (cp != IObuff) + vim_free(cp); + ++lnum; + } + fclose(fd); + } + } + FreeWild(fcount, fnames); + } + } + if (mustfree) + vim_free(rt); + } + break; + } + } +} + +/* + * ":exusage" + */ +void ex_exusage(eap) +exarg_T *eap UNUSED; +{ + do_cmdline_cmd((char_u *)"help ex-cmd-index"); +} + +/* + * ":viusage" + */ +void ex_viusage(eap) +exarg_T *eap UNUSED; +{ + do_cmdline_cmd((char_u *)"help normal-index"); +} + +static void helptags_one __ARGS((char_u *dir, char_u *ext, char_u *lang, + int add_help_tags)); + +/* + * ":helptags" + */ +void ex_helptags(eap) +exarg_T *eap; +{ + garray_T ga; + int i, j; + int len; + char_u lang[2]; + expand_T xpc; + char_u *dirname; + char_u ext[5]; + char_u fname[8]; + int filecount; + char_u **files; + int add_help_tags = FALSE; + + /* Check for ":helptags ++t {dir}". */ + if (STRNCMP(eap->arg, "++t", 3) == 0 && vim_iswhite(eap->arg[3])) { + add_help_tags = TRUE; + eap->arg = skipwhite(eap->arg + 3); + } + + ExpandInit(&xpc); + xpc.xp_context = EXPAND_DIRECTORIES; + dirname = ExpandOne(&xpc, eap->arg, NULL, + WILD_LIST_NOTFOUND|WILD_SILENT, WILD_EXPAND_FREE); + if (dirname == NULL || !mch_isdir(dirname)) { + EMSG2(_("E150: Not a directory: %s"), eap->arg); + return; + } + + /* Get a list of all files in the help directory and in subdirectories. */ + STRCPY(NameBuff, dirname); + add_pathsep(NameBuff); + STRCAT(NameBuff, "**"); + if (gen_expand_wildcards(1, &NameBuff, &filecount, &files, + EW_FILE|EW_SILENT) == FAIL + || filecount == 0) { + EMSG2("E151: No match: %s", NameBuff); + vim_free(dirname); + return; + } + + /* Go over all files in the directory to find out what languages are + * present. */ + ga_init2(&ga, 1, 10); + for (i = 0; i < filecount; ++i) { + len = (int)STRLEN(files[i]); + if (len > 4) { + if (STRICMP(files[i] + len - 4, ".txt") == 0) { + /* ".txt" -> language "en" */ + lang[0] = 'e'; + lang[1] = 'n'; + } else if (files[i][len - 4] == '.' + && ASCII_ISALPHA(files[i][len - 3]) + && ASCII_ISALPHA(files[i][len - 2]) + && TOLOWER_ASC(files[i][len - 1]) == 'x') { + /* ".abx" -> language "ab" */ + lang[0] = TOLOWER_ASC(files[i][len - 3]); + lang[1] = TOLOWER_ASC(files[i][len - 2]); + } else + continue; + + /* Did we find this language already? */ + for (j = 0; j < ga.ga_len; j += 2) + if (STRNCMP(lang, ((char_u *)ga.ga_data) + j, 2) == 0) + break; + if (j == ga.ga_len) { + /* New language, add it. */ + if (ga_grow(&ga, 2) == FAIL) + break; + ((char_u *)ga.ga_data)[ga.ga_len++] = lang[0]; + ((char_u *)ga.ga_data)[ga.ga_len++] = lang[1]; + } + } + } + + /* + * Loop over the found languages to generate a tags file for each one. + */ + for (j = 0; j < ga.ga_len; j += 2) { + STRCPY(fname, "tags-xx"); + fname[5] = ((char_u *)ga.ga_data)[j]; + fname[6] = ((char_u *)ga.ga_data)[j + 1]; + if (fname[5] == 'e' && fname[6] == 'n') { + /* English is an exception: use ".txt" and "tags". */ + fname[4] = NUL; + STRCPY(ext, ".txt"); + } else { + /* Language "ab" uses ".abx" and "tags-ab". */ + STRCPY(ext, ".xxx"); + ext[1] = fname[5]; + ext[2] = fname[6]; + } + helptags_one(dirname, ext, fname, add_help_tags); + } + + ga_clear(&ga); + FreeWild(filecount, files); + + vim_free(dirname); +} + +static void helptags_one(dir, ext, tagfname, add_help_tags) +char_u *dir; /* doc directory */ +char_u *ext; /* suffix, ".txt", ".itx", ".frx", etc. */ +char_u *tagfname; /* "tags" for English, "tags-fr" for French. */ +int add_help_tags; /* add "help-tags" tag */ +{ + FILE *fd_tags; + FILE *fd; + garray_T ga; + int filecount; + char_u **files; + char_u *p1, *p2; + int fi; + char_u *s; + int i; + char_u *fname; + int dirlen; + int utf8 = MAYBE; + int this_utf8; + int firstline; + int mix = FALSE; /* detected mixed encodings */ + + /* + * Find all *.txt files. + */ + dirlen = (int)STRLEN(dir); + STRCPY(NameBuff, dir); + STRCAT(NameBuff, "/**/*"); + STRCAT(NameBuff, ext); + if (gen_expand_wildcards(1, &NameBuff, &filecount, &files, + EW_FILE|EW_SILENT) == FAIL + || filecount == 0) { + if (!got_int) + EMSG2("E151: No match: %s", NameBuff); + return; + } + + /* + * Open the tags file for writing. + * Do this before scanning through all the files. + */ + STRCPY(NameBuff, dir); + add_pathsep(NameBuff); + STRCAT(NameBuff, tagfname); + fd_tags = mch_fopen((char *)NameBuff, "w"); + if (fd_tags == NULL) { + EMSG2(_("E152: Cannot open %s for writing"), NameBuff); + FreeWild(filecount, files); + return; + } + + /* + * If using the "++t" argument or generating tags for "$VIMRUNTIME/doc" + * add the "help-tags" tag. + */ + ga_init2(&ga, (int)sizeof(char_u *), 100); + if (add_help_tags || fullpathcmp((char_u *)"$VIMRUNTIME/doc", + dir, FALSE) == FPC_SAME) { + if (ga_grow(&ga, 1) == FAIL) + got_int = TRUE; + else { + s = alloc(18 + (unsigned)STRLEN(tagfname)); + if (s == NULL) + got_int = TRUE; + else { + sprintf((char *)s, "help-tags\t%s\t1\n", tagfname); + ((char_u **)ga.ga_data)[ga.ga_len] = s; + ++ga.ga_len; + } + } + } + + /* + * Go over all the files and extract the tags. + */ + for (fi = 0; fi < filecount && !got_int; ++fi) { + fd = mch_fopen((char *)files[fi], "r"); + if (fd == NULL) { + EMSG2(_("E153: Unable to open %s for reading"), files[fi]); + continue; + } + fname = files[fi] + dirlen + 1; + + firstline = TRUE; + while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) { + if (firstline) { + /* Detect utf-8 file by a non-ASCII char in the first line. */ + this_utf8 = MAYBE; + for (s = IObuff; *s != NUL; ++s) + if (*s >= 0x80) { + int l; + + this_utf8 = TRUE; + l = utf_ptr2len(s); + if (l == 1) { + /* Illegal UTF-8 byte sequence. */ + this_utf8 = FALSE; + break; + } + s += l - 1; + } + if (this_utf8 == MAYBE) /* only ASCII characters found */ + this_utf8 = FALSE; + if (utf8 == MAYBE) /* first file */ + utf8 = this_utf8; + else if (utf8 != this_utf8) { + EMSG2(_( + "E670: Mix of help file encodings within a language: %s"), + files[fi]); + mix = !got_int; + got_int = TRUE; + } + firstline = FALSE; + } + p1 = vim_strchr(IObuff, '*'); /* find first '*' */ + while (p1 != NULL) { + /* Use vim_strbyte() instead of vim_strchr() so that when + * 'encoding' is dbcs it still works, don't find '*' in the + * second byte. */ + p2 = vim_strbyte(p1 + 1, '*'); /* find second '*' */ + if (p2 != NULL && p2 > p1 + 1) { /* skip "*" and "**" */ + for (s = p1 + 1; s < p2; ++s) + if (*s == ' ' || *s == '\t' || *s == '|') + break; + + /* + * Only accept a *tag* when it consists of valid + * characters, there is white space before it and is + * followed by a white character or end-of-line. + */ + if (s == p2 + && (p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t') + && (vim_strchr((char_u *)" \t\n\r", s[1]) != NULL + || s[1] == '\0')) { + *p2 = '\0'; + ++p1; + if (ga_grow(&ga, 1) == FAIL) { + got_int = TRUE; + break; + } + s = alloc((unsigned)(p2 - p1 + STRLEN(fname) + 2)); + if (s == NULL) { + got_int = TRUE; + break; + } + ((char_u **)ga.ga_data)[ga.ga_len] = s; + ++ga.ga_len; + sprintf((char *)s, "%s\t%s", p1, fname); + + /* find next '*' */ + p2 = vim_strchr(p2 + 1, '*'); + } + } + p1 = p2; + } + line_breakcheck(); + } + + fclose(fd); + } + + FreeWild(filecount, files); + + if (!got_int) { + /* + * Sort the tags. + */ + sort_strings((char_u **)ga.ga_data, ga.ga_len); + + /* + * Check for duplicates. + */ + for (i = 1; i < ga.ga_len; ++i) { + p1 = ((char_u **)ga.ga_data)[i - 1]; + p2 = ((char_u **)ga.ga_data)[i]; + while (*p1 == *p2) { + if (*p2 == '\t') { + *p2 = NUL; + vim_snprintf((char *)NameBuff, MAXPATHL, + _("E154: Duplicate tag \"%s\" in file %s/%s"), + ((char_u **)ga.ga_data)[i], dir, p2 + 1); + EMSG(NameBuff); + *p2 = '\t'; + break; + } + ++p1; + ++p2; + } + } + + if (utf8 == TRUE) + fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n"); + + /* + * Write the tags into the file. + */ + for (i = 0; i < ga.ga_len; ++i) { + s = ((char_u **)ga.ga_data)[i]; + if (STRNCMP(s, "help-tags\t", 10) == 0) + /* help-tags entry was added in formatted form */ + fputs((char *)s, fd_tags); + else { + fprintf(fd_tags, "%s\t/*", s); + for (p1 = s; *p1 != '\t'; ++p1) { + /* insert backslash before '\\' and '/' */ + if (*p1 == '\\' || *p1 == '/') + putc('\\', fd_tags); + putc(*p1, fd_tags); + } + fprintf(fd_tags, "*\n"); + } + } + } + if (mix) + got_int = FALSE; /* continue with other languages */ + + for (i = 0; i < ga.ga_len; ++i) + vim_free(((char_u **)ga.ga_data)[i]); + ga_clear(&ga); + fclose(fd_tags); /* there is no check for an error... */ +} + + diff --git a/src/ex_cmds.h b/src/ex_cmds.h new file mode 100644 index 0000000000..a560789fef --- /dev/null +++ b/src/ex_cmds.h @@ -0,0 +1,1191 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * This file defines the Ex commands. + * When DO_DECLARE_EXCMD is defined, the table with ex command names and + * options results. + * When DO_DECLARE_EXCMD is NOT defined, the enum with all the Ex commands + * results. + * This clever trick was invented by Ron Aaron. + */ + +/* + * When adding an Ex command: + * 1. Add an entry in the table below. Keep it sorted on the shortest + * version of the command name that works. If it doesn't start with a + * lower case letter, add it at the end. + * 2. Add a "case: CMD_xxx" in the big switch in ex_docmd.c. + * 3. Add an entry in the index for Ex commands at ":help ex-cmd-index". + * 4. Add documentation in ../doc/xxx.txt. Add a tag for both the short and + * long name of the command. + */ + +#ifdef RANGE +# undef RANGE /* SASC on Amiga defines it */ +#endif + +#define RANGE 0x001 /* allow a linespecs */ +#define BANG 0x002 /* allow a ! after the command name */ +#define EXTRA 0x004 /* allow extra args after command name */ +#define XFILE 0x008 /* expand wildcards in extra part */ +#define NOSPC 0x010 /* no spaces allowed in the extra part */ +#define DFLALL 0x020 /* default file range is 1,$ */ +#define WHOLEFOLD 0x040 /* extend range to include whole fold also + when less than two numbers given */ +#define NEEDARG 0x080 /* argument required */ +#define TRLBAR 0x100 /* check for trailing vertical bar */ +#define REGSTR 0x200 /* allow "x for register designation */ +#define COUNT 0x400 /* allow count in argument, after command */ +#define NOTRLCOM 0x800 /* no trailing comment allowed */ +#define ZEROR 0x1000 /* zero line number allowed */ +#define USECTRLV 0x2000 /* do not remove CTRL-V from argument */ +#define NOTADR 0x4000 /* number before command is not an address */ +#define EDITCMD 0x8000 /* allow "+command" argument */ +#define BUFNAME 0x10000L /* accepts buffer name */ +#define BUFUNL 0x20000L /* accepts unlisted buffer too */ +#define ARGOPT 0x40000L /* allow "++opt=val" argument */ +#define SBOXOK 0x80000L /* allowed in the sandbox */ +#define CMDWIN 0x100000L /* allowed in cmdline window */ +#define MODIFY 0x200000L /* forbidden in non-'modifiable' buffer */ +#define EXFLAGS 0x400000L /* allow flags after count in argument */ +#define FILES (XFILE | EXTRA) /* multiple extra files allowed */ +#define WORD1 (EXTRA | NOSPC) /* one extra word allowed */ +#define FILE1 (FILES | NOSPC) /* 1 file allowed, defaults to current file */ + +#ifndef DO_DECLARE_EXCMD +typedef struct exarg exarg_T; +#endif + +/* + * This array maps ex command names to command codes. + * The order in which command names are listed below is significant -- + * ambiguous abbreviations are always resolved to be the first possible match + * (e.g. "r" is taken to mean "read", not "rewind", because "read" comes + * before "rewind"). + * Not supported commands are included to avoid ambiguities. + */ +#ifdef EX +# undef EX /* just in case */ +#endif +#ifdef DO_DECLARE_EXCMD +# define EX(a, b, c, d) {(char_u *)b, c, (long_u)(d)} + +typedef void (*ex_func_T) __ARGS ((exarg_T *eap)); + +static struct cmdname { + char_u *cmd_name; /* name of the command */ + ex_func_T cmd_func; /* function for this command */ + long_u cmd_argt; /* flags declared above */ +} +cmdnames[] = +#else +# define EX(a, b, c, d) a +enum CMD_index +#endif +{ + EX(CMD_append, "append", ex_append, + BANG|RANGE|ZEROR|TRLBAR|CMDWIN|MODIFY), + EX(CMD_abbreviate, "abbreviate", ex_abbreviate, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_abclear, "abclear", ex_abclear, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_aboveleft, "aboveleft", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_all, "all", ex_all, + BANG|RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_amenu, "amenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_anoremenu, "anoremenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_args, "args", ex_args, + BANG|FILES|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_argadd, "argadd", ex_argadd, + BANG|NEEDARG|RANGE|NOTADR|ZEROR|FILES|TRLBAR), + EX(CMD_argdelete, "argdelete", ex_argdelete, + BANG|RANGE|NOTADR|FILES|TRLBAR), + EX(CMD_argdo, "argdo", ex_listdo, + BANG|NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_argedit, "argedit", ex_argedit, + BANG|NEEDARG|RANGE|NOTADR|FILE1|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_argglobal, "argglobal", ex_args, + BANG|FILES|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_arglocal, "arglocal", ex_args, + BANG|FILES|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_argument, "argument", ex_argument, + BANG|RANGE|NOTADR|COUNT|EXTRA|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_ascii, "ascii", do_ascii, + TRLBAR|SBOXOK|CMDWIN), + EX(CMD_autocmd, "autocmd", ex_autocmd, + BANG|EXTRA|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_augroup, "augroup", ex_autocmd, + BANG|WORD1|TRLBAR|CMDWIN), + EX(CMD_aunmenu, "aunmenu", ex_menu, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_buffer, "buffer", ex_buffer, + BANG|RANGE|NOTADR|BUFNAME|BUFUNL|COUNT|EXTRA|TRLBAR), + EX(CMD_bNext, "bNext", ex_bprevious, + BANG|RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_ball, "ball", ex_buffer_all, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_badd, "badd", ex_edit, + NEEDARG|FILE1|EDITCMD|TRLBAR|CMDWIN), + EX(CMD_bdelete, "bdelete", ex_bunload, + BANG|RANGE|NOTADR|BUFNAME|COUNT|EXTRA|TRLBAR), + EX(CMD_behave, "behave", ex_behave, + NEEDARG|WORD1|TRLBAR|CMDWIN), + EX(CMD_belowright, "belowright", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_bfirst, "bfirst", ex_brewind, + BANG|RANGE|NOTADR|TRLBAR), + EX(CMD_blast, "blast", ex_blast, + BANG|RANGE|NOTADR|TRLBAR), + EX(CMD_bmodified, "bmodified", ex_bmodified, + BANG|RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_bnext, "bnext", ex_bnext, + BANG|RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_botright, "botright", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_bprevious, "bprevious", ex_bprevious, + BANG|RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_brewind, "brewind", ex_brewind, + BANG|RANGE|NOTADR|TRLBAR), + EX(CMD_break, "break", ex_break, + TRLBAR|SBOXOK|CMDWIN), + EX(CMD_breakadd, "breakadd", ex_breakadd, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_breakdel, "breakdel", ex_breakdel, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_breaklist, "breaklist", ex_breaklist, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_browse, "browse", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM|CMDWIN), + EX(CMD_buffers, "buffers", buflist_list, + BANG|TRLBAR|CMDWIN), + EX(CMD_bufdo, "bufdo", ex_listdo, + BANG|NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_bunload, "bunload", ex_bunload, + BANG|RANGE|NOTADR|BUFNAME|COUNT|EXTRA|TRLBAR), + EX(CMD_bwipeout, "bwipeout", ex_bunload, + BANG|RANGE|NOTADR|BUFNAME|BUFUNL|COUNT|EXTRA|TRLBAR), + EX(CMD_change, "change", ex_change, + BANG|WHOLEFOLD|RANGE|COUNT|TRLBAR|CMDWIN|MODIFY), + EX(CMD_cNext, "cNext", ex_cnext, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_cNfile, "cNfile", ex_cnext, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_cabbrev, "cabbrev", ex_abbreviate, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_cabclear, "cabclear", ex_abclear, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_caddbuffer, "caddbuffer", ex_cbuffer, + RANGE|NOTADR|WORD1|TRLBAR), + EX(CMD_caddexpr, "caddexpr", ex_cexpr, + NEEDARG|WORD1|NOTRLCOM|TRLBAR), + EX(CMD_caddfile, "caddfile", ex_cfile, + TRLBAR|FILE1), + EX(CMD_call, "call", ex_call, + RANGE|NEEDARG|EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_catch, "catch", ex_catch, + EXTRA|SBOXOK|CMDWIN), + EX(CMD_cbuffer, "cbuffer", ex_cbuffer, + BANG|RANGE|NOTADR|WORD1|TRLBAR), + EX(CMD_cc, "cc", ex_cc, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_cclose, "cclose", ex_cclose, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_cd, "cd", ex_cd, + BANG|FILE1|TRLBAR|CMDWIN), + EX(CMD_center, "center", ex_align, + TRLBAR|RANGE|WHOLEFOLD|EXTRA|CMDWIN|MODIFY), + EX(CMD_cexpr, "cexpr", ex_cexpr, + NEEDARG|WORD1|NOTRLCOM|TRLBAR|BANG), + EX(CMD_cfile, "cfile", ex_cfile, + TRLBAR|FILE1|BANG), + EX(CMD_cfirst, "cfirst", ex_cc, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_cgetfile, "cgetfile", ex_cfile, + TRLBAR|FILE1), + EX(CMD_cgetbuffer, "cgetbuffer", ex_cbuffer, + RANGE|NOTADR|WORD1|TRLBAR), + EX(CMD_cgetexpr, "cgetexpr", ex_cexpr, + NEEDARG|WORD1|NOTRLCOM|TRLBAR), + EX(CMD_chdir, "chdir", ex_cd, + BANG|FILE1|TRLBAR|CMDWIN), + EX(CMD_changes, "changes", ex_changes, + TRLBAR|CMDWIN), + EX(CMD_checkpath, "checkpath", ex_checkpath, + TRLBAR|BANG|CMDWIN), + EX(CMD_checktime, "checktime", ex_checktime, + RANGE|NOTADR|BUFNAME|COUNT|EXTRA|TRLBAR), + EX(CMD_clist, "clist", qf_list, + BANG|EXTRA|TRLBAR|CMDWIN), + EX(CMD_clast, "clast", ex_cc, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_close, "close", ex_close, + BANG|TRLBAR|CMDWIN), + EX(CMD_cmap, "cmap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_cmapclear, "cmapclear", ex_mapclear, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_cmenu, "cmenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_cnext, "cnext", ex_cnext, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_cnewer, "cnewer", qf_age, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_cnfile, "cnfile", ex_cnext, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_cnoremap, "cnoremap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_cnoreabbrev, "cnoreabbrev", ex_abbreviate, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_cnoremenu, "cnoremenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_copy, "copy", ex_copymove, + RANGE|WHOLEFOLD|EXTRA|TRLBAR|CMDWIN|MODIFY), + EX(CMD_colder, "colder", qf_age, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_colorscheme, "colorscheme", ex_colorscheme, + WORD1|TRLBAR|CMDWIN), + EX(CMD_command, "command", ex_command, + EXTRA|BANG|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_comclear, "comclear", ex_comclear, + TRLBAR|CMDWIN), + EX(CMD_compiler, "compiler", ex_compiler, + BANG|TRLBAR|WORD1|CMDWIN), + EX(CMD_continue, "continue", ex_continue, + TRLBAR|SBOXOK|CMDWIN), + EX(CMD_confirm, "confirm", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM|CMDWIN), + EX(CMD_copen, "copen", ex_copen, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_cprevious, "cprevious", ex_cnext, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_cpfile, "cpfile", ex_cnext, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_cquit, "cquit", ex_cquit, + TRLBAR|BANG), + EX(CMD_crewind, "crewind", ex_cc, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_cscope, "cscope", do_cscope, + EXTRA|NOTRLCOM|XFILE), + EX(CMD_cstag, "cstag", do_cstag, + BANG|TRLBAR|WORD1), + EX(CMD_cunmap, "cunmap", ex_unmap, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_cunabbrev, "cunabbrev", ex_abbreviate, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_cunmenu, "cunmenu", ex_menu, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_cwindow, "cwindow", ex_cwindow, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_delete, "delete", ex_operators, + RANGE|WHOLEFOLD|REGSTR|COUNT|TRLBAR|CMDWIN|MODIFY), + EX(CMD_delmarks, "delmarks", ex_delmarks, + BANG|EXTRA|TRLBAR|CMDWIN), + EX(CMD_debug, "debug", ex_debug, + NEEDARG|EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_debuggreedy, "debuggreedy", ex_debuggreedy, + RANGE|NOTADR|ZEROR|TRLBAR|CMDWIN), + EX(CMD_delcommand, "delcommand", ex_delcommand, + NEEDARG|WORD1|TRLBAR|CMDWIN), + EX(CMD_delfunction, "delfunction", ex_delfunction, + NEEDARG|WORD1|CMDWIN), + EX(CMD_display, "display", ex_display, + EXTRA|NOTRLCOM|TRLBAR|SBOXOK|CMDWIN), + EX(CMD_diffupdate, "diffupdate", ex_diffupdate, + BANG|TRLBAR), + EX(CMD_diffget, "diffget", ex_diffgetput, + RANGE|EXTRA|TRLBAR|MODIFY), + EX(CMD_diffoff, "diffoff", ex_diffoff, + BANG|TRLBAR), + EX(CMD_diffpatch, "diffpatch", ex_diffpatch, + EXTRA|FILE1|TRLBAR|MODIFY), + EX(CMD_diffput, "diffput", ex_diffgetput, + RANGE|EXTRA|TRLBAR), + EX(CMD_diffsplit, "diffsplit", ex_diffsplit, + EXTRA|FILE1|TRLBAR), + EX(CMD_diffthis, "diffthis", ex_diffthis, + TRLBAR), + EX(CMD_digraphs, "digraphs", ex_digraphs, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_djump, "djump", ex_findpat, + BANG|RANGE|DFLALL|WHOLEFOLD|EXTRA), + EX(CMD_dlist, "dlist", ex_findpat, + BANG|RANGE|DFLALL|WHOLEFOLD|EXTRA|CMDWIN), + EX(CMD_doautocmd, "doautocmd", ex_doautocmd, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_doautoall, "doautoall", ex_doautoall, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_drop, "drop", ex_drop, + FILES|EDITCMD|NEEDARG|ARGOPT|TRLBAR), + EX(CMD_dsearch, "dsearch", ex_findpat, + BANG|RANGE|DFLALL|WHOLEFOLD|EXTRA|CMDWIN), + EX(CMD_dsplit, "dsplit", ex_findpat, + BANG|RANGE|DFLALL|WHOLEFOLD|EXTRA), + EX(CMD_edit, "edit", ex_edit, + BANG|FILE1|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_earlier, "earlier", ex_later, + TRLBAR|EXTRA|NOSPC|CMDWIN), + EX(CMD_echo, "echo", ex_echo, + EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_echoerr, "echoerr", ex_execute, + EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_echohl, "echohl", ex_echohl, + EXTRA|TRLBAR|SBOXOK|CMDWIN), + EX(CMD_echomsg, "echomsg", ex_execute, + EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_echon, "echon", ex_echo, + EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_else, "else", ex_else, + TRLBAR|SBOXOK|CMDWIN), + EX(CMD_elseif, "elseif", ex_else, + EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_emenu, "emenu", ex_emenu, + NEEDARG|EXTRA|TRLBAR|NOTRLCOM|RANGE|NOTADR|CMDWIN), + EX(CMD_endif, "endif", ex_endif, + TRLBAR|SBOXOK|CMDWIN), + EX(CMD_endfunction, "endfunction", ex_endfunction, + TRLBAR|CMDWIN), + EX(CMD_endfor, "endfor", ex_endwhile, + TRLBAR|SBOXOK|CMDWIN), + EX(CMD_endtry, "endtry", ex_endtry, + TRLBAR|SBOXOK|CMDWIN), + EX(CMD_endwhile, "endwhile", ex_endwhile, + TRLBAR|SBOXOK|CMDWIN), + EX(CMD_enew, "enew", ex_edit, + BANG|TRLBAR), + EX(CMD_ex, "ex", ex_edit, + BANG|FILE1|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_execute, "execute", ex_execute, + EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_exit, "exit", ex_exit, + RANGE|WHOLEFOLD|BANG|FILE1|ARGOPT|DFLALL|TRLBAR|CMDWIN), + EX(CMD_exusage, "exusage", ex_exusage, + TRLBAR), + EX(CMD_file, "file", ex_file, + RANGE|NOTADR|ZEROR|BANG|FILE1|TRLBAR), + EX(CMD_files, "files", buflist_list, + BANG|TRLBAR|CMDWIN), + EX(CMD_filetype, "filetype", ex_filetype, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_find, "find", ex_find, + RANGE|NOTADR|BANG|FILE1|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_finally, "finally", ex_finally, + TRLBAR|SBOXOK|CMDWIN), + EX(CMD_finish, "finish", ex_finish, + TRLBAR|SBOXOK|CMDWIN), + EX(CMD_first, "first", ex_rewind, + EXTRA|BANG|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_fixdel, "fixdel", do_fixdel, + TRLBAR|CMDWIN), + EX(CMD_fold, "fold", ex_fold, + RANGE|WHOLEFOLD|TRLBAR|SBOXOK|CMDWIN), + EX(CMD_foldclose, "foldclose", ex_foldopen, + RANGE|BANG|WHOLEFOLD|TRLBAR|SBOXOK|CMDWIN), + EX(CMD_folddoopen, "folddoopen", ex_folddo, + RANGE|DFLALL|NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_folddoclosed, "folddoclosed", ex_folddo, + RANGE|DFLALL|NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_foldopen, "foldopen", ex_foldopen, + RANGE|BANG|WHOLEFOLD|TRLBAR|SBOXOK|CMDWIN), + EX(CMD_for, "for", ex_while, + EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_function, "function", ex_function, + EXTRA|BANG|CMDWIN), + EX(CMD_global, "global", ex_global, + RANGE|WHOLEFOLD|BANG|EXTRA|DFLALL|SBOXOK|CMDWIN), + EX(CMD_goto, "goto", ex_goto, + RANGE|NOTADR|COUNT|TRLBAR|SBOXOK|CMDWIN), + EX(CMD_grep, "grep", ex_make, + RANGE|NOTADR|BANG|NEEDARG|EXTRA|NOTRLCOM|TRLBAR|XFILE), + EX(CMD_grepadd, "grepadd", ex_make, + RANGE|NOTADR|BANG|NEEDARG|EXTRA|NOTRLCOM|TRLBAR|XFILE), + EX(CMD_gui, "gui", ex_gui, + BANG|FILES|EDITCMD|ARGOPT|TRLBAR|CMDWIN), + EX(CMD_gvim, "gvim", ex_gui, + BANG|FILES|EDITCMD|ARGOPT|TRLBAR|CMDWIN), + EX(CMD_help, "help", ex_help, + BANG|EXTRA|NOTRLCOM), + EX(CMD_helpfind, "helpfind", ex_helpfind, + EXTRA|NOTRLCOM), + EX(CMD_helpgrep, "helpgrep", ex_helpgrep, + EXTRA|NOTRLCOM|NEEDARG), + EX(CMD_helptags, "helptags", ex_helptags, + NEEDARG|FILES|TRLBAR|CMDWIN), + EX(CMD_hardcopy, "hardcopy", ex_hardcopy, + RANGE|COUNT|EXTRA|TRLBAR|DFLALL|BANG), + EX(CMD_highlight, "highlight", ex_highlight, + BANG|EXTRA|TRLBAR|SBOXOK|CMDWIN), + EX(CMD_hide, "hide", ex_hide, + BANG|EXTRA|NOTRLCOM), + EX(CMD_history, "history", ex_history, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_insert, "insert", ex_append, + BANG|RANGE|TRLBAR|CMDWIN|MODIFY), + EX(CMD_iabbrev, "iabbrev", ex_abbreviate, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_iabclear, "iabclear", ex_abclear, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_if, "if", ex_if, + EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_ijump, "ijump", ex_findpat, + BANG|RANGE|DFLALL|WHOLEFOLD|EXTRA), + EX(CMD_ilist, "ilist", ex_findpat, + BANG|RANGE|DFLALL|WHOLEFOLD|EXTRA|CMDWIN), + EX(CMD_imap, "imap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_imapclear, "imapclear", ex_mapclear, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_imenu, "imenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_inoremap, "inoremap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_inoreabbrev, "inoreabbrev", ex_abbreviate, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_inoremenu, "inoremenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_intro, "intro", ex_intro, + TRLBAR|CMDWIN), + EX(CMD_isearch, "isearch", ex_findpat, + BANG|RANGE|DFLALL|WHOLEFOLD|EXTRA|CMDWIN), + EX(CMD_isplit, "isplit", ex_findpat, + BANG|RANGE|DFLALL|WHOLEFOLD|EXTRA), + EX(CMD_iunmap, "iunmap", ex_unmap, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_iunabbrev, "iunabbrev", ex_abbreviate, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_iunmenu, "iunmenu", ex_menu, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_join, "join", ex_join, + BANG|RANGE|WHOLEFOLD|COUNT|EXFLAGS|TRLBAR|CMDWIN|MODIFY), + EX(CMD_jumps, "jumps", ex_jumps, + TRLBAR|CMDWIN), + EX(CMD_k, "k", ex_mark, + RANGE|WORD1|TRLBAR|SBOXOK|CMDWIN), + EX(CMD_keepmarks, "keepmarks", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_keepjumps, "keepjumps", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_keeppatterns, "keeppatterns", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_keepalt, "keepalt", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_list, "list", ex_print, + RANGE|WHOLEFOLD|COUNT|EXFLAGS|TRLBAR|CMDWIN), + EX(CMD_lNext, "lNext", ex_cnext, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_lNfile, "lNfile", ex_cnext, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_last, "last", ex_last, + EXTRA|BANG|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_language, "language", ex_language, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_laddexpr, "laddexpr", ex_cexpr, + NEEDARG|WORD1|NOTRLCOM|TRLBAR), + EX(CMD_laddbuffer, "laddbuffer", ex_cbuffer, + RANGE|NOTADR|WORD1|TRLBAR), + EX(CMD_laddfile, "laddfile", ex_cfile, + TRLBAR|FILE1), + EX(CMD_later, "later", ex_later, + TRLBAR|EXTRA|NOSPC|CMDWIN), + EX(CMD_lbuffer, "lbuffer", ex_cbuffer, + BANG|RANGE|NOTADR|WORD1|TRLBAR), + EX(CMD_lcd, "lcd", ex_cd, + BANG|FILE1|TRLBAR|CMDWIN), + EX(CMD_lchdir, "lchdir", ex_cd, + BANG|FILE1|TRLBAR|CMDWIN), + EX(CMD_lclose, "lclose", ex_cclose, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_lcscope, "lcscope", do_cscope, + EXTRA|NOTRLCOM|XFILE), + EX(CMD_left, "left", ex_align, + TRLBAR|RANGE|WHOLEFOLD|EXTRA|CMDWIN|MODIFY), + EX(CMD_leftabove, "leftabove", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_let, "let", ex_let, + EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_lexpr, "lexpr", ex_cexpr, + NEEDARG|WORD1|NOTRLCOM|TRLBAR|BANG), + EX(CMD_lfile, "lfile", ex_cfile, + TRLBAR|FILE1|BANG), + EX(CMD_lfirst, "lfirst", ex_cc, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_lgetfile, "lgetfile", ex_cfile, + TRLBAR|FILE1), + EX(CMD_lgetbuffer, "lgetbuffer", ex_cbuffer, + RANGE|NOTADR|WORD1|TRLBAR), + EX(CMD_lgetexpr, "lgetexpr", ex_cexpr, + NEEDARG|WORD1|NOTRLCOM|TRLBAR), + EX(CMD_lgrep, "lgrep", ex_make, + RANGE|NOTADR|BANG|NEEDARG|EXTRA|NOTRLCOM|TRLBAR|XFILE), + EX(CMD_lgrepadd, "lgrepadd", ex_make, + RANGE|NOTADR|BANG|NEEDARG|EXTRA|NOTRLCOM|TRLBAR|XFILE), + EX(CMD_lhelpgrep, "lhelpgrep", ex_helpgrep, + EXTRA|NOTRLCOM|NEEDARG), + EX(CMD_ll, "ll", ex_cc, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_llast, "llast", ex_cc, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_llist, "llist", qf_list, + BANG|EXTRA|TRLBAR|CMDWIN), + EX(CMD_lmap, "lmap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_lmapclear, "lmapclear", ex_mapclear, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_lmake, "lmake", ex_make, + BANG|EXTRA|NOTRLCOM|TRLBAR|XFILE), + EX(CMD_lnoremap, "lnoremap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_lnext, "lnext", ex_cnext, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_lnewer, "lnewer", qf_age, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_lnfile, "lnfile", ex_cnext, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_loadview, "loadview", ex_loadview, + FILE1|TRLBAR), + EX(CMD_loadkeymap, "loadkeymap", ex_loadkeymap, + CMDWIN), + EX(CMD_lockmarks, "lockmarks", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_lockvar, "lockvar", ex_lockvar, + BANG|EXTRA|NEEDARG|SBOXOK|CMDWIN), + EX(CMD_lolder, "lolder", qf_age, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_lopen, "lopen", ex_copen, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_lprevious, "lprevious", ex_cnext, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_lpfile, "lpfile", ex_cnext, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_lrewind, "lrewind", ex_cc, + RANGE|NOTADR|COUNT|TRLBAR|BANG), + EX(CMD_ltag, "ltag", ex_tag, + NOTADR|TRLBAR|BANG|WORD1), + EX(CMD_lua, "lua", ex_lua, + RANGE|EXTRA|NEEDARG|CMDWIN), + EX(CMD_luado, "luado", ex_luado, + RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN), + EX(CMD_luafile, "luafile", ex_luafile, + RANGE|FILE1|NEEDARG|CMDWIN), + EX(CMD_lunmap, "lunmap", ex_unmap, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_lvimgrep, "lvimgrep", ex_vimgrep, + RANGE|NOTADR|BANG|NEEDARG|EXTRA|NOTRLCOM|TRLBAR|XFILE), + EX(CMD_lvimgrepadd, "lvimgrepadd", ex_vimgrep, + RANGE|NOTADR|BANG|NEEDARG|EXTRA|NOTRLCOM|TRLBAR|XFILE), + EX(CMD_lwindow, "lwindow", ex_cwindow, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_ls, "ls", buflist_list, + BANG|TRLBAR|CMDWIN), + EX(CMD_move, "move", ex_copymove, + RANGE|WHOLEFOLD|EXTRA|TRLBAR|CMDWIN|MODIFY), + EX(CMD_mark, "mark", ex_mark, + RANGE|WORD1|TRLBAR|SBOXOK|CMDWIN), + EX(CMD_make, "make", ex_make, + BANG|EXTRA|NOTRLCOM|TRLBAR|XFILE), + EX(CMD_map, "map", ex_map, + BANG|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_mapclear, "mapclear", ex_mapclear, + EXTRA|BANG|TRLBAR|CMDWIN), + EX(CMD_marks, "marks", do_marks, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_match, "match", ex_match, + RANGE|NOTADR|EXTRA|CMDWIN), + EX(CMD_menu, "menu", ex_menu, + RANGE|NOTADR|ZEROR|BANG|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_menutranslate, "menutranslate", ex_menutranslate, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_messages, "messages", ex_messages, + TRLBAR|CMDWIN), + EX(CMD_mkexrc, "mkexrc", ex_mkrc, + BANG|FILE1|TRLBAR|CMDWIN), + EX(CMD_mksession, "mksession", ex_mkrc, + BANG|FILE1|TRLBAR), + EX(CMD_mkspell, "mkspell", ex_mkspell, + BANG|NEEDARG|EXTRA|NOTRLCOM|TRLBAR|XFILE), + EX(CMD_mkvimrc, "mkvimrc", ex_mkrc, + BANG|FILE1|TRLBAR|CMDWIN), + EX(CMD_mkview, "mkview", ex_mkrc, + BANG|FILE1|TRLBAR), + EX(CMD_mode, "mode", ex_mode, + WORD1|TRLBAR|CMDWIN), + EX(CMD_mzscheme, "mzscheme", ex_mzscheme, + RANGE|EXTRA|DFLALL|NEEDARG|CMDWIN|SBOXOK), + EX(CMD_mzfile, "mzfile", ex_mzfile, + RANGE|FILE1|NEEDARG|CMDWIN), + EX(CMD_next, "next", ex_next, + RANGE|NOTADR|BANG|FILES|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_nbkey, "nbkey", ex_nbkey, + EXTRA|NOTADR|NEEDARG), + EX(CMD_nbclose, "nbclose", ex_nbclose, + TRLBAR|CMDWIN), + EX(CMD_nbstart, "nbstart", ex_nbstart, + WORD1|TRLBAR|CMDWIN), + EX(CMD_new, "new", ex_splitview, + BANG|FILE1|RANGE|NOTADR|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_nmap, "nmap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_nmapclear, "nmapclear", ex_mapclear, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_nmenu, "nmenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_nnoremap, "nnoremap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_nnoremenu, "nnoremenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_noremap, "noremap", ex_map, + BANG|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_noautocmd, "noautocmd", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_nohlsearch, "nohlsearch", ex_nohlsearch, + TRLBAR|SBOXOK|CMDWIN), + EX(CMD_noreabbrev, "noreabbrev", ex_abbreviate, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_noremenu, "noremenu", ex_menu, + RANGE|NOTADR|ZEROR|BANG|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_normal, "normal", ex_normal, + RANGE|BANG|EXTRA|NEEDARG|NOTRLCOM|USECTRLV|SBOXOK|CMDWIN), + EX(CMD_number, "number", ex_print, + RANGE|WHOLEFOLD|COUNT|EXFLAGS|TRLBAR|CMDWIN), + EX(CMD_nunmap, "nunmap", ex_unmap, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_nunmenu, "nunmenu", ex_menu, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_open, "open", ex_open, + RANGE|BANG|EXTRA), + EX(CMD_oldfiles, "oldfiles", ex_oldfiles, + BANG|TRLBAR|SBOXOK|CMDWIN), + EX(CMD_omap, "omap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_omapclear, "omapclear", ex_mapclear, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_omenu, "omenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_only, "only", ex_only, + BANG|TRLBAR), + EX(CMD_onoremap, "onoremap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_onoremenu, "onoremenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_options, "options", ex_options, + TRLBAR), + EX(CMD_ounmap, "ounmap", ex_unmap, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_ounmenu, "ounmenu", ex_menu, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_ownsyntax, "ownsyntax", ex_ownsyntax, + EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_print, "print", ex_print, + RANGE|WHOLEFOLD|COUNT|EXFLAGS|TRLBAR|CMDWIN|SBOXOK), + EX(CMD_pclose, "pclose", ex_pclose, + BANG|TRLBAR), + EX(CMD_perl, "perl", ex_perl, + RANGE|EXTRA|DFLALL|NEEDARG|SBOXOK|CMDWIN), + EX(CMD_perldo, "perldo", ex_perldo, + RANGE|EXTRA|DFLALL|NEEDARG|CMDWIN), + EX(CMD_pedit, "pedit", ex_pedit, + BANG|FILE1|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_pop, "pop", ex_tag, + RANGE|NOTADR|BANG|COUNT|TRLBAR|ZEROR), + EX(CMD_popup, "popup", ex_popup, + NEEDARG|EXTRA|BANG|TRLBAR|NOTRLCOM|CMDWIN), + EX(CMD_ppop, "ppop", ex_ptag, + RANGE|NOTADR|BANG|COUNT|TRLBAR|ZEROR), + EX(CMD_preserve, "preserve", ex_preserve, + TRLBAR), + EX(CMD_previous, "previous", ex_previous, + EXTRA|RANGE|NOTADR|COUNT|BANG|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_promptfind, "promptfind", gui_mch_find_dialog, + EXTRA|NOTRLCOM|CMDWIN), + EX(CMD_promptrepl, "promptrepl", gui_mch_replace_dialog, + EXTRA|NOTRLCOM|CMDWIN), + EX(CMD_profile, "profile", ex_profile, + BANG|EXTRA|TRLBAR|CMDWIN), + EX(CMD_profdel, "profdel", ex_breakdel, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_psearch, "psearch", ex_psearch, + BANG|RANGE|WHOLEFOLD|DFLALL|EXTRA), + EX(CMD_ptag, "ptag", ex_ptag, + RANGE|NOTADR|BANG|WORD1|TRLBAR|ZEROR), + EX(CMD_ptNext, "ptNext", ex_ptag, + RANGE|NOTADR|BANG|TRLBAR|ZEROR), + EX(CMD_ptfirst, "ptfirst", ex_ptag, + RANGE|NOTADR|BANG|TRLBAR|ZEROR), + EX(CMD_ptjump, "ptjump", ex_ptag, + BANG|TRLBAR|WORD1), + EX(CMD_ptlast, "ptlast", ex_ptag, + BANG|TRLBAR), + EX(CMD_ptnext, "ptnext", ex_ptag, + RANGE|NOTADR|BANG|TRLBAR|ZEROR), + EX(CMD_ptprevious, "ptprevious", ex_ptag, + RANGE|NOTADR|BANG|TRLBAR|ZEROR), + EX(CMD_ptrewind, "ptrewind", ex_ptag, + RANGE|NOTADR|BANG|TRLBAR|ZEROR), + EX(CMD_ptselect, "ptselect", ex_ptag, + BANG|TRLBAR|WORD1), + EX(CMD_put, "put", ex_put, + RANGE|WHOLEFOLD|BANG|REGSTR|TRLBAR|ZEROR|CMDWIN|MODIFY), + EX(CMD_pwd, "pwd", ex_pwd, + TRLBAR|CMDWIN), + EX(CMD_python, "python", ex_python, + RANGE|EXTRA|NEEDARG|CMDWIN), + EX(CMD_pydo, "pydo", ex_pydo, + RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN), + EX(CMD_pyfile, "pyfile", ex_pyfile, + RANGE|FILE1|NEEDARG|CMDWIN), + EX(CMD_py3, "py3", ex_py3, + RANGE|EXTRA|NEEDARG|CMDWIN), + EX(CMD_py3do, "py3do", ex_py3do, + RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN), + EX(CMD_python3, "python3", ex_py3, + RANGE|EXTRA|NEEDARG|CMDWIN), + EX(CMD_py3file, "py3file", ex_py3file, + RANGE|FILE1|NEEDARG|CMDWIN), + EX(CMD_quit, "quit", ex_quit, + BANG|TRLBAR|CMDWIN), + EX(CMD_quitall, "quitall", ex_quit_all, + BANG|TRLBAR), + EX(CMD_qall, "qall", ex_quit_all, + BANG|TRLBAR|CMDWIN), + EX(CMD_read, "read", ex_read, + BANG|RANGE|WHOLEFOLD|FILE1|ARGOPT|TRLBAR|ZEROR|CMDWIN|MODIFY), + EX(CMD_recover, "recover", ex_recover, + BANG|FILE1|TRLBAR), + EX(CMD_redo, "redo", ex_redo, + TRLBAR|CMDWIN), + EX(CMD_redir, "redir", ex_redir, + BANG|FILES|TRLBAR|CMDWIN), + EX(CMD_redraw, "redraw", ex_redraw, + BANG|TRLBAR|CMDWIN), + EX(CMD_redrawstatus, "redrawstatus", ex_redrawstatus, + BANG|TRLBAR|CMDWIN), + EX(CMD_registers, "registers", ex_display, + EXTRA|NOTRLCOM|TRLBAR|CMDWIN), + EX(CMD_resize, "resize", ex_resize, + RANGE|NOTADR|TRLBAR|WORD1), + EX(CMD_retab, "retab", ex_retab, + TRLBAR|RANGE|WHOLEFOLD|DFLALL|BANG|WORD1|CMDWIN|MODIFY), + EX(CMD_return, "return", ex_return, + EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_rewind, "rewind", ex_rewind, + EXTRA|BANG|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_right, "right", ex_align, + TRLBAR|RANGE|WHOLEFOLD|EXTRA|CMDWIN|MODIFY), + EX(CMD_rightbelow, "rightbelow", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_runtime, "runtime", ex_runtime, + BANG|NEEDARG|FILES|TRLBAR|SBOXOK|CMDWIN), + EX(CMD_ruby, "ruby", ex_ruby, + RANGE|EXTRA|NEEDARG|CMDWIN), + EX(CMD_rubydo, "rubydo", ex_rubydo, + RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN), + EX(CMD_rubyfile, "rubyfile", ex_rubyfile, + RANGE|FILE1|NEEDARG|CMDWIN), + EX(CMD_rundo, "rundo", ex_rundo, + NEEDARG|FILE1), + EX(CMD_rviminfo, "rviminfo", ex_viminfo, + BANG|FILE1|TRLBAR|CMDWIN), + EX(CMD_substitute, "substitute", do_sub, + RANGE|WHOLEFOLD|EXTRA|CMDWIN), + EX(CMD_sNext, "sNext", ex_previous, + EXTRA|RANGE|NOTADR|COUNT|BANG|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_sargument, "sargument", ex_argument, + BANG|RANGE|NOTADR|COUNT|EXTRA|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_sall, "sall", ex_all, + BANG|RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_sandbox, "sandbox", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_saveas, "saveas", ex_write, + BANG|DFLALL|FILE1|ARGOPT|CMDWIN|TRLBAR), + EX(CMD_sbuffer, "sbuffer", ex_buffer, + BANG|RANGE|NOTADR|BUFNAME|BUFUNL|COUNT|EXTRA|TRLBAR), + EX(CMD_sbNext, "sbNext", ex_bprevious, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_sball, "sball", ex_buffer_all, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_sbfirst, "sbfirst", ex_brewind, + TRLBAR), + EX(CMD_sblast, "sblast", ex_blast, + TRLBAR), + EX(CMD_sbmodified, "sbmodified", ex_bmodified, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_sbnext, "sbnext", ex_bnext, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_sbprevious, "sbprevious", ex_bprevious, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_sbrewind, "sbrewind", ex_brewind, + TRLBAR), + EX(CMD_scriptnames, "scriptnames", ex_scriptnames, + TRLBAR|CMDWIN), + EX(CMD_scriptencoding, "scriptencoding", ex_scriptencoding, + WORD1|TRLBAR|CMDWIN), + EX(CMD_scscope, "scscope", do_scscope, + EXTRA|NOTRLCOM), + EX(CMD_set, "set", ex_set, + TRLBAR|EXTRA|CMDWIN|SBOXOK), + EX(CMD_setfiletype, "setfiletype", ex_setfiletype, + TRLBAR|EXTRA|NEEDARG|CMDWIN), + EX(CMD_setglobal, "setglobal", ex_set, + TRLBAR|EXTRA|CMDWIN|SBOXOK), + EX(CMD_setlocal, "setlocal", ex_set, + TRLBAR|EXTRA|CMDWIN|SBOXOK), + EX(CMD_sfind, "sfind", ex_splitview, + BANG|FILE1|RANGE|NOTADR|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_sfirst, "sfirst", ex_rewind, + EXTRA|BANG|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_shell, "shell", ex_shell, + TRLBAR|CMDWIN), + EX(CMD_simalt, "simalt", ex_simalt, + NEEDARG|WORD1|TRLBAR|CMDWIN), + EX(CMD_sign, "sign", ex_sign, + NEEDARG|RANGE|NOTADR|EXTRA|CMDWIN), + EX(CMD_silent, "silent", ex_wrongmodifier, + NEEDARG|EXTRA|BANG|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_sleep, "sleep", ex_sleep, + RANGE|NOTADR|COUNT|EXTRA|TRLBAR|CMDWIN), + EX(CMD_slast, "slast", ex_last, + EXTRA|BANG|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_smagic, "smagic", ex_submagic, + RANGE|WHOLEFOLD|EXTRA|CMDWIN), + EX(CMD_smap, "smap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_smapclear, "smapclear", ex_mapclear, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_smenu, "smenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_snext, "snext", ex_next, + RANGE|NOTADR|BANG|FILES|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_sniff, "sniff", ex_sniff, + EXTRA|TRLBAR), + EX(CMD_snomagic, "snomagic", ex_submagic, + RANGE|WHOLEFOLD|EXTRA|CMDWIN), + EX(CMD_snoremap, "snoremap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_snoremenu, "snoremenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_source, "source", ex_source, + BANG|FILE1|TRLBAR|SBOXOK|CMDWIN), + EX(CMD_sort, "sort", ex_sort, + RANGE|DFLALL|WHOLEFOLD|BANG|EXTRA|NOTRLCOM|MODIFY), + EX(CMD_split, "split", ex_splitview, + BANG|FILE1|RANGE|NOTADR|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_spellgood, "spellgood", ex_spell, + BANG|RANGE|NOTADR|NEEDARG|EXTRA|TRLBAR), + EX(CMD_spelldump, "spelldump", ex_spelldump, + BANG|TRLBAR), + EX(CMD_spellinfo, "spellinfo", ex_spellinfo, + TRLBAR), + EX(CMD_spellrepall, "spellrepall", ex_spellrepall, + TRLBAR), + EX(CMD_spellundo, "spellundo", ex_spell, + BANG|RANGE|NOTADR|NEEDARG|EXTRA|TRLBAR), + EX(CMD_spellwrong, "spellwrong", ex_spell, + BANG|RANGE|NOTADR|NEEDARG|EXTRA|TRLBAR), + EX(CMD_sprevious, "sprevious", ex_previous, + EXTRA|RANGE|NOTADR|COUNT|BANG|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_srewind, "srewind", ex_rewind, + EXTRA|BANG|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_stop, "stop", ex_stop, + TRLBAR|BANG|CMDWIN), + EX(CMD_stag, "stag", ex_stag, + RANGE|NOTADR|BANG|WORD1|TRLBAR|ZEROR), + EX(CMD_startinsert, "startinsert", ex_startinsert, + BANG|TRLBAR|CMDWIN), + EX(CMD_startgreplace, "startgreplace", ex_startinsert, + BANG|TRLBAR|CMDWIN), + EX(CMD_startreplace, "startreplace", ex_startinsert, + BANG|TRLBAR|CMDWIN), + EX(CMD_stopinsert, "stopinsert", ex_stopinsert, + BANG|TRLBAR|CMDWIN), + EX(CMD_stjump, "stjump", ex_stag, + BANG|TRLBAR|WORD1), + EX(CMD_stselect, "stselect", ex_stag, + BANG|TRLBAR|WORD1), + EX(CMD_sunhide, "sunhide", ex_buffer_all, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_sunmap, "sunmap", ex_unmap, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_sunmenu, "sunmenu", ex_menu, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_suspend, "suspend", ex_stop, + TRLBAR|BANG|CMDWIN), + EX(CMD_sview, "sview", ex_splitview, + BANG|FILE1|RANGE|NOTADR|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_swapname, "swapname", ex_swapname, + TRLBAR|CMDWIN), + EX(CMD_syntax, "syntax", ex_syntax, + EXTRA|NOTRLCOM|CMDWIN), + EX(CMD_syntime, "syntime", ex_syntime, + NEEDARG|WORD1|TRLBAR|CMDWIN), + EX(CMD_syncbind, "syncbind", ex_syncbind, + TRLBAR), + EX(CMD_t, "t", ex_copymove, + RANGE|WHOLEFOLD|EXTRA|TRLBAR|CMDWIN|MODIFY), + EX(CMD_tNext, "tNext", ex_tag, + RANGE|NOTADR|BANG|TRLBAR|ZEROR), + EX(CMD_tag, "tag", ex_tag, + RANGE|NOTADR|BANG|WORD1|TRLBAR|ZEROR), + EX(CMD_tags, "tags", do_tags, + TRLBAR|CMDWIN), + EX(CMD_tab, "tab", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_tabclose, "tabclose", ex_tabclose, + RANGE|NOTADR|COUNT|BANG|TRLBAR|CMDWIN), + EX(CMD_tabdo, "tabdo", ex_listdo, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_tabedit, "tabedit", ex_splitview, + BANG|FILE1|RANGE|NOTADR|ZEROR|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_tabfind, "tabfind", ex_splitview, + BANG|FILE1|RANGE|NOTADR|ZEROR|EDITCMD|ARGOPT|NEEDARG|TRLBAR), + EX(CMD_tabfirst, "tabfirst", ex_tabnext, + TRLBAR), + EX(CMD_tabmove, "tabmove", ex_tabmove, + RANGE|NOTADR|ZEROR|EXTRA|NOSPC|TRLBAR), + EX(CMD_tablast, "tablast", ex_tabnext, + TRLBAR), + EX(CMD_tabnext, "tabnext", ex_tabnext, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_tabnew, "tabnew", ex_splitview, + BANG|FILE1|RANGE|NOTADR|ZEROR|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_tabonly, "tabonly", ex_tabonly, + BANG|TRLBAR|CMDWIN), + EX(CMD_tabprevious, "tabprevious", ex_tabnext, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_tabNext, "tabNext", ex_tabnext, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_tabrewind, "tabrewind", ex_tabnext, + TRLBAR), + EX(CMD_tabs, "tabs", ex_tabs, + TRLBAR|CMDWIN), + EX(CMD_tcl, "tcl", ex_tcl, + RANGE|EXTRA|NEEDARG|CMDWIN), + EX(CMD_tcldo, "tcldo", ex_tcldo, + RANGE|DFLALL|EXTRA|NEEDARG|CMDWIN), + EX(CMD_tclfile, "tclfile", ex_tclfile, + RANGE|FILE1|NEEDARG|CMDWIN), + EX(CMD_tearoff, "tearoff", ex_tearoff, + NEEDARG|EXTRA|TRLBAR|NOTRLCOM|CMDWIN), + EX(CMD_tfirst, "tfirst", ex_tag, + RANGE|NOTADR|BANG|TRLBAR|ZEROR), + EX(CMD_throw, "throw", ex_throw, + EXTRA|NEEDARG|SBOXOK|CMDWIN), + EX(CMD_tjump, "tjump", ex_tag, + BANG|TRLBAR|WORD1), + EX(CMD_tlast, "tlast", ex_tag, + BANG|TRLBAR), + EX(CMD_tmenu, "tmenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_tnext, "tnext", ex_tag, + RANGE|NOTADR|BANG|TRLBAR|ZEROR), + EX(CMD_topleft, "topleft", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_tprevious, "tprevious", ex_tag, + RANGE|NOTADR|BANG|TRLBAR|ZEROR), + EX(CMD_trewind, "trewind", ex_tag, + RANGE|NOTADR|BANG|TRLBAR|ZEROR), + EX(CMD_try, "try", ex_try, + TRLBAR|SBOXOK|CMDWIN), + EX(CMD_tselect, "tselect", ex_tag, + BANG|TRLBAR|WORD1), + EX(CMD_tunmenu, "tunmenu", ex_menu, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_undo, "undo", ex_undo, + RANGE|NOTADR|COUNT|ZEROR|TRLBAR|CMDWIN), + EX(CMD_undojoin, "undojoin", ex_undojoin, + TRLBAR|CMDWIN), + EX(CMD_undolist, "undolist", ex_undolist, + TRLBAR|CMDWIN), + EX(CMD_unabbreviate, "unabbreviate", ex_abbreviate, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_unhide, "unhide", ex_buffer_all, + RANGE|NOTADR|COUNT|TRLBAR), + EX(CMD_unlet, "unlet", ex_unlet, + BANG|EXTRA|NEEDARG|SBOXOK|CMDWIN), + EX(CMD_unlockvar, "unlockvar", ex_lockvar, + BANG|EXTRA|NEEDARG|SBOXOK|CMDWIN), + EX(CMD_unmap, "unmap", ex_unmap, + BANG|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_unmenu, "unmenu", ex_menu, + BANG|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_unsilent, "unsilent", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_update, "update", ex_update, + RANGE|WHOLEFOLD|BANG|FILE1|ARGOPT|DFLALL|TRLBAR), + EX(CMD_vglobal, "vglobal", ex_global, + RANGE|WHOLEFOLD|EXTRA|DFLALL|CMDWIN), + EX(CMD_version, "version", ex_version, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_verbose, "verbose", ex_wrongmodifier, + NEEDARG|RANGE|NOTADR|EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_vertical, "vertical", ex_wrongmodifier, + NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_visual, "visual", ex_edit, + BANG|FILE1|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_view, "view", ex_edit, + BANG|FILE1|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_vimgrep, "vimgrep", ex_vimgrep, + RANGE|NOTADR|BANG|NEEDARG|EXTRA|NOTRLCOM|TRLBAR|XFILE), + EX(CMD_vimgrepadd, "vimgrepadd", ex_vimgrep, + RANGE|NOTADR|BANG|NEEDARG|EXTRA|NOTRLCOM|TRLBAR|XFILE), + EX(CMD_viusage, "viusage", ex_viusage, + TRLBAR), + EX(CMD_vmap, "vmap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_vmapclear, "vmapclear", ex_mapclear, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_vmenu, "vmenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_vnoremap, "vnoremap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_vnew, "vnew", ex_splitview, + BANG|FILE1|RANGE|NOTADR|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_vnoremenu, "vnoremenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_vsplit, "vsplit", ex_splitview, + BANG|FILE1|RANGE|NOTADR|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_vunmap, "vunmap", ex_unmap, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_vunmenu, "vunmenu", ex_menu, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_write, "write", ex_write, + RANGE|WHOLEFOLD|BANG|FILE1|ARGOPT|DFLALL|TRLBAR|CMDWIN), + EX(CMD_wNext, "wNext", ex_wnext, + RANGE|WHOLEFOLD|NOTADR|BANG|FILE1|ARGOPT|TRLBAR), + EX(CMD_wall, "wall", do_wqall, + BANG|TRLBAR|CMDWIN), + EX(CMD_while, "while", ex_while, + EXTRA|NOTRLCOM|SBOXOK|CMDWIN), + EX(CMD_winsize, "winsize", ex_winsize, + EXTRA|NEEDARG|TRLBAR), + EX(CMD_wincmd, "wincmd", ex_wincmd, + NEEDARG|WORD1|RANGE|NOTADR), + EX(CMD_windo, "windo", ex_listdo, + BANG|NEEDARG|EXTRA|NOTRLCOM), + EX(CMD_winpos, "winpos", ex_winpos, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_wnext, "wnext", ex_wnext, + RANGE|NOTADR|BANG|FILE1|ARGOPT|TRLBAR), + EX(CMD_wprevious, "wprevious", ex_wnext, + RANGE|NOTADR|BANG|FILE1|ARGOPT|TRLBAR), + EX(CMD_wq, "wq", ex_exit, + RANGE|WHOLEFOLD|BANG|FILE1|ARGOPT|DFLALL|TRLBAR), + EX(CMD_wqall, "wqall", do_wqall, + BANG|FILE1|ARGOPT|DFLALL|TRLBAR), + EX(CMD_wsverb, "wsverb", ex_wsverb, + EXTRA|NOTADR|NEEDARG), + EX(CMD_wundo, "wundo", ex_wundo, + BANG|NEEDARG|FILE1), + EX(CMD_wviminfo, "wviminfo", ex_viminfo, + BANG|FILE1|TRLBAR|CMDWIN), + EX(CMD_xit, "xit", ex_exit, + RANGE|WHOLEFOLD|BANG|FILE1|ARGOPT|DFLALL|TRLBAR|CMDWIN), + EX(CMD_xall, "xall", do_wqall, + BANG|TRLBAR), + EX(CMD_xmap, "xmap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_xmapclear, "xmapclear", ex_mapclear, + EXTRA|TRLBAR|CMDWIN), + EX(CMD_xmenu, "xmenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_xnoremap, "xnoremap", ex_map, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_xnoremenu, "xnoremenu", ex_menu, + RANGE|NOTADR|ZEROR|EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_xunmap, "xunmap", ex_unmap, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_xunmenu, "xunmenu", ex_menu, + EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN), + EX(CMD_yank, "yank", ex_operators, + RANGE|WHOLEFOLD|REGSTR|COUNT|TRLBAR|CMDWIN), + EX(CMD_z, "z", ex_z, + RANGE|WHOLEFOLD|EXTRA|EXFLAGS|TRLBAR|CMDWIN), + + /* commands that don't start with a lowercase letter */ + EX(CMD_bang, "!", ex_bang, + RANGE|WHOLEFOLD|BANG|FILES|CMDWIN), + EX(CMD_pound, "#", ex_print, + RANGE|WHOLEFOLD|COUNT|EXFLAGS|TRLBAR|CMDWIN), + EX(CMD_and, "&", do_sub, + RANGE|WHOLEFOLD|EXTRA|CMDWIN|MODIFY), + EX(CMD_star, "*", ex_at, + RANGE|WHOLEFOLD|EXTRA|TRLBAR|CMDWIN), + EX(CMD_lshift, "<", ex_operators, + RANGE|WHOLEFOLD|COUNT|EXFLAGS|TRLBAR|CMDWIN|MODIFY), + EX(CMD_equal, "=", ex_equal, + RANGE|TRLBAR|DFLALL|EXFLAGS|CMDWIN), + EX(CMD_rshift, ">", ex_operators, + RANGE|WHOLEFOLD|COUNT|EXFLAGS|TRLBAR|CMDWIN|MODIFY), + EX(CMD_at, "@", ex_at, + RANGE|WHOLEFOLD|EXTRA|TRLBAR|CMDWIN), + EX(CMD_Next, "Next", ex_previous, + EXTRA|RANGE|NOTADR|COUNT|BANG|EDITCMD|ARGOPT|TRLBAR), + EX(CMD_Print, "Print", ex_print, + RANGE|WHOLEFOLD|COUNT|EXFLAGS|TRLBAR|CMDWIN), + EX(CMD_X, "X", ex_X, + TRLBAR), + EX(CMD_tilde, "~", do_sub, + RANGE|WHOLEFOLD|EXTRA|CMDWIN|MODIFY), + +#ifndef DO_DECLARE_EXCMD + CMD_SIZE, /* MUST be after all real commands! */ + CMD_USER = -1, /* User-defined command */ + CMD_USER_BUF = -2 /* User-defined command local to buffer */ +#endif +}; + +#define USER_CMDIDX(idx) ((int)(idx) < 0) + +#ifndef DO_DECLARE_EXCMD +typedef enum CMD_index cmdidx_T; + +/* + * Arguments used for Ex commands. + */ +struct exarg { + char_u *arg; /* argument of the command */ + char_u *nextcmd; /* next command (NULL if none) */ + char_u *cmd; /* the name of the command (except for :make) */ + char_u **cmdlinep; /* pointer to pointer of allocated cmdline */ + cmdidx_T cmdidx; /* the index for the command */ + long argt; /* flags for the command */ + int skip; /* don't execute the command, only parse it */ + int forceit; /* TRUE if ! present */ + int addr_count; /* the number of addresses given */ + linenr_T line1; /* the first line number */ + linenr_T line2; /* the second line number or count */ + int flags; /* extra flags after count: EXFLAG_ */ + char_u *do_ecmd_cmd; /* +command arg to be used in edited file */ + linenr_T do_ecmd_lnum; /* the line number in an edited file */ + int append; /* TRUE with ":w >>file" command */ + int usefilter; /* TRUE with ":w !command" and ":r!command" */ + int amount; /* number of '>' or '<' for shift command */ + int regname; /* register name (NUL if none) */ + int force_bin; /* 0, FORCE_BIN or FORCE_NOBIN */ + int read_edit; /* ++edit argument */ + int force_ff; /* ++ff= argument (index in cmd[]) */ + int force_enc; /* ++enc= argument (index in cmd[]) */ + int bad_char; /* BAD_KEEP, BAD_DROP or replacement byte */ + int useridx; /* user command index */ + char_u *errmsg; /* returned error message */ + char_u *(*getline)__ARGS((int, void *, int)); + void *cookie; /* argument for getline() */ + struct condstack *cstack; /* condition stack for ":if" etc. */ +}; + +#define FORCE_BIN 1 /* ":edit ++bin file" */ +#define FORCE_NOBIN 2 /* ":edit ++nobin file" */ + +/* Values for "flags" */ +#define EXFLAG_LIST 0x01 /* 'l': list */ +#define EXFLAG_NR 0x02 /* '#': number */ +#define EXFLAG_PRINT 0x04 /* 'p': print */ + +#endif diff --git a/src/ex_cmds2.c b/src/ex_cmds2.c new file mode 100644 index 0000000000..42ccf5a416 --- /dev/null +++ b/src/ex_cmds2.c @@ -0,0 +1,3618 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * ex_cmds2.c: some more functions for command line commands + */ + +#include "vim.h" +#include "version.h" + +static void cmd_source __ARGS((char_u *fname, exarg_T *eap)); + +/* Growarray to store info about already sourced scripts. + * For Unix also store the dev/ino, so that we don't have to stat() each + * script when going through the list. */ +typedef struct scriptitem_S { + char_u *sn_name; +# ifdef UNIX + int sn_dev_valid; + dev_t sn_dev; + ino_t sn_ino; +# endif + int sn_prof_on; /* TRUE when script is/was profiled */ + int sn_pr_force; /* forceit: profile functions in this script */ + proftime_T sn_pr_child; /* time set when going into first child */ + int sn_pr_nest; /* nesting for sn_pr_child */ + /* profiling the script as a whole */ + int sn_pr_count; /* nr of times sourced */ + proftime_T sn_pr_total; /* time spent in script + children */ + proftime_T sn_pr_self; /* time spent in script itself */ + proftime_T sn_pr_start; /* time at script start */ + proftime_T sn_pr_children; /* time in children after script start */ + /* profiling the script per line */ + garray_T sn_prl_ga; /* things stored for every line */ + proftime_T sn_prl_start; /* start time for current line */ + proftime_T sn_prl_children; /* time spent in children for this line */ + proftime_T sn_prl_wait; /* wait start time for current line */ + int sn_prl_idx; /* index of line being timed; -1 if none */ + int sn_prl_execed; /* line being timed was executed */ +} scriptitem_T; + +static garray_T script_items = {0, 0, sizeof(scriptitem_T), 4, NULL}; +#define SCRIPT_ITEM(id) (((scriptitem_T *)script_items.ga_data)[(id) - 1]) + +/* Struct used in sn_prl_ga for every line of a script. */ +typedef struct sn_prl_S { + int snp_count; /* nr of times line was executed */ + proftime_T sn_prl_total; /* time spent in a line + children */ + proftime_T sn_prl_self; /* time spent in a line itself */ +} sn_prl_T; + +# define PRL_ITEM(si, idx) (((sn_prl_T *)(si)->sn_prl_ga.ga_data)[(idx)]) + +static int debug_greedy = FALSE; /* batch mode debugging: don't save + and restore typeahead. */ + +/* + * do_debug(): Debug mode. + * Repeatedly get Ex commands, until told to continue normal execution. + */ +void do_debug(cmd) +char_u *cmd; +{ + int save_msg_scroll = msg_scroll; + int save_State = State; + int save_did_emsg = did_emsg; + int save_cmd_silent = cmd_silent; + int save_msg_silent = msg_silent; + int save_emsg_silent = emsg_silent; + int save_redir_off = redir_off; + tasave_T typeaheadbuf; + int typeahead_saved = FALSE; + int save_ignore_script = 0; + int save_ex_normal_busy; + int n; + char_u *cmdline = NULL; + char_u *p; + char *tail = NULL; + static int last_cmd = 0; +#define CMD_CONT 1 +#define CMD_NEXT 2 +#define CMD_STEP 3 +#define CMD_FINISH 4 +#define CMD_QUIT 5 +#define CMD_INTERRUPT 6 + + + /* Make sure we are in raw mode and start termcap mode. Might have side + * effects... */ + settmode(TMODE_RAW); + starttermcap(); + + ++RedrawingDisabled; /* don't redisplay the window */ + ++no_wait_return; /* don't wait for return */ + did_emsg = FALSE; /* don't use error from debugged stuff */ + cmd_silent = FALSE; /* display commands */ + msg_silent = FALSE; /* display messages */ + emsg_silent = FALSE; /* display error messages */ + redir_off = TRUE; /* don't redirect debug commands */ + + State = NORMAL; + + if (!debug_did_msg) + MSG(_("Entering Debug mode. Type \"cont\" to continue.")); + if (sourcing_name != NULL) + msg(sourcing_name); + if (sourcing_lnum != 0) + smsg((char_u *)_("line %ld: %s"), (long)sourcing_lnum, cmd); + else + smsg((char_u *)_("cmd: %s"), cmd); + + /* + * Repeat getting a command and executing it. + */ + for (;; ) { + msg_scroll = TRUE; + need_wait_return = FALSE; + /* Save the current typeahead buffer and replace it with an empty one. + * This makes sure we get input from the user here and don't interfere + * with the commands being executed. Reset "ex_normal_busy" to avoid + * the side effects of using ":normal". Save the stuff buffer and make + * it empty. Set ignore_script to avoid reading from script input. */ + save_ex_normal_busy = ex_normal_busy; + ex_normal_busy = 0; + if (!debug_greedy) { + save_typeahead(&typeaheadbuf); + typeahead_saved = TRUE; + save_ignore_script = ignore_script; + ignore_script = TRUE; + } + + cmdline = getcmdline_prompt('>', NULL, 0, EXPAND_NOTHING, NULL); + + if (typeahead_saved) { + restore_typeahead(&typeaheadbuf); + ignore_script = save_ignore_script; + } + ex_normal_busy = save_ex_normal_busy; + + cmdline_row = msg_row; + if (cmdline != NULL) { + /* If this is a debug command, set "last_cmd". + * If not, reset "last_cmd". + * For a blank line use previous command. */ + p = skipwhite(cmdline); + if (*p != NUL) { + switch (*p) { + case 'c': last_cmd = CMD_CONT; + tail = "ont"; + break; + case 'n': last_cmd = CMD_NEXT; + tail = "ext"; + break; + case 's': last_cmd = CMD_STEP; + tail = "tep"; + break; + case 'f': last_cmd = CMD_FINISH; + tail = "inish"; + break; + case 'q': last_cmd = CMD_QUIT; + tail = "uit"; + break; + case 'i': last_cmd = CMD_INTERRUPT; + tail = "nterrupt"; + break; + default: last_cmd = 0; + } + if (last_cmd != 0) { + /* Check that the tail matches. */ + ++p; + while (*p != NUL && *p == *tail) { + ++p; + ++tail; + } + if (ASCII_ISALPHA(*p)) + last_cmd = 0; + } + } + + if (last_cmd != 0) { + /* Execute debug command: decided where to break next and + * return. */ + switch (last_cmd) { + case CMD_CONT: + debug_break_level = -1; + break; + case CMD_NEXT: + debug_break_level = ex_nesting_level; + break; + case CMD_STEP: + debug_break_level = 9999; + break; + case CMD_FINISH: + debug_break_level = ex_nesting_level - 1; + break; + case CMD_QUIT: + got_int = TRUE; + debug_break_level = -1; + break; + case CMD_INTERRUPT: + got_int = TRUE; + debug_break_level = 9999; + /* Do not repeat ">interrupt" cmd, continue stepping. */ + last_cmd = CMD_STEP; + break; + } + break; + } + + /* don't debug this command */ + n = debug_break_level; + debug_break_level = -1; + (void)do_cmdline(cmdline, getexline, NULL, + DOCMD_VERBOSE|DOCMD_EXCRESET); + debug_break_level = n; + + vim_free(cmdline); + } + lines_left = Rows - 1; + } + vim_free(cmdline); + + --RedrawingDisabled; + --no_wait_return; + redraw_all_later(NOT_VALID); + need_wait_return = FALSE; + msg_scroll = save_msg_scroll; + lines_left = Rows - 1; + State = save_State; + did_emsg = save_did_emsg; + cmd_silent = save_cmd_silent; + msg_silent = save_msg_silent; + emsg_silent = save_emsg_silent; + redir_off = save_redir_off; + + /* Only print the message again when typing a command before coming back + * here. */ + debug_did_msg = TRUE; +} + +/* + * ":debug". + */ +void ex_debug(eap) +exarg_T *eap; +{ + int debug_break_level_save = debug_break_level; + + debug_break_level = 9999; + do_cmdline_cmd(eap->arg); + debug_break_level = debug_break_level_save; +} + +static char_u *debug_breakpoint_name = NULL; +static linenr_T debug_breakpoint_lnum; + +/* + * When debugging or a breakpoint is set on a skipped command, no debug prompt + * is shown by do_one_cmd(). This situation is indicated by debug_skipped, and + * debug_skipped_name is then set to the source name in the breakpoint case. If + * a skipped command decides itself that a debug prompt should be displayed, it + * can do so by calling dbg_check_skipped(). + */ +static int debug_skipped; +static char_u *debug_skipped_name; + +/* + * Go to debug mode when a breakpoint was encountered or "ex_nesting_level" is + * at or below the break level. But only when the line is actually + * executed. Return TRUE and set breakpoint_name for skipped commands that + * decide to execute something themselves. + * Called from do_one_cmd() before executing a command. + */ +void dbg_check_breakpoint(eap) +exarg_T *eap; +{ + char_u *p; + + debug_skipped = FALSE; + if (debug_breakpoint_name != NULL) { + if (!eap->skip) { + /* replace K_SNR with "" */ + if (debug_breakpoint_name[0] == K_SPECIAL + && debug_breakpoint_name[1] == KS_EXTRA + && debug_breakpoint_name[2] == (int)KE_SNR) + p = (char_u *)""; + else + p = (char_u *)""; + smsg((char_u *)_("Breakpoint in \"%s%s\" line %ld"), + p, + debug_breakpoint_name + (*p == NUL ? 0 : 3), + (long)debug_breakpoint_lnum); + debug_breakpoint_name = NULL; + do_debug(eap->cmd); + } else { + debug_skipped = TRUE; + debug_skipped_name = debug_breakpoint_name; + debug_breakpoint_name = NULL; + } + } else if (ex_nesting_level <= debug_break_level) { + if (!eap->skip) + do_debug(eap->cmd); + else { + debug_skipped = TRUE; + debug_skipped_name = NULL; + } + } +} + +/* + * Go to debug mode if skipped by dbg_check_breakpoint() because eap->skip was + * set. Return TRUE when the debug mode is entered this time. + */ +int dbg_check_skipped(eap) +exarg_T *eap; +{ + int prev_got_int; + + if (debug_skipped) { + /* + * Save the value of got_int and reset it. We don't want a previous + * interruption cause flushing the input buffer. + */ + prev_got_int = got_int; + got_int = FALSE; + debug_breakpoint_name = debug_skipped_name; + /* eap->skip is TRUE */ + eap->skip = FALSE; + (void)dbg_check_breakpoint(eap); + eap->skip = TRUE; + got_int |= prev_got_int; + return TRUE; + } + return FALSE; +} + +/* + * The list of breakpoints: dbg_breakp. + * This is a grow-array of structs. + */ +struct debuggy { + int dbg_nr; /* breakpoint number */ + int dbg_type; /* DBG_FUNC or DBG_FILE */ + char_u *dbg_name; /* function or file name */ + regprog_T *dbg_prog; /* regexp program */ + linenr_T dbg_lnum; /* line number in function or file */ + int dbg_forceit; /* ! used */ +}; + +static garray_T dbg_breakp = {0, 0, sizeof(struct debuggy), 4, NULL}; +#define BREAKP(idx) (((struct debuggy *)dbg_breakp.ga_data)[idx]) +#define DEBUGGY(gap, idx) (((struct debuggy *)gap->ga_data)[idx]) +static int last_breakp = 0; /* nr of last defined breakpoint */ + +/* Profiling uses file and func names similar to breakpoints. */ +static garray_T prof_ga = {0, 0, sizeof(struct debuggy), 4, NULL}; +#define DBG_FUNC 1 +#define DBG_FILE 2 + +static int dbg_parsearg __ARGS((char_u *arg, garray_T *gap)); +static linenr_T debuggy_find __ARGS((int file,char_u *fname, linenr_T after, + garray_T *gap, + int *fp)); + +/* + * Parse the arguments of ":profile", ":breakadd" or ":breakdel" and put them + * in the entry just after the last one in dbg_breakp. Note that "dbg_name" + * is allocated. + * Returns FAIL for failure. + */ +static int dbg_parsearg(arg, gap) +char_u *arg; +garray_T *gap; /* either &dbg_breakp or &prof_ga */ +{ + char_u *p = arg; + char_u *q; + struct debuggy *bp; + int here = FALSE; + + if (ga_grow(gap, 1) == FAIL) + return FAIL; + bp = &DEBUGGY(gap, gap->ga_len); + + /* Find "func" or "file". */ + if (STRNCMP(p, "func", 4) == 0) + bp->dbg_type = DBG_FUNC; + else if (STRNCMP(p, "file", 4) == 0) + bp->dbg_type = DBG_FILE; + else if ( + gap != &prof_ga && + STRNCMP(p, "here", 4) == 0) { + if (curbuf->b_ffname == NULL) { + EMSG(_(e_noname)); + return FAIL; + } + bp->dbg_type = DBG_FILE; + here = TRUE; + } else { + EMSG2(_(e_invarg2), p); + return FAIL; + } + p = skipwhite(p + 4); + + /* Find optional line number. */ + if (here) + bp->dbg_lnum = curwin->w_cursor.lnum; + else if ( + gap != &prof_ga && + VIM_ISDIGIT(*p)) { + bp->dbg_lnum = getdigits(&p); + p = skipwhite(p); + } else + bp->dbg_lnum = 0; + + /* Find the function or file name. Don't accept a function name with (). */ + if ((!here && *p == NUL) + || (here && *p != NUL) + || (bp->dbg_type == DBG_FUNC && strstr((char *)p, "()") != NULL)) { + EMSG2(_(e_invarg2), arg); + return FAIL; + } + + if (bp->dbg_type == DBG_FUNC) + bp->dbg_name = vim_strsave(p); + else if (here) + bp->dbg_name = vim_strsave(curbuf->b_ffname); + else { + /* Expand the file name in the same way as do_source(). This means + * doing it twice, so that $DIR/file gets expanded when $DIR is + * "~/dir". */ + q = expand_env_save(p); + if (q == NULL) + return FAIL; + p = expand_env_save(q); + vim_free(q); + if (p == NULL) + return FAIL; + if (*p != '*') { + bp->dbg_name = fix_fname(p); + vim_free(p); + } else + bp->dbg_name = p; + } + + if (bp->dbg_name == NULL) + return FAIL; + return OK; +} + +/* + * ":breakadd". + */ +void ex_breakadd(eap) +exarg_T *eap; +{ + struct debuggy *bp; + char_u *pat; + garray_T *gap; + + gap = &dbg_breakp; + if (eap->cmdidx == CMD_profile) + gap = &prof_ga; + + if (dbg_parsearg(eap->arg, gap) == OK) { + bp = &DEBUGGY(gap, gap->ga_len); + bp->dbg_forceit = eap->forceit; + + pat = file_pat_to_reg_pat(bp->dbg_name, NULL, NULL, FALSE); + if (pat != NULL) { + bp->dbg_prog = vim_regcomp(pat, RE_MAGIC + RE_STRING); + vim_free(pat); + } + if (pat == NULL || bp->dbg_prog == NULL) + vim_free(bp->dbg_name); + else { + if (bp->dbg_lnum == 0) /* default line number is 1 */ + bp->dbg_lnum = 1; + if (eap->cmdidx != CMD_profile) { + DEBUGGY(gap, gap->ga_len).dbg_nr = ++last_breakp; + ++debug_tick; + } + ++gap->ga_len; + } + } +} + +/* + * ":debuggreedy". + */ +void ex_debuggreedy(eap) +exarg_T *eap; +{ + if (eap->addr_count == 0 || eap->line2 != 0) + debug_greedy = TRUE; + else + debug_greedy = FALSE; +} + +/* + * ":breakdel" and ":profdel". + */ +void ex_breakdel(eap) +exarg_T *eap; +{ + struct debuggy *bp, *bpi; + int nr; + int todel = -1; + int del_all = FALSE; + int i; + linenr_T best_lnum = 0; + garray_T *gap; + + gap = &dbg_breakp; + if (eap->cmdidx == CMD_profdel) { + gap = &prof_ga; + } + + if (vim_isdigit(*eap->arg)) { + /* ":breakdel {nr}" */ + nr = atol((char *)eap->arg); + for (i = 0; i < gap->ga_len; ++i) + if (DEBUGGY(gap, i).dbg_nr == nr) { + todel = i; + break; + } + } else if (*eap->arg == '*') { + todel = 0; + del_all = TRUE; + } else { + /* ":breakdel {func|file} [lnum] {name}" */ + if (dbg_parsearg(eap->arg, gap) == FAIL) + return; + bp = &DEBUGGY(gap, gap->ga_len); + for (i = 0; i < gap->ga_len; ++i) { + bpi = &DEBUGGY(gap, i); + if (bp->dbg_type == bpi->dbg_type + && STRCMP(bp->dbg_name, bpi->dbg_name) == 0 + && (bp->dbg_lnum == bpi->dbg_lnum + || (bp->dbg_lnum == 0 + && (best_lnum == 0 + || bpi->dbg_lnum < best_lnum)))) { + todel = i; + best_lnum = bpi->dbg_lnum; + } + } + vim_free(bp->dbg_name); + } + + if (todel < 0) + EMSG2(_("E161: Breakpoint not found: %s"), eap->arg); + else { + while (gap->ga_len > 0) { + vim_free(DEBUGGY(gap, todel).dbg_name); + vim_regfree(DEBUGGY(gap, todel).dbg_prog); + --gap->ga_len; + if (todel < gap->ga_len) + mch_memmove(&DEBUGGY(gap, todel), &DEBUGGY(gap, todel + 1), + (gap->ga_len - todel) * sizeof(struct debuggy)); + if (eap->cmdidx == CMD_breakdel) + ++debug_tick; + if (!del_all) + break; + } + + /* If all breakpoints were removed clear the array. */ + if (gap->ga_len == 0) + ga_clear(gap); + } +} + +/* + * ":breaklist". + */ +void ex_breaklist(eap) +exarg_T *eap UNUSED; +{ + struct debuggy *bp; + int i; + + if (dbg_breakp.ga_len == 0) + MSG(_("No breakpoints defined")); + else + for (i = 0; i < dbg_breakp.ga_len; ++i) { + bp = &BREAKP(i); + if (bp->dbg_type == DBG_FILE) + home_replace(NULL, bp->dbg_name, NameBuff, MAXPATHL, TRUE); + smsg((char_u *)_("%3d %s %s line %ld"), + bp->dbg_nr, + bp->dbg_type == DBG_FUNC ? "func" : "file", + bp->dbg_type == DBG_FUNC ? bp->dbg_name : NameBuff, + (long)bp->dbg_lnum); + } +} + +/* + * Find a breakpoint for a function or sourced file. + * Returns line number at which to break; zero when no matching breakpoint. + */ +linenr_T dbg_find_breakpoint(file, fname, after) +int file; /* TRUE for a file, FALSE for a function */ +char_u *fname; /* file or function name */ +linenr_T after; /* after this line number */ +{ + return debuggy_find(file, fname, after, &dbg_breakp, NULL); +} + +/* + * Return TRUE if profiling is on for a function or sourced file. + */ +int has_profiling(file, fname, fp) +int file; /* TRUE for a file, FALSE for a function */ +char_u *fname; /* file or function name */ +int *fp; /* return: forceit */ +{ + return debuggy_find(file, fname, (linenr_T)0, &prof_ga, fp) + != (linenr_T)0; +} + +/* + * Common code for dbg_find_breakpoint() and has_profiling(). + */ +static linenr_T debuggy_find(file, fname, after, gap, fp) +int file; /* TRUE for a file, FALSE for a function */ +char_u *fname; /* file or function name */ +linenr_T after; /* after this line number */ +garray_T *gap; /* either &dbg_breakp or &prof_ga */ +int *fp; /* if not NULL: return forceit */ +{ + struct debuggy *bp; + int i; + linenr_T lnum = 0; + regmatch_T regmatch; + char_u *name = fname; + int prev_got_int; + + /* Return quickly when there are no breakpoints. */ + if (gap->ga_len == 0) + return (linenr_T)0; + + /* Replace K_SNR in function name with "". */ + if (!file && fname[0] == K_SPECIAL) { + name = alloc((unsigned)STRLEN(fname) + 3); + if (name == NULL) + name = fname; + else { + STRCPY(name, ""); + STRCPY(name + 5, fname + 3); + } + } + + for (i = 0; i < gap->ga_len; ++i) { + /* Skip entries that are not useful or are for a line that is beyond + * an already found breakpoint. */ + bp = &DEBUGGY(gap, i); + if (((bp->dbg_type == DBG_FILE) == file && ( + gap == &prof_ga || + (bp->dbg_lnum > after && (lnum == 0 || bp->dbg_lnum < lnum))))) { + regmatch.regprog = bp->dbg_prog; + regmatch.rm_ic = FALSE; + /* + * Save the value of got_int and reset it. We don't want a + * previous interruption cancel matching, only hitting CTRL-C + * while matching should abort it. + */ + prev_got_int = got_int; + got_int = FALSE; + if (vim_regexec(®match, name, (colnr_T)0)) { + lnum = bp->dbg_lnum; + if (fp != NULL) + *fp = bp->dbg_forceit; + } + got_int |= prev_got_int; + } + } + if (name != fname) + vim_free(name); + + return lnum; +} + +/* + * Called when a breakpoint was encountered. + */ +void dbg_breakpoint(name, lnum) +char_u *name; +linenr_T lnum; +{ + /* We need to check if this line is actually executed in do_one_cmd() */ + debug_breakpoint_name = name; + debug_breakpoint_lnum = lnum; +} + + +/* + * Store the current time in "tm". + */ +void profile_start(tm) +proftime_T *tm; +{ + gettimeofday(tm, NULL); +} + +/* + * Compute the elapsed time from "tm" till now and store in "tm". + */ +void profile_end(tm) +proftime_T *tm; +{ + proftime_T now; + + gettimeofday(&now, NULL); + tm->tv_usec = now.tv_usec - tm->tv_usec; + tm->tv_sec = now.tv_sec - tm->tv_sec; + if (tm->tv_usec < 0) { + tm->tv_usec += 1000000; + --tm->tv_sec; + } +} + +/* + * Subtract the time "tm2" from "tm". + */ +void profile_sub(tm, tm2) +proftime_T *tm, *tm2; +{ + tm->tv_usec -= tm2->tv_usec; + tm->tv_sec -= tm2->tv_sec; + if (tm->tv_usec < 0) { + tm->tv_usec += 1000000; + --tm->tv_sec; + } +} + +/* + * Return a string that represents the time in "tm". + * Uses a static buffer! + */ +char * profile_msg(tm) +proftime_T *tm; +{ + static char buf[50]; + + sprintf(buf, "%3ld.%06ld", (long)tm->tv_sec, (long)tm->tv_usec); + return buf; +} + +/* + * Put the time "msec" past now in "tm". + */ +void profile_setlimit(msec, tm) +long msec; +proftime_T *tm; +{ + if (msec <= 0) /* no limit */ + profile_zero(tm); + else { + long usec; + + gettimeofday(tm, NULL); + usec = (long)tm->tv_usec + (long)msec * 1000; + tm->tv_usec = usec % 1000000L; + tm->tv_sec += usec / 1000000L; + } +} + +/* + * Return TRUE if the current time is past "tm". + */ +int profile_passed_limit(tm) +proftime_T *tm; +{ + proftime_T now; + + if (tm->tv_sec == 0) /* timer was not set */ + return FALSE; + gettimeofday(&now, NULL); + return now.tv_sec > tm->tv_sec + || (now.tv_sec == tm->tv_sec && now.tv_usec > tm->tv_usec); +} + +/* + * Set the time in "tm" to zero. + */ +void profile_zero(tm) +proftime_T *tm; +{ + tm->tv_usec = 0; + tm->tv_sec = 0; +} + + +# if defined(HAVE_MATH_H) +# include +# endif + +/* + * Divide the time "tm" by "count" and store in "tm2". + */ +void profile_divide(tm, count, tm2) +proftime_T *tm; +proftime_T *tm2; +int count; +{ + if (count == 0) + profile_zero(tm2); + else { + double usec = (tm->tv_sec * 1000000.0 + tm->tv_usec) / count; + + tm2->tv_sec = floor(usec / 1000000.0); + tm2->tv_usec = vim_round(usec - (tm2->tv_sec * 1000000.0)); + } +} + +/* + * Functions for profiling. + */ +static void script_do_profile __ARGS((scriptitem_T *si)); +static void script_dump_profile __ARGS((FILE *fd)); +static proftime_T prof_wait_time; + +/* + * Add the time "tm2" to "tm". + */ +void profile_add(tm, tm2) +proftime_T *tm, *tm2; +{ + tm->tv_usec += tm2->tv_usec; + tm->tv_sec += tm2->tv_sec; + if (tm->tv_usec >= 1000000) { + tm->tv_usec -= 1000000; + ++tm->tv_sec; + } +} + +/* + * Add the "self" time from the total time and the children's time. + */ +void profile_self(self, total, children) +proftime_T *self, *total, *children; +{ + /* Check that the result won't be negative. Can happen with recursive + * calls. */ + if (total->tv_sec < children->tv_sec + || (total->tv_sec == children->tv_sec + && total->tv_usec <= children->tv_usec)) + return; + profile_add(self, total); + profile_sub(self, children); +} + +/* + * Get the current waittime. + */ +void profile_get_wait(tm) +proftime_T *tm; +{ + *tm = prof_wait_time; +} + +/* + * Subtract the passed waittime since "tm" from "tma". + */ +void profile_sub_wait(tm, tma) +proftime_T *tm, *tma; +{ + proftime_T tm3 = prof_wait_time; + + profile_sub(&tm3, tm); + profile_sub(tma, &tm3); +} + +/* + * Return TRUE if "tm1" and "tm2" are equal. + */ +int profile_equal(tm1, tm2) +proftime_T *tm1, *tm2; +{ + return tm1->tv_usec == tm2->tv_usec && tm1->tv_sec == tm2->tv_sec; +} + +/* + * Return <0, 0 or >0 if "tm1" < "tm2", "tm1" == "tm2" or "tm1" > "tm2" + */ +int profile_cmp(tm1, tm2) +const proftime_T *tm1, *tm2; +{ + if (tm1->tv_sec == tm2->tv_sec) + return tm2->tv_usec - tm1->tv_usec; + return tm2->tv_sec - tm1->tv_sec; +} + +static char_u *profile_fname = NULL; +static proftime_T pause_time; + +/* + * ":profile cmd args" + */ +void ex_profile(eap) +exarg_T *eap; +{ + char_u *e; + int len; + + e = skiptowhite(eap->arg); + len = (int)(e - eap->arg); + e = skipwhite(e); + + if (len == 5 && STRNCMP(eap->arg, "start", 5) == 0 && *e != NUL) { + vim_free(profile_fname); + profile_fname = vim_strsave(e); + do_profiling = PROF_YES; + profile_zero(&prof_wait_time); + set_vim_var_nr(VV_PROFILING, 1L); + } else if (do_profiling == PROF_NONE) + EMSG(_("E750: First use \":profile start {fname}\"")); + else if (STRCMP(eap->arg, "pause") == 0) { + if (do_profiling == PROF_YES) + profile_start(&pause_time); + do_profiling = PROF_PAUSED; + } else if (STRCMP(eap->arg, "continue") == 0) { + if (do_profiling == PROF_PAUSED) { + profile_end(&pause_time); + profile_add(&prof_wait_time, &pause_time); + } + do_profiling = PROF_YES; + } else { + /* The rest is similar to ":breakadd". */ + ex_breakadd(eap); + } +} + +/* Command line expansion for :profile. */ +static enum { + PEXP_SUBCMD, /* expand :profile sub-commands */ + PEXP_FUNC /* expand :profile func {funcname} */ +} pexpand_what; + +static char *pexpand_cmds[] = { + "start", +#define PROFCMD_START 0 + "pause", +#define PROFCMD_PAUSE 1 + "continue", +#define PROFCMD_CONTINUE 2 + "func", +#define PROFCMD_FUNC 3 + "file", +#define PROFCMD_FILE 4 + NULL +#define PROFCMD_LAST 5 +}; + +/* + * Function given to ExpandGeneric() to obtain the profile command + * specific expansion. + */ +char_u * get_profile_name(xp, idx) +expand_T *xp UNUSED; +int idx; +{ + switch (pexpand_what) { + case PEXP_SUBCMD: + return (char_u *)pexpand_cmds[idx]; + /* case PEXP_FUNC: TODO */ + default: + return NULL; + } +} + +/* + * Handle command line completion for :profile command. + */ +void set_context_in_profile_cmd(xp, arg) +expand_T *xp; +char_u *arg; +{ + char_u *end_subcmd; + + /* Default: expand subcommands. */ + xp->xp_context = EXPAND_PROFILE; + pexpand_what = PEXP_SUBCMD; + xp->xp_pattern = arg; + + end_subcmd = skiptowhite(arg); + if (*end_subcmd == NUL) + return; + + if (end_subcmd - arg == 5 && STRNCMP(arg, "start", 5) == 0) { + xp->xp_context = EXPAND_FILES; + xp->xp_pattern = skipwhite(end_subcmd); + return; + } + + /* TODO: expand function names after "func" */ + xp->xp_context = EXPAND_NOTHING; +} + +/* + * Dump the profiling info. + */ +void profile_dump() { + FILE *fd; + + if (profile_fname != NULL) { + fd = mch_fopen((char *)profile_fname, "w"); + if (fd == NULL) + EMSG2(_(e_notopen), profile_fname); + else { + script_dump_profile(fd); + func_dump_profile(fd); + fclose(fd); + } + } +} + +/* + * Start profiling script "fp". + */ +static void script_do_profile(si) +scriptitem_T *si; +{ + si->sn_pr_count = 0; + profile_zero(&si->sn_pr_total); + profile_zero(&si->sn_pr_self); + + ga_init2(&si->sn_prl_ga, sizeof(sn_prl_T), 100); + si->sn_prl_idx = -1; + si->sn_prof_on = TRUE; + si->sn_pr_nest = 0; +} + +/* + * save time when starting to invoke another script or function. + */ +void script_prof_save(tm) +proftime_T *tm; /* place to store wait time */ +{ + scriptitem_T *si; + + if (current_SID > 0 && current_SID <= script_items.ga_len) { + si = &SCRIPT_ITEM(current_SID); + if (si->sn_prof_on && si->sn_pr_nest++ == 0) + profile_start(&si->sn_pr_child); + } + profile_get_wait(tm); +} + +/* + * Count time spent in children after invoking another script or function. + */ +void script_prof_restore(tm) +proftime_T *tm; +{ + scriptitem_T *si; + + if (current_SID > 0 && current_SID <= script_items.ga_len) { + si = &SCRIPT_ITEM(current_SID); + if (si->sn_prof_on && --si->sn_pr_nest == 0) { + profile_end(&si->sn_pr_child); + profile_sub_wait(tm, &si->sn_pr_child); /* don't count wait time */ + profile_add(&si->sn_pr_children, &si->sn_pr_child); + profile_add(&si->sn_prl_children, &si->sn_pr_child); + } + } +} + +static proftime_T inchar_time; + +/* + * Called when starting to wait for the user to type a character. + */ +void prof_inchar_enter() { + profile_start(&inchar_time); +} + +/* + * Called when finished waiting for the user to type a character. + */ +void prof_inchar_exit() { + profile_end(&inchar_time); + profile_add(&prof_wait_time, &inchar_time); +} + +/* + * Dump the profiling results for all scripts in file "fd". + */ +static void script_dump_profile(fd) +FILE *fd; +{ + int id; + scriptitem_T *si; + int i; + FILE *sfd; + sn_prl_T *pp; + + for (id = 1; id <= script_items.ga_len; ++id) { + si = &SCRIPT_ITEM(id); + if (si->sn_prof_on) { + fprintf(fd, "SCRIPT %s\n", si->sn_name); + if (si->sn_pr_count == 1) + fprintf(fd, "Sourced 1 time\n"); + else + fprintf(fd, "Sourced %d times\n", si->sn_pr_count); + fprintf(fd, "Total time: %s\n", profile_msg(&si->sn_pr_total)); + fprintf(fd, " Self time: %s\n", profile_msg(&si->sn_pr_self)); + fprintf(fd, "\n"); + fprintf(fd, "count total (s) self (s)\n"); + + sfd = mch_fopen((char *)si->sn_name, "r"); + if (sfd == NULL) + fprintf(fd, "Cannot open file!\n"); + else { + for (i = 0; i < si->sn_prl_ga.ga_len; ++i) { + if (vim_fgets(IObuff, IOSIZE, sfd)) + break; + pp = &PRL_ITEM(si, i); + if (pp->snp_count > 0) { + fprintf(fd, "%5d ", pp->snp_count); + if (profile_equal(&pp->sn_prl_total, &pp->sn_prl_self)) + fprintf(fd, " "); + else + fprintf(fd, "%s ", profile_msg(&pp->sn_prl_total)); + fprintf(fd, "%s ", profile_msg(&pp->sn_prl_self)); + } else + fprintf(fd, " "); + fprintf(fd, "%s", IObuff); + } + fclose(sfd); + } + fprintf(fd, "\n"); + } + } +} + +/* + * Return TRUE when a function defined in the current script should be + * profiled. + */ +int prof_def_func() { + if (current_SID > 0) + return SCRIPT_ITEM(current_SID).sn_pr_force; + return FALSE; +} + +/* + * If 'autowrite' option set, try to write the file. + * Careful: autocommands may make "buf" invalid! + * + * return FAIL for failure, OK otherwise + */ +int autowrite(buf, forceit) +buf_T *buf; +int forceit; +{ + int r; + + if (!(p_aw || p_awa) || !p_write + /* never autowrite a "nofile" or "nowrite" buffer */ + || bt_dontwrite(buf) + || (!forceit && buf->b_p_ro) || buf->b_ffname == NULL) + return FAIL; + r = buf_write_all(buf, forceit); + + /* Writing may succeed but the buffer still changed, e.g., when there is a + * conversion error. We do want to return FAIL then. */ + if (buf_valid(buf) && bufIsChanged(buf)) + r = FAIL; + return r; +} + +/* + * flush all buffers, except the ones that are readonly + */ +void autowrite_all() { + buf_T *buf; + + if (!(p_aw || p_awa) || !p_write) + return; + for (buf = firstbuf; buf; buf = buf->b_next) + if (bufIsChanged(buf) && !buf->b_p_ro) { + (void)buf_write_all(buf, FALSE); + /* an autocommand may have deleted the buffer */ + if (!buf_valid(buf)) + buf = firstbuf; + } +} + +/* + * Return TRUE if buffer was changed and cannot be abandoned. + * For flags use the CCGD_ values. + */ +int check_changed(buf, flags) +buf_T *buf; +int flags; +{ + int forceit = (flags & CCGD_FORCEIT); + + if ( !forceit + && bufIsChanged(buf) + && ((flags & CCGD_MULTWIN) || buf->b_nwindows <= 1) + && (!(flags & CCGD_AW) || autowrite(buf, forceit) == FAIL)) { + if ((p_confirm || cmdmod.confirm) && p_write) { + buf_T *buf2; + int count = 0; + + if (flags & CCGD_ALLBUF) + for (buf2 = firstbuf; buf2 != NULL; buf2 = buf2->b_next) + if (bufIsChanged(buf2) + && (buf2->b_ffname != NULL + )) + ++count; + if (!buf_valid(buf)) + /* Autocommand deleted buffer, oops! It's not changed now. */ + return FALSE; + dialog_changed(buf, count > 1); + if (!buf_valid(buf)) + /* Autocommand deleted buffer, oops! It's not changed now. */ + return FALSE; + return bufIsChanged(buf); + } + if (flags & CCGD_EXCMD) + EMSG(_(e_nowrtmsg)); + else + EMSG(_(e_nowrtmsg_nobang)); + return TRUE; + } + return FALSE; +} + + + +/* + * Ask the user what to do when abandoning a changed buffer. + * Must check 'write' option first! + */ +void dialog_changed(buf, checkall) +buf_T *buf; +int checkall; /* may abandon all changed buffers */ +{ + char_u buff[DIALOG_MSG_SIZE]; + int ret; + buf_T *buf2; + exarg_T ea; + + dialog_msg(buff, _("Save changes to \"%s\"?"), + (buf->b_fname != NULL) ? + buf->b_fname : (char_u *)_("Untitled")); + if (checkall) + ret = vim_dialog_yesnoallcancel(VIM_QUESTION, NULL, buff, 1); + else + ret = vim_dialog_yesnocancel(VIM_QUESTION, NULL, buff, 1); + + /* Init ea pseudo-structure, this is needed for the check_overwrite() + * function. */ + ea.append = ea.forceit = FALSE; + + if (ret == VIM_YES) { + if (buf->b_fname != NULL && check_overwrite(&ea, buf, + buf->b_fname, buf->b_ffname, FALSE) == OK) + /* didn't hit Cancel */ + (void)buf_write_all(buf, FALSE); + } else if (ret == VIM_NO) { + unchanged(buf, TRUE); + } else if (ret == VIM_ALL) { + /* + * Write all modified files that can be written. + * Skip readonly buffers, these need to be confirmed + * individually. + */ + for (buf2 = firstbuf; buf2 != NULL; buf2 = buf2->b_next) { + if (bufIsChanged(buf2) + && (buf2->b_ffname != NULL + ) + && !buf2->b_p_ro) { + if (buf2->b_fname != NULL && check_overwrite(&ea, buf2, + buf2->b_fname, buf2->b_ffname, FALSE) == OK) + /* didn't hit Cancel */ + (void)buf_write_all(buf2, FALSE); + /* an autocommand may have deleted the buffer */ + if (!buf_valid(buf2)) + buf2 = firstbuf; + } + } + } else if (ret == VIM_DISCARDALL) { + /* + * mark all buffers as unchanged + */ + for (buf2 = firstbuf; buf2 != NULL; buf2 = buf2->b_next) + unchanged(buf2, TRUE); + } +} + +/* + * Return TRUE if the buffer "buf" can be abandoned, either by making it + * hidden, autowriting it or unloading it. + */ +int can_abandon(buf, forceit) +buf_T *buf; +int forceit; +{ + return P_HID(buf) + || !bufIsChanged(buf) + || buf->b_nwindows > 1 + || autowrite(buf, forceit) == OK + || forceit; +} + +static void add_bufnum __ARGS((int *bufnrs, int *bufnump, int nr)); + +/* + * Add a buffer number to "bufnrs", unless it's already there. + */ +static void add_bufnum(bufnrs, bufnump, nr) +int *bufnrs; +int *bufnump; +int nr; +{ + int i; + + for (i = 0; i < *bufnump; ++i) + if (bufnrs[i] == nr) + return; + bufnrs[*bufnump] = nr; + *bufnump = *bufnump + 1; +} + +/* + * Return TRUE if any buffer was changed and cannot be abandoned. + * That changed buffer becomes the current buffer. + */ +int check_changed_any(hidden) +int hidden; /* Only check hidden buffers */ +{ + int ret = FALSE; + buf_T *buf; + int save; + int i; + int bufnum = 0; + int bufcount = 0; + int *bufnrs; + tabpage_T *tp; + win_T *wp; + + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + ++bufcount; + + if (bufcount == 0) + return FALSE; + + bufnrs = (int *)alloc(sizeof(int) * bufcount); + if (bufnrs == NULL) + return FALSE; + + /* curbuf */ + bufnrs[bufnum++] = curbuf->b_fnum; + /* buf in curtab */ + FOR_ALL_WINDOWS(wp) + if (wp->w_buffer != curbuf) + add_bufnum(bufnrs, &bufnum, wp->w_buffer->b_fnum); + + /* buf in other tab */ + for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) + if (tp != curtab) + for (wp = tp->tp_firstwin; wp != NULL; wp = wp->w_next) + add_bufnum(bufnrs, &bufnum, wp->w_buffer->b_fnum); + /* any other buf */ + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + add_bufnum(bufnrs, &bufnum, buf->b_fnum); + + for (i = 0; i < bufnum; ++i) { + buf = buflist_findnr(bufnrs[i]); + if (buf == NULL) + continue; + if ((!hidden || buf->b_nwindows == 0) && bufIsChanged(buf)) { + /* Try auto-writing the buffer. If this fails but the buffer no + * longer exists it's not changed, that's OK. */ + if (check_changed(buf, (p_awa ? CCGD_AW : 0) + | CCGD_MULTWIN + | CCGD_ALLBUF) && buf_valid(buf)) + break; /* didn't save - still changes */ + } + } + + if (i >= bufnum) + goto theend; + + ret = TRUE; + exiting = FALSE; + /* + * When ":confirm" used, don't give an error message. + */ + if (!(p_confirm || cmdmod.confirm)) { + /* There must be a wait_return for this message, do_buffer() + * may cause a redraw. But wait_return() is a no-op when vgetc() + * is busy (Quit used from window menu), then make sure we don't + * cause a scroll up. */ + if (vgetc_busy > 0) { + msg_row = cmdline_row; + msg_col = 0; + msg_didout = FALSE; + } + if (EMSG2(_("E162: No write since last change for buffer \"%s\""), + buf_spname(buf) != NULL ? buf_spname(buf) : buf->b_fname)) { + save = no_wait_return; + no_wait_return = FALSE; + wait_return(FALSE); + no_wait_return = save; + } + } + + /* Try to find a window that contains the buffer. */ + if (buf != curbuf) + FOR_ALL_TAB_WINDOWS(tp, wp) + if (wp->w_buffer == buf) { + goto_tabpage_win(tp, wp); + /* Paranoia: did autocms wipe out the buffer with changes? */ + if (!buf_valid(buf)) { + goto theend; + } + goto buf_found; + } +buf_found: + + /* Open the changed buffer in the current window. */ + if (buf != curbuf) + set_curbuf(buf, DOBUF_GOTO); + +theend: + vim_free(bufnrs); + return ret; +} + +/* + * return FAIL if there is no file name, OK if there is one + * give error message for FAIL + */ +int check_fname() { + if (curbuf->b_ffname == NULL) { + EMSG(_(e_noname)); + return FAIL; + } + return OK; +} + +/* + * flush the contents of a buffer, unless it has no file name + * + * return FAIL for failure, OK otherwise + */ +int buf_write_all(buf, forceit) +buf_T *buf; +int forceit; +{ + int retval; + buf_T *old_curbuf = curbuf; + + retval = (buf_write(buf, buf->b_ffname, buf->b_fname, + (linenr_T)1, buf->b_ml.ml_line_count, NULL, + FALSE, forceit, TRUE, FALSE)); + if (curbuf != old_curbuf) { + msg_source(hl_attr(HLF_W)); + MSG(_("Warning: Entered other buffer unexpectedly (check autocommands)")); + } + return retval; +} + +/* + * Code to handle the argument list. + */ + +static char_u *do_one_arg __ARGS((char_u *str)); +static int do_arglist __ARGS((char_u *str, int what, int after)); +static void alist_check_arg_idx __ARGS((void)); +static int editing_arg_idx __ARGS((win_T *win)); +static int alist_add_list __ARGS((int count, char_u **files, int after)); +#define AL_SET 1 +#define AL_ADD 2 +#define AL_DEL 3 + +/* + * Isolate one argument, taking backticks. + * Changes the argument in-place, puts a NUL after it. Backticks remain. + * Return a pointer to the start of the next argument. + */ +static char_u * do_one_arg(str) +char_u *str; +{ + char_u *p; + int inbacktick; + + inbacktick = FALSE; + for (p = str; *str; ++str) { + /* When the backslash is used for escaping the special meaning of a + * character we need to keep it until wildcard expansion. */ + if (rem_backslash(str)) { + *p++ = *str++; + *p++ = *str; + } else { + /* An item ends at a space not in backticks */ + if (!inbacktick && vim_isspace(*str)) + break; + if (*str == '`') + inbacktick ^= TRUE; + *p++ = *str; + } + } + str = skipwhite(str); + *p = NUL; + + return str; +} + +/* + * Separate the arguments in "str" and return a list of pointers in the + * growarray "gap". + */ +int get_arglist(gap, str) +garray_T *gap; +char_u *str; +{ + ga_init2(gap, (int)sizeof(char_u *), 20); + while (*str != NUL) { + if (ga_grow(gap, 1) == FAIL) { + ga_clear(gap); + return FAIL; + } + ((char_u **)gap->ga_data)[gap->ga_len++] = str; + + /* Isolate one argument, change it in-place, put a NUL after it. */ + str = do_one_arg(str); + } + return OK; +} + +/* + * Parse a list of arguments (file names), expand them and return in + * "fnames[fcountp]". When "wig" is TRUE, removes files matching 'wildignore'. + * Return FAIL or OK. + */ +int get_arglist_exp(str, fcountp, fnamesp, wig) +char_u *str; +int *fcountp; +char_u ***fnamesp; +int wig; +{ + garray_T ga; + int i; + + if (get_arglist(&ga, str) == FAIL) + return FAIL; + if (wig == TRUE) + i = expand_wildcards(ga.ga_len, (char_u **)ga.ga_data, + fcountp, fnamesp, EW_FILE|EW_NOTFOUND); + else + i = gen_expand_wildcards(ga.ga_len, (char_u **)ga.ga_data, + fcountp, fnamesp, EW_FILE|EW_NOTFOUND); + + ga_clear(&ga); + return i; +} + + +/* + * "what" == AL_SET: Redefine the argument list to 'str'. + * "what" == AL_ADD: add files in 'str' to the argument list after "after". + * "what" == AL_DEL: remove files in 'str' from the argument list. + * + * Return FAIL for failure, OK otherwise. + */ +static int do_arglist(str, what, after) +char_u *str; +int what UNUSED; +int after UNUSED; /* 0 means before first one */ +{ + garray_T new_ga; + int exp_count; + char_u **exp_files; + int i; + char_u *p; + int match; + + /* + * Collect all file name arguments in "new_ga". + */ + if (get_arglist(&new_ga, str) == FAIL) + return FAIL; + + if (what == AL_DEL) { + regmatch_T regmatch; + int didone; + + /* + * Delete the items: use each item as a regexp and find a match in the + * argument list. + */ + regmatch.rm_ic = p_fic; /* ignore case when 'fileignorecase' is set */ + for (i = 0; i < new_ga.ga_len && !got_int; ++i) { + p = ((char_u **)new_ga.ga_data)[i]; + p = file_pat_to_reg_pat(p, NULL, NULL, FALSE); + if (p == NULL) + break; + regmatch.regprog = vim_regcomp(p, p_magic ? RE_MAGIC : 0); + if (regmatch.regprog == NULL) { + vim_free(p); + break; + } + + didone = FALSE; + for (match = 0; match < ARGCOUNT; ++match) + if (vim_regexec(®match, alist_name(&ARGLIST[match]), + (colnr_T)0)) { + didone = TRUE; + vim_free(ARGLIST[match].ae_fname); + mch_memmove(ARGLIST + match, ARGLIST + match + 1, + (ARGCOUNT - match - 1) * sizeof(aentry_T)); + --ALIST(curwin)->al_ga.ga_len; + if (curwin->w_arg_idx > match) + --curwin->w_arg_idx; + --match; + } + + vim_regfree(regmatch.regprog); + vim_free(p); + if (!didone) + EMSG2(_(e_nomatch2), ((char_u **)new_ga.ga_data)[i]); + } + ga_clear(&new_ga); + } else { + i = expand_wildcards(new_ga.ga_len, (char_u **)new_ga.ga_data, + &exp_count, &exp_files, EW_DIR|EW_FILE|EW_ADDSLASH|EW_NOTFOUND); + ga_clear(&new_ga); + if (i == FAIL) + return FAIL; + if (exp_count == 0) { + EMSG(_(e_nomatch)); + return FAIL; + } + + if (what == AL_ADD) { + (void)alist_add_list(exp_count, exp_files, after); + vim_free(exp_files); + } else /* what == AL_SET */ + alist_set(ALIST(curwin), exp_count, exp_files, FALSE, NULL, 0); + } + + alist_check_arg_idx(); + + return OK; +} + +/* + * Check the validity of the arg_idx for each other window. + */ +static void alist_check_arg_idx() { + win_T *win; + tabpage_T *tp; + + FOR_ALL_TAB_WINDOWS(tp, win) + if (win->w_alist == curwin->w_alist) + check_arg_idx(win); +} + +/* + * Return TRUE if window "win" is editing the file at the current argument + * index. + */ +static int editing_arg_idx(win) +win_T *win; +{ + return !(win->w_arg_idx >= WARGCOUNT(win) + || (win->w_buffer->b_fnum + != WARGLIST(win)[win->w_arg_idx].ae_fnum + && (win->w_buffer->b_ffname == NULL + || !(fullpathcmp( + alist_name(&WARGLIST(win)[win->w_arg_idx]), + win->w_buffer->b_ffname, TRUE) & FPC_SAME)))); +} + +/* + * Check if window "win" is editing the w_arg_idx file in its argument list. + */ +void check_arg_idx(win) +win_T *win; +{ + if (WARGCOUNT(win) > 1 && !editing_arg_idx(win)) { + /* We are not editing the current entry in the argument list. + * Set "arg_had_last" if we are editing the last one. */ + win->w_arg_idx_invalid = TRUE; + if (win->w_arg_idx != WARGCOUNT(win) - 1 + && arg_had_last == FALSE + && ALIST(win) == &global_alist + && GARGCOUNT > 0 + && win->w_arg_idx < GARGCOUNT + && (win->w_buffer->b_fnum == GARGLIST[GARGCOUNT - 1].ae_fnum + || (win->w_buffer->b_ffname != NULL + && (fullpathcmp(alist_name(&GARGLIST[GARGCOUNT - 1]), + win->w_buffer->b_ffname, TRUE) & FPC_SAME)))) + arg_had_last = TRUE; + } else { + /* We are editing the current entry in the argument list. + * Set "arg_had_last" if it's also the last one */ + win->w_arg_idx_invalid = FALSE; + if (win->w_arg_idx == WARGCOUNT(win) - 1 + && win->w_alist == &global_alist + ) + arg_had_last = TRUE; + } +} + +/* + * ":args", ":argslocal" and ":argsglobal". + */ +void ex_args(eap) +exarg_T *eap; +{ + int i; + + if (eap->cmdidx != CMD_args) { + alist_unlink(ALIST(curwin)); + if (eap->cmdidx == CMD_argglobal) + ALIST(curwin) = &global_alist; + else /* eap->cmdidx == CMD_arglocal */ + alist_new(); + } + + if (!ends_excmd(*eap->arg)) { + /* + * ":args file ..": define new argument list, handle like ":next" + * Also for ":argslocal file .." and ":argsglobal file ..". + */ + ex_next(eap); + } else if (eap->cmdidx == CMD_args) { + /* + * ":args": list arguments. + */ + if (ARGCOUNT > 0) { + /* Overwrite the command, for a short list there is no scrolling + * required and no wait_return(). */ + gotocmdline(TRUE); + for (i = 0; i < ARGCOUNT; ++i) { + if (i == curwin->w_arg_idx) + msg_putchar('['); + msg_outtrans(alist_name(&ARGLIST[i])); + if (i == curwin->w_arg_idx) + msg_putchar(']'); + msg_putchar(' '); + } + } + } else if (eap->cmdidx == CMD_arglocal) { + garray_T *gap = &curwin->w_alist->al_ga; + + /* + * ":argslocal": make a local copy of the global argument list. + */ + if (ga_grow(gap, GARGCOUNT) == OK) + for (i = 0; i < GARGCOUNT; ++i) + if (GARGLIST[i].ae_fname != NULL) { + AARGLIST(curwin->w_alist)[gap->ga_len].ae_fname = + vim_strsave(GARGLIST[i].ae_fname); + AARGLIST(curwin->w_alist)[gap->ga_len].ae_fnum = + GARGLIST[i].ae_fnum; + ++gap->ga_len; + } + } +} + +/* + * ":previous", ":sprevious", ":Next" and ":sNext". + */ +void ex_previous(eap) +exarg_T *eap; +{ + /* If past the last one already, go to the last one. */ + if (curwin->w_arg_idx - (int)eap->line2 >= ARGCOUNT) + do_argfile(eap, ARGCOUNT - 1); + else + do_argfile(eap, curwin->w_arg_idx - (int)eap->line2); +} + +/* + * ":rewind", ":first", ":sfirst" and ":srewind". + */ +void ex_rewind(eap) +exarg_T *eap; +{ + do_argfile(eap, 0); +} + +/* + * ":last" and ":slast". + */ +void ex_last(eap) +exarg_T *eap; +{ + do_argfile(eap, ARGCOUNT - 1); +} + +/* + * ":argument" and ":sargument". + */ +void ex_argument(eap) +exarg_T *eap; +{ + int i; + + if (eap->addr_count > 0) + i = eap->line2 - 1; + else + i = curwin->w_arg_idx; + do_argfile(eap, i); +} + +/* + * Edit file "argn" of the argument lists. + */ +void do_argfile(eap, argn) +exarg_T *eap; +int argn; +{ + int other; + char_u *p; + int old_arg_idx = curwin->w_arg_idx; + + if (argn < 0 || argn >= ARGCOUNT) { + if (ARGCOUNT <= 1) + EMSG(_("E163: There is only one file to edit")); + else if (argn < 0) + EMSG(_("E164: Cannot go before first file")); + else + EMSG(_("E165: Cannot go beyond last file")); + } else { + setpcmark(); + + /* split window or create new tab page first */ + if (*eap->cmd == 's' || cmdmod.tab != 0) { + if (win_split(0, 0) == FAIL) + return; + RESET_BINDING(curwin); + } else { + /* + * if 'hidden' set, only check for changed file when re-editing + * the same buffer + */ + other = TRUE; + if (P_HID(curbuf)) { + p = fix_fname(alist_name(&ARGLIST[argn])); + other = otherfile(p); + vim_free(p); + } + if ((!P_HID(curbuf) || !other) + && check_changed(curbuf, CCGD_AW + | (other ? 0 : CCGD_MULTWIN) + | (eap->forceit ? CCGD_FORCEIT : 0) + | CCGD_EXCMD)) + return; + } + + curwin->w_arg_idx = argn; + if (argn == ARGCOUNT - 1 + && curwin->w_alist == &global_alist + ) + arg_had_last = TRUE; + + /* Edit the file; always use the last known line number. + * When it fails (e.g. Abort for already edited file) restore the + * argument index. */ + if (do_ecmd(0, alist_name(&ARGLIST[curwin->w_arg_idx]), NULL, + eap, ECMD_LAST, + (P_HID(curwin->w_buffer) ? ECMD_HIDE : 0) + + (eap->forceit ? ECMD_FORCEIT : 0), curwin) == FAIL) + curwin->w_arg_idx = old_arg_idx; + /* like Vi: set the mark where the cursor is in the file. */ + else if (eap->cmdidx != CMD_argdo) + setmark('\''); + } +} + +/* + * ":next", and commands that behave like it. + */ +void ex_next(eap) +exarg_T *eap; +{ + int i; + + /* + * check for changed buffer now, if this fails the argument list is not + * redefined. + */ + if ( P_HID(curbuf) + || eap->cmdidx == CMD_snext + || !check_changed(curbuf, CCGD_AW + | (eap->forceit ? CCGD_FORCEIT : 0) + | CCGD_EXCMD)) { + if (*eap->arg != NUL) { /* redefine file list */ + if (do_arglist(eap->arg, AL_SET, 0) == FAIL) + return; + i = 0; + } else + i = curwin->w_arg_idx + (int)eap->line2; + do_argfile(eap, i); + } +} + +/* + * ":argedit" + */ +void ex_argedit(eap) +exarg_T *eap; +{ + int fnum; + int i; + char_u *s; + + /* Add the argument to the buffer list and get the buffer number. */ + fnum = buflist_add(eap->arg, BLN_LISTED); + + /* Check if this argument is already in the argument list. */ + for (i = 0; i < ARGCOUNT; ++i) + if (ARGLIST[i].ae_fnum == fnum) + break; + if (i == ARGCOUNT) { + /* Can't find it, add it to the argument list. */ + s = vim_strsave(eap->arg); + if (s == NULL) + return; + i = alist_add_list(1, &s, + eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1); + if (i < 0) + return; + curwin->w_arg_idx = i; + } + + alist_check_arg_idx(); + + /* Edit the argument. */ + do_argfile(eap, i); +} + +/* + * ":argadd" + */ +void ex_argadd(eap) +exarg_T *eap; +{ + do_arglist(eap->arg, AL_ADD, + eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1); + maketitle(); +} + +/* + * ":argdelete" + */ +void ex_argdelete(eap) +exarg_T *eap; +{ + int i; + int n; + + if (eap->addr_count > 0) { + /* ":1,4argdel": Delete all arguments in the range. */ + if (eap->line2 > ARGCOUNT) + eap->line2 = ARGCOUNT; + n = eap->line2 - eap->line1 + 1; + if (*eap->arg != NUL || n <= 0) + EMSG(_(e_invarg)); + else { + for (i = eap->line1; i <= eap->line2; ++i) + vim_free(ARGLIST[i - 1].ae_fname); + mch_memmove(ARGLIST + eap->line1 - 1, ARGLIST + eap->line2, + (size_t)((ARGCOUNT - eap->line2) * sizeof(aentry_T))); + ALIST(curwin)->al_ga.ga_len -= n; + if (curwin->w_arg_idx >= eap->line2) + curwin->w_arg_idx -= n; + else if (curwin->w_arg_idx > eap->line1) + curwin->w_arg_idx = eap->line1; + } + } else if (*eap->arg == NUL) + EMSG(_(e_argreq)); + else + do_arglist(eap->arg, AL_DEL, 0); + maketitle(); +} + +/* + * ":argdo", ":windo", ":bufdo", ":tabdo" + */ +void ex_listdo(eap) +exarg_T *eap; +{ + int i; + win_T *wp; + tabpage_T *tp; + buf_T *buf; + int next_fnum = 0; + char_u *save_ei = NULL; + char_u *p_shm_save; + + + if (eap->cmdidx != CMD_windo && eap->cmdidx != CMD_tabdo) + /* Don't do syntax HL autocommands. Skipping the syntax file is a + * great speed improvement. */ + save_ei = au_event_disable(",Syntax"); + + if (eap->cmdidx == CMD_windo + || eap->cmdidx == CMD_tabdo + || P_HID(curbuf) + || !check_changed(curbuf, CCGD_AW + | (eap->forceit ? CCGD_FORCEIT : 0) + | CCGD_EXCMD)) { + /* start at the first argument/window/buffer */ + i = 0; + wp = firstwin; + tp = first_tabpage; + /* set pcmark now */ + if (eap->cmdidx == CMD_bufdo) + goto_buffer(eap, DOBUF_FIRST, FORWARD, 0); + else + setpcmark(); + listcmd_busy = TRUE; /* avoids setting pcmark below */ + + while (!got_int) { + if (eap->cmdidx == CMD_argdo) { + /* go to argument "i" */ + if (i == ARGCOUNT) + break; + /* Don't call do_argfile() when already there, it will try + * reloading the file. */ + if (curwin->w_arg_idx != i || !editing_arg_idx(curwin)) { + /* Clear 'shm' to avoid that the file message overwrites + * any output from the command. */ + p_shm_save = vim_strsave(p_shm); + set_option_value((char_u *)"shm", 0L, (char_u *)"", 0); + do_argfile(eap, i); + set_option_value((char_u *)"shm", 0L, p_shm_save, 0); + vim_free(p_shm_save); + } + if (curwin->w_arg_idx != i) + break; + ++i; + } else if (eap->cmdidx == CMD_windo) { + /* go to window "wp" */ + if (!win_valid(wp)) + break; + win_goto(wp); + if (curwin != wp) + break; /* something must be wrong */ + wp = curwin->w_next; + } else if (eap->cmdidx == CMD_tabdo) { + /* go to window "tp" */ + if (!valid_tabpage(tp)) + break; + goto_tabpage_tp(tp, TRUE, TRUE); + tp = tp->tp_next; + } else if (eap->cmdidx == CMD_bufdo) { + /* Remember the number of the next listed buffer, in case + * ":bwipe" is used or autocommands do something strange. */ + next_fnum = -1; + for (buf = curbuf->b_next; buf != NULL; buf = buf->b_next) + if (buf->b_p_bl) { + next_fnum = buf->b_fnum; + break; + } + } + + /* execute the command */ + do_cmdline(eap->arg, eap->getline, eap->cookie, + DOCMD_VERBOSE + DOCMD_NOWAIT); + + if (eap->cmdidx == CMD_bufdo) { + /* Done? */ + if (next_fnum < 0) + break; + /* Check if the buffer still exists. */ + for (buf = firstbuf; buf != NULL; buf = buf->b_next) + if (buf->b_fnum == next_fnum) + break; + if (buf == NULL) + break; + + /* Go to the next buffer. Clear 'shm' to avoid that the file + * message overwrites any output from the command. */ + p_shm_save = vim_strsave(p_shm); + set_option_value((char_u *)"shm", 0L, (char_u *)"", 0); + goto_buffer(eap, DOBUF_FIRST, FORWARD, next_fnum); + set_option_value((char_u *)"shm", 0L, p_shm_save, 0); + vim_free(p_shm_save); + + /* If autocommands took us elsewhere, quit here */ + if (curbuf->b_fnum != next_fnum) + break; + } + + if (eap->cmdidx == CMD_windo) { + validate_cursor(); /* cursor may have moved */ + /* required when 'scrollbind' has been set */ + if (curwin->w_p_scb) + do_check_scrollbind(TRUE); + } + } + listcmd_busy = FALSE; + } + + if (save_ei != NULL) { + au_event_restore(save_ei); + apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, + curbuf->b_fname, TRUE, curbuf); + } +} + +/* + * Add files[count] to the arglist of the current window after arg "after". + * The file names in files[count] must have been allocated and are taken over. + * Files[] itself is not taken over. + * Returns index of first added argument. Returns -1 when failed (out of mem). + */ +static int alist_add_list(count, files, after) +int count; +char_u **files; +int after; /* where to add: 0 = before first one */ +{ + int i; + + if (ga_grow(&ALIST(curwin)->al_ga, count) == OK) { + if (after < 0) + after = 0; + if (after > ARGCOUNT) + after = ARGCOUNT; + if (after < ARGCOUNT) + mch_memmove(&(ARGLIST[after + count]), &(ARGLIST[after]), + (ARGCOUNT - after) * sizeof(aentry_T)); + for (i = 0; i < count; ++i) { + ARGLIST[after + i].ae_fname = files[i]; + ARGLIST[after + i].ae_fnum = buflist_add(files[i], BLN_LISTED); + } + ALIST(curwin)->al_ga.ga_len += count; + if (curwin->w_arg_idx >= after) + ++curwin->w_arg_idx; + return after; + } + + for (i = 0; i < count; ++i) + vim_free(files[i]); + return -1; +} + + +/* + * ":compiler[!] {name}" + */ +void ex_compiler(eap) +exarg_T *eap; +{ + char_u *buf; + char_u *old_cur_comp = NULL; + char_u *p; + + if (*eap->arg == NUL) { + /* List all compiler scripts. */ + do_cmdline_cmd((char_u *)"echo globpath(&rtp, 'compiler/*.vim')"); + /* ) keep the indenter happy... */ + } else { + buf = alloc((unsigned)(STRLEN(eap->arg) + 14)); + if (buf != NULL) { + if (eap->forceit) { + /* ":compiler! {name}" sets global options */ + do_cmdline_cmd((char_u *) + "command -nargs=* CompilerSet set "); + } else { + /* ":compiler! {name}" sets local options. + * To remain backwards compatible "current_compiler" is always + * used. A user's compiler plugin may set it, the distributed + * plugin will then skip the settings. Afterwards set + * "b:current_compiler" and restore "current_compiler". + * Explicitly prepend "g:" to make it work in a function. */ + old_cur_comp = get_var_value((char_u *)"g:current_compiler"); + if (old_cur_comp != NULL) + old_cur_comp = vim_strsave(old_cur_comp); + do_cmdline_cmd((char_u *) + "command -nargs=* CompilerSet setlocal "); + } + do_unlet((char_u *)"g:current_compiler", TRUE); + do_unlet((char_u *)"b:current_compiler", TRUE); + + sprintf((char *)buf, "compiler/%s.vim", eap->arg); + if (source_runtime(buf, TRUE) == FAIL) + EMSG2(_("E666: compiler not supported: %s"), eap->arg); + vim_free(buf); + + do_cmdline_cmd((char_u *)":delcommand CompilerSet"); + + /* Set "b:current_compiler" from "current_compiler". */ + p = get_var_value((char_u *)"g:current_compiler"); + if (p != NULL) + set_internal_string_var((char_u *)"b:current_compiler", p); + + /* Restore "current_compiler" for ":compiler {name}". */ + if (!eap->forceit) { + if (old_cur_comp != NULL) { + set_internal_string_var((char_u *)"g:current_compiler", + old_cur_comp); + vim_free(old_cur_comp); + } else + do_unlet((char_u *)"g:current_compiler", TRUE); + } + } + } +} + +/* + * ":runtime {name}" + */ +void ex_runtime(eap) +exarg_T *eap; +{ + source_runtime(eap->arg, eap->forceit); +} + +static void source_callback __ARGS((char_u *fname, void *cookie)); + +static void source_callback(fname, cookie) +char_u *fname; +void *cookie UNUSED; +{ + (void)do_source(fname, FALSE, DOSO_NONE); +} + +/* + * Source the file "name" from all directories in 'runtimepath'. + * "name" can contain wildcards. + * When "all" is TRUE, source all files, otherwise only the first one. + * return FAIL when no file could be sourced, OK otherwise. + */ +int source_runtime(name, all) +char_u *name; +int all; +{ + return do_in_runtimepath(name, all, source_callback, NULL); +} + +/* + * Find "name" in 'runtimepath'. When found, invoke the callback function for + * it: callback(fname, "cookie") + * When "all" is TRUE repeat for all matches, otherwise only the first one is + * used. + * Returns OK when at least one match found, FAIL otherwise. + * + * If "name" is NULL calls callback for each entry in runtimepath. Cookie is + * passed by reference in this case, setting it to NULL indicates that callback + * has done its job. + */ +int do_in_runtimepath(name, all, callback, cookie) +char_u *name; +int all; +void (*callback)__ARGS((char_u *fname, void *ck)); +void *cookie; +{ + char_u *rtp; + char_u *np; + char_u *buf; + char_u *rtp_copy; + char_u *tail; + int num_files; + char_u **files; + int i; + int did_one = FALSE; + + /* Make a copy of 'runtimepath'. Invoking the callback may change the + * value. */ + rtp_copy = vim_strsave(p_rtp); + buf = alloc(MAXPATHL); + if (buf != NULL && rtp_copy != NULL) { + if (p_verbose > 1 && name != NULL) { + verbose_enter(); + smsg((char_u *)_("Searching for \"%s\" in \"%s\""), + (char *)name, (char *)p_rtp); + verbose_leave(); + } + + /* Loop over all entries in 'runtimepath'. */ + rtp = rtp_copy; + while (*rtp != NUL && (all || !did_one)) { + /* Copy the path from 'runtimepath' to buf[]. */ + copy_option_part(&rtp, buf, MAXPATHL, ","); + if (name == NULL) { + (*callback)(buf, (void *) &cookie); + if (!did_one) + did_one = (cookie == NULL); + } else if (STRLEN(buf) + STRLEN(name) + 2 < MAXPATHL) { + add_pathsep(buf); + tail = buf + STRLEN(buf); + + /* Loop over all patterns in "name" */ + np = name; + while (*np != NUL && (all || !did_one)) { + /* Append the pattern from "name" to buf[]. */ + copy_option_part(&np, tail, (int)(MAXPATHL - (tail - buf)), + "\t "); + + if (p_verbose > 2) { + verbose_enter(); + smsg((char_u *)_("Searching for \"%s\""), buf); + verbose_leave(); + } + + /* Expand wildcards, invoke the callback for each match. */ + if (gen_expand_wildcards(1, &buf, &num_files, &files, + EW_FILE) == OK) { + for (i = 0; i < num_files; ++i) { + (*callback)(files[i], cookie); + did_one = TRUE; + if (!all) + break; + } + FreeWild(num_files, files); + } + } + } + } + } + vim_free(buf); + vim_free(rtp_copy); + if (p_verbose > 0 && !did_one && name != NULL) { + verbose_enter(); + smsg((char_u *)_("not found in 'runtimepath': \"%s\""), name); + verbose_leave(); + } + + + return did_one ? OK : FAIL; +} + +/* + * ":options" + */ +void ex_options(eap) +exarg_T *eap UNUSED; +{ + cmd_source((char_u *)SYS_OPTWIN_FILE, NULL); +} + +/* + * ":source {fname}" + */ +void ex_source(eap) +exarg_T *eap; +{ + cmd_source(eap->arg, eap); +} + +static void cmd_source(fname, eap) +char_u *fname; +exarg_T *eap; +{ + if (*fname == NUL) + EMSG(_(e_argreq)); + + else if (eap != NULL && eap->forceit) + /* ":source!": read Normal mode commands + * Need to execute the commands directly. This is required at least + * for: + * - ":g" command busy + * - after ":argdo", ":windo" or ":bufdo" + * - another command follows + * - inside a loop + */ + openscript(fname, global_busy || listcmd_busy || eap->nextcmd != NULL + || eap->cstack->cs_idx >= 0 + ); + + /* ":source" read ex commands */ + else if (do_source(fname, FALSE, DOSO_NONE) == FAIL) + EMSG2(_(e_notopen), fname); +} + +/* + * ":source" and associated commands. + */ +/* + * Structure used to store info for each sourced file. + * It is shared between do_source() and getsourceline(). + * This is required, because it needs to be handed to do_cmdline() and + * sourcing can be done recursively. + */ +struct source_cookie { + FILE *fp; /* opened file for sourcing */ + char_u *nextline; /* if not NULL: line that was read ahead */ + int finished; /* ":finish" used */ +#if defined(USE_CRNL) || defined(USE_CR) + int fileformat; /* EOL_UNKNOWN, EOL_UNIX or EOL_DOS */ + int error; /* TRUE if LF found after CR-LF */ +#endif + linenr_T breakpoint; /* next line with breakpoint or zero */ + char_u *fname; /* name of sourced file */ + int dbg_tick; /* debug_tick when breakpoint was set */ + int level; /* top nesting level of sourced file */ + vimconv_T conv; /* type of conversion */ +}; + +/* + * Return the address holding the next breakpoint line for a source cookie. + */ +linenr_T * source_breakpoint(cookie) +void *cookie; +{ + return &((struct source_cookie *)cookie)->breakpoint; +} + +/* + * Return the address holding the debug tick for a source cookie. + */ +int * source_dbg_tick(cookie) +void *cookie; +{ + return &((struct source_cookie *)cookie)->dbg_tick; +} + +/* + * Return the nesting level for a source cookie. + */ +int source_level(cookie) +void *cookie; +{ + return ((struct source_cookie *)cookie)->level; +} + +static char_u *get_one_sourceline __ARGS((struct source_cookie *sp)); + +#if (defined(WIN32) && defined(FEAT_CSCOPE)) || defined(HAVE_FD_CLOEXEC) +# define USE_FOPEN_NOINH +static FILE *fopen_noinh_readbin __ARGS((char *filename)); + +/* + * Special function to open a file without handle inheritance. + * When possible the handle is closed on exec(). + */ +static FILE * fopen_noinh_readbin(filename) +char *filename; +{ + int fd_tmp = mch_open(filename, O_RDONLY, 0); + + if (fd_tmp == -1) + return NULL; + +# ifdef HAVE_FD_CLOEXEC + { + int fdflags = fcntl(fd_tmp, F_GETFD); + if (fdflags >= 0 && (fdflags & FD_CLOEXEC) == 0) + fcntl(fd_tmp, F_SETFD, fdflags | FD_CLOEXEC); + } +# endif + + return fdopen(fd_tmp, READBIN); +} +#endif + + +/* + * do_source: Read the file "fname" and execute its lines as EX commands. + * + * This function may be called recursively! + * + * return FAIL if file could not be opened, OK otherwise + */ +int do_source(fname, check_other, is_vimrc) +char_u *fname; +int check_other; /* check for .vimrc and _vimrc */ +int is_vimrc; /* DOSO_ value */ +{ + struct source_cookie cookie; + char_u *save_sourcing_name; + linenr_T save_sourcing_lnum; + char_u *p; + char_u *fname_exp; + char_u *firstline = NULL; + int retval = FAIL; + scid_T save_current_SID; + static scid_T last_current_SID = 0; + void *save_funccalp; + int save_debug_break_level = debug_break_level; + scriptitem_T *si = NULL; +# ifdef UNIX + struct stat st; + int stat_ok; +# endif +#ifdef STARTUPTIME + struct timeval tv_rel; + struct timeval tv_start; +#endif + proftime_T wait_start; + + p = expand_env_save(fname); + if (p == NULL) + return retval; + fname_exp = fix_fname(p); + vim_free(p); + if (fname_exp == NULL) + return retval; + if (mch_isdir(fname_exp)) { + smsg((char_u *)_("Cannot source a directory: \"%s\""), fname); + goto theend; + } + + /* Apply SourceCmd autocommands, they should get the file and source it. */ + if (has_autocmd(EVENT_SOURCECMD, fname_exp, NULL) + && apply_autocmds(EVENT_SOURCECMD, fname_exp, fname_exp, + FALSE, curbuf)) { + retval = aborting() ? FAIL : OK; + goto theend; + } + + /* Apply SourcePre autocommands, they may get the file. */ + apply_autocmds(EVENT_SOURCEPRE, fname_exp, fname_exp, FALSE, curbuf); + +#ifdef USE_FOPEN_NOINH + cookie.fp = fopen_noinh_readbin((char *)fname_exp); +#else + cookie.fp = mch_fopen((char *)fname_exp, READBIN); +#endif + if (cookie.fp == NULL && check_other) { + /* + * Try again, replacing file name ".vimrc" by "_vimrc" or vice versa, + * and ".exrc" by "_exrc" or vice versa. + */ + p = gettail(fname_exp); + if ((*p == '.' || *p == '_') + && (STRICMP(p + 1, "vimrc") == 0 + || STRICMP(p + 1, "gvimrc") == 0 + || STRICMP(p + 1, "exrc") == 0)) { + if (*p == '_') + *p = '.'; + else + *p = '_'; +#ifdef USE_FOPEN_NOINH + cookie.fp = fopen_noinh_readbin((char *)fname_exp); +#else + cookie.fp = mch_fopen((char *)fname_exp, READBIN); +#endif + } + } + + if (cookie.fp == NULL) { + if (p_verbose > 0) { + verbose_enter(); + if (sourcing_name == NULL) + smsg((char_u *)_("could not source \"%s\""), fname); + else + smsg((char_u *)_("line %ld: could not source \"%s\""), + sourcing_lnum, fname); + verbose_leave(); + } + goto theend; + } + + /* + * The file exists. + * - In verbose mode, give a message. + * - For a vimrc file, may want to set 'compatible', call vimrc_found(). + */ + if (p_verbose > 1) { + verbose_enter(); + if (sourcing_name == NULL) + smsg((char_u *)_("sourcing \"%s\""), fname); + else + smsg((char_u *)_("line %ld: sourcing \"%s\""), + sourcing_lnum, fname); + verbose_leave(); + } + if (is_vimrc == DOSO_VIMRC) + vimrc_found(fname_exp, (char_u *)"MYVIMRC"); + else if (is_vimrc == DOSO_GVIMRC) + vimrc_found(fname_exp, (char_u *)"MYGVIMRC"); + +#ifdef USE_CRNL + /* If no automatic file format: Set default to CR-NL. */ + if (*p_ffs == NUL) + cookie.fileformat = EOL_DOS; + else + cookie.fileformat = EOL_UNKNOWN; + cookie.error = FALSE; +#endif + +#ifdef USE_CR + /* If no automatic file format: Set default to CR. */ + if (*p_ffs == NUL) + cookie.fileformat = EOL_MAC; + else + cookie.fileformat = EOL_UNKNOWN; + cookie.error = FALSE; +#endif + + cookie.nextline = NULL; + cookie.finished = FALSE; + + /* + * Check if this script has a breakpoint. + */ + cookie.breakpoint = dbg_find_breakpoint(TRUE, fname_exp, (linenr_T)0); + cookie.fname = fname_exp; + cookie.dbg_tick = debug_tick; + + cookie.level = ex_nesting_level; + + /* + * Keep the sourcing name/lnum, for recursive calls. + */ + save_sourcing_name = sourcing_name; + sourcing_name = fname_exp; + save_sourcing_lnum = sourcing_lnum; + sourcing_lnum = 0; + + cookie.conv.vc_type = CONV_NONE; /* no conversion */ + + /* Read the first line so we can check for a UTF-8 BOM. */ + firstline = getsourceline(0, (void *)&cookie, 0); + if (firstline != NULL && STRLEN(firstline) >= 3 && firstline[0] == 0xef + && firstline[1] == 0xbb && firstline[2] == 0xbf) { + /* Found BOM; setup conversion, skip over BOM and recode the line. */ + convert_setup(&cookie.conv, (char_u *)"utf-8", p_enc); + p = string_convert(&cookie.conv, firstline + 3, NULL); + if (p == NULL) + p = vim_strsave(firstline + 3); + if (p != NULL) { + vim_free(firstline); + firstline = p; + } + } + +#ifdef STARTUPTIME + if (time_fd != NULL) + time_push(&tv_rel, &tv_start); +#endif + + if (do_profiling == PROF_YES) + prof_child_enter(&wait_start); /* entering a child now */ + + /* Don't use local function variables, if called from a function. + * Also starts profiling timer for nested script. */ + save_funccalp = save_funccal(); + + /* + * Check if this script was sourced before to finds its SID. + * If it's new, generate a new SID. + */ + save_current_SID = current_SID; +# ifdef UNIX + stat_ok = (mch_stat((char *)fname_exp, &st) >= 0); +# endif + for (current_SID = script_items.ga_len; current_SID > 0; --current_SID) { + si = &SCRIPT_ITEM(current_SID); + if (si->sn_name != NULL + && ( +# ifdef UNIX + /* Compare dev/ino when possible, it catches symbolic + * links. Also compare file names, the inode may change + * when the file was edited. */ + ((stat_ok && si->sn_dev_valid) + && (si->sn_dev == st.st_dev + && si->sn_ino == st.st_ino)) || +# endif + fnamecmp(si->sn_name, fname_exp) == 0)) + break; + } + if (current_SID == 0) { + current_SID = ++last_current_SID; + if (ga_grow(&script_items, (int)(current_SID - script_items.ga_len)) + == FAIL) + goto almosttheend; + while (script_items.ga_len < current_SID) { + ++script_items.ga_len; + SCRIPT_ITEM(script_items.ga_len).sn_name = NULL; + SCRIPT_ITEM(script_items.ga_len).sn_prof_on = FALSE; + } + si = &SCRIPT_ITEM(current_SID); + si->sn_name = fname_exp; + fname_exp = NULL; +# ifdef UNIX + if (stat_ok) { + si->sn_dev_valid = TRUE; + si->sn_dev = st.st_dev; + si->sn_ino = st.st_ino; + } else + si->sn_dev_valid = FALSE; +# endif + + /* Allocate the local script variables to use for this script. */ + new_script_vars(current_SID); + } + + if (do_profiling == PROF_YES) { + int forceit; + + /* Check if we do profiling for this script. */ + if (!si->sn_prof_on && has_profiling(TRUE, si->sn_name, &forceit)) { + script_do_profile(si); + si->sn_pr_force = forceit; + } + if (si->sn_prof_on) { + ++si->sn_pr_count; + profile_start(&si->sn_pr_start); + profile_zero(&si->sn_pr_children); + } + } + + /* + * Call do_cmdline, which will call getsourceline() to get the lines. + */ + do_cmdline(firstline, getsourceline, (void *)&cookie, + DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_REPEAT); + retval = OK; + + if (do_profiling == PROF_YES) { + /* Get "si" again, "script_items" may have been reallocated. */ + si = &SCRIPT_ITEM(current_SID); + if (si->sn_prof_on) { + profile_end(&si->sn_pr_start); + profile_sub_wait(&wait_start, &si->sn_pr_start); + profile_add(&si->sn_pr_total, &si->sn_pr_start); + profile_self(&si->sn_pr_self, &si->sn_pr_start, + &si->sn_pr_children); + } + } + + if (got_int) + EMSG(_(e_interr)); + sourcing_name = save_sourcing_name; + sourcing_lnum = save_sourcing_lnum; + if (p_verbose > 1) { + verbose_enter(); + smsg((char_u *)_("finished sourcing %s"), fname); + if (sourcing_name != NULL) + smsg((char_u *)_("continuing in %s"), sourcing_name); + verbose_leave(); + } +#ifdef STARTUPTIME + if (time_fd != NULL) { + vim_snprintf((char *)IObuff, IOSIZE, "sourcing %s", fname); + time_msg((char *)IObuff, &tv_start); + time_pop(&tv_rel); + } +#endif + + /* + * After a "finish" in debug mode, need to break at first command of next + * sourced file. + */ + if (save_debug_break_level > ex_nesting_level + && debug_break_level == ex_nesting_level) + ++debug_break_level; + +almosttheend: + current_SID = save_current_SID; + restore_funccal(save_funccalp); + if (do_profiling == PROF_YES) + prof_child_exit(&wait_start); /* leaving a child now */ + fclose(cookie.fp); + vim_free(cookie.nextline); + vim_free(firstline); + convert_setup(&cookie.conv, NULL, NULL); + +theend: + vim_free(fname_exp); + return retval; +} + + +/* + * ":scriptnames" + */ +void ex_scriptnames(eap) +exarg_T *eap UNUSED; +{ + int i; + + for (i = 1; i <= script_items.ga_len && !got_int; ++i) + if (SCRIPT_ITEM(i).sn_name != NULL) { + home_replace(NULL, SCRIPT_ITEM(i).sn_name, + NameBuff, MAXPATHL, TRUE); + smsg((char_u *)"%3d: %s", i, NameBuff); + } +} + +# if defined(BACKSLASH_IN_FILENAME) || defined(PROTO) +/* + * Fix slashes in the list of script names for 'shellslash'. + */ +void scriptnames_slash_adjust() { + int i; + + for (i = 1; i <= script_items.ga_len; ++i) + if (SCRIPT_ITEM(i).sn_name != NULL) + slash_adjust(SCRIPT_ITEM(i).sn_name); +} + +# endif + +/* + * Get a pointer to a script name. Used for ":verbose set". + */ +char_u * get_scriptname(id) +scid_T id; +{ + if (id == SID_MODELINE) + return (char_u *)_("modeline"); + if (id == SID_CMDARG) + return (char_u *)_("--cmd argument"); + if (id == SID_CARG) + return (char_u *)_("-c argument"); + if (id == SID_ENV) + return (char_u *)_("environment variable"); + if (id == SID_ERROR) + return (char_u *)_("error handler"); + return SCRIPT_ITEM(id).sn_name; +} + +# if defined(EXITFREE) || defined(PROTO) +void free_scriptnames() { + int i; + + for (i = script_items.ga_len; i > 0; --i) + vim_free(SCRIPT_ITEM(i).sn_name); + ga_clear(&script_items); +} + +# endif + + +#if defined(USE_CR) || defined(PROTO) + +# if defined(__MSL__) && (__MSL__ >= 22) +/* + * Newer version of the Metrowerks library handle DOS and UNIX files + * without help. + * Test with earlier versions, MSL 2.2 is the library supplied with + * Codewarrior Pro 2. + */ +char * fgets_cr(s, n, stream) +char *s; +int n; +FILE *stream; +{ + return fgets(s, n, stream); +} +# else +/* + * Version of fgets() which also works for lines ending in a only + * (Macintosh format). + * For older versions of the Metrowerks library. + * At least CodeWarrior 9 needed this code. + */ +char * fgets_cr(s, n, stream) +char *s; +int n; +FILE *stream; +{ + int c = 0; + int char_read = 0; + + while (!feof(stream) && c != '\r' && c != '\n' && char_read < n - 1) { + c = fgetc(stream); + s[char_read++] = c; + /* If the file is in DOS format, we need to skip a NL after a CR. I + * thought it was the other way around, but this appears to work... */ + if (c == '\n') { + c = fgetc(stream); + if (c != '\r') + ungetc(c, stream); + } + } + + s[char_read] = 0; + if (char_read == 0) + return NULL; + + if (feof(stream) && char_read == 1) + return NULL; + + return s; +} +# endif +#endif + +/* + * Get one full line from a sourced file. + * Called by do_cmdline() when it's called from do_source(). + * + * Return a pointer to the line in allocated memory. + * Return NULL for end-of-file or some error. + */ +char_u * getsourceline(c, cookie, indent) +int c UNUSED; +void *cookie; +int indent UNUSED; +{ + struct source_cookie *sp = (struct source_cookie *)cookie; + char_u *line; + char_u *p; + + /* If breakpoints have been added/deleted need to check for it. */ + if (sp->dbg_tick < debug_tick) { + sp->breakpoint = dbg_find_breakpoint(TRUE, sp->fname, sourcing_lnum); + sp->dbg_tick = debug_tick; + } + if (do_profiling == PROF_YES) + script_line_end(); + /* + * Get current line. If there is a read-ahead line, use it, otherwise get + * one now. + */ + if (sp->finished) + line = NULL; + else if (sp->nextline == NULL) + line = get_one_sourceline(sp); + else { + line = sp->nextline; + sp->nextline = NULL; + ++sourcing_lnum; + } + if (line != NULL && do_profiling == PROF_YES) + script_line_start(); + + /* Only concatenate lines starting with a \ when 'cpoptions' doesn't + * contain the 'C' flag. */ + if (line != NULL && (vim_strchr(p_cpo, CPO_CONCAT) == NULL)) { + /* compensate for the one line read-ahead */ + --sourcing_lnum; + + /* Get the next line and concatenate it when it starts with a + * backslash. We always need to read the next line, keep it in + * sp->nextline. */ + sp->nextline = get_one_sourceline(sp); + if (sp->nextline != NULL && *(p = skipwhite(sp->nextline)) == '\\') { + garray_T ga; + + ga_init2(&ga, (int)sizeof(char_u), 400); + ga_concat(&ga, line); + ga_concat(&ga, p + 1); + for (;; ) { + vim_free(sp->nextline); + sp->nextline = get_one_sourceline(sp); + if (sp->nextline == NULL) + break; + p = skipwhite(sp->nextline); + if (*p != '\\') + break; + /* Adjust the growsize to the current length to speed up + * concatenating many lines. */ + if (ga.ga_len > 400) { + if (ga.ga_len > 8000) + ga.ga_growsize = 8000; + else + ga.ga_growsize = ga.ga_len; + } + ga_concat(&ga, p + 1); + } + ga_append(&ga, NUL); + vim_free(line); + line = ga.ga_data; + } + } + + if (line != NULL && sp->conv.vc_type != CONV_NONE) { + char_u *s; + + /* Convert the encoding of the script line. */ + s = string_convert(&sp->conv, line, NULL); + if (s != NULL) { + vim_free(line); + line = s; + } + } + + /* Did we encounter a breakpoint? */ + if (sp->breakpoint != 0 && sp->breakpoint <= sourcing_lnum) { + dbg_breakpoint(sp->fname, sourcing_lnum); + /* Find next breakpoint. */ + sp->breakpoint = dbg_find_breakpoint(TRUE, sp->fname, sourcing_lnum); + sp->dbg_tick = debug_tick; + } + + return line; +} + +static char_u * get_one_sourceline(sp) +struct source_cookie *sp; +{ + garray_T ga; + int len; + int c; + char_u *buf; +#ifdef USE_CRNL + int has_cr; /* CR-LF found */ +#endif +#ifdef USE_CR + char_u *scan; +#endif + int have_read = FALSE; + + /* use a growarray to store the sourced line */ + ga_init2(&ga, 1, 250); + + /* + * Loop until there is a finished line (or end-of-file). + */ + sourcing_lnum++; + for (;; ) { + /* make room to read at least 120 (more) characters */ + if (ga_grow(&ga, 120) == FAIL) + break; + buf = (char_u *)ga.ga_data; + +#ifdef USE_CR + if (sp->fileformat == EOL_MAC) { + if (fgets_cr((char *)buf + ga.ga_len, ga.ga_maxlen - ga.ga_len, + sp->fp) == NULL) + break; + } else +#endif + if (fgets((char *)buf + ga.ga_len, ga.ga_maxlen - ga.ga_len, + sp->fp) == NULL) + break; + len = ga.ga_len + (int)STRLEN(buf + ga.ga_len); +#ifdef USE_CRNL + /* Ignore a trailing CTRL-Z, when in Dos mode. Only recognize the + * CTRL-Z by its own, or after a NL. */ + if ( (len == 1 || (len >= 2 && buf[len - 2] == '\n')) + && sp->fileformat == EOL_DOS + && buf[len - 1] == Ctrl_Z) { + buf[len - 1] = NUL; + break; + } +#endif + +#ifdef USE_CR + /* If the read doesn't stop on a new line, and there's + * some CR then we assume a Mac format */ + if (sp->fileformat == EOL_UNKNOWN) { + if (buf[len - 1] != '\n' && vim_strchr(buf, '\r') != NULL) + sp->fileformat = EOL_MAC; + else + sp->fileformat = EOL_UNIX; + } + + if (sp->fileformat == EOL_MAC) { + scan = vim_strchr(buf, '\r'); + + if (scan != NULL) { + *scan = '\n'; + if (*(scan + 1) != 0) { + *(scan + 1) = 0; + fseek(sp->fp, (long)(scan - buf - len + 1), SEEK_CUR); + } + } + len = STRLEN(buf); + } +#endif + + have_read = TRUE; + ga.ga_len = len; + + /* If the line was longer than the buffer, read more. */ + if (ga.ga_maxlen - ga.ga_len == 1 && buf[len - 1] != '\n') + continue; + + if (len >= 1 && buf[len - 1] == '\n') { /* remove trailing NL */ +#ifdef USE_CRNL + has_cr = (len >= 2 && buf[len - 2] == '\r'); + if (sp->fileformat == EOL_UNKNOWN) { + if (has_cr) + sp->fileformat = EOL_DOS; + else + sp->fileformat = EOL_UNIX; + } + + if (sp->fileformat == EOL_DOS) { + if (has_cr) { /* replace trailing CR */ + buf[len - 2] = '\n'; + --len; + --ga.ga_len; + } else { /* lines like ":map xx yy^M" will have failed */ + if (!sp->error) { + msg_source(hl_attr(HLF_W)); + EMSG(_("W15: Warning: Wrong line separator, ^M may be missing")); + } + sp->error = TRUE; + sp->fileformat = EOL_UNIX; + } + } +#endif + /* The '\n' is escaped if there is an odd number of ^V's just + * before it, first set "c" just before the 'V's and then check + * len&c parities (is faster than ((len-c)%2 == 0)) -- Acevedo */ + for (c = len - 2; c >= 0 && buf[c] == Ctrl_V; c--) + ; + if ((len & 1) != (c & 1)) { /* escaped NL, read more */ + sourcing_lnum++; + continue; + } + + buf[len - 1] = NUL; /* remove the NL */ + } + + /* + * Check for ^C here now and then, so recursive :so can be broken. + */ + line_breakcheck(); + break; + } + + if (have_read) + return (char_u *)ga.ga_data; + + vim_free(ga.ga_data); + return NULL; +} + +/* + * Called when starting to read a script line. + * "sourcing_lnum" must be correct! + * When skipping lines it may not actually be executed, but we won't find out + * until later and we need to store the time now. + */ +void script_line_start() { + scriptitem_T *si; + sn_prl_T *pp; + + if (current_SID <= 0 || current_SID > script_items.ga_len) + return; + si = &SCRIPT_ITEM(current_SID); + if (si->sn_prof_on && sourcing_lnum >= 1) { + /* Grow the array before starting the timer, so that the time spent + * here isn't counted. */ + ga_grow(&si->sn_prl_ga, (int)(sourcing_lnum - si->sn_prl_ga.ga_len)); + si->sn_prl_idx = sourcing_lnum - 1; + while (si->sn_prl_ga.ga_len <= si->sn_prl_idx + && si->sn_prl_ga.ga_len < si->sn_prl_ga.ga_maxlen) { + /* Zero counters for a line that was not used before. */ + pp = &PRL_ITEM(si, si->sn_prl_ga.ga_len); + pp->snp_count = 0; + profile_zero(&pp->sn_prl_total); + profile_zero(&pp->sn_prl_self); + ++si->sn_prl_ga.ga_len; + } + si->sn_prl_execed = FALSE; + profile_start(&si->sn_prl_start); + profile_zero(&si->sn_prl_children); + profile_get_wait(&si->sn_prl_wait); + } +} + +/* + * Called when actually executing a function line. + */ +void script_line_exec() { + scriptitem_T *si; + + if (current_SID <= 0 || current_SID > script_items.ga_len) + return; + si = &SCRIPT_ITEM(current_SID); + if (si->sn_prof_on && si->sn_prl_idx >= 0) + si->sn_prl_execed = TRUE; +} + +/* + * Called when done with a function line. + */ +void script_line_end() { + scriptitem_T *si; + sn_prl_T *pp; + + if (current_SID <= 0 || current_SID > script_items.ga_len) + return; + si = &SCRIPT_ITEM(current_SID); + if (si->sn_prof_on && si->sn_prl_idx >= 0 + && si->sn_prl_idx < si->sn_prl_ga.ga_len) { + if (si->sn_prl_execed) { + pp = &PRL_ITEM(si, si->sn_prl_idx); + ++pp->snp_count; + profile_end(&si->sn_prl_start); + profile_sub_wait(&si->sn_prl_wait, &si->sn_prl_start); + profile_add(&pp->sn_prl_total, &si->sn_prl_start); + profile_self(&pp->sn_prl_self, &si->sn_prl_start, + &si->sn_prl_children); + } + si->sn_prl_idx = -1; + } +} + +/* + * ":scriptencoding": Set encoding conversion for a sourced script. + * Without the multi-byte feature it's simply ignored. + */ +void ex_scriptencoding(eap) +exarg_T *eap UNUSED; +{ + struct source_cookie *sp; + char_u *name; + + if (!getline_equal(eap->getline, eap->cookie, getsourceline)) { + EMSG(_("E167: :scriptencoding used outside of a sourced file")); + return; + } + + if (*eap->arg != NUL) { + name = enc_canonize(eap->arg); + if (name == NULL) /* out of memory */ + return; + } else + name = eap->arg; + + /* Setup for conversion from the specified encoding to 'encoding'. */ + sp = (struct source_cookie *)getline_cookie(eap->getline, eap->cookie); + convert_setup(&sp->conv, name, p_enc); + + if (name != eap->arg) + vim_free(name); +} + +/* + * ":finish": Mark a sourced file as finished. + */ +void ex_finish(eap) +exarg_T *eap; +{ + if (getline_equal(eap->getline, eap->cookie, getsourceline)) + do_finish(eap, FALSE); + else + EMSG(_("E168: :finish used outside of a sourced file")); +} + +/* + * Mark a sourced file as finished. Possibly makes the ":finish" pending. + * Also called for a pending finish at the ":endtry" or after returning from + * an extra do_cmdline(). "reanimate" is used in the latter case. + */ +void do_finish(eap, reanimate) +exarg_T *eap; +int reanimate; +{ + int idx; + + if (reanimate) + ((struct source_cookie *)getline_cookie(eap->getline, + eap->cookie))->finished = FALSE; + + /* + * Cleanup (and inactivate) conditionals, but stop when a try conditional + * not in its finally clause (which then is to be executed next) is found. + * In this case, make the ":finish" pending for execution at the ":endtry". + * Otherwise, finish normally. + */ + idx = cleanup_conditionals(eap->cstack, 0, TRUE); + if (idx >= 0) { + eap->cstack->cs_pending[idx] = CSTP_FINISH; + report_make_pending(CSTP_FINISH, NULL); + } else + ((struct source_cookie *)getline_cookie(eap->getline, + eap->cookie))->finished = TRUE; +} + + +/* + * Return TRUE when a sourced file had the ":finish" command: Don't give error + * message for missing ":endif". + * Return FALSE when not sourcing a file. + */ +int source_finished(fgetline, cookie) +char_u *(*fgetline)__ARGS((int, void *, int)); +void *cookie; +{ + return getline_equal(fgetline, cookie, getsourceline) + && ((struct source_cookie *)getline_cookie( + fgetline, cookie))->finished; +} + +/* + * ":checktime [buffer]" + */ +void ex_checktime(eap) +exarg_T *eap; +{ + buf_T *buf; + int save_no_check_timestamps = no_check_timestamps; + + no_check_timestamps = 0; + if (eap->addr_count == 0) /* default is all buffers */ + check_timestamps(FALSE); + else { + buf = buflist_findnr((int)eap->line2); + if (buf != NULL) /* cannot happen? */ + (void)buf_check_timestamp(buf, FALSE); + } + no_check_timestamps = save_no_check_timestamps; +} + +#if (defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ + && (defined(FEAT_EVAL) || defined(FEAT_MULTI_LANG)) +# define HAVE_GET_LOCALE_VAL +static char *get_locale_val __ARGS((int what)); + +static char * get_locale_val(what) +int what; +{ + char *loc; + + /* Obtain the locale value from the libraries. For DJGPP this is + * redefined and it doesn't use the arguments. */ + loc = setlocale(what, NULL); + + + return loc; +} +#endif + + + +/* + * Obtain the current messages language. Used to set the default for + * 'helplang'. May return NULL or an empty string. + */ +char_u * get_mess_lang() { + char_u *p; + +# ifdef HAVE_GET_LOCALE_VAL +# if defined(LC_MESSAGES) + p = (char_u *)get_locale_val(LC_MESSAGES); +# else + /* This is necessary for Win32, where LC_MESSAGES is not defined and $LANG + * may be set to the LCID number. LC_COLLATE is the best guess, LC_TIME + * and LC_MONETARY may be set differently for a Japanese working in the + * US. */ + p = (char_u *)get_locale_val(LC_COLLATE); +# endif +# else + p = mch_getenv((char_u *)"LC_ALL"); + if (p == NULL || *p == NUL) { + p = mch_getenv((char_u *)"LC_MESSAGES"); + if (p == NULL || *p == NUL) + p = mch_getenv((char_u *)"LANG"); + } +# endif + return p; +} + +/* Complicated #if; matches with where get_mess_env() is used below. */ +#if (defined(FEAT_EVAL) && !((defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ + && defined(LC_MESSAGES))) \ + || ((defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ + && (defined(FEAT_GETTEXT) || defined(FEAT_MBYTE)) \ + && !defined(LC_MESSAGES)) +static char_u *get_mess_env __ARGS((void)); + +/* + * Get the language used for messages from the environment. + */ +static char_u * get_mess_env() { + char_u *p; + + p = mch_getenv((char_u *)"LC_ALL"); + if (p == NULL || *p == NUL) { + p = mch_getenv((char_u *)"LC_MESSAGES"); + if (p == NULL || *p == NUL) { + p = mch_getenv((char_u *)"LANG"); + if (p != NULL && VIM_ISDIGIT(*p)) + p = NULL; /* ignore something like "1043" */ +# ifdef HAVE_GET_LOCALE_VAL + if (p == NULL || *p == NUL) + p = (char_u *)get_locale_val(LC_CTYPE); +# endif + } + } + return p; +} + +#endif + + +/* + * Set the "v:lang" variable according to the current locale setting. + * Also do "v:lc_time"and "v:ctype". + */ +void set_lang_var() { + char_u *loc; + +# ifdef HAVE_GET_LOCALE_VAL + loc = (char_u *)get_locale_val(LC_CTYPE); +# else + /* setlocale() not supported: use the default value */ + loc = (char_u *)"C"; +# endif + set_vim_var_string(VV_CTYPE, loc, -1); + + /* When LC_MESSAGES isn't defined use the value from $LC_MESSAGES, fall + * back to LC_CTYPE if it's empty. */ +# if defined(HAVE_GET_LOCALE_VAL) && defined(LC_MESSAGES) + loc = (char_u *)get_locale_val(LC_MESSAGES); +# else + loc = get_mess_env(); +# endif + set_vim_var_string(VV_LANG, loc, -1); + +# ifdef HAVE_GET_LOCALE_VAL + loc = (char_u *)get_locale_val(LC_TIME); +# endif + set_vim_var_string(VV_LC_TIME, loc, -1); +} + +#if (defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ + && (defined(FEAT_GETTEXT) || defined(FEAT_MBYTE)) +/* + * ":language": Set the language (locale). + */ +void ex_language(eap) +exarg_T *eap; +{ + char *loc; + char_u *p; + char_u *name; + int what = LC_ALL; + char *whatstr = ""; +#ifdef LC_MESSAGES +# define VIM_LC_MESSAGES LC_MESSAGES +#else +# define VIM_LC_MESSAGES 6789 +#endif + + name = eap->arg; + + /* Check for "messages {name}", "ctype {name}" or "time {name}" argument. + * Allow abbreviation, but require at least 3 characters to avoid + * confusion with a two letter language name "me" or "ct". */ + p = skiptowhite(eap->arg); + if ((*p == NUL || vim_iswhite(*p)) && p - eap->arg >= 3) { + if (STRNICMP(eap->arg, "messages", p - eap->arg) == 0) { + what = VIM_LC_MESSAGES; + name = skipwhite(p); + whatstr = "messages "; + } else if (STRNICMP(eap->arg, "ctype", p - eap->arg) == 0) { + what = LC_CTYPE; + name = skipwhite(p); + whatstr = "ctype "; + } else if (STRNICMP(eap->arg, "time", p - eap->arg) == 0) { + what = LC_TIME; + name = skipwhite(p); + whatstr = "time "; + } + } + + if (*name == NUL) { +#ifndef LC_MESSAGES + if (what == VIM_LC_MESSAGES) + p = get_mess_env(); + else +#endif + p = (char_u *)setlocale(what, NULL); + if (p == NULL || *p == NUL) + p = (char_u *)"Unknown"; + smsg((char_u *)_("Current %slanguage: \"%s\""), whatstr, p); + } else { +#ifndef LC_MESSAGES + if (what == VIM_LC_MESSAGES) + loc = ""; + else +#endif + { + loc = setlocale(what, (char *)name); +#if defined(FEAT_FLOAT) && defined(LC_NUMERIC) + /* Make sure strtod() uses a decimal point, not a comma. */ + setlocale(LC_NUMERIC, "C"); +#endif + } + if (loc == NULL) + EMSG2(_("E197: Cannot set language to \"%s\""), name); + else { +#ifdef HAVE_NL_MSG_CAT_CNTR + /* Need to do this for GNU gettext, otherwise cached translations + * will be used again. */ + extern int _nl_msg_cat_cntr; + + ++_nl_msg_cat_cntr; +#endif + /* Reset $LC_ALL, otherwise it would overrule everything. */ + vim_setenv((char_u *)"LC_ALL", (char_u *)""); + + if (what != LC_TIME) { + /* Tell gettext() what to translate to. It apparently doesn't + * use the currently effective locale. Also do this when + * FEAT_GETTEXT isn't defined, so that shell commands use this + * value. */ + if (what == LC_ALL) { + vim_setenv((char_u *)"LANG", name); + + /* Clear $LANGUAGE because GNU gettext uses it. */ + vim_setenv((char_u *)"LANGUAGE", (char_u *)""); + } + if (what != LC_CTYPE) { + char_u *mname; + mname = name; + vim_setenv((char_u *)"LC_MESSAGES", mname); + set_helplang_default(mname); + } + } + + /* Set v:lang, v:lc_time and v:ctype to the final result. */ + set_lang_var(); + maketitle(); + } + } +} + + +static char_u **locales = NULL; /* Array of all available locales */ +static int did_init_locales = FALSE; + +static void init_locales __ARGS((void)); +static char_u **find_locales __ARGS((void)); + +/* + * Lazy initialization of all available locales. + */ +static void init_locales() { + if (!did_init_locales) { + did_init_locales = TRUE; + locales = find_locales(); + } +} + +/* Return an array of strings for all available locales + NULL for the + * last element. Return NULL in case of error. */ +static char_u ** find_locales() { + garray_T locales_ga; + char_u *loc; + + /* Find all available locales by running command "locale -a". If this + * doesn't work we won't have completion. */ + char_u *locale_a = get_cmd_output((char_u *)"locale -a", + NULL, SHELL_SILENT); + if (locale_a == NULL) + return NULL; + ga_init2(&locales_ga, sizeof(char_u *), 20); + + /* Transform locale_a string where each locale is separated by "\n" + * into an array of locale strings. */ + loc = (char_u *)strtok((char *)locale_a, "\n"); + + while (loc != NULL) { + if (ga_grow(&locales_ga, 1) == FAIL) + break; + loc = vim_strsave(loc); + if (loc == NULL) + break; + + ((char_u **)locales_ga.ga_data)[locales_ga.ga_len++] = loc; + loc = (char_u *)strtok(NULL, "\n"); + } + vim_free(locale_a); + if (ga_grow(&locales_ga, 1) == FAIL) { + ga_clear(&locales_ga); + return NULL; + } + ((char_u **)locales_ga.ga_data)[locales_ga.ga_len] = NULL; + return (char_u **)locales_ga.ga_data; +} + +# if defined(EXITFREE) || defined(PROTO) +void free_locales() { + int i; + if (locales != NULL) { + for (i = 0; locales[i] != NULL; i++) + vim_free(locales[i]); + vim_free(locales); + locales = NULL; + } +} + +# endif + +/* + * Function given to ExpandGeneric() to obtain the possible arguments of the + * ":language" command. + */ +char_u * get_lang_arg(xp, idx) +expand_T *xp UNUSED; +int idx; +{ + if (idx == 0) + return (char_u *)"messages"; + if (idx == 1) + return (char_u *)"ctype"; + if (idx == 2) + return (char_u *)"time"; + + init_locales(); + if (locales == NULL) + return NULL; + return locales[idx - 3]; +} + +/* + * Function given to ExpandGeneric() to obtain the available locales. + */ +char_u * get_locales(xp, idx) +expand_T *xp UNUSED; +int idx; +{ + init_locales(); + if (locales == NULL) + return NULL; + return locales[idx]; +} + +#endif diff --git a/src/ex_docmd.c b/src/ex_docmd.c new file mode 100644 index 0000000000..d3d9c256be --- /dev/null +++ b/src/ex_docmd.c @@ -0,0 +1,9401 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * ex_docmd.c: functions for executing an Ex command line. + */ + +#include "vim.h" + +static int quitmore = 0; +static int ex_pressedreturn = FALSE; + +typedef struct ucmd { + char_u *uc_name; /* The command name */ + long_u uc_argt; /* The argument type */ + char_u *uc_rep; /* The command's replacement string */ + long uc_def; /* The default value for a range/count */ + int uc_compl; /* completion type */ + scid_T uc_scriptID; /* SID where the command was defined */ + char_u *uc_compl_arg; /* completion argument if any */ +} ucmd_T; + +#define UC_BUFFER 1 /* -buffer: local to current buffer */ + +static garray_T ucmds = {0, 0, sizeof(ucmd_T), 4, NULL}; + +#define USER_CMD(i) (&((ucmd_T *)(ucmds.ga_data))[i]) +#define USER_CMD_GA(gap, i) (&((ucmd_T *)((gap)->ga_data))[i]) + +static void do_ucmd __ARGS((exarg_T *eap)); +static void ex_command __ARGS((exarg_T *eap)); +static void ex_delcommand __ARGS((exarg_T *eap)); +static char_u *get_user_command_name __ARGS((int idx)); + + +static char_u *do_one_cmd __ARGS((char_u **, int, struct condstack *, + char_u *(*fgetline)(int, void *, int), + void *cookie)); +static void append_command __ARGS((char_u *cmd)); +static char_u *find_command __ARGS((exarg_T *eap, int *full)); + +static void ex_abbreviate __ARGS((exarg_T *eap)); +static void ex_map __ARGS((exarg_T *eap)); +static void ex_unmap __ARGS((exarg_T *eap)); +static void ex_mapclear __ARGS((exarg_T *eap)); +static void ex_abclear __ARGS((exarg_T *eap)); +static void ex_autocmd __ARGS((exarg_T *eap)); +static void ex_doautocmd __ARGS((exarg_T *eap)); +static void ex_bunload __ARGS((exarg_T *eap)); +static void ex_buffer __ARGS((exarg_T *eap)); +static void ex_bmodified __ARGS((exarg_T *eap)); +static void ex_bnext __ARGS((exarg_T *eap)); +static void ex_bprevious __ARGS((exarg_T *eap)); +static void ex_brewind __ARGS((exarg_T *eap)); +static void ex_blast __ARGS((exarg_T *eap)); +static char_u *getargcmd __ARGS((char_u **)); +static char_u *skip_cmd_arg __ARGS((char_u *p, int rembs)); +static int getargopt __ARGS((exarg_T *eap)); + +static int check_more __ARGS((int, int)); +static linenr_T get_address __ARGS((char_u **, int skip, int to_other_file)); +static void get_flags __ARGS((exarg_T *eap)); +#if !defined(FEAT_PERL) \ + || !defined(FEAT_PYTHON) || !defined(FEAT_PYTHON3) \ + || !defined(FEAT_TCL) \ + || !defined(FEAT_RUBY) \ + || !defined(FEAT_LUA) \ + || !defined(FEAT_MZSCHEME) +# define HAVE_EX_SCRIPT_NI +static void ex_script_ni __ARGS((exarg_T *eap)); +#endif +static char_u *invalid_range __ARGS((exarg_T *eap)); +static void correct_range __ARGS((exarg_T *eap)); +static char_u *replace_makeprg __ARGS((exarg_T *eap, char_u *p, + char_u **cmdlinep)); +static char_u *repl_cmdline __ARGS((exarg_T *eap, char_u *src, int srclen, + char_u *repl, + char_u **cmdlinep)); +static void ex_highlight __ARGS((exarg_T *eap)); +static void ex_colorscheme __ARGS((exarg_T *eap)); +static void ex_quit __ARGS((exarg_T *eap)); +static void ex_cquit __ARGS((exarg_T *eap)); +static void ex_quit_all __ARGS((exarg_T *eap)); +static void ex_close __ARGS((exarg_T *eap)); +static void ex_win_close __ARGS((int forceit, win_T *win, tabpage_T *tp)); +static void ex_only __ARGS((exarg_T *eap)); +static void ex_resize __ARGS((exarg_T *eap)); +static void ex_stag __ARGS((exarg_T *eap)); +static void ex_tabclose __ARGS((exarg_T *eap)); +static void ex_tabonly __ARGS((exarg_T *eap)); +static void ex_tabnext __ARGS((exarg_T *eap)); +static void ex_tabmove __ARGS((exarg_T *eap)); +static void ex_tabs __ARGS((exarg_T *eap)); +static void ex_pclose __ARGS((exarg_T *eap)); +static void ex_ptag __ARGS((exarg_T *eap)); +static void ex_pedit __ARGS((exarg_T *eap)); +static void ex_hide __ARGS((exarg_T *eap)); +static void ex_stop __ARGS((exarg_T *eap)); +static void ex_exit __ARGS((exarg_T *eap)); +static void ex_print __ARGS((exarg_T *eap)); +static void ex_goto __ARGS((exarg_T *eap)); +static void ex_shell __ARGS((exarg_T *eap)); +static void ex_preserve __ARGS((exarg_T *eap)); +static void ex_recover __ARGS((exarg_T *eap)); +static void ex_mode __ARGS((exarg_T *eap)); +static void ex_wrongmodifier __ARGS((exarg_T *eap)); +static void ex_find __ARGS((exarg_T *eap)); +static void ex_open __ARGS((exarg_T *eap)); +static void ex_edit __ARGS((exarg_T *eap)); +# define ex_drop ex_ni +# define ex_gui ex_nogui +static void ex_nogui __ARGS((exarg_T *eap)); +# define ex_tearoff ex_ni +# define ex_popup ex_ni +# define ex_simalt ex_ni +# define gui_mch_find_dialog ex_ni +# define gui_mch_replace_dialog ex_ni +# define ex_helpfind ex_ni +# define ex_lua ex_script_ni +# define ex_luado ex_ni +# define ex_luafile ex_ni +# define ex_mzscheme ex_script_ni +# define ex_mzfile ex_ni +# define ex_perl ex_script_ni +# define ex_perldo ex_ni +# define ex_python ex_script_ni +# define ex_pydo ex_ni +# define ex_pyfile ex_ni +# define ex_py3 ex_script_ni +# define ex_py3do ex_ni +# define ex_py3file ex_ni +# define ex_tcl ex_script_ni +# define ex_tcldo ex_ni +# define ex_tclfile ex_ni +# define ex_ruby ex_script_ni +# define ex_rubydo ex_ni +# define ex_rubyfile ex_ni +# define ex_sniff ex_ni +static void ex_swapname __ARGS((exarg_T *eap)); +static void ex_syncbind __ARGS((exarg_T *eap)); +static void ex_read __ARGS((exarg_T *eap)); +static void ex_pwd __ARGS((exarg_T *eap)); +static void ex_equal __ARGS((exarg_T *eap)); +static void ex_sleep __ARGS((exarg_T *eap)); +static void do_exmap __ARGS((exarg_T *eap, int isabbrev)); +static void ex_winsize __ARGS((exarg_T *eap)); +static void ex_wincmd __ARGS((exarg_T *eap)); +#if defined(FEAT_GUI) || defined(UNIX) || defined(VMS) || defined(MSWIN) +static void ex_winpos __ARGS((exarg_T *eap)); +#else +# define ex_winpos ex_ni +#endif +static void ex_operators __ARGS((exarg_T *eap)); +static void ex_put __ARGS((exarg_T *eap)); +static void ex_copymove __ARGS((exarg_T *eap)); +static void ex_may_print __ARGS((exarg_T *eap)); +static void ex_submagic __ARGS((exarg_T *eap)); +static void ex_join __ARGS((exarg_T *eap)); +static void ex_at __ARGS((exarg_T *eap)); +static void ex_bang __ARGS((exarg_T *eap)); +static void ex_undo __ARGS((exarg_T *eap)); +static void ex_wundo __ARGS((exarg_T *eap)); +static void ex_rundo __ARGS((exarg_T *eap)); +static void ex_redo __ARGS((exarg_T *eap)); +static void ex_later __ARGS((exarg_T *eap)); +static void ex_redir __ARGS((exarg_T *eap)); +static void ex_redraw __ARGS((exarg_T *eap)); +static void ex_redrawstatus __ARGS((exarg_T *eap)); +static void close_redir __ARGS((void)); +static void ex_mkrc __ARGS((exarg_T *eap)); +static void ex_mark __ARGS((exarg_T *eap)); +static char_u *uc_fun_cmd __ARGS((void)); +static char_u *find_ucmd __ARGS((exarg_T *eap, char_u *p, int *full, + expand_T *xp, + int *compl)); +static void ex_normal __ARGS((exarg_T *eap)); +static void ex_startinsert __ARGS((exarg_T *eap)); +static void ex_stopinsert __ARGS((exarg_T *eap)); +static void ex_checkpath __ARGS((exarg_T *eap)); +static void ex_findpat __ARGS((exarg_T *eap)); +static void ex_psearch __ARGS((exarg_T *eap)); +static void ex_tag __ARGS((exarg_T *eap)); +static void ex_tag_cmd __ARGS((exarg_T *eap, char_u *name)); +static char_u *arg_all __ARGS((void)); +static int makeopens __ARGS((FILE *fd, char_u *dirnow)); +static int put_view __ARGS((FILE *fd, win_T *wp, int add_edit, unsigned *flagp, + int current_arg_idx)); +static void ex_loadview __ARGS((exarg_T *eap)); +static char_u *get_view_file __ARGS((int c)); +static int did_lcd; /* whether ":lcd" was produced for a session */ +static void ex_viminfo __ARGS((exarg_T *eap)); +static void ex_behave __ARGS((exarg_T *eap)); +static void ex_filetype __ARGS((exarg_T *eap)); +static void ex_setfiletype __ARGS((exarg_T *eap)); +static void ex_digraphs __ARGS((exarg_T *eap)); +static void ex_set __ARGS((exarg_T *eap)); +static void ex_nohlsearch __ARGS((exarg_T *eap)); +static void ex_match __ARGS((exarg_T *eap)); +static void ex_X __ARGS((exarg_T *eap)); +static void ex_fold __ARGS((exarg_T *eap)); +static void ex_foldopen __ARGS((exarg_T *eap)); +static void ex_folddo __ARGS((exarg_T *eap)); +#if !((defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ + && (defined(FEAT_GETTEXT) || defined(FEAT_MBYTE))) +# define ex_language ex_ni +#endif +# define ex_sign ex_ni +# define ex_wsverb ex_ni +# define ex_nbclose ex_ni +# define ex_nbkey ex_ni +# define ex_nbstart ex_ni + + + + +/* + * Declare cmdnames[]. + */ +#define DO_DECLARE_EXCMD +#include "ex_cmds.h" + +/* + * Table used to quickly search for a command, based on its first character. + */ +static cmdidx_T cmdidxs[27] = +{ + CMD_append, + CMD_buffer, + CMD_change, + CMD_delete, + CMD_edit, + CMD_file, + CMD_global, + CMD_help, + CMD_insert, + CMD_join, + CMD_k, + CMD_list, + CMD_move, + CMD_next, + CMD_open, + CMD_print, + CMD_quit, + CMD_read, + CMD_substitute, + CMD_t, + CMD_undo, + CMD_vglobal, + CMD_write, + CMD_xit, + CMD_yank, + CMD_z, + CMD_bang +}; + +static char_u dollar_command[2] = {'$', 0}; + + +/* Struct for storing a line inside a while/for loop */ +typedef struct { + char_u *line; /* command line */ + linenr_T lnum; /* sourcing_lnum of the line */ +} wcmd_T; + +/* + * Structure used to store info for line position in a while or for loop. + * This is required, because do_one_cmd() may invoke ex_function(), which + * reads more lines that may come from the while/for loop. + */ +struct loop_cookie { + garray_T *lines_gap; /* growarray with line info */ + int current_line; /* last read line from growarray */ + int repeating; /* TRUE when looping a second time */ + /* When "repeating" is FALSE use "getline" and "cookie" to get lines */ + char_u *(*getline)__ARGS((int, void *, int)); + void *cookie; +}; + +static char_u *get_loop_line __ARGS((int c, void *cookie, int indent)); +static int store_loop_line __ARGS((garray_T *gap, char_u *line)); +static void free_cmdlines __ARGS((garray_T *gap)); + +/* Struct to save a few things while debugging. Used in do_cmdline() only. */ +struct dbg_stuff { + int trylevel; + int force_abort; + except_T *caught_stack; + char_u *vv_exception; + char_u *vv_throwpoint; + int did_emsg; + int got_int; + int did_throw; + int need_rethrow; + int check_cstack; + except_T *current_exception; +}; + +static void save_dbg_stuff __ARGS((struct dbg_stuff *dsp)); +static void restore_dbg_stuff __ARGS((struct dbg_stuff *dsp)); + +static void save_dbg_stuff(dsp) +struct dbg_stuff *dsp; +{ + dsp->trylevel = trylevel; trylevel = 0; + dsp->force_abort = force_abort; force_abort = FALSE; + dsp->caught_stack = caught_stack; caught_stack = NULL; + dsp->vv_exception = v_exception(NULL); + dsp->vv_throwpoint = v_throwpoint(NULL); + + /* Necessary for debugging an inactive ":catch", ":finally", ":endtry" */ + dsp->did_emsg = did_emsg; did_emsg = FALSE; + dsp->got_int = got_int; got_int = FALSE; + dsp->did_throw = did_throw; did_throw = FALSE; + dsp->need_rethrow = need_rethrow; need_rethrow = FALSE; + dsp->check_cstack = check_cstack; check_cstack = FALSE; + dsp->current_exception = current_exception; current_exception = NULL; +} + +static void restore_dbg_stuff(dsp) +struct dbg_stuff *dsp; +{ + suppress_errthrow = FALSE; + trylevel = dsp->trylevel; + force_abort = dsp->force_abort; + caught_stack = dsp->caught_stack; + (void)v_exception(dsp->vv_exception); + (void)v_throwpoint(dsp->vv_throwpoint); + did_emsg = dsp->did_emsg; + got_int = dsp->got_int; + did_throw = dsp->did_throw; + need_rethrow = dsp->need_rethrow; + check_cstack = dsp->check_cstack; + current_exception = dsp->current_exception; +} + + +/* + * do_exmode(): Repeatedly get commands for the "Ex" mode, until the ":vi" + * command is given. + */ +void do_exmode(improved) +int improved; /* TRUE for "improved Ex" mode */ +{ + int save_msg_scroll; + int prev_msg_row; + linenr_T prev_line; + int changedtick; + + if (improved) + exmode_active = EXMODE_VIM; + else + exmode_active = EXMODE_NORMAL; + State = NORMAL; + + /* When using ":global /pat/ visual" and then "Q" we return to continue + * the :global command. */ + if (global_busy) + return; + + save_msg_scroll = msg_scroll; + ++RedrawingDisabled; /* don't redisplay the window */ + ++no_wait_return; /* don't wait for return */ + + MSG(_("Entering Ex mode. Type \"visual\" to go to Normal mode.")); + while (exmode_active) { + /* Check for a ":normal" command and no more characters left. */ + if (ex_normal_busy > 0 && typebuf.tb_len == 0) { + exmode_active = FALSE; + break; + } + msg_scroll = TRUE; + need_wait_return = FALSE; + ex_pressedreturn = FALSE; + ex_no_reprint = FALSE; + changedtick = curbuf->b_changedtick; + prev_msg_row = msg_row; + prev_line = curwin->w_cursor.lnum; + if (improved) { + cmdline_row = msg_row; + do_cmdline(NULL, getexline, NULL, 0); + } else + do_cmdline(NULL, getexmodeline, NULL, DOCMD_NOWAIT); + lines_left = Rows - 1; + + if ((prev_line != curwin->w_cursor.lnum + || changedtick != curbuf->b_changedtick) && !ex_no_reprint) { + if (curbuf->b_ml.ml_flags & ML_EMPTY) + EMSG(_(e_emptybuf)); + else { + if (ex_pressedreturn) { + /* go up one line, to overwrite the ":" line, so the + * output doesn't contain empty lines. */ + msg_row = prev_msg_row; + if (prev_msg_row == Rows - 1) + msg_row--; + } + msg_col = 0; + print_line_no_prefix(curwin->w_cursor.lnum, FALSE, FALSE); + msg_clr_eos(); + } + } else if (ex_pressedreturn && !ex_no_reprint) { /* must be at EOF */ + if (curbuf->b_ml.ml_flags & ML_EMPTY) + EMSG(_(e_emptybuf)); + else + EMSG(_("E501: At end-of-file")); + } + } + + --RedrawingDisabled; + --no_wait_return; + update_screen(CLEAR); + need_wait_return = FALSE; + msg_scroll = save_msg_scroll; +} + +/* + * Execute a simple command line. Used for translated commands like "*". + */ +int do_cmdline_cmd(cmd) +char_u *cmd; +{ + return do_cmdline(cmd, NULL, NULL, + DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED); +} + +/* + * do_cmdline(): execute one Ex command line + * + * 1. Execute "cmdline" when it is not NULL. + * If "cmdline" is NULL, or more lines are needed, fgetline() is used. + * 2. Split up in parts separated with '|'. + * + * This function can be called recursively! + * + * flags: + * DOCMD_VERBOSE - The command will be included in the error message. + * DOCMD_NOWAIT - Don't call wait_return() and friends. + * DOCMD_REPEAT - Repeat execution until fgetline() returns NULL. + * DOCMD_KEYTYPED - Don't reset KeyTyped. + * DOCMD_EXCRESET - Reset the exception environment (used for debugging). + * DOCMD_KEEPLINE - Store first typed line (for repeating with "."). + * + * return FAIL if cmdline could not be executed, OK otherwise + */ +int do_cmdline(cmdline, fgetline, cookie, flags) +char_u *cmdline; +char_u *(*fgetline)__ARGS((int, void *, int)); +void *cookie; /* argument for fgetline() */ +int flags; +{ + char_u *next_cmdline; /* next cmd to execute */ + char_u *cmdline_copy = NULL; /* copy of cmd line */ + int used_getline = FALSE; /* used "fgetline" to obtain command */ + static int recursive = 0; /* recursive depth */ + int msg_didout_before_start = 0; + int count = 0; /* line number count */ + int did_inc = FALSE; /* incremented RedrawingDisabled */ + int retval = OK; + struct condstack cstack; /* conditional stack */ + garray_T lines_ga; /* keep lines for ":while"/":for" */ + int current_line = 0; /* active line in lines_ga */ + char_u *fname = NULL; /* function or script name */ + linenr_T *breakpoint = NULL; /* ptr to breakpoint field in cookie */ + int *dbg_tick = NULL; /* ptr to dbg_tick field in cookie */ + struct dbg_stuff debug_saved; /* saved things for debug mode */ + int initial_trylevel; + struct msglist **saved_msg_list = NULL; + struct msglist *private_msg_list; + + /* "fgetline" and "cookie" passed to do_one_cmd() */ + char_u *(*cmd_getline)__ARGS((int, void *, int)); + void *cmd_cookie; + struct loop_cookie cmd_loop_cookie; + void *real_cookie; + int getline_is_func; + static int call_depth = 0; /* recursiveness */ + + /* For every pair of do_cmdline()/do_one_cmd() calls, use an extra memory + * location for storing error messages to be converted to an exception. + * This ensures that the do_errthrow() call in do_one_cmd() does not + * combine the messages stored by an earlier invocation of do_one_cmd() + * with the command name of the later one. This would happen when + * BufWritePost autocommands are executed after a write error. */ + saved_msg_list = msg_list; + msg_list = &private_msg_list; + private_msg_list = NULL; + + /* It's possible to create an endless loop with ":execute", catch that + * here. The value of 200 allows nested function calls, ":source", etc. */ + if (call_depth == 200) { + EMSG(_("E169: Command too recursive")); + /* When converting to an exception, we do not include the command name + * since this is not an error of the specific command. */ + do_errthrow((struct condstack *)NULL, (char_u *)NULL); + msg_list = saved_msg_list; + return FAIL; + } + ++call_depth; + + cstack.cs_idx = -1; + cstack.cs_looplevel = 0; + cstack.cs_trylevel = 0; + cstack.cs_emsg_silent_list = NULL; + cstack.cs_lflags = 0; + ga_init2(&lines_ga, (int)sizeof(wcmd_T), 10); + + real_cookie = getline_cookie(fgetline, cookie); + + /* Inside a function use a higher nesting level. */ + getline_is_func = getline_equal(fgetline, cookie, get_func_line); + if (getline_is_func && ex_nesting_level == func_level(real_cookie)) + ++ex_nesting_level; + + /* Get the function or script name and the address where the next breakpoint + * line and the debug tick for a function or script are stored. */ + if (getline_is_func) { + fname = func_name(real_cookie); + breakpoint = func_breakpoint(real_cookie); + dbg_tick = func_dbg_tick(real_cookie); + } else if (getline_equal(fgetline, cookie, getsourceline)) { + fname = sourcing_name; + breakpoint = source_breakpoint(real_cookie); + dbg_tick = source_dbg_tick(real_cookie); + } + + /* + * Initialize "force_abort" and "suppress_errthrow" at the top level. + */ + if (!recursive) { + force_abort = FALSE; + suppress_errthrow = FALSE; + } + + /* + * If requested, store and reset the global values controlling the + * exception handling (used when debugging). Otherwise clear it to avoid + * a bogus compiler warning when the optimizer uses inline functions... + */ + if (flags & DOCMD_EXCRESET) + save_dbg_stuff(&debug_saved); + else + vim_memset(&debug_saved, 0, 1); + + initial_trylevel = trylevel; + + /* + * "did_throw" will be set to TRUE when an exception is being thrown. + */ + did_throw = FALSE; + /* + * "did_emsg" will be set to TRUE when emsg() is used, in which case we + * cancel the whole command line, and any if/endif or loop. + * If force_abort is set, we cancel everything. + */ + did_emsg = FALSE; + + /* + * KeyTyped is only set when calling vgetc(). Reset it here when not + * calling vgetc() (sourced command lines). + */ + if (!(flags & DOCMD_KEYTYPED) + && !getline_equal(fgetline, cookie, getexline)) + KeyTyped = FALSE; + + /* + * Continue executing command lines: + * - when inside an ":if", ":while" or ":for" + * - for multiple commands on one line, separated with '|' + * - when repeating until there are no more lines (for ":source") + */ + next_cmdline = cmdline; + do { + getline_is_func = getline_equal(fgetline, cookie, get_func_line); + + /* stop skipping cmds for an error msg after all endif/while/for */ + if (next_cmdline == NULL + && !force_abort + && cstack.cs_idx < 0 + && !(getline_is_func && func_has_abort(real_cookie)) + ) + did_emsg = FALSE; + + /* + * 1. If repeating a line in a loop, get a line from lines_ga. + * 2. If no line given: Get an allocated line with fgetline(). + * 3. If a line is given: Make a copy, so we can mess with it. + */ + + /* 1. If repeating, get a previous line from lines_ga. */ + if (cstack.cs_looplevel > 0 && current_line < lines_ga.ga_len) { + /* Each '|' separated command is stored separately in lines_ga, to + * be able to jump to it. Don't use next_cmdline now. */ + vim_free(cmdline_copy); + cmdline_copy = NULL; + + /* Check if a function has returned or, unless it has an unclosed + * try conditional, aborted. */ + if (getline_is_func) { + if (do_profiling == PROF_YES) + func_line_end(real_cookie); + if (func_has_ended(real_cookie)) { + retval = FAIL; + break; + } + } else if (do_profiling == PROF_YES + && getline_equal(fgetline, cookie, getsourceline)) + script_line_end(); + + /* Check if a sourced file hit a ":finish" command. */ + if (source_finished(fgetline, cookie)) { + retval = FAIL; + break; + } + + /* If breakpoints have been added/deleted need to check for it. */ + if (breakpoint != NULL && dbg_tick != NULL + && *dbg_tick != debug_tick) { + *breakpoint = dbg_find_breakpoint( + getline_equal(fgetline, cookie, getsourceline), + fname, sourcing_lnum); + *dbg_tick = debug_tick; + } + + next_cmdline = ((wcmd_T *)(lines_ga.ga_data))[current_line].line; + sourcing_lnum = ((wcmd_T *)(lines_ga.ga_data))[current_line].lnum; + + /* Did we encounter a breakpoint? */ + if (breakpoint != NULL && *breakpoint != 0 + && *breakpoint <= sourcing_lnum) { + dbg_breakpoint(fname, sourcing_lnum); + /* Find next breakpoint. */ + *breakpoint = dbg_find_breakpoint( + getline_equal(fgetline, cookie, getsourceline), + fname, sourcing_lnum); + *dbg_tick = debug_tick; + } + if (do_profiling == PROF_YES) { + if (getline_is_func) + func_line_start(real_cookie); + else if (getline_equal(fgetline, cookie, getsourceline)) + script_line_start(); + } + } + + if (cstack.cs_looplevel > 0) { + /* Inside a while/for loop we need to store the lines and use them + * again. Pass a different "fgetline" function to do_one_cmd() + * below, so that it stores lines in or reads them from + * "lines_ga". Makes it possible to define a function inside a + * while/for loop. */ + cmd_getline = get_loop_line; + cmd_cookie = (void *)&cmd_loop_cookie; + cmd_loop_cookie.lines_gap = &lines_ga; + cmd_loop_cookie.current_line = current_line; + cmd_loop_cookie.getline = fgetline; + cmd_loop_cookie.cookie = cookie; + cmd_loop_cookie.repeating = (current_line < lines_ga.ga_len); + } else { + cmd_getline = fgetline; + cmd_cookie = cookie; + } + + /* 2. If no line given, get an allocated line with fgetline(). */ + if (next_cmdline == NULL) { + /* + * Need to set msg_didout for the first line after an ":if", + * otherwise the ":if" will be overwritten. + */ + if (count == 1 && getline_equal(fgetline, cookie, getexline)) + msg_didout = TRUE; + if (fgetline == NULL || (next_cmdline = fgetline(':', cookie, + cstack.cs_idx < + 0 ? 0 : (cstack.cs_idx + 1) * 2 + )) == NULL) { + /* Don't call wait_return for aborted command line. The NULL + * returned for the end of a sourced file or executed function + * doesn't do this. */ + if (KeyTyped && !(flags & DOCMD_REPEAT)) + need_wait_return = FALSE; + retval = FAIL; + break; + } + used_getline = TRUE; + + /* + * Keep the first typed line. Clear it when more lines are typed. + */ + if (flags & DOCMD_KEEPLINE) { + vim_free(repeat_cmdline); + if (count == 0) + repeat_cmdline = vim_strsave(next_cmdline); + else + repeat_cmdline = NULL; + } + } + /* 3. Make a copy of the command so we can mess with it. */ + else if (cmdline_copy == NULL) { + next_cmdline = vim_strsave(next_cmdline); + if (next_cmdline == NULL) { + EMSG(_(e_outofmem)); + retval = FAIL; + break; + } + } + cmdline_copy = next_cmdline; + + /* + * Save the current line when inside a ":while" or ":for", and when + * the command looks like a ":while" or ":for", because we may need it + * later. When there is a '|' and another command, it is stored + * separately, because we need to be able to jump back to it from an + * :endwhile/:endfor. + */ + if (current_line == lines_ga.ga_len + && (cstack.cs_looplevel || has_loop_cmd(next_cmdline))) { + if (store_loop_line(&lines_ga, next_cmdline) == FAIL) { + retval = FAIL; + break; + } + } + did_endif = FALSE; + + if (count++ == 0) { + /* + * All output from the commands is put below each other, without + * waiting for a return. Don't do this when executing commands + * from a script or when being called recursive (e.g. for ":e + * +command file"). + */ + if (!(flags & DOCMD_NOWAIT) && !recursive) { + msg_didout_before_start = msg_didout; + msg_didany = FALSE; /* no output yet */ + msg_start(); + msg_scroll = TRUE; /* put messages below each other */ + ++no_wait_return; /* don't wait for return until finished */ + ++RedrawingDisabled; + did_inc = TRUE; + } + } + + if (p_verbose >= 15 && sourcing_name != NULL) { + ++no_wait_return; + verbose_enter_scroll(); + + smsg((char_u *)_("line %ld: %s"), + (long)sourcing_lnum, cmdline_copy); + if (msg_silent == 0) + msg_puts((char_u *)"\n"); /* don't overwrite this */ + + verbose_leave_scroll(); + --no_wait_return; + } + + /* + * 2. Execute one '|' separated command. + * do_one_cmd() will return NULL if there is no trailing '|'. + * "cmdline_copy" can change, e.g. for '%' and '#' expansion. + */ + ++recursive; + next_cmdline = do_one_cmd(&cmdline_copy, flags & DOCMD_VERBOSE, + &cstack, + cmd_getline, cmd_cookie); + --recursive; + + if (cmd_cookie == (void *)&cmd_loop_cookie) + /* Use "current_line" from "cmd_loop_cookie", it may have been + * incremented when defining a function. */ + current_line = cmd_loop_cookie.current_line; + + if (next_cmdline == NULL) { + vim_free(cmdline_copy); + cmdline_copy = NULL; + /* + * If the command was typed, remember it for the ':' register. + * Do this AFTER executing the command to make :@: work. + */ + if (getline_equal(fgetline, cookie, getexline) + && new_last_cmdline != NULL) { + vim_free(last_cmdline); + last_cmdline = new_last_cmdline; + new_last_cmdline = NULL; + } + } else { + /* need to copy the command after the '|' to cmdline_copy, for the + * next do_one_cmd() */ + STRMOVE(cmdline_copy, next_cmdline); + next_cmdline = cmdline_copy; + } + + + /* reset did_emsg for a function that is not aborted by an error */ + if (did_emsg && !force_abort + && getline_equal(fgetline, cookie, get_func_line) + && !func_has_abort(real_cookie)) + did_emsg = FALSE; + + if (cstack.cs_looplevel > 0) { + ++current_line; + + /* + * An ":endwhile", ":endfor" and ":continue" is handled here. + * If we were executing commands, jump back to the ":while" or + * ":for". + * If we were not executing commands, decrement cs_looplevel. + */ + if (cstack.cs_lflags & (CSL_HAD_CONT | CSL_HAD_ENDLOOP)) { + cstack.cs_lflags &= ~(CSL_HAD_CONT | CSL_HAD_ENDLOOP); + + /* Jump back to the matching ":while" or ":for". Be careful + * not to use a cs_line[] from an entry that isn't a ":while" + * or ":for": It would make "current_line" invalid and can + * cause a crash. */ + if (!did_emsg && !got_int && !did_throw + && cstack.cs_idx >= 0 + && (cstack.cs_flags[cstack.cs_idx] + & (CSF_WHILE | CSF_FOR)) + && cstack.cs_line[cstack.cs_idx] >= 0 + && (cstack.cs_flags[cstack.cs_idx] & CSF_ACTIVE)) { + current_line = cstack.cs_line[cstack.cs_idx]; + /* remember we jumped there */ + cstack.cs_lflags |= CSL_HAD_LOOP; + line_breakcheck(); /* check if CTRL-C typed */ + + /* Check for the next breakpoint at or after the ":while" + * or ":for". */ + if (breakpoint != NULL) { + *breakpoint = dbg_find_breakpoint( + getline_equal(fgetline, cookie, getsourceline), + fname, + ((wcmd_T *)lines_ga.ga_data)[current_line].lnum-1); + *dbg_tick = debug_tick; + } + } else { + /* can only get here with ":endwhile" or ":endfor" */ + if (cstack.cs_idx >= 0) + rewind_conditionals(&cstack, cstack.cs_idx - 1, + CSF_WHILE | CSF_FOR, &cstack.cs_looplevel); + } + } + /* + * For a ":while" or ":for" we need to remember the line number. + */ + else if (cstack.cs_lflags & CSL_HAD_LOOP) { + cstack.cs_lflags &= ~CSL_HAD_LOOP; + cstack.cs_line[cstack.cs_idx] = current_line - 1; + } + } + + /* + * When not inside any ":while" loop, clear remembered lines. + */ + if (cstack.cs_looplevel == 0) { + if (lines_ga.ga_len > 0) { + sourcing_lnum = + ((wcmd_T *)lines_ga.ga_data)[lines_ga.ga_len - 1].lnum; + free_cmdlines(&lines_ga); + } + current_line = 0; + } + + /* + * A ":finally" makes did_emsg, got_int, and did_throw pending for + * being restored at the ":endtry". Reset them here and set the + * ACTIVE and FINALLY flags, so that the finally clause gets executed. + * This includes the case where a missing ":endif", ":endwhile" or + * ":endfor" was detected by the ":finally" itself. + */ + if (cstack.cs_lflags & CSL_HAD_FINA) { + cstack.cs_lflags &= ~CSL_HAD_FINA; + report_make_pending(cstack.cs_pending[cstack.cs_idx] + & (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW), + did_throw ? (void *)current_exception : NULL); + did_emsg = got_int = did_throw = FALSE; + cstack.cs_flags[cstack.cs_idx] |= CSF_ACTIVE | CSF_FINALLY; + } + + /* Update global "trylevel" for recursive calls to do_cmdline() from + * within this loop. */ + trylevel = initial_trylevel + cstack.cs_trylevel; + + /* + * If the outermost try conditional (across function calls and sourced + * files) is aborted because of an error, an interrupt, or an uncaught + * exception, cancel everything. If it is left normally, reset + * force_abort to get the non-EH compatible abortion behavior for + * the rest of the script. + */ + if (trylevel == 0 && !did_emsg && !got_int && !did_throw) + force_abort = FALSE; + + /* Convert an interrupt to an exception if appropriate. */ + (void)do_intthrow(&cstack); + + } + /* + * Continue executing command lines when: + * - no CTRL-C typed, no aborting error, no exception thrown or try + * conditionals need to be checked for executing finally clauses or + * catching an interrupt exception + * - didn't get an error message or lines are not typed + * - there is a command after '|', inside a :if, :while, :for or :try, or + * looping for ":source" command or function call. + */ + while (!((got_int + || (did_emsg && force_abort) || did_throw + ) + && cstack.cs_trylevel == 0 + ) + && !(did_emsg + /* Keep going when inside try/catch, so that the error can be + * deal with, except when it is a syntax error, it may cause + * the :endtry to be missed. */ + && (cstack.cs_trylevel == 0 || did_emsg_syntax) + && used_getline + && (getline_equal(fgetline, cookie, getexmodeline) + || getline_equal(fgetline, cookie, getexline))) + && (next_cmdline != NULL + || cstack.cs_idx >= 0 + || (flags & DOCMD_REPEAT))); + + vim_free(cmdline_copy); + did_emsg_syntax = FALSE; + free_cmdlines(&lines_ga); + ga_clear(&lines_ga); + + if (cstack.cs_idx >= 0) { + /* + * If a sourced file or executed function ran to its end, report the + * unclosed conditional. + */ + if (!got_int && !did_throw + && ((getline_equal(fgetline, cookie, getsourceline) + && !source_finished(fgetline, cookie)) + || (getline_equal(fgetline, cookie, get_func_line) + && !func_has_ended(real_cookie)))) { + if (cstack.cs_flags[cstack.cs_idx] & CSF_TRY) + EMSG(_(e_endtry)); + else if (cstack.cs_flags[cstack.cs_idx] & CSF_WHILE) + EMSG(_(e_endwhile)); + else if (cstack.cs_flags[cstack.cs_idx] & CSF_FOR) + EMSG(_(e_endfor)); + else + EMSG(_(e_endif)); + } + + /* + * Reset "trylevel" in case of a ":finish" or ":return" or a missing + * ":endtry" in a sourced file or executed function. If the try + * conditional is in its finally clause, ignore anything pending. + * If it is in a catch clause, finish the caught exception. + * Also cleanup any "cs_forinfo" structures. + */ + do { + int idx = cleanup_conditionals(&cstack, 0, TRUE); + + if (idx >= 0) + --idx; /* remove try block not in its finally clause */ + rewind_conditionals(&cstack, idx, CSF_WHILE | CSF_FOR, + &cstack.cs_looplevel); + } while (cstack.cs_idx >= 0); + trylevel = initial_trylevel; + } + + /* If a missing ":endtry", ":endwhile", ":endfor", or ":endif" or a memory + * lack was reported above and the error message is to be converted to an + * exception, do this now after rewinding the cstack. */ + do_errthrow(&cstack, getline_equal(fgetline, cookie, get_func_line) + ? (char_u *)"endfunction" : (char_u *)NULL); + + if (trylevel == 0) { + /* + * When an exception is being thrown out of the outermost try + * conditional, discard the uncaught exception, disable the conversion + * of interrupts or errors to exceptions, and ensure that no more + * commands are executed. + */ + if (did_throw) { + void *p = NULL; + char_u *saved_sourcing_name; + int saved_sourcing_lnum; + struct msglist *messages = NULL, *next; + + /* + * If the uncaught exception is a user exception, report it as an + * error. If it is an error exception, display the saved error + * message now. For an interrupt exception, do nothing; the + * interrupt message is given elsewhere. + */ + switch (current_exception->type) { + case ET_USER: + vim_snprintf((char *)IObuff, IOSIZE, + _("E605: Exception not caught: %s"), + current_exception->value); + p = vim_strsave(IObuff); + break; + case ET_ERROR: + messages = current_exception->messages; + current_exception->messages = NULL; + break; + case ET_INTERRUPT: + break; + default: + p = vim_strsave((char_u *)_(e_internal)); + } + + saved_sourcing_name = sourcing_name; + saved_sourcing_lnum = sourcing_lnum; + sourcing_name = current_exception->throw_name; + sourcing_lnum = current_exception->throw_lnum; + current_exception->throw_name = NULL; + + discard_current_exception(); /* uses IObuff if 'verbose' */ + suppress_errthrow = TRUE; + force_abort = TRUE; + + if (messages != NULL) { + do { + next = messages->next; + emsg(messages->msg); + vim_free(messages->msg); + vim_free(messages); + messages = next; + } while (messages != NULL); + } else if (p != NULL) { + emsg(p); + vim_free(p); + } + vim_free(sourcing_name); + sourcing_name = saved_sourcing_name; + sourcing_lnum = saved_sourcing_lnum; + } + /* + * On an interrupt or an aborting error not converted to an exception, + * disable the conversion of errors to exceptions. (Interrupts are not + * converted any more, here.) This enables also the interrupt message + * when force_abort is set and did_emsg unset in case of an interrupt + * from a finally clause after an error. + */ + else if (got_int || (did_emsg && force_abort)) + suppress_errthrow = TRUE; + } + + /* + * The current cstack will be freed when do_cmdline() returns. An uncaught + * exception will have to be rethrown in the previous cstack. If a function + * has just returned or a script file was just finished and the previous + * cstack belongs to the same function or, respectively, script file, it + * will have to be checked for finally clauses to be executed due to the + * ":return" or ":finish". This is done in do_one_cmd(). + */ + if (did_throw) + need_rethrow = TRUE; + if ((getline_equal(fgetline, cookie, getsourceline) + && ex_nesting_level > source_level(real_cookie)) + || (getline_equal(fgetline, cookie, get_func_line) + && ex_nesting_level > func_level(real_cookie) + 1)) { + if (!did_throw) + check_cstack = TRUE; + } else { + /* When leaving a function, reduce nesting level. */ + if (getline_equal(fgetline, cookie, get_func_line)) + --ex_nesting_level; + /* + * Go to debug mode when returning from a function in which we are + * single-stepping. + */ + if ((getline_equal(fgetline, cookie, getsourceline) + || getline_equal(fgetline, cookie, get_func_line)) + && ex_nesting_level + 1 <= debug_break_level) + do_debug(getline_equal(fgetline, cookie, getsourceline) + ? (char_u *)_("End of sourced file") + : (char_u *)_("End of function")); + } + + /* + * Restore the exception environment (done after returning from the + * debugger). + */ + if (flags & DOCMD_EXCRESET) + restore_dbg_stuff(&debug_saved); + + msg_list = saved_msg_list; + + /* + * If there was too much output to fit on the command line, ask the user to + * hit return before redrawing the screen. With the ":global" command we do + * this only once after the command is finished. + */ + if (did_inc) { + --RedrawingDisabled; + --no_wait_return; + msg_scroll = FALSE; + + /* + * When just finished an ":if"-":else" which was typed, no need to + * wait for hit-return. Also for an error situation. + */ + if (retval == FAIL + || (did_endif && KeyTyped && !did_emsg) + ) { + need_wait_return = FALSE; + msg_didany = FALSE; /* don't wait when restarting edit */ + } else if (need_wait_return) { + /* + * The msg_start() above clears msg_didout. The wait_return we do + * here should not overwrite the command that may be shown before + * doing that. + */ + msg_didout |= msg_didout_before_start; + wait_return(FALSE); + } + } + + did_endif = FALSE; /* in case do_cmdline used recursively */ + + --call_depth; + return retval; +} + +/* + * Obtain a line when inside a ":while" or ":for" loop. + */ +static char_u * get_loop_line(c, cookie, indent) +int c; +void *cookie; +int indent; +{ + struct loop_cookie *cp = (struct loop_cookie *)cookie; + wcmd_T *wp; + char_u *line; + + if (cp->current_line + 1 >= cp->lines_gap->ga_len) { + if (cp->repeating) + return NULL; /* trying to read past ":endwhile"/":endfor" */ + + /* First time inside the ":while"/":for": get line normally. */ + if (cp->getline == NULL) + line = getcmdline(c, 0L, indent); + else + line = cp->getline(c, cp->cookie, indent); + if (line != NULL && store_loop_line(cp->lines_gap, line) == OK) + ++cp->current_line; + + return line; + } + + KeyTyped = FALSE; + ++cp->current_line; + wp = (wcmd_T *)(cp->lines_gap->ga_data) + cp->current_line; + sourcing_lnum = wp->lnum; + return vim_strsave(wp->line); +} + +/* + * Store a line in "gap" so that a ":while" loop can execute it again. + */ +static int store_loop_line(gap, line) +garray_T *gap; +char_u *line; +{ + if (ga_grow(gap, 1) == FAIL) + return FAIL; + ((wcmd_T *)(gap->ga_data))[gap->ga_len].line = vim_strsave(line); + ((wcmd_T *)(gap->ga_data))[gap->ga_len].lnum = sourcing_lnum; + ++gap->ga_len; + return OK; +} + +/* + * Free the lines stored for a ":while" or ":for" loop. + */ +static void free_cmdlines(gap) +garray_T *gap; +{ + while (gap->ga_len > 0) { + vim_free(((wcmd_T *)(gap->ga_data))[gap->ga_len - 1].line); + --gap->ga_len; + } +} + +/* + * If "fgetline" is get_loop_line(), return TRUE if the getline it uses equals + * "func". * Otherwise return TRUE when "fgetline" equals "func". + */ +int getline_equal(fgetline, cookie, func) +char_u *(*fgetline)__ARGS((int, void *, int)); +void *cookie UNUSED; /* argument for fgetline() */ +char_u *(*func)__ARGS((int, void *, int)); +{ + char_u *(*gp)__ARGS((int, void *, int)); + struct loop_cookie *cp; + + /* When "fgetline" is "get_loop_line()" use the "cookie" to find the + * function that's originally used to obtain the lines. This may be + * nested several levels. */ + gp = fgetline; + cp = (struct loop_cookie *)cookie; + while (gp == get_loop_line) { + gp = cp->getline; + cp = cp->cookie; + } + return gp == func; +} + +/* + * If "fgetline" is get_loop_line(), return the cookie used by the original + * getline function. Otherwise return "cookie". + */ +void * getline_cookie(fgetline, cookie) +char_u *(*fgetline)__ARGS((int, void *, int)) UNUSED; +void *cookie; /* argument for fgetline() */ +{ + char_u *(*gp)__ARGS((int, void *, int)); + struct loop_cookie *cp; + + /* When "fgetline" is "get_loop_line()" use the "cookie" to find the + * cookie that's originally used to obtain the lines. This may be nested + * several levels. */ + gp = fgetline; + cp = (struct loop_cookie *)cookie; + while (gp == get_loop_line) { + gp = cp->getline; + cp = cp->cookie; + } + return cp; +} + +/* + * Execute one Ex command. + * + * If 'sourcing' is TRUE, the command will be included in the error message. + * + * 1. skip comment lines and leading space + * 2. handle command modifiers + * 3. parse range + * 4. parse command + * 5. parse arguments + * 6. switch on command name + * + * Note: "fgetline" can be NULL. + * + * This function may be called recursively! + */ +static char_u * do_one_cmd(cmdlinep, sourcing, + cstack, + fgetline, cookie) +char_u **cmdlinep; +int sourcing; +struct condstack *cstack; +char_u *(*fgetline)__ARGS((int, void *, int)); +void *cookie; /* argument for fgetline() */ +{ + char_u *p; + linenr_T lnum; + long n; + char_u *errormsg = NULL; /* error message */ + exarg_T ea; /* Ex command arguments */ + long verbose_save = -1; + int save_msg_scroll = msg_scroll; + int save_msg_silent = -1; + int did_esilent = 0; +#ifdef HAVE_SANDBOX + int did_sandbox = FALSE; +#endif + cmdmod_T save_cmdmod; + int ni; /* set when Not Implemented */ + + vim_memset(&ea, 0, sizeof(ea)); + ea.line1 = 1; + ea.line2 = 1; + ++ex_nesting_level; + + /* When the last file has not been edited :q has to be typed twice. */ + if (quitmore + /* avoid that a function call in 'statusline' does this */ + && !getline_equal(fgetline, cookie, get_func_line) + /* avoid that an autocommand, e.g. QuitPre, does this */ + && !getline_equal(fgetline, cookie, getnextac) + ) + --quitmore; + + /* + * Reset browse, confirm, etc.. They are restored when returning, for + * recursive calls. + */ + save_cmdmod = cmdmod; + vim_memset(&cmdmod, 0, sizeof(cmdmod)); + + /* "#!anything" is handled like a comment. */ + if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') + goto doend; + + /* + * Repeat until no more command modifiers are found. + */ + ea.cmd = *cmdlinep; + for (;; ) { + /* + * 1. skip comment lines and leading white space and colons + */ + while (*ea.cmd == ' ' || *ea.cmd == '\t' || *ea.cmd == ':') + ++ea.cmd; + + /* in ex mode, an empty line works like :+ */ + if (*ea.cmd == NUL && exmode_active + && (getline_equal(fgetline, cookie, getexmodeline) + || getline_equal(fgetline, cookie, getexline)) + && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { + ea.cmd = (char_u *)"+"; + ex_pressedreturn = TRUE; + } + + /* ignore comment and empty lines */ + if (*ea.cmd == '"') + goto doend; + if (*ea.cmd == NUL) { + ex_pressedreturn = TRUE; + goto doend; + } + + /* + * 2. handle command modifiers. + */ + p = ea.cmd; + if (VIM_ISDIGIT(*ea.cmd)) + p = skipwhite(skipdigits(ea.cmd)); + switch (*p) { + /* When adding an entry, also modify cmd_exists(). */ + case 'a': if (!checkforcmd(&ea.cmd, "aboveleft", 3)) + break; + cmdmod.split |= WSP_ABOVE; + continue; + + case 'b': if (checkforcmd(&ea.cmd, "belowright", 3)) { + cmdmod.split |= WSP_BELOW; + continue; + } + if (checkforcmd(&ea.cmd, "browse", 3)) { + continue; + } + if (!checkforcmd(&ea.cmd, "botright", 2)) + break; + cmdmod.split |= WSP_BOT; + continue; + + case 'c': if (!checkforcmd(&ea.cmd, "confirm", 4)) + break; + cmdmod.confirm = TRUE; + continue; + + case 'k': if (checkforcmd(&ea.cmd, "keepmarks", 3)) { + cmdmod.keepmarks = TRUE; + continue; + } + if (checkforcmd(&ea.cmd, "keepalt", 5)) { + cmdmod.keepalt = TRUE; + continue; + } + if (checkforcmd(&ea.cmd, "keeppatterns", 5)) { + cmdmod.keeppatterns = TRUE; + continue; + } + if (!checkforcmd(&ea.cmd, "keepjumps", 5)) + break; + cmdmod.keepjumps = TRUE; + continue; + + /* ":hide" and ":hide | cmd" are not modifiers */ + case 'h': if (p != ea.cmd || !checkforcmd(&p, "hide", 3) + || *p == NUL || ends_excmd(*p)) + break; + ea.cmd = p; + cmdmod.hide = TRUE; + continue; + + case 'l': if (checkforcmd(&ea.cmd, "lockmarks", 3)) { + cmdmod.lockmarks = TRUE; + continue; + } + + if (!checkforcmd(&ea.cmd, "leftabove", 5)) + break; + cmdmod.split |= WSP_ABOVE; + continue; + + case 'n': if (!checkforcmd(&ea.cmd, "noautocmd", 3)) + break; + if (cmdmod.save_ei == NULL) { + /* Set 'eventignore' to "all". Restore the + * existing option value later. */ + cmdmod.save_ei = vim_strsave(p_ei); + set_string_option_direct((char_u *)"ei", -1, + (char_u *)"all", OPT_FREE, SID_NONE); + } + continue; + + case 'r': if (!checkforcmd(&ea.cmd, "rightbelow", 6)) + break; + cmdmod.split |= WSP_BELOW; + continue; + + case 's': if (checkforcmd(&ea.cmd, "sandbox", 3)) { +#ifdef HAVE_SANDBOX + if (!did_sandbox) + ++sandbox; + did_sandbox = TRUE; +#endif + continue; + } + if (!checkforcmd(&ea.cmd, "silent", 3)) + break; + if (save_msg_silent == -1) + save_msg_silent = msg_silent; + ++msg_silent; + if (*ea.cmd == '!' && !vim_iswhite(ea.cmd[-1])) { + /* ":silent!", but not "silent !cmd" */ + ea.cmd = skipwhite(ea.cmd + 1); + ++emsg_silent; + ++did_esilent; + } + continue; + + case 't': if (checkforcmd(&p, "tab", 3)) { + if (vim_isdigit(*ea.cmd)) + cmdmod.tab = atoi((char *)ea.cmd) + 1; + else + cmdmod.tab = tabpage_index(curtab) + 1; + ea.cmd = p; + continue; + } + if (!checkforcmd(&ea.cmd, "topleft", 2)) + break; + cmdmod.split |= WSP_TOP; + continue; + + case 'u': if (!checkforcmd(&ea.cmd, "unsilent", 3)) + break; + if (save_msg_silent == -1) + save_msg_silent = msg_silent; + msg_silent = 0; + continue; + + case 'v': if (checkforcmd(&ea.cmd, "vertical", 4)) { + cmdmod.split |= WSP_VERT; + continue; + } + if (!checkforcmd(&p, "verbose", 4)) + break; + if (verbose_save < 0) + verbose_save = p_verbose; + if (vim_isdigit(*ea.cmd)) + p_verbose = atoi((char *)ea.cmd); + else + p_verbose = 1; + ea.cmd = p; + continue; + } + break; + } + + ea.skip = did_emsg || got_int || did_throw || (cstack->cs_idx >= 0 + && !(cstack->cs_flags[cstack-> + cs_idx] + & CSF_ACTIVE)); + + /* Count this line for profiling if ea.skip is FALSE. */ + if (do_profiling == PROF_YES && !ea.skip) { + if (getline_equal(fgetline, cookie, get_func_line)) + func_line_exec(getline_cookie(fgetline, cookie)); + else if (getline_equal(fgetline, cookie, getsourceline)) + script_line_exec(); + } + + /* May go to debug mode. If this happens and the ">quit" debug command is + * used, throw an interrupt exception and skip the next command. */ + dbg_check_breakpoint(&ea); + if (!ea.skip && got_int) { + ea.skip = TRUE; + (void)do_intthrow(cstack); + } + + /* + * 3. parse a range specifier of the form: addr [,addr] [;addr] .. + * + * where 'addr' is: + * + * % (entire file) + * $ [+-NUM] + * 'x [+-NUM] (where x denotes a currently defined mark) + * . [+-NUM] + * [+-NUM].. + * NUM + * + * The ea.cmd pointer is updated to point to the first character following the + * range spec. If an initial address is found, but no second, the upper bound + * is equal to the lower. + */ + + /* repeat for all ',' or ';' separated addresses */ + for (;; ) { + ea.line1 = ea.line2; + ea.line2 = curwin->w_cursor.lnum; /* default is current line number */ + ea.cmd = skipwhite(ea.cmd); + lnum = get_address(&ea.cmd, ea.skip, ea.addr_count == 0); + if (ea.cmd == NULL) /* error detected */ + goto doend; + if (lnum == MAXLNUM) { + if (*ea.cmd == '%') { /* '%' - all lines */ + ++ea.cmd; + ea.line1 = 1; + ea.line2 = curbuf->b_ml.ml_line_count; + ++ea.addr_count; + } + /* '*' - visual area */ + else if (*ea.cmd == '*' && vim_strchr(p_cpo, CPO_STAR) == NULL) { + pos_T *fp; + + ++ea.cmd; + if (!ea.skip) { + fp = getmark('<', FALSE); + if (check_mark(fp) == FAIL) + goto doend; + ea.line1 = fp->lnum; + fp = getmark('>', FALSE); + if (check_mark(fp) == FAIL) + goto doend; + ea.line2 = fp->lnum; + ++ea.addr_count; + } + } + } else + ea.line2 = lnum; + ea.addr_count++; + + if (*ea.cmd == ';') { + if (!ea.skip) + curwin->w_cursor.lnum = ea.line2; + } else if (*ea.cmd != ',') + break; + ++ea.cmd; + } + + /* One address given: set start and end lines */ + if (ea.addr_count == 1) { + ea.line1 = ea.line2; + /* ... but only implicit: really no address given */ + if (lnum == MAXLNUM) + ea.addr_count = 0; + } + + /* Don't leave the cursor on an illegal line (caused by ';') */ + check_cursor_lnum(); + + /* + * 4. parse command + */ + + /* + * Skip ':' and any white space + */ + ea.cmd = skipwhite(ea.cmd); + while (*ea.cmd == ':') + ea.cmd = skipwhite(ea.cmd + 1); + + /* + * If we got a line, but no command, then go to the line. + * If we find a '|' or '\n' we set ea.nextcmd. + */ + if (*ea.cmd == NUL || *ea.cmd == '"' || + (ea.nextcmd = check_nextcmd(ea.cmd)) != NULL) { + /* + * strange vi behaviour: + * ":3" jumps to line 3 + * ":3|..." prints line 3 + * ":|" prints current line + */ + if (ea.skip) /* skip this if inside :if */ + goto doend; + if (*ea.cmd == '|' || (exmode_active && ea.line1 != ea.line2)) { + ea.cmdidx = CMD_print; + ea.argt = RANGE+COUNT+TRLBAR; + if ((errormsg = invalid_range(&ea)) == NULL) { + correct_range(&ea); + ex_print(&ea); + } + } else if (ea.addr_count != 0) { + if (ea.line2 > curbuf->b_ml.ml_line_count) { + /* With '-' in 'cpoptions' a line number past the file is an + * error, otherwise put it at the end of the file. */ + if (vim_strchr(p_cpo, CPO_MINUS) != NULL) + ea.line2 = -1; + else + ea.line2 = curbuf->b_ml.ml_line_count; + } + + if (ea.line2 < 0) + errormsg = (char_u *)_(e_invrange); + else { + if (ea.line2 == 0) + curwin->w_cursor.lnum = 1; + else + curwin->w_cursor.lnum = ea.line2; + beginline(BL_SOL | BL_FIX); + } + } + goto doend; + } + + /* Find the command and let "p" point to after it. */ + p = find_command(&ea, NULL); + + if (p == NULL) { + if (!ea.skip) + errormsg = (char_u *)_("E464: Ambiguous use of user-defined command"); + goto doend; + } + /* Check for wrong commands. */ + if (*p == '!' && ea.cmd[1] == 0151 && ea.cmd[0] == 78) { + errormsg = uc_fun_cmd(); + goto doend; + } + if (ea.cmdidx == CMD_SIZE) { + if (!ea.skip) { + STRCPY(IObuff, _("E492: Not an editor command")); + if (!sourcing) + append_command(*cmdlinep); + errormsg = IObuff; + did_emsg_syntax = TRUE; + } + goto doend; + } + + ni = ( + !USER_CMDIDX(ea.cmdidx) && + (cmdnames[ea.cmdidx].cmd_func == ex_ni +#ifdef HAVE_EX_SCRIPT_NI + || cmdnames[ea.cmdidx].cmd_func == ex_script_ni +#endif + )); + + + /* forced commands */ + if (*p == '!' && ea.cmdidx != CMD_substitute + && ea.cmdidx != CMD_smagic && ea.cmdidx != CMD_snomagic) { + ++p; + ea.forceit = TRUE; + } else + ea.forceit = FALSE; + + /* + * 5. parse arguments + */ + if (!USER_CMDIDX(ea.cmdidx)) + ea.argt = (long)cmdnames[(int)ea.cmdidx].cmd_argt; + + if (!ea.skip) { +#ifdef HAVE_SANDBOX + if (sandbox != 0 && !(ea.argt & SBOXOK)) { + /* Command not allowed in sandbox. */ + errormsg = (char_u *)_(e_sandbox); + goto doend; + } +#endif + if (!curbuf->b_p_ma && (ea.argt & MODIFY)) { + /* Command not allowed in non-'modifiable' buffer */ + errormsg = (char_u *)_(e_modifiable); + goto doend; + } + + if (text_locked() && !(ea.argt & CMDWIN) + && !USER_CMDIDX(ea.cmdidx) + ) { + /* Command not allowed when editing the command line. */ + if (cmdwin_type != 0) + errormsg = (char_u *)_(e_cmdwin); + else + errormsg = (char_u *)_(e_secure); + goto doend; + } + /* Disallow editing another buffer when "curbuf_lock" is set. + * Do allow ":edit" (check for argument later). + * Do allow ":checktime" (it's postponed). */ + if (!(ea.argt & CMDWIN) + && ea.cmdidx != CMD_edit + && ea.cmdidx != CMD_checktime + && !USER_CMDIDX(ea.cmdidx) + && curbuf_locked()) + goto doend; + + if (!ni && !(ea.argt & RANGE) && ea.addr_count > 0) { + /* no range allowed */ + errormsg = (char_u *)_(e_norange); + goto doend; + } + } + + if (!ni && !(ea.argt & BANG) && ea.forceit) { /* no allowed */ + errormsg = (char_u *)_(e_nobang); + goto doend; + } + + /* + * Don't complain about the range if it is not used + * (could happen if line_count is accidentally set to 0). + */ + if (!ea.skip && !ni) { + /* + * If the range is backwards, ask for confirmation and, if given, swap + * ea.line1 & ea.line2 so it's forwards again. + * When global command is busy, don't ask, will fail below. + */ + if (!global_busy && ea.line1 > ea.line2) { + if (msg_silent == 0) { + if (sourcing || exmode_active) { + errormsg = (char_u *)_("E493: Backwards range given"); + goto doend; + } + if (ask_yesno((char_u *) + _("Backwards range given, OK to swap"), FALSE) != 'y') + goto doend; + } + lnum = ea.line1; + ea.line1 = ea.line2; + ea.line2 = lnum; + } + if ((errormsg = invalid_range(&ea)) != NULL) + goto doend; + } + + if ((ea.argt & NOTADR) && ea.addr_count == 0) /* default is 1, not cursor */ + ea.line2 = 1; + + correct_range(&ea); + + if (((ea.argt & WHOLEFOLD) || ea.addr_count >= 2) && !global_busy) { + /* Put the first line at the start of a closed fold, put the last line + * at the end of a closed fold. */ + (void)hasFolding(ea.line1, &ea.line1, NULL); + (void)hasFolding(ea.line2, NULL, &ea.line2); + } + + /* + * For the ":make" and ":grep" commands we insert the 'makeprg'/'grepprg' + * option here, so things like % get expanded. + */ + p = replace_makeprg(&ea, p, cmdlinep); + if (p == NULL) + goto doend; + + /* + * Skip to start of argument. + * Don't do this for the ":!" command, because ":!! -l" needs the space. + */ + if (ea.cmdidx == CMD_bang) + ea.arg = p; + else + ea.arg = skipwhite(p); + + /* + * Check for "++opt=val" argument. + * Must be first, allow ":w ++enc=utf8 !cmd" + */ + if (ea.argt & ARGOPT) + while (ea.arg[0] == '+' && ea.arg[1] == '+') + if (getargopt(&ea) == FAIL && !ni) { + errormsg = (char_u *)_(e_invarg); + goto doend; + } + + if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) { + if (*ea.arg == '>') { /* append */ + if (*++ea.arg != '>') { /* typed wrong */ + errormsg = (char_u *)_("E494: Use w or w>>"); + goto doend; + } + ea.arg = skipwhite(ea.arg + 1); + ea.append = TRUE; + } else if (*ea.arg == '!' && ea.cmdidx == CMD_write) { /* :w !filter */ + ++ea.arg; + ea.usefilter = TRUE; + } + } + + if (ea.cmdidx == CMD_read) { + if (ea.forceit) { + ea.usefilter = TRUE; /* :r! filter if ea.forceit */ + ea.forceit = FALSE; + } else if (*ea.arg == '!') { /* :r !filter */ + ++ea.arg; + ea.usefilter = TRUE; + } + } + + if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { + ea.amount = 1; + while (*ea.arg == *ea.cmd) { /* count number of '>' or '<' */ + ++ea.arg; + ++ea.amount; + } + ea.arg = skipwhite(ea.arg); + } + + /* + * Check for "+command" argument, before checking for next command. + * Don't do this for ":read !cmd" and ":write !cmd". + */ + if ((ea.argt & EDITCMD) && !ea.usefilter) + ea.do_ecmd_cmd = getargcmd(&ea.arg); + + /* + * Check for '|' to separate commands and '"' to start comments. + * Don't do this for ":read !cmd" and ":write !cmd". + */ + if ((ea.argt & TRLBAR) && !ea.usefilter) + separate_nextcmd(&ea); + + /* + * Check for to end a shell command. + * Also do this for ":read !cmd", ":write !cmd" and ":global". + * Any others? + */ + else if (ea.cmdidx == CMD_bang + || ea.cmdidx == CMD_global + || ea.cmdidx == CMD_vglobal + || ea.usefilter) { + for (p = ea.arg; *p; ++p) { + /* Remove one backslash before a newline, so that it's possible to + * pass a newline to the shell and also a newline that is preceded + * with a backslash. This makes it impossible to end a shell + * command in a backslash, but that doesn't appear useful. + * Halving the number of backslashes is incompatible with previous + * versions. */ + if (*p == '\\' && p[1] == '\n') + STRMOVE(p, p + 1); + else if (*p == '\n') { + ea.nextcmd = p + 1; + *p = NUL; + break; + } + } + } + + if ((ea.argt & DFLALL) && ea.addr_count == 0) { + ea.line1 = 1; + ea.line2 = curbuf->b_ml.ml_line_count; + } + + /* accept numbered register only when no count allowed (:put) */ + if ( (ea.argt & REGSTR) + && *ea.arg != NUL + /* Do not allow register = for user commands */ + && (!USER_CMDIDX(ea.cmdidx) || *ea.arg != '=') + && !((ea.argt & COUNT) && VIM_ISDIGIT(*ea.arg))) { + /* check these explicitly for a more specific error message */ + if (*ea.arg == '*' || *ea.arg == '+') { + errormsg = (char_u *)_(e_invalidreg); + goto doend; + } + if ( + valid_yank_reg(*ea.arg, (ea.cmdidx != CMD_put + && USER_CMDIDX(ea.cmdidx))) + ) { + ea.regname = *ea.arg++; + /* for '=' register: accept the rest of the line as an expression */ + if (ea.arg[-1] == '=' && ea.arg[0] != NUL) { + set_expr_line(vim_strsave(ea.arg)); + ea.arg += STRLEN(ea.arg); + } + ea.arg = skipwhite(ea.arg); + } + } + + /* + * Check for a count. When accepting a BUFNAME, don't use "123foo" as a + * count, it's a buffer name. + */ + if ((ea.argt & COUNT) && VIM_ISDIGIT(*ea.arg) + && (!(ea.argt & BUFNAME) || *(p = skipdigits(ea.arg)) == NUL + || vim_iswhite(*p))) { + n = getdigits(&ea.arg); + ea.arg = skipwhite(ea.arg); + if (n <= 0 && !ni && (ea.argt & ZEROR) == 0) { + errormsg = (char_u *)_(e_zerocount); + goto doend; + } + if (ea.argt & NOTADR) { /* e.g. :buffer 2, :sleep 3 */ + ea.line2 = n; + if (ea.addr_count == 0) + ea.addr_count = 1; + } else { + ea.line1 = ea.line2; + ea.line2 += n - 1; + ++ea.addr_count; + /* + * Be vi compatible: no error message for out of range. + */ + if (ea.line2 > curbuf->b_ml.ml_line_count) + ea.line2 = curbuf->b_ml.ml_line_count; + } + } + + /* + * Check for flags: 'l', 'p' and '#'. + */ + if (ea.argt & EXFLAGS) + get_flags(&ea); + /* no arguments allowed */ + if (!ni && !(ea.argt & EXTRA) && *ea.arg != NUL + && *ea.arg != '"' && (*ea.arg != '|' || (ea.argt & TRLBAR) == 0)) { + errormsg = (char_u *)_(e_trailing); + goto doend; + } + + if (!ni && (ea.argt & NEEDARG) && *ea.arg == NUL) { + errormsg = (char_u *)_(e_argreq); + goto doend; + } + + /* + * Skip the command when it's not going to be executed. + * The commands like :if, :endif, etc. always need to be executed. + * Also make an exception for commands that handle a trailing command + * themselves. + */ + if (ea.skip) { + switch (ea.cmdidx) { + /* commands that need evaluation */ + case CMD_while: + case CMD_endwhile: + case CMD_for: + case CMD_endfor: + case CMD_if: + case CMD_elseif: + case CMD_else: + case CMD_endif: + case CMD_try: + case CMD_catch: + case CMD_finally: + case CMD_endtry: + case CMD_function: + break; + + /* Commands that handle '|' themselves. Check: A command should + * either have the TRLBAR flag, appear in this list or appear in + * the list at ":help :bar". */ + case CMD_aboveleft: + case CMD_and: + case CMD_belowright: + case CMD_botright: + case CMD_browse: + case CMD_call: + case CMD_confirm: + case CMD_delfunction: + case CMD_djump: + case CMD_dlist: + case CMD_dsearch: + case CMD_dsplit: + case CMD_echo: + case CMD_echoerr: + case CMD_echomsg: + case CMD_echon: + case CMD_execute: + case CMD_help: + case CMD_hide: + case CMD_ijump: + case CMD_ilist: + case CMD_isearch: + case CMD_isplit: + case CMD_keepalt: + case CMD_keepjumps: + case CMD_keepmarks: + case CMD_keeppatterns: + case CMD_leftabove: + case CMD_let: + case CMD_lockmarks: + case CMD_lua: + case CMD_match: + case CMD_mzscheme: + case CMD_perl: + case CMD_psearch: + case CMD_python: + case CMD_py3: + case CMD_python3: + case CMD_return: + case CMD_rightbelow: + case CMD_ruby: + case CMD_silent: + case CMD_smagic: + case CMD_snomagic: + case CMD_substitute: + case CMD_syntax: + case CMD_tab: + case CMD_tcl: + case CMD_throw: + case CMD_tilde: + case CMD_topleft: + case CMD_unlet: + case CMD_verbose: + case CMD_vertical: + case CMD_wincmd: + break; + + default: goto doend; + } + } + + if (ea.argt & XFILE) { + if (expand_filename(&ea, cmdlinep, &errormsg) == FAIL) + goto doend; + } + + /* + * Accept buffer name. Cannot be used at the same time with a buffer + * number. Don't do this for a user command. + */ + if ((ea.argt & BUFNAME) && *ea.arg != NUL && ea.addr_count == 0 + && !USER_CMDIDX(ea.cmdidx) + ) { + /* + * :bdelete, :bwipeout and :bunload take several arguments, separated + * by spaces: find next space (skipping over escaped characters). + * The others take one argument: ignore trailing spaces. + */ + if (ea.cmdidx == CMD_bdelete || ea.cmdidx == CMD_bwipeout + || ea.cmdidx == CMD_bunload) + p = skiptowhite_esc(ea.arg); + else { + p = ea.arg + STRLEN(ea.arg); + while (p > ea.arg && vim_iswhite(p[-1])) + --p; + } + ea.line2 = buflist_findpat(ea.arg, p, (ea.argt & BUFUNL) != 0, + FALSE, FALSE); + if (ea.line2 < 0) /* failed */ + goto doend; + ea.addr_count = 1; + ea.arg = skipwhite(p); + } + + /* + * 6. switch on command name + * + * The "ea" structure holds the arguments that can be used. + */ + ea.cmdlinep = cmdlinep; + ea.getline = fgetline; + ea.cookie = cookie; + ea.cstack = cstack; + + if (USER_CMDIDX(ea.cmdidx)) { + /* + * Execute a user-defined command. + */ + do_ucmd(&ea); + } else { + /* + * Call the function to execute the command. + */ + ea.errmsg = NULL; + (cmdnames[ea.cmdidx].cmd_func)(&ea); + if (ea.errmsg != NULL) + errormsg = (char_u *)_(ea.errmsg); + } + + /* + * If the command just executed called do_cmdline(), any throw or ":return" + * or ":finish" encountered there must also check the cstack of the still + * active do_cmdline() that called this do_one_cmd(). Rethrow an uncaught + * exception, or reanimate a returned function or finished script file and + * return or finish it again. + */ + if (need_rethrow) + do_throw(cstack); + else if (check_cstack) { + if (source_finished(fgetline, cookie)) + do_finish(&ea, TRUE); + else if (getline_equal(fgetline, cookie, get_func_line) + && current_func_returned()) + do_return(&ea, TRUE, FALSE, NULL); + } + need_rethrow = check_cstack = FALSE; + +doend: + if (curwin->w_cursor.lnum == 0) /* can happen with zero line number */ + curwin->w_cursor.lnum = 1; + + if (errormsg != NULL && *errormsg != NUL && !did_emsg) { + if (sourcing) { + if (errormsg != IObuff) { + STRCPY(IObuff, errormsg); + errormsg = IObuff; + } + append_command(*cmdlinep); + } + emsg(errormsg); + } + do_errthrow(cstack, + (ea.cmdidx != CMD_SIZE + && !USER_CMDIDX(ea.cmdidx) + ) ? cmdnames[(int)ea.cmdidx].cmd_name : (char_u *)NULL); + + if (verbose_save >= 0) + p_verbose = verbose_save; + if (cmdmod.save_ei != NULL) { + /* Restore 'eventignore' to the value before ":noautocmd". */ + set_string_option_direct((char_u *)"ei", -1, cmdmod.save_ei, + OPT_FREE, SID_NONE); + free_string_option(cmdmod.save_ei); + } + + cmdmod = save_cmdmod; + + if (save_msg_silent != -1) { + /* messages could be enabled for a serious error, need to check if the + * counters don't become negative */ + if (!did_emsg || msg_silent > save_msg_silent) + msg_silent = save_msg_silent; + emsg_silent -= did_esilent; + if (emsg_silent < 0) + emsg_silent = 0; + /* Restore msg_scroll, it's set by file I/O commands, even when no + * message is actually displayed. */ + msg_scroll = save_msg_scroll; + + /* "silent reg" or "silent echo x" inside "redir" leaves msg_col + * somewhere in the line. Put it back in the first column. */ + if (redirecting()) + msg_col = 0; + } + +#ifdef HAVE_SANDBOX + if (did_sandbox) + --sandbox; +#endif + + if (ea.nextcmd && *ea.nextcmd == NUL) /* not really a next command */ + ea.nextcmd = NULL; + + --ex_nesting_level; + + return ea.nextcmd; +} + +/* + * Check for an Ex command with optional tail. + * If there is a match advance "pp" to the argument and return TRUE. + */ +int checkforcmd(pp, cmd, len) +char_u **pp; /* start of command */ +char *cmd; /* name of command */ +int len; /* required length */ +{ + int i; + + for (i = 0; cmd[i] != NUL; ++i) + if (((char_u *)cmd)[i] != (*pp)[i]) + break; + if (i >= len && !isalpha((*pp)[i])) { + *pp = skipwhite(*pp + i); + return TRUE; + } + return FALSE; +} + +/* + * Append "cmd" to the error message in IObuff. + * Takes care of limiting the length and handling 0xa0, which would be + * invisible otherwise. + */ +static void append_command(cmd) +char_u *cmd; +{ + char_u *s = cmd; + char_u *d; + + STRCAT(IObuff, ": "); + d = IObuff + STRLEN(IObuff); + while (*s != NUL && d - IObuff < IOSIZE - 7) { + if ( + enc_utf8 ? (s[0] == 0xc2 && s[1] == 0xa0) : + *s == 0xa0) { + s += + enc_utf8 ? 2 : + 1; + STRCPY(d, ""); + d += 4; + } else + MB_COPY_CHAR(s, d); + } + *d = NUL; +} + +/* + * Find an Ex command by its name, either built-in or user. + * Start of the name can be found at eap->cmd. + * Returns pointer to char after the command name. + * "full" is set to TRUE if the whole command name matched. + * Returns NULL for an ambiguous user command. + */ +static char_u * find_command(eap, full) +exarg_T *eap; +int *full UNUSED; +{ + int len; + char_u *p; + int i; + + /* + * Isolate the command and search for it in the command table. + * Exceptions: + * - the 'k' command can directly be followed by any character. + * - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' + * but :sre[wind] is another command, as are :scrip[tnames], + * :scs[cope], :sim[alt], :sig[ns] and :sil[ent]. + * - the "d" command can directly be followed by 'l' or 'p' flag. + */ + p = eap->cmd; + if (*p == 'k') { + eap->cmdidx = CMD_k; + ++p; + } else if (p[0] == 's' + && ((p[1] == 'c' && p[2] != 's' && p[2] != 'r' + && p[3] != 'i' && p[4] != 'p') + || p[1] == 'g' + || (p[1] == 'i' && p[2] != 'm' && p[2] != 'l' && p[2] != 'g') + || p[1] == 'I' + || (p[1] == 'r' && p[2] != 'e'))) { + eap->cmdidx = CMD_substitute; + ++p; + } else { + while (ASCII_ISALPHA(*p)) + ++p; + /* for python 3.x support ":py3", ":python3", ":py3file", etc. */ + if (eap->cmd[0] == 'p' && eap->cmd[1] == 'y') + while (ASCII_ISALNUM(*p)) + ++p; + + /* check for non-alpha command */ + if (p == eap->cmd && vim_strchr((char_u *)"@*!=><&~#", *p) != NULL) + ++p; + len = (int)(p - eap->cmd); + if (*eap->cmd == 'd' && (p[-1] == 'l' || p[-1] == 'p')) { + /* Check for ":dl", ":dell", etc. to ":deletel": that's + * :delete with the 'l' flag. Same for 'p'. */ + for (i = 0; i < len; ++i) + if (eap->cmd[i] != ((char_u *)"delete")[i]) + break; + if (i == len - 1) { + --len; + if (p[-1] == 'l') + eap->flags |= EXFLAG_LIST; + else + eap->flags |= EXFLAG_PRINT; + } + } + + if (ASCII_ISLOWER(*eap->cmd)) + eap->cmdidx = cmdidxs[CharOrdLow(*eap->cmd)]; + else + eap->cmdidx = cmdidxs[26]; + + for (; (int)eap->cmdidx < (int)CMD_SIZE; + eap->cmdidx = (cmdidx_T)((int)eap->cmdidx + 1)) + if (STRNCMP(cmdnames[(int)eap->cmdidx].cmd_name, (char *)eap->cmd, + (size_t)len) == 0) { + if (full != NULL + && cmdnames[(int)eap->cmdidx].cmd_name[len] == NUL) + *full = TRUE; + break; + } + + /* Look for a user defined command as a last resort. Let ":Print" be + * overruled by a user defined command. */ + if ((eap->cmdidx == CMD_SIZE || eap->cmdidx == CMD_Print) + && *eap->cmd >= 'A' && *eap->cmd <= 'Z') { + /* User defined commands may contain digits. */ + while (ASCII_ISALNUM(*p)) + ++p; + p = find_ucmd(eap, p, full, NULL, NULL); + } + if (p == eap->cmd) + eap->cmdidx = CMD_SIZE; + } + + return p; +} + +/* + * Search for a user command that matches "eap->cmd". + * Return cmdidx in "eap->cmdidx", flags in "eap->argt", idx in "eap->useridx". + * Return a pointer to just after the command. + * Return NULL if there is no matching command. + */ +static char_u * find_ucmd(eap, p, full, xp, compl) +exarg_T *eap; +char_u *p; /* end of the command (possibly including count) */ +int *full; /* set to TRUE for a full match */ +expand_T *xp; /* used for completion, NULL otherwise */ +int *compl; /* completion flags or NULL */ +{ + int len = (int)(p - eap->cmd); + int j, k, matchlen = 0; + ucmd_T *uc; + int found = FALSE; + int possible = FALSE; + char_u *cp, *np; /* Point into typed cmd and test name */ + garray_T *gap; + int amb_local = FALSE; /* Found ambiguous buffer-local command, + only full match global is accepted. */ + + /* + * Look for buffer-local user commands first, then global ones. + */ + gap = &curbuf->b_ucmds; + for (;; ) { + for (j = 0; j < gap->ga_len; ++j) { + uc = USER_CMD_GA(gap, j); + cp = eap->cmd; + np = uc->uc_name; + k = 0; + while (k < len && *np != NUL && *cp++ == *np++) + k++; + if (k == len || (*np == NUL && vim_isdigit(eap->cmd[k]))) { + /* If finding a second match, the command is ambiguous. But + * not if a buffer-local command wasn't a full match and a + * global command is a full match. */ + if (k == len && found && *np != NUL) { + if (gap == &ucmds) + return NULL; + amb_local = TRUE; + } + + if (!found || (k == len && *np == NUL)) { + /* If we matched up to a digit, then there could + * be another command including the digit that we + * should use instead. + */ + if (k == len) + found = TRUE; + else + possible = TRUE; + + if (gap == &ucmds) + eap->cmdidx = CMD_USER; + else + eap->cmdidx = CMD_USER_BUF; + eap->argt = (long)uc->uc_argt; + eap->useridx = j; + + if (compl != NULL) + *compl = uc->uc_compl; + if (xp != NULL) { + xp->xp_arg = uc->uc_compl_arg; + xp->xp_scriptID = uc->uc_scriptID; + } + /* Do not search for further abbreviations + * if this is an exact match. */ + matchlen = k; + if (k == len && *np == NUL) { + if (full != NULL) + *full = TRUE; + amb_local = FALSE; + break; + } + } + } + } + + /* Stop if we found a full match or searched all. */ + if (j < gap->ga_len || gap == &ucmds) + break; + gap = &ucmds; + } + + /* Only found ambiguous matches. */ + if (amb_local) { + if (xp != NULL) + xp->xp_context = EXPAND_UNSUCCESSFUL; + return NULL; + } + + /* The match we found may be followed immediately by a number. Move "p" + * back to point to it. */ + if (found || possible) + return p + (matchlen - len); + return p; +} + +static struct cmdmod { + char *name; + int minlen; + int has_count; /* :123verbose :3tab */ +} cmdmods[] = { + {"aboveleft", 3, FALSE}, + {"belowright", 3, FALSE}, + {"botright", 2, FALSE}, + {"browse", 3, FALSE}, + {"confirm", 4, FALSE}, + {"hide", 3, FALSE}, + {"keepalt", 5, FALSE}, + {"keepjumps", 5, FALSE}, + {"keepmarks", 3, FALSE}, + {"keeppatterns", 5, FALSE}, + {"leftabove", 5, FALSE}, + {"lockmarks", 3, FALSE}, + {"noautocmd", 3, FALSE}, + {"rightbelow", 6, FALSE}, + {"sandbox", 3, FALSE}, + {"silent", 3, FALSE}, + {"tab", 3, TRUE}, + {"topleft", 2, FALSE}, + {"unsilent", 3, FALSE}, + {"verbose", 4, TRUE}, + {"vertical", 4, FALSE}, +}; + +/* + * Return length of a command modifier (including optional count). + * Return zero when it's not a modifier. + */ +int modifier_len(cmd) +char_u *cmd; +{ + int i, j; + char_u *p = cmd; + + if (VIM_ISDIGIT(*cmd)) + p = skipwhite(skipdigits(cmd)); + for (i = 0; i < (int)(sizeof(cmdmods) / sizeof(struct cmdmod)); ++i) { + for (j = 0; p[j] != NUL; ++j) + if (p[j] != cmdmods[i].name[j]) + break; + if (!ASCII_ISALPHA(p[j]) && j >= cmdmods[i].minlen + && (p == cmd || cmdmods[i].has_count)) + return j + (int)(p - cmd); + } + return 0; +} + +/* + * Return > 0 if an Ex command "name" exists. + * Return 2 if there is an exact match. + * Return 3 if there is an ambiguous match. + */ +int cmd_exists(name) +char_u *name; +{ + exarg_T ea; + int full = FALSE; + int i; + int j; + char_u *p; + + /* Check command modifiers. */ + for (i = 0; i < (int)(sizeof(cmdmods) / sizeof(struct cmdmod)); ++i) { + for (j = 0; name[j] != NUL; ++j) + if (name[j] != cmdmods[i].name[j]) + break; + if (name[j] == NUL && j >= cmdmods[i].minlen) + return cmdmods[i].name[j] == NUL ? 2 : 1; + } + + /* Check built-in commands and user defined commands. + * For ":2match" and ":3match" we need to skip the number. */ + ea.cmd = (*name == '2' || *name == '3') ? name + 1 : name; + ea.cmdidx = (cmdidx_T)0; + p = find_command(&ea, &full); + if (p == NULL) + return 3; + if (vim_isdigit(*name) && ea.cmdidx != CMD_match) + return 0; + if (*skipwhite(p) != NUL) + return 0; /* trailing garbage */ + return ea.cmdidx == CMD_SIZE ? 0 : (full ? 2 : 1); +} + +/* + * This is all pretty much copied from do_one_cmd(), with all the extra stuff + * we don't need/want deleted. Maybe this could be done better if we didn't + * repeat all this stuff. The only problem is that they may not stay + * perfectly compatible with each other, but then the command line syntax + * probably won't change that much -- webb. + */ +char_u * set_one_cmd_context(xp, buff) +expand_T *xp; +char_u *buff; /* buffer for command string */ +{ + char_u *p; + char_u *cmd, *arg; + int len = 0; + exarg_T ea; + int compl = EXPAND_NOTHING; + int delim; + int forceit = FALSE; + int usefilter = FALSE; /* filter instead of file name */ + + ExpandInit(xp); + xp->xp_pattern = buff; + xp->xp_context = EXPAND_COMMANDS; /* Default until we get past command */ + ea.argt = 0; + + /* + * 2. skip comment lines and leading space, colons or bars + */ + for (cmd = buff; vim_strchr((char_u *)" \t:|", *cmd) != NULL; cmd++) + ; + xp->xp_pattern = cmd; + + if (*cmd == NUL) + return NULL; + if (*cmd == '"') { /* ignore comment lines */ + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + + /* + * 3. parse a range specifier of the form: addr [,addr] [;addr] .. + */ + cmd = skip_range(cmd, &xp->xp_context); + + /* + * 4. parse command + */ + xp->xp_pattern = cmd; + if (*cmd == NUL) + return NULL; + if (*cmd == '"') { + xp->xp_context = EXPAND_NOTHING; + return NULL; + } + + if (*cmd == '|' || *cmd == '\n') + return cmd + 1; /* There's another command */ + + /* + * Isolate the command and search for it in the command table. + * Exceptions: + * - the 'k' command can directly be followed by any character, but + * do accept "keepmarks", "keepalt" and "keepjumps". + * - the 's' command can be followed directly by 'c', 'g', 'i', 'I' or 'r' + */ + if (*cmd == 'k' && cmd[1] != 'e') { + ea.cmdidx = CMD_k; + p = cmd + 1; + } else { + p = cmd; + while (ASCII_ISALPHA(*p) || *p == '*') /* Allow * wild card */ + ++p; + /* check for non-alpha command */ + if (p == cmd && vim_strchr((char_u *)"@*!=><&~#", *p) != NULL) + ++p; + /* for python 3.x: ":py3*" commands completion */ + if (cmd[0] == 'p' && cmd[1] == 'y' && p == cmd + 2 && *p == '3') { + ++p; + while (ASCII_ISALPHA(*p) || *p == '*') + ++p; + } + len = (int)(p - cmd); + + if (len == 0) { + xp->xp_context = EXPAND_UNSUCCESSFUL; + return NULL; + } + for (ea.cmdidx = (cmdidx_T)0; (int)ea.cmdidx < (int)CMD_SIZE; + ea.cmdidx = (cmdidx_T)((int)ea.cmdidx + 1)) + if (STRNCMP(cmdnames[(int)ea.cmdidx].cmd_name, cmd, + (size_t)len) == 0) + break; + + if (cmd[0] >= 'A' && cmd[0] <= 'Z') + while (ASCII_ISALNUM(*p) || *p == '*') /* Allow * wild card */ + ++p; + } + + /* + * If the cursor is touching the command, and it ends in an alpha-numeric + * character, complete the command name. + */ + if (*p == NUL && ASCII_ISALNUM(p[-1])) + return NULL; + + if (ea.cmdidx == CMD_SIZE) { + if (*cmd == 's' && vim_strchr((char_u *)"cgriI", cmd[1]) != NULL) { + ea.cmdidx = CMD_substitute; + p = cmd + 1; + } else if (cmd[0] >= 'A' && cmd[0] <= 'Z') { + ea.cmd = cmd; + p = find_ucmd(&ea, p, NULL, xp, + &compl + ); + if (p == NULL) + ea.cmdidx = CMD_SIZE; /* ambiguous user command */ + } + } + if (ea.cmdidx == CMD_SIZE) { + /* Not still touching the command and it was an illegal one */ + xp->xp_context = EXPAND_UNSUCCESSFUL; + return NULL; + } + + xp->xp_context = EXPAND_NOTHING; /* Default now that we're past command */ + + if (*p == '!') { /* forced commands */ + forceit = TRUE; + ++p; + } + + /* + * 5. parse arguments + */ + if (!USER_CMDIDX(ea.cmdidx)) + ea.argt = (long)cmdnames[(int)ea.cmdidx].cmd_argt; + + arg = skipwhite(p); + + if (ea.cmdidx == CMD_write || ea.cmdidx == CMD_update) { + if (*arg == '>') { /* append */ + if (*++arg == '>') + ++arg; + arg = skipwhite(arg); + } else if (*arg == '!' && ea.cmdidx == CMD_write) { /* :w !filter */ + ++arg; + usefilter = TRUE; + } + } + + if (ea.cmdidx == CMD_read) { + usefilter = forceit; /* :r! filter if forced */ + if (*arg == '!') { /* :r !filter */ + ++arg; + usefilter = TRUE; + } + } + + if (ea.cmdidx == CMD_lshift || ea.cmdidx == CMD_rshift) { + while (*arg == *cmd) /* allow any number of '>' or '<' */ + ++arg; + arg = skipwhite(arg); + } + + /* Does command allow "+command"? */ + if ((ea.argt & EDITCMD) && !usefilter && *arg == '+') { + /* Check if we're in the +command */ + p = arg + 1; + arg = skip_cmd_arg(arg, FALSE); + + /* Still touching the command after '+'? */ + if (*arg == NUL) + return p; + + /* Skip space(s) after +command to get to the real argument */ + arg = skipwhite(arg); + } + + /* + * Check for '|' to separate commands and '"' to start comments. + * Don't do this for ":read !cmd" and ":write !cmd". + */ + if ((ea.argt & TRLBAR) && !usefilter) { + p = arg; + /* ":redir @" is not the start of a comment */ + if (ea.cmdidx == CMD_redir && p[0] == '@' && p[1] == '"') + p += 2; + while (*p) { + if (*p == Ctrl_V) { + if (p[1] != NUL) + ++p; + } else if ( (*p == '"' && !(ea.argt & NOTRLCOM)) + || *p == '|' || *p == '\n') { + if (*(p - 1) != '\\') { + if (*p == '|' || *p == '\n') + return p + 1; + return NULL; /* It's a comment */ + } + } + mb_ptr_adv(p); + } + } + + /* no arguments allowed */ + if (!(ea.argt & EXTRA) && *arg != NUL && + vim_strchr((char_u *)"|\"", *arg) == NULL) + return NULL; + + /* Find start of last argument (argument just before cursor): */ + p = buff; + xp->xp_pattern = p; + len = (int)STRLEN(buff); + while (*p && p < buff + len) { + if (*p == ' ' || *p == TAB) { + /* argument starts after a space */ + xp->xp_pattern = ++p; + } else { + if (*p == '\\' && *(p + 1) != NUL) + ++p; /* skip over escaped character */ + mb_ptr_adv(p); + } + } + + if (ea.argt & XFILE) { + int c; + int in_quote = FALSE; + char_u *bow = NULL; /* Beginning of word */ + + /* + * Allow spaces within back-quotes to count as part of the argument + * being expanded. + */ + xp->xp_pattern = skipwhite(arg); + p = xp->xp_pattern; + while (*p != NUL) { + if (has_mbyte) + c = mb_ptr2char(p); + else + c = *p; + if (c == '\\' && p[1] != NUL) + ++p; + else if (c == '`') { + if (!in_quote) { + xp->xp_pattern = p; + bow = p + 1; + } + in_quote = !in_quote; + } + /* An argument can contain just about everything, except + * characters that end the command and white space. */ + else if (c == '|' || c == '\n' || c == '"' || (vim_iswhite(c) +#ifdef SPACE_IN_FILENAME + && (!(ea.argt & NOSPC) || + usefilter) +#endif + )) { + len = 0; /* avoid getting stuck when space is in 'isfname' */ + while (*p != NUL) { + if (has_mbyte) + c = mb_ptr2char(p); + else + c = *p; + if (c == '`' || vim_isfilec_or_wc(c)) + break; + if (has_mbyte) + len = (*mb_ptr2len)(p); + else + len = 1; + mb_ptr_adv(p); + } + if (in_quote) + bow = p; + else + xp->xp_pattern = p; + p -= len; + } + mb_ptr_adv(p); + } + + /* + * If we are still inside the quotes, and we passed a space, just + * expand from there. + */ + if (bow != NULL && in_quote) + xp->xp_pattern = bow; + xp->xp_context = EXPAND_FILES; + + /* For a shell command more chars need to be escaped. */ + if (usefilter || ea.cmdidx == CMD_bang) { +#ifndef BACKSLASH_IN_FILENAME + xp->xp_shell = TRUE; +#endif + /* When still after the command name expand executables. */ + if (xp->xp_pattern == skipwhite(arg)) + xp->xp_context = EXPAND_SHELLCMD; + } + + /* Check for environment variable */ + if (*xp->xp_pattern == '$' + ) { + for (p = xp->xp_pattern + 1; *p != NUL; ++p) + if (!vim_isIDc(*p)) + break; + if (*p == NUL) { + xp->xp_context = EXPAND_ENV_VARS; + ++xp->xp_pattern; + /* Avoid that the assignment uses EXPAND_FILES again. */ + if (compl != EXPAND_USER_DEFINED && compl != EXPAND_USER_LIST) + compl = EXPAND_ENV_VARS; + } + } + /* Check for user names */ + if (*xp->xp_pattern == '~') { + for (p = xp->xp_pattern + 1; *p != NUL && *p != '/'; ++p) + ; + /* Complete ~user only if it partially matches a user name. + * A full match ~user will be replaced by user's home + * directory i.e. something like ~user -> /home/user/ */ + if (*p == NUL && p > xp->xp_pattern + 1 + && match_user(xp->xp_pattern + 1) == 1) { + xp->xp_context = EXPAND_USER; + ++xp->xp_pattern; + } + } + } + + /* + * 6. switch on command name + */ + switch (ea.cmdidx) { + case CMD_find: + case CMD_sfind: + case CMD_tabfind: + if (xp->xp_context == EXPAND_FILES) + xp->xp_context = EXPAND_FILES_IN_PATH; + break; + case CMD_cd: + case CMD_chdir: + case CMD_lcd: + case CMD_lchdir: + if (xp->xp_context == EXPAND_FILES) + xp->xp_context = EXPAND_DIRECTORIES; + break; + case CMD_help: + xp->xp_context = EXPAND_HELP; + xp->xp_pattern = arg; + break; + + /* Command modifiers: return the argument. + * Also for commands with an argument that is a command. */ + case CMD_aboveleft: + case CMD_argdo: + case CMD_belowright: + case CMD_botright: + case CMD_browse: + case CMD_bufdo: + case CMD_confirm: + case CMD_debug: + case CMD_folddoclosed: + case CMD_folddoopen: + case CMD_hide: + case CMD_keepalt: + case CMD_keepjumps: + case CMD_keepmarks: + case CMD_keeppatterns: + case CMD_leftabove: + case CMD_lockmarks: + case CMD_rightbelow: + case CMD_sandbox: + case CMD_silent: + case CMD_tab: + case CMD_tabdo: + case CMD_topleft: + case CMD_verbose: + case CMD_vertical: + case CMD_windo: + return arg; + + case CMD_match: + if (*arg == NUL || !ends_excmd(*arg)) { + /* also complete "None" */ + set_context_in_echohl_cmd(xp, arg); + arg = skipwhite(skiptowhite(arg)); + if (*arg != NUL) { + xp->xp_context = EXPAND_NOTHING; + arg = skip_regexp(arg + 1, *arg, p_magic, NULL); + } + } + return find_nextcmd(arg); + + /* + * All completion for the +cmdline_compl feature goes here. + */ + + case CMD_command: + /* Check for attributes */ + while (*arg == '-') { + arg++; /* Skip "-" */ + p = skiptowhite(arg); + if (*p == NUL) { + /* Cursor is still in the attribute */ + p = vim_strchr(arg, '='); + if (p == NULL) { + /* No "=", so complete attribute names */ + xp->xp_context = EXPAND_USER_CMD_FLAGS; + xp->xp_pattern = arg; + return NULL; + } + + /* For the -complete and -nargs attributes, we complete + * their arguments as well. + */ + if (STRNICMP(arg, "complete", p - arg) == 0) { + xp->xp_context = EXPAND_USER_COMPLETE; + xp->xp_pattern = p + 1; + return NULL; + } else if (STRNICMP(arg, "nargs", p - arg) == 0) { + xp->xp_context = EXPAND_USER_NARGS; + xp->xp_pattern = p + 1; + return NULL; + } + return NULL; + } + arg = skipwhite(p); + } + + /* After the attributes comes the new command name */ + p = skiptowhite(arg); + if (*p == NUL) { + xp->xp_context = EXPAND_USER_COMMANDS; + xp->xp_pattern = arg; + break; + } + + /* And finally comes a normal command */ + return skipwhite(p); + + case CMD_delcommand: + xp->xp_context = EXPAND_USER_COMMANDS; + xp->xp_pattern = arg; + break; + + case CMD_global: + case CMD_vglobal: + delim = *arg; /* get the delimiter */ + if (delim) + ++arg; /* skip delimiter if there is one */ + + while (arg[0] != NUL && arg[0] != delim) { + if (arg[0] == '\\' && arg[1] != NUL) + ++arg; + ++arg; + } + if (arg[0] != NUL) + return arg + 1; + break; + case CMD_and: + case CMD_substitute: + delim = *arg; + if (delim) { + /* skip "from" part */ + ++arg; + arg = skip_regexp(arg, delim, p_magic, NULL); + } + /* skip "to" part */ + while (arg[0] != NUL && arg[0] != delim) { + if (arg[0] == '\\' && arg[1] != NUL) + ++arg; + ++arg; + } + if (arg[0] != NUL) /* skip delimiter */ + ++arg; + while (arg[0] && vim_strchr((char_u *)"|\"#", arg[0]) == NULL) + ++arg; + if (arg[0] != NUL) + return arg; + break; + case CMD_isearch: + case CMD_dsearch: + case CMD_ilist: + case CMD_dlist: + case CMD_ijump: + case CMD_psearch: + case CMD_djump: + case CMD_isplit: + case CMD_dsplit: + arg = skipwhite(skipdigits(arg)); /* skip count */ + if (*arg == '/') { /* Match regexp, not just whole words */ + for (++arg; *arg && *arg != '/'; arg++) + if (*arg == '\\' && arg[1] != NUL) + arg++; + if (*arg) { + arg = skipwhite(arg + 1); + + /* Check for trailing illegal characters */ + if (*arg && vim_strchr((char_u *)"|\"\n", *arg) == NULL) + xp->xp_context = EXPAND_NOTHING; + else + return arg; + } + } + break; + case CMD_autocmd: + return set_context_in_autocmd(xp, arg, FALSE); + + case CMD_doautocmd: + case CMD_doautoall: + return set_context_in_autocmd(xp, arg, TRUE); + case CMD_set: + set_context_in_set_cmd(xp, arg, 0); + break; + case CMD_setglobal: + set_context_in_set_cmd(xp, arg, OPT_GLOBAL); + break; + case CMD_setlocal: + set_context_in_set_cmd(xp, arg, OPT_LOCAL); + break; + case CMD_tag: + case CMD_stag: + case CMD_ptag: + case CMD_ltag: + case CMD_tselect: + case CMD_stselect: + case CMD_ptselect: + case CMD_tjump: + case CMD_stjump: + case CMD_ptjump: + if (*p_wop != NUL) + xp->xp_context = EXPAND_TAGS_LISTFILES; + else + xp->xp_context = EXPAND_TAGS; + xp->xp_pattern = arg; + break; + case CMD_augroup: + xp->xp_context = EXPAND_AUGROUP; + xp->xp_pattern = arg; + break; + case CMD_syntax: + set_context_in_syntax_cmd(xp, arg); + break; + case CMD_let: + case CMD_if: + case CMD_elseif: + case CMD_while: + case CMD_for: + case CMD_echo: + case CMD_echon: + case CMD_execute: + case CMD_echomsg: + case CMD_echoerr: + case CMD_call: + case CMD_return: + set_context_for_expression(xp, arg, ea.cmdidx); + break; + + case CMD_unlet: + while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL) + arg = xp->xp_pattern + 1; + xp->xp_context = EXPAND_USER_VARS; + xp->xp_pattern = arg; + break; + + case CMD_function: + case CMD_delfunction: + xp->xp_context = EXPAND_USER_FUNC; + xp->xp_pattern = arg; + break; + + case CMD_echohl: + set_context_in_echohl_cmd(xp, arg); + break; + case CMD_highlight: + set_context_in_highlight_cmd(xp, arg); + break; + case CMD_cscope: + case CMD_lcscope: + case CMD_scscope: + set_context_in_cscope_cmd(xp, arg, ea.cmdidx); + break; + case CMD_bdelete: + case CMD_bwipeout: + case CMD_bunload: + while ((xp->xp_pattern = vim_strchr(arg, ' ')) != NULL) + arg = xp->xp_pattern + 1; + /*FALLTHROUGH*/ + case CMD_buffer: + case CMD_sbuffer: + case CMD_checktime: + xp->xp_context = EXPAND_BUFFERS; + xp->xp_pattern = arg; + break; + case CMD_USER: + case CMD_USER_BUF: + if (compl != EXPAND_NOTHING) { + /* XFILE: file names are handled above */ + if (!(ea.argt & XFILE)) { + if (compl == EXPAND_MENUS) + return set_context_in_menu_cmd(xp, cmd, arg, forceit); + if (compl == EXPAND_COMMANDS) + return arg; + if (compl == EXPAND_MAPPINGS) + return set_context_in_map_cmd(xp, (char_u *)"map", + arg, forceit, FALSE, FALSE, CMD_map); + /* Find start of last argument. */ + p = arg; + while (*p) { + if (*p == ' ') + /* argument starts after a space */ + arg = p + 1; + else if (*p == '\\' && *(p + 1) != NUL) + ++p; /* skip over escaped character */ + mb_ptr_adv(p); + } + xp->xp_pattern = arg; + } + xp->xp_context = compl; + } + break; + case CMD_map: case CMD_noremap: + case CMD_nmap: case CMD_nnoremap: + case CMD_vmap: case CMD_vnoremap: + case CMD_omap: case CMD_onoremap: + case CMD_imap: case CMD_inoremap: + case CMD_cmap: case CMD_cnoremap: + case CMD_lmap: case CMD_lnoremap: + case CMD_smap: case CMD_snoremap: + case CMD_xmap: case CMD_xnoremap: + return set_context_in_map_cmd(xp, cmd, arg, forceit, + FALSE, FALSE, ea.cmdidx); + case CMD_unmap: + case CMD_nunmap: + case CMD_vunmap: + case CMD_ounmap: + case CMD_iunmap: + case CMD_cunmap: + case CMD_lunmap: + case CMD_sunmap: + case CMD_xunmap: + return set_context_in_map_cmd(xp, cmd, arg, forceit, + FALSE, TRUE, ea.cmdidx); + case CMD_abbreviate: case CMD_noreabbrev: + case CMD_cabbrev: case CMD_cnoreabbrev: + case CMD_iabbrev: case CMD_inoreabbrev: + return set_context_in_map_cmd(xp, cmd, arg, forceit, + TRUE, FALSE, ea.cmdidx); + case CMD_unabbreviate: + case CMD_cunabbrev: + case CMD_iunabbrev: + return set_context_in_map_cmd(xp, cmd, arg, forceit, + TRUE, TRUE, ea.cmdidx); + case CMD_menu: case CMD_noremenu: case CMD_unmenu: + case CMD_amenu: case CMD_anoremenu: case CMD_aunmenu: + case CMD_nmenu: case CMD_nnoremenu: case CMD_nunmenu: + case CMD_vmenu: case CMD_vnoremenu: case CMD_vunmenu: + case CMD_omenu: case CMD_onoremenu: case CMD_ounmenu: + case CMD_imenu: case CMD_inoremenu: case CMD_iunmenu: + case CMD_cmenu: case CMD_cnoremenu: case CMD_cunmenu: + case CMD_tmenu: case CMD_tunmenu: + case CMD_popup: case CMD_tearoff: case CMD_emenu: + return set_context_in_menu_cmd(xp, cmd, arg, forceit); + + case CMD_colorscheme: + xp->xp_context = EXPAND_COLORS; + xp->xp_pattern = arg; + break; + + case CMD_compiler: + xp->xp_context = EXPAND_COMPILER; + xp->xp_pattern = arg; + break; + + case CMD_ownsyntax: + xp->xp_context = EXPAND_OWNSYNTAX; + xp->xp_pattern = arg; + break; + + case CMD_setfiletype: + xp->xp_context = EXPAND_FILETYPE; + xp->xp_pattern = arg; + break; + +#if (defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ + && (defined(FEAT_GETTEXT) || defined(FEAT_MBYTE)) + case CMD_language: + p = skiptowhite(arg); + if (*p == NUL) { + xp->xp_context = EXPAND_LANGUAGE; + xp->xp_pattern = arg; + } else { + if ( STRNCMP(arg, "messages", p - arg) == 0 + || STRNCMP(arg, "ctype", p - arg) == 0 + || STRNCMP(arg, "time", p - arg) == 0) { + xp->xp_context = EXPAND_LOCALES; + xp->xp_pattern = skipwhite(p); + } else + xp->xp_context = EXPAND_NOTHING; + } + break; +#endif + case CMD_profile: + set_context_in_profile_cmd(xp, arg); + break; + case CMD_behave: + xp->xp_context = EXPAND_BEHAVE; + xp->xp_pattern = arg; + break; + + case CMD_history: + xp->xp_context = EXPAND_HISTORY; + xp->xp_pattern = arg; + break; + case CMD_syntime: + xp->xp_context = EXPAND_SYNTIME; + xp->xp_pattern = arg; + break; + + + default: + break; + } + return NULL; +} + +/* + * skip a range specifier of the form: addr [,addr] [;addr] .. + * + * Backslashed delimiters after / or ? will be skipped, and commands will + * not be expanded between /'s and ?'s or after "'". + * + * Also skip white space and ":" characters. + * Returns the "cmd" pointer advanced to beyond the range. + */ +char_u * skip_range(cmd, ctx) +char_u *cmd; +int *ctx; /* pointer to xp_context or NULL */ +{ + unsigned delim; + + while (vim_strchr((char_u *)" \t0123456789.$%'/?-+,;", *cmd) != NULL) { + if (*cmd == '\'') { + if (*++cmd == NUL && ctx != NULL) + *ctx = EXPAND_NOTHING; + } else if (*cmd == '/' || *cmd == '?') { + delim = *cmd++; + while (*cmd != NUL && *cmd != delim) + if (*cmd++ == '\\' && *cmd != NUL) + ++cmd; + if (*cmd == NUL && ctx != NULL) + *ctx = EXPAND_NOTHING; + } + if (*cmd != NUL) + ++cmd; + } + + /* Skip ":" and white space. */ + while (*cmd == ':') + cmd = skipwhite(cmd + 1); + + return cmd; +} + +/* + * get a single EX address + * + * Set ptr to the next character after the part that was interpreted. + * Set ptr to NULL when an error is encountered. + * + * Return MAXLNUM when no Ex address was found. + */ +static linenr_T get_address(ptr, skip, to_other_file) +char_u **ptr; +int skip; /* only skip the address, don't use it */ +int to_other_file; /* flag: may jump to other file */ +{ + int c; + int i; + long n; + char_u *cmd; + pos_T pos; + pos_T *fp; + linenr_T lnum; + + cmd = skipwhite(*ptr); + lnum = MAXLNUM; + do { + switch (*cmd) { + case '.': /* '.' - Cursor position */ + ++cmd; + lnum = curwin->w_cursor.lnum; + break; + + case '$': /* '$' - last line */ + ++cmd; + lnum = curbuf->b_ml.ml_line_count; + break; + + case '\'': /* ''' - mark */ + if (*++cmd == NUL) { + cmd = NULL; + goto error; + } + if (skip) + ++cmd; + else { + /* Only accept a mark in another file when it is + * used by itself: ":'M". */ + fp = getmark(*cmd, to_other_file && cmd[1] == NUL); + ++cmd; + if (fp == (pos_T *)-1) + /* Jumped to another file. */ + lnum = curwin->w_cursor.lnum; + else { + if (check_mark(fp) == FAIL) { + cmd = NULL; + goto error; + } + lnum = fp->lnum; + } + } + break; + + case '/': + case '?': /* '/' or '?' - search */ + c = *cmd++; + if (skip) { /* skip "/pat/" */ + cmd = skip_regexp(cmd, c, (int)p_magic, NULL); + if (*cmd == c) + ++cmd; + } else { + pos = curwin->w_cursor; /* save curwin->w_cursor */ + /* + * When '/' or '?' follows another address, start + * from there. + */ + if (lnum != MAXLNUM) + curwin->w_cursor.lnum = lnum; + /* + * Start a forward search at the end of the line. + * Start a backward search at the start of the line. + * This makes sure we never match in the current + * line, and can match anywhere in the + * next/previous line. + */ + if (c == '/') + curwin->w_cursor.col = MAXCOL; + else + curwin->w_cursor.col = 0; + searchcmdlen = 0; + if (!do_search(NULL, c, cmd, 1L, + SEARCH_HIS | SEARCH_MSG, NULL)) { + curwin->w_cursor = pos; + cmd = NULL; + goto error; + } + lnum = curwin->w_cursor.lnum; + curwin->w_cursor = pos; + /* adjust command string pointer */ + cmd += searchcmdlen; + } + break; + + case '\\': /* "\?", "\/" or "\&", repeat search */ + ++cmd; + if (*cmd == '&') + i = RE_SUBST; + else if (*cmd == '?' || *cmd == '/') + i = RE_SEARCH; + else { + EMSG(_(e_backslash)); + cmd = NULL; + goto error; + } + + if (!skip) { + /* + * When search follows another address, start from + * there. + */ + if (lnum != MAXLNUM) + pos.lnum = lnum; + else + pos.lnum = curwin->w_cursor.lnum; + + /* + * Start the search just like for the above + * do_search(). + */ + if (*cmd != '?') + pos.col = MAXCOL; + else + pos.col = 0; + if (searchit(curwin, curbuf, &pos, + *cmd == '?' ? BACKWARD : FORWARD, + (char_u *)"", 1L, SEARCH_MSG, + i, (linenr_T)0, NULL) != FAIL) + lnum = pos.lnum; + else { + cmd = NULL; + goto error; + } + } + ++cmd; + break; + + default: + if (VIM_ISDIGIT(*cmd)) /* absolute line number */ + lnum = getdigits(&cmd); + } + + for (;; ) { + cmd = skipwhite(cmd); + if (*cmd != '-' && *cmd != '+' && !VIM_ISDIGIT(*cmd)) + break; + + if (lnum == MAXLNUM) + lnum = curwin->w_cursor.lnum; /* "+1" is same as ".+1" */ + if (VIM_ISDIGIT(*cmd)) + i = '+'; /* "number" is same as "+number" */ + else + i = *cmd++; + if (!VIM_ISDIGIT(*cmd)) /* '+' is '+1', but '+0' is not '+1' */ + n = 1; + else + n = getdigits(&cmd); + if (i == '-') + lnum -= n; + else + lnum += n; + } + } while (*cmd == '/' || *cmd == '?'); + +error: + *ptr = cmd; + return lnum; +} + +/* + * Get flags from an Ex command argument. + */ +static void get_flags(eap) +exarg_T *eap; +{ + while (vim_strchr((char_u *)"lp#", *eap->arg) != NULL) { + if (*eap->arg == 'l') + eap->flags |= EXFLAG_LIST; + else if (*eap->arg == 'p') + eap->flags |= EXFLAG_PRINT; + else + eap->flags |= EXFLAG_NR; + eap->arg = skipwhite(eap->arg + 1); + } +} + +/* + * Function called for command which is Not Implemented. NI! + */ +void ex_ni(eap) +exarg_T *eap; +{ + if (!eap->skip) + eap->errmsg = (char_u *)N_( + "E319: Sorry, the command is not available in this version"); +} + +#ifdef HAVE_EX_SCRIPT_NI +/* + * Function called for script command which is Not Implemented. NI! + * Skips over ":perl <skip) + ex_ni(eap); + else + vim_free(script_get(eap, eap->arg)); +} +#endif + +/* + * Check range in Ex command for validity. + * Return NULL when valid, error message when invalid. + */ +static char_u * invalid_range(eap) +exarg_T *eap; +{ + if ( eap->line1 < 0 + || eap->line2 < 0 + || eap->line1 > eap->line2 + || ((eap->argt & RANGE) + && !(eap->argt & NOTADR) + && eap->line2 > curbuf->b_ml.ml_line_count + + (eap->cmdidx == CMD_diffget) + )) + return (char_u *)_(e_invrange); + return NULL; +} + +/* + * Correct the range for zero line number, if required. + */ +static void correct_range(eap) +exarg_T *eap; +{ + if (!(eap->argt & ZEROR)) { /* zero in range not allowed */ + if (eap->line1 == 0) + eap->line1 = 1; + if (eap->line2 == 0) + eap->line2 = 1; + } +} + +static char_u *skip_grep_pat __ARGS((exarg_T *eap)); + +/* + * For a ":vimgrep" or ":vimgrepadd" command return a pointer past the + * pattern. Otherwise return eap->arg. + */ +static char_u * skip_grep_pat(eap) +exarg_T *eap; +{ + char_u *p = eap->arg; + + if (*p != NUL && (eap->cmdidx == CMD_vimgrep || eap->cmdidx == CMD_lvimgrep + || eap->cmdidx == CMD_vimgrepadd + || eap->cmdidx == CMD_lvimgrepadd + || grep_internal(eap->cmdidx))) { + p = skip_vimgrep_pat(p, NULL, NULL); + if (p == NULL) + p = eap->arg; + } + return p; +} + +/* + * For the ":make" and ":grep" commands insert the 'makeprg'/'grepprg' option + * in the command line, so that things like % get expanded. + */ +static char_u * replace_makeprg(eap, p, cmdlinep) +exarg_T *eap; +char_u *p; +char_u **cmdlinep; +{ + char_u *new_cmdline; + char_u *program; + char_u *pos; + char_u *ptr; + int len; + int i; + + /* + * Don't do it when ":vimgrep" is used for ":grep". + */ + if ((eap->cmdidx == CMD_make || eap->cmdidx == CMD_lmake + || eap->cmdidx == CMD_grep || eap->cmdidx == CMD_lgrep + || eap->cmdidx == CMD_grepadd + || eap->cmdidx == CMD_lgrepadd) + && !grep_internal(eap->cmdidx)) { + if (eap->cmdidx == CMD_grep || eap->cmdidx == CMD_lgrep + || eap->cmdidx == CMD_grepadd || eap->cmdidx == CMD_lgrepadd) { + if (*curbuf->b_p_gp == NUL) + program = p_gp; + else + program = curbuf->b_p_gp; + } else { + if (*curbuf->b_p_mp == NUL) + program = p_mp; + else + program = curbuf->b_p_mp; + } + + p = skipwhite(p); + + if ((pos = (char_u *)strstr((char *)program, "$*")) != NULL) { + /* replace $* by given arguments */ + i = 1; + while ((pos = (char_u *)strstr((char *)pos + 2, "$*")) != NULL) + ++i; + len = (int)STRLEN(p); + new_cmdline = alloc((int)(STRLEN(program) + i * (len - 2) + 1)); + if (new_cmdline == NULL) + return NULL; /* out of memory */ + ptr = new_cmdline; + while ((pos = (char_u *)strstr((char *)program, "$*")) != NULL) { + i = (int)(pos - program); + STRNCPY(ptr, program, i); + STRCPY(ptr += i, p); + ptr += len; + program = pos + 2; + } + STRCPY(ptr, program); + } else { + new_cmdline = alloc((int)(STRLEN(program) + STRLEN(p) + 2)); + if (new_cmdline == NULL) + return NULL; /* out of memory */ + STRCPY(new_cmdline, program); + STRCAT(new_cmdline, " "); + STRCAT(new_cmdline, p); + } + msg_make(p); + + /* 'eap->cmd' is not set here, because it is not used at CMD_make */ + vim_free(*cmdlinep); + *cmdlinep = new_cmdline; + p = new_cmdline; + } + return p; +} + +/* + * Expand file name in Ex command argument. + * Return FAIL for failure, OK otherwise. + */ +int expand_filename(eap, cmdlinep, errormsgp) +exarg_T *eap; +char_u **cmdlinep; +char_u **errormsgp; +{ + int has_wildcards; /* need to expand wildcards */ + char_u *repl; + int srclen; + char_u *p; + int n; + int escaped; + + /* Skip a regexp pattern for ":vimgrep[add] pat file..." */ + p = skip_grep_pat(eap); + + /* + * Decide to expand wildcards *before* replacing '%', '#', etc. If + * the file name contains a wildcard it should not cause expanding. + * (it will be expanded anyway if there is a wildcard before replacing). + */ + has_wildcards = mch_has_wildcard(p); + while (*p != NUL) { + /* Skip over `=expr`, wildcards in it are not expanded. */ + if (p[0] == '`' && p[1] == '=') { + p += 2; + (void)skip_expr(&p); + if (*p == '`') + ++p; + continue; + } + /* + * Quick check if this cannot be the start of a special string. + * Also removes backslash before '%', '#' and '<'. + */ + if (vim_strchr((char_u *)"%#<", *p) == NULL) { + ++p; + continue; + } + + /* + * Try to find a match at this position. + */ + repl = eval_vars(p, eap->arg, &srclen, &(eap->do_ecmd_lnum), + errormsgp, &escaped); + if (*errormsgp != NULL) /* error detected */ + return FAIL; + if (repl == NULL) { /* no match found */ + p += srclen; + continue; + } + + /* Wildcards won't be expanded below, the replacement is taken + * literally. But do expand "~/file", "~user/file" and "$HOME/file". */ + if (vim_strchr(repl, '$') != NULL || vim_strchr(repl, '~') != NULL) { + char_u *l = repl; + + repl = expand_env_save(repl); + vim_free(l); + } + + /* Need to escape white space et al. with a backslash. + * Don't do this for: + * - replacement that already has been escaped: "##" + * - shell commands (may have to use quotes instead). + * - non-unix systems when there is a single argument (spaces don't + * separate arguments then). + */ + if (!eap->usefilter + && !escaped + && eap->cmdidx != CMD_bang + && eap->cmdidx != CMD_make + && eap->cmdidx != CMD_lmake + && eap->cmdidx != CMD_grep + && eap->cmdidx != CMD_lgrep + && eap->cmdidx != CMD_grepadd + && eap->cmdidx != CMD_lgrepadd +#ifndef UNIX + && !(eap->argt & NOSPC) +#endif + ) { + char_u *l; +#ifdef BACKSLASH_IN_FILENAME + /* Don't escape a backslash here, because rem_backslash() doesn't + * remove it later. */ + static char_u *nobslash = (char_u *)" \t\"|"; +# define ESCAPE_CHARS nobslash +#else +# define ESCAPE_CHARS escape_chars +#endif + + for (l = repl; *l; ++l) + if (vim_strchr(ESCAPE_CHARS, *l) != NULL) { + l = vim_strsave_escaped(repl, ESCAPE_CHARS); + if (l != NULL) { + vim_free(repl); + repl = l; + } + break; + } + } + + /* For a shell command a '!' must be escaped. */ + if ((eap->usefilter || eap->cmdidx == CMD_bang) + && vim_strpbrk(repl, (char_u *)"!&;()<>") != NULL) { + char_u *l; + + l = vim_strsave_escaped(repl, (char_u *)"!&;()<>"); + if (l != NULL) { + vim_free(repl); + repl = l; + /* For a sh-like shell escape "!" another time. */ + if (strstr((char *)p_sh, "sh") != NULL) { + l = vim_strsave_escaped(repl, (char_u *)"!"); + if (l != NULL) { + vim_free(repl); + repl = l; + } + } + } + } + + p = repl_cmdline(eap, p, srclen, repl, cmdlinep); + vim_free(repl); + if (p == NULL) + return FAIL; + } + + /* + * One file argument: Expand wildcards. + * Don't do this with ":r !command" or ":w !command". + */ + if ((eap->argt & NOSPC) && !eap->usefilter) { + /* + * May do this twice: + * 1. Replace environment variables. + * 2. Replace any other wildcards, remove backslashes. + */ + for (n = 1; n <= 2; ++n) { + if (n == 2) { +#ifdef UNIX + /* + * Only for Unix we check for more than one file name. + * For other systems spaces are considered to be part + * of the file name. + * Only check here if there is no wildcard, otherwise + * ExpandOne() will check for errors. This allows + * ":e `ls ve*.c`" on Unix. + */ + if (!has_wildcards) + for (p = eap->arg; *p; ++p) { + /* skip escaped characters */ + if (p[1] && (*p == '\\' || *p == Ctrl_V)) + ++p; + else if (vim_iswhite(*p)) { + *errormsgp = (char_u *)_("E172: Only one file name allowed"); + return FAIL; + } + } +#endif + + /* + * Halve the number of backslashes (this is Vi compatible). + * For Unix and OS/2, when wildcards are expanded, this is + * done by ExpandOne() below. + */ +#if defined(UNIX) || defined(OS2) + if (!has_wildcards) +#endif + backslash_halve(eap->arg); + } + + if (has_wildcards) { + if (n == 1) { + /* + * First loop: May expand environment variables. This + * can be done much faster with expand_env() than with + * something else (e.g., calling a shell). + * After expanding environment variables, check again + * if there are still wildcards present. + */ + if (vim_strchr(eap->arg, '$') != NULL + || vim_strchr(eap->arg, '~') != NULL) { + expand_env_esc(eap->arg, NameBuff, MAXPATHL, + TRUE, TRUE, NULL); + has_wildcards = mch_has_wildcard(NameBuff); + p = NameBuff; + } else + p = NULL; + } else { /* n == 2 */ + expand_T xpc; + int options = WILD_LIST_NOTFOUND|WILD_ADD_SLASH; + + ExpandInit(&xpc); + xpc.xp_context = EXPAND_FILES; + if (p_wic) + options += WILD_ICASE; + p = ExpandOne(&xpc, eap->arg, NULL, + options, WILD_EXPAND_FREE); + if (p == NULL) + return FAIL; + } + if (p != NULL) { + (void)repl_cmdline(eap, eap->arg, (int)STRLEN(eap->arg), + p, cmdlinep); + if (n == 2) /* p came from ExpandOne() */ + vim_free(p); + } + } + } + } + return OK; +} + +/* + * Replace part of the command line, keeping eap->cmd, eap->arg and + * eap->nextcmd correct. + * "src" points to the part that is to be replaced, of length "srclen". + * "repl" is the replacement string. + * Returns a pointer to the character after the replaced string. + * Returns NULL for failure. + */ +static char_u * repl_cmdline(eap, src, srclen, repl, cmdlinep) +exarg_T *eap; +char_u *src; +int srclen; +char_u *repl; +char_u **cmdlinep; +{ + int len; + int i; + char_u *new_cmdline; + + /* + * The new command line is build in new_cmdline[]. + * First allocate it. + * Careful: a "+cmd" argument may have been NUL terminated. + */ + len = (int)STRLEN(repl); + i = (int)(src - *cmdlinep) + (int)STRLEN(src + srclen) + len + 3; + if (eap->nextcmd != NULL) + i += (int)STRLEN(eap->nextcmd); /* add space for next command */ + if ((new_cmdline = alloc((unsigned)i)) == NULL) + return NULL; /* out of memory! */ + + /* + * Copy the stuff before the expanded part. + * Copy the expanded stuff. + * Copy what came after the expanded part. + * Copy the next commands, if there are any. + */ + i = (int)(src - *cmdlinep); /* length of part before match */ + mch_memmove(new_cmdline, *cmdlinep, (size_t)i); + + mch_memmove(new_cmdline + i, repl, (size_t)len); + i += len; /* remember the end of the string */ + STRCPY(new_cmdline + i, src + srclen); + src = new_cmdline + i; /* remember where to continue */ + + if (eap->nextcmd != NULL) { /* append next command */ + i = (int)STRLEN(new_cmdline) + 1; + STRCPY(new_cmdline + i, eap->nextcmd); + eap->nextcmd = new_cmdline + i; + } + eap->cmd = new_cmdline + (eap->cmd - *cmdlinep); + eap->arg = new_cmdline + (eap->arg - *cmdlinep); + if (eap->do_ecmd_cmd != NULL && eap->do_ecmd_cmd != dollar_command) + eap->do_ecmd_cmd = new_cmdline + (eap->do_ecmd_cmd - *cmdlinep); + vim_free(*cmdlinep); + *cmdlinep = new_cmdline; + + return src; +} + +/* + * Check for '|' to separate commands and '"' to start comments. + */ +void separate_nextcmd(eap) +exarg_T *eap; +{ + char_u *p; + + p = skip_grep_pat(eap); + + for (; *p; mb_ptr_adv(p)) { + if (*p == Ctrl_V) { + if (eap->argt & (USECTRLV | XFILE)) + ++p; /* skip CTRL-V and next char */ + else + /* remove CTRL-V and skip next char */ + STRMOVE(p, p + 1); + if (*p == NUL) /* stop at NUL after CTRL-V */ + break; + } + /* Skip over `=expr` when wildcards are expanded. */ + else if (p[0] == '`' && p[1] == '=' && (eap->argt & XFILE)) { + p += 2; + (void)skip_expr(&p); + } + /* Check for '"': start of comment or '|': next command */ + /* :@" and :*" do not start a comment! + * :redir @" doesn't either. */ + else if ((*p == '"' && !(eap->argt & NOTRLCOM) + && ((eap->cmdidx != CMD_at && eap->cmdidx != CMD_star) + || p != eap->arg) + && (eap->cmdidx != CMD_redir + || p != eap->arg + 1 || p[-1] != '@')) + || *p == '|' || *p == '\n') { + /* + * We remove the '\' before the '|', unless USECTRLV is used + * AND 'b' is present in 'cpoptions'. + */ + if ((vim_strchr(p_cpo, CPO_BAR) == NULL + || !(eap->argt & USECTRLV)) && *(p - 1) == '\\') { + STRMOVE(p - 1, p); /* remove the '\' */ + --p; + } else { + eap->nextcmd = check_nextcmd(p); + *p = NUL; + break; + } + } + } + + if (!(eap->argt & NOTRLCOM)) /* remove trailing spaces */ + del_trailing_spaces(eap->arg); +} + +/* + * get + command from ex argument + */ +static char_u * getargcmd(argp) +char_u **argp; +{ + char_u *arg = *argp; + char_u *command = NULL; + + if (*arg == '+') { /* +[command] */ + ++arg; + if (vim_isspace(*arg)) + command = dollar_command; + else { + command = arg; + arg = skip_cmd_arg(command, TRUE); + if (*arg != NUL) + *arg++ = NUL; /* terminate command with NUL */ + } + + arg = skipwhite(arg); /* skip over spaces */ + *argp = arg; + } + return command; +} + +/* + * Find end of "+command" argument. Skip over "\ " and "\\". + */ +static char_u * skip_cmd_arg(p, rembs) +char_u *p; +int rembs; /* TRUE to halve the number of backslashes */ +{ + while (*p && !vim_isspace(*p)) { + if (*p == '\\' && p[1] != NUL) { + if (rembs) + STRMOVE(p, p + 1); + else + ++p; + } + mb_ptr_adv(p); + } + return p; +} + +/* + * Get "++opt=arg" argument. + * Return FAIL or OK. + */ +static int getargopt(eap) +exarg_T *eap; +{ + char_u *arg = eap->arg + 2; + int *pp = NULL; + int bad_char_idx; + char_u *p; + + /* ":edit ++[no]bin[ary] file" */ + if (STRNCMP(arg, "bin", 3) == 0 || STRNCMP(arg, "nobin", 5) == 0) { + if (*arg == 'n') { + arg += 2; + eap->force_bin = FORCE_NOBIN; + } else + eap->force_bin = FORCE_BIN; + if (!checkforcmd(&arg, "binary", 3)) + return FAIL; + eap->arg = skipwhite(arg); + return OK; + } + + /* ":read ++edit file" */ + if (STRNCMP(arg, "edit", 4) == 0) { + eap->read_edit = TRUE; + eap->arg = skipwhite(arg + 4); + return OK; + } + + if (STRNCMP(arg, "ff", 2) == 0) { + arg += 2; + pp = &eap->force_ff; + } else if (STRNCMP(arg, "fileformat", 10) == 0) { + arg += 10; + pp = &eap->force_ff; + } else if (STRNCMP(arg, "enc", 3) == 0) { + if (STRNCMP(arg, "encoding", 8) == 0) + arg += 8; + else + arg += 3; + pp = &eap->force_enc; + } else if (STRNCMP(arg, "bad", 3) == 0) { + arg += 3; + pp = &bad_char_idx; + } + + if (pp == NULL || *arg != '=') + return FAIL; + + ++arg; + *pp = (int)(arg - eap->cmd); + arg = skip_cmd_arg(arg, FALSE); + eap->arg = skipwhite(arg); + *arg = NUL; + + if (pp == &eap->force_ff) { + if (check_ff_value(eap->cmd + eap->force_ff) == FAIL) + return FAIL; + } else if (pp == &eap->force_enc) { + /* Make 'fileencoding' lower case. */ + for (p = eap->cmd + eap->force_enc; *p != NUL; ++p) + *p = TOLOWER_ASC(*p); + } else { + /* Check ++bad= argument. Must be a single-byte character, "keep" or + * "drop". */ + p = eap->cmd + bad_char_idx; + if (STRICMP(p, "keep") == 0) + eap->bad_char = BAD_KEEP; + else if (STRICMP(p, "drop") == 0) + eap->bad_char = BAD_DROP; + else if (MB_BYTE2LEN(*p) == 1 && p[1] == NUL) + eap->bad_char = *p; + else + return FAIL; + } + + return OK; +} + +/* + * ":abbreviate" and friends. + */ +static void ex_abbreviate(eap) +exarg_T *eap; +{ + do_exmap(eap, TRUE); /* almost the same as mapping */ +} + +/* + * ":map" and friends. + */ +static void ex_map(eap) +exarg_T *eap; +{ + /* + * If we are sourcing .exrc or .vimrc in current directory we + * print the mappings for security reasons. + */ + if (secure) { + secure = 2; + msg_outtrans(eap->cmd); + msg_putchar('\n'); + } + do_exmap(eap, FALSE); +} + +/* + * ":unmap" and friends. + */ +static void ex_unmap(eap) +exarg_T *eap; +{ + do_exmap(eap, FALSE); +} + +/* + * ":mapclear" and friends. + */ +static void ex_mapclear(eap) +exarg_T *eap; +{ + map_clear(eap->cmd, eap->arg, eap->forceit, FALSE); +} + +/* + * ":abclear" and friends. + */ +static void ex_abclear(eap) +exarg_T *eap; +{ + map_clear(eap->cmd, eap->arg, TRUE, TRUE); +} + +static void ex_autocmd(eap) +exarg_T *eap; +{ + /* + * Disallow auto commands from .exrc and .vimrc in current + * directory for security reasons. + */ + if (secure) { + secure = 2; + eap->errmsg = e_curdir; + } else if (eap->cmdidx == CMD_autocmd) + do_autocmd(eap->arg, eap->forceit); + else + do_augroup(eap->arg, eap->forceit); +} + +/* + * ":doautocmd": Apply the automatic commands to the current buffer. + */ +static void ex_doautocmd(eap) +exarg_T *eap; +{ + char_u *arg = eap->arg; + int call_do_modelines = check_nomodeline(&arg); + + (void)do_doautocmd(arg, TRUE); + if (call_do_modelines) /* Only when there is no . */ + do_modelines(0); +} + +/* + * :[N]bunload[!] [N] [bufname] unload buffer + * :[N]bdelete[!] [N] [bufname] delete buffer from buffer list + * :[N]bwipeout[!] [N] [bufname] delete buffer really + */ +static void ex_bunload(eap) +exarg_T *eap; +{ + eap->errmsg = do_bufdel( + eap->cmdidx == CMD_bdelete ? DOBUF_DEL + : eap->cmdidx == CMD_bwipeout ? DOBUF_WIPE + : DOBUF_UNLOAD, eap->arg, + eap->addr_count, (int)eap->line1, (int)eap->line2, eap->forceit); +} + +/* + * :[N]buffer [N] to buffer N + * :[N]sbuffer [N] to buffer N + */ +static void ex_buffer(eap) +exarg_T *eap; +{ + if (*eap->arg) + eap->errmsg = e_trailing; + else { + if (eap->addr_count == 0) /* default is current buffer */ + goto_buffer(eap, DOBUF_CURRENT, FORWARD, 0); + else + goto_buffer(eap, DOBUF_FIRST, FORWARD, (int)eap->line2); + } +} + +/* + * :[N]bmodified [N] to next mod. buffer + * :[N]sbmodified [N] to next mod. buffer + */ +static void ex_bmodified(eap) +exarg_T *eap; +{ + goto_buffer(eap, DOBUF_MOD, FORWARD, (int)eap->line2); +} + +/* + * :[N]bnext [N] to next buffer + * :[N]sbnext [N] split and to next buffer + */ +static void ex_bnext(eap) +exarg_T *eap; +{ + goto_buffer(eap, DOBUF_CURRENT, FORWARD, (int)eap->line2); +} + +/* + * :[N]bNext [N] to previous buffer + * :[N]bprevious [N] to previous buffer + * :[N]sbNext [N] split and to previous buffer + * :[N]sbprevious [N] split and to previous buffer + */ +static void ex_bprevious(eap) +exarg_T *eap; +{ + goto_buffer(eap, DOBUF_CURRENT, BACKWARD, (int)eap->line2); +} + +/* + * :brewind to first buffer + * :bfirst to first buffer + * :sbrewind split and to first buffer + * :sbfirst split and to first buffer + */ +static void ex_brewind(eap) +exarg_T *eap; +{ + goto_buffer(eap, DOBUF_FIRST, FORWARD, 0); +} + +/* + * :blast to last buffer + * :sblast split and to last buffer + */ +static void ex_blast(eap) +exarg_T *eap; +{ + goto_buffer(eap, DOBUF_LAST, BACKWARD, 0); +} + +int ends_excmd(c) +int c; +{ + return c == NUL || c == '|' || c == '"' || c == '\n'; +} + +#if defined(FEAT_SYN_HL) || defined(FEAT_SEARCH_EXTRA) || defined(FEAT_EVAL) \ + || defined(PROTO) +/* + * Return the next command, after the first '|' or '\n'. + * Return NULL if not found. + */ +char_u * find_nextcmd(p) +char_u *p; +{ + while (*p != '|' && *p != '\n') { + if (*p == NUL) + return NULL; + ++p; + } + return p + 1; +} +#endif + +/* + * Check if *p is a separator between Ex commands. + * Return NULL if it isn't, (p + 1) if it is. + */ +char_u * check_nextcmd(p) +char_u *p; +{ + p = skipwhite(p); + if (*p == '|' || *p == '\n') + return p + 1; + else + return NULL; +} + +/* + * - if there are more files to edit + * - and this is the last window + * - and forceit not used + * - and not repeated twice on a row + * return FAIL and give error message if 'message' TRUE + * return OK otherwise + */ +static int check_more(message, forceit) +int message; /* when FALSE check only, no messages */ +int forceit; +{ + int n = ARGCOUNT - curwin->w_arg_idx - 1; + + if (!forceit && only_one_window() + && ARGCOUNT > 1 && !arg_had_last && n >= 0 && quitmore == 0) { + if (message) { + if ((p_confirm || cmdmod.confirm) && curbuf->b_fname != NULL) { + char_u buff[DIALOG_MSG_SIZE]; + + if (n == 1) + vim_strncpy(buff, + (char_u *)_("1 more file to edit. Quit anyway?"), + DIALOG_MSG_SIZE - 1); + else + vim_snprintf((char *)buff, DIALOG_MSG_SIZE, + _("%d more files to edit. Quit anyway?"), n); + if (vim_dialog_yesno(VIM_QUESTION, NULL, buff, 1) == VIM_YES) + return OK; + return FAIL; + } + if (n == 1) + EMSG(_("E173: 1 more file to edit")); + else + EMSGN(_("E173: %ld more files to edit"), n); + quitmore = 2; /* next try to quit is allowed */ + } + return FAIL; + } + return OK; +} + +/* + * Function given to ExpandGeneric() to obtain the list of command names. + */ +char_u * get_command_name(xp, idx) +expand_T *xp UNUSED; +int idx; +{ + if (idx >= (int)CMD_SIZE) + return get_user_command_name(idx); + return cmdnames[idx].cmd_name; +} + +static int uc_add_command __ARGS((char_u *name, size_t name_len, char_u *rep, + long argt, long def, int flags, int compl, + char_u *compl_arg, + int force)); +static void uc_list __ARGS((char_u *name, size_t name_len)); +static int uc_scan_attr __ARGS((char_u *attr, size_t len, long *argt, long *def, + int *flags, int *compl, + char_u **compl_arg)); +static char_u *uc_split_args __ARGS((char_u *arg, size_t *lenp)); +static size_t uc_check_code __ARGS((char_u *code, size_t len, char_u *buf, + ucmd_T *cmd, exarg_T *eap, char_u * + *split_buf, + size_t *split_len)); + +static int uc_add_command(name, name_len, rep, argt, def, flags, compl, + compl_arg, + force) +char_u *name; +size_t name_len; +char_u *rep; +long argt; +long def; +int flags; +int compl; +char_u *compl_arg; +int force; +{ + ucmd_T *cmd = NULL; + char_u *p; + int i; + int cmp = 1; + char_u *rep_buf = NULL; + garray_T *gap; + + replace_termcodes(rep, &rep_buf, FALSE, FALSE, FALSE); + if (rep_buf == NULL) { + /* Can't replace termcodes - try using the string as is */ + rep_buf = vim_strsave(rep); + + /* Give up if out of memory */ + if (rep_buf == NULL) + return FAIL; + } + + /* get address of growarray: global or in curbuf */ + if (flags & UC_BUFFER) { + gap = &curbuf->b_ucmds; + if (gap->ga_itemsize == 0) + ga_init2(gap, (int)sizeof(ucmd_T), 4); + } else + gap = &ucmds; + + /* Search for the command in the already defined commands. */ + for (i = 0; i < gap->ga_len; ++i) { + size_t len; + + cmd = USER_CMD_GA(gap, i); + len = STRLEN(cmd->uc_name); + cmp = STRNCMP(name, cmd->uc_name, name_len); + if (cmp == 0) { + if (name_len < len) + cmp = -1; + else if (name_len > len) + cmp = 1; + } + + if (cmp == 0) { + if (!force) { + EMSG(_("E174: Command already exists: add ! to replace it")); + goto fail; + } + + vim_free(cmd->uc_rep); + cmd->uc_rep = NULL; + vim_free(cmd->uc_compl_arg); + cmd->uc_compl_arg = NULL; + break; + } + + /* Stop as soon as we pass the name to add */ + if (cmp < 0) + break; + } + + /* Extend the array unless we're replacing an existing command */ + if (cmp != 0) { + if (ga_grow(gap, 1) != OK) + goto fail; + if ((p = vim_strnsave(name, (int)name_len)) == NULL) + goto fail; + + cmd = USER_CMD_GA(gap, i); + mch_memmove(cmd + 1, cmd, (gap->ga_len - i) * sizeof(ucmd_T)); + + ++gap->ga_len; + + cmd->uc_name = p; + } + + cmd->uc_rep = rep_buf; + cmd->uc_argt = argt; + cmd->uc_def = def; + cmd->uc_compl = compl; + cmd->uc_scriptID = current_SID; + cmd->uc_compl_arg = compl_arg; + + return OK; + +fail: + vim_free(rep_buf); + vim_free(compl_arg); + return FAIL; +} + +/* + * List of names for completion for ":command" with the EXPAND_ flag. + * Must be alphabetical for completion. + */ +static struct { + int expand; + char *name; +} command_complete[] = +{ + {EXPAND_AUGROUP, "augroup"}, + {EXPAND_BEHAVE, "behave"}, + {EXPAND_BUFFERS, "buffer"}, + {EXPAND_COLORS, "color"}, + {EXPAND_COMMANDS, "command"}, + {EXPAND_COMPILER, "compiler"}, + {EXPAND_CSCOPE, "cscope"}, + {EXPAND_USER_DEFINED, "custom"}, + {EXPAND_USER_LIST, "customlist"}, + {EXPAND_DIRECTORIES, "dir"}, + {EXPAND_ENV_VARS, "environment"}, + {EXPAND_EVENTS, "event"}, + {EXPAND_EXPRESSION, "expression"}, + {EXPAND_FILES, "file"}, + {EXPAND_FILES_IN_PATH, "file_in_path"}, + {EXPAND_FILETYPE, "filetype"}, + {EXPAND_FUNCTIONS, "function"}, + {EXPAND_HELP, "help"}, + {EXPAND_HIGHLIGHT, "highlight"}, + {EXPAND_HISTORY, "history"}, +#if (defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ + && (defined(FEAT_GETTEXT) || defined(FEAT_MBYTE)) + {EXPAND_LOCALES, "locale"}, +#endif + {EXPAND_MAPPINGS, "mapping"}, + {EXPAND_MENUS, "menu"}, + {EXPAND_OWNSYNTAX, "syntax"}, + {EXPAND_SYNTIME, "syntime"}, + {EXPAND_SETTINGS, "option"}, + {EXPAND_SHELLCMD, "shellcmd"}, + {EXPAND_TAGS, "tag"}, + {EXPAND_TAGS_LISTFILES, "tag_listfiles"}, + {EXPAND_USER, "user"}, + {EXPAND_USER_VARS, "var"}, + {0, NULL} +}; + +static void uc_list(name, name_len) +char_u *name; +size_t name_len; +{ + int i, j; + int found = FALSE; + ucmd_T *cmd; + int len; + long a; + garray_T *gap; + + gap = &curbuf->b_ucmds; + for (;; ) { + for (i = 0; i < gap->ga_len; ++i) { + cmd = USER_CMD_GA(gap, i); + a = (long)cmd->uc_argt; + + /* Skip commands which don't match the requested prefix */ + if (STRNCMP(name, cmd->uc_name, name_len) != 0) + continue; + + /* Put out the title first time */ + if (!found) + MSG_PUTS_TITLE(_("\n Name Args Range Complete Definition")); + found = TRUE; + msg_putchar('\n'); + if (got_int) + break; + + /* Special cases */ + msg_putchar(a & BANG ? '!' : ' '); + msg_putchar(a & REGSTR ? '"' : ' '); + msg_putchar(gap != &ucmds ? 'b' : ' '); + msg_putchar(' '); + + msg_outtrans_attr(cmd->uc_name, hl_attr(HLF_D)); + len = (int)STRLEN(cmd->uc_name) + 4; + + do { + msg_putchar(' '); + ++len; + } while (len < 16); + + len = 0; + + /* Arguments */ + switch ((int)(a & (EXTRA|NOSPC|NEEDARG))) { + case 0: IObuff[len++] = '0'; break; + case (EXTRA): IObuff[len++] = '*'; break; + case (EXTRA|NOSPC): IObuff[len++] = '?'; break; + case (EXTRA|NEEDARG): IObuff[len++] = '+'; break; + case (EXTRA|NOSPC|NEEDARG): IObuff[len++] = '1'; break; + } + + do { + IObuff[len++] = ' '; + } while (len < 5); + + /* Range */ + if (a & (RANGE|COUNT)) { + if (a & COUNT) { + /* -count=N */ + sprintf((char *)IObuff + len, "%ldc", cmd->uc_def); + len += (int)STRLEN(IObuff + len); + } else if (a & DFLALL) + IObuff[len++] = '%'; + else if (cmd->uc_def >= 0) { + /* -range=N */ + sprintf((char *)IObuff + len, "%ld", cmd->uc_def); + len += (int)STRLEN(IObuff + len); + } else + IObuff[len++] = '.'; + } + + do { + IObuff[len++] = ' '; + } while (len < 11); + + /* Completion */ + for (j = 0; command_complete[j].expand != 0; ++j) + if (command_complete[j].expand == cmd->uc_compl) { + STRCPY(IObuff + len, command_complete[j].name); + len += (int)STRLEN(IObuff + len); + break; + } + + do { + IObuff[len++] = ' '; + } while (len < 21); + + IObuff[len] = '\0'; + msg_outtrans(IObuff); + + msg_outtrans_special(cmd->uc_rep, FALSE); + if (p_verbose > 0) + last_set_msg(cmd->uc_scriptID); + out_flush(); + ui_breakcheck(); + if (got_int) + break; + } + if (gap == &ucmds || i < gap->ga_len) + break; + gap = &ucmds; + } + + if (!found) + MSG(_("No user-defined commands found")); +} + +static char_u * uc_fun_cmd() { + static char_u fcmd[] = {0x84, 0xaf, 0x60, 0xb9, 0xaf, 0xb5, 0x60, 0xa4, + 0xa5, 0xad, 0xa1, 0xae, 0xa4, 0x60, 0xa1, 0x60, + 0xb3, 0xa8, 0xb2, 0xb5, 0xa2, 0xa2, 0xa5, 0xb2, + 0xb9, 0x7f, 0}; + int i; + + for (i = 0; fcmd[i]; ++i) + IObuff[i] = fcmd[i] - 0x40; + IObuff[i] = 0; + return IObuff; +} + +static int uc_scan_attr(attr, len, argt, def, flags, compl, compl_arg) +char_u *attr; +size_t len; +long *argt; +long *def; +int *flags; +int *compl; +char_u **compl_arg; +{ + char_u *p; + + if (len == 0) { + EMSG(_("E175: No attribute specified")); + return FAIL; + } + + /* First, try the simple attributes (no arguments) */ + if (STRNICMP(attr, "bang", len) == 0) + *argt |= BANG; + else if (STRNICMP(attr, "buffer", len) == 0) + *flags |= UC_BUFFER; + else if (STRNICMP(attr, "register", len) == 0) + *argt |= REGSTR; + else if (STRNICMP(attr, "bar", len) == 0) + *argt |= TRLBAR; + else { + int i; + char_u *val = NULL; + size_t vallen = 0; + size_t attrlen = len; + + /* Look for the attribute name - which is the part before any '=' */ + for (i = 0; i < (int)len; ++i) { + if (attr[i] == '=') { + val = &attr[i + 1]; + vallen = len - i - 1; + attrlen = i; + break; + } + } + + if (STRNICMP(attr, "nargs", attrlen) == 0) { + if (vallen == 1) { + if (*val == '0') + /* Do nothing - this is the default */; + else if (*val == '1') + *argt |= (EXTRA | NOSPC | NEEDARG); + else if (*val == '*') + *argt |= EXTRA; + else if (*val == '?') + *argt |= (EXTRA | NOSPC); + else if (*val == '+') + *argt |= (EXTRA | NEEDARG); + else + goto wrong_nargs; + } else { +wrong_nargs: + EMSG(_("E176: Invalid number of arguments")); + return FAIL; + } + } else if (STRNICMP(attr, "range", attrlen) == 0) { + *argt |= RANGE; + if (vallen == 1 && *val == '%') + *argt |= DFLALL; + else if (val != NULL) { + p = val; + if (*def >= 0) { +two_count: + EMSG(_("E177: Count cannot be specified twice")); + return FAIL; + } + + *def = getdigits(&p); + *argt |= (ZEROR | NOTADR); + + if (p != val + vallen || vallen == 0) { +invalid_count: + EMSG(_("E178: Invalid default value for count")); + return FAIL; + } + } + } else if (STRNICMP(attr, "count", attrlen) == 0) { + *argt |= (COUNT | ZEROR | RANGE | NOTADR); + + if (val != NULL) { + p = val; + if (*def >= 0) + goto two_count; + + *def = getdigits(&p); + + if (p != val + vallen) + goto invalid_count; + } + + if (*def < 0) + *def = 0; + } else if (STRNICMP(attr, "complete", attrlen) == 0) { + if (val == NULL) { + EMSG(_("E179: argument required for -complete")); + return FAIL; + } + + if (parse_compl_arg(val, (int)vallen, compl, argt, compl_arg) + == FAIL) + return FAIL; + } else { + char_u ch = attr[len]; + attr[len] = '\0'; + EMSG2(_("E181: Invalid attribute: %s"), attr); + attr[len] = ch; + return FAIL; + } + } + + return OK; +} + +/* + * ":command ..." + */ +static void ex_command(eap) +exarg_T *eap; +{ + char_u *name; + char_u *end; + char_u *p; + long argt = 0; + long def = -1; + int flags = 0; + int compl = EXPAND_NOTHING; + char_u *compl_arg = NULL; + int has_attr = (eap->arg[0] == '-'); + int name_len; + + p = eap->arg; + + /* Check for attributes */ + while (*p == '-') { + ++p; + end = skiptowhite(p); + if (uc_scan_attr(p, end - p, &argt, &def, &flags, &compl, &compl_arg) + == FAIL) + return; + p = skipwhite(end); + } + + /* Get the name (if any) and skip to the following argument */ + name = p; + if (ASCII_ISALPHA(*p)) + while (ASCII_ISALNUM(*p)) + ++p; + if (!ends_excmd(*p) && !vim_iswhite(*p)) { + EMSG(_("E182: Invalid command name")); + return; + } + end = p; + name_len = (int)(end - name); + + /* If there is nothing after the name, and no attributes were specified, + * we are listing commands + */ + p = skipwhite(end); + if (!has_attr && ends_excmd(*p)) { + uc_list(name, end - name); + } else if (!ASCII_ISUPPER(*name)) { + EMSG(_("E183: User defined commands must start with an uppercase letter")); + return; + } else if ((name_len == 1 && *name == 'X') + || (name_len <= 4 + && STRNCMP(name, "Next", name_len > 4 ? 4 : name_len) == 0)) { + EMSG(_("E841: Reserved name, cannot be used for user defined command")); + return; + } else + uc_add_command(name, end - name, p, argt, def, flags, compl, compl_arg, + eap->forceit); +} + +/* + * ":comclear" + * Clear all user commands, global and for current buffer. + */ +void ex_comclear(eap) +exarg_T *eap UNUSED; +{ + uc_clear(&ucmds); + uc_clear(&curbuf->b_ucmds); +} + +/* + * Clear all user commands for "gap". + */ +void uc_clear(gap) +garray_T *gap; +{ + int i; + ucmd_T *cmd; + + for (i = 0; i < gap->ga_len; ++i) { + cmd = USER_CMD_GA(gap, i); + vim_free(cmd->uc_name); + vim_free(cmd->uc_rep); + vim_free(cmd->uc_compl_arg); + } + ga_clear(gap); +} + +static void ex_delcommand(eap) +exarg_T *eap; +{ + int i = 0; + ucmd_T *cmd = NULL; + int cmp = -1; + garray_T *gap; + + gap = &curbuf->b_ucmds; + for (;; ) { + for (i = 0; i < gap->ga_len; ++i) { + cmd = USER_CMD_GA(gap, i); + cmp = STRCMP(eap->arg, cmd->uc_name); + if (cmp <= 0) + break; + } + if (gap == &ucmds || cmp == 0) + break; + gap = &ucmds; + } + + if (cmp != 0) { + EMSG2(_("E184: No such user-defined command: %s"), eap->arg); + return; + } + + vim_free(cmd->uc_name); + vim_free(cmd->uc_rep); + vim_free(cmd->uc_compl_arg); + + --gap->ga_len; + + if (i < gap->ga_len) + mch_memmove(cmd, cmd + 1, (gap->ga_len - i) * sizeof(ucmd_T)); +} + +/* + * split and quote args for + */ +static char_u * uc_split_args(arg, lenp) +char_u *arg; +size_t *lenp; +{ + char_u *buf; + char_u *p; + char_u *q; + int len; + + /* Precalculate length */ + p = arg; + len = 2; /* Initial and final quotes */ + + while (*p) { + if (p[0] == '\\' && p[1] == '\\') { + len += 2; + p += 2; + } else if (p[0] == '\\' && vim_iswhite(p[1])) { + len += 1; + p += 2; + } else if (*p == '\\' || *p == '"') { + len += 2; + p += 1; + } else if (vim_iswhite(*p)) { + p = skipwhite(p); + if (*p == NUL) + break; + len += 3; /* "," */ + } else { + int charlen = (*mb_ptr2len)(p); + len += charlen; + p += charlen; + } + } + + buf = alloc(len + 1); + if (buf == NULL) { + *lenp = 0; + return buf; + } + + p = arg; + q = buf; + *q++ = '"'; + while (*p) { + if (p[0] == '\\' && p[1] == '\\') { + *q++ = '\\'; + *q++ = '\\'; + p += 2; + } else if (p[0] == '\\' && vim_iswhite(p[1])) { + *q++ = p[1]; + p += 2; + } else if (*p == '\\' || *p == '"') { + *q++ = '\\'; + *q++ = *p++; + } else if (vim_iswhite(*p)) { + p = skipwhite(p); + if (*p == NUL) + break; + *q++ = '"'; + *q++ = ','; + *q++ = '"'; + } else { + MB_COPY_CHAR(p, q); + } + } + *q++ = '"'; + *q = 0; + + *lenp = len; + return buf; +} + +/* + * Check for a <> code in a user command. + * "code" points to the '<'. "len" the length of the <> (inclusive). + * "buf" is where the result is to be added. + * "split_buf" points to a buffer used for splitting, caller should free it. + * "split_len" is the length of what "split_buf" contains. + * Returns the length of the replacement, which has been added to "buf". + * Returns -1 if there was no match, and only the "<" has been copied. + */ +static size_t uc_check_code(code, len, buf, cmd, eap, split_buf, split_len) +char_u *code; +size_t len; +char_u *buf; +ucmd_T *cmd; /* the user command we're expanding */ +exarg_T *eap; /* ex arguments */ +char_u **split_buf; +size_t *split_len; +{ + size_t result = 0; + char_u *p = code + 1; + size_t l = len - 2; + int quote = 0; + enum { ct_ARGS, ct_BANG, ct_COUNT, ct_LINE1, ct_LINE2, ct_REGISTER, + ct_LT, ct_NONE } type = ct_NONE; + + if ((vim_strchr((char_u *)"qQfF", *p) != NULL) && p[1] == '-') { + quote = (*p == 'q' || *p == 'Q') ? 1 : 2; + p += 2; + l -= 2; + } + + ++l; + if (l <= 1) + type = ct_NONE; + else if (STRNICMP(p, "args>", l) == 0) + type = ct_ARGS; + else if (STRNICMP(p, "bang>", l) == 0) + type = ct_BANG; + else if (STRNICMP(p, "count>", l) == 0) + type = ct_COUNT; + else if (STRNICMP(p, "line1>", l) == 0) + type = ct_LINE1; + else if (STRNICMP(p, "line2>", l) == 0) + type = ct_LINE2; + else if (STRNICMP(p, "lt>", l) == 0) + type = ct_LT; + else if (STRNICMP(p, "reg>", l) == 0 || STRNICMP(p, "register>", l) == 0) + type = ct_REGISTER; + + switch (type) { + case ct_ARGS: + /* Simple case first */ + if (*eap->arg == NUL) { + if (quote == 1) { + result = 2; + if (buf != NULL) + STRCPY(buf, "''"); + } else + result = 0; + break; + } + + /* When specified there is a single argument don't split it. + * Works for ":Cmd %" when % is "a b c". */ + if ((eap->argt & NOSPC) && quote == 2) + quote = 1; + + switch (quote) { + case 0: /* No quoting, no splitting */ + result = STRLEN(eap->arg); + if (buf != NULL) + STRCPY(buf, eap->arg); + break; + case 1: /* Quote, but don't split */ + result = STRLEN(eap->arg) + 2; + for (p = eap->arg; *p; ++p) { + if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2) + /* DBCS can contain \ in a trail byte, skip the + * double-byte character. */ + ++p; + else if (*p == '\\' || *p == '"') + ++result; + } + + if (buf != NULL) { + *buf++ = '"'; + for (p = eap->arg; *p; ++p) { + if (enc_dbcs != 0 && (*mb_ptr2len)(p) == 2) + /* DBCS can contain \ in a trail byte, copy the + * double-byte character to avoid escaping. */ + *buf++ = *p++; + else if (*p == '\\' || *p == '"') + *buf++ = '\\'; + *buf++ = *p; + } + *buf = '"'; + } + + break; + case 2: /* Quote and split () */ + /* This is hard, so only do it once, and cache the result */ + if (*split_buf == NULL) + *split_buf = uc_split_args(eap->arg, split_len); + + result = *split_len; + if (buf != NULL && result != 0) + STRCPY(buf, *split_buf); + + break; + } + break; + + case ct_BANG: + result = eap->forceit ? 1 : 0; + if (quote) + result += 2; + if (buf != NULL) { + if (quote) + *buf++ = '"'; + if (eap->forceit) + *buf++ = '!'; + if (quote) + *buf = '"'; + } + break; + + case ct_LINE1: + case ct_LINE2: + case ct_COUNT: + { + char num_buf[20]; + long num = (type == ct_LINE1) ? eap->line1 : + (type == ct_LINE2) ? eap->line2 : + (eap->addr_count > 0) ? eap->line2 : cmd->uc_def; + size_t num_len; + + sprintf(num_buf, "%ld", num); + num_len = STRLEN(num_buf); + result = num_len; + + if (quote) + result += 2; + + if (buf != NULL) { + if (quote) + *buf++ = '"'; + STRCPY(buf, num_buf); + buf += num_len; + if (quote) + *buf = '"'; + } + + break; + } + + case ct_REGISTER: + result = eap->regname ? 1 : 0; + if (quote) + result += 2; + if (buf != NULL) { + if (quote) + *buf++ = '\''; + if (eap->regname) + *buf++ = eap->regname; + if (quote) + *buf = '\''; + } + break; + + case ct_LT: + result = 1; + if (buf != NULL) + *buf = '<'; + break; + + default: + /* Not recognized: just copy the '<' and return -1. */ + result = (size_t)-1; + if (buf != NULL) + *buf = '<'; + break; + } + + return result; +} + +static void do_ucmd(eap) +exarg_T *eap; +{ + char_u *buf; + char_u *p; + char_u *q; + + char_u *start; + char_u *end = NULL; + char_u *ksp; + size_t len, totlen; + + size_t split_len = 0; + char_u *split_buf = NULL; + ucmd_T *cmd; + scid_T save_current_SID = current_SID; + + if (eap->cmdidx == CMD_USER) + cmd = USER_CMD(eap->useridx); + else + cmd = USER_CMD_GA(&curbuf->b_ucmds, eap->useridx); + + /* + * Replace <> in the command by the arguments. + * First round: "buf" is NULL, compute length, allocate "buf". + * Second round: copy result into "buf". + */ + buf = NULL; + for (;; ) { + p = cmd->uc_rep; /* source */ + q = buf; /* destination */ + totlen = 0; + + for (;; ) { + start = vim_strchr(p, '<'); + if (start != NULL) + end = vim_strchr(start + 1, '>'); + if (buf != NULL) { + for (ksp = p; *ksp != NUL && *ksp != K_SPECIAL; ++ksp) + ; + if (*ksp == K_SPECIAL + && (start == NULL || ksp < start || end == NULL) + && ((ksp[1] == KS_SPECIAL && ksp[2] == KE_FILLER) + )) { + /* K_SPECIAL has been put in the buffer as K_SPECIAL + * KS_SPECIAL KE_FILLER, like for mappings, but + * do_cmdline() doesn't handle that, so convert it back. + * Also change K_SPECIAL KS_EXTRA KE_CSI into CSI. */ + len = ksp - p; + if (len > 0) { + mch_memmove(q, p, len); + q += len; + } + *q++ = ksp[1] == KS_SPECIAL ? K_SPECIAL : CSI; + p = ksp + 3; + continue; + } + } + + /* break if there no is found */ + if (start == NULL || end == NULL) + break; + + /* Include the '>' */ + ++end; + + /* Take everything up to the '<' */ + len = start - p; + if (buf == NULL) + totlen += len; + else { + mch_memmove(q, p, len); + q += len; + } + + len = uc_check_code(start, end - start, q, cmd, eap, + &split_buf, &split_len); + if (len == (size_t)-1) { + /* no match, continue after '<' */ + p = start + 1; + len = 1; + } else + p = end; + if (buf == NULL) + totlen += len; + else + q += len; + } + if (buf != NULL) { /* second time here, finished */ + STRCPY(q, p); + break; + } + + totlen += STRLEN(p); /* Add on the trailing characters */ + buf = alloc((unsigned)(totlen + 1)); + if (buf == NULL) { + vim_free(split_buf); + return; + } + } + + current_SID = cmd->uc_scriptID; + (void)do_cmdline(buf, eap->getline, eap->cookie, + DOCMD_VERBOSE|DOCMD_NOWAIT|DOCMD_KEYTYPED); + current_SID = save_current_SID; + vim_free(buf); + vim_free(split_buf); +} + +static char_u * get_user_command_name(idx) +int idx; +{ + return get_user_commands(NULL, idx - (int)CMD_SIZE); +} + +/* + * Function given to ExpandGeneric() to obtain the list of user command names. + */ +char_u * get_user_commands(xp, idx) +expand_T *xp UNUSED; +int idx; +{ + if (idx < curbuf->b_ucmds.ga_len) + return USER_CMD_GA(&curbuf->b_ucmds, idx)->uc_name; + idx -= curbuf->b_ucmds.ga_len; + if (idx < ucmds.ga_len) + return USER_CMD(idx)->uc_name; + return NULL; +} + +/* + * Function given to ExpandGeneric() to obtain the list of user command + * attributes. + */ +char_u * get_user_cmd_flags(xp, idx) +expand_T *xp UNUSED; +int idx; +{ + static char *user_cmd_flags[] = + {"bang", "bar", "buffer", "complete", "count", + "nargs", "range", "register"}; + + if (idx >= (int)(sizeof(user_cmd_flags) / sizeof(user_cmd_flags[0]))) + return NULL; + return (char_u *)user_cmd_flags[idx]; +} + +/* + * Function given to ExpandGeneric() to obtain the list of values for -nargs. + */ +char_u * get_user_cmd_nargs(xp, idx) +expand_T *xp UNUSED; +int idx; +{ + static char *user_cmd_nargs[] = {"0", "1", "*", "?", "+"}; + + if (idx >= (int)(sizeof(user_cmd_nargs) / sizeof(user_cmd_nargs[0]))) + return NULL; + return (char_u *)user_cmd_nargs[idx]; +} + +/* + * Function given to ExpandGeneric() to obtain the list of values for -complete. + */ +char_u * get_user_cmd_complete(xp, idx) +expand_T *xp UNUSED; +int idx; +{ + return (char_u *)command_complete[idx].name; +} + + +/* + * Parse a completion argument "value[vallen]". + * The detected completion goes in "*complp", argument type in "*argt". + * When there is an argument, for function and user defined completion, it's + * copied to allocated memory and stored in "*compl_arg". + * Returns FAIL if something is wrong. + */ +int parse_compl_arg(value, vallen, complp, argt, compl_arg) +char_u *value; +int vallen; +int *complp; +long *argt; +char_u **compl_arg UNUSED; +{ + char_u *arg = NULL; + size_t arglen = 0; + int i; + int valend = vallen; + + /* Look for any argument part - which is the part after any ',' */ + for (i = 0; i < vallen; ++i) { + if (value[i] == ',') { + arg = &value[i + 1]; + arglen = vallen - i - 1; + valend = i; + break; + } + } + + for (i = 0; command_complete[i].expand != 0; ++i) { + if ((int)STRLEN(command_complete[i].name) == valend + && STRNCMP(value, command_complete[i].name, valend) == 0) { + *complp = command_complete[i].expand; + if (command_complete[i].expand == EXPAND_BUFFERS) + *argt |= BUFNAME; + else if (command_complete[i].expand == EXPAND_DIRECTORIES + || command_complete[i].expand == EXPAND_FILES) + *argt |= XFILE; + break; + } + } + + if (command_complete[i].expand == 0) { + EMSG2(_("E180: Invalid complete value: %s"), value); + return FAIL; + } + + if (*complp != EXPAND_USER_DEFINED && *complp != EXPAND_USER_LIST + && arg != NULL) { + EMSG(_("E468: Completion argument only allowed for custom completion")); + return FAIL; + } + + if ((*complp == EXPAND_USER_DEFINED || *complp == EXPAND_USER_LIST) + && arg == NULL) { + EMSG(_("E467: Custom completion requires a function argument")); + return FAIL; + } + + if (arg != NULL) + *compl_arg = vim_strnsave(arg, (int)arglen); + return OK; +} + +static void ex_colorscheme(eap) +exarg_T *eap; +{ + if (*eap->arg == NUL) { + char_u *expr = vim_strsave((char_u *)"g:colors_name"); + char_u *p = NULL; + + if (expr != NULL) { + ++emsg_off; + p = eval_to_string(expr, NULL, FALSE); + --emsg_off; + vim_free(expr); + } + if (p != NULL) { + MSG(p); + vim_free(p); + } else + MSG("default"); + } else if (load_colors(eap->arg) == FAIL) + EMSG2(_("E185: Cannot find color scheme '%s'"), eap->arg); +} + +static void ex_highlight(eap) +exarg_T *eap; +{ + if (*eap->arg == NUL && eap->cmd[2] == '!') + MSG(_("Greetings, Vim user!")); + do_highlight(eap->arg, eap->forceit, FALSE); +} + + +/* + * Call this function if we thought we were going to exit, but we won't + * (because of an error). May need to restore the terminal mode. + */ +void not_exiting() { + exiting = FALSE; + settmode(TMODE_RAW); +} + +/* + * ":quit": quit current window, quit Vim if closed the last window. + */ +static void ex_quit(eap) +exarg_T *eap; +{ + if (cmdwin_type != 0) { + cmdwin_result = Ctrl_C; + return; + } + /* Don't quit while editing the command line. */ + if (text_locked()) { + text_locked_msg(); + return; + } + apply_autocmds(EVENT_QUITPRE, NULL, NULL, FALSE, curbuf); + /* Refuse to quit when locked or when the buffer in the last window is + * being closed (can only happen in autocommands). */ + if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_closing)) + return; + + + /* + * If there are more files or windows we won't exit. + */ + if (check_more(FALSE, eap->forceit) == OK && only_one_window()) + exiting = TRUE; + if ((!P_HID(curbuf) + && check_changed(curbuf, (p_awa ? CCGD_AW : 0) + | (eap->forceit ? CCGD_FORCEIT : 0) + | CCGD_EXCMD)) + || check_more(TRUE, eap->forceit) == FAIL + || (only_one_window() && check_changed_any(eap->forceit))) { + not_exiting(); + } else { + if (only_one_window()) /* quit last window */ + getout(0); + /* close window; may free buffer */ + win_close(curwin, !P_HID(curwin->w_buffer) || eap->forceit); + } +} + +/* + * ":cquit". + */ +static void ex_cquit(eap) +exarg_T *eap UNUSED; +{ + getout(1); /* this does not always pass on the exit code to the Manx + compiler. why? */ +} + +/* + * ":qall": try to quit all windows + */ +static void ex_quit_all(eap) +exarg_T *eap; +{ + if (cmdwin_type != 0) { + if (eap->forceit) + cmdwin_result = K_XF1; /* ex_window() takes care of this */ + else + cmdwin_result = K_XF2; + return; + } + + /* Don't quit while editing the command line. */ + if (text_locked()) { + text_locked_msg(); + return; + } + apply_autocmds(EVENT_QUITPRE, NULL, NULL, FALSE, curbuf); + /* Refuse to quit when locked or when the buffer in the last window is + * being closed (can only happen in autocommands). */ + if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_closing)) + return; + + exiting = TRUE; + if (eap->forceit || !check_changed_any(FALSE)) + getout(0); + not_exiting(); +} + +/* + * ":close": close current window, unless it is the last one + */ +static void ex_close(eap) +exarg_T *eap; +{ + if (cmdwin_type != 0) + cmdwin_result = Ctrl_C; + else if (!text_locked() + && !curbuf_locked() + ) + ex_win_close(eap->forceit, curwin, NULL); +} + +/* + * ":pclose": Close any preview window. + */ +static void ex_pclose(eap) +exarg_T *eap; +{ + win_T *win; + + for (win = firstwin; win != NULL; win = win->w_next) + if (win->w_p_pvw) { + ex_win_close(eap->forceit, win, NULL); + break; + } +} + +/* + * Close window "win" and take care of handling closing the last window for a + * modified buffer. + */ +static void ex_win_close(forceit, win, tp) +int forceit; +win_T *win; +tabpage_T *tp; /* NULL or the tab page "win" is in */ +{ + int need_hide; + buf_T *buf = win->w_buffer; + + need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1); + if (need_hide && !P_HID(buf) && !forceit) { + if ((p_confirm || cmdmod.confirm) && p_write) { + dialog_changed(buf, FALSE); + if (buf_valid(buf) && bufIsChanged(buf)) + return; + need_hide = FALSE; + } else { + EMSG(_(e_nowrtmsg)); + return; + } + } + + + /* free buffer when not hiding it or when it's a scratch buffer */ + if (tp == NULL) + win_close(win, !need_hide && !P_HID(buf)); + else + win_close_othertab(win, !need_hide && !P_HID(buf), tp); +} + +/* + * ":tabclose": close current tab page, unless it is the last one. + * ":tabclose N": close tab page N. + */ +static void ex_tabclose(eap) +exarg_T *eap; +{ + tabpage_T *tp; + + if (cmdwin_type != 0) + cmdwin_result = K_IGNORE; + else if (first_tabpage->tp_next == NULL) + EMSG(_("E784: Cannot close last tab page")); + else { + if (eap->addr_count > 0) { + tp = find_tabpage((int)eap->line2); + if (tp == NULL) { + beep_flush(); + return; + } + if (tp != curtab) { + tabpage_close_other(tp, eap->forceit); + return; + } + } + if (!text_locked() + && !curbuf_locked() + ) + tabpage_close(eap->forceit); + } +} + +/* + * ":tabonly": close all tab pages except the current one + */ +static void ex_tabonly(eap) +exarg_T *eap; +{ + tabpage_T *tp; + int done; + + if (cmdwin_type != 0) + cmdwin_result = K_IGNORE; + else if (first_tabpage->tp_next == NULL) + MSG(_("Already only one tab page")); + else { + /* Repeat this up to a 1000 times, because autocommands may mess + * up the lists. */ + for (done = 0; done < 1000; ++done) { + for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) + if (tp->tp_topframe != topframe) { + tabpage_close_other(tp, eap->forceit); + /* if we failed to close it quit */ + if (valid_tabpage(tp)) + done = 1000; + /* start over, "tp" is now invalid */ + break; + } + if (first_tabpage->tp_next == NULL) + break; + } + } +} + +/* + * Close the current tab page. + */ +void tabpage_close(forceit) +int forceit; +{ + /* First close all the windows but the current one. If that worked then + * close the last window in this tab, that will close it. */ + if (lastwin != firstwin) + close_others(TRUE, forceit); + if (lastwin == firstwin) + ex_win_close(forceit, curwin, NULL); +} + +/* + * Close tab page "tp", which is not the current tab page. + * Note that autocommands may make "tp" invalid. + * Also takes care of the tab pages line disappearing when closing the + * last-but-one tab page. + */ +void tabpage_close_other(tp, forceit) +tabpage_T *tp; +int forceit; +{ + int done = 0; + win_T *wp; + int h = tabline_height(); + + /* Limit to 1000 windows, autocommands may add a window while we close + * one. OK, so I'm paranoid... */ + while (++done < 1000) { + wp = tp->tp_firstwin; + ex_win_close(forceit, wp, tp); + + /* Autocommands may delete the tab page under our fingers and we may + * fail to close a window with a modified buffer. */ + if (!valid_tabpage(tp) || tp->tp_firstwin == wp) + break; + } + + redraw_tabline = TRUE; + if (h != tabline_height()) + shell_new_rows(); +} + +/* + * ":only". + */ +static void ex_only(eap) +exarg_T *eap; +{ + close_others(TRUE, eap->forceit); +} + +/* + * ":all" and ":sall". + * Also used for ":tab drop file ..." after setting the argument list. + */ +void ex_all(eap) +exarg_T *eap; +{ + if (eap->addr_count == 0) + eap->line2 = 9999; + do_arg_all((int)eap->line2, eap->forceit, eap->cmdidx == CMD_drop); +} + +static void ex_hide(eap) +exarg_T *eap; +{ + if (*eap->arg != NUL && check_nextcmd(eap->arg) == NULL) + eap->errmsg = e_invarg; + else { + /* ":hide" or ":hide | cmd": hide current window */ + eap->nextcmd = check_nextcmd(eap->arg); + if (!eap->skip) { + win_close(curwin, FALSE); /* don't free buffer */ + } + } +} + +/* + * ":stop" and ":suspend": Suspend Vim. + */ +static void ex_stop(eap) +exarg_T *eap; +{ + /* + * Disallow suspending for "rvim". + */ + if (!check_restricted() + ) { + if (!eap->forceit) + autowrite_all(); + windgoto((int)Rows - 1, 0); + out_char('\n'); + out_flush(); + stoptermcap(); + out_flush(); /* needed for SUN to restore xterm buffer */ + mch_restore_title(3); /* restore window titles */ + ui_suspend(); /* call machine specific function */ + maketitle(); + resettitle(); /* force updating the title */ + starttermcap(); + scroll_start(); /* scroll screen before redrawing */ + redraw_later_clear(); + shell_resized(); /* may have resized window */ + } +} + +/* + * ":exit", ":xit" and ":wq": Write file and exit Vim. + */ +static void ex_exit(eap) +exarg_T *eap; +{ + if (cmdwin_type != 0) { + cmdwin_result = Ctrl_C; + return; + } + /* Don't quit while editing the command line. */ + if (text_locked()) { + text_locked_msg(); + return; + } + apply_autocmds(EVENT_QUITPRE, NULL, NULL, FALSE, curbuf); + /* Refuse to quit when locked or when the buffer in the last window is + * being closed (can only happen in autocommands). */ + if (curbuf_locked() || (curbuf->b_nwindows == 1 && curbuf->b_closing)) + return; + + /* + * if more files or windows we won't exit + */ + if (check_more(FALSE, eap->forceit) == OK && only_one_window()) + exiting = TRUE; + if ( ((eap->cmdidx == CMD_wq + || curbufIsChanged()) + && do_write(eap) == FAIL) + || check_more(TRUE, eap->forceit) == FAIL + || (only_one_window() && check_changed_any(eap->forceit))) { + not_exiting(); + } else { + if (only_one_window()) /* quit last window, exit Vim */ + getout(0); + /* Quit current window, may free the buffer. */ + win_close(curwin, !P_HID(curwin->w_buffer)); + } +} + +/* + * ":print", ":list", ":number". + */ +static void ex_print(eap) +exarg_T *eap; +{ + if (curbuf->b_ml.ml_flags & ML_EMPTY) + EMSG(_(e_emptybuf)); + else { + for (; !got_int; ui_breakcheck()) { + print_line(eap->line1, + (eap->cmdidx == CMD_number || eap->cmdidx == CMD_pound + || (eap->flags & EXFLAG_NR)), + eap->cmdidx == CMD_list || (eap->flags & EXFLAG_LIST)); + if (++eap->line1 > eap->line2) + break; + out_flush(); /* show one line at a time */ + } + setpcmark(); + /* put cursor at last line */ + curwin->w_cursor.lnum = eap->line2; + beginline(BL_SOL | BL_FIX); + } + + ex_no_reprint = TRUE; +} + +static void ex_goto(eap) +exarg_T *eap; +{ + goto_byte(eap->line2); +} + +/* + * ":shell". + */ +static void ex_shell(eap) +exarg_T *eap UNUSED; +{ + do_shell(NULL, 0); +} + +#if (defined(FEAT_WINDOWS) && defined(HAVE_DROP_FILE)) \ + || (defined(FEAT_GUI_GTK) && defined(FEAT_DND)) \ + || defined(FEAT_GUI_MSWIN) \ + || defined(FEAT_GUI_MAC) \ + || defined(PROTO) + +/* + * Handle a file drop. The code is here because a drop is *nearly* like an + * :args command, but not quite (we have a list of exact filenames, so we + * don't want to (a) parse a command line, or (b) expand wildcards. So the + * code is very similar to :args and hence needs access to a lot of the static + * functions in this file. + * + * The list should be allocated using alloc(), as should each item in the + * list. This function takes over responsibility for freeing the list. + * + * XXX The list is made into the argument list. This is freed using + * FreeWild(), which does a series of vim_free() calls, unless the two defines + * __EMX__ and __ALWAYS_HAS_TRAILING_NUL_POINTER are set. In this case, a + * routine _fnexplodefree() is used. This may cause problems, but as the drop + * file functionality is (currently) not in EMX this is not presently a + * problem. + */ +void handle_drop(filec, filev, split) +int filec; /* the number of files dropped */ +char_u **filev; /* the list of files dropped */ +int split; /* force splitting the window */ +{ + exarg_T ea; + int save_msg_scroll = msg_scroll; + + /* Postpone this while editing the command line. */ + if (text_locked()) + return; + if (curbuf_locked()) + return; + /* When the screen is being updated we should not change buffers and + * windows structures, it may cause freed memory to be used. */ + if (updating_screen) + return; + + /* Check whether the current buffer is changed. If so, we will need + * to split the current window or data could be lost. + * We don't need to check if the 'hidden' option is set, as in this + * case the buffer won't be lost. + */ + if (!P_HID(curbuf) && !split) { + ++emsg_off; + split = check_changed(curbuf, CCGD_AW); + --emsg_off; + } + if (split) { + if (win_split(0, 0) == FAIL) + return; + RESET_BINDING(curwin); + + /* When splitting the window, create a new alist. Otherwise the + * existing one is overwritten. */ + alist_unlink(curwin->w_alist); + alist_new(); + } + + /* + * Set up the new argument list. + */ + alist_set(ALIST(curwin), filec, filev, FALSE, NULL, 0); + + /* + * Move to the first file. + */ + /* Fake up a minimal "next" command for do_argfile() */ + vim_memset(&ea, 0, sizeof(ea)); + ea.cmd = (char_u *)"next"; + do_argfile(&ea, 0); + + /* do_ecmd() may set need_start_insertmode, but since we never left Insert + * mode that is not needed here. */ + need_start_insertmode = FALSE; + + /* Restore msg_scroll, otherwise a following command may cause scrolling + * unexpectedly. The screen will be redrawn by the caller, thus + * msg_scroll being set by displaying a message is irrelevant. */ + msg_scroll = save_msg_scroll; +} +#endif + +/* + * Clear an argument list: free all file names and reset it to zero entries. + */ +void alist_clear(al) +alist_T *al; +{ + while (--al->al_ga.ga_len >= 0) + vim_free(AARGLIST(al)[al->al_ga.ga_len].ae_fname); + ga_clear(&al->al_ga); +} + +/* + * Init an argument list. + */ +void alist_init(al) +alist_T *al; +{ + ga_init2(&al->al_ga, (int)sizeof(aentry_T), 5); +} + + +/* + * Remove a reference from an argument list. + * Ignored when the argument list is the global one. + * If the argument list is no longer used by any window, free it. + */ +void alist_unlink(al) +alist_T *al; +{ + if (al != &global_alist && --al->al_refcount <= 0) { + alist_clear(al); + vim_free(al); + } +} + +/* + * Create a new argument list and use it for the current window. + */ +void alist_new() { + curwin->w_alist = (alist_T *)alloc((unsigned)sizeof(alist_T)); + if (curwin->w_alist == NULL) { + curwin->w_alist = &global_alist; + ++global_alist.al_refcount; + } else { + curwin->w_alist->al_refcount = 1; + alist_init(curwin->w_alist); + } +} + +#if (!defined(UNIX) && !defined(__EMX__)) || defined(ARCHIE) || defined(PROTO) +/* + * Expand the file names in the global argument list. + * If "fnum_list" is not NULL, use "fnum_list[fnum_len]" as a list of buffer + * numbers to be re-used. + */ +void alist_expand(fnum_list, fnum_len) +int *fnum_list; +int fnum_len; +{ + char_u **old_arg_files; + int old_arg_count; + char_u **new_arg_files; + int new_arg_file_count; + char_u *save_p_su = p_su; + int i; + + /* Don't use 'suffixes' here. This should work like the shell did the + * expansion. Also, the vimrc file isn't read yet, thus the user + * can't set the options. */ + p_su = empty_option; + old_arg_files = (char_u **)alloc((unsigned)(sizeof(char_u *) * GARGCOUNT)); + if (old_arg_files != NULL) { + for (i = 0; i < GARGCOUNT; ++i) + old_arg_files[i] = vim_strsave(GARGLIST[i].ae_fname); + old_arg_count = GARGCOUNT; + if (expand_wildcards(old_arg_count, old_arg_files, + &new_arg_file_count, &new_arg_files, + EW_FILE|EW_NOTFOUND|EW_ADDSLASH|EW_NOERROR) == OK + && new_arg_file_count > 0) { + alist_set(&global_alist, new_arg_file_count, new_arg_files, + TRUE, fnum_list, fnum_len); + FreeWild(old_arg_count, old_arg_files); + } + } + p_su = save_p_su; +} +#endif + +/* + * Set the argument list for the current window. + * Takes over the allocated files[] and the allocated fnames in it. + */ +void alist_set(al, count, files, use_curbuf, fnum_list, fnum_len) +alist_T *al; +int count; +char_u **files; +int use_curbuf; +int *fnum_list; +int fnum_len; +{ + int i; + + alist_clear(al); + if (ga_grow(&al->al_ga, count) == OK) { + for (i = 0; i < count; ++i) { + if (got_int) { + /* When adding many buffers this can take a long time. Allow + * interrupting here. */ + while (i < count) + vim_free(files[i++]); + break; + } + + /* May set buffer name of a buffer previously used for the + * argument list, so that it's re-used by alist_add. */ + if (fnum_list != NULL && i < fnum_len) + buf_set_name(fnum_list[i], files[i]); + + alist_add(al, files[i], use_curbuf ? 2 : 1); + ui_breakcheck(); + } + vim_free(files); + } else + FreeWild(count, files); + if (al == &global_alist) + arg_had_last = FALSE; +} + +/* + * Add file "fname" to argument list "al". + * "fname" must have been allocated and "al" must have been checked for room. + */ +void alist_add(al, fname, set_fnum) +alist_T *al; +char_u *fname; +int set_fnum; /* 1: set buffer number; 2: re-use curbuf */ +{ + if (fname == NULL) /* don't add NULL file names */ + return; +#ifdef BACKSLASH_IN_FILENAME + slash_adjust(fname); +#endif + AARGLIST(al)[al->al_ga.ga_len].ae_fname = fname; + if (set_fnum > 0) + AARGLIST(al)[al->al_ga.ga_len].ae_fnum = + buflist_add(fname, BLN_LISTED | (set_fnum == 2 ? BLN_CURBUF : 0)); + ++al->al_ga.ga_len; +} + +#if defined(BACKSLASH_IN_FILENAME) || defined(PROTO) +/* + * Adjust slashes in file names. Called after 'shellslash' was set. + */ +void alist_slash_adjust() { + int i; + win_T *wp; + tabpage_T *tp; + + for (i = 0; i < GARGCOUNT; ++i) + if (GARGLIST[i].ae_fname != NULL) + slash_adjust(GARGLIST[i].ae_fname); + FOR_ALL_TAB_WINDOWS(tp, wp) + if (wp->w_alist != &global_alist) + for (i = 0; i < WARGCOUNT(wp); ++i) + if (WARGLIST(wp)[i].ae_fname != NULL) + slash_adjust(WARGLIST(wp)[i].ae_fname); +} + +#endif + +/* + * ":preserve". + */ +static void ex_preserve(eap) +exarg_T *eap UNUSED; +{ + curbuf->b_flags |= BF_PRESERVED; + ml_preserve(curbuf, TRUE); +} + +/* + * ":recover". + */ +static void ex_recover(eap) +exarg_T *eap; +{ + /* Set recoverymode right away to avoid the ATTENTION prompt. */ + recoverymode = TRUE; + if (!check_changed(curbuf, (p_awa ? CCGD_AW : 0) + | CCGD_MULTWIN + | (eap->forceit ? CCGD_FORCEIT : 0) + | CCGD_EXCMD) + + && (*eap->arg == NUL + || setfname(curbuf, eap->arg, NULL, TRUE) == OK)) + ml_recover(); + recoverymode = FALSE; +} + +/* + * Command modifier used in a wrong way. + */ +static void ex_wrongmodifier(eap) +exarg_T *eap; +{ + eap->errmsg = e_invcmd; +} + +/* + * :sview [+command] file split window with new file, read-only + * :split [[+command] file] split window with current or new file + * :vsplit [[+command] file] split window vertically with current or new file + * :new [[+command] file] split window with no or new file + * :vnew [[+command] file] split vertically window with no or new file + * :sfind [+command] file split window with file in 'path' + * + * :tabedit open new Tab page with empty window + * :tabedit [+command] file open new Tab page and edit "file" + * :tabnew [[+command] file] just like :tabedit + * :tabfind [+command] file open new Tab page and find "file" + */ +void ex_splitview(eap) +exarg_T *eap; +{ + win_T *old_curwin = curwin; + char_u *fname = NULL; + + + + /* A ":split" in the quickfix window works like ":new". Don't want two + * quickfix windows. But it's OK when doing ":tab split". */ + if (bt_quickfix(curbuf) && cmdmod.tab == 0) { + if (eap->cmdidx == CMD_split) + eap->cmdidx = CMD_new; + if (eap->cmdidx == CMD_vsplit) + eap->cmdidx = CMD_vnew; + } + + if (eap->cmdidx == CMD_sfind || eap->cmdidx == CMD_tabfind) { + fname = find_file_in_path(eap->arg, (int)STRLEN(eap->arg), + FNAME_MESS, TRUE, curbuf->b_ffname); + if (fname == NULL) + goto theend; + eap->arg = fname; + } + + /* + * Either open new tab page or split the window. + */ + if (eap->cmdidx == CMD_tabedit + || eap->cmdidx == CMD_tabfind + || eap->cmdidx == CMD_tabnew) { + if (win_new_tabpage(cmdmod.tab != 0 ? cmdmod.tab + : eap->addr_count == 0 ? 0 + : (int)eap->line2 + 1) != FAIL) { + do_exedit(eap, old_curwin); + + /* set the alternate buffer for the window we came from */ + if (curwin != old_curwin + && win_valid(old_curwin) + && old_curwin->w_buffer != curbuf + && !cmdmod.keepalt) + old_curwin->w_alt_fnum = curbuf->b_fnum; + } + } else if (win_split(eap->addr_count > 0 ? (int)eap->line2 : 0, + *eap->cmd == 'v' ? WSP_VERT : 0) != FAIL) { + /* Reset 'scrollbind' when editing another file, but keep it when + * doing ":split" without arguments. */ + if (*eap->arg != NUL + ) { + RESET_BINDING(curwin); + } else + do_check_scrollbind(FALSE); + do_exedit(eap, old_curwin); + } + + +theend: + vim_free(fname); +} + +/* + * Open a new tab page. + */ +void tabpage_new() { + exarg_T ea; + + vim_memset(&ea, 0, sizeof(ea)); + ea.cmdidx = CMD_tabnew; + ea.cmd = (char_u *)"tabn"; + ea.arg = (char_u *)""; + ex_splitview(&ea); +} + +/* + * :tabnext command + */ +static void ex_tabnext(eap) +exarg_T *eap; +{ + switch (eap->cmdidx) { + case CMD_tabfirst: + case CMD_tabrewind: + goto_tabpage(1); + break; + case CMD_tablast: + goto_tabpage(9999); + break; + case CMD_tabprevious: + case CMD_tabNext: + goto_tabpage(eap->addr_count == 0 ? -1 : -(int)eap->line2); + break; + default: /* CMD_tabnext */ + goto_tabpage(eap->addr_count == 0 ? 0 : (int)eap->line2); + break; + } +} + +/* + * :tabmove command + */ +static void ex_tabmove(eap) +exarg_T *eap; +{ + int tab_number = 9999; + + if (eap->arg && *eap->arg != NUL) { + char_u *p = eap->arg; + int relative = 0; /* argument +N/-N means: move N places to the + * right/left relative to the current position. */ + + if (*eap->arg == '-') { + relative = -1; + p = eap->arg + 1; + } else if (*eap->arg == '+') { + relative = 1; + p = eap->arg + 1; + } else + p = eap->arg; + + if (p == skipdigits(p)) { + /* No numbers as argument. */ + eap->errmsg = e_invarg; + return; + } + + tab_number = getdigits(&p); + if (relative != 0) + tab_number = tab_number * relative + tabpage_index(curtab) - 1; ; + } else if (eap->addr_count != 0) + tab_number = eap->line2; + + tabpage_move(tab_number); +} + +/* + * :tabs command: List tabs and their contents. + */ +static void ex_tabs(eap) +exarg_T *eap UNUSED; +{ + tabpage_T *tp; + win_T *wp; + int tabcount = 1; + + msg_start(); + msg_scroll = TRUE; + for (tp = first_tabpage; tp != NULL && !got_int; tp = tp->tp_next) { + msg_putchar('\n'); + vim_snprintf((char *)IObuff, IOSIZE, _("Tab page %d"), tabcount++); + msg_outtrans_attr(IObuff, hl_attr(HLF_T)); + out_flush(); /* output one line at a time */ + ui_breakcheck(); + + if (tp == curtab) + wp = firstwin; + else + wp = tp->tp_firstwin; + for (; wp != NULL && !got_int; wp = wp->w_next) { + msg_putchar('\n'); + msg_putchar(wp == curwin ? '>' : ' '); + msg_putchar(' '); + msg_putchar(bufIsChanged(wp->w_buffer) ? '+' : ' '); + msg_putchar(' '); + if (buf_spname(wp->w_buffer) != NULL) + vim_strncpy(IObuff, buf_spname(wp->w_buffer), IOSIZE - 1); + else + home_replace(wp->w_buffer, wp->w_buffer->b_fname, + IObuff, IOSIZE, TRUE); + msg_outtrans(IObuff); + out_flush(); /* output one line at a time */ + ui_breakcheck(); + } + } +} + + +/* + * ":mode": Set screen mode. + * If no argument given, just get the screen size and redraw. + */ +static void ex_mode(eap) +exarg_T *eap; +{ + if (*eap->arg == NUL) + shell_resized(); + else + mch_screenmode(eap->arg); +} + +/* + * ":resize". + * set, increment or decrement current window height + */ +static void ex_resize(eap) +exarg_T *eap; +{ + int n; + win_T *wp = curwin; + + if (eap->addr_count > 0) { + n = eap->line2; + for (wp = firstwin; wp->w_next != NULL && --n > 0; wp = wp->w_next) + ; + } + + n = atol((char *)eap->arg); + if (cmdmod.split & WSP_VERT) { + if (*eap->arg == '-' || *eap->arg == '+') + n += W_WIDTH(curwin); + else if (n == 0 && eap->arg[0] == NUL) /* default is very wide */ + n = 9999; + win_setwidth_win((int)n, wp); + } else { + if (*eap->arg == '-' || *eap->arg == '+') + n += curwin->w_height; + else if (n == 0 && eap->arg[0] == NUL) /* default is very wide */ + n = 9999; + win_setheight_win((int)n, wp); + } +} + +/* + * ":find [+command] " command. + */ +static void ex_find(eap) +exarg_T *eap; +{ + char_u *fname; + int count; + + fname = find_file_in_path(eap->arg, (int)STRLEN(eap->arg), FNAME_MESS, + TRUE, curbuf->b_ffname); + if (eap->addr_count > 0) { + /* Repeat finding the file "count" times. This matters when it + * appears several times in the path. */ + count = eap->line2; + while (fname != NULL && --count > 0) { + vim_free(fname); + fname = find_file_in_path(NULL, 0, FNAME_MESS, + FALSE, curbuf->b_ffname); + } + } + + if (fname != NULL) { + eap->arg = fname; + do_exedit(eap, NULL); + vim_free(fname); + } +} + +/* + * ":open" simulation: for now just work like ":visual". + */ +static void ex_open(eap) +exarg_T *eap; +{ + regmatch_T regmatch; + char_u *p; + + curwin->w_cursor.lnum = eap->line2; + beginline(BL_SOL | BL_FIX); + if (*eap->arg == '/') { + /* ":open /pattern/": put cursor in column found with pattern */ + ++eap->arg; + p = skip_regexp(eap->arg, '/', p_magic, NULL); + *p = NUL; + regmatch.regprog = vim_regcomp(eap->arg, p_magic ? RE_MAGIC : 0); + if (regmatch.regprog != NULL) { + regmatch.rm_ic = p_ic; + p = ml_get_curline(); + if (vim_regexec(®match, p, (colnr_T)0)) + curwin->w_cursor.col = (colnr_T)(regmatch.startp[0] - p); + else + EMSG(_(e_nomatch)); + vim_regfree(regmatch.regprog); + } + /* Move to the NUL, ignore any other arguments. */ + eap->arg += STRLEN(eap->arg); + } + check_cursor(); + + eap->cmdidx = CMD_visual; + do_exedit(eap, NULL); +} + +/* + * ":edit", ":badd", ":visual". + */ +static void ex_edit(eap) +exarg_T *eap; +{ + do_exedit(eap, NULL); +} + +/* + * ":edit " command and alikes. + */ +void do_exedit(eap, old_curwin) +exarg_T *eap; +win_T *old_curwin; /* curwin before doing a split or NULL */ +{ + int n; + int need_hide; + int exmode_was = exmode_active; + + /* + * ":vi" command ends Ex mode. + */ + if (exmode_active && (eap->cmdidx == CMD_visual + || eap->cmdidx == CMD_view)) { + exmode_active = FALSE; + if (*eap->arg == NUL) { + /* Special case: ":global/pat/visual\NLvi-commands" */ + if (global_busy) { + int rd = RedrawingDisabled; + int nwr = no_wait_return; + int ms = msg_scroll; + + if (eap->nextcmd != NULL) { + stuffReadbuff(eap->nextcmd); + eap->nextcmd = NULL; + } + + if (exmode_was != EXMODE_VIM) + settmode(TMODE_RAW); + RedrawingDisabled = 0; + no_wait_return = 0; + need_wait_return = FALSE; + msg_scroll = 0; + must_redraw = CLEAR; + + main_loop(FALSE, TRUE); + + RedrawingDisabled = rd; + no_wait_return = nwr; + msg_scroll = ms; + } + return; + } + } + + if ((eap->cmdidx == CMD_new + || eap->cmdidx == CMD_tabnew + || eap->cmdidx == CMD_tabedit + || eap->cmdidx == CMD_vnew + ) && *eap->arg == NUL) { + /* ":new" or ":tabnew" without argument: edit an new empty buffer */ + setpcmark(); + (void)do_ecmd(0, NULL, NULL, eap, ECMD_ONE, + ECMD_HIDE + (eap->forceit ? ECMD_FORCEIT : 0), + old_curwin == NULL ? curwin : NULL); + } else if ((eap->cmdidx != CMD_split + && eap->cmdidx != CMD_vsplit + ) + || *eap->arg != NUL + ) { + /* Can't edit another file when "curbuf_lock" is set. Only ":edit" + * can bring us here, others are stopped earlier. */ + if (*eap->arg != NUL && curbuf_locked()) + return; + n = readonlymode; + if (eap->cmdidx == CMD_view || eap->cmdidx == CMD_sview) + readonlymode = TRUE; + else if (eap->cmdidx == CMD_enew) + readonlymode = FALSE; /* 'readonly' doesn't make sense in an + empty buffer */ + setpcmark(); + if (do_ecmd(0, (eap->cmdidx == CMD_enew ? NULL : eap->arg), + NULL, eap, + /* ":edit" goes to first line if Vi compatible */ + (*eap->arg == NUL && eap->do_ecmd_lnum == 0 + && vim_strchr(p_cpo, CPO_GOTO1) != NULL) + ? ECMD_ONE : eap->do_ecmd_lnum, + (P_HID(curbuf) ? ECMD_HIDE : 0) + + (eap->forceit ? ECMD_FORCEIT : 0) + + (eap->cmdidx == CMD_badd ? ECMD_ADDBUF : 0 ) + , old_curwin == NULL ? curwin : NULL) == FAIL) { + /* Editing the file failed. If the window was split, close it. */ + if (old_curwin != NULL) { + need_hide = (curbufIsChanged() && curbuf->b_nwindows <= 1); + if (!need_hide || P_HID(curbuf)) { + cleanup_T cs; + + /* Reset the error/interrupt/exception state here so that + * aborting() returns FALSE when closing a window. */ + enter_cleanup(&cs); + win_close(curwin, !need_hide && !P_HID(curbuf)); + + /* Restore the error/interrupt/exception state if not + * discarded by a new aborting error, interrupt, or + * uncaught exception. */ + leave_cleanup(&cs); + } + } + } else if (readonlymode && curbuf->b_nwindows == 1) { + /* When editing an already visited buffer, 'readonly' won't be set + * but the previous value is kept. With ":view" and ":sview" we + * want the file to be readonly, except when another window is + * editing the same buffer. */ + curbuf->b_p_ro = TRUE; + } + readonlymode = n; + } else { + if (eap->do_ecmd_cmd != NULL) + do_cmdline_cmd(eap->do_ecmd_cmd); + n = curwin->w_arg_idx_invalid; + check_arg_idx(curwin); + if (n != curwin->w_arg_idx_invalid) + maketitle(); + } + + /* + * if ":split file" worked, set alternate file name in old window to new + * file + */ + if (old_curwin != NULL + && *eap->arg != NUL + && curwin != old_curwin + && win_valid(old_curwin) + && old_curwin->w_buffer != curbuf + && !cmdmod.keepalt) + old_curwin->w_alt_fnum = curbuf->b_fnum; + + ex_no_reprint = TRUE; +} + +/* + * ":gui" and ":gvim" when there is no GUI. + */ +static void ex_nogui(eap) +exarg_T *eap; +{ + eap->errmsg = e_nogvim; +} + + + +static void ex_swapname(eap) +exarg_T *eap UNUSED; +{ + if (curbuf->b_ml.ml_mfp == NULL || curbuf->b_ml.ml_mfp->mf_fname == NULL) + MSG(_("No swap file")); + else + msg(curbuf->b_ml.ml_mfp->mf_fname); +} + +/* + * ":syncbind" forces all 'scrollbind' windows to have the same relative + * offset. + * (1998-11-02 16:21:01 R. Edward Ralston ) + */ +static void ex_syncbind(eap) +exarg_T *eap UNUSED; +{ + win_T *wp; + win_T *save_curwin = curwin; + buf_T *save_curbuf = curbuf; + long topline; + long y; + linenr_T old_linenr = curwin->w_cursor.lnum; + + setpcmark(); + + /* + * determine max topline + */ + if (curwin->w_p_scb) { + topline = curwin->w_topline; + for (wp = firstwin; wp; wp = wp->w_next) { + if (wp->w_p_scb && wp->w_buffer) { + y = wp->w_buffer->b_ml.ml_line_count - p_so; + if (topline > y) + topline = y; + } + } + if (topline < 1) + topline = 1; + } else { + topline = 1; + } + + + /* + * Set all scrollbind windows to the same topline. + */ + for (curwin = firstwin; curwin; curwin = curwin->w_next) { + if (curwin->w_p_scb) { + curbuf = curwin->w_buffer; + y = topline - curwin->w_topline; + if (y > 0) + scrollup(y, TRUE); + else + scrolldown(-y, TRUE); + curwin->w_scbind_pos = topline; + redraw_later(VALID); + cursor_correct(); + curwin->w_redr_status = TRUE; + } + } + curwin = save_curwin; + curbuf = save_curbuf; + if (curwin->w_p_scb) { + did_syncbind = TRUE; + checkpcmark(); + if (old_linenr != curwin->w_cursor.lnum) { + char_u ctrl_o[2]; + + ctrl_o[0] = Ctrl_O; + ctrl_o[1] = 0; + ins_typebuf(ctrl_o, REMAP_NONE, 0, TRUE, FALSE); + } + } +} + + +static void ex_read(eap) +exarg_T *eap; +{ + int i; + int empty = (curbuf->b_ml.ml_flags & ML_EMPTY); + linenr_T lnum; + + if (eap->usefilter) /* :r!cmd */ + do_bang(1, eap, FALSE, FALSE, TRUE); + else { + if (u_save(eap->line2, (linenr_T)(eap->line2 + 1)) == FAIL) + return; + + if (*eap->arg == NUL) { + if (check_fname() == FAIL) /* check for no file name */ + return; + i = readfile(curbuf->b_ffname, curbuf->b_fname, + eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0); + } else { + if (vim_strchr(p_cpo, CPO_ALTREAD) != NULL) + (void)setaltfname(eap->arg, eap->arg, (linenr_T)1); + i = readfile(eap->arg, NULL, + eap->line2, (linenr_T)0, (linenr_T)MAXLNUM, eap, 0); + + } + if (i == FAIL) { + if (!aborting()) + EMSG2(_(e_notopen), eap->arg); + } else { + if (empty && exmode_active) { + /* Delete the empty line that remains. Historically ex does + * this but vi doesn't. */ + if (eap->line2 == 0) + lnum = curbuf->b_ml.ml_line_count; + else + lnum = 1; + if (*ml_get(lnum) == NUL && u_savedel(lnum, 1L) == OK) { + ml_delete(lnum, FALSE); + if (curwin->w_cursor.lnum > 1 + && curwin->w_cursor.lnum >= lnum) + --curwin->w_cursor.lnum; + deleted_lines_mark(lnum, 1L); + } + } + redraw_curbuf_later(VALID); + } + } +} + +static char_u *prev_dir = NULL; + +#if defined(EXITFREE) || defined(PROTO) +void free_cd_dir() { + vim_free(prev_dir); + prev_dir = NULL; + + vim_free(globaldir); + globaldir = NULL; +} + +#endif + +/* + * Deal with the side effects of changing the current directory. + * When "local" is TRUE then this was after an ":lcd" command. + */ +void post_chdir(local) +int local; +{ + vim_free(curwin->w_localdir); + curwin->w_localdir = NULL; + if (local) { + /* If still in global directory, need to remember current + * directory as global directory. */ + if (globaldir == NULL && prev_dir != NULL) + globaldir = vim_strsave(prev_dir); + /* Remember this local directory for the window. */ + if (mch_dirname(NameBuff, MAXPATHL) == OK) + curwin->w_localdir = vim_strsave(NameBuff); + } else { + /* We are now in the global directory, no need to remember its + * name. */ + vim_free(globaldir); + globaldir = NULL; + } + + shorten_fnames(TRUE); +} + + +/* + * ":cd", ":lcd", ":chdir" and ":lchdir". + */ +void ex_cd(eap) +exarg_T *eap; +{ + char_u *new_dir; + char_u *tofree; + + new_dir = eap->arg; +#if !defined(UNIX) && !defined(VMS) + /* for non-UNIX ":cd" means: print current directory */ + if (*new_dir == NUL) + ex_pwd(NULL); + else +#endif + { + if (allbuf_locked()) + return; + if (vim_strchr(p_cpo, CPO_CHDIR) != NULL && curbufIsChanged() + && !eap->forceit) { + EMSG(_( + "E747: Cannot change directory, buffer is modified (add ! to override)")); + return; + } + + /* ":cd -": Change to previous directory */ + if (STRCMP(new_dir, "-") == 0) { + if (prev_dir == NULL) { + EMSG(_("E186: No previous directory")); + return; + } + new_dir = prev_dir; + } + + /* Save current directory for next ":cd -" */ + tofree = prev_dir; + if (mch_dirname(NameBuff, MAXPATHL) == OK) + prev_dir = vim_strsave(NameBuff); + else + prev_dir = NULL; + +#if defined(UNIX) || defined(VMS) + /* for UNIX ":cd" means: go to home directory */ + if (*new_dir == NUL) { + /* use NameBuff for home directory name */ + expand_env((char_u *)"$HOME", NameBuff, MAXPATHL); + new_dir = NameBuff; + } +#endif + if (new_dir == NULL || vim_chdir(new_dir)) + EMSG(_(e_failed)); + else { + post_chdir(eap->cmdidx == CMD_lcd || eap->cmdidx == CMD_lchdir); + + /* Echo the new current directory if the command was typed. */ + if (KeyTyped || p_verbose >= 5) + ex_pwd(eap); + } + vim_free(tofree); + } +} + +/* + * ":pwd". + */ +static void ex_pwd(eap) +exarg_T *eap UNUSED; +{ + if (mch_dirname(NameBuff, MAXPATHL) == OK) { +#ifdef BACKSLASH_IN_FILENAME + slash_adjust(NameBuff); +#endif + msg(NameBuff); + } else + EMSG(_("E187: Unknown")); +} + +/* + * ":=". + */ +static void ex_equal(eap) +exarg_T *eap; +{ + smsg((char_u *)"%ld", (long)eap->line2); + ex_may_print(eap); +} + +static void ex_sleep(eap) +exarg_T *eap; +{ + int n; + long len; + + if (cursor_valid()) { + n = W_WINROW(curwin) + curwin->w_wrow - msg_scrolled; + if (n >= 0) + windgoto((int)n, curwin->w_wcol); + } + + len = eap->line2; + switch (*eap->arg) { + case 'm': break; + case NUL: len *= 1000L; break; + default: EMSG2(_(e_invarg2), eap->arg); return; + } + do_sleep(len); +} + +/* + * Sleep for "msec" milliseconds, but keep checking for a CTRL-C every second. + */ +void do_sleep(msec) +long msec; +{ + long done; + + cursor_on(); + out_flush(); + for (done = 0; !got_int && done < msec; done += 1000L) { + ui_delay(msec - done > 1000L ? 1000L : msec - done, TRUE); + ui_breakcheck(); + } +} + +static void do_exmap(eap, isabbrev) +exarg_T *eap; +int isabbrev; +{ + int mode; + char_u *cmdp; + + cmdp = eap->cmd; + mode = get_map_mode(&cmdp, eap->forceit || isabbrev); + + switch (do_map((*cmdp == 'n') ? 2 : (*cmdp == 'u'), + eap->arg, mode, isabbrev)) { + case 1: EMSG(_(e_invarg)); + break; + case 2: EMSG(isabbrev ? _(e_noabbr) : _(e_nomap)); + break; + } +} + +/* + * ":winsize" command (obsolete). + */ +static void ex_winsize(eap) +exarg_T *eap; +{ + int w, h; + char_u *arg = eap->arg; + char_u *p; + + w = getdigits(&arg); + arg = skipwhite(arg); + p = arg; + h = getdigits(&arg); + if (*p != NUL && *arg == NUL) + set_shellsize(w, h, TRUE); + else + EMSG(_("E465: :winsize requires two number arguments")); +} + +static void ex_wincmd(eap) +exarg_T *eap; +{ + int xchar = NUL; + char_u *p; + + if (*eap->arg == 'g' || *eap->arg == Ctrl_G) { + /* CTRL-W g and CTRL-W CTRL-G have an extra command character */ + if (eap->arg[1] == NUL) { + EMSG(_(e_invarg)); + return; + } + xchar = eap->arg[1]; + p = eap->arg + 2; + } else + p = eap->arg + 1; + + eap->nextcmd = check_nextcmd(p); + p = skipwhite(p); + if (*p != NUL && *p != '"' && eap->nextcmd == NULL) + EMSG(_(e_invarg)); + else if (!eap->skip) { + /* Pass flags on for ":vertical wincmd ]". */ + postponed_split_flags = cmdmod.split; + postponed_split_tab = cmdmod.tab; + do_window(*eap->arg, eap->addr_count > 0 ? eap->line2 : 0L, xchar); + postponed_split_flags = 0; + postponed_split_tab = 0; + } +} + +#if defined(FEAT_GUI) || defined(UNIX) || defined(VMS) || defined(MSWIN) +/* + * ":winpos". + */ +static void ex_winpos(eap) +exarg_T *eap; +{ + int x, y; + char_u *arg = eap->arg; + char_u *p; + + if (*arg == NUL) { + EMSG(_("E188: Obtaining window position not implemented for this platform")); + } else { + x = getdigits(&arg); + arg = skipwhite(arg); + p = arg; + y = getdigits(&arg); + if (*p == NUL || *arg != NUL) { + EMSG(_("E466: :winpos requires two number arguments")); + return; + } +# ifdef HAVE_TGETENT + if (*T_CWP) + term_set_winpos(x, y); +# endif + } +} +#endif + +/* + * Handle command that work like operators: ":delete", ":yank", ":>" and ":<". + */ +static void ex_operators(eap) +exarg_T *eap; +{ + oparg_T oa; + + clear_oparg(&oa); + oa.regname = eap->regname; + oa.start.lnum = eap->line1; + oa.end.lnum = eap->line2; + oa.line_count = eap->line2 - eap->line1 + 1; + oa.motion_type = MLINE; + virtual_op = FALSE; + if (eap->cmdidx != CMD_yank) { /* position cursor for undo */ + setpcmark(); + curwin->w_cursor.lnum = eap->line1; + beginline(BL_SOL | BL_FIX); + } + + if (VIsual_active) + end_visual_mode(); + + switch (eap->cmdidx) { + case CMD_delete: + oa.op_type = OP_DELETE; + op_delete(&oa); + break; + + case CMD_yank: + oa.op_type = OP_YANK; + (void)op_yank(&oa, FALSE, TRUE); + break; + + default: /* CMD_rshift or CMD_lshift */ + if ( + (eap->cmdidx == CMD_rshift) ^ curwin->w_p_rl + ) + oa.op_type = OP_RSHIFT; + else + oa.op_type = OP_LSHIFT; + op_shift(&oa, FALSE, eap->amount); + break; + } + virtual_op = MAYBE; + ex_may_print(eap); +} + +/* + * ":put". + */ +static void ex_put(eap) +exarg_T *eap; +{ + /* ":0put" works like ":1put!". */ + if (eap->line2 == 0) { + eap->line2 = 1; + eap->forceit = TRUE; + } + curwin->w_cursor.lnum = eap->line2; + do_put(eap->regname, eap->forceit ? BACKWARD : FORWARD, 1L, + PUT_LINE|PUT_CURSLINE); +} + +/* + * Handle ":copy" and ":move". + */ +static void ex_copymove(eap) +exarg_T *eap; +{ + long n; + + n = get_address(&eap->arg, FALSE, FALSE); + if (eap->arg == NULL) { /* error detected */ + eap->nextcmd = NULL; + return; + } + get_flags(eap); + + /* + * move or copy lines from 'eap->line1'-'eap->line2' to below line 'n' + */ + if (n == MAXLNUM || n < 0 || n > curbuf->b_ml.ml_line_count) { + EMSG(_(e_invaddr)); + return; + } + + if (eap->cmdidx == CMD_move) { + if (do_move(eap->line1, eap->line2, n) == FAIL) + return; + } else + ex_copy(eap->line1, eap->line2, n); + u_clearline(); + beginline(BL_SOL | BL_FIX); + ex_may_print(eap); +} + +/* + * Print the current line if flags were given to the Ex command. + */ +static void ex_may_print(eap) +exarg_T *eap; +{ + if (eap->flags != 0) { + print_line(curwin->w_cursor.lnum, (eap->flags & EXFLAG_NR), + (eap->flags & EXFLAG_LIST)); + ex_no_reprint = TRUE; + } +} + +/* + * ":smagic" and ":snomagic". + */ +static void ex_submagic(eap) +exarg_T *eap; +{ + int magic_save = p_magic; + + p_magic = (eap->cmdidx == CMD_smagic); + do_sub(eap); + p_magic = magic_save; +} + +/* + * ":join". + */ +static void ex_join(eap) +exarg_T *eap; +{ + curwin->w_cursor.lnum = eap->line1; + if (eap->line1 == eap->line2) { + if (eap->addr_count >= 2) /* :2,2join does nothing */ + return; + if (eap->line2 == curbuf->b_ml.ml_line_count) { + beep_flush(); + return; + } + ++eap->line2; + } + (void)do_join(eap->line2 - eap->line1 + 1, !eap->forceit, TRUE, TRUE); + beginline(BL_WHITE | BL_FIX); + ex_may_print(eap); +} + +/* + * ":[addr]@r" or ":[addr]*r": execute register + */ +static void ex_at(eap) +exarg_T *eap; +{ + int c; + int prev_len = typebuf.tb_len; + + curwin->w_cursor.lnum = eap->line2; + +#ifdef USE_ON_FLY_SCROLL + dont_scroll = TRUE; /* disallow scrolling here */ +#endif + + /* get the register name. No name means to use the previous one */ + c = *eap->arg; + if (c == NUL || (c == '*' && *eap->cmd == '*')) + c = '@'; + /* Put the register in the typeahead buffer with the "silent" flag. */ + if (do_execreg(c, TRUE, vim_strchr(p_cpo, CPO_EXECBUF) != NULL, TRUE) + == FAIL) { + beep_flush(); + } else { + int save_efr = exec_from_reg; + + exec_from_reg = TRUE; + + /* + * Execute from the typeahead buffer. + * Continue until the stuff buffer is empty and all added characters + * have been consumed. + */ + while (!stuff_empty() || typebuf.tb_len > prev_len) + (void)do_cmdline(NULL, getexline, NULL, DOCMD_NOWAIT|DOCMD_VERBOSE); + + exec_from_reg = save_efr; + } +} + +/* + * ":!". + */ +static void ex_bang(eap) +exarg_T *eap; +{ + do_bang(eap->addr_count, eap, eap->forceit, TRUE, TRUE); +} + +/* + * ":undo". + */ +static void ex_undo(eap) +exarg_T *eap UNUSED; +{ + if (eap->addr_count == 1) /* :undo 123 */ + undo_time(eap->line2, FALSE, FALSE, TRUE); + else + u_undo(1); +} + +static void ex_wundo(eap) +exarg_T *eap; +{ + char_u hash[UNDO_HASH_SIZE]; + + u_compute_hash(hash); + u_write_undo(eap->arg, eap->forceit, curbuf, hash); +} + +static void ex_rundo(eap) +exarg_T *eap; +{ + char_u hash[UNDO_HASH_SIZE]; + + u_compute_hash(hash); + u_read_undo(eap->arg, hash, NULL); +} + +/* + * ":redo". + */ +static void ex_redo(eap) +exarg_T *eap UNUSED; +{ + u_redo(1); +} + +/* + * ":earlier" and ":later". + */ +static void ex_later(eap) +exarg_T *eap; +{ + long count = 0; + int sec = FALSE; + int file = FALSE; + char_u *p = eap->arg; + + if (*p == NUL) + count = 1; + else if (isdigit(*p)) { + count = getdigits(&p); + switch (*p) { + case 's': ++p; sec = TRUE; break; + case 'm': ++p; sec = TRUE; count *= 60; break; + case 'h': ++p; sec = TRUE; count *= 60 * 60; break; + case 'd': ++p; sec = TRUE; count *= 24 * 60 * 60; break; + case 'f': ++p; file = TRUE; break; + } + } + + if (*p != NUL) + EMSG2(_(e_invarg2), eap->arg); + else + undo_time(eap->cmdidx == CMD_earlier ? -count : count, + sec, file, FALSE); +} + +/* + * ":redir": start/stop redirection. + */ +static void ex_redir(eap) +exarg_T *eap; +{ + char *mode; + char_u *fname; + char_u *arg = eap->arg; + + if (STRICMP(eap->arg, "END") == 0) + close_redir(); + else { + if (*arg == '>') { + ++arg; + if (*arg == '>') { + ++arg; + mode = "a"; + } else + mode = "w"; + arg = skipwhite(arg); + + close_redir(); + + /* Expand environment variables and "~/". */ + fname = expand_env_save(arg); + if (fname == NULL) + return; + + redir_fd = open_exfile(fname, eap->forceit, mode); + vim_free(fname); + } else if (*arg == '@') { + /* redirect to a register a-z (resp. A-Z for appending) */ + close_redir(); + ++arg; + if (ASCII_ISALPHA(*arg) + || *arg == '"') { + redir_reg = *arg++; + if (*arg == '>' && arg[1] == '>') /* append */ + arg += 2; + else { + /* Can use both "@a" and "@a>". */ + if (*arg == '>') + arg++; + /* Make register empty when not using @A-@Z and the + * command is valid. */ + if (*arg == NUL && !isupper(redir_reg)) + write_reg_contents(redir_reg, (char_u *)"", -1, FALSE); + } + } + if (*arg != NUL) { + redir_reg = 0; + EMSG2(_(e_invarg2), eap->arg); + } + } else if (*arg == '=' && arg[1] == '>') { + int append; + + /* redirect to a variable */ + close_redir(); + arg += 2; + + if (*arg == '>') { + ++arg; + append = TRUE; + } else + append = FALSE; + + if (var_redir_start(skipwhite(arg), append) == OK) + redir_vname = 1; + } + /* TODO: redirect to a buffer */ + else + EMSG2(_(e_invarg2), eap->arg); + } + + /* Make sure redirection is not off. Can happen for cmdline completion + * that indirectly invokes a command to catch its output. */ + if (redir_fd != NULL + || redir_reg || redir_vname + ) + redir_off = FALSE; +} + +/* + * ":redraw": force redraw + */ +static void ex_redraw(eap) +exarg_T *eap; +{ + int r = RedrawingDisabled; + int p = p_lz; + + RedrawingDisabled = 0; + p_lz = FALSE; + update_topline(); + update_screen(eap->forceit ? CLEAR : + VIsual_active ? INVERTED : + 0); + if (need_maketitle) + maketitle(); + RedrawingDisabled = r; + p_lz = p; + + /* Reset msg_didout, so that a message that's there is overwritten. */ + msg_didout = FALSE; + msg_col = 0; + + /* No need to wait after an intentional redraw. */ + need_wait_return = FALSE; + + out_flush(); +} + +/* + * ":redrawstatus": force redraw of status line(s) + */ +static void ex_redrawstatus(eap) +exarg_T *eap UNUSED; +{ + int r = RedrawingDisabled; + int p = p_lz; + + RedrawingDisabled = 0; + p_lz = FALSE; + if (eap->forceit) + status_redraw_all(); + else + status_redraw_curbuf(); + update_screen( + VIsual_active ? INVERTED : + 0); + RedrawingDisabled = r; + p_lz = p; + out_flush(); +} + +static void close_redir() { + if (redir_fd != NULL) { + fclose(redir_fd); + redir_fd = NULL; + } + redir_reg = 0; + if (redir_vname) { + var_redir_stop(); + redir_vname = 0; + } +} + +#if defined(FEAT_SESSION) && defined(USE_CRNL) +# define MKSESSION_NL +static int mksession_nl = FALSE; /* use NL only in put_eol() */ +#endif + +/* + * ":mkexrc", ":mkvimrc", ":mkview" and ":mksession". + */ +static void ex_mkrc(eap) +exarg_T *eap; +{ + FILE *fd; + int failed = FALSE; + char_u *fname; + int view_session = FALSE; + int using_vdir = FALSE; /* using 'viewdir'? */ + char_u *viewFile = NULL; + unsigned *flagp; + + if (eap->cmdidx == CMD_mksession || eap->cmdidx == CMD_mkview) { + view_session = TRUE; + } + + /* Use the short file name until ":lcd" is used. We also don't use the + * short file name when 'acd' is set, that is checked later. */ + did_lcd = FALSE; + + /* ":mkview" or ":mkview 9": generate file name with 'viewdir' */ + if (eap->cmdidx == CMD_mkview + && (*eap->arg == NUL + || (vim_isdigit(*eap->arg) && eap->arg[1] == NUL))) { + eap->forceit = TRUE; + fname = get_view_file(*eap->arg); + if (fname == NULL) + return; + viewFile = fname; + using_vdir = TRUE; + } else if (*eap->arg != NUL) + fname = eap->arg; + else if (eap->cmdidx == CMD_mkvimrc) + fname = (char_u *)VIMRC_FILE; + else if (eap->cmdidx == CMD_mksession) + fname = (char_u *)SESSION_FILE; + else + fname = (char_u *)EXRC_FILE; + + +#if defined(FEAT_SESSION) && defined(vim_mkdir) + /* When using 'viewdir' may have to create the directory. */ + if (using_vdir && !mch_isdir(p_vdir)) + vim_mkdir_emsg(p_vdir, 0755); +#endif + + fd = open_exfile(fname, eap->forceit, WRITEBIN); + if (fd != NULL) { + if (eap->cmdidx == CMD_mkview) + flagp = &vop_flags; + else + flagp = &ssop_flags; + +#ifdef MKSESSION_NL + /* "unix" in 'sessionoptions': use NL line separator */ + if (view_session && (*flagp & SSOP_UNIX)) + mksession_nl = TRUE; +#endif + + /* Write the version command for :mkvimrc */ + if (eap->cmdidx == CMD_mkvimrc) + (void)put_line(fd, "version 6.0"); + + if (eap->cmdidx == CMD_mksession) { + if (put_line(fd, "let SessionLoad = 1") == FAIL) + failed = TRUE; + } + + if (eap->cmdidx != CMD_mkview) { + /* Write setting 'compatible' first, because it has side effects. + * For that same reason only do it when needed. */ + if (p_cp) + (void)put_line(fd, "if !&cp | set cp | endif"); + else + (void)put_line(fd, "if &cp | set nocp | endif"); + } + + if (!view_session + || (eap->cmdidx == CMD_mksession + && (*flagp & SSOP_OPTIONS))) + failed |= (makemap(fd, NULL) == FAIL + || makeset(fd, OPT_GLOBAL, FALSE) == FAIL); + + if (!failed && view_session) { + if (put_line(fd, + "let s:so_save = &so | let s:siso_save = &siso | set so=0 siso=0") + == FAIL) + failed = TRUE; + if (eap->cmdidx == CMD_mksession) { + char_u *dirnow; /* current directory */ + + dirnow = alloc(MAXPATHL); + if (dirnow == NULL) + failed = TRUE; + else { + /* + * Change to session file's dir. + */ + if (mch_dirname(dirnow, MAXPATHL) == FAIL + || mch_chdir((char *)dirnow) != 0) + *dirnow = NUL; + if (*dirnow != NUL && (ssop_flags & SSOP_SESDIR)) { + if (vim_chdirfile(fname) == OK) + shorten_fnames(TRUE); + } else if (*dirnow != NUL + && (ssop_flags & SSOP_CURDIR) && globaldir != NULL) { + if (mch_chdir((char *)globaldir) == 0) + shorten_fnames(TRUE); + } + + failed |= (makeopens(fd, dirnow) == FAIL); + + /* restore original dir */ + if (*dirnow != NUL && ((ssop_flags & SSOP_SESDIR) + || ((ssop_flags & SSOP_CURDIR) && globaldir != + NULL))) { + if (mch_chdir((char *)dirnow) != 0) + EMSG(_(e_prev_dir)); + shorten_fnames(TRUE); + } + vim_free(dirnow); + } + } else { + failed |= (put_view(fd, curwin, !using_vdir, flagp, + -1) == FAIL); + } + if (put_line(fd, "let &so = s:so_save | let &siso = s:siso_save") + == FAIL) + failed = TRUE; + if (put_line(fd, "doautoall SessionLoadPost") == FAIL) + failed = TRUE; + if (eap->cmdidx == CMD_mksession) { + if (put_line(fd, "unlet SessionLoad") == FAIL) + failed = TRUE; + } + } + if (put_line(fd, "\" vim: set ft=vim :") == FAIL) + failed = TRUE; + + failed |= fclose(fd); + + if (failed) + EMSG(_(e_write)); + else if (eap->cmdidx == CMD_mksession) { + /* successful session write - set this_session var */ + char_u *tbuf; + + tbuf = alloc(MAXPATHL); + if (tbuf != NULL) { + if (vim_FullName(fname, tbuf, MAXPATHL, FALSE) == OK) + set_vim_var_string(VV_THIS_SESSION, tbuf, -1); + vim_free(tbuf); + } + } +#ifdef MKSESSION_NL + mksession_nl = FALSE; +#endif + } + + vim_free(viewFile); +} + +#if ((defined(FEAT_SESSION) || defined(FEAT_EVAL)) && defined(vim_mkdir)) \ + || defined(PROTO) +int vim_mkdir_emsg(name, prot) +char_u *name; +int prot UNUSED; +{ + if (vim_mkdir(name, prot) != 0) { + EMSG2(_("E739: Cannot create directory: %s"), name); + return FAIL; + } + return OK; +} +#endif + +/* + * Open a file for writing for an Ex command, with some checks. + * Return file descriptor, or NULL on failure. + */ +FILE * open_exfile(fname, forceit, mode) +char_u *fname; +int forceit; +char *mode; /* "w" for create new file or "a" for append */ +{ + FILE *fd; + +#ifdef UNIX + /* with Unix it is possible to open a directory */ + if (mch_isdir(fname)) { + EMSG2(_(e_isadir2), fname); + return NULL; + } +#endif + if (!forceit && *mode != 'a' && vim_fexists(fname)) { + EMSG2(_("E189: \"%s\" exists (add ! to override)"), fname); + return NULL; + } + + if ((fd = mch_fopen((char *)fname, mode)) == NULL) + EMSG2(_("E190: Cannot open \"%s\" for writing"), fname); + + return fd; +} + +/* + * ":mark" and ":k". + */ +static void ex_mark(eap) +exarg_T *eap; +{ + pos_T pos; + + if (*eap->arg == NUL) /* No argument? */ + EMSG(_(e_argreq)); + else if (eap->arg[1] != NUL) /* more than one character? */ + EMSG(_(e_trailing)); + else { + pos = curwin->w_cursor; /* save curwin->w_cursor */ + curwin->w_cursor.lnum = eap->line2; + beginline(BL_WHITE | BL_FIX); + if (setmark(*eap->arg) == FAIL) /* set mark */ + EMSG(_("E191: Argument must be a letter or forward/backward quote")); + curwin->w_cursor = pos; /* restore curwin->w_cursor */ + } +} + +/* + * Update w_topline, w_leftcol and the cursor position. + */ +void update_topline_cursor() { + check_cursor(); /* put cursor on valid line */ + update_topline(); + if (!curwin->w_p_wrap) + validate_cursor(); + update_curswant(); +} + +/* + * ":normal[!] {commands}": Execute normal mode commands. + */ +static void ex_normal(eap) +exarg_T *eap; +{ + int save_msg_scroll = msg_scroll; + int save_restart_edit = restart_edit; + int save_msg_didout = msg_didout; + int save_State = State; + tasave_T tabuf; + int save_insertmode = p_im; + int save_finish_op = finish_op; + int save_opcount = opcount; + char_u *arg = NULL; + int l; + char_u *p; + + if (ex_normal_lock > 0) { + EMSG(_(e_secure)); + return; + } + if (ex_normal_busy >= p_mmd) { + EMSG(_("E192: Recursive use of :normal too deep")); + return; + } + ++ex_normal_busy; + + msg_scroll = FALSE; /* no msg scrolling in Normal mode */ + restart_edit = 0; /* don't go to Insert mode */ + p_im = FALSE; /* don't use 'insertmode' */ + + /* + * vgetc() expects a CSI and K_SPECIAL to have been escaped. Don't do + * this for the K_SPECIAL leading byte, otherwise special keys will not + * work. + */ + if (has_mbyte) { + int len = 0; + + /* Count the number of characters to be escaped. */ + for (p = eap->arg; *p != NUL; ++p) { + for (l = (*mb_ptr2len)(p) - 1; l > 0; --l) + if (*++p == K_SPECIAL /* trailbyte K_SPECIAL or CSI */ + ) + len += 2; + } + if (len > 0) { + arg = alloc((unsigned)(STRLEN(eap->arg) + len + 1)); + if (arg != NULL) { + len = 0; + for (p = eap->arg; *p != NUL; ++p) { + arg[len++] = *p; + for (l = (*mb_ptr2len)(p) - 1; l > 0; --l) { + arg[len++] = *++p; + if (*p == K_SPECIAL) { + arg[len++] = KS_SPECIAL; + arg[len++] = KE_FILLER; + } + } + arg[len] = NUL; + } + } + } + } + + /* + * Save the current typeahead. This is required to allow using ":normal" + * from an event handler and makes sure we don't hang when the argument + * ends with half a command. + */ + save_typeahead(&tabuf); + if (tabuf.typebuf_valid) { + /* + * Repeat the :normal command for each line in the range. When no + * range given, execute it just once, without positioning the cursor + * first. + */ + do { + if (eap->addr_count != 0) { + curwin->w_cursor.lnum = eap->line1++; + curwin->w_cursor.col = 0; + } + + exec_normal_cmd( + arg != NULL ? arg : + eap->arg, eap->forceit ? REMAP_NONE : REMAP_YES, FALSE); + } while (eap->addr_count > 0 && eap->line1 <= eap->line2 && !got_int); + } + + /* Might not return to the main loop when in an event handler. */ + update_topline_cursor(); + + /* Restore the previous typeahead. */ + restore_typeahead(&tabuf); + + --ex_normal_busy; + msg_scroll = save_msg_scroll; + restart_edit = save_restart_edit; + p_im = save_insertmode; + finish_op = save_finish_op; + opcount = save_opcount; + msg_didout |= save_msg_didout; /* don't reset msg_didout now */ + + /* Restore the state (needed when called from a function executed for + * 'indentexpr'). */ + State = save_State; + vim_free(arg); +} + +/* + * ":startinsert", ":startreplace" and ":startgreplace" + */ +static void ex_startinsert(eap) +exarg_T *eap; +{ + if (eap->forceit) { + coladvance((colnr_T)MAXCOL); + curwin->w_curswant = MAXCOL; + curwin->w_set_curswant = FALSE; + } + + /* Ignore the command when already in Insert mode. Inserting an + * expression register that invokes a function can do this. */ + if (State & INSERT) + return; + + if (eap->cmdidx == CMD_startinsert) + restart_edit = 'a'; + else if (eap->cmdidx == CMD_startreplace) + restart_edit = 'R'; + else + restart_edit = 'V'; + + if (!eap->forceit) { + if (eap->cmdidx == CMD_startinsert) + restart_edit = 'i'; + curwin->w_curswant = 0; /* avoid MAXCOL */ + } +} + +/* + * ":stopinsert" + */ +static void ex_stopinsert(eap) +exarg_T *eap UNUSED; +{ + restart_edit = 0; + stop_insert_mode = TRUE; +} + +/* + * Execute normal mode command "cmd". + * "remap" can be REMAP_NONE or REMAP_YES. + */ +void exec_normal_cmd(cmd, remap, silent) +char_u *cmd; +int remap; +int silent; +{ + oparg_T oa; + + /* + * Stuff the argument into the typeahead buffer. + * Execute normal_cmd() until there is no typeahead left. + */ + clear_oparg(&oa); + finish_op = FALSE; + ins_typebuf(cmd, remap, 0, TRUE, silent); + while ((!stuff_empty() || (!typebuf_typed() && typebuf.tb_len > 0)) + && !got_int) { + update_topline_cursor(); + normal_cmd(&oa, TRUE); /* execute a Normal mode cmd */ + } +} + +static void ex_checkpath(eap) +exarg_T *eap; +{ + find_pattern_in_path(NULL, 0, 0, FALSE, FALSE, CHECK_PATH, 1L, + eap->forceit ? ACTION_SHOW_ALL : ACTION_SHOW, + (linenr_T)1, (linenr_T)MAXLNUM); +} + +/* + * ":psearch" + */ +static void ex_psearch(eap) +exarg_T *eap; +{ + g_do_tagpreview = p_pvh; + ex_findpat(eap); + g_do_tagpreview = 0; +} + +static void ex_findpat(eap) +exarg_T *eap; +{ + int whole = TRUE; + long n; + char_u *p; + int action; + + switch (cmdnames[eap->cmdidx].cmd_name[2]) { + case 'e': /* ":psearch", ":isearch" and ":dsearch" */ + if (cmdnames[eap->cmdidx].cmd_name[0] == 'p') + action = ACTION_GOTO; + else + action = ACTION_SHOW; + break; + case 'i': /* ":ilist" and ":dlist" */ + action = ACTION_SHOW_ALL; + break; + case 'u': /* ":ijump" and ":djump" */ + action = ACTION_GOTO; + break; + default: /* ":isplit" and ":dsplit" */ + action = ACTION_SPLIT; + break; + } + + n = 1; + if (vim_isdigit(*eap->arg)) { /* get count */ + n = getdigits(&eap->arg); + eap->arg = skipwhite(eap->arg); + } + if (*eap->arg == '/') { /* Match regexp, not just whole words */ + whole = FALSE; + ++eap->arg; + p = skip_regexp(eap->arg, '/', p_magic, NULL); + if (*p) { + *p++ = NUL; + p = skipwhite(p); + + /* Check for trailing illegal characters */ + if (!ends_excmd(*p)) + eap->errmsg = e_trailing; + else + eap->nextcmd = check_nextcmd(p); + } + } + if (!eap->skip) + find_pattern_in_path(eap->arg, 0, (int)STRLEN(eap->arg), + whole, !eap->forceit, + *eap->cmd == 'd' ? FIND_DEFINE : FIND_ANY, + n, action, eap->line1, eap->line2); +} + + +/* + * ":ptag", ":ptselect", ":ptjump", ":ptnext", etc. + */ +static void ex_ptag(eap) +exarg_T *eap; +{ + g_do_tagpreview = p_pvh; /* will be reset to 0 in ex_tag_cmd() */ + ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name + 1); +} + +/* + * ":pedit" + */ +static void ex_pedit(eap) +exarg_T *eap; +{ + win_T *curwin_save = curwin; + + g_do_tagpreview = p_pvh; + prepare_tagpreview(TRUE); + keep_help_flag = curwin_save->w_buffer->b_help; + do_exedit(eap, NULL); + keep_help_flag = FALSE; + if (curwin != curwin_save && win_valid(curwin_save)) { + /* Return cursor to where we were */ + validate_cursor(); + redraw_later(VALID); + win_enter(curwin_save, TRUE); + } + g_do_tagpreview = 0; +} + +/* + * ":stag", ":stselect" and ":stjump". + */ +static void ex_stag(eap) +exarg_T *eap; +{ + postponed_split = -1; + postponed_split_flags = cmdmod.split; + postponed_split_tab = cmdmod.tab; + ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name + 1); + postponed_split_flags = 0; + postponed_split_tab = 0; +} + +/* + * ":tag", ":tselect", ":tjump", ":tnext", etc. + */ +static void ex_tag(eap) +exarg_T *eap; +{ + ex_tag_cmd(eap, cmdnames[eap->cmdidx].cmd_name); +} + +static void ex_tag_cmd(eap, name) +exarg_T *eap; +char_u *name; +{ + int cmd; + + switch (name[1]) { + case 'j': cmd = DT_JUMP; /* ":tjump" */ + break; + case 's': cmd = DT_SELECT; /* ":tselect" */ + break; + case 'p': cmd = DT_PREV; /* ":tprevious" */ + break; + case 'N': cmd = DT_PREV; /* ":tNext" */ + break; + case 'n': cmd = DT_NEXT; /* ":tnext" */ + break; + case 'o': cmd = DT_POP; /* ":pop" */ + break; + case 'f': /* ":tfirst" */ + case 'r': cmd = DT_FIRST; /* ":trewind" */ + break; + case 'l': cmd = DT_LAST; /* ":tlast" */ + break; + default: /* ":tag" */ + if (p_cst && *eap->arg != NUL) { + do_cstag(eap); + return; + } + cmd = DT_TAG; + break; + } + + if (name[0] == 'l') { + cmd = DT_LTAG; + } + + do_tag(eap->arg, cmd, eap->addr_count > 0 ? (int)eap->line2 : 1, + eap->forceit, TRUE); +} + +/* + * Check "str" for starting with a special cmdline variable. + * If found return one of the SPEC_ values and set "*usedlen" to the length of + * the variable. Otherwise return -1 and "*usedlen" is unchanged. + */ +int find_cmdline_var(src, usedlen) +char_u *src; +int *usedlen; +{ + int len; + int i; + static char *(spec_str[]) = { + "%", +#define SPEC_PERC 0 + "#", +#define SPEC_HASH 1 + "", /* cursor word */ +#define SPEC_CWORD 2 + "", /* cursor WORD */ +#define SPEC_CCWORD 3 + "", /* cursor path name */ +#define SPEC_CFILE 4 + "", /* ":so" file name */ +#define SPEC_SFILE 5 + "", /* ":so" file line number */ +#define SPEC_SLNUM 6 + "", /* autocommand file name */ +# define SPEC_AFILE 7 + "", /* autocommand buffer number */ +# define SPEC_ABUF 8 + "", /* autocommand match name */ +# define SPEC_AMATCH 9 + }; + + for (i = 0; i < (int)(sizeof(spec_str) / sizeof(char *)); ++i) { + len = (int)STRLEN(spec_str[i]); + if (STRNCMP(src, spec_str[i], len) == 0) { + *usedlen = len; + return i; + } + } + return -1; +} + +/* + * Evaluate cmdline variables. + * + * change '%' to curbuf->b_ffname + * '#' to curwin->w_altfile + * '' to word under the cursor + * '' to WORD under the cursor + * '' to path name under the cursor + * '' to sourced file name + * '' to sourced file line number + * '' to file name for autocommand + * '' to buffer number for autocommand + * '' to matching name for autocommand + * + * When an error is detected, "errormsg" is set to a non-NULL pointer (may be + * "" for error without a message) and NULL is returned. + * Returns an allocated string if a valid match was found. + * Returns NULL if no match was found. "usedlen" then still contains the + * number of characters to skip. + */ +char_u * eval_vars(src, srcstart, usedlen, lnump, errormsg, escaped) +char_u *src; /* pointer into commandline */ +char_u *srcstart; /* beginning of valid memory for src */ +int *usedlen; /* characters after src that are used */ +linenr_T *lnump; /* line number for :e command, or NULL */ +char_u **errormsg; /* pointer to error message */ +int *escaped; /* return value has escaped white space (can + * be NULL) */ +{ + int i; + char_u *s; + char_u *result; + char_u *resultbuf = NULL; + int resultlen; + buf_T *buf; + int valid = VALID_HEAD + VALID_PATH; /* assume valid result */ + int spec_idx; + int skip_mod = FALSE; + char_u strbuf[30]; + + *errormsg = NULL; + if (escaped != NULL) + *escaped = FALSE; + + /* + * Check if there is something to do. + */ + spec_idx = find_cmdline_var(src, usedlen); + if (spec_idx < 0) { /* no match */ + *usedlen = 1; + return NULL; + } + + /* + * Skip when preceded with a backslash "\%" and "\#". + * Note: In "\\%" the % is also not recognized! + */ + if (src > srcstart && src[-1] == '\\') { + *usedlen = 0; + STRMOVE(src - 1, src); /* remove backslash */ + return NULL; + } + + /* + * word or WORD under cursor + */ + if (spec_idx == SPEC_CWORD || spec_idx == SPEC_CCWORD) { + resultlen = find_ident_under_cursor(&result, spec_idx == SPEC_CWORD ? + (FIND_IDENT|FIND_STRING) : FIND_STRING); + if (resultlen == 0) { + *errormsg = (char_u *)""; + return NULL; + } + } + /* + * '#': Alternate file name + * '%': Current file name + * File name under the cursor + * File name for autocommand + * and following modifiers + */ + else { + switch (spec_idx) { + case SPEC_PERC: /* '%': current file */ + if (curbuf->b_fname == NULL) { + result = (char_u *)""; + valid = 0; /* Must have ":p:h" to be valid */ + } else + result = curbuf->b_fname; + break; + + case SPEC_HASH: /* '#' or "#99": alternate file */ + if (src[1] == '#') { /* "##": the argument list */ + result = arg_all(); + resultbuf = result; + *usedlen = 2; + if (escaped != NULL) + *escaped = TRUE; + skip_mod = TRUE; + break; + } + s = src + 1; + if (*s == '<') /* "#<99" uses v:oldfiles */ + ++s; + i = (int)getdigits(&s); + *usedlen = (int)(s - src); /* length of what we expand */ + + if (src[1] == '<') { + if (*usedlen < 2) { + /* Should we give an error message for #b_fname == NULL) { + result = (char_u *)""; + valid = 0; /* Must have ":p:h" to be valid */ + } else + result = buf->b_fname; + } + break; + + case SPEC_CFILE: /* file name under cursor */ + result = file_name_at_cursor(FNAME_MESS|FNAME_HYP, 1L, NULL); + if (result == NULL) { + *errormsg = (char_u *)""; + return NULL; + } + resultbuf = result; /* remember allocated string */ + break; + + case SPEC_AFILE: /* file name for autocommand */ + result = autocmd_fname; + if (result != NULL && !autocmd_fname_full) { + /* Still need to turn the fname into a full path. It is + * postponed to avoid a delay when is not used. */ + autocmd_fname_full = TRUE; + result = FullName_save(autocmd_fname, FALSE); + vim_free(autocmd_fname); + autocmd_fname = result; + } + if (result == NULL) { + *errormsg = (char_u *)_( + "E495: no autocommand file name to substitute for \"\""); + return NULL; + } + result = shorten_fname1(result); + break; + + case SPEC_ABUF: /* buffer number for autocommand */ + if (autocmd_bufnr <= 0) { + *errormsg = (char_u *)_( + "E496: no autocommand buffer number to substitute for \"\""); + return NULL; + } + sprintf((char *)strbuf, "%d", autocmd_bufnr); + result = strbuf; + break; + + case SPEC_AMATCH: /* match name for autocommand */ + result = autocmd_match; + if (result == NULL) { + *errormsg = (char_u *)_( + "E497: no autocommand match name to substitute for \"\""); + return NULL; + } + break; + + case SPEC_SFILE: /* file name for ":so" command */ + result = sourcing_name; + if (result == NULL) { + *errormsg = (char_u *)_( + "E498: no :source file name to substitute for \"\""); + return NULL; + } + break; + case SPEC_SLNUM: /* line in file for ":so" command */ + if (sourcing_name == NULL || sourcing_lnum == 0) { + *errormsg = (char_u *)_("E842: no line number to use for \"\""); + return NULL; + } + sprintf((char *)strbuf, "%ld", (long)sourcing_lnum); + result = strbuf; + break; + } + + resultlen = (int)STRLEN(result); /* length of new string */ + if (src[*usedlen] == '<') { /* remove the file name extension */ + ++*usedlen; + if ((s = vim_strrchr(result, '.')) != NULL && s >= gettail(result)) + resultlen = (int)(s - result); + } else if (!skip_mod) { + valid |= modify_fname(src, usedlen, &result, &resultbuf, + &resultlen); + if (result == NULL) { + *errormsg = (char_u *)""; + return NULL; + } + } + } + + if (resultlen == 0 || valid != VALID_HEAD + VALID_PATH) { + if (valid != VALID_HEAD + VALID_PATH) + /* xgettext:no-c-format */ + *errormsg = (char_u *)_( + "E499: Empty file name for '%' or '#', only works with \":p:h\""); + else + *errormsg = (char_u *)_("E500: Evaluates to an empty string"); + result = NULL; + } else + result = vim_strnsave(result, resultlen); + vim_free(resultbuf); + return result; +} + +/* + * Concatenate all files in the argument list, separated by spaces, and return + * it in one allocated string. + * Spaces and backslashes in the file names are escaped with a backslash. + * Returns NULL when out of memory. + */ +static char_u * arg_all() { + int len; + int idx; + char_u *retval = NULL; + char_u *p; + + /* + * Do this loop two times: + * first time: compute the total length + * second time: concatenate the names + */ + for (;; ) { + len = 0; + for (idx = 0; idx < ARGCOUNT; ++idx) { + p = alist_name(&ARGLIST[idx]); + if (p != NULL) { + if (len > 0) { + /* insert a space in between names */ + if (retval != NULL) + retval[len] = ' '; + ++len; + } + for (; *p != NUL; ++p) { + if (*p == ' ' || *p == '\\') { + /* insert a backslash */ + if (retval != NULL) + retval[len] = '\\'; + ++len; + } + if (retval != NULL) + retval[len] = *p; + ++len; + } + } + } + + /* second time: break here */ + if (retval != NULL) { + retval[len] = NUL; + break; + } + + /* allocate memory */ + retval = alloc((unsigned)len + 1); + if (retval == NULL) + break; + } + + return retval; +} + +/* + * Expand the string in "arg". + * + * Returns an allocated string, or NULL for any error. + */ +char_u * expand_sfile(arg) +char_u *arg; +{ + char_u *errormsg; + int len; + char_u *result; + char_u *newres; + char_u *repl; + int srclen; + char_u *p; + + result = vim_strsave(arg); + if (result == NULL) + return NULL; + + for (p = result; *p; ) { + if (STRNCMP(p, "", 7) != 0) + ++p; + else { + /* replace "" with the sourced file name, and do ":" stuff */ + repl = eval_vars(p, result, &srclen, NULL, &errormsg, NULL); + if (errormsg != NULL) { + if (*errormsg) + emsg(errormsg); + vim_free(result); + return NULL; + } + if (repl == NULL) { /* no match (cannot happen) */ + p += srclen; + continue; + } + len = (int)STRLEN(result) - srclen + (int)STRLEN(repl) + 1; + newres = alloc(len); + if (newres == NULL) { + vim_free(repl); + vim_free(result); + return NULL; + } + mch_memmove(newres, result, (size_t)(p - result)); + STRCPY(newres + (p - result), repl); + len = (int)STRLEN(newres); + STRCAT(newres, p + srclen); + vim_free(repl); + vim_free(result); + result = newres; + p = newres + len; /* continue after the match */ + } + } + + return result; +} + +static int ses_winsizes __ARGS((FILE *fd, int restore_size, + win_T *tab_firstwin)); +static int ses_win_rec __ARGS((FILE *fd, frame_T *fr)); +static frame_T *ses_skipframe __ARGS((frame_T *fr)); +static int ses_do_frame __ARGS((frame_T *fr)); +static int ses_do_win __ARGS((win_T *wp)); +static int ses_arglist __ARGS((FILE *fd, char *cmd, garray_T *gap, int fullname, + unsigned *flagp)); +static int ses_put_fname __ARGS((FILE *fd, char_u *name, unsigned *flagp)); +static int ses_fname __ARGS((FILE *fd, buf_T *buf, unsigned *flagp)); + +/* + * Write openfile commands for the current buffers to an .exrc file. + * Return FAIL on error, OK otherwise. + */ +static int makeopens(fd, dirnow) +FILE *fd; +char_u *dirnow; /* Current directory name */ +{ + buf_T *buf; + int only_save_windows = TRUE; + int nr; + int cnr = 1; + int restore_size = TRUE; + win_T *wp; + char_u *sname; + win_T *edited_win = NULL; + int tabnr; + win_T *tab_firstwin; + frame_T *tab_topframe; + int cur_arg_idx = 0; + int next_arg_idx = 0; + + if (ssop_flags & SSOP_BUFFERS) + only_save_windows = FALSE; /* Save ALL buffers */ + + /* + * Begin by setting the this_session variable, and then other + * sessionable variables. + */ + if (put_line(fd, "let v:this_session=expand(\":p\")") == FAIL) + return FAIL; + if (ssop_flags & SSOP_GLOBALS) + if (store_session_globals(fd) == FAIL) + return FAIL; + + /* + * Close all windows but one. + */ + if (put_line(fd, "silent only") == FAIL) + return FAIL; + + /* + * Now a :cd command to the session directory or the current directory + */ + if (ssop_flags & SSOP_SESDIR) { + if (put_line(fd, "exe \"cd \" . escape(expand(\":p:h\"), ' ')") + == FAIL) + return FAIL; + } else if (ssop_flags & SSOP_CURDIR) { + sname = home_replace_save(NULL, globaldir != NULL ? globaldir : dirnow); + if (sname == NULL + || fputs("cd ", fd) < 0 + || ses_put_fname(fd, sname, &ssop_flags) == FAIL + || put_eol(fd) == FAIL) { + vim_free(sname); + return FAIL; + } + vim_free(sname); + } + + /* + * If there is an empty, unnamed buffer we will wipe it out later. + * Remember the buffer number. + */ + if (put_line(fd, + "if expand('%') == '' && !&modified && line('$') <= 1 && getline(1) == ''") + == + FAIL) + return FAIL; + if (put_line(fd, " let s:wipebuf = bufnr('%')") == FAIL) + return FAIL; + if (put_line(fd, "endif") == FAIL) + return FAIL; + + /* + * Now save the current files, current buffer first. + */ + if (put_line(fd, "set shortmess=aoO") == FAIL) + return FAIL; + + /* Now put the other buffers into the buffer list */ + for (buf = firstbuf; buf != NULL; buf = buf->b_next) { + if (!(only_save_windows && buf->b_nwindows == 0) + && !(buf->b_help && !(ssop_flags & SSOP_HELP)) + && buf->b_fname != NULL + && buf->b_p_bl) { + if (fprintf(fd, "badd +%ld ", buf->b_wininfo == NULL ? 1L + : buf->b_wininfo->wi_fpos.lnum) < 0 + || ses_fname(fd, buf, &ssop_flags) == FAIL) + return FAIL; + } + } + + /* the global argument list */ + if (ses_arglist(fd, "args", &global_alist.al_ga, + !(ssop_flags & SSOP_CURDIR), &ssop_flags) == FAIL) + return FAIL; + + if (ssop_flags & SSOP_RESIZE) { + /* Note: after the restore we still check it worked!*/ + if (fprintf(fd, "set lines=%ld columns=%ld", Rows, Columns) < 0 + || put_eol(fd) == FAIL) + return FAIL; + } + + + /* + * May repeat putting Windows for each tab, when "tabpages" is in + * 'sessionoptions'. + * Don't use goto_tabpage(), it may change directory and trigger + * autocommands. + */ + tab_firstwin = firstwin; /* first window in tab page "tabnr" */ + tab_topframe = topframe; + for (tabnr = 1;; ++tabnr) { + int need_tabnew = FALSE; + + if ((ssop_flags & SSOP_TABPAGES)) { + tabpage_T *tp = find_tabpage(tabnr); + + if (tp == NULL) + break; /* done all tab pages */ + if (tp == curtab) { + tab_firstwin = firstwin; + tab_topframe = topframe; + } else { + tab_firstwin = tp->tp_firstwin; + tab_topframe = tp->tp_topframe; + } + if (tabnr > 1) + need_tabnew = TRUE; + } + + /* + * Before creating the window layout, try loading one file. If this + * is aborted we don't end up with a number of useless windows. + * This may have side effects! (e.g., compressed or network file). + */ + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { + if (ses_do_win(wp) + && wp->w_buffer->b_ffname != NULL + && !wp->w_buffer->b_help + && !bt_nofile(wp->w_buffer) + ) { + if (fputs(need_tabnew ? "tabedit " : "edit ", fd) < 0 + || ses_fname(fd, wp->w_buffer, &ssop_flags) == FAIL) + return FAIL; + need_tabnew = FALSE; + if (!wp->w_arg_idx_invalid) + edited_win = wp; + break; + } + } + + /* If no file got edited create an empty tab page. */ + if (need_tabnew && put_line(fd, "tabnew") == FAIL) + return FAIL; + + /* + * Save current window layout. + */ + if (put_line(fd, "set splitbelow splitright") == FAIL) + return FAIL; + if (ses_win_rec(fd, tab_topframe) == FAIL) + return FAIL; + if (!p_sb && put_line(fd, "set nosplitbelow") == FAIL) + return FAIL; + if (!p_spr && put_line(fd, "set nosplitright") == FAIL) + return FAIL; + + /* + * Check if window sizes can be restored (no windows omitted). + * Remember the window number of the current window after restoring. + */ + nr = 0; + for (wp = tab_firstwin; wp != NULL; wp = W_NEXT(wp)) { + if (ses_do_win(wp)) + ++nr; + else + restore_size = FALSE; + if (curwin == wp) + cnr = nr; + } + + /* Go to the first window. */ + if (put_line(fd, "wincmd t") == FAIL) + return FAIL; + + /* + * If more than one window, see if sizes can be restored. + * First set 'winheight' and 'winwidth' to 1 to avoid the windows being + * resized when moving between windows. + * Do this before restoring the view, so that the topline and the + * cursor can be set. This is done again below. + */ + if (put_line(fd, "set winheight=1 winwidth=1") == FAIL) + return FAIL; + if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) + return FAIL; + + /* + * Restore the view of the window (options, file, cursor, etc.). + */ + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { + if (!ses_do_win(wp)) + continue; + if (put_view(fd, wp, wp != edited_win, &ssop_flags, + cur_arg_idx) == FAIL) + return FAIL; + if (nr > 1 && put_line(fd, "wincmd w") == FAIL) + return FAIL; + next_arg_idx = wp->w_arg_idx; + } + + /* The argument index in the first tab page is zero, need to set it in + * each window. For further tab pages it's the window where we do + * "tabedit". */ + cur_arg_idx = next_arg_idx; + + /* + * Restore cursor to the current window if it's not the first one. + */ + if (cnr > 1 && (fprintf(fd, "%dwincmd w", cnr) < 0 + || put_eol(fd) == FAIL)) + return FAIL; + + /* + * Restore window sizes again after jumping around in windows, because + * the current window has a minimum size while others may not. + */ + if (nr > 1 && ses_winsizes(fd, restore_size, tab_firstwin) == FAIL) + return FAIL; + + /* Don't continue in another tab page when doing only the current one + * or when at the last tab page. */ + if (!(ssop_flags & SSOP_TABPAGES)) + break; + } + + if (ssop_flags & SSOP_TABPAGES) { + if (fprintf(fd, "tabnext %d", tabpage_index(curtab)) < 0 + || put_eol(fd) == FAIL) + return FAIL; + } + + /* + * Wipe out an empty unnamed buffer we started in. + */ + if (put_line(fd, "if exists('s:wipebuf')") == FAIL) + return FAIL; + if (put_line(fd, " silent exe 'bwipe ' . s:wipebuf") == FAIL) + return FAIL; + if (put_line(fd, "endif") == FAIL) + return FAIL; + if (put_line(fd, "unlet! s:wipebuf") == FAIL) + return FAIL; + + /* Re-apply 'winheight', 'winwidth' and 'shortmess'. */ + if (fprintf(fd, "set winheight=%ld winwidth=%ld shortmess=%s", + p_wh, p_wiw, p_shm) < 0 || put_eol(fd) == FAIL) + return FAIL; + + /* + * Lastly, execute the x.vim file if it exists. + */ + if (put_line(fd, "let s:sx = expand(\":p:r\").\"x.vim\"") == FAIL + || put_line(fd, "if file_readable(s:sx)") == FAIL + || put_line(fd, " exe \"source \" . fnameescape(s:sx)") == FAIL + || put_line(fd, "endif") == FAIL) + return FAIL; + + return OK; +} + +static int ses_winsizes(fd, restore_size, tab_firstwin) +FILE *fd; +int restore_size; +win_T *tab_firstwin; +{ + int n = 0; + win_T *wp; + + if (restore_size && (ssop_flags & SSOP_WINSIZE)) { + for (wp = tab_firstwin; wp != NULL; wp = wp->w_next) { + if (!ses_do_win(wp)) + continue; + ++n; + + /* restore height when not full height */ + if (wp->w_height + wp->w_status_height < topframe->fr_height + && (fprintf(fd, + "exe '%dresize ' . ((&lines * %ld + %ld) / %ld)", + n, (long)wp->w_height, Rows / 2, Rows) < 0 + || put_eol(fd) == FAIL)) + return FAIL; + + /* restore width when not full width */ + if (wp->w_width < Columns && (fprintf(fd, + "exe 'vert %dresize ' . ((&columns * %ld + %ld) / %ld)", + n, (long)wp->w_width, Columns / 2, + Columns) < 0 + || put_eol(fd) == FAIL)) + return FAIL; + } + } else { + /* Just equalise window sizes */ + if (put_line(fd, "wincmd =") == FAIL) + return FAIL; + } + return OK; +} + +/* + * Write commands to "fd" to recursively create windows for frame "fr", + * horizontally and vertically split. + * After the commands the last window in the frame is the current window. + * Returns FAIL when writing the commands to "fd" fails. + */ +static int ses_win_rec(fd, fr) +FILE *fd; +frame_T *fr; +{ + frame_T *frc; + int count = 0; + + if (fr->fr_layout != FR_LEAF) { + /* Find first frame that's not skipped and then create a window for + * each following one (first frame is already there). */ + frc = ses_skipframe(fr->fr_child); + if (frc != NULL) + while ((frc = ses_skipframe(frc->fr_next)) != NULL) { + /* Make window as big as possible so that we have lots of room + * to split. */ + if (put_line(fd, "wincmd _ | wincmd |") == FAIL + || put_line(fd, fr->fr_layout == FR_COL + ? "split" : "vsplit") == FAIL) + return FAIL; + ++count; + } + + /* Go back to the first window. */ + if (count > 0 && (fprintf(fd, fr->fr_layout == FR_COL + ? "%dwincmd k" : "%dwincmd h", count) < 0 + || put_eol(fd) == FAIL)) + return FAIL; + + /* Recursively create frames/windows in each window of this column or + * row. */ + frc = ses_skipframe(fr->fr_child); + while (frc != NULL) { + ses_win_rec(fd, frc); + frc = ses_skipframe(frc->fr_next); + /* Go to next window. */ + if (frc != NULL && put_line(fd, "wincmd w") == FAIL) + return FAIL; + } + } + return OK; +} + +/* + * Skip frames that don't contain windows we want to save in the Session. + * Returns NULL when there none. + */ +static frame_T * ses_skipframe(fr) +frame_T *fr; +{ + frame_T *frc; + + for (frc = fr; frc != NULL; frc = frc->fr_next) + if (ses_do_frame(frc)) + break; + return frc; +} + +/* + * Return TRUE if frame "fr" has a window somewhere that we want to save in + * the Session. + */ +static int ses_do_frame(fr) +frame_T *fr; +{ + frame_T *frc; + + if (fr->fr_layout == FR_LEAF) + return ses_do_win(fr->fr_win); + for (frc = fr->fr_child; frc != NULL; frc = frc->fr_next) + if (ses_do_frame(frc)) + return TRUE; + return FALSE; +} + +/* + * Return non-zero if window "wp" is to be stored in the Session. + */ +static int ses_do_win(wp) +win_T *wp; +{ + if (wp->w_buffer->b_fname == NULL + /* When 'buftype' is "nofile" can't restore the window contents. */ + || bt_nofile(wp->w_buffer) + ) + return ssop_flags & SSOP_BLANK; + if (wp->w_buffer->b_help) + return ssop_flags & SSOP_HELP; + return TRUE; +} + +/* + * Write commands to "fd" to restore the view of a window. + * Caller must make sure 'scrolloff' is zero. + */ +static int put_view(fd, wp, add_edit, flagp, current_arg_idx) +FILE *fd; +win_T *wp; +int add_edit; /* add ":edit" command to view */ +unsigned *flagp; /* vop_flags or ssop_flags */ +int current_arg_idx; /* current argument index of the window, use + * -1 if unknown */ +{ + win_T *save_curwin; + int f; + int do_cursor; + int did_next = FALSE; + + /* Always restore cursor position for ":mksession". For ":mkview" only + * when 'viewoptions' contains "cursor". */ + do_cursor = (flagp == &ssop_flags || *flagp & SSOP_CURSOR); + + /* + * Local argument list. + */ + if (wp->w_alist == &global_alist) { + if (put_line(fd, "argglobal") == FAIL) + return FAIL; + } else { + if (ses_arglist(fd, "arglocal", &wp->w_alist->al_ga, + flagp == &vop_flags + || !(*flagp & SSOP_CURDIR) + || wp->w_localdir != NULL, flagp) == FAIL) + return FAIL; + } + + /* Only when part of a session: restore the argument index. Some + * arguments may have been deleted, check if the index is valid. */ + if (wp->w_arg_idx != current_arg_idx && wp->w_arg_idx < WARGCOUNT(wp) + && flagp == &ssop_flags) { + if (fprintf(fd, "%ldargu", (long)wp->w_arg_idx + 1) < 0 + || put_eol(fd) == FAIL) + return FAIL; + did_next = TRUE; + } + + /* Edit the file. Skip this when ":next" already did it. */ + if (add_edit && (!did_next || wp->w_arg_idx_invalid)) { + /* + * Load the file. + */ + if (wp->w_buffer->b_ffname != NULL + && !bt_nofile(wp->w_buffer) + ) { + /* + * Editing a file in this buffer: use ":edit file". + * This may have side effects! (e.g., compressed or network file). + */ + if (fputs("edit ", fd) < 0 + || ses_fname(fd, wp->w_buffer, flagp) == FAIL) + return FAIL; + } else { + /* No file in this buffer, just make it empty. */ + if (put_line(fd, "enew") == FAIL) + return FAIL; + if (wp->w_buffer->b_ffname != NULL) { + /* The buffer does have a name, but it's not a file name. */ + if (fputs("file ", fd) < 0 + || ses_fname(fd, wp->w_buffer, flagp) == FAIL) + return FAIL; + } + do_cursor = FALSE; + } + } + + /* + * Local mappings and abbreviations. + */ + if ((*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) + && makemap(fd, wp->w_buffer) == FAIL) + return FAIL; + + /* + * Local options. Need to go to the window temporarily. + * Store only local values when using ":mkview" and when ":mksession" is + * used and 'sessionoptions' doesn't include "options". + * Some folding options are always stored when "folds" is included, + * otherwise the folds would not be restored correctly. + */ + save_curwin = curwin; + curwin = wp; + curbuf = curwin->w_buffer; + if (*flagp & (SSOP_OPTIONS | SSOP_LOCALOPTIONS)) + f = makeset(fd, OPT_LOCAL, + flagp == &vop_flags || !(*flagp & SSOP_OPTIONS)); + else if (*flagp & SSOP_FOLDS) + f = makefoldset(fd); + else + f = OK; + curwin = save_curwin; + curbuf = curwin->w_buffer; + if (f == FAIL) + return FAIL; + + /* + * Save Folds when 'buftype' is empty and for help files. + */ + if ((*flagp & SSOP_FOLDS) + && wp->w_buffer->b_ffname != NULL + && (*wp->w_buffer->b_p_bt == NUL || wp->w_buffer->b_help) + ) { + if (put_folds(fd, wp) == FAIL) + return FAIL; + } + + /* + * Set the cursor after creating folds, since that moves the cursor. + */ + if (do_cursor) { + + /* Restore the cursor line in the file and relatively in the + * window. Don't use "G", it changes the jumplist. */ + if (fprintf(fd, "let s:l = %ld - ((%ld * winheight(0) + %ld) / %ld)", + (long)wp->w_cursor.lnum, + (long)(wp->w_cursor.lnum - wp->w_topline), + (long)wp->w_height / 2, (long)wp->w_height) < 0 + || put_eol(fd) == FAIL + || put_line(fd, "if s:l < 1 | let s:l = 1 | endif") == FAIL + || put_line(fd, "exe s:l") == FAIL + || put_line(fd, "normal! zt") == FAIL + || fprintf(fd, "%ld", (long)wp->w_cursor.lnum) < 0 + || put_eol(fd) == FAIL) + return FAIL; + /* Restore the cursor column and left offset when not wrapping. */ + if (wp->w_cursor.col == 0) { + if (put_line(fd, "normal! 0") == FAIL) + return FAIL; + } else { + if (!wp->w_p_wrap && wp->w_leftcol > 0 && wp->w_width > 0) { + if (fprintf(fd, + "let s:c = %ld - ((%ld * winwidth(0) + %ld) / %ld)", + (long)wp->w_virtcol + 1, + (long)(wp->w_virtcol - wp->w_leftcol), + (long)wp->w_width / 2, (long)wp->w_width) < 0 + || put_eol(fd) == FAIL + || put_line(fd, "if s:c > 0") == FAIL + || fprintf(fd, + " exe 'normal! ' . s:c . '|zs' . %ld . '|'", + (long)wp->w_virtcol + 1) < 0 + || put_eol(fd) == FAIL + || put_line(fd, "else") == FAIL + || fprintf(fd, " normal! 0%d|", wp->w_virtcol + 1) < 0 + || put_eol(fd) == FAIL + || put_line(fd, "endif") == FAIL) + return FAIL; + } else { + if (fprintf(fd, "normal! 0%d|", wp->w_virtcol + 1) < 0 + || put_eol(fd) == FAIL) + return FAIL; + } + } + } + + /* + * Local directory. + */ + if (wp->w_localdir != NULL) { + if (fputs("lcd ", fd) < 0 + || ses_put_fname(fd, wp->w_localdir, flagp) == FAIL + || put_eol(fd) == FAIL) + return FAIL; + did_lcd = TRUE; + } + + return OK; +} + +/* + * Write an argument list to the session file. + * Returns FAIL if writing fails. + */ +static int ses_arglist(fd, cmd, gap, fullname, flagp) +FILE *fd; +char *cmd; +garray_T *gap; +int fullname; /* TRUE: use full path name */ +unsigned *flagp; +{ + int i; + char_u *buf = NULL; + char_u *s; + + if (gap->ga_len == 0) + return put_line(fd, "silent! argdel *"); + if (fputs(cmd, fd) < 0) + return FAIL; + for (i = 0; i < gap->ga_len; ++i) { + /* NULL file names are skipped (only happens when out of memory). */ + s = alist_name(&((aentry_T *)gap->ga_data)[i]); + if (s != NULL) { + if (fullname) { + buf = alloc(MAXPATHL); + if (buf != NULL) { + (void)vim_FullName(s, buf, MAXPATHL, FALSE); + s = buf; + } + } + if (fputs(" ", fd) < 0 || ses_put_fname(fd, s, flagp) == FAIL) { + vim_free(buf); + return FAIL; + } + vim_free(buf); + } + } + return put_eol(fd); +} + +/* + * Write a buffer name to the session file. + * Also ends the line. + * Returns FAIL if writing fails. + */ +static int ses_fname(fd, buf, flagp) +FILE *fd; +buf_T *buf; +unsigned *flagp; +{ + char_u *name; + + /* Use the short file name if the current directory is known at the time + * the session file will be sourced. + * Don't do this for ":mkview", we don't know the current directory. + * Don't do this after ":lcd", we don't keep track of what the current + * directory is. */ + if (buf->b_sfname != NULL + && flagp == &ssop_flags + && (ssop_flags & (SSOP_CURDIR | SSOP_SESDIR)) + && !p_acd + && !did_lcd) + name = buf->b_sfname; + else + name = buf->b_ffname; + if (ses_put_fname(fd, name, flagp) == FAIL || put_eol(fd) == FAIL) + return FAIL; + return OK; +} + +/* + * Write a file name to the session file. + * Takes care of the "slash" option in 'sessionoptions' and escapes special + * characters. + * Returns FAIL if writing fails or out of memory. + */ +static int ses_put_fname(fd, name, flagp) +FILE *fd; +char_u *name; +unsigned *flagp; +{ + char_u *sname; + char_u *p; + int retval = OK; + + sname = home_replace_save(NULL, name); + if (sname == NULL) + return FAIL; + + if (*flagp & SSOP_SLASH) { + /* change all backslashes to forward slashes */ + for (p = sname; *p != NUL; mb_ptr_adv(p)) + if (*p == '\\') + *p = '/'; + } + + /* escape special characters */ + p = vim_strsave_fnameescape(sname, FALSE); + vim_free(sname); + if (p == NULL) + return FAIL; + + /* write the result */ + if (fputs((char *)p, fd) < 0) + retval = FAIL; + + vim_free(p); + return retval; +} + +/* + * ":loadview [nr]" + */ +static void ex_loadview(eap) +exarg_T *eap; +{ + char_u *fname; + + fname = get_view_file(*eap->arg); + if (fname != NULL) { + do_source(fname, FALSE, DOSO_NONE); + vim_free(fname); + } +} + +/* + * Get the name of the view file for the current buffer. + */ +static char_u * get_view_file(c) +int c; +{ + int len = 0; + char_u *p, *s; + char_u *retval; + char_u *sname; + + if (curbuf->b_ffname == NULL) { + EMSG(_(e_noname)); + return NULL; + } + sname = home_replace_save(NULL, curbuf->b_ffname); + if (sname == NULL) + return NULL; + + /* + * We want a file name without separators, because we're not going to make + * a directory. + * "normal" path separator -> "=+" + * "=" -> "==" + * ":" path separator -> "=-" + */ + for (p = sname; *p; ++p) + if (*p == '=' || vim_ispathsep(*p)) + ++len; + retval = alloc((unsigned)(STRLEN(sname) + len + STRLEN(p_vdir) + 9)); + if (retval != NULL) { + STRCPY(retval, p_vdir); + add_pathsep(retval); + s = retval + STRLEN(retval); + for (p = sname; *p; ++p) { + if (*p == '=') { + *s++ = '='; + *s++ = '='; + } else if (vim_ispathsep(*p)) { + *s++ = '='; +#if defined(BACKSLASH_IN_FILENAME) || defined(AMIGA) || defined(VMS) + if (*p == ':') + *s++ = '-'; + else +#endif + *s++ = '+'; + } else + *s++ = *p; + } + *s++ = '='; + *s++ = c; + STRCPY(s, ".vim"); + } + + vim_free(sname); + return retval; +} + + +/* + * Write end-of-line character(s) for ":mkexrc", ":mkvimrc" and ":mksession". + * Return FAIL for a write error. + */ +int put_eol(fd) +FILE *fd; +{ + if ( +#ifdef USE_CRNL + ( +# ifdef MKSESSION_NL + !mksession_nl && +# endif + (putc('\r', fd) < 0)) || +#endif + (putc('\n', fd) < 0)) + return FAIL; + return OK; +} + +/* + * Write a line to "fd". + * Return FAIL for a write error. + */ +int put_line(fd, s) +FILE *fd; +char *s; +{ + if (fputs(s, fd) < 0 || put_eol(fd) == FAIL) + return FAIL; + return OK; +} + +/* + * ":rviminfo" and ":wviminfo". + */ +static void ex_viminfo(eap) +exarg_T *eap; +{ + char_u *save_viminfo; + + save_viminfo = p_viminfo; + if (*p_viminfo == NUL) + p_viminfo = (char_u *)"'100"; + if (eap->cmdidx == CMD_rviminfo) { + if (read_viminfo(eap->arg, VIF_WANT_INFO | VIF_WANT_MARKS + | (eap->forceit ? VIF_FORCEIT : 0)) == FAIL) + EMSG(_("E195: Cannot open viminfo file for reading")); + } else + write_viminfo(eap->arg, eap->forceit); + p_viminfo = save_viminfo; +} + +/* + * Make a dialog message in "buff[DIALOG_MSG_SIZE]". + * "format" must contain "%s". + */ +void dialog_msg(buff, format, fname) +char_u *buff; +char *format; +char_u *fname; +{ + if (fname == NULL) + fname = (char_u *)_("Untitled"); + vim_snprintf((char *)buff, DIALOG_MSG_SIZE, format, fname); +} + +/* + * ":behave {mswin,xterm}" + */ +static void ex_behave(eap) +exarg_T *eap; +{ + if (STRCMP(eap->arg, "mswin") == 0) { + set_option_value((char_u *)"selection", 0L, (char_u *)"exclusive", 0); + set_option_value((char_u *)"selectmode", 0L, (char_u *)"mouse,key", 0); + set_option_value((char_u *)"mousemodel", 0L, (char_u *)"popup", 0); + set_option_value((char_u *)"keymodel", 0L, + (char_u *)"startsel,stopsel", 0); + } else if (STRCMP(eap->arg, "xterm") == 0) { + set_option_value((char_u *)"selection", 0L, (char_u *)"inclusive", 0); + set_option_value((char_u *)"selectmode", 0L, (char_u *)"", 0); + set_option_value((char_u *)"mousemodel", 0L, (char_u *)"extend", 0); + set_option_value((char_u *)"keymodel", 0L, (char_u *)"", 0); + } else + EMSG2(_(e_invarg2), eap->arg); +} + +/* + * Function given to ExpandGeneric() to obtain the possible arguments of the + * ":behave {mswin,xterm}" command. + */ +char_u * get_behave_arg(xp, idx) +expand_T *xp UNUSED; +int idx; +{ + if (idx == 0) + return (char_u *)"mswin"; + if (idx == 1) + return (char_u *)"xterm"; + return NULL; +} + +static int filetype_detect = FALSE; +static int filetype_plugin = FALSE; +static int filetype_indent = FALSE; + +/* + * ":filetype [plugin] [indent] {on,off,detect}" + * on: Load the filetype.vim file to install autocommands for file types. + * off: Load the ftoff.vim file to remove all autocommands for file types. + * plugin on: load filetype.vim and ftplugin.vim + * plugin off: load ftplugof.vim + * indent on: load filetype.vim and indent.vim + * indent off: load indoff.vim + */ +static void ex_filetype(eap) +exarg_T *eap; +{ + char_u *arg = eap->arg; + int plugin = FALSE; + int indent = FALSE; + + if (*eap->arg == NUL) { + /* Print current status. */ + smsg((char_u *)"filetype detection:%s plugin:%s indent:%s", + filetype_detect ? "ON" : "OFF", + filetype_plugin ? (filetype_detect ? "ON" : "(on)") : "OFF", + filetype_indent ? (filetype_detect ? "ON" : "(on)") : "OFF"); + return; + } + + /* Accept "plugin" and "indent" in any order. */ + for (;; ) { + if (STRNCMP(arg, "plugin", 6) == 0) { + plugin = TRUE; + arg = skipwhite(arg + 6); + continue; + } + if (STRNCMP(arg, "indent", 6) == 0) { + indent = TRUE; + arg = skipwhite(arg + 6); + continue; + } + break; + } + if (STRCMP(arg, "on") == 0 || STRCMP(arg, "detect") == 0) { + if (*arg == 'o' || !filetype_detect) { + source_runtime((char_u *)FILETYPE_FILE, TRUE); + filetype_detect = TRUE; + if (plugin) { + source_runtime((char_u *)FTPLUGIN_FILE, TRUE); + filetype_plugin = TRUE; + } + if (indent) { + source_runtime((char_u *)INDENT_FILE, TRUE); + filetype_indent = TRUE; + } + } + if (*arg == 'd') { + (void)do_doautocmd((char_u *)"filetypedetect BufRead", TRUE); + do_modelines(0); + } + } else if (STRCMP(arg, "off") == 0) { + if (plugin || indent) { + if (plugin) { + source_runtime((char_u *)FTPLUGOF_FILE, TRUE); + filetype_plugin = FALSE; + } + if (indent) { + source_runtime((char_u *)INDOFF_FILE, TRUE); + filetype_indent = FALSE; + } + } else { + source_runtime((char_u *)FTOFF_FILE, TRUE); + filetype_detect = FALSE; + } + } else + EMSG2(_(e_invarg2), arg); +} + +/* + * ":setfiletype {name}" + */ +static void ex_setfiletype(eap) +exarg_T *eap; +{ + if (!did_filetype) + set_option_value((char_u *)"filetype", 0L, eap->arg, OPT_LOCAL); +} + +static void ex_digraphs(eap) +exarg_T *eap UNUSED; +{ + if (*eap->arg != NUL) + putdigraph(eap->arg); + else + listdigraphs(); +} + +static void ex_set(eap) +exarg_T *eap; +{ + int flags = 0; + + if (eap->cmdidx == CMD_setlocal) + flags = OPT_LOCAL; + else if (eap->cmdidx == CMD_setglobal) + flags = OPT_GLOBAL; + (void)do_set(eap->arg, flags); +} + +/* + * ":nohlsearch" + */ +static void ex_nohlsearch(eap) +exarg_T *eap UNUSED; +{ + SET_NO_HLSEARCH(TRUE); + redraw_all_later(SOME_VALID); +} + +/* + * ":[N]match {group} {pattern}" + * Sets nextcmd to the start of the next command, if any. Also called when + * skipping commands to find the next command. + */ +static void ex_match(eap) +exarg_T *eap; +{ + char_u *p; + char_u *g = NULL; + char_u *end; + int c; + int id; + + if (eap->line2 <= 3) + id = eap->line2; + else { + EMSG(e_invcmd); + return; + } + + /* First clear any old pattern. */ + if (!eap->skip) + match_delete(curwin, id, FALSE); + + if (ends_excmd(*eap->arg)) + end = eap->arg; + else if ((STRNICMP(eap->arg, "none", 4) == 0 + && (vim_iswhite(eap->arg[4]) || ends_excmd(eap->arg[4])))) + end = eap->arg + 4; + else { + p = skiptowhite(eap->arg); + if (!eap->skip) + g = vim_strnsave(eap->arg, (int)(p - eap->arg)); + p = skipwhite(p); + if (*p == NUL) { + /* There must be two arguments. */ + EMSG2(_(e_invarg2), eap->arg); + return; + } + end = skip_regexp(p + 1, *p, TRUE, NULL); + if (!eap->skip) { + if (*end != NUL && !ends_excmd(*skipwhite(end + 1))) { + eap->errmsg = e_trailing; + return; + } + if (*end != *p) { + EMSG2(_(e_invarg2), p); + return; + } + + c = *end; + *end = NUL; + match_add(curwin, g, p + 1, 10, id); + vim_free(g); + *end = c; + } + } + eap->nextcmd = find_nextcmd(end); +} + +/* + * ":X": Get crypt key + */ +static void ex_X(eap) +exarg_T *eap UNUSED; +{ + if (get_crypt_method(curbuf) == 0 || blowfish_self_test() == OK) + (void)get_crypt_key(TRUE, TRUE); +} + +static void ex_fold(eap) +exarg_T *eap; +{ + if (foldManualAllowed(TRUE)) + foldCreate(eap->line1, eap->line2); +} + +static void ex_foldopen(eap) +exarg_T *eap; +{ + opFoldRange(eap->line1, eap->line2, eap->cmdidx == CMD_foldopen, + eap->forceit, FALSE); +} + +static void ex_folddo(eap) +exarg_T *eap; +{ + linenr_T lnum; + + /* First set the marks for all lines closed/open. */ + for (lnum = eap->line1; lnum <= eap->line2; ++lnum) + if (hasFolding(lnum, NULL, NULL) == (eap->cmdidx == CMD_folddoclosed)) + ml_setmarked(lnum); + + /* Execute the command on the marked lines. */ + global_exe(eap->arg); + ml_clearmarked(); /* clear rest of the marks */ +} diff --git a/src/ex_eval.c b/src/ex_eval.c new file mode 100644 index 0000000000..5f845c3f33 --- /dev/null +++ b/src/ex_eval.c @@ -0,0 +1,2113 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * ex_eval.c: functions for Ex command line for the +eval feature. + */ + +#include "vim.h" + + +static void free_msglist __ARGS((struct msglist *l)); +static int throw_exception __ARGS((void *, int, char_u *)); +static char_u *get_end_emsg __ARGS((struct condstack *cstack)); + +/* + * Exception handling terms: + * + * :try ":try" command \ + * ... try block | + * :catch RE ":catch" command | + * ... catch clause |- try conditional + * :finally ":finally" command | + * ... finally clause | + * :endtry ":endtry" command / + * + * The try conditional may have any number of catch clauses and at most one + * finally clause. A ":throw" command can be inside the try block, a catch + * clause, the finally clause, or in a function called or script sourced from + * there or even outside the try conditional. Try conditionals may be nested. + */ + +/* + * Configuration whether an exception is thrown on error or interrupt. When + * the preprocessor macros below evaluate to FALSE, an error (did_emsg) or + * interrupt (got_int) under an active try conditional terminates the script + * after the non-active finally clauses of all active try conditionals have been + * executed. Otherwise, errors and/or interrupts are converted into catchable + * exceptions (did_throw additionally set), which terminate the script only if + * not caught. For user exceptions, only did_throw is set. (Note: got_int can + * be set asynchronously afterwards by a SIGINT, so did_throw && got_int is not + * a reliant test that the exception currently being thrown is an interrupt + * exception. Similarly, did_emsg can be set afterwards on an error in an + * (unskipped) conditional command inside an inactive conditional, so did_throw + * && did_emsg is not a reliant test that the exception currently being thrown + * is an error exception.) - The macros can be defined as expressions checking + * for a variable that is allowed to be changed during execution of a script. + */ +/* Values used for the Vim release. */ +# define THROW_ON_ERROR TRUE +# define THROW_ON_ERROR_TRUE +# define THROW_ON_INTERRUPT TRUE +# define THROW_ON_INTERRUPT_TRUE + +static void catch_exception __ARGS((except_T *excp)); +static void finish_exception __ARGS((except_T *excp)); +static void discard_exception __ARGS((except_T *excp, int was_finished)); +static void report_pending __ARGS((int action, int pending, void *value)); + +/* + * When several errors appear in a row, setting "force_abort" is delayed until + * the failing command returned. "cause_abort" is set to TRUE meanwhile, in + * order to indicate that situation. This is useful when "force_abort" was set + * during execution of a function call from an expression: the aborting of the + * expression evaluation is done without producing any error messages, but all + * error messages on parsing errors during the expression evaluation are given + * (even if a try conditional is active). + */ +static int cause_abort = FALSE; + +/* + * Return TRUE when immediately aborting on error, or when an interrupt + * occurred or an exception was thrown but not caught. Use for ":{range}call" + * to check whether an aborted function that does not handle a range itself + * should be called again for the next line in the range. Also used for + * cancelling expression evaluation after a function call caused an immediate + * abort. Note that the first emsg() call temporarily resets "force_abort" + * until the throw point for error messages has been reached. That is, during + * cancellation of an expression evaluation after an aborting function call or + * due to a parsing error, aborting() always returns the same value. + */ +int aborting() { + return (did_emsg && force_abort) || got_int || did_throw; +} + +/* + * The value of "force_abort" is temporarily reset by the first emsg() call + * during an expression evaluation, and "cause_abort" is used instead. It might + * be necessary to restore "force_abort" even before the throw point for the + * error message has been reached. update_force_abort() should be called then. + */ +void update_force_abort() { + if (cause_abort) + force_abort = TRUE; +} + +/* + * Return TRUE if a command with a subcommand resulting in "retcode" should + * abort the script processing. Can be used to suppress an autocommand after + * execution of a failing subcommand as long as the error message has not been + * displayed and actually caused the abortion. + */ +int should_abort(retcode) +int retcode; +{ + return (retcode == FAIL && trylevel != 0 && !emsg_silent) || aborting(); +} + +/* + * Return TRUE if a function with the "abort" flag should not be considered + * ended on an error. This means that parsing commands is continued in order + * to find finally clauses to be executed, and that some errors in skipped + * commands are still reported. + */ +int aborted_in_try() { + /* This function is only called after an error. In this case, "force_abort" + * determines whether searching for finally clauses is necessary. */ + return force_abort; +} + +/* + * cause_errthrow(): Cause a throw of an error exception if appropriate. + * Return TRUE if the error message should not be displayed by emsg(). + * Sets "ignore", if the emsg() call should be ignored completely. + * + * When several messages appear in the same command, the first is usually the + * most specific one and used as the exception value. The "severe" flag can be + * set to TRUE, if a later but severer message should be used instead. + */ +int cause_errthrow(mesg, severe, ignore) +char_u *mesg; +int severe; +int *ignore; +{ + struct msglist *elem; + struct msglist **plist; + + /* + * Do nothing when displaying the interrupt message or reporting an + * uncaught exception (which has already been discarded then) at the top + * level. Also when no exception can be thrown. The message will be + * displayed by emsg(). + */ + if (suppress_errthrow) + return FALSE; + + /* + * If emsg() has not been called previously, temporarily reset + * "force_abort" until the throw point for error messages has been + * reached. This ensures that aborting() returns the same value for all + * errors that appear in the same command. This means particularly that + * for parsing errors during expression evaluation emsg() will be called + * multiply, even when the expression is evaluated from a finally clause + * that was activated due to an aborting error, interrupt, or exception. + */ + if (!did_emsg) { + cause_abort = force_abort; + force_abort = FALSE; + } + + /* + * If no try conditional is active and no exception is being thrown and + * there has not been an error in a try conditional or a throw so far, do + * nothing (for compatibility of non-EH scripts). The message will then + * be displayed by emsg(). When ":silent!" was used and we are not + * currently throwing an exception, do nothing. The message text will + * then be stored to v:errmsg by emsg() without displaying it. + */ + if (((trylevel == 0 && !cause_abort) || emsg_silent) && !did_throw) + return FALSE; + + /* + * Ignore an interrupt message when inside a try conditional or when an + * exception is being thrown or when an error in a try conditional or + * throw has been detected previously. This is important in order that an + * interrupt exception is catchable by the innermost try conditional and + * not replaced by an interrupt message error exception. + */ + if (mesg == (char_u *)_(e_interr)) { + *ignore = TRUE; + return TRUE; + } + + /* + * Ensure that all commands in nested function calls and sourced files + * are aborted immediately. + */ + cause_abort = TRUE; + + /* + * When an exception is being thrown, some commands (like conditionals) are + * not skipped. Errors in those commands may affect what of the subsequent + * commands are regarded part of catch and finally clauses. Catching the + * exception would then cause execution of commands not intended by the + * user, who wouldn't even get aware of the problem. Therefor, discard the + * exception currently being thrown to prevent it from being caught. Just + * execute finally clauses and terminate. + */ + if (did_throw) { + /* When discarding an interrupt exception, reset got_int to prevent the + * same interrupt being converted to an exception again and discarding + * the error exception we are about to throw here. */ + if (current_exception->type == ET_INTERRUPT) + got_int = FALSE; + discard_current_exception(); + } + +#ifdef THROW_TEST + if (!THROW_ON_ERROR) { + /* + * Print error message immediately without searching for a matching + * catch clause; just finally clauses are executed before the script + * is terminated. + */ + return FALSE; + } else +#endif + { + /* + * Prepare the throw of an error exception, so that everything will + * be aborted (except for executing finally clauses), until the error + * exception is caught; if still uncaught at the top level, the error + * message will be displayed and the script processing terminated + * then. - This function has no access to the conditional stack. + * Thus, the actual throw is made after the failing command has + * returned. - Throw only the first of several errors in a row, except + * a severe error is following. + */ + if (msg_list != NULL) { + plist = msg_list; + while (*plist != NULL) + plist = &(*plist)->next; + + elem = (struct msglist *)alloc((unsigned)sizeof(struct msglist)); + if (elem == NULL) { + suppress_errthrow = TRUE; + EMSG(_(e_outofmem)); + } else { + elem->msg = vim_strsave(mesg); + if (elem->msg == NULL) { + vim_free(elem); + suppress_errthrow = TRUE; + EMSG(_(e_outofmem)); + } else { + elem->next = NULL; + elem->throw_msg = NULL; + *plist = elem; + if (plist == msg_list || severe) { + char_u *tmsg; + + /* Skip the extra "Vim " prefix for message "E458". */ + tmsg = elem->msg; + if (STRNCMP(tmsg, "Vim E", 5) == 0 + && VIM_ISDIGIT(tmsg[5]) + && VIM_ISDIGIT(tmsg[6]) + && VIM_ISDIGIT(tmsg[7]) + && tmsg[8] == ':' + && tmsg[9] == ' ') + (*msg_list)->throw_msg = &tmsg[4]; + else + (*msg_list)->throw_msg = tmsg; + } + } + } + } + return TRUE; + } +} + +/* + * Free a "msg_list" and the messages it contains. + */ +static void free_msglist(l) +struct msglist *l; +{ + struct msglist *messages, *next; + + messages = l; + while (messages != NULL) { + next = messages->next; + vim_free(messages->msg); + vim_free(messages); + messages = next; + } +} + +/* + * Free global "*msg_list" and the messages it contains, then set "*msg_list" + * to NULL. + */ +void free_global_msglist() { + free_msglist(*msg_list); + *msg_list = NULL; +} + +/* + * Throw the message specified in the call to cause_errthrow() above as an + * error exception. If cstack is NULL, postpone the throw until do_cmdline() + * has returned (see do_one_cmd()). + */ +void do_errthrow(cstack, cmdname) +struct condstack *cstack; +char_u *cmdname; +{ + /* + * Ensure that all commands in nested function calls and sourced files + * are aborted immediately. + */ + if (cause_abort) { + cause_abort = FALSE; + force_abort = TRUE; + } + + /* If no exception is to be thrown or the conversion should be done after + * returning to a previous invocation of do_one_cmd(), do nothing. */ + if (msg_list == NULL || *msg_list == NULL) + return; + + if (throw_exception(*msg_list, ET_ERROR, cmdname) == FAIL) + free_msglist(*msg_list); + else { + if (cstack != NULL) + do_throw(cstack); + else + need_rethrow = TRUE; + } + *msg_list = NULL; +} + +/* + * do_intthrow(): Replace the current exception by an interrupt or interrupt + * exception if appropriate. Return TRUE if the current exception is discarded, + * FALSE otherwise. + */ +int do_intthrow(cstack) +struct condstack *cstack; +{ + /* + * If no interrupt occurred or no try conditional is active and no exception + * is being thrown, do nothing (for compatibility of non-EH scripts). + */ + if (!got_int || (trylevel == 0 && !did_throw)) + return FALSE; + +#ifdef THROW_TEST /* avoid warning for condition always true */ + if (!THROW_ON_INTERRUPT) { + /* + * The interrupt aborts everything except for executing finally clauses. + * Discard any user or error or interrupt exception currently being + * thrown. + */ + if (did_throw) + discard_current_exception(); + } else +#endif + { + /* + * Throw an interrupt exception, so that everything will be aborted + * (except for executing finally clauses), until the interrupt exception + * is caught; if still uncaught at the top level, the script processing + * will be terminated then. - If an interrupt exception is already + * being thrown, do nothing. + * + */ + if (did_throw) { + if (current_exception->type == ET_INTERRUPT) + return FALSE; + + /* An interrupt exception replaces any user or error exception. */ + discard_current_exception(); + } + if (throw_exception("Vim:Interrupt", ET_INTERRUPT, NULL) != FAIL) + do_throw(cstack); + } + + return TRUE; +} + +/* + * Get an exception message that is to be stored in current_exception->value. + */ +char_u * get_exception_string(value, type, cmdname, should_free) +void *value; +int type; +char_u *cmdname; +int *should_free; +{ + char_u *ret, *mesg; + int cmdlen; + char_u *p, *val; + + if (type == ET_ERROR) { + *should_free = FALSE; + mesg = ((struct msglist *)value)->throw_msg; + if (cmdname != NULL && *cmdname != NUL) { + cmdlen = (int)STRLEN(cmdname); + ret = vim_strnsave((char_u *)"Vim(", + 4 + cmdlen + 2 + (int)STRLEN(mesg)); + if (ret == NULL) + return ret; + STRCPY(&ret[4], cmdname); + STRCPY(&ret[4 + cmdlen], "):"); + val = ret + 4 + cmdlen + 2; + } else { + ret = vim_strnsave((char_u *)"Vim:", 4 + (int)STRLEN(mesg)); + if (ret == NULL) + return ret; + val = ret + 4; + } + + /* msg_add_fname may have been used to prefix the message with a file + * name in quotes. In the exception value, put the file name in + * parentheses and move it to the end. */ + for (p = mesg;; p++) { + if (*p == NUL + || (*p == 'E' + && VIM_ISDIGIT(p[1]) + && (p[2] == ':' + || (VIM_ISDIGIT(p[2]) + && (p[3] == ':' + || (VIM_ISDIGIT(p[3]) + && p[4] == ':')))))) { + if (*p == NUL || p == mesg) + STRCAT(val, mesg); /* 'E123' missing or at beginning */ + else { + /* '"filename" E123: message text' */ + if (mesg[0] != '"' || p-2 < &mesg[1] || + p[-2] != '"' || p[-1] != ' ') + /* "E123:" is part of the file name. */ + continue; + + STRCAT(val, p); + p[-2] = NUL; + sprintf((char *)(val + STRLEN(p)), " (%s)", &mesg[1]); + p[-2] = '"'; + } + break; + } + } + } else { + *should_free = FALSE; + ret = (char_u *) value; + } + + return ret; +} + + +/* + * Throw a new exception. Return FAIL when out of memory or it was tried to + * throw an illegal user exception. "value" is the exception string for a + * user or interrupt exception, or points to a message list in case of an + * error exception. + */ +static int throw_exception(value, type, cmdname) +void *value; +int type; +char_u *cmdname; +{ + except_T *excp; + int should_free; + + /* + * Disallow faking Interrupt or error exceptions as user exceptions. They + * would be treated differently from real interrupt or error exceptions + * when no active try block is found, see do_cmdline(). + */ + if (type == ET_USER) { + if (STRNCMP((char_u *)value, "Vim", 3) == 0 + && (((char_u *)value)[3] == NUL || ((char_u *)value)[3] == ':' + || ((char_u *)value)[3] == '(')) { + EMSG(_("E608: Cannot :throw exceptions with 'Vim' prefix")); + goto fail; + } + } + + excp = (except_T *)alloc((unsigned)sizeof(except_T)); + if (excp == NULL) + goto nomem; + + if (type == ET_ERROR) + /* Store the original message and prefix the exception value with + * "Vim:" or, if a command name is given, "Vim(cmdname):". */ + excp->messages = (struct msglist *)value; + + excp->value = get_exception_string(value, type, cmdname, &should_free); + if (excp->value == NULL && should_free) + goto nomem; + + excp->type = type; + excp->throw_name = vim_strsave(sourcing_name == NULL + ? (char_u *)"" : sourcing_name); + if (excp->throw_name == NULL) { + if (should_free) + vim_free(excp->value); + goto nomem; + } + excp->throw_lnum = sourcing_lnum; + + if (p_verbose >= 13 || debug_break_level > 0) { + int save_msg_silent = msg_silent; + + if (debug_break_level > 0) + msg_silent = FALSE; /* display messages */ + else + verbose_enter(); + ++no_wait_return; + if (debug_break_level > 0 || *p_vfile == NUL) + msg_scroll = TRUE; /* always scroll up, don't overwrite */ + + smsg((char_u *)_("Exception thrown: %s"), excp->value); + msg_puts((char_u *)"\n"); /* don't overwrite this either */ + + if (debug_break_level > 0 || *p_vfile == NUL) + cmdline_row = msg_row; + --no_wait_return; + if (debug_break_level > 0) + msg_silent = save_msg_silent; + else + verbose_leave(); + } + + current_exception = excp; + return OK; + +nomem: + vim_free(excp); + suppress_errthrow = TRUE; + EMSG(_(e_outofmem)); +fail: + current_exception = NULL; + return FAIL; +} + +/* + * Discard an exception. "was_finished" is set when the exception has been + * caught and the catch clause has been ended normally. + */ +static void discard_exception(excp, was_finished) +except_T *excp; +int was_finished; +{ + char_u *saved_IObuff; + + if (excp == NULL) { + EMSG(_(e_internal)); + return; + } + + if (p_verbose >= 13 || debug_break_level > 0) { + int save_msg_silent = msg_silent; + + saved_IObuff = vim_strsave(IObuff); + if (debug_break_level > 0) + msg_silent = FALSE; /* display messages */ + else + verbose_enter(); + ++no_wait_return; + if (debug_break_level > 0 || *p_vfile == NUL) + msg_scroll = TRUE; /* always scroll up, don't overwrite */ + smsg(was_finished + ? (char_u *)_("Exception finished: %s") + : (char_u *)_("Exception discarded: %s"), + excp->value); + msg_puts((char_u *)"\n"); /* don't overwrite this either */ + if (debug_break_level > 0 || *p_vfile == NUL) + cmdline_row = msg_row; + --no_wait_return; + if (debug_break_level > 0) + msg_silent = save_msg_silent; + else + verbose_leave(); + STRCPY(IObuff, saved_IObuff); + vim_free(saved_IObuff); + } + if (excp->type != ET_INTERRUPT) + vim_free(excp->value); + if (excp->type == ET_ERROR) + free_msglist(excp->messages); + vim_free(excp->throw_name); + vim_free(excp); +} + +/* + * Discard the exception currently being thrown. + */ +void discard_current_exception() { + discard_exception(current_exception, FALSE); + current_exception = NULL; + did_throw = FALSE; + need_rethrow = FALSE; +} + +/* + * Put an exception on the caught stack. + */ +static void catch_exception(excp) +except_T *excp; +{ + excp->caught = caught_stack; + caught_stack = excp; + set_vim_var_string(VV_EXCEPTION, excp->value, -1); + if (*excp->throw_name != NUL) { + if (excp->throw_lnum != 0) + vim_snprintf((char *)IObuff, IOSIZE, _("%s, line %ld"), + excp->throw_name, (long)excp->throw_lnum); + else + vim_snprintf((char *)IObuff, IOSIZE, "%s", excp->throw_name); + set_vim_var_string(VV_THROWPOINT, IObuff, -1); + } else + /* throw_name not set on an exception from a command that was typed. */ + set_vim_var_string(VV_THROWPOINT, NULL, -1); + + if (p_verbose >= 13 || debug_break_level > 0) { + int save_msg_silent = msg_silent; + + if (debug_break_level > 0) + msg_silent = FALSE; /* display messages */ + else + verbose_enter(); + ++no_wait_return; + if (debug_break_level > 0 || *p_vfile == NUL) + msg_scroll = TRUE; /* always scroll up, don't overwrite */ + + smsg((char_u *)_("Exception caught: %s"), excp->value); + msg_puts((char_u *)"\n"); /* don't overwrite this either */ + + if (debug_break_level > 0 || *p_vfile == NUL) + cmdline_row = msg_row; + --no_wait_return; + if (debug_break_level > 0) + msg_silent = save_msg_silent; + else + verbose_leave(); + } +} + +/* + * Remove an exception from the caught stack. + */ +static void finish_exception(excp) +except_T *excp; +{ + if (excp != caught_stack) + EMSG(_(e_internal)); + caught_stack = caught_stack->caught; + if (caught_stack != NULL) { + set_vim_var_string(VV_EXCEPTION, caught_stack->value, -1); + if (*caught_stack->throw_name != NUL) { + if (caught_stack->throw_lnum != 0) + vim_snprintf((char *)IObuff, IOSIZE, + _("%s, line %ld"), caught_stack->throw_name, + (long)caught_stack->throw_lnum); + else + vim_snprintf((char *)IObuff, IOSIZE, "%s", + caught_stack->throw_name); + set_vim_var_string(VV_THROWPOINT, IObuff, -1); + } else + /* throw_name not set on an exception from a command that was + * typed. */ + set_vim_var_string(VV_THROWPOINT, NULL, -1); + } else { + set_vim_var_string(VV_EXCEPTION, NULL, -1); + set_vim_var_string(VV_THROWPOINT, NULL, -1); + } + + /* Discard the exception, but use the finish message for 'verbose'. */ + discard_exception(excp, TRUE); +} + +/* + * Flags specifying the message displayed by report_pending. + */ +#define RP_MAKE 0 +#define RP_RESUME 1 +#define RP_DISCARD 2 + +/* + * Report information about something pending in a finally clause if required by + * the 'verbose' option or when debugging. "action" tells whether something is + * made pending or something pending is resumed or discarded. "pending" tells + * what is pending. "value" specifies the return value for a pending ":return" + * or the exception value for a pending exception. + */ +static void report_pending(action, pending, value) +int action; +int pending; +void *value; +{ + char_u *mesg; + char *s; + int save_msg_silent; + + + switch (action) { + case RP_MAKE: + mesg = (char_u *)_("%s made pending"); + break; + case RP_RESUME: + mesg = (char_u *)_("%s resumed"); + break; + /* case RP_DISCARD: */ + default: + mesg = (char_u *)_("%s discarded"); + break; + } + + switch (pending) { + case CSTP_NONE: + return; + + case CSTP_CONTINUE: + s = ":continue"; + break; + case CSTP_BREAK: + s = ":break"; + break; + case CSTP_FINISH: + s = ":finish"; + break; + case CSTP_RETURN: + /* ":return" command producing value, allocated */ + s = (char *)get_return_cmd(value); + break; + + default: + if (pending & CSTP_THROW) { + vim_snprintf((char *)IObuff, IOSIZE, + (char *)mesg, _("Exception")); + mesg = vim_strnsave(IObuff, (int)STRLEN(IObuff) + 4); + STRCAT(mesg, ": %s"); + s = (char *)((except_T *)value)->value; + } else if ((pending & CSTP_ERROR) && (pending & CSTP_INTERRUPT)) + s = _("Error and interrupt"); + else if (pending & CSTP_ERROR) + s = _("Error"); + else /* if (pending & CSTP_INTERRUPT) */ + s = _("Interrupt"); + } + + save_msg_silent = msg_silent; + if (debug_break_level > 0) + msg_silent = FALSE; /* display messages */ + ++no_wait_return; + msg_scroll = TRUE; /* always scroll up, don't overwrite */ + smsg(mesg, (char_u *)s); + msg_puts((char_u *)"\n"); /* don't overwrite this either */ + cmdline_row = msg_row; + --no_wait_return; + if (debug_break_level > 0) + msg_silent = save_msg_silent; + + if (pending == CSTP_RETURN) + vim_free(s); + else if (pending & CSTP_THROW) + vim_free(mesg); +} + +/* + * If something is made pending in a finally clause, report it if required by + * the 'verbose' option or when debugging. + */ +void report_make_pending(pending, value) +int pending; +void *value; +{ + if (p_verbose >= 14 || debug_break_level > 0) { + if (debug_break_level <= 0) + verbose_enter(); + report_pending(RP_MAKE, pending, value); + if (debug_break_level <= 0) + verbose_leave(); + } +} + +/* + * If something pending in a finally clause is resumed at the ":endtry", report + * it if required by the 'verbose' option or when debugging. + */ +void report_resume_pending(pending, value) +int pending; +void *value; +{ + if (p_verbose >= 14 || debug_break_level > 0) { + if (debug_break_level <= 0) + verbose_enter(); + report_pending(RP_RESUME, pending, value); + if (debug_break_level <= 0) + verbose_leave(); + } +} + +/* + * If something pending in a finally clause is discarded, report it if required + * by the 'verbose' option or when debugging. + */ +void report_discard_pending(pending, value) +int pending; +void *value; +{ + if (p_verbose >= 14 || debug_break_level > 0) { + if (debug_break_level <= 0) + verbose_enter(); + report_pending(RP_DISCARD, pending, value); + if (debug_break_level <= 0) + verbose_leave(); + } +} + + +/* + * ":if". + */ +void ex_if(eap) +exarg_T *eap; +{ + int error; + int skip; + int result; + struct condstack *cstack = eap->cstack; + + if (cstack->cs_idx == CSTACK_LEN - 1) + eap->errmsg = (char_u *)N_("E579: :if nesting too deep"); + else { + ++cstack->cs_idx; + cstack->cs_flags[cstack->cs_idx] = 0; + + /* + * Don't do something after an error, interrupt, or throw, or when there + * is a surrounding conditional and it was not active. + */ + skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 + && !(cstack->cs_flags[cstack-> + cs_idx - + 1] & + CSF_ACTIVE)); + + result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); + + if (!skip && !error) { + if (result) + cstack->cs_flags[cstack->cs_idx] = CSF_ACTIVE | CSF_TRUE; + } else + /* set TRUE, so this conditional will never get active */ + cstack->cs_flags[cstack->cs_idx] = CSF_TRUE; + } +} + +/* + * ":endif". + */ +void ex_endif(eap) +exarg_T *eap; +{ + did_endif = TRUE; + if (eap->cstack->cs_idx < 0 + || (eap->cstack->cs_flags[eap->cstack->cs_idx] + & (CSF_WHILE | CSF_FOR | CSF_TRY))) + eap->errmsg = (char_u *)N_("E580: :endif without :if"); + else { + /* + * When debugging or a breakpoint was encountered, display the debug + * prompt (if not already done). This shows the user that an ":endif" + * is executed when the ":if" or a previous ":elseif" was not TRUE. + * Handle a ">quit" debug command as if an interrupt had occurred before + * the ":endif". That is, throw an interrupt exception if appropriate. + * Doing this here prevents an exception for a parsing error being + * discarded by throwing the interrupt exception later on. + */ + if (!(eap->cstack->cs_flags[eap->cstack->cs_idx] & CSF_TRUE) + && dbg_check_skipped(eap)) + (void)do_intthrow(eap->cstack); + + --eap->cstack->cs_idx; + } +} + +/* + * ":else" and ":elseif". + */ +void ex_else(eap) +exarg_T *eap; +{ + int error; + int skip; + int result; + struct condstack *cstack = eap->cstack; + + /* + * Don't do something after an error, interrupt, or throw, or when there is + * a surrounding conditional and it was not active. + */ + skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 + && !(cstack->cs_flags[cstack-> + cs_idx - + 1] & + CSF_ACTIVE)); + + if (cstack->cs_idx < 0 + || (cstack->cs_flags[cstack->cs_idx] + & (CSF_WHILE | CSF_FOR | CSF_TRY))) { + if (eap->cmdidx == CMD_else) { + eap->errmsg = (char_u *)N_("E581: :else without :if"); + return; + } + eap->errmsg = (char_u *)N_("E582: :elseif without :if"); + skip = TRUE; + } else if (cstack->cs_flags[cstack->cs_idx] & CSF_ELSE) { + if (eap->cmdidx == CMD_else) { + eap->errmsg = (char_u *)N_("E583: multiple :else"); + return; + } + eap->errmsg = (char_u *)N_("E584: :elseif after :else"); + skip = TRUE; + } + + /* if skipping or the ":if" was TRUE, reset ACTIVE, otherwise set it */ + if (skip || cstack->cs_flags[cstack->cs_idx] & CSF_TRUE) { + if (eap->errmsg == NULL) + cstack->cs_flags[cstack->cs_idx] = CSF_TRUE; + skip = TRUE; /* don't evaluate an ":elseif" */ + } else + cstack->cs_flags[cstack->cs_idx] = CSF_ACTIVE; + + /* + * When debugging or a breakpoint was encountered, display the debug prompt + * (if not already done). This shows the user that an ":else" or ":elseif" + * is executed when the ":if" or previous ":elseif" was not TRUE. Handle + * a ">quit" debug command as if an interrupt had occurred before the + * ":else" or ":elseif". That is, set "skip" and throw an interrupt + * exception if appropriate. Doing this here prevents that an exception + * for a parsing errors is discarded when throwing the interrupt exception + * later on. + */ + if (!skip && dbg_check_skipped(eap) && got_int) { + (void)do_intthrow(cstack); + skip = TRUE; + } + + if (eap->cmdidx == CMD_elseif) { + result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); + /* When throwing error exceptions, we want to throw always the first + * of several errors in a row. This is what actually happens when + * a conditional error was detected above and there is another failure + * when parsing the expression. Since the skip flag is set in this + * case, the parsing error will be ignored by emsg(). */ + + if (!skip && !error) { + if (result) + cstack->cs_flags[cstack->cs_idx] = CSF_ACTIVE | CSF_TRUE; + else + cstack->cs_flags[cstack->cs_idx] = 0; + } else if (eap->errmsg == NULL) + /* set TRUE, so this conditional will never get active */ + cstack->cs_flags[cstack->cs_idx] = CSF_TRUE; + } else + cstack->cs_flags[cstack->cs_idx] |= CSF_ELSE; +} + +/* + * Handle ":while" and ":for". + */ +void ex_while(eap) +exarg_T *eap; +{ + int error; + int skip; + int result; + struct condstack *cstack = eap->cstack; + + if (cstack->cs_idx == CSTACK_LEN - 1) + eap->errmsg = (char_u *)N_("E585: :while/:for nesting too deep"); + else { + /* + * The loop flag is set when we have jumped back from the matching + * ":endwhile" or ":endfor". When not set, need to initialise this + * cstack entry. + */ + if ((cstack->cs_lflags & CSL_HAD_LOOP) == 0) { + ++cstack->cs_idx; + ++cstack->cs_looplevel; + cstack->cs_line[cstack->cs_idx] = -1; + } + cstack->cs_flags[cstack->cs_idx] = + eap->cmdidx == CMD_while ? CSF_WHILE : CSF_FOR; + + /* + * Don't do something after an error, interrupt, or throw, or when + * there is a surrounding conditional and it was not active. + */ + skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 + && !(cstack->cs_flags[cstack-> + cs_idx - + 1] & + CSF_ACTIVE)); + if (eap->cmdidx == CMD_while) { + /* + * ":while bool-expr" + */ + result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); + } else { + void *fi; + + /* + * ":for var in list-expr" + */ + if ((cstack->cs_lflags & CSL_HAD_LOOP) != 0) { + /* Jumping here from a ":continue" or ":endfor": use the + * previously evaluated list. */ + fi = cstack->cs_forinfo[cstack->cs_idx]; + error = FALSE; + } else { + /* Evaluate the argument and get the info in a structure. */ + fi = eval_for_line(eap->arg, &error, &eap->nextcmd, skip); + cstack->cs_forinfo[cstack->cs_idx] = fi; + } + + /* use the element at the start of the list and advance */ + if (!error && fi != NULL && !skip) + result = next_for_item(fi, eap->arg); + else + result = FALSE; + + if (!result) { + free_for_info(fi); + cstack->cs_forinfo[cstack->cs_idx] = NULL; + } + } + + /* + * If this cstack entry was just initialised and is active, set the + * loop flag, so do_cmdline() will set the line number in cs_line[]. + * If executing the command a second time, clear the loop flag. + */ + if (!skip && !error && result) { + cstack->cs_flags[cstack->cs_idx] |= (CSF_ACTIVE | CSF_TRUE); + cstack->cs_lflags ^= CSL_HAD_LOOP; + } else { + cstack->cs_lflags &= ~CSL_HAD_LOOP; + /* If the ":while" evaluates to FALSE or ":for" is past the end of + * the list, show the debug prompt at the ":endwhile"/":endfor" as + * if there was a ":break" in a ":while"/":for" evaluating to + * TRUE. */ + if (!skip && !error) + cstack->cs_flags[cstack->cs_idx] |= CSF_TRUE; + } + } +} + +/* + * ":continue" + */ +void ex_continue(eap) +exarg_T *eap; +{ + int idx; + struct condstack *cstack = eap->cstack; + + if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) + eap->errmsg = (char_u *)N_("E586: :continue without :while or :for"); + else { + /* Try to find the matching ":while". This might stop at a try + * conditional not in its finally clause (which is then to be executed + * next). Therefor, inactivate all conditionals except the ":while" + * itself (if reached). */ + idx = cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, FALSE); + if (idx >= 0 && (cstack->cs_flags[idx] & (CSF_WHILE | CSF_FOR))) { + rewind_conditionals(cstack, idx, CSF_TRY, &cstack->cs_trylevel); + + /* + * Set CSL_HAD_CONT, so do_cmdline() will jump back to the + * matching ":while". + */ + cstack->cs_lflags |= CSL_HAD_CONT; /* let do_cmdline() handle it */ + } else { + /* If a try conditional not in its finally clause is reached first, + * make the ":continue" pending for execution at the ":endtry". */ + cstack->cs_pending[idx] = CSTP_CONTINUE; + report_make_pending(CSTP_CONTINUE, NULL); + } + } +} + +/* + * ":break" + */ +void ex_break(eap) +exarg_T *eap; +{ + int idx; + struct condstack *cstack = eap->cstack; + + if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) + eap->errmsg = (char_u *)N_("E587: :break without :while or :for"); + else { + /* Inactivate conditionals until the matching ":while" or a try + * conditional not in its finally clause (which is then to be + * executed next) is found. In the latter case, make the ":break" + * pending for execution at the ":endtry". */ + idx = cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, TRUE); + if (idx >= 0 && !(cstack->cs_flags[idx] & (CSF_WHILE | CSF_FOR))) { + cstack->cs_pending[idx] = CSTP_BREAK; + report_make_pending(CSTP_BREAK, NULL); + } + } +} + +/* + * ":endwhile" and ":endfor" + */ +void ex_endwhile(eap) +exarg_T *eap; +{ + struct condstack *cstack = eap->cstack; + int idx; + char_u *err; + int csf; + int fl; + + if (eap->cmdidx == CMD_endwhile) { + err = e_while; + csf = CSF_WHILE; + } else { + err = e_for; + csf = CSF_FOR; + } + + if (cstack->cs_looplevel <= 0 || cstack->cs_idx < 0) + eap->errmsg = err; + else { + fl = cstack->cs_flags[cstack->cs_idx]; + if (!(fl & csf)) { + /* If we are in a ":while" or ":for" but used the wrong endloop + * command, do not rewind to the next enclosing ":for"/":while". */ + if (fl & CSF_WHILE) + eap->errmsg = (char_u *)_("E732: Using :endfor with :while"); + else if (fl & CSF_FOR) + eap->errmsg = (char_u *)_("E733: Using :endwhile with :for"); + } + if (!(fl & (CSF_WHILE | CSF_FOR))) { + if (!(fl & CSF_TRY)) + eap->errmsg = e_endif; + else if (fl & CSF_FINALLY) + eap->errmsg = e_endtry; + /* Try to find the matching ":while" and report what's missing. */ + for (idx = cstack->cs_idx; idx > 0; --idx) { + fl = cstack->cs_flags[idx]; + if ((fl & CSF_TRY) && !(fl & CSF_FINALLY)) { + /* Give up at a try conditional not in its finally clause. + * Ignore the ":endwhile"/":endfor". */ + eap->errmsg = err; + return; + } + if (fl & csf) + break; + } + /* Cleanup and rewind all contained (and unclosed) conditionals. */ + (void)cleanup_conditionals(cstack, CSF_WHILE | CSF_FOR, FALSE); + rewind_conditionals(cstack, idx, CSF_TRY, &cstack->cs_trylevel); + } + /* + * When debugging or a breakpoint was encountered, display the debug + * prompt (if not already done). This shows the user that an + * ":endwhile"/":endfor" is executed when the ":while" was not TRUE or + * after a ":break". Handle a ">quit" debug command as if an + * interrupt had occurred before the ":endwhile"/":endfor". That is, + * throw an interrupt exception if appropriate. Doing this here + * prevents that an exception for a parsing error is discarded when + * throwing the interrupt exception later on. + */ + else if (cstack->cs_flags[cstack->cs_idx] & CSF_TRUE + && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE) + && dbg_check_skipped(eap)) + (void)do_intthrow(cstack); + + /* + * Set loop flag, so do_cmdline() will jump back to the matching + * ":while" or ":for". + */ + cstack->cs_lflags |= CSL_HAD_ENDLOOP; + } +} + + +/* + * ":throw expr" + */ +void ex_throw(eap) +exarg_T *eap; +{ + char_u *arg = eap->arg; + char_u *value; + + if (*arg != NUL && *arg != '|' && *arg != '\n') + value = eval_to_string_skip(arg, &eap->nextcmd, eap->skip); + else { + EMSG(_(e_argreq)); + value = NULL; + } + + /* On error or when an exception is thrown during argument evaluation, do + * not throw. */ + if (!eap->skip && value != NULL) { + if (throw_exception(value, ET_USER, NULL) == FAIL) + vim_free(value); + else + do_throw(eap->cstack); + } +} + +/* + * Throw the current exception through the specified cstack. Common routine + * for ":throw" (user exception) and error and interrupt exceptions. Also + * used for rethrowing an uncaught exception. + */ +void do_throw(cstack) +struct condstack *cstack; +{ + int idx; + int inactivate_try = FALSE; + + /* + * Cleanup and inactivate up to the next surrounding try conditional that + * is not in its finally clause. Normally, do not inactivate the try + * conditional itself, so that its ACTIVE flag can be tested below. But + * if a previous error or interrupt has not been converted to an exception, + * inactivate the try conditional, too, as if the conversion had been done, + * and reset the did_emsg or got_int flag, so this won't happen again at + * the next surrounding try conditional. + */ +#ifndef THROW_ON_ERROR_TRUE + if (did_emsg && !THROW_ON_ERROR) { + inactivate_try = TRUE; + did_emsg = FALSE; + } +#endif +#ifndef THROW_ON_INTERRUPT_TRUE + if (got_int && !THROW_ON_INTERRUPT) { + inactivate_try = TRUE; + got_int = FALSE; + } +#endif + idx = cleanup_conditionals(cstack, 0, inactivate_try); + if (idx >= 0) { + /* + * If this try conditional is active and we are before its first + * ":catch", set THROWN so that the ":catch" commands will check + * whether the exception matches. When the exception came from any of + * the catch clauses, it will be made pending at the ":finally" (if + * present) and rethrown at the ":endtry". This will also happen if + * the try conditional is inactive. This is the case when we are + * throwing an exception due to an error or interrupt on the way from + * a preceding ":continue", ":break", ":return", ":finish", error or + * interrupt (not converted to an exception) to the finally clause or + * from a preceding throw of a user or error or interrupt exception to + * the matching catch clause or the finally clause. + */ + if (!(cstack->cs_flags[idx] & CSF_CAUGHT)) { + if (cstack->cs_flags[idx] & CSF_ACTIVE) + cstack->cs_flags[idx] |= CSF_THROWN; + else + /* THROWN may have already been set for a catchable exception + * that has been discarded. Ensure it is reset for the new + * exception. */ + cstack->cs_flags[idx] &= ~CSF_THROWN; + } + cstack->cs_flags[idx] &= ~CSF_ACTIVE; + cstack->cs_exception[idx] = current_exception; + } + + did_throw = TRUE; +} + +/* + * ":try" + */ +void ex_try(eap) +exarg_T *eap; +{ + int skip; + struct condstack *cstack = eap->cstack; + + if (cstack->cs_idx == CSTACK_LEN - 1) + eap->errmsg = (char_u *)N_("E601: :try nesting too deep"); + else { + ++cstack->cs_idx; + ++cstack->cs_trylevel; + cstack->cs_flags[cstack->cs_idx] = CSF_TRY; + cstack->cs_pending[cstack->cs_idx] = CSTP_NONE; + + /* + * Don't do something after an error, interrupt, or throw, or when there + * is a surrounding conditional and it was not active. + */ + skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 + && !(cstack->cs_flags[cstack-> + cs_idx - + 1] & + CSF_ACTIVE)); + + if (!skip) { + /* Set ACTIVE and TRUE. TRUE means that the corresponding ":catch" + * commands should check for a match if an exception is thrown and + * that the finally clause needs to be executed. */ + cstack->cs_flags[cstack->cs_idx] |= CSF_ACTIVE | CSF_TRUE; + + /* + * ":silent!", even when used in a try conditional, disables + * displaying of error messages and conversion of errors to + * exceptions. When the silent commands again open a try + * conditional, save "emsg_silent" and reset it so that errors are + * again converted to exceptions. The value is restored when that + * try conditional is left. If it is left normally, the commands + * following the ":endtry" are again silent. If it is left by + * a ":continue", ":break", ":return", or ":finish", the commands + * executed next are again silent. If it is left due to an + * aborting error, an interrupt, or an exception, restoring + * "emsg_silent" does not matter since we are already in the + * aborting state and/or the exception has already been thrown. + * The effect is then just freeing the memory that was allocated + * to save the value. + */ + if (emsg_silent) { + eslist_T *elem; + + elem = (eslist_T *)alloc((unsigned)sizeof(struct eslist_elem)); + if (elem == NULL) + EMSG(_(e_outofmem)); + else { + elem->saved_emsg_silent = emsg_silent; + elem->next = cstack->cs_emsg_silent_list; + cstack->cs_emsg_silent_list = elem; + cstack->cs_flags[cstack->cs_idx] |= CSF_SILENT; + emsg_silent = 0; + } + } + } + + } +} + +/* + * ":catch /{pattern}/" and ":catch" + */ +void ex_catch(eap) +exarg_T *eap; +{ + int idx = 0; + int give_up = FALSE; + int skip = FALSE; + int caught = FALSE; + char_u *end; + int save_char = 0; + char_u *save_cpo; + regmatch_T regmatch; + int prev_got_int; + struct condstack *cstack = eap->cstack; + char_u *pat; + + if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) { + eap->errmsg = (char_u *)N_("E603: :catch without :try"); + give_up = TRUE; + } else { + if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { + /* Report what's missing if the matching ":try" is not in its + * finally clause. */ + eap->errmsg = get_end_emsg(cstack); + skip = TRUE; + } + for (idx = cstack->cs_idx; idx > 0; --idx) + if (cstack->cs_flags[idx] & CSF_TRY) + break; + if (cstack->cs_flags[idx] & CSF_FINALLY) { + /* Give up for a ":catch" after ":finally" and ignore it. + * Just parse. */ + eap->errmsg = (char_u *)N_("E604: :catch after :finally"); + give_up = TRUE; + } else + rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, + &cstack->cs_looplevel); + } + + if (ends_excmd(*eap->arg)) { /* no argument, catch all errors */ + pat = (char_u *)".*"; + end = NULL; + eap->nextcmd = find_nextcmd(eap->arg); + } else { + pat = eap->arg + 1; + end = skip_regexp(pat, *eap->arg, TRUE, NULL); + } + + if (!give_up) { + /* + * Don't do something when no exception has been thrown or when the + * corresponding try block never got active (because of an inactive + * surrounding conditional or after an error or interrupt or throw). + */ + if (!did_throw || !(cstack->cs_flags[idx] & CSF_TRUE)) + skip = TRUE; + + /* + * Check for a match only if an exception is thrown but not caught by + * a previous ":catch". An exception that has replaced a discarded + * exception is not checked (THROWN is not set then). + */ + if (!skip && (cstack->cs_flags[idx] & CSF_THROWN) + && !(cstack->cs_flags[idx] & CSF_CAUGHT)) { + if (end != NULL && *end != NUL && !ends_excmd(*skipwhite(end + 1))) { + EMSG(_(e_trailing)); + return; + } + + /* When debugging or a breakpoint was encountered, display the + * debug prompt (if not already done) before checking for a match. + * This is a helpful hint for the user when the regular expression + * matching fails. Handle a ">quit" debug command as if an + * interrupt had occurred before the ":catch". That is, discard + * the original exception, replace it by an interrupt exception, + * and don't catch it in this try block. */ + if (!dbg_check_skipped(eap) || !do_intthrow(cstack)) { + /* Terminate the pattern and avoid the 'l' flag in 'cpoptions' + * while compiling it. */ + if (end != NULL) { + save_char = *end; + *end = NUL; + } + save_cpo = p_cpo; + p_cpo = (char_u *)""; + regmatch.regprog = vim_regcomp(pat, RE_MAGIC + RE_STRING); + regmatch.rm_ic = FALSE; + if (end != NULL) + *end = save_char; + p_cpo = save_cpo; + if (regmatch.regprog == NULL) + EMSG2(_(e_invarg2), pat); + else { + /* + * Save the value of got_int and reset it. We don't want + * a previous interruption cancel matching, only hitting + * CTRL-C while matching should abort it. + */ + prev_got_int = got_int; + got_int = FALSE; + caught = vim_regexec_nl(®match, current_exception->value, + (colnr_T)0); + got_int |= prev_got_int; + vim_regfree(regmatch.regprog); + } + } + } + + if (caught) { + /* Make this ":catch" clause active and reset did_emsg, got_int, + * and did_throw. Put the exception on the caught stack. */ + cstack->cs_flags[idx] |= CSF_ACTIVE | CSF_CAUGHT; + did_emsg = got_int = did_throw = FALSE; + catch_exception((except_T *)cstack->cs_exception[idx]); + /* It's mandatory that the current exception is stored in the cstack + * so that it can be discarded at the next ":catch", ":finally", or + * ":endtry" or when the catch clause is left by a ":continue", + * ":break", ":return", ":finish", error, interrupt, or another + * exception. */ + if (cstack->cs_exception[cstack->cs_idx] != current_exception) + EMSG(_(e_internal)); + } else { + /* + * If there is a preceding catch clause and it caught the exception, + * finish the exception now. This happens also after errors except + * when this ":catch" was after the ":finally" or not within + * a ":try". Make the try conditional inactive so that the + * following catch clauses are skipped. On an error or interrupt + * after the preceding try block or catch clause was left by + * a ":continue", ":break", ":return", or ":finish", discard the + * pending action. + */ + cleanup_conditionals(cstack, CSF_TRY, TRUE); + } + } + + if (end != NULL) + eap->nextcmd = find_nextcmd(end); +} + +/* + * ":finally" + */ +void ex_finally(eap) +exarg_T *eap; +{ + int idx; + int skip = FALSE; + int pending = CSTP_NONE; + struct condstack *cstack = eap->cstack; + + if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) + eap->errmsg = (char_u *)N_("E606: :finally without :try"); + else { + if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { + eap->errmsg = get_end_emsg(cstack); + for (idx = cstack->cs_idx - 1; idx > 0; --idx) + if (cstack->cs_flags[idx] & CSF_TRY) + break; + /* Make this error pending, so that the commands in the following + * finally clause can be executed. This overrules also a pending + * ":continue", ":break", ":return", or ":finish". */ + pending = CSTP_ERROR; + } else + idx = cstack->cs_idx; + + if (cstack->cs_flags[idx] & CSF_FINALLY) { + /* Give up for a multiple ":finally" and ignore it. */ + eap->errmsg = (char_u *)N_("E607: multiple :finally"); + return; + } + rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, + &cstack->cs_looplevel); + + /* + * Don't do something when the corresponding try block never got active + * (because of an inactive surrounding conditional or after an error or + * interrupt or throw) or for a ":finally" without ":try" or a multiple + * ":finally". After every other error (did_emsg or the conditional + * errors detected above) or after an interrupt (got_int) or an + * exception (did_throw), the finally clause must be executed. + */ + skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); + + if (!skip) { + /* When debugging or a breakpoint was encountered, display the + * debug prompt (if not already done). The user then knows that the + * finally clause is executed. */ + if (dbg_check_skipped(eap)) { + /* Handle a ">quit" debug command as if an interrupt had + * occurred before the ":finally". That is, discard the + * original exception and replace it by an interrupt + * exception. */ + (void)do_intthrow(cstack); + } + + /* + * If there is a preceding catch clause and it caught the exception, + * finish the exception now. This happens also after errors except + * when this is a multiple ":finally" or one not within a ":try". + * After an error or interrupt, this also discards a pending + * ":continue", ":break", ":finish", or ":return" from the preceding + * try block or catch clause. + */ + cleanup_conditionals(cstack, CSF_TRY, FALSE); + + /* + * Make did_emsg, got_int, did_throw pending. If set, they overrule + * a pending ":continue", ":break", ":return", or ":finish". Then + * we have particularly to discard a pending return value (as done + * by the call to cleanup_conditionals() above when did_emsg or + * got_int is set). The pending values are restored by the + * ":endtry", except if there is a new error, interrupt, exception, + * ":continue", ":break", ":return", or ":finish" in the following + * finally clause. A missing ":endwhile", ":endfor" or ":endif" + * detected here is treated as if did_emsg and did_throw had + * already been set, respectively in case that the error is not + * converted to an exception, did_throw had already been unset. + * We must not set did_emsg here since that would suppress the + * error message. + */ + if (pending == CSTP_ERROR || did_emsg || got_int || did_throw) { + if (cstack->cs_pending[cstack->cs_idx] == CSTP_RETURN) { + report_discard_pending(CSTP_RETURN, + cstack->cs_rettv[cstack->cs_idx]); + discard_pending_return(cstack->cs_rettv[cstack->cs_idx]); + } + if (pending == CSTP_ERROR && !did_emsg) + pending |= (THROW_ON_ERROR) ? CSTP_THROW : 0; + else + pending |= did_throw ? CSTP_THROW : 0; + pending |= did_emsg ? CSTP_ERROR : 0; + pending |= got_int ? CSTP_INTERRUPT : 0; + cstack->cs_pending[cstack->cs_idx] = pending; + + /* It's mandatory that the current exception is stored in the + * cstack so that it can be rethrown at the ":endtry" or be + * discarded if the finally clause is left by a ":continue", + * ":break", ":return", ":finish", error, interrupt, or another + * exception. When emsg() is called for a missing ":endif" or + * a missing ":endwhile"/":endfor" detected here, the + * exception will be discarded. */ + if (did_throw && cstack->cs_exception[cstack->cs_idx] + != current_exception) + EMSG(_(e_internal)); + } + + /* + * Set CSL_HAD_FINA, so do_cmdline() will reset did_emsg, + * got_int, and did_throw and make the finally clause active. + * This will happen after emsg() has been called for a missing + * ":endif" or a missing ":endwhile"/":endfor" detected here, so + * that the following finally clause will be executed even then. + */ + cstack->cs_lflags |= CSL_HAD_FINA; + } + } +} + +/* + * ":endtry" + */ +void ex_endtry(eap) +exarg_T *eap; +{ + int idx; + int skip; + int rethrow = FALSE; + int pending = CSTP_NONE; + void *rettv = NULL; + struct condstack *cstack = eap->cstack; + + if (cstack->cs_trylevel <= 0 || cstack->cs_idx < 0) + eap->errmsg = (char_u *)N_("E602: :endtry without :try"); + else { + /* + * Don't do something after an error, interrupt or throw in the try + * block, catch clause, or finally clause preceding this ":endtry" or + * when an error or interrupt occurred after a ":continue", ":break", + * ":return", or ":finish" in a try block or catch clause preceding this + * ":endtry" or when the try block never got active (because of an + * inactive surrounding conditional or after an error or interrupt or + * throw) or when there is a surrounding conditional and it has been + * made inactive by a ":continue", ":break", ":return", or ":finish" in + * the finally clause. The latter case need not be tested since then + * anything pending has already been discarded. */ + skip = did_emsg || got_int || did_throw || + !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); + + if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { + eap->errmsg = get_end_emsg(cstack); + /* Find the matching ":try" and report what's missing. */ + idx = cstack->cs_idx; + do + --idx; + while (idx > 0 && !(cstack->cs_flags[idx] & CSF_TRY)); + rewind_conditionals(cstack, idx, CSF_WHILE | CSF_FOR, + &cstack->cs_looplevel); + skip = TRUE; + + /* + * If an exception is being thrown, discard it to prevent it from + * being rethrown at the end of this function. It would be + * discarded by the error message, anyway. Resets did_throw. + * This does not affect the script termination due to the error + * since "trylevel" is decremented after emsg() has been called. + */ + if (did_throw) + discard_current_exception(); + } else { + idx = cstack->cs_idx; + + /* + * If we stopped with the exception currently being thrown at this + * try conditional since we didn't know that it doesn't have + * a finally clause, we need to rethrow it after closing the try + * conditional. + */ + if (did_throw && (cstack->cs_flags[idx] & CSF_TRUE) + && !(cstack->cs_flags[idx] & CSF_FINALLY)) + rethrow = TRUE; + } + + /* If there was no finally clause, show the user when debugging or + * a breakpoint was encountered that the end of the try conditional has + * been reached: display the debug prompt (if not already done). Do + * this on normal control flow or when an exception was thrown, but not + * on an interrupt or error not converted to an exception or when + * a ":break", ":continue", ":return", or ":finish" is pending. These + * actions are carried out immediately. + */ + if ((rethrow || (!skip + && !(cstack->cs_flags[idx] & CSF_FINALLY) + && !cstack->cs_pending[idx])) + && dbg_check_skipped(eap)) { + /* Handle a ">quit" debug command as if an interrupt had occurred + * before the ":endtry". That is, throw an interrupt exception and + * set "skip" and "rethrow". */ + if (got_int) { + skip = TRUE; + (void)do_intthrow(cstack); + /* The do_intthrow() call may have reset did_throw or + * cstack->cs_pending[idx].*/ + rethrow = FALSE; + if (did_throw && !(cstack->cs_flags[idx] & CSF_FINALLY)) + rethrow = TRUE; + } + } + + /* + * If a ":return" is pending, we need to resume it after closing the + * try conditional; remember the return value. If there was a finally + * clause making an exception pending, we need to rethrow it. Make it + * the exception currently being thrown. + */ + if (!skip) { + pending = cstack->cs_pending[idx]; + cstack->cs_pending[idx] = CSTP_NONE; + if (pending == CSTP_RETURN) + rettv = cstack->cs_rettv[idx]; + else if (pending & CSTP_THROW) + current_exception = cstack->cs_exception[idx]; + } + + /* + * Discard anything pending on an error, interrupt, or throw in the + * finally clause. If there was no ":finally", discard a pending + * ":continue", ":break", ":return", or ":finish" if an error or + * interrupt occurred afterwards, but before the ":endtry" was reached. + * If an exception was caught by the last of the catch clauses and there + * was no finally clause, finish the exception now. This happens also + * after errors except when this ":endtry" is not within a ":try". + * Restore "emsg_silent" if it has been reset by this try conditional. + */ + (void)cleanup_conditionals(cstack, CSF_TRY | CSF_SILENT, TRUE); + + --cstack->cs_idx; + --cstack->cs_trylevel; + + if (!skip) { + report_resume_pending(pending, + (pending == CSTP_RETURN) ? rettv : + (pending & CSTP_THROW) ? (void *)current_exception : NULL); + switch (pending) { + case CSTP_NONE: + break; + + /* Reactivate a pending ":continue", ":break", ":return", + * ":finish" from the try block or a catch clause of this try + * conditional. This is skipped, if there was an error in an + * (unskipped) conditional command or an interrupt afterwards + * or if the finally clause is present and executed a new error, + * interrupt, throw, ":continue", ":break", ":return", or + * ":finish". */ + case CSTP_CONTINUE: + ex_continue(eap); + break; + case CSTP_BREAK: + ex_break(eap); + break; + case CSTP_RETURN: + do_return(eap, FALSE, FALSE, rettv); + break; + case CSTP_FINISH: + do_finish(eap, FALSE); + break; + + /* When the finally clause was entered due to an error, + * interrupt or throw (as opposed to a ":continue", ":break", + * ":return", or ":finish"), restore the pending values of + * did_emsg, got_int, and did_throw. This is skipped, if there + * was a new error, interrupt, throw, ":continue", ":break", + * ":return", or ":finish". in the finally clause. */ + default: + if (pending & CSTP_ERROR) + did_emsg = TRUE; + if (pending & CSTP_INTERRUPT) + got_int = TRUE; + if (pending & CSTP_THROW) + rethrow = TRUE; + break; + } + } + + if (rethrow) + /* Rethrow the current exception (within this cstack). */ + do_throw(cstack); + } +} + +/* + * enter_cleanup() and leave_cleanup() + * + * Functions to be called before/after invoking a sequence of autocommands for + * cleanup for a failed command. (Failure means here that a call to emsg() + * has been made, an interrupt occurred, or there is an uncaught exception + * from a previous autocommand execution of the same command.) + * + * Call enter_cleanup() with a pointer to a cleanup_T and pass the same + * pointer to leave_cleanup(). The cleanup_T structure stores the pending + * error/interrupt/exception state. + */ + +/* + * This function works a bit like ex_finally() except that there was not + * actually an extra try block around the part that failed and an error or + * interrupt has not (yet) been converted to an exception. This function + * saves the error/interrupt/ exception state and prepares for the call to + * do_cmdline() that is going to be made for the cleanup autocommand + * execution. + */ +void enter_cleanup(csp) +cleanup_T *csp; +{ + int pending = CSTP_NONE; + + /* + * Postpone did_emsg, got_int, did_throw. The pending values will be + * restored by leave_cleanup() except if there was an aborting error, + * interrupt, or uncaught exception after this function ends. + */ + if (did_emsg || got_int || did_throw || need_rethrow) { + csp->pending = (did_emsg ? CSTP_ERROR : 0) + | (got_int ? CSTP_INTERRUPT : 0) + | (did_throw ? CSTP_THROW : 0) + | (need_rethrow ? CSTP_THROW : 0); + + /* If we are currently throwing an exception (did_throw), save it as + * well. On an error not yet converted to an exception, update + * "force_abort" and reset "cause_abort" (as do_errthrow() would do). + * This is needed for the do_cmdline() call that is going to be made + * for autocommand execution. We need not save *msg_list because + * there is an extra instance for every call of do_cmdline(), anyway. + */ + if (did_throw || need_rethrow) + csp->exception = current_exception; + else { + csp->exception = NULL; + if (did_emsg) { + force_abort |= cause_abort; + cause_abort = FALSE; + } + } + did_emsg = got_int = did_throw = need_rethrow = FALSE; + + /* Report if required by the 'verbose' option or when debugging. */ + report_make_pending(pending, csp->exception); + } else { + csp->pending = CSTP_NONE; + csp->exception = NULL; + } +} + +/* + * See comment above enter_cleanup() for how this function is used. + * + * This function is a bit like ex_endtry() except that there was not actually + * an extra try block around the part that failed and an error or interrupt + * had not (yet) been converted to an exception when the cleanup autocommand + * sequence was invoked. + * + * This function has to be called with the address of the cleanup_T structure + * filled by enter_cleanup() as an argument; it restores the error/interrupt/ + * exception state saved by that function - except there was an aborting + * error, an interrupt or an uncaught exception during execution of the + * cleanup autocommands. In the latter case, the saved error/interrupt/ + * exception state is discarded. + */ +void leave_cleanup(csp) +cleanup_T *csp; +{ + int pending = csp->pending; + + if (pending == CSTP_NONE) /* nothing to do */ + return; + + /* If there was an aborting error, an interrupt, or an uncaught exception + * after the corresponding call to enter_cleanup(), discard what has been + * made pending by it. Report this to the user if required by the + * 'verbose' option or when debugging. */ + if (aborting() || need_rethrow) { + if (pending & CSTP_THROW) + /* Cancel the pending exception (includes report). */ + discard_exception((except_T *)csp->exception, FALSE); + else + report_discard_pending(pending, NULL); + + /* If an error was about to be converted to an exception when + * enter_cleanup() was called, free the message list. */ + if (msg_list != NULL) + free_global_msglist(); + } + /* + * If there was no new error, interrupt, or throw between the calls + * to enter_cleanup() and leave_cleanup(), restore the pending + * error/interrupt/exception state. + */ + else { + /* + * If there was an exception being thrown when enter_cleanup() was + * called, we need to rethrow it. Make it the exception currently + * being thrown. + */ + if (pending & CSTP_THROW) + current_exception = csp->exception; + + /* + * If an error was about to be converted to an exception when + * enter_cleanup() was called, let "cause_abort" take the part of + * "force_abort" (as done by cause_errthrow()). + */ + else if (pending & CSTP_ERROR) { + cause_abort = force_abort; + force_abort = FALSE; + } + + /* + * Restore the pending values of did_emsg, got_int, and did_throw. + */ + if (pending & CSTP_ERROR) + did_emsg = TRUE; + if (pending & CSTP_INTERRUPT) + got_int = TRUE; + if (pending & CSTP_THROW) + need_rethrow = TRUE; /* did_throw will be set by do_one_cmd() */ + + /* Report if required by the 'verbose' option or when debugging. */ + report_resume_pending(pending, + (pending & CSTP_THROW) ? (void *)current_exception : NULL); + } +} + + +/* + * Make conditionals inactive and discard what's pending in finally clauses + * until the conditional type searched for or a try conditional not in its + * finally clause is reached. If this is in an active catch clause, finish + * the caught exception. + * Return the cstack index where the search stopped. + * Values used for "searched_cond" are (CSF_WHILE | CSF_FOR) or CSF_TRY or 0, + * the latter meaning the innermost try conditional not in its finally clause. + * "inclusive" tells whether the conditional searched for should be made + * inactive itself (a try conditional not in its finally clause possibly find + * before is always made inactive). If "inclusive" is TRUE and + * "searched_cond" is CSF_TRY|CSF_SILENT, the saved former value of + * "emsg_silent", if reset when the try conditional finally reached was + * entered, is restored (used by ex_endtry()). This is normally done only + * when such a try conditional is left. + */ +int cleanup_conditionals(cstack, searched_cond, inclusive) +struct condstack *cstack; +int searched_cond; +int inclusive; +{ + int idx; + int stop = FALSE; + + for (idx = cstack->cs_idx; idx >= 0; --idx) { + if (cstack->cs_flags[idx] & CSF_TRY) { + /* + * Discard anything pending in a finally clause and continue the + * search. There may also be a pending ":continue", ":break", + * ":return", or ":finish" before the finally clause. We must not + * discard it, unless an error or interrupt occurred afterwards. + */ + if (did_emsg || got_int || (cstack->cs_flags[idx] & CSF_FINALLY)) { + switch (cstack->cs_pending[idx]) { + case CSTP_NONE: + break; + + case CSTP_CONTINUE: + case CSTP_BREAK: + case CSTP_FINISH: + report_discard_pending(cstack->cs_pending[idx], NULL); + cstack->cs_pending[idx] = CSTP_NONE; + break; + + case CSTP_RETURN: + report_discard_pending(CSTP_RETURN, + cstack->cs_rettv[idx]); + discard_pending_return(cstack->cs_rettv[idx]); + cstack->cs_pending[idx] = CSTP_NONE; + break; + + default: + if (cstack->cs_flags[idx] & CSF_FINALLY) { + if (cstack->cs_pending[idx] & CSTP_THROW) { + /* Cancel the pending exception. This is in the + * finally clause, so that the stack of the + * caught exceptions is not involved. */ + discard_exception((except_T *) + cstack->cs_exception[idx], + FALSE); + } else + report_discard_pending(cstack->cs_pending[idx], + NULL); + cstack->cs_pending[idx] = CSTP_NONE; + } + break; + } + } + + /* + * Stop at a try conditional not in its finally clause. If this try + * conditional is in an active catch clause, finish the caught + * exception. + */ + if (!(cstack->cs_flags[idx] & CSF_FINALLY)) { + if ((cstack->cs_flags[idx] & CSF_ACTIVE) + && (cstack->cs_flags[idx] & CSF_CAUGHT)) + finish_exception((except_T *)cstack->cs_exception[idx]); + /* Stop at this try conditional - except the try block never + * got active (because of an inactive surrounding conditional + * or when the ":try" appeared after an error or interrupt or + * throw). */ + if (cstack->cs_flags[idx] & CSF_TRUE) { + if (searched_cond == 0 && !inclusive) + break; + stop = TRUE; + } + } + } + + /* Stop on the searched conditional type (even when the surrounding + * conditional is not active or something has been made pending). + * If "inclusive" is TRUE and "searched_cond" is CSF_TRY|CSF_SILENT, + * check first whether "emsg_silent" needs to be restored. */ + if (cstack->cs_flags[idx] & searched_cond) { + if (!inclusive) + break; + stop = TRUE; + } + cstack->cs_flags[idx] &= ~CSF_ACTIVE; + if (stop && searched_cond != (CSF_TRY | CSF_SILENT)) + break; + + /* + * When leaving a try conditional that reset "emsg_silent" on its + * entry after saving the original value, restore that value here and + * free the memory used to store it. + */ + if ((cstack->cs_flags[idx] & CSF_TRY) + && (cstack->cs_flags[idx] & CSF_SILENT)) { + eslist_T *elem; + + elem = cstack->cs_emsg_silent_list; + cstack->cs_emsg_silent_list = elem->next; + emsg_silent = elem->saved_emsg_silent; + vim_free(elem); + cstack->cs_flags[idx] &= ~CSF_SILENT; + } + if (stop) + break; + } + return idx; +} + +/* + * Return an appropriate error message for a missing endwhile/endfor/endif. + */ +static char_u * get_end_emsg(cstack) +struct condstack *cstack; +{ + if (cstack->cs_flags[cstack->cs_idx] & CSF_WHILE) + return e_endwhile; + if (cstack->cs_flags[cstack->cs_idx] & CSF_FOR) + return e_endfor; + return e_endif; +} + + +/* + * Rewind conditionals until index "idx" is reached. "cond_type" and + * "cond_level" specify a conditional type and the address of a level variable + * which is to be decremented with each skipped conditional of the specified + * type. + * Also free "for info" structures where needed. + */ +void rewind_conditionals(cstack, idx, cond_type, cond_level) +struct condstack *cstack; +int idx; +int cond_type; +int *cond_level; +{ + while (cstack->cs_idx > idx) { + if (cstack->cs_flags[cstack->cs_idx] & cond_type) + --*cond_level; + if (cstack->cs_flags[cstack->cs_idx] & CSF_FOR) + free_for_info(cstack->cs_forinfo[cstack->cs_idx]); + --cstack->cs_idx; + } +} + +/* + * ":endfunction" when not after a ":function" + */ +void ex_endfunction(eap) +exarg_T *eap UNUSED; +{ + EMSG(_("E193: :endfunction not inside a function")); +} + +/* + * Return TRUE if the string "p" looks like a ":while" or ":for" command. + */ +int has_loop_cmd(p) +char_u *p; +{ + int len; + + /* skip modifiers, white space and ':' */ + for (;; ) { + while (*p == ' ' || *p == '\t' || *p == ':') + ++p; + len = modifier_len(p); + if (len == 0) + break; + p += len; + } + if ((p[0] == 'w' && p[1] == 'h') + || (p[0] == 'f' && p[1] == 'o' && p[2] == 'r')) + return TRUE; + return FALSE; +} + diff --git a/src/ex_getln.c b/src/ex_getln.c new file mode 100644 index 0000000000..ee9fc079fe --- /dev/null +++ b/src/ex_getln.c @@ -0,0 +1,5487 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * ex_getln.c: Functions for entering and editing an Ex command line. + */ + +#include "vim.h" + +/* + * Variables shared between getcmdline(), redrawcmdline() and others. + * These need to be saved when using CTRL-R |, that's why they are in a + * structure. + */ +struct cmdline_info { + char_u *cmdbuff; /* pointer to command line buffer */ + int cmdbufflen; /* length of cmdbuff */ + int cmdlen; /* number of chars in command line */ + int cmdpos; /* current cursor position */ + int cmdspos; /* cursor column on screen */ + int cmdfirstc; /* ':', '/', '?', '=', '>' or NUL */ + int cmdindent; /* number of spaces before cmdline */ + char_u *cmdprompt; /* message in front of cmdline */ + int cmdattr; /* attributes for prompt */ + int overstrike; /* Typing mode on the command line. Shared by + getcmdline() and put_on_cmdline(). */ + expand_T *xpc; /* struct being used for expansion, xp_pattern + may point into cmdbuff */ + int xp_context; /* type of expansion */ + char_u *xp_arg; /* user-defined expansion arg */ + int input_fn; /* when TRUE Invoked for input() function */ +}; + +/* The current cmdline_info. It is initialized in getcmdline() and after that + * used by other functions. When invoking getcmdline() recursively it needs + * to be saved with save_cmdline() and restored with restore_cmdline(). + * TODO: make it local to getcmdline() and pass it around. */ +static struct cmdline_info ccline; + +static int cmd_showtail; /* Only show path tail in lists ? */ + +static int new_cmdpos; /* position set by set_cmdline_pos() */ + +typedef struct hist_entry { + int hisnum; /* identifying number */ + int viminfo; /* when TRUE hisstr comes from viminfo */ + char_u *hisstr; /* actual entry, separator char after the NUL */ +} histentry_T; + +static histentry_T *(history[HIST_COUNT]) = {NULL, NULL, NULL, NULL, NULL}; +static int hisidx[HIST_COUNT] = {-1, -1, -1, -1, -1}; /* lastused entry */ +static int hisnum[HIST_COUNT] = {0, 0, 0, 0, 0}; +/* identifying (unique) number of newest history entry */ +static int hislen = 0; /* actual length of history tables */ + +static int hist_char2type __ARGS((int c)); + +static int in_history __ARGS((int, char_u *, int, int, int)); +static int calc_hist_idx __ARGS((int histype, int num)); + +static int cmd_hkmap = 0; /* Hebrew mapping during command line */ + +static int cmd_fkmap = 0; /* Farsi mapping during command line */ + +static int cmdline_charsize __ARGS((int idx)); +static void set_cmdspos __ARGS((void)); +static void set_cmdspos_cursor __ARGS((void)); +static void correct_cmdspos __ARGS((int idx, int cells)); +static void alloc_cmdbuff __ARGS((int len)); +static int realloc_cmdbuff __ARGS((int len)); +static void draw_cmdline __ARGS((int start, int len)); +static void save_cmdline __ARGS((struct cmdline_info *ccp)); +static void restore_cmdline __ARGS((struct cmdline_info *ccp)); +static int cmdline_paste __ARGS((int regname, int literally, int remcr)); +static void cmdline_del __ARGS((int from)); +static void redrawcmdprompt __ARGS((void)); +static void cursorcmd __ARGS((void)); +static int ccheck_abbr __ARGS((int)); +static int nextwild __ARGS((expand_T *xp, int type, int options, int escape)); +static void escape_fname __ARGS((char_u **pp)); +static int showmatches __ARGS((expand_T *xp, int wildmenu)); +static void set_expand_context __ARGS((expand_T *xp)); +static int ExpandFromContext __ARGS((expand_T *xp, char_u *, int *, char_u ***, + int)); +static int expand_showtail __ARGS((expand_T *xp)); +static int expand_shellcmd __ARGS((char_u *filepat, int *num_file, char_u * + **file, + int flagsarg)); +static int ExpandRTDir __ARGS((char_u *pat, int *num_file, char_u ***file, + char *dirname[])); +static char_u *get_history_arg __ARGS((expand_T *xp, int idx)); +static int ExpandUserDefined __ARGS((expand_T *xp, regmatch_T *regmatch, + int *num_file, + char_u ***file)); +static int ExpandUserList __ARGS((expand_T *xp, int *num_file, char_u ***file)); +static void clear_hist_entry __ARGS((histentry_T *hisptr)); + +static int ex_window __ARGS((void)); + +static int +sort_func_compare __ARGS((const void *s1, const void *s2)); + +/* + * getcmdline() - accept a command line starting with firstc. + * + * firstc == ':' get ":" command line. + * firstc == '/' or '?' get search pattern + * firstc == '=' get expression + * firstc == '@' get text for input() function + * firstc == '>' get text for debug mode + * firstc == NUL get text for :insert command + * firstc == -1 like NUL, and break on CTRL-C + * + * The line is collected in ccline.cmdbuff, which is reallocated to fit the + * command line. + * + * Careful: getcmdline() can be called recursively! + * + * Return pointer to allocated string if there is a commandline, NULL + * otherwise. + */ +char_u * getcmdline(firstc, count, indent) +int firstc; +long count UNUSED; /* only used for incremental search */ +int indent; /* indent for inside conditionals */ +{ + int c; + int i; + int j; + int gotesc = FALSE; /* TRUE when just typed */ + int do_abbr; /* when TRUE check for abbr. */ + char_u *lookfor = NULL; /* string to match */ + int hiscnt; /* current history line in use */ + int histype; /* history type to be used */ + pos_T old_cursor; + colnr_T old_curswant; + colnr_T old_leftcol; + linenr_T old_topline; + int old_topfill; + linenr_T old_botline; + int did_incsearch = FALSE; + int incsearch_postponed = FALSE; + int did_wild_list = FALSE; /* did wild_list() recently */ + int wim_index = 0; /* index in wim_flags[] */ + int res; + int save_msg_scroll = msg_scroll; + int save_State = State; /* remember State when called */ + int some_key_typed = FALSE; /* one of the keys was typed */ + /* mouse drag and release events are ignored, unless they are + * preceded with a mouse down event */ + int ignore_drag_release = TRUE; + int break_ctrl_c = FALSE; + expand_T xpc; + long *b_im_ptr = NULL; + /* Everything that may work recursively should save and restore the + * current command line in save_ccline. That includes update_screen(), a + * custom status line may invoke ":normal". */ + struct cmdline_info save_ccline; + + if (firstc == -1) { + firstc = NUL; + break_ctrl_c = TRUE; + } + /* start without Hebrew mapping for a command line */ + if (firstc == ':' || firstc == '=' || firstc == '>') + cmd_hkmap = 0; + + ccline.overstrike = FALSE; /* always start in insert mode */ + old_cursor = curwin->w_cursor; /* needs to be restored later */ + old_curswant = curwin->w_curswant; + old_leftcol = curwin->w_leftcol; + old_topline = curwin->w_topline; + old_topfill = curwin->w_topfill; + old_botline = curwin->w_botline; + + /* + * set some variables for redrawcmd() + */ + ccline.cmdfirstc = (firstc == '@' ? 0 : firstc); + ccline.cmdindent = (firstc > 0 ? indent : 0); + + /* alloc initial ccline.cmdbuff */ + alloc_cmdbuff(exmode_active ? 250 : indent + 1); + if (ccline.cmdbuff == NULL) + return NULL; /* out of memory */ + ccline.cmdlen = ccline.cmdpos = 0; + ccline.cmdbuff[0] = NUL; + + /* autoindent for :insert and :append */ + if (firstc <= 0) { + copy_spaces(ccline.cmdbuff, indent); + ccline.cmdbuff[indent] = NUL; + ccline.cmdpos = indent; + ccline.cmdspos = indent; + ccline.cmdlen = indent; + } + + ExpandInit(&xpc); + ccline.xpc = &xpc; + + if (curwin->w_p_rl && *curwin->w_p_rlc == 's' + && (firstc == '/' || firstc == '?')) + cmdmsg_rl = TRUE; + else + cmdmsg_rl = FALSE; + + redir_off = TRUE; /* don't redirect the typed command */ + if (!cmd_silent) { + i = msg_scrolled; + msg_scrolled = 0; /* avoid wait_return message */ + gotocmdline(TRUE); + msg_scrolled += i; + redrawcmdprompt(); /* draw prompt or indent */ + set_cmdspos(); + } + xpc.xp_context = EXPAND_NOTHING; + xpc.xp_backslash = XP_BS_NONE; +#ifndef BACKSLASH_IN_FILENAME + xpc.xp_shell = FALSE; +#endif + + if (ccline.input_fn) { + xpc.xp_context = ccline.xp_context; + xpc.xp_pattern = ccline.cmdbuff; + xpc.xp_arg = ccline.xp_arg; + } + + /* + * Avoid scrolling when called by a recursive do_cmdline(), e.g. when + * doing ":@0" when register 0 doesn't contain a CR. + */ + msg_scroll = FALSE; + + State = CMDLINE; + + if (firstc == '/' || firstc == '?' || firstc == '@') { + /* Use ":lmap" mappings for search pattern and input(). */ + if (curbuf->b_p_imsearch == B_IMODE_USE_INSERT) + b_im_ptr = &curbuf->b_p_iminsert; + else + b_im_ptr = &curbuf->b_p_imsearch; + if (*b_im_ptr == B_IMODE_LMAP) + State |= LANGMAP; +#ifdef USE_IM_CONTROL + im_set_active(*b_im_ptr == B_IMODE_IM); +#endif + } +#ifdef USE_IM_CONTROL + else if (p_imcmdline) + im_set_active(TRUE); +#endif + + setmouse(); +#ifdef CURSOR_SHAPE + ui_cursor_shape(); /* may show different cursor shape */ +#endif + + /* When inside an autocommand for writing "exiting" may be set and + * terminal mode set to cooked. Need to set raw mode here then. */ + settmode(TMODE_RAW); + + init_history(); + hiscnt = hislen; /* set hiscnt to impossible history value */ + histype = hist_char2type(firstc); + + do_digraph(-1); /* init digraph typeahead */ + + /* + * Collect the command string, handling editing keys. + */ + for (;; ) { + redir_off = TRUE; /* Don't redirect the typed command. + Repeated, because a ":redir" inside + completion may switch it on. */ +#ifdef USE_ON_FLY_SCROLL + dont_scroll = FALSE; /* allow scrolling here */ +#endif + quit_more = FALSE; /* reset after CTRL-D which had a more-prompt */ + + cursorcmd(); /* set the cursor on the right spot */ + + /* Get a character. Ignore K_IGNORE, it should not do anything, such + * as stop completion. */ + do { + c = safe_vgetc(); + } while (c == K_IGNORE); + + if (KeyTyped) { + some_key_typed = TRUE; + if (cmd_hkmap) + c = hkmap(c); + if (cmd_fkmap) + c = cmdl_fkmap(c); + if (cmdmsg_rl && !KeyStuffed) { + /* Invert horizontal movements and operations. Only when + * typed by the user directly, not when the result of a + * mapping. */ + switch (c) { + case K_RIGHT: c = K_LEFT; break; + case K_S_RIGHT: c = K_S_LEFT; break; + case K_C_RIGHT: c = K_C_LEFT; break; + case K_LEFT: c = K_RIGHT; break; + case K_S_LEFT: c = K_S_RIGHT; break; + case K_C_LEFT: c = K_C_RIGHT; break; + } + } + } + + /* + * Ignore got_int when CTRL-C was typed here. + * Don't ignore it in :global, we really need to break then, e.g., for + * ":g/pat/normal /pat" (without the ). + * Don't ignore it for the input() function. + */ + if ((c == Ctrl_C +#ifdef UNIX + || c == intr_char +#endif + ) + && firstc != '@' + && !break_ctrl_c + && !global_busy) + got_int = FALSE; + + /* free old command line when finished moving around in the history + * list */ + if (lookfor != NULL + && c != K_S_DOWN && c != K_S_UP + && c != K_DOWN && c != K_UP + && c != K_PAGEDOWN && c != K_PAGEUP + && c != K_KPAGEDOWN && c != K_KPAGEUP + && c != K_LEFT && c != K_RIGHT + && (xpc.xp_numfiles > 0 || (c != Ctrl_P && c != Ctrl_N))) { + vim_free(lookfor); + lookfor = NULL; + } + + /* + * When there are matching completions to select works like + * CTRL-P (unless 'wc' is ). + */ + if (c != p_wc && c == K_S_TAB && xpc.xp_numfiles > 0) + c = Ctrl_P; + + /* Special translations for 'wildmenu' */ + if (did_wild_list && p_wmnu) { + if (c == K_LEFT) + c = Ctrl_P; + else if (c == K_RIGHT) + c = Ctrl_N; + } + /* Hitting CR after "emenu Name.": complete submenu */ + if (xpc.xp_context == EXPAND_MENUNAMES && p_wmnu + && ccline.cmdpos > 1 + && ccline.cmdbuff[ccline.cmdpos - 1] == '.' + && ccline.cmdbuff[ccline.cmdpos - 2] != '\\' + && (c == '\n' || c == '\r' || c == K_KENTER)) + c = K_DOWN; + + /* free expanded names when finished walking through matches */ + if (xpc.xp_numfiles != -1 + && !(c == p_wc && KeyTyped) && c != p_wcm + && c != Ctrl_N && c != Ctrl_P && c != Ctrl_A + && c != Ctrl_L) { + (void)ExpandOne(&xpc, NULL, NULL, 0, WILD_FREE); + did_wild_list = FALSE; + if (!p_wmnu || (c != K_UP && c != K_DOWN)) + xpc.xp_context = EXPAND_NOTHING; + wim_index = 0; + if (p_wmnu && wild_menu_showing != 0) { + int skt = KeyTyped; + int old_RedrawingDisabled = RedrawingDisabled; + + if (ccline.input_fn) + RedrawingDisabled = 0; + + if (wild_menu_showing == WM_SCROLLED) { + /* Entered command line, move it up */ + cmdline_row--; + redrawcmd(); + } else if (save_p_ls != -1) { + /* restore 'laststatus' and 'winminheight' */ + p_ls = save_p_ls; + p_wmh = save_p_wmh; + last_status(FALSE); + save_cmdline(&save_ccline); + update_screen(VALID); /* redraw the screen NOW */ + restore_cmdline(&save_ccline); + redrawcmd(); + save_p_ls = -1; + } else { + win_redraw_last_status(topframe); + redraw_statuslines(); + } + KeyTyped = skt; + wild_menu_showing = 0; + if (ccline.input_fn) + RedrawingDisabled = old_RedrawingDisabled; + } + } + + /* Special translations for 'wildmenu' */ + if (xpc.xp_context == EXPAND_MENUNAMES && p_wmnu) { + /* Hitting after "emenu Name.": complete submenu */ + if (c == K_DOWN && ccline.cmdpos > 0 + && ccline.cmdbuff[ccline.cmdpos - 1] == '.') + c = p_wc; + else if (c == K_UP) { + /* Hitting : Remove one submenu name in front of the + * cursor */ + int found = FALSE; + + j = (int)(xpc.xp_pattern - ccline.cmdbuff); + i = 0; + while (--j > 0) { + /* check for start of menu name */ + if (ccline.cmdbuff[j] == ' ' + && ccline.cmdbuff[j - 1] != '\\') { + i = j + 1; + break; + } + /* check for start of submenu name */ + if (ccline.cmdbuff[j] == '.' + && ccline.cmdbuff[j - 1] != '\\') { + if (found) { + i = j + 1; + break; + } else + found = TRUE; + } + } + if (i > 0) + cmdline_del(i); + c = p_wc; + xpc.xp_context = EXPAND_NOTHING; + } + } + if ((xpc.xp_context == EXPAND_FILES + || xpc.xp_context == EXPAND_DIRECTORIES + || xpc.xp_context == EXPAND_SHELLCMD) && p_wmnu) { + char_u upseg[5]; + + upseg[0] = PATHSEP; + upseg[1] = '.'; + upseg[2] = '.'; + upseg[3] = PATHSEP; + upseg[4] = NUL; + + if (c == K_DOWN + && ccline.cmdpos > 0 + && ccline.cmdbuff[ccline.cmdpos - 1] == PATHSEP + && (ccline.cmdpos < 3 + || ccline.cmdbuff[ccline.cmdpos - 2] != '.' + || ccline.cmdbuff[ccline.cmdpos - 3] != '.')) { + /* go down a directory */ + c = p_wc; + } else if (STRNCMP(xpc.xp_pattern, upseg + 1, 3) == 0 && c == K_DOWN) { + /* If in a direct ancestor, strip off one ../ to go down */ + int found = FALSE; + + j = ccline.cmdpos; + i = (int)(xpc.xp_pattern - ccline.cmdbuff); + while (--j > i) { + if (has_mbyte) + j -= (*mb_head_off)(ccline.cmdbuff, ccline.cmdbuff + j); + if (vim_ispathsep(ccline.cmdbuff[j])) { + found = TRUE; + break; + } + } + if (found + && ccline.cmdbuff[j - 1] == '.' + && ccline.cmdbuff[j - 2] == '.' + && (vim_ispathsep(ccline.cmdbuff[j - 3]) || j == i + 2)) { + cmdline_del(j - 2); + c = p_wc; + } + } else if (c == K_UP) { + /* go up a directory */ + int found = FALSE; + + j = ccline.cmdpos - 1; + i = (int)(xpc.xp_pattern - ccline.cmdbuff); + while (--j > i) { + if (has_mbyte) + j -= (*mb_head_off)(ccline.cmdbuff, ccline.cmdbuff + j); + if (vim_ispathsep(ccline.cmdbuff[j]) +#ifdef BACKSLASH_IN_FILENAME + && vim_strchr(" *?[{`$%#", ccline.cmdbuff[j + 1]) + == NULL +#endif + ) { + if (found) { + i = j + 1; + break; + } else + found = TRUE; + } + } + + if (!found) + j = i; + else if (STRNCMP(ccline.cmdbuff + j, upseg, 4) == 0) + j += 4; + else if (STRNCMP(ccline.cmdbuff + j, upseg + 1, 3) == 0 + && j == i) + j += 3; + else + j = 0; + if (j > 0) { + /* TODO this is only for DOS/UNIX systems - need to put in + * machine-specific stuff here and in upseg init */ + cmdline_del(j); + put_on_cmdline(upseg + 1, 3, FALSE); + } else if (ccline.cmdpos > i) + cmdline_del(i); + + /* Now complete in the new directory. Set KeyTyped in case the + * Up key came from a mapping. */ + c = p_wc; + KeyTyped = TRUE; + } + } + + + /* CTRL-\ CTRL-N goes to Normal mode, CTRL-\ CTRL-G goes to Insert + * mode when 'insertmode' is set, CTRL-\ e prompts for an expression. */ + if (c == Ctrl_BSL) { + ++no_mapping; + ++allow_keys; + c = plain_vgetc(); + --no_mapping; + --allow_keys; + /* CTRL-\ e doesn't work when obtaining an expression, unless it + * is in a mapping. */ + if (c != Ctrl_N && c != Ctrl_G && (c != 'e' + || (ccline.cmdfirstc == '=' && + KeyTyped))) { + vungetc(c); + c = Ctrl_BSL; + } else if (c == 'e') { + char_u *p = NULL; + int len; + + /* + * Replace the command line with the result of an expression. + * Need to save and restore the current command line, to be + * able to enter a new one... + */ + if (ccline.cmdpos == ccline.cmdlen) + new_cmdpos = 99999; /* keep it at the end */ + else + new_cmdpos = ccline.cmdpos; + + save_cmdline(&save_ccline); + c = get_expr_register(); + restore_cmdline(&save_ccline); + if (c == '=') { + /* Need to save and restore ccline. And set "textlock" + * to avoid nasty things like going to another buffer when + * evaluating an expression. */ + save_cmdline(&save_ccline); + ++textlock; + p = get_expr_line(); + --textlock; + restore_cmdline(&save_ccline); + + if (p != NULL) { + len = (int)STRLEN(p); + if (realloc_cmdbuff(len + 1) == OK) { + ccline.cmdlen = len; + STRCPY(ccline.cmdbuff, p); + vim_free(p); + + /* Restore the cursor or use the position set with + * set_cmdline_pos(). */ + if (new_cmdpos > ccline.cmdlen) + ccline.cmdpos = ccline.cmdlen; + else + ccline.cmdpos = new_cmdpos; + + KeyTyped = FALSE; /* Don't do p_wc completion. */ + redrawcmd(); + goto cmdline_changed; + } + } + } + beep_flush(); + got_int = FALSE; /* don't abandon the command line */ + did_emsg = FALSE; + emsg_on_display = FALSE; + redrawcmd(); + goto cmdline_not_changed; + } else { + if (c == Ctrl_G && p_im && restart_edit == 0) + restart_edit = 'a'; + gotesc = TRUE; /* will free ccline.cmdbuff after putting it + in history */ + goto returncmd; /* back to Normal mode */ + } + } + + if (c == cedit_key || c == K_CMDWIN) { + /* + * Open a window to edit the command line (and history). + */ + c = ex_window(); + some_key_typed = TRUE; + } else + c = do_digraph(c); + + if (c == '\n' || c == '\r' || c == K_KENTER || (c == ESC + && (!KeyTyped || + vim_strchr(p_cpo, + CPO_ESC) != + NULL))) { + /* In Ex mode a backslash escapes a newline. */ + if (exmode_active + && c != ESC + && ccline.cmdpos == ccline.cmdlen + && ccline.cmdpos > 0 + && ccline.cmdbuff[ccline.cmdpos - 1] == '\\') { + if (c == K_KENTER) + c = '\n'; + } else { + gotesc = FALSE; /* Might have typed ESC previously, don't + truncate the cmdline now. */ + if (ccheck_abbr(c + ABBR_OFF)) + goto cmdline_changed; + if (!cmd_silent) { + windgoto(msg_row, 0); + out_flush(); + } + break; + } + } + + /* + * Completion for 'wildchar' or 'wildcharm' key. + * - hitting twice means: abandon command line. + * - wildcard expansion is only done when the 'wildchar' key is really + * typed, not when it comes from a macro + */ + if ((c == p_wc && !gotesc && KeyTyped) || c == p_wcm) { + if (xpc.xp_numfiles > 0) { /* typed p_wc at least twice */ + /* if 'wildmode' contains "list" may still need to list */ + if (xpc.xp_numfiles > 1 + && !did_wild_list + && (wim_flags[wim_index] & WIM_LIST)) { + (void)showmatches(&xpc, FALSE); + redrawcmd(); + did_wild_list = TRUE; + } + if (wim_flags[wim_index] & WIM_LONGEST) + res = nextwild(&xpc, WILD_LONGEST, WILD_NO_BEEP, + firstc != '@'); + else if (wim_flags[wim_index] & WIM_FULL) + res = nextwild(&xpc, WILD_NEXT, WILD_NO_BEEP, + firstc != '@'); + else + res = OK; /* don't insert 'wildchar' now */ + } else { /* typed p_wc first time */ + wim_index = 0; + j = ccline.cmdpos; + /* if 'wildmode' first contains "longest", get longest + * common part */ + if (wim_flags[0] & WIM_LONGEST) + res = nextwild(&xpc, WILD_LONGEST, WILD_NO_BEEP, + firstc != '@'); + else + res = nextwild(&xpc, WILD_EXPAND_KEEP, WILD_NO_BEEP, + firstc != '@'); + + /* if interrupted while completing, behave like it failed */ + if (got_int) { + (void)vpeekc(); /* remove from input stream */ + got_int = FALSE; /* don't abandon the command line */ + (void)ExpandOne(&xpc, NULL, NULL, 0, WILD_FREE); + xpc.xp_context = EXPAND_NOTHING; + goto cmdline_changed; + } + + /* when more than one match, and 'wildmode' first contains + * "list", or no change and 'wildmode' contains "longest,list", + * list all matches */ + if (res == OK && xpc.xp_numfiles > 1) { + /* a "longest" that didn't do anything is skipped (but not + * "list:longest") */ + if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j) + wim_index = 1; + if ((wim_flags[wim_index] & WIM_LIST) + || (p_wmnu && (wim_flags[wim_index] & WIM_FULL) != 0) + ) { + if (!(wim_flags[0] & WIM_LONGEST)) { + int p_wmnu_save = p_wmnu; + p_wmnu = 0; + /* remove match */ + nextwild(&xpc, WILD_PREV, 0, firstc != '@'); + p_wmnu = p_wmnu_save; + } + (void)showmatches(&xpc, p_wmnu + && ((wim_flags[wim_index] & WIM_LIST) == 0)); + redrawcmd(); + did_wild_list = TRUE; + if (wim_flags[wim_index] & WIM_LONGEST) + nextwild(&xpc, WILD_LONGEST, WILD_NO_BEEP, + firstc != '@'); + else if (wim_flags[wim_index] & WIM_FULL) + nextwild(&xpc, WILD_NEXT, WILD_NO_BEEP, + firstc != '@'); + } else + vim_beep(); + } else if (xpc.xp_numfiles == -1) + xpc.xp_context = EXPAND_NOTHING; + } + if (wim_index < 3) + ++wim_index; + if (c == ESC) + gotesc = TRUE; + if (res == OK) + goto cmdline_changed; + } + + gotesc = FALSE; + + /* goes to last match, in a clumsy way */ + if (c == K_S_TAB && KeyTyped) { + if (nextwild(&xpc, WILD_EXPAND_KEEP, 0, firstc != '@') == OK + && nextwild(&xpc, WILD_PREV, 0, firstc != '@') == OK + && nextwild(&xpc, WILD_PREV, 0, firstc != '@') == OK) + goto cmdline_changed; + } + + if (c == NUL || c == K_ZERO) /* NUL is stored as NL */ + c = NL; + + do_abbr = TRUE; /* default: check for abbreviation */ + + /* + * Big switch for a typed command line character. + */ + switch (c) { + case K_BS: + case Ctrl_H: + case K_DEL: + case K_KDEL: + case Ctrl_W: + if (cmd_fkmap && c == K_BS) + c = K_DEL; + if (c == K_KDEL) + c = K_DEL; + + /* + * delete current character is the same as backspace on next + * character, except at end of line + */ + if (c == K_DEL && ccline.cmdpos != ccline.cmdlen) + ++ccline.cmdpos; + if (has_mbyte && c == K_DEL) + ccline.cmdpos += mb_off_next(ccline.cmdbuff, + ccline.cmdbuff + ccline.cmdpos); + if (ccline.cmdpos > 0) { + char_u *p; + + j = ccline.cmdpos; + p = ccline.cmdbuff + j; + if (has_mbyte) { + p = mb_prevptr(ccline.cmdbuff, p); + if (c == Ctrl_W) { + while (p > ccline.cmdbuff && vim_isspace(*p)) + p = mb_prevptr(ccline.cmdbuff, p); + i = mb_get_class(p); + while (p > ccline.cmdbuff && mb_get_class(p) == i) + p = mb_prevptr(ccline.cmdbuff, p); + if (mb_get_class(p) != i) + p += (*mb_ptr2len)(p); + } + } else if (c == Ctrl_W) { + while (p > ccline.cmdbuff && vim_isspace(p[-1])) + --p; + i = vim_iswordc(p[-1]); + while (p > ccline.cmdbuff && !vim_isspace(p[-1]) + && vim_iswordc(p[-1]) == i) + --p; + } else + --p; + ccline.cmdpos = (int)(p - ccline.cmdbuff); + ccline.cmdlen -= j - ccline.cmdpos; + i = ccline.cmdpos; + while (i < ccline.cmdlen) + ccline.cmdbuff[i++] = ccline.cmdbuff[j++]; + + /* Truncate at the end, required for multi-byte chars. */ + ccline.cmdbuff[ccline.cmdlen] = NUL; + redrawcmd(); + } else if (ccline.cmdlen == 0 && c != Ctrl_W + && ccline.cmdprompt == NULL && indent == 0) { + /* In ex and debug mode it doesn't make sense to return. */ + if (exmode_active + || ccline.cmdfirstc == '>' + ) + goto cmdline_not_changed; + + vim_free(ccline.cmdbuff); /* no commandline to return */ + ccline.cmdbuff = NULL; + if (!cmd_silent) { + if (cmdmsg_rl) + msg_col = Columns; + else + msg_col = 0; + msg_putchar(' '); /* delete ':' */ + } + redraw_cmdline = TRUE; + goto returncmd; /* back to cmd mode */ + } + goto cmdline_changed; + + case K_INS: + case K_KINS: + /* if Farsi mode set, we are in reverse insert mode - + Do not change the mode */ + if (cmd_fkmap) + beep_flush(); + else + ccline.overstrike = !ccline.overstrike; +#ifdef CURSOR_SHAPE + ui_cursor_shape(); /* may show different cursor shape */ +#endif + goto cmdline_not_changed; + + case Ctrl_HAT: + if (map_to_exists_mode((char_u *)"", LANGMAP, FALSE)) { + /* ":lmap" mappings exists, toggle use of mappings. */ + State ^= LANGMAP; +#ifdef USE_IM_CONTROL + im_set_active(FALSE); /* Disable input method */ +#endif + if (b_im_ptr != NULL) { + if (State & LANGMAP) + *b_im_ptr = B_IMODE_LMAP; + else + *b_im_ptr = B_IMODE_NONE; + } + } +#ifdef USE_IM_CONTROL + else { + /* There are no ":lmap" mappings, toggle IM. When + * 'imdisable' is set don't try getting the status, it's + * always off. */ + if ((p_imdisable && b_im_ptr != NULL) + ? *b_im_ptr == B_IMODE_IM : im_get_status()) { + im_set_active(FALSE); /* Disable input method */ + if (b_im_ptr != NULL) + *b_im_ptr = B_IMODE_NONE; + } else { + im_set_active(TRUE); /* Enable input method */ + if (b_im_ptr != NULL) + *b_im_ptr = B_IMODE_IM; + } + } +#endif + if (b_im_ptr != NULL) { + if (b_im_ptr == &curbuf->b_p_iminsert) + set_iminsert_global(); + else + set_imsearch_global(); + } +#ifdef CURSOR_SHAPE + ui_cursor_shape(); /* may show different cursor shape */ +#endif + /* Show/unshow value of 'keymap' in status lines later. */ + status_redraw_curbuf(); + goto cmdline_not_changed; + + /* case '@': only in very old vi */ + case Ctrl_U: + /* delete all characters left of the cursor */ + j = ccline.cmdpos; + ccline.cmdlen -= j; + i = ccline.cmdpos = 0; + while (i < ccline.cmdlen) + ccline.cmdbuff[i++] = ccline.cmdbuff[j++]; + /* Truncate at the end, required for multi-byte chars. */ + ccline.cmdbuff[ccline.cmdlen] = NUL; + redrawcmd(); + goto cmdline_changed; + + + case ESC: /* get here if p_wc != ESC or when ESC typed twice */ + case Ctrl_C: + /* In exmode it doesn't make sense to return. Except when + * ":normal" runs out of characters. */ + if (exmode_active + && (ex_normal_busy == 0 || typebuf.tb_len > 0) + ) + goto cmdline_not_changed; + + gotesc = TRUE; /* will free ccline.cmdbuff after + putting it in history */ + goto returncmd; /* back to cmd mode */ + + case Ctrl_R: /* insert register */ +#ifdef USE_ON_FLY_SCROLL + dont_scroll = TRUE; /* disallow scrolling here */ +#endif + putcmdline('"', TRUE); + ++no_mapping; + i = c = plain_vgetc(); /* CTRL-R */ + if (i == Ctrl_O) + i = Ctrl_R; /* CTRL-R CTRL-O == CTRL-R CTRL-R */ + if (i == Ctrl_R) + c = plain_vgetc(); /* CTRL-R CTRL-R */ + --no_mapping; + /* + * Insert the result of an expression. + * Need to save the current command line, to be able to enter + * a new one... + */ + new_cmdpos = -1; + if (c == '=') { + if (ccline.cmdfirstc == '=') { /* can't do this recursively */ + beep_flush(); + c = ESC; + } else { + save_cmdline(&save_ccline); + c = get_expr_register(); + restore_cmdline(&save_ccline); + } + } + if (c != ESC) { /* use ESC to cancel inserting register */ + cmdline_paste(c, i == Ctrl_R, FALSE); + + /* When there was a serious error abort getting the + * command line. */ + if (aborting()) { + gotesc = TRUE; /* will free ccline.cmdbuff after + putting it in history */ + goto returncmd; /* back to cmd mode */ + } + KeyTyped = FALSE; /* Don't do p_wc completion. */ + if (new_cmdpos >= 0) { + /* set_cmdline_pos() was used */ + if (new_cmdpos > ccline.cmdlen) + ccline.cmdpos = ccline.cmdlen; + else + ccline.cmdpos = new_cmdpos; + } + } + redrawcmd(); + goto cmdline_changed; + + case Ctrl_D: + if (showmatches(&xpc, FALSE) == EXPAND_NOTHING) + break; /* Use ^D as normal char instead */ + + redrawcmd(); + continue; /* don't do incremental search now */ + + case K_RIGHT: + case K_S_RIGHT: + case K_C_RIGHT: + do { + if (ccline.cmdpos >= ccline.cmdlen) + break; + i = cmdline_charsize(ccline.cmdpos); + if (KeyTyped && ccline.cmdspos + i >= Columns * Rows) + break; + ccline.cmdspos += i; + if (has_mbyte) + ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff + + ccline.cmdpos); + else + ++ccline.cmdpos; + } while ((c == K_S_RIGHT || c == K_C_RIGHT + || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) + && ccline.cmdbuff[ccline.cmdpos] != ' '); + if (has_mbyte) + set_cmdspos_cursor(); + goto cmdline_not_changed; + + case K_LEFT: + case K_S_LEFT: + case K_C_LEFT: + if (ccline.cmdpos == 0) + goto cmdline_not_changed; + do { + --ccline.cmdpos; + if (has_mbyte) /* move to first byte of char */ + ccline.cmdpos -= (*mb_head_off)(ccline.cmdbuff, + ccline.cmdbuff + ccline.cmdpos); + ccline.cmdspos -= cmdline_charsize(ccline.cmdpos); + } while (ccline.cmdpos > 0 + && (c == K_S_LEFT || c == K_C_LEFT + || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) + && ccline.cmdbuff[ccline.cmdpos - 1] != ' '); + if (has_mbyte) + set_cmdspos_cursor(); + goto cmdline_not_changed; + + case K_IGNORE: + /* Ignore mouse event or ex_window() result. */ + goto cmdline_not_changed; + + + case K_MIDDLEDRAG: + case K_MIDDLERELEASE: + goto cmdline_not_changed; /* Ignore mouse */ + + case K_MIDDLEMOUSE: + if (!mouse_has(MOUSE_COMMAND)) + goto cmdline_not_changed; /* Ignore mouse */ + cmdline_paste(0, TRUE, TRUE); + redrawcmd(); + goto cmdline_changed; + + + case K_LEFTDRAG: + case K_LEFTRELEASE: + case K_RIGHTDRAG: + case K_RIGHTRELEASE: + /* Ignore drag and release events when the button-down wasn't + * seen before. */ + if (ignore_drag_release) + goto cmdline_not_changed; + /* FALLTHROUGH */ + case K_LEFTMOUSE: + case K_RIGHTMOUSE: + if (c == K_LEFTRELEASE || c == K_RIGHTRELEASE) + ignore_drag_release = TRUE; + else + ignore_drag_release = FALSE; + if (!mouse_has(MOUSE_COMMAND)) + goto cmdline_not_changed; /* Ignore mouse */ + + set_cmdspos(); + for (ccline.cmdpos = 0; ccline.cmdpos < ccline.cmdlen; + ++ccline.cmdpos) { + i = cmdline_charsize(ccline.cmdpos); + if (mouse_row <= cmdline_row + ccline.cmdspos / Columns + && mouse_col < ccline.cmdspos % Columns + i) + break; + if (has_mbyte) { + /* Count ">" for double-wide char that doesn't fit. */ + correct_cmdspos(ccline.cmdpos, i); + ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff + + ccline.cmdpos) - 1; + } + ccline.cmdspos += i; + } + goto cmdline_not_changed; + + /* Mouse scroll wheel: ignored here */ + case K_MOUSEDOWN: + case K_MOUSEUP: + case K_MOUSELEFT: + case K_MOUSERIGHT: + /* Alternate buttons ignored here */ + case K_X1MOUSE: + case K_X1DRAG: + case K_X1RELEASE: + case K_X2MOUSE: + case K_X2DRAG: + case K_X2RELEASE: + goto cmdline_not_changed; + + + + case K_SELECT: /* end of Select mode mapping - ignore */ + goto cmdline_not_changed; + + case Ctrl_B: /* begin of command line */ + case K_HOME: + case K_KHOME: + case K_S_HOME: + case K_C_HOME: + ccline.cmdpos = 0; + set_cmdspos(); + goto cmdline_not_changed; + + case Ctrl_E: /* end of command line */ + case K_END: + case K_KEND: + case K_S_END: + case K_C_END: + ccline.cmdpos = ccline.cmdlen; + set_cmdspos_cursor(); + goto cmdline_not_changed; + + case Ctrl_A: /* all matches */ + if (nextwild(&xpc, WILD_ALL, 0, firstc != '@') == FAIL) + break; + goto cmdline_changed; + + case Ctrl_L: + if (p_is && !cmd_silent && (firstc == '/' || firstc == '?')) { + /* Add a character from under the cursor for 'incsearch' */ + if (did_incsearch + && !equalpos(curwin->w_cursor, old_cursor)) { + c = gchar_cursor(); + /* If 'ignorecase' and 'smartcase' are set and the + * command line has no uppercase characters, convert + * the character to lowercase */ + if (p_ic && p_scs && !pat_has_uppercase(ccline.cmdbuff)) + c = MB_TOLOWER(c); + if (c != NUL) { + if (c == firstc || vim_strchr((char_u *)( + p_magic ? "\\^$.*[" : "\\^$"), c) + != NULL) { + /* put a backslash before special characters */ + stuffcharReadbuff(c); + c = '\\'; + } + break; + } + } + goto cmdline_not_changed; + } + + /* completion: longest common part */ + if (nextwild(&xpc, WILD_LONGEST, 0, firstc != '@') == FAIL) + break; + goto cmdline_changed; + + case Ctrl_N: /* next match */ + case Ctrl_P: /* previous match */ + if (xpc.xp_numfiles > 0) { + if (nextwild(&xpc, (c == Ctrl_P) ? WILD_PREV : WILD_NEXT, + 0, firstc != '@') == FAIL) + break; + goto cmdline_changed; + } + + case K_UP: + case K_DOWN: + case K_S_UP: + case K_S_DOWN: + case K_PAGEUP: + case K_KPAGEUP: + case K_PAGEDOWN: + case K_KPAGEDOWN: + if (hislen == 0 || firstc == NUL) /* no history */ + goto cmdline_not_changed; + + i = hiscnt; + + /* save current command string so it can be restored later */ + if (lookfor == NULL) { + if ((lookfor = vim_strsave(ccline.cmdbuff)) == NULL) + goto cmdline_not_changed; + lookfor[ccline.cmdpos] = NUL; + } + + j = (int)STRLEN(lookfor); + for (;; ) { + /* one step backwards */ + if (c == K_UP|| c == K_S_UP || c == Ctrl_P + || c == K_PAGEUP || c == K_KPAGEUP) { + if (hiscnt == hislen) /* first time */ + hiscnt = hisidx[histype]; + else if (hiscnt == 0 && hisidx[histype] != hislen - 1) + hiscnt = hislen - 1; + else if (hiscnt != hisidx[histype] + 1) + --hiscnt; + else { /* at top of list */ + hiscnt = i; + break; + } + } else { /* one step forwards */ + /* on last entry, clear the line */ + if (hiscnt == hisidx[histype]) { + hiscnt = hislen; + break; + } + + /* not on a history line, nothing to do */ + if (hiscnt == hislen) + break; + if (hiscnt == hislen - 1) /* wrap around */ + hiscnt = 0; + else + ++hiscnt; + } + if (hiscnt < 0 || history[histype][hiscnt].hisstr == NULL) { + hiscnt = i; + break; + } + if ((c != K_UP && c != K_DOWN) + || hiscnt == i + || STRNCMP(history[histype][hiscnt].hisstr, + lookfor, (size_t)j) == 0) + break; + } + + if (hiscnt != i) { /* jumped to other entry */ + char_u *p; + int len; + int old_firstc; + + vim_free(ccline.cmdbuff); + xpc.xp_context = EXPAND_NOTHING; + if (hiscnt == hislen) + p = lookfor; /* back to the old one */ + else + p = history[histype][hiscnt].hisstr; + + if (histype == HIST_SEARCH + && p != lookfor + && (old_firstc = p[STRLEN(p) + 1]) != firstc) { + /* Correct for the separator character used when + * adding the history entry vs the one used now. + * First loop: count length. + * Second loop: copy the characters. */ + for (i = 0; i <= 1; ++i) { + len = 0; + for (j = 0; p[j] != NUL; ++j) { + /* Replace old sep with new sep, unless it is + * escaped. */ + if (p[j] == old_firstc + && (j == 0 || p[j - 1] != '\\')) { + if (i > 0) + ccline.cmdbuff[len] = firstc; + } else { + /* Escape new sep, unless it is already + * escaped. */ + if (p[j] == firstc + && (j == 0 || p[j - 1] != '\\')) { + if (i > 0) + ccline.cmdbuff[len] = '\\'; + ++len; + } + if (i > 0) + ccline.cmdbuff[len] = p[j]; + } + ++len; + } + if (i == 0) { + alloc_cmdbuff(len); + if (ccline.cmdbuff == NULL) + goto returncmd; + } + } + ccline.cmdbuff[len] = NUL; + } else { + alloc_cmdbuff((int)STRLEN(p)); + if (ccline.cmdbuff == NULL) + goto returncmd; + STRCPY(ccline.cmdbuff, p); + } + + ccline.cmdpos = ccline.cmdlen = (int)STRLEN(ccline.cmdbuff); + redrawcmd(); + goto cmdline_changed; + } + beep_flush(); + goto cmdline_not_changed; + + case Ctrl_V: + case Ctrl_Q: + ignore_drag_release = TRUE; + putcmdline('^', TRUE); + c = get_literal(); /* get next (two) character(s) */ + do_abbr = FALSE; /* don't do abbreviation now */ + /* may need to remove ^ when composing char was typed */ + if (enc_utf8 && utf_iscomposing(c) && !cmd_silent) { + draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos); + msg_putchar(' '); + cursorcmd(); + } + break; + + case Ctrl_K: + ignore_drag_release = TRUE; + putcmdline('?', TRUE); +#ifdef USE_ON_FLY_SCROLL + dont_scroll = TRUE; /* disallow scrolling here */ +#endif + c = get_digraph(TRUE); + if (c != NUL) + break; + + redrawcmd(); + goto cmdline_not_changed; + + case Ctrl__: /* CTRL-_: switch language mode */ + if (!p_ari) + break; + if (p_altkeymap) { + cmd_fkmap = !cmd_fkmap; + if (cmd_fkmap) /* in Farsi always in Insert mode */ + ccline.overstrike = FALSE; + } else /* Hebrew is default */ + cmd_hkmap = !cmd_hkmap; + goto cmdline_not_changed; + + default: +#ifdef UNIX + if (c == intr_char) { + gotesc = TRUE; /* will free ccline.cmdbuff after + putting it in history */ + goto returncmd; /* back to Normal mode */ + } +#endif + /* + * Normal character with no special meaning. Just set mod_mask + * to 0x0 so that typing Shift-Space in the GUI doesn't enter + * the string . This should only happen after ^V. + */ + if (!IS_SPECIAL(c)) + mod_mask = 0x0; + break; + } + /* + * End of switch on command line character. + * We come here if we have a normal character. + */ + + if (do_abbr && (IS_SPECIAL(c) || !vim_iswordc(c)) && (ccheck_abbr( + /* Add ABBR_OFF for characters above 0x100, this is + * what check_abbr() expects. */ + (has_mbyte && + c >= + 0x100) ? (c + + ABBR_OFF) : + c) || c == + Ctrl_RSB)) + goto cmdline_changed; + + /* + * put the character in the command line + */ + if (IS_SPECIAL(c) || mod_mask != 0) + put_on_cmdline(get_special_key_name(c, mod_mask), -1, TRUE); + else { + if (has_mbyte) { + j = (*mb_char2bytes)(c, IObuff); + IObuff[j] = NUL; /* exclude composing chars */ + put_on_cmdline(IObuff, j, TRUE); + } else { + IObuff[0] = c; + put_on_cmdline(IObuff, 1, TRUE); + } + } + goto cmdline_changed; + + /* + * This part implements incremental searches for "/" and "?" + * Jump to cmdline_not_changed when a character has been read but the command + * line did not change. Then we only search and redraw if something changed in + * the past. + * Jump to cmdline_changed when the command line did change. + * (Sorry for the goto's, I know it is ugly). + */ +cmdline_not_changed: + if (!incsearch_postponed) + continue; + +cmdline_changed: + /* + * 'incsearch' highlighting. + */ + if (p_is && !cmd_silent && (firstc == '/' || firstc == '?')) { + pos_T end_pos; + proftime_T tm; + + /* if there is a character waiting, search and redraw later */ + if (char_avail()) { + incsearch_postponed = TRUE; + continue; + } + incsearch_postponed = FALSE; + curwin->w_cursor = old_cursor; /* start at old position */ + + /* If there is no command line, don't do anything */ + if (ccline.cmdlen == 0) + i = 0; + else { + cursor_off(); /* so the user knows we're busy */ + out_flush(); + ++emsg_off; /* So it doesn't beep if bad expr */ + /* Set the time limit to half a second. */ + profile_setlimit(500L, &tm); + i = do_search(NULL, firstc, ccline.cmdbuff, count, + SEARCH_KEEP + SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK, + &tm + ); + --emsg_off; + /* if interrupted while searching, behave like it failed */ + if (got_int) { + (void)vpeekc(); /* remove from input stream */ + got_int = FALSE; /* don't abandon the command line */ + i = 0; + } else if (char_avail()) + /* cancelled searching because a char was typed */ + incsearch_postponed = TRUE; + } + if (i != 0) + highlight_match = TRUE; /* highlight position */ + else + highlight_match = FALSE; /* remove highlight */ + + /* first restore the old curwin values, so the screen is + * positioned in the same way as the actual search command */ + curwin->w_leftcol = old_leftcol; + curwin->w_topline = old_topline; + curwin->w_topfill = old_topfill; + curwin->w_botline = old_botline; + changed_cline_bef_curs(); + update_topline(); + + if (i != 0) { + pos_T save_pos = curwin->w_cursor; + + /* + * First move cursor to end of match, then to the start. This + * moves the whole match onto the screen when 'nowrap' is set. + */ + curwin->w_cursor.lnum += search_match_lines; + curwin->w_cursor.col = search_match_endcol; + if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) { + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + coladvance((colnr_T)MAXCOL); + } + validate_cursor(); + end_pos = curwin->w_cursor; + curwin->w_cursor = save_pos; + } else + end_pos = curwin->w_cursor; /* shutup gcc 4 */ + + validate_cursor(); + /* May redraw the status line to show the cursor position. */ + if (p_ru && curwin->w_status_height > 0) + curwin->w_redr_status = TRUE; + + save_cmdline(&save_ccline); + update_screen(SOME_VALID); + restore_cmdline(&save_ccline); + + /* Leave it at the end to make CTRL-R CTRL-W work. */ + if (i != 0) + curwin->w_cursor = end_pos; + + msg_starthere(); + redrawcmdline(); + did_incsearch = TRUE; + } + + if (cmdmsg_rl + || (p_arshape && !p_tbidi && enc_utf8) + ) + /* Always redraw the whole command line to fix shaping and + * right-left typing. Not efficient, but it works. + * Do it only when there are no characters left to read + * to avoid useless intermediate redraws. */ + if (vpeekc() == NUL) + redrawcmd(); + } + +returncmd: + + cmdmsg_rl = FALSE; + + cmd_fkmap = 0; + + ExpandCleanup(&xpc); + ccline.xpc = NULL; + + if (did_incsearch) { + curwin->w_cursor = old_cursor; + curwin->w_curswant = old_curswant; + curwin->w_leftcol = old_leftcol; + curwin->w_topline = old_topline; + curwin->w_topfill = old_topfill; + curwin->w_botline = old_botline; + highlight_match = FALSE; + validate_cursor(); /* needed for TAB */ + redraw_later(SOME_VALID); + } + + if (ccline.cmdbuff != NULL) { + /* + * Put line in history buffer (":" and "=" only when it was typed). + */ + if (ccline.cmdlen && firstc != NUL + && (some_key_typed || histype == HIST_SEARCH)) { + add_to_history(histype, ccline.cmdbuff, TRUE, + histype == HIST_SEARCH ? firstc : NUL); + if (firstc == ':') { + vim_free(new_last_cmdline); + new_last_cmdline = vim_strsave(ccline.cmdbuff); + } + } + + if (gotesc) { /* abandon command line */ + vim_free(ccline.cmdbuff); + ccline.cmdbuff = NULL; + if (msg_scrolled == 0) + compute_cmdrow(); + MSG(""); + redraw_cmdline = TRUE; + } + } + + /* + * If the screen was shifted up, redraw the whole screen (later). + * If the line is too long, clear it, so ruler and shown command do + * not get printed in the middle of it. + */ + msg_check(); + msg_scroll = save_msg_scroll; + redir_off = FALSE; + + /* When the command line was typed, no need for a wait-return prompt. */ + if (some_key_typed) + need_wait_return = FALSE; + + State = save_State; +#ifdef USE_IM_CONTROL + if (b_im_ptr != NULL && *b_im_ptr != B_IMODE_LMAP) + im_save_status(b_im_ptr); + im_set_active(FALSE); +#endif + setmouse(); +#ifdef CURSOR_SHAPE + ui_cursor_shape(); /* may show different cursor shape */ +#endif + + { + char_u *p = ccline.cmdbuff; + + /* Make ccline empty, getcmdline() may try to use it. */ + ccline.cmdbuff = NULL; + return p; + } +} + +/* + * Get a command line with a prompt. + * This is prepared to be called recursively from getcmdline() (e.g. by + * f_input() when evaluating an expression from CTRL-R =). + * Returns the command line in allocated memory, or NULL. + */ +char_u * getcmdline_prompt(firstc, prompt, attr, xp_context, xp_arg) +int firstc; +char_u *prompt; /* command line prompt */ +int attr; /* attributes for prompt */ +int xp_context; /* type of expansion */ +char_u *xp_arg; /* user-defined expansion argument */ +{ + char_u *s; + struct cmdline_info save_ccline; + int msg_col_save = msg_col; + + save_cmdline(&save_ccline); + ccline.cmdprompt = prompt; + ccline.cmdattr = attr; + ccline.xp_context = xp_context; + ccline.xp_arg = xp_arg; + ccline.input_fn = (firstc == '@'); + s = getcmdline(firstc, 1L, 0); + restore_cmdline(&save_ccline); + /* Restore msg_col, the prompt from input() may have changed it. + * But only if called recursively and the commandline is therefore being + * restored to an old one; if not, the input() prompt stays on the screen, + * so we need its modified msg_col left intact. */ + if (ccline.cmdbuff != NULL) + msg_col = msg_col_save; + + return s; +} + +/* + * Return TRUE when the text must not be changed and we can't switch to + * another window or buffer. Used when editing the command line, evaluating + * 'balloonexpr', etc. + */ +int text_locked() { + if (cmdwin_type != 0) + return TRUE; + return textlock != 0; +} + +/* + * Give an error message for a command that isn't allowed while the cmdline + * window is open or editing the cmdline in another way. + */ +void text_locked_msg() { + if (cmdwin_type != 0) + EMSG(_(e_cmdwin)); + else + EMSG(_(e_secure)); +} + +/* + * Check if "curbuf_lock" or "allbuf_lock" is set and return TRUE when it is + * and give an error message. + */ +int curbuf_locked() { + if (curbuf_lock > 0) { + EMSG(_("E788: Not allowed to edit another buffer now")); + return TRUE; + } + return allbuf_locked(); +} + +/* + * Check if "allbuf_lock" is set and return TRUE when it is and give an error + * message. + */ +int allbuf_locked() { + if (allbuf_lock > 0) { + EMSG(_("E811: Not allowed to change buffer information now")); + return TRUE; + } + return FALSE; +} + +static int cmdline_charsize(idx) +int idx; +{ + if (cmdline_star > 0) /* showing '*', always 1 position */ + return 1; + return ptr2cells(ccline.cmdbuff + idx); +} + +/* + * Compute the offset of the cursor on the command line for the prompt and + * indent. + */ +static void set_cmdspos() { + if (ccline.cmdfirstc != NUL) + ccline.cmdspos = 1 + ccline.cmdindent; + else + ccline.cmdspos = 0 + ccline.cmdindent; +} + +/* + * Compute the screen position for the cursor on the command line. + */ +static void set_cmdspos_cursor() { + int i, m, c; + + set_cmdspos(); + if (KeyTyped) { + m = Columns * Rows; + if (m < 0) /* overflow, Columns or Rows at weird value */ + m = MAXCOL; + } else + m = MAXCOL; + for (i = 0; i < ccline.cmdlen && i < ccline.cmdpos; ++i) { + c = cmdline_charsize(i); + /* Count ">" for double-wide multi-byte char that doesn't fit. */ + if (has_mbyte) + correct_cmdspos(i, c); + /* If the cmdline doesn't fit, show cursor on last visible char. + * Don't move the cursor itself, so we can still append. */ + if ((ccline.cmdspos += c) >= m) { + ccline.cmdspos -= c; + break; + } + if (has_mbyte) + i += (*mb_ptr2len)(ccline.cmdbuff + i) - 1; + } +} + +/* + * Check if the character at "idx", which is "cells" wide, is a multi-byte + * character that doesn't fit, so that a ">" must be displayed. + */ +static void correct_cmdspos(idx, cells) +int idx; +int cells; +{ + if ((*mb_ptr2len)(ccline.cmdbuff + idx) > 1 + && (*mb_ptr2cells)(ccline.cmdbuff + idx) > 1 + && ccline.cmdspos % Columns + cells > Columns) + ccline.cmdspos++; +} + +/* + * Get an Ex command line for the ":" command. + */ +char_u * getexline(c, cookie, indent) +int c; /* normally ':', NUL for ":append" */ +void *cookie UNUSED; +int indent; /* indent for inside conditionals */ +{ + /* When executing a register, remove ':' that's in front of each line. */ + if (exec_from_reg && vpeekc() == ':') + (void)vgetc(); + return getcmdline(c, 1L, indent); +} + +/* + * Get an Ex command line for Ex mode. + * In Ex mode we only use the OS supplied line editing features and no + * mappings or abbreviations. + * Returns a string in allocated memory or NULL. + */ +char_u * getexmodeline(promptc, cookie, indent) +int promptc; /* normally ':', NUL for ":append" and '?' for + :s prompt */ +void *cookie UNUSED; +int indent; /* indent for inside conditionals */ +{ + garray_T line_ga; + char_u *pend; + int startcol = 0; + int c1 = 0; + int escaped = FALSE; /* CTRL-V typed */ + int vcol = 0; + char_u *p; + int prev_char; + + /* Switch cursor on now. This avoids that it happens after the "\n", which + * confuses the system function that computes tabstops. */ + cursor_on(); + + /* always start in column 0; write a newline if necessary */ + compute_cmdrow(); + if ((msg_col || msg_didout) && promptc != '?') + msg_putchar('\n'); + if (promptc == ':') { + /* indent that is only displayed, not in the line itself */ + if (p_prompt) + msg_putchar(':'); + while (indent-- > 0) + msg_putchar(' '); + startcol = msg_col; + } + + ga_init2(&line_ga, 1, 30); + + /* autoindent for :insert and :append is in the line itself */ + if (promptc <= 0) { + vcol = indent; + while (indent >= 8) { + ga_append(&line_ga, TAB); + msg_puts((char_u *)" "); + indent -= 8; + } + while (indent-- > 0) { + ga_append(&line_ga, ' '); + msg_putchar(' '); + } + } + ++no_mapping; + ++allow_keys; + + /* + * Get the line, one character at a time. + */ + got_int = FALSE; + while (!got_int) { + if (ga_grow(&line_ga, 40) == FAIL) + break; + + /* Get one character at a time. Don't use inchar(), it can't handle + * special characters. */ + prev_char = c1; + c1 = vgetc(); + + /* + * Handle line editing. + * Previously this was left to the system, putting the terminal in + * cooked mode, but then CTRL-D and CTRL-T can't be used properly. + */ + if (got_int) { + msg_putchar('\n'); + break; + } + + if (!escaped) { + /* CR typed means "enter", which is NL */ + if (c1 == '\r') + c1 = '\n'; + + if (c1 == BS || c1 == K_BS + || c1 == DEL || c1 == K_DEL || c1 == K_KDEL) { + if (line_ga.ga_len > 0) { + --line_ga.ga_len; + goto redraw; + } + continue; + } + + if (c1 == Ctrl_U) { + msg_col = startcol; + msg_clr_eos(); + line_ga.ga_len = 0; + continue; + } + + if (c1 == Ctrl_T) { + long sw = get_sw_value(curbuf); + + p = (char_u *)line_ga.ga_data; + p[line_ga.ga_len] = NUL; + indent = get_indent_str(p, 8); + indent += sw - indent % sw; +add_indent: + while (get_indent_str(p, 8) < indent) { + char_u *s = skipwhite(p); + + ga_grow(&line_ga, 1); + mch_memmove(s + 1, s, line_ga.ga_len - (s - p) + 1); + *s = ' '; + ++line_ga.ga_len; + } +redraw: + /* redraw the line */ + msg_col = startcol; + vcol = 0; + for (p = (char_u *)line_ga.ga_data; + p < (char_u *)line_ga.ga_data + line_ga.ga_len; ++p) { + if (*p == TAB) { + do { + msg_putchar(' '); + } while (++vcol % 8); + } else { + msg_outtrans_len(p, 1); + vcol += char2cells(*p); + } + } + msg_clr_eos(); + windgoto(msg_row, msg_col); + continue; + } + + if (c1 == Ctrl_D) { + /* Delete one shiftwidth. */ + p = (char_u *)line_ga.ga_data; + if (prev_char == '0' || prev_char == '^') { + if (prev_char == '^') + ex_keep_indent = TRUE; + indent = 0; + p[--line_ga.ga_len] = NUL; + } else { + p[line_ga.ga_len] = NUL; + indent = get_indent_str(p, 8); + --indent; + indent -= indent % get_sw_value(curbuf); + } + while (get_indent_str(p, 8) > indent) { + char_u *s = skipwhite(p); + + mch_memmove(s - 1, s, line_ga.ga_len - (s - p) + 1); + --line_ga.ga_len; + } + goto add_indent; + } + + if (c1 == Ctrl_V || c1 == Ctrl_Q) { + escaped = TRUE; + continue; + } + + /* Ignore special key codes: mouse movement, K_IGNORE, etc. */ + if (IS_SPECIAL(c1)) + continue; + } + + if (IS_SPECIAL(c1)) + c1 = '?'; + ((char_u *)line_ga.ga_data)[line_ga.ga_len] = c1; + if (c1 == '\n') + msg_putchar('\n'); + else if (c1 == TAB) { + /* Don't use chartabsize(), 'ts' can be different */ + do { + msg_putchar(' '); + } while (++vcol % 8); + } else { + msg_outtrans_len( + ((char_u *)line_ga.ga_data) + line_ga.ga_len, 1); + vcol += char2cells(c1); + } + ++line_ga.ga_len; + escaped = FALSE; + + windgoto(msg_row, msg_col); + pend = (char_u *)(line_ga.ga_data) + line_ga.ga_len; + + /* We are done when a NL is entered, but not when it comes after an + * odd number of backslashes, that results in a NUL. */ + if (line_ga.ga_len > 0 && pend[-1] == '\n') { + int bcount = 0; + + while (line_ga.ga_len - 2 >= bcount && pend[-2 - bcount] == '\\') + ++bcount; + + if (bcount > 0) { + /* Halve the number of backslashes: "\NL" -> "NUL", "\\NL" -> + * "\NL", etc. */ + line_ga.ga_len -= (bcount + 1) / 2; + pend -= (bcount + 1) / 2; + pend[-1] = '\n'; + } + + if ((bcount & 1) == 0) { + --line_ga.ga_len; + --pend; + *pend = NUL; + break; + } + } + } + + --no_mapping; + --allow_keys; + + /* make following messages go to the next line */ + msg_didout = FALSE; + msg_col = 0; + if (msg_row < Rows - 1) + ++msg_row; + emsg_on_display = FALSE; /* don't want ui_delay() */ + + if (got_int) + ga_clear(&line_ga); + + return (char_u *)line_ga.ga_data; +} + +# if defined(MCH_CURSOR_SHAPE) || defined(FEAT_GUI) \ + || defined(FEAT_MOUSESHAPE) || defined(PROTO) +/* + * Return TRUE if ccline.overstrike is on. + */ +int cmdline_overstrike() { + return ccline.overstrike; +} + +/* + * Return TRUE if the cursor is at the end of the cmdline. + */ +int cmdline_at_end() { + return ccline.cmdpos >= ccline.cmdlen; +} + +#endif + + + +/* + * Allocate a new command line buffer. + * Assigns the new buffer to ccline.cmdbuff and ccline.cmdbufflen. + * Returns the new value of ccline.cmdbuff and ccline.cmdbufflen. + */ +static void alloc_cmdbuff(len) +int len; +{ + /* + * give some extra space to avoid having to allocate all the time + */ + if (len < 80) + len = 100; + else + len += 20; + + ccline.cmdbuff = alloc(len); /* caller should check for out-of-memory */ + ccline.cmdbufflen = len; +} + +/* + * Re-allocate the command line to length len + something extra. + * return FAIL for failure, OK otherwise + */ +static int realloc_cmdbuff(len) +int len; +{ + char_u *p; + + if (len < ccline.cmdbufflen) + return OK; /* no need to resize */ + + p = ccline.cmdbuff; + alloc_cmdbuff(len); /* will get some more */ + if (ccline.cmdbuff == NULL) { /* out of memory */ + ccline.cmdbuff = p; /* keep the old one */ + return FAIL; + } + /* There isn't always a NUL after the command, but it may need to be + * there, thus copy up to the NUL and add a NUL. */ + mch_memmove(ccline.cmdbuff, p, (size_t)ccline.cmdlen); + ccline.cmdbuff[ccline.cmdlen] = NUL; + vim_free(p); + + if (ccline.xpc != NULL + && ccline.xpc->xp_pattern != NULL + && ccline.xpc->xp_context != EXPAND_NOTHING + && ccline.xpc->xp_context != EXPAND_UNSUCCESSFUL) { + int i = (int)(ccline.xpc->xp_pattern - p); + + /* If xp_pattern points inside the old cmdbuff it needs to be adjusted + * to point into the newly allocated memory. */ + if (i >= 0 && i <= ccline.cmdlen) + ccline.xpc->xp_pattern = ccline.cmdbuff + i; + } + + return OK; +} + +static char_u *arshape_buf = NULL; + +# if defined(EXITFREE) || defined(PROTO) +void free_cmdline_buf() { + vim_free(arshape_buf); +} + +# endif + +/* + * Draw part of the cmdline at the current cursor position. But draw stars + * when cmdline_star is TRUE. + */ +static void draw_cmdline(start, len) +int start; +int len; +{ + int i; + + if (cmdline_star > 0) + for (i = 0; i < len; ++i) { + msg_putchar('*'); + if (has_mbyte) + i += (*mb_ptr2len)(ccline.cmdbuff + start + i) - 1; + } + else if (p_arshape && !p_tbidi && enc_utf8 && len > 0) { + static int buflen = 0; + char_u *p; + int j; + int newlen = 0; + int mb_l; + int pc, pc1 = 0; + int prev_c = 0; + int prev_c1 = 0; + int u8c; + int u8cc[MAX_MCO]; + int nc = 0; + + /* + * Do arabic shaping into a temporary buffer. This is very + * inefficient! + */ + if (len * 2 + 2 > buflen) { + /* Re-allocate the buffer. We keep it around to avoid a lot of + * alloc()/free() calls. */ + vim_free(arshape_buf); + buflen = len * 2 + 2; + arshape_buf = alloc(buflen); + if (arshape_buf == NULL) + return; /* out of memory */ + } + + if (utf_iscomposing(utf_ptr2char(ccline.cmdbuff + start))) { + /* Prepend a space to draw the leading composing char on. */ + arshape_buf[0] = ' '; + newlen = 1; + } + + for (j = start; j < start + len; j += mb_l) { + p = ccline.cmdbuff + j; + u8c = utfc_ptr2char_len(p, u8cc, start + len - j); + mb_l = utfc_ptr2len_len(p, start + len - j); + if (ARABIC_CHAR(u8c)) { + /* Do Arabic shaping. */ + if (cmdmsg_rl) { + /* displaying from right to left */ + pc = prev_c; + pc1 = prev_c1; + prev_c1 = u8cc[0]; + if (j + mb_l >= start + len) + nc = NUL; + else + nc = utf_ptr2char(p + mb_l); + } else { + /* displaying from left to right */ + if (j + mb_l >= start + len) + pc = NUL; + else { + int pcc[MAX_MCO]; + + pc = utfc_ptr2char_len(p + mb_l, pcc, + start + len - j - mb_l); + pc1 = pcc[0]; + } + nc = prev_c; + } + prev_c = u8c; + + u8c = arabic_shape(u8c, NULL, &u8cc[0], pc, pc1, nc); + + newlen += (*mb_char2bytes)(u8c, arshape_buf + newlen); + if (u8cc[0] != 0) { + newlen += (*mb_char2bytes)(u8cc[0], arshape_buf + newlen); + if (u8cc[1] != 0) + newlen += (*mb_char2bytes)(u8cc[1], + arshape_buf + newlen); + } + } else { + prev_c = u8c; + mch_memmove(arshape_buf + newlen, p, mb_l); + newlen += mb_l; + } + } + + msg_outtrans_len(arshape_buf, newlen); + } else + msg_outtrans_len(ccline.cmdbuff + start, len); +} + +/* + * Put a character on the command line. Shifts the following text to the + * right when "shift" is TRUE. Used for CTRL-V, CTRL-K, etc. + * "c" must be printable (fit in one display cell)! + */ +void putcmdline(c, shift) +int c; +int shift; +{ + if (cmd_silent) + return; + msg_no_more = TRUE; + msg_putchar(c); + if (shift) + draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos); + msg_no_more = FALSE; + cursorcmd(); +} + +/* + * Undo a putcmdline(c, FALSE). + */ +void unputcmdline() { + if (cmd_silent) + return; + msg_no_more = TRUE; + if (ccline.cmdlen == ccline.cmdpos) + msg_putchar(' '); + else if (has_mbyte) + draw_cmdline(ccline.cmdpos, + (*mb_ptr2len)(ccline.cmdbuff + ccline.cmdpos)); + else + draw_cmdline(ccline.cmdpos, 1); + msg_no_more = FALSE; + cursorcmd(); +} + +/* + * Put the given string, of the given length, onto the command line. + * If len is -1, then STRLEN() is used to calculate the length. + * If 'redraw' is TRUE then the new part of the command line, and the remaining + * part will be redrawn, otherwise it will not. If this function is called + * twice in a row, then 'redraw' should be FALSE and redrawcmd() should be + * called afterwards. + */ +int put_on_cmdline(str, len, redraw) +char_u *str; +int len; +int redraw; +{ + int retval; + int i; + int m; + int c; + + if (len < 0) + len = (int)STRLEN(str); + + /* Check if ccline.cmdbuff needs to be longer */ + if (ccline.cmdlen + len + 1 >= ccline.cmdbufflen) + retval = realloc_cmdbuff(ccline.cmdlen + len + 1); + else + retval = OK; + if (retval == OK) { + if (!ccline.overstrike) { + mch_memmove(ccline.cmdbuff + ccline.cmdpos + len, + ccline.cmdbuff + ccline.cmdpos, + (size_t)(ccline.cmdlen - ccline.cmdpos)); + ccline.cmdlen += len; + } else { + if (has_mbyte) { + /* Count nr of characters in the new string. */ + m = 0; + for (i = 0; i < len; i += (*mb_ptr2len)(str + i)) + ++m; + /* Count nr of bytes in cmdline that are overwritten by these + * characters. */ + for (i = ccline.cmdpos; i < ccline.cmdlen && m > 0; + i += (*mb_ptr2len)(ccline.cmdbuff + i)) + --m; + if (i < ccline.cmdlen) { + mch_memmove(ccline.cmdbuff + ccline.cmdpos + len, + ccline.cmdbuff + i, (size_t)(ccline.cmdlen - i)); + ccline.cmdlen += ccline.cmdpos + len - i; + } else + ccline.cmdlen = ccline.cmdpos + len; + } else if (ccline.cmdpos + len > ccline.cmdlen) + ccline.cmdlen = ccline.cmdpos + len; + } + mch_memmove(ccline.cmdbuff + ccline.cmdpos, str, (size_t)len); + ccline.cmdbuff[ccline.cmdlen] = NUL; + + if (enc_utf8) { + /* When the inserted text starts with a composing character, + * backup to the character before it. There could be two of them. + */ + i = 0; + c = utf_ptr2char(ccline.cmdbuff + ccline.cmdpos); + while (ccline.cmdpos > 0 && utf_iscomposing(c)) { + i = (*mb_head_off)(ccline.cmdbuff, + ccline.cmdbuff + ccline.cmdpos - 1) + 1; + ccline.cmdpos -= i; + len += i; + c = utf_ptr2char(ccline.cmdbuff + ccline.cmdpos); + } + if (i == 0 && ccline.cmdpos > 0 && arabic_maycombine(c)) { + /* Check the previous character for Arabic combining pair. */ + i = (*mb_head_off)(ccline.cmdbuff, + ccline.cmdbuff + ccline.cmdpos - 1) + 1; + if (arabic_combine(utf_ptr2char(ccline.cmdbuff + + ccline.cmdpos - i), c)) { + ccline.cmdpos -= i; + len += i; + } else + i = 0; + } + if (i != 0) { + /* Also backup the cursor position. */ + i = ptr2cells(ccline.cmdbuff + ccline.cmdpos); + ccline.cmdspos -= i; + msg_col -= i; + if (msg_col < 0) { + msg_col += Columns; + --msg_row; + } + } + } + + if (redraw && !cmd_silent) { + msg_no_more = TRUE; + i = cmdline_row; + cursorcmd(); + draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos); + /* Avoid clearing the rest of the line too often. */ + if (cmdline_row != i || ccline.overstrike) + msg_clr_eos(); + msg_no_more = FALSE; + } + /* + * If we are in Farsi command mode, the character input must be in + * Insert mode. So do not advance the cmdpos. + */ + if (!cmd_fkmap) { + if (KeyTyped) { + m = Columns * Rows; + if (m < 0) /* overflow, Columns or Rows at weird value */ + m = MAXCOL; + } else + m = MAXCOL; + for (i = 0; i < len; ++i) { + c = cmdline_charsize(ccline.cmdpos); + /* count ">" for a double-wide char that doesn't fit. */ + if (has_mbyte) + correct_cmdspos(ccline.cmdpos, c); + /* Stop cursor at the end of the screen, but do increment the + * insert position, so that entering a very long command + * works, even though you can't see it. */ + if (ccline.cmdspos + c < m) + ccline.cmdspos += c; + if (has_mbyte) { + c = (*mb_ptr2len)(ccline.cmdbuff + ccline.cmdpos) - 1; + if (c > len - i - 1) + c = len - i - 1; + ccline.cmdpos += c; + i += c; + } + ++ccline.cmdpos; + } + } + } + if (redraw) + msg_check(); + return retval; +} + +static struct cmdline_info prev_ccline; +static int prev_ccline_used = FALSE; + +/* + * Save ccline, because obtaining the "=" register may execute "normal :cmd" + * and overwrite it. But get_cmdline_str() may need it, thus make it + * available globally in prev_ccline. + */ +static void save_cmdline(ccp) +struct cmdline_info *ccp; +{ + if (!prev_ccline_used) { + vim_memset(&prev_ccline, 0, sizeof(struct cmdline_info)); + prev_ccline_used = TRUE; + } + *ccp = prev_ccline; + prev_ccline = ccline; + ccline.cmdbuff = NULL; + ccline.cmdprompt = NULL; + ccline.xpc = NULL; +} + +/* + * Restore ccline after it has been saved with save_cmdline(). + */ +static void restore_cmdline(ccp) +struct cmdline_info *ccp; +{ + ccline = prev_ccline; + prev_ccline = *ccp; +} + +/* + * Save the command line into allocated memory. Returns a pointer to be + * passed to restore_cmdline_alloc() later. + * Returns NULL when failed. + */ +char_u * save_cmdline_alloc() { + struct cmdline_info *p; + + p = (struct cmdline_info *)alloc((unsigned)sizeof(struct cmdline_info)); + if (p != NULL) + save_cmdline(p); + return (char_u *)p; +} + +/* + * Restore the command line from the return value of save_cmdline_alloc(). + */ +void restore_cmdline_alloc(p) +char_u *p; +{ + if (p != NULL) { + restore_cmdline((struct cmdline_info *)p); + vim_free(p); + } +} + +/* + * paste a yank register into the command line. + * used by CTRL-R command in command-line mode + * insert_reg() can't be used here, because special characters from the + * register contents will be interpreted as commands. + * + * return FAIL for failure, OK otherwise + */ +static int cmdline_paste(regname, literally, remcr) +int regname; +int literally; /* Insert text literally instead of "as typed" */ +int remcr; /* remove trailing CR */ +{ + long i; + char_u *arg; + char_u *p; + int allocated; + struct cmdline_info save_ccline; + + /* check for valid regname; also accept special characters for CTRL-R in + * the command line */ + if (regname != Ctrl_F && regname != Ctrl_P && regname != Ctrl_W + && regname != Ctrl_A && !valid_yank_reg(regname, FALSE)) + return FAIL; + + /* A register containing CTRL-R can cause an endless loop. Allow using + * CTRL-C to break the loop. */ + line_breakcheck(); + if (got_int) + return FAIL; + + + /* Need to save and restore ccline. And set "textlock" to avoid nasty + * things like going to another buffer when evaluating an expression. */ + save_cmdline(&save_ccline); + ++textlock; + i = get_spec_reg(regname, &arg, &allocated, TRUE); + --textlock; + restore_cmdline(&save_ccline); + + if (i) { + /* Got the value of a special register in "arg". */ + if (arg == NULL) + return FAIL; + + /* When 'incsearch' is set and CTRL-R CTRL-W used: skip the duplicate + * part of the word. */ + p = arg; + if (p_is && regname == Ctrl_W) { + char_u *w; + int len; + + /* Locate start of last word in the cmd buffer. */ + for (w = ccline.cmdbuff + ccline.cmdpos; w > ccline.cmdbuff; ) { + if (has_mbyte) { + len = (*mb_head_off)(ccline.cmdbuff, w - 1) + 1; + if (!vim_iswordc(mb_ptr2char(w - len))) + break; + w -= len; + } else { + if (!vim_iswordc(w[-1])) + break; + --w; + } + } + len = (int)((ccline.cmdbuff + ccline.cmdpos) - w); + if (p_ic ? STRNICMP(w, arg, len) == 0 : STRNCMP(w, arg, len) == 0) + p += len; + } + + cmdline_paste_str(p, literally); + if (allocated) + vim_free(arg); + return OK; + } + + return cmdline_paste_reg(regname, literally, remcr); +} + +/* + * Put a string on the command line. + * When "literally" is TRUE, insert literally. + * When "literally" is FALSE, insert as typed, but don't leave the command + * line. + */ +void cmdline_paste_str(s, literally) +char_u *s; +int literally; +{ + int c, cv; + + if (literally) + put_on_cmdline(s, -1, TRUE); + else + while (*s != NUL) { + cv = *s; + if (cv == Ctrl_V && s[1]) + ++s; + if (has_mbyte) + c = mb_cptr2char_adv(&s); + else + c = *s++; + if (cv == Ctrl_V || c == ESC || c == Ctrl_C + || c == CAR || c == NL || c == Ctrl_L +#ifdef UNIX + || c == intr_char +#endif + || (c == Ctrl_BSL && *s == Ctrl_N)) + stuffcharReadbuff(Ctrl_V); + stuffcharReadbuff(c); + } +} + +/* + * Delete characters on the command line, from "from" to the current + * position. + */ +static void cmdline_del(from) +int from; +{ + mch_memmove(ccline.cmdbuff + from, ccline.cmdbuff + ccline.cmdpos, + (size_t)(ccline.cmdlen - ccline.cmdpos + 1)); + ccline.cmdlen -= ccline.cmdpos - from; + ccline.cmdpos = from; +} + +/* + * this function is called when the screen size changes and with incremental + * search + */ +void redrawcmdline() { + if (cmd_silent) + return; + need_wait_return = FALSE; + compute_cmdrow(); + redrawcmd(); + cursorcmd(); +} + +static void redrawcmdprompt() { + int i; + + if (cmd_silent) + return; + if (ccline.cmdfirstc != NUL) + msg_putchar(ccline.cmdfirstc); + if (ccline.cmdprompt != NULL) { + msg_puts_attr(ccline.cmdprompt, ccline.cmdattr); + ccline.cmdindent = msg_col + (msg_row - cmdline_row) * Columns; + /* do the reverse of set_cmdspos() */ + if (ccline.cmdfirstc != NUL) + --ccline.cmdindent; + } else + for (i = ccline.cmdindent; i > 0; --i) + msg_putchar(' '); +} + +/* + * Redraw what is currently on the command line. + */ +void redrawcmd() { + if (cmd_silent) + return; + + /* when 'incsearch' is set there may be no command line while redrawing */ + if (ccline.cmdbuff == NULL) { + windgoto(cmdline_row, 0); + msg_clr_eos(); + return; + } + + msg_start(); + redrawcmdprompt(); + + /* Don't use more prompt, truncate the cmdline if it doesn't fit. */ + msg_no_more = TRUE; + draw_cmdline(0, ccline.cmdlen); + msg_clr_eos(); + msg_no_more = FALSE; + + set_cmdspos_cursor(); + + /* + * An emsg() before may have set msg_scroll. This is used in normal mode, + * in cmdline mode we can reset them now. + */ + msg_scroll = FALSE; /* next message overwrites cmdline */ + + /* Typing ':' at the more prompt may set skip_redraw. We don't want this + * in cmdline mode */ + skip_redraw = FALSE; +} + +void compute_cmdrow() { + if (exmode_active || msg_scrolled != 0) + cmdline_row = Rows - 1; + else + cmdline_row = W_WINROW(lastwin) + lastwin->w_height + + W_STATUS_HEIGHT(lastwin); +} + +static void cursorcmd() { + if (cmd_silent) + return; + + if (cmdmsg_rl) { + msg_row = cmdline_row + (ccline.cmdspos / (int)(Columns - 1)); + msg_col = (int)Columns - (ccline.cmdspos % (int)(Columns - 1)) - 1; + if (msg_row <= 0) + msg_row = Rows - 1; + } else { + msg_row = cmdline_row + (ccline.cmdspos / (int)Columns); + msg_col = ccline.cmdspos % (int)Columns; + if (msg_row >= Rows) + msg_row = Rows - 1; + } + + windgoto(msg_row, msg_col); +} + +void gotocmdline(clr) +int clr; +{ + msg_start(); + if (cmdmsg_rl) + msg_col = Columns - 1; + else + msg_col = 0; /* always start in column 0 */ + if (clr) /* clear the bottom line(s) */ + msg_clr_eos(); /* will reset clear_cmdline */ + windgoto(cmdline_row, 0); +} + +/* + * Check the word in front of the cursor for an abbreviation. + * Called when the non-id character "c" has been entered. + * When an abbreviation is recognized it is removed from the text with + * backspaces and the replacement string is inserted, followed by "c". + */ +static int ccheck_abbr(c) +int c; +{ + if (p_paste || no_abbr) /* no abbreviations or in paste mode */ + return FALSE; + + return check_abbr(c, ccline.cmdbuff, ccline.cmdpos, 0); +} + +static int sort_func_compare(s1, s2) +const void *s1; +const void *s2; +{ + char_u *p1 = *(char_u **)s1; + char_u *p2 = *(char_u **)s2; + + if (*p1 != '<' && *p2 == '<') return -1; + if (*p1 == '<' && *p2 != '<') return 1; + return STRCMP(p1, p2); +} + +/* + * Return FAIL if this is not an appropriate context in which to do + * completion of anything, return OK if it is (even if there are no matches). + * For the caller, this means that the character is just passed through like a + * normal character (instead of being expanded). This allows :s/^I^D etc. + */ +static int nextwild(xp, type, options, escape) +expand_T *xp; +int type; +int options; /* extra options for ExpandOne() */ +int escape; /* if TRUE, escape the returned matches */ +{ + int i, j; + char_u *p1; + char_u *p2; + int difflen; + int v; + + if (xp->xp_numfiles == -1) { + set_expand_context(xp); + cmd_showtail = expand_showtail(xp); + } + + if (xp->xp_context == EXPAND_UNSUCCESSFUL) { + beep_flush(); + return OK; /* Something illegal on command line */ + } + if (xp->xp_context == EXPAND_NOTHING) { + /* Caller can use the character as a normal char instead */ + return FAIL; + } + + MSG_PUTS("..."); /* show that we are busy */ + out_flush(); + + i = (int)(xp->xp_pattern - ccline.cmdbuff); + xp->xp_pattern_len = ccline.cmdpos - i; + + if (type == WILD_NEXT || type == WILD_PREV) { + /* + * Get next/previous match for a previous expanded pattern. + */ + p2 = ExpandOne(xp, NULL, NULL, 0, type); + } else { + /* + * Translate string into pattern and expand it. + */ + if ((p1 = addstar(xp->xp_pattern, xp->xp_pattern_len, + xp->xp_context)) == NULL) + p2 = NULL; + else { + int use_options = options | + WILD_HOME_REPLACE|WILD_ADD_SLASH|WILD_SILENT; + if (escape) + use_options |= WILD_ESCAPE; + + if (p_wic) + use_options += WILD_ICASE; + p2 = ExpandOne(xp, p1, + vim_strnsave(&ccline.cmdbuff[i], xp->xp_pattern_len), + use_options, type); + vim_free(p1); + /* longest match: make sure it is not shorter, happens with :help */ + if (p2 != NULL && type == WILD_LONGEST) { + for (j = 0; j < xp->xp_pattern_len; ++j) + if (ccline.cmdbuff[i + j] == '*' + || ccline.cmdbuff[i + j] == '?') + break; + if ((int)STRLEN(p2) < j) { + vim_free(p2); + p2 = NULL; + } + } + } + } + + if (p2 != NULL && !got_int) { + difflen = (int)STRLEN(p2) - xp->xp_pattern_len; + if (ccline.cmdlen + difflen + 4 > ccline.cmdbufflen) { + v = realloc_cmdbuff(ccline.cmdlen + difflen + 4); + xp->xp_pattern = ccline.cmdbuff + i; + } else + v = OK; + if (v == OK) { + mch_memmove(&ccline.cmdbuff[ccline.cmdpos + difflen], + &ccline.cmdbuff[ccline.cmdpos], + (size_t)(ccline.cmdlen - ccline.cmdpos + 1)); + mch_memmove(&ccline.cmdbuff[i], p2, STRLEN(p2)); + ccline.cmdlen += difflen; + ccline.cmdpos += difflen; + } + } + vim_free(p2); + + redrawcmd(); + cursorcmd(); + + /* When expanding a ":map" command and no matches are found, assume that + * the key is supposed to be inserted literally */ + if (xp->xp_context == EXPAND_MAPPINGS && p2 == NULL) + return FAIL; + + if (xp->xp_numfiles <= 0 && p2 == NULL) + beep_flush(); + else if (xp->xp_numfiles == 1) + /* free expanded pattern */ + (void)ExpandOne(xp, NULL, NULL, 0, WILD_FREE); + + return OK; +} + +/* + * Do wildcard expansion on the string 'str'. + * Chars that should not be expanded must be preceded with a backslash. + * Return a pointer to allocated memory containing the new string. + * Return NULL for failure. + * + * "orig" is the originally expanded string, copied to allocated memory. It + * should either be kept in orig_save or freed. When "mode" is WILD_NEXT or + * WILD_PREV "orig" should be NULL. + * + * Results are cached in xp->xp_files and xp->xp_numfiles, except when "mode" + * is WILD_EXPAND_FREE or WILD_ALL. + * + * mode = WILD_FREE: just free previously expanded matches + * mode = WILD_EXPAND_FREE: normal expansion, do not keep matches + * mode = WILD_EXPAND_KEEP: normal expansion, keep matches + * mode = WILD_NEXT: use next match in multiple match, wrap to first + * mode = WILD_PREV: use previous match in multiple match, wrap to first + * mode = WILD_ALL: return all matches concatenated + * mode = WILD_LONGEST: return longest matched part + * mode = WILD_ALL_KEEP: get all matches, keep matches + * + * options = WILD_LIST_NOTFOUND: list entries without a match + * options = WILD_HOME_REPLACE: do home_replace() for buffer names + * options = WILD_USE_NL: Use '\n' for WILD_ALL + * options = WILD_NO_BEEP: Don't beep for multiple matches + * options = WILD_ADD_SLASH: add a slash after directory names + * options = WILD_KEEP_ALL: don't remove 'wildignore' entries + * options = WILD_SILENT: don't print warning messages + * options = WILD_ESCAPE: put backslash before special chars + * options = WILD_ICASE: ignore case for files + * + * The variables xp->xp_context and xp->xp_backslash must have been set! + */ +char_u * ExpandOne(xp, str, orig, options, mode) +expand_T *xp; +char_u *str; +char_u *orig; /* allocated copy of original of expanded string */ +int options; +int mode; +{ + char_u *ss = NULL; + static int findex; + static char_u *orig_save = NULL; /* kept value of orig */ + int orig_saved = FALSE; + int i; + long_u len; + int non_suf_match; /* number without matching suffix */ + + /* + * first handle the case of using an old match + */ + if (mode == WILD_NEXT || mode == WILD_PREV) { + if (xp->xp_numfiles > 0) { + if (mode == WILD_PREV) { + if (findex == -1) + findex = xp->xp_numfiles; + --findex; + } else /* mode == WILD_NEXT */ + ++findex; + + /* + * When wrapping around, return the original string, set findex to + * -1. + */ + if (findex < 0) { + if (orig_save == NULL) + findex = xp->xp_numfiles - 1; + else + findex = -1; + } + if (findex >= xp->xp_numfiles) { + if (orig_save == NULL) + findex = 0; + else + findex = -1; + } + if (p_wmnu) + win_redr_status_matches(xp, xp->xp_numfiles, xp->xp_files, + findex, cmd_showtail); + if (findex == -1) + return vim_strsave(orig_save); + return vim_strsave(xp->xp_files[findex]); + } else + return NULL; + } + + /* free old names */ + if (xp->xp_numfiles != -1 && mode != WILD_ALL && mode != WILD_LONGEST) { + FreeWild(xp->xp_numfiles, xp->xp_files); + xp->xp_numfiles = -1; + vim_free(orig_save); + orig_save = NULL; + } + findex = 0; + + if (mode == WILD_FREE) /* only release file name */ + return NULL; + + if (xp->xp_numfiles == -1) { + vim_free(orig_save); + orig_save = orig; + orig_saved = TRUE; + + /* + * Do the expansion. + */ + if (ExpandFromContext(xp, str, &xp->xp_numfiles, &xp->xp_files, + options) == FAIL) { +#ifdef FNAME_ILLEGAL + /* Illegal file name has been silently skipped. But when there + * are wildcards, the real problem is that there was no match, + * causing the pattern to be added, which has illegal characters. + */ + if (!(options & WILD_SILENT) && (options & WILD_LIST_NOTFOUND)) + EMSG2(_(e_nomatch2), str); +#endif + } else if (xp->xp_numfiles == 0) { + if (!(options & WILD_SILENT)) + EMSG2(_(e_nomatch2), str); + } else { + /* Escape the matches for use on the command line. */ + ExpandEscape(xp, str, xp->xp_numfiles, xp->xp_files, options); + + /* + * Check for matching suffixes in file names. + */ + if (mode != WILD_ALL && mode != WILD_ALL_KEEP + && mode != WILD_LONGEST) { + if (xp->xp_numfiles) + non_suf_match = xp->xp_numfiles; + else + non_suf_match = 1; + if ((xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES) + && xp->xp_numfiles > 1) { + /* + * More than one match; check suffix. + * The files will have been sorted on matching suffix in + * expand_wildcards, only need to check the first two. + */ + non_suf_match = 0; + for (i = 0; i < 2; ++i) + if (match_suffix(xp->xp_files[i])) + ++non_suf_match; + } + if (non_suf_match != 1) { + /* Can we ever get here unless it's while expanding + * interactively? If not, we can get rid of this all + * together. Don't really want to wait for this message + * (and possibly have to hit return to continue!). + */ + if (!(options & WILD_SILENT)) + EMSG(_(e_toomany)); + else if (!(options & WILD_NO_BEEP)) + beep_flush(); + } + if (!(non_suf_match != 1 && mode == WILD_EXPAND_FREE)) + ss = vim_strsave(xp->xp_files[0]); + } + } + } + + /* Find longest common part */ + if (mode == WILD_LONGEST && xp->xp_numfiles > 0) { + for (len = 0; xp->xp_files[0][len]; ++len) { + for (i = 0; i < xp->xp_numfiles; ++i) { + if (p_fic && (xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS)) { + if (TOLOWER_LOC(xp->xp_files[i][len]) != + TOLOWER_LOC(xp->xp_files[0][len])) + break; + } else if (xp->xp_files[i][len] != xp->xp_files[0][len]) + break; + } + if (i < xp->xp_numfiles) { + if (!(options & WILD_NO_BEEP)) + vim_beep(); + break; + } + } + ss = alloc((unsigned)len + 1); + if (ss) + vim_strncpy(ss, xp->xp_files[0], (size_t)len); + findex = -1; /* next p_wc gets first one */ + } + + /* Concatenate all matching names */ + if (mode == WILD_ALL && xp->xp_numfiles > 0) { + len = 0; + for (i = 0; i < xp->xp_numfiles; ++i) + len += (long_u)STRLEN(xp->xp_files[i]) + 1; + ss = lalloc(len, TRUE); + if (ss != NULL) { + *ss = NUL; + for (i = 0; i < xp->xp_numfiles; ++i) { + STRCAT(ss, xp->xp_files[i]); + if (i != xp->xp_numfiles - 1) + STRCAT(ss, (options & WILD_USE_NL) ? "\n" : " "); + } + } + } + + if (mode == WILD_EXPAND_FREE || mode == WILD_ALL) + ExpandCleanup(xp); + + /* Free "orig" if it wasn't stored in "orig_save". */ + if (!orig_saved) + vim_free(orig); + + return ss; +} + +/* + * Prepare an expand structure for use. + */ +void ExpandInit(xp) +expand_T *xp; +{ + xp->xp_pattern = NULL; + xp->xp_pattern_len = 0; + xp->xp_backslash = XP_BS_NONE; +#ifndef BACKSLASH_IN_FILENAME + xp->xp_shell = FALSE; +#endif + xp->xp_numfiles = -1; + xp->xp_files = NULL; + xp->xp_arg = NULL; + xp->xp_line = NULL; +} + +/* + * Cleanup an expand structure after use. + */ +void ExpandCleanup(xp) +expand_T *xp; +{ + if (xp->xp_numfiles >= 0) { + FreeWild(xp->xp_numfiles, xp->xp_files); + xp->xp_numfiles = -1; + } +} + +void ExpandEscape(xp, str, numfiles, files, options) +expand_T *xp; +char_u *str; +int numfiles; +char_u **files; +int options; +{ + int i; + char_u *p; + + /* + * May change home directory back to "~" + */ + if (options & WILD_HOME_REPLACE) + tilde_replace(str, numfiles, files); + + if (options & WILD_ESCAPE) { + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_FILES_IN_PATH + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS + || xp->xp_context == EXPAND_DIRECTORIES) { + /* + * Insert a backslash into a file name before a space, \, %, # + * and wildmatch characters, except '~'. + */ + for (i = 0; i < numfiles; ++i) { + /* for ":set path=" we need to escape spaces twice */ + if (xp->xp_backslash == XP_BS_THREE) { + p = vim_strsave_escaped(files[i], (char_u *)" "); + if (p != NULL) { + vim_free(files[i]); + files[i] = p; +#if defined(BACKSLASH_IN_FILENAME) + p = vim_strsave_escaped(files[i], (char_u *)" "); + if (p != NULL) { + vim_free(files[i]); + files[i] = p; + } +#endif + } + } +#ifdef BACKSLASH_IN_FILENAME + p = vim_strsave_fnameescape(files[i], FALSE); +#else + p = vim_strsave_fnameescape(files[i], xp->xp_shell); +#endif + if (p != NULL) { + vim_free(files[i]); + files[i] = p; + } + + /* If 'str' starts with "\~", replace "~" at start of + * files[i] with "\~". */ + if (str[0] == '\\' && str[1] == '~' && files[i][0] == '~') + escape_fname(&files[i]); + } + xp->xp_backslash = XP_BS_NONE; + + /* If the first file starts with a '+' escape it. Otherwise it + * could be seen as "+cmd". */ + if (*files[0] == '+') + escape_fname(&files[0]); + } else if (xp->xp_context == EXPAND_TAGS) { + /* + * Insert a backslash before characters in a tag name that + * would terminate the ":tag" command. + */ + for (i = 0; i < numfiles; ++i) { + p = vim_strsave_escaped(files[i], (char_u *)"\\|\""); + if (p != NULL) { + vim_free(files[i]); + files[i] = p; + } + } + } + } +} + +/* + * Escape special characters in "fname" for when used as a file name argument + * after a Vim command, or, when "shell" is non-zero, a shell command. + * Returns the result in allocated memory. + */ +char_u * vim_strsave_fnameescape(fname, shell) +char_u *fname; +int shell; +{ + char_u *p; +#ifdef BACKSLASH_IN_FILENAME + char_u buf[20]; + int j = 0; + + /* Don't escape '[', '{' and '!' if they are in 'isfname'. */ + for (p = PATH_ESC_CHARS; *p != NUL; ++p) + if ((*p != '[' && *p != '{' && *p != '!') || !vim_isfilec(*p)) + buf[j++] = *p; + buf[j] = NUL; + p = vim_strsave_escaped(fname, buf); +#else + p = vim_strsave_escaped(fname, shell ? SHELL_ESC_CHARS : PATH_ESC_CHARS); + if (shell && csh_like_shell() && p != NULL) { + char_u *s; + + /* For csh and similar shells need to put two backslashes before '!'. + * One is taken by Vim, one by the shell. */ + s = vim_strsave_escaped(p, (char_u *)"!"); + vim_free(p); + p = s; + } +#endif + + /* '>' and '+' are special at the start of some commands, e.g. ":edit" and + * ":write". "cd -" has a special meaning. */ + if (p != NULL && (*p == '>' || *p == '+' || (*p == '-' && p[1] == NUL))) + escape_fname(&p); + + return p; +} + +/* + * Put a backslash before the file name in "pp", which is in allocated memory. + */ +static void escape_fname(pp) +char_u **pp; +{ + char_u *p; + + p = alloc((unsigned)(STRLEN(*pp) + 2)); + if (p != NULL) { + p[0] = '\\'; + STRCPY(p + 1, *pp); + vim_free(*pp); + *pp = p; + } +} + +/* + * For each file name in files[num_files]: + * If 'orig_pat' starts with "~/", replace the home directory with "~". + */ +void tilde_replace(orig_pat, num_files, files) +char_u *orig_pat; +int num_files; +char_u **files; +{ + int i; + char_u *p; + + if (orig_pat[0] == '~' && vim_ispathsep(orig_pat[1])) { + for (i = 0; i < num_files; ++i) { + p = home_replace_save(NULL, files[i]); + if (p != NULL) { + vim_free(files[i]); + files[i] = p; + } + } + } +} + +/* + * Show all matches for completion on the command line. + * Returns EXPAND_NOTHING when the character that triggered expansion should + * be inserted like a normal character. + */ +static int showmatches(xp, wildmenu) +expand_T *xp; +int wildmenu UNUSED; +{ +#define L_SHOWFILE(m) (showtail ? sm_gettail(files_found[m]) : files_found[m]) + int num_files; + char_u **files_found; + int i, j, k; + int maxlen; + int lines; + int columns; + char_u *p; + int lastlen; + int attr; + int showtail; + + if (xp->xp_numfiles == -1) { + set_expand_context(xp); + i = expand_cmdline(xp, ccline.cmdbuff, ccline.cmdpos, + &num_files, &files_found); + showtail = expand_showtail(xp); + if (i != EXPAND_OK) + return i; + + } else { + num_files = xp->xp_numfiles; + files_found = xp->xp_files; + showtail = cmd_showtail; + } + + if (!wildmenu) { + msg_didany = FALSE; /* lines_left will be set */ + msg_start(); /* prepare for paging */ + msg_putchar('\n'); + out_flush(); + cmdline_row = msg_row; + msg_didany = FALSE; /* lines_left will be set again */ + msg_start(); /* prepare for paging */ + } + + if (got_int) + got_int = FALSE; /* only int. the completion, not the cmd line */ + else if (wildmenu) + win_redr_status_matches(xp, num_files, files_found, 0, showtail); + else { + /* find the length of the longest file name */ + maxlen = 0; + for (i = 0; i < num_files; ++i) { + if (!showtail && (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS)) { + home_replace(NULL, files_found[i], NameBuff, MAXPATHL, TRUE); + j = vim_strsize(NameBuff); + } else + j = vim_strsize(L_SHOWFILE(i)); + if (j > maxlen) + maxlen = j; + } + + if (xp->xp_context == EXPAND_TAGS_LISTFILES) + lines = num_files; + else { + /* compute the number of columns and lines for the listing */ + maxlen += 2; /* two spaces between file names */ + columns = ((int)Columns + 2) / maxlen; + if (columns < 1) + columns = 1; + lines = (num_files + columns - 1) / columns; + } + + attr = hl_attr(HLF_D); /* find out highlighting for directories */ + + if (xp->xp_context == EXPAND_TAGS_LISTFILES) { + MSG_PUTS_ATTR(_("tagname"), hl_attr(HLF_T)); + msg_clr_eos(); + msg_advance(maxlen - 3); + MSG_PUTS_ATTR(_(" kind file\n"), hl_attr(HLF_T)); + } + + /* list the files line by line */ + for (i = 0; i < lines; ++i) { + lastlen = 999; + for (k = i; k < num_files; k += lines) { + if (xp->xp_context == EXPAND_TAGS_LISTFILES) { + msg_outtrans_attr(files_found[k], hl_attr(HLF_D)); + p = files_found[k] + STRLEN(files_found[k]) + 1; + msg_advance(maxlen + 1); + msg_puts(p); + msg_advance(maxlen + 3); + msg_puts_long_attr(p + 2, hl_attr(HLF_D)); + break; + } + for (j = maxlen - lastlen; --j >= 0; ) + msg_putchar(' '); + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_SHELLCMD + || xp->xp_context == EXPAND_BUFFERS) { + /* highlight directories */ + if (xp->xp_numfiles != -1) { + char_u *halved_slash; + char_u *exp_path; + + /* Expansion was done before and special characters + * were escaped, need to halve backslashes. Also + * $HOME has been replaced with ~/. */ + exp_path = expand_env_save_opt(files_found[k], TRUE); + halved_slash = backslash_halve_save( + exp_path != NULL ? exp_path : files_found[k]); + j = mch_isdir(halved_slash != NULL ? halved_slash + : files_found[k]); + vim_free(exp_path); + vim_free(halved_slash); + } else + /* Expansion was done here, file names are literal. */ + j = mch_isdir(files_found[k]); + if (showtail) + p = L_SHOWFILE(k); + else { + home_replace(NULL, files_found[k], NameBuff, MAXPATHL, + TRUE); + p = NameBuff; + } + } else { + j = FALSE; + p = L_SHOWFILE(k); + } + lastlen = msg_outtrans_attr(p, j ? attr : 0); + } + if (msg_col > 0) { /* when not wrapped around */ + msg_clr_eos(); + msg_putchar('\n'); + } + out_flush(); /* show one line at a time */ + if (got_int) { + got_int = FALSE; + break; + } + } + + /* + * we redraw the command below the lines that we have just listed + * This is a bit tricky, but it saves a lot of screen updating. + */ + cmdline_row = msg_row; /* will put it back later */ + } + + if (xp->xp_numfiles == -1) + FreeWild(num_files, files_found); + + return EXPAND_OK; +} + +/* + * Private gettail for showmatches() (and win_redr_status_matches()): + * Find tail of file name path, but ignore trailing "/". + */ +char_u * sm_gettail(s) +char_u *s; +{ + char_u *p; + char_u *t = s; + int had_sep = FALSE; + + for (p = s; *p != NUL; ) { + if (vim_ispathsep(*p) +#ifdef BACKSLASH_IN_FILENAME + && !rem_backslash(p) +#endif + ) + had_sep = TRUE; + else if (had_sep) { + t = p; + had_sep = FALSE; + } + mb_ptr_adv(p); + } + return t; +} + +/* + * Return TRUE if we only need to show the tail of completion matches. + * When not completing file names or there is a wildcard in the path FALSE is + * returned. + */ +static int expand_showtail(xp) +expand_T *xp; +{ + char_u *s; + char_u *end; + + /* When not completing file names a "/" may mean something different. */ + if (xp->xp_context != EXPAND_FILES + && xp->xp_context != EXPAND_SHELLCMD + && xp->xp_context != EXPAND_DIRECTORIES) + return FALSE; + + end = gettail(xp->xp_pattern); + if (end == xp->xp_pattern) /* there is no path separator */ + return FALSE; + + for (s = xp->xp_pattern; s < end; s++) { + /* Skip escaped wildcards. Only when the backslash is not a path + * separator, on DOS the '*' "path\*\file" must not be skipped. */ + if (rem_backslash(s)) + ++s; + else if (vim_strchr((char_u *)"*?[", *s) != NULL) + return FALSE; + } + return TRUE; +} + +/* + * Prepare a string for expansion. + * When expanding file names: The string will be used with expand_wildcards(). + * Copy "fname[len]" into allocated memory and add a '*' at the end. + * When expanding other names: The string will be used with regcomp(). Copy + * the name into allocated memory and prepend "^". + */ +char_u * addstar(fname, len, context) +char_u *fname; +int len; +int context; /* EXPAND_FILES etc. */ +{ + char_u *retval; + int i, j; + int new_len; + char_u *tail; + int ends_in_star; + + if (context != EXPAND_FILES + && context != EXPAND_FILES_IN_PATH + && context != EXPAND_SHELLCMD + && context != EXPAND_DIRECTORIES) { + /* + * Matching will be done internally (on something other than files). + * So we convert the file-matching-type wildcards into our kind for + * use with vim_regcomp(). First work out how long it will be: + */ + + /* For help tags the translation is done in find_help_tags(). + * For a tag pattern starting with "/" no translation is needed. */ + if (context == EXPAND_HELP + || context == EXPAND_COLORS + || context == EXPAND_COMPILER + || context == EXPAND_OWNSYNTAX + || context == EXPAND_FILETYPE + || (context == EXPAND_TAGS && fname[0] == '/')) + retval = vim_strnsave(fname, len); + else { + new_len = len + 2; /* +2 for '^' at start, NUL at end */ + for (i = 0; i < len; i++) { + if (fname[i] == '*' || fname[i] == '~') + new_len++; /* '*' needs to be replaced by ".*" + '~' needs to be replaced by "\~" */ + + /* Buffer names are like file names. "." should be literal */ + if (context == EXPAND_BUFFERS && fname[i] == '.') + new_len++; /* "." becomes "\." */ + + /* Custom expansion takes care of special things, match + * backslashes literally (perhaps also for other types?) */ + if ((context == EXPAND_USER_DEFINED + || context == EXPAND_USER_LIST) && fname[i] == '\\') + new_len++; /* '\' becomes "\\" */ + } + retval = alloc(new_len); + if (retval != NULL) { + retval[0] = '^'; + j = 1; + for (i = 0; i < len; i++, j++) { + /* Skip backslash. But why? At least keep it for custom + * expansion. */ + if (context != EXPAND_USER_DEFINED + && context != EXPAND_USER_LIST + && fname[i] == '\\' + && ++i == len) + break; + + switch (fname[i]) { + case '*': retval[j++] = '.'; + break; + case '~': retval[j++] = '\\'; + break; + case '?': retval[j] = '.'; + continue; + case '.': if (context == EXPAND_BUFFERS) + retval[j++] = '\\'; + break; + case '\\': if (context == EXPAND_USER_DEFINED + || context == EXPAND_USER_LIST) + retval[j++] = '\\'; + break; + } + retval[j] = fname[i]; + } + retval[j] = NUL; + } + } + } else { + retval = alloc(len + 4); + if (retval != NULL) { + vim_strncpy(retval, fname, len); + + /* + * Don't add a star to *, ~, ~user, $var or `cmd`. + * * would become **, which walks the whole tree. + * ~ would be at the start of the file name, but not the tail. + * $ could be anywhere in the tail. + * ` could be anywhere in the file name. + * When the name ends in '$' don't add a star, remove the '$'. + */ + tail = gettail(retval); + ends_in_star = (len > 0 && retval[len - 1] == '*'); +#ifndef BACKSLASH_IN_FILENAME + for (i = len - 2; i >= 0; --i) { + if (retval[i] != '\\') + break; + ends_in_star = !ends_in_star; + } +#endif + if ((*retval != '~' || tail != retval) + && !ends_in_star + && vim_strchr(tail, '$') == NULL + && vim_strchr(retval, '`') == NULL) + retval[len++] = '*'; + else if (len > 0 && retval[len - 1] == '$') + --len; + retval[len] = NUL; + } + } + return retval; +} + +/* + * Must parse the command line so far to work out what context we are in. + * Completion can then be done based on that context. + * This routine sets the variables: + * xp->xp_pattern The start of the pattern to be expanded within + * the command line (ends at the cursor). + * xp->xp_context The type of thing to expand. Will be one of: + * + * EXPAND_UNSUCCESSFUL Used sometimes when there is something illegal on + * the command line, like an unknown command. Caller + * should beep. + * EXPAND_NOTHING Unrecognised context for completion, use char like + * a normal char, rather than for completion. eg + * :s/^I/ + * EXPAND_COMMANDS Cursor is still touching the command, so complete + * it. + * EXPAND_BUFFERS Complete file names for :buf and :sbuf commands. + * EXPAND_FILES After command with XFILE set, or after setting + * with P_EXPAND set. eg :e ^I, :w>>^I + * EXPAND_DIRECTORIES In some cases this is used instead of the latter + * when we know only directories are of interest. eg + * :set dir=^I + * EXPAND_SHELLCMD After ":!cmd", ":r !cmd" or ":w !cmd". + * EXPAND_SETTINGS Complete variable names. eg :set d^I + * EXPAND_BOOL_SETTINGS Complete boolean variables only, eg :set no^I + * EXPAND_TAGS Complete tags from the files in p_tags. eg :ta a^I + * EXPAND_TAGS_LISTFILES As above, but list filenames on ^D, after :tselect + * EXPAND_HELP Complete tags from the file 'helpfile'/tags + * EXPAND_EVENTS Complete event names + * EXPAND_SYNTAX Complete :syntax command arguments + * EXPAND_HIGHLIGHT Complete highlight (syntax) group names + * EXPAND_AUGROUP Complete autocommand group names + * EXPAND_USER_VARS Complete user defined variable names, eg :unlet a^I + * EXPAND_MAPPINGS Complete mapping and abbreviation names, + * eg :unmap a^I , :cunab x^I + * EXPAND_FUNCTIONS Complete internal or user defined function names, + * eg :call sub^I + * EXPAND_USER_FUNC Complete user defined function names, eg :delf F^I + * EXPAND_EXPRESSION Complete internal or user defined function/variable + * names in expressions, eg :while s^I + * EXPAND_ENV_VARS Complete environment variable names + * EXPAND_USER Complete user names + */ +static void set_expand_context(xp) +expand_T *xp; +{ + /* only expansion for ':', '>' and '=' command-lines */ + if (ccline.cmdfirstc != ':' + && ccline.cmdfirstc != '>' && ccline.cmdfirstc != '=' + && !ccline.input_fn + ) { + xp->xp_context = EXPAND_NOTHING; + return; + } + set_cmd_context(xp, ccline.cmdbuff, ccline.cmdlen, ccline.cmdpos); +} + +void set_cmd_context(xp, str, len, col) +expand_T *xp; +char_u *str; /* start of command line */ +int len; /* length of command line (excl. NUL) */ +int col; /* position of cursor */ +{ + int old_char = NUL; + char_u *nextcomm; + + /* + * Avoid a UMR warning from Purify, only save the character if it has been + * written before. + */ + if (col < len) + old_char = str[col]; + str[col] = NUL; + nextcomm = str; + + if (ccline.cmdfirstc == '=') { + /* pass CMD_SIZE because there is no real command */ + set_context_for_expression(xp, str, CMD_SIZE); + } else if (ccline.input_fn) { + xp->xp_context = ccline.xp_context; + xp->xp_pattern = ccline.cmdbuff; + xp->xp_arg = ccline.xp_arg; + } else + while (nextcomm != NULL) + nextcomm = set_one_cmd_context(xp, nextcomm); + + /* Store the string here so that call_user_expand_func() can get to them + * easily. */ + xp->xp_line = str; + xp->xp_col = col; + + str[col] = old_char; +} + +/* + * Expand the command line "str" from context "xp". + * "xp" must have been set by set_cmd_context(). + * xp->xp_pattern points into "str", to where the text that is to be expanded + * starts. + * Returns EXPAND_UNSUCCESSFUL when there is something illegal before the + * cursor. + * Returns EXPAND_NOTHING when there is nothing to expand, might insert the + * key that triggered expansion literally. + * Returns EXPAND_OK otherwise. + */ +int expand_cmdline(xp, str, col, matchcount, matches) +expand_T *xp; +char_u *str; /* start of command line */ +int col; /* position of cursor */ +int *matchcount; /* return: nr of matches */ +char_u ***matches; /* return: array of pointers to matches */ +{ + char_u *file_str = NULL; + int options = WILD_ADD_SLASH|WILD_SILENT; + + if (xp->xp_context == EXPAND_UNSUCCESSFUL) { + beep_flush(); + return EXPAND_UNSUCCESSFUL; /* Something illegal on command line */ + } + if (xp->xp_context == EXPAND_NOTHING) { + /* Caller can use the character as a normal char instead */ + return EXPAND_NOTHING; + } + + /* add star to file name, or convert to regexp if not exp. files. */ + xp->xp_pattern_len = (int)(str + col - xp->xp_pattern); + file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); + if (file_str == NULL) + return EXPAND_UNSUCCESSFUL; + + if (p_wic) + options += WILD_ICASE; + + /* find all files that match the description */ + if (ExpandFromContext(xp, file_str, matchcount, matches, options) == FAIL) { + *matchcount = 0; + *matches = NULL; + } + vim_free(file_str); + + return EXPAND_OK; +} + +/* + * Cleanup matches for help tags: remove "@en" if "en" is the only language. + */ +static void cleanup_help_tags __ARGS((int num_file, char_u **file)); + +static void cleanup_help_tags(num_file, file) +int num_file; +char_u **file; +{ + int i, j; + int len; + + for (i = 0; i < num_file; ++i) { + len = (int)STRLEN(file[i]) - 3; + if (len > 0 && STRCMP(file[i] + len, "@en") == 0) { + /* Sorting on priority means the same item in another language may + * be anywhere. Search all items for a match up to the "@en". */ + for (j = 0; j < num_file; ++j) + if (j != i + && (int)STRLEN(file[j]) == len + 3 + && STRNCMP(file[i], file[j], len + 1) == 0) + break; + if (j == num_file) + file[i][len] = NUL; + } + } +} + +/* + * Do the expansion based on xp->xp_context and "pat". + */ +static int ExpandFromContext(xp, pat, num_file, file, options) +expand_T *xp; +char_u *pat; +int *num_file; +char_u ***file; +int options; /* EW_ flags */ +{ + regmatch_T regmatch; + int ret; + int flags; + + flags = EW_DIR; /* include directories */ + if (options & WILD_LIST_NOTFOUND) + flags |= EW_NOTFOUND; + if (options & WILD_ADD_SLASH) + flags |= EW_ADDSLASH; + if (options & WILD_KEEP_ALL) + flags |= EW_KEEPALL; + if (options & WILD_SILENT) + flags |= EW_SILENT; + + if (xp->xp_context == EXPAND_FILES + || xp->xp_context == EXPAND_DIRECTORIES + || xp->xp_context == EXPAND_FILES_IN_PATH) { + /* + * Expand file or directory names. + */ + int free_pat = FALSE; + int i; + + /* for ":set path=" and ":set tags=" halve backslashes for escaped + * space */ + if (xp->xp_backslash != XP_BS_NONE) { + free_pat = TRUE; + pat = vim_strsave(pat); + for (i = 0; pat[i]; ++i) + if (pat[i] == '\\') { + if (xp->xp_backslash == XP_BS_THREE + && pat[i + 1] == '\\' + && pat[i + 2] == '\\' + && pat[i + 3] == ' ') + STRMOVE(pat + i, pat + i + 3); + if (xp->xp_backslash == XP_BS_ONE + && pat[i + 1] == ' ') + STRMOVE(pat + i, pat + i + 1); + } + } + + if (xp->xp_context == EXPAND_FILES) + flags |= EW_FILE; + else if (xp->xp_context == EXPAND_FILES_IN_PATH) + flags |= (EW_FILE | EW_PATH); + else + flags = (flags | EW_DIR) & ~EW_FILE; + if (options & WILD_ICASE) + flags |= EW_ICASE; + + /* Expand wildcards, supporting %:h and the like. */ + ret = expand_wildcards_eval(&pat, num_file, file, flags); + if (free_pat) + vim_free(pat); + return ret; + } + + *file = (char_u **)""; + *num_file = 0; + if (xp->xp_context == EXPAND_HELP) { + /* With an empty argument we would get all the help tags, which is + * very slow. Get matches for "help" instead. */ + if (find_help_tags(*pat == NUL ? (char_u *)"help" : pat, + num_file, file, FALSE) == OK) { + cleanup_help_tags(*num_file, *file); + return OK; + } + return FAIL; + } + + if (xp->xp_context == EXPAND_SHELLCMD) + return expand_shellcmd(pat, num_file, file, flags); + if (xp->xp_context == EXPAND_OLD_SETTING) + return ExpandOldSetting(num_file, file); + if (xp->xp_context == EXPAND_BUFFERS) + return ExpandBufnames(pat, num_file, file, options); + if (xp->xp_context == EXPAND_TAGS + || xp->xp_context == EXPAND_TAGS_LISTFILES) + return expand_tags(xp->xp_context == EXPAND_TAGS, pat, num_file, file); + if (xp->xp_context == EXPAND_COLORS) { + char *directories[] = {"colors", NULL}; + return ExpandRTDir(pat, num_file, file, directories); + } + if (xp->xp_context == EXPAND_COMPILER) { + char *directories[] = {"compiler", NULL}; + return ExpandRTDir(pat, num_file, file, directories); + } + if (xp->xp_context == EXPAND_OWNSYNTAX) { + char *directories[] = {"syntax", NULL}; + return ExpandRTDir(pat, num_file, file, directories); + } + if (xp->xp_context == EXPAND_FILETYPE) { + char *directories[] = {"syntax", "indent", "ftplugin", NULL}; + return ExpandRTDir(pat, num_file, file, directories); + } + if (xp->xp_context == EXPAND_USER_LIST) + return ExpandUserList(xp, num_file, file); + + regmatch.regprog = vim_regcomp(pat, p_magic ? RE_MAGIC : 0); + if (regmatch.regprog == NULL) + return FAIL; + + /* set ignore-case according to p_ic, p_scs and pat */ + regmatch.rm_ic = ignorecase(pat); + + if (xp->xp_context == EXPAND_SETTINGS + || xp->xp_context == EXPAND_BOOL_SETTINGS) + ret = ExpandSettings(xp, ®match, num_file, file); + else if (xp->xp_context == EXPAND_MAPPINGS) + ret = ExpandMappings(®match, num_file, file); + else if (xp->xp_context == EXPAND_USER_DEFINED) + ret = ExpandUserDefined(xp, ®match, num_file, file); + else { + static struct expgen { + int context; + char_u *((*func)__ARGS((expand_T *, int))); + int ic; + int escaped; + } tab[] = + { + {EXPAND_COMMANDS, get_command_name, FALSE, TRUE}, + {EXPAND_BEHAVE, get_behave_arg, TRUE, TRUE}, + {EXPAND_HISTORY, get_history_arg, TRUE, TRUE}, + {EXPAND_USER_COMMANDS, get_user_commands, FALSE, TRUE}, + {EXPAND_USER_CMD_FLAGS, get_user_cmd_flags, FALSE, TRUE}, + {EXPAND_USER_NARGS, get_user_cmd_nargs, FALSE, TRUE}, + {EXPAND_USER_COMPLETE, get_user_cmd_complete, FALSE, TRUE}, + {EXPAND_USER_VARS, get_user_var_name, FALSE, TRUE}, + {EXPAND_FUNCTIONS, get_function_name, FALSE, TRUE}, + {EXPAND_USER_FUNC, get_user_func_name, FALSE, TRUE}, + {EXPAND_EXPRESSION, get_expr_name, FALSE, TRUE}, + {EXPAND_MENUS, get_menu_name, FALSE, TRUE}, + {EXPAND_MENUNAMES, get_menu_names, FALSE, TRUE}, + {EXPAND_SYNTAX, get_syntax_name, TRUE, TRUE}, + {EXPAND_SYNTIME, get_syntime_arg, TRUE, TRUE}, + {EXPAND_HIGHLIGHT, get_highlight_name, TRUE, TRUE}, + {EXPAND_EVENTS, get_event_name, TRUE, TRUE}, + {EXPAND_AUGROUP, get_augroup_name, TRUE, TRUE}, + {EXPAND_CSCOPE, get_cscope_name, TRUE, TRUE}, + {EXPAND_PROFILE, get_profile_name, TRUE, TRUE}, +#if (defined(HAVE_LOCALE_H) || defined(X_LOCALE)) \ + && (defined(FEAT_GETTEXT) || defined(FEAT_MBYTE)) + {EXPAND_LANGUAGE, get_lang_arg, TRUE, FALSE}, + {EXPAND_LOCALES, get_locales, TRUE, FALSE}, +#endif + {EXPAND_ENV_VARS, get_env_name, TRUE, TRUE}, + {EXPAND_USER, get_users, TRUE, FALSE}, + }; + int i; + + /* + * Find a context in the table and call the ExpandGeneric() with the + * right function to do the expansion. + */ + ret = FAIL; + for (i = 0; i < (int)(sizeof(tab) / sizeof(struct expgen)); ++i) + if (xp->xp_context == tab[i].context) { + if (tab[i].ic) + regmatch.rm_ic = TRUE; + ret = ExpandGeneric(xp, ®match, num_file, file, + tab[i].func, tab[i].escaped); + break; + } + } + + vim_regfree(regmatch.regprog); + + return ret; +} + +/* + * Expand a list of names. + * + * Generic function for command line completion. It calls a function to + * obtain strings, one by one. The strings are matched against a regexp + * program. Matching strings are copied into an array, which is returned. + * + * Returns OK when no problems encountered, FAIL for error (out of memory). + */ +int ExpandGeneric(xp, regmatch, num_file, file, func, escaped) +expand_T *xp; +regmatch_T *regmatch; +int *num_file; +char_u ***file; +char_u *((*func)__ARGS((expand_T *, int))); +/* returns a string from the list */ +int escaped; +{ + int i; + int count = 0; + int round; + char_u *str; + + /* do this loop twice: + * round == 0: count the number of matching names + * round == 1: copy the matching names into allocated memory + */ + for (round = 0; round <= 1; ++round) { + for (i = 0;; ++i) { + str = (*func)(xp, i); + if (str == NULL) /* end of list */ + break; + if (*str == NUL) /* skip empty strings */ + continue; + + if (vim_regexec(regmatch, str, (colnr_T)0)) { + if (round) { + if (escaped) + str = vim_strsave_escaped(str, (char_u *)" \t\\."); + else + str = vim_strsave(str); + (*file)[count] = str; + if (func == get_menu_names && str != NULL) { + /* test for separator added by get_menu_names() */ + str += STRLEN(str) - 1; + if (*str == '\001') + *str = '.'; + } + } + ++count; + } + } + if (round == 0) { + if (count == 0) + return OK; + *num_file = count; + *file = (char_u **)alloc((unsigned)(count * sizeof(char_u *))); + if (*file == NULL) { + *file = (char_u **)""; + return FAIL; + } + count = 0; + } + } + + /* Sort the results. Keep menu's in the specified order. */ + if (xp->xp_context != EXPAND_MENUNAMES && xp->xp_context != EXPAND_MENUS) { + if (xp->xp_context == EXPAND_EXPRESSION + || xp->xp_context == EXPAND_FUNCTIONS + || xp->xp_context == EXPAND_USER_FUNC) + /* functions should be sorted to the end. */ + qsort((void *)*file, (size_t)*num_file, sizeof(char_u *), + sort_func_compare); + else + sort_strings(*file, *num_file); + } + + /* Reset the variables used for special highlight names expansion, so that + * they don't show up when getting normal highlight names by ID. */ + reset_expand_highlight(); + + return OK; +} + +/* + * Complete a shell command. + * Returns FAIL or OK; + */ +static int expand_shellcmd(filepat, num_file, file, flagsarg) +char_u *filepat; /* pattern to match with command names */ +int *num_file; /* return: number of matches */ +char_u ***file; /* return: array with matches */ +int flagsarg; /* EW_ flags */ +{ + char_u *pat; + int i; + char_u *path; + int mustfree = FALSE; + garray_T ga; + char_u *buf = alloc(MAXPATHL); + size_t l; + char_u *s, *e; + int flags = flagsarg; + int ret; + + if (buf == NULL) + return FAIL; + + /* for ":set path=" and ":set tags=" halve backslashes for escaped + * space */ + pat = vim_strsave(filepat); + for (i = 0; pat[i]; ++i) + if (pat[i] == '\\' && pat[i + 1] == ' ') + STRMOVE(pat + i, pat + i + 1); + + flags |= EW_FILE | EW_EXEC; + + /* For an absolute name we don't use $PATH. */ + if (mch_isFullName(pat)) + path = (char_u *)" "; + else if ((pat[0] == '.' && (vim_ispathsep(pat[1]) + || (pat[1] == '.' && vim_ispathsep(pat[2]))))) + path = (char_u *)"."; + else { + path = vim_getenv((char_u *)"PATH", &mustfree); + if (path == NULL) + path = (char_u *)""; + } + + /* + * Go over all directories in $PATH. Expand matches in that directory and + * collect them in "ga". + */ + ga_init2(&ga, (int)sizeof(char *), 10); + for (s = path; *s != NUL; s = e) { + if (*s == ' ') + ++s; /* Skip space used for absolute path name. */ + + e = vim_strchr(s, ':'); + if (e == NULL) + e = s + STRLEN(s); + + l = e - s; + if (l > MAXPATHL - 5) + break; + vim_strncpy(buf, s, l); + add_pathsep(buf); + l = STRLEN(buf); + vim_strncpy(buf + l, pat, MAXPATHL - 1 - l); + + /* Expand matches in one directory of $PATH. */ + ret = expand_wildcards(1, &buf, num_file, file, flags); + if (ret == OK) { + if (ga_grow(&ga, *num_file) == FAIL) + FreeWild(*num_file, *file); + else { + for (i = 0; i < *num_file; ++i) { + s = (*file)[i]; + if (STRLEN(s) > l) { + /* Remove the path again. */ + STRMOVE(s, s + l); + ((char_u **)ga.ga_data)[ga.ga_len++] = s; + } else + vim_free(s); + } + vim_free(*file); + } + } + if (*e != NUL) + ++e; + } + *file = ga.ga_data; + *num_file = ga.ga_len; + + vim_free(buf); + vim_free(pat); + if (mustfree) + vim_free(path); + return OK; +} + + +static void * call_user_expand_func __ARGS((void *(*user_expand_func)__ARGS( + (char_u *, int, char_u **, + int)), expand_T *xp, + int *num_file, char_u ***file)); + +/* + * Call "user_expand_func()" to invoke a user defined VimL function and return + * the result (either a string or a List). + */ +static void * call_user_expand_func(user_expand_func, xp, num_file, file) +void *(*user_expand_func)__ARGS((char_u *, int, char_u **, int)); +expand_T *xp; +int *num_file; +char_u ***file; +{ + int keep = 0; + char_u num[50]; + char_u *args[3]; + int save_current_SID = current_SID; + void *ret; + struct cmdline_info save_ccline; + + if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) + return NULL; + *num_file = 0; + *file = NULL; + + if (ccline.cmdbuff != NULL) { + keep = ccline.cmdbuff[ccline.cmdlen]; + ccline.cmdbuff[ccline.cmdlen] = 0; + } + + args[0] = vim_strnsave(xp->xp_pattern, xp->xp_pattern_len); + args[1] = xp->xp_line; + sprintf((char *)num, "%d", xp->xp_col); + args[2] = num; + + /* Save the cmdline, we don't know what the function may do. */ + save_ccline = ccline; + ccline.cmdbuff = NULL; + ccline.cmdprompt = NULL; + current_SID = xp->xp_scriptID; + + ret = user_expand_func(xp->xp_arg, 3, args, FALSE); + + ccline = save_ccline; + current_SID = save_current_SID; + if (ccline.cmdbuff != NULL) + ccline.cmdbuff[ccline.cmdlen] = keep; + + vim_free(args[0]); + return ret; +} + +/* + * Expand names with a function defined by the user. + */ +static int ExpandUserDefined(xp, regmatch, num_file, file) +expand_T *xp; +regmatch_T *regmatch; +int *num_file; +char_u ***file; +{ + char_u *retstr; + char_u *s; + char_u *e; + char_u keep; + garray_T ga; + + retstr = call_user_expand_func(call_func_retstr, xp, num_file, file); + if (retstr == NULL) + return FAIL; + + ga_init2(&ga, (int)sizeof(char *), 3); + for (s = retstr; *s != NUL; s = e) { + e = vim_strchr(s, '\n'); + if (e == NULL) + e = s + STRLEN(s); + keep = *e; + *e = 0; + + if (xp->xp_pattern[0] && vim_regexec(regmatch, s, (colnr_T)0) == 0) { + *e = keep; + if (*e != NUL) + ++e; + continue; + } + + if (ga_grow(&ga, 1) == FAIL) + break; + + ((char_u **)ga.ga_data)[ga.ga_len] = vim_strnsave(s, (int)(e - s)); + ++ga.ga_len; + + *e = keep; + if (*e != NUL) + ++e; + } + vim_free(retstr); + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} + +/* + * Expand names with a list returned by a function defined by the user. + */ +static int ExpandUserList(xp, num_file, file) +expand_T *xp; +int *num_file; +char_u ***file; +{ + list_T *retlist; + listitem_T *li; + garray_T ga; + + retlist = call_user_expand_func(call_func_retlist, xp, num_file, file); + if (retlist == NULL) + return FAIL; + + ga_init2(&ga, (int)sizeof(char *), 3); + /* Loop over the items in the list. */ + for (li = retlist->lv_first; li != NULL; li = li->li_next) { + if (li->li_tv.v_type != VAR_STRING || li->li_tv.vval.v_string == NULL) + continue; /* Skip non-string items and empty strings */ + + if (ga_grow(&ga, 1) == FAIL) + break; + + ((char_u **)ga.ga_data)[ga.ga_len] = + vim_strsave(li->li_tv.vval.v_string); + ++ga.ga_len; + } + list_unref(retlist); + + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} + +/* + * Expand color scheme, compiler or filetype names: + * 'runtimepath'/{dirnames}/{pat}.vim + * "dirnames" is an array with one or more directory names. + */ +static int ExpandRTDir(pat, num_file, file, dirnames) +char_u *pat; +int *num_file; +char_u ***file; +char *dirnames[]; +{ + char_u *matches; + char_u *s; + char_u *e; + garray_T ga; + int i; + int pat_len; + + *num_file = 0; + *file = NULL; + pat_len = (int)STRLEN(pat); + ga_init2(&ga, (int)sizeof(char *), 10); + + for (i = 0; dirnames[i] != NULL; ++i) { + s = alloc((unsigned)(STRLEN(dirnames[i]) + pat_len + 7)); + if (s == NULL) { + ga_clear_strings(&ga); + return FAIL; + } + sprintf((char *)s, "%s/%s*.vim", dirnames[i], pat); + matches = globpath(p_rtp, s, 0); + vim_free(s); + if (matches == NULL) + continue; + + for (s = matches; *s != NUL; s = e) { + e = vim_strchr(s, '\n'); + if (e == NULL) + e = s + STRLEN(s); + if (ga_grow(&ga, 1) == FAIL) + break; + if (e - 4 > s && STRNICMP(e - 4, ".vim", 4) == 0) { + for (s = e - 4; s > matches; mb_ptr_back(matches, s)) + if (*s == '\n' || vim_ispathsep(*s)) + break; + ++s; + ((char_u **)ga.ga_data)[ga.ga_len] = + vim_strnsave(s, (int)(e - s - 4)); + ++ga.ga_len; + } + if (*e != NUL) + ++e; + } + vim_free(matches); + } + if (ga.ga_len == 0) + return FAIL; + + /* Sort and remove duplicates which can happen when specifying multiple + * directories in dirnames. */ + remove_duplicates(&ga); + + *file = ga.ga_data; + *num_file = ga.ga_len; + return OK; +} + + +/* + * Expand "file" for all comma-separated directories in "path". + * Returns an allocated string with all matches concatenated, separated by + * newlines. Returns NULL for an error or no matches. + */ +char_u * globpath(path, file, expand_options) +char_u *path; +char_u *file; +int expand_options; +{ + expand_T xpc; + char_u *buf; + garray_T ga; + int i; + int len; + int num_p; + char_u **p; + char_u *cur = NULL; + + buf = alloc(MAXPATHL); + if (buf == NULL) + return NULL; + + ExpandInit(&xpc); + xpc.xp_context = EXPAND_FILES; + + ga_init2(&ga, 1, 100); + + /* Loop over all entries in {path}. */ + while (*path != NUL) { + /* Copy one item of the path to buf[] and concatenate the file name. */ + copy_option_part(&path, buf, MAXPATHL, ","); + if (STRLEN(buf) + STRLEN(file) + 2 < MAXPATHL) { + add_pathsep(buf); + STRCAT(buf, file); + if (ExpandFromContext(&xpc, buf, &num_p, &p, + WILD_SILENT|expand_options) != FAIL && num_p > 0) { + ExpandEscape(&xpc, buf, num_p, p, WILD_SILENT|expand_options); + for (len = 0, i = 0; i < num_p; ++i) + len += (int)STRLEN(p[i]) + 1; + + /* Concatenate new results to previous ones. */ + if (ga_grow(&ga, len) == OK) { + cur = (char_u *)ga.ga_data + ga.ga_len; + for (i = 0; i < num_p; ++i) { + STRCPY(cur, p[i]); + cur += STRLEN(p[i]); + *cur++ = '\n'; + } + ga.ga_len += len; + } + FreeWild(num_p, p); + } + } + } + if (cur != NULL) + *--cur = 0; /* Replace trailing newline with NUL */ + + vim_free(buf); + return (char_u *)ga.ga_data; +} + + + +/********************************* +* Command line history stuff * +*********************************/ + +/* + * Translate a history character to the associated type number. + */ +static int hist_char2type(c) +int c; +{ + if (c == ':') + return HIST_CMD; + if (c == '=') + return HIST_EXPR; + if (c == '@') + return HIST_INPUT; + if (c == '>') + return HIST_DEBUG; + return HIST_SEARCH; /* must be '?' or '/' */ +} + +/* + * Table of history names. + * These names are used in :history and various hist...() functions. + * It is sufficient to give the significant prefix of a history name. + */ + +static char *(history_names[]) = +{ + "cmd", + "search", + "expr", + "input", + "debug", + NULL +}; + +/* + * Function given to ExpandGeneric() to obtain the possible first + * arguments of the ":history command. + */ +static char_u * get_history_arg(xp, idx) +expand_T *xp UNUSED; +int idx; +{ + static char_u compl[2] = { NUL, NUL }; + char *short_names = ":=@>?/"; + int short_names_count = (int)STRLEN(short_names); + int history_name_count = sizeof(history_names) / sizeof(char *) - 1; + + if (idx < short_names_count) { + compl[0] = (char_u)short_names[idx]; + return compl; + } + if (idx < short_names_count + history_name_count) + return (char_u *)history_names[idx - short_names_count]; + if (idx == short_names_count + history_name_count) + return (char_u *)"all"; + return NULL; +} + +/* + * init_history() - Initialize the command line history. + * Also used to re-allocate the history when the size changes. + */ +void init_history() { + int newlen; /* new length of history table */ + histentry_T *temp; + int i; + int j; + int type; + + /* + * If size of history table changed, reallocate it + */ + newlen = (int)p_hi; + if (newlen != hislen) { /* history length changed */ + for (type = 0; type < HIST_COUNT; ++type) { /* adjust the tables */ + if (newlen) { + temp = (histentry_T *)lalloc( + (long_u)(newlen * sizeof(histentry_T)), TRUE); + if (temp == NULL) { /* out of memory! */ + if (type == 0) { /* first one: just keep the old length */ + newlen = hislen; + break; + } + /* Already changed one table, now we can only have zero + * length for all tables. */ + newlen = 0; + type = -1; + continue; + } + } else + temp = NULL; + if (newlen == 0 || temp != NULL) { + if (hisidx[type] < 0) { /* there are no entries yet */ + for (i = 0; i < newlen; ++i) + clear_hist_entry(&temp[i]); + } else if (newlen > hislen) { /* array becomes bigger */ + for (i = 0; i <= hisidx[type]; ++i) + temp[i] = history[type][i]; + j = i; + for (; i <= newlen - (hislen - hisidx[type]); ++i) + clear_hist_entry(&temp[i]); + for (; j < hislen; ++i, ++j) + temp[i] = history[type][j]; + } else { /* array becomes smaller or 0 */ + j = hisidx[type]; + for (i = newlen - 1;; --i) { + if (i >= 0) /* copy newest entries */ + temp[i] = history[type][j]; + else /* remove older entries */ + vim_free(history[type][j].hisstr); + if (--j < 0) + j = hislen - 1; + if (j == hisidx[type]) + break; + } + hisidx[type] = newlen - 1; + } + vim_free(history[type]); + history[type] = temp; + } + } + hislen = newlen; + } +} + +static void clear_hist_entry(hisptr) +histentry_T *hisptr; +{ + hisptr->hisnum = 0; + hisptr->viminfo = FALSE; + hisptr->hisstr = NULL; +} + +/* + * Check if command line 'str' is already in history. + * If 'move_to_front' is TRUE, matching entry is moved to end of history. + */ +static int in_history(type, str, move_to_front, sep, writing) +int type; +char_u *str; +int move_to_front; /* Move the entry to the front if it exists */ +int sep; +int writing; /* ignore entries read from viminfo */ +{ + int i; + int last_i = -1; + char_u *p; + + if (hisidx[type] < 0) + return FALSE; + i = hisidx[type]; + do { + if (history[type][i].hisstr == NULL) + return FALSE; + + /* For search history, check that the separator character matches as + * well. */ + p = history[type][i].hisstr; + if (STRCMP(str, p) == 0 + && !(writing && history[type][i].viminfo) + && (type != HIST_SEARCH || sep == p[STRLEN(p) + 1])) { + if (!move_to_front) + return TRUE; + last_i = i; + break; + } + if (--i < 0) + i = hislen - 1; + } while (i != hisidx[type]); + + if (last_i >= 0) { + str = history[type][i].hisstr; + while (i != hisidx[type]) { + if (++i >= hislen) + i = 0; + history[type][last_i] = history[type][i]; + last_i = i; + } + history[type][i].hisnum = ++hisnum[type]; + history[type][i].viminfo = FALSE; + history[type][i].hisstr = str; + return TRUE; + } + return FALSE; +} + +/* + * Convert history name (from table above) to its HIST_ equivalent. + * When "name" is empty, return "cmd" history. + * Returns -1 for unknown history name. + */ +int get_histtype(name) +char_u *name; +{ + int i; + int len = (int)STRLEN(name); + + /* No argument: use current history. */ + if (len == 0) + return hist_char2type(ccline.cmdfirstc); + + for (i = 0; history_names[i] != NULL; ++i) + if (STRNICMP(name, history_names[i], len) == 0) + return i; + + if (vim_strchr((char_u *)":=@>?/", name[0]) != NULL && name[1] == NUL) + return hist_char2type(name[0]); + + return -1; +} + +static int last_maptick = -1; /* last seen maptick */ + +/* + * Add the given string to the given history. If the string is already in the + * history then it is moved to the front. "histype" may be one of he HIST_ + * values. + */ +void add_to_history(histype, new_entry, in_map, sep) +int histype; +char_u *new_entry; +int in_map; /* consider maptick when inside a mapping */ +int sep; /* separator character used (search hist) */ +{ + histentry_T *hisptr; + int len; + + if (hislen == 0) /* no history */ + return; + + if (cmdmod.keeppatterns && histype == HIST_SEARCH) + return; + + /* + * Searches inside the same mapping overwrite each other, so that only + * the last line is kept. Be careful not to remove a line that was moved + * down, only lines that were added. + */ + if (histype == HIST_SEARCH && in_map) { + if (maptick == last_maptick) { + /* Current line is from the same mapping, remove it */ + hisptr = &history[HIST_SEARCH][hisidx[HIST_SEARCH]]; + vim_free(hisptr->hisstr); + clear_hist_entry(hisptr); + --hisnum[histype]; + if (--hisidx[HIST_SEARCH] < 0) + hisidx[HIST_SEARCH] = hislen - 1; + } + last_maptick = -1; + } + if (!in_history(histype, new_entry, TRUE, sep, FALSE)) { + if (++hisidx[histype] == hislen) + hisidx[histype] = 0; + hisptr = &history[histype][hisidx[histype]]; + vim_free(hisptr->hisstr); + + /* Store the separator after the NUL of the string. */ + len = (int)STRLEN(new_entry); + hisptr->hisstr = vim_strnsave(new_entry, len + 2); + if (hisptr->hisstr != NULL) + hisptr->hisstr[len + 1] = sep; + + hisptr->hisnum = ++hisnum[histype]; + hisptr->viminfo = FALSE; + if (histype == HIST_SEARCH && in_map) + last_maptick = maptick; + } +} + + +/* + * Get identifier of newest history entry. + * "histype" may be one of the HIST_ values. + */ +int get_history_idx(histype) +int histype; +{ + if (hislen == 0 || histype < 0 || histype >= HIST_COUNT + || hisidx[histype] < 0) + return -1; + + return history[histype][hisidx[histype]].hisnum; +} + +static struct cmdline_info *get_ccline_ptr __ARGS((void)); + +/* + * Get pointer to the command line info to use. cmdline_paste() may clear + * ccline and put the previous value in prev_ccline. + */ +static struct cmdline_info * get_ccline_ptr() +{ + if ((State & CMDLINE) == 0) + return NULL; + if (ccline.cmdbuff != NULL) + return &ccline; + if (prev_ccline_used && prev_ccline.cmdbuff != NULL) + return &prev_ccline; + return NULL; +} + +/* + * Get the current command line in allocated memory. + * Only works when the command line is being edited. + * Returns NULL when something is wrong. + */ +char_u * get_cmdline_str() { + struct cmdline_info *p = get_ccline_ptr(); + + if (p == NULL) + return NULL; + return vim_strnsave(p->cmdbuff, p->cmdlen); +} + +/* + * Get the current command line position, counted in bytes. + * Zero is the first position. + * Only works when the command line is being edited. + * Returns -1 when something is wrong. + */ +int get_cmdline_pos() { + struct cmdline_info *p = get_ccline_ptr(); + + if (p == NULL) + return -1; + return p->cmdpos; +} + +/* + * Set the command line byte position to "pos". Zero is the first position. + * Only works when the command line is being edited. + * Returns 1 when failed, 0 when OK. + */ +int set_cmdline_pos(pos) +int pos; +{ + struct cmdline_info *p = get_ccline_ptr(); + + if (p == NULL) + return 1; + + /* The position is not set directly but after CTRL-\ e or CTRL-R = has + * changed the command line. */ + if (pos < 0) + new_cmdpos = 0; + else + new_cmdpos = pos; + return 0; +} + +/* + * Get the current command-line type. + * Returns ':' or '/' or '?' or '@' or '>' or '-' + * Only works when the command line is being edited. + * Returns NUL when something is wrong. + */ +int get_cmdline_type() { + struct cmdline_info *p = get_ccline_ptr(); + + if (p == NULL) + return NUL; + if (p->cmdfirstc == NUL) + return (p->input_fn) ? '@' : '-'; + return p->cmdfirstc; +} + +/* + * Calculate history index from a number: + * num > 0: seen as identifying number of a history entry + * num < 0: relative position in history wrt newest entry + * "histype" may be one of the HIST_ values. + */ +static int calc_hist_idx(histype, num) +int histype; +int num; +{ + int i; + histentry_T *hist; + int wrapped = FALSE; + + if (hislen == 0 || histype < 0 || histype >= HIST_COUNT + || (i = hisidx[histype]) < 0 || num == 0) + return -1; + + hist = history[histype]; + if (num > 0) { + while (hist[i].hisnum > num) + if (--i < 0) { + if (wrapped) + break; + i += hislen; + wrapped = TRUE; + } + if (hist[i].hisnum == num && hist[i].hisstr != NULL) + return i; + } else if (-num <= hislen) { + i += num + 1; + if (i < 0) + i += hislen; + if (hist[i].hisstr != NULL) + return i; + } + return -1; +} + +/* + * Get a history entry by its index. + * "histype" may be one of the HIST_ values. + */ +char_u * get_history_entry(histype, idx) +int histype; +int idx; +{ + idx = calc_hist_idx(histype, idx); + if (idx >= 0) + return history[histype][idx].hisstr; + else + return (char_u *)""; +} + +/* + * Clear all entries of a history. + * "histype" may be one of the HIST_ values. + */ +int clr_history(histype) +int histype; +{ + int i; + histentry_T *hisptr; + + if (hislen != 0 && histype >= 0 && histype < HIST_COUNT) { + hisptr = history[histype]; + for (i = hislen; i--; ) { + vim_free(hisptr->hisstr); + clear_hist_entry(hisptr); + } + hisidx[histype] = -1; /* mark history as cleared */ + hisnum[histype] = 0; /* reset identifier counter */ + return OK; + } + return FAIL; +} + +/* + * Remove all entries matching {str} from a history. + * "histype" may be one of the HIST_ values. + */ +int del_history_entry(histype, str) +int histype; +char_u *str; +{ + regmatch_T regmatch; + histentry_T *hisptr; + int idx; + int i; + int last; + int found = FALSE; + + regmatch.regprog = NULL; + regmatch.rm_ic = FALSE; /* always match case */ + if (hislen != 0 + && histype >= 0 + && histype < HIST_COUNT + && *str != NUL + && (idx = hisidx[histype]) >= 0 + && (regmatch.regprog = vim_regcomp(str, RE_MAGIC + RE_STRING)) + != NULL) { + i = last = idx; + do { + hisptr = &history[histype][i]; + if (hisptr->hisstr == NULL) + break; + if (vim_regexec(®match, hisptr->hisstr, (colnr_T)0)) { + found = TRUE; + vim_free(hisptr->hisstr); + clear_hist_entry(hisptr); + } else { + if (i != last) { + history[histype][last] = *hisptr; + clear_hist_entry(hisptr); + } + if (--last < 0) + last += hislen; + } + if (--i < 0) + i += hislen; + } while (i != idx); + if (history[histype][idx].hisstr == NULL) + hisidx[histype] = -1; + } + vim_regfree(regmatch.regprog); + return found; +} + +/* + * Remove an indexed entry from a history. + * "histype" may be one of the HIST_ values. + */ +int del_history_idx(histype, idx) +int histype; +int idx; +{ + int i, j; + + i = calc_hist_idx(histype, idx); + if (i < 0) + return FALSE; + idx = hisidx[histype]; + vim_free(history[histype][i].hisstr); + + /* When deleting the last added search string in a mapping, reset + * last_maptick, so that the last added search string isn't deleted again. + */ + if (histype == HIST_SEARCH && maptick == last_maptick && i == idx) + last_maptick = -1; + + while (i != idx) { + j = (i + 1) % hislen; + history[histype][i] = history[histype][j]; + i = j; + } + clear_hist_entry(&history[histype][i]); + if (--i < 0) + i += hislen; + hisidx[histype] = i; + return TRUE; +} + + +/* + * Very specific function to remove the value in ":set key=val" from the + * history. + */ +void remove_key_from_history() { + char_u *p; + int i; + + i = hisidx[HIST_CMD]; + if (i < 0) + return; + p = history[HIST_CMD][i].hisstr; + if (p != NULL) + for (; *p; ++p) + if (STRNCMP(p, "key", 3) == 0 && !isalpha(p[3])) { + p = vim_strchr(p + 3, '='); + if (p == NULL) + break; + ++p; + for (i = 0; p[i] && !vim_iswhite(p[i]); ++i) + if (p[i] == '\\' && p[i + 1]) + ++i; + STRMOVE(p, p + i); + --p; + } +} + +/* + * Get indices "num1,num2" that specify a range within a list (not a range of + * text lines in a buffer!) from a string. Used for ":history" and ":clist". + * Returns OK if parsed successfully, otherwise FAIL. + */ +int get_list_range(str, num1, num2) +char_u **str; +int *num1; +int *num2; +{ + int len; + int first = FALSE; + long num; + + *str = skipwhite(*str); + if (**str == '-' || vim_isdigit(**str)) { /* parse "from" part of range */ + vim_str2nr(*str, NULL, &len, FALSE, FALSE, &num, NULL); + *str += len; + *num1 = (int)num; + first = TRUE; + } + *str = skipwhite(*str); + if (**str == ',') { /* parse "to" part of range */ + *str = skipwhite(*str + 1); + vim_str2nr(*str, NULL, &len, FALSE, FALSE, &num, NULL); + if (len > 0) { + *num2 = (int)num; + *str = skipwhite(*str + len); + } else if (!first) /* no number given at all */ + return FAIL; + } else if (first) /* only one number given */ + *num2 = *num1; + return OK; +} + +/* + * :history command - print a history + */ +void ex_history(eap) +exarg_T *eap; +{ + histentry_T *hist; + int histype1 = HIST_CMD; + int histype2 = HIST_CMD; + int hisidx1 = 1; + int hisidx2 = -1; + int idx; + int i, j, k; + char_u *end; + char_u *arg = eap->arg; + + if (hislen == 0) { + MSG(_("'history' option is zero")); + return; + } + + if (!(VIM_ISDIGIT(*arg) || *arg == '-' || *arg == ',')) { + end = arg; + while (ASCII_ISALPHA(*end) + || vim_strchr((char_u *)":=@>/?", *end) != NULL) + end++; + i = *end; + *end = NUL; + histype1 = get_histtype(arg); + if (histype1 == -1) { + if (STRNICMP(arg, "all", STRLEN(arg)) == 0) { + histype1 = 0; + histype2 = HIST_COUNT-1; + } else { + *end = i; + EMSG(_(e_trailing)); + return; + } + } else + histype2 = histype1; + *end = i; + } else + end = arg; + if (!get_list_range(&end, &hisidx1, &hisidx2) || *end != NUL) { + EMSG(_(e_trailing)); + return; + } + + for (; !got_int && histype1 <= histype2; ++histype1) { + STRCPY(IObuff, "\n # "); + STRCAT(STRCAT(IObuff, history_names[histype1]), " history"); + MSG_PUTS_TITLE(IObuff); + idx = hisidx[histype1]; + hist = history[histype1]; + j = hisidx1; + k = hisidx2; + if (j < 0) + j = (-j > hislen) ? 0 : hist[(hislen+j+idx+1) % hislen].hisnum; + if (k < 0) + k = (-k > hislen) ? 0 : hist[(hislen+k+idx+1) % hislen].hisnum; + if (idx >= 0 && j <= k) + for (i = idx + 1; !got_int; ++i) { + if (i == hislen) + i = 0; + if (hist[i].hisstr != NULL + && hist[i].hisnum >= j && hist[i].hisnum <= k) { + msg_putchar('\n'); + sprintf((char *)IObuff, "%c%6d ", i == idx ? '>' : ' ', + hist[i].hisnum); + if (vim_strsize(hist[i].hisstr) > (int)Columns - 10) + trunc_string(hist[i].hisstr, IObuff + STRLEN(IObuff), + (int)Columns - 10, IOSIZE - (int)STRLEN(IObuff)); + else + STRCAT(IObuff, hist[i].hisstr); + msg_outtrans(IObuff); + out_flush(); + } + if (i == idx) + break; + } + } +} + +/* + * Buffers for history read from a viminfo file. Only valid while reading. + */ +static char_u **viminfo_history[HIST_COUNT] = {NULL, NULL, NULL, NULL}; +static int viminfo_hisidx[HIST_COUNT] = {0, 0, 0, 0}; +static int viminfo_hislen[HIST_COUNT] = {0, 0, 0, 0}; +static int viminfo_add_at_front = FALSE; + +static int hist_type2char __ARGS((int type, int use_question)); + +/* + * Translate a history type number to the associated character. + */ +static int hist_type2char(type, use_question) +int type; +int use_question; /* use '?' instead of '/' */ +{ + if (type == HIST_CMD) + return ':'; + if (type == HIST_SEARCH) { + if (use_question) + return '?'; + else + return '/'; + } + if (type == HIST_EXPR) + return '='; + return '@'; +} + +/* + * Prepare for reading the history from the viminfo file. + * This allocates history arrays to store the read history lines. + */ +void prepare_viminfo_history(asklen, writing) +int asklen; +int writing; +{ + int i; + int num; + int type; + int len; + + init_history(); + viminfo_add_at_front = (asklen != 0 && !writing); + if (asklen > hislen) + asklen = hislen; + + for (type = 0; type < HIST_COUNT; ++type) { + /* Count the number of empty spaces in the history list. Entries read + * from viminfo previously are also considered empty. If there are + * more spaces available than we request, then fill them up. */ + for (i = 0, num = 0; i < hislen; i++) + if (history[type][i].hisstr == NULL || history[type][i].viminfo) + num++; + len = asklen; + if (num > len) + len = num; + if (len <= 0) + viminfo_history[type] = NULL; + else + viminfo_history[type] = + (char_u **)lalloc((long_u)(len * sizeof(char_u *)), FALSE); + if (viminfo_history[type] == NULL) + len = 0; + viminfo_hislen[type] = len; + viminfo_hisidx[type] = 0; + } +} + +/* + * Accept a line from the viminfo, store it in the history array when it's + * new. + */ +int read_viminfo_history(virp, writing) +vir_T *virp; +int writing; +{ + int type; + long_u len; + char_u *val; + char_u *p; + + type = hist_char2type(virp->vir_line[0]); + if (viminfo_hisidx[type] < viminfo_hislen[type]) { + val = viminfo_readstring(virp, 1, TRUE); + if (val != NULL && *val != NUL) { + int sep = (*val == ' ' ? NUL : *val); + + if (!in_history(type, val + (type == HIST_SEARCH), + viminfo_add_at_front, sep, writing)) { + /* Need to re-allocate to append the separator byte. */ + len = STRLEN(val); + p = lalloc(len + 2, TRUE); + if (p != NULL) { + if (type == HIST_SEARCH) { + /* Search entry: Move the separator from the first + * column to after the NUL. */ + mch_memmove(p, val + 1, (size_t)len); + p[len] = sep; + } else { + /* Not a search entry: No separator in the viminfo + * file, add a NUL separator. */ + mch_memmove(p, val, (size_t)len + 1); + p[len + 1] = NUL; + } + viminfo_history[type][viminfo_hisidx[type]++] = p; + } + } + } + vim_free(val); + } + return viminfo_readline(virp); +} + +/* + * Finish reading history lines from viminfo. Not used when writing viminfo. + */ +void finish_viminfo_history() { + int idx; + int i; + int type; + + for (type = 0; type < HIST_COUNT; ++type) { + if (history[type] == NULL) + continue; + idx = hisidx[type] + viminfo_hisidx[type]; + if (idx >= hislen) + idx -= hislen; + else if (idx < 0) + idx = hislen - 1; + if (viminfo_add_at_front) + hisidx[type] = idx; + else { + if (hisidx[type] == -1) + hisidx[type] = hislen - 1; + do { + if (history[type][idx].hisstr != NULL + || history[type][idx].viminfo) + break; + if (++idx == hislen) + idx = 0; + } while (idx != hisidx[type]); + if (idx != hisidx[type] && --idx < 0) + idx = hislen - 1; + } + for (i = 0; i < viminfo_hisidx[type]; i++) { + vim_free(history[type][idx].hisstr); + history[type][idx].hisstr = viminfo_history[type][i]; + history[type][idx].viminfo = TRUE; + if (--idx < 0) + idx = hislen - 1; + } + idx += 1; + idx %= hislen; + for (i = 0; i < viminfo_hisidx[type]; i++) { + history[type][idx++].hisnum = ++hisnum[type]; + idx %= hislen; + } + vim_free(viminfo_history[type]); + viminfo_history[type] = NULL; + viminfo_hisidx[type] = 0; + } +} + +/* + * Write history to viminfo file in "fp". + * When "merge" is TRUE merge history lines with a previously read viminfo + * file, data is in viminfo_history[]. + * When "merge" is FALSE just write all history lines. Used for ":wviminfo!". + */ +void write_viminfo_history(fp, merge) +FILE *fp; +int merge; +{ + int i; + int type; + int num_saved; + char_u *p; + int c; + int round; + + init_history(); + if (hislen == 0) + return; + for (type = 0; type < HIST_COUNT; ++type) { + num_saved = get_viminfo_parameter(hist_type2char(type, FALSE)); + if (num_saved == 0) + continue; + if (num_saved < 0) /* Use default */ + num_saved = hislen; + fprintf(fp, _("\n# %s History (newest to oldest):\n"), + type == HIST_CMD ? _("Command Line") : + type == HIST_SEARCH ? _("Search String") : + type == HIST_EXPR ? _("Expression") : + _("Input Line")); + if (num_saved > hislen) + num_saved = hislen; + + /* + * Merge typed and viminfo history: + * round 1: history of typed commands. + * round 2: history from recently read viminfo. + */ + for (round = 1; round <= 2; ++round) { + if (round == 1) + /* start at newest entry, somewhere in the list */ + i = hisidx[type]; + else if (viminfo_hisidx[type] > 0) + /* start at newest entry, first in the list */ + i = 0; + else + /* empty list */ + i = -1; + if (i >= 0) + while (num_saved > 0 + && !(round == 2 && i >= viminfo_hisidx[type])) { + p = round == 1 ? history[type][i].hisstr + : viminfo_history[type] == NULL ? NULL + : viminfo_history[type][i]; + if (p != NULL && (round == 2 + || !merge + || !history[type][i].viminfo)) { + --num_saved; + fputc(hist_type2char(type, TRUE), fp); + /* For the search history: put the separator in the + * second column; use a space if there isn't one. */ + if (type == HIST_SEARCH) { + c = p[STRLEN(p) + 1]; + putc(c == NUL ? ' ' : c, fp); + } + viminfo_writestring(fp, p); + } + if (round == 1) { + /* Decrement index, loop around and stop when back at + * the start. */ + if (--i < 0) + i = hislen - 1; + if (i == hisidx[type]) + break; + } else { + /* Increment index. Stop at the end in the while. */ + ++i; + } + } + } + for (i = 0; i < viminfo_hisidx[type]; ++i) + if (viminfo_history[type] != NULL) + vim_free(viminfo_history[type][i]); + vim_free(viminfo_history[type]); + viminfo_history[type] = NULL; + viminfo_hisidx[type] = 0; + } +} + +/* + * Write a character at the current cursor+offset position. + * It is directly written into the command buffer block. + */ +void cmd_pchar(c, offset) +int c, offset; +{ + if (ccline.cmdpos + offset >= ccline.cmdlen || ccline.cmdpos + offset < 0) { + EMSG(_("E198: cmd_pchar beyond the command length")); + return; + } + ccline.cmdbuff[ccline.cmdpos + offset] = (char_u)c; + ccline.cmdbuff[ccline.cmdlen] = NUL; +} + +int cmd_gchar(offset) +int offset; +{ + if (ccline.cmdpos + offset >= ccline.cmdlen || ccline.cmdpos + offset < 0) { + /* EMSG(_("cmd_gchar beyond the command length")); */ + return NUL; + } + return (int)ccline.cmdbuff[ccline.cmdpos + offset]; +} + +/* + * Open a window on the current command line and history. Allow editing in + * the window. Returns when the window is closed. + * Returns: + * CR if the command is to be executed + * Ctrl_C if it is to be abandoned + * K_IGNORE if editing continues + */ +static int ex_window() { + struct cmdline_info save_ccline; + buf_T *old_curbuf = curbuf; + win_T *old_curwin = curwin; + buf_T *bp; + win_T *wp; + int i; + linenr_T lnum; + int histtype; + garray_T winsizes; + char_u typestr[2]; + int save_restart_edit = restart_edit; + int save_State = State; + int save_exmode = exmode_active; + int save_cmdmsg_rl = cmdmsg_rl; + + /* Can't do this recursively. Can't do it when typing a password. */ + if (cmdwin_type != 0 + || cmdline_star > 0 + ) { + beep_flush(); + return K_IGNORE; + } + + /* Save current window sizes. */ + win_size_save(&winsizes); + + /* Don't execute autocommands while creating the window. */ + block_autocmds(); + /* don't use a new tab page */ + cmdmod.tab = 0; + + /* Create a window for the command-line buffer. */ + if (win_split((int)p_cwh, WSP_BOT) == FAIL) { + beep_flush(); + unblock_autocmds(); + return K_IGNORE; + } + cmdwin_type = get_cmdline_type(); + + /* Create the command-line buffer empty. */ + (void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, NULL); + (void)setfname(curbuf, (char_u *)"[Command Line]", NULL, TRUE); + set_option_value((char_u *)"bt", 0L, (char_u *)"nofile", OPT_LOCAL); + set_option_value((char_u *)"swf", 0L, NULL, OPT_LOCAL); + curbuf->b_p_ma = TRUE; + curwin->w_p_fen = FALSE; + curwin->w_p_rl = cmdmsg_rl; + cmdmsg_rl = FALSE; + RESET_BINDING(curwin); + + /* Do execute autocommands for setting the filetype (load syntax). */ + unblock_autocmds(); + + /* Showing the prompt may have set need_wait_return, reset it. */ + need_wait_return = FALSE; + + histtype = hist_char2type(cmdwin_type); + if (histtype == HIST_CMD || histtype == HIST_DEBUG) { + if (p_wc == TAB) { + add_map((char_u *)" ", INSERT); + add_map((char_u *)" a", NORMAL); + } + set_option_value((char_u *)"ft", 0L, (char_u *)"vim", OPT_LOCAL); + } + + /* Reset 'textwidth' after setting 'filetype' (the Vim filetype plugin + * sets 'textwidth' to 78). */ + curbuf->b_p_tw = 0; + + /* Fill the buffer with the history. */ + init_history(); + if (hislen > 0) { + i = hisidx[histtype]; + if (i >= 0) { + lnum = 0; + do { + if (++i == hislen) + i = 0; + if (history[histtype][i].hisstr != NULL) + ml_append(lnum++, history[histtype][i].hisstr, + (colnr_T)0, FALSE); + } while (i != hisidx[histtype]); + } + } + + /* Replace the empty last line with the current command-line and put the + * cursor there. */ + ml_replace(curbuf->b_ml.ml_line_count, ccline.cmdbuff, TRUE); + curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count; + curwin->w_cursor.col = ccline.cmdpos; + changed_line_abv_curs(); + invalidate_botline(); + redraw_later(SOME_VALID); + + /* Save the command line info, can be used recursively. */ + save_ccline = ccline; + ccline.cmdbuff = NULL; + ccline.cmdprompt = NULL; + + /* No Ex mode here! */ + exmode_active = 0; + + State = NORMAL; + setmouse(); + + /* Trigger CmdwinEnter autocommands. */ + typestr[0] = cmdwin_type; + typestr[1] = NUL; + apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, FALSE, curbuf); + if (restart_edit != 0) /* autocmd with ":startinsert" */ + stuffcharReadbuff(K_NOP); + + i = RedrawingDisabled; + RedrawingDisabled = 0; + + /* + * Call the main loop until or CTRL-C is typed. + */ + cmdwin_result = 0; + main_loop(TRUE, FALSE); + + RedrawingDisabled = i; + + /* Trigger CmdwinLeave autocommands. */ + apply_autocmds(EVENT_CMDWINLEAVE, typestr, typestr, FALSE, curbuf); + + /* Restore the command line info. */ + ccline = save_ccline; + cmdwin_type = 0; + + exmode_active = save_exmode; + + /* Safety check: The old window or buffer was deleted: It's a bug when + * this happens! */ + if (!win_valid(old_curwin) || !buf_valid(old_curbuf)) { + cmdwin_result = Ctrl_C; + EMSG(_("E199: Active window or buffer deleted")); + } else { + /* autocmds may abort script processing */ + if (aborting() && cmdwin_result != K_IGNORE) + cmdwin_result = Ctrl_C; + /* Set the new command line from the cmdline buffer. */ + vim_free(ccline.cmdbuff); + if (cmdwin_result == K_XF1 || cmdwin_result == K_XF2) { /* :qa[!] typed */ + char *p = (cmdwin_result == K_XF2) ? "qa" : "qa!"; + + if (histtype == HIST_CMD) { + /* Execute the command directly. */ + ccline.cmdbuff = vim_strsave((char_u *)p); + cmdwin_result = CAR; + } else { + /* First need to cancel what we were doing. */ + ccline.cmdbuff = NULL; + stuffcharReadbuff(':'); + stuffReadbuff((char_u *)p); + stuffcharReadbuff(CAR); + } + } else if (cmdwin_result == K_XF2) { /* :qa typed */ + ccline.cmdbuff = vim_strsave((char_u *)"qa"); + cmdwin_result = CAR; + } else if (cmdwin_result == Ctrl_C) { + /* :q or :close, don't execute any command + * and don't modify the cmd window. */ + ccline.cmdbuff = NULL; + } else + ccline.cmdbuff = vim_strsave(ml_get_curline()); + if (ccline.cmdbuff == NULL) + cmdwin_result = Ctrl_C; + else { + ccline.cmdlen = (int)STRLEN(ccline.cmdbuff); + ccline.cmdbufflen = ccline.cmdlen + 1; + ccline.cmdpos = curwin->w_cursor.col; + if (ccline.cmdpos > ccline.cmdlen) + ccline.cmdpos = ccline.cmdlen; + if (cmdwin_result == K_IGNORE) { + set_cmdspos_cursor(); + redrawcmd(); + } + } + + /* Don't execute autocommands while deleting the window. */ + block_autocmds(); + wp = curwin; + bp = curbuf; + win_goto(old_curwin); + win_close(wp, TRUE); + + /* win_close() may have already wiped the buffer when 'bh' is + * set to 'wipe' */ + if (buf_valid(bp)) + close_buffer(NULL, bp, DOBUF_WIPE, FALSE); + + /* Restore window sizes. */ + win_size_restore(&winsizes); + + unblock_autocmds(); + } + + ga_clear(&winsizes); + restart_edit = save_restart_edit; + cmdmsg_rl = save_cmdmsg_rl; + + State = save_State; + setmouse(); + + return cmdwin_result; +} + +/* + * Used for commands that either take a simple command string argument, or: + * cmd << endmarker + * {script} + * endmarker + * Returns a pointer to allocated memory with {script} or NULL. + */ +char_u * script_get(eap, cmd) +exarg_T *eap; +char_u *cmd; +{ + char_u *theline; + char *end_pattern = NULL; + char dot[] = "."; + garray_T ga; + + if (cmd[0] != '<' || cmd[1] != '<' || eap->getline == NULL) + return NULL; + + ga_init2(&ga, 1, 0x400); + + if (cmd[2] != NUL) + end_pattern = (char *)skipwhite(cmd + 2); + else + end_pattern = dot; + + for (;; ) { + theline = eap->getline( + eap->cstack->cs_looplevel > 0 ? -1 : + NUL, eap->cookie, 0); + + if (theline == NULL || STRCMP(end_pattern, theline) == 0) { + vim_free(theline); + break; + } + + ga_concat(&ga, theline); + ga_append(&ga, '\n'); + vim_free(theline); + } + ga_append(&ga, NUL); + + return (char_u *)ga.ga_data; +} diff --git a/src/farsi.c b/src/farsi.c new file mode 100644 index 0000000000..a76f37fd26 --- /dev/null +++ b/src/farsi.c @@ -0,0 +1,2183 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * farsi.c: functions for Farsi language + * + * Included by main.c, when FEAT_FKMAP is defined. + */ + +static int toF_Xor_X_ __ARGS((int c)); +static int F_is_TyE __ARGS((int c)); +static int F_is_TyC_TyD __ARGS((int c)); +static int F_is_TyB_TyC_TyD __ARGS((int src, int offset)); +static int toF_TyB __ARGS((int c)); +static void put_curr_and_l_to_X __ARGS((int c)); +static void put_and_redo __ARGS((int c)); +static void chg_c_toX_orX __ARGS((void)); +static void chg_c_to_X_orX_ __ARGS((void)); +static void chg_c_to_X_or_X __ARGS((void)); +static void chg_l_to_X_orX_ __ARGS((void)); +static void chg_l_toXor_X __ARGS((void)); +static void chg_r_to_Xor_X_ __ARGS((void)); +static int toF_leading __ARGS((int c)); +static int toF_Rjoin __ARGS((int c)); +static int canF_Ljoin __ARGS((int c)); +static int canF_Rjoin __ARGS((int c)); +static int F_isterm __ARGS((int c)); +static int toF_ending __ARGS((int c)); +static void lrswapbuf __ARGS((char_u *buf, int len)); + +/* +** Convert the given Farsi character into a _X or _X_ type +*/ +static int toF_Xor_X_(c) +int c; +{ + int tempc; + + switch (c) { + case BE: + return _BE; + case PE: + return _PE; + case TE: + return _TE; + case SE: + return _SE; + case JIM: + return _JIM; + case CHE: + return _CHE; + case HE_J: + return _HE_J; + case XE: + return _XE; + case SIN: + return _SIN; + case SHIN: + return _SHIN; + case SAD: + return _SAD; + case ZAD: + return _ZAD; + case AYN: + return _AYN; + case AYN_: + return _AYN_; + case GHAYN: + return _GHAYN; + case GHAYN_: + return _GHAYN_; + case FE: + return _FE; + case GHAF: + return _GHAF; + case KAF: + return _KAF; + case GAF: + return _GAF; + case LAM: + return _LAM; + case MIM: + return _MIM; + case NOON: + return _NOON; + case YE: + case YE_: + return _YE; + case YEE: + case YEE_: + return _YEE; + case IE: + case IE_: + return _IE; + case F_HE: + tempc = _HE; + + if (p_ri && (curwin->w_cursor.col + 1 + < (colnr_T)STRLEN(ml_get_curline()))) { + inc_cursor(); + + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) + tempc = _HE_; + + dec_cursor(); + } + if (!p_ri && STRLEN(ml_get_curline())) { + dec_cursor(); + + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) + tempc = _HE_; + + inc_cursor(); + } + + return tempc; + } + return 0; +} + +/* +** Convert the given Farsi character into Farsi capital character . +*/ +int toF_TyA(c) +int c; +{ + switch (c) { + case ALEF_: + return ALEF; + case ALEF_U_H_: + return ALEF_U_H; + case _BE: + return BE; + case _PE: + return PE; + case _TE: + return TE; + case _SE: + return SE; + case _JIM: + return JIM; + case _CHE: + return CHE; + case _HE_J: + return HE_J; + case _XE: + return XE; + case _SIN: + return SIN; + case _SHIN: + return SHIN; + case _SAD: + return SAD; + case _ZAD: + return ZAD; + case _AYN: + case AYN_: + case _AYN_: + return AYN; + case _GHAYN: + case GHAYN_: + case _GHAYN_: + return GHAYN; + case _FE: + return FE; + case _GHAF: + return GHAF; + /* I am not sure what it is !!! case _KAF_H: */ + case _KAF: + return KAF; + case _GAF: + return GAF; + case _LAM: + return LAM; + case _MIM: + return MIM; + case _NOON: + return NOON; + case _YE: + case YE_: + return YE; + case _YEE: + case YEE_: + return YEE; + case TEE_: + return TEE; + case _IE: + case IE_: + return IE; + case _HE: + case _HE_: + return F_HE; + } + return c; +} + +/* +** Is the character under the cursor+offset in the given buffer a join type. +** That is a character that is combined with the others. +** Note: the offset is used only for command line buffer. +*/ +static int F_is_TyB_TyC_TyD(src, offset) +int src, offset; +{ + int c; + + if (src == SRC_EDT) + c = gchar_cursor(); + else + c = cmd_gchar(AT_CURSOR+offset); + + switch (c) { + case _LAM: + case _BE: + case _PE: + case _TE: + case _SE: + case _JIM: + case _CHE: + case _HE_J: + case _XE: + case _SIN: + case _SHIN: + case _SAD: + case _ZAD: + case _TA: + case _ZA: + case _AYN: + case _AYN_: + case _GHAYN: + case _GHAYN_: + case _FE: + case _GHAF: + case _KAF: + case _KAF_H: + case _GAF: + case _MIM: + case _NOON: + case _YE: + case _YEE: + case _IE: + case _HE_: + case _HE: + return TRUE; + } + return FALSE; +} + +/* +** Is the Farsi character one of the terminating only type. +*/ +static int F_is_TyE(c) +int c; +{ + switch (c) { + case ALEF_A: + case ALEF_D_H: + case DAL: + case ZAL: + case RE: + case ZE: + case JE: + case WAW: + case WAW_H: + case HAMZE: + return TRUE; + } + return FALSE; +} + +/* +** Is the Farsi character one of the none leading type. +*/ +static int F_is_TyC_TyD(c) +int c; +{ + switch (c) { + case ALEF_: + case ALEF_U_H_: + case _AYN_: + case AYN_: + case _GHAYN_: + case GHAYN_: + case _HE_: + case YE_: + case IE_: + case TEE_: + case YEE_: + return TRUE; + } + return FALSE; +} + +/* +** Convert a none leading Farsi char into a leading type. +*/ +static int toF_TyB(c) +int c; +{ + switch (c) { + case ALEF_: return ALEF; + case ALEF_U_H_: return ALEF_U_H; + case _AYN_: return _AYN; + case AYN_: return AYN; /* exception - there are many of them */ + case _GHAYN_: return _GHAYN; + case GHAYN_: return GHAYN; /* exception - there are many of them */ + case _HE_: return _HE; + case YE_: return YE; + case IE_: return IE; + case TEE_: return TEE; + case YEE_: return YEE; + } + return c; +} + +/* +** Overwrite the current redo and cursor characters + left adjust +*/ +static void put_curr_and_l_to_X(c) +int c; +{ + int tempc; + + if (curwin->w_p_rl && p_ri) + return; + + if ((curwin->w_cursor.col < (colnr_T)STRLEN(ml_get_curline()))) { + if ((p_ri && curwin->w_cursor.col) || !p_ri) { + if (p_ri) + dec_cursor(); + else + inc_cursor(); + + if (F_is_TyC_TyD((tempc = gchar_cursor()))) { + pchar_cursor(toF_TyB(tempc)); + AppendCharToRedobuff(K_BS); + AppendCharToRedobuff(tempc); + } + + if (p_ri) + inc_cursor(); + else + dec_cursor(); + } + } + + put_and_redo(c); +} + +static void put_and_redo(c) +int c; +{ + pchar_cursor(c); + AppendCharToRedobuff(K_BS); + AppendCharToRedobuff(c); +} + +/* +** Change the char. under the cursor to a X_ or X type +*/ +static void chg_c_toX_orX() { + int tempc, curc; + + switch ((curc = gchar_cursor())) { + case _BE: + tempc = BE; + break; + case _PE: + tempc = PE; + break; + case _TE: + tempc = TE; + break; + case _SE: + tempc = SE; + break; + case _JIM: + tempc = JIM; + break; + case _CHE: + tempc = CHE; + break; + case _HE_J: + tempc = HE_J; + break; + case _XE: + tempc = XE; + break; + case _SIN: + tempc = SIN; + break; + case _SHIN: + tempc = SHIN; + break; + case _SAD: + tempc = SAD; + break; + case _ZAD: + tempc = ZAD; + break; + case _FE: + tempc = FE; + break; + case _GHAF: + tempc = GHAF; + break; + case _KAF_H: + case _KAF: + tempc = KAF; + break; + case _GAF: + tempc = GAF; + break; + case _AYN: + tempc = AYN; + break; + case _AYN_: + tempc = AYN_; + break; + case _GHAYN: + tempc = GHAYN; + break; + case _GHAYN_: + tempc = GHAYN_; + break; + case _LAM: + tempc = LAM; + break; + case _MIM: + tempc = MIM; + break; + case _NOON: + tempc = NOON; + break; + case _HE: + case _HE_: + tempc = F_HE; + break; + case _YE: + case _IE: + case _YEE: + if (p_ri) { + inc_cursor(); + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) + tempc = (curc == _YE ? YE_ : + (curc == _IE ? IE_ : YEE_)); + else + tempc = (curc == _YE ? YE : + (curc == _IE ? IE : YEE)); + dec_cursor(); + } else { + if (curwin->w_cursor.col) { + dec_cursor(); + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) + tempc = (curc == _YE ? YE_ : + (curc == _IE ? IE_ : YEE_)); + else + tempc = (curc == _YE ? YE : + (curc == _IE ? IE : YEE)); + inc_cursor(); + } else + tempc = (curc == _YE ? YE : + (curc == _IE ? IE : YEE)); + } + break; + default: + tempc = 0; + } + + if (tempc) + put_and_redo(tempc); +} + +/* +** Change the char. under the cursor to a _X_ or X_ type +*/ + +static void chg_c_to_X_orX_() { + int tempc; + + switch (gchar_cursor()) { + case ALEF: + tempc = ALEF_; + break; + case ALEF_U_H: + tempc = ALEF_U_H_; + break; + case _AYN: + tempc = _AYN_; + break; + case AYN: + tempc = AYN_; + break; + case _GHAYN: + tempc = _GHAYN_; + break; + case GHAYN: + tempc = GHAYN_; + break; + case _HE: + tempc = _HE_; + break; + case YE: + tempc = YE_; + break; + case IE: + tempc = IE_; + break; + case TEE: + tempc = TEE_; + break; + case YEE: + tempc = YEE_; + break; + default: + tempc = 0; + } + + if (tempc) + put_and_redo(tempc); +} + +/* +** Change the char. under the cursor to a _X_ or _X type +*/ +static void chg_c_to_X_or_X () { + int tempc; + + tempc = gchar_cursor(); + + if (curwin->w_cursor.col + 1 < (colnr_T)STRLEN(ml_get_curline())) { + inc_cursor(); + + if ((tempc == F_HE) && (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR))) { + tempc = _HE_; + + dec_cursor(); + + put_and_redo(tempc); + return; + } + + dec_cursor(); + } + + if ((tempc = toF_Xor_X_(tempc)) != 0) + put_and_redo(tempc); +} + +/* +** Change the character left to the cursor to a _X_ or X_ type +*/ +static void chg_l_to_X_orX_ () { + int tempc; + + if (curwin->w_cursor.col != 0 && + (curwin->w_cursor.col + 1 == (colnr_T)STRLEN(ml_get_curline()))) + return; + + if (!curwin->w_cursor.col && p_ri) + return; + + if (p_ri) + dec_cursor(); + else + inc_cursor(); + + switch (gchar_cursor()) { + case ALEF: + tempc = ALEF_; + break; + case ALEF_U_H: + tempc = ALEF_U_H_; + break; + case _AYN: + tempc = _AYN_; + break; + case AYN: + tempc = AYN_; + break; + case _GHAYN: + tempc = _GHAYN_; + break; + case GHAYN: + tempc = GHAYN_; + break; + case _HE: + tempc = _HE_; + break; + case YE: + tempc = YE_; + break; + case IE: + tempc = IE_; + break; + case TEE: + tempc = TEE_; + break; + case YEE: + tempc = YEE_; + break; + default: + tempc = 0; + } + + if (tempc) + put_and_redo(tempc); + + if (p_ri) + inc_cursor(); + else + dec_cursor(); +} + +/* +** Change the character left to the cursor to a X or _X type +*/ + +static void chg_l_toXor_X () { + int tempc; + + if (curwin->w_cursor.col != 0 && + (curwin->w_cursor.col + 1 == (colnr_T)STRLEN(ml_get_curline()))) + return; + + if (!curwin->w_cursor.col && p_ri) + return; + + if (p_ri) + dec_cursor(); + else + inc_cursor(); + + switch (gchar_cursor()) { + case ALEF_: + tempc = ALEF; + break; + case ALEF_U_H_: + tempc = ALEF_U_H; + break; + case _AYN_: + tempc = _AYN; + break; + case AYN_: + tempc = AYN; + break; + case _GHAYN_: + tempc = _GHAYN; + break; + case GHAYN_: + tempc = GHAYN; + break; + case _HE_: + tempc = _HE; + break; + case YE_: + tempc = YE; + break; + case IE_: + tempc = IE; + break; + case TEE_: + tempc = TEE; + break; + case YEE_: + tempc = YEE; + break; + default: + tempc = 0; + } + + if (tempc) + put_and_redo(tempc); + + if (p_ri) + inc_cursor(); + else + dec_cursor(); +} + +/* +** Change the character right to the cursor to a _X or _X_ type +*/ + +static void chg_r_to_Xor_X_() { + int tempc, c; + + if (curwin->w_cursor.col) { + if (!p_ri) + dec_cursor(); + + tempc = gchar_cursor(); + + if ((c = toF_Xor_X_(tempc)) != 0) + put_and_redo(c); + + if (!p_ri) + inc_cursor(); + + } +} + +/* +** Map Farsi keyboard when in fkmap mode. +*/ + +int fkmap(c) +int c; +{ + int tempc; + static int revins; + + if (IS_SPECIAL(c)) + return c; + + if (VIM_ISDIGIT(c) || ((c == '.' || c == '+' || c == '-' || + c == '^' || c == '%' || c == '#' || + c == '=') && revins)) { + if (!revins) { + if (curwin->w_cursor.col) { + if (!p_ri) + dec_cursor(); + + chg_c_toX_orX (); + chg_l_toXor_X (); + + if (!p_ri) + inc_cursor(); + } + } + + arrow_used = TRUE; + (void)stop_arrow(); + + if (!curwin->w_p_rl && revins) + inc_cursor(); + + ++revins; + p_ri=1; + } else { + if (revins) { + arrow_used = TRUE; + (void)stop_arrow(); + + revins = 0; + if (curwin->w_p_rl) { + while ((F_isdigit(gchar_cursor()) + || (gchar_cursor() == F_PERIOD + || gchar_cursor() == F_PLUS + || gchar_cursor() == F_MINUS + || gchar_cursor() == F_MUL + || gchar_cursor() == F_DIVIDE + || gchar_cursor() == F_PERCENT + || gchar_cursor() == F_EQUALS)) + && gchar_cursor() != NUL) + ++curwin->w_cursor.col; + } else { + if (curwin->w_cursor.col) + while ((F_isdigit(gchar_cursor()) + || (gchar_cursor() == F_PERIOD + || gchar_cursor() == F_PLUS + || gchar_cursor() == F_MINUS + || gchar_cursor() == F_MUL + || gchar_cursor() == F_DIVIDE + || gchar_cursor() == F_PERCENT + || gchar_cursor() == F_EQUALS)) + && --curwin->w_cursor.col) + ; + + if (!F_isdigit(gchar_cursor())) + ++curwin->w_cursor.col; + } + } + } + + if (!revins) { + if (curwin->w_p_rl) + p_ri=0; + if (!curwin->w_p_rl) + p_ri=1; + } + + if ((c < 0x100) && (isalpha(c) || c == '&' || c == '^' || c == ';' || + c == '\''|| c == ',' || c == '[' || + c == ']' || c == '{' || c == '}' )) + chg_r_to_Xor_X_(); + + tempc = 0; + + switch (c) { + case '`': + case ' ': + case '.': + case '!': + case '"': + case '$': + case '%': + case '^': + case '&': + case '/': + case '(': + case ')': + case '=': + case '\\': + case '?': + case '+': + case '-': + case '_': + case '*': + case ':': + case '#': + case '~': + case '@': + case '<': + case '>': + case '{': + case '}': + case '|': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case 'B': + case 'E': + case 'F': + case 'H': + case 'I': + case 'K': + case 'L': + case 'M': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'T': + case 'U': + case 'W': + case 'Y': + case NL: + case TAB: + + if (p_ri && c == NL && curwin->w_cursor.col) { + /* + ** If the char before the cursor is _X_ or X_ do not change + ** the one under the cursor with X type. + */ + + dec_cursor(); + + if (F_isalpha(gchar_cursor())) { + inc_cursor(); + return NL; + } + + inc_cursor(); + } + + if (!p_ri) + if (!curwin->w_cursor.col) { + switch (c) { + case '0': return FARSI_0; + case '1': return FARSI_1; + case '2': return FARSI_2; + case '3': return FARSI_3; + case '4': return FARSI_4; + case '5': return FARSI_5; + case '6': return FARSI_6; + case '7': return FARSI_7; + case '8': return FARSI_8; + case '9': return FARSI_9; + case 'B': return F_PSP; + case 'E': return JAZR_N; + case 'F': return ALEF_D_H; + case 'H': return ALEF_A; + case 'I': return TASH; + case 'K': return F_LQUOT; + case 'L': return F_RQUOT; + case 'M': return HAMZE; + case 'O': return '['; + case 'P': return ']'; + case 'Q': return OO; + case 'R': return MAD_N; + case 'T': return OW; + case 'U': return MAD; + case 'W': return OW_OW; + case 'Y': return JAZR; + case '`': return F_PCN; + case '!': return F_EXCL; + case '@': return F_COMMA; + case '#': return F_DIVIDE; + case '$': return F_CURRENCY; + case '%': return F_PERCENT; + case '^': return F_MUL; + case '&': return F_BCOMMA; + case '*': return F_STAR; + case '(': return F_LPARENT; + case ')': return F_RPARENT; + case '-': return F_MINUS; + case '_': return F_UNDERLINE; + case '=': return F_EQUALS; + case '+': return F_PLUS; + case '\\': return F_BSLASH; + case '|': return F_PIPE; + case ':': return F_DCOLON; + case '"': return F_SEMICOLON; + case '.': return F_PERIOD; + case '/': return F_SLASH; + case '<': return F_LESS; + case '>': return F_GREATER; + case '?': return F_QUESTION; + case ' ': return F_BLANK; + } + break; + } + if (!p_ri) + dec_cursor(); + + switch ((tempc = gchar_cursor())) { + case _BE: + case _PE: + case _TE: + case _SE: + case _JIM: + case _CHE: + case _HE_J: + case _XE: + case _SIN: + case _SHIN: + case _SAD: + case _ZAD: + case _FE: + case _GHAF: + case _KAF: + case _KAF_H: + case _GAF: + case _LAM: + case _MIM: + case _NOON: + case _HE: + case _HE_: + case _TA: + case _ZA: + put_curr_and_l_to_X(toF_TyA(tempc)); + break; + case _AYN: + case _AYN_: + + if (!p_ri) + if (!curwin->w_cursor.col) { + put_curr_and_l_to_X(AYN); + break; + } + + if (p_ri) + inc_cursor(); + else + dec_cursor(); + + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) + tempc = AYN_; + else + tempc = AYN; + + if (p_ri) + dec_cursor(); + else + inc_cursor(); + + put_curr_and_l_to_X(tempc); + + break; + case _GHAYN: + case _GHAYN_: + + if (!p_ri) + if (!curwin->w_cursor.col) { + put_curr_and_l_to_X(GHAYN); + break; + } + + if (p_ri) + inc_cursor(); + else + dec_cursor(); + + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) + tempc = GHAYN_; + else + tempc = GHAYN; + + if (p_ri) + dec_cursor(); + else + inc_cursor(); + + put_curr_and_l_to_X(tempc); + break; + case _YE: + case _IE: + case _YEE: + if (!p_ri) + if (!curwin->w_cursor.col) { + put_curr_and_l_to_X((tempc == _YE ? YE : + (tempc == _IE ? IE : YEE))); + break; + } + + if (p_ri) + inc_cursor(); + else + dec_cursor(); + + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) + tempc = (tempc == _YE ? YE_ : + (tempc == _IE ? IE_ : YEE_)); + else + tempc = (tempc == _YE ? YE : + (tempc == _IE ? IE : YEE)); + + if (p_ri) + dec_cursor(); + else + inc_cursor(); + + put_curr_and_l_to_X(tempc); + break; + } + + if (!p_ri) + inc_cursor(); + + tempc = 0; + + switch (c) { + case '0': return FARSI_0; + case '1': return FARSI_1; + case '2': return FARSI_2; + case '3': return FARSI_3; + case '4': return FARSI_4; + case '5': return FARSI_5; + case '6': return FARSI_6; + case '7': return FARSI_7; + case '8': return FARSI_8; + case '9': return FARSI_9; + case 'B': return F_PSP; + case 'E': return JAZR_N; + case 'F': return ALEF_D_H; + case 'H': return ALEF_A; + case 'I': return TASH; + case 'K': return F_LQUOT; + case 'L': return F_RQUOT; + case 'M': return HAMZE; + case 'O': return '['; + case 'P': return ']'; + case 'Q': return OO; + case 'R': return MAD_N; + case 'T': return OW; + case 'U': return MAD; + case 'W': return OW_OW; + case 'Y': return JAZR; + case '`': return F_PCN; + case '!': return F_EXCL; + case '@': return F_COMMA; + case '#': return F_DIVIDE; + case '$': return F_CURRENCY; + case '%': return F_PERCENT; + case '^': return F_MUL; + case '&': return F_BCOMMA; + case '*': return F_STAR; + case '(': return F_LPARENT; + case ')': return F_RPARENT; + case '-': return F_MINUS; + case '_': return F_UNDERLINE; + case '=': return F_EQUALS; + case '+': return F_PLUS; + case '\\': return F_BSLASH; + case '|': return F_PIPE; + case ':': return F_DCOLON; + case '"': return F_SEMICOLON; + case '.': return F_PERIOD; + case '/': return F_SLASH; + case '<': return F_LESS; + case '>': return F_GREATER; + case '?': return F_QUESTION; + case ' ': return F_BLANK; + } + break; + + case 'a': + tempc = _SHIN; + break; + case 'A': + tempc = WAW_H; + break; + case 'b': + tempc = ZAL; + break; + case 'c': + tempc = ZE; + break; + case 'C': + tempc = JE; + break; + case 'd': + tempc = _YE; + break; + case 'D': + tempc = _YEE; + break; + case 'e': + tempc = _SE; + break; + case 'f': + tempc = _BE; + break; + case 'g': + tempc = _LAM; + break; + case 'G': + if (!curwin->w_cursor.col && STRLEN(ml_get_curline())) { + + if (gchar_cursor() == _LAM) + chg_c_toX_orX (); + else if (p_ri) + chg_c_to_X_or_X (); + } + + if (!p_ri) + if (!curwin->w_cursor.col) + return ALEF_U_H; + + if (!p_ri) + dec_cursor(); + + if (gchar_cursor() == _LAM) { + chg_c_toX_orX (); + chg_l_toXor_X (); + tempc = ALEF_U_H; + } else if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) { + tempc = ALEF_U_H_; + chg_l_toXor_X (); + } else + tempc = ALEF_U_H; + + if (!p_ri) + inc_cursor(); + + return tempc; + case 'h': + if (!curwin->w_cursor.col && STRLEN(ml_get_curline())) { + if (p_ri) + chg_c_to_X_or_X (); + + } + + if (!p_ri) + if (!curwin->w_cursor.col) + return ALEF; + + if (!p_ri) + dec_cursor(); + + if (gchar_cursor() == _LAM) { + chg_l_toXor_X(); + del_char(FALSE); + AppendCharToRedobuff(K_BS); + + if (!p_ri) + dec_cursor(); + + tempc = LA; + } else { + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) { + tempc = ALEF_; + chg_l_toXor_X (); + } else + tempc = ALEF; + } + + if (!p_ri) + inc_cursor(); + + return tempc; + case 'i': + if (!curwin->w_cursor.col && STRLEN(ml_get_curline())) { + if (!p_ri && !F_is_TyE(tempc)) + chg_c_to_X_orX_ (); + if (p_ri) + chg_c_to_X_or_X (); + + } + + if (!p_ri && !curwin->w_cursor.col) + return _HE; + + if (!p_ri) + dec_cursor(); + + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) + tempc = _HE_; + else + tempc = _HE; + + if (!p_ri) + inc_cursor(); + break; + case 'j': + tempc = _TE; + break; + case 'J': + if (!curwin->w_cursor.col && STRLEN(ml_get_curline())) { + if (p_ri) + chg_c_to_X_or_X (); + + } + + if (!p_ri) + if (!curwin->w_cursor.col) + return TEE; + + if (!p_ri) + dec_cursor(); + + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) { + tempc = TEE_; + chg_l_toXor_X (); + } else + tempc = TEE; + + if (!p_ri) + inc_cursor(); + + return tempc; + case 'k': + tempc = _NOON; + break; + case 'l': + tempc = _MIM; + break; + case 'm': + tempc = _PE; + break; + case 'n': + case 'N': + tempc = DAL; + break; + case 'o': + tempc = _XE; + break; + case 'p': + tempc = _HE_J; + break; + case 'q': + tempc = _ZAD; + break; + case 'r': + tempc = _GHAF; + break; + case 's': + tempc = _SIN; + break; + case 'S': + tempc = _IE; + break; + case 't': + tempc = _FE; + break; + case 'u': + if (!curwin->w_cursor.col && STRLEN(ml_get_curline())) { + if (!p_ri && !F_is_TyE(tempc)) + chg_c_to_X_orX_ (); + if (p_ri) + chg_c_to_X_or_X (); + + } + + if (!p_ri && !curwin->w_cursor.col) + return _AYN; + + if (!p_ri) + dec_cursor(); + + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) + tempc = _AYN_; + else + tempc = _AYN; + + if (!p_ri) + inc_cursor(); + break; + case 'v': + case 'V': + tempc = RE; + break; + case 'w': + tempc = _SAD; + break; + case 'x': + case 'X': + tempc = _TA; + break; + case 'y': + if (!curwin->w_cursor.col && STRLEN(ml_get_curline())) { + if (!p_ri && !F_is_TyE(tempc)) + chg_c_to_X_orX_ (); + if (p_ri) + chg_c_to_X_or_X (); + + } + + if (!p_ri && !curwin->w_cursor.col) + return _GHAYN; + + if (!p_ri) + dec_cursor(); + + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) + tempc = _GHAYN_; + else + tempc = _GHAYN; + + if (!p_ri) + inc_cursor(); + + break; + case 'z': + tempc = _ZA; + break; + case 'Z': + tempc = _KAF_H; + break; + case ';': + tempc = _KAF; + break; + case '\'': + tempc = _GAF; + break; + case ',': + tempc = WAW; + break; + case '[': + tempc = _JIM; + break; + case ']': + tempc = _CHE; + break; + } + + if ((F_isalpha(tempc) || F_isdigit(tempc))) { + if (!curwin->w_cursor.col && STRLEN(ml_get_curline())) { + if (!p_ri && !F_is_TyE(tempc)) + chg_c_to_X_orX_ (); + if (p_ri) + chg_c_to_X_or_X (); + } + + if (curwin->w_cursor.col) { + if (!p_ri) + dec_cursor(); + + if (F_is_TyE(tempc)) + chg_l_toXor_X (); + else + chg_l_to_X_orX_ (); + + if (!p_ri) + inc_cursor(); + } + } + if (tempc) + return tempc; + return c; +} + +/* +** Convert a none leading Farsi char into a leading type. +*/ +static int toF_leading(c) +int c; +{ + switch (c) { + case ALEF_: return ALEF; + case ALEF_U_H_: return ALEF_U_H; + case BE: return _BE; + case PE: return _PE; + case TE: return _TE; + case SE: return _SE; + case JIM: return _JIM; + case CHE: return _CHE; + case HE_J: return _HE_J; + case XE: return _XE; + case SIN: return _SIN; + case SHIN: return _SHIN; + case SAD: return _SAD; + case ZAD: return _ZAD; + + case AYN: + case AYN_: + case _AYN_: return _AYN; + + case GHAYN: + case GHAYN_: + case _GHAYN_: return _GHAYN; + + case FE: return _FE; + case GHAF: return _GHAF; + case KAF: return _KAF; + case GAF: return _GAF; + case LAM: return _LAM; + case MIM: return _MIM; + case NOON: return _NOON; + + case _HE_: + case F_HE: return _HE; + + case YE: + case YE_: return _YE; + + case IE_: + case IE: return _IE; + + case YEE: + case YEE_: return _YEE; + } + return c; +} + +/* +** Convert a given Farsi char into right joining type. +*/ +static int toF_Rjoin(c) +int c; +{ + switch (c) { + case ALEF: return ALEF_; + case ALEF_U_H: return ALEF_U_H_; + case BE: return _BE; + case PE: return _PE; + case TE: return _TE; + case SE: return _SE; + case JIM: return _JIM; + case CHE: return _CHE; + case HE_J: return _HE_J; + case XE: return _XE; + case SIN: return _SIN; + case SHIN: return _SHIN; + case SAD: return _SAD; + case ZAD: return _ZAD; + + case AYN: + case AYN_: + case _AYN: return _AYN_; + + case GHAYN: + case GHAYN_: + case _GHAYN_: return _GHAYN_; + + case FE: return _FE; + case GHAF: return _GHAF; + case KAF: return _KAF; + case GAF: return _GAF; + case LAM: return _LAM; + case MIM: return _MIM; + case NOON: return _NOON; + + case _HE: + case F_HE: return _HE_; + + case YE: + case YE_: return _YE; + + case IE_: + case IE: return _IE; + + case TEE: return TEE_; + + case YEE: + case YEE_: return _YEE; + } + return c; +} + +/* +** Can a given Farsi character join via its left edj. +*/ +static int canF_Ljoin(c) +int c; +{ + switch (c) { + case _BE: + case BE: + case PE: + case _PE: + case TE: + case _TE: + case SE: + case _SE: + case JIM: + case _JIM: + case CHE: + case _CHE: + case HE_J: + case _HE_J: + case XE: + case _XE: + case SIN: + case _SIN: + case SHIN: + case _SHIN: + case SAD: + case _SAD: + case ZAD: + case _ZAD: + case _TA: + case _ZA: + case AYN: + case _AYN: + case _AYN_: + case AYN_: + case GHAYN: + case GHAYN_: + case _GHAYN_: + case _GHAYN: + case FE: + case _FE: + case GHAF: + case _GHAF: + case _KAF_H: + case KAF: + case _KAF: + case GAF: + case _GAF: + case LAM: + case _LAM: + case MIM: + case _MIM: + case NOON: + case _NOON: + case IE: + case _IE: + case IE_: + case YE: + case _YE: + case YE_: + case YEE: + case _YEE: + case YEE_: + case F_HE: + case _HE: + case _HE_: + return TRUE; + } + return FALSE; +} + +/* +** Can a given Farsi character join via its right edj. +*/ +static int canF_Rjoin(c) +int c; +{ + switch (c) { + case ALEF: + case ALEF_: + case ALEF_U_H: + case ALEF_U_H_: + case DAL: + case ZAL: + case RE: + case JE: + case ZE: + case TEE: + case TEE_: + case WAW: + case WAW_H: + return TRUE; + } + + return canF_Ljoin(c); + +} + +/* +** is a given Farsi character a terminating type. +*/ +static int F_isterm(c) +int c; +{ + switch (c) { + case ALEF: + case ALEF_: + case ALEF_U_H: + case ALEF_U_H_: + case DAL: + case ZAL: + case RE: + case JE: + case ZE: + case WAW: + case WAW_H: + case TEE: + case TEE_: + return TRUE; + } + + return FALSE; +} + +/* +** Convert the given Farsi character into a ending type . +*/ +static int toF_ending(c) +int c; +{ + + switch (c) { + case _BE: + return BE; + case _PE: + return PE; + case _TE: + return TE; + case _SE: + return SE; + case _JIM: + return JIM; + case _CHE: + return CHE; + case _HE_J: + return HE_J; + case _XE: + return XE; + case _SIN: + return SIN; + case _SHIN: + return SHIN; + case _SAD: + return SAD; + case _ZAD: + return ZAD; + case _AYN: + return AYN; + case _AYN_: + return AYN_; + case _GHAYN: + return GHAYN; + case _GHAYN_: + return GHAYN_; + case _FE: + return FE; + case _GHAF: + return GHAF; + case _KAF_H: + case _KAF: + return KAF; + case _GAF: + return GAF; + case _LAM: + return LAM; + case _MIM: + return MIM; + case _NOON: + return NOON; + case _YE: + return YE_; + case YE_: + return YE; + case _YEE: + return YEE_; + case YEE_: + return YEE; + case TEE: + return TEE_; + case _IE: + return IE_; + case IE_: + return IE; + case _HE: + case _HE_: + return F_HE; + } + return c; +} + +/* +** Convert the Farsi 3342 standard into Farsi VIM. +*/ +void conv_to_pvim() { + char_u *ptr; + int lnum, llen, i; + + for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) { + ptr = ml_get((linenr_T)lnum); + + llen = (int)STRLEN(ptr); + + for ( i = 0; i < llen-1; i++) { + if (canF_Ljoin(ptr[i]) && canF_Rjoin(ptr[i+1])) { + ptr[i] = toF_leading(ptr[i]); + ++i; + + while (canF_Rjoin(ptr[i]) && i < llen) { + ptr[i] = toF_Rjoin(ptr[i]); + if (F_isterm(ptr[i]) || !F_isalpha(ptr[i])) + break; + ++i; + } + if (!F_isalpha(ptr[i]) || !canF_Rjoin(ptr[i])) + ptr[i-1] = toF_ending(ptr[i-1]); + } else + ptr[i] = toF_TyA(ptr[i]); + } + } + + /* + * Following lines contains Farsi encoded character. + */ + + do_cmdline_cmd((char_u *)"%s/\202\231/\232/g"); + do_cmdline_cmd((char_u *)"%s/\201\231/\370\334/g"); + + /* Assume the screen has been messed up: clear it and redraw. */ + redraw_later(CLEAR); + MSG_ATTR(farsi_text_1, hl_attr(HLF_S)); +} + +/* + * Convert the Farsi VIM into Farsi 3342 standard. + */ +void conv_to_pstd() { + char_u *ptr; + int lnum, llen, i; + + /* + * Following line contains Farsi encoded character. + */ + + do_cmdline_cmd((char_u *)"%s/\232/\202\231/g"); + + for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) { + ptr = ml_get((linenr_T)lnum); + + llen = (int)STRLEN(ptr); + + for ( i = 0; i < llen; i++) { + ptr[i] = toF_TyA(ptr[i]); + + } + } + + /* Assume the screen has been messed up: clear it and redraw. */ + redraw_later(CLEAR); + MSG_ATTR(farsi_text_2, hl_attr(HLF_S)); +} + +/* + * left-right swap the characters in buf[len]. + */ +static void lrswapbuf(buf, len) +char_u *buf; +int len; +{ + char_u *s, *e; + int c; + + s = buf; + e = buf + len - 1; + + while (e > s) { + c = *s; + *s = *e; + *e = c; + ++s; + --e; + } +} + +/* + * swap all the characters in reverse direction + */ +char_u * lrswap(ibuf) +char_u *ibuf; +{ + if (ibuf != NULL && *ibuf != NUL) + lrswapbuf(ibuf, (int)STRLEN(ibuf)); + return ibuf; +} + +/* + * swap all the Farsi characters in reverse direction + */ +char_u * lrFswap(cmdbuf, len) +char_u *cmdbuf; +int len; +{ + int i, cnt; + + if (cmdbuf == NULL) + return cmdbuf; + + if (len == 0 && (len = (int)STRLEN(cmdbuf)) == 0) + return cmdbuf; + + for (i = 0; i < len; i++) { + for (cnt = 0; i + cnt < len + && (F_isalpha(cmdbuf[i + cnt]) + || F_isdigit(cmdbuf[i + cnt]) + || cmdbuf[i + cnt] == ' '); ++cnt) + ; + + lrswapbuf(cmdbuf + i, cnt); + i += cnt; + } + return cmdbuf; +} + +/* + * Reverse the characters in the search path and substitute section + * accordingly. + * TODO: handle different separator characters. Use skip_regexp(). + */ +char_u * lrF_sub(ibuf) +char_u *ibuf; +{ + char_u *p, *ep; + int i, cnt; + + p = ibuf; + + /* Find the boundary of the search path */ + while (((p = vim_strchr(p + 1, '/')) != NULL) && p[-1] == '\\') + ; + + if (p == NULL) + return ibuf; + + /* Reverse the Farsi characters in the search path. */ + lrFswap(ibuf, (int)(p-ibuf)); + + /* Now find the boundary of the substitute section */ + if ((ep = (char_u *)strrchr((char *)++p, '/')) != NULL) + cnt = (int)(ep - p); + else + cnt = (int)STRLEN(p); + + /* Reverse the characters in the substitute section and take care of '\' */ + for (i = 0; i < cnt-1; i++) + if (p[i] == '\\') { + p[i] = p[i+1]; + p[++i] = '\\'; + } + + lrswapbuf(p, cnt); + + return ibuf; +} + +/* + * Map Farsi keyboard when in cmd_fkmap mode. + */ +int cmdl_fkmap(c) +int c; +{ + int tempc; + + switch (c) { + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '`': + case ' ': + case '.': + case '!': + case '"': + case '$': + case '%': + case '^': + case '&': + case '/': + case '(': + case ')': + case '=': + case '\\': + case '?': + case '+': + case '-': + case '_': + case '*': + case ':': + case '#': + case '~': + case '@': + case '<': + case '>': + case '{': + case '}': + case '|': + case 'B': + case 'E': + case 'F': + case 'H': + case 'I': + case 'K': + case 'L': + case 'M': + case 'O': + case 'P': + case 'Q': + case 'R': + case 'T': + case 'U': + case 'W': + case 'Y': + case NL: + case TAB: + + switch ((tempc = cmd_gchar(AT_CURSOR))) { + case _BE: + case _PE: + case _TE: + case _SE: + case _JIM: + case _CHE: + case _HE_J: + case _XE: + case _SIN: + case _SHIN: + case _SAD: + case _ZAD: + case _AYN: + case _GHAYN: + case _FE: + case _GHAF: + case _KAF: + case _GAF: + case _LAM: + case _MIM: + case _NOON: + case _HE: + case _HE_: + cmd_pchar(toF_TyA(tempc), AT_CURSOR); + break; + case _AYN_: + cmd_pchar(AYN_, AT_CURSOR); + break; + case _GHAYN_: + cmd_pchar(GHAYN_, AT_CURSOR); + break; + case _IE: + if (F_is_TyB_TyC_TyD(SRC_CMD, AT_CURSOR+1)) + cmd_pchar(IE_, AT_CURSOR); + else + cmd_pchar(IE, AT_CURSOR); + break; + case _YEE: + if (F_is_TyB_TyC_TyD(SRC_CMD, AT_CURSOR+1)) + cmd_pchar(YEE_, AT_CURSOR); + else + cmd_pchar(YEE, AT_CURSOR); + break; + case _YE: + if (F_is_TyB_TyC_TyD(SRC_CMD, AT_CURSOR+1)) + cmd_pchar(YE_, AT_CURSOR); + else + cmd_pchar(YE, AT_CURSOR); + } + + switch (c) { + case '0': return FARSI_0; + case '1': return FARSI_1; + case '2': return FARSI_2; + case '3': return FARSI_3; + case '4': return FARSI_4; + case '5': return FARSI_5; + case '6': return FARSI_6; + case '7': return FARSI_7; + case '8': return FARSI_8; + case '9': return FARSI_9; + case 'B': return F_PSP; + case 'E': return JAZR_N; + case 'F': return ALEF_D_H; + case 'H': return ALEF_A; + case 'I': return TASH; + case 'K': return F_LQUOT; + case 'L': return F_RQUOT; + case 'M': return HAMZE; + case 'O': return '['; + case 'P': return ']'; + case 'Q': return OO; + case 'R': return MAD_N; + case 'T': return OW; + case 'U': return MAD; + case 'W': return OW_OW; + case 'Y': return JAZR; + case '`': return F_PCN; + case '!': return F_EXCL; + case '@': return F_COMMA; + case '#': return F_DIVIDE; + case '$': return F_CURRENCY; + case '%': return F_PERCENT; + case '^': return F_MUL; + case '&': return F_BCOMMA; + case '*': return F_STAR; + case '(': return F_LPARENT; + case ')': return F_RPARENT; + case '-': return F_MINUS; + case '_': return F_UNDERLINE; + case '=': return F_EQUALS; + case '+': return F_PLUS; + case '\\': return F_BSLASH; + case '|': return F_PIPE; + case ':': return F_DCOLON; + case '"': return F_SEMICOLON; + case '.': return F_PERIOD; + case '/': return F_SLASH; + case '<': return F_LESS; + case '>': return F_GREATER; + case '?': return F_QUESTION; + case ' ': return F_BLANK; + } + + break; + + case 'a': return _SHIN; + case 'A': return WAW_H; + case 'b': return ZAL; + case 'c': return ZE; + case 'C': return JE; + case 'd': return _YE; + case 'D': return _YEE; + case 'e': return _SE; + case 'f': return _BE; + case 'g': return _LAM; + case 'G': + if (cmd_gchar(AT_CURSOR) == _LAM ) { + cmd_pchar(LAM, AT_CURSOR); + return ALEF_U_H; + } + + if (F_is_TyB_TyC_TyD(SRC_CMD, AT_CURSOR)) + return ALEF_U_H_; + else + return ALEF_U_H; + case 'h': + if (cmd_gchar(AT_CURSOR) == _LAM ) { + cmd_pchar(LA, AT_CURSOR); + redrawcmdline(); + return K_IGNORE; + } + + if (F_is_TyB_TyC_TyD(SRC_CMD, AT_CURSOR)) + return ALEF_; + else + return ALEF; + case 'i': + if (F_is_TyB_TyC_TyD(SRC_CMD, AT_CURSOR)) + return _HE_; + else + return _HE; + case 'j': return _TE; + case 'J': + if (F_is_TyB_TyC_TyD(SRC_CMD, AT_CURSOR)) + return TEE_; + else + return TEE; + case 'k': return _NOON; + case 'l': return _MIM; + case 'm': return _PE; + case 'n': + case 'N': return DAL; + case 'o': return _XE; + case 'p': return _HE_J; + case 'q': return _ZAD; + case 'r': return _GHAF; + case 's': return _SIN; + case 'S': return _IE; + case 't': return _FE; + case 'u': + if (F_is_TyB_TyC_TyD(SRC_CMD, AT_CURSOR)) + return _AYN_; + else + return _AYN; + case 'v': + case 'V': return RE; + case 'w': return _SAD; + case 'x': + case 'X': return _TA; + case 'y': + if (F_is_TyB_TyC_TyD(SRC_CMD, AT_CURSOR)) + return _GHAYN_; + else + return _GHAYN; + case 'z': + case 'Z': return _ZA; + case ';': return _KAF; + case '\'': return _GAF; + case ',': return WAW; + case '[': return _JIM; + case ']': return _CHE; + } + + return c; +} + +/* + * F_isalpha returns TRUE if 'c' is a Farsi alphabet + */ +int F_isalpha(c) +int c; +{ + return ( c >= TEE_ && c <= _YE) + || (c >= ALEF_A && c <= YE) + || (c >= _IE && c <= YE_); +} + +/* + * F_isdigit returns TRUE if 'c' is a Farsi digit + */ +int F_isdigit(c) +int c; +{ + return c >= FARSI_0 && c <= FARSI_9; +} + +/* + * F_ischar returns TRUE if 'c' is a Farsi character. + */ +int F_ischar(c) +int c; +{ + return c >= TEE_ && c <= YE_; +} + +void farsi_fkey(cap) +cmdarg_T *cap; +{ + int c = cap->cmdchar; + + if (c == K_F8) { + if (p_altkeymap) { + if (curwin->w_farsi & W_R_L) { + p_fkmap = 0; + do_cmdline_cmd((char_u *)"set norl"); + MSG(""); + } else { + p_fkmap = 1; + do_cmdline_cmd((char_u *)"set rl"); + MSG(""); + } + + curwin->w_farsi = curwin->w_farsi ^ W_R_L; + } + } + + if (c == K_F9) { + if (p_altkeymap && curwin->w_p_rl) { + curwin->w_farsi = curwin->w_farsi ^ W_CONV; + if (curwin->w_farsi & W_CONV) + conv_to_pvim(); + else + conv_to_pstd(); + } + } +} diff --git a/src/farsi.h b/src/farsi.h new file mode 100644 index 0000000000..a9dd2fb79f --- /dev/null +++ b/src/farsi.h @@ -0,0 +1,226 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + */ + +/* + * Farsi characters are categorized into following types: + * + * TyA (for capital letter representation) + * TyB (for types that look like _X e.g. AYN) + * TyC (for types that look like X_ e.g. YE_) + * TyD (for types that look like _X_ e.g. _AYN_) + * TyE (for types that look like X e.g. RE) + */ + +/* + * Farsi character set definition + */ + +/* + * Begin of the non-standard part + */ + +#define TEE_ 0x80 +#define ALEF_U_H_ 0x81 +#define ALEF_ 0x82 +#define _BE 0x83 +#define _PE 0x84 +#define _TE 0x85 +#define _SE 0x86 +#define _JIM 0x87 +#define _CHE 0x88 +#define _HE_J 0x89 +#define _XE 0x8a +#define _SIN 0x8b +#define _SHIN 0x8c +#define _SAD 0x8d +#define _ZAD 0x8e +#define _AYN 0x8f +#define _AYN_ 0x90 +#define AYN_ 0x91 +#define _GHAYN 0x92 +#define _GHAYN_ 0x93 +#define GHAYN_ 0x94 +#define _FE 0x95 +#define _GHAF 0x96 +#define _KAF 0x97 +#define _GAF 0x98 +#define _LAM 0x99 +#define LA 0x9a +#define _MIM 0x9b +#define _NOON 0x9c +#define _HE 0x9d +#define _HE_ 0x9e +#define _YE 0x9f +#define _IE 0xec +#define IE_ 0xed +#define IE 0xfb +#define _YEE 0xee +#define YEE_ 0xef +#define YE_ 0xff + +/* + * End of the non-standard part + */ + +/* + * Standard part + */ + +#define F_BLANK 0xa0 /* Farsi ' ' (SP) character */ +#define F_PSP 0xa1 /* PSP for capitalizing of a character */ +#define F_PCN 0xa2 /* PCN for redefining of the hamye meaning */ +#define F_EXCL 0xa3 /* Farsi ! character */ +#define F_CURRENCY 0xa4 /* Farsi Rial character */ +#define F_PERCENT 0xa5 /* Farsi % character */ +#define F_PERIOD 0xa6 /* Farsi '.' character */ +#define F_COMMA 0xa7 /* Farsi ',' character */ +#define F_LPARENT 0xa8 /* Farsi '(' character */ +#define F_RPARENT 0xa9 /* Farsi ')' character */ +#define F_MUL 0xaa /* Farsi 'x' character */ +#define F_PLUS 0xab /* Farsi '+' character */ +#define F_BCOMMA 0xac /* Farsi comma character */ +#define F_MINUS 0xad /* Farsi '-' character */ +#define F_DIVIDE 0xae /* Farsi divide (/) character */ +#define F_SLASH 0xaf /* Farsi '/' character */ + +#define FARSI_0 0xb0 +#define FARSI_1 0xb1 +#define FARSI_2 0xb2 +#define FARSI_3 0xb3 +#define FARSI_4 0xb4 +#define FARSI_5 0xb5 +#define FARSI_6 0xb6 +#define FARSI_7 0xb7 +#define FARSI_8 0xb8 +#define FARSI_9 0xb9 + +#define F_DCOLON 0xba /* Farsi ':' character */ +#define F_SEMICOLON 0xbb /* Farsi ';' character */ +#define F_GREATER 0xbc /* Farsi '>' character */ +#define F_EQUALS 0xbd /* Farsi '=' character */ +#define F_LESS 0xbe /* Farsi '<' character */ +#define F_QUESTION 0xbf /* Farsi ? character */ + +#define ALEF_A 0xc0 +#define ALEF 0xc1 +#define HAMZE 0xc2 +#define BE 0xc3 +#define PE 0xc4 +#define TE 0xc5 +#define SE 0xc6 +#define JIM 0xc7 +#define CHE 0xc8 +#define HE_J 0xc9 +#define XE 0xca +#define DAL 0xcb +#define ZAL 0xcc +#define RE 0xcd +#define ZE 0xce +#define JE 0xcf +#define SIN 0xd0 +#define SHIN 0xd1 +#define SAD 0xd2 +#define ZAD 0xd3 +#define _TA 0xd4 +#define _ZA 0xd5 +#define AYN 0xd6 +#define GHAYN 0xd7 +#define FE 0xd8 +#define GHAF 0xd9 +#define KAF 0xda +#define GAF 0xdb +#define LAM 0xdc +#define MIM 0xdd +#define NOON 0xde +#define WAW 0xdf +#define F_HE 0xe0 /* F_ added for name clash with Perl */ +#define YE 0xe1 +#define TEE 0xfc +#define _KAF_H 0xfd +#define YEE 0xfe + +#define F_LBRACK 0xe2 /* Farsi '[' character */ +#define F_RBRACK 0xe3 /* Farsi ']' character */ +#define F_LBRACE 0xe4 /* Farsi '{' character */ +#define F_RBRACE 0xe5 /* Farsi '}' character */ +#define F_LQUOT 0xe6 /* Farsi left quotation character */ +#define F_RQUOT 0xe7 /* Farsi right quotation character */ +#define F_STAR 0xe8 /* Farsi '*' character */ +#define F_UNDERLINE 0xe9 /* Farsi '_' character */ +#define F_PIPE 0xea /* Farsi '|' character */ +#define F_BSLASH 0xeb /* Farsi '\' character */ + +#define MAD 0xf0 +#define JAZR 0xf1 +#define OW 0xf2 +#define MAD_N 0xf3 +#define JAZR_N 0xf4 +#define OW_OW 0xf5 +#define TASH 0xf6 +#define OO 0xf7 +#define ALEF_U_H 0xf8 +#define WAW_H 0xf9 +#define ALEF_D_H 0xfa + +/* + * global definitions + * ================== + */ + +#define SRC_EDT 0 +#define SRC_CMD 1 + +#define AT_CURSOR 0 + +/* + * definitions for the window dependent functions (w_farsi). + */ +#define W_CONV 0x1 +#define W_R_L 0x2 + + +/* special Farsi text messages */ + +EXTERN char_u farsi_text_1[] +#ifdef DO_INIT + = { YE_, _SIN, RE, ALEF_, _FE, ' ', 'V', 'I', 'M', + ' ', F_HE, _BE, ' ', SHIN, RE, _GAF, DAL,' ', NOON, + ALEF_, _YE, ALEF_, _PE, '\0'} + +#endif +; + +EXTERN char_u farsi_text_2[] +#ifdef DO_INIT + = { YE_, _SIN, RE, ALEF_, _FE, ' ', FARSI_3, FARSI_3, + FARSI_4, FARSI_2, ' ', DAL, RE, ALEF, DAL, _NOON, + ALEF_, _TE, _SIN, ALEF, ' ', F_HE, _BE, ' ', SHIN, + RE, _GAF, DAL, ' ', NOON, ALEF_, _YE, ALEF_, _PE, '\0'} + +#endif +; + +EXTERN char_u farsi_text_3[] +#ifdef DO_INIT + = { DAL, WAW, _SHIN, _YE, _MIM, _NOON, ' ', YE_, _NOON, + ALEF_,_BE, _YE, _TE, _SHIN, _PE, ' ', 'R','E','P','L', + 'A','C','E', ' ', NOON, ALEF_, _MIM, RE, _FE, ZE, ALEF, + ' ', 'R', 'E', 'V', 'E', 'R', 'S', 'E', ' ', 'I', 'N', + 'S', 'E', 'R', 'T', ' ', SHIN, WAW, RE, ' ', ALEF_, _BE, + ' ', YE_, _SIN, RE, ALEF_, _FE, ' ', RE, DAL, ' ', RE, + ALEF_, _KAF,' ', MIM, ALEF_, _GAF, _NOON, _HE, '\0'} + +#endif +; + + +EXTERN char_u farsi_text_5[] +#ifdef DO_INIT + = { ' ', YE_, _SIN, RE, ALEF_, _FE, '\0'} +#endif +; diff --git a/src/fileio.c b/src/fileio.c new file mode 100644 index 0000000000..d05181e654 --- /dev/null +++ b/src/fileio.c @@ -0,0 +1,8496 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * fileio.c: read from and write to a file + */ + +#include "vim.h" + + +#if defined(HAVE_UTIME) && defined(HAVE_UTIME_H) +# include /* for struct utimbuf */ +#endif + +#define BUFSIZE 8192 /* size of normal write buffer */ +#define SMBUFSIZE 256 /* size of emergency write buffer */ + +/* crypt_magic[0] is pkzip crypt, crypt_magic[1] is sha2+blowfish */ +static char *crypt_magic[] = {"VimCrypt~01!", "VimCrypt~02!"}; +static char crypt_magic_head[] = "VimCrypt~"; +# define CRYPT_MAGIC_LEN 12 /* must be multiple of 4! */ + +/* For blowfish, after the magic header, we store 8 bytes of salt and then 8 + * bytes of seed (initialisation vector). */ +static int crypt_salt_len[] = {0, 8}; +static int crypt_seed_len[] = {0, 8}; +#define CRYPT_SALT_LEN_MAX 8 +#define CRYPT_SEED_LEN_MAX 8 + +/* Is there any system that doesn't have access()? */ +#define USE_MCH_ACCESS + +static char_u *next_fenc __ARGS((char_u **pp)); +static char_u *readfile_charconvert __ARGS((char_u *fname, char_u *fenc, + int *fdp)); +static void check_marks_read __ARGS((void)); +static int crypt_method_from_magic __ARGS((char *ptr, int len)); +static char_u *check_for_cryptkey __ARGS((char_u *cryptkey, char_u *ptr, + long *sizep, off_t *filesizep, + int newfile, char_u *fname, + int *did_ask)); +#ifdef UNIX +static void set_file_time __ARGS((char_u *fname, time_t atime, time_t mtime)); +#endif +static int set_rw_fname __ARGS((char_u *fname, char_u *sfname)); +static int msg_add_fileformat __ARGS((int eol_type)); +static void msg_add_eol __ARGS((void)); +static int check_mtime __ARGS((buf_T *buf, struct stat *s)); +static int time_differs __ARGS((long t1, long t2)); +static int apply_autocmds_exarg __ARGS((event_T event, char_u *fname, char_u * + fname_io, int force, buf_T *buf, + exarg_T *eap)); +static int au_find_group __ARGS((char_u *name)); + +# define AUGROUP_DEFAULT -1 /* default autocmd group */ +# define AUGROUP_ERROR -2 /* erroneous autocmd group */ +# define AUGROUP_ALL -3 /* all autocmd groups */ + +# define HAS_BW_FLAGS +# define FIO_LATIN1 0x01 /* convert Latin1 */ +# define FIO_UTF8 0x02 /* convert UTF-8 */ +# define FIO_UCS2 0x04 /* convert UCS-2 */ +# define FIO_UCS4 0x08 /* convert UCS-4 */ +# define FIO_UTF16 0x10 /* convert UTF-16 */ +# define FIO_ENDIAN_L 0x80 /* little endian */ +# define FIO_ENCRYPTED 0x1000 /* encrypt written bytes */ +# define FIO_NOCONVERT 0x2000 /* skip encoding conversion */ +# define FIO_UCSBOM 0x4000 /* check for BOM at start of file */ +# define FIO_ALL -1 /* allow all formats */ + +/* When converting, a read() or write() may leave some bytes to be converted + * for the next call. The value is guessed... */ +#define CONV_RESTLEN 30 + +/* We have to guess how much a sequence of bytes may expand when converting + * with iconv() to be able to allocate a buffer. */ +#define ICONV_MULT 8 + +/* + * Structure to pass arguments from buf_write() to buf_write_bytes(). + */ +struct bw_info { + int bw_fd; /* file descriptor */ + char_u *bw_buf; /* buffer with data to be written */ + int bw_len; /* length of data */ +#ifdef HAS_BW_FLAGS + int bw_flags; /* FIO_ flags */ +#endif + char_u bw_rest[CONV_RESTLEN]; /* not converted bytes */ + int bw_restlen; /* nr of bytes in bw_rest[] */ + int bw_first; /* first write call */ + char_u *bw_conv_buf; /* buffer for writing converted chars */ + int bw_conv_buflen; /* size of bw_conv_buf */ + int bw_conv_error; /* set for conversion error */ + linenr_T bw_conv_error_lnum; /* first line with error or zero */ + linenr_T bw_start_lnum; /* line number at start of buffer */ +# ifdef USE_ICONV + iconv_t bw_iconv_fd; /* descriptor for iconv() or -1 */ +# endif +}; + +static int buf_write_bytes __ARGS((struct bw_info *ip)); + +static linenr_T readfile_linenr __ARGS((linenr_T linecnt, char_u *p, + char_u *endp)); +static int ucs2bytes __ARGS((unsigned c, char_u **pp, int flags)); +static int need_conversion __ARGS((char_u *fenc)); +static int get_fio_flags __ARGS((char_u *ptr)); +static char_u *check_for_bom __ARGS((char_u *p, long size, int *lenp, int flags)); +static int make_bom __ARGS((char_u *buf, char_u *name)); +static int move_lines __ARGS((buf_T *frombuf, buf_T *tobuf)); +#ifdef TEMPDIRNAMES +static void vim_settempdir __ARGS((char_u *tempdir)); +#endif +static char *e_auchangedbuf = N_( + "E812: Autocommands changed buffer or buffer name"); + +void filemess(buf, name, s, attr) +buf_T *buf; +char_u *name; +char_u *s; +int attr; +{ + int msg_scroll_save; + + if (msg_silent != 0) + return; + msg_add_fname(buf, name); /* put file name in IObuff with quotes */ + /* If it's extremely long, truncate it. */ + if (STRLEN(IObuff) > IOSIZE - 80) + IObuff[IOSIZE - 80] = NUL; + STRCAT(IObuff, s); + /* + * For the first message may have to start a new line. + * For further ones overwrite the previous one, reset msg_scroll before + * calling filemess(). + */ + msg_scroll_save = msg_scroll; + if (shortmess(SHM_OVERALL) && !exiting && p_verbose == 0) + msg_scroll = FALSE; + if (!msg_scroll) /* wait a bit when overwriting an error msg */ + check_for_delay(FALSE); + msg_start(); + msg_scroll = msg_scroll_save; + msg_scrolled_ign = TRUE; + /* may truncate the message to avoid a hit-return prompt */ + msg_outtrans_attr(msg_may_trunc(FALSE, IObuff), attr); + msg_clr_eos(); + out_flush(); + msg_scrolled_ign = FALSE; +} + +/* + * Read lines from file "fname" into the buffer after line "from". + * + * 1. We allocate blocks with lalloc, as big as possible. + * 2. Each block is filled with characters from the file with a single read(). + * 3. The lines are inserted in the buffer with ml_append(). + * + * (caller must check that fname != NULL, unless READ_STDIN is used) + * + * "lines_to_skip" is the number of lines that must be skipped + * "lines_to_read" is the number of lines that are appended + * When not recovering lines_to_skip is 0 and lines_to_read MAXLNUM. + * + * flags: + * READ_NEW starting to edit a new buffer + * READ_FILTER reading filter output + * READ_STDIN read from stdin instead of a file + * READ_BUFFER read from curbuf instead of a file (converting after reading + * stdin) + * READ_DUMMY read into a dummy buffer (to check if file contents changed) + * READ_KEEP_UNDO don't clear undo info or read it from a file + * + * return FAIL for failure, OK otherwise + */ +int readfile(fname, sfname, from, lines_to_skip, lines_to_read, eap, flags) +char_u *fname; +char_u *sfname; +linenr_T from; +linenr_T lines_to_skip; +linenr_T lines_to_read; +exarg_T *eap; /* can be NULL! */ +int flags; +{ + int fd = 0; + int newfile = (flags & READ_NEW); + int check_readonly; + int filtering = (flags & READ_FILTER); + int read_stdin = (flags & READ_STDIN); + int read_buffer = (flags & READ_BUFFER); + int set_options = newfile || read_buffer + || (eap != NULL && eap->read_edit); + linenr_T read_buf_lnum = 1; /* next line to read from curbuf */ + colnr_T read_buf_col = 0; /* next char to read from this line */ + char_u c; + linenr_T lnum = from; + char_u *ptr = NULL; /* pointer into read buffer */ + char_u *buffer = NULL; /* read buffer */ + char_u *new_buffer = NULL; /* init to shut up gcc */ + char_u *line_start = NULL; /* init to shut up gcc */ + int wasempty; /* buffer was empty before reading */ + colnr_T len; + long size = 0; + char_u *p; + off_t filesize = 0; + int skip_read = FALSE; + char_u *cryptkey = NULL; + int did_ask_for_key = FALSE; + int crypt_method_used; + context_sha256_T sha_ctx; + int read_undo_file = FALSE; + int split = 0; /* number of split lines */ +#define UNKNOWN 0x0fffffff /* file size is unknown */ + linenr_T linecnt; + int error = FALSE; /* errors encountered */ + int ff_error = EOL_UNKNOWN; /* file format with errors */ + long linerest = 0; /* remaining chars in line */ +#ifdef UNIX + int perm = 0; + int swap_mode = -1; /* protection bits for swap file */ +#else + int perm; +#endif + int fileformat = 0; /* end-of-line format */ + int keep_fileformat = FALSE; + struct stat st; + int file_readonly; + linenr_T skip_count = 0; + linenr_T read_count = 0; + int msg_save = msg_scroll; + linenr_T read_no_eol_lnum = 0; /* non-zero lnum when last line of + * last read was missing the eol */ + int try_mac = (vim_strchr(p_ffs, 'm') != NULL); + int try_dos = (vim_strchr(p_ffs, 'd') != NULL); + int try_unix = (vim_strchr(p_ffs, 'x') != NULL); + int file_rewind = FALSE; + int can_retry; + linenr_T conv_error = 0; /* line nr with conversion error */ + linenr_T illegal_byte = 0; /* line nr with illegal byte */ + int keep_dest_enc = FALSE; /* don't retry when char doesn't fit + in destination encoding */ + int bad_char_behavior = BAD_REPLACE; + /* BAD_KEEP, BAD_DROP or character to + * replace with */ + char_u *tmpname = NULL; /* name of 'charconvert' output file */ + int fio_flags = 0; + char_u *fenc; /* fileencoding to use */ + int fenc_alloced; /* fenc_next is in allocated memory */ + char_u *fenc_next = NULL; /* next item in 'fencs' or NULL */ + int advance_fenc = FALSE; + long real_size = 0; +# ifdef USE_ICONV + iconv_t iconv_fd = (iconv_t)-1; /* descriptor for iconv() or -1 */ + int did_iconv = FALSE; /* TRUE when iconv() failed and trying + 'charconvert' next */ +# endif + int converted = FALSE; /* TRUE if conversion done */ + int notconverted = FALSE; /* TRUE if conversion wanted but it + wasn't possible */ + char_u conv_rest[CONV_RESTLEN]; + int conv_restlen = 0; /* nr of bytes in conv_rest[] */ + buf_T *old_curbuf; + char_u *old_b_ffname; + char_u *old_b_fname; + int using_b_ffname; + int using_b_fname; + + curbuf->b_no_eol_lnum = 0; /* in case it was set by the previous read */ + + /* + * If there is no file name yet, use the one for the read file. + * BF_NOTEDITED is set to reflect this. + * Don't do this for a read from a filter. + * Only do this when 'cpoptions' contains the 'f' flag. + */ + if (curbuf->b_ffname == NULL + && !filtering + && fname != NULL + && vim_strchr(p_cpo, CPO_FNAMER) != NULL + && !(flags & READ_DUMMY)) { + if (set_rw_fname(fname, sfname) == FAIL) + return FAIL; + } + + /* Remember the initial values of curbuf, curbuf->b_ffname and + * curbuf->b_fname to detect whether they are altered as a result of + * executing nasty autocommands. Also check if "fname" and "sfname" + * point to one of these values. */ + old_curbuf = curbuf; + old_b_ffname = curbuf->b_ffname; + old_b_fname = curbuf->b_fname; + using_b_ffname = (fname == curbuf->b_ffname) + || (sfname == curbuf->b_ffname); + using_b_fname = (fname == curbuf->b_fname) || (sfname == curbuf->b_fname); + + /* After reading a file the cursor line changes but we don't want to + * display the line. */ + ex_no_reprint = TRUE; + + /* don't display the file info for another buffer now */ + need_fileinfo = FALSE; + + /* + * For Unix: Use the short file name whenever possible. + * Avoids problems with networks and when directory names are changed. + * Don't do this for MS-DOS, a "cd" in a sub-shell may have moved us to + * another directory, which we don't detect. + */ + if (sfname == NULL) + sfname = fname; +#if defined(UNIX) || defined(__EMX__) + fname = sfname; +#endif + + /* + * The BufReadCmd and FileReadCmd events intercept the reading process by + * executing the associated commands instead. + */ + if (!filtering && !read_stdin && !read_buffer) { + pos_T pos; + + pos = curbuf->b_op_start; + + /* Set '[ mark to the line above where the lines go (line 1 if zero). */ + curbuf->b_op_start.lnum = ((from == 0) ? 1 : from); + curbuf->b_op_start.col = 0; + + if (newfile) { + if (apply_autocmds_exarg(EVENT_BUFREADCMD, NULL, sfname, + FALSE, curbuf, eap)) + return aborting() ? FAIL : OK; + } else if (apply_autocmds_exarg(EVENT_FILEREADCMD, sfname, sfname, + FALSE, NULL, eap)) + return aborting() ? FAIL : OK; + + curbuf->b_op_start = pos; + } + + if ((shortmess(SHM_OVER) || curbuf->b_help) && p_verbose == 0) + msg_scroll = FALSE; /* overwrite previous file message */ + else + msg_scroll = TRUE; /* don't overwrite previous file message */ + + /* + * If the name ends in a path separator, we can't open it. Check here, + * because reading the file may actually work, but then creating the swap + * file may destroy it! Reported on MS-DOS and Win 95. + * If the name is too long we might crash further on, quit here. + */ + if (fname != NULL && *fname != NUL) { + p = fname + STRLEN(fname); + if (after_pathsep(fname, p) || STRLEN(fname) >= MAXPATHL) { + filemess(curbuf, fname, (char_u *)_("Illegal file name"), 0); + msg_end(); + msg_scroll = msg_save; + return FAIL; + } + } + + if (!read_stdin && !read_buffer) { +#ifdef UNIX + /* + * On Unix it is possible to read a directory, so we have to + * check for it before the mch_open(). + */ + perm = mch_getperm(fname); + if (perm >= 0 && !S_ISREG(perm) /* not a regular file ... */ +# ifdef S_ISFIFO + && !S_ISFIFO(perm) /* ... or fifo */ +# endif +# ifdef S_ISSOCK + && !S_ISSOCK(perm) /* ... or socket */ +# endif +# ifdef OPEN_CHR_FILES + && !(S_ISCHR(perm) && is_dev_fd_file(fname)) + /* ... or a character special file named /dev/fd/ */ +# endif + ) { + if (S_ISDIR(perm)) + filemess(curbuf, fname, (char_u *)_("is a directory"), 0); + else + filemess(curbuf, fname, (char_u *)_("is not a file"), 0); + msg_end(); + msg_scroll = msg_save; + return FAIL; + } +#endif + } + + /* Set default or forced 'fileformat' and 'binary'. */ + set_file_options(set_options, eap); + + /* + * When opening a new file we take the readonly flag from the file. + * Default is r/w, can be set to r/o below. + * Don't reset it when in readonly mode + * Only set/reset b_p_ro when BF_CHECK_RO is set. + */ + check_readonly = (newfile && (curbuf->b_flags & BF_CHECK_RO)); + if (check_readonly && !readonlymode) + curbuf->b_p_ro = FALSE; + + if (newfile && !read_stdin && !read_buffer) { + /* Remember time of file. */ + if (mch_stat((char *)fname, &st) >= 0) { + buf_store_time(curbuf, &st, fname); + curbuf->b_mtime_read = curbuf->b_mtime; +#ifdef UNIX + /* + * Use the protection bits of the original file for the swap file. + * This makes it possible for others to read the name of the + * edited file from the swapfile, but only if they can read the + * edited file. + * Remove the "write" and "execute" bits for group and others + * (they must not write the swapfile). + * Add the "read" and "write" bits for the user, otherwise we may + * not be able to write to the file ourselves. + * Setting the bits is done below, after creating the swap file. + */ + swap_mode = (st.st_mode & 0644) | 0600; +#endif + } else { + curbuf->b_mtime = 0; + curbuf->b_mtime_read = 0; + curbuf->b_orig_size = 0; + curbuf->b_orig_mode = 0; + } + + /* Reset the "new file" flag. It will be set again below when the + * file doesn't exist. */ + curbuf->b_flags &= ~(BF_NEW | BF_NEW_W); + } + + /* + * for UNIX: check readonly with perm and mch_access() + * for MSDOS and Amiga: check readonly by trying to open the file for writing + */ + file_readonly = FALSE; + if (read_stdin) { + } else if (!read_buffer) { +#ifdef USE_MCH_ACCESS + if ( +# ifdef UNIX + !(perm & 0222) || +# endif + mch_access((char *)fname, W_OK)) + file_readonly = TRUE; + fd = mch_open((char *)fname, O_RDONLY | O_EXTRA, 0); +#else + if (!newfile + || readonlymode + || (fd = mch_open((char *)fname, O_RDWR | O_EXTRA, 0)) < 0) { + file_readonly = TRUE; + /* try to open ro */ + fd = mch_open((char *)fname, O_RDONLY | O_EXTRA, 0); + } +#endif + } + + if (fd < 0) { /* cannot open at all */ +#ifndef UNIX + int isdir_f; +#endif + msg_scroll = msg_save; +#ifndef UNIX + /* + * On MSDOS and Amiga we can't open a directory, check here. + */ + isdir_f = (mch_isdir(fname)); + perm = mch_getperm(fname); /* check if the file exists */ + if (isdir_f) { + filemess(curbuf, sfname, (char_u *)_("is a directory"), 0); + curbuf->b_p_ro = TRUE; /* must use "w!" now */ + } else +#endif + if (newfile) { + if (perm < 0 +#ifdef ENOENT + && errno == ENOENT +#endif + ) { + /* + * Set the 'new-file' flag, so that when the file has + * been created by someone else, a ":w" will complain. + */ + curbuf->b_flags |= BF_NEW; + + /* Create a swap file now, so that other Vims are warned + * that we are editing this file. Don't do this for a + * "nofile" or "nowrite" buffer type. */ + if (!bt_dontwrite(curbuf)) { + check_need_swap(newfile); + /* SwapExists autocommand may mess things up */ + if (curbuf != old_curbuf + || (using_b_ffname + && (old_b_ffname != curbuf->b_ffname)) + || (using_b_fname + && (old_b_fname != curbuf->b_fname))) { + EMSG(_(e_auchangedbuf)); + return FAIL; + } + } + if (dir_of_file_exists(fname)) + filemess(curbuf, sfname, (char_u *)_("[New File]"), 0); + else + filemess(curbuf, sfname, + (char_u *)_("[New DIRECTORY]"), 0); + /* Even though this is a new file, it might have been + * edited before and deleted. Get the old marks. */ + check_marks_read(); + /* Set forced 'fileencoding'. */ + if (eap != NULL) + set_forced_fenc(eap); + apply_autocmds_exarg(EVENT_BUFNEWFILE, sfname, sfname, + FALSE, curbuf, eap); + /* remember the current fileformat */ + save_file_ff(curbuf); + + if (aborting()) /* autocmds may abort script processing */ + return FAIL; + return OK; /* a new file is not an error */ + } else { + filemess(curbuf, sfname, (char_u *)( +# ifdef EFBIG + (errno == EFBIG) ? _("[File too big]") : +# endif +# ifdef EOVERFLOW + (errno == EOVERFLOW) ? _("[File too big]") : +# endif + _("[Permission Denied]")), 0); + curbuf->b_p_ro = TRUE; /* must use "w!" now */ + } + } + + return FAIL; + } + + /* + * Only set the 'ro' flag for readonly files the first time they are + * loaded. Help files always get readonly mode + */ + if ((check_readonly && file_readonly) || curbuf->b_help) + curbuf->b_p_ro = TRUE; + + if (set_options) { + /* Don't change 'eol' if reading from buffer as it will already be + * correctly set when reading stdin. */ + if (!read_buffer) { + curbuf->b_p_eol = TRUE; + curbuf->b_start_eol = TRUE; + } + curbuf->b_p_bomb = FALSE; + curbuf->b_start_bomb = FALSE; + } + + /* Create a swap file now, so that other Vims are warned that we are + * editing this file. + * Don't do this for a "nofile" or "nowrite" buffer type. */ + if (!bt_dontwrite(curbuf)) { + check_need_swap(newfile); + if (!read_stdin && (curbuf != old_curbuf + || (using_b_ffname && (old_b_ffname != curbuf->b_ffname)) + || (using_b_fname && + (old_b_fname != curbuf->b_fname)))) { + EMSG(_(e_auchangedbuf)); + if (!read_buffer) + close(fd); + return FAIL; + } +#ifdef UNIX + /* Set swap file protection bits after creating it. */ + if (swap_mode > 0 && curbuf->b_ml.ml_mfp != NULL + && curbuf->b_ml.ml_mfp->mf_fname != NULL) + (void)mch_setperm(curbuf->b_ml.ml_mfp->mf_fname, (long)swap_mode); +#endif + } + +#if defined(HAS_SWAP_EXISTS_ACTION) + /* If "Quit" selected at ATTENTION dialog, don't load the file */ + if (swap_exists_action == SEA_QUIT) { + if (!read_buffer && !read_stdin) + close(fd); + return FAIL; + } +#endif + + ++no_wait_return; /* don't wait for return yet */ + + /* + * Set '[ mark to the line above where the lines go (line 1 if zero). + */ + curbuf->b_op_start.lnum = ((from == 0) ? 1 : from); + curbuf->b_op_start.col = 0; + + if (!read_buffer) { + int m = msg_scroll; + int n = msg_scrolled; + + /* + * The file must be closed again, the autocommands may want to change + * the file before reading it. + */ + if (!read_stdin) + close(fd); /* ignore errors */ + + /* + * The output from the autocommands should not overwrite anything and + * should not be overwritten: Set msg_scroll, restore its value if no + * output was done. + */ + msg_scroll = TRUE; + if (filtering) + apply_autocmds_exarg(EVENT_FILTERREADPRE, NULL, sfname, + FALSE, curbuf, eap); + else if (read_stdin) + apply_autocmds_exarg(EVENT_STDINREADPRE, NULL, sfname, + FALSE, curbuf, eap); + else if (newfile) + apply_autocmds_exarg(EVENT_BUFREADPRE, NULL, sfname, + FALSE, curbuf, eap); + else + apply_autocmds_exarg(EVENT_FILEREADPRE, sfname, sfname, + FALSE, NULL, eap); + if (msg_scrolled == n) + msg_scroll = m; + + if (aborting()) { /* autocmds may abort script processing */ + --no_wait_return; + msg_scroll = msg_save; + curbuf->b_p_ro = TRUE; /* must use "w!" now */ + return FAIL; + } + /* + * Don't allow the autocommands to change the current buffer. + * Try to re-open the file. + * + * Don't allow the autocommands to change the buffer name either + * (cd for example) if it invalidates fname or sfname. + */ + if (!read_stdin && (curbuf != old_curbuf + || (using_b_ffname && (old_b_ffname != curbuf->b_ffname)) + || (using_b_fname && (old_b_fname != curbuf->b_fname)) + || (fd = + mch_open((char *)fname, O_RDONLY | O_EXTRA, + 0)) < 0)) { + --no_wait_return; + msg_scroll = msg_save; + if (fd < 0) + EMSG(_("E200: *ReadPre autocommands made the file unreadable")); + else + EMSG(_("E201: *ReadPre autocommands must not change current buffer")); + curbuf->b_p_ro = TRUE; /* must use "w!" now */ + return FAIL; + } + } + + /* Autocommands may add lines to the file, need to check if it is empty */ + wasempty = (curbuf->b_ml.ml_flags & ML_EMPTY); + + if (!recoverymode && !filtering && !(flags & READ_DUMMY)) { + /* + * Show the user that we are busy reading the input. Sometimes this + * may take a while. When reading from stdin another program may + * still be running, don't move the cursor to the last line, unless + * always using the GUI. + */ + if (read_stdin) { + mch_msg(_("Vim: Reading from stdin...\n")); + } else if (!read_buffer) + filemess(curbuf, sfname, (char_u *)"", 0); + } + + msg_scroll = FALSE; /* overwrite the file message */ + + /* + * Set linecnt now, before the "retry" caused by a wrong guess for + * fileformat, and after the autocommands, which may change them. + */ + linecnt = curbuf->b_ml.ml_line_count; + + /* "++bad=" argument. */ + if (eap != NULL && eap->bad_char != 0) { + bad_char_behavior = eap->bad_char; + if (set_options) + curbuf->b_bad_char = eap->bad_char; + } else + curbuf->b_bad_char = 0; + + /* + * Decide which 'encoding' to use or use first. + */ + if (eap != NULL && eap->force_enc != 0) { + fenc = enc_canonize(eap->cmd + eap->force_enc); + fenc_alloced = TRUE; + keep_dest_enc = TRUE; + } else if (curbuf->b_p_bin) { + fenc = (char_u *)""; /* binary: don't convert */ + fenc_alloced = FALSE; + } else if (curbuf->b_help) { + char_u firstline[80]; + int fc; + + /* Help files are either utf-8 or latin1. Try utf-8 first, if this + * fails it must be latin1. + * Always do this when 'encoding' is "utf-8". Otherwise only do + * this when needed to avoid [converted] remarks all the time. + * It is needed when the first line contains non-ASCII characters. + * That is only in *.??x files. */ + fenc = (char_u *)"latin1"; + c = enc_utf8; + if (!c && !read_stdin) { + fc = fname[STRLEN(fname) - 1]; + if (TOLOWER_ASC(fc) == 'x') { + /* Read the first line (and a bit more). Immediately rewind to + * the start of the file. If the read() fails "len" is -1. */ + len = read_eintr(fd, firstline, 80); + lseek(fd, (off_t)0L, SEEK_SET); + for (p = firstline; p < firstline + len; ++p) + if (*p >= 0x80) { + c = TRUE; + break; + } + } + } + + if (c) { + fenc_next = fenc; + fenc = (char_u *)"utf-8"; + + /* When the file is utf-8 but a character doesn't fit in + * 'encoding' don't retry. In help text editing utf-8 bytes + * doesn't make sense. */ + if (!enc_utf8) + keep_dest_enc = TRUE; + } + fenc_alloced = FALSE; + } else if (*p_fencs == NUL) { + fenc = curbuf->b_p_fenc; /* use format from buffer */ + fenc_alloced = FALSE; + } else { + fenc_next = p_fencs; /* try items in 'fileencodings' */ + fenc = next_fenc(&fenc_next); + fenc_alloced = TRUE; + } + + /* + * Jump back here to retry reading the file in different ways. + * Reasons to retry: + * - encoding conversion failed: try another one from "fenc_next" + * - BOM detected and fenc was set, need to setup conversion + * - "fileformat" check failed: try another + * + * Variables set for special retry actions: + * "file_rewind" Rewind the file to start reading it again. + * "advance_fenc" Advance "fenc" using "fenc_next". + * "skip_read" Re-use already read bytes (BOM detected). + * "did_iconv" iconv() conversion failed, try 'charconvert'. + * "keep_fileformat" Don't reset "fileformat". + * + * Other status indicators: + * "tmpname" When != NULL did conversion with 'charconvert'. + * Output file has to be deleted afterwards. + * "iconv_fd" When != -1 did conversion with iconv(). + */ +retry: + + if (file_rewind) { + if (read_buffer) { + read_buf_lnum = 1; + read_buf_col = 0; + } else if (read_stdin || lseek(fd, (off_t)0L, SEEK_SET) != 0) { + /* Can't rewind the file, give up. */ + error = TRUE; + goto failed; + } + /* Delete the previously read lines. */ + while (lnum > from) + ml_delete(lnum--, FALSE); + file_rewind = FALSE; + if (set_options) { + curbuf->b_p_bomb = FALSE; + curbuf->b_start_bomb = FALSE; + } + conv_error = 0; + } + + if (cryptkey != NULL) + /* Need to reset the state, but keep the key, don't want to ask for it + * again. */ + crypt_pop_state(); + + /* + * When retrying with another "fenc" and the first time "fileformat" + * will be reset. + */ + if (keep_fileformat) + keep_fileformat = FALSE; + else { + if (eap != NULL && eap->force_ff != 0) { + fileformat = get_fileformat_force(curbuf, eap); + try_unix = try_dos = try_mac = FALSE; + } else if (curbuf->b_p_bin) + fileformat = EOL_UNIX; /* binary: use Unix format */ + else if (*p_ffs == NUL) + fileformat = get_fileformat(curbuf); /* use format from buffer */ + else + fileformat = EOL_UNKNOWN; /* detect from file */ + } + +# ifdef USE_ICONV + if (iconv_fd != (iconv_t)-1) { + /* aborted conversion with iconv(), close the descriptor */ + iconv_close(iconv_fd); + iconv_fd = (iconv_t)-1; + } +# endif + + if (advance_fenc) { + /* + * Try the next entry in 'fileencodings'. + */ + advance_fenc = FALSE; + + if (eap != NULL && eap->force_enc != 0) { + /* Conversion given with "++cc=" wasn't possible, read + * without conversion. */ + notconverted = TRUE; + conv_error = 0; + if (fenc_alloced) + vim_free(fenc); + fenc = (char_u *)""; + fenc_alloced = FALSE; + } else { + if (fenc_alloced) + vim_free(fenc); + if (fenc_next != NULL) { + fenc = next_fenc(&fenc_next); + fenc_alloced = (fenc_next != NULL); + } else { + fenc = (char_u *)""; + fenc_alloced = FALSE; + } + } + if (tmpname != NULL) { + mch_remove(tmpname); /* delete converted file */ + vim_free(tmpname); + tmpname = NULL; + } + } + + /* + * Conversion may be required when the encoding of the file is different + * from 'encoding' or 'encoding' is UTF-16, UCS-2 or UCS-4. + */ + fio_flags = 0; + converted = need_conversion(fenc); + if (converted) { + + /* "ucs-bom" means we need to check the first bytes of the file + * for a BOM. */ + if (STRCMP(fenc, ENC_UCSBOM) == 0) + fio_flags = FIO_UCSBOM; + + /* + * Check if UCS-2/4 or Latin1 to UTF-8 conversion needs to be + * done. This is handled below after read(). Prepare the + * fio_flags to avoid having to parse the string each time. + * Also check for Unicode to Latin1 conversion, because iconv() + * appears not to handle this correctly. This works just like + * conversion to UTF-8 except how the resulting character is put in + * the buffer. + */ + else if (enc_utf8 || STRCMP(p_enc, "latin1") == 0) + fio_flags = get_fio_flags(fenc); + + + +# ifdef USE_ICONV + /* + * Try using iconv() if we can't convert internally. + */ + if (fio_flags == 0 + && !did_iconv + ) + iconv_fd = (iconv_t)my_iconv_open( + enc_utf8 ? (char_u *)"utf-8" : p_enc, fenc); +# endif + + /* + * Use the 'charconvert' expression when conversion is required + * and we can't do it internally or with iconv(). + */ + if (fio_flags == 0 && !read_stdin && !read_buffer && *p_ccv != NUL +# ifdef USE_ICONV + && iconv_fd == (iconv_t)-1 +# endif + ) { +# ifdef USE_ICONV + did_iconv = FALSE; +# endif + /* Skip conversion when it's already done (retry for wrong + * "fileformat"). */ + if (tmpname == NULL) { + tmpname = readfile_charconvert(fname, fenc, &fd); + if (tmpname == NULL) { + /* Conversion failed. Try another one. */ + advance_fenc = TRUE; + if (fd < 0) { + /* Re-opening the original file failed! */ + EMSG(_("E202: Conversion made file unreadable!")); + error = TRUE; + goto failed; + } + goto retry; + } + } + } else { + if (fio_flags == 0 +# ifdef USE_ICONV + && iconv_fd == (iconv_t)-1 +# endif + ) { + /* Conversion wanted but we can't. + * Try the next conversion in 'fileencodings' */ + advance_fenc = TRUE; + goto retry; + } + } + } + + /* Set "can_retry" when it's possible to rewind the file and try with + * another "fenc" value. It's FALSE when no other "fenc" to try, reading + * stdin or fixed at a specific encoding. */ + can_retry = (*fenc != NUL && !read_stdin && !keep_dest_enc); + + if (!skip_read) { + linerest = 0; + filesize = 0; + skip_count = lines_to_skip; + read_count = lines_to_read; + conv_restlen = 0; + read_undo_file = (newfile && (flags & READ_KEEP_UNDO) == 0 + && curbuf->b_ffname != NULL + && curbuf->b_p_udf + && !filtering + && !read_stdin + && !read_buffer); + if (read_undo_file) + sha256_start(&sha_ctx); + } + + while (!error && !got_int) { + /* + * We allocate as much space for the file as we can get, plus + * space for the old line plus room for one terminating NUL. + * The amount is limited by the fact that read() only can read + * upto max_unsigned characters (and other things). + */ +#if SIZEOF_INT <= 2 + if (linerest >= 0x7ff0) { + ++split; + *ptr = NL; /* split line by inserting a NL */ + size = 1; + } else +#endif + { + if (!skip_read) { +#if SIZEOF_INT > 2 +# if defined(SSIZE_MAX) && (SSIZE_MAX < 0x10000L) + size = SSIZE_MAX; /* use max I/O size, 52K */ +# else + size = 0x10000L; /* use buffer >= 64K */ +# endif +#else + size = 0x7ff0L - linerest; /* limit buffer to 32K */ +#endif + + for (; size >= 10; size = (long)((long_u)size >> 1)) { + if ((new_buffer = lalloc((long_u)(size + linerest + 1), + FALSE)) != NULL) + break; + } + if (new_buffer == NULL) { + do_outofmem_msg((long_u)(size * 2 + linerest + 1)); + error = TRUE; + break; + } + if (linerest) /* copy characters from the previous buffer */ + mch_memmove(new_buffer, ptr - linerest, (size_t)linerest); + vim_free(buffer); + buffer = new_buffer; + ptr = buffer + linerest; + line_start = buffer; + + /* May need room to translate into. + * For iconv() we don't really know the required space, use a + * factor ICONV_MULT. + * latin1 to utf-8: 1 byte becomes up to 2 bytes + * utf-16 to utf-8: 2 bytes become up to 3 bytes, 4 bytes + * become up to 4 bytes, size must be multiple of 2 + * ucs-2 to utf-8: 2 bytes become up to 3 bytes, size must be + * multiple of 2 + * ucs-4 to utf-8: 4 bytes become up to 6 bytes, size must be + * multiple of 4 */ + real_size = (int)size; +# ifdef USE_ICONV + if (iconv_fd != (iconv_t)-1) + size = size / ICONV_MULT; + else +# endif + if (fio_flags & FIO_LATIN1) + size = size / 2; + else if (fio_flags & (FIO_UCS2 | FIO_UTF16)) + size = (size * 2 / 3) & ~1; + else if (fio_flags & FIO_UCS4) + size = (size * 2 / 3) & ~3; + else if (fio_flags == FIO_UCSBOM) + size = size / ICONV_MULT; /* worst case */ + + if (conv_restlen > 0) { + /* Insert unconverted bytes from previous line. */ + mch_memmove(ptr, conv_rest, conv_restlen); + ptr += conv_restlen; + size -= conv_restlen; + } + + if (read_buffer) { + /* + * Read bytes from curbuf. Used for converting text read + * from stdin. + */ + if (read_buf_lnum > from) + size = 0; + else { + int n, ni; + long tlen; + + tlen = 0; + for (;; ) { + p = ml_get(read_buf_lnum) + read_buf_col; + n = (int)STRLEN(p); + if ((int)tlen + n + 1 > size) { + /* Filled up to "size", append partial line. + * Change NL to NUL to reverse the effect done + * below. */ + n = (int)(size - tlen); + for (ni = 0; ni < n; ++ni) { + if (p[ni] == NL) + ptr[tlen++] = NUL; + else + ptr[tlen++] = p[ni]; + } + read_buf_col += n; + break; + } else { + /* Append whole line and new-line. Change NL + * to NUL to reverse the effect done below. */ + for (ni = 0; ni < n; ++ni) { + if (p[ni] == NL) + ptr[tlen++] = NUL; + else + ptr[tlen++] = p[ni]; + } + ptr[tlen++] = NL; + read_buf_col = 0; + if (++read_buf_lnum > from) { + /* When the last line didn't have an + * end-of-line don't add it now either. */ + if (!curbuf->b_p_eol) + --tlen; + size = tlen; + break; + } + } + } + } + } else { + /* + * Read bytes from the file. + */ + size = read_eintr(fd, ptr, size); + } + + if (size <= 0) { + if (size < 0) /* read error */ + error = TRUE; + else if (conv_restlen > 0) { + /* + * Reached end-of-file but some trailing bytes could + * not be converted. Truncated file? + */ + + /* When we did a conversion report an error. */ + if (fio_flags != 0 +# ifdef USE_ICONV + || iconv_fd != (iconv_t)-1 +# endif + ) { + if (can_retry) + goto rewind_retry; + if (conv_error == 0) + conv_error = curbuf->b_ml.ml_line_count + - linecnt + 1; + } + /* Remember the first linenr with an illegal byte */ + else if (illegal_byte == 0) + illegal_byte = curbuf->b_ml.ml_line_count + - linecnt + 1; + if (bad_char_behavior == BAD_DROP) { + *(ptr - conv_restlen) = NUL; + conv_restlen = 0; + } else { + /* Replace the trailing bytes with the replacement + * character if we were converting; if we weren't, + * leave the UTF8 checking code to do it, as it + * works slightly differently. */ + if (bad_char_behavior != BAD_KEEP && (fio_flags != 0 +# ifdef USE_ICONV + || iconv_fd != (iconv_t)-1 +# endif + )) { + while (conv_restlen > 0) { + *(--ptr) = bad_char_behavior; + --conv_restlen; + } + } + fio_flags = 0; /* don't convert this */ +# ifdef USE_ICONV + if (iconv_fd != (iconv_t)-1) { + iconv_close(iconv_fd); + iconv_fd = (iconv_t)-1; + } +# endif + } + } + } + + /* + * At start of file: Check for magic number of encryption. + */ + if (filesize == 0) + cryptkey = check_for_cryptkey(cryptkey, ptr, &size, + &filesize, newfile, sfname, + &did_ask_for_key); + /* + * Decrypt the read bytes. + */ + if (cryptkey != NULL && size > 0) + crypt_decode(ptr, size); + } + skip_read = FALSE; + + /* + * At start of file (or after crypt magic number): Check for BOM. + * Also check for a BOM for other Unicode encodings, but not after + * converting with 'charconvert' or when a BOM has already been + * found. + */ + if ((filesize == 0 + || (filesize == (CRYPT_MAGIC_LEN + + crypt_salt_len[use_crypt_method] + + crypt_seed_len[use_crypt_method]) + && cryptkey != NULL) + ) + && (fio_flags == FIO_UCSBOM + || (!curbuf->b_p_bomb + && tmpname == NULL + && (*fenc == 'u' || (*fenc == NUL && enc_utf8))))) { + char_u *ccname; + int blen; + + /* no BOM detection in a short file or in binary mode */ + if (size < 2 || curbuf->b_p_bin) + ccname = NULL; + else + ccname = check_for_bom(ptr, size, &blen, + fio_flags == FIO_UCSBOM ? FIO_ALL : get_fio_flags(fenc)); + if (ccname != NULL) { + /* Remove BOM from the text */ + filesize += blen; + size -= blen; + mch_memmove(ptr, ptr + blen, (size_t)size); + if (set_options) { + curbuf->b_p_bomb = TRUE; + curbuf->b_start_bomb = TRUE; + } + } + + if (fio_flags == FIO_UCSBOM) { + if (ccname == NULL) { + /* No BOM detected: retry with next encoding. */ + advance_fenc = TRUE; + } else { + /* BOM detected: set "fenc" and jump back */ + if (fenc_alloced) + vim_free(fenc); + fenc = ccname; + fenc_alloced = FALSE; + } + /* retry reading without getting new bytes or rewinding */ + skip_read = TRUE; + goto retry; + } + } + + /* Include not converted bytes. */ + ptr -= conv_restlen; + size += conv_restlen; + conv_restlen = 0; + /* + * Break here for a read error or end-of-file. + */ + if (size <= 0) + break; + + +# ifdef USE_ICONV + if (iconv_fd != (iconv_t)-1) { + /* + * Attempt conversion of the read bytes to 'encoding' using + * iconv(). + */ + const char *fromp; + char *top; + size_t from_size; + size_t to_size; + + fromp = (char *)ptr; + from_size = size; + ptr += size; + top = (char *)ptr; + to_size = real_size - size; + + /* + * If there is conversion error or not enough room try using + * another conversion. Except for when there is no + * alternative (help files). + */ + while ((iconv(iconv_fd, (void *)&fromp, &from_size, + &top, &to_size) + == (size_t)-1 && ICONV_ERRNO != ICONV_EINVAL) + || from_size > CONV_RESTLEN) { + if (can_retry) + goto rewind_retry; + if (conv_error == 0) + conv_error = readfile_linenr(linecnt, + ptr, (char_u *)top); + + /* Deal with a bad byte and continue with the next. */ + ++fromp; + --from_size; + if (bad_char_behavior == BAD_KEEP) { + *top++ = *(fromp - 1); + --to_size; + } else if (bad_char_behavior != BAD_DROP) { + *top++ = bad_char_behavior; + --to_size; + } + } + + if (from_size > 0) { + /* Some remaining characters, keep them for the next + * round. */ + mch_memmove(conv_rest, (char_u *)fromp, from_size); + conv_restlen = (int)from_size; + } + + /* move the linerest to before the converted characters */ + line_start = ptr - linerest; + mch_memmove(line_start, buffer, (size_t)linerest); + size = (long)((char_u *)top - ptr); + } +# endif + +# ifdef MACOS_CONVERT + if (fio_flags & FIO_MACROMAN) { + /* + * Conversion from Apple MacRoman char encoding to UTF-8 or + * latin1. This is in os_mac_conv.c. + */ + if (macroman2enc(ptr, &size, real_size) == FAIL) + goto rewind_retry; + } else +# endif + if (fio_flags != 0) { + int u8c; + char_u *dest; + char_u *tail = NULL; + + /* + * "enc_utf8" set: Convert Unicode or Latin1 to UTF-8. + * "enc_utf8" not set: Convert Unicode to Latin1. + * Go from end to start through the buffer, because the number + * of bytes may increase. + * "dest" points to after where the UTF-8 bytes go, "p" points + * to after the next character to convert. + */ + dest = ptr + real_size; + if (fio_flags == FIO_LATIN1 || fio_flags == FIO_UTF8) { + p = ptr + size; + if (fio_flags == FIO_UTF8) { + /* Check for a trailing incomplete UTF-8 sequence */ + tail = ptr + size - 1; + while (tail > ptr && (*tail & 0xc0) == 0x80) + --tail; + if (tail + utf_byte2len(*tail) <= ptr + size) + tail = NULL; + else + p = tail; + } + } else if (fio_flags & (FIO_UCS2 | FIO_UTF16)) { + /* Check for a trailing byte */ + p = ptr + (size & ~1); + if (size & 1) + tail = p; + if ((fio_flags & FIO_UTF16) && p > ptr) { + /* Check for a trailing leading word */ + if (fio_flags & FIO_ENDIAN_L) { + u8c = (*--p << 8); + u8c += *--p; + } else { + u8c = *--p; + u8c += (*--p << 8); + } + if (u8c >= 0xd800 && u8c <= 0xdbff) + tail = p; + else + p += 2; + } + } else { /* FIO_UCS4 */ + /* Check for trailing 1, 2 or 3 bytes */ + p = ptr + (size & ~3); + if (size & 3) + tail = p; + } + + /* If there is a trailing incomplete sequence move it to + * conv_rest[]. */ + if (tail != NULL) { + conv_restlen = (int)((ptr + size) - tail); + mch_memmove(conv_rest, (char_u *)tail, conv_restlen); + size -= conv_restlen; + } + + + while (p > ptr) { + if (fio_flags & FIO_LATIN1) + u8c = *--p; + else if (fio_flags & (FIO_UCS2 | FIO_UTF16)) { + if (fio_flags & FIO_ENDIAN_L) { + u8c = (*--p << 8); + u8c += *--p; + } else { + u8c = *--p; + u8c += (*--p << 8); + } + if ((fio_flags & FIO_UTF16) + && u8c >= 0xdc00 && u8c <= 0xdfff) { + int u16c; + + if (p == ptr) { + /* Missing leading word. */ + if (can_retry) + goto rewind_retry; + if (conv_error == 0) + conv_error = readfile_linenr(linecnt, + ptr, p); + if (bad_char_behavior == BAD_DROP) + continue; + if (bad_char_behavior != BAD_KEEP) + u8c = bad_char_behavior; + } + + /* found second word of double-word, get the first + * word and compute the resulting character */ + if (fio_flags & FIO_ENDIAN_L) { + u16c = (*--p << 8); + u16c += *--p; + } else { + u16c = *--p; + u16c += (*--p << 8); + } + u8c = 0x10000 + ((u16c & 0x3ff) << 10) + + (u8c & 0x3ff); + + /* Check if the word is indeed a leading word. */ + if (u16c < 0xd800 || u16c > 0xdbff) { + if (can_retry) + goto rewind_retry; + if (conv_error == 0) + conv_error = readfile_linenr(linecnt, + ptr, p); + if (bad_char_behavior == BAD_DROP) + continue; + if (bad_char_behavior != BAD_KEEP) + u8c = bad_char_behavior; + } + } + } else if (fio_flags & FIO_UCS4) { + if (fio_flags & FIO_ENDIAN_L) { + u8c = (*--p << 24); + u8c += (*--p << 16); + u8c += (*--p << 8); + u8c += *--p; + } else { /* big endian */ + u8c = *--p; + u8c += (*--p << 8); + u8c += (*--p << 16); + u8c += (*--p << 24); + } + } else { /* UTF-8 */ + if (*--p < 0x80) + u8c = *p; + else { + len = utf_head_off(ptr, p); + p -= len; + u8c = utf_ptr2char(p); + if (len == 0) { + /* Not a valid UTF-8 character, retry with + * another fenc when possible, otherwise just + * report the error. */ + if (can_retry) + goto rewind_retry; + if (conv_error == 0) + conv_error = readfile_linenr(linecnt, + ptr, p); + if (bad_char_behavior == BAD_DROP) + continue; + if (bad_char_behavior != BAD_KEEP) + u8c = bad_char_behavior; + } + } + } + if (enc_utf8) { /* produce UTF-8 */ + dest -= utf_char2len(u8c); + (void)utf_char2bytes(u8c, dest); + } else { /* produce Latin1 */ + --dest; + if (u8c >= 0x100) { + /* character doesn't fit in latin1, retry with + * another fenc when possible, otherwise just + * report the error. */ + if (can_retry) + goto rewind_retry; + if (conv_error == 0) + conv_error = readfile_linenr(linecnt, ptr, p); + if (bad_char_behavior == BAD_DROP) + ++dest; + else if (bad_char_behavior == BAD_KEEP) + *dest = u8c; + else if (eap != NULL && eap->bad_char != 0) + *dest = bad_char_behavior; + else + *dest = 0xBF; + } else + *dest = u8c; + } + } + + /* move the linerest to before the converted characters */ + line_start = dest - linerest; + mch_memmove(line_start, buffer, (size_t)linerest); + size = (long)((ptr + real_size) - dest); + ptr = dest; + } else if (enc_utf8 && !curbuf->b_p_bin) { + int incomplete_tail = FALSE; + + /* Reading UTF-8: Check if the bytes are valid UTF-8. */ + for (p = ptr;; ++p) { + int todo = (int)((ptr + size) - p); + int l; + + if (todo <= 0) + break; + if (*p >= 0x80) { + /* A length of 1 means it's an illegal byte. Accept + * an incomplete character at the end though, the next + * read() will get the next bytes, we'll check it + * then. */ + l = utf_ptr2len_len(p, todo); + if (l > todo && !incomplete_tail) { + /* Avoid retrying with a different encoding when + * a truncated file is more likely, or attempting + * to read the rest of an incomplete sequence when + * we have already done so. */ + if (p > ptr || filesize > 0) + incomplete_tail = TRUE; + /* Incomplete byte sequence, move it to conv_rest[] + * and try to read the rest of it, unless we've + * already done so. */ + if (p > ptr) { + conv_restlen = todo; + mch_memmove(conv_rest, p, conv_restlen); + size -= conv_restlen; + break; + } + } + if (l == 1 || l > todo) { + /* Illegal byte. If we can try another encoding + * do that, unless at EOF where a truncated + * file is more likely than a conversion error. */ + if (can_retry && !incomplete_tail) + break; +# ifdef USE_ICONV + /* When we did a conversion report an error. */ + if (iconv_fd != (iconv_t)-1 && conv_error == 0) + conv_error = readfile_linenr(linecnt, ptr, p); +# endif + /* Remember the first linenr with an illegal byte */ + if (conv_error == 0 && illegal_byte == 0) + illegal_byte = readfile_linenr(linecnt, ptr, p); + + /* Drop, keep or replace the bad byte. */ + if (bad_char_behavior == BAD_DROP) { + mch_memmove(p, p + 1, todo - 1); + --p; + --size; + } else if (bad_char_behavior != BAD_KEEP) + *p = bad_char_behavior; + } else + p += l - 1; + } + } + if (p < ptr + size && !incomplete_tail) { + /* Detected a UTF-8 error. */ +rewind_retry: + /* Retry reading with another conversion. */ +# if defined(FEAT_EVAL) && defined(USE_ICONV) + if (*p_ccv != NUL && iconv_fd != (iconv_t)-1) + /* iconv() failed, try 'charconvert' */ + did_iconv = TRUE; + else +# endif + /* use next item from 'fileencodings' */ + advance_fenc = TRUE; + file_rewind = TRUE; + goto retry; + } + } + + /* count the number of characters (after conversion!) */ + filesize += size; + + /* + * when reading the first part of a file: guess EOL type + */ + if (fileformat == EOL_UNKNOWN) { + /* First try finding a NL, for Dos and Unix */ + if (try_dos || try_unix) { + for (p = ptr; p < ptr + size; ++p) { + if (*p == NL) { + if (!try_unix + || (try_dos && p > ptr && p[-1] == CAR)) + fileformat = EOL_DOS; + else + fileformat = EOL_UNIX; + break; + } + } + + /* Don't give in to EOL_UNIX if EOL_MAC is more likely */ + if (fileformat == EOL_UNIX && try_mac) { + /* Need to reset the counters when retrying fenc. */ + try_mac = 1; + try_unix = 1; + for (; p >= ptr && *p != CAR; p--) + ; + if (p >= ptr) { + for (p = ptr; p < ptr + size; ++p) { + if (*p == NL) + try_unix++; + else if (*p == CAR) + try_mac++; + } + if (try_mac > try_unix) + fileformat = EOL_MAC; + } + } + } + + /* No NL found: may use Mac format */ + if (fileformat == EOL_UNKNOWN && try_mac) + fileformat = EOL_MAC; + + /* Still nothing found? Use first format in 'ffs' */ + if (fileformat == EOL_UNKNOWN) + fileformat = default_fileformat(); + + /* if editing a new file: may set p_tx and p_ff */ + if (set_options) + set_fileformat(fileformat, OPT_LOCAL); + } + } + + /* + * This loop is executed once for every character read. + * Keep it fast! + */ + if (fileformat == EOL_MAC) { + --ptr; + while (++ptr, --size >= 0) { + /* catch most common case first */ + if ((c = *ptr) != NUL && c != CAR && c != NL) + continue; + if (c == NUL) + *ptr = NL; /* NULs are replaced by newlines! */ + else if (c == NL) + *ptr = CAR; /* NLs are replaced by CRs! */ + else { + if (skip_count == 0) { + *ptr = NUL; /* end of line */ + len = (colnr_T) (ptr - line_start + 1); + if (ml_append(lnum, line_start, len, newfile) == FAIL) { + error = TRUE; + break; + } + if (read_undo_file) + sha256_update(&sha_ctx, line_start, len); + ++lnum; + if (--read_count == 0) { + error = TRUE; /* break loop */ + line_start = ptr; /* nothing left to write */ + break; + } + } else + --skip_count; + line_start = ptr + 1; + } + } + } else { + --ptr; + while (++ptr, --size >= 0) { + if ((c = *ptr) != NUL && c != NL) /* catch most common case */ + continue; + if (c == NUL) + *ptr = NL; /* NULs are replaced by newlines! */ + else { + if (skip_count == 0) { + *ptr = NUL; /* end of line */ + len = (colnr_T)(ptr - line_start + 1); + if (fileformat == EOL_DOS) { + if (ptr[-1] == CAR) { /* remove CR */ + ptr[-1] = NUL; + --len; + } + /* + * Reading in Dos format, but no CR-LF found! + * When 'fileformats' includes "unix", delete all + * the lines read so far and start all over again. + * Otherwise give an error message later. + */ + else if (ff_error != EOL_DOS) { + if ( try_unix + && !read_stdin + && (read_buffer + || lseek(fd, (off_t)0L, SEEK_SET) == 0)) { + fileformat = EOL_UNIX; + if (set_options) + set_fileformat(EOL_UNIX, OPT_LOCAL); + file_rewind = TRUE; + keep_fileformat = TRUE; + goto retry; + } + ff_error = EOL_DOS; + } + } + if (ml_append(lnum, line_start, len, newfile) == FAIL) { + error = TRUE; + break; + } + if (read_undo_file) + sha256_update(&sha_ctx, line_start, len); + ++lnum; + if (--read_count == 0) { + error = TRUE; /* break loop */ + line_start = ptr; /* nothing left to write */ + break; + } + } else + --skip_count; + line_start = ptr + 1; + } + } + } + linerest = (long)(ptr - line_start); + ui_breakcheck(); + } + +failed: + /* not an error, max. number of lines reached */ + if (error && read_count == 0) + error = FALSE; + + /* + * If we get EOF in the middle of a line, note the fact and + * complete the line ourselves. + * In Dos format ignore a trailing CTRL-Z, unless 'binary' set. + */ + if (!error + && !got_int + && linerest != 0 + && !(!curbuf->b_p_bin + && fileformat == EOL_DOS + && *line_start == Ctrl_Z + && ptr == line_start + 1)) { + /* remember for when writing */ + if (set_options) + curbuf->b_p_eol = FALSE; + *ptr = NUL; + len = (colnr_T)(ptr - line_start + 1); + if (ml_append(lnum, line_start, len, newfile) == FAIL) + error = TRUE; + else { + if (read_undo_file) + sha256_update(&sha_ctx, line_start, len); + read_no_eol_lnum = ++lnum; + } + } + + if (set_options) + save_file_ff(curbuf); /* remember the current file format */ + + crypt_method_used = use_crypt_method; + if (cryptkey != NULL) { + crypt_pop_state(); + if (cryptkey != curbuf->b_p_key) + free_crypt_key(cryptkey); + /* don't set cryptkey to NULL, it's used below as a flag that + * encryption was used */ + } + + /* If editing a new file: set 'fenc' for the current buffer. + * Also for ":read ++edit file". */ + if (set_options) + set_string_option_direct((char_u *)"fenc", -1, fenc, + OPT_FREE|OPT_LOCAL, 0); + if (fenc_alloced) + vim_free(fenc); +# ifdef USE_ICONV + if (iconv_fd != (iconv_t)-1) { + iconv_close(iconv_fd); + iconv_fd = (iconv_t)-1; + } +# endif + + if (!read_buffer && !read_stdin) + close(fd); /* errors are ignored */ +#ifdef HAVE_FD_CLOEXEC + else { + int fdflags = fcntl(fd, F_GETFD); + if (fdflags >= 0 && (fdflags & FD_CLOEXEC) == 0) + fcntl(fd, F_SETFD, fdflags | FD_CLOEXEC); + } +#endif + vim_free(buffer); + +#ifdef HAVE_DUP + if (read_stdin) { + /* Use stderr for stdin, makes shell commands work. */ + close(0); + ignored = dup(2); + } +#endif + + if (tmpname != NULL) { + mch_remove(tmpname); /* delete converted file */ + vim_free(tmpname); + } + --no_wait_return; /* may wait for return now */ + + /* + * In recovery mode everything but autocommands is skipped. + */ + if (!recoverymode) { + /* need to delete the last line, which comes from the empty buffer */ + if (newfile && wasempty && !(curbuf->b_ml.ml_flags & ML_EMPTY)) { + ml_delete(curbuf->b_ml.ml_line_count, FALSE); + --linecnt; + } + linecnt = curbuf->b_ml.ml_line_count - linecnt; + if (filesize == 0) + linecnt = 0; + if (newfile || read_buffer) { + redraw_curbuf_later(NOT_VALID); + /* After reading the text into the buffer the diff info needs to + * be updated. */ + diff_invalidate(curbuf); + /* All folds in the window are invalid now. Mark them for update + * before triggering autocommands. */ + foldUpdateAll(curwin); + } else if (linecnt) /* appended at least one line */ + appended_lines_mark(from, linecnt); + + /* + * If we were reading from the same terminal as where messages go, + * the screen will have been messed up. + * Switch on raw mode now and clear the screen. + */ + if (read_stdin) { + settmode(TMODE_RAW); /* set to raw mode */ + starttermcap(); + screenclear(); + } + + if (got_int) { + if (!(flags & READ_DUMMY)) { + filemess(curbuf, sfname, (char_u *)_(e_interr), 0); + if (newfile) + curbuf->b_p_ro = TRUE; /* must use "w!" now */ + } + msg_scroll = msg_save; + check_marks_read(); + return OK; /* an interrupt isn't really an error */ + } + + if (!filtering && !(flags & READ_DUMMY)) { + msg_add_fname(curbuf, sfname); /* fname in IObuff with quotes */ + c = FALSE; + +#ifdef UNIX +# ifdef S_ISFIFO + if (S_ISFIFO(perm)) { /* fifo or socket */ + STRCAT(IObuff, _("[fifo/socket]")); + c = TRUE; + } +# else +# ifdef S_IFIFO + if ((perm & S_IFMT) == S_IFIFO) { /* fifo */ + STRCAT(IObuff, _("[fifo]")); + c = TRUE; + } +# endif +# ifdef S_IFSOCK + if ((perm & S_IFMT) == S_IFSOCK) { /* or socket */ + STRCAT(IObuff, _("[socket]")); + c = TRUE; + } +# endif +# endif +# ifdef OPEN_CHR_FILES + if (S_ISCHR(perm)) { /* or character special */ + STRCAT(IObuff, _("[character special]")); + c = TRUE; + } +# endif +#endif + if (curbuf->b_p_ro) { + STRCAT(IObuff, shortmess(SHM_RO) ? _("[RO]") : _("[readonly]")); + c = TRUE; + } + if (read_no_eol_lnum) { + msg_add_eol(); + c = TRUE; + } + if (ff_error == EOL_DOS) { + STRCAT(IObuff, _("[CR missing]")); + c = TRUE; + } + if (split) { + STRCAT(IObuff, _("[long lines split]")); + c = TRUE; + } + if (notconverted) { + STRCAT(IObuff, _("[NOT converted]")); + c = TRUE; + } else if (converted) { + STRCAT(IObuff, _("[converted]")); + c = TRUE; + } + if (cryptkey != NULL) { + if (crypt_method_used == 1) + STRCAT(IObuff, _("[blowfish]")); + else + STRCAT(IObuff, _("[crypted]")); + c = TRUE; + } + if (conv_error != 0) { + sprintf((char *)IObuff + STRLEN(IObuff), + _("[CONVERSION ERROR in line %ld]"), (long)conv_error); + c = TRUE; + } else if (illegal_byte > 0) { + sprintf((char *)IObuff + STRLEN(IObuff), + _("[ILLEGAL BYTE in line %ld]"), (long)illegal_byte); + c = TRUE; + } else if (error) { + STRCAT(IObuff, _("[READ ERRORS]")); + c = TRUE; + } + if (msg_add_fileformat(fileformat)) + c = TRUE; + if (cryptkey != NULL) + msg_add_lines(c, (long)linecnt, filesize + - CRYPT_MAGIC_LEN + - crypt_salt_len[use_crypt_method] + - crypt_seed_len[use_crypt_method]); + else + msg_add_lines(c, (long)linecnt, filesize); + + vim_free(keep_msg); + keep_msg = NULL; + msg_scrolled_ign = TRUE; + p = msg_trunc_attr(IObuff, FALSE, 0); + if (read_stdin || read_buffer || restart_edit != 0 + || (msg_scrolled != 0 && !need_wait_return)) + /* Need to repeat the message after redrawing when: + * - When reading from stdin (the screen will be cleared next). + * - When restart_edit is set (otherwise there will be a delay + * before redrawing). + * - When the screen was scrolled but there is no wait-return + * prompt. */ + set_keep_msg(p, 0); + msg_scrolled_ign = FALSE; + } + + /* with errors writing the file requires ":w!" */ + if (newfile && (error + || conv_error != 0 + || (illegal_byte > 0 && bad_char_behavior != BAD_KEEP) + )) + curbuf->b_p_ro = TRUE; + + u_clearline(); /* cannot use "U" command after adding lines */ + + /* + * In Ex mode: cursor at last new line. + * Otherwise: cursor at first new line. + */ + if (exmode_active) + curwin->w_cursor.lnum = from + linecnt; + else + curwin->w_cursor.lnum = from + 1; + check_cursor_lnum(); + beginline(BL_WHITE | BL_FIX); /* on first non-blank */ + + /* + * Set '[ and '] marks to the newly read lines. + */ + curbuf->b_op_start.lnum = from + 1; + curbuf->b_op_start.col = 0; + curbuf->b_op_end.lnum = from + linecnt; + curbuf->b_op_end.col = 0; + + } + msg_scroll = msg_save; + + /* + * Get the marks before executing autocommands, so they can be used there. + */ + check_marks_read(); + + /* + * Trick: We remember if the last line of the read didn't have + * an eol even when 'binary' is off, for when writing it again with + * 'binary' on. This is required for + * ":autocmd FileReadPost *.gz set bin|'[,']!gunzip" to work. + */ + curbuf->b_no_eol_lnum = read_no_eol_lnum; + + /* When reloading a buffer put the cursor at the first line that is + * different. */ + if (flags & READ_KEEP_UNDO) + u_find_first_changed(); + + /* + * When opening a new file locate undo info and read it. + */ + if (read_undo_file) { + char_u hash[UNDO_HASH_SIZE]; + + sha256_finish(&sha_ctx, hash); + u_read_undo(NULL, hash, fname); + } + + if (!read_stdin && !read_buffer) { + int m = msg_scroll; + int n = msg_scrolled; + + /* Save the fileformat now, otherwise the buffer will be considered + * modified if the format/encoding was automatically detected. */ + if (set_options) + save_file_ff(curbuf); + + /* + * The output from the autocommands should not overwrite anything and + * should not be overwritten: Set msg_scroll, restore its value if no + * output was done. + */ + msg_scroll = TRUE; + if (filtering) + apply_autocmds_exarg(EVENT_FILTERREADPOST, NULL, sfname, + FALSE, curbuf, eap); + else if (newfile) + apply_autocmds_exarg(EVENT_BUFREADPOST, NULL, sfname, + FALSE, curbuf, eap); + else + apply_autocmds_exarg(EVENT_FILEREADPOST, sfname, sfname, + FALSE, NULL, eap); + if (msg_scrolled == n) + msg_scroll = m; + if (aborting()) /* autocmds may abort script processing */ + return FAIL; + } + + if (recoverymode && error) + return FAIL; + return OK; +} + +#ifdef OPEN_CHR_FILES +/* + * Returns TRUE if the file name argument is of the form "/dev/fd/\d\+", + * which is the name of files used for process substitution output by + * some shells on some operating systems, e.g., bash on SunOS. + * Do not accept "/dev/fd/[012]", opening these may hang Vim. + */ +static int is_dev_fd_file(fname) +char_u *fname; +{ + return STRNCMP(fname, "/dev/fd/", 8) == 0 + && VIM_ISDIGIT(fname[8]) + && *skipdigits(fname + 9) == NUL + && (fname[9] != NUL + || (fname[8] != '0' && fname[8] != '1' && fname[8] != '2')); +} +#endif + + +/* + * From the current line count and characters read after that, estimate the + * line number where we are now. + * Used for error messages that include a line number. + */ +static linenr_T readfile_linenr(linecnt, p, endp) +linenr_T linecnt; /* line count before reading more bytes */ +char_u *p; /* start of more bytes read */ +char_u *endp; /* end of more bytes read */ +{ + char_u *s; + linenr_T lnum; + + lnum = curbuf->b_ml.ml_line_count - linecnt + 1; + for (s = p; s < endp; ++s) + if (*s == '\n') + ++lnum; + return lnum; +} + +/* + * Fill "*eap" to force the 'fileencoding', 'fileformat' and 'binary to be + * equal to the buffer "buf". Used for calling readfile(). + * Returns OK or FAIL. + */ +int prep_exarg(eap, buf) +exarg_T *eap; +buf_T *buf; +{ + eap->cmd = alloc((unsigned)(STRLEN(buf->b_p_ff) + + STRLEN(buf->b_p_fenc) + + 15)); + if (eap->cmd == NULL) + return FAIL; + + sprintf((char *)eap->cmd, "e ++ff=%s ++enc=%s", buf->b_p_ff, buf->b_p_fenc); + eap->force_enc = 14 + (int)STRLEN(buf->b_p_ff); + eap->bad_char = buf->b_bad_char; + eap->force_ff = 7; + + eap->force_bin = buf->b_p_bin ? FORCE_BIN : FORCE_NOBIN; + eap->read_edit = FALSE; + eap->forceit = FALSE; + return OK; +} + +/* + * Set default or forced 'fileformat' and 'binary'. + */ +void set_file_options(set_options, eap) +int set_options; +exarg_T *eap; +{ + /* set default 'fileformat' */ + if (set_options) { + if (eap != NULL && eap->force_ff != 0) + set_fileformat(get_fileformat_force(curbuf, eap), OPT_LOCAL); + else if (*p_ffs != NUL) + set_fileformat(default_fileformat(), OPT_LOCAL); + } + + /* set or reset 'binary' */ + if (eap != NULL && eap->force_bin != 0) { + int oldval = curbuf->b_p_bin; + + curbuf->b_p_bin = (eap->force_bin == FORCE_BIN); + set_options_bin(oldval, curbuf->b_p_bin, OPT_LOCAL); + } +} + +/* + * Set forced 'fileencoding'. + */ +void set_forced_fenc(eap) +exarg_T *eap; +{ + if (eap->force_enc != 0) { + char_u *fenc = enc_canonize(eap->cmd + eap->force_enc); + + if (fenc != NULL) + set_string_option_direct((char_u *)"fenc", -1, + fenc, OPT_FREE|OPT_LOCAL, 0); + vim_free(fenc); + } +} + +/* + * Find next fileencoding to use from 'fileencodings'. + * "pp" points to fenc_next. It's advanced to the next item. + * When there are no more items, an empty string is returned and *pp is set to + * NULL. + * When *pp is not set to NULL, the result is in allocated memory. + */ +static char_u * next_fenc(pp) +char_u **pp; +{ + char_u *p; + char_u *r; + + if (**pp == NUL) { + *pp = NULL; + return (char_u *)""; + } + p = vim_strchr(*pp, ','); + if (p == NULL) { + r = enc_canonize(*pp); + *pp += STRLEN(*pp); + } else { + r = vim_strnsave(*pp, (int)(p - *pp)); + *pp = p + 1; + if (r != NULL) { + p = enc_canonize(r); + vim_free(r); + r = p; + } + } + if (r == NULL) { /* out of memory */ + r = (char_u *)""; + *pp = NULL; + } + return r; +} + +/* + * Convert a file with the 'charconvert' expression. + * This closes the file which is to be read, converts it and opens the + * resulting file for reading. + * Returns name of the resulting converted file (the caller should delete it + * after reading it). + * Returns NULL if the conversion failed ("*fdp" is not set) . + */ +static char_u * readfile_charconvert(fname, fenc, fdp) +char_u *fname; /* name of input file */ +char_u *fenc; /* converted from */ +int *fdp; /* in/out: file descriptor of file */ +{ + char_u *tmpname; + char_u *errmsg = NULL; + + tmpname = vim_tempname('r'); + if (tmpname == NULL) + errmsg = (char_u *)_("Can't find temp file for conversion"); + else { + close(*fdp); /* close the input file, ignore errors */ + *fdp = -1; + if (eval_charconvert(fenc, enc_utf8 ? (char_u *)"utf-8" : p_enc, + fname, tmpname) == FAIL) + errmsg = (char_u *)_("Conversion with 'charconvert' failed"); + if (errmsg == NULL && (*fdp = mch_open((char *)tmpname, + O_RDONLY | O_EXTRA, 0)) < 0) + errmsg = (char_u *)_("can't read output of 'charconvert'"); + } + + if (errmsg != NULL) { + /* Don't use emsg(), it breaks mappings, the retry with + * another type of conversion might still work. */ + MSG(errmsg); + if (tmpname != NULL) { + mch_remove(tmpname); /* delete converted file */ + vim_free(tmpname); + tmpname = NULL; + } + } + + /* If the input file is closed, open it (caller should check for error). */ + if (*fdp < 0) + *fdp = mch_open((char *)fname, O_RDONLY | O_EXTRA, 0); + + return tmpname; +} + + +/* + * Read marks for the current buffer from the viminfo file, when we support + * buffer marks and the buffer has a name. + */ +static void check_marks_read() { + if (!curbuf->b_marks_read && get_viminfo_parameter('\'') > 0 + && curbuf->b_ffname != NULL) + read_viminfo(NULL, VIF_WANT_MARKS); + + /* Always set b_marks_read; needed when 'viminfo' is changed to include + * the ' parameter after opening a buffer. */ + curbuf->b_marks_read = TRUE; +} + +/* + * Get the crypt method used for a file from "ptr[len]", the magic text at the + * start of the file. + * Returns -1 when no encryption used. + */ +static int crypt_method_from_magic(ptr, len) +char *ptr; +int len; +{ + int i; + + for (i = 0; i < (int)(sizeof(crypt_magic) / sizeof(crypt_magic[0])); i++) { + if (len < (CRYPT_MAGIC_LEN + crypt_salt_len[i] + crypt_seed_len[i])) + continue; + if (memcmp(ptr, crypt_magic[i], CRYPT_MAGIC_LEN) == 0) + return i; + } + + i = (int)STRLEN(crypt_magic_head); + if (len >= i && memcmp(ptr, crypt_magic_head, i) == 0) + EMSG(_("E821: File is encrypted with unknown method")); + + return -1; +} + +/* + * Check for magic number used for encryption. Applies to the current buffer. + * If found, the magic number is removed from ptr[*sizep] and *sizep and + * *filesizep are updated. + * Return the (new) encryption key, NULL for no encryption. + */ +static char_u * check_for_cryptkey(cryptkey, ptr, sizep, filesizep, newfile, + fname, + did_ask) +char_u *cryptkey; /* previous encryption key or NULL */ +char_u *ptr; /* pointer to read bytes */ +long *sizep; /* length of read bytes */ +off_t *filesizep; /* nr of bytes used from file */ +int newfile; /* editing a new buffer */ +char_u *fname; /* file name to display */ +int *did_ask; /* flag: whether already asked for key */ +{ + int method = crypt_method_from_magic((char *)ptr, *sizep); + int b_p_ro = curbuf->b_p_ro; + + if (method >= 0) { + /* Mark the buffer as read-only until the decryption has taken place. + * Avoids accidentally overwriting the file with garbage. */ + curbuf->b_p_ro = TRUE; + + set_crypt_method(curbuf, method); + if (method > 0) + (void)blowfish_self_test(); + if (cryptkey == NULL && !*did_ask) { + if (*curbuf->b_p_key) + cryptkey = curbuf->b_p_key; + else { + /* When newfile is TRUE, store the typed key in the 'key' + * option and don't free it. bf needs hash of the key saved. + * Don't ask for the key again when first time Enter was hit. + * Happens when retrying to detect encoding. */ + smsg((char_u *)_(need_key_msg), fname); + msg_scroll = TRUE; + cryptkey = get_crypt_key(newfile, FALSE); + *did_ask = TRUE; + + /* check if empty key entered */ + if (cryptkey != NULL && *cryptkey == NUL) { + if (cryptkey != curbuf->b_p_key) + vim_free(cryptkey); + cryptkey = NULL; + } + } + } + + if (cryptkey != NULL) { + int seed_len = crypt_seed_len[method]; + int salt_len = crypt_salt_len[method]; + + crypt_push_state(); + use_crypt_method = method; + if (method == 0) + crypt_init_keys(cryptkey); + else { + bf_key_init(cryptkey, ptr + CRYPT_MAGIC_LEN, salt_len); + bf_ofb_init(ptr + CRYPT_MAGIC_LEN + salt_len, seed_len); + } + + /* Remove magic number from the text */ + *filesizep += CRYPT_MAGIC_LEN + salt_len + seed_len; + *sizep -= CRYPT_MAGIC_LEN + salt_len + seed_len; + mch_memmove(ptr, ptr + CRYPT_MAGIC_LEN + salt_len + seed_len, + (size_t)*sizep); + /* Restore the read-only flag. */ + curbuf->b_p_ro = b_p_ro; + } + } + /* When starting to edit a new file which does not have encryption, clear + * the 'key' option, except when starting up (called with -x argument) */ + else if (newfile && *curbuf->b_p_key != NUL && !starting) + set_option_value((char_u *)"key", 0L, (char_u *)"", OPT_LOCAL); + + return cryptkey; +} + +/* + * Check for magic number used for encryption. Applies to the current buffer. + * If found and decryption is possible returns OK; + */ +int prepare_crypt_read(fp) +FILE *fp; +{ + int method; + char_u buffer[CRYPT_MAGIC_LEN + CRYPT_SALT_LEN_MAX + + CRYPT_SEED_LEN_MAX + 2]; + + if (fread(buffer, CRYPT_MAGIC_LEN, 1, fp) != 1) + return FAIL; + method = crypt_method_from_magic((char *)buffer, + CRYPT_MAGIC_LEN + + CRYPT_SEED_LEN_MAX + + CRYPT_SALT_LEN_MAX); + if (method < 0 || method != get_crypt_method(curbuf)) + return FAIL; + + crypt_push_state(); + if (method == 0) + crypt_init_keys(curbuf->b_p_key); + else { + int salt_len = crypt_salt_len[method]; + int seed_len = crypt_seed_len[method]; + + if (fread(buffer, salt_len + seed_len, 1, fp) != 1) + return FAIL; + bf_key_init(curbuf->b_p_key, buffer, salt_len); + bf_ofb_init(buffer + salt_len, seed_len); + } + return OK; +} + +/* + * Prepare for writing encrypted bytes for buffer "buf". + * Returns a pointer to an allocated header of length "*lenp". + * When out of memory returns NULL. + * Otherwise calls crypt_push_state(), call crypt_pop_state() later. + */ +char_u * prepare_crypt_write(buf, lenp) +buf_T *buf; +int *lenp; +{ + char_u *header; + int seed_len = crypt_seed_len[get_crypt_method(buf)]; + int salt_len = crypt_salt_len[get_crypt_method(buf)]; + char_u *salt; + char_u *seed; + + header = alloc_clear(CRYPT_MAGIC_LEN + CRYPT_SALT_LEN_MAX + + CRYPT_SEED_LEN_MAX + 2); + if (header != NULL) { + crypt_push_state(); + use_crypt_method = get_crypt_method(buf); /* select zip or blowfish */ + vim_strncpy(header, (char_u *)crypt_magic[use_crypt_method], + CRYPT_MAGIC_LEN); + if (use_crypt_method == 0) + crypt_init_keys(buf->b_p_key); + else { + /* Using blowfish, add salt and seed. */ + salt = header + CRYPT_MAGIC_LEN; + seed = salt + salt_len; + sha2_seed(salt, salt_len, seed, seed_len); + bf_key_init(buf->b_p_key, salt, salt_len); + bf_ofb_init(seed, seed_len); + } + } + *lenp = CRYPT_MAGIC_LEN + salt_len + seed_len; + return header; +} + + +#ifdef UNIX +static void set_file_time(fname, atime, mtime) +char_u *fname; +time_t atime; /* access time */ +time_t mtime; /* modification time */ +{ +# if defined(HAVE_UTIME) && defined(HAVE_UTIME_H) + struct utimbuf buf; + + buf.actime = atime; + buf.modtime = mtime; + (void)utime((char *)fname, &buf); +# else +# if defined(HAVE_UTIMES) + struct timeval tvp[2]; + + tvp[0].tv_sec = atime; + tvp[0].tv_usec = 0; + tvp[1].tv_sec = mtime; + tvp[1].tv_usec = 0; + (void)utimes((char *)fname, (const struct timeval *)&tvp); +# endif +# endif +} +#endif /* UNIX */ + + +/* + * Return TRUE if a file appears to be read-only from the file permissions. + */ +int check_file_readonly(fname, perm) +char_u *fname; /* full path to file */ +int perm; /* known permissions on file */ +{ +#ifndef USE_MCH_ACCESS + int fd = 0; +#endif + + return +#ifdef USE_MCH_ACCESS +# ifdef UNIX + (perm & 0222) == 0 || +# endif + mch_access((char *)fname, W_OK) +#else + (fd = mch_open((char *)fname, O_RDWR | O_EXTRA, 0)) < 0 + ? TRUE : (close(fd), FALSE) +#endif + ; +} + + +/* + * buf_write() - write to file "fname" lines "start" through "end" + * + * We do our own buffering here because fwrite() is so slow. + * + * If "forceit" is true, we don't care for errors when attempting backups. + * In case of an error everything possible is done to restore the original + * file. But when "forceit" is TRUE, we risk losing it. + * + * When "reset_changed" is TRUE and "append" == FALSE and "start" == 1 and + * "end" == curbuf->b_ml.ml_line_count, reset curbuf->b_changed. + * + * This function must NOT use NameBuff (because it's called by autowrite()). + * + * return FAIL for failure, OK otherwise + */ +int buf_write(buf, fname, sfname, start, end, eap, append, forceit, + reset_changed, filtering) +buf_T *buf; +char_u *fname; +char_u *sfname; +linenr_T start, end; +exarg_T *eap; /* for forced 'ff' and 'fenc', can be + NULL! */ +int append; /* append to the file */ +int forceit; +int reset_changed; +int filtering; +{ + int fd; + char_u *backup = NULL; + int backup_copy = FALSE; /* copy the original file? */ + int dobackup; + char_u *ffname; + char_u *wfname = NULL; /* name of file to write to */ + char_u *s; + char_u *ptr; + char_u c; + int len; + linenr_T lnum; + long nchars; + char_u *errmsg = NULL; + int errmsg_allocated = FALSE; + char_u *errnum = NULL; + char_u *buffer; + char_u smallbuf[SMBUFSIZE]; + char_u *backup_ext; + int bufsize; + long perm; /* file permissions */ + int retval = OK; + int newfile = FALSE; /* TRUE if file doesn't exist yet */ + int msg_save = msg_scroll; + int overwriting; /* TRUE if writing over original */ + int no_eol = FALSE; /* no end-of-line written */ + int device = FALSE; /* writing to a device */ + struct stat st_old; + int prev_got_int = got_int; + int file_readonly = FALSE; /* overwritten file is read-only */ + static char *err_readonly = + "is read-only (cannot override: \"W\" in 'cpoptions')"; +#if defined(UNIX) || defined(__EMX__XX) /*XXX fix me sometime? */ + int made_writable = FALSE; /* 'w' bit has been set */ +#endif + /* writing everything */ + int whole = (start == 1 && end == buf->b_ml.ml_line_count); + linenr_T old_line_count = buf->b_ml.ml_line_count; + int attr; + int fileformat; + int write_bin; + struct bw_info write_info; /* info for buf_write_bytes() */ + int converted = FALSE; + int notconverted = FALSE; + char_u *fenc; /* effective 'fileencoding' */ + char_u *fenc_tofree = NULL; /* allocated "fenc" */ +#ifdef HAS_BW_FLAGS + int wb_flags = 0; +#endif +#ifdef HAVE_ACL + vim_acl_T acl = NULL; /* ACL copied from original file to + backup or new file */ +#endif + int write_undo_file = FALSE; + context_sha256_T sha_ctx; + int crypt_method_used; + + if (fname == NULL || *fname == NUL) /* safety check */ + return FAIL; + if (buf->b_ml.ml_mfp == NULL) { + /* This can happen during startup when there is a stray "w" in the + * vimrc file. */ + EMSG(_(e_emptybuf)); + return FAIL; + } + + /* + * Disallow writing from .exrc and .vimrc in current directory for + * security reasons. + */ + if (check_secure()) + return FAIL; + + /* Avoid a crash for a long name. */ + if (STRLEN(fname) >= MAXPATHL) { + EMSG(_(e_longname)); + return FAIL; + } + + /* must init bw_conv_buf and bw_iconv_fd before jumping to "fail" */ + write_info.bw_conv_buf = NULL; + write_info.bw_conv_error = FALSE; + write_info.bw_conv_error_lnum = 0; + write_info.bw_restlen = 0; +# ifdef USE_ICONV + write_info.bw_iconv_fd = (iconv_t)-1; +# endif + + /* After writing a file changedtick changes but we don't want to display + * the line. */ + ex_no_reprint = TRUE; + + /* + * If there is no file name yet, use the one for the written file. + * BF_NOTEDITED is set to reflect this (in case the write fails). + * Don't do this when the write is for a filter command. + * Don't do this when appending. + * Only do this when 'cpoptions' contains the 'F' flag. + */ + if (buf->b_ffname == NULL + && reset_changed + && whole + && buf == curbuf + && !bt_nofile(buf) + && !filtering + && (!append || vim_strchr(p_cpo, CPO_FNAMEAPP) != NULL) + && vim_strchr(p_cpo, CPO_FNAMEW) != NULL) { + if (set_rw_fname(fname, sfname) == FAIL) + return FAIL; + buf = curbuf; /* just in case autocmds made "buf" invalid */ + } + + if (sfname == NULL) + sfname = fname; + /* + * For Unix: Use the short file name whenever possible. + * Avoids problems with networks and when directory names are changed. + * Don't do this for MS-DOS, a "cd" in a sub-shell may have moved us to + * another directory, which we don't detect + */ + ffname = fname; /* remember full fname */ +#ifdef UNIX + fname = sfname; +#endif + + if (buf->b_ffname != NULL && fnamecmp(ffname, buf->b_ffname) == 0) + overwriting = TRUE; + else + overwriting = FALSE; + + if (exiting) + settmode(TMODE_COOK); /* when exiting allow typeahead now */ + + ++no_wait_return; /* don't wait for return yet */ + + /* + * Set '[ and '] marks to the lines to be written. + */ + buf->b_op_start.lnum = start; + buf->b_op_start.col = 0; + buf->b_op_end.lnum = end; + buf->b_op_end.col = 0; + + { + aco_save_T aco; + int buf_ffname = FALSE; + int buf_sfname = FALSE; + int buf_fname_f = FALSE; + int buf_fname_s = FALSE; + int did_cmd = FALSE; + int nofile_err = FALSE; + int empty_memline = (buf->b_ml.ml_mfp == NULL); + + /* + * Apply PRE autocommands. + * Set curbuf to the buffer to be written. + * Careful: The autocommands may call buf_write() recursively! + */ + if (ffname == buf->b_ffname) + buf_ffname = TRUE; + if (sfname == buf->b_sfname) + buf_sfname = TRUE; + if (fname == buf->b_ffname) + buf_fname_f = TRUE; + if (fname == buf->b_sfname) + buf_fname_s = TRUE; + + /* set curwin/curbuf to buf and save a few things */ + aucmd_prepbuf(&aco, buf); + + if (append) { + if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEAPPENDCMD, + sfname, sfname, FALSE, curbuf, eap))) { + if (overwriting && bt_nofile(curbuf)) + nofile_err = TRUE; + else + apply_autocmds_exarg(EVENT_FILEAPPENDPRE, + sfname, sfname, FALSE, curbuf, eap); + } + } else if (filtering) { + apply_autocmds_exarg(EVENT_FILTERWRITEPRE, + NULL, sfname, FALSE, curbuf, eap); + } else if (reset_changed && whole) { + int was_changed = curbufIsChanged(); + + did_cmd = apply_autocmds_exarg(EVENT_BUFWRITECMD, + sfname, sfname, FALSE, curbuf, eap); + if (did_cmd) { + if (was_changed && !curbufIsChanged()) { + /* Written everything correctly and BufWriteCmd has reset + * 'modified': Correct the undo information so that an + * undo now sets 'modified'. */ + u_unchanged(curbuf); + u_update_save_nr(curbuf); + } + } else { + if (overwriting && bt_nofile(curbuf)) + nofile_err = TRUE; + else + apply_autocmds_exarg(EVENT_BUFWRITEPRE, + sfname, sfname, FALSE, curbuf, eap); + } + } else { + if (!(did_cmd = apply_autocmds_exarg(EVENT_FILEWRITECMD, + sfname, sfname, FALSE, curbuf, eap))) { + if (overwriting && bt_nofile(curbuf)) + nofile_err = TRUE; + else + apply_autocmds_exarg(EVENT_FILEWRITEPRE, + sfname, sfname, FALSE, curbuf, eap); + } + } + + /* restore curwin/curbuf and a few other things */ + aucmd_restbuf(&aco); + + /* + * In three situations we return here and don't write the file: + * 1. the autocommands deleted or unloaded the buffer. + * 2. The autocommands abort script processing. + * 3. If one of the "Cmd" autocommands was executed. + */ + if (!buf_valid(buf)) + buf = NULL; + if (buf == NULL || (buf->b_ml.ml_mfp == NULL && !empty_memline) + || did_cmd || nofile_err + || aborting() + ) { + --no_wait_return; + msg_scroll = msg_save; + if (nofile_err) + EMSG(_("E676: No matching autocommands for acwrite buffer")); + + if (nofile_err + || aborting() + ) + /* An aborting error, interrupt or exception in the + * autocommands. */ + return FAIL; + if (did_cmd) { + if (buf == NULL) + /* The buffer was deleted. We assume it was written + * (can't retry anyway). */ + return OK; + if (overwriting) { + /* Assume the buffer was written, update the timestamp. */ + ml_timestamp(buf); + if (append) + buf->b_flags &= ~BF_NEW; + else + buf->b_flags &= ~BF_WRITE_MASK; + } + if (reset_changed && buf->b_changed && !append + && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL)) + /* Buffer still changed, the autocommands didn't work + * properly. */ + return FAIL; + return OK; + } + if (!aborting()) + EMSG(_("E203: Autocommands deleted or unloaded buffer to be written")); + return FAIL; + } + + /* + * The autocommands may have changed the number of lines in the file. + * When writing the whole file, adjust the end. + * When writing part of the file, assume that the autocommands only + * changed the number of lines that are to be written (tricky!). + */ + if (buf->b_ml.ml_line_count != old_line_count) { + if (whole) /* write all */ + end = buf->b_ml.ml_line_count; + else if (buf->b_ml.ml_line_count > old_line_count) /* more lines */ + end += buf->b_ml.ml_line_count - old_line_count; + else { /* less lines */ + end -= old_line_count - buf->b_ml.ml_line_count; + if (end < start) { + --no_wait_return; + msg_scroll = msg_save; + EMSG(_("E204: Autocommand changed number of lines in unexpected way")); + return FAIL; + } + } + } + + /* + * The autocommands may have changed the name of the buffer, which may + * be kept in fname, ffname and sfname. + */ + if (buf_ffname) + ffname = buf->b_ffname; + if (buf_sfname) + sfname = buf->b_sfname; + if (buf_fname_f) + fname = buf->b_ffname; + if (buf_fname_s) + fname = buf->b_sfname; + } + + + if (shortmess(SHM_OVER) && !exiting) + msg_scroll = FALSE; /* overwrite previous file message */ + else + msg_scroll = TRUE; /* don't overwrite previous file message */ + if (!filtering) + filemess(buf, +#ifndef UNIX + sfname, +#else + fname, +#endif + (char_u *)"", 0); /* show that we are busy */ + msg_scroll = FALSE; /* always overwrite the file message now */ + + buffer = alloc(BUFSIZE); + if (buffer == NULL) { /* can't allocate big buffer, use small + * one (to be able to write when out of + * memory) */ + buffer = smallbuf; + bufsize = SMBUFSIZE; + } else + bufsize = BUFSIZE; + + /* + * Get information about original file (if there is one). + */ +#if defined(UNIX) && !defined(ARCHIE) + st_old.st_dev = 0; + st_old.st_ino = 0; + perm = -1; + if (mch_stat((char *)fname, &st_old) < 0) + newfile = TRUE; + else { + perm = st_old.st_mode; + if (!S_ISREG(st_old.st_mode)) { /* not a file */ + if (S_ISDIR(st_old.st_mode)) { + errnum = (char_u *)"E502: "; + errmsg = (char_u *)_("is a directory"); + goto fail; + } + if (mch_nodetype(fname) != NODE_WRITABLE) { + errnum = (char_u *)"E503: "; + errmsg = (char_u *)_("is not a file or writable device"); + goto fail; + } + /* It's a device of some kind (or a fifo) which we can write to + * but for which we can't make a backup. */ + device = TRUE; + newfile = TRUE; + perm = -1; + } + } +#else /* !UNIX */ + /* + * Check for a writable device name. + */ + c = mch_nodetype(fname); + if (c == NODE_OTHER) { + errnum = (char_u *)"E503: "; + errmsg = (char_u *)_("is not a file or writable device"); + goto fail; + } + if (c == NODE_WRITABLE) { + device = TRUE; + newfile = TRUE; + perm = -1; + } else { + perm = mch_getperm(fname); + if (perm < 0) + newfile = TRUE; + else if (mch_isdir(fname)) { + errnum = (char_u *)"E502: "; + errmsg = (char_u *)_("is a directory"); + goto fail; + } + if (overwriting) + (void)mch_stat((char *)fname, &st_old); + } +#endif /* !UNIX */ + + if (!device && !newfile) { + /* + * Check if the file is really writable (when renaming the file to + * make a backup we won't discover it later). + */ + file_readonly = check_file_readonly(fname, (int)perm); + + if (!forceit && file_readonly) { + if (vim_strchr(p_cpo, CPO_FWRITE) != NULL) { + errnum = (char_u *)"E504: "; + errmsg = (char_u *)_(err_readonly); + } else { + errnum = (char_u *)"E505: "; + errmsg = (char_u *)_("is read-only (add ! to override)"); + } + goto fail; + } + + /* + * Check if the timestamp hasn't changed since reading the file. + */ + if (overwriting) { + retval = check_mtime(buf, &st_old); + if (retval == FAIL) + goto fail; + } + } + +#ifdef HAVE_ACL + /* + * For systems that support ACL: get the ACL from the original file. + */ + if (!newfile) + acl = mch_get_acl(fname); +#endif + + /* + * If 'backupskip' is not empty, don't make a backup for some files. + */ + dobackup = (p_wb || p_bk || *p_pm != NUL); + if (dobackup && *p_bsk != NUL && match_file_list(p_bsk, sfname, ffname)) + dobackup = FALSE; + + /* + * Save the value of got_int and reset it. We don't want a previous + * interruption cancel writing, only hitting CTRL-C while writing should + * abort it. + */ + prev_got_int = got_int; + got_int = FALSE; + + /* Mark the buffer as 'being saved' to prevent changed buffer warnings */ + buf->b_saving = TRUE; + + /* + * If we are not appending or filtering, the file exists, and the + * 'writebackup', 'backup' or 'patchmode' option is set, need a backup. + * When 'patchmode' is set also make a backup when appending. + * + * Do not make any backup, if 'writebackup' and 'backup' are both switched + * off. This helps when editing large files on almost-full disks. + */ + if (!(append && *p_pm == NUL) && !filtering && perm >= 0 && dobackup) { +#if defined(UNIX) || defined(WIN32) + struct stat st; +#endif + + if ((bkc_flags & BKC_YES) || append) /* "yes" */ + backup_copy = TRUE; +#if defined(UNIX) || defined(WIN32) + else if ((bkc_flags & BKC_AUTO)) { /* "auto" */ + int i; + +# ifdef UNIX + /* + * Don't rename the file when: + * - it's a hard link + * - it's a symbolic link + * - we don't have write permission in the directory + * - we can't set the owner/group of the new file + */ + if (st_old.st_nlink > 1 + || mch_lstat((char *)fname, &st) < 0 + || st.st_dev != st_old.st_dev + || st.st_ino != st_old.st_ino +# ifndef HAVE_FCHOWN + || st.st_uid != st_old.st_uid + || st.st_gid != st_old.st_gid +# endif + ) + backup_copy = TRUE; + else +# else +# endif + { + /* + * Check if we can create a file and set the owner/group to + * the ones from the original file. + * First find a file name that doesn't exist yet (use some + * arbitrary numbers). + */ + STRCPY(IObuff, fname); + for (i = 4913;; i += 123) { + sprintf((char *)gettail(IObuff), "%d", i); + if (mch_lstat((char *)IObuff, &st) < 0) + break; + } + fd = mch_open((char *)IObuff, + O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm); + if (fd < 0) /* can't write in directory */ + backup_copy = TRUE; + else { +# ifdef UNIX +# ifdef HAVE_FCHOWN + ignored = fchown(fd, st_old.st_uid, st_old.st_gid); +# endif + if (mch_stat((char *)IObuff, &st) < 0 + || st.st_uid != st_old.st_uid + || st.st_gid != st_old.st_gid + || (long)st.st_mode != perm) + backup_copy = TRUE; +# endif + /* Close the file before removing it, on MS-Windows we + * can't delete an open file. */ + close(fd); + mch_remove(IObuff); + } + } + } + + /* + * Break symlinks and/or hardlinks if we've been asked to. + */ + if ((bkc_flags & BKC_BREAKSYMLINK) || (bkc_flags & BKC_BREAKHARDLINK)) { +# ifdef UNIX + int lstat_res; + + lstat_res = mch_lstat((char *)fname, &st); + + /* Symlinks. */ + if ((bkc_flags & BKC_BREAKSYMLINK) + && lstat_res == 0 + && st.st_ino != st_old.st_ino) + backup_copy = FALSE; + + /* Hardlinks. */ + if ((bkc_flags & BKC_BREAKHARDLINK) + && st_old.st_nlink > 1 + && (lstat_res != 0 || st.st_ino == st_old.st_ino)) + backup_copy = FALSE; +# else +# endif + } + +#endif + + /* make sure we have a valid backup extension to use */ + if (*p_bex == NUL) + backup_ext = (char_u *)".bak"; + else + backup_ext = p_bex; + + if (backup_copy + && (fd = mch_open((char *)fname, O_RDONLY | O_EXTRA, 0)) >= 0) { + int bfd; + char_u *copybuf, *wp; + int some_error = FALSE; + struct stat st_new; + char_u *dirp; + char_u *rootname; +#if defined(UNIX) && !defined(SHORT_FNAME) + int did_set_shortname; +#endif + + copybuf = alloc(BUFSIZE + 1); + if (copybuf == NULL) { + some_error = TRUE; /* out of memory */ + goto nobackup; + } + + /* + * Try to make the backup in each directory in the 'bdir' option. + * + * Unix semantics has it, that we may have a writable file, + * that cannot be recreated with a simple open(..., O_CREAT, ) e.g: + * - the directory is not writable, + * - the file may be a symbolic link, + * - the file may belong to another user/group, etc. + * + * For these reasons, the existing writable file must be truncated + * and reused. Creation of a backup COPY will be attempted. + */ + dirp = p_bdir; + while (*dirp) { +#ifdef UNIX + st_new.st_ino = 0; + st_new.st_dev = 0; + st_new.st_gid = 0; +#endif + + /* + * Isolate one directory name, using an entry in 'bdir'. + */ + (void)copy_option_part(&dirp, copybuf, BUFSIZE, ","); + rootname = get_file_in_dir(fname, copybuf); + if (rootname == NULL) { + some_error = TRUE; /* out of memory */ + goto nobackup; + } + +#if defined(UNIX) && !defined(SHORT_FNAME) + did_set_shortname = FALSE; +#endif + + /* + * May try twice if 'shortname' not set. + */ + for (;; ) { + /* + * Make backup file name. + */ + backup = buf_modname( +#ifdef SHORT_FNAME + TRUE, +#else + (buf->b_p_sn || buf->b_shortname), +#endif + rootname, backup_ext, FALSE); + if (backup == NULL) { + vim_free(rootname); + some_error = TRUE; /* out of memory */ + goto nobackup; + } + + /* + * Check if backup file already exists. + */ + if (mch_stat((char *)backup, &st_new) >= 0) { +#ifdef UNIX + /* + * Check if backup file is same as original file. + * May happen when modname() gave the same file back. + * E.g. silly link, or file name-length reached. + * If we don't check here, we either ruin the file + * when copying or erase it after writing. jw. + */ + if (st_new.st_dev == st_old.st_dev + && st_new.st_ino == st_old.st_ino) { + vim_free(backup); + backup = NULL; /* no backup file to delete */ +# ifndef SHORT_FNAME + /* + * may try again with 'shortname' set + */ + if (!(buf->b_shortname || buf->b_p_sn)) { + buf->b_shortname = TRUE; + did_set_shortname = TRUE; + continue; + } + /* setting shortname didn't help */ + if (did_set_shortname) + buf->b_shortname = FALSE; +# endif + break; + } +#endif + + /* + * If we are not going to keep the backup file, don't + * delete an existing one, try to use another name. + * Change one character, just before the extension. + */ + if (!p_bk) { + wp = backup + STRLEN(backup) - 1 + - STRLEN(backup_ext); + if (wp < backup) /* empty file name ??? */ + wp = backup; + *wp = 'z'; + while (*wp > 'a' + && mch_stat((char *)backup, &st_new) >= 0) + --*wp; + /* They all exist??? Must be something wrong. */ + if (*wp == 'a') { + vim_free(backup); + backup = NULL; + } + } + } + break; + } + vim_free(rootname); + + /* + * Try to create the backup file + */ + if (backup != NULL) { + /* remove old backup, if present */ + mch_remove(backup); + /* Open with O_EXCL to avoid the file being created while + * we were sleeping (symlink hacker attack?) */ + bfd = mch_open((char *)backup, + O_WRONLY|O_CREAT|O_EXTRA|O_EXCL|O_NOFOLLOW, + perm & 0777); + if (bfd < 0) { + vim_free(backup); + backup = NULL; + } else { + /* set file protection same as original file, but + * strip s-bit */ + (void)mch_setperm(backup, perm & 0777); + +#ifdef UNIX + /* + * Try to set the group of the backup same as the + * original file. If this fails, set the protection + * bits for the group same as the protection bits for + * others. + */ + if (st_new.st_gid != st_old.st_gid +# ifdef HAVE_FCHOWN /* sequent-ptx lacks fchown() */ + && fchown(bfd, (uid_t)-1, st_old.st_gid) != 0 +# endif + ) + mch_setperm(backup, + (perm & 0707) | ((perm & 07) << 3)); +# ifdef HAVE_SELINUX + mch_copy_sec(fname, backup); +# endif +#endif + + /* + * copy the file. + */ + write_info.bw_fd = bfd; + write_info.bw_buf = copybuf; +#ifdef HAS_BW_FLAGS + write_info.bw_flags = FIO_NOCONVERT; +#endif + while ((write_info.bw_len = read_eintr(fd, copybuf, + BUFSIZE)) > 0) { + if (buf_write_bytes(&write_info) == FAIL) { + errmsg = (char_u *)_( + "E506: Can't write to backup file (add ! to override)"); + break; + } + ui_breakcheck(); + if (got_int) { + errmsg = (char_u *)_(e_interr); + break; + } + } + + if (close(bfd) < 0 && errmsg == NULL) + errmsg = (char_u *)_( + "E507: Close error for backup file (add ! to override)"); + if (write_info.bw_len < 0) + errmsg = (char_u *)_( + "E508: Can't read file for backup (add ! to override)"); +#ifdef UNIX + set_file_time(backup, st_old.st_atime, st_old.st_mtime); +#endif +#ifdef HAVE_ACL + mch_set_acl(backup, acl); +#endif +#ifdef HAVE_SELINUX + mch_copy_sec(fname, backup); +#endif + break; + } + } + } +nobackup: + close(fd); /* ignore errors for closing read file */ + vim_free(copybuf); + + if (backup == NULL && errmsg == NULL) + errmsg = (char_u *)_( + "E509: Cannot create backup file (add ! to override)"); + /* ignore errors when forceit is TRUE */ + if ((some_error || errmsg != NULL) && !forceit) { + retval = FAIL; + goto fail; + } + errmsg = NULL; + } else { + char_u *dirp; + char_u *p; + char_u *rootname; + + /* + * Make a backup by renaming the original file. + */ + /* + * If 'cpoptions' includes the "W" flag, we don't want to + * overwrite a read-only file. But rename may be possible + * anyway, thus we need an extra check here. + */ + if (file_readonly && vim_strchr(p_cpo, CPO_FWRITE) != NULL) { + errnum = (char_u *)"E504: "; + errmsg = (char_u *)_(err_readonly); + goto fail; + } + + /* + * + * Form the backup file name - change path/fo.o.h to + * path/fo.o.h.bak Try all directories in 'backupdir', first one + * that works is used. + */ + dirp = p_bdir; + while (*dirp) { + /* + * Isolate one directory name and make the backup file name. + */ + (void)copy_option_part(&dirp, IObuff, IOSIZE, ","); + rootname = get_file_in_dir(fname, IObuff); + if (rootname == NULL) + backup = NULL; + else { + backup = buf_modname( +#ifdef SHORT_FNAME + TRUE, +#else + (buf->b_p_sn || buf->b_shortname), +#endif + rootname, backup_ext, FALSE); + vim_free(rootname); + } + + if (backup != NULL) { + /* + * If we are not going to keep the backup file, don't + * delete an existing one, try to use another name. + * Change one character, just before the extension. + */ + if (!p_bk && mch_getperm(backup) >= 0) { + p = backup + STRLEN(backup) - 1 - STRLEN(backup_ext); + if (p < backup) /* empty file name ??? */ + p = backup; + *p = 'z'; + while (*p > 'a' && mch_getperm(backup) >= 0) + --*p; + /* They all exist??? Must be something wrong! */ + if (*p == 'a') { + vim_free(backup); + backup = NULL; + } + } + } + if (backup != NULL) { + /* + * Delete any existing backup and move the current version + * to the backup. For safety, we don't remove the backup + * until the write has finished successfully. And if the + * 'backup' option is set, leave it around. + */ + /* + * If the renaming of the original file to the backup file + * works, quit here. + */ + if (vim_rename(fname, backup) == 0) + break; + + vim_free(backup); /* don't do the rename below */ + backup = NULL; + } + } + if (backup == NULL && !forceit) { + errmsg = (char_u *)_("E510: Can't make backup file (add ! to override)"); + goto fail; + } + } + } + +#if defined(UNIX) && !defined(ARCHIE) + /* When using ":w!" and the file was read-only: make it writable */ + if (forceit && perm >= 0 && !(perm & 0200) && st_old.st_uid == getuid() + && vim_strchr(p_cpo, CPO_FWRITE) == NULL) { + perm |= 0200; + (void)mch_setperm(fname, perm); + made_writable = TRUE; + } +#endif + + /* When using ":w!" and writing to the current file, 'readonly' makes no + * sense, reset it, unless 'Z' appears in 'cpoptions'. */ + if (forceit && overwriting && vim_strchr(p_cpo, CPO_KEEPRO) == NULL) { + buf->b_p_ro = FALSE; + need_maketitle = TRUE; /* set window title later */ + status_redraw_all(); /* redraw status lines later */ + } + + if (end > buf->b_ml.ml_line_count) + end = buf->b_ml.ml_line_count; + if (buf->b_ml.ml_flags & ML_EMPTY) + start = end + 1; + + /* + * If the original file is being overwritten, there is a small chance that + * we crash in the middle of writing. Therefore the file is preserved now. + * This makes all block numbers positive so that recovery does not need + * the original file. + * Don't do this if there is a backup file and we are exiting. + */ + if (reset_changed && !newfile && overwriting + && !(exiting && backup != NULL)) { + ml_preserve(buf, FALSE); + if (got_int) { + errmsg = (char_u *)_(e_interr); + goto restore_backup; + } + } + + + /* Default: write the file directly. May write to a temp file for + * multi-byte conversion. */ + wfname = fname; + + /* Check for forced 'fileencoding' from "++opt=val" argument. */ + if (eap != NULL && eap->force_enc != 0) { + fenc = eap->cmd + eap->force_enc; + fenc = enc_canonize(fenc); + fenc_tofree = fenc; + } else + fenc = buf->b_p_fenc; + + /* + * Check if the file needs to be converted. + */ + converted = need_conversion(fenc); + + /* + * Check if UTF-8 to UCS-2/4 or Latin1 conversion needs to be done. Or + * Latin1 to Unicode conversion. This is handled in buf_write_bytes(). + * Prepare the flags for it and allocate bw_conv_buf when needed. + */ + if (converted && (enc_utf8 || STRCMP(p_enc, "latin1") == 0)) { + wb_flags = get_fio_flags(fenc); + if (wb_flags & (FIO_UCS2 | FIO_UCS4 | FIO_UTF16 | FIO_UTF8)) { + /* Need to allocate a buffer to translate into. */ + if (wb_flags & (FIO_UCS2 | FIO_UTF16 | FIO_UTF8)) + write_info.bw_conv_buflen = bufsize * 2; + else /* FIO_UCS4 */ + write_info.bw_conv_buflen = bufsize * 4; + write_info.bw_conv_buf + = lalloc((long_u)write_info.bw_conv_buflen, TRUE); + if (write_info.bw_conv_buf == NULL) + end = 0; + } + } + + + + if (converted && wb_flags == 0) { +# ifdef USE_ICONV + /* + * Use iconv() conversion when conversion is needed and it's not done + * internally. + */ + write_info.bw_iconv_fd = (iconv_t)my_iconv_open(fenc, + enc_utf8 ? (char_u *)"utf-8" : p_enc); + if (write_info.bw_iconv_fd != (iconv_t)-1) { + /* We're going to use iconv(), allocate a buffer to convert in. */ + write_info.bw_conv_buflen = bufsize * ICONV_MULT; + write_info.bw_conv_buf + = lalloc((long_u)write_info.bw_conv_buflen, TRUE); + if (write_info.bw_conv_buf == NULL) + end = 0; + write_info.bw_first = TRUE; + } else +# endif + + /* + * When the file needs to be converted with 'charconvert' after + * writing, write to a temp file instead and let the conversion + * overwrite the original file. + */ + if (*p_ccv != NUL) { + wfname = vim_tempname('w'); + if (wfname == NULL) { /* Can't write without a tempfile! */ + errmsg = (char_u *)_("E214: Can't find temp file for writing"); + goto restore_backup; + } + } + } + if (converted && wb_flags == 0 +# ifdef USE_ICONV + && write_info.bw_iconv_fd == (iconv_t)-1 +# endif + && wfname == fname + ) { + if (!forceit) { + errmsg = (char_u *)_( + "E213: Cannot convert (add ! to write without conversion)"); + goto restore_backup; + } + notconverted = TRUE; + } + + /* + * Open the file "wfname" for writing. + * We may try to open the file twice: If we can't write to the + * file and forceit is TRUE we delete the existing file and try to create + * a new one. If this still fails we may have lost the original file! + * (this may happen when the user reached his quotum for number of files). + * Appending will fail if the file does not exist and forceit is FALSE. + */ + while ((fd = mch_open((char *)wfname, O_WRONLY | O_EXTRA | (append + ? (forceit ? ( + O_APPEND | + O_CREAT) : + O_APPEND) + : (O_CREAT | + O_TRUNC)) + , perm < 0 ? 0666 : (perm & 0777))) < 0) { + /* + * A forced write will try to create a new file if the old one is + * still readonly. This may also happen when the directory is + * read-only. In that case the mch_remove() will fail. + */ + if (errmsg == NULL) { +#ifdef UNIX + struct stat st; + + /* Don't delete the file when it's a hard or symbolic link. */ + if ((!newfile && st_old.st_nlink > 1) + || (mch_lstat((char *)fname, &st) == 0 + && (st.st_dev != st_old.st_dev + || st.st_ino != st_old.st_ino))) + errmsg = (char_u *)_("E166: Can't open linked file for writing"); + else +#endif + { + errmsg = (char_u *)_("E212: Can't open file for writing"); + if (forceit && vim_strchr(p_cpo, CPO_FWRITE) == NULL + && perm >= 0) { +#ifdef UNIX + /* we write to the file, thus it should be marked + writable after all */ + if (!(perm & 0200)) + made_writable = TRUE; + perm |= 0200; + if (st_old.st_uid != getuid() || st_old.st_gid != getgid()) + perm &= 0777; +#endif + if (!append) /* don't remove when appending */ + mch_remove(wfname); + continue; + } + } + } + +restore_backup: + { + struct stat st; + + /* + * If we failed to open the file, we don't need a backup. Throw it + * away. If we moved or removed the original file try to put the + * backup in its place. + */ + if (backup != NULL && wfname == fname) { + if (backup_copy) { + /* + * There is a small chance that we removed the original, + * try to move the copy in its place. + * This may not work if the vim_rename() fails. + * In that case we leave the copy around. + */ + /* If file does not exist, put the copy in its place */ + if (mch_stat((char *)fname, &st) < 0) + vim_rename(backup, fname); + /* if original file does exist throw away the copy */ + if (mch_stat((char *)fname, &st) >= 0) + mch_remove(backup); + } else { + /* try to put the original file back */ + vim_rename(backup, fname); + } + } + + /* if original file no longer exists give an extra warning */ + if (!newfile && mch_stat((char *)fname, &st) < 0) + end = 0; + } + + if (wfname != fname) + vim_free(wfname); + goto fail; + } + errmsg = NULL; + + + write_info.bw_fd = fd; + + if (*buf->b_p_key != NUL && !filtering) { + char_u *header; + int header_len; + + header = prepare_crypt_write(buf, &header_len); + if (header == NULL) + end = 0; + else { + /* Write magic number, so that Vim knows that this file is + * encrypted when reading it again. This also undergoes utf-8 to + * ucs-2/4 conversion when needed. */ + write_info.bw_buf = header; + write_info.bw_len = header_len; + write_info.bw_flags = FIO_NOCONVERT; + if (buf_write_bytes(&write_info) == FAIL) + end = 0; + wb_flags |= FIO_ENCRYPTED; + vim_free(header); + } + } + + write_info.bw_buf = buffer; + nchars = 0; + + /* use "++bin", "++nobin" or 'binary' */ + if (eap != NULL && eap->force_bin != 0) + write_bin = (eap->force_bin == FORCE_BIN); + else + write_bin = buf->b_p_bin; + + /* + * The BOM is written just after the encryption magic number. + * Skip it when appending and the file already existed, the BOM only makes + * sense at the start of the file. + */ + if (buf->b_p_bomb && !write_bin && (!append || perm < 0)) { + write_info.bw_len = make_bom(buffer, fenc); + if (write_info.bw_len > 0) { + /* don't convert, do encryption */ + write_info.bw_flags = FIO_NOCONVERT | wb_flags; + if (buf_write_bytes(&write_info) == FAIL) + end = 0; + else + nchars += write_info.bw_len; + } + } + write_info.bw_start_lnum = start; + + write_undo_file = (buf->b_p_udf && overwriting && !append + && !filtering && reset_changed); + if (write_undo_file) + /* Prepare for computing the hash value of the text. */ + sha256_start(&sha_ctx); + + write_info.bw_len = bufsize; +#ifdef HAS_BW_FLAGS + write_info.bw_flags = wb_flags; +#endif + fileformat = get_fileformat_force(buf, eap); + s = buffer; + len = 0; + for (lnum = start; lnum <= end; ++lnum) { + /* + * The next while loop is done once for each character written. + * Keep it fast! + */ + ptr = ml_get_buf(buf, lnum, FALSE) - 1; + if (write_undo_file) + sha256_update(&sha_ctx, ptr + 1, (UINT32_T)(STRLEN(ptr + 1) + 1)); + while ((c = *++ptr) != NUL) { + if (c == NL) + *s = NUL; /* replace newlines with NULs */ + else if (c == CAR && fileformat == EOL_MAC) + *s = NL; /* Mac: replace CRs with NLs */ + else + *s = c; + ++s; + if (++len != bufsize) + continue; + if (buf_write_bytes(&write_info) == FAIL) { + end = 0; /* write error: break loop */ + break; + } + nchars += bufsize; + s = buffer; + len = 0; + write_info.bw_start_lnum = lnum; + } + /* write failed or last line has no EOL: stop here */ + if (end == 0 + || (lnum == end + && write_bin + && (lnum == buf->b_no_eol_lnum + || (lnum == buf->b_ml.ml_line_count && !buf->b_p_eol)))) { + ++lnum; /* written the line, count it */ + no_eol = TRUE; + break; + } + if (fileformat == EOL_UNIX) + *s++ = NL; + else { + *s++ = CAR; /* EOL_MAC or EOL_DOS: write CR */ + if (fileformat == EOL_DOS) { /* write CR-NL */ + if (++len == bufsize) { + if (buf_write_bytes(&write_info) == FAIL) { + end = 0; /* write error: break loop */ + break; + } + nchars += bufsize; + s = buffer; + len = 0; + } + *s++ = NL; + } + } + if (++len == bufsize && end) { + if (buf_write_bytes(&write_info) == FAIL) { + end = 0; /* write error: break loop */ + break; + } + nchars += bufsize; + s = buffer; + len = 0; + + ui_breakcheck(); + if (got_int) { + end = 0; /* Interrupted, break loop */ + break; + } + } + } + if (len > 0 && end > 0) { + write_info.bw_len = len; + if (buf_write_bytes(&write_info) == FAIL) + end = 0; /* write error */ + nchars += len; + } + +#if defined(UNIX) && defined(HAVE_FSYNC) + /* On many journalling file systems there is a bug that causes both the + * original and the backup file to be lost when halting the system right + * after writing the file. That's because only the meta-data is + * journalled. Syncing the file slows down the system, but assures it has + * been written to disk and we don't lose it. + * For a device do try the fsync() but don't complain if it does not work + * (could be a pipe). + * If the 'fsync' option is FALSE, don't fsync(). Useful for laptops. */ + if (p_fs && fsync(fd) != 0 && !device) { + errmsg = (char_u *)_("E667: Fsync failed"); + end = 0; + } +#endif + +#ifdef HAVE_SELINUX + /* Probably need to set the security context. */ + if (!backup_copy) + mch_copy_sec(backup, wfname); +#endif + +#ifdef UNIX + /* When creating a new file, set its owner/group to that of the original + * file. Get the new device and inode number. */ + if (backup != NULL && !backup_copy) { +# ifdef HAVE_FCHOWN + struct stat st; + + /* don't change the owner when it's already OK, some systems remove + * permission or ACL stuff */ + if (mch_stat((char *)wfname, &st) < 0 + || st.st_uid != st_old.st_uid + || st.st_gid != st_old.st_gid) { + ignored = fchown(fd, st_old.st_uid, st_old.st_gid); + if (perm >= 0) /* set permission again, may have changed */ + (void)mch_setperm(wfname, perm); + } +# endif + buf_setino(buf); + } else if (!buf->b_dev_valid) + /* Set the inode when creating a new file. */ + buf_setino(buf); +#endif + + if (close(fd) != 0) { + errmsg = (char_u *)_("E512: Close failed"); + end = 0; + } + +#ifdef UNIX + if (made_writable) + perm &= ~0200; /* reset 'w' bit for security reasons */ +#endif + if (perm >= 0) /* set perm. of new file same as old file */ + (void)mch_setperm(wfname, perm); +#ifdef HAVE_ACL + /* Probably need to set the ACL before changing the user (can't set the + * ACL on a file the user doesn't own). */ + if (!backup_copy) + mch_set_acl(wfname, acl); +#endif + crypt_method_used = use_crypt_method; + if (wb_flags & FIO_ENCRYPTED) + crypt_pop_state(); + + + if (wfname != fname) { + /* + * The file was written to a temp file, now it needs to be converted + * with 'charconvert' to (overwrite) the output file. + */ + if (end != 0) { + if (eval_charconvert(enc_utf8 ? (char_u *)"utf-8" : p_enc, fenc, + wfname, fname) == FAIL) { + write_info.bw_conv_error = TRUE; + end = 0; + } + } + mch_remove(wfname); + vim_free(wfname); + } + + if (end == 0) { + if (errmsg == NULL) { + if (write_info.bw_conv_error) { + if (write_info.bw_conv_error_lnum == 0) + errmsg = (char_u *)_( + "E513: write error, conversion failed (make 'fenc' empty to override)"); + else { + errmsg_allocated = TRUE; + errmsg = alloc(300); + vim_snprintf((char *)errmsg, 300, + _( + "E513: write error, conversion failed in line %ld (make 'fenc' empty to override)"), + (long)write_info.bw_conv_error_lnum); + } + } else if (got_int) + errmsg = (char_u *)_(e_interr); + else + errmsg = (char_u *)_("E514: write error (file system full?)"); + } + + /* + * If we have a backup file, try to put it in place of the new file, + * because the new file is probably corrupt. This avoids losing the + * original file when trying to make a backup when writing the file a + * second time. + * When "backup_copy" is set we need to copy the backup over the new + * file. Otherwise rename the backup file. + * If this is OK, don't give the extra warning message. + */ + if (backup != NULL) { + if (backup_copy) { + /* This may take a while, if we were interrupted let the user + * know we got the message. */ + if (got_int) { + MSG(_(e_interr)); + out_flush(); + } + if ((fd = mch_open((char *)backup, O_RDONLY | O_EXTRA, 0)) >= 0) { + if ((write_info.bw_fd = mch_open((char *)fname, + O_WRONLY | O_CREAT | O_TRUNC | O_EXTRA, + perm & 0777)) >= 0) { + /* copy the file. */ + write_info.bw_buf = smallbuf; +#ifdef HAS_BW_FLAGS + write_info.bw_flags = FIO_NOCONVERT; +#endif + while ((write_info.bw_len = read_eintr(fd, smallbuf, + SMBUFSIZE)) > 0) + if (buf_write_bytes(&write_info) == FAIL) + break; + + if (close(write_info.bw_fd) >= 0 + && write_info.bw_len == 0) + end = 1; /* success */ + } + close(fd); /* ignore errors for closing read file */ + } + } else { + if (vim_rename(backup, fname) == 0) + end = 1; + } + } + goto fail; + } + + lnum -= start; /* compute number of written lines */ + --no_wait_return; /* may wait for return now */ + +#if !(defined(UNIX) || defined(VMS)) + fname = sfname; /* use shortname now, for the messages */ +#endif + if (!filtering) { + msg_add_fname(buf, fname); /* put fname in IObuff with quotes */ + c = FALSE; + if (write_info.bw_conv_error) { + STRCAT(IObuff, _(" CONVERSION ERROR")); + c = TRUE; + if (write_info.bw_conv_error_lnum != 0) + vim_snprintf_add((char *)IObuff, IOSIZE, _(" in line %ld;"), + (long)write_info.bw_conv_error_lnum); + } else if (notconverted) { + STRCAT(IObuff, _("[NOT converted]")); + c = TRUE; + } else if (converted) { + STRCAT(IObuff, _("[converted]")); + c = TRUE; + } + if (device) { + STRCAT(IObuff, _("[Device]")); + c = TRUE; + } else if (newfile) { + STRCAT(IObuff, shortmess(SHM_NEW) ? _("[New]") : _("[New File]")); + c = TRUE; + } + if (no_eol) { + msg_add_eol(); + c = TRUE; + } + /* may add [unix/dos/mac] */ + if (msg_add_fileformat(fileformat)) + c = TRUE; + if (wb_flags & FIO_ENCRYPTED) { + if (crypt_method_used == 1) + STRCAT(IObuff, _("[blowfish]")); + else + STRCAT(IObuff, _("[crypted]")); + c = TRUE; + } + msg_add_lines(c, (long)lnum, nchars); /* add line/char count */ + if (!shortmess(SHM_WRITE)) { + if (append) + STRCAT(IObuff, shortmess(SHM_WRI) ? _(" [a]") : _(" appended")); + else + STRCAT(IObuff, shortmess(SHM_WRI) ? _(" [w]") : _(" written")); + } + + set_keep_msg(msg_trunc_attr(IObuff, FALSE, 0), 0); + } + + /* When written everything correctly: reset 'modified'. Unless not + * writing to the original file and '+' is not in 'cpoptions'. */ + if (reset_changed && whole && !append + && !write_info.bw_conv_error + && (overwriting || vim_strchr(p_cpo, CPO_PLUS) != NULL) + ) { + unchanged(buf, TRUE); + u_unchanged(buf); + u_update_save_nr(buf); + } + + /* + * If written to the current file, update the timestamp of the swap file + * and reset the BF_WRITE_MASK flags. Also sets buf->b_mtime. + */ + if (overwriting) { + ml_timestamp(buf); + if (append) + buf->b_flags &= ~BF_NEW; + else + buf->b_flags &= ~BF_WRITE_MASK; + } + + /* + * If we kept a backup until now, and we are in patch mode, then we make + * the backup file our 'original' file. + */ + if (*p_pm && dobackup) { + char *org = (char *)buf_modname( +#ifdef SHORT_FNAME + TRUE, +#else + (buf->b_p_sn || buf->b_shortname), +#endif + fname, p_pm, FALSE); + + if (backup != NULL) { + struct stat st; + + /* + * If the original file does not exist yet + * the current backup file becomes the original file + */ + if (org == NULL) + EMSG(_("E205: Patchmode: can't save original file")); + else if (mch_stat(org, &st) < 0) { + vim_rename(backup, (char_u *)org); + vim_free(backup); /* don't delete the file */ + backup = NULL; +#ifdef UNIX + set_file_time((char_u *)org, st_old.st_atime, st_old.st_mtime); +#endif + } + } + /* + * If there is no backup file, remember that a (new) file was + * created. + */ + else { + int empty_fd; + + if (org == NULL + || (empty_fd = mch_open(org, + O_CREAT | O_EXTRA | O_EXCL | O_NOFOLLOW, + perm < 0 ? 0666 : (perm & 0777))) < 0) + EMSG(_("E206: patchmode: can't touch empty original file")); + else + close(empty_fd); + } + if (org != NULL) { + mch_setperm((char_u *)org, mch_getperm(fname) & 0777); + vim_free(org); + } + } + + /* + * Remove the backup unless 'backup' option is set + */ + if (!p_bk && backup != NULL && mch_remove(backup) != 0) + EMSG(_("E207: Can't delete backup file")); + + + goto nofail; + + /* + * Finish up. We get here either after failure or success. + */ +fail: + --no_wait_return; /* may wait for return now */ +nofail: + + /* Done saving, we accept changed buffer warnings again */ + buf->b_saving = FALSE; + + vim_free(backup); + if (buffer != smallbuf) + vim_free(buffer); + vim_free(fenc_tofree); + vim_free(write_info.bw_conv_buf); +# ifdef USE_ICONV + if (write_info.bw_iconv_fd != (iconv_t)-1) { + iconv_close(write_info.bw_iconv_fd); + write_info.bw_iconv_fd = (iconv_t)-1; + } +# endif +#ifdef HAVE_ACL + mch_free_acl(acl); +#endif + + if (errmsg != NULL) { + int numlen = errnum != NULL ? (int)STRLEN(errnum) : 0; + + attr = hl_attr(HLF_E); /* set highlight for error messages */ + msg_add_fname(buf, +#ifndef UNIX + sfname +#else + fname +#endif + ); /* put file name in IObuff with quotes */ + if (STRLEN(IObuff) + STRLEN(errmsg) + numlen >= IOSIZE) + IObuff[IOSIZE - STRLEN(errmsg) - numlen - 1] = NUL; + /* If the error message has the form "is ...", put the error number in + * front of the file name. */ + if (errnum != NULL) { + STRMOVE(IObuff + numlen, IObuff); + mch_memmove(IObuff, errnum, (size_t)numlen); + } + STRCAT(IObuff, errmsg); + emsg(IObuff); + if (errmsg_allocated) + vim_free(errmsg); + + retval = FAIL; + if (end == 0) { + MSG_PUTS_ATTR(_("\nWARNING: Original file may be lost or damaged\n"), + attr | MSG_HIST); + MSG_PUTS_ATTR(_( + "don't quit the editor until the file is successfully written!"), + attr | MSG_HIST); + + /* Update the timestamp to avoid an "overwrite changed file" + * prompt when writing again. */ + if (mch_stat((char *)fname, &st_old) >= 0) { + buf_store_time(buf, &st_old, fname); + buf->b_mtime_read = buf->b_mtime; + } + } + } + msg_scroll = msg_save; + + /* + * When writing the whole file and 'undofile' is set, also write the undo + * file. + */ + if (retval == OK && write_undo_file) { + char_u hash[UNDO_HASH_SIZE]; + + sha256_finish(&sha_ctx, hash); + u_write_undo(NULL, FALSE, buf, hash); + } + + if (!should_abort(retval)) { + aco_save_T aco; + + curbuf->b_no_eol_lnum = 0; /* in case it was set by the previous read */ + + /* + * Apply POST autocommands. + * Careful: The autocommands may call buf_write() recursively! + */ + aucmd_prepbuf(&aco, buf); + + if (append) + apply_autocmds_exarg(EVENT_FILEAPPENDPOST, fname, fname, + FALSE, curbuf, eap); + else if (filtering) + apply_autocmds_exarg(EVENT_FILTERWRITEPOST, NULL, fname, + FALSE, curbuf, eap); + else if (reset_changed && whole) + apply_autocmds_exarg(EVENT_BUFWRITEPOST, fname, fname, + FALSE, curbuf, eap); + else + apply_autocmds_exarg(EVENT_FILEWRITEPOST, fname, fname, + FALSE, curbuf, eap); + + /* restore curwin/curbuf and a few other things */ + aucmd_restbuf(&aco); + + if (aborting()) /* autocmds may abort script processing */ + retval = FALSE; + } + + got_int |= prev_got_int; + + return retval; +} + +/* + * Set the name of the current buffer. Use when the buffer doesn't have a + * name and a ":r" or ":w" command with a file name is used. + */ +static int set_rw_fname(fname, sfname) +char_u *fname; +char_u *sfname; +{ + buf_T *buf = curbuf; + + /* It's like the unnamed buffer is deleted.... */ + if (curbuf->b_p_bl) + apply_autocmds(EVENT_BUFDELETE, NULL, NULL, FALSE, curbuf); + apply_autocmds(EVENT_BUFWIPEOUT, NULL, NULL, FALSE, curbuf); + if (aborting()) /* autocmds may abort script processing */ + return FAIL; + if (curbuf != buf) { + /* We are in another buffer now, don't do the renaming. */ + EMSG(_(e_auchangedbuf)); + return FAIL; + } + + if (setfname(curbuf, fname, sfname, FALSE) == OK) + curbuf->b_flags |= BF_NOTEDITED; + + /* ....and a new named one is created */ + apply_autocmds(EVENT_BUFNEW, NULL, NULL, FALSE, curbuf); + if (curbuf->b_p_bl) + apply_autocmds(EVENT_BUFADD, NULL, NULL, FALSE, curbuf); + if (aborting()) /* autocmds may abort script processing */ + return FAIL; + + /* Do filetype detection now if 'filetype' is empty. */ + if (*curbuf->b_p_ft == NUL) { + if (au_has_group((char_u *)"filetypedetect")) + (void)do_doautocmd((char_u *)"filetypedetect BufRead", FALSE); + do_modelines(0); + } + + return OK; +} + +/* + * Put file name into IObuff with quotes. + */ +void msg_add_fname(buf, fname) +buf_T *buf; +char_u *fname; +{ + if (fname == NULL) + fname = (char_u *)"-stdin-"; + home_replace(buf, fname, IObuff + 1, IOSIZE - 4, TRUE); + IObuff[0] = '"'; + STRCAT(IObuff, "\" "); +} + +/* + * Append message for text mode to IObuff. + * Return TRUE if something appended. + */ +static int msg_add_fileformat(eol_type) +int eol_type; +{ +#ifndef USE_CRNL + if (eol_type == EOL_DOS) { + STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[dos]") : _("[dos format]")); + return TRUE; + } +#endif +#ifndef USE_CR + if (eol_type == EOL_MAC) { + STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[mac]") : _("[mac format]")); + return TRUE; + } +#endif +#if defined(USE_CRNL) || defined(USE_CR) + if (eol_type == EOL_UNIX) { + STRCAT(IObuff, shortmess(SHM_TEXT) ? _("[unix]") : _("[unix format]")); + return TRUE; + } +#endif + return FALSE; +} + +/* + * Append line and character count to IObuff. + */ +void msg_add_lines(insert_space, lnum, nchars) +int insert_space; +long lnum; +off_t nchars; +{ + char_u *p; + + p = IObuff + STRLEN(IObuff); + + if (insert_space) + *p++ = ' '; + if (shortmess(SHM_LINES)) + sprintf((char *)p, +#ifdef LONG_LONG_OFF_T + "%ldL, %lldC", lnum, nchars +#else + /* Explicit typecast avoids warning on Mac OS X 10.6 */ + "%ldL, %ldC", lnum, (long)nchars +#endif + ); + else { + if (lnum == 1) + STRCPY(p, _("1 line, ")); + else + sprintf((char *)p, _("%ld lines, "), lnum); + p += STRLEN(p); + if (nchars == 1) + STRCPY(p, _("1 character")); + else + sprintf((char *)p, +#ifdef LONG_LONG_OFF_T + _("%lld characters"), nchars +#else + /* Explicit typecast avoids warning on Mac OS X 10.6 */ + _("%ld characters"), (long)nchars +#endif + ); + } +} + +/* + * Append message for missing line separator to IObuff. + */ +static void msg_add_eol() { + STRCAT(IObuff, + shortmess(SHM_LAST) ? _("[noeol]") : _("[Incomplete last line]")); +} + +/* + * Check modification time of file, before writing to it. + * The size isn't checked, because using a tool like "gzip" takes care of + * using the same timestamp but can't set the size. + */ +static int check_mtime(buf, st) +buf_T *buf; +struct stat *st; +{ + if (buf->b_mtime_read != 0 + && time_differs((long)st->st_mtime, buf->b_mtime_read)) { + msg_scroll = TRUE; /* don't overwrite messages here */ + msg_silent = 0; /* must give this prompt */ + /* don't use emsg() here, don't want to flush the buffers */ + MSG_ATTR(_("WARNING: The file has been changed since reading it!!!"), + hl_attr(HLF_E)); + if (ask_yesno((char_u *)_("Do you really want to write to it"), + TRUE) == 'n') + return FAIL; + msg_scroll = FALSE; /* always overwrite the file message now */ + } + return OK; +} + +static int time_differs(t1, t2) +long t1, t2; +{ +#if defined(__linux__) || defined(MSDOS) || defined(MSWIN) + /* On a FAT filesystem, esp. under Linux, there are only 5 bits to store + * the seconds. Since the roundoff is done when flushing the inode, the + * time may change unexpectedly by one second!!! */ + return t1 - t2 > 1 || t2 - t1 > 1; +#else + return t1 != t2; +#endif +} + +/* + * Call write() to write a number of bytes to the file. + * Handles encryption and 'encoding' conversion. + * + * Return FAIL for failure, OK otherwise. + */ +static int buf_write_bytes(ip) +struct bw_info *ip; +{ + int wlen; + char_u *buf = ip->bw_buf; /* data to write */ + int len = ip->bw_len; /* length of data */ +#ifdef HAS_BW_FLAGS + int flags = ip->bw_flags; /* extra flags */ +#endif + + /* + * Skip conversion when writing the crypt magic number or the BOM. + */ + if (!(flags & FIO_NOCONVERT)) { + char_u *p; + unsigned c; + int n; + + if (flags & FIO_UTF8) { + /* + * Convert latin1 in the buffer to UTF-8 in the file. + */ + p = ip->bw_conv_buf; /* translate to buffer */ + for (wlen = 0; wlen < len; ++wlen) + p += utf_char2bytes(buf[wlen], p); + buf = ip->bw_conv_buf; + len = (int)(p - ip->bw_conv_buf); + } else if (flags & (FIO_UCS4 | FIO_UTF16 | FIO_UCS2 | FIO_LATIN1)) { + /* + * Convert UTF-8 bytes in the buffer to UCS-2, UCS-4, UTF-16 or + * Latin1 chars in the file. + */ + if (flags & FIO_LATIN1) + p = buf; /* translate in-place (can only get shorter) */ + else + p = ip->bw_conv_buf; /* translate to buffer */ + for (wlen = 0; wlen < len; wlen += n) { + if (wlen == 0 && ip->bw_restlen != 0) { + int l; + + /* Use remainder of previous call. Append the start of + * buf[] to get a full sequence. Might still be too + * short! */ + l = CONV_RESTLEN - ip->bw_restlen; + if (l > len) + l = len; + mch_memmove(ip->bw_rest + ip->bw_restlen, buf, (size_t)l); + n = utf_ptr2len_len(ip->bw_rest, ip->bw_restlen + l); + if (n > ip->bw_restlen + len) { + /* We have an incomplete byte sequence at the end to + * be written. We can't convert it without the + * remaining bytes. Keep them for the next call. */ + if (ip->bw_restlen + len > CONV_RESTLEN) + return FAIL; + ip->bw_restlen += len; + break; + } + if (n > 1) + c = utf_ptr2char(ip->bw_rest); + else + c = ip->bw_rest[0]; + if (n >= ip->bw_restlen) { + n -= ip->bw_restlen; + ip->bw_restlen = 0; + } else { + ip->bw_restlen -= n; + mch_memmove(ip->bw_rest, ip->bw_rest + n, + (size_t)ip->bw_restlen); + n = 0; + } + } else { + n = utf_ptr2len_len(buf + wlen, len - wlen); + if (n > len - wlen) { + /* We have an incomplete byte sequence at the end to + * be written. We can't convert it without the + * remaining bytes. Keep them for the next call. */ + if (len - wlen > CONV_RESTLEN) + return FAIL; + ip->bw_restlen = len - wlen; + mch_memmove(ip->bw_rest, buf + wlen, + (size_t)ip->bw_restlen); + break; + } + if (n > 1) + c = utf_ptr2char(buf + wlen); + else + c = buf[wlen]; + } + + if (ucs2bytes(c, &p, flags) && !ip->bw_conv_error) { + ip->bw_conv_error = TRUE; + ip->bw_conv_error_lnum = ip->bw_start_lnum; + } + if (c == NL) + ++ip->bw_start_lnum; + } + if (flags & FIO_LATIN1) + len = (int)(p - buf); + else { + buf = ip->bw_conv_buf; + len = (int)(p - ip->bw_conv_buf); + } + } + + +# ifdef MACOS_CONVERT + else if (flags & FIO_MACROMAN) { + /* + * Convert UTF-8 or latin1 to Apple MacRoman. + */ + char_u *from; + size_t fromlen; + + if (ip->bw_restlen > 0) { + /* Need to concatenate the remainder of the previous call and + * the bytes of the current call. Use the end of the + * conversion buffer for this. */ + fromlen = len + ip->bw_restlen; + from = ip->bw_conv_buf + ip->bw_conv_buflen - fromlen; + mch_memmove(from, ip->bw_rest, (size_t)ip->bw_restlen); + mch_memmove(from + ip->bw_restlen, buf, (size_t)len); + } else { + from = buf; + fromlen = len; + } + + if (enc2macroman(from, fromlen, + ip->bw_conv_buf, &len, ip->bw_conv_buflen, + ip->bw_rest, &ip->bw_restlen) == FAIL) { + ip->bw_conv_error = TRUE; + return FAIL; + } + buf = ip->bw_conv_buf; + } +# endif + +# ifdef USE_ICONV + if (ip->bw_iconv_fd != (iconv_t)-1) { + const char *from; + size_t fromlen; + char *to; + size_t tolen; + + /* Convert with iconv(). */ + if (ip->bw_restlen > 0) { + char *fp; + + /* Need to concatenate the remainder of the previous call and + * the bytes of the current call. Use the end of the + * conversion buffer for this. */ + fromlen = len + ip->bw_restlen; + fp = (char *)ip->bw_conv_buf + ip->bw_conv_buflen - fromlen; + mch_memmove(fp, ip->bw_rest, (size_t)ip->bw_restlen); + mch_memmove(fp + ip->bw_restlen, buf, (size_t)len); + from = fp; + tolen = ip->bw_conv_buflen - fromlen; + } else { + from = (const char *)buf; + fromlen = len; + tolen = ip->bw_conv_buflen; + } + to = (char *)ip->bw_conv_buf; + + if (ip->bw_first) { + size_t save_len = tolen; + + /* output the initial shift state sequence */ + (void)iconv(ip->bw_iconv_fd, NULL, NULL, &to, &tolen); + + /* There is a bug in iconv() on Linux (which appears to be + * wide-spread) which sets "to" to NULL and messes up "tolen". + */ + if (to == NULL) { + to = (char *)ip->bw_conv_buf; + tolen = save_len; + } + ip->bw_first = FALSE; + } + + /* + * If iconv() has an error or there is not enough room, fail. + */ + if ((iconv(ip->bw_iconv_fd, (void *)&from, &fromlen, &to, &tolen) + == (size_t)-1 && ICONV_ERRNO != ICONV_EINVAL) + || fromlen > CONV_RESTLEN) { + ip->bw_conv_error = TRUE; + return FAIL; + } + + /* copy remainder to ip->bw_rest[] to be used for the next call. */ + if (fromlen > 0) + mch_memmove(ip->bw_rest, (void *)from, fromlen); + ip->bw_restlen = (int)fromlen; + + buf = ip->bw_conv_buf; + len = (int)((char_u *)to - ip->bw_conv_buf); + } +# endif + } + + if (flags & FIO_ENCRYPTED) /* encrypt the data */ + crypt_encode(buf, len, buf); + + wlen = write_eintr(ip->bw_fd, buf, len); + return (wlen < len) ? FAIL : OK; +} + +/* + * Convert a Unicode character to bytes. + * Return TRUE for an error, FALSE when it's OK. + */ +static int ucs2bytes(c, pp, flags) +unsigned c; /* in: character */ +char_u **pp; /* in/out: pointer to result */ +int flags; /* FIO_ flags */ +{ + char_u *p = *pp; + int error = FALSE; + int cc; + + + if (flags & FIO_UCS4) { + if (flags & FIO_ENDIAN_L) { + *p++ = c; + *p++ = (c >> 8); + *p++ = (c >> 16); + *p++ = (c >> 24); + } else { + *p++ = (c >> 24); + *p++ = (c >> 16); + *p++ = (c >> 8); + *p++ = c; + } + } else if (flags & (FIO_UCS2 | FIO_UTF16)) { + if (c >= 0x10000) { + if (flags & FIO_UTF16) { + /* Make two words, ten bits of the character in each. First + * word is 0xd800 - 0xdbff, second one 0xdc00 - 0xdfff */ + c -= 0x10000; + if (c >= 0x100000) + error = TRUE; + cc = ((c >> 10) & 0x3ff) + 0xd800; + if (flags & FIO_ENDIAN_L) { + *p++ = cc; + *p++ = ((unsigned)cc >> 8); + } else { + *p++ = ((unsigned)cc >> 8); + *p++ = cc; + } + c = (c & 0x3ff) + 0xdc00; + } else + error = TRUE; + } + if (flags & FIO_ENDIAN_L) { + *p++ = c; + *p++ = (c >> 8); + } else { + *p++ = (c >> 8); + *p++ = c; + } + } else { /* Latin1 */ + if (c >= 0x100) { + error = TRUE; + *p++ = 0xBF; + } else + *p++ = c; + } + + *pp = p; + return error; +} + +/* + * Return TRUE if file encoding "fenc" requires conversion from or to + * 'encoding'. + */ +static int need_conversion(fenc) +char_u *fenc; +{ + int same_encoding; + int enc_flags; + int fenc_flags; + + if (*fenc == NUL || STRCMP(p_enc, fenc) == 0) { + same_encoding = TRUE; + fenc_flags = 0; + } else { + /* Ignore difference between "ansi" and "latin1", "ucs-4" and + * "ucs-4be", etc. */ + enc_flags = get_fio_flags(p_enc); + fenc_flags = get_fio_flags(fenc); + same_encoding = (enc_flags != 0 && fenc_flags == enc_flags); + } + if (same_encoding) { + /* Specified encoding matches with 'encoding'. This requires + * conversion when 'encoding' is Unicode but not UTF-8. */ + return enc_unicode != 0; + } + + /* Encodings differ. However, conversion is not needed when 'enc' is any + * Unicode encoding and the file is UTF-8. */ + return !(enc_utf8 && fenc_flags == FIO_UTF8); +} + +/* + * Check "ptr" for a unicode encoding and return the FIO_ flags needed for the + * internal conversion. + * if "ptr" is an empty string, use 'encoding'. + */ +static int get_fio_flags(ptr) +char_u *ptr; +{ + int prop; + + if (*ptr == NUL) + ptr = p_enc; + + prop = enc_canon_props(ptr); + if (prop & ENC_UNICODE) { + if (prop & ENC_2BYTE) { + if (prop & ENC_ENDIAN_L) + return FIO_UCS2 | FIO_ENDIAN_L; + return FIO_UCS2; + } + if (prop & ENC_4BYTE) { + if (prop & ENC_ENDIAN_L) + return FIO_UCS4 | FIO_ENDIAN_L; + return FIO_UCS4; + } + if (prop & ENC_2WORD) { + if (prop & ENC_ENDIAN_L) + return FIO_UTF16 | FIO_ENDIAN_L; + return FIO_UTF16; + } + return FIO_UTF8; + } + if (prop & ENC_LATIN1) + return FIO_LATIN1; + /* must be ENC_DBCS, requires iconv() */ + return 0; +} + + + +/* + * Check for a Unicode BOM (Byte Order Mark) at the start of p[size]. + * "size" must be at least 2. + * Return the name of the encoding and set "*lenp" to the length. + * Returns NULL when no BOM found. + */ +static char_u * check_for_bom(p, size, lenp, flags) +char_u *p; +long size; +int *lenp; +int flags; +{ + char *name = NULL; + int len = 2; + + if (p[0] == 0xef && p[1] == 0xbb && size >= 3 && p[2] == 0xbf + && (flags == FIO_ALL || flags == FIO_UTF8 || flags == 0)) { + name = "utf-8"; /* EF BB BF */ + len = 3; + } else if (p[0] == 0xff && p[1] == 0xfe) { + if (size >= 4 && p[2] == 0 && p[3] == 0 + && (flags == FIO_ALL || flags == (FIO_UCS4 | FIO_ENDIAN_L))) { + name = "ucs-4le"; /* FF FE 00 00 */ + len = 4; + } else if (flags == (FIO_UCS2 | FIO_ENDIAN_L)) + name = "ucs-2le"; /* FF FE */ + else if (flags == FIO_ALL || flags == (FIO_UTF16 | FIO_ENDIAN_L)) + /* utf-16le is preferred, it also works for ucs-2le text */ + name = "utf-16le"; /* FF FE */ + } else if (p[0] == 0xfe && p[1] == 0xff + && (flags == FIO_ALL || flags == FIO_UCS2 || flags == + FIO_UTF16)) { + /* Default to utf-16, it works also for ucs-2 text. */ + if (flags == FIO_UCS2) + name = "ucs-2"; /* FE FF */ + else + name = "utf-16"; /* FE FF */ + } else if (size >= 4 && p[0] == 0 && p[1] == 0 && p[2] == 0xfe + && p[3] == 0xff && (flags == FIO_ALL || flags == FIO_UCS4)) { + name = "ucs-4"; /* 00 00 FE FF */ + len = 4; + } + + *lenp = len; + return (char_u *)name; +} + +/* + * Generate a BOM in "buf[4]" for encoding "name". + * Return the length of the BOM (zero when no BOM). + */ +static int make_bom(buf, name) +char_u *buf; +char_u *name; +{ + int flags; + char_u *p; + + flags = get_fio_flags(name); + + /* Can't put a BOM in a non-Unicode file. */ + if (flags == FIO_LATIN1 || flags == 0) + return 0; + + if (flags == FIO_UTF8) { /* UTF-8 */ + buf[0] = 0xef; + buf[1] = 0xbb; + buf[2] = 0xbf; + return 3; + } + p = buf; + (void)ucs2bytes(0xfeff, &p, flags); + return (int)(p - buf); +} + +#if defined(FEAT_VIMINFO) || defined(FEAT_BROWSE) || \ + defined(FEAT_QUICKFIX) || defined(FEAT_AUTOCMD) || defined(PROTO) +/* + * Try to find a shortname by comparing the fullname with the current + * directory. + * Returns "full_path" or pointer into "full_path" if shortened. + */ +char_u * shorten_fname1(full_path) +char_u *full_path; +{ + char_u *dirname; + char_u *p = full_path; + + dirname = alloc(MAXPATHL); + if (dirname == NULL) + return full_path; + if (mch_dirname(dirname, MAXPATHL) == OK) { + p = shorten_fname(full_path, dirname); + if (p == NULL || *p == NUL) + p = full_path; + } + vim_free(dirname); + return p; +} +#endif + +/* + * Try to find a shortname by comparing the fullname with the current + * directory. + * Returns NULL if not shorter name possible, pointer into "full_path" + * otherwise. + */ +char_u * shorten_fname(full_path, dir_name) +char_u *full_path; +char_u *dir_name; +{ + int len; + char_u *p; + + if (full_path == NULL) + return NULL; + len = (int)STRLEN(dir_name); + if (fnamencmp(dir_name, full_path, len) == 0) { + p = full_path + len; + { + if (vim_ispathsep(*p)) + ++p; + else + p = NULL; + } + } else + p = NULL; + return p; +} + +/* + * Shorten filenames for all buffers. + * When "force" is TRUE: Use full path from now on for files currently being + * edited, both for file name and swap file name. Try to shorten the file + * names a bit, if safe to do so. + * When "force" is FALSE: Only try to shorten absolute file names. + * For buffers that have buftype "nofile" or "scratch": never change the file + * name. + */ +void shorten_fnames(force) +int force; +{ + char_u dirname[MAXPATHL]; + buf_T *buf; + char_u *p; + + mch_dirname(dirname, MAXPATHL); + for (buf = firstbuf; buf != NULL; buf = buf->b_next) { + if (buf->b_fname != NULL + && !bt_nofile(buf) + && !path_with_url(buf->b_fname) + && (force + || buf->b_sfname == NULL + || mch_isFullName(buf->b_sfname))) { + vim_free(buf->b_sfname); + buf->b_sfname = NULL; + p = shorten_fname(buf->b_ffname, dirname); + if (p != NULL) { + buf->b_sfname = vim_strsave(p); + buf->b_fname = buf->b_sfname; + } + if (p == NULL || buf->b_fname == NULL) + buf->b_fname = buf->b_ffname; + } + + /* Always make the swap file name a full path, a "nofile" buffer may + * also have a swap file. */ + mf_fullname(buf->b_ml.ml_mfp); + } + status_redraw_all(); + redraw_tabline = TRUE; +} + +#if (defined(FEAT_DND) && defined(FEAT_GUI_GTK)) \ + || defined(FEAT_GUI_MSWIN) \ + || defined(FEAT_GUI_MAC) \ + || defined(PROTO) +/* + * Shorten all filenames in "fnames[count]" by current directory. + */ +void shorten_filenames(fnames, count) +char_u **fnames; +int count; +{ + int i; + char_u dirname[MAXPATHL]; + char_u *p; + + if (fnames == NULL || count < 1) + return; + mch_dirname(dirname, sizeof(dirname)); + for (i = 0; i < count; ++i) { + if ((p = shorten_fname(fnames[i], dirname)) != NULL) { + /* shorten_fname() returns pointer in given "fnames[i]". If free + * "fnames[i]" first, "p" becomes invalid. So we need to copy + * "p" first then free fnames[i]. */ + p = vim_strsave(p); + vim_free(fnames[i]); + fnames[i] = p; + } + } +} +#endif + +/* + * add extension to file name - change path/fo.o.h to path/fo.o.h.ext or + * fo_o_h.ext for MSDOS or when shortname option set. + * + * Assumed that fname is a valid name found in the filesystem we assure that + * the return value is a different name and ends in 'ext'. + * "ext" MUST be at most 4 characters long if it starts with a dot, 3 + * characters otherwise. + * Space for the returned name is allocated, must be freed later. + * Returns NULL when out of memory. + */ +char_u * modname(fname, ext, prepend_dot) +char_u *fname, *ext; +int prepend_dot; /* may prepend a '.' to file name */ +{ + return buf_modname( +#ifdef SHORT_FNAME + TRUE, +#else + (curbuf->b_p_sn || curbuf->b_shortname), +#endif + fname, ext, prepend_dot); +} + +char_u * buf_modname(shortname, fname, ext, prepend_dot) +int shortname; /* use 8.3 file name */ +char_u *fname, *ext; +int prepend_dot; /* may prepend a '.' to file name */ +{ + char_u *retval; + char_u *s; + char_u *e; + char_u *ptr; + int fnamelen, extlen; + + extlen = (int)STRLEN(ext); + + /* + * If there is no file name we must get the name of the current directory + * (we need the full path in case :cd is used). + */ + if (fname == NULL || *fname == NUL) { + retval = alloc((unsigned)(MAXPATHL + extlen + 3)); + if (retval == NULL) + return NULL; + if (mch_dirname(retval, MAXPATHL) == FAIL || + (fnamelen = (int)STRLEN(retval)) == 0) { + vim_free(retval); + return NULL; + } + if (!after_pathsep(retval, retval + fnamelen)) { + retval[fnamelen++] = PATHSEP; + retval[fnamelen] = NUL; + } +#ifndef SHORT_FNAME + prepend_dot = FALSE; /* nothing to prepend a dot to */ +#endif + } else { + fnamelen = (int)STRLEN(fname); + retval = alloc((unsigned)(fnamelen + extlen + 3)); + if (retval == NULL) + return NULL; + STRCPY(retval, fname); + } + + /* + * search backwards until we hit a '/', '\' or ':' replacing all '.' + * by '_' for MSDOS or when shortname option set and ext starts with a dot. + * Then truncate what is after the '/', '\' or ':' to 8 characters for + * MSDOS and 26 characters for AMIGA, a lot more for UNIX. + */ + for (ptr = retval + fnamelen; ptr > retval; mb_ptr_back(retval, ptr)) { + if (*ext == '.' +#ifdef USE_LONG_FNAME + && (!USE_LONG_FNAME || shortname) +#else +# ifndef SHORT_FNAME + && shortname +# endif +#endif + ) + if (*ptr == '.') /* replace '.' by '_' */ + *ptr = '_'; + if (vim_ispathsep(*ptr)) { + ++ptr; + break; + } + } + + /* the file name has at most BASENAMELEN characters. */ +#ifndef SHORT_FNAME + if (STRLEN(ptr) > (unsigned)BASENAMELEN) + ptr[BASENAMELEN] = '\0'; +#endif + + s = ptr + STRLEN(ptr); + + /* + * For 8.3 file names we may have to reduce the length. + */ +#ifdef USE_LONG_FNAME + if (!USE_LONG_FNAME || shortname) +#else +# ifndef SHORT_FNAME + if (shortname) +# endif +#endif + { + /* + * If there is no file name, or the file name ends in '/', and the + * extension starts with '.', put a '_' before the dot, because just + * ".ext" is invalid. + */ + if (fname == NULL || *fname == NUL + || vim_ispathsep(fname[STRLEN(fname) - 1])) { + if (*ext == '.') + *s++ = '_'; + } + /* + * If the extension starts with '.', truncate the base name at 8 + * characters + */ + else if (*ext == '.') { + if ((size_t)(s - ptr) > (size_t)8) { + s = ptr + 8; + *s = '\0'; + } + } + /* + * If the extension doesn't start with '.', and the file name + * doesn't have an extension yet, append a '.' + */ + else if ((e = vim_strchr(ptr, '.')) == NULL) + *s++ = '.'; + /* + * If the extension doesn't start with '.', and there already is an + * extension, it may need to be truncated + */ + else if ((int)STRLEN(e) + extlen > 4) + s = e + 4 - extlen; + } +#if defined(OS2) || defined(USE_LONG_FNAME) || defined(WIN3264) + /* + * If there is no file name, and the extension starts with '.', put a + * '_' before the dot, because just ".ext" may be invalid if it's on a + * FAT partition, and on HPFS it doesn't matter. + */ + else if ((fname == NULL || *fname == NUL) && *ext == '.') + *s++ = '_'; +#endif + + /* + * Append the extension. + * ext can start with '.' and cannot exceed 3 more characters. + */ + STRCPY(s, ext); + +#ifndef SHORT_FNAME + /* + * Prepend the dot. + */ + if (prepend_dot && !shortname && *(e = gettail(retval)) != '.' +#ifdef USE_LONG_FNAME + && USE_LONG_FNAME +#endif + ) { + STRMOVE(e + 1, e); + *e = '.'; + } +#endif + + /* + * Check that, after appending the extension, the file name is really + * different. + */ + if (fname != NULL && STRCMP(fname, retval) == 0) { + /* we search for a character that can be replaced by '_' */ + while (--s >= ptr) { + if (*s != '_') { + *s = '_'; + break; + } + } + if (s < ptr) /* fname was "________.", how tricky! */ + *ptr = 'v'; + } + return retval; +} + +/* + * Like fgets(), but if the file line is too long, it is truncated and the + * rest of the line is thrown away. Returns TRUE for end-of-file. + */ +int vim_fgets(buf, size, fp) +char_u *buf; +int size; +FILE *fp; +{ + char *eof; +#define FGETS_SIZE 200 + char tbuf[FGETS_SIZE]; + + buf[size - 2] = NUL; +#ifdef USE_CR + eof = fgets_cr((char *)buf, size, fp); +#else + eof = fgets((char *)buf, size, fp); +#endif + if (buf[size - 2] != NUL && buf[size - 2] != '\n') { + buf[size - 1] = NUL; /* Truncate the line */ + + /* Now throw away the rest of the line: */ + do { + tbuf[FGETS_SIZE - 2] = NUL; +#ifdef USE_CR + ignoredp = fgets_cr((char *)tbuf, FGETS_SIZE, fp); +#else + ignoredp = fgets((char *)tbuf, FGETS_SIZE, fp); +#endif + } while (tbuf[FGETS_SIZE - 2] != NUL && tbuf[FGETS_SIZE - 2] != '\n'); + } + return eof == NULL; +} + +#if defined(USE_CR) || defined(PROTO) +/* + * Like vim_fgets(), but accept any line terminator: CR, CR-LF or LF. + * Returns TRUE for end-of-file. + * Only used for the Mac, because it's much slower than vim_fgets(). + */ +int tag_fgets(buf, size, fp) +char_u *buf; +int size; +FILE *fp; +{ + int i = 0; + int c; + int eof = FALSE; + + for (;; ) { + c = fgetc(fp); + if (c == EOF) { + eof = TRUE; + break; + } + if (c == '\r') { + /* Always store a NL for end-of-line. */ + if (i < size - 1) + buf[i++] = '\n'; + c = fgetc(fp); + if (c != '\n') /* Macintosh format: single CR. */ + ungetc(c, fp); + break; + } + if (i < size - 1) + buf[i++] = c; + if (c == '\n') + break; + } + buf[i] = NUL; + return eof; +} +#endif + +/* + * rename() only works if both files are on the same file system, this + * function will (attempts to?) copy the file across if rename fails -- webb + * Return -1 for failure, 0 for success. + */ +int vim_rename(from, to) +char_u *from; +char_u *to; +{ + int fd_in; + int fd_out; + int n; + char *errmsg = NULL; + char *buffer; + struct stat st; + long perm; +#ifdef HAVE_ACL + vim_acl_T acl; /* ACL from original file */ +#endif + int use_tmp_file = FALSE; + + /* + * When the names are identical, there is nothing to do. When they refer + * to the same file (ignoring case and slash/backslash differences) but + * the file name differs we need to go through a temp file. + */ + if (fnamecmp(from, to) == 0) { + if (p_fic && STRCMP(gettail(from), gettail(to)) != 0) + use_tmp_file = TRUE; + else + return 0; + } + + /* + * Fail if the "from" file doesn't exist. Avoids that "to" is deleted. + */ + if (mch_stat((char *)from, &st) < 0) + return -1; + +#ifdef UNIX + { + struct stat st_to; + + /* It's possible for the source and destination to be the same file. + * This happens when "from" and "to" differ in case and are on a FAT32 + * filesystem. In that case go through a temp file name. */ + if (mch_stat((char *)to, &st_to) >= 0 + && st.st_dev == st_to.st_dev + && st.st_ino == st_to.st_ino) + use_tmp_file = TRUE; + } +#endif + + if (use_tmp_file) { + char tempname[MAXPATHL + 1]; + + /* + * Find a name that doesn't exist and is in the same directory. + * Rename "from" to "tempname" and then rename "tempname" to "to". + */ + if (STRLEN(from) >= MAXPATHL - 5) + return -1; + STRCPY(tempname, from); + for (n = 123; n < 99999; ++n) { + sprintf((char *)gettail((char_u *)tempname), "%d", n); + if (mch_stat(tempname, &st) < 0) { + if (mch_rename((char *)from, tempname) == 0) { + if (mch_rename(tempname, (char *)to) == 0) + return 0; + /* Strange, the second step failed. Try moving the + * file back and return failure. */ + mch_rename(tempname, (char *)from); + return -1; + } + /* If it fails for one temp name it will most likely fail + * for any temp name, give up. */ + return -1; + } + } + return -1; + } + + /* + * Delete the "to" file, this is required on some systems to make the + * mch_rename() work, on other systems it makes sure that we don't have + * two files when the mch_rename() fails. + */ + + mch_remove(to); + + /* + * First try a normal rename, return if it works. + */ + if (mch_rename((char *)from, (char *)to) == 0) + return 0; + + /* + * Rename() failed, try copying the file. + */ + perm = mch_getperm(from); +#ifdef HAVE_ACL + /* For systems that support ACL: get the ACL from the original file. */ + acl = mch_get_acl(from); +#endif + fd_in = mch_open((char *)from, O_RDONLY|O_EXTRA, 0); + if (fd_in == -1) { +#ifdef HAVE_ACL + mch_free_acl(acl); +#endif + return -1; + } + + /* Create the new file with same permissions as the original. */ + fd_out = mch_open((char *)to, + O_CREAT|O_EXCL|O_WRONLY|O_EXTRA|O_NOFOLLOW, (int)perm); + if (fd_out == -1) { + close(fd_in); +#ifdef HAVE_ACL + mch_free_acl(acl); +#endif + return -1; + } + + buffer = (char *)alloc(BUFSIZE); + if (buffer == NULL) { + close(fd_out); + close(fd_in); +#ifdef HAVE_ACL + mch_free_acl(acl); +#endif + return -1; + } + + while ((n = read_eintr(fd_in, buffer, BUFSIZE)) > 0) + if (write_eintr(fd_out, buffer, n) != n) { + errmsg = _("E208: Error writing to \"%s\""); + break; + } + + vim_free(buffer); + close(fd_in); + if (close(fd_out) < 0) + errmsg = _("E209: Error closing \"%s\""); + if (n < 0) { + errmsg = _("E210: Error reading \"%s\""); + to = from; + } +#ifndef UNIX /* for Unix mch_open() already set the permission */ + mch_setperm(to, perm); +#endif +#ifdef HAVE_ACL + mch_set_acl(to, acl); + mch_free_acl(acl); +#endif +#ifdef HAVE_SELINUX + mch_copy_sec(from, to); +#endif + if (errmsg != NULL) { + EMSG2(errmsg, to); + return -1; + } + mch_remove(from); + return 0; +} + +static int already_warned = FALSE; + +/* + * Check if any not hidden buffer has been changed. + * Postpone the check if there are characters in the stuff buffer, a global + * command is being executed, a mapping is being executed or an autocommand is + * busy. + * Returns TRUE if some message was written (screen should be redrawn and + * cursor positioned). + */ +int check_timestamps(focus) +int focus; /* called for GUI focus event */ +{ + buf_T *buf; + int didit = 0; + int n; + + /* Don't check timestamps while system() or another low-level function may + * cause us to lose and gain focus. */ + if (no_check_timestamps > 0) + return FALSE; + + /* Avoid doing a check twice. The OK/Reload dialog can cause a focus + * event and we would keep on checking if the file is steadily growing. + * Do check again after typing something. */ + if (focus && did_check_timestamps) { + need_check_timestamps = TRUE; + return FALSE; + } + + if (!stuff_empty() || global_busy || !typebuf_typed() + || autocmd_busy || curbuf_lock > 0 || allbuf_lock > 0 + ) + need_check_timestamps = TRUE; /* check later */ + else { + ++no_wait_return; + did_check_timestamps = TRUE; + already_warned = FALSE; + for (buf = firstbuf; buf != NULL; ) { + /* Only check buffers in a window. */ + if (buf->b_nwindows > 0) { + n = buf_check_timestamp(buf, focus); + if (didit < n) + didit = n; + if (n > 0 && !buf_valid(buf)) { + /* Autocommands have removed the buffer, start at the + * first one again. */ + buf = firstbuf; + continue; + } + } + buf = buf->b_next; + } + --no_wait_return; + need_check_timestamps = FALSE; + if (need_wait_return && didit == 2) { + /* make sure msg isn't overwritten */ + msg_puts((char_u *)"\n"); + out_flush(); + } + } + return didit; +} + +/* + * Move all the lines from buffer "frombuf" to buffer "tobuf". + * Return OK or FAIL. When FAIL "tobuf" is incomplete and/or "frombuf" is not + * empty. + */ +static int move_lines(frombuf, tobuf) +buf_T *frombuf; +buf_T *tobuf; +{ + buf_T *tbuf = curbuf; + int retval = OK; + linenr_T lnum; + char_u *p; + + /* Copy the lines in "frombuf" to "tobuf". */ + curbuf = tobuf; + for (lnum = 1; lnum <= frombuf->b_ml.ml_line_count; ++lnum) { + p = vim_strsave(ml_get_buf(frombuf, lnum, FALSE)); + if (p == NULL || ml_append(lnum - 1, p, 0, FALSE) == FAIL) { + vim_free(p); + retval = FAIL; + break; + } + vim_free(p); + } + + /* Delete all the lines in "frombuf". */ + if (retval != FAIL) { + curbuf = frombuf; + for (lnum = curbuf->b_ml.ml_line_count; lnum > 0; --lnum) + if (ml_delete(lnum, FALSE) == FAIL) { + /* Oops! We could try putting back the saved lines, but that + * might fail again... */ + retval = FAIL; + break; + } + } + + curbuf = tbuf; + return retval; +} + +/* + * Check if buffer "buf" has been changed. + * Also check if the file for a new buffer unexpectedly appeared. + * return 1 if a changed buffer was found. + * return 2 if a message has been displayed. + * return 0 otherwise. + */ +int buf_check_timestamp(buf, focus) +buf_T *buf; +int focus UNUSED; /* called for GUI focus event */ +{ + struct stat st; + int stat_res; + int retval = 0; + char_u *path; + char_u *tbuf; + char *mesg = NULL; + char *mesg2 = ""; + int helpmesg = FALSE; + int reload = FALSE; + int can_reload = FALSE; + off_t orig_size = buf->b_orig_size; + int orig_mode = buf->b_orig_mode; + static int busy = FALSE; + int n; + char_u *s; + char *reason; + + /* If there is no file name, the buffer is not loaded, 'buftype' is + * set, we are in the middle of a save or being called recursively: ignore + * this buffer. */ + if (buf->b_ffname == NULL + || buf->b_ml.ml_mfp == NULL + || *buf->b_p_bt != NUL + || buf->b_saving + || busy + ) + return 0; + + if ( !(buf->b_flags & BF_NOTEDITED) + && buf->b_mtime != 0 + && ((stat_res = mch_stat((char *)buf->b_ffname, &st)) < 0 + || time_differs((long)st.st_mtime, buf->b_mtime) +#ifdef HAVE_ST_MODE + || (int)st.st_mode != buf->b_orig_mode +#else + || mch_getperm(buf->b_ffname) != buf->b_orig_mode +#endif + )) { + retval = 1; + + /* set b_mtime to stop further warnings (e.g., when executing + * FileChangedShell autocmd) */ + if (stat_res < 0) { + buf->b_mtime = 0; + buf->b_orig_size = 0; + buf->b_orig_mode = 0; + } else + buf_store_time(buf, &st, buf->b_ffname); + + /* Don't do anything for a directory. Might contain the file + * explorer. */ + if (mch_isdir(buf->b_fname)) + ; + + /* + * If 'autoread' is set, the buffer has no changes and the file still + * exists, reload the buffer. Use the buffer-local option value if it + * was set, the global option value otherwise. + */ + else if ((buf->b_p_ar >= 0 ? buf->b_p_ar : p_ar) + && !bufIsChanged(buf) && stat_res >= 0) + reload = TRUE; + else { + if (stat_res < 0) + reason = "deleted"; + else if (bufIsChanged(buf)) + reason = "conflict"; + else if (orig_size != buf->b_orig_size || buf_contents_changed(buf)) + reason = "changed"; + else if (orig_mode != buf->b_orig_mode) + reason = "mode"; + else + reason = "time"; + + /* + * Only give the warning if there are no FileChangedShell + * autocommands. + * Avoid being called recursively by setting "busy". + */ + busy = TRUE; + set_vim_var_string(VV_FCS_REASON, (char_u *)reason, -1); + set_vim_var_string(VV_FCS_CHOICE, (char_u *)"", -1); + ++allbuf_lock; + n = apply_autocmds(EVENT_FILECHANGEDSHELL, + buf->b_fname, buf->b_fname, FALSE, buf); + --allbuf_lock; + busy = FALSE; + if (n) { + if (!buf_valid(buf)) + EMSG(_("E246: FileChangedShell autocommand deleted buffer")); + s = get_vim_var_str(VV_FCS_CHOICE); + if (STRCMP(s, "reload") == 0 && *reason != 'd') + reload = TRUE; + else if (STRCMP(s, "ask") == 0) + n = FALSE; + else + return 2; + } + if (!n) { + if (*reason == 'd') + mesg = _("E211: File \"%s\" no longer available"); + else { + helpmesg = TRUE; + can_reload = TRUE; + /* + * Check if the file contents really changed to avoid + * giving a warning when only the timestamp was set (e.g., + * checked out of CVS). Always warn when the buffer was + * changed. + */ + if (reason[2] == 'n') { + mesg = _( + "W12: Warning: File \"%s\" has changed and the buffer was changed in Vim as well"); + mesg2 = _("See \":help W12\" for more info."); + } else if (reason[1] == 'h') { + mesg = _( + "W11: Warning: File \"%s\" has changed since editing started"); + mesg2 = _("See \":help W11\" for more info."); + } else if (*reason == 'm') { + mesg = _( + "W16: Warning: Mode of file \"%s\" has changed since editing started"); + mesg2 = _("See \":help W16\" for more info."); + } else + /* Only timestamp changed, store it to avoid a warning + * in check_mtime() later. */ + buf->b_mtime_read = buf->b_mtime; + } + } + } + + } else if ((buf->b_flags & BF_NEW) && !(buf->b_flags & BF_NEW_W) + && vim_fexists(buf->b_ffname)) { + retval = 1; + mesg = _("W13: Warning: File \"%s\" has been created after editing started"); + buf->b_flags |= BF_NEW_W; + can_reload = TRUE; + } + + if (mesg != NULL) { + path = home_replace_save(buf, buf->b_fname); + if (path != NULL) { + if (!helpmesg) + mesg2 = ""; + tbuf = alloc((unsigned)(STRLEN(path) + STRLEN(mesg) + + STRLEN(mesg2) + 2)); + sprintf((char *)tbuf, mesg, path); + /* Set warningmsg here, before the unimportant and output-specific + * mesg2 has been appended. */ + set_vim_var_string(VV_WARNINGMSG, tbuf, -1); + if (can_reload) { + if (*mesg2 != NUL) { + STRCAT(tbuf, "\n"); + STRCAT(tbuf, mesg2); + } + if (do_dialog(VIM_WARNING, (char_u *)_("Warning"), tbuf, + (char_u *)_("&OK\n&Load File"), 1, NULL, TRUE) == 2) + reload = TRUE; + } else if (State > NORMAL_BUSY || (State & CMDLINE) || + already_warned) { + if (*mesg2 != NUL) { + STRCAT(tbuf, "; "); + STRCAT(tbuf, mesg2); + } + EMSG(tbuf); + retval = 2; + } else { + if (!autocmd_busy) { + msg_start(); + msg_puts_attr(tbuf, hl_attr(HLF_E) + MSG_HIST); + if (*mesg2 != NUL) + msg_puts_attr((char_u *)mesg2, + hl_attr(HLF_W) + MSG_HIST); + msg_clr_eos(); + (void)msg_end(); + if (emsg_silent == 0) { + out_flush(); + /* give the user some time to think about it */ + ui_delay(1000L, TRUE); + + /* don't redraw and erase the message */ + redraw_cmdline = FALSE; + } + } + already_warned = TRUE; + } + + vim_free(path); + vim_free(tbuf); + } + } + + if (reload) { + /* Reload the buffer. */ + buf_reload(buf, orig_mode); + if (buf->b_p_udf && buf->b_ffname != NULL) { + char_u hash[UNDO_HASH_SIZE]; + buf_T *save_curbuf = curbuf; + + /* Any existing undo file is unusable, write it now. */ + curbuf = buf; + u_compute_hash(hash); + u_write_undo(NULL, FALSE, buf, hash); + curbuf = save_curbuf; + } + } + + /* Trigger FileChangedShell when the file was changed in any way. */ + if (buf_valid(buf) && retval != 0) + (void)apply_autocmds(EVENT_FILECHANGEDSHELLPOST, + buf->b_fname, buf->b_fname, FALSE, buf); + + return retval; +} + +/* + * Reload a buffer that is already loaded. + * Used when the file was changed outside of Vim. + * "orig_mode" is buf->b_orig_mode before the need for reloading was detected. + * buf->b_orig_mode may have been reset already. + */ +void buf_reload(buf, orig_mode) +buf_T *buf; +int orig_mode; +{ + exarg_T ea; + pos_T old_cursor; + linenr_T old_topline; + int old_ro = buf->b_p_ro; + buf_T *savebuf; + int saved = OK; + aco_save_T aco; + int flags = READ_NEW; + + /* set curwin/curbuf for "buf" and save some things */ + aucmd_prepbuf(&aco, buf); + + /* We only want to read the text from the file, not reset the syntax + * highlighting, clear marks, diff status, etc. Force the fileformat + * and encoding to be the same. */ + if (prep_exarg(&ea, buf) == OK) { + old_cursor = curwin->w_cursor; + old_topline = curwin->w_topline; + + if (p_ur < 0 || curbuf->b_ml.ml_line_count <= p_ur) { + /* Save all the text, so that the reload can be undone. + * Sync first so that this is a separate undo-able action. */ + u_sync(FALSE); + saved = u_savecommon(0, curbuf->b_ml.ml_line_count + 1, 0, TRUE); + flags |= READ_KEEP_UNDO; + } + + /* + * To behave like when a new file is edited (matters for + * BufReadPost autocommands) we first need to delete the current + * buffer contents. But if reading the file fails we should keep + * the old contents. Can't use memory only, the file might be + * too big. Use a hidden buffer to move the buffer contents to. + */ + if (bufempty() || saved == FAIL) + savebuf = NULL; + else { + /* Allocate a buffer without putting it in the buffer list. */ + savebuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY); + if (savebuf != NULL && buf == curbuf) { + /* Open the memline. */ + curbuf = savebuf; + curwin->w_buffer = savebuf; + saved = ml_open(curbuf); + curbuf = buf; + curwin->w_buffer = buf; + } + if (savebuf == NULL || saved == FAIL || buf != curbuf + || move_lines(buf, savebuf) == FAIL) { + EMSG2(_("E462: Could not prepare for reloading \"%s\""), + buf->b_fname); + saved = FAIL; + } + } + + if (saved == OK) { + curbuf->b_flags |= BF_CHECK_RO; /* check for RO again */ + keep_filetype = TRUE; /* don't detect 'filetype' */ + if (readfile(buf->b_ffname, buf->b_fname, (linenr_T)0, + (linenr_T)0, + (linenr_T)MAXLNUM, &ea, flags) == FAIL) { + if (!aborting()) + EMSG2(_("E321: Could not reload \"%s\""), buf->b_fname); + if (savebuf != NULL && buf_valid(savebuf) && buf == curbuf) { + /* Put the text back from the save buffer. First + * delete any lines that readfile() added. */ + while (!bufempty()) + if (ml_delete(buf->b_ml.ml_line_count, FALSE) == FAIL) + break; + (void)move_lines(savebuf, buf); + } + } else if (buf == curbuf) { /* "buf" still valid */ + /* Mark the buffer as unmodified and free undo info. */ + unchanged(buf, TRUE); + if ((flags & READ_KEEP_UNDO) == 0) { + u_blockfree(buf); + u_clearall(buf); + } else { + /* Mark all undo states as changed. */ + u_unchanged(curbuf); + } + } + } + vim_free(ea.cmd); + + if (savebuf != NULL && buf_valid(savebuf)) + wipe_buffer(savebuf, FALSE); + + /* Invalidate diff info if necessary. */ + diff_invalidate(curbuf); + + /* Restore the topline and cursor position and check it (lines may + * have been removed). */ + if (old_topline > curbuf->b_ml.ml_line_count) + curwin->w_topline = curbuf->b_ml.ml_line_count; + else + curwin->w_topline = old_topline; + curwin->w_cursor = old_cursor; + check_cursor(); + update_topline(); + keep_filetype = FALSE; + { + win_T *wp; + tabpage_T *tp; + + /* Update folds unless they are defined manually. */ + FOR_ALL_TAB_WINDOWS(tp, wp) + if (wp->w_buffer == curwin->w_buffer + && !foldmethodIsManual(wp)) + foldUpdateAll(wp); + } + /* If the mode didn't change and 'readonly' was set, keep the old + * value; the user probably used the ":view" command. But don't + * reset it, might have had a read error. */ + if (orig_mode == curbuf->b_orig_mode) + curbuf->b_p_ro |= old_ro; + + /* Modelines must override settings done by autocommands. */ + do_modelines(0); + } + + /* restore curwin/curbuf and a few other things */ + aucmd_restbuf(&aco); + /* Careful: autocommands may have made "buf" invalid! */ +} + +void buf_store_time(buf, st, fname) +buf_T *buf; +struct stat *st; +char_u *fname UNUSED; +{ + buf->b_mtime = (long)st->st_mtime; + buf->b_orig_size = st->st_size; +#ifdef HAVE_ST_MODE + buf->b_orig_mode = (int)st->st_mode; +#else + buf->b_orig_mode = mch_getperm(fname); +#endif +} + +/* + * Adjust the line with missing eol, used for the next write. + * Used for do_filter(), when the input lines for the filter are deleted. + */ +void write_lnum_adjust(offset) +linenr_T offset; +{ + if (curbuf->b_no_eol_lnum != 0) /* only if there is a missing eol */ + curbuf->b_no_eol_lnum += offset; +} + +#if defined(TEMPDIRNAMES) || defined(PROTO) +static long temp_count = 0; /* Temp filename counter. */ + +/* + * Delete the temp directory and all files it contains. + */ +void vim_deltempdir() { + char_u **files; + int file_count; + int i; + + if (vim_tempdir != NULL) { + sprintf((char *)NameBuff, "%s*", vim_tempdir); + if (gen_expand_wildcards(1, &NameBuff, &file_count, &files, + EW_DIR|EW_FILE|EW_SILENT) == OK) { + for (i = 0; i < file_count; ++i) + mch_remove(files[i]); + FreeWild(file_count, files); + } + gettail(NameBuff)[-1] = NUL; + (void)mch_rmdir(NameBuff); + + vim_free(vim_tempdir); + vim_tempdir = NULL; + } +} + +#endif + +#ifdef TEMPDIRNAMES +/* + * Directory "tempdir" was created. Expand this name to a full path and put + * it in "vim_tempdir". This avoids that using ":cd" would confuse us. + * "tempdir" must be no longer than MAXPATHL. + */ +static void vim_settempdir(tempdir) +char_u *tempdir; +{ + char_u *buf; + + buf = alloc((unsigned)MAXPATHL + 2); + if (buf != NULL) { + if (vim_FullName(tempdir, buf, MAXPATHL, FALSE) == FAIL) + STRCPY(buf, tempdir); + add_pathsep(buf); + vim_tempdir = vim_strsave(buf); + vim_free(buf); + } +} +#endif + +/* + * vim_tempname(): Return a unique name that can be used for a temp file. + * + * The temp file is NOT created. + * + * The returned pointer is to allocated memory. + * The returned pointer is NULL if no valid name was found. + */ +char_u * vim_tempname(extra_char) +int extra_char UNUSED; /* char to use in the name instead of '?' */ +{ +#ifdef USE_TMPNAM + char_u itmp[L_tmpnam]; /* use tmpnam() */ +#else + char_u itmp[TEMPNAMELEN]; +#endif + +#ifdef TEMPDIRNAMES + static char *(tempdirs[]) = {TEMPDIRNAMES}; + int i; +# ifndef EEXIST + struct stat st; +# endif + + /* + * This will create a directory for private use by this instance of Vim. + * This is done once, and the same directory is used for all temp files. + * This method avoids security problems because of symlink attacks et al. + * It's also a bit faster, because we only need to check for an existing + * file when creating the directory and not for each temp file. + */ + if (vim_tempdir == NULL) { + /* + * Try the entries in TEMPDIRNAMES to create the temp directory. + */ + for (i = 0; i < (int)(sizeof(tempdirs) / sizeof(char *)); ++i) { +# ifndef HAVE_MKDTEMP + size_t itmplen; + long nr; + long off; +# endif + + /* expand $TMP, leave room for "/v1100000/999999999" */ + expand_env((char_u *)tempdirs[i], itmp, TEMPNAMELEN - 20); + if (mch_isdir(itmp)) { /* directory exists */ + add_pathsep(itmp); + +# ifdef HAVE_MKDTEMP + /* Leave room for filename */ + STRCAT(itmp, "vXXXXXX"); + if (mkdtemp((char *)itmp) != NULL) + vim_settempdir(itmp); +# else + /* Get an arbitrary number of up to 6 digits. When it's + * unlikely that it already exists it will be faster, + * otherwise it doesn't matter. The use of mkdir() avoids any + * security problems because of the predictable number. */ + nr = (mch_get_pid() + (long)time(NULL)) % 1000000L; + itmplen = STRLEN(itmp); + + /* Try up to 10000 different values until we find a name that + * doesn't exist. */ + for (off = 0; off < 10000L; ++off) { + int r; +# if defined(UNIX) || defined(VMS) + mode_t umask_save; +# endif + + sprintf((char *)itmp + itmplen, "v%ld", nr + off); +# ifndef EEXIST + /* If mkdir() does not set errno to EEXIST, check for + * existing file here. There is a race condition then, + * although it's fail-safe. */ + if (mch_stat((char *)itmp, &st) >= 0) + continue; +# endif +# if defined(UNIX) || defined(VMS) + /* Make sure the umask doesn't remove the executable bit. + * "repl" has been reported to use "177". */ + umask_save = umask(077); +# endif + r = vim_mkdir(itmp, 0700); +# if defined(UNIX) || defined(VMS) + (void)umask(umask_save); +# endif + if (r == 0) { + vim_settempdir(itmp); + break; + } +# ifdef EEXIST + /* If the mkdir() didn't fail because the file/dir exists, + * we probably can't create any dir here, try another + * place. */ + if (errno != EEXIST) +# endif + break; + } +# endif /* HAVE_MKDTEMP */ + if (vim_tempdir != NULL) + break; + } + } + } + + if (vim_tempdir != NULL) { + /* There is no need to check if the file exists, because we own the + * directory and nobody else creates a file in it. */ + sprintf((char *)itmp, "%s%ld", vim_tempdir, temp_count++); + return vim_strsave(itmp); + } + + return NULL; + +#else /* TEMPDIRNAMES */ + + +# ifdef USE_TMPNAM + char_u *p; + + /* tmpnam() will make its own name */ + p = tmpnam((char *)itmp); + if (p == NULL || *p == NUL) + return NULL; +# else + char_u *p; + +# ifdef VMS_TEMPNAM + /* mktemp() is not working on VMS. It seems to be + * a do-nothing function. Therefore we use tempnam(). + */ + sprintf((char *)itmp, "VIM%c", extra_char); + p = (char_u *)tempnam("tmp:", (char *)itmp); + if (p != NULL) { + /* VMS will use '.LOG' if we don't explicitly specify an extension, + * and VIM will then be unable to find the file later */ + STRCPY(itmp, p); + STRCAT(itmp, ".txt"); + free(p); + } else + return NULL; +# else + STRCPY(itmp, TEMPNAME); + if ((p = vim_strchr(itmp, '?')) != NULL) + *p = extra_char; + if (mktemp((char *)itmp) == NULL) + return NULL; +# endif +# endif + + return vim_strsave(itmp); +#endif /* TEMPDIRNAMES */ +} + +#if defined(BACKSLASH_IN_FILENAME) || defined(PROTO) +/* + * Convert all backslashes in fname to forward slashes in-place. + */ +void forward_slash(fname) +char_u *fname; +{ + char_u *p; + + for (p = fname; *p != NUL; ++p) + /* The Big5 encoding can have '\' in the trail byte. */ + if (enc_dbcs != 0 && (*mb_ptr2len)(p) > 1) + ++p; + else if (*p == '\\') + *p = '/'; +} +#endif + + +/* + * Code for automatic commands. + * + * Only included when "FEAT_AUTOCMD" has been defined. + */ + + +/* + * The autocommands are stored in a list for each event. + * Autocommands for the same pattern, that are consecutive, are joined + * together, to avoid having to match the pattern too often. + * The result is an array of Autopat lists, which point to AutoCmd lists: + * + * first_autopat[0] --> Autopat.next --> Autopat.next --> NULL + * Autopat.cmds Autopat.cmds + * | | + * V V + * AutoCmd.next AutoCmd.next + * | | + * V V + * AutoCmd.next NULL + * | + * V + * NULL + * + * first_autopat[1] --> Autopat.next --> NULL + * Autopat.cmds + * | + * V + * AutoCmd.next + * | + * V + * NULL + * etc. + * + * The order of AutoCmds is important, this is the order in which they were + * defined and will have to be executed. + */ +typedef struct AutoCmd { + char_u *cmd; /* The command to be executed (NULL + when command has been removed) */ + char nested; /* If autocommands nest here */ + char last; /* last command in list */ + scid_T scriptID; /* script ID where defined */ + struct AutoCmd *next; /* Next AutoCmd in list */ +} AutoCmd; + +typedef struct AutoPat { + char_u *pat; /* pattern as typed (NULL when pattern + has been removed) */ + regprog_T *reg_prog; /* compiled regprog for pattern */ + AutoCmd *cmds; /* list of commands to do */ + struct AutoPat *next; /* next AutoPat in AutoPat list */ + int group; /* group ID */ + int patlen; /* strlen() of pat */ + int buflocal_nr; /* !=0 for buffer-local AutoPat */ + char allow_dirs; /* Pattern may match whole path */ + char last; /* last pattern for apply_autocmds() */ +} AutoPat; + +static struct event_name { + char *name; /* event name */ + event_T event; /* event number */ +} event_names[] = +{ + {"BufAdd", EVENT_BUFADD}, + {"BufCreate", EVENT_BUFADD}, + {"BufDelete", EVENT_BUFDELETE}, + {"BufEnter", EVENT_BUFENTER}, + {"BufFilePost", EVENT_BUFFILEPOST}, + {"BufFilePre", EVENT_BUFFILEPRE}, + {"BufHidden", EVENT_BUFHIDDEN}, + {"BufLeave", EVENT_BUFLEAVE}, + {"BufNew", EVENT_BUFNEW}, + {"BufNewFile", EVENT_BUFNEWFILE}, + {"BufRead", EVENT_BUFREADPOST}, + {"BufReadCmd", EVENT_BUFREADCMD}, + {"BufReadPost", EVENT_BUFREADPOST}, + {"BufReadPre", EVENT_BUFREADPRE}, + {"BufUnload", EVENT_BUFUNLOAD}, + {"BufWinEnter", EVENT_BUFWINENTER}, + {"BufWinLeave", EVENT_BUFWINLEAVE}, + {"BufWipeout", EVENT_BUFWIPEOUT}, + {"BufWrite", EVENT_BUFWRITEPRE}, + {"BufWritePost", EVENT_BUFWRITEPOST}, + {"BufWritePre", EVENT_BUFWRITEPRE}, + {"BufWriteCmd", EVENT_BUFWRITECMD}, + {"CmdwinEnter", EVENT_CMDWINENTER}, + {"CmdwinLeave", EVENT_CMDWINLEAVE}, + {"ColorScheme", EVENT_COLORSCHEME}, + {"CompleteDone", EVENT_COMPLETEDONE}, + {"CursorHold", EVENT_CURSORHOLD}, + {"CursorHoldI", EVENT_CURSORHOLDI}, + {"CursorMoved", EVENT_CURSORMOVED}, + {"CursorMovedI", EVENT_CURSORMOVEDI}, + {"EncodingChanged", EVENT_ENCODINGCHANGED}, + {"FileEncoding", EVENT_ENCODINGCHANGED}, + {"FileAppendPost", EVENT_FILEAPPENDPOST}, + {"FileAppendPre", EVENT_FILEAPPENDPRE}, + {"FileAppendCmd", EVENT_FILEAPPENDCMD}, + {"FileChangedShell",EVENT_FILECHANGEDSHELL}, + {"FileChangedShellPost",EVENT_FILECHANGEDSHELLPOST}, + {"FileChangedRO", EVENT_FILECHANGEDRO}, + {"FileReadPost", EVENT_FILEREADPOST}, + {"FileReadPre", EVENT_FILEREADPRE}, + {"FileReadCmd", EVENT_FILEREADCMD}, + {"FileType", EVENT_FILETYPE}, + {"FileWritePost", EVENT_FILEWRITEPOST}, + {"FileWritePre", EVENT_FILEWRITEPRE}, + {"FileWriteCmd", EVENT_FILEWRITECMD}, + {"FilterReadPost", EVENT_FILTERREADPOST}, + {"FilterReadPre", EVENT_FILTERREADPRE}, + {"FilterWritePost", EVENT_FILTERWRITEPOST}, + {"FilterWritePre", EVENT_FILTERWRITEPRE}, + {"FocusGained", EVENT_FOCUSGAINED}, + {"FocusLost", EVENT_FOCUSLOST}, + {"FuncUndefined", EVENT_FUNCUNDEFINED}, + {"GUIEnter", EVENT_GUIENTER}, + {"GUIFailed", EVENT_GUIFAILED}, + {"InsertChange", EVENT_INSERTCHANGE}, + {"InsertEnter", EVENT_INSERTENTER}, + {"InsertLeave", EVENT_INSERTLEAVE}, + {"InsertCharPre", EVENT_INSERTCHARPRE}, + {"MenuPopup", EVENT_MENUPOPUP}, + {"QuickFixCmdPost", EVENT_QUICKFIXCMDPOST}, + {"QuickFixCmdPre", EVENT_QUICKFIXCMDPRE}, + {"QuitPre", EVENT_QUITPRE}, + {"RemoteReply", EVENT_REMOTEREPLY}, + {"SessionLoadPost", EVENT_SESSIONLOADPOST}, + {"ShellCmdPost", EVENT_SHELLCMDPOST}, + {"ShellFilterPost", EVENT_SHELLFILTERPOST}, + {"SourcePre", EVENT_SOURCEPRE}, + {"SourceCmd", EVENT_SOURCECMD}, + {"SpellFileMissing",EVENT_SPELLFILEMISSING}, + {"StdinReadPost", EVENT_STDINREADPOST}, + {"StdinReadPre", EVENT_STDINREADPRE}, + {"SwapExists", EVENT_SWAPEXISTS}, + {"Syntax", EVENT_SYNTAX}, + {"TabEnter", EVENT_TABENTER}, + {"TabLeave", EVENT_TABLEAVE}, + {"TermChanged", EVENT_TERMCHANGED}, + {"TermResponse", EVENT_TERMRESPONSE}, + {"TextChanged", EVENT_TEXTCHANGED}, + {"TextChangedI", EVENT_TEXTCHANGEDI}, + {"User", EVENT_USER}, + {"VimEnter", EVENT_VIMENTER}, + {"VimLeave", EVENT_VIMLEAVE}, + {"VimLeavePre", EVENT_VIMLEAVEPRE}, + {"WinEnter", EVENT_WINENTER}, + {"WinLeave", EVENT_WINLEAVE}, + {"VimResized", EVENT_VIMRESIZED}, + {NULL, (event_T)0} +}; + +static AutoPat *first_autopat[NUM_EVENTS] = +{ + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + +/* + * struct used to keep status while executing autocommands for an event. + */ +typedef struct AutoPatCmd { + AutoPat *curpat; /* next AutoPat to examine */ + AutoCmd *nextcmd; /* next AutoCmd to execute */ + int group; /* group being used */ + char_u *fname; /* fname to match with */ + char_u *sfname; /* sfname to match with */ + char_u *tail; /* tail of fname */ + event_T event; /* current event */ + int arg_bufnr; /* initially equal to , set to zero when + buf is deleted */ + struct AutoPatCmd *next; /* chain of active apc-s for auto-invalidation*/ +} AutoPatCmd; + +static AutoPatCmd *active_apc_list = NULL; /* stack of active autocommands */ + +/* + * augroups stores a list of autocmd group names. + */ +static garray_T augroups = {0, 0, sizeof(char_u *), 10, NULL}; +#define AUGROUP_NAME(i) (((char_u **)augroups.ga_data)[i]) + +/* + * The ID of the current group. Group 0 is the default one. + */ +static int current_augroup = AUGROUP_DEFAULT; + +static int au_need_clean = FALSE; /* need to delete marked patterns */ + +static void show_autocmd __ARGS((AutoPat *ap, event_T event)); +static void au_remove_pat __ARGS((AutoPat *ap)); +static void au_remove_cmds __ARGS((AutoPat *ap)); +static void au_cleanup __ARGS((void)); +static int au_new_group __ARGS((char_u *name)); +static void au_del_group __ARGS((char_u *name)); +static event_T event_name2nr __ARGS((char_u *start, char_u **end)); +static char_u *event_nr2name __ARGS((event_T event)); +static char_u *find_end_event __ARGS((char_u *arg, int have_group)); +static int event_ignored __ARGS((event_T event)); +static int au_get_grouparg __ARGS((char_u **argp)); +static int do_autocmd_event __ARGS((event_T event, char_u *pat, int nested, + char_u *cmd, int forceit, + int group)); +static int apply_autocmds_group __ARGS((event_T event, char_u *fname, char_u * + fname_io, int force, int group, buf_T * + buf, + exarg_T *eap)); +static void auto_next_pat __ARGS((AutoPatCmd *apc, int stop_at_last)); + + +static event_T last_event; +static int last_group; +static int autocmd_blocked = 0; /* block all autocmds */ + +/* + * Show the autocommands for one AutoPat. + */ +static void show_autocmd(ap, event) +AutoPat *ap; +event_T event; +{ + AutoCmd *ac; + + /* Check for "got_int" (here and at various places below), which is set + * when "q" has been hit for the "--more--" prompt */ + if (got_int) + return; + if (ap->pat == NULL) /* pattern has been removed */ + return; + + msg_putchar('\n'); + if (got_int) + return; + if (event != last_event || ap->group != last_group) { + if (ap->group != AUGROUP_DEFAULT) { + if (AUGROUP_NAME(ap->group) == NULL) + msg_puts_attr((char_u *)_("--Deleted--"), hl_attr(HLF_E)); + else + msg_puts_attr(AUGROUP_NAME(ap->group), hl_attr(HLF_T)); + msg_puts((char_u *)" "); + } + msg_puts_attr(event_nr2name(event), hl_attr(HLF_T)); + last_event = event; + last_group = ap->group; + msg_putchar('\n'); + if (got_int) + return; + } + msg_col = 4; + msg_outtrans(ap->pat); + + for (ac = ap->cmds; ac != NULL; ac = ac->next) { + if (ac->cmd != NULL) { /* skip removed commands */ + if (msg_col >= 14) + msg_putchar('\n'); + msg_col = 14; + if (got_int) + return; + msg_outtrans(ac->cmd); + if (p_verbose > 0) + last_set_msg(ac->scriptID); + if (got_int) + return; + if (ac->next != NULL) { + msg_putchar('\n'); + if (got_int) + return; + } + } + } +} + +/* + * Mark an autocommand pattern for deletion. + */ +static void au_remove_pat(ap) +AutoPat *ap; +{ + vim_free(ap->pat); + ap->pat = NULL; + ap->buflocal_nr = -1; + au_need_clean = TRUE; +} + +/* + * Mark all commands for a pattern for deletion. + */ +static void au_remove_cmds(ap) +AutoPat *ap; +{ + AutoCmd *ac; + + for (ac = ap->cmds; ac != NULL; ac = ac->next) { + vim_free(ac->cmd); + ac->cmd = NULL; + } + au_need_clean = TRUE; +} + +/* + * Cleanup autocommands and patterns that have been deleted. + * This is only done when not executing autocommands. + */ +static void au_cleanup() { + AutoPat *ap, **prev_ap; + AutoCmd *ac, **prev_ac; + event_T event; + + if (autocmd_busy || !au_need_clean) + return; + + /* loop over all events */ + for (event = (event_T)0; (int)event < (int)NUM_EVENTS; + event = (event_T)((int)event + 1)) { + /* loop over all autocommand patterns */ + prev_ap = &(first_autopat[(int)event]); + for (ap = *prev_ap; ap != NULL; ap = *prev_ap) { + /* loop over all commands for this pattern */ + prev_ac = &(ap->cmds); + for (ac = *prev_ac; ac != NULL; ac = *prev_ac) { + /* remove the command if the pattern is to be deleted or when + * the command has been marked for deletion */ + if (ap->pat == NULL || ac->cmd == NULL) { + *prev_ac = ac->next; + vim_free(ac->cmd); + vim_free(ac); + } else + prev_ac = &(ac->next); + } + + /* remove the pattern if it has been marked for deletion */ + if (ap->pat == NULL) { + *prev_ap = ap->next; + vim_regfree(ap->reg_prog); + vim_free(ap); + } else + prev_ap = &(ap->next); + } + } + + au_need_clean = FALSE; +} + +/* + * Called when buffer is freed, to remove/invalidate related buffer-local + * autocmds. + */ +void aubuflocal_remove(buf) +buf_T *buf; +{ + AutoPat *ap; + event_T event; + AutoPatCmd *apc; + + /* invalidate currently executing autocommands */ + for (apc = active_apc_list; apc; apc = apc->next) + if (buf->b_fnum == apc->arg_bufnr) + apc->arg_bufnr = 0; + + /* invalidate buflocals looping through events */ + for (event = (event_T)0; (int)event < (int)NUM_EVENTS; + event = (event_T)((int)event + 1)) + /* loop over all autocommand patterns */ + for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) + if (ap->buflocal_nr == buf->b_fnum) { + au_remove_pat(ap); + if (p_verbose >= 6) { + verbose_enter(); + smsg((char_u *) + _("auto-removing autocommand: %s "), + event_nr2name(event), buf->b_fnum); + verbose_leave(); + } + } + au_cleanup(); +} + +/* + * Add an autocmd group name. + * Return it's ID. Returns AUGROUP_ERROR (< 0) for error. + */ +static int au_new_group(name) +char_u *name; +{ + int i; + + i = au_find_group(name); + if (i == AUGROUP_ERROR) { /* the group doesn't exist yet, add it */ + /* First try using a free entry. */ + for (i = 0; i < augroups.ga_len; ++i) + if (AUGROUP_NAME(i) == NULL) + break; + if (i == augroups.ga_len && ga_grow(&augroups, 1) == FAIL) + return AUGROUP_ERROR; + + AUGROUP_NAME(i) = vim_strsave(name); + if (AUGROUP_NAME(i) == NULL) + return AUGROUP_ERROR; + if (i == augroups.ga_len) + ++augroups.ga_len; + } + + return i; +} + +static void au_del_group(name) +char_u *name; +{ + int i; + + i = au_find_group(name); + if (i == AUGROUP_ERROR) /* the group doesn't exist */ + EMSG2(_("E367: No such group: \"%s\""), name); + else { + vim_free(AUGROUP_NAME(i)); + AUGROUP_NAME(i) = NULL; + } +} + +/* + * Find the ID of an autocmd group name. + * Return it's ID. Returns AUGROUP_ERROR (< 0) for error. + */ +static int au_find_group(name) +char_u *name; +{ + int i; + + for (i = 0; i < augroups.ga_len; ++i) + if (AUGROUP_NAME(i) != NULL && STRCMP(AUGROUP_NAME(i), name) == 0) + return i; + return AUGROUP_ERROR; +} + +/* + * Return TRUE if augroup "name" exists. + */ +int au_has_group(name) +char_u *name; +{ + return au_find_group(name) != AUGROUP_ERROR; +} + +/* + * ":augroup {name}". + */ +void do_augroup(arg, del_group) +char_u *arg; +int del_group; +{ + int i; + + if (del_group) { + if (*arg == NUL) + EMSG(_(e_argreq)); + else + au_del_group(arg); + } else if (STRICMP(arg, "end") == 0) /* ":aug end": back to group 0 */ + current_augroup = AUGROUP_DEFAULT; + else if (*arg) { /* ":aug xxx": switch to group xxx */ + i = au_new_group(arg); + if (i != AUGROUP_ERROR) + current_augroup = i; + } else { /* ":aug": list the group names */ + msg_start(); + for (i = 0; i < augroups.ga_len; ++i) { + if (AUGROUP_NAME(i) != NULL) { + msg_puts(AUGROUP_NAME(i)); + msg_puts((char_u *)" "); + } + } + msg_clr_eos(); + msg_end(); + } +} + +#if defined(EXITFREE) || defined(PROTO) +void free_all_autocmds() { + for (current_augroup = -1; current_augroup < augroups.ga_len; + ++current_augroup) + do_autocmd((char_u *)"", TRUE); + ga_clear_strings(&augroups); +} + +#endif + +/* + * Return the event number for event name "start". + * Return NUM_EVENTS if the event name was not found. + * Return a pointer to the next event name in "end". + */ +static event_T event_name2nr(start, end) +char_u *start; +char_u **end; +{ + char_u *p; + int i; + int len; + + /* the event name ends with end of line, a blank or a comma */ + for (p = start; *p && !vim_iswhite(*p) && *p != ','; ++p) + ; + for (i = 0; event_names[i].name != NULL; ++i) { + len = (int)STRLEN(event_names[i].name); + if (len == p - start && STRNICMP(event_names[i].name, start, len) == 0) + break; + } + if (*p == ',') + ++p; + *end = p; + if (event_names[i].name == NULL) + return NUM_EVENTS; + return event_names[i].event; +} + +/* + * Return the name for event "event". + */ +static char_u * event_nr2name(event) +event_T event; +{ + int i; + + for (i = 0; event_names[i].name != NULL; ++i) + if (event_names[i].event == event) + return (char_u *)event_names[i].name; + return (char_u *)"Unknown"; +} + +/* + * Scan over the events. "*" stands for all events. + */ +static char_u * find_end_event(arg, have_group) +char_u *arg; +int have_group; /* TRUE when group name was found */ +{ + char_u *pat; + char_u *p; + + if (*arg == '*') { + if (arg[1] && !vim_iswhite(arg[1])) { + EMSG2(_("E215: Illegal character after *: %s"), arg); + return NULL; + } + pat = arg + 1; + } else { + for (pat = arg; *pat && !vim_iswhite(*pat); pat = p) { + if ((int)event_name2nr(pat, &p) >= (int)NUM_EVENTS) { + if (have_group) + EMSG2(_("E216: No such event: %s"), pat); + else + EMSG2(_("E216: No such group or event: %s"), pat); + return NULL; + } + } + } + return pat; +} + +/* + * Return TRUE if "event" is included in 'eventignore'. + */ +static int event_ignored(event) +event_T event; +{ + char_u *p = p_ei; + + while (*p != NUL) { + if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) + return TRUE; + if (event_name2nr(p, &p) == event) + return TRUE; + } + + return FALSE; +} + +/* + * Return OK when the contents of p_ei is valid, FAIL otherwise. + */ +int check_ei() { + char_u *p = p_ei; + + while (*p) { + if (STRNICMP(p, "all", 3) == 0 && (p[3] == NUL || p[3] == ',')) { + p += 3; + if (*p == ',') + ++p; + } else if (event_name2nr(p, &p) == NUM_EVENTS) + return FAIL; + } + + return OK; +} + +/* + * Add "what" to 'eventignore' to skip loading syntax highlighting for every + * buffer loaded into the window. "what" must start with a comma. + * Returns the old value of 'eventignore' in allocated memory. + */ +char_u * au_event_disable(what) +char *what; +{ + char_u *new_ei; + char_u *save_ei; + + save_ei = vim_strsave(p_ei); + if (save_ei != NULL) { + new_ei = vim_strnsave(p_ei, (int)(STRLEN(p_ei) + STRLEN(what))); + if (new_ei != NULL) { + if (*what == ',' && *p_ei == NUL) + STRCPY(new_ei, what + 1); + else + STRCAT(new_ei, what); + set_string_option_direct((char_u *)"ei", -1, new_ei, + OPT_FREE, SID_NONE); + vim_free(new_ei); + } + } + return save_ei; +} + +void au_event_restore(old_ei) +char_u *old_ei; +{ + if (old_ei != NULL) { + set_string_option_direct((char_u *)"ei", -1, old_ei, + OPT_FREE, SID_NONE); + vim_free(old_ei); + } +} + +/* + * do_autocmd() -- implements the :autocmd command. Can be used in the + * following ways: + * + * :autocmd Add to the list of commands that + * will be automatically executed for + * when editing a file matching , in + * the current group. + * :autocmd Show the auto-commands associated with + * and . + * :autocmd Show the auto-commands associated with + * . + * :autocmd Show all auto-commands. + * :autocmd! Remove all auto-commands associated with + * and , and add the command + * , for the current group. + * :autocmd! Remove all auto-commands associated with + * and for the current group. + * :autocmd! Remove all auto-commands associated with + * for the current group. + * :autocmd! Remove ALL auto-commands for the current + * group. + * + * Multiple events and patterns may be given separated by commas. Here are + * some examples: + * :autocmd bufread,bufenter *.c,*.h set tw=0 smartindent noic + * :autocmd bufleave * set tw=79 nosmartindent ic infercase + * + * :autocmd * *.c show all autocommands for *.c files. + * + * Mostly a {group} argument can optionally appear before . + */ +void do_autocmd(arg, forceit) +char_u *arg; +int forceit; +{ + char_u *pat; + char_u *envpat = NULL; + char_u *cmd; + event_T event; + int need_free = FALSE; + int nested = FALSE; + int group; + + /* + * Check for a legal group name. If not, use AUGROUP_ALL. + */ + group = au_get_grouparg(&arg); + if (arg == NULL) /* out of memory */ + return; + + /* + * Scan over the events. + * If we find an illegal name, return here, don't do anything. + */ + pat = find_end_event(arg, group != AUGROUP_ALL); + if (pat == NULL) + return; + + /* + * Scan over the pattern. Put a NUL at the end. + */ + pat = skipwhite(pat); + cmd = pat; + while (*cmd && (!vim_iswhite(*cmd) || cmd[-1] == '\\')) + cmd++; + if (*cmd) + *cmd++ = NUL; + + /* Expand environment variables in the pattern. Set 'shellslash', we want + * forward slashes here. */ + if (vim_strchr(pat, '$') != NULL || vim_strchr(pat, '~') != NULL) { +#ifdef BACKSLASH_IN_FILENAME + int p_ssl_save = p_ssl; + + p_ssl = TRUE; +#endif + envpat = expand_env_save(pat); +#ifdef BACKSLASH_IN_FILENAME + p_ssl = p_ssl_save; +#endif + if (envpat != NULL) + pat = envpat; + } + + /* + * Check for "nested" flag. + */ + cmd = skipwhite(cmd); + if (*cmd != NUL && STRNCMP(cmd, "nested", 6) == 0 && vim_iswhite(cmd[6])) { + nested = TRUE; + cmd = skipwhite(cmd + 6); + } + + /* + * Find the start of the commands. + * Expand in it. + */ + if (*cmd != NUL) { + cmd = expand_sfile(cmd); + if (cmd == NULL) /* some error */ + return; + need_free = TRUE; + } + + /* + * Print header when showing autocommands. + */ + if (!forceit && *cmd == NUL) { + /* Highlight title */ + MSG_PUTS_TITLE(_("\n--- Auto-Commands ---")); + } + + /* + * Loop over the events. + */ + last_event = (event_T)-1; /* for listing the event name */ + last_group = AUGROUP_ERROR; /* for listing the group name */ + if (*arg == '*' || *arg == NUL) { + for (event = (event_T)0; (int)event < (int)NUM_EVENTS; + event = (event_T)((int)event + 1)) + if (do_autocmd_event(event, pat, + nested, cmd, forceit, group) == FAIL) + break; + } else { + while (*arg && !vim_iswhite(*arg)) + if (do_autocmd_event(event_name2nr(arg, &arg), pat, + nested, cmd, forceit, group) == FAIL) + break; + } + + if (need_free) + vim_free(cmd); + vim_free(envpat); +} + +/* + * Find the group ID in a ":autocmd" or ":doautocmd" argument. + * The "argp" argument is advanced to the following argument. + * + * Returns the group ID, AUGROUP_ERROR for error (out of memory). + */ +static int au_get_grouparg(argp) +char_u **argp; +{ + char_u *group_name; + char_u *p; + char_u *arg = *argp; + int group = AUGROUP_ALL; + + p = skiptowhite(arg); + if (p > arg) { + group_name = vim_strnsave(arg, (int)(p - arg)); + if (group_name == NULL) /* out of memory */ + return AUGROUP_ERROR; + group = au_find_group(group_name); + if (group == AUGROUP_ERROR) + group = AUGROUP_ALL; /* no match, use all groups */ + else + *argp = skipwhite(p); /* match, skip over group name */ + vim_free(group_name); + } + return group; +} + +/* + * do_autocmd() for one event. + * If *pat == NUL do for all patterns. + * If *cmd == NUL show entries. + * If forceit == TRUE delete entries. + * If group is not AUGROUP_ALL, only use this group. + */ +static int do_autocmd_event(event, pat, nested, cmd, forceit, group) +event_T event; +char_u *pat; +int nested; +char_u *cmd; +int forceit; +int group; +{ + AutoPat *ap; + AutoPat **prev_ap; + AutoCmd *ac; + AutoCmd **prev_ac; + int brace_level; + char_u *endpat; + int findgroup; + int allgroups; + int patlen; + int is_buflocal; + int buflocal_nr; + char_u buflocal_pat[25]; /* for "" */ + + if (group == AUGROUP_ALL) + findgroup = current_augroup; + else + findgroup = group; + allgroups = (group == AUGROUP_ALL && !forceit && *cmd == NUL); + + /* + * Show or delete all patterns for an event. + */ + if (*pat == NUL) { + for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) { + if (forceit) { /* delete the AutoPat, if it's in the current group */ + if (ap->group == findgroup) + au_remove_pat(ap); + } else if (group == AUGROUP_ALL || ap->group == group) + show_autocmd(ap, event); + } + } + + /* + * Loop through all the specified patterns. + */ + for (; *pat; pat = (*endpat == ',' ? endpat + 1 : endpat)) { + /* + * Find end of the pattern. + * Watch out for a comma in braces, like "*.\{obj,o\}". + */ + brace_level = 0; + for (endpat = pat; *endpat && (*endpat != ',' || brace_level + || endpat[-1] == '\\'); ++endpat) { + if (*endpat == '{') + brace_level++; + else if (*endpat == '}') + brace_level--; + } + if (pat == endpat) /* ignore single comma */ + continue; + patlen = (int)(endpat - pat); + + /* + * detect special buffer-local patterns + */ + is_buflocal = FALSE; + buflocal_nr = 0; + + if (patlen >= 7 && STRNCMP(pat, "') { + /* Error will be printed only for addition. printing and removing + * will proceed silently. */ + is_buflocal = TRUE; + if (patlen == 8) + buflocal_nr = curbuf->b_fnum; + else if (patlen > 9 && pat[7] == '=') { + /* */ + if (patlen == 13 && STRNICMP(pat, "", 13)) + buflocal_nr = autocmd_bufnr; + /* */ + else if (skipdigits(pat + 8) == pat + patlen - 1) + buflocal_nr = atoi((char *)pat + 8); + } + } + + if (is_buflocal) { + /* normalize pat into standard "#N" form */ + sprintf((char *)buflocal_pat, "", buflocal_nr); + pat = buflocal_pat; /* can modify pat and patlen */ + patlen = (int)STRLEN(buflocal_pat); /* but not endpat */ + } + + /* + * Find AutoPat entries with this pattern. + */ + prev_ap = &first_autopat[(int)event]; + while ((ap = *prev_ap) != NULL) { + if (ap->pat != NULL) { + /* Accept a pattern when: + * - a group was specified and it's that group, or a group was + * not specified and it's the current group, or a group was + * not specified and we are listing + * - the length of the pattern matches + * - the pattern matches. + * For , this condition works because we normalize + * all buffer-local patterns. + */ + if ((allgroups || ap->group == findgroup) + && ap->patlen == patlen + && STRNCMP(pat, ap->pat, patlen) == 0) { + /* + * Remove existing autocommands. + * If adding any new autocmd's for this AutoPat, don't + * delete the pattern from the autopat list, append to + * this list. + */ + if (forceit) { + if (*cmd != NUL && ap->next == NULL) { + au_remove_cmds(ap); + break; + } + au_remove_pat(ap); + } + /* + * Show autocmd's for this autopat, or buflocals + */ + else if (*cmd == NUL) + show_autocmd(ap, event); + + /* + * Add autocmd to this autopat, if it's the last one. + */ + else if (ap->next == NULL) + break; + } + } + prev_ap = &ap->next; + } + + /* + * Add a new command. + */ + if (*cmd != NUL) { + /* + * If the pattern we want to add a command to does appear at the + * end of the list (or not is not in the list at all), add the + * pattern at the end of the list. + */ + if (ap == NULL) { + /* refuse to add buffer-local ap if buffer number is invalid */ + if (is_buflocal && (buflocal_nr == 0 + || buflist_findnr(buflocal_nr) == NULL)) { + EMSGN(_("E680: : invalid buffer number "), + buflocal_nr); + return FAIL; + } + + ap = (AutoPat *)alloc((unsigned)sizeof(AutoPat)); + if (ap == NULL) + return FAIL; + ap->pat = vim_strnsave(pat, patlen); + ap->patlen = patlen; + if (ap->pat == NULL) { + vim_free(ap); + return FAIL; + } + + if (is_buflocal) { + ap->buflocal_nr = buflocal_nr; + ap->reg_prog = NULL; + } else { + char_u *reg_pat; + + ap->buflocal_nr = 0; + reg_pat = file_pat_to_reg_pat(pat, endpat, + &ap->allow_dirs, TRUE); + if (reg_pat != NULL) + ap->reg_prog = vim_regcomp(reg_pat, RE_MAGIC); + vim_free(reg_pat); + if (reg_pat == NULL || ap->reg_prog == NULL) { + vim_free(ap->pat); + vim_free(ap); + return FAIL; + } + } + ap->cmds = NULL; + *prev_ap = ap; + ap->next = NULL; + if (group == AUGROUP_ALL) + ap->group = current_augroup; + else + ap->group = group; + } + + /* + * Add the autocmd at the end of the AutoCmd list. + */ + prev_ac = &(ap->cmds); + while ((ac = *prev_ac) != NULL) + prev_ac = &ac->next; + ac = (AutoCmd *)alloc((unsigned)sizeof(AutoCmd)); + if (ac == NULL) + return FAIL; + ac->cmd = vim_strsave(cmd); + ac->scriptID = current_SID; + if (ac->cmd == NULL) { + vim_free(ac); + return FAIL; + } + ac->next = NULL; + *prev_ac = ac; + ac->nested = nested; + } + } + + au_cleanup(); /* may really delete removed patterns/commands now */ + return OK; +} + +/* + * Implementation of ":doautocmd [group] event [fname]". + * Return OK for success, FAIL for failure; + */ +int do_doautocmd(arg, do_msg) +char_u *arg; +int do_msg; /* give message for no matching autocmds? */ +{ + char_u *fname; + int nothing_done = TRUE; + int group; + + /* + * Check for a legal group name. If not, use AUGROUP_ALL. + */ + group = au_get_grouparg(&arg); + if (arg == NULL) /* out of memory */ + return FAIL; + + if (*arg == '*') { + EMSG(_("E217: Can't execute autocommands for ALL events")); + return FAIL; + } + + /* + * Scan over the events. + * If we find an illegal name, return here, don't do anything. + */ + fname = find_end_event(arg, group != AUGROUP_ALL); + if (fname == NULL) + return FAIL; + + fname = skipwhite(fname); + + /* + * Loop over the events. + */ + while (*arg && !vim_iswhite(*arg)) + if (apply_autocmds_group(event_name2nr(arg, &arg), + fname, NULL, TRUE, group, curbuf, NULL)) + nothing_done = FALSE; + + if (nothing_done && do_msg) + MSG(_("No matching autocommands")); + + return aborting() ? FAIL : OK; +} + +/* + * ":doautoall": execute autocommands for each loaded buffer. + */ +void ex_doautoall(eap) +exarg_T *eap; +{ + int retval; + aco_save_T aco; + buf_T *buf; + char_u *arg = eap->arg; + int call_do_modelines = check_nomodeline(&arg); + + /* + * This is a bit tricky: For some commands curwin->w_buffer needs to be + * equal to curbuf, but for some buffers there may not be a window. + * So we change the buffer for the current window for a moment. This + * gives problems when the autocommands make changes to the list of + * buffers or windows... + */ + for (buf = firstbuf; buf != NULL; buf = buf->b_next) { + if (buf->b_ml.ml_mfp != NULL) { + /* find a window for this buffer and save some values */ + aucmd_prepbuf(&aco, buf); + + /* execute the autocommands for this buffer */ + retval = do_doautocmd(arg, FALSE); + + if (call_do_modelines) { + /* Execute the modeline settings, but don't set window-local + * options if we are using the current window for another + * buffer. */ + do_modelines(curwin == aucmd_win ? OPT_NOWIN : 0); + } + + /* restore the current window */ + aucmd_restbuf(&aco); + + /* stop if there is some error or buffer was deleted */ + if (retval == FAIL || !buf_valid(buf)) + break; + } + } + + check_cursor(); /* just in case lines got deleted */ +} + +/* + * Check *argp for . When it is present return FALSE, otherwise + * return TRUE and advance *argp to after it. + * Thus return TRUE when do_modelines() should be called. + */ +int check_nomodeline(argp) +char_u **argp; +{ + if (STRNCMP(*argp, "", 12) == 0) { + *argp = skipwhite(*argp + 12); + return FALSE; + } + return TRUE; +} + +/* + * Prepare for executing autocommands for (hidden) buffer "buf". + * Search for a visible window containing the current buffer. If there isn't + * one then use "aucmd_win". + * Set "curbuf" and "curwin" to match "buf". + * When FEAT_AUTOCMD is not defined another version is used, see below. + */ +void aucmd_prepbuf(aco, buf) +aco_save_T *aco; /* structure to save values in */ +buf_T *buf; /* new curbuf */ +{ + win_T *win; + int save_ea; + int save_acd; + + /* Find a window that is for the new buffer */ + if (buf == curbuf) /* be quick when buf is curbuf */ + win = curwin; + else + for (win = firstwin; win != NULL; win = win->w_next) + if (win->w_buffer == buf) + break; + + /* Allocate "aucmd_win" when needed. If this fails (out of memory) fall + * back to using the current window. */ + if (win == NULL && aucmd_win == NULL) { + win_alloc_aucmd_win(); + if (aucmd_win == NULL) + win = curwin; + } + if (win == NULL && aucmd_win_used) + /* Strange recursive autocommand, fall back to using the current + * window. Expect a few side effects... */ + win = curwin; + + aco->save_curwin = curwin; + aco->save_curbuf = curbuf; + if (win != NULL) { + /* There is a window for "buf" in the current tab page, make it the + * curwin. This is preferred, it has the least side effects (esp. if + * "buf" is curbuf). */ + aco->use_aucmd_win = FALSE; + curwin = win; + } else { + /* There is no window for "buf", use "aucmd_win". To minimize the side + * effects, insert it in the current tab page. + * Anything related to a window (e.g., setting folds) may have + * unexpected results. */ + aco->use_aucmd_win = TRUE; + aucmd_win_used = TRUE; + aucmd_win->w_buffer = buf; + aucmd_win->w_s = &buf->b_s; + ++buf->b_nwindows; + win_init_empty(aucmd_win); /* set cursor and topline to safe values */ + + /* Make sure w_localdir and globaldir are NULL to avoid a chdir() in + * win_enter_ext(). */ + vim_free(aucmd_win->w_localdir); + aucmd_win->w_localdir = NULL; + aco->globaldir = globaldir; + globaldir = NULL; + + + /* Split the current window, put the aucmd_win in the upper half. + * We don't want the BufEnter or WinEnter autocommands. */ + block_autocmds(); + make_snapshot(SNAP_AUCMD_IDX); + save_ea = p_ea; + p_ea = FALSE; + + /* Prevent chdir() call in win_enter_ext(), through do_autochdir(). */ + save_acd = p_acd; + p_acd = FALSE; + + (void)win_split_ins(0, WSP_TOP, aucmd_win, 0); + (void)win_comp_pos(); /* recompute window positions */ + p_ea = save_ea; + p_acd = save_acd; + unblock_autocmds(); + curwin = aucmd_win; + } + curbuf = buf; + aco->new_curwin = curwin; + aco->new_curbuf = curbuf; +} + +/* + * Cleanup after executing autocommands for a (hidden) buffer. + * Restore the window as it was (if possible). + * When FEAT_AUTOCMD is not defined another version is used, see below. + */ +void aucmd_restbuf(aco) +aco_save_T *aco; /* structure holding saved values */ +{ + int dummy; + + if (aco->use_aucmd_win) { + --curbuf->b_nwindows; + /* Find "aucmd_win", it can't be closed, but it may be in another tab + * page. Do not trigger autocommands here. */ + block_autocmds(); + if (curwin != aucmd_win) { + tabpage_T *tp; + win_T *wp; + + FOR_ALL_TAB_WINDOWS(tp, wp) + { + if (wp == aucmd_win) { + if (tp != curtab) + goto_tabpage_tp(tp, TRUE, TRUE); + win_goto(aucmd_win); + goto win_found; + } + } + } +win_found: + + /* Remove the window and frame from the tree of frames. */ + (void)winframe_remove(curwin, &dummy, NULL); + win_remove(curwin, NULL); + aucmd_win_used = FALSE; + last_status(FALSE); /* may need to remove last status line */ + restore_snapshot(SNAP_AUCMD_IDX, FALSE); + (void)win_comp_pos(); /* recompute window positions */ + unblock_autocmds(); + + if (win_valid(aco->save_curwin)) + curwin = aco->save_curwin; + else + /* Hmm, original window disappeared. Just use the first one. */ + curwin = firstwin; + vars_clear(&aucmd_win->w_vars->dv_hashtab); /* free all w: variables */ + hash_init(&aucmd_win->w_vars->dv_hashtab); /* re-use the hashtab */ + curbuf = curwin->w_buffer; + + vim_free(globaldir); + globaldir = aco->globaldir; + + /* the buffer contents may have changed */ + check_cursor(); + if (curwin->w_topline > curbuf->b_ml.ml_line_count) { + curwin->w_topline = curbuf->b_ml.ml_line_count; + curwin->w_topfill = 0; + } + } else { + /* restore curwin */ + if (win_valid(aco->save_curwin)) { + /* Restore the buffer which was previously edited by curwin, if + * it was changed, we are still the same window and the buffer is + * valid. */ + if (curwin == aco->new_curwin + && curbuf != aco->new_curbuf + && buf_valid(aco->new_curbuf) + && aco->new_curbuf->b_ml.ml_mfp != NULL) { + if (curwin->w_s == &curbuf->b_s) + curwin->w_s = &aco->new_curbuf->b_s; + --curbuf->b_nwindows; + curbuf = aco->new_curbuf; + curwin->w_buffer = curbuf; + ++curbuf->b_nwindows; + } + + curwin = aco->save_curwin; + curbuf = curwin->w_buffer; + } + } +} + +static int autocmd_nested = FALSE; + +/* + * Execute autocommands for "event" and file name "fname". + * Return TRUE if some commands were executed. + */ +int apply_autocmds(event, fname, fname_io, force, buf) +event_T event; +char_u *fname; /* NULL or empty means use actual file name */ +char_u *fname_io; /* fname to use for on cmdline */ +int force; /* when TRUE, ignore autocmd_busy */ +buf_T *buf; /* buffer for */ +{ + return apply_autocmds_group(event, fname, fname_io, force, + AUGROUP_ALL, buf, NULL); +} + +/* + * Like apply_autocmds(), but with extra "eap" argument. This takes care of + * setting v:filearg. + */ +static int apply_autocmds_exarg(event, fname, fname_io, force, buf, eap) +event_T event; +char_u *fname; +char_u *fname_io; +int force; +buf_T *buf; +exarg_T *eap; +{ + return apply_autocmds_group(event, fname, fname_io, force, + AUGROUP_ALL, buf, eap); +} + +/* + * Like apply_autocmds(), but handles the caller's retval. If the script + * processing is being aborted or if retval is FAIL when inside a try + * conditional, no autocommands are executed. If otherwise the autocommands + * cause the script to be aborted, retval is set to FAIL. + */ +int apply_autocmds_retval(event, fname, fname_io, force, buf, retval) +event_T event; +char_u *fname; /* NULL or empty means use actual file name */ +char_u *fname_io; /* fname to use for on cmdline */ +int force; /* when TRUE, ignore autocmd_busy */ +buf_T *buf; /* buffer for */ +int *retval; /* pointer to caller's retval */ +{ + int did_cmd; + + if (should_abort(*retval)) + return FALSE; + + did_cmd = apply_autocmds_group(event, fname, fname_io, force, + AUGROUP_ALL, buf, NULL); + if (did_cmd + && aborting() + ) + *retval = FAIL; + return did_cmd; +} + +/* + * Return TRUE when there is a CursorHold autocommand defined. + */ +int has_cursorhold() { + return first_autopat[(int)(get_real_state() == NORMAL_BUSY + ? EVENT_CURSORHOLD : EVENT_CURSORHOLDI)] != NULL; +} + +/* + * Return TRUE if the CursorHold event can be triggered. + */ +int trigger_cursorhold() { + int state; + + if (!did_cursorhold + && has_cursorhold() + && !Recording + && typebuf.tb_len == 0 + && !ins_compl_active() + ) { + state = get_real_state(); + if (state == NORMAL_BUSY || (state & INSERT) != 0) + return TRUE; + } + return FALSE; +} + +/* + * Return TRUE when there is a CursorMoved autocommand defined. + */ +int has_cursormoved() { + return first_autopat[(int)EVENT_CURSORMOVED] != NULL; +} + +/* + * Return TRUE when there is a CursorMovedI autocommand defined. + */ +int has_cursormovedI() { + return first_autopat[(int)EVENT_CURSORMOVEDI] != NULL; +} + +/* + * Return TRUE when there is a TextChanged autocommand defined. + */ +int has_textchanged() { + return first_autopat[(int)EVENT_TEXTCHANGED] != NULL; +} + +/* + * Return TRUE when there is a TextChangedI autocommand defined. + */ +int has_textchangedI() { + return first_autopat[(int)EVENT_TEXTCHANGEDI] != NULL; +} + +/* + * Return TRUE when there is an InsertCharPre autocommand defined. + */ +int has_insertcharpre() { + return first_autopat[(int)EVENT_INSERTCHARPRE] != NULL; +} + +static int apply_autocmds_group(event, fname, fname_io, force, group, buf, eap) +event_T event; +char_u *fname; /* NULL or empty means use actual file name */ +char_u *fname_io; /* fname to use for on cmdline, NULL means + use fname */ +int force; /* when TRUE, ignore autocmd_busy */ +int group; /* group ID, or AUGROUP_ALL */ +buf_T *buf; /* buffer for */ +exarg_T *eap; /* command arguments */ +{ + char_u *sfname = NULL; /* short file name */ + char_u *tail; + int save_changed; + buf_T *old_curbuf; + int retval = FALSE; + char_u *save_sourcing_name; + linenr_T save_sourcing_lnum; + char_u *save_autocmd_fname; + int save_autocmd_fname_full; + int save_autocmd_bufnr; + char_u *save_autocmd_match; + int save_autocmd_busy; + int save_autocmd_nested; + static int nesting = 0; + AutoPatCmd patcmd; + AutoPat *ap; + scid_T save_current_SID; + void *save_funccalp; + char_u *save_cmdarg; + long save_cmdbang; + static int filechangeshell_busy = FALSE; + proftime_T wait_time; + + /* + * Quickly return if there are no autocommands for this event or + * autocommands are blocked. + */ + if (first_autopat[(int)event] == NULL || autocmd_blocked > 0) + goto BYPASS_AU; + + /* + * When autocommands are busy, new autocommands are only executed when + * explicitly enabled with the "nested" flag. + */ + if (autocmd_busy && !(force || autocmd_nested)) + goto BYPASS_AU; + + /* + * Quickly return when immediately aborting on error, or when an interrupt + * occurred or an exception was thrown but not caught. + */ + if (aborting()) + goto BYPASS_AU; + + /* + * FileChangedShell never nests, because it can create an endless loop. + */ + if (filechangeshell_busy && (event == EVENT_FILECHANGEDSHELL + || event == EVENT_FILECHANGEDSHELLPOST)) + goto BYPASS_AU; + + /* + * Ignore events in 'eventignore'. + */ + if (event_ignored(event)) + goto BYPASS_AU; + + /* + * Allow nesting of autocommands, but restrict the depth, because it's + * possible to create an endless loop. + */ + if (nesting == 10) { + EMSG(_("E218: autocommand nesting too deep")); + goto BYPASS_AU; + } + + /* + * Check if these autocommands are disabled. Used when doing ":all" or + * ":ball". + */ + if ( (autocmd_no_enter + && (event == EVENT_WINENTER || event == EVENT_BUFENTER)) + || (autocmd_no_leave + && (event == EVENT_WINLEAVE || event == EVENT_BUFLEAVE))) + goto BYPASS_AU; + + /* + * Save the autocmd_* variables and info about the current buffer. + */ + save_autocmd_fname = autocmd_fname; + save_autocmd_fname_full = autocmd_fname_full; + save_autocmd_bufnr = autocmd_bufnr; + save_autocmd_match = autocmd_match; + save_autocmd_busy = autocmd_busy; + save_autocmd_nested = autocmd_nested; + save_changed = curbuf->b_changed; + old_curbuf = curbuf; + + /* + * Set the file name to be used for . + * Make a copy to avoid that changing a buffer name or directory makes it + * invalid. + */ + if (fname_io == NULL) { + if (event == EVENT_COLORSCHEME) + autocmd_fname = NULL; + else if (fname != NULL && *fname != NUL) + autocmd_fname = fname; + else if (buf != NULL) + autocmd_fname = buf->b_ffname; + else + autocmd_fname = NULL; + } else + autocmd_fname = fname_io; + if (autocmd_fname != NULL) + autocmd_fname = vim_strsave(autocmd_fname); + autocmd_fname_full = FALSE; /* call FullName_save() later */ + + /* + * Set the buffer number to be used for . + */ + if (buf == NULL) + autocmd_bufnr = 0; + else + autocmd_bufnr = buf->b_fnum; + + /* + * When the file name is NULL or empty, use the file name of buffer "buf". + * Always use the full path of the file name to match with, in case + * "allow_dirs" is set. + */ + if (fname == NULL || *fname == NUL) { + if (buf == NULL) + fname = NULL; + else { + if (event == EVENT_SYNTAX) + fname = buf->b_p_syn; + else if (event == EVENT_FILETYPE) + fname = buf->b_p_ft; + else { + if (buf->b_sfname != NULL) + sfname = vim_strsave(buf->b_sfname); + fname = buf->b_ffname; + } + } + if (fname == NULL) + fname = (char_u *)""; + fname = vim_strsave(fname); /* make a copy, so we can change it */ + } else { + sfname = vim_strsave(fname); + /* Don't try expanding FileType, Syntax, FuncUndefined, WindowID, + * ColorScheme or QuickFixCmd* */ + if (event == EVENT_FILETYPE + || event == EVENT_SYNTAX + || event == EVENT_FUNCUNDEFINED + || event == EVENT_REMOTEREPLY + || event == EVENT_SPELLFILEMISSING + || event == EVENT_QUICKFIXCMDPRE + || event == EVENT_COLORSCHEME + || event == EVENT_QUICKFIXCMDPOST) + fname = vim_strsave(fname); + else + fname = FullName_save(fname, FALSE); + } + if (fname == NULL) { /* out of memory */ + vim_free(sfname); + retval = FALSE; + goto BYPASS_AU; + } + +#ifdef BACKSLASH_IN_FILENAME + /* + * Replace all backslashes with forward slashes. This makes the + * autocommand patterns portable between Unix and MS-DOS. + */ + if (sfname != NULL) + forward_slash(sfname); + forward_slash(fname); +#endif + + + /* + * Set the name to be used for . + */ + autocmd_match = fname; + + + /* Don't redraw while doing auto commands. */ + ++RedrawingDisabled; + save_sourcing_name = sourcing_name; + sourcing_name = NULL; /* don't free this one */ + save_sourcing_lnum = sourcing_lnum; + sourcing_lnum = 0; /* no line number here */ + + save_current_SID = current_SID; + + if (do_profiling == PROF_YES) + prof_child_enter(&wait_time); /* doesn't count for the caller itself */ + + /* Don't use local function variables, if called from a function */ + save_funccalp = save_funccal(); + + /* + * When starting to execute autocommands, save the search patterns. + */ + if (!autocmd_busy) { + save_search_patterns(); + saveRedobuff(); + did_filetype = keep_filetype; + } + + /* + * Note that we are applying autocmds. Some commands need to know. + */ + autocmd_busy = TRUE; + filechangeshell_busy = (event == EVENT_FILECHANGEDSHELL); + ++nesting; /* see matching decrement below */ + + /* Remember that FileType was triggered. Used for did_filetype(). */ + if (event == EVENT_FILETYPE) + did_filetype = TRUE; + + tail = gettail(fname); + + /* Find first autocommand that matches */ + patcmd.curpat = first_autopat[(int)event]; + patcmd.nextcmd = NULL; + patcmd.group = group; + patcmd.fname = fname; + patcmd.sfname = sfname; + patcmd.tail = tail; + patcmd.event = event; + patcmd.arg_bufnr = autocmd_bufnr; + patcmd.next = NULL; + auto_next_pat(&patcmd, FALSE); + + /* found one, start executing the autocommands */ + if (patcmd.curpat != NULL) { + /* add to active_apc_list */ + patcmd.next = active_apc_list; + active_apc_list = &patcmd; + + /* set v:cmdarg (only when there is a matching pattern) */ + save_cmdbang = get_vim_var_nr(VV_CMDBANG); + if (eap != NULL) { + save_cmdarg = set_cmdarg(eap, NULL); + set_vim_var_nr(VV_CMDBANG, (long)eap->forceit); + } else + save_cmdarg = NULL; /* avoid gcc warning */ + retval = TRUE; + /* mark the last pattern, to avoid an endless loop when more patterns + * are added when executing autocommands */ + for (ap = patcmd.curpat; ap->next != NULL; ap = ap->next) + ap->last = FALSE; + ap->last = TRUE; + check_lnums(TRUE); /* make sure cursor and topline are valid */ + do_cmdline(NULL, getnextac, (void *)&patcmd, + DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT); + if (eap != NULL) { + (void)set_cmdarg(NULL, save_cmdarg); + set_vim_var_nr(VV_CMDBANG, save_cmdbang); + } + /* delete from active_apc_list */ + if (active_apc_list == &patcmd) /* just in case */ + active_apc_list = patcmd.next; + } + + --RedrawingDisabled; + autocmd_busy = save_autocmd_busy; + filechangeshell_busy = FALSE; + autocmd_nested = save_autocmd_nested; + vim_free(sourcing_name); + sourcing_name = save_sourcing_name; + sourcing_lnum = save_sourcing_lnum; + vim_free(autocmd_fname); + autocmd_fname = save_autocmd_fname; + autocmd_fname_full = save_autocmd_fname_full; + autocmd_bufnr = save_autocmd_bufnr; + autocmd_match = save_autocmd_match; + current_SID = save_current_SID; + restore_funccal(save_funccalp); + if (do_profiling == PROF_YES) + prof_child_exit(&wait_time); + vim_free(fname); + vim_free(sfname); + --nesting; /* see matching increment above */ + + /* + * When stopping to execute autocommands, restore the search patterns and + * the redo buffer. + */ + if (!autocmd_busy) { + restore_search_patterns(); + restoreRedobuff(); + did_filetype = FALSE; + } + + /* + * Some events don't set or reset the Changed flag. + * Check if still in the same buffer! + */ + if (curbuf == old_curbuf + && (event == EVENT_BUFREADPOST + || event == EVENT_BUFWRITEPOST + || event == EVENT_FILEAPPENDPOST + || event == EVENT_VIMLEAVE + || event == EVENT_VIMLEAVEPRE)) { + if (curbuf->b_changed != save_changed) + need_maketitle = TRUE; + curbuf->b_changed = save_changed; + } + + au_cleanup(); /* may really delete removed patterns/commands now */ + +BYPASS_AU: + /* When wiping out a buffer make sure all its buffer-local autocommands + * are deleted. */ + if (event == EVENT_BUFWIPEOUT && buf != NULL) + aubuflocal_remove(buf); + + return retval; +} + +static char_u *old_termresponse = NULL; + +/* + * Block triggering autocommands until unblock_autocmd() is called. + * Can be used recursively, so long as it's symmetric. + */ +void block_autocmds() { + /* Remember the value of v:termresponse. */ + if (autocmd_blocked == 0) + old_termresponse = get_vim_var_str(VV_TERMRESPONSE); + ++autocmd_blocked; +} + +void unblock_autocmds() { + --autocmd_blocked; + + /* When v:termresponse was set while autocommands were blocked, trigger + * the autocommands now. Esp. useful when executing a shell command + * during startup (vimdiff). */ + if (autocmd_blocked == 0 + && get_vim_var_str(VV_TERMRESPONSE) != old_termresponse) + apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, FALSE, curbuf); +} + +int is_autocmd_blocked() { + return autocmd_blocked != 0; +} + +/* + * Find next autocommand pattern that matches. + */ +static void auto_next_pat(apc, stop_at_last) +AutoPatCmd *apc; +int stop_at_last; /* stop when 'last' flag is set */ +{ + AutoPat *ap; + AutoCmd *cp; + char_u *name; + char *s; + + vim_free(sourcing_name); + sourcing_name = NULL; + + for (ap = apc->curpat; ap != NULL && !got_int; ap = ap->next) { + apc->curpat = NULL; + + /* Only use a pattern when it has not been removed, has commands and + * the group matches. For buffer-local autocommands only check the + * buffer number. */ + if (ap->pat != NULL && ap->cmds != NULL + && (apc->group == AUGROUP_ALL || apc->group == ap->group)) { + /* execution-condition */ + if (ap->buflocal_nr == 0 + ? (match_file_pat(NULL, ap->reg_prog, apc->fname, + apc->sfname, apc->tail, ap->allow_dirs)) + : ap->buflocal_nr == apc->arg_bufnr) { + name = event_nr2name(apc->event); + s = _("%s Auto commands for \"%s\""); + sourcing_name = alloc((unsigned)(STRLEN(s) + + STRLEN(name) + ap->patlen + 1)); + if (sourcing_name != NULL) { + sprintf((char *)sourcing_name, s, + (char *)name, (char *)ap->pat); + if (p_verbose >= 8) { + verbose_enter(); + smsg((char_u *)_("Executing %s"), sourcing_name); + verbose_leave(); + } + } + + apc->curpat = ap; + apc->nextcmd = ap->cmds; + /* mark last command */ + for (cp = ap->cmds; cp->next != NULL; cp = cp->next) + cp->last = FALSE; + cp->last = TRUE; + } + line_breakcheck(); + if (apc->curpat != NULL) /* found a match */ + break; + } + if (stop_at_last && ap->last) + break; + } +} + +/* + * Get next autocommand command. + * Called by do_cmdline() to get the next line for ":if". + * Returns allocated string, or NULL for end of autocommands. + */ +char_u * getnextac(c, cookie, indent) +int c UNUSED; +void *cookie; +int indent UNUSED; +{ + AutoPatCmd *acp = (AutoPatCmd *)cookie; + char_u *retval; + AutoCmd *ac; + + /* Can be called again after returning the last line. */ + if (acp->curpat == NULL) + return NULL; + + /* repeat until we find an autocommand to execute */ + for (;; ) { + /* skip removed commands */ + while (acp->nextcmd != NULL && acp->nextcmd->cmd == NULL) + if (acp->nextcmd->last) + acp->nextcmd = NULL; + else + acp->nextcmd = acp->nextcmd->next; + + if (acp->nextcmd != NULL) + break; + + /* at end of commands, find next pattern that matches */ + if (acp->curpat->last) + acp->curpat = NULL; + else + acp->curpat = acp->curpat->next; + if (acp->curpat != NULL) + auto_next_pat(acp, TRUE); + if (acp->curpat == NULL) + return NULL; + } + + ac = acp->nextcmd; + + if (p_verbose >= 9) { + verbose_enter_scroll(); + smsg((char_u *)_("autocommand %s"), ac->cmd); + msg_puts((char_u *)"\n"); /* don't overwrite this either */ + verbose_leave_scroll(); + } + retval = vim_strsave(ac->cmd); + autocmd_nested = ac->nested; + current_SID = ac->scriptID; + if (ac->last) + acp->nextcmd = NULL; + else + acp->nextcmd = ac->next; + return retval; +} + +/* + * Return TRUE if there is a matching autocommand for "fname". + * To account for buffer-local autocommands, function needs to know + * in which buffer the file will be opened. + */ +int has_autocmd(event, sfname, buf) +event_T event; +char_u *sfname; +buf_T *buf; +{ + AutoPat *ap; + char_u *fname; + char_u *tail = gettail(sfname); + int retval = FALSE; + + fname = FullName_save(sfname, FALSE); + if (fname == NULL) + return FALSE; + +#ifdef BACKSLASH_IN_FILENAME + /* + * Replace all backslashes with forward slashes. This makes the + * autocommand patterns portable between Unix and MS-DOS. + */ + sfname = vim_strsave(sfname); + if (sfname != NULL) + forward_slash(sfname); + forward_slash(fname); +#endif + + for (ap = first_autopat[(int)event]; ap != NULL; ap = ap->next) + if (ap->pat != NULL && ap->cmds != NULL + && (ap->buflocal_nr == 0 + ? match_file_pat(NULL, ap->reg_prog, + fname, sfname, tail, ap->allow_dirs) + : buf != NULL && ap->buflocal_nr == buf->b_fnum + )) { + retval = TRUE; + break; + } + + vim_free(fname); +#ifdef BACKSLASH_IN_FILENAME + vim_free(sfname); +#endif + + return retval; +} + +/* + * Function given to ExpandGeneric() to obtain the list of autocommand group + * names. + */ +char_u * get_augroup_name(xp, idx) +expand_T *xp UNUSED; +int idx; +{ + if (idx == augroups.ga_len) /* add "END" add the end */ + return (char_u *)"END"; + if (idx >= augroups.ga_len) /* end of list */ + return NULL; + if (AUGROUP_NAME(idx) == NULL) /* skip deleted entries */ + return (char_u *)""; + return AUGROUP_NAME(idx); /* return a name */ +} + +static int include_groups = FALSE; + +char_u * set_context_in_autocmd(xp, arg, doautocmd) +expand_T *xp; +char_u *arg; +int doautocmd; /* TRUE for :doauto*, FALSE for :autocmd */ +{ + char_u *p; + int group; + + /* check for a group name, skip it if present */ + include_groups = FALSE; + p = arg; + group = au_get_grouparg(&arg); + if (group == AUGROUP_ERROR) + return NULL; + /* If there only is a group name that's what we expand. */ + if (*arg == NUL && group != AUGROUP_ALL && !vim_iswhite(arg[-1])) { + arg = p; + group = AUGROUP_ALL; + } + + /* skip over event name */ + for (p = arg; *p != NUL && !vim_iswhite(*p); ++p) + if (*p == ',') + arg = p + 1; + if (*p == NUL) { + if (group == AUGROUP_ALL) + include_groups = TRUE; + xp->xp_context = EXPAND_EVENTS; /* expand event name */ + xp->xp_pattern = arg; + return NULL; + } + + /* skip over pattern */ + arg = skipwhite(p); + while (*arg && (!vim_iswhite(*arg) || arg[-1] == '\\')) + arg++; + if (*arg) + return arg; /* expand (next) command */ + + if (doautocmd) + xp->xp_context = EXPAND_FILES; /* expand file names */ + else + xp->xp_context = EXPAND_NOTHING; /* pattern is not expanded */ + return NULL; +} + +/* + * Function given to ExpandGeneric() to obtain the list of event names. + */ +char_u * get_event_name(xp, idx) +expand_T *xp UNUSED; +int idx; +{ + if (idx < augroups.ga_len) { /* First list group names, if wanted */ + if (!include_groups || AUGROUP_NAME(idx) == NULL) + return (char_u *)""; /* skip deleted entries */ + return AUGROUP_NAME(idx); /* return a name */ + } + return (char_u *)event_names[idx - augroups.ga_len].name; +} + + +/* + * Return TRUE if autocmd is supported. + */ +int autocmd_supported(name) +char_u *name; +{ + char_u *p; + + return event_name2nr(name, &p) != NUM_EVENTS; +} + +/* + * Return TRUE if an autocommand is defined for a group, event and + * pattern: The group can be omitted to accept any group. "event" and "pattern" + * can be NULL to accept any event and pattern. "pattern" can be NULL to accept + * any pattern. Buffer-local patterns or are accepted. + * Used for: + * exists("#Group") or + * exists("#Group#Event") or + * exists("#Group#Event#pat") or + * exists("#Event") or + * exists("#Event#pat") + */ +int au_exists(arg) +char_u *arg; +{ + char_u *arg_save; + char_u *pattern = NULL; + char_u *event_name; + char_u *p; + event_T event; + AutoPat *ap; + buf_T *buflocal_buf = NULL; + int group; + int retval = FALSE; + + /* Make a copy so that we can change the '#' chars to a NUL. */ + arg_save = vim_strsave(arg); + if (arg_save == NULL) + return FALSE; + p = vim_strchr(arg_save, '#'); + if (p != NULL) + *p++ = NUL; + + /* First, look for an autocmd group name */ + group = au_find_group(arg_save); + if (group == AUGROUP_ERROR) { + /* Didn't match a group name, assume the first argument is an event. */ + group = AUGROUP_ALL; + event_name = arg_save; + } else { + if (p == NULL) { + /* "Group": group name is present and it's recognized */ + retval = TRUE; + goto theend; + } + + /* Must be "Group#Event" or "Group#Event#pat". */ + event_name = p; + p = vim_strchr(event_name, '#'); + if (p != NULL) + *p++ = NUL; /* "Group#Event#pat" */ + } + + pattern = p; /* "pattern" is NULL when there is no pattern */ + + /* find the index (enum) for the event name */ + event = event_name2nr(event_name, &p); + + /* return FALSE if the event name is not recognized */ + if (event == NUM_EVENTS) + goto theend; + + /* Find the first autocommand for this event. + * If there isn't any, return FALSE; + * If there is one and no pattern given, return TRUE; */ + ap = first_autopat[(int)event]; + if (ap == NULL) + goto theend; + + /* if pattern is "", special handling is needed which uses curbuf */ + /* for pattern ", fnamecmp() will work fine */ + if (pattern != NULL && STRICMP(pattern, "") == 0) + buflocal_buf = curbuf; + + /* Check if there is an autocommand with the given pattern. */ + for (; ap != NULL; ap = ap->next) + /* only use a pattern when it has not been removed and has commands. */ + /* For buffer-local autocommands, fnamecmp() works fine. */ + if (ap->pat != NULL && ap->cmds != NULL + && (group == AUGROUP_ALL || ap->group == group) + && (pattern == NULL + || (buflocal_buf == NULL + ? fnamecmp(ap->pat, pattern) == 0 + : ap->buflocal_nr == buflocal_buf->b_fnum))) { + retval = TRUE; + break; + } + +theend: + vim_free(arg_save); + return retval; +} + + + +/* + * Try matching a filename with a "pattern" ("prog" is NULL), or use the + * precompiled regprog "prog" ("pattern" is NULL). That avoids calling + * vim_regcomp() often. + * Used for autocommands and 'wildignore'. + * Returns TRUE if there is a match, FALSE otherwise. + */ +int match_file_pat(pattern, prog, fname, sfname, tail, allow_dirs) +char_u *pattern; /* pattern to match with */ +regprog_T *prog; /* pre-compiled regprog or NULL */ +char_u *fname; /* full path of file name */ +char_u *sfname; /* short file name or NULL */ +char_u *tail; /* tail of path */ +int allow_dirs; /* allow matching with dir */ +{ + regmatch_T regmatch; + int result = FALSE; +#ifdef FEAT_OSFILETYPE + int no_pattern = FALSE; /* TRUE if check is filetype only */ + char_u *type_start; + char_u c; + int match = FALSE; +#endif + + regmatch.rm_ic = p_fic; /* ignore case if 'fileignorecase' is set */ +#ifdef FEAT_OSFILETYPE + if (*pattern == '<') { + /* There is a filetype condition specified with this pattern. + * Check the filetype matches first. If not, don't bother with the + * pattern (set regprog to NULL). + * Always use magic for the regexp. + */ + + for (type_start = pattern + 1; (c = *pattern); pattern++) { + if ((c == ';' || c == '>') && match == FALSE) { + *pattern = NUL; /* Terminate the string */ + /* TODO: match with 'filetype' of buffer that "fname" comes + * from. */ + match = mch_check_filetype(fname, type_start); + *pattern = c; /* Restore the terminator */ + type_start = pattern + 1; + } + if (c == '>') + break; + } + + /* (c should never be NUL, but check anyway) */ + if (match == FALSE || c == NUL) + regmatch.regprog = NULL; /* Doesn't match - don't check pat. */ + else if (*pattern == NUL) { + regmatch.regprog = NULL; /* Vim will try to free regprog later */ + no_pattern = TRUE; /* Always matches - don't check pat. */ + } else + regmatch.regprog = vim_regcomp(pattern + 1, RE_MAGIC); + } else +#endif + { + if (prog != NULL) + regmatch.regprog = prog; + else + regmatch.regprog = vim_regcomp(pattern, RE_MAGIC); + } + + /* + * Try for a match with the pattern with: + * 1. the full file name, when the pattern has a '/'. + * 2. the short file name, when the pattern has a '/'. + * 3. the tail of the file name, when the pattern has no '/'. + */ + if ( +#ifdef FEAT_OSFILETYPE + /* If the check is for a filetype only and we don't care + * about the path then skip all the regexp stuff. + */ + no_pattern || +#endif + (regmatch.regprog != NULL + && ((allow_dirs + && (vim_regexec(®match, fname, (colnr_T)0) + || (sfname != NULL + && vim_regexec(®match, sfname, (colnr_T)0)))) + || (!allow_dirs && vim_regexec(®match, tail, (colnr_T)0))))) + result = TRUE; + + if (prog == NULL) + vim_regfree(regmatch.regprog); + return result; +} + +/* + * Return TRUE if a file matches with a pattern in "list". + * "list" is a comma-separated list of patterns, like 'wildignore'. + * "sfname" is the short file name or NULL, "ffname" the long file name. + */ +int match_file_list(list, sfname, ffname) +char_u *list; +char_u *sfname; +char_u *ffname; +{ + char_u buf[100]; + char_u *tail; + char_u *regpat; + char allow_dirs; + int match; + char_u *p; + + tail = gettail(sfname); + + /* try all patterns in 'wildignore' */ + p = list; + while (*p) { + copy_option_part(&p, buf, 100, ","); + regpat = file_pat_to_reg_pat(buf, NULL, &allow_dirs, FALSE); + if (regpat == NULL) + break; + match = match_file_pat(regpat, NULL, ffname, sfname, + tail, (int)allow_dirs); + vim_free(regpat); + if (match) + return TRUE; + } + return FALSE; +} + +/* + * Convert the given pattern "pat" which has shell style wildcards in it, into + * a regular expression, and return the result in allocated memory. If there + * is a directory path separator to be matched, then TRUE is put in + * allow_dirs, otherwise FALSE is put there -- webb. + * Handle backslashes before special characters, like "\*" and "\ ". + * + * If FEAT_OSFILETYPE defined then pass initial through unchanged. Eg: + * 'myfile' becomes '^myfile$' -- leonard. + * + * Returns NULL when out of memory. + */ +char_u * file_pat_to_reg_pat(pat, pat_end, allow_dirs, no_bslash) +char_u *pat; +char_u *pat_end; /* first char after pattern or NULL */ +char *allow_dirs; /* Result passed back out in here */ +int no_bslash UNUSED; /* Don't use a backward slash as pathsep */ +{ + int size; + char_u *endp; + char_u *reg_pat; + char_u *p; + int i; + int nested = 0; + int add_dollar = TRUE; +#ifdef FEAT_OSFILETYPE + int check_length = 0; +#endif + + if (allow_dirs != NULL) + *allow_dirs = FALSE; + if (pat_end == NULL) + pat_end = pat + STRLEN(pat); + +#ifdef FEAT_OSFILETYPE + /* Find out how much of the string is the filetype check */ + if (*pat == '<') { + /* Count chars until the next '>' */ + for (p = pat + 1; p < pat_end && *p != '>'; p++) + ; + if (p < pat_end) { + /* Pattern is of the form <.*>.* */ + check_length = p - pat + 1; + if (p + 1 >= pat_end) { + /* The 'pattern' is a filetype check ONLY */ + reg_pat = (char_u *)alloc(check_length + 1); + if (reg_pat != NULL) { + mch_memmove(reg_pat, pat, (size_t)check_length); + reg_pat[check_length] = NUL; + } + return reg_pat; + } + } + /* else: there was no closing '>' - assume it was a normal pattern */ + + } + pat += check_length; + size = 2 + check_length; +#else + size = 2; /* '^' at start, '$' at end */ +#endif + + for (p = pat; p < pat_end; p++) { + switch (*p) { + case '*': + case '.': + case ',': + case '{': + case '}': + case '~': + size += 2; /* extra backslash */ + break; +#ifdef BACKSLASH_IN_FILENAME + case '\\': + case '/': + size += 4; /* could become "[\/]" */ + break; +#endif + default: + size++; + if (enc_dbcs != 0 && (*mb_ptr2len)(p) > 1) { + ++p; + ++size; + } + break; + } + } + reg_pat = alloc(size + 1); + if (reg_pat == NULL) + return NULL; + +#ifdef FEAT_OSFILETYPE + /* Copy the type check in to the start. */ + if (check_length) + mch_memmove(reg_pat, pat - check_length, (size_t)check_length); + i = check_length; +#else + i = 0; +#endif + + if (pat[0] == '*') + while (pat[0] == '*' && pat < pat_end - 1) + pat++; + else + reg_pat[i++] = '^'; + endp = pat_end - 1; + if (*endp == '*') { + while (endp - pat > 0 && *endp == '*') + endp--; + add_dollar = FALSE; + } + for (p = pat; *p && nested >= 0 && p <= endp; p++) { + switch (*p) { + case '*': + reg_pat[i++] = '.'; + reg_pat[i++] = '*'; + while (p[1] == '*') /* "**" matches like "*" */ + ++p; + break; + case '.': + case '~': + reg_pat[i++] = '\\'; + reg_pat[i++] = *p; + break; + case '?': + reg_pat[i++] = '.'; + break; + case '\\': + if (p[1] == NUL) + break; +#ifdef BACKSLASH_IN_FILENAME + if (!no_bslash) { + /* translate: + * "\x" to "\\x" e.g., "dir\file" + * "\*" to "\\.*" e.g., "dir\*.c" + * "\?" to "\\." e.g., "dir\??.c" + * "\+" to "\+" e.g., "fileX\+.c" + */ + if ((vim_isfilec(p[1]) || p[1] == '*' || p[1] == '?') + && p[1] != '+') { + reg_pat[i++] = '['; + reg_pat[i++] = '\\'; + reg_pat[i++] = '/'; + reg_pat[i++] = ']'; + if (allow_dirs != NULL) + *allow_dirs = TRUE; + break; + } + } +#endif + /* Undo escaping from ExpandEscape(): + * foo\?bar -> foo?bar + * foo\%bar -> foo%bar + * foo\,bar -> foo,bar + * foo\ bar -> foo bar + * Don't unescape \, * and others that are also special in a + * regexp. + * An escaped { must be unescaped since we use magic not + * verymagic. Use "\\\{n,m\}"" to get "\{n,m}". + */ + if (*++p == '?' +#ifdef BACKSLASH_IN_FILENAME + && no_bslash +#endif + ) + reg_pat[i++] = '?'; + else if (*p == ',' || *p == '%' || *p == '#' + || *p == ' ' || *p == '{' || *p == '}') + reg_pat[i++] = *p; + else if (*p == '\\' && p[1] == '\\' && p[2] == '{') { + reg_pat[i++] = '\\'; + reg_pat[i++] = '{'; + p += 2; + } else { + if (allow_dirs != NULL && vim_ispathsep(*p) +#ifdef BACKSLASH_IN_FILENAME + && (!no_bslash || *p != '\\') +#endif + ) + *allow_dirs = TRUE; + reg_pat[i++] = '\\'; + reg_pat[i++] = *p; + } + break; +#ifdef BACKSLASH_IN_FILENAME + case '/': + reg_pat[i++] = '['; + reg_pat[i++] = '\\'; + reg_pat[i++] = '/'; + reg_pat[i++] = ']'; + if (allow_dirs != NULL) + *allow_dirs = TRUE; + break; +#endif + case '{': + reg_pat[i++] = '\\'; + reg_pat[i++] = '('; + nested++; + break; + case '}': + reg_pat[i++] = '\\'; + reg_pat[i++] = ')'; + --nested; + break; + case ',': + if (nested) { + reg_pat[i++] = '\\'; + reg_pat[i++] = '|'; + } else + reg_pat[i++] = ','; + break; + default: + if (enc_dbcs != 0 && (*mb_ptr2len)(p) > 1) + reg_pat[i++] = *p++; + else if (allow_dirs != NULL && vim_ispathsep(*p)) + *allow_dirs = TRUE; + reg_pat[i++] = *p; + break; + } + } + if (add_dollar) + reg_pat[i++] = '$'; + reg_pat[i] = NUL; + if (nested != 0) { + if (nested < 0) + EMSG(_("E219: Missing {.")); + else + EMSG(_("E220: Missing }.")); + vim_free(reg_pat); + reg_pat = NULL; + } + return reg_pat; +} + +#if defined(EINTR) || defined(PROTO) +/* + * Version of read() that retries when interrupted by EINTR (possibly + * by a SIGWINCH). + */ +long read_eintr(fd, buf, bufsize) +int fd; +void *buf; +size_t bufsize; +{ + long ret; + + for (;; ) { + ret = vim_read(fd, buf, bufsize); + if (ret >= 0 || errno != EINTR) + break; + } + return ret; +} + +/* + * Version of write() that retries when interrupted by EINTR (possibly + * by a SIGWINCH). + */ +long write_eintr(fd, buf, bufsize) +int fd; +void *buf; +size_t bufsize; +{ + long ret = 0; + long wlen; + + /* Repeat the write() so long it didn't fail, other than being interrupted + * by a signal. */ + while (ret < (long)bufsize) { + wlen = vim_write(fd, (char *)buf + ret, bufsize - ret); + if (wlen < 0) { + if (errno != EINTR) + break; + } else + ret += wlen; + } + return ret; +} +#endif diff --git a/src/fold.c b/src/fold.c new file mode 100644 index 0000000000..b3a3d84a2b --- /dev/null +++ b/src/fold.c @@ -0,0 +1,3104 @@ +/* vim:set ts=8 sts=4 sw=4: + * vim600:fdm=marker fdl=1 fdc=3: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * fold.c: code for folding + */ + +#include "vim.h" + + +/* local declarations. {{{1 */ +/* typedef fold_T {{{2 */ +/* + * The toplevel folds for each window are stored in the w_folds growarray. + * Each toplevel fold can contain an array of second level folds in the + * fd_nested growarray. + * The info stored in both growarrays is the same: An array of fold_T. + */ +typedef struct { + linenr_T fd_top; /* first line of fold; for nested fold + * relative to parent */ + linenr_T fd_len; /* number of lines in the fold */ + garray_T fd_nested; /* array of nested folds */ + char fd_flags; /* see below */ + char fd_small; /* TRUE, FALSE or MAYBE: fold smaller than + 'foldminlines'; MAYBE applies to nested + folds too */ +} fold_T; + +#define FD_OPEN 0 /* fold is open (nested ones can be closed) */ +#define FD_CLOSED 1 /* fold is closed */ +#define FD_LEVEL 2 /* depends on 'foldlevel' (nested folds too) */ + +#define MAX_LEVEL 20 /* maximum fold depth */ + +/* static functions {{{2 */ +static void newFoldLevelWin __ARGS((win_T *wp)); +static int checkCloseRec __ARGS((garray_T *gap, linenr_T lnum, int level)); +static int foldFind __ARGS((garray_T *gap, linenr_T lnum, fold_T **fpp)); +static int foldLevelWin __ARGS((win_T *wp, linenr_T lnum)); +static void checkupdate __ARGS((win_T *wp)); +static void setFoldRepeat __ARGS((linenr_T lnum, long count, int do_open)); +static linenr_T setManualFold __ARGS((linenr_T lnum, int opening, int recurse, + int *donep)); +static linenr_T setManualFoldWin __ARGS((win_T *wp, linenr_T lnum, int opening, + int recurse, + int *donep)); +static void foldOpenNested __ARGS((fold_T *fpr)); +static void deleteFoldEntry __ARGS((garray_T *gap, int idx, int recursive)); +static void foldMarkAdjustRecurse __ARGS((garray_T *gap, linenr_T line1, + linenr_T line2, long amount, + long amount_after)); +static int getDeepestNestingRecurse __ARGS((garray_T *gap)); +static int check_closed __ARGS((win_T *win, fold_T *fp, int *use_levelp, + int level, int *maybe_smallp, + linenr_T lnum_off)); +static void checkSmall __ARGS((win_T *wp, fold_T *fp, linenr_T lnum_off)); +static void setSmallMaybe __ARGS((garray_T *gap)); +static void foldCreateMarkers __ARGS((linenr_T start, linenr_T end)); +static void foldAddMarker __ARGS((linenr_T lnum, char_u *marker, int markerlen)); +static void deleteFoldMarkers __ARGS((fold_T *fp, int recursive, + linenr_T lnum_off)); +static void foldDelMarker __ARGS((linenr_T lnum, char_u *marker, int markerlen)); +static void foldUpdateIEMS __ARGS((win_T *wp, linenr_T top, linenr_T bot)); +static void parseMarker __ARGS((win_T *wp)); + +static char *e_nofold = N_("E490: No fold found"); + +/* + * While updating the folds lines between invalid_top and invalid_bot have an + * undefined fold level. Only used for the window currently being updated. + */ +static linenr_T invalid_top = (linenr_T)0; +static linenr_T invalid_bot = (linenr_T)0; + +/* + * When using 'foldexpr' we sometimes get the level of the next line, which + * calls foldlevel() to get the level of the current line, which hasn't been + * stored yet. To get around this chicken-egg problem the level of the + * previous line is stored here when available. prev_lnum is zero when the + * level is not available. + */ +static linenr_T prev_lnum = 0; +static int prev_lnum_lvl = -1; + +/* Flags used for "done" argument of setManualFold. */ +#define DONE_NOTHING 0 +#define DONE_ACTION 1 /* did close or open a fold */ +#define DONE_FOLD 2 /* did find a fold */ + +static int foldstartmarkerlen; +static char_u *foldendmarker; +static int foldendmarkerlen; + +/* Exported folding functions. {{{1 */ +/* copyFoldingState() {{{2 */ +/* + * Copy that folding state from window "wp_from" to window "wp_to". + */ +void copyFoldingState(wp_from, wp_to) +win_T *wp_from; +win_T *wp_to; +{ + wp_to->w_fold_manual = wp_from->w_fold_manual; + wp_to->w_foldinvalid = wp_from->w_foldinvalid; + cloneFoldGrowArray(&wp_from->w_folds, &wp_to->w_folds); +} + +/* hasAnyFolding() {{{2 */ +/* + * Return TRUE if there may be folded lines in the current window. + */ +int hasAnyFolding(win) +win_T *win; +{ + /* very simple now, but can become more complex later */ + return win->w_p_fen + && (!foldmethodIsManual(win) || win->w_folds.ga_len > 0); +} + +/* hasFolding() {{{2 */ +/* + * Return TRUE if line "lnum" in the current window is part of a closed + * fold. + * When returning TRUE, *firstp and *lastp are set to the first and last + * lnum of the sequence of folded lines (skipped when NULL). + */ +int hasFolding(lnum, firstp, lastp) +linenr_T lnum; +linenr_T *firstp; +linenr_T *lastp; +{ + return hasFoldingWin(curwin, lnum, firstp, lastp, TRUE, NULL); +} + +/* hasFoldingWin() {{{2 */ +int hasFoldingWin(win, lnum, firstp, lastp, cache, infop) +win_T *win; +linenr_T lnum; +linenr_T *firstp; +linenr_T *lastp; +int cache; /* when TRUE: use cached values of window */ +foldinfo_T *infop; /* where to store fold info */ +{ + int had_folded = FALSE; + linenr_T first = 0; + linenr_T last = 0; + linenr_T lnum_rel = lnum; + int x; + fold_T *fp; + int level = 0; + int use_level = FALSE; + int maybe_small = FALSE; + garray_T *gap; + int low_level = 0;; + + checkupdate(win); + /* + * Return quickly when there is no folding at all in this window. + */ + if (!hasAnyFolding(win)) { + if (infop != NULL) + infop->fi_level = 0; + return FALSE; + } + + if (cache) { + /* + * First look in cached info for displayed lines. This is probably + * the fastest, but it can only be used if the entry is still valid. + */ + x = find_wl_entry(win, lnum); + if (x >= 0) { + first = win->w_lines[x].wl_lnum; + last = win->w_lines[x].wl_lastlnum; + had_folded = win->w_lines[x].wl_folded; + } + } + + if (first == 0) { + /* + * Recursively search for a fold that contains "lnum". + */ + gap = &win->w_folds; + for (;; ) { + if (!foldFind(gap, lnum_rel, &fp)) + break; + + /* Remember lowest level of fold that starts in "lnum". */ + if (lnum_rel == fp->fd_top && low_level == 0) + low_level = level + 1; + + first += fp->fd_top; + last += fp->fd_top; + + /* is this fold closed? */ + had_folded = check_closed(win, fp, &use_level, level, + &maybe_small, lnum - lnum_rel); + if (had_folded) { + /* Fold closed: Set last and quit loop. */ + last += fp->fd_len - 1; + break; + } + + /* Fold found, but it's open: Check nested folds. Line number is + * relative to containing fold. */ + gap = &fp->fd_nested; + lnum_rel -= fp->fd_top; + ++level; + } + } + + if (!had_folded) { + if (infop != NULL) { + infop->fi_level = level; + infop->fi_lnum = lnum - lnum_rel; + infop->fi_low_level = low_level == 0 ? level : low_level; + } + return FALSE; + } + + if (lastp != NULL) + *lastp = last; + if (firstp != NULL) + *firstp = first; + if (infop != NULL) { + infop->fi_level = level + 1; + infop->fi_lnum = first; + infop->fi_low_level = low_level == 0 ? level + 1 : low_level; + } + return TRUE; +} + +/* foldLevel() {{{2 */ +/* + * Return fold level at line number "lnum" in the current window. + */ +int foldLevel(lnum) +linenr_T lnum; +{ + /* While updating the folds lines between invalid_top and invalid_bot have + * an undefined fold level. Otherwise update the folds first. */ + if (invalid_top == (linenr_T)0) + checkupdate(curwin); + else if (lnum == prev_lnum && prev_lnum_lvl >= 0) + return prev_lnum_lvl; + else if (lnum >= invalid_top && lnum <= invalid_bot) + return -1; + + /* Return quickly when there is no folding at all in this window. */ + if (!hasAnyFolding(curwin)) + return 0; + + return foldLevelWin(curwin, lnum); +} + +/* lineFolded() {{{2 */ +/* + * Low level function to check if a line is folded. Doesn't use any caching. + * Return TRUE if line is folded. + * Return FALSE if line is not folded. + * Return MAYBE if the line is folded when next to a folded line. + */ +int lineFolded(win, lnum) +win_T *win; +linenr_T lnum; +{ + return foldedCount(win, lnum, NULL) != 0; +} + +/* foldedCount() {{{2 */ +/* + * Count the number of lines that are folded at line number "lnum". + * Normally "lnum" is the first line of a possible fold, and the returned + * number is the number of lines in the fold. + * Doesn't use caching from the displayed window. + * Returns number of folded lines from "lnum", or 0 if line is not folded. + * When "infop" is not NULL, fills *infop with the fold level info. + */ +long foldedCount(win, lnum, infop) +win_T *win; +linenr_T lnum; +foldinfo_T *infop; +{ + linenr_T last; + + if (hasFoldingWin(win, lnum, NULL, &last, FALSE, infop)) + return (long)(last - lnum + 1); + return 0; +} + +/* foldmethodIsManual() {{{2 */ +/* + * Return TRUE if 'foldmethod' is "manual" + */ +int foldmethodIsManual(wp) +win_T *wp; +{ + return wp->w_p_fdm[3] == 'u'; +} + +/* foldmethodIsIndent() {{{2 */ +/* + * Return TRUE if 'foldmethod' is "indent" + */ +int foldmethodIsIndent(wp) +win_T *wp; +{ + return wp->w_p_fdm[0] == 'i'; +} + +/* foldmethodIsExpr() {{{2 */ +/* + * Return TRUE if 'foldmethod' is "expr" + */ +int foldmethodIsExpr(wp) +win_T *wp; +{ + return wp->w_p_fdm[1] == 'x'; +} + +/* foldmethodIsMarker() {{{2 */ +/* + * Return TRUE if 'foldmethod' is "marker" + */ +int foldmethodIsMarker(wp) +win_T *wp; +{ + return wp->w_p_fdm[2] == 'r'; +} + +/* foldmethodIsSyntax() {{{2 */ +/* + * Return TRUE if 'foldmethod' is "syntax" + */ +int foldmethodIsSyntax(wp) +win_T *wp; +{ + return wp->w_p_fdm[0] == 's'; +} + +/* foldmethodIsDiff() {{{2 */ +/* + * Return TRUE if 'foldmethod' is "diff" + */ +int foldmethodIsDiff(wp) +win_T *wp; +{ + return wp->w_p_fdm[0] == 'd'; +} + +/* closeFold() {{{2 */ +/* + * Close fold for current window at line "lnum". + * Repeat "count" times. + */ +void closeFold(lnum, count) +linenr_T lnum; +long count; +{ + setFoldRepeat(lnum, count, FALSE); +} + +/* closeFoldRecurse() {{{2 */ +/* + * Close fold for current window at line "lnum" recursively. + */ +void closeFoldRecurse(lnum) +linenr_T lnum; +{ + (void)setManualFold(lnum, FALSE, TRUE, NULL); +} + +/* opFoldRange() {{{2 */ +/* + * Open or Close folds for current window in lines "first" to "last". + * Used for "zo", "zO", "zc" and "zC" in Visual mode. + */ +void opFoldRange(first, last, opening, recurse, had_visual) +linenr_T first; +linenr_T last; +int opening; /* TRUE to open, FALSE to close */ +int recurse; /* TRUE to do it recursively */ +int had_visual; /* TRUE when Visual selection used */ +{ + int done = DONE_NOTHING; /* avoid error messages */ + linenr_T lnum; + linenr_T lnum_next; + + for (lnum = first; lnum <= last; lnum = lnum_next + 1) { + lnum_next = lnum; + /* Opening one level only: next fold to open is after the one going to + * be opened. */ + if (opening && !recurse) + (void)hasFolding(lnum, NULL, &lnum_next); + (void)setManualFold(lnum, opening, recurse, &done); + /* Closing one level only: next line to close a fold is after just + * closed fold. */ + if (!opening && !recurse) + (void)hasFolding(lnum, NULL, &lnum_next); + } + if (done == DONE_NOTHING) + EMSG(_(e_nofold)); + /* Force a redraw to remove the Visual highlighting. */ + if (had_visual) + redraw_curbuf_later(INVERTED); +} + +/* openFold() {{{2 */ +/* + * Open fold for current window at line "lnum". + * Repeat "count" times. + */ +void openFold(lnum, count) +linenr_T lnum; +long count; +{ + setFoldRepeat(lnum, count, TRUE); +} + +/* openFoldRecurse() {{{2 */ +/* + * Open fold for current window at line "lnum" recursively. + */ +void openFoldRecurse(lnum) +linenr_T lnum; +{ + (void)setManualFold(lnum, TRUE, TRUE, NULL); +} + +/* foldOpenCursor() {{{2 */ +/* + * Open folds until the cursor line is not in a closed fold. + */ +void foldOpenCursor() { + int done; + + checkupdate(curwin); + if (hasAnyFolding(curwin)) + for (;; ) { + done = DONE_NOTHING; + (void)setManualFold(curwin->w_cursor.lnum, TRUE, FALSE, &done); + if (!(done & DONE_ACTION)) + break; + } +} + +/* newFoldLevel() {{{2 */ +/* + * Set new foldlevel for current window. + */ +void newFoldLevel() { + newFoldLevelWin(curwin); + + if (foldmethodIsDiff(curwin) && curwin->w_p_scb) { + win_T *wp; + + /* + * Set the same foldlevel in other windows in diff mode. + */ + FOR_ALL_WINDOWS(wp) + { + if (wp != curwin && foldmethodIsDiff(wp) && wp->w_p_scb) { + wp->w_p_fdl = curwin->w_p_fdl; + newFoldLevelWin(wp); + } + } + } +} + +static void newFoldLevelWin(wp) +win_T *wp; +{ + fold_T *fp; + int i; + + checkupdate(wp); + if (wp->w_fold_manual) { + /* Set all flags for the first level of folds to FD_LEVEL. Following + * manual open/close will then change the flags to FD_OPEN or + * FD_CLOSED for those folds that don't use 'foldlevel'. */ + fp = (fold_T *)wp->w_folds.ga_data; + for (i = 0; i < wp->w_folds.ga_len; ++i) + fp[i].fd_flags = FD_LEVEL; + wp->w_fold_manual = FALSE; + } + changed_window_setting_win(wp); +} + +/* foldCheckClose() {{{2 */ +/* + * Apply 'foldlevel' to all folds that don't contain the cursor. + */ +void foldCheckClose() { + if (*p_fcl != NUL) { /* can only be "all" right now */ + checkupdate(curwin); + if (checkCloseRec(&curwin->w_folds, curwin->w_cursor.lnum, + (int)curwin->w_p_fdl)) + changed_window_setting(); + } +} + +/* checkCloseRec() {{{2 */ +static int checkCloseRec(gap, lnum, level) +garray_T *gap; +linenr_T lnum; +int level; +{ + fold_T *fp; + int retval = FALSE; + int i; + + fp = (fold_T *)gap->ga_data; + for (i = 0; i < gap->ga_len; ++i) { + /* Only manually opened folds may need to be closed. */ + if (fp[i].fd_flags == FD_OPEN) { + if (level <= 0 && (lnum < fp[i].fd_top + || lnum >= fp[i].fd_top + fp[i].fd_len)) { + fp[i].fd_flags = FD_LEVEL; + retval = TRUE; + } else + retval |= checkCloseRec(&fp[i].fd_nested, lnum - fp[i].fd_top, + level - 1); + } + } + return retval; +} + +/* foldCreateAllowed() {{{2 */ +/* + * Return TRUE if it's allowed to manually create or delete a fold. + * Give an error message and return FALSE if not. + */ +int foldManualAllowed(create) +int create; +{ + if (foldmethodIsManual(curwin) || foldmethodIsMarker(curwin)) + return TRUE; + if (create) + EMSG(_("E350: Cannot create fold with current 'foldmethod'")); + else + EMSG(_("E351: Cannot delete fold with current 'foldmethod'")); + return FALSE; +} + +/* foldCreate() {{{2 */ +/* + * Create a fold from line "start" to line "end" (inclusive) in the current + * window. + */ +void foldCreate(start, end) +linenr_T start; +linenr_T end; +{ + fold_T *fp; + garray_T *gap; + garray_T fold_ga; + int i, j; + int cont; + int use_level = FALSE; + int closed = FALSE; + int level = 0; + linenr_T start_rel = start; + linenr_T end_rel = end; + + if (start > end) { + /* reverse the range */ + end = start_rel; + start = end_rel; + start_rel = start; + end_rel = end; + } + + /* When 'foldmethod' is "marker" add markers, which creates the folds. */ + if (foldmethodIsMarker(curwin)) { + foldCreateMarkers(start, end); + return; + } + + checkupdate(curwin); + + /* Find the place to insert the new fold. */ + gap = &curwin->w_folds; + for (;; ) { + if (!foldFind(gap, start_rel, &fp)) + break; + if (fp->fd_top + fp->fd_len > end_rel) { + /* New fold is completely inside this fold: Go one level deeper. */ + gap = &fp->fd_nested; + start_rel -= fp->fd_top; + end_rel -= fp->fd_top; + if (use_level || fp->fd_flags == FD_LEVEL) { + use_level = TRUE; + if (level >= curwin->w_p_fdl) + closed = TRUE; + } else if (fp->fd_flags == FD_CLOSED) + closed = TRUE; + ++level; + } else { + /* This fold and new fold overlap: Insert here and move some folds + * inside the new fold. */ + break; + } + } + + i = (int)(fp - (fold_T *)gap->ga_data); + if (ga_grow(gap, 1) == OK) { + fp = (fold_T *)gap->ga_data + i; + ga_init2(&fold_ga, (int)sizeof(fold_T), 10); + + /* Count number of folds that will be contained in the new fold. */ + for (cont = 0; i + cont < gap->ga_len; ++cont) + if (fp[cont].fd_top > end_rel) + break; + if (cont > 0 && ga_grow(&fold_ga, cont) == OK) { + /* If the first fold starts before the new fold, let the new fold + * start there. Otherwise the existing fold would change. */ + if (start_rel > fp->fd_top) + start_rel = fp->fd_top; + + /* When last contained fold isn't completely contained, adjust end + * of new fold. */ + if (end_rel < fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1) + end_rel = fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1; + /* Move contained folds to inside new fold. */ + mch_memmove(fold_ga.ga_data, fp, sizeof(fold_T) * cont); + fold_ga.ga_len += cont; + i += cont; + + /* Adjust line numbers in contained folds to be relative to the + * new fold. */ + for (j = 0; j < cont; ++j) + ((fold_T *)fold_ga.ga_data)[j].fd_top -= start_rel; + } + /* Move remaining entries to after the new fold. */ + if (i < gap->ga_len) + mch_memmove(fp + 1, (fold_T *)gap->ga_data + i, + sizeof(fold_T) * (gap->ga_len - i)); + gap->ga_len = gap->ga_len + 1 - cont; + + /* insert new fold */ + fp->fd_nested = fold_ga; + fp->fd_top = start_rel; + fp->fd_len = end_rel - start_rel + 1; + + /* We want the new fold to be closed. If it would remain open because + * of using 'foldlevel', need to adjust fd_flags of containing folds. + */ + if (use_level && !closed && level < curwin->w_p_fdl) + closeFold(start, 1L); + if (!use_level) + curwin->w_fold_manual = TRUE; + fp->fd_flags = FD_CLOSED; + fp->fd_small = MAYBE; + + /* redraw */ + changed_window_setting(); + } +} + +/* deleteFold() {{{2 */ +/* + * Delete a fold at line "start" in the current window. + * When "end" is not 0, delete all folds from "start" to "end". + * When "recursive" is TRUE delete recursively. + */ +void deleteFold(start, end, recursive, had_visual) +linenr_T start; +linenr_T end; +int recursive; +int had_visual; /* TRUE when Visual selection used */ +{ + garray_T *gap; + fold_T *fp; + garray_T *found_ga; + fold_T *found_fp = NULL; + linenr_T found_off = 0; + int use_level; + int maybe_small = FALSE; + int level = 0; + linenr_T lnum = start; + linenr_T lnum_off; + int did_one = FALSE; + linenr_T first_lnum = MAXLNUM; + linenr_T last_lnum = 0; + + checkupdate(curwin); + + while (lnum <= end) { + /* Find the deepest fold for "start". */ + gap = &curwin->w_folds; + found_ga = NULL; + lnum_off = 0; + use_level = FALSE; + for (;; ) { + if (!foldFind(gap, lnum - lnum_off, &fp)) + break; + /* lnum is inside this fold, remember info */ + found_ga = gap; + found_fp = fp; + found_off = lnum_off; + + /* if "lnum" is folded, don't check nesting */ + if (check_closed(curwin, fp, &use_level, level, + &maybe_small, lnum_off)) + break; + + /* check nested folds */ + gap = &fp->fd_nested; + lnum_off += fp->fd_top; + ++level; + } + if (found_ga == NULL) { + ++lnum; + } else { + lnum = found_fp->fd_top + found_fp->fd_len + found_off; + + if (foldmethodIsManual(curwin)) + deleteFoldEntry(found_ga, + (int)(found_fp - (fold_T *)found_ga->ga_data), recursive); + else { + if (first_lnum > found_fp->fd_top + found_off) + first_lnum = found_fp->fd_top + found_off; + if (last_lnum < lnum) + last_lnum = lnum; + if (!did_one) + parseMarker(curwin); + deleteFoldMarkers(found_fp, recursive, found_off); + } + did_one = TRUE; + + /* redraw window */ + changed_window_setting(); + } + } + if (!did_one) { + EMSG(_(e_nofold)); + /* Force a redraw to remove the Visual highlighting. */ + if (had_visual) + redraw_curbuf_later(INVERTED); + } else + /* Deleting markers may make cursor column invalid. */ + check_cursor_col(); + + if (last_lnum > 0) + changed_lines(first_lnum, (colnr_T)0, last_lnum, 0L); +} + +/* clearFolding() {{{2 */ +/* + * Remove all folding for window "win". + */ +void clearFolding(win) +win_T *win; +{ + deleteFoldRecurse(&win->w_folds); + win->w_foldinvalid = FALSE; +} + +/* foldUpdate() {{{2 */ +/* + * Update folds for changes in the buffer of a window. + * Note that inserted/deleted lines must have already been taken care of by + * calling foldMarkAdjust(). + * The changes in lines from top to bot (inclusive). + */ +void foldUpdate(wp, top, bot) +win_T *wp; +linenr_T top; +linenr_T bot; +{ + fold_T *fp; + + /* Mark all folds from top to bot as maybe-small. */ + (void)foldFind(&curwin->w_folds, top, &fp); + while (fp < (fold_T *)curwin->w_folds.ga_data + curwin->w_folds.ga_len + && fp->fd_top < bot) { + fp->fd_small = MAYBE; + ++fp; + } + + if (foldmethodIsIndent(wp) + || foldmethodIsExpr(wp) + || foldmethodIsMarker(wp) + || foldmethodIsDiff(wp) + || foldmethodIsSyntax(wp)) { + int save_got_int = got_int; + + /* reset got_int here, otherwise it won't work */ + got_int = FALSE; + foldUpdateIEMS(wp, top, bot); + got_int |= save_got_int; + } +} + +/* foldUpdateAll() {{{2 */ +/* + * Update all lines in a window for folding. + * Used when a fold setting changes or after reloading the buffer. + * The actual updating is postponed until fold info is used, to avoid doing + * every time a setting is changed or a syntax item is added. + */ +void foldUpdateAll(win) +win_T *win; +{ + win->w_foldinvalid = TRUE; + redraw_win_later(win, NOT_VALID); +} + +/* foldMoveTo() {{{2 */ +/* + * If "updown" is FALSE: Move to the start or end of the fold. + * If "updown" is TRUE: move to fold at the same level. + * If not moved return FAIL. + */ +int foldMoveTo(updown, dir, count) +int updown; +int dir; /* FORWARD or BACKWARD */ +long count; +{ + long n; + int retval = FAIL; + linenr_T lnum_off; + linenr_T lnum_found; + linenr_T lnum; + int use_level; + int maybe_small; + garray_T *gap; + fold_T *fp; + int level; + int last; + + checkupdate(curwin); + + /* Repeat "count" times. */ + for (n = 0; n < count; ++n) { + /* Find nested folds. Stop when a fold is closed. The deepest fold + * that moves the cursor is used. */ + lnum_off = 0; + gap = &curwin->w_folds; + use_level = FALSE; + maybe_small = FALSE; + lnum_found = curwin->w_cursor.lnum; + level = 0; + last = FALSE; + for (;; ) { + if (!foldFind(gap, curwin->w_cursor.lnum - lnum_off, &fp)) { + if (!updown) + break; + + /* When moving up, consider a fold above the cursor; when + * moving down consider a fold below the cursor. */ + if (dir == FORWARD) { + if (fp - (fold_T *)gap->ga_data >= gap->ga_len) + break; + --fp; + } else { + if (fp == (fold_T *)gap->ga_data) + break; + } + /* don't look for contained folds, they will always move + * the cursor too far. */ + last = TRUE; + } + + if (!last) { + /* Check if this fold is closed. */ + if (check_closed(curwin, fp, &use_level, level, + &maybe_small, lnum_off)) + last = TRUE; + + /* "[z" and "]z" stop at closed fold */ + if (last && !updown) + break; + } + + if (updown) { + if (dir == FORWARD) { + /* to start of next fold if there is one */ + if (fp + 1 - (fold_T *)gap->ga_data < gap->ga_len) { + lnum = fp[1].fd_top + lnum_off; + if (lnum > curwin->w_cursor.lnum) + lnum_found = lnum; + } + } else { + /* to end of previous fold if there is one */ + if (fp > (fold_T *)gap->ga_data) { + lnum = fp[-1].fd_top + lnum_off + fp[-1].fd_len - 1; + if (lnum < curwin->w_cursor.lnum) + lnum_found = lnum; + } + } + } else { + /* Open fold found, set cursor to its start/end and then check + * nested folds. */ + if (dir == FORWARD) { + lnum = fp->fd_top + lnum_off + fp->fd_len - 1; + if (lnum > curwin->w_cursor.lnum) + lnum_found = lnum; + } else { + lnum = fp->fd_top + lnum_off; + if (lnum < curwin->w_cursor.lnum) + lnum_found = lnum; + } + } + + if (last) + break; + + /* Check nested folds (if any). */ + gap = &fp->fd_nested; + lnum_off += fp->fd_top; + ++level; + } + if (lnum_found != curwin->w_cursor.lnum) { + if (retval == FAIL) + setpcmark(); + curwin->w_cursor.lnum = lnum_found; + curwin->w_cursor.col = 0; + retval = OK; + } else + break; + } + + return retval; +} + +/* foldInitWin() {{{2 */ +/* + * Init the fold info in a new window. + */ +void foldInitWin(new_win) +win_T *new_win; +{ + ga_init2(&new_win->w_folds, (int)sizeof(fold_T), 10); +} + +/* find_wl_entry() {{{2 */ +/* + * Find an entry in the win->w_lines[] array for buffer line "lnum". + * Only valid entries are considered (for entries where wl_valid is FALSE the + * line number can be wrong). + * Returns index of entry or -1 if not found. + */ +int find_wl_entry(win, lnum) +win_T *win; +linenr_T lnum; +{ + int i; + + for (i = 0; i < win->w_lines_valid; ++i) + if (win->w_lines[i].wl_valid) { + if (lnum < win->w_lines[i].wl_lnum) + return -1; + if (lnum <= win->w_lines[i].wl_lastlnum) + return i; + } + return -1; +} + +/* foldAdjustVisual() {{{2 */ +/* + * Adjust the Visual area to include any fold at the start or end completely. + */ +void foldAdjustVisual() { + pos_T *start, *end; + char_u *ptr; + + if (!VIsual_active || !hasAnyFolding(curwin)) + return; + + if (ltoreq(VIsual, curwin->w_cursor)) { + start = &VIsual; + end = &curwin->w_cursor; + } else { + start = &curwin->w_cursor; + end = &VIsual; + } + if (hasFolding(start->lnum, &start->lnum, NULL)) + start->col = 0; + if (hasFolding(end->lnum, NULL, &end->lnum)) { + ptr = ml_get(end->lnum); + end->col = (colnr_T)STRLEN(ptr); + if (end->col > 0 && *p_sel == 'o') + --end->col; + /* prevent cursor from moving on the trail byte */ + if (has_mbyte) + mb_adjust_cursor(); + } +} + +/* cursor_foldstart() {{{2 */ +/* + * Move the cursor to the first line of a closed fold. + */ +void foldAdjustCursor() { + (void)hasFolding(curwin->w_cursor.lnum, &curwin->w_cursor.lnum, NULL); +} + +/* Internal functions for "fold_T" {{{1 */ +/* cloneFoldGrowArray() {{{2 */ +/* + * Will "clone" (i.e deep copy) a garray_T of folds. + * + * Return FAIL if the operation cannot be completed, otherwise OK. + */ +void cloneFoldGrowArray(from, to) +garray_T *from; +garray_T *to; +{ + int i; + fold_T *from_p; + fold_T *to_p; + + ga_init2(to, from->ga_itemsize, from->ga_growsize); + if (from->ga_len == 0 || ga_grow(to, from->ga_len) == FAIL) + return; + + from_p = (fold_T *)from->ga_data; + to_p = (fold_T *)to->ga_data; + + for (i = 0; i < from->ga_len; i++) { + to_p->fd_top = from_p->fd_top; + to_p->fd_len = from_p->fd_len; + to_p->fd_flags = from_p->fd_flags; + to_p->fd_small = from_p->fd_small; + cloneFoldGrowArray(&from_p->fd_nested, &to_p->fd_nested); + ++to->ga_len; + ++from_p; + ++to_p; + } +} + +/* foldFind() {{{2 */ +/* + * Search for line "lnum" in folds of growarray "gap". + * Set *fpp to the fold struct for the fold that contains "lnum" or + * the first fold below it (careful: it can be beyond the end of the array!). + * Returns FALSE when there is no fold that contains "lnum". + */ +static int foldFind(gap, lnum, fpp) +garray_T *gap; +linenr_T lnum; +fold_T **fpp; +{ + linenr_T low, high; + fold_T *fp; + int i; + + /* + * Perform a binary search. + * "low" is lowest index of possible match. + * "high" is highest index of possible match. + */ + fp = (fold_T *)gap->ga_data; + low = 0; + high = gap->ga_len - 1; + while (low <= high) { + i = (low + high) / 2; + if (fp[i].fd_top > lnum) + /* fold below lnum, adjust high */ + high = i - 1; + else if (fp[i].fd_top + fp[i].fd_len <= lnum) + /* fold above lnum, adjust low */ + low = i + 1; + else { + /* lnum is inside this fold */ + *fpp = fp + i; + return TRUE; + } + } + *fpp = fp + low; + return FALSE; +} + +/* foldLevelWin() {{{2 */ +/* + * Return fold level at line number "lnum" in window "wp". + */ +static int foldLevelWin(wp, lnum) +win_T *wp; +linenr_T lnum; +{ + fold_T *fp; + linenr_T lnum_rel = lnum; + int level = 0; + garray_T *gap; + + /* Recursively search for a fold that contains "lnum". */ + gap = &wp->w_folds; + for (;; ) { + if (!foldFind(gap, lnum_rel, &fp)) + break; + /* Check nested folds. Line number is relative to containing fold. */ + gap = &fp->fd_nested; + lnum_rel -= fp->fd_top; + ++level; + } + + return level; +} + +/* checkupdate() {{{2 */ +/* + * Check if the folds in window "wp" are invalid and update them if needed. + */ +static void checkupdate(wp) +win_T *wp; +{ + if (wp->w_foldinvalid) { + foldUpdate(wp, (linenr_T)1, (linenr_T)MAXLNUM); /* will update all */ + wp->w_foldinvalid = FALSE; + } +} + +/* setFoldRepeat() {{{2 */ +/* + * Open or close fold for current window at line "lnum". + * Repeat "count" times. + */ +static void setFoldRepeat(lnum, count, do_open) +linenr_T lnum; +long count; +int do_open; +{ + int done; + long n; + + for (n = 0; n < count; ++n) { + done = DONE_NOTHING; + (void)setManualFold(lnum, do_open, FALSE, &done); + if (!(done & DONE_ACTION)) { + /* Only give an error message when no fold could be opened. */ + if (n == 0 && !(done & DONE_FOLD)) + EMSG(_(e_nofold)); + break; + } + } +} + +/* setManualFold() {{{2 */ +/* + * Open or close the fold in the current window which contains "lnum". + * Also does this for other windows in diff mode when needed. + */ +static linenr_T setManualFold(lnum, opening, recurse, donep) +linenr_T lnum; +int opening; /* TRUE when opening, FALSE when closing */ +int recurse; /* TRUE when closing/opening recursive */ +int *donep; +{ + if (foldmethodIsDiff(curwin) && curwin->w_p_scb) { + win_T *wp; + linenr_T dlnum; + + /* + * Do the same operation in other windows in diff mode. Calculate the + * line number from the diffs. + */ + FOR_ALL_WINDOWS(wp) + { + if (wp != curwin && foldmethodIsDiff(wp) && wp->w_p_scb) { + dlnum = diff_lnum_win(curwin->w_cursor.lnum, wp); + if (dlnum != 0) + (void)setManualFoldWin(wp, dlnum, opening, recurse, NULL); + } + } + } + + return setManualFoldWin(curwin, lnum, opening, recurse, donep); +} + +/* setManualFoldWin() {{{2 */ +/* + * Open or close the fold in window "wp" which contains "lnum". + * "donep", when not NULL, points to flag that is set to DONE_FOLD when some + * fold was found and to DONE_ACTION when some fold was opened or closed. + * When "donep" is NULL give an error message when no fold was found for + * "lnum", but only if "wp" is "curwin". + * Return the line number of the next line that could be closed. + * It's only valid when "opening" is TRUE! + */ +static linenr_T setManualFoldWin(wp, lnum, opening, recurse, donep) +win_T *wp; +linenr_T lnum; +int opening; /* TRUE when opening, FALSE when closing */ +int recurse; /* TRUE when closing/opening recursive */ +int *donep; +{ + fold_T *fp; + fold_T *fp2; + fold_T *found = NULL; + int j; + int level = 0; + int use_level = FALSE; + int found_fold = FALSE; + garray_T *gap; + linenr_T next = MAXLNUM; + linenr_T off = 0; + int done = 0; + + checkupdate(wp); + + /* + * Find the fold, open or close it. + */ + gap = &wp->w_folds; + for (;; ) { + if (!foldFind(gap, lnum, &fp)) { + /* If there is a following fold, continue there next time. */ + if (fp < (fold_T *)gap->ga_data + gap->ga_len) + next = fp->fd_top + off; + break; + } + + /* lnum is inside this fold */ + found_fold = TRUE; + + /* If there is a following fold, continue there next time. */ + if (fp + 1 < (fold_T *)gap->ga_data + gap->ga_len) + next = fp[1].fd_top + off; + + /* Change from level-dependent folding to manual. */ + if (use_level || fp->fd_flags == FD_LEVEL) { + use_level = TRUE; + if (level >= wp->w_p_fdl) + fp->fd_flags = FD_CLOSED; + else + fp->fd_flags = FD_OPEN; + fp2 = (fold_T *)fp->fd_nested.ga_data; + for (j = 0; j < fp->fd_nested.ga_len; ++j) + fp2[j].fd_flags = FD_LEVEL; + } + + /* Simple case: Close recursively means closing the fold. */ + if (!opening && recurse) { + if (fp->fd_flags != FD_CLOSED) { + done |= DONE_ACTION; + fp->fd_flags = FD_CLOSED; + } + } else if (fp->fd_flags == FD_CLOSED) { + /* When opening, open topmost closed fold. */ + if (opening) { + fp->fd_flags = FD_OPEN; + done |= DONE_ACTION; + if (recurse) + foldOpenNested(fp); + } + break; + } + + /* fold is open, check nested folds */ + found = fp; + gap = &fp->fd_nested; + lnum -= fp->fd_top; + off += fp->fd_top; + ++level; + } + if (found_fold) { + /* When closing and not recurse, close deepest open fold. */ + if (!opening && found != NULL) { + found->fd_flags = FD_CLOSED; + done |= DONE_ACTION; + } + wp->w_fold_manual = TRUE; + if (done & DONE_ACTION) + changed_window_setting_win(wp); + done |= DONE_FOLD; + } else if (donep == NULL && wp == curwin) + EMSG(_(e_nofold)); + + if (donep != NULL) + *donep |= done; + + return next; +} + +/* foldOpenNested() {{{2 */ +/* + * Open all nested folds in fold "fpr" recursively. + */ +static void foldOpenNested(fpr) +fold_T *fpr; +{ + int i; + fold_T *fp; + + fp = (fold_T *)fpr->fd_nested.ga_data; + for (i = 0; i < fpr->fd_nested.ga_len; ++i) { + foldOpenNested(&fp[i]); + fp[i].fd_flags = FD_OPEN; + } +} + +/* deleteFoldEntry() {{{2 */ +/* + * Delete fold "idx" from growarray "gap". + * When "recursive" is TRUE also delete all the folds contained in it. + * When "recursive" is FALSE contained folds are moved one level up. + */ +static void deleteFoldEntry(gap, idx, recursive) +garray_T *gap; +int idx; +int recursive; +{ + fold_T *fp; + int i; + long moved; + fold_T *nfp; + + fp = (fold_T *)gap->ga_data + idx; + if (recursive || fp->fd_nested.ga_len == 0) { + /* recursively delete the contained folds */ + deleteFoldRecurse(&fp->fd_nested); + --gap->ga_len; + if (idx < gap->ga_len) + mch_memmove(fp, fp + 1, sizeof(fold_T) * (gap->ga_len - idx)); + } else { + /* Move nested folds one level up, to overwrite the fold that is + * deleted. */ + moved = fp->fd_nested.ga_len; + if (ga_grow(gap, (int)(moved - 1)) == OK) { + /* Get "fp" again, the array may have been reallocated. */ + fp = (fold_T *)gap->ga_data + idx; + + /* adjust fd_top and fd_flags for the moved folds */ + nfp = (fold_T *)fp->fd_nested.ga_data; + for (i = 0; i < moved; ++i) { + nfp[i].fd_top += fp->fd_top; + if (fp->fd_flags == FD_LEVEL) + nfp[i].fd_flags = FD_LEVEL; + if (fp->fd_small == MAYBE) + nfp[i].fd_small = MAYBE; + } + + /* move the existing folds down to make room */ + if (idx + 1 < gap->ga_len) + mch_memmove(fp + moved, fp + 1, + sizeof(fold_T) * (gap->ga_len - (idx + 1))); + /* move the contained folds one level up */ + mch_memmove(fp, nfp, (size_t)(sizeof(fold_T) * moved)); + vim_free(nfp); + gap->ga_len += moved - 1; + } + } +} + +/* deleteFoldRecurse() {{{2 */ +/* + * Delete nested folds in a fold. + */ +void deleteFoldRecurse(gap) +garray_T *gap; +{ + int i; + + for (i = 0; i < gap->ga_len; ++i) + deleteFoldRecurse(&(((fold_T *)(gap->ga_data))[i].fd_nested)); + ga_clear(gap); +} + +/* foldMarkAdjust() {{{2 */ +/* + * Update line numbers of folds for inserted/deleted lines. + */ +void foldMarkAdjust(wp, line1, line2, amount, amount_after) +win_T *wp; +linenr_T line1; +linenr_T line2; +long amount; +long amount_after; +{ + /* If deleting marks from line1 to line2, but not deleting all those + * lines, set line2 so that only deleted lines have their folds removed. */ + if (amount == MAXLNUM && line2 >= line1 && line2 - line1 >= -amount_after) + line2 = line1 - amount_after - 1; + /* If appending a line in Insert mode, it should be included in the fold + * just above the line. */ + if ((State & INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM) + --line1; + foldMarkAdjustRecurse(&wp->w_folds, line1, line2, amount, amount_after); +} + +/* foldMarkAdjustRecurse() {{{2 */ +static void foldMarkAdjustRecurse(gap, line1, line2, amount, amount_after) +garray_T *gap; +linenr_T line1; +linenr_T line2; +long amount; +long amount_after; +{ + fold_T *fp; + int i; + linenr_T last; + linenr_T top; + + /* In Insert mode an inserted line at the top of a fold is considered part + * of the fold, otherwise it isn't. */ + if ((State & INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM) + top = line1 + 1; + else + top = line1; + + /* Find the fold containing or just below "line1". */ + (void)foldFind(gap, line1, &fp); + + /* + * Adjust all folds below "line1" that are affected. + */ + for (i = (int)(fp - (fold_T *)gap->ga_data); i < gap->ga_len; ++i, ++fp) { + /* + * Check for these situations: + * 1 2 3 + * 1 2 3 + * line1 2 3 4 5 + * 2 3 4 5 + * 2 3 4 5 + * line2 2 3 4 5 + * 3 5 6 + * 3 5 6 + */ + + last = fp->fd_top + fp->fd_len - 1; /* last line of fold */ + + /* 1. fold completely above line1: nothing to do */ + if (last < line1) + continue; + + /* 6. fold below line2: only adjust for amount_after */ + if (fp->fd_top > line2) { + if (amount_after == 0) + break; + fp->fd_top += amount_after; + } else { + if (fp->fd_top >= top && last <= line2) { + /* 4. fold completely contained in range */ + if (amount == MAXLNUM) { + /* Deleting lines: delete the fold completely */ + deleteFoldEntry(gap, i, TRUE); + --i; /* adjust index for deletion */ + --fp; + } else + fp->fd_top += amount; + } else { + if (fp->fd_top < top) { + /* 2 or 3: need to correct nested folds too */ + foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top, + line2 - fp->fd_top, amount, amount_after); + if (last <= line2) { + /* 2. fold contains line1, line2 is below fold */ + if (amount == MAXLNUM) + fp->fd_len = line1 - fp->fd_top; + else + fp->fd_len += amount; + } else { + /* 3. fold contains line1 and line2 */ + fp->fd_len += amount_after; + } + } else { + /* 5. fold is below line1 and contains line2; need to + * correct nested folds too */ + foldMarkAdjustRecurse(&fp->fd_nested, line1 - fp->fd_top, + line2 - fp->fd_top, amount, + amount_after + (fp->fd_top - top)); + if (amount == MAXLNUM) { + fp->fd_len -= line2 - fp->fd_top + 1; + fp->fd_top = line1; + } else { + fp->fd_len += amount_after - amount; + fp->fd_top += amount; + } + } + } + } + } +} + +/* getDeepestNesting() {{{2 */ +/* + * Get the lowest 'foldlevel' value that makes the deepest nested fold in the + * current window open. + */ +int getDeepestNesting() { + checkupdate(curwin); + return getDeepestNestingRecurse(&curwin->w_folds); +} + +static int getDeepestNestingRecurse(gap) +garray_T *gap; +{ + int i; + int level; + int maxlevel = 0; + fold_T *fp; + + fp = (fold_T *)gap->ga_data; + for (i = 0; i < gap->ga_len; ++i) { + level = getDeepestNestingRecurse(&fp[i].fd_nested) + 1; + if (level > maxlevel) + maxlevel = level; + } + + return maxlevel; +} + +/* check_closed() {{{2 */ +/* + * Check if a fold is closed and update the info needed to check nested folds. + */ +static int check_closed(win, fp, use_levelp, level, maybe_smallp, lnum_off) +win_T *win; +fold_T *fp; +int *use_levelp; /* TRUE: outer fold had FD_LEVEL */ +int level; /* folding depth */ +int *maybe_smallp; /* TRUE: outer this had fd_small == MAYBE */ +linenr_T lnum_off; /* line number offset for fp->fd_top */ +{ + int closed = FALSE; + + /* Check if this fold is closed. If the flag is FD_LEVEL this + * fold and all folds it contains depend on 'foldlevel'. */ + if (*use_levelp || fp->fd_flags == FD_LEVEL) { + *use_levelp = TRUE; + if (level >= win->w_p_fdl) + closed = TRUE; + } else if (fp->fd_flags == FD_CLOSED) + closed = TRUE; + + /* Small fold isn't closed anyway. */ + if (fp->fd_small == MAYBE) + *maybe_smallp = TRUE; + if (closed) { + if (*maybe_smallp) + fp->fd_small = MAYBE; + checkSmall(win, fp, lnum_off); + if (fp->fd_small == TRUE) + closed = FALSE; + } + return closed; +} + +/* checkSmall() {{{2 */ +/* + * Update fd_small field of fold "fp". + */ +static void checkSmall(wp, fp, lnum_off) +win_T *wp; +fold_T *fp; +linenr_T lnum_off; /* offset for fp->fd_top */ +{ + int count; + int n; + + if (fp->fd_small == MAYBE) { + /* Mark any nested folds to maybe-small */ + setSmallMaybe(&fp->fd_nested); + + if (fp->fd_len > curwin->w_p_fml) + fp->fd_small = FALSE; + else { + count = 0; + for (n = 0; n < fp->fd_len; ++n) { + count += plines_win_nofold(wp, fp->fd_top + lnum_off + n); + if (count > curwin->w_p_fml) { + fp->fd_small = FALSE; + return; + } + } + fp->fd_small = TRUE; + } + } +} + +/* setSmallMaybe() {{{2 */ +/* + * Set small flags in "gap" to MAYBE. + */ +static void setSmallMaybe(gap) +garray_T *gap; +{ + int i; + fold_T *fp; + + fp = (fold_T *)gap->ga_data; + for (i = 0; i < gap->ga_len; ++i) + fp[i].fd_small = MAYBE; +} + +/* foldCreateMarkers() {{{2 */ +/* + * Create a fold from line "start" to line "end" (inclusive) in the current + * window by adding markers. + */ +static void foldCreateMarkers(start, end) +linenr_T start; +linenr_T end; +{ + if (!curbuf->b_p_ma) { + EMSG(_(e_modifiable)); + return; + } + parseMarker(curwin); + + foldAddMarker(start, curwin->w_p_fmr, foldstartmarkerlen); + foldAddMarker(end, foldendmarker, foldendmarkerlen); + + /* Update both changes here, to avoid all folds after the start are + * changed when the start marker is inserted and the end isn't. */ + changed_lines(start, (colnr_T)0, end, 0L); +} + +/* foldAddMarker() {{{2 */ +/* + * Add "marker[markerlen]" in 'commentstring' to line "lnum". + */ +static void foldAddMarker(lnum, marker, markerlen) +linenr_T lnum; +char_u *marker; +int markerlen; +{ + char_u *cms = curbuf->b_p_cms; + char_u *line; + int line_len; + char_u *newline; + char_u *p = (char_u *)strstr((char *)curbuf->b_p_cms, "%s"); + + /* Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end */ + line = ml_get(lnum); + line_len = (int)STRLEN(line); + + if (u_save(lnum - 1, lnum + 1) == OK) { + newline = alloc((unsigned)(line_len + markerlen + STRLEN(cms) + 1)); + if (newline == NULL) + return; + STRCPY(newline, line); + if (p == NULL) + vim_strncpy(newline + line_len, marker, markerlen); + else { + STRCPY(newline + line_len, cms); + STRNCPY(newline + line_len + (p - cms), marker, markerlen); + STRCPY(newline + line_len + (p - cms) + markerlen, p + 2); + } + + ml_replace(lnum, newline, FALSE); + } +} + +/* deleteFoldMarkers() {{{2 */ +/* + * Delete the markers for a fold, causing it to be deleted. + */ +static void deleteFoldMarkers(fp, recursive, lnum_off) +fold_T *fp; +int recursive; +linenr_T lnum_off; /* offset for fp->fd_top */ +{ + int i; + + if (recursive) + for (i = 0; i < fp->fd_nested.ga_len; ++i) + deleteFoldMarkers((fold_T *)fp->fd_nested.ga_data + i, TRUE, + lnum_off + fp->fd_top); + foldDelMarker(fp->fd_top + lnum_off, curwin->w_p_fmr, foldstartmarkerlen); + foldDelMarker(fp->fd_top + lnum_off + fp->fd_len - 1, + foldendmarker, foldendmarkerlen); +} + +/* foldDelMarker() {{{2 */ +/* + * Delete marker "marker[markerlen]" at the end of line "lnum". + * Delete 'commentstring' if it matches. + * If the marker is not found, there is no error message. Could a missing + * close-marker. + */ +static void foldDelMarker(lnum, marker, markerlen) +linenr_T lnum; +char_u *marker; +int markerlen; +{ + char_u *line; + char_u *newline; + char_u *p; + int len; + char_u *cms = curbuf->b_p_cms; + char_u *cms2; + + line = ml_get(lnum); + for (p = line; *p != NUL; ++p) + if (STRNCMP(p, marker, markerlen) == 0) { + /* Found the marker, include a digit if it's there. */ + len = markerlen; + if (VIM_ISDIGIT(p[len])) + ++len; + if (*cms != NUL) { + /* Also delete 'commentstring' if it matches. */ + cms2 = (char_u *)strstr((char *)cms, "%s"); + if (p - line >= cms2 - cms + && STRNCMP(p - (cms2 - cms), cms, cms2 - cms) == 0 + && STRNCMP(p + len, cms2 + 2, STRLEN(cms2 + 2)) == 0) { + p -= cms2 - cms; + len += (int)STRLEN(cms) - 2; + } + } + if (u_save(lnum - 1, lnum + 1) == OK) { + /* Make new line: text-before-marker + text-after-marker */ + newline = alloc((unsigned)(STRLEN(line) - len + 1)); + if (newline != NULL) { + STRNCPY(newline, line, p - line); + STRCPY(newline + (p - line), p + len); + ml_replace(lnum, newline, FALSE); + } + } + break; + } +} + +/* get_foldtext() {{{2 */ +/* + * Return the text for a closed fold at line "lnum", with last line "lnume". + * When 'foldtext' isn't set puts the result in "buf[51]". Otherwise the + * result is in allocated memory. + */ +char_u * get_foldtext(wp, lnum, lnume, foldinfo, buf) +win_T *wp; +linenr_T lnum, lnume; +foldinfo_T *foldinfo; +char_u *buf; +{ + char_u *text = NULL; + /* an error occurred when evaluating 'fdt' setting */ + static int got_fdt_error = FALSE; + int save_did_emsg = did_emsg; + static win_T *last_wp = NULL; + static linenr_T last_lnum = 0; + + if (last_wp != wp || last_wp == NULL + || last_lnum > lnum || last_lnum == 0) + /* window changed, try evaluating foldtext setting once again */ + got_fdt_error = FALSE; + + if (!got_fdt_error) + /* a previous error should not abort evaluating 'foldexpr' */ + did_emsg = FALSE; + + if (*wp->w_p_fdt != NUL) { + char_u dashes[MAX_LEVEL + 2]; + win_T *save_curwin; + int level; + char_u *p; + + /* Set "v:foldstart" and "v:foldend". */ + set_vim_var_nr(VV_FOLDSTART, lnum); + set_vim_var_nr(VV_FOLDEND, lnume); + + /* Set "v:folddashes" to a string of "level" dashes. */ + /* Set "v:foldlevel" to "level". */ + level = foldinfo->fi_level; + if (level > (int)sizeof(dashes) - 1) + level = (int)sizeof(dashes) - 1; + vim_memset(dashes, '-', (size_t)level); + dashes[level] = NUL; + set_vim_var_string(VV_FOLDDASHES, dashes, -1); + set_vim_var_nr(VV_FOLDLEVEL, (long)level); + + /* skip evaluating foldtext on errors */ + if (!got_fdt_error) { + save_curwin = curwin; + curwin = wp; + curbuf = wp->w_buffer; + + ++emsg_silent; /* handle exceptions, but don't display errors */ + text = eval_to_string_safe(wp->w_p_fdt, NULL, + was_set_insecurely((char_u *)"foldtext", OPT_LOCAL)); + --emsg_silent; + + if (text == NULL || did_emsg) + got_fdt_error = TRUE; + + curwin = save_curwin; + curbuf = curwin->w_buffer; + } + last_lnum = lnum; + last_wp = wp; + set_vim_var_string(VV_FOLDDASHES, NULL, -1); + + if (!did_emsg && save_did_emsg) + did_emsg = save_did_emsg; + + if (text != NULL) { + /* Replace unprintable characters, if there are any. But + * replace a TAB with a space. */ + for (p = text; *p != NUL; ++p) { + int len; + + if (has_mbyte && (len = (*mb_ptr2len)(p)) > 1) { + if (!vim_isprintc((*mb_ptr2char)(p))) + break; + p += len - 1; + } else if (*p == TAB) + *p = ' '; + else if (ptr2cells(p) > 1) + break; + } + if (*p != NUL) { + p = transstr(text); + vim_free(text); + text = p; + } + } + } + if (text == NULL) { + sprintf((char *)buf, _("+--%3ld lines folded "), + (long)(lnume - lnum + 1)); + text = buf; + } + return text; +} + +/* foldtext_cleanup() {{{2 */ +/* + * Remove 'foldmarker' and 'commentstring' from "str" (in-place). + */ +void foldtext_cleanup(str) +char_u *str; +{ + char_u *cms_start; /* first part or the whole comment */ + int cms_slen = 0; /* length of cms_start */ + char_u *cms_end; /* last part of the comment or NULL */ + int cms_elen = 0; /* length of cms_end */ + char_u *s; + char_u *p; + int len; + int did1 = FALSE; + int did2 = FALSE; + + /* Ignore leading and trailing white space in 'commentstring'. */ + cms_start = skipwhite(curbuf->b_p_cms); + cms_slen = (int)STRLEN(cms_start); + while (cms_slen > 0 && vim_iswhite(cms_start[cms_slen - 1])) + --cms_slen; + + /* locate "%s" in 'commentstring', use the part before and after it. */ + cms_end = (char_u *)strstr((char *)cms_start, "%s"); + if (cms_end != NULL) { + cms_elen = cms_slen - (int)(cms_end - cms_start); + cms_slen = (int)(cms_end - cms_start); + + /* exclude white space before "%s" */ + while (cms_slen > 0 && vim_iswhite(cms_start[cms_slen - 1])) + --cms_slen; + + /* skip "%s" and white space after it */ + s = skipwhite(cms_end + 2); + cms_elen -= (int)(s - cms_end); + cms_end = s; + } + parseMarker(curwin); + + for (s = str; *s != NUL; ) { + len = 0; + if (STRNCMP(s, curwin->w_p_fmr, foldstartmarkerlen) == 0) + len = foldstartmarkerlen; + else if (STRNCMP(s, foldendmarker, foldendmarkerlen) == 0) + len = foldendmarkerlen; + if (len > 0) { + if (VIM_ISDIGIT(s[len])) + ++len; + + /* May remove 'commentstring' start. Useful when it's a double + * quote and we already removed a double quote. */ + for (p = s; p > str && vim_iswhite(p[-1]); --p) + ; + if (p >= str + cms_slen + && STRNCMP(p - cms_slen, cms_start, cms_slen) == 0) { + len += (int)(s - p) + cms_slen; + s = p - cms_slen; + } + } else if (cms_end != NULL) { + if (!did1 && cms_slen > 0 && STRNCMP(s, cms_start, cms_slen) == 0) { + len = cms_slen; + did1 = TRUE; + } else if (!did2 && cms_elen > 0 + && STRNCMP(s, cms_end, cms_elen) == 0) { + len = cms_elen; + did2 = TRUE; + } + } + if (len != 0) { + while (vim_iswhite(s[len])) + ++len; + STRMOVE(s, s + len); + } else { + mb_ptr_adv(s); + } + } +} + +/* Folding by indent, expr, marker and syntax. {{{1 */ +/* Define "fline_T", passed to get fold level for a line. {{{2 */ +typedef struct { + win_T *wp; /* window */ + linenr_T lnum; /* current line number */ + linenr_T off; /* offset between lnum and real line number */ + linenr_T lnum_save; /* line nr used by foldUpdateIEMSRecurse() */ + int lvl; /* current level (-1 for undefined) */ + int lvl_next; /* level used for next line */ + int start; /* number of folds that are forced to start at + this line. */ + int end; /* level of fold that is forced to end below + this line */ + int had_end; /* level of fold that is forced to end above + this line (copy of "end" of prev. line) */ +} fline_T; + +/* Flag is set when redrawing is needed. */ +static int fold_changed; + +/* Function declarations. {{{2 */ +static linenr_T foldUpdateIEMSRecurse __ARGS((garray_T *gap, int level, + linenr_T startlnum, fline_T *flp, + void (*getlevel)__ARGS( + (fline_T *)), linenr_T bot, + int topflags)); +static int foldInsert __ARGS((garray_T *gap, int i)); +static void foldSplit __ARGS((garray_T *gap, int i, linenr_T top, linenr_T bot)); +static void foldRemove __ARGS((garray_T *gap, linenr_T top, linenr_T bot)); +static void foldMerge __ARGS((fold_T *fp1, garray_T *gap, fold_T *fp2)); +static void foldlevelIndent __ARGS((fline_T *flp)); +static void foldlevelDiff __ARGS((fline_T *flp)); +static void foldlevelExpr __ARGS((fline_T *flp)); +static void foldlevelMarker __ARGS((fline_T *flp)); +static void foldlevelSyntax __ARGS((fline_T *flp)); + +/* foldUpdateIEMS() {{{2 */ +/* + * Update the folding for window "wp", at least from lines "top" to "bot". + * Return TRUE if any folds did change. + */ +static void foldUpdateIEMS(wp, top, bot) +win_T *wp; +linenr_T top; +linenr_T bot; +{ + linenr_T start; + linenr_T end; + fline_T fline; + void (*getlevel)__ARGS((fline_T *)); + int level; + fold_T *fp; + + /* Avoid problems when being called recursively. */ + if (invalid_top != (linenr_T)0) + return; + + if (wp->w_foldinvalid) { + /* Need to update all folds. */ + top = 1; + bot = wp->w_buffer->b_ml.ml_line_count; + wp->w_foldinvalid = FALSE; + + /* Mark all folds a maybe-small. */ + setSmallMaybe(&wp->w_folds); + } + + /* add the context for "diff" folding */ + if (foldmethodIsDiff(wp)) { + if (top > diff_context) + top -= diff_context; + else + top = 1; + bot += diff_context; + } + + /* When deleting lines at the end of the buffer "top" can be past the end + * of the buffer. */ + if (top > wp->w_buffer->b_ml.ml_line_count) + top = wp->w_buffer->b_ml.ml_line_count; + + fold_changed = FALSE; + fline.wp = wp; + fline.off = 0; + fline.lvl = 0; + fline.lvl_next = -1; + fline.start = 0; + fline.end = MAX_LEVEL + 1; + fline.had_end = MAX_LEVEL + 1; + + invalid_top = top; + invalid_bot = bot; + + if (foldmethodIsMarker(wp)) { + getlevel = foldlevelMarker; + + /* Init marker variables to speed up foldlevelMarker(). */ + parseMarker(wp); + + /* Need to get the level of the line above top, it is used if there is + * no marker at the top. */ + if (top > 1) { + /* Get the fold level at top - 1. */ + level = foldLevelWin(wp, top - 1); + + /* The fold may end just above the top, check for that. */ + fline.lnum = top - 1; + fline.lvl = level; + getlevel(&fline); + + /* If a fold started here, we already had the level, if it stops + * here, we need to use lvl_next. Could also start and end a fold + * in the same line. */ + if (fline.lvl > level) + fline.lvl = level - (fline.lvl - fline.lvl_next); + else + fline.lvl = fline.lvl_next; + } + fline.lnum = top; + getlevel(&fline); + } else { + fline.lnum = top; + if (foldmethodIsExpr(wp)) { + getlevel = foldlevelExpr; + /* start one line back, because a "<1" may indicate the end of a + * fold in the topline */ + if (top > 1) + --fline.lnum; + } else if (foldmethodIsSyntax(wp)) + getlevel = foldlevelSyntax; + else if (foldmethodIsDiff(wp)) + getlevel = foldlevelDiff; + else + getlevel = foldlevelIndent; + + /* Backup to a line for which the fold level is defined. Since it's + * always defined for line one, we will stop there. */ + fline.lvl = -1; + for (; !got_int; --fline.lnum) { + /* Reset lvl_next each time, because it will be set to a value for + * the next line, but we search backwards here. */ + fline.lvl_next = -1; + getlevel(&fline); + if (fline.lvl >= 0) + break; + } + } + + /* + * If folding is defined by the syntax, it is possible that a change in + * one line will cause all sub-folds of the current fold to change (e.g., + * closing a C-style comment can cause folds in the subsequent lines to + * appear). To take that into account we should adjust the value of "bot" + * to point to the end of the current fold: + */ + if (foldlevelSyntax == getlevel) { + garray_T *gap = &wp->w_folds; + fold_T *fpn = NULL; + int current_fdl = 0; + linenr_T fold_start_lnum = 0; + linenr_T lnum_rel = fline.lnum; + + while (current_fdl < fline.lvl) { + if (!foldFind(gap, lnum_rel, &fpn)) + break; + ++current_fdl; + + fold_start_lnum += fpn->fd_top; + gap = &fpn->fd_nested; + lnum_rel -= fpn->fd_top; + } + if (fpn != NULL && current_fdl == fline.lvl) { + linenr_T fold_end_lnum = fold_start_lnum + fpn->fd_len; + + if (fold_end_lnum > bot) + bot = fold_end_lnum; + } + } + + start = fline.lnum; + end = bot; + /* Do at least one line. */ + if (start > end && end < wp->w_buffer->b_ml.ml_line_count) + end = start; + while (!got_int) { + /* Always stop at the end of the file ("end" can be past the end of + * the file). */ + if (fline.lnum > wp->w_buffer->b_ml.ml_line_count) + break; + if (fline.lnum > end) { + /* For "marker", "expr" and "syntax" methods: If a change caused + * a fold to be removed, we need to continue at least until where + * it ended. */ + if (getlevel != foldlevelMarker + && getlevel != foldlevelSyntax + && getlevel != foldlevelExpr) + break; + if ((start <= end + && foldFind(&wp->w_folds, end, &fp) + && fp->fd_top + fp->fd_len - 1 > end) + || (fline.lvl == 0 + && foldFind(&wp->w_folds, fline.lnum, &fp) + && fp->fd_top < fline.lnum)) + end = fp->fd_top + fp->fd_len - 1; + else if (getlevel == foldlevelSyntax + && foldLevelWin(wp, fline.lnum) != fline.lvl) + /* For "syntax" method: Compare the foldlevel that the syntax + * tells us to the foldlevel from the existing folds. If they + * don't match continue updating folds. */ + end = fline.lnum; + else + break; + } + + /* A level 1 fold starts at a line with foldlevel > 0. */ + if (fline.lvl > 0) { + invalid_top = fline.lnum; + invalid_bot = end; + end = foldUpdateIEMSRecurse(&wp->w_folds, + 1, start, &fline, getlevel, end, FD_LEVEL); + start = fline.lnum; + } else { + if (fline.lnum == wp->w_buffer->b_ml.ml_line_count) + break; + ++fline.lnum; + fline.lvl = fline.lvl_next; + getlevel(&fline); + } + } + + /* There can't be any folds from start until end now. */ + foldRemove(&wp->w_folds, start, end); + + /* If some fold changed, need to redraw and position cursor. */ + if (fold_changed && wp->w_p_fen) + changed_window_setting_win(wp); + + /* If we updated folds past "bot", need to redraw more lines. Don't do + * this in other situations, the changed lines will be redrawn anyway and + * this method can cause the whole window to be updated. */ + if (end != bot) { + if (wp->w_redraw_top == 0 || wp->w_redraw_top > top) + wp->w_redraw_top = top; + if (wp->w_redraw_bot < end) + wp->w_redraw_bot = end; + } + + invalid_top = (linenr_T)0; +} + +/* foldUpdateIEMSRecurse() {{{2 */ +/* + * Update a fold that starts at "flp->lnum". At this line there is always a + * valid foldlevel, and its level >= "level". + * "flp" is valid for "flp->lnum" when called and it's valid when returning. + * "flp->lnum" is set to the lnum just below the fold, if it ends before + * "bot", it's "bot" plus one if the fold continues and it's bigger when using + * the marker method and a text change made following folds to change. + * When returning, "flp->lnum_save" is the line number that was used to get + * the level when the level at "flp->lnum" is invalid. + * Remove any folds from "startlnum" up to here at this level. + * Recursively update nested folds. + * Below line "bot" there are no changes in the text. + * "flp->lnum", "flp->lnum_save" and "bot" are relative to the start of the + * outer fold. + * "flp->off" is the offset to the real line number in the buffer. + * + * All this would be a lot simpler if all folds in the range would be deleted + * and then created again. But we would lose all information about the + * folds, even when making changes that don't affect the folding (e.g. "vj~"). + * + * Returns bot, which may have been increased for lines that also need to be + * updated as a result of a detected change in the fold. + */ +static linenr_T foldUpdateIEMSRecurse(gap, level, startlnum, flp, getlevel, bot, + topflags) +garray_T *gap; +int level; +linenr_T startlnum; +fline_T *flp; +void (*getlevel)__ARGS((fline_T *)); +linenr_T bot; +int topflags; /* flags used by containing fold */ +{ + linenr_T ll; + fold_T *fp = NULL; + fold_T *fp2; + int lvl = level; + linenr_T startlnum2 = startlnum; + linenr_T firstlnum = flp->lnum; /* first lnum we got */ + int i; + int finish = FALSE; + linenr_T linecount = flp->wp->w_buffer->b_ml.ml_line_count - flp->off; + int concat; + + /* + * If using the marker method, the start line is not the start of a fold + * at the level we're dealing with and the level is non-zero, we must use + * the previous fold. But ignore a fold that starts at or below + * startlnum, it must be deleted. + */ + if (getlevel == foldlevelMarker && flp->start <= flp->lvl - level + && flp->lvl > 0) { + foldFind(gap, startlnum - 1, &fp); + if (fp >= ((fold_T *)gap->ga_data) + gap->ga_len + || fp->fd_top >= startlnum) + fp = NULL; + } + + /* + * Loop over all lines in this fold, or until "bot" is hit. + * Handle nested folds inside of this fold. + * "flp->lnum" is the current line. When finding the end of the fold, it + * is just below the end of the fold. + * "*flp" contains the level of the line "flp->lnum" or a following one if + * there are lines with an invalid fold level. "flp->lnum_save" is the + * line number that was used to get the fold level (below "flp->lnum" when + * it has an invalid fold level). When called the fold level is always + * valid, thus "flp->lnum_save" is equal to "flp->lnum". + */ + flp->lnum_save = flp->lnum; + while (!got_int) { + /* Updating folds can be slow, check for CTRL-C. */ + line_breakcheck(); + + /* Set "lvl" to the level of line "flp->lnum". When flp->start is set + * and after the first line of the fold, set the level to zero to + * force the fold to end. Do the same when had_end is set: Previous + * line was marked as end of a fold. */ + lvl = flp->lvl; + if (lvl > MAX_LEVEL) + lvl = MAX_LEVEL; + if (flp->lnum > firstlnum + && (level > lvl - flp->start || level >= flp->had_end)) + lvl = 0; + + if (flp->lnum > bot && !finish && fp != NULL) { + /* For "marker" and "syntax" methods: + * - If a change caused a nested fold to be removed, we need to + * delete it and continue at least until where it ended. + * - If a change caused a nested fold to be created, or this fold + * to continue below its original end, need to finish this fold. + */ + if (getlevel != foldlevelMarker + && getlevel != foldlevelExpr + && getlevel != foldlevelSyntax) + break; + i = 0; + fp2 = fp; + if (lvl >= level) { + /* Compute how deep the folds currently are, if it's deeper + * than "lvl" then some must be deleted, need to update + * at least one nested fold. */ + ll = flp->lnum - fp->fd_top; + while (foldFind(&fp2->fd_nested, ll, &fp2)) { + ++i; + ll -= fp2->fd_top; + } + } + if (lvl < level + i) { + foldFind(&fp->fd_nested, flp->lnum - fp->fd_top, &fp2); + if (fp2 != NULL) + bot = fp2->fd_top + fp2->fd_len - 1 + fp->fd_top; + } else if (fp->fd_top + fp->fd_len <= flp->lnum && lvl >= level) + finish = TRUE; + else + break; + } + + /* At the start of the first nested fold and at the end of the current + * fold: check if existing folds at this level, before the current + * one, need to be deleted or truncated. */ + if (fp == NULL + && (lvl != level + || flp->lnum_save >= bot + || flp->start != 0 + || flp->had_end <= MAX_LEVEL + || flp->lnum == linecount)) { + /* + * Remove or update folds that have lines between startlnum and + * firstlnum. + */ + while (!got_int) { + /* set concat to 1 if it's allowed to concatenated this fold + * with a previous one that touches it. */ + if (flp->start != 0 || flp->had_end <= MAX_LEVEL) + concat = 0; + else + concat = 1; + + /* Find an existing fold to re-use. Preferably one that + * includes startlnum, otherwise one that ends just before + * startlnum or starts after it. */ + if (foldFind(gap, startlnum, &fp) + || (fp < ((fold_T *)gap->ga_data) + gap->ga_len + && fp->fd_top <= firstlnum) + || foldFind(gap, firstlnum - concat, &fp) + || (fp < ((fold_T *)gap->ga_data) + gap->ga_len + && ((lvl < level && fp->fd_top < flp->lnum) + || (lvl >= level + && fp->fd_top <= flp->lnum_save)))) { + if (fp->fd_top + fp->fd_len + concat > firstlnum) { + /* Use existing fold for the new fold. If it starts + * before where we started looking, extend it. If it + * starts at another line, update nested folds to keep + * their position, compensating for the new fd_top. */ + if (fp->fd_top >= startlnum && fp->fd_top != firstlnum) { + if (fp->fd_top > firstlnum) + /* like lines are inserted */ + foldMarkAdjustRecurse(&fp->fd_nested, + (linenr_T)0, (linenr_T)MAXLNUM, + (long)(fp->fd_top - firstlnum), 0L); + else + /* like lines are deleted */ + foldMarkAdjustRecurse(&fp->fd_nested, + (linenr_T)0, + (long)(firstlnum - fp->fd_top - 1), + (linenr_T)MAXLNUM, + (long)(fp->fd_top - firstlnum)); + fp->fd_len += fp->fd_top - firstlnum; + fp->fd_top = firstlnum; + fold_changed = TRUE; + } else if (flp->start != 0 && lvl == level + && fp->fd_top != firstlnum) { + /* Existing fold that includes startlnum must stop + * if we find the start of a new fold at the same + * level. Split it. Delete contained folds at + * this point to split them too. */ + foldRemove(&fp->fd_nested, flp->lnum - fp->fd_top, + flp->lnum - fp->fd_top); + i = (int)(fp - (fold_T *)gap->ga_data); + foldSplit(gap, i, flp->lnum, flp->lnum - 1); + fp = (fold_T *)gap->ga_data + i + 1; + /* If using the "marker" or "syntax" method, we + * need to continue until the end of the fold is + * found. */ + if (getlevel == foldlevelMarker + || getlevel == foldlevelExpr + || getlevel == foldlevelSyntax) + finish = TRUE; + } + break; + } + if (fp->fd_top >= startlnum) { + /* A fold that starts at or after startlnum and stops + * before the new fold must be deleted. Continue + * looking for the next one. */ + deleteFoldEntry(gap, + (int)(fp - (fold_T *)gap->ga_data), TRUE); + } else { + /* A fold has some lines above startlnum, truncate it + * to stop just above startlnum. */ + fp->fd_len = startlnum - fp->fd_top; + foldMarkAdjustRecurse(&fp->fd_nested, + (linenr_T)fp->fd_len, (linenr_T)MAXLNUM, + (linenr_T)MAXLNUM, 0L); + fold_changed = TRUE; + } + } else { + /* Insert new fold. Careful: ga_data may be NULL and it + * may change! */ + i = (int)(fp - (fold_T *)gap->ga_data); + if (foldInsert(gap, i) != OK) + return bot; + fp = (fold_T *)gap->ga_data + i; + /* The new fold continues until bot, unless we find the + * end earlier. */ + fp->fd_top = firstlnum; + fp->fd_len = bot - firstlnum + 1; + /* When the containing fold is open, the new fold is open. + * The new fold is closed if the fold above it is closed. + * The first fold depends on the containing fold. */ + if (topflags == FD_OPEN) { + flp->wp->w_fold_manual = TRUE; + fp->fd_flags = FD_OPEN; + } else if (i <= 0) { + fp->fd_flags = topflags; + if (topflags != FD_LEVEL) + flp->wp->w_fold_manual = TRUE; + } else + fp->fd_flags = (fp - 1)->fd_flags; + fp->fd_small = MAYBE; + /* If using the "marker", "expr" or "syntax" method, we + * need to continue until the end of the fold is found. */ + if (getlevel == foldlevelMarker + || getlevel == foldlevelExpr + || getlevel == foldlevelSyntax) + finish = TRUE; + fold_changed = TRUE; + break; + } + } + } + + if (lvl < level || flp->lnum > linecount) { + /* + * Found a line with a lower foldlevel, this fold ends just above + * "flp->lnum". + */ + break; + } + + /* + * The fold includes the line "flp->lnum" and "flp->lnum_save". + * Check "fp" for safety. + */ + if (lvl > level && fp != NULL) { + /* + * There is a nested fold, handle it recursively. + */ + /* At least do one line (can happen when finish is TRUE). */ + if (bot < flp->lnum) + bot = flp->lnum; + + /* Line numbers in the nested fold are relative to the start of + * this fold. */ + flp->lnum = flp->lnum_save - fp->fd_top; + flp->off += fp->fd_top; + i = (int)(fp - (fold_T *)gap->ga_data); + bot = foldUpdateIEMSRecurse(&fp->fd_nested, level + 1, + startlnum2 - fp->fd_top, flp, getlevel, + bot - fp->fd_top, fp->fd_flags); + fp = (fold_T *)gap->ga_data + i; + flp->lnum += fp->fd_top; + flp->lnum_save += fp->fd_top; + flp->off -= fp->fd_top; + bot += fp->fd_top; + startlnum2 = flp->lnum; + + /* This fold may end at the same line, don't incr. flp->lnum. */ + } else { + /* + * Get the level of the next line, then continue the loop to check + * if it ends there. + * Skip over undefined lines, to find the foldlevel after it. + * For the last line in the file the foldlevel is always valid. + */ + flp->lnum = flp->lnum_save; + ll = flp->lnum + 1; + while (!got_int) { + /* Make the previous level available to foldlevel(). */ + prev_lnum = flp->lnum; + prev_lnum_lvl = flp->lvl; + + if (++flp->lnum > linecount) + break; + flp->lvl = flp->lvl_next; + getlevel(flp); + if (flp->lvl >= 0 || flp->had_end <= MAX_LEVEL) + break; + } + prev_lnum = 0; + if (flp->lnum > linecount) + break; + + /* leave flp->lnum_save to lnum of the line that was used to get + * the level, flp->lnum to the lnum of the next line. */ + flp->lnum_save = flp->lnum; + flp->lnum = ll; + } + } + + if (fp == NULL) /* only happens when got_int is set */ + return bot; + + /* + * Get here when: + * lvl < level: the folds ends just above "flp->lnum" + * lvl >= level: fold continues below "bot" + */ + + /* Current fold at least extends until lnum. */ + if (fp->fd_len < flp->lnum - fp->fd_top) { + fp->fd_len = flp->lnum - fp->fd_top; + fp->fd_small = MAYBE; + fold_changed = TRUE; + } + + /* Delete contained folds from the end of the last one found until where + * we stopped looking. */ + foldRemove(&fp->fd_nested, startlnum2 - fp->fd_top, + flp->lnum - 1 - fp->fd_top); + + if (lvl < level) { + /* End of fold found, update the length when it got shorter. */ + if (fp->fd_len != flp->lnum - fp->fd_top) { + if (fp->fd_top + fp->fd_len > bot + 1) { + /* fold continued below bot */ + if (getlevel == foldlevelMarker + || getlevel == foldlevelExpr + || getlevel == foldlevelSyntax) { + /* marker method: truncate the fold and make sure the + * previously included lines are processed again */ + bot = fp->fd_top + fp->fd_len - 1; + fp->fd_len = flp->lnum - fp->fd_top; + } else { + /* indent or expr method: split fold to create a new one + * below bot */ + i = (int)(fp - (fold_T *)gap->ga_data); + foldSplit(gap, i, flp->lnum, bot); + fp = (fold_T *)gap->ga_data + i; + } + } else + fp->fd_len = flp->lnum - fp->fd_top; + fold_changed = TRUE; + } + } + + /* delete following folds that end before the current line */ + for (;; ) { + fp2 = fp + 1; + if (fp2 >= (fold_T *)gap->ga_data + gap->ga_len + || fp2->fd_top > flp->lnum) + break; + if (fp2->fd_top + fp2->fd_len > flp->lnum) { + if (fp2->fd_top < flp->lnum) { + /* Make fold that includes lnum start at lnum. */ + foldMarkAdjustRecurse(&fp2->fd_nested, + (linenr_T)0, (long)(flp->lnum - fp2->fd_top - 1), + (linenr_T)MAXLNUM, (long)(fp2->fd_top - flp->lnum)); + fp2->fd_len -= flp->lnum - fp2->fd_top; + fp2->fd_top = flp->lnum; + fold_changed = TRUE; + } + + if (lvl >= level) { + /* merge new fold with existing fold that follows */ + foldMerge(fp, gap, fp2); + } + break; + } + fold_changed = TRUE; + deleteFoldEntry(gap, (int)(fp2 - (fold_T *)gap->ga_data), TRUE); + } + + /* Need to redraw the lines we inspected, which might be further down than + * was asked for. */ + if (bot < flp->lnum - 1) + bot = flp->lnum - 1; + + return bot; +} + +/* foldInsert() {{{2 */ +/* + * Insert a new fold in "gap" at position "i". + * Returns OK for success, FAIL for failure. + */ +static int foldInsert(gap, i) +garray_T *gap; +int i; +{ + fold_T *fp; + + if (ga_grow(gap, 1) != OK) + return FAIL; + fp = (fold_T *)gap->ga_data + i; + if (i < gap->ga_len) + mch_memmove(fp + 1, fp, sizeof(fold_T) * (gap->ga_len - i)); + ++gap->ga_len; + ga_init2(&fp->fd_nested, (int)sizeof(fold_T), 10); + return OK; +} + +/* foldSplit() {{{2 */ +/* + * Split the "i"th fold in "gap", which starts before "top" and ends below + * "bot" in two pieces, one ending above "top" and the other starting below + * "bot". + * The caller must first have taken care of any nested folds from "top" to + * "bot"! + */ +static void foldSplit(gap, i, top, bot) +garray_T *gap; +int i; +linenr_T top; +linenr_T bot; +{ + fold_T *fp; + fold_T *fp2; + garray_T *gap1; + garray_T *gap2; + int idx; + int len; + + /* The fold continues below bot, need to split it. */ + if (foldInsert(gap, i + 1) == FAIL) + return; + fp = (fold_T *)gap->ga_data + i; + fp[1].fd_top = bot + 1; + fp[1].fd_len = fp->fd_len - (fp[1].fd_top - fp->fd_top); + fp[1].fd_flags = fp->fd_flags; + fp[1].fd_small = MAYBE; + fp->fd_small = MAYBE; + + /* Move nested folds below bot to new fold. There can't be + * any between top and bot, they have been removed by the caller. */ + gap1 = &fp->fd_nested; + gap2 = &fp[1].fd_nested; + (void)(foldFind(gap1, bot + 1 - fp->fd_top, &fp2)); + len = (int)((fold_T *)gap1->ga_data + gap1->ga_len - fp2); + if (len > 0 && ga_grow(gap2, len) == OK) { + for (idx = 0; idx < len; ++idx) { + ((fold_T *)gap2->ga_data)[idx] = fp2[idx]; + ((fold_T *)gap2->ga_data)[idx].fd_top + -= fp[1].fd_top - fp->fd_top; + } + gap2->ga_len = len; + gap1->ga_len -= len; + } + fp->fd_len = top - fp->fd_top; + fold_changed = TRUE; +} + +/* foldRemove() {{{2 */ +/* + * Remove folds within the range "top" to and including "bot". + * Check for these situations: + * 1 2 3 + * 1 2 3 + * top 2 3 4 5 + * 2 3 4 5 + * bot 2 3 4 5 + * 3 5 6 + * 3 5 6 + * + * 1: not changed + * 2: truncate to stop above "top" + * 3: split in two parts, one stops above "top", other starts below "bot". + * 4: deleted + * 5: made to start below "bot". + * 6: not changed + */ +static void foldRemove(gap, top, bot) +garray_T *gap; +linenr_T top; +linenr_T bot; +{ + fold_T *fp = NULL; + + if (bot < top) + return; /* nothing to do */ + + for (;; ) { + /* Find fold that includes top or a following one. */ + if (foldFind(gap, top, &fp) && fp->fd_top < top) { + /* 2: or 3: need to delete nested folds */ + foldRemove(&fp->fd_nested, top - fp->fd_top, bot - fp->fd_top); + if (fp->fd_top + fp->fd_len > bot + 1) { + /* 3: need to split it. */ + foldSplit(gap, (int)(fp - (fold_T *)gap->ga_data), top, bot); + } else { + /* 2: truncate fold at "top". */ + fp->fd_len = top - fp->fd_top; + } + fold_changed = TRUE; + continue; + } + if (fp >= (fold_T *)(gap->ga_data) + gap->ga_len + || fp->fd_top > bot) { + /* 6: Found a fold below bot, can stop looking. */ + break; + } + if (fp->fd_top >= top) { + /* Found an entry below top. */ + fold_changed = TRUE; + if (fp->fd_top + fp->fd_len - 1 > bot) { + /* 5: Make fold that includes bot start below bot. */ + foldMarkAdjustRecurse(&fp->fd_nested, + (linenr_T)0, (long)(bot - fp->fd_top), + (linenr_T)MAXLNUM, (long)(fp->fd_top - bot - 1)); + fp->fd_len -= bot - fp->fd_top + 1; + fp->fd_top = bot + 1; + break; + } + + /* 4: Delete completely contained fold. */ + deleteFoldEntry(gap, (int)(fp - (fold_T *)gap->ga_data), TRUE); + } + } +} + +/* foldMerge() {{{2 */ +/* + * Merge two adjacent folds (and the nested ones in them). + * This only works correctly when the folds are really adjacent! Thus "fp1" + * must end just above "fp2". + * The resulting fold is "fp1", nested folds are moved from "fp2" to "fp1". + * Fold entry "fp2" in "gap" is deleted. + */ +static void foldMerge(fp1, gap, fp2) +fold_T *fp1; +garray_T *gap; +fold_T *fp2; +{ + fold_T *fp3; + fold_T *fp4; + int idx; + garray_T *gap1 = &fp1->fd_nested; + garray_T *gap2 = &fp2->fd_nested; + + /* If the last nested fold in fp1 touches the first nested fold in fp2, + * merge them recursively. */ + if (foldFind(gap1, fp1->fd_len - 1L, &fp3) && foldFind(gap2, 0L, &fp4)) + foldMerge(fp3, gap2, fp4); + + /* Move nested folds in fp2 to the end of fp1. */ + if (gap2->ga_len > 0 && ga_grow(gap1, gap2->ga_len) == OK) { + for (idx = 0; idx < gap2->ga_len; ++idx) { + ((fold_T *)gap1->ga_data)[gap1->ga_len] + = ((fold_T *)gap2->ga_data)[idx]; + ((fold_T *)gap1->ga_data)[gap1->ga_len].fd_top += fp1->fd_len; + ++gap1->ga_len; + } + gap2->ga_len = 0; + } + + fp1->fd_len += fp2->fd_len; + deleteFoldEntry(gap, (int)(fp2 - (fold_T *)gap->ga_data), TRUE); + fold_changed = TRUE; +} + +/* foldlevelIndent() {{{2 */ +/* + * Low level function to get the foldlevel for the "indent" method. + * Doesn't use any caching. + * Returns a level of -1 if the foldlevel depends on surrounding lines. + */ +static void foldlevelIndent(flp) +fline_T *flp; +{ + char_u *s; + buf_T *buf; + linenr_T lnum = flp->lnum + flp->off; + + buf = flp->wp->w_buffer; + s = skipwhite(ml_get_buf(buf, lnum, FALSE)); + + /* empty line or lines starting with a character in 'foldignore': level + * depends on surrounding lines */ + if (*s == NUL || vim_strchr(flp->wp->w_p_fdi, *s) != NULL) { + /* first and last line can't be undefined, use level 0 */ + if (lnum == 1 || lnum == buf->b_ml.ml_line_count) + flp->lvl = 0; + else + flp->lvl = -1; + } else + flp->lvl = get_indent_buf(buf, lnum) / get_sw_value(curbuf); + if (flp->lvl > flp->wp->w_p_fdn) { + flp->lvl = flp->wp->w_p_fdn; + if (flp->lvl < 0) + flp->lvl = 0; + } +} + +/* foldlevelDiff() {{{2 */ +/* + * Low level function to get the foldlevel for the "diff" method. + * Doesn't use any caching. + */ +static void foldlevelDiff(flp) +fline_T *flp; +{ + if (diff_infold(flp->wp, flp->lnum + flp->off)) + flp->lvl = 1; + else + flp->lvl = 0; +} + +/* foldlevelExpr() {{{2 */ +/* + * Low level function to get the foldlevel for the "expr" method. + * Doesn't use any caching. + * Returns a level of -1 if the foldlevel depends on surrounding lines. + */ +static void foldlevelExpr(flp) +fline_T *flp; +{ + win_T *win; + int n; + int c; + linenr_T lnum = flp->lnum + flp->off; + int save_keytyped; + + win = curwin; + curwin = flp->wp; + curbuf = flp->wp->w_buffer; + set_vim_var_nr(VV_LNUM, lnum); + + flp->start = 0; + flp->had_end = flp->end; + flp->end = MAX_LEVEL + 1; + if (lnum <= 1) + flp->lvl = 0; + + /* KeyTyped may be reset to 0 when calling a function which invokes + * do_cmdline(). To make 'foldopen' work correctly restore KeyTyped. */ + save_keytyped = KeyTyped; + n = eval_foldexpr(flp->wp->w_p_fde, &c); + KeyTyped = save_keytyped; + + switch (c) { + /* "a1", "a2", .. : add to the fold level */ + case 'a': if (flp->lvl >= 0) { + flp->lvl += n; + flp->lvl_next = flp->lvl; + } + flp->start = n; + break; + + /* "s1", "s2", .. : subtract from the fold level */ + case 's': if (flp->lvl >= 0) { + if (n > flp->lvl) + flp->lvl_next = 0; + else + flp->lvl_next = flp->lvl - n; + flp->end = flp->lvl_next + 1; + } + break; + + /* ">1", ">2", .. : start a fold with a certain level */ + case '>': flp->lvl = n; + flp->lvl_next = n; + flp->start = 1; + break; + + /* "<1", "<2", .. : end a fold with a certain level */ + case '<': flp->lvl_next = n - 1; + flp->end = n; + break; + + /* "=": No change in level */ + case '=': flp->lvl_next = flp->lvl; + break; + + /* "-1", "0", "1", ..: set fold level */ + default: if (n < 0) + /* Use the current level for the next line, so that "a1" + * will work there. */ + flp->lvl_next = flp->lvl; + else + flp->lvl_next = n; + flp->lvl = n; + break; + } + + /* If the level is unknown for the first or the last line in the file, use + * level 0. */ + if (flp->lvl < 0) { + if (lnum <= 1) { + flp->lvl = 0; + flp->lvl_next = 0; + } + if (lnum == curbuf->b_ml.ml_line_count) + flp->lvl_next = 0; + } + + curwin = win; + curbuf = curwin->w_buffer; +} + +/* parseMarker() {{{2 */ +/* + * Parse 'foldmarker' and set "foldendmarker", "foldstartmarkerlen" and + * "foldendmarkerlen". + * Relies on the option value to have been checked for correctness already. + */ +static void parseMarker(wp) +win_T *wp; +{ + foldendmarker = vim_strchr(wp->w_p_fmr, ','); + foldstartmarkerlen = (int)(foldendmarker++ - wp->w_p_fmr); + foldendmarkerlen = (int)STRLEN(foldendmarker); +} + +/* foldlevelMarker() {{{2 */ +/* + * Low level function to get the foldlevel for the "marker" method. + * "foldendmarker", "foldstartmarkerlen" and "foldendmarkerlen" must have been + * set before calling this. + * Requires that flp->lvl is set to the fold level of the previous line! + * Careful: This means you can't call this function twice on the same line. + * Doesn't use any caching. + * Sets flp->start when a start marker was found. + */ +static void foldlevelMarker(flp) +fline_T *flp; +{ + char_u *startmarker; + int cstart; + int cend; + int start_lvl = flp->lvl; + char_u *s; + int n; + + /* cache a few values for speed */ + startmarker = flp->wp->w_p_fmr; + cstart = *startmarker; + ++startmarker; + cend = *foldendmarker; + + /* Default: no start found, next level is same as current level */ + flp->start = 0; + flp->lvl_next = flp->lvl; + + s = ml_get_buf(flp->wp->w_buffer, flp->lnum + flp->off, FALSE); + while (*s) { + if (*s == cstart + && STRNCMP(s + 1, startmarker, foldstartmarkerlen - 1) == 0) { + /* found startmarker: set flp->lvl */ + s += foldstartmarkerlen; + if (VIM_ISDIGIT(*s)) { + n = atoi((char *)s); + if (n > 0) { + flp->lvl = n; + flp->lvl_next = n; + if (n <= start_lvl) + flp->start = 1; + else + flp->start = n - start_lvl; + } + } else { + ++flp->lvl; + ++flp->lvl_next; + ++flp->start; + } + } else if (*s == cend + && STRNCMP(s + 1, foldendmarker + 1, + foldendmarkerlen - 1) == 0) { + /* found endmarker: set flp->lvl_next */ + s += foldendmarkerlen; + if (VIM_ISDIGIT(*s)) { + n = atoi((char *)s); + if (n > 0) { + flp->lvl = n; + flp->lvl_next = n - 1; + /* never start a fold with an end marker */ + if (flp->lvl_next > start_lvl) + flp->lvl_next = start_lvl; + } + } else + --flp->lvl_next; + } else + mb_ptr_adv(s); + } + + /* The level can't go negative, must be missing a start marker. */ + if (flp->lvl_next < 0) + flp->lvl_next = 0; +} + +/* foldlevelSyntax() {{{2 */ +/* + * Low level function to get the foldlevel for the "syntax" method. + * Doesn't use any caching. + */ +static void foldlevelSyntax(flp) +fline_T *flp; +{ + linenr_T lnum = flp->lnum + flp->off; + int n; + + /* Use the maximum fold level at the start of this line and the next. */ + flp->lvl = syn_get_foldlevel(flp->wp, lnum); + flp->start = 0; + if (lnum < flp->wp->w_buffer->b_ml.ml_line_count) { + n = syn_get_foldlevel(flp->wp, lnum + 1); + if (n > flp->lvl) { + flp->start = n - flp->lvl; /* fold(s) start here */ + flp->lvl = n; + } + } +} + +/* functions for storing the fold state in a View {{{1 */ +/* put_folds() {{{2 */ +static int put_folds_recurse __ARGS((FILE *fd, garray_T *gap, linenr_T off)); +static int put_foldopen_recurse __ARGS((FILE *fd, win_T *wp, garray_T *gap, + linenr_T off)); +static int put_fold_open_close __ARGS((FILE *fd, fold_T *fp, linenr_T off)); + +/* + * Write commands to "fd" to restore the manual folds in window "wp". + * Return FAIL if writing fails. + */ +int put_folds(fd, wp) +FILE *fd; +win_T *wp; +{ + if (foldmethodIsManual(wp)) { + if (put_line(fd, "silent! normal! zE") == FAIL + || put_folds_recurse(fd, &wp->w_folds, (linenr_T)0) == FAIL) + return FAIL; + } + + /* If some folds are manually opened/closed, need to restore that. */ + if (wp->w_fold_manual) + return put_foldopen_recurse(fd, wp, &wp->w_folds, (linenr_T)0); + + return OK; +} + +/* put_folds_recurse() {{{2 */ +/* + * Write commands to "fd" to recreate manually created folds. + * Returns FAIL when writing failed. + */ +static int put_folds_recurse(fd, gap, off) +FILE *fd; +garray_T *gap; +linenr_T off; +{ + int i; + fold_T *fp; + + fp = (fold_T *)gap->ga_data; + for (i = 0; i < gap->ga_len; i++) { + /* Do nested folds first, they will be created closed. */ + if (put_folds_recurse(fd, &fp->fd_nested, off + fp->fd_top) == FAIL) + return FAIL; + if (fprintf(fd, "%ld,%ldfold", fp->fd_top + off, + fp->fd_top + off + fp->fd_len - 1) < 0 + || put_eol(fd) == FAIL) + return FAIL; + ++fp; + } + return OK; +} + +/* put_foldopen_recurse() {{{2 */ +/* + * Write commands to "fd" to open and close manually opened/closed folds. + * Returns FAIL when writing failed. + */ +static int put_foldopen_recurse(fd, wp, gap, off) +FILE *fd; +win_T *wp; +garray_T *gap; +linenr_T off; +{ + int i; + int level; + fold_T *fp; + + fp = (fold_T *)gap->ga_data; + for (i = 0; i < gap->ga_len; i++) { + if (fp->fd_flags != FD_LEVEL) { + if (fp->fd_nested.ga_len > 0) { + /* open nested folds while this fold is open */ + if (fprintf(fd, "%ld", fp->fd_top + off) < 0 + || put_eol(fd) == FAIL + || put_line(fd, "normal! zo") == FAIL) + return FAIL; + if (put_foldopen_recurse(fd, wp, &fp->fd_nested, + off + fp->fd_top) + == FAIL) + return FAIL; + /* close the parent when needed */ + if (fp->fd_flags == FD_CLOSED) { + if (put_fold_open_close(fd, fp, off) == FAIL) + return FAIL; + } + } else { + /* Open or close the leaf according to the window foldlevel. + * Do not close a leaf that is already closed, as it will close + * the parent. */ + level = foldLevelWin(wp, off + fp->fd_top); + if ((fp->fd_flags == FD_CLOSED && wp->w_p_fdl >= level) + || (fp->fd_flags != FD_CLOSED && wp->w_p_fdl < level)) + if (put_fold_open_close(fd, fp, off) == FAIL) + return FAIL; + } + } + ++fp; + } + + return OK; +} + +/* put_fold_open_close() {{{2 */ +/* + * Write the open or close command to "fd". + * Returns FAIL when writing failed. + */ +static int put_fold_open_close(fd, fp, off) +FILE *fd; +fold_T *fp; +linenr_T off; +{ + if (fprintf(fd, "%ld", fp->fd_top + off) < 0 + || put_eol(fd) == FAIL + || fprintf(fd, "normal! z%c", + fp->fd_flags == FD_CLOSED ? 'c' : 'o') < 0 + || put_eol(fd) == FAIL) + return FAIL; + + return OK; +} + +/* }}}1 */ diff --git a/src/getchar.c b/src/getchar.c new file mode 100644 index 0000000000..134f6d175b --- /dev/null +++ b/src/getchar.c @@ -0,0 +1,4279 @@ +/* vi:set ts=8 sts=4 sw=4: + * + * VIM - Vi IMproved by Bram Moolenaar + * + * Do ":help uganda" in Vim to read copying and usage conditions. + * Do ":help credits" in Vim to see a list of people who contributed. + * See README.txt for an overview of the Vim source code. + */ + +/* + * getchar.c + * + * functions related with getting a character from the user/mapping/redo/... + * + * manipulations with redo buffer and stuff buffer + * mappings and abbreviations + */ + +#include "vim.h" + +/* + * These buffers are used for storing: + * - stuffed characters: A command that is translated into another command. + * - redo characters: will redo the last change. + * - recorded characters: for the "q" command. + * + * The bytes are stored like in the typeahead buffer: + * - K_SPECIAL introduces a special key (two more bytes follow). A literal + * K_SPECIAL is stored as K_SPECIAL KS_SPECIAL KE_FILLER. + * - CSI introduces a GUI termcap code (also when gui.in_use is FALSE, + * otherwise switching the GUI on would make mappings invalid). + * A literal CSI is stored as CSI KS_EXTRA KE_CSI. + * These translations are also done on multi-byte characters! + * + * Escaping CSI bytes is done by the system-specific input functions, called + * by ui_inchar(). + * Escaping K_SPECIAL is done by inchar(). + * Un-escaping is done by vgetc(). + */ + +#define MINIMAL_SIZE 20 /* minimal size for b_str */ + +static struct buffheader redobuff = {{NULL, {NUL}}, NULL, 0, 0}; +static struct buffheader old_redobuff = {{NULL, {NUL}}, NULL, 0, 0}; +static struct buffheader save_redobuff = {{NULL, {NUL}}, NULL, 0, 0}; +static struct buffheader save_old_redobuff = {{NULL, {NUL}}, NULL, 0, 0}; +static struct buffheader recordbuff = {{NULL, {NUL}}, NULL, 0, 0}; + +static int typeahead_char = 0; /* typeahead char that's not flushed */ + +/* + * when block_redo is TRUE redo buffer will not be changed + * used by edit() to repeat insertions and 'V' command for redoing + */ +static int block_redo = FALSE; + +/* + * Make a hash value for a mapping. + * "mode" is the lower 4 bits of the State for the mapping. + * "c1" is the first character of the "lhs". + * Returns a value between 0 and 255, index in maphash. + * Put Normal/Visual mode mappings mostly separately from Insert/Cmdline mode. + */ +#define MAP_HASH(mode, \ + c1) (((mode) & \ + (NORMAL + VISUAL + SELECTMODE + \ + OP_PENDING)) ? (c1) : ((c1) ^ 0x80)) + +/* + * Each mapping is put in one of the 256 hash lists, to speed up finding it. + */ +static mapblock_T *(maphash[256]); +static int maphash_valid = FALSE; + +/* + * List used for abbreviations. + */ +static mapblock_T *first_abbr = NULL; /* first entry in abbrlist */ + +static int KeyNoremap = 0; /* remapping flags */ + +/* + * variables used by vgetorpeek() and flush_buffers() + * + * typebuf.tb_buf[] contains all characters that are not consumed yet. + * typebuf.tb_buf[typebuf.tb_off] is the first valid character. + * typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len - 1] is the last valid char. + * typebuf.tb_buf[typebuf.tb_off + typebuf.tb_len] must be NUL. + * The head of the buffer may contain the result of mappings, abbreviations + * and @a commands. The length of this part is typebuf.tb_maplen. + * typebuf.tb_silent is the part where applies. + * After the head are characters that come from the terminal. + * typebuf.tb_no_abbr_cnt is the number of characters in typebuf.tb_buf that + * should not be considered for abbreviations. + * Some parts of typebuf.tb_buf may not be mapped. These parts are remembered + * in typebuf.tb_noremap[], which is the same length as typebuf.tb_buf and + * contains RM_NONE for the characters that are not to be remapped. + * typebuf.tb_noremap[typebuf.tb_off] is the first valid flag. + * (typebuf has been put in globals.h, because check_termcode() needs it). + */ +#define RM_YES 0 /* tb_noremap: remap */ +#define RM_NONE 1 /* tb_noremap: don't remap */ +#define RM_SCRIPT 2 /* tb_noremap: remap local script mappings */ +#define RM_ABBR 4 /* tb_noremap: don't remap, do abbrev. */ + +/* typebuf.tb_buf has three parts: room in front (for result of mappings), the + * middle for typeahead and room for new characters (which needs to be 3 * + * MAXMAPLEN) for the Amiga). + */ +#define TYPELEN_INIT (5 * (MAXMAPLEN + 3)) +static char_u typebuf_init[TYPELEN_INIT]; /* initial typebuf.tb_buf */ +static char_u noremapbuf_init[TYPELEN_INIT]; /* initial typebuf.tb_noremap */ + +static int last_recorded_len = 0; /* number of last recorded chars */ + +static char_u *get_buffcont __ARGS((struct buffheader *, int)); +static void add_buff __ARGS((struct buffheader *, char_u *, long n)); +static void add_num_buff __ARGS((struct buffheader *, long)); +static void add_char_buff __ARGS((struct buffheader *, int)); +static int read_stuff __ARGS((int advance)); +static void start_stuff __ARGS((void)); +static int read_redo __ARGS((int, int)); +static void copy_redo __ARGS((int)); +static void init_typebuf __ARGS((void)); +static void gotchars __ARGS((char_u *, int)); +static void may_sync_undo __ARGS((void)); +static void closescript __ARGS((void)); +static int vgetorpeek __ARGS((int)); +static void map_free __ARGS((mapblock_T **)); +static void validate_maphash __ARGS((void)); +static void showmap __ARGS((mapblock_T *mp, int local)); +static char_u *eval_map_expr __ARGS((char_u *str, int c)); + +/* + * Free and clear a buffer. + */ +void free_buff(buf) +struct buffheader *buf; +{ + struct buffblock *p, *np; + + for (p = buf->bh_first.b_next; p != NULL; p = np) { + np = p->b_next; + vim_free(p); + } + buf->bh_first.b_next = NULL; +} + +/* + * Return the contents of a buffer as a single string. + * K_SPECIAL and CSI in the returned string are escaped. + */ +static char_u * get_buffcont(buffer, dozero) +struct buffheader *buffer; +int dozero; /* count == zero is not an error */ +{ + long_u count = 0; + char_u *p = NULL; + char_u *p2; + char_u *str; + struct buffblock *bp; + + /* compute the total length of the string */ + for (bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next) + count += (long_u)STRLEN(bp->b_str); + + if ((count || dozero) && (p = lalloc(count + 1, TRUE)) != NULL) { + p2 = p; + for (bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next) + for (str = bp->b_str; *str; ) + *p2++ = *str++; + *p2 = NUL; + } + return p; +} + +/* + * Return the contents of the record buffer as a single string + * and clear the record buffer. + * K_SPECIAL and CSI in the returned string are escaped. + */ +char_u * get_recorded() { + char_u *p; + size_t len; + + p = get_buffcont(&recordbuff, TRUE); + free_buff(&recordbuff); + + /* + * Remove the characters that were added the last time, these must be the + * (possibly mapped) characters that stopped the recording. + */ + len = STRLEN(p); + if ((int)len >= last_recorded_len) { + len -= last_recorded_len; + p[len] = NUL; + } + + /* + * When stopping recording from Insert mode with CTRL-O q, also remove the + * CTRL-O. + */ + if (len > 0 && restart_edit != 0 && p[len - 1] == Ctrl_O) + p[len - 1] = NUL; + + return p; +} + +/* + * Return the contents of the redo buffer as a single string. + * K_SPECIAL and CSI in the returned string are escaped. + */ +char_u * get_inserted() { + return get_buffcont(&redobuff, FALSE); +} + +/* + * Add string "s" after the current block of buffer "buf". + * K_SPECIAL and CSI should have been escaped already. + */ +static void add_buff(buf, s, slen) +struct buffheader *buf; +char_u *s; +long slen; /* length of "s" or -1 */ +{ + struct buffblock *p; + long_u len; + + if (slen < 0) + slen = (long)STRLEN(s); + if (slen == 0) /* don't add empty strings */ + return; + + if (buf->bh_first.b_next == NULL) { /* first add to list */ + buf->bh_space = 0; + buf->bh_curr = &(buf->bh_first); + } else if (buf->bh_curr == NULL) { /* buffer has already been read */ + EMSG(_("E222: Add to read buffer")); + return; + } else if (buf->bh_index != 0) + mch_memmove(buf->bh_first.b_next->b_str, + buf->bh_first.b_next->b_str + buf->bh_index, + STRLEN(buf->bh_first.b_next->b_str + buf->bh_index) + 1); + buf->bh_index = 0; + + if (buf->bh_space >= (int)slen) { + len = (long_u)STRLEN(buf->bh_curr->b_str); + vim_strncpy(buf->bh_curr->b_str + len, s, (size_t)slen); + buf->bh_space -= slen; + } else { + if (slen < MINIMAL_SIZE) + len = MINIMAL_SIZE; + else + len = slen; + p = (struct buffblock *)lalloc((long_u)(sizeof(struct buffblock) + len), + TRUE); + if (p == NULL) + return; /* no space, just forget it */ + buf->bh_space = (int)(len - slen); + vim_strncpy(p->b_str, s, (size_t)slen); + + p->b_next = buf->bh_curr->b_next; + buf->bh_curr->b_next = p; + buf->bh_curr = p; + } + return; +} + +/* + * Add number "n" to buffer "buf". + */ +static void add_num_buff(buf, n) +struct buffheader *buf; +long n; +{ + char_u number[32]; + + sprintf((char *)number, "%ld", n); + add_buff(buf, number, -1L); +} + +/* + * Add character 'c' to buffer "buf". + * Translates special keys, NUL, CSI, K_SPECIAL and multibyte characters. + */ +static void add_char_buff(buf, c) +struct buffheader *buf; +int c; +{ + char_u bytes[MB_MAXBYTES + 1]; + int len; + int i; + char_u temp[4]; + + if (IS_SPECIAL(c)) + len = 1; + else + len = (*mb_char2bytes)(c, bytes); + for (i = 0; i < len; ++i) { + if (!IS_SPECIAL(c)) + c = bytes[i]; + + if (IS_SPECIAL(c) || c == K_SPECIAL || c == NUL) { + /* translate special key code into three byte sequence */ + temp[0] = K_SPECIAL; + temp[1] = K_SECOND(c); + temp[2] = K_THIRD(c); + temp[3] = NUL; + } else { + temp[0] = c; + temp[1] = NUL; + } + add_buff(buf, temp, -1L); + } +} + +/* + * Get one byte from the stuff buffer. + * If advance == TRUE go to the next char. + * No translation is done K_SPECIAL and CSI are escaped. + */ +static int read_stuff(advance) +int advance; +{ + char_u c; + struct buffblock *curr; + + if (stuffbuff.bh_first.b_next == NULL) /* buffer is empty */ + return NUL; + + curr = stuffbuff.bh_first.b_next; + c = curr->b_str[stuffbuff.bh_index]; + + if (advance) { + if (curr->b_str[++stuffbuff.bh_index] == NUL) { + stuffbuff.bh_first.b_next = curr->b_next; + vim_free(curr); + stuffbuff.bh_index = 0; + } + } + return c; +} + +/* + * Prepare the stuff buffer for reading (if it contains something). + */ +static void start_stuff() { + if (stuffbuff.bh_first.b_next != NULL) { + stuffbuff.bh_curr = &(stuffbuff.bh_first); + stuffbuff.bh_space = 0; + } +} + +/* + * Return TRUE if the stuff buffer is empty. + */ +int stuff_empty() { + return stuffbuff.bh_first.b_next == NULL; +} + +/* + * Set a typeahead character that won't be flushed. + */ +void typeahead_noflush(c) +int c; +{ + typeahead_char = c; +} + +/* + * Remove the contents of the stuff buffer and the mapped characters in the + * typeahead buffer (used in case of an error). If "flush_typeahead" is true, + * flush all typeahead characters (used when interrupted by a CTRL-C). + */ +void flush_buffers(flush_typeahead) +int flush_typeahead; +{ + init_typebuf(); + + start_stuff(); + while (read_stuff(TRUE) != NUL) + ; + + if (flush_typeahead) { /* remove all typeahead */ + /* + * We have to get all characters, because we may delete the first part + * of an escape sequence. + * In an xterm we get one char at a time and we have to get them all. + */ + while (inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 10L, + typebuf.tb_change_cnt) != 0) + ; + typebuf.tb_off = MAXMAPLEN; + typebuf.tb_len = 0; + } else { /* remove mapped characters at the start only */ + typebuf.tb_off += typebuf.tb_maplen; + typebuf.tb_len -= typebuf.tb_maplen; + } + typebuf.tb_maplen = 0; + typebuf.tb_silent = 0; + cmd_silent = FALSE; + typebuf.tb_no_abbr_cnt = 0; +} + +/* + * The previous contents of the redo buffer is kept in old_redobuffer. + * This is used for the CTRL-O <.> command in insert mode. + */ +void ResetRedobuff() { + if (!block_redo) { + free_buff(&old_redobuff); + old_redobuff = redobuff; + redobuff.bh_first.b_next = NULL; + } +} + +/* + * Discard the contents of the redo buffer and restore the previous redo + * buffer. + */ +void CancelRedo() { + if (!block_redo) { + free_buff(&redobuff); + redobuff = old_redobuff; + old_redobuff.bh_first.b_next = NULL; + start_stuff(); + while (read_stuff(TRUE) != NUL) + ; + } +} + +/* + * Save redobuff and old_redobuff to save_redobuff and save_old_redobuff. + * Used before executing autocommands and user functions. + */ +static int save_level = 0; + +void saveRedobuff() { + char_u *s; + + if (save_level++ == 0) { + save_redobuff = redobuff; + redobuff.bh_first.b_next = NULL; + save_old_redobuff = old_redobuff; + old_redobuff.bh_first.b_next = NULL; + + /* Make a copy, so that ":normal ." in a function works. */ + s = get_buffcont(&save_redobuff, FALSE); + if (s != NULL) { + add_buff(&redobuff, s, -1L); + vim_free(s); + } + } +} + +/* + * Restore redobuff and old_redobuff from save_redobuff and save_old_redobuff. + * Used after executing autocommands and user functions. + */ +void restoreRedobuff() { + if (--save_level == 0) { + free_buff(&redobuff); + redobuff = save_redobuff; + free_buff(&old_redobuff); + old_redobuff = save_old_redobuff; + } +} + +/* + * Append "s" to the redo buffer. + * K_SPECIAL and CSI should already have been escaped. + */ +void AppendToRedobuff(s) +char_u *s; +{ + if (!block_redo) + add_buff(&redobuff, s, -1L); +} + +/* + * Append to Redo buffer literally, escaping special characters with CTRL-V. + * K_SPECIAL and CSI are escaped as well. + */ +void AppendToRedobuffLit(str, len) +char_u *str; +int len; /* length of "str" or -1 for up to the NUL */ +{ + char_u *s = str; + int c; + char_u *start; + + if (block_redo) + return; + + while (len < 0 ? *s != NUL : s - str < len) { + /* Put a string of normal characters in the redo buffer (that's + * faster). */ + start = s; + while (*s >= ' ' + && *s < DEL /* EBCDIC: all chars above space are normal */ + && (len < 0 || s - str < len)) + ++s; + + /* Don't put '0' or '^' as last character, just in case a CTRL-D is + * typed next. */ + if (*s == NUL && (s[-1] == '0' || s[-1] == '^')) + --s; + if (s > start) + add_buff(&redobuff, start, (long)(s - start)); + + if (*s == NUL || (len >= 0 && s - str >= len)) + break; + + /* Handle a special or multibyte character. */ + if (has_mbyte) + /* Handle composing chars separately. */ + c = mb_cptr2char_adv(&s); + else + c = *s++; + if (c < ' ' || c == DEL || (*s == NUL && (c == '0' || c == '^'))) + add_char_buff(&redobuff, Ctrl_V); + + /* CTRL-V '0' must be inserted as CTRL-V 048 (EBCDIC: xf0) */ + if (*s == NUL && c == '0') + add_buff(&redobuff, (char_u *)"048", 3L); + else + add_char_buff(&redobuff, c); + } +} + +/* + * Append a character to the redo buffer. + * Translates special keys, NUL, CSI, K_SPECIAL and multibyte characters. + */ +void AppendCharToRedobuff(c) +int c; +{ + if (!block_redo) + add_char_buff(&redobuff, c); +} + +/* + * Append a number to the redo buffer. + */ +void AppendNumberToRedobuff(n) +long n; +{ + if (!block_redo) + add_num_buff(&redobuff, n); +} + +/* + * Append string "s" to the stuff buffer. + * CSI and K_SPECIAL must already have been escaped. + */ +void stuffReadbuff(s) +char_u *s; +{ + add_buff(&stuffbuff, s, -1L); +} + +void stuffReadbuffLen(s, len) +char_u *s; +long len; +{ + add_buff(&stuffbuff, s, len); +} + +/* + * Stuff "s" into the stuff buffer, leaving special key codes unmodified and + * escaping other K_SPECIAL and CSI bytes. + * Change CR, LF and ESC into a space. + */ +void stuffReadbuffSpec(s) +char_u *s; +{ + int c; + + while (*s != NUL) { + if (*s == K_SPECIAL && s[1] != NUL && s[2] != NUL) { + /* Insert special key literally. */ + stuffReadbuffLen(s, 3L); + s += 3; + } else { + c = mb_ptr2char_adv(&s); + if (c == CAR || c == NL || c == ESC) + c = ' '; + stuffcharReadbuff(c); + } + } +} + +/* + * Append a character to the stuff buffer. + * Translates special keys, NUL, CSI, K_SPECIAL and multibyte characters. + */ +void stuffcharReadbuff(c) +int c; +{ + add_char_buff(&stuffbuff, c); +} + +/* + * Append a number to the stuff buffer. + */ +void stuffnumReadbuff(n) +long n; +{ + add_num_buff(&stuffbuff, n); +} + +/* + * Read a character from the redo buffer. Translates K_SPECIAL, CSI and + * multibyte characters. + * The redo buffer is left as it is. + * If init is TRUE, prepare for redo, return FAIL if nothing to redo, OK + * otherwise. + * If old is TRUE, use old_redobuff instead of redobuff. + */ +static int read_redo(init, old_redo) +int init; +int old_redo; +{ + static struct buffblock *bp; + static char_u *p; + int c; + int n; + char_u buf[MB_MAXBYTES + 1]; + int i; + + if (init) { + if (old_redo) + bp = old_redobuff.bh_first.b_next; + else + bp = redobuff.bh_first.b_next; + if (bp == NULL) + return FAIL; + p = bp->b_str; + return OK; + } + if ((c = *p) != NUL) { + /* Reverse the conversion done by add_char_buff() */ + /* For a multi-byte character get all the bytes and return the + * converted character. */ + if (has_mbyte && (c != K_SPECIAL || p[1] == KS_SPECIAL)) + n = MB_BYTE2LEN_CHECK(c); + else + n = 1; + for (i = 0;; ++i) { + if (c == K_SPECIAL) { /* special key or escaped K_SPECIAL */ + c = TO_SPECIAL(p[1], p[2]); + p += 2; + } + if (*++p == NUL && bp->b_next != NULL) { + bp = bp->b_next; + p = bp->b_str; + } + buf[i] = c; + if (i == n - 1) { /* last byte of a character */ + if (n != 1) + c = (*mb_ptr2char)(buf); + break; + } + c = *p; + if (c == NUL) /* cannot happen? */ + break; + } + } + + return c; +} + +/* + * Copy the rest of the redo buffer into the stuff buffer (in a slow way). + * If old_redo is TRUE, use old_redobuff instead of redobuff. + * The escaped K_SPECIAL and CSI are copied without translation. + */ +static void copy_redo(old_redo) +int old_redo; +{ + int c; + + while ((c = read_redo(FALSE, old_redo)) != NUL) + stuffcharReadbuff(c); +} + +/* + * Stuff the redo buffer into the stuffbuff. + * Insert the redo count into the command. + * If "old_redo" is TRUE, the last but one command is repeated + * instead of the last command (inserting text). This is used for + * CTRL-O <.> in insert mode + * + * return FAIL for failure, OK otherwise + */ +int start_redo(count, old_redo) +long count; +int old_redo; +{ + int c; + + /* init the pointers; return if nothing to redo */ + if (read_redo(TRUE, old_redo) == FAIL) + return FAIL; + + c = read_redo(FALSE, old_redo); + + /* copy the buffer name, if present */ + if (c == '"') { + add_buff(&stuffbuff, (char_u *)"\"", 1L); + c = read_redo(FALSE, old_redo); + + /* if a numbered buffer is used, increment the number */ + if (c >= '1' && c < '9') + ++c; + add_char_buff(&stuffbuff, c); + c = read_redo(FALSE, old_redo); + } + + if (c == 'v') { /* redo Visual */ + VIsual = curwin->w_cursor; + VIsual_active = TRUE; + VIsual_select = FALSE; + VIsual_reselect = TRUE; + redo_VIsual_busy = TRUE; + c = read_redo(FALSE, old_redo); + } + + /* try to enter the count (in place of a previous count) */ + if (count) { + while (VIM_ISDIGIT(c)) /* skip "old" count */ + c = read_redo(FALSE, old_redo); + add_num_buff(&stuffbuff, count); + } + + /* copy from the redo buffer into the stuff buffer */ + add_char_buff(&stuffbuff, c); + copy_redo(old_redo); + return OK; +} + +/* + * Repeat the last insert (R, o, O, a, A, i or I command) by stuffing + * the redo buffer into the stuffbuff. + * return FAIL for failure, OK otherwise + */ +int start_redo_ins() { + int c; + + if (read_redo(TRUE, FALSE) == FAIL) + return FAIL; + start_stuff(); + + /* skip the count and the command character */ + while ((c = read_redo(FALSE, FALSE)) != NUL) { + if (vim_strchr((char_u *)"AaIiRrOo", c) != NULL) { + if (c == 'O' || c == 'o') + stuffReadbuff(NL_STR); + break; + } + } + + /* copy the typed text from the redo buffer into the stuff buffer */ + copy_redo(FALSE); + block_redo = TRUE; + return OK; +} + +void stop_redo_ins() { + block_redo = FALSE; +} + +/* + * Initialize typebuf.tb_buf to point to typebuf_init. + * alloc() cannot be used here: In out-of-memory situations it would + * be impossible to type anything. + */ +static void init_typebuf() { + if (typebuf.tb_buf == NULL) { + typebuf.tb_buf = typebuf_init; + typebuf.tb_noremap = noremapbuf_init; + typebuf.tb_buflen = TYPELEN_INIT; + typebuf.tb_len = 0; + typebuf.tb_off = 0; + typebuf.tb_change_cnt = 1; + } +} + +/* + * insert a string in position 'offset' in the typeahead buffer (for "@r" + * and ":normal" command, vgetorpeek() and check_termcode()) + * + * If noremap is REMAP_YES, new string can be mapped again. + * If noremap is REMAP_NONE, new string cannot be mapped again. + * If noremap is REMAP_SKIP, fist char of new string cannot be mapped again, + * but abbreviations are allowed. + * If noremap is REMAP_SCRIPT, new string cannot be mapped again, except for + * script-local mappings. + * If noremap is > 0, that many characters of the new string cannot be mapped. + * + * If nottyped is TRUE, the string does not return KeyTyped (don't use when + * offset is non-zero!). + * + * If silent is TRUE, cmd_silent is set when the characters are obtained. + * + * return FAIL for failure, OK otherwise + */ +int ins_typebuf(str, noremap, offset, nottyped, silent) +char_u *str; +int noremap; +int offset; +int nottyped; +int silent; +{ + char_u *s1, *s2; + int newlen; + int addlen; + int i; + int newoff; + int val; + int nrm; + + init_typebuf(); + if (++typebuf.tb_change_cnt == 0) + typebuf.tb_change_cnt = 1; + + addlen = (int)STRLEN(str); + + /* + * Easy case: there is room in front of typebuf.tb_buf[typebuf.tb_off] + */ + if (offset == 0 && addlen <= typebuf.tb_off) { + typebuf.tb_off -= addlen; + mch_memmove(typebuf.tb_buf + typebuf.tb_off, str, (size_t)addlen); + } + /* + * Need to allocate a new buffer. + * In typebuf.tb_buf there must always be room for 3 * MAXMAPLEN + 4 + * characters. We add some extra room to avoid having to allocate too + * often. + */ + else { + newoff = MAXMAPLEN + 4; + newlen = typebuf.tb_len + addlen + newoff + 4 * (MAXMAPLEN + 4); + if (newlen < 0) { /* string is getting too long */ + EMSG(_(e_toocompl)); /* also calls flush_buffers */ + setcursor(); + return FAIL; + } + s1 = alloc(newlen); + if (s1 == NULL) /* out of memory */ + return FAIL; + s2 = alloc(newlen); + if (s2 == NULL) { /* out of memory */ + vim_free(s1); + return FAIL; + } + typebuf.tb_buflen = newlen; + + /* copy the old chars, before the insertion point */ + mch_memmove(s1 + newoff, typebuf.tb_buf + typebuf.tb_off, + (size_t)offset); + /* copy the new chars */ + mch_memmove(s1 + newoff + offset, str, (size_t)addlen); + /* copy the old chars, after the insertion point, including the NUL at + * the end */ + mch_memmove(s1 + newoff + offset + addlen, + typebuf.tb_buf + typebuf.tb_off + offset, + (size_t)(typebuf.tb_len - offset + 1)); + if (typebuf.tb_buf != typebuf_init) + vim_free(typebuf.tb_buf); + typebuf.tb_buf = s1; + + mch_memmove(s2 + newoff, typebuf.tb_noremap + typebuf.tb_off, + (size_t)offset); + mch_memmove(s2 + newoff + offset + addlen, + typebuf.tb_noremap + typebuf.tb_off + offset, + (size_t)(typebuf.tb_len - offset)); + if (typebuf.tb_noremap != noremapbuf_init) + vim_free(typebuf.tb_noremap); + typebuf.tb_noremap = s2; + + typebuf.tb_off = newoff; + } + typebuf.tb_len += addlen; + + /* If noremap == REMAP_SCRIPT: do remap script-local mappings. */ + if (noremap == REMAP_SCRIPT) + val = RM_SCRIPT; + else if (noremap == REMAP_SKIP) + val = RM_ABBR; + else + val = RM_NONE; + + /* + * Adjust typebuf.tb_noremap[] for the new characters: + * If noremap == REMAP_NONE or REMAP_SCRIPT: new characters are + * (sometimes) not remappable + * If noremap == REMAP_YES: all the new characters are mappable + * If noremap > 0: "noremap" characters are not remappable, the rest + * mappable + */ + if (noremap == REMAP_SKIP) + nrm = 1; + else if (noremap < 0) + nrm = addlen; + else + nrm = noremap; + for (i = 0; i < addlen; ++i) + typebuf.tb_noremap[typebuf.tb_off + i + offset] = + (--nrm >= 0) ? val : RM_YES; + + /* tb_maplen and tb_silent only remember the length of mapped and/or + * silent mappings at the start of the buffer, assuming that a mapped + * sequence doesn't result in typed characters. */ + if (nottyped || typebuf.tb_maplen > offset) + typebuf.tb_maplen += addlen; + if (silent || typebuf.tb_silent > offset) { + typebuf.tb_silent += addlen; + cmd_silent = TRUE; + } + if (typebuf.tb_no_abbr_cnt && offset == 0) /* and not used for abbrev.s */ + typebuf.tb_no_abbr_cnt += addlen; + + return OK; +} + +/* + * Put character "c" back into the typeahead buffer. + * Can be used for a character obtained by vgetc() that needs to be put back. + * Uses cmd_silent, KeyTyped and KeyNoremap to restore the flags belonging to + * the char. + */ +void ins_char_typebuf(c) +int c; +{ + char_u buf[MB_MAXBYTES + 1]; + if (IS_SPECIAL(c)) { + buf[0] = K_SPECIAL; + buf[1] = K_SECOND(c); + buf[2] = K_THIRD(c); + buf[3] = NUL; + } else { + buf[(*mb_char2bytes)(c, buf)] = NUL; + } + (void)ins_typebuf(buf, KeyNoremap, 0, !KeyTyped, cmd_silent); +} + +/* + * Return TRUE if the typeahead buffer was changed (while waiting for a + * character to arrive). Happens when a message was received from a client or + * from feedkeys(). + * But check in a more generic way to avoid trouble: When "typebuf.tb_buf" + * changed it was reallocated and the old pointer can no longer be used. + * Or "typebuf.tb_off" may have been changed and we would overwrite characters + * that was just added. + */ +int typebuf_changed(tb_change_cnt) +int tb_change_cnt; /* old value of typebuf.tb_change_cnt */ +{ + return tb_change_cnt != 0 && (typebuf.tb_change_cnt != tb_change_cnt + || typebuf_was_filled + ); +} + +/* + * Return TRUE if there are no characters in the typeahead buffer that have + * not been typed (result from a mapping or come from ":normal"). + */ +int typebuf_typed() { + return typebuf.tb_maplen == 0; +} + +/* + * Return the number of characters that are mapped (or not typed). + */ +int typebuf_maplen() { + return typebuf.tb_maplen; +} + +/* + * remove "len" characters from typebuf.tb_buf[typebuf.tb_off + offset] + */ +void del_typebuf(len, offset) +int len; +int offset; +{ + int i; + + if (len == 0) + return; /* nothing to do */ + + typebuf.tb_len -= len; + + /* + * Easy case: Just increase typebuf.tb_off. + */ + if (offset == 0 && typebuf.tb_buflen - (typebuf.tb_off + len) + >= 3 * MAXMAPLEN + 3) + typebuf.tb_off += len; + /* + * Have to move the characters in typebuf.tb_buf[] and typebuf.tb_noremap[] + */ + else { + i = typebuf.tb_off + offset; + /* + * Leave some extra room at the end to avoid reallocation. + */ + if (typebuf.tb_off > MAXMAPLEN) { + mch_memmove(typebuf.tb_buf + MAXMAPLEN, + typebuf.tb_buf + typebuf.tb_off, (size_t)offset); + mch_memmove(typebuf.tb_noremap + MAXMAPLEN, + typebuf.tb_noremap + typebuf.tb_off, (size_t)offset); + typebuf.tb_off = MAXMAPLEN; + } + /* adjust typebuf.tb_buf (include the NUL at the end) */ + mch_memmove(typebuf.tb_buf + typebuf.tb_off + offset, + typebuf.tb_buf + i + len, + (size_t)(typebuf.tb_len - offset + 1)); + /* adjust typebuf.tb_noremap[] */ + mch_memmove(typebuf.tb_noremap + typebuf.tb_off + offset, + typebuf.tb_noremap + i + len, + (size_t)(typebuf.tb_len - offset)); + } + + if (typebuf.tb_maplen > offset) { /* adjust tb_maplen */ + if (typebuf.tb_maplen < offset + len) + typebuf.tb_maplen = offset; + else + typebuf.tb_maplen -= len; + } + if (typebuf.tb_silent > offset) { /* adjust tb_silent */ + if (typebuf.tb_silent < offset + len) + typebuf.tb_silent = offset; + else + typebuf.tb_silent -= len; + } + if (typebuf.tb_no_abbr_cnt > offset) { /* adjust tb_no_abbr_cnt */ + if (typebuf.tb_no_abbr_cnt < offset + len) + typebuf.tb_no_abbr_cnt = offset; + else + typebuf.tb_no_abbr_cnt -= len; + } + + /* Reset the flag that text received from a client or from feedkeys() + * was inserted in the typeahead buffer. */ + typebuf_was_filled = FALSE; + if (++typebuf.tb_change_cnt == 0) + typebuf.tb_change_cnt = 1; +} + +/* + * Write typed characters to script file. + * If recording is on put the character in the recordbuffer. + */ +static void gotchars(chars, len) +char_u *chars; +int len; +{ + char_u *s = chars; + int c; + char_u buf[2]; + int todo = len; + + /* remember how many chars were last recorded */ + if (Recording) + last_recorded_len += len; + + buf[1] = NUL; + while (todo--) { + /* Handle one byte at a time; no translation to be done. */ + c = *s++; + updatescript(c); + + if (Recording) { + buf[0] = c; + add_buff(&recordbuff, buf, 1L); + } + } + may_sync_undo(); + + /* output "debug mode" message next time in debug mode */ + debug_did_msg = FALSE; + + /* Since characters have been typed, consider the following to be in + * another mapping. Search string will be kept in history. */ + ++maptick; +} + +/* + * Sync undo. Called when typed characters are obtained from the typeahead + * buffer, or when a menu is used. + * Do not sync: + * - In Insert mode, unless cursor key has been used. + * - While reading a script file. + * - When no_u_sync is non-zero. + */ +static void may_sync_undo() { + if ((!(State & (INSERT + CMDLINE)) || arrow_used) + && scriptin[curscript] == NULL) + u_sync(FALSE); +} + +/* + * Make "typebuf" empty and allocate new buffers. + * Returns FAIL when out of memory. + */ +int alloc_typebuf() { + typebuf.tb_buf = alloc(TYPELEN_INIT); + typebuf.tb_noremap = alloc(TYPELEN_INIT); + if (typebuf.tb_buf == NULL || typebuf.tb_noremap == NULL) { + free_typebuf(); + return FAIL; + } + typebuf.tb_buflen = TYPELEN_INIT; + typebuf.tb_off = 0; + typebuf.tb_len = 0; + typebuf.tb_maplen = 0; + typebuf.tb_silent = 0; + typebuf.tb_no_abbr_cnt = 0; + if (++typebuf.tb_change_cnt == 0) + typebuf.tb_change_cnt = 1; + return OK; +} + +/* + * Free the buffers of "typebuf". + */ +void free_typebuf() { + if (typebuf.tb_buf == typebuf_init) + EMSG2(_(e_intern2), "Free typebuf 1"); + else + vim_free(typebuf.tb_buf); + if (typebuf.tb_noremap == noremapbuf_init) + EMSG2(_(e_intern2), "Free typebuf 2"); + else + vim_free(typebuf.tb_noremap); +} + +/* + * When doing ":so! file", the current typeahead needs to be saved, and + * restored when "file" has been read completely. + */ +static typebuf_T saved_typebuf[NSCRIPT]; + +int save_typebuf() { + init_typebuf(); + saved_typebuf[curscript] = typebuf; + /* If out of memory: restore typebuf and close file. */ + if (alloc_typebuf() == FAIL) { + closescript(); + return FAIL; + } + return OK; +} + +static int old_char = -1; /* character put back by vungetc() */ +static int old_mod_mask; /* mod_mask for ungotten character */ +static int old_mouse_row; /* mouse_row related to old_char */ +static int old_mouse_col; /* mouse_col related to old_char */ + + +/* + * Save all three kinds of typeahead, so that the user must type at a prompt. + */ +void save_typeahead(tp) +tasave_T *tp; +{ + tp->save_typebuf = typebuf; + tp->typebuf_valid = (alloc_typebuf() == OK); + if (!tp->typebuf_valid) + typebuf = tp->save_typebuf; + + tp->old_char = old_char; + tp->old_mod_mask = old_mod_mask; + old_char = -1; + + tp->save_stuffbuff = stuffbuff; + stuffbuff.bh_first.b_next = NULL; +# ifdef USE_INPUT_BUF + tp->save_inputbuf = get_input_buf(); +# endif +} + +/* + * Restore the typeahead to what it was before calling save_typeahead(). + * The allocated memory is freed, can only be called once! + */ +void restore_typeahead(tp) +tasave_T *tp; +{ + if (tp->typebuf_valid) { + free_typebuf(); + typebuf = tp->save_typebuf; + } + + old_char = tp->old_char; + old_mod_mask = tp->old_mod_mask; + + free_buff(&stuffbuff); + stuffbuff = tp->save_stuffbuff; +# ifdef USE_INPUT_BUF + set_input_buf(tp->save_inputbuf); +# endif +} + +/* + * Open a new script file for the ":source!" command. + */ +void openscript(name, directly) +char_u *name; +int directly; /* when TRUE execute directly */ +{ + if (curscript + 1 == NSCRIPT) { + EMSG(_(e_nesting)); + return; + } + if (ignore_script) + /* Not reading from script, also don't open one. Warning message? */ + return; + + if (scriptin[curscript] != NULL) /* already reading script */ + ++curscript; + /* use NameBuff for expanded name */ + expand_env(name, NameBuff, MAXPATHL); + if ((scriptin[curscript] = mch_fopen((char *)NameBuff, READBIN)) == NULL) { + EMSG2(_(e_notopen), name); + if (curscript) + --curscript; + return; + } + if (save_typebuf() == FAIL) + return; + + /* + * Execute the commands from the file right now when using ":source!" + * after ":global" or ":argdo" or in a loop. Also when another command + * follows. This means the display won't be updated. Don't do this + * always, "make test" would fail. + */ + if (directly) { + oparg_T oa; + int oldcurscript; + int save_State = State; + int save_restart_edit = restart_edit; + int save_insertmode = p_im; + int save_finish_op = finish_op; + int save_msg_scroll = msg_scroll; + + State = NORMAL; + msg_scroll = FALSE; /* no msg scrolling in Normal mode */ + restart_edit = 0; /* don't go to Insert mode */ + p_im = FALSE; /* don't use 'insertmode' */ + clear_oparg(&oa); + finish_op = FALSE; + + oldcurscript = curscript; + do { + update_topline_cursor(); /* update cursor position and topline */ + normal_cmd(&oa, FALSE); /* execute one command */ + vpeekc(); /* check for end of file */ + } while (scriptin[oldcurscript] != NULL); + + State = save_State; + msg_scroll = save_msg_scroll; + restart_edit = save_restart_edit; + p_im = save_insertmode; + finish_op = save_finish_op; + } +} + +/* + * Close the currently active input script. + */ +static void closescript() { + free_typebuf(); + typebuf = saved_typebuf[curscript]; + + fclose(scriptin[curscript]); + scriptin[curscript] = NULL; + if (curscript > 0) + --curscript; +} + +#if defined(EXITFREE) || defined(PROTO) +void close_all_scripts() { + while (scriptin[0] != NULL) + closescript(); +} + +#endif + +/* + * Return TRUE when reading keys from a script file. + */ +int using_script() { + return scriptin[curscript] != NULL; +} + +/* + * This function is called just before doing a blocking wait. Thus after + * waiting 'updatetime' for a character to arrive. + */ +void before_blocking() { + updatescript(0); + if (may_garbage_collect) + garbage_collect(); +} + +/* + * updatescipt() is called when a character can be written into the script file + * or when we have waited some time for a character (c == 0) + * + * All the changed memfiles are synced if c == 0 or when the number of typed + * characters reaches 'updatecount' and 'updatecount' is non-zero. + */ +void updatescript(c) +int c; +{ + static int count = 0; + + if (c && scriptout) + putc(c, scriptout); + if (c == 0 || (p_uc > 0 && ++count >= p_uc)) { + ml_sync_all(c == 0, TRUE); + count = 0; + } +} + +/* + * Get the next input character. + * Can return a special key or a multi-byte character. + * Can return NUL when called recursively, use safe_vgetc() if that's not + * wanted. + * This translates escaped K_SPECIAL and CSI bytes to a K_SPECIAL or CSI byte. + * Collects the bytes of a multibyte character into the whole character. + * Returns the modifiers in the global "mod_mask". + */ +int vgetc() { + int c, c2; + int n; + char_u buf[MB_MAXBYTES + 1]; + int i; + + /* Do garbage collection when garbagecollect() was called previously and + * we are now at the toplevel. */ + if (may_garbage_collect && want_garbage_collect) + garbage_collect(); + + /* + * If a character was put back with vungetc, it was already processed. + * Return it directly. + */ + if (old_char != -1) { + c = old_char; + old_char = -1; + mod_mask = old_mod_mask; + mouse_row = old_mouse_row; + mouse_col = old_mouse_col; + } else { + mod_mask = 0x0; + last_recorded_len = 0; + for (;; ) { /* this is done twice if there are modifiers */ + if (mod_mask) { /* no mapping after modifier has been read */ + ++no_mapping; + ++allow_keys; + } + c = vgetorpeek(TRUE); + if (mod_mask) { + --no_mapping; + --allow_keys; + } + + /* Get two extra bytes for special keys */ + if (c == K_SPECIAL + ) { + int save_allow_keys = allow_keys; + + ++no_mapping; + allow_keys = 0; /* make sure BS is not found */ + c2 = vgetorpeek(TRUE); /* no mapping for these chars */ + c = vgetorpeek(TRUE); + --no_mapping; + allow_keys = save_allow_keys; + if (c2 == KS_MODIFIER) { + mod_mask = c; + continue; + } + c = TO_SPECIAL(c2, c); + + } + + /* a keypad or special function key was not mapped, use it like + * its ASCII equivalent */ + switch (c) { + case K_KPLUS: c = '+'; break; + case K_KMINUS: c = '-'; break; + case K_KDIVIDE: c = '/'; break; + case K_KMULTIPLY: c = '*'; break; + case K_KENTER: c = CAR; break; + case K_KPOINT: + c = '.'; break; + case K_K0: c = '0'; break; + case K_K1: c = '1'; break; + case K_K2: c = '2'; break; + case K_K3: c = '3'; break; + case K_K4: c = '4'; break; + case K_K5: c = '5'; break; + case K_K6: c = '6'; break; + case K_K7: c = '7'; break; + case K_K8: c = '8'; break; + case K_K9: c = '9'; break; + + case K_XHOME: + case K_ZHOME: if (mod_mask == MOD_MASK_SHIFT) { + c = K_S_HOME; + mod_mask = 0; + } else if (mod_mask == MOD_MASK_CTRL) { + c = K_C_HOME; + mod_mask = 0; + } else + c = K_HOME; + break; + case K_XEND: + case K_ZEND: if (mod_mask == MOD_MASK_SHIFT) { + c = K_S_END; + mod_mask = 0; + } else if (mod_mask == MOD_MASK_CTRL) { + c = K_C_END; + mod_mask = 0; + } else + c = K_END; + break; + + case K_XUP: c = K_UP; break; + case K_XDOWN: c = K_DOWN; break; + case K_XLEFT: c = K_LEFT; break; + case K_XRIGHT: c = K_RIGHT; break; + } + + /* For a multi-byte character get all the bytes and return the + * converted character. + * Note: This will loop until enough bytes are received! + */ + if (has_mbyte && (n = MB_BYTE2LEN_CHECK(c)) > 1) { + ++no_mapping; + buf[0] = c; + for (i = 1; i < n; ++i) { + buf[i] = vgetorpeek(TRUE); + if (buf[i] == K_SPECIAL + ) { + /* Must be a K_SPECIAL - KS_SPECIAL - KE_FILLER sequence, + * which represents a K_SPECIAL (0x80), + * or a CSI - KS_EXTRA - KE_CSI sequence, which represents + * a CSI (0x9B), + * of a K_SPECIAL - KS_EXTRA - KE_CSI, which is CSI too. */ + c = vgetorpeek(TRUE); + if (vgetorpeek(TRUE) == (int)KE_CSI && c == KS_EXTRA) + buf[i] = CSI; + } + } + --no_mapping; + c = (*mb_ptr2char)(buf); + } + + break; + } + } + + /* + * In the main loop "may_garbage_collect" can be set to do garbage + * collection in the first next vgetc(). It's disabled after that to + * avoid internally used Lists and Dicts to be freed. + */ + may_garbage_collect = FALSE; + + return c; +} + +/* + * Like vgetc(), but never return a NUL when called recursively, get a key + * directly from the user (ignoring typeahead). + */ +int safe_vgetc() { + int c; + + c = vgetc(); + if (c == NUL) + c = get_keystroke(); + return c; +} + +/* + * Like safe_vgetc(), but loop to handle K_IGNORE. + * Also ignore scrollbar events. + */ +int plain_vgetc() { + int c; + + do { + c = safe_vgetc(); + } while (c == K_IGNORE || c == K_VER_SCROLLBAR || c == K_HOR_SCROLLBAR); + return c; +} + +/* + * Check if a character is available, such that vgetc() will not block. + * If the next character is a special character or multi-byte, the returned + * character is not valid!. + */ +int vpeekc() { + if (old_char != -1) + return old_char; + return vgetorpeek(FALSE); +} + +/* + * Like vpeekc(), but don't allow mapping. Do allow checking for terminal + * codes. + */ +int vpeekc_nomap() { + int c; + + ++no_mapping; + ++allow_keys; + c = vpeekc(); + --no_mapping; + --allow_keys; + return c; +} + +/* + * Check if any character is available, also half an escape sequence. + * Trick: when no typeahead found, but there is something in the typeahead + * buffer, it must be an ESC that is recognized as the start of a key code. + */ +int vpeekc_any() { + int c; + + c = vpeekc(); + if (c == NUL && typebuf.tb_len > 0) + c = ESC; + return c; +} + +/* + * Call vpeekc() without causing anything to be mapped. + * Return TRUE if a character is available, FALSE otherwise. + */ +int char_avail() { + int retval; + + ++no_mapping; + retval = vpeekc(); + --no_mapping; + return retval != NUL; +} + +void vungetc(c) /* unget one character (can only be done once!) */ +int c; +{ + old_char = c; + old_mod_mask = mod_mask; + old_mouse_row = mouse_row; + old_mouse_col = mouse_col; +} + +/* + * get a character: + * 1. from the stuffbuffer + * This is used for abbreviated commands like "D" -> "d$". + * Also used to redo a command for ".". + * 2. from the typeahead buffer + * Stores text obtained previously but not used yet. + * Also stores the result of mappings. + * Also used for the ":normal" command. + * 3. from the user + * This may do a blocking wait if "advance" is TRUE. + * + * if "advance" is TRUE (vgetc()): + * really get the character. + * KeyTyped is set to TRUE in the case the user typed the key. + * KeyStuffed is TRUE if the character comes from the stuff buffer. + * if "advance" is FALSE (vpeekc()): + * just look whether there is a character available. + * + * When "no_mapping" is zero, checks for mappings in the current mode. + * Only returns one byte (of a multi-byte character). + * K_SPECIAL and CSI may be escaped, need to get two more bytes then. + */ +static int vgetorpeek(advance) +int advance; +{ + int c, c1; + int keylen; + char_u *s; + mapblock_T *mp; + mapblock_T *mp2; + mapblock_T *mp_match; + int mp_match_len = 0; + int timedout = FALSE; /* waited for more than 1 second + for mapping to complete */ + int mapdepth = 0; /* check for recursive mapping */ + int mode_deleted = FALSE; /* set when mode has been deleted */ + int local_State; + int mlen; + int max_mlen; + int i; + int new_wcol, new_wrow; + int n; + int nolmaplen; + int old_wcol, old_wrow; + int wait_tb_len; + + /* + * This function doesn't work very well when called recursively. This may + * happen though, because of: + * 1. The call to add_to_showcmd(). char_avail() is then used to check if + * there is a character available, which calls this function. In that + * case we must return NUL, to indicate no character is available. + * 2. A GUI callback function writes to the screen, causing a + * wait_return(). + * Using ":normal" can also do this, but it saves the typeahead buffer, + * thus it should be OK. But don't get a key from the user then. + */ + if (vgetc_busy > 0 + && ex_normal_busy == 0 + ) + return NUL; + + local_State = get_real_state(); + + ++vgetc_busy; + + if (advance) + KeyStuffed = FALSE; + + init_typebuf(); + start_stuff(); + if (advance && typebuf.tb_maplen == 0) + Exec_reg = FALSE; + do { + /* + * get a character: 1. from the stuffbuffer + */ + if (typeahead_char != 0) { + c = typeahead_char; + if (advance) + typeahead_char = 0; + } else + c = read_stuff(advance); + if (c != NUL && !got_int) { + if (advance) { + /* KeyTyped = FALSE; When the command that stuffed something + * was typed, behave like the stuffed command was typed. + * needed for CTRL-W CTRl-] to open a fold, for example. */ + KeyStuffed = TRUE; + } + if (typebuf.tb_no_abbr_cnt == 0) + typebuf.tb_no_abbr_cnt = 1; /* no abbreviations now */ + } else { + /* + * Loop until we either find a matching mapped key, or we + * are sure that it is not a mapped key. + * If a mapped key sequence is found we go back to the start to + * try re-mapping. + */ + for (;; ) { + /* + * ui_breakcheck() is slow, don't use it too often when + * inside a mapping. But call it each time for typed + * characters. + */ + if (typebuf.tb_maplen) + line_breakcheck(); + else + ui_breakcheck(); /* check for CTRL-C */ + keylen = 0; + if (got_int) { + /* flush all input */ + c = inchar(typebuf.tb_buf, typebuf.tb_buflen - 1, 0L, + typebuf.tb_change_cnt); + /* + * If inchar() returns TRUE (script file was active) or we + * are inside a mapping, get out of insert mode. + * Otherwise we behave like having gotten a CTRL-C. + * As a result typing CTRL-C in insert mode will + * really insert a CTRL-C. + */ + if ((c || typebuf.tb_maplen) + && (State & (INSERT + CMDLINE))) + c = ESC; + else + c = Ctrl_C; + flush_buffers(TRUE); /* flush all typeahead */ + + if (advance) { + /* Also record this character, it might be needed to + * get out of Insert mode. */ + *typebuf.tb_buf = c; + gotchars(typebuf.tb_buf, 1); + } + cmd_silent = FALSE; + + break; + } else if (typebuf.tb_len > 0) { + /* + * Check for a mappable key sequence. + * Walk through one maphash[] list until we find an + * entry that matches. + * + * Don't look for mappings if: + * - no_mapping set: mapping disabled (e.g. for CTRL-V) + * - maphash_valid not set: no mappings present. + * - typebuf.tb_buf[typebuf.tb_off] should not be remapped + * - in insert or cmdline mode and 'paste' option set + * - waiting for "hit return to continue" and CR or SPACE + * typed + * - waiting for a char with --more-- + * - in Ctrl-X mode, and we get a valid char for that mode + */ + mp = NULL; + max_mlen = 0; + c1 = typebuf.tb_buf[typebuf.tb_off]; + if (no_mapping == 0 && maphash_valid + && (no_zero_mapping == 0 || c1 != '0') + && (typebuf.tb_maplen == 0 + || (p_remap + && (typebuf.tb_noremap[typebuf.tb_off] + & (RM_NONE|RM_ABBR)) == 0)) + && !(p_paste && (State & (INSERT + CMDLINE))) + && !(State == HITRETURN && (c1 == CAR || c1 == ' ')) + && State != ASKMORE + && State != CONFIRM + && !((ctrl_x_mode != 0 && vim_is_ctrl_x_key(c1)) + || ((compl_cont_status & CONT_LOCAL) + && (c1 == Ctrl_N || c1 == Ctrl_P))) + ) { + if (c1 == K_SPECIAL) + nolmaplen = 2; + else { + LANGMAP_ADJUST(c1, TRUE); + nolmaplen = 0; + } + /* First try buffer-local mappings. */ + mp = curbuf->b_maphash[MAP_HASH(local_State, c1)]; + mp2 = maphash[MAP_HASH(local_State, c1)]; + if (mp == NULL) { + /* There are no buffer-local mappings. */ + mp = mp2; + mp2 = NULL; + } + /* + * Loop until a partly matching mapping is found or + * all (local) mappings have been checked. + * The longest full match is remembered in "mp_match". + * A full match is only accepted if there is no partly + * match, so "aa" and "aaa" can both be mapped. + */ + mp_match = NULL; + mp_match_len = 0; + for (; mp != NULL; + mp->m_next == NULL ? (mp = mp2, mp2 = NULL) : + (mp = mp->m_next)) { + /* + * Only consider an entry if the first character + * matches and it is for the current state. + * Skip ":lmap" mappings if keys were mapped. + */ + if (mp->m_keys[0] == c1 + && (mp->m_mode & local_State) + && ((mp->m_mode & LANGMAP) == 0 + || typebuf.tb_maplen == 0)) { + int nomap = nolmaplen; + int c2; + /* find the match length of this mapping */ + for (mlen = 1; mlen < typebuf.tb_len; ++mlen) { + c2 = typebuf.tb_buf[typebuf.tb_off + mlen]; + if (nomap > 0) + --nomap; + else if (c2 == K_SPECIAL) + nomap = 2; + else + LANGMAP_ADJUST(c2, TRUE); + if (mp->m_keys[mlen] != c2) + break; + } + + /* Don't allow mapping the first byte(s) of a + * multi-byte char. Happens when mapping + * and then changing 'encoding'. */ + if (has_mbyte && MB_BYTE2LEN(c1) + > (*mb_ptr2len)(mp->m_keys)) + mlen = 0; + /* + * Check an entry whether it matches. + * - Full match: mlen == keylen + * - Partly match: mlen == typebuf.tb_len + */ + keylen = mp->m_keylen; + if (mlen == keylen + || (mlen == typebuf.tb_len + && typebuf.tb_len < keylen)) { + /* + * If only script-local mappings are + * allowed, check if the mapping starts + * with K_SNR. + */ + s = typebuf.tb_noremap + typebuf.tb_off; + if (*s == RM_SCRIPT + && (mp->m_keys[0] != K_SPECIAL + || mp->m_keys[1] != KS_EXTRA + || mp->m_keys[2] + != (int)KE_SNR)) + continue; + /* + * If one of the typed keys cannot be + * remapped, skip the entry. + */ + for (n = mlen; --n >= 0; ) + if (*s++ & (RM_NONE|RM_ABBR)) + break; + if (n >= 0) + continue; + + if (keylen > typebuf.tb_len) { + if (!timedout && !(mp_match != NULL + && mp_match->m_nowait)) { + /* break at a partly match */ + keylen = KEYLEN_PART_MAP; + break; + } + } else if (keylen > mp_match_len) { + /* found a longer match */ + mp_match = mp; + mp_match_len = keylen; + } + } else + /* No match; may have to check for + * termcode at next character. */ + if (max_mlen < mlen) + max_mlen = mlen; + } + } + + /* If no partly match found, use the longest full + * match. */ + if (keylen != KEYLEN_PART_MAP) { + mp = mp_match; + keylen = mp_match_len; + } + } + + /* Check for match with 'pastetoggle' */ + if (*p_pt != NUL && mp == NULL && (State & (INSERT|NORMAL))) { + for (mlen = 0; mlen < typebuf.tb_len && p_pt[mlen]; + ++mlen) + if (p_pt[mlen] != typebuf.tb_buf[typebuf.tb_off + + mlen]) + break; + if (p_pt[mlen] == NUL) { /* match */ + /* write chars to script file(s) */ + if (mlen > typebuf.tb_maplen) + gotchars(typebuf.tb_buf + typebuf.tb_off + + typebuf.tb_maplen, + mlen - typebuf.tb_maplen); + + del_typebuf(mlen, 0); /* remove the chars */ + set_option_value((char_u *)"paste", + (long)!p_paste, NULL, 0); + if (!(State & INSERT)) { + msg_col = 0; + msg_row = Rows - 1; + msg_clr_eos(); /* clear ruler */ + } + showmode(); + setcursor(); + continue; + } + /* Need more chars for partly match. */ + if (mlen == typebuf.tb_len) + keylen = KEYLEN_PART_KEY; + else if (max_mlen < mlen) + /* no match, may have to check for termcode at + * next character */ + max_mlen = mlen + 1; + } + + if ((mp == NULL || max_mlen >= mp_match_len) + && keylen != KEYLEN_PART_MAP) { + int save_keylen = keylen; + + /* + * When no matching mapping found or found a + * non-matching mapping that matches at least what the + * matching mapping matched: + * Check if we have a terminal code, when: + * mapping is allowed, + * keys have not been mapped, + * and not an ESC sequence, not in insert mode or + * p_ek is on, + * and when not timed out, + */ + if ((no_mapping == 0 || allow_keys != 0) + && (typebuf.tb_maplen == 0 + || (p_remap && typebuf.tb_noremap[ + typebuf.tb_off] == RM_YES)) + && !timedout) { + keylen = check_termcode(max_mlen + 1, + NULL, 0, NULL); + + /* If no termcode matched but 'pastetoggle' + * matched partially it's like an incomplete key + * sequence. */ + if (keylen == 0 && save_keylen == KEYLEN_PART_KEY) + keylen = KEYLEN_PART_KEY; + + /* + * When getting a partial match, but the last + * characters were not typed, don't wait for a + * typed character to complete the termcode. + * This helps a lot when a ":normal" command ends + * in an ESC. + */ + if (keylen < 0 + && typebuf.tb_len == typebuf.tb_maplen) + keylen = 0; + } else + keylen = 0; + if (keylen == 0) { /* no matching terminal code */ + /* When there was a matching mapping and no + * termcode could be replaced after another one, + * use that mapping (loop around). If there was + * no mapping use the character from the + * typeahead buffer right here. */ + if (mp == NULL) { + /* + * get a character: 2. from the typeahead buffer + */ + c = typebuf.tb_buf[typebuf.tb_off] & 255; + if (advance) { /* remove chars from tb_buf */ + cmd_silent = (typebuf.tb_silent > 0); + if (typebuf.tb_maplen > 0) + KeyTyped = FALSE; + else { + KeyTyped = TRUE; + /* write char to script file(s) */ + gotchars(typebuf.tb_buf + + typebuf.tb_off, 1); + } + KeyNoremap = typebuf.tb_noremap[ + typebuf.tb_off]; + del_typebuf(1, 0); + } + break; /* got character, break for loop */ + } + } + if (keylen > 0) { /* full matching terminal code */ + continue; /* try mapping again */ + } + + /* Partial match: get some more characters. When a + * matching mapping was found use that one. */ + if (mp == NULL || keylen < 0) + keylen = KEYLEN_PART_KEY; + else + keylen = mp_match_len; + } + + /* complete match */ + if (keylen >= 0 && keylen <= typebuf.tb_len) { + int save_m_expr; + int save_m_noremap; + int save_m_silent; + char_u *save_m_keys; + char_u *save_m_str; + + /* write chars to script file(s) */ + if (keylen > typebuf.tb_maplen) + gotchars(typebuf.tb_buf + typebuf.tb_off + + typebuf.tb_maplen, + keylen - typebuf.tb_maplen); + + cmd_silent = (typebuf.tb_silent > 0); + del_typebuf(keylen, 0); /* remove the mapped keys */ + + /* + * Put the replacement string in front of mapstr. + * The depth check catches ":map x y" and ":map y x". + */ + if (++mapdepth >= p_mmd) { + EMSG(_("E223: recursive mapping")); + if (State & CMDLINE) + redrawcmdline(); + else + setcursor(); + flush_buffers(FALSE); + mapdepth = 0; /* for next one */ + c = -1; + break; + } + + /* + * In Select mode and a Visual mode mapping is used: + * Switch to Visual mode temporarily. Append K_SELECT + * to switch back to Select mode. + */ + if (VIsual_active && VIsual_select + && (mp->m_mode & VISUAL)) { + VIsual_select = FALSE; + (void)ins_typebuf(K_SELECT_STRING, REMAP_NONE, + 0, TRUE, FALSE); + } + + /* Copy the values from *mp that are used, because + * evaluating the expression may invoke a function + * that redefines the mapping, thereby making *mp + * invalid. */ + save_m_expr = mp->m_expr; + save_m_noremap = mp->m_noremap; + save_m_silent = mp->m_silent; + save_m_keys = NULL; /* only saved when needed */ + save_m_str = NULL; /* only saved when needed */ + + /* + * Handle ":map ": evaluate the {rhs} as an + * expression. Also save and restore the command line + * for "normal :". + */ + if (mp->m_expr) { + int save_vgetc_busy = vgetc_busy; + + vgetc_busy = 0; + save_m_keys = vim_strsave(mp->m_keys); + save_m_str = vim_strsave(mp->m_str); + s = eval_map_expr(save_m_str, NUL); + vgetc_busy = save_vgetc_busy; + } else + s = mp->m_str; + + /* + * Insert the 'to' part in the typebuf.tb_buf. + * If 'from' field is the same as the start of the + * 'to' field, don't remap the first character (but do + * allow abbreviations). + * If m_noremap is set, don't remap the whole 'to' + * part. + */ + if (s == NULL) + i = FAIL; + else { + int noremap; + + if (save_m_noremap != REMAP_YES) + noremap = save_m_noremap; + else if ( + STRNCMP(s, save_m_keys != NULL + ? save_m_keys : mp->m_keys, + (size_t)keylen) + != 0) + noremap = REMAP_YES; + else + noremap = REMAP_SKIP; + i = ins_typebuf(s, noremap, + 0, TRUE, cmd_silent || save_m_silent); + if (save_m_expr) + vim_free(s); + } + vim_free(save_m_keys); + vim_free(save_m_str); + if (i == FAIL) { + c = -1; + break; + } + continue; + } + } + + /* + * get a character: 3. from the user - handle in Insert mode + */ + /* + * special case: if we get an in insert mode and there + * are no more characters at once, we pretend to go out of + * insert mode. This prevents the one second delay after + * typing an . If we get something after all, we may + * have to redisplay the mode. That the cursor is in the wrong + * place does not matter. + */ + c = 0; + new_wcol = curwin->w_wcol; + new_wrow = curwin->w_wrow; + if ( advance + && typebuf.tb_len == 1 + && typebuf.tb_buf[typebuf.tb_off] == ESC + && !no_mapping + && ex_normal_busy == 0 + && typebuf.tb_maplen == 0 + && (State & INSERT) + && (p_timeout + || (keylen == KEYLEN_PART_KEY && p_ttimeout)) + && (c = inchar(typebuf.tb_buf + typebuf.tb_off + + typebuf.tb_len, 3, 25L, + typebuf.tb_change_cnt)) == 0) { + colnr_T col = 0, vcol; + char_u *ptr; + + if (mode_displayed) { + unshowmode(TRUE); + mode_deleted = TRUE; + } + validate_cursor(); + old_wcol = curwin->w_wcol; + old_wrow = curwin->w_wrow; + + /* move cursor left, if possible */ + if (curwin->w_cursor.col != 0) { + if (curwin->w_wcol > 0) { + if (did_ai) { + /* + * We are expecting to truncate the trailing + * white-space, so find the last non-white + * character -- webb + */ + col = vcol = curwin->w_wcol = 0; + ptr = ml_get_curline(); + while (col < curwin->w_cursor.col) { + if (!vim_iswhite(ptr[col])) + curwin->w_wcol = vcol; + vcol += lbr_chartabsize(ptr + col, + (colnr_T)vcol); + if (has_mbyte) + col += (*mb_ptr2len)(ptr + col); + else + ++col; + } + curwin->w_wrow = curwin->w_cline_row + + curwin->w_wcol / W_WIDTH(curwin); + curwin->w_wcol %= W_WIDTH(curwin); + curwin->w_wcol += curwin_col_off(); + col = 0; /* no correction needed */ + } else { + --curwin->w_wcol; + col = curwin->w_cursor.col - 1; + } + } else if (curwin->w_p_wrap && curwin->w_wrow) { + --curwin->w_wrow; + curwin->w_wcol = W_WIDTH(curwin) - 1; + col = curwin->w_cursor.col - 1; + } + if (has_mbyte && col > 0 && curwin->w_wcol > 0) { + /* Correct when the cursor is on the right halve + * of a double-wide character. */ + ptr = ml_get_curline(); + col -= (*mb_head_off)(ptr, ptr + col); + if ((*mb_ptr2cells)(ptr + col) > 1) + --curwin->w_wcol; + } + } + setcursor(); + out_flush(); + new_wcol = curwin->w_wcol; + new_wrow = curwin->w_wrow; + curwin->w_wcol = old_wcol; + curwin->w_wrow = old_wrow; + } + if (c < 0) + continue; /* end of input script reached */ + typebuf.tb_len += c; + + /* buffer full, don't map */ + if (typebuf.tb_len >= typebuf.tb_maplen + MAXMAPLEN) { + timedout = TRUE; + continue; + } + + if (ex_normal_busy > 0) { + static int tc = 0; + + /* No typeahead left and inside ":normal". Must return + * something to avoid getting stuck. When an incomplete + * mapping is present, behave like it timed out. */ + if (typebuf.tb_len > 0) { + timedout = TRUE; + continue; + } + /* When 'insertmode' is set, ESC just beeps in Insert + * mode. Use CTRL-L to make edit() return. + * For the command line only CTRL-C always breaks it. + * For the cmdline window: Alternate between ESC and + * CTRL-C: ESC for most situations and CTRL-C to close the + * cmdline window. */ + if (p_im && (State & INSERT)) + c = Ctrl_L; + else if ((State & CMDLINE) + || (cmdwin_type > 0 && tc == ESC) + ) + c = Ctrl_C; + else + c = ESC; + tc = c; + break; + } + + /* + * get a character: 3. from the user - update display + */ + /* In insert mode a screen update is skipped when characters + * are still available. But when those available characters + * are part of a mapping, and we are going to do a blocking + * wait here. Need to update the screen to display the + * changed text so far. Also for when 'lazyredraw' is set and + * redrawing was postponed because there was something in the + * input buffer (e.g., termresponse). */ + if (((State & INSERT) != 0 || p_lz) && (State & CMDLINE) == 0 + && advance && must_redraw != 0 && !need_wait_return) { + update_screen(0); + setcursor(); /* put cursor back where it belongs */ + } + + /* + * If we have a partial match (and are going to wait for more + * input from the user), show the partially matched characters + * to the user with showcmd. + */ + i = 0; + c1 = 0; + if (typebuf.tb_len > 0 && advance && !exmode_active) { + if (((State & (NORMAL | INSERT)) || State == LANGMAP) + && State != HITRETURN) { + /* this looks nice when typing a dead character map */ + if (State & INSERT + && ptr2cells(typebuf.tb_buf + typebuf.tb_off + + typebuf.tb_len - 1) == 1) { + edit_putchar(typebuf.tb_buf[typebuf.tb_off + + typebuf.tb_len - 1], FALSE); + setcursor(); /* put cursor back where it belongs */ + c1 = 1; + } + /* need to use the col and row from above here */ + old_wcol = curwin->w_wcol; + old_wrow = curwin->w_wrow; + curwin->w_wcol = new_wcol; + curwin->w_wrow = new_wrow; + push_showcmd(); + if (typebuf.tb_len > SHOWCMD_COLS) + i = typebuf.tb_len - SHOWCMD_COLS; + while (i < typebuf.tb_len) + (void)add_to_showcmd(typebuf.tb_buf[typebuf.tb_off + + i++]); + curwin->w_wcol = old_wcol; + curwin->w_wrow = old_wrow; + } + + /* this looks nice when typing a dead character map */ + if ((State & CMDLINE) + && cmdline_star == 0 + && ptr2cells(typebuf.tb_buf + typebuf.tb_off + + typebuf.tb_len - 1) == 1) { + putcmdline(typebuf.tb_buf[typebuf.tb_off + + typebuf.tb_len - 1], FALSE); + c1 = 1; + } + } + + /* + * get a character: 3. from the user - get it + */ + wait_tb_len = typebuf.tb_len; + c = inchar(typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len, + typebuf.tb_buflen - typebuf.tb_off - typebuf.tb_len - 1, + !advance + ? 0 + : ((typebuf.tb_len == 0 + || !(p_timeout || (p_ttimeout + && keylen == KEYLEN_PART_KEY))) + ? -1L + : ((keylen == KEYLEN_PART_KEY && p_ttm >= 0) + ? p_ttm + : p_tm)), typebuf.tb_change_cnt); + + if (i != 0) + pop_showcmd(); + if (c1 == 1) { + if (State & INSERT) + edit_unputchar(); + if (State & CMDLINE) + unputcmdline(); + else + setcursor(); /* put cursor back where it belongs */ + } + + if (c < 0) + continue; /* end of input script reached */ + if (c == NUL) { /* no character available */ + if (!advance) + break; + if (wait_tb_len > 0) { /* timed out */ + timedout = TRUE; + continue; + } + } else { /* allow mapping for just typed characters */ + while (typebuf.tb_buf[typebuf.tb_off + + typebuf.tb_len] != NUL) + typebuf.tb_noremap[typebuf.tb_off + + typebuf.tb_len++] = RM_YES; +#ifdef USE_IM_CONTROL + /* Get IM status right after getting keys, not after the + * timeout for a mapping (focus may be lost by then). */ + vgetc_im_active = im_get_status(); +#endif + } + } /* for (;;) */ + } /* if (!character from stuffbuf) */ + + /* if advance is FALSE don't loop on NULs */ + } while (c < 0 || (advance && c == NUL)); + + /* + * The "INSERT" message is taken care of here: + * if we return an ESC to exit insert mode, the message is deleted + * if we don't return an ESC but deleted the message before, redisplay it + */ + if (advance && p_smd && msg_silent == 0 && (State & INSERT)) { + if (c == ESC && !mode_deleted && !no_mapping && mode_displayed) { + if (typebuf.tb_len && !KeyTyped) + redraw_cmdline = TRUE; /* delete mode later */ + else + unshowmode(FALSE); + } else if (c != ESC && mode_deleted) { + if (typebuf.tb_len && !KeyTyped) + redraw_cmdline = TRUE; /* show mode later */ + else + showmode(); + } + } + + --vgetc_busy; + + return c; +} + +/* + * inchar() - get one character from + * 1. a scriptfile + * 2. the keyboard + * + * As much characters as we can get (upto 'maxlen') are put in "buf" and + * NUL terminated (buffer length must be 'maxlen' + 1). + * Minimum for "maxlen" is 3!!!! + * + * "tb_change_cnt" is the value of typebuf.tb_change_cnt if "buf" points into + * it. When typebuf.tb_change_cnt changes (e.g., when a message is received + * from a remote client) "buf" can no longer be used. "tb_change_cnt" is 0 + * otherwise. + * + * If we got an interrupt all input is read until none is available. + * + * If wait_time == 0 there is no waiting for the char. + * If wait_time == n we wait for n msec for a character to arrive. + * If wait_time == -1 we wait forever for a character to arrive. + * + * Return the number of obtained characters. + * Return -1 when end of input script reached. + */ +int inchar(buf, maxlen, wait_time, tb_change_cnt) +char_u *buf; +int maxlen; +long wait_time; /* milli seconds */ +int tb_change_cnt; +{ + int len = 0; /* init for GCC */ + int retesc = FALSE; /* return ESC with gotint */ + int script_char; + + if (wait_time == -1L || wait_time > 100L) { /* flush output before waiting */ + cursor_on(); + out_flush(); + } + + /* + * Don't reset these when at the hit-return prompt, otherwise a endless + * recursive loop may result (write error in swapfile, hit-return, timeout + * on char wait, flush swapfile, write error....). + */ + if (State != HITRETURN) { + did_outofmem_msg = FALSE; /* display out of memory message (again) */ + did_swapwrite_msg = FALSE; /* display swap file write error again */ + } + undo_off = FALSE; /* restart undo now */ + + /* + * Get a character from a script file if there is one. + * If interrupted: Stop reading script files, close them all. + */ + script_char = -1; + while (scriptin[curscript] != NULL && script_char < 0 + && !ignore_script + ) { + + + if (got_int || (script_char = getc(scriptin[curscript])) < 0) { + /* Reached EOF. + * Careful: closescript() frees typebuf.tb_buf[] and buf[] may + * point inside typebuf.tb_buf[]. Don't use buf[] after this! */ + closescript(); + /* + * When reading script file is interrupted, return an ESC to get + * back to normal mode. + * Otherwise return -1, because typebuf.tb_buf[] has changed. + */ + if (got_int) + retesc = TRUE; + else + return -1; + } else { + buf[0] = script_char; + len = 1; + } + } + + if (script_char < 0) { /* did not get a character from script */ + /* + * If we got an interrupt, skip all previously typed characters and + * return TRUE if quit reading script file. + * Stop reading typeahead when a single CTRL-C was read, + * fill_input_buf() returns this when not able to read from stdin. + * Don't use buf[] here, closescript() may have freed typebuf.tb_buf[] + * and buf may be pointing inside typebuf.tb_buf[]. + */ + if (got_int) { +#define DUM_LEN MAXMAPLEN * 3 + 3 + char_u dum[DUM_LEN + 1]; + + for (;; ) { + len = ui_inchar(dum, DUM_LEN, 0L, 0); + if (len == 0 || (len == 1 && dum[0] == 3)) + break; + } + return retesc; + } + + /* + * Always flush the output characters when getting input characters + * from the user. + */ + out_flush(); + + /* + * Fill up to a third of the buffer, because each character may be + * tripled below. + */ + len = ui_inchar(buf, maxlen / 3, wait_time, tb_change_cnt); + } + + if (typebuf_changed(tb_change_cnt)) + return 0; + + return fix_input_buffer(buf, len, script_char >= 0); +} + +/* + * Fix typed characters for use by vgetc() and check_termcode(). + * buf[] must have room to triple the number of bytes! + * Returns the new length. + */ +int fix_input_buffer(buf, len, script) +char_u *buf; +int len; +int script; /* TRUE when reading from a script */ +{ + int i; + char_u *p = buf; + + /* + * Two characters are special: NUL and K_SPECIAL. + * When compiled With the GUI CSI is also special. + * Replace NUL by K_SPECIAL KS_ZERO KE_FILLER + * Replace K_SPECIAL by K_SPECIAL KS_SPECIAL KE_FILLER + * Replace CSI by K_SPECIAL KS_EXTRA KE_CSI + * Don't replace K_SPECIAL when reading a script file. + */ + for (i = len; --i >= 0; ++p) { + if (p[0] == NUL || (p[0] == K_SPECIAL && !script + /* timeout may generate K_CURSORHOLD */ + && (i < 2 || p[1] != KS_EXTRA || p[2] != + (int)KE_CURSORHOLD) + )) { + mch_memmove(p + 3, p + 1, (size_t)i); + p[2] = K_THIRD(p[0]); + p[1] = K_SECOND(p[0]); + p[0] = K_SPECIAL; + p += 2; + len += 2; + } + } + *p = NUL; /* add trailing NUL */ + return len; +} + +#if defined(USE_INPUT_BUF) || defined(PROTO) +/* + * Return TRUE when bytes are in the input buffer or in the typeahead buffer. + * Normally the input buffer would be sufficient, but the server_to_input_buf() + * or feedkeys() may insert characters in the typeahead buffer while we are + * waiting for input to arrive. + */ +int input_available() { + return !vim_is_input_buf_empty() + || typebuf_was_filled + ; +} + +#endif + +/* + * map[!] : show all key mappings + * map[!] {lhs} : show key mapping for {lhs} + * map[!] {lhs} {rhs} : set key mapping for {lhs} to {rhs} + * noremap[!] {lhs} {rhs} : same, but no remapping for {rhs} + * unmap[!] {lhs} : remove key mapping for {lhs} + * abbr : show all abbreviations + * abbr {lhs} : show abbreviations for {lhs} + * abbr {lhs} {rhs} : set abbreviation for {lhs} to {rhs} + * noreabbr {lhs} {rhs} : same, but no remapping for {rhs} + * unabbr {lhs} : remove abbreviation for {lhs} + * + * maptype: 0 for :map, 1 for :unmap, 2 for noremap. + * + * arg is pointer to any arguments. Note: arg cannot be a read-only string, + * it will be modified. + * + * for :map mode is NORMAL + VISUAL + SELECTMODE + OP_PENDING + * for :map! mode is INSERT + CMDLINE + * for :cmap mode is CMDLINE + * for :imap mode is INSERT + * for :lmap mode is LANGMAP + * for :nmap mode is NORMAL + * for :vmap mode is VISUAL + SELECTMODE + * for :xmap mode is VISUAL + * for :smap mode is SELECTMODE + * for :omap mode is OP_PENDING + * + * for :abbr mode is INSERT + CMDLINE + * for :iabbr mode is INSERT + * for :cabbr mode is CMDLINE + * + * Return 0 for success + * 1 for invalid arguments + * 2 for no match + * 4 for out of mem + * 5 for entry not unique + */ +int do_map(maptype, arg, mode, abbrev) +int maptype; +char_u *arg; +int mode; +int abbrev; /* not a mapping but an abbreviation */ +{ + char_u *keys; + mapblock_T *mp, **mpp; + char_u *rhs; + char_u *p; + int n; + int len = 0; /* init for GCC */ + char_u *newstr; + int hasarg; + int haskey; + int did_it = FALSE; + int did_local = FALSE; + int round; + char_u *keys_buf = NULL; + char_u *arg_buf = NULL; + int retval = 0; + int do_backslash; + int hash; + int new_hash; + mapblock_T **abbr_table; + mapblock_T **map_table; + int unique = FALSE; + int nowait = FALSE; + int silent = FALSE; + int special = FALSE; + int expr = FALSE; + int noremap; + char_u *orig_rhs; + + keys = arg; + map_table = maphash; + abbr_table = &first_abbr; + + /* For ":noremap" don't remap, otherwise do remap. */ + if (maptype == 2) + noremap = REMAP_NONE; + else + noremap = REMAP_YES; + + /* Accept , , ,