diff options
Diffstat (limited to 'src')
62 files changed, 1981 insertions, 595 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 598f19d19d..6e6a890cd1 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -237,7 +237,7 @@ install_helper(TARGETS 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 COMPILE_FLAGS "-fno-sanitize-recover -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address -fsanitize=undefined -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/.asan-blacklist") set_property(TARGET nvim APPEND_STRING PROPERTY LINK_FLAGS "-fsanitize=address -fsanitize=undefined ") elseif(CLANG_MSAN) message(STATUS "Enabling Clang memory sanitizer for nvim.") diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 915c5f74d7..12c97cfffb 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -70,7 +70,6 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) /// @param line The new line. /// @param[out] err Details of an error that may have occurred void buffer_set_line(Buffer buffer, Integer index, String line, Error *err) - FUNC_ATTR_DEFERRED { Object l = STRING_OBJ(line); Array array = {.items = &l, .size = 1}; @@ -83,7 +82,6 @@ void buffer_set_line(Buffer buffer, Integer index, String line, Error *err) /// @param index The line index /// @param[out] err Details of an error that may have occurred void buffer_del_line(Buffer buffer, Integer index, Error *err) - FUNC_ATTR_DEFERRED { Array array = ARRAY_DICT_INIT; buffer_set_line_slice(buffer, index, index, true, true, array, err); @@ -171,7 +169,6 @@ void buffer_set_line_slice(Buffer buffer, Boolean include_end, ArrayOf(String) replacement, Error *err) - FUNC_ATTR_DEFERRED { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -339,7 +336,6 @@ Object buffer_get_var(Buffer buffer, String name, Error *err) /// @param[out] err Details of an error that may have occurred /// @return The old value Object buffer_set_var(Buffer buffer, String name, Object value, Error *err) - FUNC_ATTR_DEFERRED { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -375,7 +371,6 @@ Object buffer_get_option(Buffer buffer, String name, Error *err) /// @param value The option value /// @param[out] err Details of an error that may have occurred void buffer_set_option(Buffer buffer, String name, Object value, Error *err) - FUNC_ATTR_DEFERRED { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -426,7 +421,6 @@ String buffer_get_name(Buffer buffer, Error *err) /// @param name The buffer name /// @param[out] err Details of an error that may have occurred void buffer_set_name(Buffer buffer, String name, Error *err) - FUNC_ATTR_DEFERRED { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -472,7 +466,6 @@ void buffer_insert(Buffer buffer, Integer lnum, ArrayOf(String) lines, Error *err) - FUNC_ATTR_DEFERRED { buffer_set_line_slice(buffer, lnum, lnum, false, true, lines, err); } diff --git a/src/nvim/api/tabpage.c b/src/nvim/api/tabpage.c index 1c958118e1..126ee4072d 100644 --- a/src/nvim/api/tabpage.c +++ b/src/nvim/api/tabpage.c @@ -62,7 +62,6 @@ Object tabpage_get_var(Tabpage tabpage, String name, Error *err) /// @param[out] err Details of an error that may have occurred /// @return The tab page handle Object tabpage_set_var(Tabpage tabpage, String name, Object value, Error *err) - FUNC_ATTR_DEFERRED { tabpage_T *tab = find_tab_by_handle(tabpage, err); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f5dadb00ea..b9900b5d5a 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -37,7 +37,6 @@ /// @param str The command str /// @param[out] err Details of an error that may have occurred void vim_command(String str, Error *err) - FUNC_ATTR_DEFERRED { // Run the command try_start(); @@ -54,7 +53,6 @@ void vim_command(String str, Error *err) /// @see feedkeys() /// @see vim_strsave_escape_csi void vim_feedkeys(String keys, String mode, Boolean escape_csi) - FUNC_ATTR_DEFERRED { bool remap = true; bool insert = false; @@ -100,6 +98,7 @@ void vim_feedkeys(String keys, String mode, Boolean escape_csi) /// @return The number of bytes actually written, which can be lower than /// requested if the buffer becomes full. Integer vim_input(String keys) + FUNC_ATTR_ASYNC { return (Integer)input_enqueue(keys); } @@ -143,7 +142,6 @@ String vim_command_output(String str, Error *err) /// @param[out] err Details of an error that may have occurred /// @return The expanded object Object vim_eval(String str, Error *err) - FUNC_ATTR_DEFERRED { Object rv = OBJECT_INIT; // Evaluate the expression @@ -171,7 +169,6 @@ Object vim_eval(String str, Error *err) /// @param[out] err Details of an error that may have occurred /// @return Result of the function call Object vim_call_function(String fname, Array args, Error *err) - FUNC_ATTR_DEFERRED { Object rv = OBJECT_INIT; if (args.size > MAX_FUNC_ARGS) { @@ -312,7 +309,6 @@ String vim_get_current_line(Error *err) /// @param line The line contents /// @param[out] err Details of an error that may have occurred void vim_set_current_line(String line, Error *err) - FUNC_ATTR_DEFERRED { buffer_set_line(curbuf->handle, curwin->w_cursor.lnum - 1, line, err); } @@ -321,7 +317,6 @@ void vim_set_current_line(String line, Error *err) /// /// @param[out] err Details of an error that may have occurred void vim_del_current_line(Error *err) - FUNC_ATTR_DEFERRED { buffer_del_line(curbuf->handle, curwin->w_cursor.lnum - 1, err); } @@ -343,7 +338,6 @@ Object vim_get_var(String name, Error *err) /// @param[out] err Details of an error that may have occurred /// @return the old value if any Object vim_set_var(String name, Object value, Error *err) - FUNC_ATTR_DEFERRED { return dict_set_value(&globvardict, name, value, err); } @@ -374,7 +368,6 @@ Object vim_get_option(String name, Error *err) /// @param value The new option value /// @param[out] err Details of an error that may have occurred void vim_set_option(String name, Object value, Error *err) - FUNC_ATTR_DEFERRED { set_option_to(NULL, SREQ_GLOBAL, name, value, err); } @@ -383,7 +376,6 @@ void vim_set_option(String name, Object value, Error *err) /// /// @param str The message void vim_out_write(String str) - FUNC_ATTR_DEFERRED { write_msg(str, false); } @@ -392,7 +384,6 @@ void vim_out_write(String str) /// /// @param str The message void vim_err_write(String str) - FUNC_ATTR_DEFERRED { write_msg(str, true); } @@ -402,7 +393,6 @@ void vim_err_write(String str) /// /// @param str The message void vim_report_error(String str) - FUNC_ATTR_DEFERRED { vim_err_write(str); vim_err_write((String) {.data = "\n", .size = 1}); @@ -442,7 +432,6 @@ Buffer vim_get_current_buffer(void) /// @param id The buffer handle /// @param[out] err Details of an error that may have occurred void vim_set_current_buffer(Buffer buffer, Error *err) - FUNC_ATTR_DEFERRED { buf_T *buf = find_buffer_by_handle(buffer, err); @@ -493,7 +482,6 @@ Window vim_get_current_window(void) /// /// @param handle The window handle void vim_set_current_window(Window window, Error *err) - FUNC_ATTR_DEFERRED { win_T *win = find_window_by_handle(window, err); @@ -545,7 +533,6 @@ Tabpage vim_get_current_tabpage(void) /// @param handle The tab page handle /// @param[out] err Details of an error that may have occurred void vim_set_current_tabpage(Tabpage tabpage, Error *err) - FUNC_ATTR_DEFERRED { tabpage_T *tp = find_tab_by_handle(tabpage, err); @@ -609,6 +596,7 @@ Dictionary vim_get_color_map(void) Array vim_get_api_info(uint64_t channel_id) + FUNC_ATTR_ASYNC { Array rv = ARRAY_DICT_INIT; diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 5034c26c83..aad616c7bf 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -54,7 +54,6 @@ ArrayOf(Integer, 2) window_get_cursor(Window window, Error *err) /// @param pos the (row, col) tuple representing the new position /// @param[out] err Details of an error that may have occurred void window_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err) - FUNC_ATTR_DEFERRED { win_T *win = find_window_by_handle(window, err); @@ -118,7 +117,6 @@ Integer window_get_height(Window window, Error *err) /// @param height the new height in rows /// @param[out] err Details of an error that may have occurred void window_set_height(Window window, Integer height, Error *err) - FUNC_ATTR_DEFERRED { win_T *win = find_window_by_handle(window, err); @@ -162,7 +160,6 @@ Integer window_get_width(Window window, Error *err) /// @param width the new width in columns /// @param[out] err Details of an error that may have occurred void window_set_width(Window window, Integer width, Error *err) - FUNC_ATTR_DEFERRED { win_T *win = find_window_by_handle(window, err); @@ -208,7 +205,6 @@ Object window_get_var(Window window, String name, Error *err) /// @param[out] err Details of an error that may have occurred /// @return The old value Object window_set_var(Window window, String name, Object value, Error *err) - FUNC_ATTR_DEFERRED { win_T *win = find_window_by_handle(window, err); @@ -244,7 +240,6 @@ Object window_get_option(Window window, String name, Error *err) /// @param value The option value /// @param[out] err Details of an error that may have occurred void window_set_option(Window window, String name, Object value, Error *err) - FUNC_ATTR_DEFERRED { win_T *win = find_window_by_handle(window, err); diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 390d62210b..6bcf5e804a 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -254,7 +254,7 @@ edit ( ) { if (curbuf->terminal) { - terminal_enter(true); + terminal_enter(); return false; } @@ -601,15 +601,15 @@ edit ( * Get a character for Insert mode. Ignore K_IGNORE. */ lastc = c; /* remember previous char for CTRL-D */ - loop_enable_deferred_events(&loop); + input_enable_events(); do { c = safe_vgetc(); } while (c == K_IGNORE); - loop_disable_deferred_events(&loop); + input_disable_events(); if (c == K_EVENT) { c = lastc; - loop_process_event(&loop); + queue_process_events(loop.events); continue; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 18188696b9..bf9a219e28 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -20,6 +20,7 @@ #include <stdbool.h> #include <math.h> #include <limits.h> +#include <msgpack.h> #include "nvim/assert.h" #include "nvim/vim.h" @@ -90,11 +91,13 @@ #include "nvim/os/time.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" +#include "nvim/msgpack_rpc/helpers.h" #include "nvim/api/private/helpers.h" #include "nvim/api/vim.h" #include "nvim/os/dl.h" #include "nvim/os/input.h" #include "nvim/event/loop.h" +#include "nvim/lib/kvec.h" #define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */ @@ -160,6 +163,13 @@ typedef struct lval_S { char_u *ll_newkey; /* New key for Dict in alloc. mem or NULL. */ } lval_T; +/// Structure defining state for read_from_list() +typedef struct { + const listitem_T *li; ///< Item currently read. + size_t offset; ///< Byte offset inside the read item. + size_t li_length; ///< Length of the string inside the read item. +} ListReaderState; + static char *e_letunexp = N_("E18: Unexpected characters in :let"); static char *e_listidx = N_("E684: list index out of range: %" PRId64); @@ -429,6 +439,7 @@ static struct vimvar { {VV_NAME("progpath", VAR_STRING), VV_RO}, {VV_NAME("command_output", VAR_STRING), 0}, {VV_NAME("completed_item", VAR_DICT), VV_RO}, + {VV_NAME("msgpack_types", VAR_DICT), VV_RO}, }; /* shorthand */ @@ -457,17 +468,32 @@ typedef struct { dict_T *self; int *status_ptr; uint64_t id; + Queue *events; } TerminalJobData; +/// Structure representing current VimL to messagepack conversion state +typedef struct { + enum { + kMPConvDict, ///< Convert dict_T *dictionary. + kMPConvList, ///< Convert list_T *list. + kMPConvPairs, ///< Convert mapping represented as a list_T* of pairs. + } type; + union { + struct { + const dict_T *dict; ///< Currently converted dictionary. + const hashitem_T *hi; ///< Currently converted dictionary item. + size_t todo; ///< Amount of items left to process. + } d; ///< State of dictionary conversion. + struct { + const list_T *list; ///< Currently converted list. + const listitem_T *li; ///< Currently converted list item. + } l; ///< State of list or generic mapping conversion. + } data; ///< Data to convert. +} MPConvStackVal; + +/// Stack used to convert VimL values to messagepack. +typedef kvec_t(MPConvStackVal) MPConvStack; -#ifdef INCLUDE_GENERATED_DECLARATIONS -# include "eval.c.generated.h" -#endif - -#define FNE_INCL_BR 1 /* find_name_end(): include [] in name */ -#define FNE_CHECK_START 2 /* find_name_end(): check name starts with - valid character */ -// Memory pool for reusing JobEvent structures typedef struct { TerminalJobData *data; ufunc_T *callback; @@ -475,10 +501,51 @@ typedef struct { list_T *received; int status; } JobEvent; -static int disable_job_defer = 0; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "eval.c.generated.h" +#endif + +#define FNE_INCL_BR 1 /* find_name_end(): include [] in name */ +#define FNE_CHECK_START 2 /* find_name_end(): check name starts with + valid character */ static uint64_t current_job_id = 1; static PMap(uint64_t) *jobs = NULL; +typedef enum { + kMPNil, + kMPBoolean, + kMPInteger, + kMPFloat, + kMPString, + kMPBinary, + kMPArray, + kMPMap, + kMPExt, +} MessagePackType; +static const char *const msgpack_type_names[] = { + [kMPNil] = "nil", + [kMPBoolean] = "boolean", + [kMPInteger] = "integer", + [kMPFloat] = "float", + [kMPString] = "string", + [kMPBinary] = "binary", + [kMPArray] = "array", + [kMPMap] = "map", + [kMPExt] = "ext", +}; +static const list_T *msgpack_type_lists[] = { + [kMPNil] = NULL, + [kMPBoolean] = NULL, + [kMPInteger] = NULL, + [kMPFloat] = NULL, + [kMPString] = NULL, + [kMPBinary] = NULL, + [kMPArray] = NULL, + [kMPMap] = NULL, + [kMPExt] = NULL, +}; + /* * Initialize the global and v: variables. */ @@ -511,6 +578,27 @@ void eval_init(void) /* add to compat scope dict */ hash_add(&compat_hashtab, p->vv_di.di_key); } + + dict_T *const msgpack_types_dict = dict_alloc(); + for (size_t i = 0; i < ARRAY_SIZE(msgpack_type_names); i++) { + list_T *const type_list = list_alloc(); + type_list->lv_lock = VAR_FIXED; + type_list->lv_refcount = 1; + dictitem_T *const di = dictitem_alloc((char_u *) msgpack_type_names[i]); + di->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX; + di->di_tv = (typval_T) { + .v_type = VAR_LIST, + .vval = { .v_list = type_list, }, + }; + msgpack_type_lists[i] = type_list; + if (dict_add(msgpack_types_dict, di) == FAIL) { + // There must not be duplicate items in this dictionary by definition. + assert(false); + } + } + msgpack_types_dict->dv_lock = VAR_FIXED; + + set_vim_var_dict(VV_MSGPACK_TYPES, msgpack_types_dict); set_vim_var_dict(VV_COMPLETED_ITEM, dict_alloc()); set_vim_var_nr(VV_SEARCHFORWARD, 1L); set_vim_var_nr(VV_HLSEARCH, 1L); @@ -5156,24 +5244,40 @@ void list_append_dict(list_T *list, dict_T *dict) ++dict->dv_refcount; } -/* - * Make a copy of "str" and append it as an item to list "l". - * When "len" >= 0 use "str[len]". - */ -void list_append_string(list_T *l, char_u *str, int len) +/// Make a copy of "str" and append it as an item to list "l" +/// +/// @param[out] l List to append to. +/// @param[in] str String to append. +/// @param[in] len Length of the appended string. May be negative, in this +/// case string is considered to be usual zero-terminated +/// string. +void list_append_string(list_T *l, const char_u *str, int len) + FUNC_ATTR_NONNULL_ARG(1) +{ + if (str == NULL) { + list_append_allocated_string(l, NULL); + } else { + list_append_allocated_string(l, (len >= 0 + ? xmemdupz((char *) str, len) + : xstrdup((char *) str))); + } +} + +/// Append given string to the list +/// +/// Unlike list_append_string this function does not copy the string. +/// +/// @param[out] l List to append to. +/// @param[in] str String to append. +void list_append_allocated_string(list_T *l, char *const str) + FUNC_ATTR_NONNULL_ARG(1) { listitem_T *li = listitem_alloc(); list_append(l, li); li->li_tv.v_type = VAR_STRING; li->li_tv.v_lock = 0; - - if (str == NULL) { - li->li_tv.vval.v_string = NULL; - } else { - li->li_tv.vval.v_string = (len >= 0) ? vim_strnsave(str, len) - : vim_strsave(str); - } + li->li_tv.vval.v_string = (char_u *) str; } /* @@ -6582,6 +6686,8 @@ static struct fst { {"min", 1, 1, f_min}, {"mkdir", 1, 3, f_mkdir}, {"mode", 0, 1, f_mode}, + {"msgpackdump", 1, 1, f_msgpackdump}, + {"msgpackparse", 1, 1, f_msgpackparse}, {"nextnonblank", 1, 1, f_nextnonblank}, {"nr2char", 1, 2, f_nr2char}, {"or", 2, 2, f_or}, @@ -8456,27 +8562,12 @@ static void f_feedkeys(typval_T *argvars, typval_T *rettv) } } -/* - * "filereadable()" function - */ +/// "filereadable()" function static void f_filereadable(typval_T *argvars, typval_T *rettv) { - int fd; - char_u *p; - int n; - -#ifndef O_NONBLOCK -# define O_NONBLOCK 0 -#endif - p = get_tv_string(&argvars[0]); - if (*p && !os_isdir(p) && (fd = os_open((char *)p, - O_RDONLY | O_NONBLOCK, 0)) >= 0) { - n = TRUE; - close(fd); - } else - n = FALSE; - - rettv->vval.v_number = n; + char_u *p = get_tv_string(&argvars[0]); + rettv->vval.v_number = + (*p && !os_isdir(p) && os_file_is_readable((char*)p)); } /* @@ -10671,7 +10762,7 @@ static void f_jobclose(typval_T *argvars, typval_T *rettv) return; } - TerminalJobData *data = pmap_get(uint64_t)(jobs, argvars[0].vval.v_number); + TerminalJobData *data = find_job(argvars[0].vval.v_number); if (!data) { EMSG(_(e_invjob)); return; @@ -10712,7 +10803,7 @@ static void f_jobsend(typval_T *argvars, typval_T *rettv) return; } - TerminalJobData *data = pmap_get(uint64_t)(jobs, argvars[0].vval.v_number); + TerminalJobData *data = find_job(argvars[0].vval.v_number); if (!data) { EMSG(_(e_invjob)); return; @@ -10753,7 +10844,7 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv) } - TerminalJobData *data = pmap_get(uint64_t)(jobs, argvars[0].vval.v_number); + TerminalJobData *data = find_job(argvars[0].vval.v_number); if (!data) { EMSG(_(e_invjob)); return; @@ -10900,8 +10991,8 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv) } - TerminalJobData *data = pmap_get(uint64_t)(jobs, argvars[0].vval.v_number); - if (!data || data->stopped) { + TerminalJobData *data = find_job(argvars[0].vval.v_number); + if (!data) { EMSG(_(e_invjob)); return; } @@ -10931,28 +11022,24 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv) list_T *rv = list_alloc(); ui_busy_start(); - // disable breakchecks, which could result in job callbacks being executed - // at unexpected places - disable_breakcheck++; - // disable job event deferring so the callbacks are processed while waiting. - if (!disable_job_defer++) { - // process any pending job events in the deferred queue, but only do this if - // deferred is not disabled(at the top-level `jobwait()` call) - loop_process_event(&loop); - } + Queue *waiting_jobs = queue_new_parent(loop_on_put, &loop); // For each item in the input list append an integer to the output list. -3 // is used to represent an invalid job id, -2 is for a interrupted job and // -1 for jobs that were skipped or timed out. for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { TerminalJobData *data = NULL; if (arg->li_tv.v_type != VAR_NUMBER - || !(data = pmap_get(uint64_t)(jobs, arg->li_tv.vval.v_number))) { + || !(data = find_job(arg->li_tv.vval.v_number))) { list_append_number(rv, -3); } else { // append the list item and set the status pointer so we'll collect the // status code when the job exits list_append_number(rv, -1); data->status_ptr = &rv->lv_last->li_tv.vval.v_number; + // Process any pending events for the job because we'll temporarily + // replace the parent queue + queue_process_events(data->events); + queue_replace_parent(data->events, waiting_jobs); } } @@ -10970,10 +11057,10 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv) break; } if (arg->li_tv.v_type != VAR_NUMBER - || !(data = pmap_get(uint64_t)(jobs, arg->li_tv.vval.v_number))) { + || !(data = find_job(arg->li_tv.vval.v_number))) { continue; } - int status = process_wait((Process *)&data->proc, remaining); + int status = process_wait((Process *)&data->proc, remaining, waiting_jobs); if (status < 0) { // interrupted or timed out, skip remaining jobs. if (status == -2) { @@ -10993,23 +11080,31 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv) } } - // poll to ensure any pending callbacks from the last job are invoked - loop_poll_events(&loop, 0); - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { TerminalJobData *data = NULL; if (arg->li_tv.v_type != VAR_NUMBER - || !(data = pmap_get(uint64_t)(jobs, arg->li_tv.vval.v_number))) { + || !(data = find_job(arg->li_tv.vval.v_number))) { continue; } // remove the status pointer because the list may be freed before the // job exits data->status_ptr = NULL; } - disable_job_defer--; - disable_breakcheck--; - ui_busy_stop(); + // restore the parent queue for any jobs still alive + for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { + TerminalJobData *data = NULL; + if (arg->li_tv.v_type != VAR_NUMBER + || !(data = pmap_get(uint64_t)(jobs, arg->li_tv.vval.v_number))) { + continue; + } + // restore the parent queue for the job + queue_process_events(data->events); + queue_replace_parent(data->events, loop.events); + } + + queue_free(waiting_jobs); + ui_busy_stop(); rv->lv_refcount++; rettv->v_type = VAR_LIST; rettv->vval.v_list = rv; @@ -11782,6 +11877,827 @@ static void f_mode(typval_T *argvars, typval_T *rettv) rettv->v_type = VAR_STRING; } +/// Msgpack callback for writing to readfile()-style list +static int msgpack_list_write(void *data, const char *buf, size_t len) +{ + if (len == 0) { + return 0; + } + list_T *const list = (list_T *) data; + const char *const end = buf + len; + const char *line_end = buf; + if (list->lv_last == NULL) { + list_append_string(list, NULL, 0); + } + listitem_T *li = list->lv_last; + do { + const char *line_start = line_end; + line_end = xmemscan(line_start, NL, end - line_start); + if (line_end == line_start) { + list_append_allocated_string(list, NULL); + } else { + const size_t line_length = line_end - line_start; + char *str; + if (li == NULL) { + str = xmemdupz(line_start, line_length); + } else { + const size_t li_len = (li->li_tv.vval.v_string == NULL + ? 0 + : STRLEN(li->li_tv.vval.v_string)); + li->li_tv.vval.v_string = xrealloc(li->li_tv.vval.v_string, + li_len + line_length + 1); + str = (char *) li->li_tv.vval.v_string + li_len; + memmove(str, line_start, line_length); + str[line_length] = 0; + } + for (size_t i = 0; i < line_length; i++) { + if (str[i] == NUL) { + str[i] = NL; + } + } + if (li == NULL) { + list_append_allocated_string(list, str); + } else { + li = NULL; + } + if (line_end == end - 1) { + list_append_allocated_string(list, NULL); + } + } + line_end++; + } while (line_end < end); + return 0; +} + +/// Convert readfile()-style list to a char * buffer with length +/// +/// @param[in] list Converted list. +/// @param[out] ret_len Resulting buffer length. +/// @param[out] ret_buf Allocated buffer with the result or NULL if ret_len is +/// zero. +/// +/// @return true in case of success, false in case of failure. +static inline bool vim_list_to_buf(const list_T *const list, + size_t *const ret_len, char **const ret_buf) + FUNC_ATTR_NONNULL_ARG(2,3) FUNC_ATTR_WARN_UNUSED_RESULT +{ + size_t len = 0; + if (list != NULL) { + for (const listitem_T *li = list->lv_first; + li != NULL; + li = li->li_next) { + if (li->li_tv.v_type != VAR_STRING) { + return false; + } + len++; + if (li->li_tv.vval.v_string != 0) { + len += STRLEN(li->li_tv.vval.v_string); + } + } + if (len) { + len--; + } + } + *ret_len = len; + if (len == 0) { + *ret_buf = NULL; + return true; + } + ListReaderState lrstate = init_lrstate(list); + char *const buf = xmalloc(len); + size_t read_bytes; + if (read_from_list(&lrstate, buf, len, &read_bytes) != OK) { + assert(false); + } + assert(len == read_bytes); + *ret_buf = buf; + return true; +} + +/// Convert one VimL value to msgpack +/// +/// @param packer Messagepack packer. +/// @param[out] mpstack Stack with values to convert. Only used for pushing +/// values to it. +/// @param[in] tv Converted value. +/// +/// @return OK in case of success, FAIL otherwise. +static int convert_one_value(msgpack_packer *const packer, + MPConvStack *const mpstack, + const typval_T *const tv) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + switch (tv->v_type) { +#define CHECK_SELF_REFERENCE(conv_type, vval_name, ptr) \ + do { \ + for (size_t i = 0; i < kv_size(*mpstack); i++) { \ + if (kv_A(*mpstack, i).type == conv_type \ + && kv_A(*mpstack, i).data.vval_name == ptr) { \ + EMSG2(_(e_invarg2), "container references itself"); \ + return FAIL; \ + } \ + } \ + } while (0) + case VAR_STRING: { + if (tv->vval.v_string == NULL) { + msgpack_pack_bin(packer, 0); + } else { + const size_t len = STRLEN(tv->vval.v_string); + msgpack_pack_bin(packer, len); + msgpack_pack_bin_body(packer, tv->vval.v_string, len); + } + break; + } + case VAR_NUMBER: { + msgpack_pack_int64(packer, (int64_t) tv->vval.v_number); + break; + } + case VAR_FLOAT: { + msgpack_pack_double(packer, (double) tv->vval.v_float); + break; + } + case VAR_FUNC: { + EMSG2(_(e_invarg2), "attempt to dump function reference"); + return FAIL; + } + case VAR_LIST: { + if (tv->vval.v_list == NULL) { + msgpack_pack_array(packer, 0); + break; + } + CHECK_SELF_REFERENCE(kMPConvList, l.list, tv->vval.v_list); + msgpack_pack_array(packer, tv->vval.v_list->lv_len); + kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) { + .type = kMPConvList, + .data = { + .l = { + .list = tv->vval.v_list, + .li = tv->vval.v_list->lv_first, + }, + }, + })); + break; + } + case VAR_DICT: { + if (tv->vval.v_dict == NULL) { + msgpack_pack_map(packer, 0); + break; + } + const dictitem_T *type_di; + const dictitem_T *val_di; + if (tv->vval.v_dict->dv_hashtab.ht_used == 2 + && (type_di = dict_find((dict_T *) tv->vval.v_dict, + (char_u *) "_TYPE", -1)) != NULL + && type_di->di_tv.v_type == VAR_LIST + && (val_di = dict_find((dict_T *) tv->vval.v_dict, + (char_u *) "_VAL", -1)) != NULL) { + size_t i; + for (i = 0; i < ARRAY_SIZE(msgpack_type_lists); i++) { + if (type_di->di_tv.vval.v_list == msgpack_type_lists[i]) { + break; + } + } + if (i == ARRAY_SIZE(msgpack_type_lists)) { + goto vim_to_msgpack_regural_dict; + } + switch ((MessagePackType) i) { + case kMPNil: { + msgpack_pack_nil(packer); + break; + } + case kMPBoolean: { + if (val_di->di_tv.v_type != VAR_NUMBER) { + goto vim_to_msgpack_regural_dict; + } + if (val_di->di_tv.vval.v_number) { + msgpack_pack_true(packer); + } else { + msgpack_pack_false(packer); + } + break; + } + case kMPInteger: { + const list_T *val_list; + varnumber_T sign; + varnumber_T highest_bits; + varnumber_T high_bits; + varnumber_T low_bits; + // List of 4 integers; first is signed (should be 1 or -1, but this + // is not checked), second is unsigned and have at most one (sign is + // -1) or two (sign is 1) non-zero bits (number of bits is not + // checked), other unsigned and have at most 31 non-zero bits + // (number of bits is not checked). + if (val_di->di_tv.v_type != VAR_LIST + || (val_list = val_di->di_tv.vval.v_list) == NULL + || val_list->lv_len != 4 + || val_list->lv_first->li_tv.v_type != VAR_NUMBER + || (sign = val_list->lv_first->li_tv.vval.v_number) == 0 + || val_list->lv_first->li_next->li_tv.v_type != VAR_NUMBER + || (highest_bits = + val_list->lv_first->li_next->li_tv.vval.v_number) < 0 + || val_list->lv_last->li_prev->li_tv.v_type != VAR_NUMBER + || (high_bits = + val_list->lv_last->li_prev->li_tv.vval.v_number) < 0 + || val_list->lv_last->li_tv.v_type != VAR_NUMBER + || (low_bits = val_list->lv_last->li_tv.vval.v_number) < 0) { + goto vim_to_msgpack_regural_dict; + } + uint64_t number = ((uint64_t) (((uint64_t) highest_bits) << 62) + | (uint64_t) (((uint64_t) high_bits) << 31) + | (uint64_t) low_bits); + if (sign > 0) { + msgpack_pack_uint64(packer, number); + } else { + msgpack_pack_int64(packer, (int64_t) (-number)); + } + break; + } + case kMPFloat: { + if (val_di->di_tv.v_type != VAR_FLOAT) { + goto vim_to_msgpack_regural_dict; + } + msgpack_pack_double(packer, val_di->di_tv.vval.v_float); + break; + } + case kMPString: + case kMPBinary: { + const bool is_string = ((MessagePackType) i == kMPString); + if (val_di->di_tv.v_type != VAR_LIST) { + goto vim_to_msgpack_regural_dict; + } + size_t len; + char *buf; + if (!vim_list_to_buf(val_di->di_tv.vval.v_list, &len, &buf)) { + goto vim_to_msgpack_regural_dict; + } + if (is_string) { + msgpack_pack_str(packer, len); + } else { + msgpack_pack_bin(packer, len); + } + if (len == 0) { + break; + } + if (is_string) { + msgpack_pack_str_body(packer, buf, len); + } else { + msgpack_pack_bin_body(packer, buf, len); + } + xfree(buf); + break; + } + case kMPArray: { + if (val_di->di_tv.v_type != VAR_LIST) { + goto vim_to_msgpack_regural_dict; + } + CHECK_SELF_REFERENCE(kMPConvList, l.list, + val_di->di_tv.vval.v_list); + msgpack_pack_array(packer, val_di->di_tv.vval.v_list->lv_len); + kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) { + .type = kMPConvList, + .data = { + .l = { + .list = val_di->di_tv.vval.v_list, + .li = val_di->di_tv.vval.v_list->lv_first, + }, + }, + })); + break; + } + case kMPMap: { + if (val_di->di_tv.v_type != VAR_LIST) { + goto vim_to_msgpack_regural_dict; + } + if (val_di->di_tv.vval.v_list == NULL) { + msgpack_pack_map(packer, 0); + break; + } + const list_T *const val_list = val_di->di_tv.vval.v_list; + for (const listitem_T *li = val_list->lv_first; li != NULL; + li = li->li_next) { + if (li->li_tv.v_type != VAR_LIST + || li->li_tv.vval.v_list->lv_len != 2) { + goto vim_to_msgpack_regural_dict; + } + } + CHECK_SELF_REFERENCE(kMPConvPairs, l.list, val_list); + msgpack_pack_map(packer, val_list->lv_len); + kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) { + .type = kMPConvPairs, + .data = { + .l = { + .list = val_list, + .li = val_list->lv_first, + }, + }, + })); + break; + } + case kMPExt: { + const list_T *val_list; + varnumber_T type; + if (val_di->di_tv.v_type != VAR_LIST + || (val_list = val_di->di_tv.vval.v_list) == NULL + || val_list->lv_len != 2 + || (val_list->lv_first->li_tv.v_type != VAR_NUMBER) + || (type = val_list->lv_first->li_tv.vval.v_number) > INT8_MAX + || type < INT8_MIN + || (val_list->lv_last->li_tv.v_type != VAR_LIST)) { + goto vim_to_msgpack_regural_dict; + } + size_t len; + char *buf; + if (!vim_list_to_buf(val_list->lv_last->li_tv.vval.v_list, + &len, &buf)) { + goto vim_to_msgpack_regural_dict; + } + msgpack_pack_ext(packer, len, (int8_t) type); + msgpack_pack_ext_body(packer, buf, len); + xfree(buf); + break; + } + } + break; + } +vim_to_msgpack_regural_dict: + CHECK_SELF_REFERENCE(kMPConvDict, d.dict, tv->vval.v_dict); + msgpack_pack_map(packer, tv->vval.v_dict->dv_hashtab.ht_used); + kv_push(MPConvStackVal, *mpstack, ((MPConvStackVal) { + .type = kMPConvDict, + .data = { + .d = { + .dict = tv->vval.v_dict, + .hi = tv->vval.v_dict->dv_hashtab.ht_array, + .todo = tv->vval.v_dict->dv_hashtab.ht_used, + }, + }, + })); + break; + } + } +#undef CHECK_SELF_REFERENCE + return OK; +} + +/// Convert typval_T to messagepack +static int vim_to_msgpack(msgpack_packer *const packer, + const typval_T *const tv) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + MPConvStack mpstack; + kv_init(mpstack); + if (convert_one_value(packer, &mpstack, tv) == FAIL) { + goto vim_to_msgpack_error_ret; + } + while (kv_size(mpstack)) { + MPConvStackVal *cur_mpsv = &kv_A(mpstack, kv_size(mpstack) - 1); + const typval_T *cur_tv = NULL; + switch (cur_mpsv->type) { + case kMPConvDict: { + if (!cur_mpsv->data.d.todo) { + (void) kv_pop(mpstack); + continue; + } + while (HASHITEM_EMPTY(cur_mpsv->data.d.hi)) { + cur_mpsv->data.d.hi++; + } + const dictitem_T *const di = HI2DI(cur_mpsv->data.d.hi); + cur_mpsv->data.d.todo--; + cur_mpsv->data.d.hi++; + const size_t key_len = STRLEN(&di->di_key[0]); + msgpack_pack_str(packer, key_len); + msgpack_pack_str_body(packer, &di->di_key[0], key_len); + cur_tv = &di->di_tv; + break; + } + case kMPConvList: { + if (cur_mpsv->data.l.li == NULL) { + (void) kv_pop(mpstack); + continue; + } + cur_tv = &cur_mpsv->data.l.li->li_tv; + cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; + break; + } + case kMPConvPairs: { + if (cur_mpsv->data.l.li == NULL) { + (void) kv_pop(mpstack); + continue; + } + const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; + if (convert_one_value(packer, &mpstack, &kv_pair->lv_first->li_tv) + == FAIL) { + goto vim_to_msgpack_error_ret; + } + cur_tv = &kv_pair->lv_last->li_tv; + cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; + break; + } + } + if (convert_one_value(packer, &mpstack, cur_tv) == FAIL) { + goto vim_to_msgpack_error_ret; + } + } + kv_destroy(mpstack); + return OK; +vim_to_msgpack_error_ret: + kv_destroy(mpstack); + return FAIL; +} + +/// "msgpackdump()" function +static void f_msgpackdump(typval_T *argvars, typval_T *rettv) + FUNC_ATTR_NONNULL_ALL +{ + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "msgpackdump()"); + return; + } + list_T *ret_list = rettv_list_alloc(rettv); + const list_T *list = argvars[0].vval.v_list; + if (list == NULL) { + return; + } + msgpack_packer *lpacker = msgpack_packer_new(ret_list, &msgpack_list_write); + for (const listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { + if (vim_to_msgpack(lpacker, &li->li_tv) == FAIL) { + break; + } + } + msgpack_packer_free(lpacker); +} + +/// Read bytes from list +/// +/// @param[in,out] state Structure describing position in list from which +/// reading should start. Is updated to reflect position +/// at which reading ended. +/// @param[out] buf Buffer to write to. +/// @param[in] nbuf Buffer length. +/// @param[out] read_bytes Is set to amount of bytes read. +/// +/// @return OK when reading was finished, FAIL in case of error (i.e. list item +/// was not a string), NOTDONE if reading was successfull, but there are +/// more bytes to read. +static int read_from_list(ListReaderState *const state, char *const buf, + const size_t nbuf, size_t *const read_bytes) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + char *const buf_end = buf + nbuf; + char *p = buf; + while (p < buf_end) { + for (size_t i = state->offset; i < state->li_length && p < buf_end; i++) { + const char ch = state->li->li_tv.vval.v_string[state->offset++]; + *p++ = (ch == NL ? NUL : ch); + } + if (p < buf_end) { + state->li = state->li->li_next; + if (state->li == NULL) { + *read_bytes = (size_t) (p - buf); + return OK; + } + *p++ = NL; + if (state->li->li_tv.v_type != VAR_STRING) { + *read_bytes = (size_t) (p - buf); + return FAIL; + } + state->offset = 0; + state->li_length = (state->li->li_tv.vval.v_string == NULL + ? 0 + : STRLEN(state->li->li_tv.vval.v_string)); + } + } + *read_bytes = nbuf; + return (state->offset < state->li_length || state->li->li_next != NULL + ? NOTDONE + : OK); +} + +/// Initialize ListReaderState structure +static inline ListReaderState init_lrstate(const list_T *const list) + FUNC_ATTR_NONNULL_ALL +{ + return (ListReaderState) { + .li = list->lv_first, + .offset = 0, + .li_length = (list->lv_first->li_tv.vval.v_string == NULL + ? 0 + : STRLEN(list->lv_first->li_tv.vval.v_string)), + }; +} + +/// Convert msgpack object to a VimL one +static int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ +#define INIT_SPECIAL_DICT(tv, type, val) \ + do { \ + dict_T *const dict = dict_alloc(); \ + dictitem_T *const type_di = dictitem_alloc((char_u *) "_TYPE"); \ + type_di->di_tv.v_type = VAR_LIST; \ + type_di->di_tv.v_lock = 0; \ + type_di->di_tv.vval.v_list = (list_T *) msgpack_type_lists[type]; \ + type_di->di_tv.vval.v_list->lv_refcount++; \ + dict_add(dict, type_di); \ + dictitem_T *const val_di = dictitem_alloc((char_u *) "_VAL"); \ + val_di->di_tv = val; \ + dict_add(dict, val_di); \ + tv->v_type = VAR_DICT; \ + dict->dv_refcount++; \ + tv->vval.v_dict = dict; \ + } while (0) + switch (mobj.type) { + case MSGPACK_OBJECT_NIL: { + INIT_SPECIAL_DICT(rettv, kMPNil, ((typval_T) { + .v_type = VAR_NUMBER, + .v_lock = 0, + .vval = { .v_number = 0 }, + })); + break; + } + case MSGPACK_OBJECT_BOOLEAN: { + INIT_SPECIAL_DICT(rettv, kMPBoolean, + ((typval_T) { + .v_type = VAR_NUMBER, + .v_lock = 0, + .vval = { + .v_number = (varnumber_T) mobj.via.boolean, + }, + })); + break; + } + case MSGPACK_OBJECT_POSITIVE_INTEGER: { + if (mobj.via.u64 <= VARNUMBER_MAX) { + *rettv = (typval_T) { + .v_type = VAR_NUMBER, + .v_lock = 0, + .vval = { .v_number = (varnumber_T) mobj.via.u64 }, + }; + } else { + list_T *const list = list_alloc(); + list->lv_refcount++; + INIT_SPECIAL_DICT(rettv, kMPInteger, + ((typval_T) { + .v_type = VAR_LIST, + .v_lock = 0, + .vval = { .v_list = list }, + })); + uint64_t n = mobj.via.u64; + list_append_number(list, 1); + list_append_number(list, (varnumber_T) ((n >> 62) & 0x3)); + list_append_number(list, (varnumber_T) ((n >> 31) & 0x7FFFFFFF)); + list_append_number(list, (varnumber_T) (n & 0x7FFFFFFF)); + } + break; + } + case MSGPACK_OBJECT_NEGATIVE_INTEGER: { + if (mobj.via.i64 >= VARNUMBER_MIN) { + *rettv = (typval_T) { + .v_type = VAR_NUMBER, + .v_lock = 0, + .vval = { .v_number = (varnumber_T) mobj.via.i64 }, + }; + } else { + list_T *const list = list_alloc(); + list->lv_refcount++; + INIT_SPECIAL_DICT(rettv, kMPInteger, + ((typval_T) { + .v_type = VAR_LIST, + .v_lock = 0, + .vval = { .v_list = list }, + })); + uint64_t n = -((uint64_t) mobj.via.i64); + list_append_number(list, -1); + list_append_number(list, (varnumber_T) ((n >> 62) & 0x3)); + list_append_number(list, (varnumber_T) ((n >> 31) & 0x7FFFFFFF)); + list_append_number(list, (varnumber_T) (n & 0x7FFFFFFF)); + } + break; + } + case MSGPACK_OBJECT_FLOAT: { + *rettv = (typval_T) { + .v_type = VAR_FLOAT, + .v_lock = 0, + .vval = { .v_float = mobj.via.f64 }, + }; + break; + } + case MSGPACK_OBJECT_STR: { + list_T *const list = list_alloc(); + list->lv_refcount++; + INIT_SPECIAL_DICT(rettv, kMPString, + ((typval_T) { + .v_type = VAR_LIST, + .v_lock = 0, + .vval = { .v_list = list }, + })); + if (msgpack_list_write((void *) list, mobj.via.str.ptr, mobj.via.str.size) + == -1) { + return FAIL; + } + break; + } + case MSGPACK_OBJECT_BIN: { + if (memchr(mobj.via.bin.ptr, NUL, mobj.via.bin.size) == NULL) { + *rettv = (typval_T) { + .v_type = VAR_STRING, + .v_lock = 0, + .vval = { .v_string = xmemdupz(mobj.via.bin.ptr, mobj.via.bin.size) }, + }; + break; + } + list_T *const list = list_alloc(); + list->lv_refcount++; + INIT_SPECIAL_DICT(rettv, kMPBinary, + ((typval_T) { + .v_type = VAR_LIST, + .v_lock = 0, + .vval = { .v_list = list }, + })); + if (msgpack_list_write((void *) list, mobj.via.bin.ptr, mobj.via.bin.size) + == -1) { + return FAIL; + } + break; + } + case MSGPACK_OBJECT_ARRAY: { + list_T *const list = list_alloc(); + list->lv_refcount++; + *rettv = (typval_T) { + .v_type = VAR_LIST, + .v_lock = 0, + .vval = { .v_list = list }, + }; + for (size_t i = 0; i < mobj.via.array.size; i++) { + listitem_T *const li = listitem_alloc(); + li->li_tv.v_type = VAR_UNKNOWN; + list_append(list, li); + if (msgpack_to_vim(mobj.via.array.ptr[i], &li->li_tv) == FAIL) { + return FAIL; + } + } + break; + } + case MSGPACK_OBJECT_MAP: { + for (size_t i = 0; i < mobj.via.map.size; i++) { + if (mobj.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR + || mobj.via.map.ptr[i].key.via.str.size == 0 + || memchr(mobj.via.map.ptr[i].key.via.str.ptr, NUL, + mobj.via.map.ptr[i].key.via.str.size) != NULL) { + goto msgpack_to_vim_generic_map; + } + } + dict_T *const dict = dict_alloc(); + dict->dv_refcount++; + *rettv = (typval_T) { + .v_type = VAR_DICT, + .v_lock = 0, + .vval = { .v_dict = dict }, + }; + for (size_t i = 0; i < mobj.via.map.size; i++) { + dictitem_T *const di = xmallocz(offsetof(dictitem_T, di_key) + + mobj.via.map.ptr[i].key.via.str.size); + memcpy(&di->di_key[0], mobj.via.map.ptr[i].key.via.str.ptr, + mobj.via.map.ptr[i].key.via.str.size); + di->di_tv.v_type = VAR_UNKNOWN; + if (dict_add(dict, di) == FAIL) { + // Duplicate key: fallback to generic map + clear_tv(rettv); + xfree(di); + goto msgpack_to_vim_generic_map; + } + if (msgpack_to_vim(mobj.via.map.ptr[i].val, &di->di_tv) == FAIL) { + return FAIL; + } + } + break; +msgpack_to_vim_generic_map: {} + list_T *const list = list_alloc(); + list->lv_refcount++; + INIT_SPECIAL_DICT(rettv, kMPMap, + ((typval_T) { + .v_type = VAR_LIST, + .v_lock = 0, + .vval = { .v_list = list }, + })); + for (size_t i = 0; i < mobj.via.map.size; i++) { + list_T *const kv_pair = list_alloc(); + list_append_list(list, kv_pair); + listitem_T *const key_li = listitem_alloc(); + key_li->li_tv.v_type = VAR_UNKNOWN; + list_append(kv_pair, key_li); + listitem_T *const val_li = listitem_alloc(); + val_li->li_tv.v_type = VAR_UNKNOWN; + list_append(kv_pair, val_li); + if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_li->li_tv) == FAIL) { + return FAIL; + } + if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_li->li_tv) == FAIL) { + return FAIL; + } + } + break; + } + case MSGPACK_OBJECT_EXT: { + list_T *const list = list_alloc(); + list->lv_refcount++; + list_append_number(list, mobj.via.ext.type); + list_T *const ext_val_list = list_alloc(); + list_append_list(list, ext_val_list); + INIT_SPECIAL_DICT(rettv, kMPExt, + ((typval_T) { + .v_type = VAR_LIST, + .v_lock = 0, + .vval = { .v_list = list }, + })); + if (msgpack_list_write((void *) ext_val_list, mobj.via.ext.ptr, + mobj.via.ext.size) == -1) { + return FAIL; + } + break; + } + } +#undef INIT_SPECIAL_DICT + return OK; +} + +/// "msgpackparse" function +static void f_msgpackparse(typval_T *argvars, typval_T *rettv) + FUNC_ATTR_NONNULL_ALL +{ + if (argvars[0].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "msgpackparse()"); + } + list_T *ret_list = rettv_list_alloc(rettv); + const list_T *list = argvars[0].vval.v_list; + if (list == NULL || list->lv_first == NULL) { + return; + } + if (list->lv_first->li_tv.v_type != VAR_STRING) { + EMSG2(_(e_invarg2), "List item is not a string"); + return; + } + ListReaderState lrstate = init_lrstate(list); + msgpack_unpacker *const unpacker = msgpack_unpacker_new(IOSIZE); + if (unpacker == NULL) { + EMSG(_(e_outofmem)); + return; + } + msgpack_unpacked unpacked; + msgpack_unpacked_init(&unpacked); + do { + if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) { + EMSG(_(e_outofmem)); + goto f_msgpackparse_exit; + } + size_t read_bytes; + const int rlret = read_from_list( + &lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE, &read_bytes); + if (rlret == FAIL) { + EMSG2(_(e_invarg2), "List item is not a string"); + goto f_msgpackparse_exit; + } + msgpack_unpacker_buffer_consumed(unpacker, read_bytes); + if (read_bytes == 0) { + break; + } + while (unpacker->off < unpacker->used) { + const msgpack_unpack_return result = msgpack_unpacker_next(unpacker, + &unpacked); + if (result == MSGPACK_UNPACK_PARSE_ERROR) { + EMSG2(_(e_invarg2), "Failed to parse msgpack string"); + goto f_msgpackparse_exit; + } + if (result == MSGPACK_UNPACK_NOMEM_ERROR) { + EMSG(_(e_outofmem)); + goto f_msgpackparse_exit; + } + if (result == MSGPACK_UNPACK_SUCCESS) { + listitem_T *li = listitem_alloc(); + li->li_tv.v_type = VAR_UNKNOWN; + list_append(ret_list, li); + if (msgpack_to_vim(unpacked.data, &li->li_tv) == FAIL) { + EMSG2(_(e_invarg2), "Failed to convert msgpack string"); + goto f_msgpackparse_exit; + } + } + if (result == MSGPACK_UNPACK_CONTINUE) { + if (rlret == OK) { + EMSG2(_(e_invarg2), "Incomplete msgpack string"); + } + break; + } + } + if (rlret == OK) { + break; + } + } while (true); + +f_msgpackparse_exit: + msgpack_unpacked_destroy(&unpacked); + msgpack_unpacker_free(unpacker); + return; +} /* * "nextnonblank()" function @@ -20125,17 +21041,21 @@ static inline TerminalJobData *common_job_init(char **argv, ufunc_T *on_stdout, data->on_stderr = on_stderr; data->on_exit = on_exit; data->self = self; + data->events = queue_new_child(loop.events); if (pty) { - data->proc.pty = pty_process_init(data); + data->proc.pty = pty_process_init(&loop, data); } else { - data->proc.uv = uv_process_init(data); + data->proc.uv = uv_process_init(&loop, data); } Process *proc = (Process *)&data->proc; proc->argv = argv; proc->in = &data->in; proc->out = &data->out; - proc->err = &data->err; + if (!pty) { + proc->err = &data->err; + } proc->cb = on_process_exit; + proc->events = data->events; return data; } @@ -20166,8 +21086,12 @@ static inline bool common_job_start(TerminalJobData *data, typval_T *rettv) { data->refcount++; Process *proc = (Process *)&data->proc; - if (!process_spawn(&loop, proc)) { + if (!process_spawn(proc)) { EMSG(_(e_jobexe)); + if (proc->type == kProcessTypePty) { + xfree(data->proc.pty.term_name); + free_term_job_data(data); + } return false; } @@ -20186,7 +21110,9 @@ static inline bool common_job_start(TerminalJobData *data, typval_T *rettv) return true; } -static inline void free_term_job_data(TerminalJobData *data) { +static inline void free_term_job_data_event(void **argv) +{ + TerminalJobData *data = argv[0]; if (data->on_stdout) { user_func_unref(data->on_stdout); } @@ -20201,17 +21127,25 @@ static inline void free_term_job_data(TerminalJobData *data) { data->self->internal_refcount--; dict_unref(data->self); } + queue_free(data->events); xfree(data); } +static inline void free_term_job_data(TerminalJobData *data) +{ + // data->queue may still be used after this function returns(process_wait), so + // only free in the next event loop iteration + queue_put(loop.fast_events, free_term_job_data_event, 1, data); +} + // vimscript job callbacks must be executed on Nvim main loop -static inline void push_job_event(TerminalJobData *data, ufunc_T *callback, +static inline void process_job_event(TerminalJobData *data, ufunc_T *callback, const char *type, char *buf, size_t count, int status) { - JobEvent *event_data = xmalloc(sizeof(JobEvent)); - event_data->received = NULL; + JobEvent event_data; + event_data.received = NULL; if (buf) { - event_data->received = list_alloc(); + event_data.received = list_alloc(); char *ptr = buf; size_t remaining = count; size_t off = 0; @@ -20219,7 +21153,7 @@ static inline void push_job_event(TerminalJobData *data, ufunc_T *callback, while (off < remaining) { // append the line if (ptr[off] == NL) { - list_append_string(event_data->received, (uint8_t *)ptr, off); + list_append_string(event_data.received, (uint8_t *)ptr, off); size_t skip = off + 1; ptr += skip; remaining -= skip; @@ -20232,51 +21166,53 @@ static inline void push_job_event(TerminalJobData *data, ufunc_T *callback, } off++; } - list_append_string(event_data->received, (uint8_t *)ptr, off); + list_append_string(event_data.received, (uint8_t *)ptr, off); } else { - event_data->status = status; + event_data.status = status; } - event_data->data = data; - event_data->callback = callback; - event_data->type = type; - loop_push_event(&loop, (Event) { - .handler = on_job_event, - .data = event_data - }, !disable_job_defer); + event_data.data = data; + event_data.callback = callback; + event_data.type = type; + on_job_event(&event_data); } -static void on_job_stdout(Stream *stream, RBuffer *buf, void *job, bool eof) +static void on_job_stdout(Stream *stream, RBuffer *buf, size_t count, + void *job, bool eof) { TerminalJobData *data = job; - on_job_output(stream, job, buf, eof, data->on_stdout, "stdout"); + on_job_output(stream, job, buf, count, eof, data->on_stdout, "stdout"); } -static void on_job_stderr(Stream *stream, RBuffer *buf, void *job, bool eof) +static void on_job_stderr(Stream *stream, RBuffer *buf, size_t count, + void *job, bool eof) { TerminalJobData *data = job; - on_job_output(stream, job, buf, eof, data->on_stderr, "stderr"); + on_job_output(stream, job, buf, count, eof, data->on_stderr, "stderr"); } static void on_job_output(Stream *stream, TerminalJobData *data, RBuffer *buf, - bool eof, ufunc_T *callback, const char *type) + size_t count, bool eof, ufunc_T *callback, const char *type) { if (eof) { return; } - 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); - } + // stub variable, to keep reading consistent with the order of events, only + // consider the count parameter. + size_t r; + char *ptr = rbuffer_read_ptr(buf, &r); - if (callback) { - push_job_event(data, callback, type, ptr, len, 0); - } + // The order here matters, the terminal must receive the data first because + // process_job_event will modify the read buffer(convert NULs into NLs) + if (data->term) { + terminal_receive(data->term, ptr, count); + } - rbuffer_consumed(buf, len); + if (callback) { + process_job_event(data, callback, type, ptr, count, 0); } + + rbuffer_consumed(buf, count); } static void on_process_exit(Process *proc, int status, void *d) @@ -20292,7 +21228,7 @@ static void on_process_exit(Process *proc, int status, void *d) *data->status_ptr = status; } - push_job_event(data, data->on_exit, "exit", NULL, 0, status); + process_job_event(data, data->on_exit, "exit", NULL, 0, status); } static void term_write(char *buf, size_t size, void *d) @@ -20326,10 +21262,8 @@ static void term_job_data_decref(TerminalJobData *data) } } -static void on_job_event(Event event) +static void on_job_event(JobEvent *ev) { - JobEvent *ev = event.data; - if (!ev->callback) { goto end; } @@ -20374,7 +21308,15 @@ end: pmap_del(uint64_t)(jobs, ev->data->id); term_job_data_decref(ev->data); } - xfree(ev); +} + +static TerminalJobData *find_job(uint64_t id) +{ + TerminalJobData *data = pmap_get(uint64_t)(jobs, id); + if (!data || data->stopped) { + return NULL; + } + return data; } static void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) diff --git a/src/nvim/eval.h b/src/nvim/eval.h index bf48bc74b8..8f065eda33 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -65,6 +65,7 @@ enum { VV_PROGPATH, VV_COMMAND_OUTPUT, VV_COMPLETED_ITEM, + VV_MSGPACK_TYPES, VV_LEN, /* number of v: vars */ }; diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h index 34a36004d6..0faf860588 100644 --- a/src/nvim/eval_defs.h +++ b/src/nvim/eval_defs.h @@ -1,11 +1,16 @@ #ifndef NVIM_EVAL_DEFS_H #define NVIM_EVAL_DEFS_H +#include <limits.h> + #include "nvim/hashtab.h" typedef int varnumber_T; typedef double float_T; +#define VARNUMBER_MAX INT_MAX +#define VARNUMBER_MIN INT_MIN + typedef struct listvar_S list_T; typedef struct dictvar_S dict_T; diff --git a/src/nvim/event/defs.h b/src/nvim/event/defs.h new file mode 100644 index 0000000000..5126d52241 --- /dev/null +++ b/src/nvim/event/defs.h @@ -0,0 +1,39 @@ +#ifndef NVIM_EVENT_DEFS_H +#define NVIM_EVENT_DEFS_H + +#include <assert.h> +#include <stdarg.h> + +#define EVENT_HANDLER_MAX_ARGC 4 + +typedef void (*argv_callback)(void **argv); +typedef struct message { + int priority; + argv_callback handler; + void *argv[EVENT_HANDLER_MAX_ARGC]; +} Event; + +#define VA_EVENT_INIT(event, p, h, a) \ + do { \ + assert(a <= EVENT_HANDLER_MAX_ARGC); \ + (event)->priority = p; \ + (event)->handler = h; \ + if (a) { \ + va_list args; \ + va_start(args, a); \ + for (int i = 0; i < a; i++) { \ + (event)->argv[i] = va_arg(args, void *); \ + } \ + va_end(args); \ + } \ + } while (0) + +static inline Event event_create(int priority, argv_callback cb, int argc, ...) +{ + assert(argc <= EVENT_HANDLER_MAX_ARGC); + Event event; + VA_EVENT_INIT(&event, priority, cb, argc); + return event; +} + +#endif // NVIM_EVENT_DEFS_H diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index d90565002e..3d3288f858 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -1,3 +1,4 @@ +#include <stdarg.h> #include <stdint.h> #include <uv.h> @@ -9,17 +10,23 @@ # include "event/loop.c.generated.h" #endif +typedef struct idle_event { + uv_idle_t idle; + Event event; +} IdleEvent; + void loop_init(Loop *loop, void *data) { uv_loop_init(&loop->uv); loop->uv.data = loop; - loop->deferred_events = kl_init(Event); - loop->immediate_events = kl_init(Event); loop->children = kl_init(WatcherPtr); loop->children_stop_requests = 0; + loop->events = queue_new_parent(loop_on_put, loop); + loop->fast_events = queue_new_child(loop->events); uv_signal_init(&loop->uv, &loop->children_watcher); uv_timer_init(&loop->uv, &loop->children_kill_timer); + uv_timer_init(&loop->uv, &loop->poll_timer); } void loop_poll_events(Loop *loop, int ms) @@ -30,89 +37,36 @@ void loop_poll_events(Loop *loop, int ms) abort(); // Should not re-enter uv_run } - bool wait = true; - uv_timer_t timer; + uv_run_mode mode = UV_RUN_ONCE; if (ms > 0) { - uv_timer_init(&loop->uv, &timer); // Use a repeating timeout of ms milliseconds to make sure // we do not block indefinitely for I/O. - uv_timer_start(&timer, timer_cb, (uint64_t)ms, (uint64_t)ms); + uv_timer_start(&loop->poll_timer, timer_cb, (uint64_t)ms, (uint64_t)ms); } else if (ms == 0) { // For ms == 0, we need to do a non-blocking event poll by // setting the run mode to UV_RUN_NOWAIT. - wait = false; + mode = UV_RUN_NOWAIT; } - if (wait) { - loop_run_once(loop); - } else { - loop_run_nowait(loop); - } + uv_run(&loop->uv, mode); if (ms > 0) { - // Ensure the timer handle is closed and run the event loop - // once more to let libuv perform it's cleanup - uv_timer_stop(&timer); - uv_close((uv_handle_t *)&timer, NULL); - loop_run_nowait(loop); + uv_timer_stop(&loop->poll_timer); } recursive--; // Can re-enter uv_run now - process_events_from(loop->immediate_events); -} - -bool loop_has_deferred_events(Loop *loop) -{ - return loop->deferred_events_allowed && !kl_empty(loop->deferred_events); + queue_process_events(loop->fast_events); } -void loop_enable_deferred_events(Loop *loop) -{ - ++loop->deferred_events_allowed; -} - -void loop_disable_deferred_events(Loop *loop) -{ - --loop->deferred_events_allowed; -} - -// Queue an event -void loop_push_event(Loop *loop, Event event, bool deferred) +void loop_on_put(Queue *queue, void *data) { + Loop *loop = data; // Sometimes libuv will run pending callbacks(timer for example) before // blocking for a poll. If this happens and the callback pushes a event to one // of the queues, the event would only be processed after the poll // returns(user hits a key for example). To avoid this scenario, we call // uv_stop when a event is enqueued. - loop_stop(loop); - kl_push(Event, deferred ? loop->deferred_events : loop->immediate_events, - event); -} - -void loop_process_event(Loop *loop) -{ - process_events_from(loop->deferred_events); -} - - -void loop_run(Loop *loop) -{ - uv_run(&loop->uv, UV_RUN_DEFAULT); -} - -void loop_run_once(Loop *loop) -{ - uv_run(&loop->uv, UV_RUN_ONCE); -} - -void loop_run_nowait(Loop *loop) -{ - uv_run(&loop->uv, UV_RUN_NOWAIT); -} - -void loop_stop(Loop *loop) -{ uv_stop(&loop->uv); } @@ -120,25 +74,12 @@ void loop_close(Loop *loop) { uv_close((uv_handle_t *)&loop->children_watcher, NULL); uv_close((uv_handle_t *)&loop->children_kill_timer, NULL); + uv_close((uv_handle_t *)&loop->poll_timer, NULL); do { uv_run(&loop->uv, UV_RUN_DEFAULT); } while (uv_loop_close(&loop->uv)); } -void loop_process_all_events(Loop *loop) -{ - process_events_from(loop->immediate_events); - process_events_from(loop->deferred_events); -} - -static void process_events_from(klist_t(Event) *queue) -{ - while (!kl_empty(queue)) { - Event event = kl_shift(Event, queue); - event.handler(event); - } -} - static void timer_cb(uv_timer_t *handle) { } diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h index 5eb4d32ca8..9212a45aa4 100644 --- a/src/nvim/event/loop.h +++ b/src/nvim/event/loop.h @@ -7,38 +7,39 @@ #include "nvim/lib/klist.h" #include "nvim/os/time.h" - -typedef struct event Event; -typedef void (*event_handler)(Event event); - -struct event { - void *data; - event_handler handler; -}; +#include "nvim/event/queue.h" typedef void * WatcherPtr; #define _noop(x) KLIST_INIT(WatcherPtr, WatcherPtr, _noop) -KLIST_INIT(Event, Event, _noop) typedef struct loop { uv_loop_t uv; - klist_t(Event) *deferred_events, *immediate_events; - int deferred_events_allowed; + Queue *events, *fast_events; klist_t(WatcherPtr) *children; uv_signal_t children_watcher; - uv_timer_t children_kill_timer; + uv_timer_t children_kill_timer, poll_timer; size_t children_stop_requests; } Loop; +#define CREATE_EVENT(queue, handler, argc, ...) \ + do { \ + if (queue) { \ + queue_put((queue), (handler), argc, __VA_ARGS__); \ + } else { \ + void *argv[argc] = {__VA_ARGS__}; \ + (handler)(argv); \ + } \ + } while (0) + // Poll for events until a condition or timeout -#define LOOP_POLL_EVENTS_UNTIL(loop, timeout, condition) \ +#define LOOP_PROCESS_EVENTS_UNTIL(loop, queue, timeout, condition) \ do { \ int remaining = timeout; \ uint64_t before = (remaining > 0) ? os_hrtime() : 0; \ while (!(condition)) { \ - loop_poll_events(loop, remaining); \ + LOOP_PROCESS_EVENTS(loop, queue, remaining); \ if (remaining == 0) { \ break; \ } else if (remaining > 0) { \ @@ -52,6 +53,16 @@ typedef struct loop { } \ } while (0) +#define LOOP_PROCESS_EVENTS(loop, queue, timeout) \ + do { \ + if (queue && !queue_empty(queue)) { \ + queue_process_events(queue); \ + } else { \ + loop_poll_events(loop, timeout); \ + } \ + } while (0) + + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "event/loop.h.generated.h" #endif diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 2b1f1ae096..81d4e690c3 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -22,7 +22,7 @@ #define TERM_TIMEOUT 1000000000 #define KILL_TIMEOUT (TERM_TIMEOUT * 2) -#define CLOSE_PROC_STREAM(proc, stream) \ +#define CLOSE_PROC_STREAM(proc, stream) \ do { \ if (proc->stream && !proc->stream->closed) { \ stream_close(proc->stream, NULL); \ @@ -30,19 +30,18 @@ } while (0) -bool process_spawn(Loop *loop, Process *proc) FUNC_ATTR_NONNULL_ALL +bool process_spawn(Process *proc) FUNC_ATTR_NONNULL_ALL { - proc->loop = loop; if (proc->in) { - uv_pipe_init(&loop->uv, &proc->in->uv.pipe, 0); + uv_pipe_init(&proc->loop->uv, &proc->in->uv.pipe, 0); } if (proc->out) { - uv_pipe_init(&loop->uv, &proc->out->uv.pipe, 0); + uv_pipe_init(&proc->loop->uv, &proc->out->uv.pipe, 0); } if (proc->err) { - uv_pipe_init(&loop->uv, &proc->err->uv.pipe, 0); + uv_pipe_init(&proc->loop->uv, &proc->err->uv.pipe, 0); } bool success; @@ -77,6 +76,7 @@ bool process_spawn(Loop *loop, Process *proc) FUNC_ATTR_NONNULL_ALL if (proc->in) { stream_init(NULL, proc->in, -1, (uv_stream_t *)&proc->in->uv.pipe, data); + proc->in->events = proc->events; proc->in->internal_data = proc; proc->in->internal_close_cb = on_process_stream_close; proc->refcount++; @@ -84,6 +84,7 @@ bool process_spawn(Loop *loop, Process *proc) FUNC_ATTR_NONNULL_ALL if (proc->out) { stream_init(NULL, proc->out, -1, (uv_stream_t *)&proc->out->uv.pipe, data); + proc->out->events = proc->events; proc->out->internal_data = proc; proc->out->internal_close_cb = on_process_stream_close; proc->refcount++; @@ -91,6 +92,7 @@ bool process_spawn(Loop *loop, Process *proc) FUNC_ATTR_NONNULL_ALL if (proc->err) { stream_init(NULL, proc->err, -1, (uv_stream_t *)&proc->err->uv.pipe, data); + proc->err->events = proc->events; proc->err->internal_data = proc; proc->err->internal_close_cb = on_process_stream_close; proc->refcount++; @@ -99,7 +101,7 @@ bool process_spawn(Loop *loop, Process *proc) FUNC_ATTR_NONNULL_ALL proc->internal_exit_cb = on_process_exit; proc->internal_close_cb = decref; proc->refcount++; - kl_push(WatcherPtr, loop->children, proc); + kl_push(WatcherPtr, proc->loop->children, proc); return true; } @@ -113,7 +115,7 @@ void process_teardown(Loop *loop) FUNC_ATTR_NONNULL_ALL } // Wait until all children exit - LOOP_POLL_EVENTS_UNTIL(loop, -1, kl_empty(loop->children)); + LOOP_PROCESS_EVENTS_UNTIL(loop, loop->events, -1, kl_empty(loop->children)); pty_process_teardown(loop); } @@ -150,16 +152,24 @@ void process_close_err(Process *proc) FUNC_ATTR_NONNULL_ALL /// indistinguishable from the process returning -1 by itself. Which /// is possible on some OS. Returns -2 if an user has interruped the /// wait. -int process_wait(Process *proc, int ms) FUNC_ATTR_NONNULL_ALL +int process_wait(Process *proc, int ms, Queue *events) FUNC_ATTR_NONNULL_ARG(1) { // The default status is -1, which represents a timeout int status = -1; bool interrupted = false; + if (!proc->refcount) { + LOOP_PROCESS_EVENTS(proc->loop, proc->events, 0); + return proc->status; + } + + if (!events) { + events = proc->events; + } // Increase refcount to stop the exit callback from being called(and possibly // being freed) before we have a chance to get the status. proc->refcount++; - LOOP_POLL_EVENTS_UNTIL(proc->loop, ms, + LOOP_PROCESS_EVENTS_UNTIL(proc->loop, events, ms, // Until... got_int || // interrupted by the user proc->refcount == 1); // job exited @@ -171,12 +181,12 @@ int process_wait(Process *proc, int ms) FUNC_ATTR_NONNULL_ALL got_int = false; process_stop(proc); if (ms == -1) { - // We can only return, if all streams/handles are closed and the job - + // We can only return if all streams/handles are closed and the job // exited. - LOOP_POLL_EVENTS_UNTIL(proc->loop, -1, proc->refcount == 1); + LOOP_PROCESS_EVENTS_UNTIL(proc->loop, events, -1, + proc->refcount == 1); } else { - loop_poll_events(proc->loop, 0); + LOOP_PROCESS_EVENTS(proc->loop, events, 0); } } @@ -185,6 +195,10 @@ int process_wait(Process *proc, int ms) FUNC_ATTR_NONNULL_ALL // resources status = interrupted ? -2 : proc->status; decref(proc); + if (events) { + // the decref call created an exit event, process it now + queue_process_events(events); + } } else { proc->refcount--; } @@ -250,6 +264,18 @@ static void children_kill_cb(uv_timer_t *handle) } } +static void process_close_event(void **argv) +{ + Process *proc = argv[0]; + shell_free_argv(proc->argv); + if (proc->type == kProcessTypePty) { + xfree(((PtyProcess *)proc)->term_name); + } + if (proc->cb) { + proc->cb(proc, proc->status, proc->data); + } +} + static void decref(Process *proc) { if (--proc->refcount != 0) { @@ -264,16 +290,9 @@ static void decref(Process *proc) break; } } - assert(node); kl_shift_at(WatcherPtr, loop->children, node); - shell_free_argv(proc->argv); - if (proc->type == kProcessTypePty) { - xfree(((PtyProcess *)proc)->term_name); - } - if (proc->cb) { - proc->cb(proc, proc->status, proc->data); - } + CREATE_EVENT(proc->events, process_close_event, 1, proc); } static void process_close(Process *proc) @@ -293,28 +312,27 @@ static void process_close(Process *proc) } } -static void on_process_exit(Process *proc) +static void process_close_handles(void **argv) { - if (exiting) { - on_process_exit_event((Event) {.data = proc}); - } else { - loop_push_event(proc->loop, - (Event) {.handler = on_process_exit_event, .data = proc}, false); - } + Process *proc = argv[0]; + process_close_streams(proc); + process_close(proc); +} +static void on_process_exit(Process *proc) +{ Loop *loop = proc->loop; - if (loop->children_stop_requests && !--loop->children_stop_requests) { + if (proc->stopped_time && loop->children_stop_requests + && !--loop->children_stop_requests) { // Stop the timer if no more stop requests are pending DLOG("Stopping process kill timer"); uv_timer_stop(&loop->children_kill_timer); } -} - -static void on_process_exit_event(Event event) -{ - Process *proc = event.data; - process_close_streams(proc); - process_close(proc); + // Process handles are closed in the next event loop tick. This is done to + // give libuv more time to read data from the OS after the process exits(If + // process_close_streams is called with data still in the OS buffer, we lose + // it) + CREATE_EVENT(proc->events, process_close_handles, 1, proc); } static void on_process_stream_close(Stream *stream, void *data) diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h index 5c84a7d1d0..45edc46b95 100644 --- a/src/nvim/event/process.h +++ b/src/nvim/event/process.h @@ -26,14 +26,16 @@ struct process { process_exit_cb cb; internal_process_cb internal_exit_cb, internal_close_cb; bool closed, term_sent; + Queue *events; }; -static inline Process process_init(ProcessType type, void *data) +static inline Process process_init(Loop *loop, ProcessType type, void *data) { return (Process) { .type = type, .data = data, - .loop = NULL, + .loop = loop, + .events = NULL, .pid = 0, .status = 0, .refcount = 0, diff --git a/src/nvim/event/pty_process.c b/src/nvim/event/pty_process.c index 1e24d7c919..8eef72f12f 100644 --- a/src/nvim/event/pty_process.c +++ b/src/nvim/event/pty_process.c @@ -33,17 +33,18 @@ # include "event/pty_process.c.generated.h" #endif -static const unsigned int KILL_RETRIES = 5; -static const unsigned int KILL_TIMEOUT = 2; // seconds - bool pty_process_spawn(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL { + static struct termios termios; + if (!termios.c_cflag) { + init_termios(&termios); + } + Process *proc = (Process *)ptyproc; + assert(!proc->err); uv_signal_start(&proc->loop->children_watcher, chld_handler, SIGCHLD); ptyproc->winsize = (struct winsize){ptyproc->height, ptyproc->width, 0, 0}; - struct termios termios; - init_termios(&termios); uv_disable_stdio_inheritance(); int master; int pid = forkpty(&master, NULL, &termios, &ptyproc->winsize); @@ -73,9 +74,6 @@ bool pty_process_spawn(PtyProcess *ptyproc) if (proc->out && !set_duplicating_descriptor(master, &proc->out->uv.pipe)) { goto error; } - if (proc->err && !set_duplicating_descriptor(master, &proc->err->uv.pipe)) { - goto error; - } ptyproc->tty_fd = master; proc->pid = pid; @@ -83,19 +81,8 @@ bool pty_process_spawn(PtyProcess *ptyproc) error: close(master); - - // terminate spawned process - kill(pid, SIGTERM); - int status, child; - unsigned int try = 0; - while (try++ < KILL_RETRIES && !(child = waitpid(pid, &status, WNOHANG))) { - sleep(KILL_TIMEOUT); - } - if (child != pid) { - kill(pid, SIGKILL); - waitpid(pid, NULL, 0); - } - + kill(pid, SIGKILL); + waitpid(pid, NULL, 0); return false; } @@ -152,7 +139,6 @@ static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL { - memset(termios, 0, sizeof(struct termios)); // Taken from pangoterm termios->c_iflag = ICRNL|IXON; termios->c_oflag = OPOST|ONLCR; diff --git a/src/nvim/event/pty_process.h b/src/nvim/event/pty_process.h index a12b5489c5..446d7fd3c8 100644 --- a/src/nvim/event/pty_process.h +++ b/src/nvim/event/pty_process.h @@ -13,10 +13,10 @@ typedef struct pty_process { int tty_fd; } PtyProcess; -static inline PtyProcess pty_process_init(void *data) +static inline PtyProcess pty_process_init(Loop *loop, void *data) { PtyProcess rv; - rv.process = process_init(kProcessTypePty, data); + rv.process = process_init(loop, kProcessTypePty, data); rv.term_name = NULL; rv.width = 80; rv.height = 24; diff --git a/src/nvim/event/queue.c b/src/nvim/event/queue.c new file mode 100644 index 0000000000..19eca14144 --- /dev/null +++ b/src/nvim/event/queue.c @@ -0,0 +1,208 @@ +// Queue for selective async event processing. Instances of this queue support a +// parent/child relationship with the following properties: +// +// - pushing a node to a child queue will push a corresponding link node to the +// parent queue +// - removing a link node from a parent queue will remove the next node +// in the linked child queue +// - removing a node from a child queue will remove the corresponding link node +// in the parent queue +// +// These properties allow neovim to organize and process events from different +// sources with a certain degree of control. Here's how the queue is used: +// +// +----------------+ +// | Main loop | +// +----------------+ +// ^ +// | +// +----------------+ +// +-------------->| Event loop |<------------+ +// | +--+-------------+ | +// | ^ ^ | +// | | | | +// +-----------+ +-----------+ +---------+ +---------+ +// | Channel 1 | | Channel 2 | | Job 1 | | Job 2 | +// +-----------+ +-----------+ +---------+ +---------+ +// +// +// In the above diagram, the lower boxes represents event emitters, each with +// it's own private queue that have the event loop queue as the parent. +// +// When idle, the main loop spins the event loop which queues events from many +// sources(channels, jobs, user...). Each event emitter pushes events to its own +// private queue which is propagated to the event loop queue. When the main loop +// consumes an event, the corresponding event is removed from the emitter's +// queue. +// +// The main reason for this queue hierarchy is to allow focusing on a single +// event emitter while blocking the main loop. For example, if the `jobwait` +// vimscript function is called on job1, the main loop will temporarily stop +// polling the event loop queue and poll job1 queue instead. Same with channels, +// when calling `rpcrequest`, we want to temporarily stop processing events from +// other sources and focus on a specific channel. + +#include <assert.h> +#include <stdarg.h> +#include <stdbool.h> +#include <stdint.h> + + +#include <uv.h> + +#include "nvim/event/queue.h" +#include "nvim/memory.h" +#include "nvim/os/time.h" + +typedef struct queue_item QueueItem; +struct queue_item { + union { + Queue *queue; + struct { + Event event; + QueueItem *parent; + } item; + } data; + bool link; // this is just a link to a node in a child queue + QUEUE node; +}; + +struct queue { + Queue *parent; + QUEUE headtail; + put_callback put_cb; + void *data; +}; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "event/queue.c.generated.h" +#endif + +static Event NILEVENT = {.handler = NULL, .argv = {NULL}}; + +Queue *queue_new_parent(put_callback put_cb, void *data) +{ + return queue_new(NULL, put_cb, data); +} + +Queue *queue_new_child(Queue *parent) + FUNC_ATTR_NONNULL_ALL +{ + assert(!parent->parent); + return queue_new(parent, NULL, NULL); +} + +static Queue *queue_new(Queue *parent, put_callback put_cb, void *data) +{ + Queue *rv = xmalloc(sizeof(Queue)); + QUEUE_INIT(&rv->headtail); + rv->parent = parent; + rv->put_cb = put_cb; + rv->data = data; + return rv; +} + +void queue_free(Queue *queue) +{ + assert(queue); + if (queue->parent) { + while (!QUEUE_EMPTY(&queue->headtail)) { + QUEUE *q = QUEUE_HEAD(&queue->headtail); + QueueItem *item = queue_node_data(q); + assert(!item->link); + QUEUE_REMOVE(&item->data.item.parent->node); + xfree(item->data.item.parent); + QUEUE_REMOVE(q); + xfree(item); + } + } + + xfree(queue); +} + +Event queue_get(Queue *queue) +{ + return queue_empty(queue) ? NILEVENT : queue_remove(queue); +} + +void queue_put_event(Queue *queue, Event event) +{ + assert(queue); + assert(queue->parent); // don't push directly to the parent queue + queue_push(queue, event); + if (queue->parent->put_cb) { + queue->parent->put_cb(queue->parent, queue->parent->data); + } +} + +void queue_process_events(Queue *queue) +{ + assert(queue); + while (!queue_empty(queue)) { + Event event = queue_get(queue); + if (event.handler) { + event.handler(event.argv); + } + } +} + +bool queue_empty(Queue *queue) +{ + assert(queue); + return QUEUE_EMPTY(&queue->headtail); +} + +void queue_replace_parent(Queue *queue, Queue *new_parent) +{ + assert(queue_empty(queue)); + queue->parent = new_parent; +} + +static Event queue_remove(Queue *queue) +{ + assert(!queue_empty(queue)); + QUEUE *h = QUEUE_HEAD(&queue->headtail); + QUEUE_REMOVE(h); + QueueItem *item = queue_node_data(h); + Event rv; + + if (item->link) { + assert(!queue->parent); + // remove the next node in the linked queue + Queue *linked = item->data.queue; + assert(!queue_empty(linked)); + QueueItem *child = + queue_node_data(QUEUE_HEAD(&linked->headtail)); + QUEUE_REMOVE(&child->node); + rv = child->data.item.event; + xfree(child); + } else { + assert(queue->parent); + assert(!queue_empty(queue->parent)); + // remove the corresponding link node in the parent queue + QUEUE_REMOVE(&item->data.item.parent->node); + xfree(item->data.item.parent); + rv = item->data.item.event; + } + + xfree(item); + return rv; +} + +static void queue_push(Queue *queue, Event event) +{ + QueueItem *item = xmalloc(sizeof(QueueItem)); + item->link = false; + item->data.item.event = event; + QUEUE_INSERT_TAIL(&queue->headtail, &item->node); + // push link node to the parent queue + item->data.item.parent = xmalloc(sizeof(QueueItem)); + item->data.item.parent->link = true; + item->data.item.parent->data.queue = queue; + QUEUE_INSERT_TAIL(&queue->parent->headtail, &item->data.item.parent->node); +} + +static QueueItem *queue_node_data(QUEUE *q) +{ + return QUEUE_DATA(q, QueueItem, node); +} diff --git a/src/nvim/event/queue.h b/src/nvim/event/queue.h new file mode 100644 index 0000000000..85fc59f8b2 --- /dev/null +++ b/src/nvim/event/queue.h @@ -0,0 +1,19 @@ +#ifndef NVIM_EVENT_QUEUE_H +#define NVIM_EVENT_QUEUE_H + +#include <uv.h> + +#include "nvim/event/defs.h" +#include "nvim/lib/queue.h" + +typedef struct queue Queue; +typedef void (*put_callback)(Queue *queue, void *data); + +#define queue_put(q, h, ...) \ + queue_put_event(q, event_create(1, h, __VA_ARGS__)); + + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "event/queue.h.generated.h" +#endif +#endif // NVIM_EVENT_QUEUE_H diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c index 9d2439ac2b..9f3fbc25ff 100644 --- a/src/nvim/event/rstream.c +++ b/src/nvim/event/rstream.c @@ -49,6 +49,7 @@ void rstream_init(Stream *stream, size_t bufsize) /// /// @param stream The `Stream` instance void rstream_start(Stream *stream, stream_read_cb cb) + FUNC_ATTR_NONNULL_ARG(1) { stream->read_cb = cb; if (stream->uvstream) { @@ -62,6 +63,7 @@ void rstream_start(Stream *stream, stream_read_cb cb) /// /// @param stream The `Stream` instance void rstream_stop(Stream *stream) + FUNC_ATTR_NONNULL_ALL { if (stream->uvstream) { uv_read_stop(stream->uvstream); @@ -112,7 +114,7 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf) // Read error or EOF, either way stop the stream and invoke the callback // with eof == true uv_read_stop(uvstream); - invoke_read_cb(stream, true); + invoke_read_cb(stream, 0, true); } return; } @@ -122,7 +124,7 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf) // Data was already written, so all we need is to update 'wpos' to reflect // the space actually used in the buffer. rbuffer_produced(stream->buffer, nread); - invoke_read_cb(stream, false); + invoke_read_cb(stream, nread, false); } // Called by the by the 'idle' handle to emulate a reading event @@ -156,7 +158,7 @@ static void fread_idle_cb(uv_idle_t *handle) if (req.result <= 0) { uv_idle_stop(&stream->uv.idle); - invoke_read_cb(stream, true); + invoke_read_cb(stream, 0, true); return; } @@ -164,12 +166,36 @@ static void fread_idle_cb(uv_idle_t *handle) size_t nread = (size_t) req.result; rbuffer_produced(stream->buffer, nread); stream->fpos += nread; - invoke_read_cb(stream, false); + invoke_read_cb(stream, nread, false); } -static void invoke_read_cb(Stream *stream, bool eof) +static void read_event(void **argv) { + Stream *stream = argv[0]; if (stream->read_cb) { - stream->read_cb(stream, stream->buffer, stream->data, eof); + size_t count = (uintptr_t)argv[1]; + bool eof = (uintptr_t)argv[2]; + stream->read_cb(stream, stream->buffer, count, stream->data, eof); + } + stream->pending_reqs--; + if (stream->closed && !stream->pending_reqs) { + stream_close_handle(stream); } } + +static void invoke_read_cb(Stream *stream, size_t count, bool eof) +{ + if (stream->closed) { + return; + } + + // Don't let the stream be closed before the event is processed. + stream->pending_reqs++; + + CREATE_EVENT(stream->events, + read_event, + 3, + stream, + (void *)(uintptr_t *)count, + (void *)(uintptr_t)eof); +} diff --git a/src/nvim/event/signal.c b/src/nvim/event/signal.c index 63133b4f57..11ce15a882 100644 --- a/src/nvim/event/signal.c +++ b/src/nvim/event/signal.c @@ -15,6 +15,7 @@ void signal_watcher_init(Loop *loop, SignalWatcher *watcher, void *data) watcher->uv.data = watcher; watcher->data = data; watcher->cb = NULL; + watcher->events = loop->fast_events; } void signal_watcher_start(SignalWatcher *watcher, signal_cb cb, int signum) @@ -37,10 +38,16 @@ void signal_watcher_close(SignalWatcher *watcher, signal_close_cb cb) uv_close((uv_handle_t *)&watcher->uv, close_cb); } +static void signal_event(void **argv) +{ + SignalWatcher *watcher = argv[0]; + watcher->cb(watcher, watcher->uv.signum, watcher->data); +} + static void signal_watcher_cb(uv_signal_t *handle, int signum) { SignalWatcher *watcher = handle->data; - watcher->cb(watcher, signum, watcher->data); + CREATE_EVENT(watcher->events, signal_event, 1, watcher); } static void close_cb(uv_handle_t *handle) diff --git a/src/nvim/event/signal.h b/src/nvim/event/signal.h index c269fa9d95..e32608acc0 100644 --- a/src/nvim/event/signal.h +++ b/src/nvim/event/signal.h @@ -14,6 +14,7 @@ struct signal_watcher { void *data; signal_cb cb; signal_close_cb close_cb; + Queue *events; }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c index bdc632abf0..347e464d25 100644 --- a/src/nvim/event/socket.c +++ b/src/nvim/event/socket.c @@ -77,6 +77,7 @@ void socket_watcher_init(Loop *loop, SocketWatcher *watcher, watcher->stream->data = watcher; watcher->cb = NULL; watcher->close_cb = NULL; + watcher->events = NULL; } int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb) @@ -113,6 +114,7 @@ int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb) } int socket_watcher_accept(SocketWatcher *watcher, Stream *stream, void *data) + FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(2) { uv_stream_t *client; @@ -142,10 +144,18 @@ void socket_watcher_close(SocketWatcher *watcher, socket_close_cb cb) uv_close((uv_handle_t *)watcher->stream, close_cb); } +static void connection_event(void **argv) +{ + SocketWatcher *watcher = argv[0]; + int status = (int)(uintptr_t)(argv[1]); + watcher->cb(watcher, status, watcher->data); +} + static void connection_cb(uv_stream_t *handle, int status) { SocketWatcher *watcher = handle->data; - watcher->cb(watcher, status, watcher->data); + CREATE_EVENT(watcher->events, connection_event, 2, watcher, + (void *)(uintptr_t)status); } static void close_cb(uv_handle_t *handle) diff --git a/src/nvim/event/socket.h b/src/nvim/event/socket.h index 17fd39f33b..ad59fdbe3a 100644 --- a/src/nvim/event/socket.h +++ b/src/nvim/event/socket.h @@ -30,6 +30,7 @@ struct socket_watcher { void *data; socket_cb cb; socket_close_cb close_cb; + Queue *events; }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c index 959b532146..6caad6fdcc 100644 --- a/src/nvim/event/stream.c +++ b/src/nvim/event/stream.c @@ -32,6 +32,7 @@ int stream_set_blocking(int fd, bool blocking) void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream, void *data) + FUNC_ATTR_NONNULL_ARG(2) { stream->uvstream = uvstream; @@ -55,6 +56,7 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream, if (stream->uvstream) { stream->uvstream->data = stream; + loop = stream->uvstream->loop->data; } stream->data = data; @@ -69,16 +71,13 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream, stream->internal_close_cb = NULL; stream->closed = false; stream->buffer = NULL; + stream->events = NULL; } void stream_close(Stream *stream, stream_close_cb on_stream_close) + FUNC_ATTR_NONNULL_ARG(1) { assert(!stream->closed); - - if (stream->buffer) { - rbuffer_free(stream->buffer); - } - stream->closed = true; stream->close_cb = on_stream_close; @@ -88,6 +87,7 @@ void stream_close(Stream *stream, stream_close_cb on_stream_close) } void stream_close_handle(Stream *stream) + FUNC_ATTR_NONNULL_ALL { if (stream->uvstream) { uv_close((uv_handle_t *)stream->uvstream, close_cb); @@ -99,6 +99,9 @@ void stream_close_handle(Stream *stream) static void close_cb(uv_handle_t *handle) { Stream *stream = handle->data; + if (stream->buffer) { + rbuffer_free(stream->buffer); + } if (stream->close_cb) { stream->close_cb(stream, stream->data); } diff --git a/src/nvim/event/stream.h b/src/nvim/event/stream.h index 37410b2036..c6baac0db7 100644 --- a/src/nvim/event/stream.h +++ b/src/nvim/event/stream.h @@ -14,10 +14,14 @@ typedef struct stream Stream; /// /// @param stream The Stream instance /// @param rbuffer The associated RBuffer instance +/// @param count Number of bytes to read. This must be respected if keeping +/// the order of events is a requirement. This is because events +/// may be queued and only processed later when more data is copied +/// into to the buffer, so one read may starve another. /// @param data User-defined data /// @param eof If the stream reached EOF. -typedef void (*stream_read_cb)(Stream *stream, RBuffer *buf, void *data, - bool eof); +typedef void (*stream_read_cb)(Stream *stream, RBuffer *buf, size_t count, + void *data, bool eof); /// Type of function called when the Stream has information about a write /// request. @@ -47,6 +51,7 @@ struct stream { size_t pending_reqs; void *data, *internal_data; bool closed; + Queue *events; }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/event/time.c b/src/nvim/event/time.c index ce33cdfc10..7bf333bcea 100644 --- a/src/nvim/event/time.c +++ b/src/nvim/event/time.c @@ -16,6 +16,7 @@ void time_watcher_init(Loop *loop, TimeWatcher *watcher, void *data) uv_timer_init(&loop->uv, &watcher->uv); watcher->uv.data = watcher; watcher->data = data; + watcher->events = loop->fast_events; } void time_watcher_start(TimeWatcher *watcher, time_cb cb, uint64_t timeout, @@ -39,11 +40,17 @@ void time_watcher_close(TimeWatcher *watcher, time_cb cb) uv_close((uv_handle_t *)&watcher->uv, close_cb); } +static void time_event(void **argv) +{ + TimeWatcher *watcher = argv[0]; + watcher->cb(watcher, watcher->data); +} + static void time_watcher_cb(uv_timer_t *handle) FUNC_ATTR_NONNULL_ALL { TimeWatcher *watcher = handle->data; - watcher->cb(watcher, watcher->data); + CREATE_EVENT(watcher->events, time_event, 1, watcher); } static void close_cb(uv_handle_t *handle) diff --git a/src/nvim/event/time.h b/src/nvim/event/time.h index ee50e53d11..7882b2b627 100644 --- a/src/nvim/event/time.h +++ b/src/nvim/event/time.h @@ -12,6 +12,7 @@ struct time_watcher { uv_timer_t uv; void *data; time_cb cb, close_cb; + Queue *events; }; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/event/uv_process.h b/src/nvim/event/uv_process.h index a17f1446b3..5ee73044b5 100644 --- a/src/nvim/event/uv_process.h +++ b/src/nvim/event/uv_process.h @@ -12,10 +12,10 @@ typedef struct uv_process { uv_stdio_container_t uvstdio[3]; } UvProcess; -static inline UvProcess uv_process_init(void *data) +static inline UvProcess uv_process_init(Loop *loop, void *data) { UvProcess rv; - rv.process = process_init(kProcessTypeUv, data); + rv.process = process_init(loop, kProcessTypeUv, data); return rv; } diff --git a/src/nvim/event/wstream.c b/src/nvim/event/wstream.c index 5fcb724fe3..8028e35e6b 100644 --- a/src/nvim/event/wstream.c +++ b/src/nvim/event/wstream.c @@ -118,6 +118,7 @@ WBuffer *wstream_new_buffer(char *data, size_t size, size_t refcount, wbuffer_data_finalizer cb) + FUNC_ATTR_NONNULL_ARG(1) { WBuffer *rv = xmalloc(sizeof(WBuffer)); rv->size = size; @@ -151,6 +152,7 @@ static void write_cb(uv_write_t *req, int status) } void wstream_release_wbuffer(WBuffer *buffer) + FUNC_ATTR_NONNULL_ALL { if (!--buffer->refcount) { if (buffer->cb) { diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 2c92113404..802a378b07 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -2395,7 +2395,7 @@ static int check_readonly(int *forceit, buf_T *buf) * the file exists and permissions are read-only. */ if (!*forceit && (buf->b_p_ro || (os_file_exists(buf->b_ffname) - && os_file_is_readonly((char *)buf->b_ffname)))) { + && !os_file_is_writable((char *)buf->b_ffname)))) { if ((p_confirm || cmdmod.confirm) && buf->b_fname != NULL) { char_u buff[DIALOG_MSG_SIZE]; @@ -6125,7 +6125,7 @@ char_u * sign_typenr2name(int typenr) /* * Undefine/free all signs. */ -void free_signs() +void free_signs(void) { while (first_sign != NULL) sign_undefine(first_sign, NULL); diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index d610aa6d19..efa18aa681 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -2624,7 +2624,7 @@ char_u *get_scriptname(scid_T id) } # if defined(EXITFREE) -void free_scriptnames() +void free_scriptnames(void) { # define FREE_SCRIPTNAME(item) xfree((item)->sn_name) GA_DEEP_CLEAR(&script_items, scriptitem_T, FREE_SCRIPTNAME); diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 785db1dbd1..03116d454f 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -62,6 +62,7 @@ #include "nvim/tag.h" #include "nvim/window.h" #include "nvim/ui.h" +#include "nvim/os/input.h" #include "nvim/os/os.h" #include "nvim/event/loop.h" @@ -298,14 +299,14 @@ getcmdline ( /* Get a character. Ignore K_IGNORE, it should not do anything, such * as stop completion. */ - loop_enable_deferred_events(&loop); + input_enable_events(); do { c = safe_vgetc(); } while (c == K_IGNORE); - loop_disable_deferred_events(&loop); + input_disable_events(); if (c == K_EVENT) { - loop_process_event(&loop); + queue_process_events(loop.events); continue; } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index dee9680230..70cdac7947 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -415,14 +415,10 @@ readfile ( msg_scroll = TRUE; /* don't overwrite previous file message */ /* - * If the name ends in a path separator, we can't open it. Check here, - * because reading the file may actually work, but then creating the swap - * file may destroy it! Reported on MS-DOS and Win 95. * If the name is too long we might crash further on, quit here. */ if (fname != NULL && *fname != NUL) { - p = fname + STRLEN(fname); - if (after_pathsep((char *)fname, (char *)p) || STRLEN(fname) >= MAXPATHL) { + if (STRLEN(fname) >= MAXPATHL) { filemess(curbuf, fname, (char_u *)_("Illegal file name"), 0); msg_end(); msg_scroll = msg_save; @@ -2627,7 +2623,7 @@ buf_write ( * Check if the file is really writable (when renaming the file to * make a backup we won't discover it later). */ - file_readonly = os_file_is_readonly((char *)fname); + file_readonly = !os_file_is_writable((char *)fname); if (!forceit && file_readonly) { if (vim_strchr(p_cpo, CPO_FWRITE) != NULL) { diff --git a/src/nvim/func_attr.h b/src/nvim/func_attr.h index 519f61c763..c31d21ec6d 100644 --- a/src/nvim/func_attr.h +++ b/src/nvim/func_attr.h @@ -179,7 +179,7 @@ #endif #ifdef DEFINE_FUNC_ATTRIBUTES - #define FUNC_ATTR_DEFERRED + #define FUNC_ATTR_ASYNC #define FUNC_ATTR_MALLOC REAL_FATTR_MALLOC #define FUNC_ATTR_ALLOC_SIZE(x) REAL_FATTR_ALLOC_SIZE(x) #define FUNC_ATTR_ALLOC_SIZE_PROD(x,y) REAL_FATTR_ALLOC_SIZE_PROD(x,y) diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index bbeef376b0..6a6e4f2214 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -1758,7 +1758,7 @@ static int vgetorpeek(int advance) if (c1 == K_SPECIAL) nolmaplen = 2; else { - LANGMAP_ADJUST(c1, (State & INSERT) == 0); + LANGMAP_ADJUST(c1, (State & (CMDLINE | INSERT)) == 0); nolmaplen = 0; } /* First try buffer-local mappings. */ diff --git a/src/nvim/globals.h b/src/nvim/globals.h index e4dcad9afb..68cb923e42 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -898,14 +898,6 @@ EXTERN FILE *scriptout INIT(= NULL); /* stream to write script to */ /* volatile because it is used in signal handler catch_sigint(). */ EXTERN volatile int got_int INIT(= FALSE); /* set to TRUE when interrupt signal occurred */ -EXTERN int disable_breakcheck INIT(= 0); // > 0 if breakchecks should be - // ignored. FIXME(tarruda): Hacky - // way to run functions that would - // result in *_breakcheck calls - // while events that would normally - // be deferred are being processed - // immediately. Ref: - // neovim/neovim#2371 EXTERN int bangredo INIT(= FALSE); /* set to TRUE with ! command */ EXTERN int searchcmdlen; /* length of previous search cmd */ EXTERN int reg_do_extmatch INIT(= 0); /* Used when compiling regexp: diff --git a/src/nvim/lib/queue.h b/src/nvim/lib/queue.h new file mode 100644 index 0000000000..fe02b454ea --- /dev/null +++ b/src/nvim/lib/queue.h @@ -0,0 +1,92 @@ +/* Copyright (c) 2013, Ben Noordhuis <info@bnoordhuis.nl> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef QUEUE_H_ +#define QUEUE_H_ + +typedef void *QUEUE[2]; + +/* Private macros. */ +#define QUEUE_NEXT(q) (*(QUEUE **) &((*(q))[0])) +#define QUEUE_PREV(q) (*(QUEUE **) &((*(q))[1])) +#define QUEUE_PREV_NEXT(q) (QUEUE_NEXT(QUEUE_PREV(q))) +#define QUEUE_NEXT_PREV(q) (QUEUE_PREV(QUEUE_NEXT(q))) + +/* Public macros. */ +#define QUEUE_DATA(ptr, type, field) \ + ((type *) ((char *) (ptr) - ((char *) &((type *) 0)->field))) + +#define QUEUE_FOREACH(q, h) \ + for ((q) = QUEUE_NEXT(h); (q) != (h); (q) = QUEUE_NEXT(q)) + +#define QUEUE_EMPTY(q) \ + ((const QUEUE *) (q) == (const QUEUE *) QUEUE_NEXT(q)) + +#define QUEUE_HEAD(q) \ + (QUEUE_NEXT(q)) + +#define QUEUE_INIT(q) \ + do { \ + QUEUE_NEXT(q) = (q); \ + QUEUE_PREV(q) = (q); \ + } \ + while (0) + +#define QUEUE_ADD(h, n) \ + do { \ + QUEUE_PREV_NEXT(h) = QUEUE_NEXT(n); \ + QUEUE_NEXT_PREV(n) = QUEUE_PREV(h); \ + QUEUE_PREV(h) = QUEUE_PREV(n); \ + QUEUE_PREV_NEXT(h) = (h); \ + } \ + while (0) + +#define QUEUE_SPLIT(h, q, n) \ + do { \ + QUEUE_PREV(n) = QUEUE_PREV(h); \ + QUEUE_PREV_NEXT(n) = (n); \ + QUEUE_NEXT(n) = (q); \ + QUEUE_PREV(h) = QUEUE_PREV(q); \ + QUEUE_PREV_NEXT(h) = (h); \ + QUEUE_PREV(q) = (n); \ + } \ + while (0) + +#define QUEUE_INSERT_HEAD(h, q) \ + do { \ + QUEUE_NEXT(q) = QUEUE_NEXT(h); \ + QUEUE_PREV(q) = (h); \ + QUEUE_NEXT_PREV(q) = (q); \ + QUEUE_NEXT(h) = (q); \ + } \ + while (0) + +#define QUEUE_INSERT_TAIL(h, q) \ + do { \ + QUEUE_NEXT(q) = (h); \ + QUEUE_PREV(q) = QUEUE_PREV(h); \ + QUEUE_PREV_NEXT(q) = (q); \ + QUEUE_PREV(h) = (q); \ + } \ + while (0) + +#define QUEUE_REMOVE(q) \ + do { \ + QUEUE_PREV_NEXT(q) = QUEUE_NEXT(q); \ + QUEUE_NEXT_PREV(q) = QUEUE_PREV(q); \ + } \ + while (0) + +#endif /* QUEUE_H_ */ diff --git a/src/nvim/main.c b/src/nvim/main.c index bfaeada6de..e11db16c61 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -157,11 +157,11 @@ void event_init(void) void event_teardown(void) { - if (!loop.deferred_events) { + if (!loop.events) { return; } - loop_process_all_events(&loop); + queue_process_events(loop.events); input_stop(); channel_teardown(); process_teardown(&loop); diff --git a/src/nvim/map.c b/src/nvim/map.c index 5d83020619..ed7bda4cce 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -116,5 +116,5 @@ MAP_IMPL(cstr_t, uint64_t, DEFAULT_INITIALIZER) MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER) -#define MSGPACK_HANDLER_INITIALIZER {.fn = NULL, .defer = false} +#define MSGPACK_HANDLER_INITIALIZER {.fn = NULL, .async = false} MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER) diff --git a/src/nvim/memline.c b/src/nvim/memline.c index 53f9a544b4..d90e91be5d 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -3934,8 +3934,9 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp) size += lnum - 1; /* Don't count the last line break if 'bin' and 'noeol'. */ - if (buf->b_p_bin && !buf->b_p_eol) + if (buf->b_p_bin && !buf->b_p_eol && buf->b_ml.ml_line_count == lnum) { size -= ffdos + 1; + } } return size; diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index 861614f147..0e3b8200c9 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -68,6 +68,7 @@ typedef struct { uint64_t next_request_id; kvec_t(ChannelCallFrame *) call_stack; kvec_t(WBuffer *) delayed_notifications; + Queue *events; } Channel; typedef struct { @@ -123,14 +124,14 @@ void channel_teardown(void) uint64_t channel_from_process(char **argv) { Channel *channel = register_channel(kChannelTypeProc); - channel->data.process.uvproc = uv_process_init(channel); + channel->data.process.uvproc = uv_process_init(&loop, channel); Process *proc = &channel->data.process.uvproc.process; proc->argv = argv; proc->in = &channel->data.process.in; proc->out = &channel->data.process.out; proc->err = &channel->data.process.err; proc->cb = process_exit; - if (!process_spawn(&loop, proc)) { + if (!process_spawn(proc)) { loop_poll_events(&loop, 0); decref(channel); return 0; @@ -153,6 +154,9 @@ void channel_from_connection(SocketWatcher *watcher) { Channel *channel = register_channel(kChannelTypeSocket); socket_watcher_accept(watcher, &channel->data.stream, channel); + 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, parse_msgpack); @@ -221,7 +225,7 @@ Object channel_send_call(uint64_t id, ChannelCallFrame frame = {request_id, false, false, NIL}; kv_push(ChannelCallFrame *, channel->call_stack, &frame); channel->pending_requests++; - LOOP_POLL_EVENTS_UNTIL(&loop, -1, frame.returned); + LOOP_PROCESS_EVENTS_UNTIL(&loop, channel->events, -1, frame.returned); (void)kv_pop(channel->call_stack); channel->pending_requests--; @@ -324,7 +328,8 @@ static void channel_from_stdio(void) wstream_init_fd(&loop, &channel->data.std.out, 1, 0, NULL); } -static void forward_stderr(Stream *stream, RBuffer *rbuf, void *data, bool eof) +static void forward_stderr(Stream *stream, RBuffer *rbuf, size_t count, + void *data, bool eof) { while (rbuffer_size(rbuf)) { char buf[256]; @@ -339,7 +344,8 @@ static void process_exit(Process *proc, int status, void *data) decref(data); } -static void parse_msgpack(Stream *stream, RBuffer *rbuf, void *data, bool eof) +static void parse_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data, + bool eof) { Channel *channel = data; incref(channel); @@ -447,31 +453,31 @@ static void handle_request(Channel *channel, msgpack_object *request) method->via.bin.size); } else { handler.fn = msgpack_rpc_handle_missing_method; - handler.defer = false; + handler.async = true; } Array args = ARRAY_DICT_INIT; if (!msgpack_rpc_to_array(msgpack_rpc_args(request), &args)) { handler.fn = msgpack_rpc_handle_invalid_arguments; - handler.defer = false; + handler.async = true; } - bool defer = (!kv_size(channel->call_stack) && handler.defer); RequestEvent *event_data = xmalloc(sizeof(RequestEvent)); event_data->channel = channel; event_data->handler = handler; event_data->args = args; event_data->request_id = request_id; incref(channel); - loop_push_event(&loop, (Event) { - .handler = on_request_event, - .data = event_data - }, defer); + if (handler.async) { + on_request_event((void **)&event_data); + } else { + queue_put(channel->events, on_request_event, 1, event_data); + } } -static void on_request_event(Event event) +static void on_request_event(void **argv) { - RequestEvent *e = event.data; + RequestEvent *e = argv[0]; Channel *channel = e->channel; MsgpackRpcRequestHandler handler = e->handler; Array args = e->args; @@ -636,7 +642,7 @@ static void close_channel(Channel *channel) switch (channel->type) { case kChannelTypeSocket: - stream_close(&channel->data.stream, close_cb); + stream_close(&channel->data.stream, NULL); break; case kChannelTypeProc: if (!channel->data.process.uvproc.process.closed) { @@ -646,9 +652,8 @@ static void close_channel(Channel *channel) case kChannelTypeStdio: stream_close(&channel->data.std.in, NULL); stream_close(&channel->data.std.out, NULL); - loop_push_event(&loop, - (Event) { .handler = on_stdio_close, .data = channel }, false); - break; + queue_put(loop.fast_events, exit_event, 1, channel); + return; default: abort(); } @@ -656,9 +661,9 @@ static void close_channel(Channel *channel) decref(channel); } -static void on_stdio_close(Event e) +static void exit_event(void **argv) { - decref(e.data); + decref(argv[0]); if (!exiting) { mch_exit(0); @@ -680,17 +685,19 @@ static void free_channel(Channel *channel) pmap_free(cstr_t)(channel->subscribed_events); kv_destroy(channel->call_stack); kv_destroy(channel->delayed_notifications); + queue_free(channel->events); xfree(channel); } static void close_cb(Stream *stream, void *data) { - xfree(data); + decref(data); } static Channel *register_channel(ChannelType type) { Channel *rv = xmalloc(sizeof(Channel)); + rv->events = queue_new_child(loop.events); rv->type = type; rv->refcount = 1; rv->closed = false; diff --git a/src/nvim/msgpack_rpc/defs.h b/src/nvim/msgpack_rpc/defs.h index 0492a65290..d97cf28ca1 100644 --- a/src/nvim/msgpack_rpc/defs.h +++ b/src/nvim/msgpack_rpc/defs.h @@ -11,9 +11,8 @@ typedef struct { uint64_t request_id, Array args, Error *error); - bool defer; // Should the call be deferred to the main loop? This should - // be true if the function mutates editor data structures such - // as buffers, windows, tabs, or if it executes vimscript code. + bool async; // function is always safe to run immediately instead of being + // put in a request queue for handling when nvim waits for input. } MsgpackRpcRequestHandler; /// Initializes the msgpack-rpc method table diff --git a/src/nvim/msgpack_rpc/remote_ui.c b/src/nvim/msgpack_rpc/remote_ui.c index 07d78dd9d7..3334b0e6af 100644 --- a/src/nvim/msgpack_rpc/remote_ui.c +++ b/src/nvim/msgpack_rpc/remote_ui.c @@ -28,7 +28,7 @@ void remote_ui_init(void) connected_uis = pmap_new(uint64_t)(); // Add handler for "attach_ui" String method = cstr_as_string("ui_attach"); - MsgpackRpcRequestHandler handler = {.fn = remote_ui_attach, .defer = false}; + MsgpackRpcRequestHandler handler = {.fn = remote_ui_attach, .async = true}; msgpack_rpc_add_method_handler(method, handler); method = cstr_as_string("ui_detach"); handler.fn = remote_ui_detach; @@ -86,8 +86,7 @@ static Object remote_ui_attach(uint64_t channel_id, uint64_t request_id, ui->busy_stop = remote_ui_busy_stop; ui->mouse_on = remote_ui_mouse_on; ui->mouse_off = remote_ui_mouse_off; - ui->insert_mode = remote_ui_insert_mode; - ui->normal_mode = remote_ui_normal_mode; + ui->mode_change = remote_ui_mode_change; ui->set_scroll_region = remote_ui_set_scroll_region; ui->scroll = remote_ui_scroll; ui->highlight_set = remote_ui_highlight_set; @@ -214,16 +213,18 @@ static void remote_ui_mouse_off(UI *ui) push_call(ui, "mouse_off", args); } -static void remote_ui_insert_mode(UI *ui) +static void remote_ui_mode_change(UI *ui, int mode) { Array args = ARRAY_DICT_INIT; - push_call(ui, "insert_mode", args); -} - -static void remote_ui_normal_mode(UI *ui) -{ - Array args = ARRAY_DICT_INIT; - push_call(ui, "normal_mode", args); + if (mode == INSERT) { + ADD(args, STRING_OBJ(cstr_to_string("insert"))); + } else if (mode == REPLACE) { + ADD(args, STRING_OBJ(cstr_to_string("replace"))); + } else { + assert(mode == NORMAL); + ADD(args, STRING_OBJ(cstr_to_string("normal"))); + } + push_call(ui, "mode_change", args); } static void remote_ui_set_scroll_region(UI *ui, int top, int bot, int left, diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 95e1c3d113..5b35af9209 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -63,6 +63,7 @@ #include "nvim/window.h" #include "nvim/event/loop.h" #include "nvim/os/time.h" +#include "nvim/os/input.h" /* * The Visual area is remembered for reselection. @@ -487,12 +488,12 @@ normal_cmd ( /* * Get the command character from the user. */ - loop_enable_deferred_events(&loop); + input_enable_events(); c = safe_vgetc(); - loop_disable_deferred_events(&loop); + input_disable_events(); if (c == K_EVENT) { - loop_process_event(&loop); + queue_process_events(loop.events); return; } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 8a7ced138b..766b5720d9 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -5278,11 +5278,11 @@ static yankreg_T *adjust_clipboard_name(int *name, bool quiet) } yankreg_T *target; if (cb_flags & CB_UNNAMEDPLUS) { - *name = '+'; - target = &y_regs[STAR_REGISTER]; + *name = cb_flags & CB_UNNAMED ? '"': '+'; + target = &y_regs[PLUS_REGISTER]; } else { *name = '*'; - target = &y_regs[PLUS_REGISTER]; + target = &y_regs[STAR_REGISTER]; } return target; // unnamed register } diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 5eeb275701..785c79127f 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -270,13 +270,16 @@ bool os_file_exists(const char_u *name) return os_stat((char *)name, &statbuf); } -/// Check if a file is readonly. +/// Check if a file is readable. /// -/// @return `true` if `name` is readonly. -bool os_file_is_readonly(const char *name) - FUNC_ATTR_NONNULL_ALL +/// @return true if `name` is readable, otherwise false. +bool os_file_is_readable(const char *name) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - return access(name, W_OK) != 0; + uv_fs_t req; + int r = uv_fs_access(&fs_loop, &req, name, R_OK, NULL); + uv_fs_req_cleanup(&req); + return (r == 0); } /// Check if a file is writable. @@ -285,13 +288,13 @@ bool os_file_is_readonly(const char *name) /// @return `1` if `name` is writable, /// @return `2` for a directory which we have rights to write into. int os_file_is_writable(const char *name) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - if (access(name, W_OK) == 0) { - if (os_isdir((char_u *)name)) { - return 2; - } - return 1; + uv_fs_t req; + int r = uv_fs_access(&fs_loop, &req, name, W_OK, NULL); + uv_fs_req_cleanup(&req); + if (r == 0) { + return os_isdir((char_u *)name) ? 2 : 1; } return 0; } diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index b0e0f57e60..09f162f79d 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -33,6 +33,7 @@ static Stream read_stream = {.closed = true}; static RBuffer *input_buffer = NULL; static bool input_eof = false; static int global_fd = 0; +static int events_enabled = 0; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/input.c.generated.h" @@ -110,8 +111,8 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt) return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen); } - // If there are deferred events, return the keys directly - if (loop_has_deferred_events(&loop)) { + // If there are events, return the keys directly + if (pending_events()) { return push_event_key(buf, maxlen); } @@ -131,11 +132,21 @@ bool os_char_avail(void) // Check for CTRL-C typed by reading all available characters. void os_breakcheck(void) { - if (!disable_breakcheck && !got_int) { + if (!got_int) { loop_poll_events(&loop, 0); } } +void input_enable_events(void) +{ + events_enabled++; +} + +void input_disable_events(void) +{ + events_enabled--; +} + /// Test whether a file descriptor refers to a terminal. /// /// @param fd File descriptor. @@ -281,7 +292,7 @@ static bool input_poll(int ms) prof_inchar_enter(); } - LOOP_POLL_EVENTS_UNTIL(&loop, ms, input_ready() || input_eof); + LOOP_PROCESS_EVENTS_UNTIL(&loop, NULL, ms, input_ready() || input_eof); if (do_profiling == PROF_YES && ms) { prof_inchar_exit(); @@ -305,7 +316,8 @@ static InbufPollResult inbuf_poll(int ms) return input_eof ? kInputEof : kInputNone; } -static void read_cb(Stream *stream, RBuffer *buf, void *data, bool at_eof) +static void read_cb(Stream *stream, RBuffer *buf, size_t c, void *data, + bool at_eof) { if (at_eof) { input_eof = true; @@ -358,7 +370,7 @@ static bool input_ready(void) { return typebuf_was_filled || // API call filled typeahead rbuffer_size(input_buffer) || // Input buffer filled - loop_has_deferred_events(&loop); // Events must be processed + pending_events(); // Events must be processed } // Exit because of an input read error. @@ -369,3 +381,8 @@ static void read_error_exit(void) STRCPY(IObuff, _("Vim: Error reading input, exiting...\n")); preserve_exit(); } + +static bool pending_events(void) +{ + return events_enabled && !queue_empty(loop.events); +} diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index e0d67d4951..2d97c4bf4f 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -205,13 +205,15 @@ static int do_os_system(char **argv, xstrlcpy(prog, argv[0], MAXPATHL); Stream in, out, err; - UvProcess uvproc = uv_process_init(&buf); + UvProcess uvproc = uv_process_init(&loop, &buf); Process *proc = &uvproc.process; + Queue *events = queue_new_child(loop.events); + proc->events = events; proc->argv = argv; proc->in = input != NULL ? &in : NULL; proc->out = &out; proc->err = &err; - if (!process_spawn(&loop, proc)) { + if (!process_spawn(proc)) { loop_poll_events(&loop, 0); // Failed, probably due to `sh` not being executable if (!silent) { @@ -219,14 +221,22 @@ static int do_os_system(char **argv, msg_outtrans((char_u *)prog); msg_putchar('\n'); } + queue_free(events); return -1; } + // We want to deal with stream events as fast a possible while queueing + // process events, so reset everything to NULL. It prevents closing the + // streams while there's still data in the OS buffer(due to the process + // exiting before all data is read). if (input != NULL) { + proc->in->events = NULL; wstream_init(proc->in, 0); } + proc->out->events = NULL; rstream_init(proc->out, 0); rstream_start(proc->out, data_cb); + proc->err->events = NULL; rstream_init(proc->err, 0); rstream_start(proc->err, data_cb); @@ -247,7 +257,7 @@ static int do_os_system(char **argv, // the UI ui_busy_start(); ui_flush(); - int status = process_wait(proc, -1); + int status = process_wait(proc, -1, NULL); ui_busy_stop(); // prepare the out parameters if requested @@ -267,6 +277,9 @@ static int do_os_system(char **argv, } } + assert(queue_empty(events)); + queue_free(events); + return status; } @@ -285,7 +298,8 @@ static void dynamic_buffer_ensure(DynamicBuffer *buf, size_t desired) buf->data = xrealloc(buf->data, buf->cap); } -static void system_data_cb(Stream *stream, RBuffer *buf, void *data, bool eof) +static void system_data_cb(Stream *stream, RBuffer *buf, size_t count, + void *data, bool eof) { DynamicBuffer *dbuf = data; @@ -295,16 +309,25 @@ static void system_data_cb(Stream *stream, RBuffer *buf, void *data, bool eof) dbuf->len += nread; } -static void out_data_cb(Stream *stream, RBuffer *buf, void *data, bool eof) +static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data, + bool eof) { - 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; - } + size_t cnt; + char *ptr = rbuffer_read_ptr(buf, &cnt); + + if (!cnt) { + return; + } + + size_t written = write_output(ptr, cnt, false, eof); + // No output written, force emptying the Rbuffer if it is full. + if (!written && rbuffer_size(buf) == rbuffer_capacity(buf)) { + screen_del_lines(0, 0, 1, (int)Rows, NULL); + screen_puts_len((char_u *)ptr, (int)cnt, (int)Rows - 1, 0, 0); + written = cnt; + } + if (written) { + rbuffer_consumed(buf, written); } } @@ -421,6 +444,7 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer, if (!output) { return 0; } + char replacement_NUL = to_buffer ? NL : 1; char *start = output; size_t off = 0; @@ -428,9 +452,10 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer, while (off < remaining) { if (output[off] == NL) { // Insert the line - output[off] = NUL; if (to_buffer) { - ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false); + output[off] = NUL; + ml_append(curwin->w_cursor.lnum++, (char_u *)output, (int)off + 1, + false); } else { screen_del_lines(0, 0, 1, (int)Rows, NULL); screen_puts_len((char_u *)output, (int)off, lastrow, 0, 0); @@ -444,7 +469,7 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer, if (output[off] == NUL) { // Translate NUL to NL - output[off] = NL; + output[off] = replacement_NUL; } off++; } diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index 6de3435c4c..7158721433 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -115,16 +115,6 @@ static void deadly_signal(int signum) static void on_signal(SignalWatcher *handle, int signum, void *data) { assert(signum >= 0); - loop_push_event(&loop, (Event) { - .handler = on_signal_event, - .data = (void *)(uintptr_t)signum - }, false); -} - -static void on_signal_event(Event event) -{ - int signum = (int)(uintptr_t)event.data; - switch (signum) { #ifdef SIGPWR case SIGPWR: @@ -148,4 +138,3 @@ static void on_signal_event(Event event) break; } } - diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index 6b5d4359db..ee17938afc 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -43,7 +43,7 @@ void os_delay(uint64_t milliseconds, bool ignoreinput) if (milliseconds > INT_MAX) { milliseconds = INT_MAX; } - LOOP_POLL_EVENTS_UNTIL(&loop, (int)milliseconds, got_int); + LOOP_PROCESS_EVENTS_UNTIL(&loop, NULL, (int)milliseconds, got_int); } else { os_microdelay(milliseconds * 1000); } diff --git a/src/nvim/os/users.c b/src/nvim/os/users.c index a57ba41af1..637a86c74f 100644 --- a/src/nvim/os/users.c +++ b/src/nvim/os/users.c @@ -41,7 +41,12 @@ int os_get_usernames(garray_T *users) // Return OK if a name found. int os_get_user_name(char *s, size_t len) { +#ifdef UNIX return os_get_uname(getuid(), s, len); +#else + // TODO(equalsraf): Windows GetUserName() + return os_get_uname(0, s, len); +#endif } // Insert user name for "uid" in s[len]. diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h index 19d796bd08..a51898c9e7 100644 --- a/src/nvim/os/win_defs.h +++ b/src/nvim/os/win_defs.h @@ -21,4 +21,12 @@ // - SYS_VIMRC_FILE // - SPECIAL_WILDCHAR +// _access(): https://msdn.microsoft.com/en-us/library/1w06ktdy.aspx +#ifndef R_OK +# define R_OK 4 +#endif +#ifndef W_OK +# define W_OK 2 +#endif + #endif // NVIM_OS_WIN_DEFS_H diff --git a/src/nvim/path.c b/src/nvim/path.c index 152154e5f4..72980fcd0e 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -481,6 +481,21 @@ static size_t path_expand(garray_T *gap, const char_u *path, int flags) return do_path_expand(gap, path, 0, flags, false); } +static const char *scandir_next_with_dots(Directory *dir) +{ + static int count = 0; + if (dir == NULL) { // initialize + count = 0; + return NULL; + } + + count += 1; + if (count == 1 || count == 2) { + return (count == 1) ? "." : ".."; + } + return os_scandir_next(dir); +} + /// Implementation of path_expand(). /// /// Chars before `path + wildoff` do not get expanded. @@ -597,11 +612,12 @@ static size_t do_path_expand(garray_T *gap, const char_u *path, *s = NUL; Directory dir; - // open the directory for scanning - if (os_scandir(&dir, *buf == NUL ? "." : (char *)buf)) { + if (os_scandir(&dir, *buf == NUL ? "." : (char *)buf) + || os_isdir(*buf == NUL ? (char_u *)"." : (char_u *)buf)) { // Find all matching entries. char_u *name; - while((name = (char_u *) os_scandir_next(&dir))) { + scandir_next_with_dots(NULL /* initialize */); + while((name = (char_u *) scandir_next_with_dots(&dir)) && name != NULL) { if ((name[0] != '.' || starts_with_dot) && ((regmatch.regprog != NULL && vim_regexec(®match, name, 0)) || ((flags & EW_NOTWILD) diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 4724a07895..e2c4b590d0 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -961,10 +961,10 @@ static void reg_equi_class(int c) REGMBC(0x107) REGMBC(0x109) REGMBC(0x10b) REGMBC(0x10d) return; - case 'd': CASEMBC(0x10f) CASEMBC(0x111) CASEMBC(0x1d0b) - CASEMBC(0x1e11) + case 'd': CASEMBC(0x10f) CASEMBC(0x111) CASEMBC(0x1e0b) + CASEMBC(0x1e0f) CASEMBC(0x1e11) regmbc('d'); REGMBC(0x10f) REGMBC(0x111) - REGMBC(0x1e0b) REGMBC(0x01e0f) REGMBC(0x1e11) + REGMBC(0x1e0b) REGMBC(0x1e0f) REGMBC(0x1e11) return; case 'e': case '\350': case '\351': case '\352': case '\353': CASEMBC(0x113) CASEMBC(0x115) CASEMBC(0x117) CASEMBC(0x119) diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 42cebb0198..484dfe1e1f 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -270,8 +270,10 @@ typedef struct { /* When REG_MULTI is TRUE list.multi is used, otherwise list.line. */ union { struct multipos { - lpos_T start; - lpos_T end; + linenr_T start_lnum; + linenr_T end_lnum; + colnr_T start_col; + colnr_T end_col; } multi[NSUBEXP]; struct linepos { char_u *start; @@ -716,8 +718,8 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl) */ static void nfa_emit_equi_class(int c) { -#define EMIT2(c) EMIT(c); EMIT(NFA_CONCAT); -# define EMITMBC(c) EMIT(c); EMIT(NFA_CONCAT); +#define EMIT2(c) EMIT(c); EMIT(NFA_CONCAT); +#define EMITMBC(c) EMIT(c); EMIT(NFA_CONCAT); if (enc_utf8 || STRCMP(p_enc, "latin1") == 0 || STRCMP(p_enc, "iso-8859-15") == 0) { @@ -906,10 +908,10 @@ static void nfa_emit_equi_class(int c) EMITMBC(0x10b) EMITMBC(0x10d) return; - case 'd': CASEMBC(0x10f) CASEMBC(0x111) CASEMBC(0x1d0b) - CASEMBC(0x1e11) + case 'd': CASEMBC(0x10f) CASEMBC(0x111) CASEMBC(0x1e0b) + CASEMBC(0x1e0f) CASEMBC(0x1e11) EMIT2('d'); EMITMBC(0x10f) EMITMBC(0x111) EMITMBC(0x1e0b) - EMITMBC(0x01e0f) EMITMBC(0x1e11) + EMITMBC(0x1e0f) EMITMBC(0x1e11) return; case 'e': case 0350: case 0351: case 0352: case 0353: @@ -1391,7 +1393,7 @@ static int nfa_regatom(void) * matched an unlimited number of times. NFA_NOPEN is * added only once at a position, while NFA_SPLIT is * added multiple times. This is more efficient than - * not allowsing NFA_SPLIT multiple times, it is used + * not allowing NFA_SPLIT multiple times, it is used * a lot. */ EMIT(NFA_NOPEN); break; @@ -3431,10 +3433,10 @@ static void log_subexpr(regsub_T *sub) if (REG_MULTI) fprintf(log_fd, "*** group %d, start: c=%d, l=%d, end: c=%d, l=%d\n", j, - sub->list.multi[j].start.col, - (int)sub->list.multi[j].start.lnum, - sub->list.multi[j].end.col, - (int)sub->list.multi[j].end.lnum); + sub->list.multi[j].start_col, + (int)sub->list.multi[j].start_lnum, + sub->list.multi[j].end_col, + (int)sub->list.multi[j].end_lnum); else { char *s = (char *)sub->list.line[j].start; char *e = (char *)sub->list.line[j].end; @@ -3535,8 +3537,10 @@ static void copy_ze_off(regsub_T *to, regsub_T *from) { if (nfa_has_zend) { if (REG_MULTI) { - if (from->list.multi[0].end.lnum >= 0) - to->list.multi[0].end = from->list.multi[0].end; + if (from->list.multi[0].end_lnum >= 0){ + to->list.multi[0].end_lnum = from->list.multi[0].end_lnum; + to->list.multi[0].end_col = from->list.multi[0].end_col; + } } else { if (from->list.line[0].end != NULL) to->list.line[0].end = from->list.line[0].end; @@ -3559,27 +3563,27 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2) if (REG_MULTI) { for (i = 0; i < todo; ++i) { if (i < sub1->in_use) - s1 = sub1->list.multi[i].start.lnum; + s1 = sub1->list.multi[i].start_lnum; else s1 = -1; if (i < sub2->in_use) - s2 = sub2->list.multi[i].start.lnum; + s2 = sub2->list.multi[i].start_lnum; else s2 = -1; if (s1 != s2) return FALSE; - if (s1 != -1 && sub1->list.multi[i].start.col - != sub2->list.multi[i].start.col) + if (s1 != -1 && sub1->list.multi[i].start_col + != sub2->list.multi[i].start_col) return FALSE; if (nfa_has_backref) { if (i < sub1->in_use) { - s1 = sub1->list.multi[i].end.lnum; + s1 = sub1->list.multi[i].end_lnum; } else { s1 = -1; } if (i < sub2->in_use) { - s2 = sub2->list.multi[i].end.lnum; + s2 = sub2->list.multi[i].end_lnum; } else { s2 = -1; } @@ -3587,7 +3591,7 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2) return FALSE; } if (s1 != -1 - && sub1->list.multi[i].end.col != sub2->list.multi[i].end.col) { + && sub1->list.multi[i].end_col != sub2->list.multi[i].end_col) { return FALSE; } } @@ -3637,7 +3641,7 @@ static void report_state(char *action, if (sub->in_use <= 0) col = -1; else if (REG_MULTI) - col = sub->list.multi[0].start.col; + col = sub->list.multi[0].start_col; else col = (int)(sub->list.line[0].start - regline); nfa_set_code(state->c); @@ -4023,22 +4027,24 @@ skip_add: * and restore it when it was in use. Otherwise fill any gap. */ if (REG_MULTI) { if (subidx < sub->in_use) { - save_lpos = sub->list.multi[subidx].start; + save_lpos.lnum = sub->list.multi[subidx].start_lnum; + save_lpos.col = sub->list.multi[subidx].start_col; save_in_use = -1; } else { save_in_use = sub->in_use; for (i = sub->in_use; i < subidx; ++i) { - sub->list.multi[i].start.lnum = -1; - sub->list.multi[i].end.lnum = -1; + sub->list.multi[i].start_lnum = -1; + sub->list.multi[i].end_lnum = -1; } sub->in_use = subidx + 1; } if (off == -1) { - sub->list.multi[subidx].start.lnum = reglnum + 1; - sub->list.multi[subidx].start.col = 0; + sub->list.multi[subidx].start_lnum = reglnum + 1; + sub->list.multi[subidx].start_col = 0; } else { - sub->list.multi[subidx].start.lnum = reglnum; - sub->list.multi[subidx].start.col = + + sub->list.multi[subidx].start_lnum = reglnum; + sub->list.multi[subidx].start_col = (colnr_T)(reginput - regline + off); } } else { @@ -4064,8 +4070,10 @@ skip_add: sub = &subs->norm; if (save_in_use == -1) { - if (REG_MULTI) - sub->list.multi[subidx].start = save_lpos; + if (REG_MULTI){ + sub->list.multi[subidx].start_lnum = save_lpos.lnum; + sub->list.multi[subidx].start_col = save_lpos.col; + } else sub->list.line[subidx].start = save_ptr; } else @@ -4074,7 +4082,7 @@ skip_add: case NFA_MCLOSE: if (nfa_has_zend && (REG_MULTI - ? subs->norm.list.multi[0].end.lnum >= 0 + ? subs->norm.list.multi[0].end_lnum >= 0 : subs->norm.list.line[0].end != NULL)) { /* Do not overwrite the position set by \ze. */ subs = addstate(l, state->out, subs, pim, off); @@ -4117,13 +4125,14 @@ skip_add: if (sub->in_use <= subidx) sub->in_use = subidx + 1; if (REG_MULTI) { - save_lpos = sub->list.multi[subidx].end; + save_lpos.lnum = sub->list.multi[subidx].end_lnum; + save_lpos.col = sub->list.multi[subidx].end_col; if (off == -1) { - sub->list.multi[subidx].end.lnum = reglnum + 1; - sub->list.multi[subidx].end.col = 0; + sub->list.multi[subidx].end_lnum = reglnum + 1; + sub->list.multi[subidx].end_col = 0; } else { - sub->list.multi[subidx].end.lnum = reglnum; - sub->list.multi[subidx].end.col = + sub->list.multi[subidx].end_lnum = reglnum; + sub->list.multi[subidx].end_col = (colnr_T)(reginput - regline + off); } /* avoid compiler warnings */ @@ -4143,8 +4152,10 @@ skip_add: else sub = &subs->norm; - if (REG_MULTI) - sub->list.multi[subidx].end = save_lpos; + if (REG_MULTI){ + sub->list.multi[subidx].end_lnum = save_lpos.lnum; + sub->list.multi[subidx].end_col = save_lpos.col; + } else sub->list.line[subidx].end = save_ptr; sub->in_use = save_in_use; @@ -4319,24 +4330,24 @@ retempty: } if (REG_MULTI) { - if (sub->list.multi[subidx].start.lnum < 0 - || sub->list.multi[subidx].end.lnum < 0) + if (sub->list.multi[subidx].start_lnum < 0 + || sub->list.multi[subidx].end_lnum < 0) goto retempty; - if (sub->list.multi[subidx].start.lnum == reglnum - && sub->list.multi[subidx].end.lnum == reglnum) { - len = sub->list.multi[subidx].end.col - - sub->list.multi[subidx].start.col; - if (cstrncmp(regline + sub->list.multi[subidx].start.col, + if (sub->list.multi[subidx].start_lnum == reglnum + && sub->list.multi[subidx].end_lnum == reglnum) { + len = sub->list.multi[subidx].end_col + - sub->list.multi[subidx].start_col; + if (cstrncmp(regline + sub->list.multi[subidx].start_col, reginput, &len) == 0) { *bytelen = len; return TRUE; } } else { if (match_with_backref( - sub->list.multi[subidx].start.lnum, - sub->list.multi[subidx].start.col, - sub->list.multi[subidx].end.lnum, - sub->list.multi[subidx].end.col, + sub->list.multi[subidx].start_lnum, + sub->list.multi[subidx].start_col, + sub->list.multi[subidx].end_lnum, + sub->list.multi[subidx].end_col, bytelen) == RA_MATCH) return TRUE; } @@ -4873,8 +4884,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm * it's the first MOPEN. */ if (toplevel) { if (REG_MULTI) { - m->norm.list.multi[0].start.lnum = reglnum; - m->norm.list.multi[0].start.col = (colnr_T)(reginput - regline); + m->norm.list.multi[0].start_lnum = reglnum; + m->norm.list.multi[0].start_col = (colnr_T)(reginput - regline); } else m->norm.list.line[0].start = reginput; m->norm.in_use = 1; @@ -4962,7 +4973,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm if (t->subs.norm.in_use <= 0) col = -1; else if (REG_MULTI) - col = t->subs.norm.list.multi[0].start.col; + col = t->subs.norm.list.multi[0].start_col; else col = (int)(t->subs.norm.list.line[0].start - regline); nfa_set_code(t->state->c); @@ -5214,7 +5225,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm * continue with what follows. */ if (REG_MULTI) /* TODO: multi-line match */ - bytelen = m->norm.list.multi[0].end.col + bytelen = m->norm.list.multi[0].end_col - (int)(reginput - regline); else bytelen = (int)(m->norm.list.line[0].end - reginput); @@ -6018,7 +6029,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm if (add) { if (REG_MULTI) - m->norm.list.multi[0].start.col = + m->norm.list.multi[0].start_col = (colnr_T)(reginput - regline) + clen; else m->norm.list.line[0].start = reginput + clen; @@ -6123,8 +6134,11 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col) cleanup_subexpr(); if (REG_MULTI) { for (i = 0; i < subs.norm.in_use; i++) { - reg_startpos[i] = subs.norm.list.multi[i].start; - reg_endpos[i] = subs.norm.list.multi[i].end; + reg_startpos[i].lnum = subs.norm.list.multi[i].start_lnum; + reg_startpos[i].col = subs.norm.list.multi[i].start_col; + + reg_endpos[i].lnum = subs.norm.list.multi[i].end_lnum; + reg_endpos[i].col = subs.norm.list.multi[i].end_col; } if (reg_startpos[0].lnum < 0) { @@ -6162,12 +6176,12 @@ static long nfa_regtry(nfa_regprog_T *prog, colnr_T col) struct multipos *mpos = &subs.synt.list.multi[i]; // Only accept single line matches that are valid. - if (mpos->start.lnum >= 0 - && mpos->start.lnum == mpos->end.lnum - && mpos->end.col >= mpos->start.col) { + if (mpos->start_lnum >= 0 + && mpos->start_lnum == mpos->end_lnum + && mpos->end_col >= mpos->start_col) { re_extmatch_out->matches[i] = - vim_strnsave(reg_getline(mpos->start.lnum) + mpos->start.col, - mpos->end.col - mpos->start.col); + vim_strnsave(reg_getline(mpos->start_lnum) + mpos->start_col, + mpos->end_col - mpos->start_col); } } else { struct linepos *lpos = &subs.synt.list.line[i]; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 47fef692db..b9bc4c6d78 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -69,6 +69,7 @@ #include "nvim/fileio.h" #include "nvim/event/loop.h" #include "nvim/event/time.h" +#include "nvim/os/input.h" #include "nvim/api/private/helpers.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -324,7 +325,7 @@ void terminal_resize(Terminal *term, uint16_t width, uint16_t height) invalidate_terminal(term, -1, -1); } -void terminal_enter(bool process_deferred) +void terminal_enter(void) { Terminal *term = curbuf->terminal; assert(term && "should only be called when curbuf has a terminal"); @@ -353,15 +354,9 @@ void terminal_enter(bool process_deferred) bool got_bs = false; // True if the last input was <C-\> while (term->buf == curbuf) { - if (process_deferred) { - loop_enable_deferred_events(&loop); - } - + input_enable_events(); c = safe_vgetc(); - - if (process_deferred) { - loop_disable_deferred_events(&loop); - } + input_disable_events(); switch (c) { case K_LEFTMOUSE: @@ -381,7 +376,7 @@ void terminal_enter(bool process_deferred) break; case K_EVENT: - loop_process_event(&loop); + queue_process_events(loop.events); break; case Ctrl_N: @@ -427,7 +422,13 @@ void terminal_destroy(Terminal *term) term->buf->terminal = NULL; } term->buf = NULL; - pmap_del(ptr_t)(invalidated_terminals, term); + if (pmap_has(ptr_t)(invalidated_terminals, term)) { + // flush any pending changes to the buffer + block_autocmds(); + refresh_terminal(term); + unblock_autocmds(); + pmap_del(ptr_t)(invalidated_terminals, term); + } for (size_t i = 0 ; i < term->sb_current; i++) { xfree(term->sb_buffer[i]); } @@ -883,48 +884,47 @@ static void invalidate_terminal(Terminal *term, int start_row, int end_row) } } +static void refresh_terminal(Terminal *term) +{ + // TODO(SplinterOfChaos): Find the condition that makes term->buf invalid. + bool valid = true; + if (!term->buf || !(valid = buf_valid(term->buf))) { + // destroyed by `close_buffer`. Dont do anything else + if (!valid) { + term->buf = NULL; + } + return; + } + bool pending_resize = term->pending_resize; + WITH_BUFFER(term->buf, { + refresh_size(term); + refresh_scrollback(term); + refresh_screen(term); + redraw_buf_later(term->buf, NOT_VALID); + }); + adjust_topline(term, pending_resize); +} // libuv timer callback. This will enqueue on_refresh to be processed as an // event. static void refresh_timer_cb(TimeWatcher *watcher, void *data) { - loop_push_event(&loop, (Event) {.handler = on_refresh}, false); - refresh_pending = false; -} - -// Refresh all invalidated terminals -static void on_refresh(Event event) -{ if (exiting) { // bad things can happen if we redraw when exiting, and there's no need to // update the buffer. - return; + goto end; } Terminal *term; void *stub; (void)(stub); // don't process autocommands while updating terminal buffers block_autocmds(); map_foreach(invalidated_terminals, term, stub, { - // TODO(SplinterOfChaos): Find the condition that makes term->buf invalid. - bool valid = true; - if (!term->buf || !(valid = buf_valid(term->buf))) { - // destroyed by `close_buffer`. Dont do anything else - if (!valid) { - term->buf = NULL; - } - continue; - } - bool pending_resize = term->pending_resize; - WITH_BUFFER(term->buf, { - refresh_size(term); - refresh_scrollback(term); - refresh_screen(term); - redraw_buf_later(term->buf, NOT_VALID); - }); - adjust_topline(term, pending_resize); + refresh_terminal(term); }); pmap_clear(ptr_t)(invalidated_terminals); unblock_autocmds(); redraw(true); +end: + refresh_pending = false; } static void refresh_size(Terminal *term) diff --git a/src/nvim/tui/term_input.inl b/src/nvim/tui/term_input.inl index 0a84a3688b..c396557160 100644 --- a/src/nvim/tui/term_input.inl +++ b/src/nvim/tui/term_input.inl @@ -206,9 +206,10 @@ static bool handle_forced_escape(TermInput *input) return false; } -static void restart_reading(Event event); +static void restart_reading(void **argv); -static void read_cb(Stream *stream, RBuffer *buf, void *data, bool eof) +static void read_cb(Stream *stream, RBuffer *buf, size_t c, void *data, + bool eof) { TermInput *input = data; @@ -226,8 +227,7 @@ static void read_cb(Stream *stream, RBuffer *buf, void *data, bool eof) // ls *.md | xargs nvim input->in_fd = 2; stream_close(&input->read_stream, NULL); - loop_push_event(&loop, - (Event) { .data = input, .handler = restart_reading }, false); + queue_put(loop.fast_events, restart_reading, 1, input); } else { input_done(); } @@ -272,9 +272,9 @@ static void read_cb(Stream *stream, RBuffer *buf, void *data, bool eof) rbuffer_reset(input->read_stream.buffer); } -static void restart_reading(Event event) +static void restart_reading(void **argv) { - TermInput *input = event.data; + TermInput *input = argv[0]; rstream_init_fd(&loop, &input->read_stream, input->in_fd, 0xfff, input); rstream_start(&input->read_stream, read_cb); } diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index a1f56d2695..57a2b896f7 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -57,14 +57,17 @@ typedef struct { bool busy; HlAttrs attrs, print_attrs; Cell **screen; + int showing_mode; struct { int enable_mouse, disable_mouse; int enable_bracketed_paste, disable_bracketed_paste; - int enter_insert_mode, exit_insert_mode; + int enter_insert_mode, enter_replace_mode, exit_insert_mode; int set_rgb_foreground, set_rgb_background; } unibi_ext; } TUIData; +static bool volatile got_winch = false; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "tui/tui.c.generated.h" #endif @@ -98,11 +101,13 @@ UI *tui_start(void) data->can_use_terminal_scroll = true; data->bufpos = 0; data->bufsize = sizeof(data->buf) - CNORM_COMMAND_MAX_SIZE; + data->showing_mode = 0; data->unibi_ext.enable_mouse = -1; data->unibi_ext.disable_mouse = -1; data->unibi_ext.enable_bracketed_paste = -1; data->unibi_ext.disable_bracketed_paste = -1; data->unibi_ext.enter_insert_mode = -1; + data->unibi_ext.enter_replace_mode = -1; data->unibi_ext.exit_insert_mode = -1; // write output to stderr if stdout is not a tty @@ -148,8 +153,7 @@ UI *tui_start(void) ui->busy_stop = tui_busy_stop; ui->mouse_on = tui_mouse_on; ui->mouse_off = tui_mouse_off; - ui->insert_mode = tui_insert_mode; - ui->normal_mode = tui_normal_mode; + ui->mode_change = tui_mode_change; ui->set_scroll_region = tui_set_scroll_region; ui->scroll = tui_scroll; ui->highlight_set = tui_highlight_set; @@ -178,7 +182,7 @@ static void tui_stop(UI *ui) // Destroy input stuff term_input_destroy(data->input); // Destroy output stuff - tui_normal_mode(ui); + tui_mode_change(ui, NORMAL); tui_mouse_off(ui); unibi_out(ui, unibi_exit_attribute_mode); // cursor should be set to normal before exiting alternate screen @@ -201,23 +205,14 @@ static void tui_stop(UI *ui) xfree(ui); } -static void try_resize(Event ev) +static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data) { - UI *ui = ev.data; + got_winch = true; + UI *ui = data; update_size(ui); ui_refresh(); } -static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data) -{ - // Queue the event because resizing can result in recursive event_poll calls - // FIXME(blueyed): TUI does not resize properly when not deferred. Why? #2322 - loop_push_event(&loop, (Event) { - .data = data, - .handler = try_resize - }, true); -} - static bool attrs_differ(HlAttrs a1, HlAttrs a2) { return a1.foreground != a2.foreground || a1.background != a2.background @@ -352,10 +347,13 @@ static void tui_resize(UI *ui, int width, int height) data->scroll_region.right = width - 1; data->row = data->col = 0; - // try to resize the terminal window - char r[16]; // enough for 9999x9999 - snprintf(r, sizeof(r), "\x1b[8;%d;%dt", height, width); - out(ui, r, strlen(r)); + if (!got_winch) { // Try to resize the terminal window. + char r[16]; // enough for 9999x9999 + snprintf(r, sizeof(r), "\x1b[8;%d;%dt", height, width); + out(ui, r, strlen(r)); + } else { // Already handled the SIGWINCH signal; avoid double-resize. + got_winch = false; + } } static void tui_clear(UI *ui) @@ -404,16 +402,25 @@ static void tui_mouse_off(UI *ui) data->mouse_enabled = false; } -static void tui_insert_mode(UI *ui) +static void tui_mode_change(UI *ui, int mode) { TUIData *data = ui->data; - unibi_out(ui, data->unibi_ext.enter_insert_mode); -} -static void tui_normal_mode(UI *ui) -{ - TUIData *data = ui->data; - unibi_out(ui, data->unibi_ext.exit_insert_mode); + if (mode == INSERT) { + if (data->showing_mode != INSERT) { + unibi_out(ui, data->unibi_ext.enter_insert_mode); + } + } else if (mode == REPLACE) { + if (data->showing_mode != REPLACE) { + unibi_out(ui, data->unibi_ext.enter_replace_mode); + } + } else { + assert(mode == NORMAL); + if (data->showing_mode != NORMAL) { + unibi_out(ui, data->unibi_ext.exit_insert_mode); + } + } + data->showing_mode = mode; } static void tui_set_scroll_region(UI *ui, int top, int bot, int left, @@ -798,18 +805,25 @@ static void fix_terminfo(TUIData *data) #define TMUX_WRAP(seq) (inside_tmux ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq) // Support changing cursor shape on some popular terminals. const char *term_prog = os_getenv("TERM_PROGRAM"); + const char *vte_version = os_getenv("VTE_VERSION"); - if ((term_prog && !strcmp(term_prog, "iTerm.app")) - || os_getenv("ITERM_SESSION_ID") != NULL) { - // iterm + if ((term_prog && !strcmp(term_prog, "Konsole")) + || os_getenv("KONSOLE_DBUS_SESSION") != NULL) { + // Konsole uses a proprietary escape code to set the cursor shape + // and does not suppport DECSCUSR. data->unibi_ext.enter_insert_mode = (int)unibi_add_ext_str(ut, NULL, TMUX_WRAP("\x1b]50;CursorShape=1;BlinkingCursorEnabled=1\x07")); + data->unibi_ext.enter_replace_mode = (int)unibi_add_ext_str(ut, NULL, + TMUX_WRAP("\x1b]50;CursorShape=2;BlinkingCursorEnabled=1\x07")); data->unibi_ext.exit_insert_mode = (int)unibi_add_ext_str(ut, NULL, TMUX_WRAP("\x1b]50;CursorShape=0;BlinkingCursorEnabled=0\x07")); - } else { - // xterm-like sequences for blinking bar and solid block + } else if (!vte_version || atoi(vte_version) >= 3900) { + // Assume that the terminal supports DECSCUSR unless it is an + // old VTE based terminal data->unibi_ext.enter_insert_mode = (int)unibi_add_ext_str(ut, NULL, TMUX_WRAP("\x1b[5 q")); + data->unibi_ext.enter_replace_mode = (int)unibi_add_ext_str(ut, NULL, + TMUX_WRAP("\x1b[3 q")); data->unibi_ext.exit_insert_mode = (int)unibi_add_ext_str(ut, NULL, TMUX_WRAP("\x1b[2 q")); } diff --git a/src/nvim/ui.c b/src/nvim/ui.c index dc2bc0898c..ad875367c9 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -121,7 +121,7 @@ void ui_update_encoding(void) // May update the shape of the cursor. void ui_cursor_shape(void) { - ui_change_mode(); + ui_mode_change(); } void ui_refresh(void) @@ -214,9 +214,9 @@ void ui_detach(UI *ui) shift_index++; } - ui_count--; - // schedule a refresh - loop_push_event(&loop, (Event) { .handler = refresh }, false); + if (--ui_count) { + ui_refresh(); + } } void ui_clear(void) @@ -469,32 +469,19 @@ static void flush_cursor_update(void) // Notify that the current mode has changed. Can be used to change cursor // shape, for example. -static void ui_change_mode(void) +static void ui_mode_change(void) { - static int showing_insert_mode = MAYBE; - + int mode; if (!full_screen) { return; } - - if (State & INSERT) { - if (showing_insert_mode != TRUE) { - UI_CALL(insert_mode); - } - showing_insert_mode = TRUE; - } else { - if (showing_insert_mode != FALSE) { - UI_CALL(normal_mode); - } - showing_insert_mode = FALSE; - } + /* Get a simple UI mode out of State. */ + if ((State & REPLACE) == REPLACE) + mode = REPLACE; + else if (State & INSERT) + mode = INSERT; + else + mode = NORMAL; + UI_CALL(mode_change, mode); conceal_check_cursur_line(); } - -static void refresh(Event event) -{ - if (ui_count) { - ui_refresh(); - } -} - diff --git a/src/nvim/ui.h b/src/nvim/ui.h index 76ceec7775..9cfd99c096 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -24,8 +24,7 @@ struct ui_t { void (*busy_stop)(UI *ui); void (*mouse_on)(UI *ui); void (*mouse_off)(UI *ui); - void (*insert_mode)(UI *ui); - void (*normal_mode)(UI *ui); + void (*mode_change)(UI *ui, int mode); void (*set_scroll_region)(UI *ui, int top, int bot, int left, int right); void (*scroll)(UI *ui, int count); void (*highlight_set)(UI *ui, HlAttrs attrs); diff --git a/src/nvim/version.c b/src/nvim/version.c index a496af13b4..7ab7467661 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -80,15 +80,15 @@ static int included_patches[] = { 784, //783, //782, - //781, + 781, //780, //779, //778, //777, - //776, + 776, 775, 774, - //773, + 773, //772, //771, //770, @@ -261,7 +261,7 @@ static int included_patches[] = { //603, //602, 601, - //600, + 600, 599, //598, 597, @@ -1098,6 +1098,15 @@ void intro_message(int colon) N_("by Bram Moolenaar et al."), N_("Vim is open source and freely distributable"), "", + N_("First time using a vi-like editor?"), + N_("Type :Tutor<Enter> to get started!"), + "", + N_("Already know your way around Vim?"), + N_("See :help nvim-intro for an introduction to Neovim."), + "", + N_("Still have questions?"), + N_("Reach out to the Neovim community at neovim.io/community."), + "", N_("Help poor children in Uganda!"), N_("type :help iccf<Enter> for information "), "", diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 17f9cbc310..9fbc6dd300 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -6,7 +6,7 @@ */ #ifndef NVIM_VIM_H -# define NVIM_VIM_H +#define NVIM_VIM_H #define MIN(X, Y) (X < Y ? X : Y) #define MAX(X, Y) (X > Y ? X : Y) |