aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/api/buffer.c20
-rw-r--r--src/nvim/api/private/helpers.c39
-rw-r--r--src/nvim/api/vim.c11
-rw-r--r--src/nvim/buffer_defs.h7
-rw-r--r--src/nvim/eval.c130
-rw-r--r--src/nvim/eval.lua1
-rw-r--r--src/nvim/event/socket.c194
-rw-r--r--src/nvim/event/socket.h2
-rw-r--r--src/nvim/getchar.c27
-rw-r--r--src/nvim/if_cscope.c2
-rw-r--r--src/nvim/msgpack_rpc/channel.c97
-rw-r--r--src/nvim/msgpack_rpc/server.c46
-rw-r--r--src/nvim/os/pty_process_unix.c2
-rw-r--r--src/nvim/path.c2
-rw-r--r--src/nvim/quickfix.c14
-rw-r--r--src/nvim/terminal.c2
16 files changed, 480 insertions, 116 deletions
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 53e5f71fd4..0cffb2c87d 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -742,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_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/eval.c b/src/nvim/eval.c
index 35ca236831..7187386ec7 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -12103,22 +12103,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);
}
}
}
@@ -12135,6 +12120,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
*/
@@ -14297,22 +14322,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
@@ -15027,6 +15069,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;
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/event/socket.c b/src/nvim/event/socket.c
index 922e9c8be8..30a71a5586 100644
--- a/src/nvim/event/socket.c
+++ b/src/nvim/event/socket.c
@@ -15,62 +15,56 @@
#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 = STRUCT_CAST(uv_stream_t, &watcher->uv.tcp.handle);
} else {
@@ -82,33 +76,60 @@ void socket_watcher_init(Loop *loop, SocketWatcher *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;
@@ -169,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/getchar.c b/src/nvim/getchar.c
index c3c393f1ec..4e42042959 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.
@@ -1907,7 +1908,7 @@ static int vgetorpeek(int advance)
}
if ((mp == NULL || max_mlen >= mp_match_len)
- && keylen != KEYLEN_PART_MAP && keylen != KEYLEN_PART_KEY) {
+ && keylen != KEYLEN_PART_MAP) {
// No matching mapping found or found a non-matching mapping that
// matches at least what the matching mapping matched
keylen = 0;
@@ -4237,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/if_cscope.c b/src/nvim/if_cscope.c
index e1c7df9175..f7706f5a0d 100644
--- a/src/nvim/if_cscope.c
+++ b/src/nvim/if_cscope.c
@@ -1972,7 +1972,7 @@ static void cs_release_csp(size_t i, int freefnpp)
static int cs_reset(exarg_T *eap)
{
char **dblist = NULL, **pplist = NULL, **fllist = NULL;
- char buf[20]; /* for snprintf " (#%zu)" */
+ char buf[25]; // for snprintf " (#%zu)"
if (csinfo_size == 0)
return CSCOPE_SUCCESS;
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index cd64e14976..413b800af5 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
{
@@ -485,7 +532,7 @@ static void on_request_event(void **argv)
static bool channel_write(Channel *channel, WBuffer *buffer)
{
- bool success;
+ bool success = false;
if (channel->closed) {
wstream_release_wbuffer(buffer);
@@ -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/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 9162b6da4d..f2339c8046 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -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);
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/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);