diff options
33 files changed, 887 insertions, 174 deletions
diff --git a/.travis.yml b/.travis.yml index 5c600ce823..8ca6f58fec 100644 --- a/.travis.yml +++ b/.travis.yml @@ -48,14 +48,19 @@ env: - FUNCTIONALTEST=functionaltest - CI_TARGET=tests -matrix: +jobs: include: + - stage: sanitizers + os: linux + compiler: clang-3.9 + env: > + CLANG_SANITIZER=ASAN_UBSAN + CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUA=ON" - os: linux - env: CI_TARGET=lint - - os: linux - compiler: gcc-5 - env: GCOV=gcov-5 CMAKE_FLAGS="$CMAKE_FLAGS -DUSE_GCOV=ON" - - os: linux + compiler: clang-3.9 + env: CLANG_SANITIZER=TSAN + - stage: normal builds + os: linux compiler: gcc-5 env: FUNCTIONALTEST=functionaltest-lua - os: linux @@ -64,20 +69,19 @@ matrix: # dependencies in a separate cache. compiler: gcc-5 -m32 env: BUILD_32BIT=ON - - os: linux - compiler: clang-3.9 - env: > - CLANG_SANITIZER=ASAN_UBSAN - CMAKE_FLAGS="$CMAKE_FLAGS -DPREFER_LUAJIT=false" - - os: linux - compiler: clang-3.9 - env: CLANG_SANITIZER=TSAN - os: osx compiler: clang osx_image: xcode7.3 # macOS 10.11 - os: osx compiler: gcc-4.9 osx_image: xcode7.3 # macOS 10.11 + - stage: lint + os: linux + env: CI_TARGET=lint + - stage: coverage + os: linux + compiler: gcc-5 + env: GCOV=gcov-5 CMAKE_FLAGS="$CMAKE_FLAGS -DUSE_GCOV=ON" allow_failures: - env: GCOV=gcov-5 CMAKE_FLAGS="$CMAKE_FLAGS -DUSE_GCOV=ON" fast_finish: true diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d1c0e8fd5..ab7595eb11 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,9 +68,9 @@ set(NVIM_VERSION_PATCH 1) set(NVIM_VERSION_PRERELEASE "-dev") # for package maintainers # API level -set(NVIM_API_LEVEL 2) # Bump this after any API change. +set(NVIM_API_LEVEL 3) # Bump this after any API change. set(NVIM_API_LEVEL_COMPAT 0) # Adjust this after a _breaking_ API change. -set(NVIM_API_PRERELEASE false) +set(NVIM_API_PRERELEASE true) file(TO_CMAKE_PATH ${CMAKE_CURRENT_LIST_DIR}/.git FORCED_GIT_DIR) include(GetGitRevisionDescription) @@ -221,6 +221,11 @@ else() add_definitions(-Wall -Wextra -pedantic -Wno-unused-parameter -Wstrict-prototypes -std=gnu99) + check_c_compiler_flag(-Wimplicit-fallthrough HAS_WIMPLICIT_FALLTHROUGH_FLAG) + if(HAS_WIMPLICIT_FALLTHROUGH_FLAG) + add_definitions(-Wimplicit-fallthrough) + endif() + # On FreeBSD 64 math.h uses unguarded C11 extension, which taints clang # 3.4.1 used there. if(CMAKE_SYSTEM_NAME STREQUAL "FreeBSD") @@ -309,12 +314,18 @@ include_directories(SYSTEM ${LIBUV_INCLUDE_DIRS}) find_package(Msgpack 1.0.0 REQUIRED) include_directories(SYSTEM ${MSGPACK_INCLUDE_DIRS}) -option(PREFER_LUAJIT "Prefer LuaJIT over Lua when compiling executable. Test library always uses luajit." ON) +# Note: The test lib requires LuaJIT; it will be skipped if LuaJIT is missing. +option(PREFER_LUA "Prefer Lua over LuaJIT in the nvim executable." OFF) -find_package(LuaJit REQUIRED) - -if(NOT PREFER_LUAJIT) +if(PREFER_LUA) find_package(Lua REQUIRED) + set(LUA_PREFERRED_INCLUDE_DIRS ${LUA_INCLUDE_DIR}) + set(LUA_PREFERRED_LIBRARIES ${LUA_LIBRARIES}) + find_package(LuaJit) +else() + find_package(LuaJit REQUIRED) + set(LUA_PREFERRED_INCLUDE_DIRS ${LUAJIT_INCLUDE_DIRS}) + set(LUA_PREFERRED_LIBRARIES ${LUAJIT_LIBRARIES}) endif() list(APPEND CMAKE_REQUIRED_INCLUDES "${MSGPACK_INCLUDE_DIRS}") diff --git a/runtime/doc/deprecated.txt b/runtime/doc/deprecated.txt index e77d20172e..26cd5b24cc 100644 --- a/runtime/doc/deprecated.txt +++ b/runtime/doc/deprecated.txt @@ -26,9 +26,6 @@ Events ~ *EncodingChanged* Never fired; 'encoding' is always "utf-8". *FileEncoding* Never fired; equivalent to |EncodingChanged|. -Highlight groups ~ -*hl-VisualNOS* Obsolete. |vim-differences| {Nvim} - Keycodes ~ *<MouseDown>* Use <ScrollWheelUp> instead. *<MouseUp>* Use <ScrollWheelDown> instead. diff --git a/runtime/doc/eval.txt b/runtime/doc/eval.txt index 911fe43819..873200cb30 100644 --- a/runtime/doc/eval.txt +++ b/runtime/doc/eval.txt @@ -4665,10 +4665,23 @@ index({list}, {expr} [, {start} [, {ic}]]) *index()* input({prompt} [, {text} [, {completion}]]) *input()* +input({opts}) The result is a String, which is whatever the user typed on the command-line. The {prompt} argument is either a prompt string, or a blank string (for no prompt). A '\n' can be used in the prompt to start a new line. + + In the second form it accepts a single dictionary with the + following keys, any of which may be omitted: + + Key Default Description ~ + prompt "" Same as {prompt} in the first form. + default "" Same as {text} in the first form. + completion nothing Same as {completion} in the first form. + cancelreturn "" Same as {cancelreturn} from + |inputdialog()|. Also works with + input(). + The highlighting set with |:echohl| is used for the prompt. The input is entered just like a command-line, with the same editing commands and mappings. There is a separate history @@ -4710,6 +4723,7 @@ input({prompt} [, {text} [, {completion}]]) *input()* :endfunction inputdialog({prompt} [, {text} [, {cancelreturn}]]) *inputdialog()* +inputdialog({opts}) Like |input()|, but when the GUI is running and text dialogs are supported, a dialog window pops up to input the text. Example: > @@ -4721,7 +4735,6 @@ inputdialog({prompt} [, {text} [, {cancelreturn}]]) *inputdialog()* omitted an empty string is returned. Hitting <Enter> works like pressing the OK button. Hitting <Esc> works like pressing the Cancel button. - NOTE: Command-line completion is not supported. inputlist({textlist}) *inputlist()* {textlist} must be a |List| of strings. This |List| is diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt index 5452bacffc..8949d27dd6 100644 --- a/runtime/doc/options.txt +++ b/runtime/doc/options.txt @@ -3122,8 +3122,7 @@ A jump table for the options with a short description can be found at |Q_op|. *'highlight'* *'hl'* 'highlight' 'hl' Removed. |vim-differences| global - In nvim the highlight group names for ui highlighting are fixed. - See |highlight-groups|. + The builtin |highlight-groups| cannot be changed. *'hlsearch'* *'hls'* *'nohlsearch'* *'nohls'* 'hlsearch' 'hls' boolean (default on) diff --git a/runtime/doc/syntax.txt b/runtime/doc/syntax.txt index f3f11dcd34..d711aa6a29 100644 --- a/runtime/doc/syntax.txt +++ b/runtime/doc/syntax.txt @@ -4972,6 +4972,8 @@ TabLineSel tab pages line, active tab page label Title titles for output from ":set all", ":autocmd" etc. *hl-Visual* Visual Visual mode selection + *hl-VisualNOS* +VisualNOS Visual mode selection when vim is "Not Owning the Selection". *hl-WarningMsg* WarningMsg warning messages *hl-Whitespace* diff --git a/runtime/doc/vim_diff.txt b/runtime/doc/vim_diff.txt index 4fa84c12b3..9a7789c0f5 100644 --- a/runtime/doc/vim_diff.txt +++ b/runtime/doc/vim_diff.txt @@ -107,8 +107,10 @@ Options: 'cpoptions' flags: |cpo-_| 'guicursor' works in the terminal 'inccommand' shows interactive results for |:substitute|-like commands + 'scrollback' 'statusline' supports unlimited alignment sections 'tabline' %@Func@foo%X can call any function on mouse-click + 'winhighlight' window-local highlights Variables: |v:event| @@ -244,6 +246,10 @@ Lua interface (|if_lua.txt|): - Lua has direct access to Nvim |API| via `vim.api`. - Currently, most legacy Vim features are missing. +|input()| and |inputdialog()| gained support for each other’s features (return +on cancel and completion respectively) via dictionary argument (replaces all +other arguments if used). + ============================================================================== 5. Missing legacy features *nvim-features-missing* @@ -287,9 +293,6 @@ MS-DOS support: 'bioskey' 'conskey' -Highlight groups: - |hl-VisualNOS| - Test functions: test_alloc_fail() test_autochdir() @@ -310,7 +313,7 @@ Other options: 'esckeys' 'guioptions' "t" flag was removed *'guipty'* (Nvim uses pipes and PTYs consistently on all platforms.) - 'highlight' (the builtin default group names are always used) + 'highlight' (the builtin |highlight-groups| cannot be changed) *'imactivatefunc'* *'imaf'* *'imactivatekey'* *'imak'* *'imstatusfunc'* *'imsf'* diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index a5d4170062..c46c0bed6d 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -171,7 +171,7 @@ if(CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN) endif() get_directory_property(gen_includes INCLUDE_DIRECTORIES) -foreach(gen_include ${gen_includes} ${LUAJIT_INCLUDE_DIRS}) +foreach(gen_include ${gen_includes} ${LUA_PREFERRED_INCLUDE_DIRS}) list(APPEND gen_cflags "-I${gen_include}") endforeach() string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type) @@ -367,24 +367,13 @@ if(UNIX) ) endif() -set(NVIM_EXEC_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES}) -set(NVIM_TEST_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES}) +set(NVIM_EXEC_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES} ${LUA_PREFERRED_LIBRARIES}) if(CMAKE_VERSION VERSION_LESS "2.8.8") - if(PREFER_LUAJIT) - include_directories(${LUAJIT_INCLUDE_DIRS}) - else() - message(FATAL_ERROR - "Must support INCLUDE_DIRECTORIES target property to build") - endif() -endif() - -if(PREFER_LUAJIT) - list(APPEND NVIM_EXEC_LINK_LIBRARIES ${LUAJIT_LIBRARIES}) -else() - list(APPEND NVIM_EXEC_LINK_LIBRARIES ${LUA_LIBRARIES}) + # Use include_directories() because INCLUDE_DIRECTORIES target property + # is not supported + include_directories(${LUA_PREFERRED_INCLUDE_DIRS}) endif() -list(APPEND NVIM_TEST_LINK_LIBRARIES ${LUAJIT_LIBRARIES}) # Don't use jemalloc in the unit test library. if(JEMALLOC_FOUND) @@ -396,11 +385,6 @@ add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES}) install_helper(TARGETS nvim) -if(PREFER_LUAJIT) - set(LUA_PREFERRED_INCLUDE_DIRS ${LUAJIT_INCLUDE_DIRS}) -else() - set(LUA_PREFERRED_INCLUDE_DIRS ${LUA_INCLUDE_DIRS}) -endif() set_property(TARGET nvim APPEND PROPERTY INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) @@ -458,8 +442,7 @@ add_library( ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} ) set_property(TARGET libnvim APPEND PROPERTY - INCLUDE_DIRECTORIES ${LUAJIT_INCLUDE_DIRS}) -target_link_libraries(libnvim ${NVIM_TEST_LINK_LIBRARIES}) + INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS}) set_target_properties( libnvim PROPERTIES @@ -471,28 +454,32 @@ set_property( APPEND_STRING PROPERTY COMPILE_FLAGS " -DMAKE_LIB " ) -add_library( - nvim-test - MODULE - EXCLUDE_FROM_ALL - ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES} - ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} - ${UNIT_TEST_FIXTURES} -) -target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES}) -set_property( - TARGET nvim-test - APPEND PROPERTY INCLUDE_DIRECTORIES ${LUAJIT_INCLUDE_DIRS} -) -set_target_properties( - nvim-test - PROPERTIES - POSITION_INDEPENDENT_CODE ON -) -set_property( - TARGET nvim-test - APPEND_STRING PROPERTY COMPILE_FLAGS " -DUNIT_TESTING " -) +if(LUAJIT_FOUND) + set(NVIM_TEST_LINK_LIBRARIES ${NVIM_LINK_LIBRARIES} ${LUAJIT_LIBRARIES}) + add_library( + nvim-test + MODULE + EXCLUDE_FROM_ALL + ${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES} + ${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS} + ${UNIT_TEST_FIXTURES} + ) + target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES}) + target_link_libraries(libnvim ${NVIM_TEST_LINK_LIBRARIES}) + set_property( + TARGET nvim-test + APPEND PROPERTY INCLUDE_DIRECTORIES ${LUAJIT_INCLUDE_DIRS} + ) + set_target_properties( + nvim-test + PROPERTIES + POSITION_INDEPENDENT_CODE ON + ) + set_property( + TARGET nvim-test + APPEND_STRING PROPERTY COMPILE_FLAGS " -DUNIT_TESTING " + ) +endif() if(CLANG_ASAN_UBSAN) message(STATUS "Enabling Clang address sanitizer and undefined behavior sanitizer for nvim.") diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index d0bb840b8d..1fedaf30ef 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -15,6 +15,7 @@ #include "nvim/api/private/defs.h" #include "nvim/api/buffer.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/lua/executor.h" #include "nvim/vim.h" #include "nvim/buffer.h" #include "nvim/file_search.h" @@ -254,6 +255,25 @@ free_vim_args: return rv; } +/// Execute lua code. Parameters might be passed, they are available inside +/// the chunk as `...`. The chunk can return a value. +/// +/// To evaluate an expression, it must be prefixed with "return ". For +/// instance, to call a lua function with arguments sent in and get its +/// return value back, use the code "return my_function(...)". +/// +/// @param code lua code to execute +/// @param args Arguments to the code +/// @param[out] err Details of an error encountered while parsing +/// or executing the lua code. +/// +/// @return Return value of lua code if present or NIL. +Object nvim_execute_lua(String code, Array args, Error *err) + FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY +{ + return executor_exec_lua_api(code, args, err); +} + /// Calculates the number of display cells occupied by `text`. /// <Tab> counts as one cell. /// diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index f54979eb1e..e3897e3929 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -3607,6 +3607,7 @@ int build_stl_str_hl( case STL_OFFSET_X: base = kNumBaseHexadecimal; + // fallthrough case STL_OFFSET: { long l = ml_find_line_or_offset(wp->w_buffer, wp->w_cursor.lnum, NULL); @@ -3617,6 +3618,7 @@ int build_stl_str_hl( } case STL_BYTEVAL_X: base = kNumBaseHexadecimal; + // fallthrough case STL_BYTEVAL: num = byteval; if (num == NL) diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 678fa851eb..bfdec90a32 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -4916,14 +4916,17 @@ static unsigned quote_meta(char_u *dest, char_u *src, int len) if (ctrl_x_mode == CTRL_X_DICTIONARY || ctrl_x_mode == CTRL_X_THESAURUS) break; + // fallthrough case '~': if (!p_magic) /* quote these only if magic is set */ break; + // fallthrough case '\\': if (ctrl_x_mode == CTRL_X_DICTIONARY || ctrl_x_mode == CTRL_X_THESAURUS) break; - case '^': /* currently it's not needed. */ + // fallthrough + case '^': // currently it's not needed. case '$': m++; if (dest != NULL) diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 56a6632fad..75cf564f55 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -9596,13 +9596,15 @@ static void f_getcwd(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (from) { break; } - case kCdScopeTab: // FALLTHROUGH + // fallthrough + case kCdScopeTab: assert(tp); from = tp->tp_localdir; if (from) { break; } - case kCdScopeGlobal: // FALLTHROUGH + // fallthrough + case kCdScopeGlobal: if (globaldir) { // `globaldir` is not always set. from = globaldir; } else if (os_dirname(cwd, MAXPATHL) == FAIL) { // Get the OS CWD. @@ -10978,81 +10980,122 @@ void get_user_input(const typval_T *const argvars, typval_T *const rettv, const bool inputdialog) FUNC_ATTR_NONNULL_ALL { - const char *prompt = tv_get_string_chk(&argvars[0]); - int cmd_silent_save = cmd_silent; - int xp_type = EXPAND_NOTHING; - char_u *xp_arg = NULL; - rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; - 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. - const char *p = strrchr(prompt, '\n'); - if (p == NULL) { - p = prompt; - } else { - p++; - msg_start(); - msg_clr_eos(); - msg_puts_attr_len(prompt, p - prompt, echo_attr); - msg_didout = false; - msg_starthere(); + const char *prompt = ""; + const char *defstr = ""; + const char *cancelreturn = NULL; + const char *xp_name = NULL; + char prompt_buf[NUMBUFLEN]; + char defstr_buf[NUMBUFLEN]; + char cancelreturn_buf[NUMBUFLEN]; + char xp_name_buf[NUMBUFLEN]; + if (argvars[0].v_type == VAR_DICT) { + if (argvars[1].v_type != VAR_UNKNOWN) { + emsgf(_("E5050: {opts} must be the only argument")); + return; + } + const dict_T *const dict = argvars[0].vval.v_dict; + prompt = tv_dict_get_string_buf_chk(dict, S_LEN("prompt"), prompt_buf, ""); + if (prompt == NULL) { + return; + } + defstr = tv_dict_get_string_buf_chk(dict, S_LEN("default"), defstr_buf, ""); + if (defstr == NULL) { + return; + } + char def[1] = { 0 }; + cancelreturn = tv_dict_get_string_buf_chk(dict, S_LEN("cancelreturn"), + cancelreturn_buf, def); + if (cancelreturn == NULL) { // error + return; + } + if (*cancelreturn == NUL) { + cancelreturn = NULL; + } + xp_name = tv_dict_get_string_buf_chk(dict, S_LEN("completion"), + xp_name_buf, def); + if (xp_name == NULL) { // error + return; + } + if (xp_name == def) { // default to NULL + xp_name = NULL; + } + } else { + prompt = tv_get_string_buf_chk(&argvars[0], prompt_buf); + if (prompt == NULL) { + return; } - cmdline_row = msg_row; - - const char *defstr = ""; - char buf[NUMBUFLEN]; if (argvars[1].v_type != VAR_UNKNOWN) { - defstr = tv_get_string_buf_chk(&argvars[1], buf); - if (defstr != NULL) { - stuffReadbuffSpec(defstr); + defstr = tv_get_string_buf_chk(&argvars[1], defstr_buf); + if (defstr == NULL) { + return; } - - if (!inputdialog && argvars[2].v_type != VAR_UNKNOWN) { - char buf2[NUMBUFLEN]; - // input() with a third argument: completion - rettv->vval.v_string = NULL; - - const char *const xp_name = tv_get_string_buf_chk(&argvars[2], buf2); - if (xp_name == NULL) { + if (argvars[2].v_type != VAR_UNKNOWN) { + const char *const arg2 = tv_get_string_buf_chk(&argvars[2], + cancelreturn_buf); + if (arg2 == NULL) { return; } - - const int xp_namelen = (int)strlen(xp_name); - - uint32_t argt; - if (parse_compl_arg((char_u *)xp_name, xp_namelen, &xp_type, &argt, - &xp_arg) == FAIL) { - return; + if (inputdialog) { + cancelreturn = arg2; + } else { + xp_name = arg2; } } } + } - if (defstr != NULL) { - int save_ex_normal_busy = ex_normal_busy; - ex_normal_busy = 0; - rettv->vval.v_string = - getcmdline_prompt(inputsecret_flag ? NUL : '@', (char_u *)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) { - char buf[NUMBUFLEN]; - rettv->vval.v_string = (char_u *)xstrdup(tv_get_string_buf( - &argvars[2], buf)); + int xp_type = EXPAND_NOTHING; + char *xp_arg = NULL; + if (xp_name != NULL) { + // input() with a third argument: completion + const int xp_namelen = (int)strlen(xp_name); + + uint32_t argt; + if (parse_compl_arg((char_u *)xp_name, xp_namelen, &xp_type, + &argt, (char_u **)&xp_arg) == FAIL) { + return; } + } - xfree(xp_arg); + int cmd_silent_save = cmd_silent; - /* since the user typed this, no need to wait for return */ - need_wait_return = FALSE; - msg_didout = FALSE; + cmd_silent = false; // Want to see the prompt. + // Only the part of the message after the last NL is considered as + // prompt for the command line. + const char *p = strrchr(prompt, '\n'); + if (p == NULL) { + p = prompt; + } else { + p++; + msg_start(); + msg_clr_eos(); + msg_puts_attr_len(prompt, p - prompt, echo_attr); + msg_didout = false; + msg_starthere(); } + cmdline_row = msg_row; + + stuffReadbuffSpec(defstr); + + int save_ex_normal_busy = ex_normal_busy; + ex_normal_busy = 0; + rettv->vval.v_string = + getcmdline_prompt(inputsecret_flag ? NUL : '@', (char_u *)p, echo_attr, + xp_type, (char_u *)xp_arg); + ex_normal_busy = save_ex_normal_busy; + + if (rettv->vval.v_string == NULL && cancelreturn != NULL) { + rettv->vval.v_string = (char_u *)xstrdup(cancelreturn); + } + + xfree(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; } diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 786b766689..f017f57b12 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1210,7 +1210,8 @@ char *tv_dict_get_string(const dict_T *const d, const char *const key, /// /// @param[in] d Dictionary to get item from. /// @param[in] key Dictionary key. -/// @param[in] numbuf Numbuf for. +/// @param[in] numbuf Buffer for non-string items converted to strings, at +/// least of #NUMBUFLEN length. /// /// @return NULL if key does not exist, empty string in case of type error, /// string item value otherwise. @@ -1225,6 +1226,32 @@ const char *tv_dict_get_string_buf(const dict_T *const d, const char *const key, return tv_get_string_buf(&di->di_tv, numbuf); } +/// Get a string item from a dictionary +/// +/// @param[in] d Dictionary to get item from. +/// @param[in] key Dictionary key. +/// @param[in] key_len Key length. +/// @param[in] numbuf Buffer for non-string items converted to strings, at +/// least of #NUMBUFLEN length. +/// @param[in] def Default return when key does not exist. +/// +/// @return `def` when key does not exist, +/// NULL in case of type error, +/// string item value in case of success. +const char *tv_dict_get_string_buf_chk(const dict_T *const d, + const char *const key, + const ptrdiff_t key_len, + char *const numbuf, + const char *const def) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + const dictitem_T *const di = tv_dict_find(d, key, key_len); + if (di == NULL) { + return def; + } + return tv_get_string_buf_chk(&di->di_tv, numbuf); +} + /// Get a function from a dictionary /// /// @param[in] d Dictionary to get callback from. @@ -1869,7 +1896,7 @@ void tv_free(typval_T *tv) } case VAR_FUNC: { func_unref(tv->vval.v_string); - // FALLTHROUGH + FALLTHROUGH; } case VAR_STRING: { xfree(tv->vval.v_string); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 018a228843..a528a65abb 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -4878,8 +4878,9 @@ void fix_help_buffer(void) continue; e1 = vim_strrchr(t1, '.'); e2 = vim_strrchr(path_tail(f2), '.'); - if (e1 == NUL || e2 == NUL) + if (e1 == NULL || e2 == NULL) { continue; + } if (fnamecmp(e1, ".txt") != 0 && fnamecmp(e1, fname + 4) != 0) { /* Not .txt and not .abx, remove it. */ @@ -5949,9 +5950,8 @@ void set_context_in_sign_cmd(expand_T *xp, char_u *arg) // :sign define {name} {args}... {last}= // | | // last p - if (p == NUL) - { - /* Expand last argument name (before equal sign). */ + if (p == NULL) { + // Expand last argument name (before equal sign). xp->xp_pattern = last; switch (cmd_idx) { diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index fe45ba4568..0c14bf4255 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -1268,6 +1268,7 @@ static int command_line_handle_key(CommandLineState *s) } return command_line_changed(s); } + // fallthrough case K_UP: case K_DOWN: @@ -5401,7 +5402,7 @@ char *script_get(exarg_T *const eap, size_t *const lenp) if (cmd[0] != '<' || cmd[1] != '<' || eap->getline == NULL) { *lenp = STRLEN(eap->arg); - return xmemdupz(eap->arg, *lenp); + return eap->skip ? NULL : xmemdupz(eap->arg, *lenp); } garray_T ga = { .ga_data = NULL, .ga_len = 0 }; diff --git a/src/nvim/func_attr.h b/src/nvim/func_attr.h index 9146a1a8ea..cc94a41f80 100644 --- a/src/nvim/func_attr.h +++ b/src/nvim/func_attr.h @@ -89,6 +89,10 @@ # undef FUNC_ATTR_NONNULL_RET #endif +#ifdef FUNC_ATTR_NORETURN +# undef FUNC_ATTR_NORETURN +#endif + #ifndef DID_REAL_ATTR # define DID_REAL_ATTR # ifdef __GNUC__ @@ -107,6 +111,7 @@ # define REAL_FATTR_UNUSED __attribute__((unused)) # define REAL_FATTR_NONNULL_ALL __attribute__((nonnull)) # define REAL_FATTR_NONNULL_ARG(...) __attribute__((nonnull(__VA_ARGS__))) +# define REAL_FATTR_NORETURN __attribute__((noreturn)) # ifdef __clang__ // clang only @@ -176,6 +181,10 @@ # ifndef REAL_FATTR_NONNULL_RET # define REAL_FATTR_NONNULL_RET # endif + +# ifndef REAL_FATTR_NORETURN +# define REAL_FATTR_NORETURN +# endif #endif #ifdef DEFINE_FUNC_ATTRIBUTES @@ -196,6 +205,7 @@ # define FUNC_ATTR_NONNULL_ALL REAL_FATTR_NONNULL_ALL # define FUNC_ATTR_NONNULL_ARG(...) REAL_FATTR_NONNULL_ARG(__VA_ARGS__) # define FUNC_ATTR_NONNULL_RET REAL_FATTR_NONNULL_RET +# define FUNC_ATTR_NORETURN REAL_FATTR_NORETURN #elif !defined(DO_NOT_DEFINE_EMPTY_ATTRIBUTES) # define FUNC_ATTR_MALLOC # define FUNC_ATTR_ALLOC_SIZE(x) @@ -209,4 +219,5 @@ # define FUNC_ATTR_NONNULL_ALL # define FUNC_ATTR_NONNULL_ARG(...) # define FUNC_ATTR_NONNULL_RET +# define FUNC_ATTR_NORETURN #endif diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 94f4f27a0c..a3657f2122 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -515,7 +515,7 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_MC] = "ColorColumn", [HLF_QFL] = "QuickFixLine", [HLF_0] = "Whitespace", - [HLF_INACTIVE] = "NormalNC" + [HLF_INACTIVE] = "NormalNC", }); diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 7cf326aef5..a7d5af36a1 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -373,6 +373,46 @@ static int nlua_eval_lua_string(lua_State *const lstate) return 0; } +/// Evaluate lua string +/// +/// Expects four values on the stack: string to evaluate, pointer to args array, +/// and locations where result and error are saved, respectively. Always +/// returns nothing (from the lua point of view). +static int nlua_exec_lua_string_api(lua_State *const lstate) + FUNC_ATTR_NONNULL_ALL +{ + const String *str = (const String *)lua_touserdata(lstate, 1); + const Array *args = (const Array *)lua_touserdata(lstate, 2); + Object *retval = (Object *)lua_touserdata(lstate, 3); + Error *err = (Error *)lua_touserdata(lstate, 4); + + lua_pop(lstate, 4); + + if (luaL_loadbuffer(lstate, str->data, str->size, "<nvim>")) { + size_t len; + const char *str = lua_tolstring(lstate, -1, &len); + api_set_error(err, kErrorTypeValidation, + "Error loading lua: %.*s", (int)len, str); + return 0; + } + + for (size_t i = 0; i < args->size; i++) { + nlua_push_Object(lstate, args->items[i]); + } + + if (lua_pcall(lstate, (int)args->size, 1, 0)) { + size_t len; + const char *str = lua_tolstring(lstate, -1, &len); + api_set_error(err, kErrorTypeException, + "Error executing lua: %.*s", (int)len, str); + return 0; + } + + *retval = nlua_pop_Object(lstate, err); + + return 0; +} + /// Print as a Vim message /// /// @param lstate Lua interpreter state. @@ -516,6 +556,28 @@ void executor_eval_lua(const String str, typval_T *const arg, (void *)&str, arg, ret_tv); } +/// Execute lua string +/// +/// Used for nvim_execute_lua(). +/// +/// @param[in] str String to execute. +/// @param[in] args array of ... args +/// @param[out] err Location where error will be saved. +/// +/// @return Return value of the execution. +Object executor_exec_lua_api(const String str, const Array args, Error *err) +{ + if (global_lstate == NULL) { + global_lstate = init_lua(); + } + + Object retval = NIL; + NLUA_CALL_C_FUNCTION_4(global_lstate, nlua_exec_lua_string_api, 0, + (void *)&str, (void *)&args, &retval, err); + return retval; +} + + /// Run lua string /// /// Used for :lua. diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 214af82422..9ab6dc5d2b 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -153,4 +153,22 @@ #define STR_(x) #x #define STR(x) STR_(x) +#ifndef __has_attribute +# define NVIM_HAS_ATTRIBUTE(x) 0 +#elif defined(__clang__) && __clang__ == 1 \ + && (__clang_major__ < 3 || (__clang_major__ == 3 && __clang_minor__ <= 5)) +// Starting in Clang 3.6, __has_attribute was fixed to only report true for +// GNU-style attributes. Prior to that, it reported true if _any_ backend +// supported the attribute. +# define NVIM_HAS_ATTRIBUTE(x) 0 +#else +# define NVIM_HAS_ATTRIBUTE __has_attribute +#endif + +#if NVIM_HAS_ATTRIBUTE(fallthrough) +# define FALLTHROUGH __attribute__((fallthrough)) +#else +# define FALLTHROUGH +#endif + #endif // NVIM_MACROS_H diff --git a/src/nvim/normal.c b/src/nvim/normal.c index f73e3079b9..050020d79d 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -1861,6 +1861,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } else { bangredo = true; // do_bang() will put cmd in redo buffer. } + // fallthrough case OP_INDENT: case OP_COLON: diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index c5a42204be..7cf0d7817c 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -133,7 +133,7 @@ void mch_free_acl(vim_acl_T aclent) } #endif -void mch_exit(int r) +void mch_exit(int r) FUNC_ATTR_NORETURN { exiting = true; diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 85fae9d82e..24c156d2ba 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -634,6 +634,7 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) config |= CLASS_o7; break; } + return FAIL; case 'a': if (*(p + 2) == 'z') { config |= CLASS_az; @@ -642,6 +643,7 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) config |= CLASS_af; break; } + return FAIL; case 'A': if (*(p + 2) == 'Z') { config |= CLASS_AZ; @@ -650,7 +652,7 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) config |= CLASS_AF; break; } - /* FALLTHROUGH */ + return FAIL; default: return FAIL; } @@ -4194,6 +4196,7 @@ skip_add: subs = addstate(l, state->out, subs, pim, off_arg); break; } + // fallthrough case NFA_MCLOSE1: case NFA_MCLOSE2: case NFA_MCLOSE3: diff --git a/src/nvim/shada.c b/src/nvim/shada.c index a6d8cb6563..e1879ca8c0 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -2047,7 +2047,7 @@ static inline ShaDaWriteResult shada_read_when_writing( } case kSDReadStatusNotShaDa: { ret = kSDWriteReadNotShada; - // fallthrough + FALLTHROUGH; } case kSDReadStatusReadError: { return ret; diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 64ad0f0103..6832622cdf 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -134,7 +134,7 @@ else endif " Names of flaky tests. -let s:flaky = ['Test_with_partial_callback'] +let s:flaky = ['Test_with_partial_callback()'] " Locate Test_ functions and execute them. set nomore diff --git a/test/functional/api/vim_spec.lua b/test/functional/api/vim_spec.lua index 282ecbfd87..61ec6ea829 100644 --- a/test/functional/api/vim_spec.lua +++ b/test/functional/api/vim_spec.lua @@ -81,6 +81,36 @@ describe('api', function() end) end) + describe('nvim_execute_lua', function() + it('works', function() + meths.execute_lua('vim.api.nvim_set_var("test", 3)', {}) + eq(3, meths.get_var('test')) + + eq(17, meths.execute_lua('a, b = ...\nreturn a + b', {10,7})) + + eq(NIL, meths.execute_lua('function xx(a,b)\nreturn a..b\nend',{})) + eq("xy", meths.execute_lua('return xx(...)', {'x','y'})) + end) + + it('reports errors', function() + eq({false, 'Error loading lua: [string "<nvim>"]:1: '.. + "'=' expected near '+'"}, + meth_pcall(meths.execute_lua, 'a+*b', {})) + + eq({false, 'Error loading lua: [string "<nvim>"]:1: '.. + "unexpected symbol near '1'"}, + meth_pcall(meths.execute_lua, '1+2', {})) + + eq({false, 'Error loading lua: [string "<nvim>"]:1: '.. + "unexpected symbol"}, + meth_pcall(meths.execute_lua, 'aa=bb\0', {})) + + eq({false, 'Error executing lua: [string "<nvim>"]:1: '.. + "attempt to call global 'bork' (a nil value)"}, + meth_pcall(meths.execute_lua, 'bork()', {})) + end) + end) + describe('nvim_input', function() it("VimL error: does NOT fail, updates v:errmsg", function() local status, _ = pcall(nvim, "input", ":call bogus_fn()<CR>") diff --git a/test/functional/eval/input_spec.lua b/test/functional/eval/input_spec.lua index 393fc10175..74ad32bc6c 100644 --- a/test/functional/eval/input_spec.lua +++ b/test/functional/eval/input_spec.lua @@ -1,9 +1,13 @@ local helpers = require('test.functional.helpers')(after_each) local Screen = require('test.functional.ui.screen') +local eq = helpers.eq local feed = helpers.feed +local meths = helpers.meths local clear = helpers.clear +local source = helpers.source local command = helpers.command +local exc_exec = helpers.exc_exec local screen @@ -11,28 +15,352 @@ before_each(function() clear() screen = Screen.new(25, 5) screen:attach() + source([[ + hi Test ctermfg=Red guifg=Red term=bold + function CustomCompl(...) + return 'TEST' + endfunction + function CustomListCompl(...) + return ['FOO'] + endfunction + ]]) + screen:set_default_attr_ids({ + EOB={bold = true, foreground = Screen.colors.Blue1}, + T={foreground=Screen.colors.Red}, + }) end) describe('input()', function() - it('works correctly with multiline prompts', function() + it('works with multiline prompts', function() feed([[:call input("Test\nFoo")<CR>]]) screen:expect([[ - {1:~ }| - {1:~ }| - {1:~ }| + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| Test | Foo^ | - ]], {{bold=true, foreground=Screen.colors.Blue}}) + ]]) end) - it('works correctly with multiline prompts and :echohl', function() - command('hi Test ctermfg=Red guifg=Red term=bold') + it('works with multiline prompts and :echohl', function() feed([[:echohl Test | call input("Test\nFoo")<CR>]]) screen:expect([[ - {1:~ }| - {1:~ }| - {1:~ }| - {2:Test} | - {2:Foo}^ | - ]], {{bold=true, foreground=Screen.colors.Blue}, {foreground=Screen.colors.Red}}) + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:Test} | + {T:Foo}^ | + ]]) + command('redraw!') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:Foo}^ | + ]]) + end) + it('allows unequal numeric arguments when using multiple args', function() + command('echohl Test') + feed([[:call input(1, 2)<CR>]]) + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:1}2^ | + ]]) + feed('<BS>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:1}^ | + ]]) + end) + it('allows unequal numeric values when using {opts} dictionary', function() + command('echohl Test') + meths.set_var('opts', {prompt=1, default=2, cancelreturn=3}) + feed([[:echo input(opts)<CR>]]) + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:1}2^ | + ]]) + feed('<BS>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:1}^ | + ]]) + feed('<Esc>') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:3} | + ]]) + end) + it('works with redraw', function() + command('echohl Test') + meths.set_var('opts', {prompt='Foo>', default='Bar'}) + feed([[:echo inputdialog(opts)<CR>]]) + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:Foo>}Bar^ | + ]]) + command('redraw!') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:Foo>}Bar^ | + ]]) + feed('<BS>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:Foo>}Ba^ | + ]]) + command('redraw!') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:Foo>}Ba^ | + ]]) + end) + it('allows omitting everything with dictionary argument', function() + command('echohl Test') + feed([[:call input({})<CR>]]) + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + ^ | + ]]) + end) + it('supports completion', function() + feed(':let var = input("", "", "custom,CustomCompl")<CR>') + feed('<Tab><CR>') + eq('TEST', meths.get_var('var')) + + feed(':let var = input({"completion": "customlist,CustomListCompl"})<CR>') + feed('<Tab><CR>') + eq('FOO', meths.get_var('var')) + end) + it('supports cancelreturn', function() + feed(':let var = input({"cancelreturn": "BAR"})<CR>') + feed('<Esc>') + eq('BAR', meths.get_var('var')) + end) + it('supports default string', function() + feed(':let var = input("", "DEF1")<CR>') + feed('<CR>') + eq('DEF1', meths.get_var('var')) + + feed(':let var = input({"default": "DEF2"})<CR>') + feed('<CR>') + eq('DEF2', meths.get_var('var')) + end) + it('errors out on invalid inputs', function() + eq('Vim(call):E730: using List as a String', + exc_exec('call input([])')) + eq('Vim(call):E730: using List as a String', + exc_exec('call input("", [])')) + eq('Vim(call):E730: using List as a String', + exc_exec('call input("", "", [])')) + eq('Vim(call):E730: using List as a String', + exc_exec('call input({"prompt": []})')) + eq('Vim(call):E730: using List as a String', + exc_exec('call input({"cancelreturn": []})')) + eq('Vim(call):E730: using List as a String', + exc_exec('call input({"default": []})')) + eq('Vim(call):E730: using List as a String', + exc_exec('call input({"completion": []})')) + eq('Vim(call):E5050: {opts} must be the only argument', + exc_exec('call input({}, "default")')) + eq('Vim(call):E118: Too many arguments for function: input', + exc_exec('call input("prompt> ", "default", "file", "extra")')) + end) +end) +describe('inputdialog()', function() + it('works with multiline prompts', function() + feed([[:call inputdialog("Test\nFoo")<CR>]]) + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + Test | + Foo^ | + ]]) + end) + it('works with multiline prompts and :echohl', function() + feed([[:echohl Test | call inputdialog("Test\nFoo")<CR>]]) + screen:expect([[ + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:Test} | + {T:Foo}^ | + ]]) + command('redraw!') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:Foo}^ | + ]]) + end) + it('allows unequal numeric arguments when using multiple args', function() + command('echohl Test') + feed([[:call inputdialog(1, 2)<CR>]]) + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:1}2^ | + ]]) + feed('<BS>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:1}^ | + ]]) + end) + it('allows unequal numeric values when using {opts} dictionary', function() + command('echohl Test') + meths.set_var('opts', {prompt=1, default=2, cancelreturn=3}) + feed([[:echo input(opts)<CR>]]) + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:1}2^ | + ]]) + feed('<BS>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:1}^ | + ]]) + feed('<Esc>') + screen:expect([[ + ^ | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:3} | + ]]) + end) + it('works with redraw', function() + command('echohl Test') + meths.set_var('opts', {prompt='Foo>', default='Bar'}) + feed([[:echo input(opts)<CR>]]) + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:Foo>}Bar^ | + ]]) + command('redraw!') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:Foo>}Bar^ | + ]]) + feed('<BS>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:Foo>}Ba^ | + ]]) + command('redraw!') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + {T:Foo>}Ba^ | + ]]) + end) + it('allows omitting everything with dictionary argument', function() + command('echohl Test') + feed(':echo inputdialog({})<CR>') + screen:expect([[ + | + {EOB:~ }| + {EOB:~ }| + {EOB:~ }| + ^ | + ]]) + end) + it('supports completion', function() + feed(':let var = inputdialog({"completion": "customlist,CustomListCompl"})<CR>') + feed('<Tab><CR>') + eq('FOO', meths.get_var('var')) + end) + it('supports cancelreturn', function() + feed(':let var = inputdialog("", "", "CR1")<CR>') + feed('<Esc>') + eq('CR1', meths.get_var('var')) + + feed(':let var = inputdialog({"cancelreturn": "BAR"})<CR>') + feed('<Esc>') + eq('BAR', meths.get_var('var')) + end) + it('supports default string', function() + feed(':let var = inputdialog("", "DEF1")<CR>') + feed('<CR>') + eq('DEF1', meths.get_var('var')) + + feed(':let var = inputdialog({"default": "DEF2"})<CR>') + feed('<CR>') + eq('DEF2', meths.get_var('var')) + end) + it('errors out on invalid inputs', function() + eq('Vim(call):E730: using List as a String', + exc_exec('call inputdialog([])')) + eq('Vim(call):E730: using List as a String', + exc_exec('call inputdialog("", [])')) + eq('Vim(call):E730: using List as a String', + exc_exec('call inputdialog("", "", [])')) + eq('Vim(call):E730: using List as a String', + exc_exec('call inputdialog({"prompt": []})')) + eq('Vim(call):E730: using List as a String', + exc_exec('call inputdialog({"cancelreturn": []})')) + eq('Vim(call):E730: using List as a String', + exc_exec('call inputdialog({"default": []})')) + eq('Vim(call):E730: using List as a String', + exc_exec('call inputdialog({"completion": []})')) + eq('Vim(call):E5050: {opts} must be the only argument', + exc_exec('call inputdialog({}, "default")')) + eq('Vim(call):E118: Too many arguments for function: inputdialog', + exc_exec('call inputdialog("prompt> ", "default", "file", "extra")')) end) end) diff --git a/test/functional/ex_cmds/script_spec.lua b/test/functional/ex_cmds/script_spec.lua new file mode 100644 index 0000000000..4e57d2755d --- /dev/null +++ b/test/functional/ex_cmds/script_spec.lua @@ -0,0 +1,75 @@ +local helpers = require('test.functional.helpers')(after_each) + +local eq = helpers.eq +local neq = helpers.neq +local meths = helpers.meths +local clear = helpers.clear +local dedent = helpers.dedent +local source = helpers.source +local exc_exec = helpers.exc_exec +local missing_provider = helpers.missing_provider + +before_each(clear) + +describe('script_get-based command', function() + local garbage = ')}{+*({}]*[;(+}{&[]}{*])(' + + local function test_garbage_exec(cmd, check_neq) + describe(cmd, function() + it('works correctly when skipping oneline variant', function() + eq(true, pcall(source, (dedent([[ + if 0 + %s %s + endif + ]])):format(cmd, garbage))) + eq('', meths.command_output('messages')) + if check_neq then + neq(0, exc_exec(dedent([[ + %s %s + ]])):format(cmd, garbage)) + end + end) + it('works correctly when skipping HEREdoc variant', function() + eq(true, pcall(source, (dedent([[ + if 0 + %s << EOF + %s + EOF + endif + ]])):format(cmd, garbage))) + eq('', meths.command_output('messages')) + if check_neq then + eq(true, pcall(source, (dedent([[ + let g:exc = 0 + try + %s << EOF + %s + EOF + catch + let g:exc = v:exception + endtry + ]])):format(cmd, garbage))) + neq(0, meths.get_var('exc')) + end + end) + end) + end + + clear() + + -- Built-in scripts + test_garbage_exec('lua', true) + + -- Provider-based scripts + test_garbage_exec('ruby', not missing_provider('ruby')) + test_garbage_exec('python', not missing_provider('python')) + test_garbage_exec('python3', not missing_provider('python3')) + + -- Missing scripts + test_garbage_exec('tcl', false) + test_garbage_exec('mzscheme', false) + test_garbage_exec('perl', false) + + -- Not really a script + test_garbage_exec('xxxinvalidlanguagexxx', true) +end) diff --git a/test/functional/helpers.lua b/test/functional/helpers.lua index 1be70f917c..b03840b3fe 100644 --- a/test/functional/helpers.lua +++ b/test/functional/helpers.lua @@ -566,6 +566,19 @@ local function get_pathsep() return funcs.fnamemodify('.', ':p'):sub(-1) end +local function missing_provider(provider) + if provider == 'ruby' then + local prog = funcs['provider#' .. provider .. '#Detect']() + return prog == '' and (provider .. ' not detected') or false + elseif provider == 'python' or provider == 'python3' then + local py_major_version = (provider == 'python3' and 3 or 2) + local errors = funcs['provider#pythonx#Detect'](py_major_version)[2] + return errors ~= '' and errors or false + else + assert(false, 'Unknown provider: ' .. provider) + end +end + local module = { prepend_argv = prepend_argv, clear = clear, @@ -632,6 +645,7 @@ local module = { meth_pcall = meth_pcall, NIL = mpack.NIL, get_pathsep = get_pathsep, + missing_provider = missing_provider, } return function(after_each) diff --git a/test/functional/legacy/036_regexp_character_classes_spec.lua b/test/functional/legacy/036_regexp_character_classes_spec.lua index 110f7dd852..38e8145d1c 100644 --- a/test/functional/legacy/036_regexp_character_classes_spec.lua +++ b/test/functional/legacy/036_regexp_character_classes_spec.lua @@ -275,4 +275,16 @@ describe('character classes in regexp', function() diff(sixlines(string.sub(punct1, 1)..digits..punct2..upper..punct3.. lower..punct4..ctrl2..iso_text)) end) + it('does not convert character class ranges to an incorrect class', function() + source([[ + 1 s/\%#=0[0-z]//g + 2 s/\%#=1[0-z]//g + 3 s/\%#=2[0-z]//g + 4 s/\%#=0[^0-z]//g + 5 s/\%#=1[^0-z]//g + 6 s/\%#=2[^0-z]//g + ]]) + diff(string.rep(ctrl1..punct1..punct4..ctrl2..iso_text..'\n', 3) + ..string.rep(digits..punct2..upper..punct3..lower..'\n', 3)) + end) end) diff --git a/test/functional/provider/python3_spec.lua b/test/functional/provider/python3_spec.lua index 89a546675f..aa50f53451 100644 --- a/test/functional/provider/python3_spec.lua +++ b/test/functional/provider/python3_spec.lua @@ -3,14 +3,14 @@ local eval, command, feed = helpers.eval, helpers.command, helpers.feed local eq, clear, insert = helpers.eq, helpers.clear, helpers.insert local expect, write_file = helpers.expect, helpers.write_file local feed_command = helpers.feed_command +local missing_provider = helpers.missing_provider do clear() - command('let [g:interp, g:errors] = provider#pythonx#Detect(3)') - local errors = eval('g:errors') - if errors ~= '' then + local err = missing_provider('python3') + if err then pending( - 'Python 3 (or the Python 3 neovim module) is broken or missing:\n' .. errors, + 'Python 3 (or the Python 3 neovim module) is broken or missing:\n' .. err, function() end) return end diff --git a/test/functional/provider/python_spec.lua b/test/functional/provider/python_spec.lua index 94dfa90ea8..25f5e0a6d0 100644 --- a/test/functional/provider/python_spec.lua +++ b/test/functional/provider/python_spec.lua @@ -12,14 +12,14 @@ local command = helpers.command local exc_exec = helpers.exc_exec local write_file = helpers.write_file local curbufmeths = helpers.curbufmeths +local missing_provider = helpers.missing_provider do clear() - command('let [g:interp, g:errors] = provider#pythonx#Detect(2)') - local errors = meths.get_var('errors') - if errors ~= '' then + local err = missing_provider('python') + if err then pending( - 'Python 2 (or the Python 2 neovim module) is broken or missing:\n' .. errors, + 'Python 2 (or the Python 2 neovim module) is broken or missing:\n' .. err, function() end) return end diff --git a/test/functional/provider/ruby_spec.lua b/test/functional/provider/ruby_spec.lua index 7b0e17688d..9f5ef3b3fc 100644 --- a/test/functional/provider/ruby_spec.lua +++ b/test/functional/provider/ruby_spec.lua @@ -10,13 +10,11 @@ local expect = helpers.expect local command = helpers.command local write_file = helpers.write_file local curbufmeths = helpers.curbufmeths +local missing_provider = helpers.missing_provider do clear() - command('let g:prog = provider#ruby#Detect()') - local prog = meths.get_var('prog') - - if prog == '' then + if missing_provider('ruby') then pending( "Cannot find the neovim RubyGem. Try :CheckHealth", function() end) diff --git a/test/unit/eval/typval_spec.lua b/test/unit/eval/typval_spec.lua index 6308ae7367..5d543f914f 100644 --- a/test/unit/eval/typval_spec.lua +++ b/test/unit/eval/typval_spec.lua @@ -1751,6 +1751,55 @@ describe('typval.c', function() eq('2', s) end) end) + describe('get_string_buf_chk()', function() + local function tv_dict_get_string_buf_chk(d, key, len, buf, def, emsg) + buf = buf or ffi.gc(lib.xmalloc(lib.NUMBUFLEN), lib.xfree) + def = def or ffi.gc(lib.xstrdup('DEFAULT'), lib.xfree) + len = len or #key + alloc_log:clear() + local ret = check_emsg(function() return lib.tv_dict_get_string_buf_chk(d, key, len, buf, def) end, + emsg) + local s_ret = (ret ~= nil) and ffi.string(ret) or nil + if not emsg then + alloc_log:check({}) + end + return s_ret, ret, buf, def + end + itp('works with NULL dict', function() + eq('DEFAULT', tv_dict_get_string_buf_chk(nil, 'test')) + end) + itp('works', function() + local lua_d = { + ['']={}, + t=1, + te=int(2), + tes=empty_list, + test='tset', + testt=5, + } + local d = dict(lua_d) + alloc_log:clear() + eq(lua_d, dct2tbl(d)) + alloc_log:check({}) + local s, r, b, def + s, r, b, def = tv_dict_get_string_buf_chk(d, 'test') + neq(r, b) + neq(r, def) + eq('tset', s) + s, r, b, def = tv_dict_get_string_buf_chk(d, 'test', 1, nil, nil, 'E806: using Float as a String') + neq(r, b) + neq(r, def) + eq(nil, s) + s, r, b, def = tv_dict_get_string_buf_chk(d, 'te') + eq(r, b) + neq(r, def) + eq('2', s) + s, r, b, def = tv_dict_get_string_buf_chk(d, 'TEST') + eq(r, def) + neq(r, b) + eq('DEFAULT', s) + end) + end) describe('get_callback()', function() local function tv_dict_get_callback(d, key, key_len, emsg) key_len = key_len or #key |