aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/nvim/CMakeLists.txt19
-rw-r--r--src/nvim/eval.c31
-rw-r--r--src/nvim/ex_cmds2.c8
-rw-r--r--src/nvim/lib/klist.h35
-rw-r--r--src/nvim/main.c1
-rw-r--r--src/nvim/memory.h1
-rw-r--r--src/nvim/msgpack_rpc/channel.c20
-rw-r--r--src/nvim/normal.c5
-rw-r--r--src/nvim/ops.c2
-rw-r--r--src/nvim/option.c5
-rw-r--r--src/nvim/option_defs.h1
-rw-r--r--src/nvim/os/event.c7
-rw-r--r--src/nvim/os/fs.c33
-rw-r--r--src/nvim/os/input.c31
-rw-r--r--src/nvim/os/job.c6
-rw-r--r--src/nvim/os/rstream.c211
-rw-r--r--src/nvim/os/rstream.h1
-rw-r--r--src/nvim/os/rstream_defs.h7
-rw-r--r--src/nvim/os/shell.c29
-rw-r--r--src/nvim/rbuffer.c205
-rw-r--r--src/nvim/rbuffer.h83
-rw-r--r--src/nvim/spell.c2
-rw-r--r--src/nvim/testdir/test86.in3
-rw-r--r--src/nvim/testdir/test87.in5
-rw-r--r--src/nvim/tui/term_input.inl55
25 files changed, 489 insertions, 317 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 2e45b5e9d6..ad5bac1898 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -40,6 +40,7 @@ file(MAKE_DIRECTORY ${GENERATED_INCLUDES_DIR}/tui)
file(GLOB NEOVIM_SOURCES *.c os/*.c api/*.c api/private/*.c msgpack_rpc/*.c
tui/*.c)
file(GLOB_RECURSE NEOVIM_HEADERS *.h)
+file(GLOB UNIT_TEST_FIXTURES ${PROJECT_SOURCE_DIR}/test/unit/fixtures/*.c)
foreach(sfile ${NEOVIM_SOURCES})
get_filename_component(f ${sfile} NAME)
@@ -94,7 +95,7 @@ foreach(gen_cdef ${gen_cdefs} DO_NOT_DEFINE_EMPTY_ATTRIBUTES)
list(APPEND gen_cflags "-D${gen_cdef}")
endif()
endforeach()
-if (SANITIZE)
+if(CLANG_ASAN_UBSAN OR CLANG_MSAN OR CLANG_TSAN)
list(APPEND gen_cflags "-DEXITFREE")
endif()
@@ -189,11 +190,21 @@ add_executable(nvim ${NEOVIM_GENERATED_SOURCES} ${NEOVIM_SOURCES}
target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES})
install_helper(TARGETS nvim)
-if(SANITIZE)
- message(STATUS "Enabling Clang sanitizers for nvim")
+if(CLANG_ASAN_UBSAN)
+ message(STATUS "Enabling Clang address sanitizer and undefined behavior sanitizer for nvim.")
set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "-DEXITFREE ")
set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "-fno-sanitize-recover -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address -fsanitize=undefined ")
set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=address -fsanitize=undefined ")
+elseif(CLANG_MSAN)
+ message(STATUS "Enabling Clang memory sanitizer for nvim.")
+ set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "-DEXITFREE ")
+ set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "-fsanitize=memory -fsanitize-memory-track-origins -fno-omit-frame-pointer -fno-optimize-sibling-calls ")
+ set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=memory -fsanitize-memory-track-origins ")
+elseif(CLANG_TSAN)
+ message(STATUS "Enabling Clang thread sanitizer for nvim.")
+ set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "-DEXITFREE ")
+ set_property(TARGET nvim APPEND_STRING PROPERTY COMPILE_FLAGS "-fsanitize=thread ")
+ set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=thread ")
endif()
add_library(libnvim STATIC EXCLUDE_FROM_ALL ${NEOVIM_GENERATED_SOURCES}
@@ -205,7 +216,7 @@ set_target_properties(libnvim PROPERTIES
set_property(TARGET libnvim APPEND_STRING PROPERTY COMPILE_FLAGS " -DMAKE_LIB ")
add_library(nvim-test MODULE EXCLUDE_FROM_ALL ${NEOVIM_GENERATED_SOURCES}
- ${NEOVIM_SOURCES} ${NEOVIM_HEADERS})
+ ${NEOVIM_SOURCES} ${UNIT_TEST_FIXTURES} ${NEOVIM_HEADERS})
target_link_libraries(nvim-test ${NVIM_LINK_LIBRARIES})
set_property(TARGET nvim-test APPEND_STRING PROPERTY COMPILE_FLAGS -DUNIT_TESTING)
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 93590445c9..d3ab47a505 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -20341,19 +20341,19 @@ static inline void push_job_event(Job *job, ufunc_T *callback,
}, !disable_job_defer);
}
-static void on_job_stdout(RStream *rstream, void *job, bool eof)
+static void on_job_stdout(RStream *rstream, RBuffer *buf, void *job, bool eof)
{
TerminalJobData *data = job_data(job);
- on_job_output(rstream, job, eof, data->on_stdout, "stdout");
+ on_job_output(rstream, job, buf, eof, data->on_stdout, "stdout");
}
-static void on_job_stderr(RStream *rstream, void *job, bool eof)
+static void on_job_stderr(RStream *rstream, RBuffer *buf, void *job, bool eof)
{
TerminalJobData *data = job_data(job);
- on_job_output(rstream, job, eof, data->on_stderr, "stderr");
+ on_job_output(rstream, job, buf, eof, data->on_stderr, "stderr");
}
-static void on_job_output(RStream *rstream, Job *job, bool eof,
+static void on_job_output(RStream *rstream, Job *job, RBuffer *buf, bool eof,
ufunc_T *callback, const char *type)
{
if (eof) {
@@ -20361,20 +20361,19 @@ static void on_job_output(RStream *rstream, Job *job, bool eof,
}
TerminalJobData *data = job_data(job);
- char *ptr = rstream_read_ptr(rstream);
- size_t len = rstream_pending(rstream);
+ RBUFFER_UNTIL_EMPTY(buf, ptr, len) {
+ // The order here matters, the terminal must receive the data first because
+ // push_job_event will modify the read buffer(convert NULs into NLs)
+ if (data->term) {
+ terminal_receive(data->term, ptr, len);
+ }
- // The order here matters, the terminal must receive the data first because
- // push_job_event will modify the read buffer(convert NULs into NLs)
- if (data->term) {
- terminal_receive(data->term, ptr, len);
- }
+ if (callback) {
+ push_job_event(job, callback, type, ptr, len, 0);
+ }
- if (callback) {
- push_job_event(job, callback, type, ptr, len, 0);
+ rbuffer_consumed(buf, len);
}
-
- rbuffer_consumed(rstream_buffer(rstream), len);
}
static void on_job_exit(Job *job, int status, void *d)
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index e7029b8762..d9a8dc2d69 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -2992,12 +2992,8 @@ void ex_checktime(exarg_T *eap)
static char *get_locale_val(int what)
{
- char *loc;
-
- /* Obtain the locale value from the libraries. For DJGPP this is
- * redefined and it doesn't use the arguments. */
- loc = setlocale(what, NULL);
-
+ // Obtain the locale value from the libraries.
+ char *loc = setlocale(what, NULL);
return loc;
}
diff --git a/src/nvim/lib/klist.h b/src/nvim/lib/klist.h
index 7df809f07b..10d6846133 100644
--- a/src/nvim/lib/klist.h
+++ b/src/nvim/lib/klist.h
@@ -27,10 +27,12 @@
#define _AC_KLIST_H
#include <stdlib.h>
+#include <assert.h>
#include "nvim/memory.h"
#include "nvim/func_attr.h"
+
#define KMEMPOOL_INIT(name, kmptype_t, kmpfree_f) \
typedef struct { \
size_t cnt, n, max; \
@@ -95,23 +97,27 @@
kmp_free(name, kl->mp, p); \
kmp_free(name, kl->mp, p); \
kmp_destroy(name, kl->mp); \
- xfree(kl); \
+ xfree(kl); \
} \
- static inline kltype_t *kl_pushp_##name(kl_##name##_t *kl) { \
+ static inline void kl_push_##name(kl_##name##_t *kl, kltype_t d) { \
kl1_##name *q, *p = kmp_alloc(name, kl->mp); \
q = kl->tail; p->next = 0; kl->tail->next = p; kl->tail = p; \
++kl->size; \
- return &q->data; \
+ q->data = d; \
} \
- static inline int kl_shift_##name(kl_##name##_t *kl, kltype_t *d) { \
+ static inline kltype_t kl_shift_at_##name(kl_##name##_t *kl, \
+ kl1_##name **n) { \
+ assert((*n)->next); \
kl1_##name *p; \
- if (kl->head->next == 0) return -1; \
--kl->size; \
- p = kl->head; kl->head = kl->head->next; \
- if (d) *d = p->data; \
+ p = *n; \
+ *n = (*n)->next; \
+ if (p == kl->head) kl->head = *n; \
+ kltype_t d = p->data; \
kmp_free(name, kl->mp, p); \
- return 0; \
- }
+ return d; \
+ } \
+
#define kliter_t(name) kl1_##name
#define klist_t(name) kl_##name##_t
@@ -122,7 +128,14 @@
#define kl_init(name) kl_init_##name()
#define kl_destroy(name, kl) kl_destroy_##name(kl)
-#define kl_pushp(name, kl) kl_pushp_##name(kl)
-#define kl_shift(name, kl, d) kl_shift_##name(kl, d)
+#define kl_push(name, kl, d) kl_push_##name(kl, d)
+#define kl_shift_at(name, kl, node) kl_shift_at_##name(kl, node)
+#define kl_shift(name, kl) kl_shift_at(name, kl, &kl->head)
#define kl_empty(kl) ((kl)->size == 0)
+// Iteration macros. It's ok to modify the list while iterating as long as a
+// `break` statement is executed before the next iteration.
+#define kl_iter(name, kl, p) kl_iter_at(name, kl, p, NULL)
+#define kl_iter_at(name, kl, p, h) \
+ for (kl1_##name *p = h ? h : kl->head; p != kl->tail; p = p->next)
+
#endif
diff --git a/src/nvim/main.c b/src/nvim/main.c
index e1bb2d0b66..50c16c51d6 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -138,6 +138,7 @@ static const char *err_extra_cmd =
/// Needed for unit tests. Must be called after `time_init()`.
void early_init(void)
{
+ fs_init();
handle_init();
(void)mb_init(); // init mb_bytelen_tab[] to ones
diff --git a/src/nvim/memory.h b/src/nvim/memory.h
index 4ff31ff732..7b477da2f5 100644
--- a/src/nvim/memory.h
+++ b/src/nvim/memory.h
@@ -1,6 +1,7 @@
#ifndef NVIM_MEMORY_H
#define NVIM_MEMORY_H
+#include <stdint.h> // for uint8_t
#include <stddef.h> // for size_t
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index df78f822d6..2a81b4f160 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -328,19 +328,17 @@ static void channel_from_stdio(void)
channel->data.streams.uv = NULL;
}
-static void job_out(RStream *rstream, void *data, bool eof)
+static void job_out(RStream *rstream, RBuffer *buf, void *data, bool eof)
{
Job *job = data;
- parse_msgpack(rstream, job_data(job), eof);
+ parse_msgpack(rstream, buf, job_data(job), eof);
}
-static void job_err(RStream *rstream, void *data, bool eof)
+static void job_err(RStream *rstream, RBuffer *rbuf, void *data, bool eof)
{
- size_t count;
- char buf[256];
-
- while ((count = rstream_pending(rstream))) {
- size_t read = rstream_read(rstream, buf, sizeof(buf) - 1);
+ while (rbuffer_size(rbuf)) {
+ char buf[256];
+ size_t read = rbuffer_read(rbuf, buf, sizeof(buf) - 1);
buf[read] = NUL;
ELOG("Channel %" PRIu64 " stderr: %s",
((Channel *)job_data(data))->id, buf);
@@ -352,7 +350,7 @@ static void job_exit(Job *job, int status, void *data)
decref(data);
}
-static void parse_msgpack(RStream *rstream, void *data, bool eof)
+static void parse_msgpack(RStream *rstream, RBuffer *rbuf, void *data, bool eof)
{
Channel *channel = data;
incref(channel);
@@ -363,14 +361,14 @@ static void parse_msgpack(RStream *rstream, void *data, bool eof)
goto end;
}
- size_t count = rstream_pending(rstream);
+ size_t count = rbuffer_size(rbuf);
DLOG("Feeding the msgpack parser with %u bytes of data from RStream(%p)",
count,
rstream);
// Feed the unpacker with data
msgpack_unpacker_reserve_buffer(channel->unpacker, count);
- rstream_read(rstream, msgpack_unpacker_buffer(channel->unpacker), count);
+ rbuffer_read(rbuf, msgpack_unpacker_buffer(channel->unpacker), count);
msgpack_unpacker_buffer_consumed(channel->unpacker, count);
msgpack_unpacked unpacked;
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 7b42467184..92734e404a 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -7278,7 +7278,10 @@ static void nv_put(cmdarg_T *cap)
*/
was_visual = true;
regname = cap->oap->regname;
- if (regname == 0 || regname == '"'
+ // '+' and '*' could be the same selection
+ bool clipoverwrite = (regname == '+' || regname == '*')
+ && (cb_flags & CB_UNNAMEDMASK);
+ if (regname == 0 || regname == '"' || clipoverwrite
|| ascii_isdigit(regname) || regname == '-') {
// The delete might overwrite the register we want to put, save it first
savereg = copy_register(regname);
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index d8df6ae72d..063ad154f1 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -67,8 +67,6 @@
#define PLUS_REGISTER 38
#define NUM_REGISTERS 39
-#define CB_UNNAMEDMASK (CB_UNNAMED | CB_UNNAMEDPLUS)
-
static yankreg_T y_regs[NUM_REGISTERS];
static yankreg_T *y_previous = NULL; /* ptr to last written yankreg */
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 8ec5640b7a..113c47f112 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -366,7 +366,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,V:VisualNOS,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn," \
+ "v:Visual,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," \
@@ -842,9 +842,6 @@ static vimoption_T
(char_u *)NULL, PV_NONE,
{(char_u *)NULL, (char_u *)0L}
SCRIPTID_INIT},
- {"guipty", NULL, P_BOOL|P_VI_DEF,
- (char_u *)NULL, PV_NONE,
- {(char_u *)TRUE, (char_u *)0L} SCRIPTID_INIT},
{"guitablabel", "gtl", P_STRING|P_VI_DEF|P_RWIN,
(char_u *)NULL, PV_NONE,
{(char_u *)NULL, (char_u *)0L}
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index 80f2373a85..b13ead9cbb 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -318,6 +318,7 @@ static char *(p_cb_values[]) = {"unnamed", "unnamedplus", NULL};
#endif
# define CB_UNNAMED 0x001
# define CB_UNNAMEDPLUS 0x002
+# define CB_UNNAMEDMASK (CB_UNNAMED | CB_UNNAMEDPLUS)
EXTERN long p_cwh; /* 'cmdwinheight' */
EXTERN long p_ch; /* 'cmdheight' */
EXTERN int p_confirm; /* 'confirm' */
diff --git a/src/nvim/os/event.c b/src/nvim/os/event.c
index 4c3a4581c3..56874b495d 100644
--- a/src/nvim/os/event.c
+++ b/src/nvim/os/event.c
@@ -149,7 +149,7 @@ void event_push(Event event, bool deferred)
// returns(user hits a key for example). To avoid this scenario, we call
// uv_stop when a event is enqueued.
uv_stop(uv_default_loop());
- *kl_pushp(Event, deferred ? deferred_events : immediate_events) = event;
+ kl_push(Event, deferred ? deferred_events : immediate_events, event);
}
void event_process(void)
@@ -159,9 +159,8 @@ void event_process(void)
static void process_events_from(klist_t(Event) *queue)
{
- Event event;
-
- while (kl_shift(Event, queue, &event) == 0) {
+ while (!kl_empty(queue)) {
+ Event event = kl_shift(Event, queue);
event.handler(event);
}
}
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 52c10d0ca7..553dda5e88 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -19,6 +19,15 @@
// Many fs functions from libuv return that value on success.
static const int kLibuvSuccess = 0;
+static uv_loop_t fs_loop;
+
+
+// Initialize the fs module
+void fs_init(void)
+{
+ uv_loop_init(&fs_loop);
+}
+
/// Change to the given directory.
///
@@ -184,7 +193,7 @@ int os_open(const char* path, int flags, int mode)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t open_req;
- int r = uv_fs_open(uv_default_loop(), &open_req, path, flags, mode, NULL);
+ int r = uv_fs_open(&fs_loop, &open_req, path, flags, mode, NULL);
uv_fs_req_cleanup(&open_req);
// r is the same as open_req.result (except for OOM: then only r is set).
return r;
@@ -197,7 +206,7 @@ static bool os_stat(const char *name, uv_stat_t *statbuf)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- int result = uv_fs_stat(uv_default_loop(), &request, name, NULL);
+ int result = uv_fs_stat(&fs_loop, &request, name, NULL);
*statbuf = request.statbuf;
uv_fs_req_cleanup(&request);
return (result == kLibuvSuccess);
@@ -224,7 +233,7 @@ int os_setperm(const char_u *name, int perm)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- int result = uv_fs_chmod(uv_default_loop(), &request,
+ int result = uv_fs_chmod(&fs_loop, &request,
(const char*)name, perm, NULL);
uv_fs_req_cleanup(&request);
@@ -245,7 +254,7 @@ int os_fchown(int file_descriptor, uv_uid_t owner, uv_gid_t group)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- int result = uv_fs_fchown(uv_default_loop(), &request, file_descriptor,
+ int result = uv_fs_fchown(&fs_loop, &request, file_descriptor,
owner, group, NULL);
uv_fs_req_cleanup(&request);
return result;
@@ -294,7 +303,7 @@ int os_rename(const char_u *path, const char_u *new_path)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- int result = uv_fs_rename(uv_default_loop(), &request,
+ int result = uv_fs_rename(&fs_loop, &request,
(const char *)path, (const char *)new_path, NULL);
uv_fs_req_cleanup(&request);
@@ -312,7 +321,7 @@ int os_mkdir(const char *path, int32_t mode)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- int result = uv_fs_mkdir(uv_default_loop(), &request, path, mode, NULL);
+ int result = uv_fs_mkdir(&fs_loop, &request, path, mode, NULL);
uv_fs_req_cleanup(&request);
return result;
}
@@ -328,7 +337,7 @@ int os_mkdtemp(const char *template, char *path)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- int result = uv_fs_mkdtemp(uv_default_loop(), &request, template, NULL);
+ int result = uv_fs_mkdtemp(&fs_loop, &request, template, NULL);
if (result == kLibuvSuccess) {
STRNCPY(path, request.path, TEMP_FILE_PATH_MAXLEN);
}
@@ -343,7 +352,7 @@ int os_rmdir(const char *path)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- int result = uv_fs_rmdir(uv_default_loop(), &request, path, NULL);
+ int result = uv_fs_rmdir(&fs_loop, &request, path, NULL);
uv_fs_req_cleanup(&request);
return result;
}
@@ -356,7 +365,7 @@ int os_rmdir(const char *path)
bool os_scandir(Directory *dir, const char *path)
FUNC_ATTR_NONNULL_ALL
{
- int r = uv_fs_scandir(uv_default_loop(), &dir->request, path, 0, NULL);
+ int r = uv_fs_scandir(&fs_loop, &dir->request, path, 0, NULL);
if (r <= 0) {
os_closedir(dir);
}
@@ -388,7 +397,7 @@ int os_remove(const char *path)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- int result = uv_fs_unlink(uv_default_loop(), &request, path, NULL);
+ int result = uv_fs_unlink(&fs_loop, &request, path, NULL);
uv_fs_req_cleanup(&request);
return result;
}
@@ -413,7 +422,7 @@ bool os_fileinfo_link(const char *path, FileInfo *file_info)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- int result = uv_fs_lstat(uv_default_loop(), &request, path, NULL);
+ int result = uv_fs_lstat(&fs_loop, &request, path, NULL);
file_info->stat = request.statbuf;
uv_fs_req_cleanup(&request);
return (result == kLibuvSuccess);
@@ -428,7 +437,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- int result = uv_fs_fstat(uv_default_loop(), &request, file_descriptor, NULL);
+ int result = uv_fs_fstat(&fs_loop, &request, file_descriptor, NULL);
file_info->stat = request.statbuf;
uv_fs_req_cleanup(&request);
return (result == kLibuvSuccess);
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 74a5d3bc2e..726335bd9a 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -79,7 +79,7 @@ void input_stop(void)
// Low level input function
int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt)
{
- if (rbuffer_pending(input_buffer)) {
+ if (rbuffer_size(input_buffer)) {
return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen);
}
@@ -108,7 +108,7 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt)
return 0;
}
- if (rbuffer_pending(input_buffer)) {
+ if (rbuffer_size(input_buffer)) {
// Safe to convert rbuffer_read to int, it will never overflow since we use
// relatively small buffers.
return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen);
@@ -153,7 +153,7 @@ size_t input_enqueue(String keys)
{
char *ptr = keys.data, *end = ptr + keys.size;
- while (rbuffer_available(input_buffer) >= 6 && ptr < end) {
+ while (rbuffer_space(input_buffer) >= 6 && ptr < end) {
uint8_t buf[6] = {0};
unsigned int new_size = trans_special((uint8_t **)&ptr, buf, true);
@@ -309,16 +309,17 @@ static InbufPollResult inbuf_poll(int ms)
return input_eof ? kInputEof : kInputNone;
}
-static void read_cb(RStream *rstream, void *data, bool at_eof)
+static void read_cb(RStream *rstream, RBuffer *buf, void *data, bool at_eof)
{
if (at_eof) {
input_eof = true;
}
- char *buf = rbuffer_read_ptr(read_buffer);
- size_t buf_size = rbuffer_pending(read_buffer);
- (void)rbuffer_write(input_buffer, buf, buf_size);
- rbuffer_consumed(read_buffer, buf_size);
+ assert(rbuffer_space(input_buffer) >= rbuffer_size(read_buffer));
+ RBUFFER_UNTIL_EMPTY(read_buffer, ptr, len) {
+ (void)rbuffer_write(input_buffer, ptr, len);
+ rbuffer_consumed(read_buffer, len);
+ }
}
static void process_interrupts(void)
@@ -327,18 +328,16 @@ static void process_interrupts(void)
return;
}
- char *inbuf = rbuffer_read_ptr(input_buffer);
- size_t count = rbuffer_pending(input_buffer), consume_count = 0;
-
- for (int i = (int)count - 1; i >= 0; i--) {
- if (inbuf[i] == 3) {
+ size_t consume_count = 0;
+ RBUFFER_EACH_REVERSE(input_buffer, c, i) {
+ if ((uint8_t)c == 3) {
got_int = true;
- consume_count = (size_t)i;
+ consume_count = i;
break;
}
}
- if (got_int) {
+ if (got_int && consume_count) {
// Remove everything typed before the CTRL-C
rbuffer_consumed(input_buffer, consume_count);
}
@@ -362,7 +361,7 @@ static int push_event_key(uint8_t *buf, int maxlen)
static bool input_ready(void)
{
return typebuf_was_filled || // API call filled typeahead
- rbuffer_pending(input_buffer) > 0 || // Input buffer filled
+ rbuffer_size(input_buffer) || // Input buffer filled
event_has_deferred(); // Events must be processed
}
diff --git a/src/nvim/os/job.c b/src/nvim/os/job.c
index 038d0e3c26..4769ee4d2f 100644
--- a/src/nvim/os/job.c
+++ b/src/nvim/os/job.c
@@ -404,17 +404,17 @@ static void job_stop_timer_cb(uv_timer_t *handle)
}
// Wraps the call to std{out,err}_cb and emits a JobExit event if necessary.
-static void read_cb(RStream *rstream, void *data, bool eof)
+static void read_cb(RStream *rstream, RBuffer *buf, void *data, bool eof)
{
Job *job = data;
if (rstream == job->out) {
- job->opts.stdout_cb(rstream, data, eof);
+ job->opts.stdout_cb(rstream, buf, data, eof);
if (eof) {
close_job_out(job);
}
} else {
- job->opts.stderr_cb(rstream, data, eof);
+ job->opts.stderr_cb(rstream, buf, data, eof);
if (eof) {
close_job_err(job);
}
diff --git a/src/nvim/os/rstream.c b/src/nvim/os/rstream.c
index 702f282d53..af84288f0f 100644
--- a/src/nvim/os/rstream.c
+++ b/src/nvim/os/rstream.c
@@ -14,12 +14,6 @@
#include "nvim/log.h"
#include "nvim/misc1.h"
-struct rbuffer {
- char *data;
- size_t capacity, rpos, wpos;
- RStream *rstream;
-};
-
struct rstream {
void *data;
uv_buf_t uvbuf;
@@ -37,135 +31,6 @@ struct rstream {
# include "os/rstream.c.generated.h"
#endif
-/// Creates a new `RBuffer` instance.
-RBuffer *rbuffer_new(size_t capacity)
-{
- RBuffer *rv = xmalloc(sizeof(RBuffer));
- rv->data = xmalloc(capacity);
- rv->capacity = capacity;
- rv->rpos = rv->wpos = 0;
- rv->rstream = NULL;
- return rv;
-}
-
-/// Advances `rbuffer` read pointers to consume data. If the associated
-/// RStream had stopped because the buffer was full, this will restart it.
-///
-/// This is called automatically by rbuffer_read, but when using
-/// `rbuffer_read_ptr` directly, this needs to called after the data was
-/// consumed.
-void rbuffer_consumed(RBuffer *rbuffer, size_t count)
-{
- rbuffer->rpos += count;
- if (count && rbuffer->wpos == rbuffer->capacity) {
- // `wpos` is at the end of the buffer, so free some space by moving unread
- // data...
- rbuffer_relocate(rbuffer);
- if (rbuffer->rstream) {
- // restart the associated RStream
- rstream_start(rbuffer->rstream);
- }
- }
-}
-
-/// Advances `rbuffer` write pointers. If the internal buffer becomes full,
-/// this will stop the associated RStream instance.
-void rbuffer_produced(RBuffer *rbuffer, size_t count)
-{
- rbuffer->wpos += count;
- DLOG("Received %u bytes from RStream(%p)", (size_t)count, rbuffer->rstream);
-
- rbuffer_relocate(rbuffer);
- if (rbuffer->rstream && rbuffer->wpos == rbuffer->capacity) {
- // The last read filled the buffer, stop reading for now
- //
- rstream_stop(rbuffer->rstream);
- DLOG("Buffer for RStream(%p) is full, stopping it", rbuffer->rstream);
- }
-}
-
-/// Reads data from a `RBuffer` instance into a raw buffer.
-///
-/// @param rbuffer The `RBuffer` instance
-/// @param buffer The buffer which will receive the data
-/// @param count Number of bytes that `buffer` can accept
-/// @return The number of bytes copied into `buffer`
-size_t rbuffer_read(RBuffer *rbuffer, char *buffer, size_t count)
-{
- size_t read_count = rbuffer_pending(rbuffer);
-
- if (count < read_count) {
- read_count = count;
- }
-
- if (read_count > 0) {
- memcpy(buffer, rbuffer_read_ptr(rbuffer), read_count);
- rbuffer_consumed(rbuffer, read_count);
- }
-
- return read_count;
-}
-
-/// Copies data to `rbuffer` read queue.
-///
-/// @param rbuffer the `RBuffer` instance
-/// @param buffer The buffer containing data to be copied
-/// @param count Number of bytes that should be copied
-/// @return The number of bytes actually copied
-size_t rbuffer_write(RBuffer *rbuffer, char *buffer, size_t count)
-{
- size_t write_count = rbuffer_available(rbuffer);
-
- if (count < write_count) {
- write_count = count;
- }
-
- if (write_count > 0) {
- memcpy(rbuffer_write_ptr(rbuffer), buffer, write_count);
- rbuffer_produced(rbuffer, write_count);
- }
-
- return write_count;
-}
-
-/// Returns a pointer to a raw buffer containing the first byte available for
-/// reading.
-char *rbuffer_read_ptr(RBuffer *rbuffer)
-{
- return rbuffer->data + rbuffer->rpos;
-}
-
-/// Returns a pointer to a raw buffer containing the first byte available for
-/// write.
-char *rbuffer_write_ptr(RBuffer *rbuffer)
-{
- return rbuffer->data + rbuffer->wpos;
-}
-
-/// Returns the number of bytes ready for consumption in `rbuffer`
-///
-/// @param rbuffer The `RBuffer` instance
-/// @return The number of bytes ready for consumption
-size_t rbuffer_pending(RBuffer *rbuffer)
-{
- return rbuffer->wpos - rbuffer->rpos;
-}
-
-/// Returns available space in `rbuffer`
-///
-/// @param rbuffer The `RBuffer` instance
-/// @return The space available in number of bytes
-size_t rbuffer_available(RBuffer *rbuffer)
-{
- return rbuffer->capacity - rbuffer->wpos;
-}
-
-void rbuffer_free(RBuffer *rbuffer)
-{
- xfree(rbuffer->data);
- xfree(rbuffer);
-}
-
/// Creates a new RStream instance. A RStream encapsulates all the boilerplate
/// necessary for reading from a libuv stream.
///
@@ -177,8 +42,10 @@ void rbuffer_free(RBuffer *rbuffer)
RStream * rstream_new(rstream_cb cb, RBuffer *buffer, void *data)
{
RStream *rv = xmalloc(sizeof(RStream));
+ buffer->data = rv;
+ buffer->full_cb = on_rbuffer_full;
+ buffer->nonfull_cb = on_rbuffer_nonfull;
rv->buffer = buffer;
- rv->buffer->rstream = rv;
rv->fpos = 0;
rv->data = data;
rv->cb = cb;
@@ -190,16 +57,14 @@ RStream * rstream_new(rstream_cb cb, RBuffer *buffer, void *data)
return rv;
}
-/// Returns the read pointer used by the rstream.
-char *rstream_read_ptr(RStream *rstream)
+static void on_rbuffer_full(RBuffer *buf, void *data)
{
- return rbuffer_read_ptr(rstream->buffer);
+ rstream_stop(data);
}
-/// Returns the number of bytes before the rstream is full.
-size_t rstream_available(RStream *rstream)
+static void on_rbuffer_nonfull(RBuffer *buf, void *data)
{
- return rbuffer_available(rstream->buffer);
+ rstream_start(data);
}
/// Frees all memory allocated for a RStream instance
@@ -297,37 +162,13 @@ void rstream_stop(RStream *rstream)
}
}
-/// Returns the number of bytes ready for consumption in `rstream`
-size_t rstream_pending(RStream *rstream)
-{
- return rbuffer_pending(rstream->buffer);
-}
-
-/// Reads data from a `RStream` instance into a buffer.
-///
-/// @param rstream The `RStream` instance
-/// @param buffer The buffer which will receive the data
-/// @param count Number of bytes that `buffer` can accept
-/// @return The number of bytes copied into `buffer`
-size_t rstream_read(RStream *rstream, char *buffer, size_t count)
-{
- return rbuffer_read(rstream->buffer, buffer, count);
-}
-
-RBuffer *rstream_buffer(RStream *rstream)
-{
- return rstream->buffer;
-}
-
// Callbacks used by libuv
// Called by libuv to allocate memory for reading.
static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf)
{
RStream *rstream = handle_get_rstream(handle);
-
- buf->len = rbuffer_available(rstream->buffer);
- buf->base = rbuffer_write_ptr(rstream->buffer);
+ buf->base = rbuffer_write_ptr(rstream->buffer, &buf->len);
}
// Callback invoked by libuv after it copies the data into the buffer provided
@@ -338,23 +179,30 @@ static void read_cb(uv_stream_t *stream, ssize_t cnt, const uv_buf_t *buf)
RStream *rstream = handle_get_rstream((uv_handle_t *)stream);
if (cnt <= 0) {
- if (cnt != UV_ENOBUFS) {
- DLOG("Closing RStream(%p)", rstream);
+ if (cnt != UV_ENOBUFS
+ // cnt == 0 means libuv asked for a buffer and decided it wasn't needed:
+ // http://docs.libuv.org/en/latest/stream.html#c.uv_read_start.
+ //
+ // We don't need to do anything with the RBuffer because the next call
+ // to `alloc_cb` will return the same unused pointer(`rbuffer_produced`
+ // won't be called)
+ && cnt != 0) {
+ DLOG("Closing RStream(%p) because of %s(%zd)", rstream,
+ uv_strerror((int)cnt), cnt);
// Read error or EOF, either way stop the stream and invoke the callback
// with eof == true
uv_read_stop(stream);
- rstream->cb(rstream, rstream->data, true);
+ rstream->cb(rstream, rstream->buffer, rstream->data, true);
}
return;
}
// at this point we're sure that cnt is positive, no error occurred
- size_t nread = (size_t) cnt;
-
+ size_t nread = (size_t)cnt;
// Data was already written, so all we need is to update 'wpos' to reflect
// the space actually used in the buffer.
rbuffer_produced(rstream->buffer, nread);
- rstream->cb(rstream, rstream->data, false);
+ rstream->cb(rstream, rstream->buffer, rstream->data, false);
}
// Called by the by the 'idle' handle to emulate a reading event
@@ -363,8 +211,7 @@ static void fread_idle_cb(uv_idle_t *handle)
uv_fs_t req;
RStream *rstream = handle_get_rstream((uv_handle_t *)handle);
- rstream->uvbuf.len = rbuffer_available(rstream->buffer);
- rstream->uvbuf.base = rbuffer_write_ptr(rstream->buffer);
+ rstream->uvbuf.base = rbuffer_write_ptr(rstream->buffer, &rstream->uvbuf.len);
// the offset argument to uv_fs_read is int64_t, could someone really try
// to read more than 9 quintillion (9e18) bytes?
@@ -389,7 +236,7 @@ static void fread_idle_cb(uv_idle_t *handle)
if (req.result <= 0) {
uv_idle_stop(rstream->fread_idle);
- rstream->cb(rstream, rstream->data, true);
+ rstream->cb(rstream, rstream->buffer, rstream->data, true);
return;
}
@@ -404,15 +251,3 @@ static void close_cb(uv_handle_t *handle)
xfree(handle->data);
xfree(handle);
}
-
-static void rbuffer_relocate(RBuffer *rbuffer)
-{
- assert(rbuffer->rpos <= rbuffer->wpos);
- // Move data ...
- memmove(
- rbuffer->data, // ...to the beginning of the buffer(rpos 0)
- rbuffer->data + rbuffer->rpos, // ...From the first unread position
- rbuffer->wpos - rbuffer->rpos); // ...By the number of unread bytes
- rbuffer->wpos -= rbuffer->rpos;
- rbuffer->rpos = 0;
-}
diff --git a/src/nvim/os/rstream.h b/src/nvim/os/rstream.h
index 713d1e77e6..3e24724573 100644
--- a/src/nvim/os/rstream.h
+++ b/src/nvim/os/rstream.h
@@ -5,7 +5,6 @@
#include <stdint.h>
#include <uv.h>
#include "nvim/os/event_defs.h"
-
#include "nvim/os/rstream_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/os/rstream_defs.h b/src/nvim/os/rstream_defs.h
index 1d71160963..45dced0b62 100644
--- a/src/nvim/os/rstream_defs.h
+++ b/src/nvim/os/rstream_defs.h
@@ -3,15 +3,18 @@
#include <stdbool.h>
-typedef struct rbuffer RBuffer;
+#include "nvim/rbuffer.h"
+
typedef struct rstream RStream;
/// Type of function called when the RStream receives data
///
/// @param rstream The RStream instance
+/// @param rbuffer The associated RBuffer instance
/// @param data State associated with the RStream instance
/// @param eof If the stream reached EOF.
-typedef void (*rstream_cb)(RStream *rstream, void *data, bool eof);
+typedef void (*rstream_cb)(RStream *rstream, RBuffer *buf, void *data,
+ bool eof);
#endif // NVIM_OS_RSTREAM_DEFS_H
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index 2de3b1aeed..48174533a6 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -283,25 +283,28 @@ static void dynamic_buffer_ensure(DynamicBuffer *buf, size_t desired)
buf->data = xrealloc(buf->data, buf->cap);
}
-static void system_data_cb(RStream *rstream, void *data, bool eof)
+static void system_data_cb(RStream *rstream, RBuffer *buf, void *data, bool eof)
{
Job *job = data;
- DynamicBuffer *buf = job_data(job);
+ DynamicBuffer *dbuf = job_data(job);
- size_t nread = rstream_pending(rstream);
-
- dynamic_buffer_ensure(buf, buf->len + nread + 1);
- rstream_read(rstream, buf->data + buf->len, nread);
-
- buf->len += nread;
+ size_t nread = buf->size;
+ dynamic_buffer_ensure(dbuf, dbuf->len + nread + 1);
+ rbuffer_read(buf, dbuf->data + dbuf->len, nread);
+ dbuf->len += nread;
}
-static void out_data_cb(RStream *rstream, void *data, bool eof)
+static void out_data_cb(RStream *rstream, RBuffer *buf, void *data, bool eof)
{
- RBuffer *rbuffer = rstream_buffer(rstream);
- size_t written = write_output(rbuffer_read_ptr(rbuffer),
- rbuffer_pending(rbuffer), false, eof);
- rbuffer_consumed(rbuffer, written);
+ RBUFFER_UNTIL_EMPTY(buf, ptr, len) {
+ size_t written = write_output(ptr, len, false,
+ eof && len <= rbuffer_size(buf));
+ if (written) {
+ rbuffer_consumed(buf, written);
+ } else {
+ break;
+ }
+ }
}
/// Parses a command string into a sequence of words, taking quotes into
diff --git a/src/nvim/rbuffer.c b/src/nvim/rbuffer.c
new file mode 100644
index 0000000000..9cf681585b
--- /dev/null
+++ b/src/nvim/rbuffer.c
@@ -0,0 +1,205 @@
+#include <assert.h>
+#include <stddef.h>
+#include <string.h>
+
+#include "nvim/memory.h"
+#include "nvim/vim.h"
+#include "nvim/rbuffer.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "rbuffer.c.generated.h"
+#endif
+
+/// Creates a new `RBuffer` instance.
+RBuffer *rbuffer_new(size_t capacity)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
+{
+ if (!capacity) {
+ capacity = 0xffff;
+ }
+
+ RBuffer *rv = xmalloc(sizeof(RBuffer) + capacity);
+ rv->full_cb = rv->nonfull_cb = NULL;
+ rv->data = NULL;
+ rv->size = 0;
+ rv->write_ptr = rv->read_ptr = rv->start_ptr;
+ rv->end_ptr = rv->start_ptr + capacity;
+ return rv;
+}
+
+void rbuffer_free(RBuffer *buf)
+{
+ xfree(buf);
+}
+
+size_t rbuffer_size(RBuffer *buf) FUNC_ATTR_NONNULL_ALL
+{
+ return buf->size;
+}
+
+size_t rbuffer_capacity(RBuffer *buf) FUNC_ATTR_NONNULL_ALL
+{
+ return (size_t)(buf->end_ptr - buf->start_ptr);
+}
+
+size_t rbuffer_space(RBuffer *buf) FUNC_ATTR_NONNULL_ALL
+{
+ return rbuffer_capacity(buf) - buf->size;
+}
+
+/// Return a pointer to a raw buffer containing the first empty slot available
+/// for writing. The second argument is a pointer to the maximum number of
+/// bytes that could be written.
+///
+/// It is necessary to call this function twice to ensure all empty space was
+/// used. See RBUFFER_UNTIL_FULL for a macro that simplifies this task.
+char *rbuffer_write_ptr(RBuffer *buf, size_t *write_count) FUNC_ATTR_NONNULL_ALL
+{
+ if (buf->size == rbuffer_capacity(buf)) {
+ *write_count = 0;
+ return NULL;
+ }
+
+ if (buf->write_ptr >= buf->read_ptr) {
+ *write_count = (size_t)(buf->end_ptr - buf->write_ptr);
+ } else {
+ *write_count = (size_t)(buf->read_ptr - buf->write_ptr);
+ }
+
+ return buf->write_ptr;
+}
+
+/// Adjust `rbuffer` write pointer to reflect produced data. This is called
+/// automatically by `rbuffer_write`, but when using `rbuffer_write_ptr`
+/// directly, this needs to called after the data was copied to the internal
+/// buffer. The write pointer will be wrapped if required.
+void rbuffer_produced(RBuffer *buf, size_t count) FUNC_ATTR_NONNULL_ALL
+{
+ assert(count && count <= rbuffer_space(buf));
+
+ buf->write_ptr += count;
+ if (buf->write_ptr >= buf->end_ptr) {
+ // wrap around
+ buf->write_ptr -= rbuffer_capacity(buf);
+ }
+
+ buf->size += count;
+ if (buf->full_cb && !rbuffer_space(buf)) {
+ buf->full_cb(buf, buf->data);
+ }
+}
+
+/// Return a pointer to a raw buffer containing the first byte available
+/// for reading. The second argument is a pointer to the maximum number of
+/// bytes that could be read.
+///
+/// It is necessary to call this function twice to ensure all available bytes
+/// were read. See RBUFFER_UNTIL_EMPTY for a macro that simplifies this task.
+char *rbuffer_read_ptr(RBuffer *buf, size_t *read_count) FUNC_ATTR_NONNULL_ALL
+{
+ if (!buf->size) {
+ *read_count = 0;
+ return NULL;
+ }
+
+ if (buf->read_ptr < buf->write_ptr) {
+ *read_count = (size_t)(buf->write_ptr - buf->read_ptr);
+ } else {
+ *read_count = (size_t)(buf->end_ptr - buf->read_ptr);
+ }
+
+ return buf->read_ptr;
+}
+
+/// Adjust `rbuffer` read pointer to reflect consumed data. This is called
+/// automatically by `rbuffer_read`, but when using `rbuffer_read_ptr`
+/// directly, this needs to called after the data was copied from the internal
+/// buffer. The read pointer will be wrapped if required.
+void rbuffer_consumed(RBuffer *buf, size_t count)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(count && count <= buf->size);
+
+ buf->read_ptr += count;
+ if (buf->read_ptr >= buf->end_ptr) {
+ buf->read_ptr -= rbuffer_capacity(buf);
+ }
+
+ bool was_full = buf->size == rbuffer_capacity(buf);
+ buf->size -= count;
+ if (buf->nonfull_cb && was_full) {
+ buf->nonfull_cb(buf, buf->data);
+ }
+}
+
+// Higher level functions for copying from/to RBuffer instances and data
+// pointers
+size_t rbuffer_write(RBuffer *buf, char *src, size_t src_size)
+ FUNC_ATTR_NONNULL_ALL
+{
+ size_t size = src_size;
+
+ RBUFFER_UNTIL_FULL(buf, wptr, wcnt) {
+ size_t copy_count = MIN(src_size, wcnt);
+ memcpy(wptr, src, copy_count);
+ rbuffer_produced(buf, copy_count);
+
+ if (!(src_size -= copy_count)) {
+ return size;
+ }
+
+ src += copy_count;
+ }
+
+ return size - src_size;
+}
+
+size_t rbuffer_read(RBuffer *buf, char *dst, size_t dst_size)
+ FUNC_ATTR_NONNULL_ALL
+{
+ size_t size = dst_size;
+
+ RBUFFER_UNTIL_EMPTY(buf, rptr, rcnt) {
+ size_t copy_count = MIN(dst_size, rcnt);
+ memcpy(dst, rptr, copy_count);
+ rbuffer_consumed(buf, copy_count);
+
+ if (!(dst_size -= copy_count)) {
+ return size;
+ }
+
+ dst += copy_count;
+ }
+
+ return size - dst_size;
+}
+
+char *rbuffer_get(RBuffer *buf, size_t index)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+{
+ assert(index < buf->size);
+ char *rptr = buf->read_ptr + index;
+ if (rptr >= buf->end_ptr) {
+ rptr -= rbuffer_capacity(buf);
+ }
+ return rptr;
+}
+
+int rbuffer_cmp(RBuffer *buf, const char *str, size_t count)
+ FUNC_ATTR_NONNULL_ALL
+{
+ assert(count <= buf->size);
+ size_t rcnt;
+ (void)rbuffer_read_ptr(buf, &rcnt);
+ size_t n = MIN(count, rcnt);
+ int rv = memcmp(str, buf->read_ptr, n);
+ count -= n;
+ size_t remaining = buf->size - rcnt;
+
+ if (rv || !count || !remaining) {
+ return rv;
+ }
+
+ return memcmp(str + n, buf->start_ptr, count);
+}
+
diff --git a/src/nvim/rbuffer.h b/src/nvim/rbuffer.h
new file mode 100644
index 0000000000..b205db0b5a
--- /dev/null
+++ b/src/nvim/rbuffer.h
@@ -0,0 +1,83 @@
+// Ring buffer implementation. This is basically an array that wraps read/write
+// pointers around the memory region. It should be more efficient than the old
+// RBuffer which required memmove() calls to relocate read/write positions.
+//
+// The main purpose of RBuffer is simplify memory management when reading from
+// uv_stream_t instances:
+//
+// - The event loop writes data to a RBuffer, advancing the write pointer
+// - The main loop reads data, advancing the read pointer
+// - If the buffer becomes full(size == capacity) the rstream is temporarily
+// stopped(automatic backpressure handling)
+//
+// Reference: http://en.wikipedia.org/wiki/Circular_buffer
+#ifndef NVIM_RBUFFER_H
+#define NVIM_RBUFFER_H
+
+#include <stddef.h>
+#include <stdint.h>
+
+// Macros that simplify working with the read/write pointers directly by hiding
+// ring buffer wrap logic. Some examples:
+//
+// - Pass the write pointer to a function(write_data) that incrementally
+// produces data, returning the number of bytes actually written to the
+// ring buffer:
+//
+// RBUFFER_UNTIL_FULL(rbuf, ptr, cnt)
+// rbuffer_produced(rbuf, write_data(state, ptr, cnt));
+//
+// - Pass the read pointer to a function(read_data) that incrementally
+// consumes data, returning the number of bytes actually read from the
+// ring buffer:
+//
+// RBUFFER_UNTIL_EMPTY(rbuf, ptr, cnt)
+// rbuffer_consumed(rbuf, read_data(state, ptr, cnt));
+//
+// Note that the rbuffer_{produced,consumed} calls are necessary or these macros
+// create infinite loops
+#define RBUFFER_UNTIL_EMPTY(buf, rptr, rcnt) \
+ for (size_t rcnt = 0, _r = 1; _r; _r = 0) \
+ for (char *rptr = rbuffer_read_ptr(buf, &rcnt); \
+ buf->size; \
+ rptr = rbuffer_read_ptr(buf, &rcnt))
+
+#define RBUFFER_UNTIL_FULL(buf, wptr, wcnt) \
+ for (size_t wcnt = 0, _r = 1; _r; _r = 0) \
+ for (char *wptr = rbuffer_write_ptr(buf, &wcnt); \
+ rbuffer_space(buf); \
+ wptr = rbuffer_write_ptr(buf, &wcnt))
+
+
+// Iteration
+#define RBUFFER_EACH(buf, c, i) \
+ for (size_t i = 0; i < buf->size; i = buf->size) \
+ for (char c = 0; \
+ i < buf->size ? ((int)(c = *rbuffer_get(buf, i))) || 1 : 0; \
+ i++)
+
+#define RBUFFER_EACH_REVERSE(buf, c, i) \
+ for (size_t i = buf->size; i != SIZE_MAX; i = SIZE_MAX) \
+ for (char c = 0; \
+ i-- > 0 ? ((int)(c = *rbuffer_get(buf, i))) || 1 : 0; \
+ )
+
+typedef struct rbuffer RBuffer;
+/// Type of function invoked during certain events:
+/// - When the RBuffer switches to the full state
+/// - When the RBuffer switches to the non-full state
+typedef void(*rbuffer_callback)(RBuffer *buf, void *data);
+
+struct rbuffer {
+ rbuffer_callback full_cb, nonfull_cb;
+ void *data;
+ size_t size;
+ char *end_ptr, *read_ptr, *write_ptr;
+ char start_ptr[];
+};
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "rbuffer.h.generated.h"
+#endif
+
+#endif // NVIM_RBUFFER_H
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 63fc7b80a7..f312670a96 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -4116,7 +4116,7 @@ static int badword_captype(char_u *word, char_u *end)
// Delete the internal wordlist and its .spl file.
void spell_delete_wordlist(void)
{
- char_u fname[MAXPATHL];
+ char_u fname[MAXPATHL] = {0};
if (int_wordlist != NULL) {
os_remove((char *)int_wordlist);
diff --git a/src/nvim/testdir/test86.in b/src/nvim/testdir/test86.in
index 958bd57e29..41d9a2fa32 100644
--- a/src/nvim/testdir/test86.in
+++ b/src/nvim/testdir/test86.in
@@ -1,5 +1,8 @@
Tests for various python features. vim: set ft=vim :
+This test is not run in Neovim(see the has('nvim') check below) because the
+python compatibility layer is not complete.
+
NOTE: This will cause errors when run under valgrind.
This would require recompiling Python with:
./configure --without-pymalloc
diff --git a/src/nvim/testdir/test87.in b/src/nvim/testdir/test87.in
index cad778e858..58f9ac6418 100644
--- a/src/nvim/testdir/test87.in
+++ b/src/nvim/testdir/test87.in
@@ -1,9 +1,12 @@
Tests for various python features. vim: set ft=vim :
+This test is not run in Neovim(see the has('nvim') check below) because the
+python3 compatibility layer is not complete.
+
STARTTEST
:so small.vim
:set noswapfile
-:if !has('python3') | e! test.ok | wq! test.out | endif
+:if !has('python3') || has('nvim') | e! test.ok | wq! test.out | endif
:lang C
:fun Test()
:py3 import vim
diff --git a/src/nvim/tui/term_input.inl b/src/nvim/tui/term_input.inl
index d25cbb7ba1..451ac195f2 100644
--- a/src/nvim/tui/term_input.inl
+++ b/src/nvim/tui/term_input.inl
@@ -162,11 +162,10 @@ static void timer_cb(uv_timer_t *handle)
static bool handle_bracketed_paste(TermInput *input)
{
- char *ptr = rbuffer_read_ptr(input->read_buffer);
- size_t len = rbuffer_pending(input->read_buffer);
- if (len > 5 && (!strncmp(ptr, "\x1b[200~", 6)
- || !strncmp(ptr, "\x1b[201~", 6))) {
- bool enable = ptr[4] == '0';
+ if (rbuffer_size(input->read_buffer) > 5 &&
+ (!rbuffer_cmp(input->read_buffer, "\x1b[200~", 6) ||
+ !rbuffer_cmp(input->read_buffer, "\x1b[201~", 6))) {
+ bool enable = *rbuffer_get(input->read_buffer, 4) == '0';
// Advance past the sequence
rbuffer_consumed(input->read_buffer, 6);
if (input->paste_enabled == enable) {
@@ -195,11 +194,11 @@ static bool handle_bracketed_paste(TermInput *input)
static bool handle_forced_escape(TermInput *input)
{
- char *ptr = rbuffer_read_ptr(input->read_buffer);
- size_t len = rbuffer_pending(input->read_buffer);
- if (len > 1 && ptr[0] == ESC && ptr[1] == NUL) {
+ if (rbuffer_size(input->read_buffer) > 1
+ && !rbuffer_cmp(input->read_buffer, "\x1b\x00", 2)) {
// skip the ESC and NUL and push one <esc> to the input buffer
- termkey_push_bytes(input->tk, ptr, 1);
+ size_t rcnt;
+ termkey_push_bytes(input->tk, rbuffer_read_ptr(input->read_buffer, &rcnt), 1);
rbuffer_consumed(input->read_buffer, 2);
tk_getkeys(input, true);
return true;
@@ -207,9 +206,9 @@ static bool handle_forced_escape(TermInput *input)
return false;
}
-static void read_cb(RStream *rstream, void *rstream_data, bool eof)
+static void read_cb(RStream *rstream, RBuffer *buf, void *data, bool eof)
{
- TermInput *input = rstream_data;
+ TermInput *input = data;
if (eof) {
if (input->in_fd == 0 && !os_isatty(0) && os_isatty(2)) {
@@ -236,19 +235,33 @@ static void read_cb(RStream *rstream, void *rstream_data, bool eof)
if (handle_bracketed_paste(input) || handle_forced_escape(input)) {
continue;
}
- char *ptr = rbuffer_read_ptr(input->read_buffer);
- size_t len = rbuffer_pending(input->read_buffer);
- // Find the next 'esc' and push everything up to it(excluding)
- size_t i;
- for (i = ptr[0] == ESC ? 1 : 0; i < len; i++) {
- if (ptr[i] == '\x1b') {
+
+ // Find the next 'esc' and push everything up to it(excluding). This is done
+ // so the `handle_bracketed_paste`/`handle_forced_escape` calls above work
+ // as expected.
+ size_t count = 0;
+ RBUFFER_EACH(input->read_buffer, c, i) {
+ count = i + 1;
+ if (c == '\x1b' && count > 1) {
+ break;
+ }
+ }
+
+ RBUFFER_UNTIL_EMPTY(input->read_buffer, ptr, len) {
+ size_t consumed = termkey_push_bytes(input->tk, ptr, MIN(count, len));
+ // termkey_push_bytes can return (size_t)-1, so it is possible that
+ // `consumed > input->read_buffer->size`, but since tk_getkeys is called
+ // soon, it shouldn't happen
+ assert(consumed <= input->read_buffer->size);
+ rbuffer_consumed(input->read_buffer, consumed);
+ // Need to process the keys now since there's no guarantee "count" will
+ // fit into libtermkey's input buffer.
+ tk_getkeys(input, false);
+ if (!(count -= consumed)) {
break;
}
}
- size_t consumed = termkey_push_bytes(input->tk, ptr, i);
- rbuffer_consumed(input->read_buffer, consumed);
- tk_getkeys(input, false);
- } while (rbuffer_pending(input->read_buffer));
+ } while (rbuffer_size(input->read_buffer));
}
static TermInput *term_input_new(void)