diff options
Diffstat (limited to 'src')
43 files changed, 1159 insertions, 508 deletions
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/buffer.c b/src/nvim/api/buffer.c index 0b8d39d0d2..fc11708bd6 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -453,6 +453,26 @@ Integer nvim_buf_get_changedtick(Buffer buffer, Error *err) return buf->b_changedtick; } +/// Get a list of dictionaries describing buffer-local mappings +/// Note that the buffer key in the dictionary will represent the buffer +/// handle where the mapping is present +/// +/// @param mode The abbreviation for the mode +/// @param buffer_id Buffer handle +/// @param[out] err Error details, if any +/// @returns An array of maparg() like dictionaries describing mappings +ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Error *err) + FUNC_API_SINCE(3) +{ + buf_T *buf = find_buffer_by_handle(buffer, err); + + if (!buf) { + return (Array)ARRAY_DICT_INIT; + } + + return keymap_array(mode, buf); +} + /// Sets a buffer-scoped (b:) variable /// /// @param buffer Buffer handle diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 1d7b305da3..ef789b3ed4 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -24,6 +24,7 @@ #include "nvim/option_defs.h" #include "nvim/version.h" #include "nvim/lib/kvec.h" +#include "nvim/getchar.h" /// Helper structure for vim_to_object typedef struct { @@ -1034,3 +1035,41 @@ void api_set_error(Error *err, ErrorType errType, const char *format, ...) err->type = errType; } + +/// Get an array containing dictionaries describing mappings +/// based on mode and buffer id +/// +/// @param mode The abbreviation for the mode +/// @param buf The buffer to get the mapping array. NULL for global +/// @returns An array of maparg() like dictionaries describing mappings +ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf) +{ + Array mappings = ARRAY_DICT_INIT; + dict_T *const dict = tv_dict_alloc(); + + // Convert the string mode to the integer mode + // that is stored within each mapblock + char_u *p = (char_u *)mode.data; + int int_mode = get_map_mode(&p, 0); + + // Determine the desired buffer value + long buffer_value = (buf == NULL) ? 0 : buf->handle; + + for (int i = 0; i < MAX_MAPHASH; i++) { + for (const mapblock_T *current_maphash = get_maphash(i, buf); + current_maphash; + current_maphash = current_maphash->m_next) { + // Check for correct mode + if (int_mode & current_maphash->m_mode) { + mapblock_fill_dict(dict, current_maphash, buffer_value, false); + ADD(mappings, vim_to_object( + (typval_T[]) { { .v_type = VAR_DICT, .vval.v_dict = dict } })); + + tv_dict_clear(dict); + } + } + } + tv_dict_free(dict); + + return mappings; +} diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index d0bb840b8d..0cffb2c87d 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" @@ -134,7 +135,8 @@ Integer nvim_input(String keys) return (Integer)input_enqueue(keys); } -/// Replaces any terminal codes with the internal representation +/// Replaces terminal codes and key codes (<CR>, <Esc>, ...) in a string with +/// the internal representation. /// /// @see replace_termcodes /// @see cpoptions @@ -254,6 +256,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. /// @@ -721,6 +742,17 @@ Dictionary nvim_get_mode(void) return rv; } +/// Get a list of dictionaries describing global (i.e. non-buffer) mappings +/// Note that the "buffer" key will be 0 to represent false. +/// +/// @param mode The abbreviation for the mode +/// @returns An array of maparg() like dictionaries describing mappings +ArrayOf(Dictionary) nvim_get_keymap(String mode) + FUNC_API_SINCE(3) +{ + return keymap_array(mode, NULL); +} + Array nvim_get_api_info(uint64_t channel_id) FUNC_API_SINCE(1) FUNC_API_ASYNC FUNC_API_REMOTE_ONLY { diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index e3897e3929..6abd505ead 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -828,7 +828,7 @@ void handle_swap_exists(bufref_T *old_curbuf) * new aborting error, interrupt, or uncaught exception. */ leave_cleanup(&cs); } - swap_exists_action = SEA_NONE; + swap_exists_action = SEA_NONE; // -V519 } /* diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 88fa9726a4..d96b9355f1 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -438,6 +438,9 @@ typedef TV_DICTITEM_STRUCT(sizeof("changedtick")) ChangedtickDictItem; #define BUF_HAS_QF_ENTRY 1 #define BUF_HAS_LL_ENTRY 2 +// Maximum number of maphash blocks we will have +#define MAX_MAPHASH 256 + /* * buffer: structure that holds information about one file * @@ -526,8 +529,8 @@ struct file_buffer { */ uint64_t b_chartab[4]; - /* Table used for mappings local to a buffer. */ - mapblock_T *(b_maphash[256]); + // Table used for mappings local to a buffer. + mapblock_T *(b_maphash[MAX_MAPHASH]); /* First abbreviation local to a buffer. */ mapblock_T *b_first_abbr; diff --git a/src/nvim/charset.c b/src/nvim/charset.c index ee58e0af91..5a0590d075 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -545,18 +545,8 @@ void transchar_nonprint(char_u *buf, int c) buf[1] = (char_u)(c ^ 0x40); buf[2] = NUL; - } else if (c >= 0x80) { - transchar_hex(buf, c); - } else if ((c >= ' ' + 0x80) && (c <= '~' + 0x80)) { - // 0xa0 - 0xfe - buf[0] = '|'; - buf[1] = (char_u)(c - 0x80); - buf[2] = NUL; } else { - // 0x80 - 0x9f and 0xff - buf[0] = '~'; - buf[1] = (char_u)((c - 0x80) ^ 0x40); - buf[2] = NUL; + transchar_hex(buf, c); } } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index bfdec90a32..08a2f42f74 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -3422,7 +3422,6 @@ static void ins_compl_fixRedoBufForLeader(char_u *ptr_arg) else return; /* nothing to do */ } - assert(ptr != NULL); if (compl_orig_text != NULL) { p = compl_orig_text; for (len = 0; p[len] != NUL && p[len] == ptr[len]; ++len) @@ -3434,7 +3433,6 @@ static void ins_compl_fixRedoBufForLeader(char_u *ptr_arg) } else { len = 0; } - assert(ptr != NULL); AppendToRedobuffLit(ptr + len, -1); } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 31e5ae8806..4e303414a3 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -10980,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; } @@ -12061,22 +12102,7 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact) tv_dict_alloc_ret(rettv); if (rhs != NULL) { // Return a dictionary. - char_u *lhs = str2special_save(mp->m_keys, true); - char *const mapmode = map_mode_to_chars(mp->m_mode); - dict_T *dict = rettv->vval.v_dict; - - tv_dict_add_str(dict, S_LEN("lhs"), (const char *)lhs); - tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str); - tv_dict_add_nr(dict, S_LEN("noremap"), mp->m_noremap ? 1 : 0); - tv_dict_add_nr(dict, S_LEN("expr"), mp->m_expr ? 1 : 0); - tv_dict_add_nr(dict, S_LEN("silent"), mp->m_silent ? 1 : 0); - tv_dict_add_nr(dict, S_LEN("sid"), (varnumber_T)mp->m_script_ID); - tv_dict_add_nr(dict, S_LEN("buffer"), (varnumber_T)buffer_local); - tv_dict_add_nr(dict, S_LEN("nowait"), mp->m_nowait ? 1 : 0); - tv_dict_add_str(dict, S_LEN("mode"), mapmode); - - xfree(lhs); - xfree(mapmode); + mapblock_fill_dict(rettv->vval.v_dict, mp, buffer_local, true); } } } @@ -12093,6 +12119,46 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr) executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv); } +/// Fill a dictionary with all applicable maparg() like dictionaries +/// +/// @param dict The dictionary to be filled +/// @param mp The maphash that contains the mapping information +/// @param buffer_value The "buffer" value +/// @param compatible True for compatible with old maparg() dict +void mapblock_fill_dict(dict_T *const dict, + const mapblock_T *const mp, + long buffer_value, + bool compatible) + FUNC_ATTR_NONNULL_ALL +{ + char_u *lhs = str2special_save(mp->m_keys, true); + char *const mapmode = map_mode_to_chars(mp->m_mode); + varnumber_T noremap_value; + + if (compatible) { + // Keep old compatible behavior + // This is unable to determine whether a mapping is a <script> mapping + noremap_value = !!mp->m_noremap; + } else { + // Distinguish between <script> mapping + // If it's not a <script> mapping, check if it's a noremap + noremap_value = mp->m_noremap == REMAP_SCRIPT ? 2 : !!mp->m_noremap; + } + + tv_dict_add_str(dict, S_LEN("lhs"), (const char *)lhs); + tv_dict_add_str(dict, S_LEN("rhs"), (const char *)mp->m_orig_str); + tv_dict_add_nr(dict, S_LEN("noremap"), noremap_value); + tv_dict_add_nr(dict, S_LEN("expr"), mp->m_expr ? 1 : 0); + tv_dict_add_nr(dict, S_LEN("silent"), mp->m_silent ? 1 : 0); + tv_dict_add_nr(dict, S_LEN("sid"), (varnumber_T)mp->m_script_ID); + tv_dict_add_nr(dict, S_LEN("buffer"), (varnumber_T)buffer_value); + tv_dict_add_nr(dict, S_LEN("nowait"), mp->m_nowait ? 1 : 0); + tv_dict_add_str(dict, S_LEN("mode"), mapmode); + + xfree(lhs); + xfree(mapmode); +} + /* * "map()" function */ @@ -13017,8 +13083,9 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) /* have to shuffle buf to close gap */ int adjust_prevlen = 0; - if (dest < buf) { - adjust_prevlen = (int)(buf - dest); /* must be 1 or 2 */ + if (dest < buf) { // -V782 + adjust_prevlen = (int)(buf - dest); // -V782 + // adjust_prevlen must be 1 or 2. dest = buf; } if (readlen > p - buf + 1) @@ -14254,22 +14321,39 @@ static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + char *address; // If the user supplied an address, use it, otherwise use a temp. if (argvars[0].v_type != VAR_UNKNOWN) { if (argvars[0].v_type != VAR_STRING) { EMSG(_(e_invarg)); return; } else { - rettv->vval.v_string = (char_u *)xstrdup(tv_get_string(argvars)); + address = xstrdup(tv_get_string(argvars)); } } else { - rettv->vval.v_string = (char_u *)server_address_new(); + address = server_address_new(); } - int result = server_start((char *) rettv->vval.v_string); + int result = server_start(address); + xfree(address); + if (result != 0) { - EMSG2("Failed to start server: %s", uv_strerror(result)); + EMSG2("Failed to start server: %s", + result > 0 ? "Unknonwn system error" : uv_strerror(result)); + return; + } + + // Since it's possible server_start adjusted the given {address} (e.g., + // "localhost:" will now have a port), return the final value to the user. + size_t n; + char **addrs = server_address_list(&n); + rettv->vval.v_string = (char_u *)addrs[n - 1]; + + n--; + for (size_t i = 0; i < n; i++) { + xfree(addrs[i]); } + xfree(addrs); } /// "serverstop()" function @@ -14974,6 +15058,54 @@ static void f_simplify(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; } +/// "sockconnect()" function +static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_STRING || argvars[1].v_type != VAR_STRING) { + EMSG(_(e_invarg)); + return; + } + if (argvars[2].v_type != VAR_DICT && argvars[2].v_type != VAR_UNKNOWN) { + // Wrong argument types + EMSG2(_(e_invarg2), "expected dictionary"); + return; + } + + const char *mode = tv_get_string(&argvars[0]); + const char *address = tv_get_string(&argvars[1]); + + bool tcp; + if (strcmp(mode, "tcp") == 0) { + tcp = true; + } else if (strcmp(mode, "pipe") == 0) { + tcp = false; + } else { + EMSG2(_(e_invarg2), "invalid mode"); + return; + } + + bool rpc = false; + if (argvars[2].v_type == VAR_DICT) { + dict_T *opts = argvars[2].vval.v_dict; + rpc = tv_dict_get_number(opts, "rpc") != 0; + } + + if (!rpc) { + EMSG2(_(e_invarg2), "rpc option must be true"); + return; + } + + const char *error = NULL; + uint64_t id = channel_connect(tcp, address, 50, &error); + + if (error) { + EMSG2(_("connection failed: %s"), error); + } + + rettv->vval.v_number = (varnumber_T)id; + rettv->v_type = VAR_NUMBER; +} + /// struct used in the array that's given to qsort() typedef struct { listitem_T *item; @@ -21052,9 +21184,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, // Do not want errors such as E724 here. emsg_off++; char *tofree = encode_tv2string(&argvars[i], NULL); - char *s = tofree; emsg_off--; - if (s != NULL) { + if (tofree != NULL) { + char *s = tofree; char buf[MSG_BUF_LEN]; if (vim_strsize((char_u *)s) > MSG_BUF_CLEN) { trunc_string((char_u *)s, (char_u *)buf, MSG_BUF_CLEN, @@ -21118,7 +21250,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, if (func_or_func_caller_profiling) { call_start = profile_end(call_start); - call_start = profile_sub_wait(wait_start, call_start); + call_start = profile_sub_wait(wait_start, call_start); // -V614 fp->uf_tm_total = profile_add(fp->uf_tm_total, call_start); fp->uf_tm_self = profile_self(fp->uf_tm_self, call_start, fp->uf_tm_children); diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 533403b2b0..334e10eb6c 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -268,6 +268,7 @@ return { simplify={args=1}, sin={args=1, func="float_op_wrapper", data="&sin"}, sinh={args=1, func="float_op_wrapper", data="&sinh"}, + sockconnect={args={2,3}}, sort={args={1, 3}}, soundfold={args=1}, spellbadword={args={0, 1}}, diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index 742497c1ca..ef647b3ee4 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -253,9 +253,11 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, char *const buf_end = buf + nbuf; char *p = buf; while (p < buf_end) { + assert(state->li_length == 0 || state->li->li_tv.vval.v_string != NULL); for (size_t i = state->offset; i < state->li_length && p < buf_end; i++) { - const char ch = (char) state->li->li_tv.vval.v_string[state->offset++]; - *p++ = (char) ((char) ch == (char) NL ? (char) NUL : (char) ch); + assert(state->li->li_tv.vval.v_string != NULL); + const char ch = (char)state->li->li_tv.vval.v_string[state->offset++]; + *p++ = (char)((char)ch == (char)NL ? (char)NUL : (char)ch); } if (p < buf_end) { state->li = state->li->li_next; diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 19d9d56058..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. diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index 3116adbde8..f6a567a520 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -11,6 +11,7 @@ #include "nvim/event/process.h" #include "nvim/event/libuv_process.h" #include "nvim/log.h" +#include "nvim/macros.h" #include "nvim/os/os.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -47,17 +48,20 @@ int libuv_process_spawn(LibuvProcess *uvproc) if (proc->in) { uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; - uvproc->uvstdio[0].data.stream = (uv_stream_t *)&proc->in->uv.pipe; + uvproc->uvstdio[0].data.stream = STRUCT_CAST(uv_stream_t, + &proc->in->uv.pipe); } if (proc->out) { uvproc->uvstdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; - uvproc->uvstdio[1].data.stream = (uv_stream_t *)&proc->out->uv.pipe; + uvproc->uvstdio[1].data.stream = STRUCT_CAST(uv_stream_t, + &proc->out->uv.pipe); } if (proc->err) { uvproc->uvstdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; - uvproc->uvstdio[2].data.stream = (uv_stream_t *)&proc->err->uv.pipe; + uvproc->uvstdio[2].data.stream = STRUCT_CAST(uv_stream_t, + &proc->err->uv.pipe); } int status; diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index ffda10a494..cad49e2007 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -14,6 +14,7 @@ #include "nvim/event/libuv_process.h" #include "nvim/os/pty_process.h" #include "nvim/globals.h" +#include "nvim/macros.h" #include "nvim/log.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -82,7 +83,8 @@ int process_spawn(Process *proc) FUNC_ATTR_NONNULL_ALL } if (proc->in) { - stream_init(NULL, proc->in, -1, (uv_stream_t *)&proc->in->uv.pipe); + stream_init(NULL, proc->in, -1, + STRUCT_CAST(uv_stream_t, &proc->in->uv.pipe)); proc->in->events = proc->events; proc->in->internal_data = proc; proc->in->internal_close_cb = on_process_stream_close; @@ -90,7 +92,8 @@ int process_spawn(Process *proc) FUNC_ATTR_NONNULL_ALL } if (proc->out) { - stream_init(NULL, proc->out, -1, (uv_stream_t *)&proc->out->uv.pipe); + stream_init(NULL, proc->out, -1, + STRUCT_CAST(uv_stream_t, &proc->out->uv.pipe)); proc->out->events = proc->events; proc->out->internal_data = proc; proc->out->internal_close_cb = on_process_stream_close; @@ -98,7 +101,8 @@ int process_spawn(Process *proc) FUNC_ATTR_NONNULL_ALL } if (proc->err) { - stream_init(NULL, proc->err, -1, (uv_stream_t *)&proc->err->uv.pipe); + stream_init(NULL, proc->err, -1, + STRUCT_CAST(uv_stream_t, &proc->err->uv.pipe)); proc->err->events = proc->events; proc->err->internal_data = proc; proc->err->internal_close_cb = on_process_stream_close; diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c index e536d79a2a..30a71a5586 100644 --- a/src/nvim/event/socket.c +++ b/src/nvim/event/socket.c @@ -15,99 +15,121 @@ #include "nvim/vim.h" #include "nvim/strings.h" #include "nvim/path.h" +#include "nvim/main.h" #include "nvim/memory.h" +#include "nvim/macros.h" +#include "nvim/charset.h" +#include "nvim/log.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/socket.c.generated.h" #endif -#define NVIM_DEFAULT_TCP_PORT 7450 - -void socket_watcher_init(Loop *loop, SocketWatcher *watcher, - const char *endpoint, void *data) - FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(2) FUNC_ATTR_NONNULL_ARG(3) +int socket_watcher_init(Loop *loop, SocketWatcher *watcher, + const char *endpoint) + FUNC_ATTR_NONNULL_ALL { - // Trim to `ADDRESS_MAX_SIZE` - if (xstrlcpy(watcher->addr, endpoint, sizeof(watcher->addr)) - >= sizeof(watcher->addr)) { - // TODO(aktau): since this is not what the user wanted, perhaps we - // should return an error here - WLOG("Address was too long, truncated to %s", watcher->addr); - } + xstrlcpy(watcher->addr, endpoint, sizeof(watcher->addr)); + char *addr = watcher->addr; + char *host_end = strrchr(addr, ':'); - bool tcp = true; - char ip[16], *ip_end = xstrchrnul(watcher->addr, ':'); + if (host_end && addr != host_end) { + // Split user specified address into two strings, addr(hostname) and port. + // The port part in watcher->addr will be updated later. + *host_end = '\0'; + char *port = host_end + 1; + intmax_t iport; - // (ip_end - addr) is always > 0, so convert to size_t - size_t addr_len = (size_t)(ip_end - watcher->addr); - - if (addr_len > sizeof(ip) - 1) { - // Maximum length of an IPv4 address buffer is 15 (eg: 255.255.255.255) - addr_len = sizeof(ip) - 1; - } + int ret = getdigits_safe(&(char_u *){ (char_u *)port }, &iport); + if (ret == FAIL || iport < 0 || iport > UINT16_MAX) { + ELOG("Invalid port: %s", port); + return UV_EINVAL; + } - // Extract the address part - xstrlcpy(ip, watcher->addr, addr_len + 1); - int port = NVIM_DEFAULT_TCP_PORT; - - if (*ip_end == ':') { - // Extract the port - long lport = strtol(ip_end + 1, NULL, 10); // NOLINT - if (lport <= 0 || lport > 0xffff) { - // Invalid port, treat as named pipe or unix socket - tcp = false; - } else { - port = (int) lport; + if (*port == NUL) { + // When no port is given, (uv_)getaddrinfo expects NULL otherwise the + // implementation may attempt to lookup the service by name (and fail) + port = NULL; } - } - if (tcp) { - // Try to parse ip address - if (uv_ip4_addr(ip, port, &watcher->uv.tcp.addr)) { - // Invalid address, treat as named pipe or unix socket - tcp = false; + uv_getaddrinfo_t request; + + int retval = uv_getaddrinfo(&loop->uv, &request, NULL, addr, port, + &(struct addrinfo){ + .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + }); + if (retval != 0) { + ELOG("Host lookup failed: %s", endpoint); + return retval; } - } + watcher->uv.tcp.addrinfo = request.addrinfo; - if (tcp) { uv_tcp_init(&loop->uv, &watcher->uv.tcp.handle); - watcher->stream = (uv_stream_t *)&watcher->uv.tcp.handle; + watcher->stream = STRUCT_CAST(uv_stream_t, &watcher->uv.tcp.handle); } else { uv_pipe_init(&loop->uv, &watcher->uv.pipe.handle, 0); - watcher->stream = (uv_stream_t *)&watcher->uv.pipe.handle; + watcher->stream = STRUCT_CAST(uv_stream_t, &watcher->uv.pipe.handle); } watcher->stream->data = watcher; watcher->cb = NULL; watcher->close_cb = NULL; watcher->events = NULL; + watcher->data = NULL; + + return 0; } int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb) FUNC_ATTR_NONNULL_ALL { watcher->cb = cb; - int result; + int result = UV_EINVAL; if (watcher->stream->type == UV_TCP) { - result = uv_tcp_bind(&watcher->uv.tcp.handle, - (const struct sockaddr *)&watcher->uv.tcp.addr, 0); + struct addrinfo *ai = watcher->uv.tcp.addrinfo; + + for (; ai; ai = ai->ai_next) { + result = uv_tcp_bind(&watcher->uv.tcp.handle, ai->ai_addr, 0); + if (result != 0) { + continue; + } + result = uv_listen(watcher->stream, backlog, connection_cb); + if (result == 0) { + struct sockaddr_storage sas; + + // When the endpoint in socket_watcher_init() didn't specify a port + // number, a free random port number will be assigned. sin_port will + // contain 0 in this case, unless uv_tcp_getsockname() is used first. + uv_tcp_getsockname(&watcher->uv.tcp.handle, (struct sockaddr *)&sas, + &(int){ sizeof(sas) }); + uint16_t port = (uint16_t)((sas.ss_family == AF_INET) + ? ((struct sockaddr_in *)&sas)->sin_port + : ((struct sockaddr_in6 *)&sas)->sin6_port); + // v:servername uses the string from watcher->addr + size_t len = strlen(watcher->addr); + snprintf(watcher->addr+len, sizeof(watcher->addr)-len, ":%" PRIu16, + ntohs(port)); + break; + } + } + uv_freeaddrinfo(watcher->uv.tcp.addrinfo); } else { result = uv_pipe_bind(&watcher->uv.pipe.handle, watcher->addr); - } - - if (result == 0) { - result = uv_listen(watcher->stream, backlog, connection_cb); + if (result == 0) { + result = uv_listen(watcher->stream, backlog, connection_cb); + } } assert(result <= 0); // libuv should return negative error code or zero. if (result < 0) { - if (result == -EACCES) { + if (result == UV_EACCES) { // Libuv converts ENOENT to EACCES for Windows compatibility, but if // the parent directory does not exist, ENOENT would be more accurate. *path_tail((char_u *)watcher->addr) = NUL; if (!os_path_exists((char_u *)watcher->addr)) { - result = -ENOENT; + result = UV_ENOENT; } } return result; @@ -122,10 +144,10 @@ int socket_watcher_accept(SocketWatcher *watcher, Stream *stream) uv_stream_t *client; if (watcher->stream->type == UV_TCP) { - client = (uv_stream_t *)&stream->uv.tcp; + client = STRUCT_CAST(uv_stream_t, &stream->uv.tcp); uv_tcp_init(watcher->uv.tcp.handle.loop, (uv_tcp_t *)client); } else { - client = (uv_stream_t *)&stream->uv.pipe; + client = STRUCT_CAST(uv_stream_t, &stream->uv.pipe); uv_pipe_init(watcher->uv.pipe.handle.loop, (uv_pipe_t *)client, 0); } @@ -168,3 +190,76 @@ static void close_cb(uv_handle_t *handle) watcher->close_cb(watcher, watcher->data); } } + +static void connect_cb(uv_connect_t *req, int status) +{ + int *ret_status = req->data; + *ret_status = status; + if (status != 0) { + uv_close((uv_handle_t *)req->handle, NULL); + } +} + +bool socket_connect(Loop *loop, Stream *stream, + bool is_tcp, const char *address, + int timeout, const char **error) +{ + bool success = false; + int status; + uv_connect_t req; + req.data = &status; + uv_stream_t *uv_stream; + + uv_tcp_t *tcp = &stream->uv.tcp; + uv_getaddrinfo_t addr_req; + addr_req.addrinfo = NULL; + const struct addrinfo *addrinfo = NULL; + char *addr = NULL; + if (is_tcp) { + addr = xstrdup(address); + char *host_end = strrchr(addr, ':'); + if (!host_end) { + *error = _("tcp address must be host:port"); + goto cleanup; + } + *host_end = NUL; + + const struct addrinfo hints = { .ai_family = AF_UNSPEC, + .ai_socktype = SOCK_STREAM, + .ai_flags = AI_NUMERICSERV }; + int retval = uv_getaddrinfo(&loop->uv, &addr_req, NULL, + addr, host_end+1, &hints); + if (retval != 0) { + *error = _("failed to lookup host or port"); + goto cleanup; + } + addrinfo = addr_req.addrinfo; + +tcp_retry: + uv_tcp_init(&loop->uv, tcp); + uv_tcp_connect(&req, tcp, addrinfo->ai_addr, connect_cb); + uv_stream = (uv_stream_t *)tcp; + + } else { + uv_pipe_t *pipe = &stream->uv.pipe; + uv_pipe_init(&loop->uv, pipe, 0); + uv_pipe_connect(&req, pipe, address, connect_cb); + uv_stream = (uv_stream_t *)pipe; + } + status = 1; + LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, timeout, status != 1); + if (status == 0) { + stream_init(NULL, stream, -1, uv_stream); + success = true; + } else if (is_tcp && addrinfo->ai_next) { + addrinfo = addrinfo->ai_next; + goto tcp_retry; + } else { + *error = _("connection refused"); + } + +cleanup: + xfree(addr); + uv_freeaddrinfo(addr_req.addrinfo); + return success; +} diff --git a/src/nvim/event/socket.h b/src/nvim/event/socket.h index eb0823c76d..d30ae45502 100644 --- a/src/nvim/event/socket.h +++ b/src/nvim/event/socket.h @@ -20,7 +20,7 @@ struct socket_watcher { union { struct { uv_tcp_t handle; - struct sockaddr_in addr; + struct addrinfo *addrinfo; } tcp; struct { uv_pipe_t handle; diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c index 860a957b3e..60ceff9b24 100644 --- a/src/nvim/event/stream.c +++ b/src/nvim/event/stream.c @@ -8,6 +8,7 @@ #include <uv.h> #include "nvim/rbuffer.h" +#include "nvim/macros.h" #include "nvim/event/stream.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -26,8 +27,9 @@ int stream_set_blocking(int fd, bool blocking) uv_loop_init(&loop); uv_pipe_init(&loop, &stream, 0); uv_pipe_open(&stream, fd); - int retval = uv_stream_set_blocking((uv_stream_t *)&stream, blocking); - uv_close((uv_handle_t *)&stream, NULL); + int retval = uv_stream_set_blocking(STRUCT_CAST(uv_stream_t, &stream), + blocking); + uv_close(STRUCT_CAST(uv_handle_t, &stream), NULL); uv_run(&loop, UV_RUN_NOWAIT); // not necessary, but couldn't hurt. uv_loop_close(&loop); return retval; @@ -52,7 +54,7 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream) assert(type == UV_NAMED_PIPE || type == UV_TTY); uv_pipe_init(&loop->uv, &stream->uv.pipe, 0); uv_pipe_open(&stream->uv.pipe, fd); - stream->uvstream = (uv_stream_t *)&stream->uv.pipe; + stream->uvstream = STRUCT_CAST(uv_stream_t, &stream->uv.pipe); } } diff --git a/src/nvim/func_attr.h b/src/nvim/func_attr.h index cc94a41f80..f1a1d9a563 100644 --- a/src/nvim/func_attr.h +++ b/src/nvim/func_attr.h @@ -41,6 +41,8 @@ // $ gcc -E -dM - </dev/null // $ echo | clang -dM -E - +#include "nvim/macros.h" + #ifdef FUNC_ATTR_MALLOC # undef FUNC_ATTR_MALLOC #endif @@ -96,8 +98,7 @@ #ifndef DID_REAL_ATTR # define DID_REAL_ATTR # ifdef __GNUC__ -// place defines for all gnulikes here, for now that's gcc, clang and -// intel. +// For all gnulikes: gcc, clang, intel. // place these after the argument list of the function declaration // (not definition), like so: @@ -113,26 +114,17 @@ # define REAL_FATTR_NONNULL_ARG(...) __attribute__((nonnull(__VA_ARGS__))) # define REAL_FATTR_NORETURN __attribute__((noreturn)) -# ifdef __clang__ -// clang only -# elif defined(__INTEL_COMPILER) -// intel only -# else -# define GCC_VERSION \ - (__GNUC__ * 10000 + \ - __GNUC_MINOR__ * 100 + \ - __GNUC_PATCHLEVEL__) -// gcc only +# if NVIM_HAS_ATTRIBUTE(returns_nonnull) +# define REAL_FATTR_NONNULL_RET __attribute__((returns_nonnull)) +# endif + +# if NVIM_HAS_ATTRIBUTE(alloc_size) # define REAL_FATTR_ALLOC_SIZE(x) __attribute__((alloc_size(x))) # define REAL_FATTR_ALLOC_SIZE_PROD(x, y) __attribute__((alloc_size(x, y))) -# if GCC_VERSION >= 40900 -# define REAL_FATTR_NONNULL_RET __attribute__((returns_nonnull)) -# endif # endif # endif -// define function attributes that haven't been defined for this specific -// compiler. +// Define attributes that are not defined for this compiler. # ifndef REAL_FATTR_MALLOC # define REAL_FATTR_MALLOC diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 9d7f235a3b..ca0134043c 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -118,7 +118,7 @@ local get_value = function(v) end local get_defaults = function(d) - return '{' .. get_value(d.vi) .. ', ' .. get_value(d.vim) .. '}' + return ('{' .. get_value(d.vi) .. ', ' .. get_value(d.vim) .. '}') end local defines = {} diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 79b95272de..382caa8548 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -18,6 +18,7 @@ #include "nvim/vim.h" #include "nvim/ascii.h" #include "nvim/getchar.h" +#include "nvim/buffer_defs.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/edit.h" @@ -47,6 +48,7 @@ #include "nvim/event/loop.h" #include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/api/private/handle.h" /* * These buffers are used for storing: @@ -102,11 +104,10 @@ static int block_redo = FALSE; (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; +// Each mapping is put in one of the MAX_MAPHASH hash lists, +// to speed up finding it. +static mapblock_T *(maphash[MAX_MAPHASH]); +static bool maphash_valid = false; /* * List used for abbreviations. @@ -3794,8 +3795,7 @@ makemap ( char *cmd; int abbr; int hash; - int did_cpo = FALSE; - int i; + bool did_cpo = false; validate_maphash(); @@ -3923,13 +3923,15 @@ makemap ( /* When outputting <> form, need to make sure that 'cpo' * is set to the Vim default. */ if (!did_cpo) { - if (*mp->m_str == NUL) /* will use <Nop> */ - did_cpo = TRUE; - else - for (i = 0; i < 2; ++i) - for (p = (i ? mp->m_str : mp->m_keys); *p; ++p) - if (*p == K_SPECIAL || *p == NL) - did_cpo = TRUE; + if (*mp->m_str == NUL) { // Will use <Nop>. + did_cpo = true; + } else { + const char specials[] = { (char)(uint8_t)K_SPECIAL, NL, NUL }; + if (strpbrk((const char *)mp->m_str, specials) != NULL + || strpbrk((const char *)mp->m_keys, specials) != NULL) { + did_cpo = true; + } + } if (did_cpo) { if (fprintf(fd, "let s:cpo_save=&cpo") < 0 || put_eol(fd) < 0 @@ -4236,3 +4238,17 @@ static bool typebuf_match_len(const uint8_t *str, int *mlen) *mlen = i; return str[i] == NUL; // matched the whole string } + +/// Retrieve the mapblock at the index either globally or for a certain buffer +/// +/// @param index The index in the maphash[] +/// @param buf The buffer to get the maphash from. NULL for global +mapblock_T *get_maphash(int index, buf_T *buf) + FUNC_ATTR_PURE +{ + if (index > MAX_MAPHASH) { + return NULL; + } + + return (buf == NULL) ? maphash[index] : buf->b_maphash[index]; +} diff --git a/src/nvim/globals.h b/src/nvim/globals.h index b820965680..a3657f2122 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -413,8 +413,7 @@ EXTERN int no_check_timestamps INIT(= 0); /* Don't check timestamps */ /* * Values for index in highlight_attr[]. - * When making changes, also update HL_FLAGS below! And update the default - * value of 'highlight' in option.c. + * When making changes, also update hlf_names below! */ typedef enum { HLF_8 = 0 /* Meta & special keys listed with ":map", text that is @@ -447,8 +446,8 @@ typedef enum { , HLF_CHD // Changed diff line , HLF_DED // Deleted diff line , HLF_TXD // Text Changed in diff line - , HLF_CONCEAL // Concealed text , HLF_SC // Sign column + , HLF_CONCEAL // Concealed text , HLF_SPB // SpellBad , HLF_SPC // SpellCap , HLF_SPR // SpellRare @@ -469,12 +468,56 @@ typedef enum { , HLF_COUNT // MUST be the last one } hlf_T; -/* The HL_FLAGS must be in the same order as the HLF_ enums! - * When changing this also adjust the default for 'highlight'. */ -#define HL_FLAGS { '8', '~', 'z', 'Z', '@', 'd', 'e', 'i', 'l', 'm', 'M', 'n', \ - 'N', 'r', 's', 'S', 'c', 't', 'v', 'V', 'w', 'W', 'f', 'F', \ - 'A', 'C', 'D', 'T', '-', '>', 'B', 'P', 'R', 'L', '+', '=', \ - 'x', 'X', '*', '#', '_', '!', '.', 'o', 'q', '0', 'I' } +EXTERN const char *hlf_names[] INIT(= { + [HLF_8] = "SpecialKey", + [HLF_EOB] = "EndOfBuffer", + [HLF_TERM] = "TermCursor", + [HLF_TERMNC] = "TermCursorNC", + [HLF_AT] = "NonText", + [HLF_D] = "Directory", + [HLF_E] = "ErrorMsg", + [HLF_I] = "IncSearch", + [HLF_L] = "Search", + [HLF_M] = "MoreMsg", + [HLF_CM] = "ModeMsg", + [HLF_N] = "LineNr", + [HLF_CLN] = "CursorLineNr", + [HLF_R] = "Question", + [HLF_S] = "StatusLine", + [HLF_SNC] = "StatusLineNC", + [HLF_C] = "VertSplit", + [HLF_T] = "Title", + [HLF_V] = "Visual", + [HLF_VNC] = "VisualNOS", + [HLF_W] = "WarningMsg", + [HLF_WM] = "WildMenu", + [HLF_FL] = "Folded", + [HLF_FC] = "FoldColumn", + [HLF_ADD] = "DiffAdd", + [HLF_CHD] = "DiffChange", + [HLF_DED] = "DiffDelete", + [HLF_TXD] = "DiffText", + [HLF_SC] = "SignColumn", + [HLF_CONCEAL] = "Conceal", + [HLF_SPB] = "SpellBad", + [HLF_SPC] = "SpellCap", + [HLF_SPR] = "SpellRare", + [HLF_SPL] = "SpellLocal", + [HLF_PNI] = "Pmenu", + [HLF_PSI] = "PmenuSel", + [HLF_PSB] = "PmenuSbar", + [HLF_PST] = "PmenuThumb", + [HLF_TP] = "TabLine", + [HLF_TPS] = "TabLineSel", + [HLF_TPF] = "TabLineFill", + [HLF_CUC] = "CursorColumn", + [HLF_CUL] = "CursorLine", + [HLF_MC] = "ColorColumn", + [HLF_QFL] = "QuickFixLine", + [HLF_0] = "Whitespace", + [HLF_INACTIVE] = "NormalNC", +}); + EXTERN int highlight_attr[HLF_COUNT]; /* Highl. attr for each context. */ EXTERN int highlight_user[9]; /* User[1-9] attributes */ diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 31e49df8f9..cacba3ce87 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -1,3 +1,6 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + #include <lua.h> #include <lualib.h> #include <lauxlib.h> diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 7cf326aef5..6f9e381d8d 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -1,3 +1,6 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check +// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com + #include <lua.h> #include <lualib.h> #include <lauxlib.h> @@ -373,6 +376,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 +559,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 9ab6dc5d2b..26d4f74b6a 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -171,4 +171,16 @@ # define FALLTHROUGH #endif +// -V:STRUCT_CAST:641 + +/// Change type of structure pointers: cast `struct a *` to `struct b *` +/// +/// Used to silence PVS errors. +/// +/// @param Type Structure to cast to. +/// @param obj Object to cast. +/// +/// @return ((Type *)obj). +#define STRUCT_CAST(Type, obj) ((Type *)(obj)) + #endif // NVIM_MACROS_H diff --git a/src/nvim/main.c b/src/nvim/main.c index 40b553e93c..46607da6ea 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -124,7 +124,7 @@ typedef struct { Loop main_loop; -static char *argv0; +static char *argv0 = NULL; // Error messages static const char *err_arg_missing = N_("Argument missing after"); @@ -179,11 +179,9 @@ void early_init(void) log_init(); fs_init(); handle_init(); - eval_init(); // init global variables - - // Init the table of Normal mode commands. - init_normal_cmds(); + init_path(argv0 ? argv0 : "nvim"); + init_normal_cmds(); // Init the table of Normal mode commands. #if defined(HAVE_LOCALE_H) // Setup to use the current locale (for ctype() and many other things). @@ -221,7 +219,7 @@ int nvim_main(int argc, char **argv) int main(int argc, char **argv) #endif { - argv0 = (char *)path_tail((char_u *)argv[0]); + argv0 = argv[0]; char_u *fname = NULL; // file name from command line mparm_T params; // various parameters passed between @@ -241,8 +239,6 @@ int main(int argc, char **argv) // Check if we have an interactive window. check_and_set_isatty(¶ms); - init_path(argv[0]); - event_init(); /* * Process the command line arguments. File names are put in the global @@ -660,8 +656,9 @@ void getout(int exitval) /// /// @return argument's numeric value otherwise static int get_number_arg(const char *p, int *idx, int def) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - if (ascii_isdigit(p[*idx])) { + if (ascii_isdigit(p[*idx])) { // -V522 def = atoi(&(p[*idx])); while (ascii_isdigit(p[*idx])) { *idx = *idx + 1; @@ -1217,13 +1214,15 @@ static void check_and_set_isatty(mparm_T *paramp) } // Sets v:progname and v:progpath. Also modifies $PATH on Windows. -static void init_path(char *exename) +static void init_path(const char *exename) + FUNC_ATTR_NONNULL_ALL { char exepath[MAXPATHL] = { 0 }; size_t exepathlen = MAXPATHL; // Make v:progpath absolute. if (os_exepath(exepath, &exepathlen) != 0) { - EMSG2(e_intern2, "init_path()"); + // Fall back to argv[0]. Missing procfs? #6734 + path_guess_exepath(exename, exepath, sizeof(exepath)); } set_vim_var_string(VV_PROGPATH, exepath, -1); set_vim_var_string(VV_PROGNAME, (char *)path_tail((char_u *)exename), -1); @@ -1684,7 +1683,7 @@ static bool do_user_initialization(void) do { const char *dir; size_t dir_len; - iter = vim_colon_env_iter(config_dirs, iter, &dir, &dir_len); + iter = vim_env_iter(':', config_dirs, iter, &dir, &dir_len); if (dir == NULL || dir_len == 0) { break; } @@ -1833,9 +1832,11 @@ static bool file_owned(const char *fname) /// @param str string to append to the primary error message, or NULL static void mainerr(const char *errstr, const char *str) { + char *prgname = (char *)path_tail((char_u *)argv0); + signal_stop(); // kill us with CTRL-C here, if you like - mch_errmsg(argv0); + mch_errmsg(prgname); mch_errmsg(": "); mch_errmsg(_(errstr)); if (str != NULL) { @@ -1844,7 +1845,7 @@ static void mainerr(const char *errstr, const char *str) mch_errmsg("\""); } mch_errmsg(_("\nMore info with \"")); - mch_errmsg(argv0); + mch_errmsg(prgname); mch_errmsg(" -h\"\n"); mch_exit(1); diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index efaf1f94c5..1abc69727c 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -54,6 +54,7 @@ #include "nvim/memory.h" #include "nvim/os_unix.h" #include "nvim/path.h" +#include "nvim/assert.h" #include "nvim/os/os.h" #include "nvim/os/input.h" @@ -108,7 +109,8 @@ memfile_T *mf_open(char_u *fname, int flags) if (mfp->mf_fd >= 0 && os_fileinfo_fd(mfp->mf_fd, &file_info)) { uint64_t blocksize = os_fileinfo_blocksize(&file_info); if (blocksize >= MIN_SWAP_PAGE_SIZE && blocksize <= MAX_SWAP_PAGE_SIZE) { - assert(blocksize <= UINT_MAX); + STATIC_ASSERT(MAX_SWAP_PAGE_SIZE <= UINT_MAX, + "MAX_SWAP_PAGE_SIZE must fit into an unsigned"); mfp->mf_page_size = (unsigned)blocksize; } } diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index cd64e14976..e8ee0ede75 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -12,6 +12,7 @@ #include "nvim/api/vim.h" #include "nvim/api/ui.h" #include "nvim/msgpack_rpc/channel.h" +#include "nvim/msgpack_rpc/server.h" #include "nvim/event/loop.h" #include "nvim/event/libuv_process.h" #include "nvim/event/rstream.h" @@ -28,6 +29,7 @@ #include "nvim/map.h" #include "nvim/log.h" #include "nvim/misc1.h" +#include "nvim/path.h" #include "nvim/lib/kvec.h" #include "nvim/os/input.h" @@ -41,7 +43,8 @@ typedef enum { kChannelTypeSocket, kChannelTypeProc, - kChannelTypeStdio + kChannelTypeStdio, + kChannelTypeInternal } ChannelType; typedef struct { @@ -125,7 +128,7 @@ uint64_t channel_from_process(Process *proc, uint64_t id) wstream_init(proc->in, 0); rstream_init(proc->out, 0); - rstream_start(proc->out, parse_msgpack, channel); + rstream_start(proc->out, receive_msgpack, channel); return channel->id; } @@ -142,7 +145,36 @@ void channel_from_connection(SocketWatcher *watcher) channel->data.stream.internal_data = channel; wstream_init(&channel->data.stream, 0); rstream_init(&channel->data.stream, CHANNEL_BUFFER_SIZE); - rstream_start(&channel->data.stream, parse_msgpack, channel); + rstream_start(&channel->data.stream, receive_msgpack, channel); +} + +uint64_t channel_connect(bool tcp, const char *address, + int timeout, const char **error) +{ + if (!tcp) { + char *path = fix_fname(address); + if (server_owns_pipe_address(path)) { + // avoid deadlock + xfree(path); + return channel_create_internal(); + } + xfree(path); + } + + Channel *channel = register_channel(kChannelTypeSocket, 0, NULL); + if (!socket_connect(&main_loop, &channel->data.stream, + tcp, address, timeout, error)) { + decref(channel); + return 0; + } + + incref(channel); // close channel only after the stream is closed + channel->data.stream.internal_close_cb = close_cb; + channel->data.stream.internal_data = channel; + wstream_init(&channel->data.stream, 0); + rstream_init(&channel->data.stream, CHANNEL_BUFFER_SIZE); + rstream_start(&channel->data.stream, receive_msgpack, channel); + return channel->id; } /// Sends event/arguments to channel @@ -305,11 +337,20 @@ void channel_from_stdio(void) incref(channel); // stdio channels are only closed on exit // read stream rstream_init_fd(&main_loop, &channel->data.std.in, 0, CHANNEL_BUFFER_SIZE); - rstream_start(&channel->data.std.in, parse_msgpack, channel); + rstream_start(&channel->data.std.in, receive_msgpack, channel); // write stream wstream_init_fd(&main_loop, &channel->data.std.out, 1, 0); } +/// Creates a loopback channel. This is used to avoid deadlock +/// when an instance connects to its own named pipe. +uint64_t channel_create_internal(void) +{ + Channel *channel = register_channel(kChannelTypeInternal, 0, NULL); + incref(channel); // internal channel lives until process exit + return channel->id; +} + void channel_process_exit(uint64_t id, int status) { Channel *channel = pmap_get(uint64_t)(channels, id); @@ -318,8 +359,8 @@ void channel_process_exit(uint64_t id, int status) decref(channel); } -static void parse_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data, - bool eof) +static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c, + void *data, bool eof) { Channel *channel = data; incref(channel); @@ -341,6 +382,14 @@ static void parse_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data, rbuffer_read(rbuf, msgpack_unpacker_buffer(channel->unpacker), count); msgpack_unpacker_buffer_consumed(channel->unpacker, count); + parse_msgpack(channel); + +end: + decref(channel); +} + +static void parse_msgpack(Channel *channel) +{ msgpack_unpacked unpacked; msgpack_unpacked_init(&unpacked); msgpack_unpack_return result; @@ -364,7 +413,7 @@ static void parse_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data, } msgpack_unpacked_destroy(&unpacked); // Bail out from this event loop iteration - goto end; + return; } handle_request(channel, &unpacked.data); @@ -388,11 +437,9 @@ static void parse_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data, "This error can also happen when deserializing " "an object with high level of nesting"); } - -end: - decref(channel); } + static void handle_request(Channel *channel, msgpack_object *request) FUNC_ATTR_NONNULL_ALL { @@ -502,8 +549,11 @@ static bool channel_write(Channel *channel, WBuffer *buffer) case kChannelTypeStdio: success = wstream_write(&channel->data.std.out, buffer); break; - default: - abort(); + case kChannelTypeInternal: + incref(channel); + CREATE_EVENT(channel->events, internal_read_event, 2, channel, buffer); + success = true; + break; } if (!success) { @@ -520,6 +570,22 @@ static bool channel_write(Channel *channel, WBuffer *buffer) return success; } +static void internal_read_event(void **argv) +{ + Channel *channel = argv[0]; + WBuffer *buffer = argv[1]; + + msgpack_unpacker_reserve_buffer(channel->unpacker, buffer->size); + memcpy(msgpack_unpacker_buffer(channel->unpacker), + buffer->data, buffer->size); + msgpack_unpacker_buffer_consumed(channel->unpacker, buffer->size); + + parse_msgpack(channel); + + decref(channel); + wstream_release_wbuffer(buffer); +} + static void send_error(Channel *channel, uint64_t id, char *err) { Error e = ERROR_INIT; @@ -636,8 +702,9 @@ static void close_channel(Channel *channel) stream_close(&channel->data.std.out, NULL, NULL); multiqueue_put(main_loop.fast_events, exit_event, 1, channel); return; - default: - abort(); + case kChannelTypeInternal: + // nothing to free. + break; } decref(channel); diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index b6958088ca..c9edd05dc2 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -97,37 +97,59 @@ char *server_address_new(void) #endif } -/// Starts listening for API calls on the TCP address or pipe path `endpoint`. +/// Check if this instance owns a pipe address. +/// The argument must already be resolved to an absolute path! +bool server_owns_pipe_address(const char *path) +{ + for (int i = 0; i < watchers.ga_len; i++) { + if (!strcmp(path, ((SocketWatcher **)watchers.ga_data)[i]->addr)) { + return true; + } + } + return false; +} + +/// Starts listening for API calls. +/// /// The socket type is determined by parsing `endpoint`: If it's a valid IPv4 -/// address in 'ip[:port]' format, then it will be TCP socket. The port is -/// optional and if omitted defaults to NVIM_DEFAULT_TCP_PORT. Otherwise it -/// will be a unix socket or named pipe. +/// or IPv6 address in 'ip:[port]' format, then it will be a TCP socket. +/// Otherwise it will be a Unix socket or named pipe (Windows). +/// +/// If no port is given, a random one will be assigned. /// -/// @param endpoint Address of the server. Either a 'ip[:port]' string or an -/// arbitrary identifier (trimmed to 256 bytes) for the unix socket or -/// named pipe. +/// @param endpoint Address of the server. Either a 'ip:[port]' string or an +/// arbitrary identifier (trimmed to 256 bytes) for the Unix +/// socket or named pipe. /// @returns 0 on success, 1 on a regular error, and negative errno -/// on failure to bind or connect. +/// on failure to bind or listen. int server_start(const char *endpoint) { - if (endpoint == NULL) { - ELOG("Attempting to start server on NULL endpoint"); + if (endpoint == NULL || endpoint[0] == '\0') { + ELOG("Empty or NULL endpoint"); return 1; } SocketWatcher *watcher = xmalloc(sizeof(SocketWatcher)); - socket_watcher_init(&main_loop, watcher, endpoint, NULL); + + int result = socket_watcher_init(&main_loop, watcher, endpoint); + if (result < 0) { + xfree(watcher); + return result; + } // Check if a watcher for the endpoint already exists for (int i = 0; i < watchers.ga_len; i++) { if (!strcmp(watcher->addr, ((SocketWatcher **)watchers.ga_data)[i]->addr)) { ELOG("Already listening on %s", watcher->addr); + if (watcher->stream->type == UV_TCP) { + uv_freeaddrinfo(watcher->uv.tcp.addrinfo); + } socket_watcher_close(watcher, free_server); return 1; } } - int result = socket_watcher_start(watcher, MAX_CONNECTIONS, connection_cb); + result = socket_watcher_start(watcher, MAX_CONNECTIONS, connection_cb); if (result < 0) { ELOG("Failed to start server: %s", uv_strerror(result)); socket_watcher_close(watcher, free_server); diff --git a/src/nvim/option.c b/src/nvim/option.c index 9b4cd0924b..392a2f3908 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -247,7 +247,7 @@ typedef struct vimoption { "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText," \ "d:Directory,e:ErrorMsg,i:IncSearch,l:Search,m:MoreMsg,M:ModeMsg,n:LineNr," \ "N:CursorLineNr,r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title," \ - "v:Visual,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn," \ + "v:Visual,V:VisualNOS,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn," \ "A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn,-:Conceal," \ "B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel," \ "x:PmenuSbar,X:PmenuThumb,*:TabLine,#:TabLineSel,_:TabLineFill," \ @@ -341,7 +341,7 @@ static inline size_t compute_double_colon_len(const char *const val, do { size_t dir_len; const char *dir; - iter = vim_colon_env_iter(val, iter, &dir, &dir_len); + iter = vim_env_iter(':', val, iter, &dir, &dir_len); if (dir != NULL && dir_len > 0) { ret += ((dir_len + memcnt(dir, ',', dir_len) + common_suf_len + !after_pathsep(dir, dir + dir_len)) * 2 @@ -385,8 +385,8 @@ static inline char *add_colon_dirs(char *dest, const char *const val, do { size_t dir_len; const char *dir; - iter = (forward ? vim_colon_env_iter : vim_colon_env_iter_rev)( - val, iter, &dir, &dir_len); + iter = (forward ? vim_env_iter : vim_env_iter_rev)(':', val, iter, &dir, + &dir_len); if (dir != NULL && dir_len > 0) { dest = strcpy_comma_escaped(dest, dir, dir_len); if (!after_pathsep(dest - 1, dest)) { @@ -2124,7 +2124,7 @@ static void didset_options(void) static void didset_options2(void) { // Initialize the highlight_attr[] table. - (void)highlight_changed(); + highlight_changed(); // Parse default for 'clipboard'. (void)opt_strings_flags(p_cb, p_cb_values, &cb_flags, true); @@ -2538,11 +2538,11 @@ did_set_string_option ( if (s[2] == NUL) break; } - } - /* 'highlight' */ - else if (varp == &p_hl) { - if (highlight_changed() == FAIL) - errmsg = e_invarg; /* invalid flags */ + } else if (varp == &p_hl) { + // 'highlight' + if (strcmp((char *)(*varp), HIGHLIGHT_INIT) != 0) { + errmsg = e_unsupportedoption; + } } /* 'nrformats' */ else if (gvarp == &p_nf) { @@ -2639,7 +2639,7 @@ did_set_string_option ( if (varp == &p_enc) { // only encoding=utf-8 allowed if (STRCMP(p_enc, "utf-8") != 0) { - errmsg = e_invarg; + errmsg = e_unsupportedoption; } } } @@ -3207,8 +3207,6 @@ did_set_string_option ( */ if (did_chartab) (void)init_chartab(); - if (varp == &p_hl) - (void)highlight_changed(); } else { /* Remember where the option was set. */ set_option_scriptID_idx(opt_idx, opt_flags, current_SID); @@ -4827,17 +4825,6 @@ char *set_option_value(const char *const name, const long number, return NULL; } -char_u *get_highlight_default(void) -{ - int i; - - i = findoption("hl"); - if (i >= 0) { - return options[i].def_val[VI_DEFAULT]; - } - return (char_u *)NULL; -} - /* * Translate a string like "t_xx", "<t_xx>" or "<S-Tab>" to a key number. */ diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 6ad0501f0a..c2778a6329 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -34,6 +34,11 @@ local macros=function(s) return s end end +local imacros=function(s) + return function() + return '(intptr_t)' .. s + end +end local N_=function(s) return function() return 'N_(' .. cstr(s) .. ')' @@ -2648,7 +2653,7 @@ return { type='number', scope={'global'}, vim=true, varname='p_wc', - defaults={if_true={vi=macros('Ctrl_E'), vim=macros('TAB')}} + defaults={if_true={vi=imacros('Ctrl_E'), vim=imacros('TAB')}} }, { full_name='wildcharm', abbreviation='wcm', diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 8f7a6e72b5..de0cd10d9c 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -521,10 +521,11 @@ static char *remove_tail(char *path, char *pend, char *dirname) return pend; } -/// Iterate over colon-separated list +/// Iterate over a delimited list. /// /// @note Environment variables must not be modified during iteration. /// +/// @param[in] delim Delimiter character. /// @param[in] val Value of the environment variable to iterate over. /// @param[in] iter Pointer used for iteration. Must be NULL on first /// iteration. @@ -533,18 +534,19 @@ static char *remove_tail(char *path, char *pend, char *dirname) /// @param[out] len Location where current directory length should be saved. /// /// @return Next iter argument value or NULL when iteration should stop. -const void *vim_colon_env_iter(const char *const val, - const void *const iter, - const char **const dir, - size_t *const len) - FUNC_ATTR_NONNULL_ARG(1, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT +const void *vim_env_iter(const char delim, + const char *const val, + const void *const iter, + const char **const dir, + size_t *const len) + FUNC_ATTR_NONNULL_ARG(2, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT { const char *varval = (const char *) iter; if (varval == NULL) { varval = val; } *dir = varval; - const char *const dirend = strchr(varval, ':'); + const char *const dirend = strchr(varval, delim); if (dirend == NULL) { *len = strlen(varval); return NULL; @@ -554,10 +556,11 @@ const void *vim_colon_env_iter(const char *const val, } } -/// Iterate over colon-separated list in reverse order +/// Iterate over a delimited list in reverse order. /// /// @note Environment variables must not be modified during iteration. /// +/// @param[in] delim Delimiter character. /// @param[in] val Value of the environment variable to iterate over. /// @param[in] iter Pointer used for iteration. Must be NULL on first /// iteration. @@ -566,18 +569,19 @@ const void *vim_colon_env_iter(const char *const val, /// @param[out] len Location where current directory length should be saved. /// /// @return Next iter argument value or NULL when iteration should stop. -const void *vim_colon_env_iter_rev(const char *const val, - const void *const iter, - const char **const dir, - size_t *const len) - FUNC_ATTR_NONNULL_ARG(1, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT +const void *vim_env_iter_rev(const char delim, + const char *const val, + const void *const iter, + const char **const dir, + size_t *const len) + FUNC_ATTR_NONNULL_ARG(2, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT { const char *varend = (const char *) iter; if (varend == NULL) { varend = val + strlen(val) - 1; } - const size_t varlen = (size_t) (varend - val) + 1; - const char *const colon = xmemrchr(val, ':', varlen); + const size_t varlen = (size_t)(varend - val) + 1; + const char *const colon = xmemrchr(val, (uint8_t)delim, varlen); if (colon == NULL) { *len = varlen; *dir = val; @@ -596,6 +600,9 @@ const void *vim_colon_env_iter_rev(const char *const val, /// @param name Environment variable to expand char *vim_getenv(const char *name) { + // init_path() should have been called before now. + assert(get_vim_var_str(VV_PROGPATH)[0] != NUL); + const char *kos_env_path = os_getenv(name); if (kos_env_path != NULL) { return xstrdup(kos_env_path); @@ -634,18 +641,17 @@ char *vim_getenv(const char *name) char exe_name[MAXPATHL]; // Find runtime path relative to the nvim binary: ../share/nvim/runtime if (vim_path == NULL) { - size_t exe_name_len = MAXPATHL; - if (os_exepath(exe_name, &exe_name_len) == 0) { - char *path_end = (char *)path_tail_with_sep((char_u *)exe_name); - *path_end = '\0'; // remove the trailing "nvim.exe" - path_end = (char *)path_tail((char_u *)exe_name); - *path_end = '\0'; // remove the trailing "bin/" - if (append_path( - exe_name, - "share" _PATHSEPSTR "nvim" _PATHSEPSTR "runtime" _PATHSEPSTR, - MAXPATHL) == OK) { - vim_path = exe_name; // -V507 - } + xstrlcpy(exe_name, (char *)get_vim_var_str(VV_PROGPATH), + sizeof(exe_name)); + char *path_end = (char *)path_tail_with_sep((char_u *)exe_name); + *path_end = '\0'; // remove the trailing "nvim.exe" + path_end = (char *)path_tail((char_u *)exe_name); + *path_end = '\0'; // remove the trailing "bin/" + if (append_path( + exe_name, + "share" _PATHSEPSTR "nvim" _PATHSEPSTR "runtime" _PATHSEPSTR, + MAXPATHL) == OK) { + vim_path = exe_name; // -V507 } } diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index aaa750db50..b9a9480cb8 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -196,11 +196,13 @@ int os_nodetype(const char *name) } /// Gets the absolute path of the currently running executable. +/// May fail if procfs is missing. #6734 +/// @see path_exepath /// -/// @param[out] buffer Returns the path string. +/// @param[out] buffer Full path to the executable. /// @param[in] size Size of `buffer`. /// -/// @return `0` on success, or libuv error code on failure. +/// @return 0 on success, or libuv error code. int os_exepath(char *buffer, size_t *size) FUNC_ATTR_NONNULL_ALL { diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index eb9335b03c..ee3ab96a83 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -12,7 +12,7 @@ #include <sys/ioctl.h> // forkpty is not in POSIX, so headers are platform-specific -#if defined(__FreeBSD__) +#if defined(__FreeBSD__) || defined (__DragonFly__) # include <libutil.h> #elif defined(__OpenBSD__) || defined(__NetBSD__) || defined(__APPLE__) # include <util.h> diff --git a/src/nvim/path.c b/src/nvim/path.c index 12952f49db..f2339c8046 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -435,7 +435,7 @@ bool add_pathsep(char *p) /// @return [allocated] Copy of absolute path to `fname` or NULL when /// `fname` is NULL. char *FullName_save(const char *fname, bool force) - FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC + FUNC_ATTR_MALLOC { if (fname == NULL) { return NULL; @@ -453,7 +453,7 @@ char *FullName_save(const char *fname, bool force) /// @param name An absolute or relative path. /// @return The absolute path of `name`. char_u *save_absolute_path(const char_u *name) - FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { if (!path_is_absolute_path(name)) { return (char_u *)FullName_save((char *)name, true); @@ -1715,7 +1715,7 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force) /// /// @param fname is the filename to expand /// @return [allocated] Full path (NULL for failure). -char *fix_fname(char *fname) +char *fix_fname(const char *fname) { #ifdef UNIX return FullName_save(fname, true); @@ -2237,3 +2237,50 @@ int path_is_absolute_path(const char_u *fname) return *fname == '/' || *fname == '~'; #endif } + +/// Builds a full path from an invocation name `argv0`, based on heuristics. +/// +/// @param[in] argv0 Name by which Nvim was invoked. +/// @param[out] buf Guessed full path to `argv0`. +/// @param[in] bufsize Size of `buf`. +/// +/// @see os_exepath +void path_guess_exepath(const char *argv0, char *buf, size_t bufsize) + FUNC_ATTR_NONNULL_ALL +{ + char *path = getenv("PATH"); + + if (path == NULL || path_is_absolute_path((char_u *)argv0)) { + xstrlcpy(buf, argv0, bufsize); + } else if (argv0[0] == '.' || strchr(argv0, PATHSEP)) { + // Relative to CWD. + if (os_dirname((char_u *)buf, MAXPATHL) != OK) { + buf[0] = NUL; + } + xstrlcat(buf, PATHSEPSTR, bufsize); + xstrlcat(buf, argv0, bufsize); + } else { + // Search $PATH for plausible location. + const void *iter = NULL; + do { + const char *dir; + size_t dir_len; + iter = vim_env_iter(ENV_SEPCHAR, path, iter, &dir, &dir_len); + if (dir == NULL || dir_len == 0) { + break; + } + if (dir_len + 1 > sizeof(NameBuff)) { + continue; + } + xstrlcpy((char *)NameBuff, dir, dir_len + 1); + xstrlcat((char *)NameBuff, PATHSEPSTR, sizeof(NameBuff)); + xstrlcat((char *)NameBuff, argv0, sizeof(NameBuff)); + if (os_can_exe(NameBuff, NULL, false)) { + xstrlcpy(buf, (char *)NameBuff, bufsize); + return; + } + } while (iter != NULL); + // Not found in $PATH, fall back to argv0. + xstrlcpy(buf, argv0, bufsize); + } +} diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 0ec0d5df9d..ff51bf289e 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -188,6 +188,11 @@ typedef struct { */ #define GET_LOC_LIST(wp) (IS_LL_WINDOW(wp) ? wp->w_llist_ref : wp->w_llist) +// Looking up a buffer can be slow if there are many. Remember the last one +// to make this a lot faster if there are multiple matches in the same file. +static char_u *qf_last_bufname = NULL; +static bufref_T qf_last_bufref = { NULL, 0 }; + /* * Read the errorfile "efile" into memory, line by line, building the error * list. Set the error list's title to qf_title. @@ -968,6 +973,10 @@ qf_init_ext( int retval = -1; // default: return error flag int status; + // Do not used the cached buffer, it may have been wiped out. + xfree(qf_last_bufname); + qf_last_bufname = NULL; + fields.namebuf = xmalloc(CMDBUFFSIZE + 1); fields.errmsglen = CMDBUFFSIZE + 1; fields.errmsg = xmalloc(fields.errmsglen); @@ -1399,11 +1408,6 @@ void copy_loclist(win_T *from, win_T *to) to->w_llist->qf_curlist = qi->qf_curlist; /* current list */ } -// Looking up a buffer can be slow if there are many. Remember the last one to -// make this a lot faster if there are multiple matches in the same file. -static char_u *qf_last_bufname = NULL; -static bufref_T qf_last_bufref = { NULL, 0 }; - // Get buffer number for file "directory.fname". // Also sets the b_has_qf_entry flag. static int qf_get_fnum(qf_info_T *qi, char_u *directory, char_u *fname) diff --git a/src/nvim/search.c b/src/nvim/search.c index c662e3ba40..61ef2e9ba3 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -616,7 +616,7 @@ int searchit( * otherwise "/$" will get stuck on end of line. */ while (matchpos.lnum == 0 - && ((options & SEARCH_END) && first_match + && (((options & SEARCH_END) && first_match) ? (nmatched == 1 && (int)endpos.col - 1 < (int)start_pos.col + extra_col) diff --git a/src/nvim/shada.c b/src/nvim/shada.c index e1879ca8c0..87b4617099 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -1277,8 +1277,6 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) if (cur_entry.data.search_pattern.is_last_used) { set_last_used_pattern( cur_entry.data.search_pattern.is_substitute_pattern); - } - if (cur_entry.data.search_pattern.is_last_used) { SET_NO_HLSEARCH(!cur_entry.data.search_pattern.highlighted); } // Do not free shada entry: its allocated memory was saved above. diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index d34d69b3a4..1f7f616782 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -2468,8 +2468,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) } } - if (aff_entry->ae_chop == NULL - && aff_entry->ae_flags == NULL) { + if (aff_entry->ae_chop == NULL) { int idx; char_u **pp; int n; diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index ce48547163..a4bb260183 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -309,6 +309,8 @@ static keyentry_T dumkey; #define HIKEY2KE(p) ((keyentry_T *)((p) - (dumkey.keyword - (char_u *)&dumkey))) #define HI2KE(hi) HIKEY2KE((hi)->hi_key) +// -V:HI2KE:782 + /* * To reduce the time spent in keepend(), remember at which level in the state * stack the first item with "keepend" is present. When "-1", there is no @@ -7304,110 +7306,34 @@ static void highlight_attr_set_all(void) } } -/* - * Translate the 'highlight' option into attributes in highlight_attr[] and - * set up the user highlights User1..9. A set of - * corresponding highlights to use on top of HLF_SNC is computed. - * Called only when the 'highlight' option has been changed and upon first - * screen redraw after any :highlight command. - * Return FAIL when an invalid flag is found in 'highlight'. OK otherwise. - */ -int highlight_changed(void) +/// Tranlate highlight groups into attributes in highlight_attr[] and set up +/// the user highlights User1..9. A set of corresponding highlights to use on +/// top of HLF_SNC is computed. Called only when nvim starts and upon first +/// screen redraw after any :highlight command. +void highlight_changed(void) { - int hlf; - int i; - char_u *p; int attr; - char_u *end; int id; char_u userhl[10]; int id_SNC = -1; int id_S = -1; int hlcnt; - static int hl_flags[HLF_COUNT] = HL_FLAGS; need_highlight_changed = FALSE; - /* - * Clear all attributes. - */ - for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf) - highlight_attr[hlf] = 0; - - /* - * First set all attributes to their default value. - * Then use the attributes from the 'highlight' option. - */ - for (i = 0; i < 2; ++i) { - if (i) - p = p_hl; - else - p = get_highlight_default(); - if (p == NULL) /* just in case */ - continue; - - while (*p) { - for (hlf = 0; hlf < (int)HLF_COUNT; ++hlf) - if (hl_flags[hlf] == *p) - break; - ++p; - if (hlf == (int)HLF_COUNT || *p == NUL) - return FAIL; - - /* - * Allow several hl_flags to be combined, like "bu" for - * bold-underlined. - */ - attr = 0; - bool colon = false; - for (; *p && *p != ','; ++p) { // parse upto comma - if (ascii_iswhite(*p)) { // ignore white space - continue; - } - - if (colon) /* Combination with ':' is not allowed. */ - return FAIL; - - switch (*p) { - case 'b': attr |= HL_BOLD; - break; - case 'i': attr |= HL_ITALIC; - break; - case '-': - case 'n': /* no highlighting */ - break; - case 'r': attr |= HL_INVERSE; - break; - case 's': attr |= HL_STANDOUT; - break; - case 'u': attr |= HL_UNDERLINE; - break; - case 'c': attr |= HL_UNDERCURL; - break; - case ':': ++p; /* highlight group name */ - if (attr || *p == NUL) /* no combinations */ - return FAIL; - colon = true; - end = vim_strchr(p, ','); - if (end == NULL) - end = p + STRLEN(p); - id = syn_check_group(p, (int)(end - p)); - if (id == 0) - return FAIL; - attr = syn_id2attr(id); - p = end - 1; - if (hlf == (int)HLF_SNC) - id_SNC = syn_get_final_id(id); - else if (hlf == (int)HLF_S) - id_S = syn_get_final_id(id); - break; - default: return FAIL; - } - } - highlight_attr[hlf] = attr; - - p = skip_to_option_part(p); /* skip comma and spaces */ + /// Translate builtin highlight groups into attributes for quick lookup. + for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) { + id = syn_check_group((char_u *)hlf_names[hlf], STRLEN(hlf_names[hlf])); + if (id == 0) { + abort(); + } + attr = syn_id2attr(id); + if (hlf == (int)HLF_SNC) { + id_SNC = syn_get_final_id(id); + } else if (hlf == (int)HLF_S) { + id_S = syn_get_final_id(id); } + highlight_attr[hlf] = attr; } /* Setup the user highlights @@ -7472,8 +7398,6 @@ int highlight_changed(void) } } highlight_ga.ga_len = hlcnt; - - return OK; } diff --git a/src/nvim/tag.c b/src/nvim/tag.c index b8b86bf979..88b45add54 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -208,10 +208,9 @@ do_tag ( clearpos(&saved_fmark.mark); /* shutup gcc 4.0 */ saved_fmark.fnum = 0; - /* - * Don't add a tag to the tagstack if 'tagstack' has been reset. - */ - if (!p_tgst && *tag != NUL) { + // Don't add a tag to the tagstack if 'tagstack' has been reset. + assert(tag != NULL); + if (!p_tgst && *tag != NUL) { // -V522 use_tagstack = false; new_tag = true; if (g_do_tagpreview != 0) { diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index b8b7085c5e..5b250ebf54 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -237,8 +237,6 @@ Terminal *terminal_open(TerminalOptions opts) curbuf->b_p_scbk = p_scbk; // 'scrollback' curbuf->b_p_tw = 0; // 'textwidth' set_option_value("wrap", false, NULL, OPT_LOCAL); - set_option_value("number", false, NULL, OPT_LOCAL); - set_option_value("relativenumber", false, NULL, OPT_LOCAL); set_option_value("list", false, NULL, OPT_LOCAL); buf_set_term_title(curbuf, (char *)curbuf->b_ffname); RESET_BINDING(curwin); diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 6832622cdf..732b0aaf74 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -134,7 +134,11 @@ else endif " Names of flaky tests. -let s:flaky = ['Test_with_partial_callback()'] +let s:flaky = [ + \ 'Test_with_partial_callback()', + \ 'Test_oneshot()' + \ 'Test_lambda_with_timer()' + \ ] " Locate Test_ functions and execute them. set nomore diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 4a81b32199..736d50ee8b 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -36,6 +36,7 @@ #include "nvim/tui/tui.h" #include "nvim/cursor_shape.h" #include "nvim/syntax.h" +#include "nvim/macros.h" // Space reserved in the output buffer to restore the cursor to normal when // flushing. No existing terminal will require 32 bytes to do that. @@ -52,6 +53,9 @@ typedef enum TermType { kTermiTerm, kTermKonsole, kTermRxvt, + kTermDTTerm, + kTermXTerm, + kTermTeraTerm, } TermType; typedef struct { @@ -78,7 +82,10 @@ typedef struct { UGrid grid; kvec_t(Rect) invalid_regions; int out_fd; - bool can_use_terminal_scroll; + bool scroll_region_is_full_screen; + bool can_change_scroll_region; + bool can_set_lr_margin; + bool can_set_left_right_margin; bool mouse_enabled; bool busy; cursorentry_T cursor_shapes[SHAPE_IDX_COUNT]; @@ -88,9 +95,12 @@ typedef struct { struct { int enable_mouse, disable_mouse; int enable_bracketed_paste, disable_bracketed_paste; + int enable_lr_margin, disable_lr_margin; int set_rgb_foreground, set_rgb_background; int set_cursor_color; int enable_focus_reporting, disable_focus_reporting; + int resize_screen; + int reset_scroll_region; } unibi_ext; } TUIData; @@ -142,7 +152,7 @@ UI *tui_start(void) static void terminfo_start(UI *ui) { TUIData *data = ui->data; - data->can_use_terminal_scroll = true; + data->scroll_region_is_full_screen = true; data->bufpos = 0; data->bufsize = sizeof(data->buf) - CNORM_COMMAND_MAX_SIZE; data->showing_mode = SHAPE_IDX_N; @@ -151,8 +161,12 @@ static void terminfo_start(UI *ui) data->unibi_ext.set_cursor_color = -1; data->unibi_ext.enable_bracketed_paste = -1; data->unibi_ext.disable_bracketed_paste = -1; + data->unibi_ext.enable_lr_margin = -1; + data->unibi_ext.disable_lr_margin = -1; data->unibi_ext.enable_focus_reporting = -1; data->unibi_ext.disable_focus_reporting = -1; + data->unibi_ext.resize_screen = -1; + data->unibi_ext.reset_scroll_region = -1; data->out_fd = 1; data->out_isatty = os_isatty(data->out_fd); // setup unibilium @@ -163,6 +177,13 @@ static void terminfo_start(UI *ui) data->ut = unibi_dummy(); } fix_terminfo(data); + data->can_change_scroll_region = + !!unibi_get_str(data->ut, unibi_change_scroll_region); + data->can_set_lr_margin = + !!unibi_get_str(data->ut, unibi_set_lr_margin); + data->can_set_left_right_margin = + !!unibi_get_str(data->ut, unibi_set_left_margin_parm) + && !!unibi_get_str(data->ut, unibi_set_right_margin_parm); // Set 't_Co' from the result of unibilium & fix_terminfo. t_colors = unibi_get_num(data->ut, unibi_max_colors); // Enter alternate screen and clear @@ -215,11 +236,6 @@ static void tui_terminal_start(UI *ui) terminfo_start(ui); update_size(ui); signal_watcher_start(&data->winch_handle, sigwinch_cb, SIGWINCH); - -#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 - data->input.tk_ti_hook_fn = tui_tk_ti_getstr; -#endif - term_input_init(&data->input, data->loop); term_input_start(&data->input); } @@ -254,8 +270,14 @@ static void tui_main(UIBridgeData *bridge, UI *ui) #ifdef UNIX signal_watcher_start(&data->cont_handle, sigcont_cb, SIGCONT); #endif + +#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 + data->input.tk_ti_hook_fn = tui_tk_ti_getstr; +#endif + term_input_init(&data->input, &tui_loop); tui_terminal_start(ui); data->stop = false; + // allow the main thread to continue, we are ready to start handling UI // callbacks CONTINUE(bridge); @@ -417,15 +439,83 @@ static void clear_region(UI *ui, int top, int bot, int left, int right) unibi_goto(ui, grid->row, grid->col); } +static bool can_use_scroll(UI * ui) +{ + TUIData *data = ui->data; + UGrid *grid = &data->grid; + + return data->scroll_region_is_full_screen + || (data->can_change_scroll_region + && ((grid->left == 0 && grid->right == ui->width - 1) + || data->can_set_lr_margin + || data->can_set_left_right_margin)); +} + +static void set_scroll_region(UI *ui) +{ + TUIData *data = ui->data; + UGrid *grid = &data->grid; + + data->params[0].i = grid->top; + data->params[1].i = grid->bot; + unibi_out(ui, unibi_change_scroll_region); + if (grid->left != 0 || grid->right != ui->width - 1) { + unibi_out(ui, data->unibi_ext.enable_lr_margin); + if (data->can_set_lr_margin) { + data->params[0].i = grid->left; + data->params[1].i = grid->right; + unibi_out(ui, unibi_set_lr_margin); + } else { + data->params[0].i = grid->left; + unibi_out(ui, unibi_set_left_margin_parm); + data->params[0].i = grid->right; + unibi_out(ui, unibi_set_right_margin_parm); + } + } + unibi_goto(ui, grid->row, grid->col); +} + +static void reset_scroll_region(UI *ui) +{ + TUIData *data = ui->data; + UGrid *grid = &data->grid; + + if (0 <= data->unibi_ext.reset_scroll_region) { + unibi_out(ui, data->unibi_ext.reset_scroll_region); + } else { + data->params[0].i = 0; + data->params[1].i = ui->height - 1; + unibi_out(ui, unibi_change_scroll_region); + } + if (grid->left != 0 || grid->right != ui->width - 1) { + if (data->can_set_lr_margin) { + data->params[0].i = 0; + data->params[1].i = ui->width - 1; + unibi_out(ui, unibi_set_lr_margin); + } else { + data->params[0].i = 0; + unibi_out(ui, unibi_set_left_margin_parm); + data->params[0].i = ui->width - 1; + unibi_out(ui, unibi_set_right_margin_parm); + } + unibi_out(ui, data->unibi_ext.disable_lr_margin); + } + unibi_goto(ui, grid->row, grid->col); +} + static void tui_resize(UI *ui, Integer width, Integer height) { TUIData *data = ui->data; ugrid_resize(&data->grid, (int)width, (int)height); if (!got_winch) { // Try to resize the terminal window. - char r[16]; // enough for 9999x9999 - snprintf(r, sizeof(r), "\x1b[8;%d;%dt", (int)height, (int)width); - out(ui, r, strlen(r)); + data->params[0].i = (int)height; + data->params[1].i = (int)width; + unibi_out(ui, data->unibi_ext.resize_screen); + // DECSLPP does not reset the scroll region. + if (data->scroll_region_is_full_screen) { + reset_scroll_region(ui); + } } else { // Already handled the SIGWINCH signal; avoid double-resize. got_winch = false; } @@ -612,10 +702,9 @@ static void tui_set_scroll_region(UI *ui, Integer top, Integer bot, TUIData *data = ui->data; ugrid_set_scroll_region(&data->grid, (int)top, (int)bot, (int)left, (int)right); - data->can_use_terminal_scroll = + data->scroll_region_is_full_screen = left == 0 && right == ui->width - 1 - && ((top == 0 && bot == ui->height - 1) - || unibi_get_str(data->ut, unibi_change_scroll_region)); + && top == 0 && bot == ui->height - 1; } static void tui_scroll(UI *ui, Integer count) @@ -625,31 +714,31 @@ static void tui_scroll(UI *ui, Integer count) int clear_top, clear_bot; ugrid_scroll(grid, (int)count, &clear_top, &clear_bot); - if (data->can_use_terminal_scroll) { + if (can_use_scroll(ui)) { + bool scroll_clears_to_current_colour = + unibi_get_bool(data->ut, unibi_back_color_erase); + // Change terminal scroll region and move cursor to the top - data->params[0].i = grid->top; - data->params[1].i = grid->bot; - unibi_out(ui, unibi_change_scroll_region); + if (!data->scroll_region_is_full_screen) { + set_scroll_region(ui); + } unibi_goto(ui, grid->top, grid->left); // also set default color attributes or some terminals can become funny - HlAttrs clear_attrs = EMPTY_ATTRS; - clear_attrs.foreground = grid->fg; - clear_attrs.background = grid->bg; - update_attrs(ui, clear_attrs); - } + if (scroll_clears_to_current_colour) { + HlAttrs clear_attrs = EMPTY_ATTRS; + clear_attrs.foreground = grid->fg; + clear_attrs.background = grid->bg; + update_attrs(ui, clear_attrs); + } - if (count > 0) { - if (data->can_use_terminal_scroll) { + if (count > 0) { if (count == 1) { unibi_out(ui, unibi_delete_line); } else { data->params[0].i = (int)count; unibi_out(ui, unibi_parm_delete_line); } - } - - } else { - if (data->can_use_terminal_scroll) { + } else { if (count == -1) { unibi_out(ui, unibi_insert_line); } else { @@ -657,20 +746,16 @@ static void tui_scroll(UI *ui, Integer count) unibi_out(ui, unibi_parm_insert_line); } } - } - if (data->can_use_terminal_scroll) { // Restore terminal scroll region and cursor - data->params[0].i = 0; - data->params[1].i = ui->height - 1; - unibi_out(ui, unibi_change_scroll_region); + if (!data->scroll_region_is_full_screen) { + reset_scroll_region(ui); + } unibi_goto(ui, grid->row, grid->col); - if (grid->bg != -1) { - // Update the cleared area of the terminal if its builtin scrolling - // facility was used and the background color is not the default. This is - // required because scrolling may leave wrong background in the cleared - // area. + if (!scroll_clears_to_current_colour) { + // This is required because scrolling will leave wrong background in the + // cleared area on non-bge terminals. clear_region(ui, clear_top, clear_bot, grid->left, grid->right); } } else { @@ -955,6 +1040,15 @@ static TermType detect_term(const char *term, const char *colorterm) if (colorterm && strstr(colorterm, "gnome-terminal")) { return kTermGnome; } + if (STARTS_WITH(term, "xterm")) { + return kTermXTerm; + } + if (STARTS_WITH(term, "dtterm")) { + return kTermDTTerm; + } + if (STARTS_WITH(term, "teraterm")) { + return kTermTeraTerm; + } return kTermUnknown; } @@ -975,14 +1069,14 @@ static void fix_terminfo(TUIData *data) unibi_set_if_empty(ut, unibi_flash_screen, "\x1b[?5h$<20/>\x1b[?5l"); unibi_set_if_empty(ut, unibi_enter_italics_mode, "\x1b[3m"); unibi_set_if_empty(ut, unibi_to_status_line, "\x1b]2"); - } else if (STARTS_WITH(term, "xterm")) { + } else if (data->term == kTermXTerm) { unibi_set_if_empty(ut, unibi_to_status_line, "\x1b]0;"); } else if (STARTS_WITH(term, "screen") || STARTS_WITH(term, "tmux")) { unibi_set_if_empty(ut, unibi_to_status_line, "\x1b_"); unibi_set_if_empty(ut, unibi_from_status_line, "\x1b\\"); } - if (STARTS_WITH(term, "xterm") || data->term == kTermRxvt) { + if (data->term == kTermXTerm || data->term == kTermRxvt) { const char *normal = unibi_get_str(ut, unibi_cursor_normal); if (!normal) { unibi_set_str(ut, unibi_cursor_normal, "\x1b[?25h"); @@ -996,11 +1090,21 @@ static void fix_terminfo(TUIData *data) unibi_set_if_empty(ut, unibi_cursor_invisible, "\x1b[?25l"); unibi_set_if_empty(ut, unibi_flash_screen, "\x1b[?5h$<100/>\x1b[?5l"); unibi_set_if_empty(ut, unibi_exit_attribute_mode, "\x1b(B\x1b[m"); + unibi_set_if_empty(ut, unibi_set_tb_margin, "\x1b[%i%p1%d;%p2%dr"); + unibi_set_if_empty(ut, unibi_set_lr_margin, "\x1b[%i%p1%d;%p2%ds"); + unibi_set_if_empty(ut, unibi_set_left_margin_parm, "\x1b[%i%p1%ds"); + unibi_set_if_empty(ut, unibi_set_right_margin_parm, "\x1b[%i;%p2%ds"); unibi_set_if_empty(ut, unibi_change_scroll_region, "\x1b[%i%p1%d;%p2%dr"); unibi_set_if_empty(ut, unibi_clear_screen, "\x1b[H\x1b[2J"); unibi_set_if_empty(ut, unibi_from_status_line, "\x07"); + unibi_set_bool(ut, unibi_back_color_erase, true); } + data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(ut, NULL, + "\x1b[?69h"); + data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(ut, NULL, + "\x1b[?69l"); + data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL, "\x1b[?2004h"); data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL, @@ -1027,6 +1131,21 @@ static void fix_terminfo(TUIData *data) unibi_set_str(ut, unibi_set_a_background, XTERM_SETAB); } + // Only define this capability for terminal types that we know understand it. + if (data->term == kTermDTTerm // originated this extension + || data->term == kTermXTerm // per xterm ctlseqs doc + || data->term == kTermKonsole // per commentary in VT102Emulation.cpp + || data->term == kTermTeraTerm // per "Supported Control Functions" doc + || data->term == kTermRxvt) { // per command.C + data->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, NULL, + "\x1b[8;%p1%d;%p2%dt"); + } + + if (data->term == kTermXTerm || data->term == kTermRxvt) { + data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, NULL, + "\x1b[r"); + } + end: // Fill some empty slots with common terminal strings if (data->term == kTermiTerm) { @@ -1079,7 +1198,7 @@ static void flush_buf(UI *ui, bool toggle_cursor) buf.base = data->buf; buf.len = data->bufpos; - uv_write(&req, (uv_stream_t *)&data->output_handle, &buf, 1, NULL); + uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle), &buf, 1, NULL); uv_run(&data->write_loop, UV_RUN_DEFAULT); data->bufpos = 0; @@ -1122,13 +1241,13 @@ static const char *tui_tk_ti_getstr(const char *name, const char *value, if (strequal(name, "key_backspace")) { ILOG("libtermkey:kbs=%s", value); - if (stty_erase != NULL && stty_erase[0] != 0) { + if (stty_erase[0] != 0) { return stty_erase; } } else if (strequal(name, "key_dc")) { ILOG("libtermkey:kdch1=%s", value); // Vim: "If <BS> and <DEL> are now the same, redefine <DEL>." - if (stty_erase != NULL && value != NULL && strequal(stty_erase, value)) { + if (value != NULL && strequal(stty_erase, value)) { return stty_erase[0] == DEL ? CTRL_H_STR : DEL_STR; } } |