aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/CMakeLists.txt77
-rw-r--r--src/nvim/api/buffer.c20
-rw-r--r--src/nvim/api/private/helpers.c39
-rw-r--r--src/nvim/api/vim.c34
-rw-r--r--src/nvim/buffer.c2
-rw-r--r--src/nvim/buffer_defs.h7
-rw-r--r--src/nvim/charset.c12
-rw-r--r--src/nvim/edit.c2
-rw-r--r--src/nvim/eval.c300
-rw-r--r--src/nvim/eval.lua1
-rw-r--r--src/nvim/eval/encode.c6
-rw-r--r--src/nvim/eval/typval.c29
-rw-r--r--src/nvim/event/libuv_process.c10
-rw-r--r--src/nvim/event/process.c10
-rw-r--r--src/nvim/event/socket.c203
-rw-r--r--src/nvim/event/socket.h2
-rw-r--r--src/nvim/event/stream.c8
-rw-r--r--src/nvim/func_attr.h26
-rw-r--r--src/nvim/generators/gen_options.lua2
-rw-r--r--src/nvim/getchar.c44
-rw-r--r--src/nvim/globals.h61
-rw-r--r--src/nvim/lua/converter.c3
-rw-r--r--src/nvim/lua/executor.c65
-rw-r--r--src/nvim/macros.h12
-rw-r--r--src/nvim/main.c29
-rw-r--r--src/nvim/memfile.c4
-rw-r--r--src/nvim/msgpack_rpc/channel.c95
-rw-r--r--src/nvim/msgpack_rpc/server.c46
-rw-r--r--src/nvim/option.c35
-rw-r--r--src/nvim/options.lua7
-rw-r--r--src/nvim/os/env.c60
-rw-r--r--src/nvim/os/fs.c6
-rw-r--r--src/nvim/os/pty_process_unix.c2
-rw-r--r--src/nvim/path.c53
-rw-r--r--src/nvim/quickfix.c14
-rw-r--r--src/nvim/search.c2
-rw-r--r--src/nvim/shada.c2
-rw-r--r--src/nvim/spellfile.c3
-rw-r--r--src/nvim/syntax.c114
-rw-r--r--src/nvim/tag.c7
-rw-r--r--src/nvim/terminal.c2
-rw-r--r--src/nvim/testdir/runtest.vim6
-rw-r--r--src/nvim/tui/tui.c205
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(&params);
- 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;
}
}