diff options
Diffstat (limited to 'src')
55 files changed, 923 insertions, 483 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 606baff619..2d803792c8 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -359,6 +359,10 @@ endforeach() # Our dependencies come first. +if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + list(APPEND NVIM_LINK_LIBRARIES pthread c++abi) +endif() + if (LibIntl_FOUND) list(APPEND NVIM_LINK_LIBRARIES ${LibIntl_LIBRARY}) endif() @@ -437,6 +441,7 @@ if(WIN32) COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/tidy.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/win32yank.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/winpty-agent.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/xxd.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/D3Dcompiler_47.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/libEGL.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 4870c3fb8a..4cd2657561 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -56,7 +56,8 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, kErrorTypeException, "UI already attached for channel"); + api_set_error(err, kErrorTypeException, + "UI already attached to channel: %" PRId64, channel_id); return; } @@ -130,7 +131,8 @@ void nvim_ui_detach(uint64_t channel_id, Error *err) FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, kErrorTypeException, "UI is not attached for channel"); + api_set_error(err, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } remote_ui_disconnect(channel_id); @@ -142,7 +144,8 @@ void nvim_ui_try_resize(uint64_t channel_id, Integer width, FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, kErrorTypeException, "UI is not attached for channel"); + api_set_error(err, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } @@ -163,7 +166,8 @@ void nvim_ui_set_option(uint64_t channel_id, String name, FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(error, kErrorTypeException, "UI is not attached for channel"); + api_set_error(error, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); @@ -209,7 +213,8 @@ static void ui_set_option(UI *ui, String name, Object value, Error *error) return; } - api_set_error(error, kErrorTypeValidation, "No such ui option"); + api_set_error(error, kErrorTypeValidation, "No such UI option: %s", + name.data); #undef UI_EXT_OPTION } diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index c599b0ce72..96d494460b 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -10,7 +10,7 @@ #include "nvim/func_attr.h" #include "nvim/ui.h" -void resize(Integer rows, Integer columns) +void resize(Integer width, Integer height) FUNC_API_SINCE(3); void clear(void) FUNC_API_SINCE(3); diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 86827224b7..af3d379870 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -239,15 +239,17 @@ String nvim_command_output(String command, Error *err) } if (capture_local.ga_len > 1) { - // redir always(?) prepends a newline; remove it. - char *s = capture_local.ga_data; - assert(s[0] == '\n'); - memmove(s, s + 1, (size_t)capture_local.ga_len); - s[capture_local.ga_len - 1] = '\0'; - return (String) { // Caller will free the memory. - .data = s, - .size = (size_t)(capture_local.ga_len - 1), + String s = (String){ + .data = capture_local.ga_data, + .size = (size_t)capture_local.ga_len, }; + // redir usually (except :echon) prepends a newline. + if (s.data[0] == '\n') { + memmove(s.data, s.data + 1, s.size); + s.data[s.size - 1] = '\0'; + s.size = s.size - 1; + } + return s; // Caller will free the memory. } theend: @@ -1471,6 +1473,17 @@ Float nvim__id_float(Float flt) return flt; } +/// Gets internal stats. +/// +/// @return Map of various internal stats. +Dictionary nvim__stats(void) +{ + Dictionary rv = ARRAY_DICT_INIT; + PUT(rv, "fsync", INTEGER_OBJ(g_stats.fsync)); + PUT(rv, "redraw", INTEGER_OBJ(g_stats.redraw)); + return rv; +} + /// Gets a list of dictionaries representing attached UIs. /// /// @return Array of UI dictionaries diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index 7dfaf54ff0..1153314e76 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -92,6 +92,8 @@ return { 'VimLeave', -- before exiting Vim 'VimLeavePre', -- before exiting Vim and writing ShaDa file 'VimResized', -- after Vim window was resized + 'VimResume', -- after Nvim is resumed + 'VimSuspend', -- before Nvim is suspended 'WinNew', -- when entering a new window 'WinEnter', -- after entering a window 'WinLeave', -- before leaving a window diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 2e32af2e9a..4e6ca8d278 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -237,15 +237,16 @@ void channel_create_event(Channel *chan, const char *ext_source) #endif } -void channel_incref(Channel *channel) +void channel_incref(Channel *chan) { - channel->refcount++; + chan->refcount++; } -void channel_decref(Channel *channel) +void channel_decref(Channel *chan) { - if (!(--channel->refcount)) { - multiqueue_put(main_loop.fast_events, free_channel_event, 1, channel); + if (!(--chan->refcount)) { + // delay free, so that libuv is done with the handles + multiqueue_put(main_loop.events, free_channel_event, 1, chan); } } @@ -267,18 +268,18 @@ void callback_reader_start(CallbackReader *reader) static void free_channel_event(void **argv) { - Channel *channel = argv[0]; - if (channel->is_rpc) { - rpc_free(channel); + Channel *chan = argv[0]; + if (chan->is_rpc) { + rpc_free(chan); } - callback_reader_free(&channel->on_stdout); - callback_reader_free(&channel->on_stderr); - callback_free(&channel->on_exit); + callback_reader_free(&chan->on_stdout); + callback_reader_free(&chan->on_stderr); + callback_free(&chan->on_exit); - pmap_del(uint64_t)(channels, channel->id); - multiqueue_free(channel->events); - xfree(channel); + pmap_del(uint64_t)(channels, chan->id); + multiqueue_free(chan->events); + xfree(chan); } static void channel_destroy_early(Channel *chan) @@ -286,12 +287,15 @@ static void channel_destroy_early(Channel *chan) if ((chan->id != --next_chan_id)) { abort(); } + pmap_del(uint64_t)(channels, chan->id); + chan->id = 0; if ((--chan->refcount != 0)) { abort(); } - free_channel_event((void **)&chan); + // uv will keep a reference to handles until next loop tick, so delay free + multiqueue_put(main_loop.events, free_channel_event, 1, chan); } @@ -654,9 +658,9 @@ static void channel_process_exit_cb(Process *proc, int status, void *data) terminal_close(chan->term, msg); } - // if status is -1 the process did not really exit, - // we just closed the handle onto a detached process - if (status >= 0) { + // If process did not exit, we only closed the handle of a detached process. + bool exited = (status >= 0); + if (exited) { process_channel_event(chan, &chan->on_exit, "exit", NULL, 0, status); } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index f9c3e5f0d5..a1987cf2d5 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -847,7 +847,7 @@ static int insert_handle_key(InsertState *s) case ' ': - if (mod_mask != 4) { + if (mod_mask != MOD_MASK_CTRL) { goto normalchar; } // FALLTHROUGH @@ -1180,6 +1180,14 @@ static int insert_handle_key(InsertState *s) normalchar: // Insert a normal character. + + if (mod_mask == MOD_MASK_ALT || mod_mask == MOD_MASK_META) { + // Unmapped ALT/META chord behaves like ESC+c. #8213 + stuffcharReadbuff(ESC); + stuffcharReadbuff(s->c); + break; + } + if (!p_paste) { // Trigger InsertCharPre. char_u *str = do_insert_char_pre(s->c); @@ -1432,7 +1440,7 @@ static void ins_ctrl_v(void) * line and will not removed by the redraw */ edit_unputchar(); clear_showcmd(); - insert_special(c, FALSE, TRUE); + insert_special(c, true, true); revins_chars++; revins_legal++; } @@ -3615,6 +3623,9 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir) memset(cptext, 0, sizeof(cptext)); } if (word == NULL || (!aempty && *word == NUL)) { + for (size_t i = 0; i < CPT_COUNT; i++) { + xfree(cptext[i]); + } return FAIL; } return ins_compl_add((char_u *)word, -1, icase, NULL, @@ -5054,13 +5065,11 @@ static void insert_special(int c, int allow_modmask, int ctrlv) char_u *p; int len; - /* - * Special function key, translate into "<Key>". Up to the last '>' is - * inserted with ins_str(), so as not to replace characters in replace - * mode. - * Only use mod_mask for special keys, to avoid things like <S-Space>, - * unless 'allow_modmask' is TRUE. - */ + // Special function key, translate into "<Key>". Up to the last '>' is + // inserted with ins_str(), so as not to replace characters in replace + // mode. + // Only use mod_mask for special keys, to avoid things like <S-Space>, + // unless 'allow_modmask' is TRUE. if (mod_mask & MOD_MASK_CMD) { // Command-key never produces a normal key. allow_modmask = true; } diff --git a/src/nvim/eval.c b/src/nvim/eval.c index e38f7e7dda..cc29496968 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -14394,8 +14394,11 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; if (argvars[0].vval.v_string) { - server_stop((char *) argvars[0].vval.v_string); + bool rv = server_stop((char *)argvars[0].vval.v_string); + rettv->vval.v_number = (rv ? 1 : 0); } } @@ -15694,6 +15697,56 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) p_cpo = save_cpo; } +/// "stdpath()" helper for list results +static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv) + FUNC_ATTR_NONNULL_ALL +{ + const void *iter = NULL; + list_T *const list = tv_list_alloc(kListLenShouldKnow); + rettv->v_type = VAR_LIST; + rettv->vval.v_list = list; + tv_list_ref(list); + char *const dirs = stdpaths_get_xdg_var(xdg); + do { + size_t dir_len; + const char *dir; + iter = vim_env_iter(':', dirs, iter, &dir, &dir_len); + if (dir != NULL && dir_len > 0) { + char *dir_with_nvim = xmemdupz(dir, dir_len); + dir_with_nvim = concat_fnames_realloc(dir_with_nvim, "nvim", true); + tv_list_append_string(list, dir_with_nvim, strlen(dir_with_nvim)); + xfree(dir_with_nvim); + } + } while (iter != NULL); + xfree(dirs); +} + +/// "stdpath(type)" function +static void f_stdpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + + const char *const p = tv_get_string_chk(&argvars[0]); + if (p == NULL) { + return; // Type error; errmsg already given. + } + + if (strcmp(p, "config") == 0) { + rettv->vval.v_string = (char_u *)get_xdg_home(kXDGConfigHome); + } else if (strcmp(p, "data") == 0) { + rettv->vval.v_string = (char_u *)get_xdg_home(kXDGDataHome); + } else if (strcmp(p, "cache") == 0) { + rettv->vval.v_string = (char_u *)get_xdg_home(kXDGCacheHome); + } else if (strcmp(p, "config_dirs") == 0) { + get_xdg_var_list(kXDGConfigDirs, rettv); + } else if (strcmp(p, "data_dirs") == 0) { + get_xdg_var_list(kXDGDataDirs, rettv); + } else { + EMSG2(_("E6100: \"%s\" is not a valid stdpath"), p); + } +} + /* * "str2float()" function */ diff --git a/src/nvim/eval.h b/src/nvim/eval.h index b798eae187..149dae688e 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -10,6 +10,7 @@ #include "nvim/event/rstream.h" #include "nvim/event/wstream.h" #include "nvim/channel.h" +#include "nvim/os/stdpaths_defs.h" #define COPYID_INC 2 #define COPYID_MASK (~0x1) diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index daa3b637a3..801d2cc468 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -280,6 +280,7 @@ return { spellsuggest={args={1, 3}}, split={args={1, 3}}, sqrt={args=1, func="float_op_wrapper", data="&sqrt"}, + stdpath={args=1}, str2float={args=1}, str2nr={args={1, 2}}, strcharpart={args={2, 3}}, diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index 0c2bf397e5..ffe2db9b76 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -51,12 +51,19 @@ int libuv_process_spawn(LibuvProcess *uvproc) if (!proc->in.closed) { uvproc->uvstdio[0].flags = UV_CREATE_PIPE | UV_READABLE_PIPE; +#ifdef WIN32 + uvproc->uvstdio[0].flags |= UV_OVERLAPPED_PIPE; +#endif uvproc->uvstdio[0].data.stream = STRUCT_CAST(uv_stream_t, &proc->in.uv.pipe); } if (!proc->out.closed) { uvproc->uvstdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE; +#ifdef WIN32 + // pipe must be readable for IOCP to work. + uvproc->uvstdio[1].flags |= UV_READABLE_PIPE | UV_OVERLAPPED_PIPE; +#endif uvproc->uvstdio[1].data.stream = STRUCT_CAST(uv_stream_t, &proc->out.uv.pipe); } diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index d92464f17b..7998e0b8d0 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -21,7 +21,6 @@ void loop_init(Loop *loop, void *data) loop->recursive = 0; loop->uv.data = loop; loop->children = kl_init(WatcherPtr); - loop->children_stop_requests = 0; loop->events = multiqueue_new_parent(loop_on_put, loop); loop->fast_events = multiqueue_new_child(loop->events); loop->thread_events = multiqueue_new_parent(NULL, NULL); diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h index 1b288fc4cb..f5dd23ac8b 100644 --- a/src/nvim/event/loop.h +++ b/src/nvim/event/loop.h @@ -37,7 +37,6 @@ typedef struct loop { // generic timer, used by loop_poll_events() uv_timer_t poll_timer; - size_t children_stop_requests; uv_async_t async; uv_mutex_t mutex; int recursive; diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index fa31024a64..7a8a39dbcf 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -23,7 +23,7 @@ #endif // Time for a process to exit cleanly before we send KILL. -// For pty processes SIGTERM is sent first (in case SIGHUP was not enough). +// For PTY processes SIGTERM is sent first (in case SIGHUP was not enough). #define KILL_TIMEOUT_MS 2000 static bool process_is_tearing_down = false; @@ -111,6 +111,7 @@ int process_spawn(Process *proc, bool in, bool out, bool err) proc->internal_close_cb = decref; proc->refcount++; kl_push(WatcherPtr, proc->loop->children, proc); + DLOG("new: pid=%d argv=[%s]", proc->pid, *proc->argv); return 0; } @@ -188,8 +189,7 @@ int process_wait(Process *proc, int ms, MultiQueue *events) } if (proc->refcount == 1) { - // Job exited, collect status and manually invoke close_cb to free the job - // resources + // Job exited, free its resources. decref(proc); if (events) { // the decref call created an exit event, process it now @@ -205,11 +205,12 @@ int process_wait(Process *proc, int ms, MultiQueue *events) /// Ask a process to terminate and eventually kill if it doesn't respond void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL { - if (proc->stopped_time) { + bool exited = (proc->status >= 0); + if (exited || proc->stopped_time) { return; } - proc->stopped_time = os_hrtime(); + switch (proc->type) { case kProcessTypeUv: // Close the process's stdin. If the process doesn't close its own @@ -227,35 +228,32 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL abort(); } - Loop *loop = proc->loop; - if (!loop->children_stop_requests++) { - // When there's at least one stop request pending, start a timer that - // will periodically check if a signal should be send to the job. - ILOG("starting job kill timer"); - uv_timer_start(&loop->children_kill_timer, children_kill_cb, - KILL_TIMEOUT_MS, KILL_TIMEOUT_MS); - } + // (Re)start timer to verify that stopped process(es) died. + uv_timer_start(&proc->loop->children_kill_timer, children_kill_cb, + KILL_TIMEOUT_MS, 0); } -/// Iterates the process list sending SIGTERM to stopped processes and SIGKILL -/// to those that didn't die from SIGTERM after a while(exit_timeout is 0). +/// Sends SIGKILL (or SIGTERM..SIGKILL for PTY jobs) to processes that did +/// not terminate after process_stop(). static void children_kill_cb(uv_timer_t *handle) { Loop *loop = handle->loop->data; - uint64_t now = os_hrtime(); kl_iter(WatcherPtr, loop->children, current) { Process *proc = (*current)->data; - if (!proc->stopped_time) { + bool exited = (proc->status >= 0); + if (exited || !proc->stopped_time) { continue; } - uint64_t elapsed = (now - proc->stopped_time) / 1000000 + 1; - - if (elapsed >= KILL_TIMEOUT_MS) { - int sig = proc->type == kProcessTypePty && elapsed < KILL_TIMEOUT_MS * 2 - ? SIGTERM - : SIGKILL; - os_proc_tree_kill(proc->pid, sig); + uint64_t term_sent = UINT64_MAX == proc->stopped_time; + if (kProcessTypePty != proc->type || term_sent) { + os_proc_tree_kill(proc->pid, SIGKILL); + } else { + os_proc_tree_kill(proc->pid, SIGTERM); + proc->stopped_time = UINT64_MAX; // Flag: SIGTERM was sent. + // Restart timer. + uv_timer_start(&proc->loop->children_kill_timer, children_kill_cb, + KILL_TIMEOUT_MS, 0); } } } @@ -267,7 +265,7 @@ static void process_close_event(void **argv) if (proc->type == kProcessTypePty) { xfree(((PtyProcess *)proc)->term_name); } - if (proc->cb) { + if (proc->cb) { // "on_exit" for jobstart(). See channel_job_start(). proc->cb(proc, proc->status, proc->data); } } @@ -383,12 +381,8 @@ static void process_close_handles(void **argv) static void on_process_exit(Process *proc) { Loop *loop = proc->loop; - 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); - } + ILOG("exited: pid=%d status=%d stoptime=%" PRIu64, proc->pid, proc->status, + proc->stopped_time); // Process has terminated, but there could still be data to be read from the // OS. We are still in the libuv loop, so we cannot call code that polls for diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h index 033ce3604b..ba2c2a6a11 100644 --- a/src/nvim/event/process.h +++ b/src/nvim/event/process.h @@ -19,8 +19,7 @@ struct process { Loop *loop; void *data; int pid, status, refcount; - // set to the hrtime of when process_stop was called for the process. - uint64_t stopped_time; + uint64_t stopped_time; // process_stop() timestamp const char *cwd; char **argv; Stream in, out, err; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index 33e2805538..3dbeccaaa5 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -603,6 +603,11 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, cmd_getline, cmd_cookie); recursive--; + // Ignore trailing '|'-separated commands in preview-mode ('inccommand'). + if (State & CMDPREVIEW) { + next_cmdline = NULL; + } + if (cmd_cookie == (void *)&cmd_loop_cookie) /* Use "current_line" from "cmd_loop_cookie", it may have been * incremented when defining a function. */ @@ -6300,15 +6305,18 @@ static void ex_stop(exarg_T *eap) if (!eap->forceit) { autowrite_all(); } + apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL); ui_cursor_goto((int)Rows - 1, 0); ui_linefeed(); ui_flush(); ui_call_suspend(); // call machine specific function + ui_flush(); maketitle(); resettitle(); // force updating the title redraw_later_clear(); ui_refresh(); // may have resized window + apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL); } } @@ -6544,18 +6552,14 @@ void alist_slash_adjust(void) #endif -/* - * ":preserve". - */ +/// ":preserve". static void ex_preserve(exarg_T *eap) { curbuf->b_flags |= BF_PRESERVED; - ml_preserve(curbuf, TRUE); + ml_preserve(curbuf, true, true); } -/* - * ":recover". - */ +/// ":recover". static void ex_recover(exarg_T *eap) { /* Set recoverymode right away to avoid the ATTENTION prompt. */ @@ -6975,12 +6979,10 @@ do_exedit ( ex_no_reprint = TRUE; } -/* - * ":gui" and ":gvim" when there is no GUI. - */ +/// ":gui" and ":gvim" when there is no GUI. static void ex_nogui(exarg_T *eap) { - eap->errmsg = e_nogvim; + eap->errmsg = (char_u *)N_("E25: Nvim does not have a built-in GUI"); } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index c34b52cf27..efeee1ba2b 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -1,9 +1,7 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * fileio.c: read from and write to a file - */ +// fileio.c: read from and write to a file #include <assert.h> #include <errno.h> @@ -65,57 +63,62 @@ #define BUFSIZE 8192 /* size of normal write buffer */ #define SMBUFSIZE 256 /* size of emergency write buffer */ -/* - * The autocommands are stored in a list for each event. - * Autocommands for the same pattern, that are consecutive, are joined - * together, to avoid having to match the pattern too often. - * The result is an array of Autopat lists, which point to AutoCmd lists: - * - * first_autopat[0] --> Autopat.next --> Autopat.next --> NULL - * Autopat.cmds Autopat.cmds - * | | - * V V - * AutoCmd.next AutoCmd.next - * | | - * V V - * AutoCmd.next NULL - * | - * V - * NULL - * - * first_autopat[1] --> Autopat.next --> NULL - * Autopat.cmds - * | - * V - * AutoCmd.next - * | - * V - * NULL - * etc. - * - * The order of AutoCmds is important, this is the order in which they were - * defined and will have to be executed. - */ +// +// The autocommands are stored in a list for each event. +// Autocommands for the same pattern, that are consecutive, are joined +// together, to avoid having to match the pattern too often. +// The result is an array of Autopat lists, which point to AutoCmd lists: +// +// last_autopat[0] -----------------------------+ +// V +// first_autopat[0] --> Autopat.next --> Autopat.next --> NULL +// Autopat.cmds Autopat.cmds +// | | +// V V +// AutoCmd.next AutoCmd.next +// | | +// V V +// AutoCmd.next NULL +// | +// V +// NULL +// +// last_autopat[1] --------+ +// V +// first_autopat[1] --> Autopat.next --> NULL +// Autopat.cmds +// | +// V +// AutoCmd.next +// | +// V +// NULL +// etc. +// +// The order of AutoCmds is important, this is the order in which they were +// defined and will have to be executed. +// typedef struct AutoCmd { - char_u *cmd; /* The command to be executed (NULL - when command has been removed) */ - char nested; /* If autocommands nest here */ - char last; /* last command in list */ - scid_T scriptID; /* script ID where defined */ - struct AutoCmd *next; /* Next AutoCmd in list */ + char_u *cmd; // The command to be executed (NULL + // when command has been removed) + char nested; // If autocommands nest here + char last; // last command in list + scid_T scriptID; // script ID where defined + struct AutoCmd *next; // Next AutoCmd in list } AutoCmd; typedef struct AutoPat { - char_u *pat; /* pattern as typed (NULL when pattern - has been removed) */ - regprog_T *reg_prog; /* compiled regprog for pattern */ - AutoCmd *cmds; /* list of commands to do */ - struct AutoPat *next; /* next AutoPat in AutoPat list */ - int group; /* group ID */ - int patlen; /* strlen() of pat */ - int buflocal_nr; /* !=0 for buffer-local AutoPat */ - char allow_dirs; /* Pattern may match whole path */ - char last; /* last pattern for apply_autocmds() */ + struct AutoPat *next; // next AutoPat in AutoPat list; MUST + // be the first entry + char_u *pat; // pattern as typed (NULL when pattern + // has been removed) + regprog_T *reg_prog; // compiled regprog for pattern + AutoCmd *cmds; // list of commands to do + int group; // group ID + int patlen; // strlen() of pat + int buflocal_nr; // !=0 for buffer-local AutoPat + char allow_dirs; // Pattern may match whole path + char last; // last pattern for apply_autocmds() } AutoPat; /* @@ -226,6 +229,15 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr) msg_scrolled_ign = FALSE; } +static AutoPat *last_autopat[NUM_EVENTS] = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + /* * Read lines from file "fname" into the buffer after line "from". * @@ -1724,9 +1736,17 @@ failed: xfree(buffer); if (read_stdin) { - /* Use stderr for stdin, makes shell commands work. */ close(0); +#ifndef WIN32 + // On Unix, use stderr for stdin, makes shell commands work. ignored = dup(2); +#else + // On Windows, use the console input handle for stdin. + HANDLE conin = CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL, + OPEN_EXISTING, 0, (HANDLE)NULL); + ignored = _open_osfhandle(conin, _O_RDONLY); +#endif } if (tmpname != NULL) { @@ -3043,7 +3063,7 @@ nobackup: */ if (reset_changed && !newfile && overwriting && !(exiting && backup != NULL)) { - ml_preserve(buf, FALSE); + ml_preserve(buf, false, !!p_fs); if (got_int) { SET_ERRMSG(_(e_interr)); goto restore_backup; @@ -4435,7 +4455,7 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) /// @param size size of the buffer /// @param fp file to read from /// -/// @return true for end-of-file. +/// @return true for EOF or error bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL { char *retval; @@ -4446,7 +4466,7 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL do { errno = 0; retval = fgets((char *)buf, size, fp); - } while (retval == NULL && errno == EINTR); + } while (retval == NULL && errno == EINTR && ferror(fp)); if (buf[size - 2] != NUL && buf[size - 2] != '\n') { char tbuf[200]; @@ -4458,12 +4478,12 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL tbuf[sizeof(tbuf) - 2] = NUL; errno = 0; retval = fgets((char *)tbuf, sizeof(tbuf), fp); - if (retval == NULL && errno != EINTR) { + if (retval == NULL && (feof(fp) || errno != EINTR)) { break; } } while (tbuf[sizeof(tbuf) - 2] != NUL && tbuf[sizeof(tbuf) - 2] != '\n'); } - return retval ? false : feof(fp); + return retval == NULL; } /// Read 2 bytes from "fd" and turn them into an int, MSB first. @@ -5523,6 +5543,15 @@ static void au_cleanup(void) /* remove the pattern if it has been marked for deletion */ if (ap->pat == NULL) { + if (ap->next == NULL) { + if (prev_ap == &(first_autopat[(int)event])) { + last_autopat[(int)event] = NULL; + } else { + // this depends on the "next" field being the first in + // the struct + last_autopat[(int)event] = (AutoPat *)prev_ap; + } + } *prev_ap = ap->next; vim_regfree(ap->reg_prog); xfree(ap); @@ -6115,10 +6144,13 @@ static int do_autocmd_event(event_T event, char_u *pat, int nested, char_u *cmd, patlen = (int)STRLEN(buflocal_pat); /* but not endpat */ } - /* - * Find AutoPat entries with this pattern. - */ - prev_ap = &first_autopat[(int)event]; + // Find AutoPat entries with this pattern. When adding a command it + // always goes at or after the last one, so start at the end. + if (!forceit && *cmd != NUL && last_autopat[(int)event] != NULL) { + prev_ap = &last_autopat[(int)event]; + } else { + prev_ap = &first_autopat[(int)event]; + } while ((ap = *prev_ap) != NULL) { if (ap->pat != NULL) { /* Accept a pattern when: @@ -6204,6 +6236,7 @@ static int do_autocmd_event(event_T event, char_u *pat, int nested, char_u *cmd, } ap->cmds = NULL; *prev_ap = ap; + last_autopat[(int)event] = ap; ap->next = NULL; if (group == AUGROUP_ALL) ap->group = current_augroup; diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 5d4e61d56a..e2566c8c66 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -48,8 +48,14 @@ #include "nvim/event/loop.h" #include "nvim/os/input.h" #include "nvim/os/os.h" +#include "nvim/os/fileio.h" #include "nvim/api/private/handle.h" + +/// Index in scriptin +static int curscript = 0; +FileDescriptor *scriptin[NSCRIPT] = { NULL }; + /* * These buffers are used for storing: * - stuffed characters: A command that is translated into another command. @@ -1243,10 +1249,13 @@ openscript ( ++curscript; /* use NameBuff for expanded name */ expand_env(name, NameBuff, MAXPATHL); - if ((scriptin[curscript] = mch_fopen((char *)NameBuff, READBIN)) == NULL) { - EMSG2(_(e_notopen), name); - if (curscript) - --curscript; + int error; + if ((scriptin[curscript] = file_open_new(&error, (char *)NameBuff, + kFileReadOnly, 0)) == NULL) { + emsgf(_(e_notopen_2), name, os_strerror(error)); + if (curscript) { + curscript--; + } return; } save_typebuf(); @@ -1296,7 +1305,7 @@ static void closescript(void) free_typebuf(); typebuf = saved_typebuf[curscript]; - fclose(scriptin[curscript]); + file_free(scriptin[curscript], false); scriptin[curscript] = NULL; if (curscript > 0) --curscript; @@ -1319,10 +1328,8 @@ int using_script(void) return scriptin[curscript] != NULL; } -/* - * This function is called just before doing a blocking wait. Thus after - * waiting 'updatetime' for a character to arrive. - */ +/// This function is called just before doing a blocking wait. Thus after +/// waiting 'updatetime' for a character to arrive. void before_blocking(void) { updatescript(0); @@ -1331,21 +1338,22 @@ void before_blocking(void) } } -/* - * updatescipt() is called when a character can be written into the script file - * or when we have waited some time for a character (c == 0) - * - * All the changed memfiles are synced if c == 0 or when the number of typed - * characters reaches 'updatecount' and 'updatecount' is non-zero. - */ -void updatescript(int c) +/// updatescript() is called when a character can be written to the script file +/// or when we have waited some time for a character (c == 0). +/// +/// All the changed memfiles are synced if c == 0 or when the number of typed +/// characters reaches 'updatecount' and 'updatecount' is non-zero. +static void updatescript(int c) { static int count = 0; - if (c && scriptout) + if (c && scriptout) { putc(c, scriptout); - if (c == 0 || (p_uc > 0 && ++count >= p_uc)) { - ml_sync_all(c == 0, TRUE); + } + bool idle = (c == 0); + if (idle || (p_uc > 0 && ++count >= p_uc)) { + ml_sync_all(idle, true, + (!!p_fs || idle)); // Always fsync at idle (CursorHold). count = 0; } } @@ -1577,7 +1585,7 @@ vungetc ( /* unget one character (can only be done once!) */ old_mouse_col = mouse_col; } -/// get a character: +/// Gets a character: /// 1. from the stuffbuffer /// This is used for abbreviated commands like "D" -> "d$". /// Also used to redo a command for ".". @@ -1595,7 +1603,7 @@ vungetc ( /* unget one character (can only be done once!) */ /// if "advance" is FALSE (vpeekc()): /// just look whether there is a character available. /// -/// When "no_mapping" is zero, checks for mappings in the current mode. +/// When `no_mapping` (global) is zero, checks for mappings in the current mode. /// Only returns one byte (of a multi-byte character). /// K_SPECIAL and CSI may be escaped, need to get two more bytes then. static int vgetorpeek(int advance) @@ -2336,9 +2344,8 @@ inchar ( int tb_change_cnt ) { - int len = 0; /* init for GCC */ - int retesc = FALSE; /* return ESC with gotint */ - int script_char; + int len = 0; // Init for GCC. + int retesc = false; // Return ESC with gotint. if (wait_time == -1L || wait_time > 100L) { // flush output before waiting @@ -2356,45 +2363,38 @@ inchar ( } undo_off = FALSE; /* restart undo now */ - /* - * Get a character from a script file if there is one. - * If interrupted: Stop reading script files, close them all. - */ - script_char = -1; - while (scriptin[curscript] != NULL && script_char < 0 - && !ignore_script - ) { - - - if (got_int || (script_char = getc(scriptin[curscript])) < 0) { - /* Reached EOF. - * Careful: closescript() frees typebuf.tb_buf[] and buf[] may - * point inside typebuf.tb_buf[]. Don't use buf[] after this! */ + // Get a character from a script file if there is one. + // If interrupted: Stop reading script files, close them all. + ptrdiff_t read_size = -1; + while (scriptin[curscript] != NULL && read_size <= 0 && !ignore_script) { + char script_char; + if (got_int + || (read_size = file_read(scriptin[curscript], &script_char, 1)) != 1) { + // Reached EOF or some error occurred. + // Careful: closescript() frees typebuf.tb_buf[] and buf[] may + // point inside typebuf.tb_buf[]. Don't use buf[] after this! closescript(); - /* - * When reading script file is interrupted, return an ESC to get - * back to normal mode. - * Otherwise return -1, because typebuf.tb_buf[] has changed. - */ - if (got_int) - retesc = TRUE; - else + // When reading script file is interrupted, return an ESC to get + // back to normal mode. + // Otherwise return -1, because typebuf.tb_buf[] has changed. + if (got_int) { + retesc = true; + } else { return -1; + } } else { buf[0] = (char_u)script_char; len = 1; } } - if (script_char < 0) { /* did not get a character from script */ - /* - * If we got an interrupt, skip all previously typed characters and - * return TRUE if quit reading script file. - * Stop reading typeahead when a single CTRL-C was read, - * fill_input_buf() returns this when not able to read from stdin. - * Don't use buf[] here, closescript() may have freed typebuf.tb_buf[] - * and buf may be pointing inside typebuf.tb_buf[]. - */ + if (read_size <= 0) { // Did not get a character from script. + // If we got an interrupt, skip all previously typed characters and + // return TRUE if quit reading script file. + // Stop reading typeahead when a single CTRL-C was read, + // fill_input_buf() returns this when not able to read from stdin. + // Don't use buf[] here, closescript() may have freed typebuf.tb_buf[] + // and buf may be pointing inside typebuf.tb_buf[]. if (got_int) { #define DUM_LEN MAXMAPLEN * 3 + 3 char_u dum[DUM_LEN + 1]; @@ -2407,21 +2407,18 @@ inchar ( return retesc; } - /* - * Always flush the output characters when getting input characters - * from the user. - */ + // Always flush the output characters when getting input characters + // from the user. ui_flush(); - /* - * Fill up to a third of the buffer, because each character may be - * tripled below. - */ + // Fill up to a third of the buffer, because each character may be + // tripled below. len = os_inchar(buf, maxlen / 3, (int)wait_time, tb_change_cnt); } - if (typebuf_changed(tb_change_cnt)) + if (typebuf_changed(tb_change_cnt)) { return 0; + } return fix_input_buffer(buf, len); } diff --git a/src/nvim/getchar.h b/src/nvim/getchar.h index e634273e0d..38a2e75663 100644 --- a/src/nvim/getchar.h +++ b/src/nvim/getchar.h @@ -1,24 +1,30 @@ #ifndef NVIM_GETCHAR_H #define NVIM_GETCHAR_H +#include "nvim/os/fileio.h" #include "nvim/types.h" #include "nvim/buffer_defs.h" #include "nvim/ex_cmds_defs.h" -/// Values for "noremap" argument of ins_typebuf(). Also used for -/// map->m_noremap and menu->noremap[]. -/// @addtogroup REMAP_VALUES -/// @{ -#define REMAP_YES 0 ///< allow remapping -#define REMAP_NONE -1 ///< no remapping -#define REMAP_SCRIPT -2 ///< remap script-local mappings only -#define REMAP_SKIP -3 ///< no remapping for first char -/// @} +/// Values for "noremap" argument of ins_typebuf() +/// +/// Also used for map->m_noremap and menu->noremap[]. +enum { + REMAP_YES = 0, ///< Allow remapping. + REMAP_NONE = -1, ///< No remapping. + REMAP_SCRIPT = -2, ///< Remap script-local mappings only. + REMAP_SKIP = -3, ///< No remapping for first char. +} RemapValues; #define KEYLEN_PART_KEY -1 /* keylen value for incomplete key-code */ #define KEYLEN_PART_MAP -2 /* keylen value for incomplete mapping */ #define KEYLEN_REMOVED 9999 /* keylen value for removed sequence */ +/// Maximum number of streams to read script from +enum { NSCRIPT = 15 }; + +/// Streams to read script from +extern FileDescriptor *scriptin[NSCRIPT]; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "getchar.h.generated.h" diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 4542b7f133..e02100e933 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -78,6 +78,11 @@ typedef enum { kTrue = 1, } TriState; +EXTERN struct nvim_stats_s { + int64_t fsync; + int64_t redraw; +} g_stats INIT(= { 0, 0 }); + /* Values for "starting" */ #define NO_SCREEN 2 /* no screen updating yet */ #define NO_BUFFERS 1 /* not all buffers loaded yet */ @@ -828,10 +833,7 @@ EXTERN int do_redraw INIT(= FALSE); /* extra redraw once */ EXTERN int need_highlight_changed INIT(= true); EXTERN char *used_shada_file INIT(= NULL); // name of the ShaDa file to use -#define NSCRIPT 15 -EXTERN FILE *scriptin[NSCRIPT]; /* streams to read script from */ -EXTERN int curscript INIT(= 0); /* index in scriptin[] */ -EXTERN FILE *scriptout INIT(= NULL); /* stream to write script to */ +EXTERN FILE *scriptout INIT(= NULL); ///< Stream to write script to. // volatile because it is used in a signal handler. EXTERN volatile int got_int INIT(= false); // set to true when interrupt @@ -1064,7 +1066,6 @@ EXTERN char_u e_nesting[] INIT(= N_("E22: Scripts nested too deep")); EXTERN char_u e_noalt[] INIT(= N_("E23: No alternate file")); EXTERN char_u e_noabbr[] INIT(= N_("E24: No such abbreviation")); EXTERN char_u e_nobang[] INIT(= N_("E477: No ! allowed")); -EXTERN char_u e_nogvim[] INIT(= N_("E25: Nvim does not have a built-in GUI")); EXTERN char_u e_nogroup[] INIT(= N_("E28: No such highlight group name: %s")); EXTERN char_u e_noinstext[] INIT(= N_("E29: No inserted text yet")); EXTERN char_u e_nolastcmd[] INIT(= N_("E30: No previous command line")); @@ -1080,6 +1081,7 @@ EXTERN char_u e_norange[] INIT(= N_("E481: No range allowed")); EXTERN char_u e_noroom[] INIT(= N_("E36: Not enough room")); EXTERN char_u e_notmp[] INIT(= N_("E483: Can't get temp file name")); EXTERN char_u e_notopen[] INIT(= N_("E484: Can't open file %s")); +EXTERN char_u e_notopen_2[] INIT(= N_("E484: Can't open file %s: %s")); EXTERN char_u e_notread[] INIT(= N_("E485: Can't read file %s")); EXTERN char_u e_nowrtmsg[] INIT(= N_( "E37: No write since last change (add ! to override)")); diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index 00e9cf6ed3..c64691e8ea 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -443,7 +443,7 @@ enum key_extra { #define MOD_MASK_2CLICK 0x20 // use MOD_MASK_MULTI_CLICK #define MOD_MASK_3CLICK 0x40 // use MOD_MASK_MULTI_CLICK #define MOD_MASK_4CLICK 0x60 // use MOD_MASK_MULTI_CLICK -#define MOD_MASK_CMD 0x80 // "super" key (OSX/Mac: command-key) +#define MOD_MASK_CMD 0x80 // "super" key (macOS: command-key) #define MOD_MASK_MULTI_CLICK (MOD_MASK_2CLICK|MOD_MASK_3CLICK| \ MOD_MASK_4CLICK) diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 4e01265498..348df2d9b6 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -136,13 +136,21 @@ # define RESET_BINDING(wp) (wp)->w_p_scb = FALSE; (wp)->w_p_crb = FALSE -/// Calculate the length of a C array. +/// Calculate the length of a C array /// /// This should be called with a real array. Calling this with a pointer is an -/// error. A mechanism to detect many (though not all) of those errors at compile -/// time is implemented. It works by the second division producing a division by -/// zero in those cases (-Wdiv-by-zero in GCC). -#define ARRAY_SIZE(arr) ((sizeof(arr)/sizeof((arr)[0])) / ((size_t)(!(sizeof(arr) % sizeof((arr)[0]))))) +/// error. A mechanism to detect many (though not all) of those errors at +/// compile time is implemented. It works by the second division producing +/// a division by zero in those cases (-Wdiv-by-zero in GCC). +#define ARRAY_SIZE(arr) \ + ((sizeof(arr)/sizeof((arr)[0])) \ + / ((size_t)(!(sizeof(arr) % sizeof((arr)[0]))))) + +/// Get last array entry +/// +/// This should be called with a real array. Calling this with a pointer is an +/// error. +#define ARRAY_LAST_ENTRY(arr) (arr)[ARRAY_SIZE(arr) - 1] // Duplicated in os/win_defs.h to avoid include-order sensitivity. #define RGB_(r, g, b) ((r << 16) | (g << 8) | b) diff --git a/src/nvim/main.c b/src/nvim/main.c index 4288d7f9d7..a4ed868af1 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -72,30 +72,30 @@ # include "nvim/os/pty_process_unix.h" #endif -/* Maximum number of commands from + or -c arguments. */ +// Maximum number of commands from + or -c arguments. #define MAX_ARG_CMDS 10 -/* values for "window_layout" */ -#define WIN_HOR 1 /* "-o" horizontally split windows */ -#define WIN_VER 2 /* "-O" vertically split windows */ -#define WIN_TABS 3 /* "-p" windows on tab pages */ +// values for "window_layout" +#define WIN_HOR 1 // "-o" horizontally split windows +#define WIN_VER 2 // "-O" vertically split windows +#define WIN_TABS 3 // "-p" windows on tab pages -/* Struct for various parameters passed between main() and other functions. */ +// Struct for various parameters passed between main() and other functions. typedef struct { int argc; char **argv; char *use_vimrc; // vimrc from -u argument - int n_commands; /* no. of commands from + or -c */ + int n_commands; // no. of commands from + or -c char *commands[MAX_ARG_CMDS]; // commands from + or -c arg - char_u cmds_tofree[MAX_ARG_CMDS]; /* commands that need free() */ - int n_pre_commands; /* no. of commands from --cmd */ + char_u cmds_tofree[MAX_ARG_CMDS]; // commands that need free() + int n_pre_commands; // no. of commands from --cmd char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument - int edit_type; /* type of editing to do */ - char_u *tagname; /* tag from -t argument */ - char_u *use_ef; /* 'errorfile' from -q argument */ + int edit_type; // type of editing to do + char_u *tagname; // tag from -t argument + char_u *use_ef; // 'errorfile' from -q argument int want_full_screen; bool input_isatty; // stdin is a terminal @@ -103,13 +103,15 @@ typedef struct { bool err_isatty; // stderr is a terminal int no_swap_file; // "-n" argument used int use_debug_break_level; - int window_count; /* number of windows to use */ - int window_layout; /* 0, WIN_HOR, WIN_VER or WIN_TABS */ + int window_count; // number of windows to use + int window_layout; // 0, WIN_HOR, WIN_VER or WIN_TABS #if !defined(UNIX) - int literal; /* don't expand file names */ + int literal; // don't expand file names #endif - int diff_mode; /* start with 'diff' set */ + int diff_mode; // start with 'diff' set + + char *listen_addr; // --listen {address} } mparm_T; /* Values for edit_type. */ @@ -150,7 +152,6 @@ void event_init(void) signal_init(); // finish mspgack-rpc initialization channel_init(); - server_init(); terminal_init(); } @@ -241,9 +242,8 @@ int main(int argc, char **argv) char_u *cwd = NULL; // current workding dir on startup time_init(); - /* Many variables are in "params" so that we can pass them to invoked - * functions without a lot of arguments. "argc" and "argv" are also - * copied, so that they can be changed. */ + // Many variables are in `params` so that we can pass them around easily. + // `argc` and `argv` are also copied, so that they can be changed. init_params(¶ms, argc, argv); init_startuptime(¶ms); @@ -254,11 +254,10 @@ int main(int argc, char **argv) check_and_set_isatty(¶ms); event_init(); - /* - * Process the command line arguments. File names are put in the global - * argument list "global_alist". - */ + // Process the command line arguments. File names are put in the global + // argument list "global_alist". command_line_scan(¶ms); + server_init(params.listen_addr); if (GARGCOUNT > 0) { fname = get_fname(¶ms, cwd); @@ -726,9 +725,7 @@ static void init_locale(void) #endif -/* - * Scan the command line arguments. - */ +/// Scan the command line arguments. static void command_line_scan(mparm_T *parmp) { int argc = parmp->argc; @@ -790,7 +787,8 @@ static void command_line_scan(mparm_T *parmp) mch_exit(0); } else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) { FileDescriptor fp; - const int fof_ret = file_open_fd(&fp, STDOUT_FILENO, true); + const int fof_ret = file_open_fd(&fp, STDOUT_FILENO, + kFileWriteOnly); msgpack_packer *p = msgpack_packer_new(&fp, msgpack_file_write); if (fof_ret != 0) { @@ -819,6 +817,9 @@ static void command_line_scan(mparm_T *parmp) if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) { abort(); } + } else if (STRNICMP(argv[0] + argv_idx, "listen", 6) == 0) { + want_argument = true; + argv_idx += 6; } else if (STRNICMP(argv[0] + argv_idx, "literal", 7) == 0) { #if !defined(UNIX) parmp->literal = TRUE; @@ -864,10 +865,6 @@ static void command_line_scan(mparm_T *parmp) case 'f': /* "-f" GUI: run in foreground. */ break; - case 'g': /* "-g" start GUI */ - main_start_gui(); - break; - case 'F': { // "-F" start in Farsi mode: rl + fkmap set. p_fkmap = true; set_option_value("rl", 1L, NULL, 0); @@ -898,26 +895,17 @@ static void command_line_scan(mparm_T *parmp) p_write = FALSE; break; - case 'N': /* "-N" Nocompatible */ - /* No-op */ + case 'N': // "-N" Nocompatible + case 'X': // "-X" Do not connect to X server + // No-op break; case 'n': /* "-n" no swap file */ parmp->no_swap_file = TRUE; break; - case 'p': /* "-p[N]" open N tab pages */ -#ifdef TARGET_API_MAC_OSX - /* For some reason on MacOS X, an argument like: - -psn_0_10223617 is passed in when invoke from Finder - or with the 'open' command */ - if (argv[0][argv_idx] == 's') { - argv_idx = -1; /* bypass full -psn */ - main_start_gui(); - break; - } -#endif - /* default is 0: open window for each file */ + case 'p': // "-p[N]" open N tab pages + // default is 0: open window for each file parmp->window_count = get_number_arg(argv[0], &argv_idx, 0); parmp->window_layout = WIN_TABS; break; @@ -1030,15 +1018,12 @@ static void command_line_scan(mparm_T *parmp) mainerr(err_opt_unknown, argv[0]); } - /* - * Handle option arguments with argument. - */ + // Handle option arguments with argument. if (want_argument) { - /* - * Check for garbage immediately after the option letter. - */ - if (argv[0][argv_idx] != NUL) + // Check for garbage immediately after the option letter. + if (argv[0][argv_idx] != NUL) { mainerr(err_opt_garbage, argv[0]); + } --argc; if (argc < 1 && c != 'S') /* -S has an optional argument */ @@ -1077,13 +1062,17 @@ static void command_line_scan(mparm_T *parmp) break; case '-': - if (argv[-1][2] == 'c') { - /* "--cmd {command}" execute command */ - if (parmp->n_pre_commands >= MAX_ARG_CMDS) + if (strequal(argv[-1], "--cmd")) { + // "--cmd {command}" execute command + if (parmp->n_pre_commands >= MAX_ARG_CMDS) { mainerr(err_extra_cmd, NULL); + } parmp->pre_commands[parmp->n_pre_commands++] = argv[0]; + } else if (strequal(argv[-1], "--listen")) { + // "--listen {address}" + parmp->listen_addr = argv[0]; } - /* "--startuptime <file>" already handled */ + // "--startuptime <file>" already handled break; case 'q': /* "-q {errorfile}" QuickFix mode */ @@ -1097,17 +1086,36 @@ static void command_line_scan(mparm_T *parmp) case 's': /* "-s {scriptin}" read from script file */ if (scriptin[0] != NULL) { scripterror: - mch_errmsg(_("Attempt to open script file again: \"")); - mch_errmsg(argv[-1]); - mch_errmsg(" "); - mch_errmsg(argv[0]); - mch_errmsg("\"\n"); + vim_snprintf((char *)IObuff, IOSIZE, + _("Attempt to open script file again: \"%s %s\"\n"), + argv[-1], argv[0]); + mch_errmsg((const char *)IObuff); mch_exit(2); } - if ((scriptin[0] = mch_fopen(argv[0], READBIN)) == NULL) { - mch_errmsg(_("Cannot open for reading: \"")); - mch_errmsg(argv[0]); - mch_errmsg("\"\n"); + int error; + if (STRCMP(argv[0], "-") == 0) { + const int stdin_dup_fd = os_dup(STDIN_FILENO); +#ifdef WIN32 + // On Windows, replace the original stdin with the + // console input handle. + close(STDIN_FILENO); + const HANDLE conin_handle = + CreateFile("CONIN$", GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ, (LPSECURITY_ATTRIBUTES)NULL, + OPEN_EXISTING, 0, (HANDLE)NULL); + const int conin_fd = _open_osfhandle(conin_handle, _O_RDONLY); + assert(conin_fd == STDIN_FILENO); +#endif + FileDescriptor *const stdin_dup = file_open_fd_new( + &error, stdin_dup_fd, kFileReadOnly|kFileNonBlocking); + assert(stdin_dup != NULL); + scriptin[0] = stdin_dup; + } else if ((scriptin[0] = file_open_new( + &error, argv[0], kFileReadOnly|kFileNonBlocking, 0)) == NULL) { + vim_snprintf((char *)IObuff, IOSIZE, + _("Cannot open for reading: \"%s\": %s\n"), + argv[0], os_strerror(error)); + mch_errmsg((const char *)IObuff); mch_exit(2); } save_typebuf(); @@ -1224,11 +1232,10 @@ static void init_params(mparm_T *paramp, int argc, char **argv) paramp->want_full_screen = true; paramp->use_debug_break_level = -1; paramp->window_count = -1; + paramp->listen_addr = NULL; } -/* - * Initialize global startuptime file if "--startuptime" passed as an argument. - */ +/// Initialize global startuptime file if "--startuptime" passed as an argument. static void init_startuptime(mparm_T *paramp) { for (int i = 1; i < paramp->argc; i++) { @@ -1834,17 +1841,6 @@ static void source_startup_scripts(const mparm_T *const parmp) TIME_MSG("sourcing vimrc file(s)"); } -/* - * Setup to start using the GUI. Exit with an error when not available. - */ -static void main_start_gui(void) -{ - mch_errmsg(_(e_nogvim)); - mch_errmsg("\n"); - mch_exit(2); -} - - /// Get an environment variable, and execute it as Ex commands. /// /// @param env environment variable to execute @@ -1968,6 +1964,7 @@ static void usage(void) mch_msg(_(" --api-info Write msgpack-encoded API metadata to stdout\n")); mch_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n")); mch_msg(_(" --headless Don't start a user interface\n")); + mch_msg(_(" --listen <address> Start RPC server at this address\n")); #if !defined(UNIX) mch_msg(_(" --literal Don't expand wildcards\n")); #endif diff --git a/src/nvim/memline.c b/src/nvim/memline.c index d4708f61ff..1f388dd34c 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1588,7 +1588,7 @@ static int recov_file_names(char_u **names, char_u *path, int prepend_dot) * If 'check_char' is TRUE, stop syncing when character becomes available, but * always sync at least one block. */ -void ml_sync_all(int check_file, int check_char) +void ml_sync_all(int check_file, int check_char, bool do_fsync) { FOR_ALL_BUFFERS(buf) { if (buf->b_ml.ml_mfp == NULL || buf->b_ml.ml_mfp->mf_fname == NULL) @@ -1607,16 +1607,17 @@ void ml_sync_all(int check_file, int check_char) if (!os_fileinfo((char *)buf->b_ffname, &file_info) || file_info.stat.st_mtim.tv_sec != buf->b_mtime_read || os_fileinfo_size(&file_info) != buf->b_orig_size) { - ml_preserve(buf, FALSE); - did_check_timestamps = FALSE; - need_check_timestamps = TRUE; /* give message later */ + ml_preserve(buf, false, do_fsync); + did_check_timestamps = false; + need_check_timestamps = true; // give message later } } if (buf->b_ml.ml_mfp->mf_dirty) { (void)mf_sync(buf->b_ml.ml_mfp, (check_char ? MFS_STOP : 0) - | (bufIsChanged(buf) ? MFS_FLUSH : 0)); - if (check_char && os_char_avail()) /* character available now */ + | (do_fsync && bufIsChanged(buf) ? MFS_FLUSH : 0)); + if (check_char && os_char_avail()) { // character available now break; + } } } } @@ -1631,7 +1632,7 @@ void ml_sync_all(int check_file, int check_char) * * when message is TRUE the success of preserving is reported */ -void ml_preserve(buf_T *buf, int message) +void ml_preserve(buf_T *buf, int message, bool do_fsync) { bhdr_T *hp; linenr_T lnum; @@ -1649,9 +1650,9 @@ void ml_preserve(buf_T *buf, int message) * before. */ got_int = FALSE; - ml_flush_line(buf); /* flush buffered line */ - (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); /* flush locked block */ - status = mf_sync(mfp, MFS_ALL | MFS_FLUSH); + ml_flush_line(buf); // flush buffered line + (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush locked block + status = mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0)); /* stack is invalid after mf_sync(.., MFS_ALL) */ buf->b_ml.ml_stack_top = 0; @@ -1679,11 +1680,12 @@ void ml_preserve(buf_T *buf, int message) CHECK(buf->b_ml.ml_locked_low != lnum, "low != lnum"); lnum = buf->b_ml.ml_locked_high + 1; } - (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); /* flush locked block */ - /* sync the updated pointer blocks */ - if (mf_sync(mfp, MFS_ALL | MFS_FLUSH) == FAIL) + (void)ml_find_line(buf, (linenr_T)0, ML_FLUSH); // flush locked block + // sync the updated pointer blocks + if (mf_sync(mfp, MFS_ALL | (do_fsync ? MFS_FLUSH : 0)) == FAIL) { status = FAIL; - buf->b_ml.ml_stack_top = 0; /* stack is invalid now */ + } + buf->b_ml.ml_stack_top = 0; // stack is invalid now } theend: got_int |= got_int_save; diff --git a/src/nvim/message.c b/src/nvim/message.c index abe21193d6..7ca82c2878 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -2348,10 +2348,9 @@ static int do_more_prompt(int typed_char) * yet. When stderr can't be used, collect error messages until the GUI has * started and they can be displayed in a message box. */ -void mch_errmsg(char *str) +void mch_errmsg(const char *const str) + FUNC_ATTR_NONNULL_ALL { - int len; - #ifdef UNIX /* On Unix use stderr if it's a tty. * When not going to start the GUI also use stderr. @@ -2365,14 +2364,13 @@ void mch_errmsg(char *str) /* avoid a delay for a message that isn't there */ emsg_on_display = FALSE; - len = (int)STRLEN(str) + 1; + const size_t len = strlen(str) + 1; if (error_ga.ga_data == NULL) { ga_set_growsize(&error_ga, 80); error_ga.ga_itemsize = 1; } ga_grow(&error_ga, len); - memmove((char_u *)error_ga.ga_data + error_ga.ga_len, - (char_u *)str, len); + memmove(error_ga.ga_data + error_ga.ga_len, str, len); #ifdef UNIX /* remove CR characters, they are displayed */ { diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index b0232b6516..28455f0ba9 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -2643,7 +2643,7 @@ void preserve_exit(void) if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) { mch_errmsg((uint8_t *)"Vim: preserving files...\n"); ui_flush(); - ml_sync_all(false, false); // preserve all swap files + ml_sync_all(false, false, true); // preserve all swap files break; } } diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index 9bf122f4db..e5d80aea1d 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -32,26 +32,27 @@ static garray_T watchers = GA_EMPTY_INIT_VALUE; #endif /// Initializes the module -bool server_init(void) +bool server_init(const char *listen_addr) { ga_init(&watchers, sizeof(SocketWatcher *), 1); - bool must_free = false; - const char *listen_address = os_getenv(LISTEN_ADDRESS_ENV_VAR); - if (listen_address == NULL) { - must_free = true; - listen_address = server_address_new(); - } + // $NVIM_LISTEN_ADDRESS + const char *env_addr = os_getenv(LISTEN_ADDRESS_ENV_VAR); + int rv = listen_addr == NULL ? 1 : server_start(listen_addr); - if (!listen_address) { - return false; + if (0 != rv) { + rv = env_addr == NULL ? 1 : server_start(env_addr); + if (0 != rv) { + listen_addr = server_address_new(); + if (listen_addr == NULL) { + return false; + } + rv = server_start(listen_addr); + xfree((char *)listen_addr); + } } - bool ok = (server_start(listen_address) == 0); - if (must_free) { - xfree((char *) listen_address); - } - return ok; + return rv == 0; } /// Teardown a single server @@ -120,8 +121,8 @@ bool server_owns_pipe_address(const char *path) /// @param endpoint Address of the server. Either a 'ip:[port]' string or an /// arbitrary identifier (trimmed to 256 bytes) for the Unix /// socket or named pipe. -/// @returns 0 on success, 1 on a regular error, and negative errno -/// on failure to bind or listen. +/// @returns 0: success, 1: validation error, 2: already listening, +/// -errno: failed to bind or listen. int server_start(const char *endpoint) { if (endpoint == NULL || endpoint[0] == '\0') { @@ -145,7 +146,7 @@ int server_start(const char *endpoint) uv_freeaddrinfo(watcher->uv.tcp.addrinfo); } socket_watcher_close(watcher, free_server); - return 1; + return 2; } } @@ -177,7 +178,7 @@ int server_start(const char *endpoint) /// Stops listening on the address specified by `endpoint`. /// /// @param endpoint Address of the server. -void server_stop(char *endpoint) +bool server_stop(char *endpoint) { SocketWatcher *watcher; bool watcher_found = false; @@ -196,8 +197,8 @@ void server_stop(char *endpoint) } if (!watcher_found) { - ELOG("Not listening on %s", addr); - return; + WLOG("Not listening on %s", addr); + return false; } // Unset $NVIM_LISTEN_ADDRESS if it is the stopped address. @@ -219,6 +220,8 @@ void server_stop(char *endpoint) if (STRCMP(addr, get_vim_var_str(VV_SEND_SERVER)) == 0) { set_vservername(&watchers); } + + return true; } /// Returns an allocated array of server addresses. diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 80484d0ad2..66018b2475 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -976,7 +976,7 @@ return { secure=true, vi_def=true, varname='p_fs', - defaults={if_true={vi=true}} + defaults={if_true={vi=false}} }, { full_name='gdefault', abbreviation='gd', diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index a95adc86b6..ccf35fd57c 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -83,7 +83,7 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname, if (fd < 0) { return fd; } - return file_open_fd(ret_fp, fd, (wr == kTrue)); + return file_open_fd(ret_fp, fd, flags); } /// Wrap file descriptor with FileDescriptor structure @@ -94,14 +94,23 @@ int file_open(FileDescriptor *const ret_fp, const char *const fname, /// @param[out] ret_fp Address where information needed for reading from or /// writing to a file is saved /// @param[in] fd File descriptor to wrap. -/// @param[in] wr True if fd is opened for writing only, false if it is read -/// only. +/// @param[in] flags Flags, @see FileOpenFlags. Currently reading from and +/// writing to the file at once is not supported, so either +/// FILE_WRITE_ONLY or FILE_READ_ONLY is required. /// /// @return Error code (@see os_strerror()) or 0. Currently always returns 0. -int file_open_fd(FileDescriptor *const ret_fp, const int fd, const bool wr) +int file_open_fd(FileDescriptor *const ret_fp, const int fd, + const int flags) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - ret_fp->wr = wr; + ret_fp->wr = !!(flags & (kFileCreate + |kFileCreateOnly + |kFileTruncate + |kFileAppend + |kFileWriteOnly)); + ret_fp->non_blocking = !!(flags & kFileNonBlocking); + // Non-blocking writes not supported currently. + assert(!ret_fp->wr || !ret_fp->non_blocking); ret_fp->fd = fd; ret_fp->eof = false; ret_fp->rv = rbuffer_new(kRWBufferSize); @@ -138,15 +147,17 @@ FileDescriptor *file_open_new(int *const error, const char *const fname, /// /// @param[out] error Error code, or 0 on success. @see os_strerror() /// @param[in] fd File descriptor to wrap. -/// @param[in] wr True if fd is opened for writing only, false if it is read -/// only. +/// @param[in] flags Flags, @see FileOpenFlags. +/// @param[in] mode Permissions for the newly created file (ignored if flags +/// does not have FILE_CREATE\*). /// /// @return [allocated] Opened file or NULL in case of error. -FileDescriptor *file_open_fd_new(int *const error, const int fd, const bool wr) +FileDescriptor *file_open_fd_new(int *const error, const int fd, + const int flags) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT { FileDescriptor *const fp = xmalloc(sizeof(*fp)); - if ((*error = file_open_fd(fp, fd, wr)) != 0) { + if ((*error = file_open_fd(fp, fd, flags)) != 0) { xfree(fp); return NULL; } @@ -244,7 +255,8 @@ static void file_rb_write_full_cb(RBuffer *const rv, FileDescriptor *const fp) return; } const size_t read_bytes = rbuffer_read(rv, writebuf, kRWBufferSize); - const ptrdiff_t wres = os_write(fp->fd, writebuf, read_bytes); + const ptrdiff_t wres = os_write(fp->fd, writebuf, read_bytes, + fp->non_blocking); if (wres != (ptrdiff_t)read_bytes) { if (wres >= 0) { fp->_error = UV_EIO; @@ -270,6 +282,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, char *buf = ret_buf; size_t read_remaining = size; RBuffer *const rv = fp->rv; + bool called_read = false; while (read_remaining) { const size_t rv_size = rbuffer_size(rv); if (rv_size > 0) { @@ -277,7 +290,9 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, buf += rsize; read_remaining -= rsize; } - if (fp->eof) { + if (fp->eof + // Allow only at most one os_read[v] call. + || (called_read && fp->non_blocking)) { break; } if (read_remaining) { @@ -294,7 +309,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, }; assert(write_count == kRWBufferSize); const ptrdiff_t r_ret = os_readv(fp->fd, &fp->eof, iov, - ARRAY_SIZE(iov)); + ARRAY_SIZE(iov), fp->non_blocking); if (r_ret > 0) { if (r_ret > (ptrdiff_t)read_remaining) { rbuffer_produced(rv, (size_t)(r_ret - (ptrdiff_t)read_remaining)); @@ -310,7 +325,8 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, if (read_remaining >= kRWBufferSize) { // …otherwise leave RBuffer empty and populate only target buffer, // because filtering information through rbuffer will be more syscalls. - const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, buf, read_remaining); + const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, buf, read_remaining, + fp->non_blocking); if (r_ret >= 0) { read_remaining -= (size_t)r_ret; return (ptrdiff_t)(size - read_remaining); @@ -321,7 +337,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, size_t write_count; const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, rbuffer_write_ptr(rv, &write_count), - kRWBufferSize); + kRWBufferSize, fp->non_blocking); assert(write_count == kRWBufferSize); if (r_ret > 0) { rbuffer_produced(rv, (size_t)r_ret); @@ -330,6 +346,7 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, } } #endif + called_read = true; } } return (ptrdiff_t)(size - read_remaining); diff --git a/src/nvim/os/fileio.h b/src/nvim/os/fileio.h index 0b55cc695f..7c53cd4f07 100644 --- a/src/nvim/os/fileio.h +++ b/src/nvim/os/fileio.h @@ -14,6 +14,7 @@ typedef struct { RBuffer *rv; ///< Read or write buffer. bool wr; ///< True if file is in write mode. bool eof; ///< True if end of file was encountered. + bool non_blocking; ///< True if EAGAIN should not restart syscalls. } FileDescriptor; /// file_open() flags @@ -32,6 +33,8 @@ typedef enum { ///< kFileCreateOnly. kFileAppend = 64, ///< Append to the file. Implies kFileWriteOnly. Cannot ///< be used with kFileCreateOnly. + kFileNonBlocking = 128, ///< Do not restart read() or write() syscall if + ///< EAGAIN was encountered. } FileOpenFlags; static inline bool file_eof(const FileDescriptor *const fp) diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 924b3d2359..5412c5daae 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -436,6 +436,29 @@ int os_close(const int fd) return r; } +/// Duplicate file descriptor +/// +/// @param[in] fd File descriptor to duplicate. +/// +/// @return New file descriptor or libuv error code (< 0). +int os_dup(const int fd) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + int ret; +os_dup_dup: + ret = dup(fd); + if (ret < 0) { + const int error = os_translate_sys_error(errno); + errno = 0; + if (error == UV_EINTR) { + goto os_dup_dup; + } else { + return error; + } + } + return ret; +} + /// Read from a file /// /// Handles EINTR and ENOMEM, but not other errors. @@ -445,10 +468,11 @@ int os_close(const int fd) /// to false. Initial value is ignored. /// @param[out] ret_buf Buffer to write to. May be NULL if size is zero. /// @param[in] size Amount of bytes to read. +/// @param[in] non_blocking Do not restart syscall if EAGAIN was encountered. /// /// @return Number of bytes read or libuv error code (< 0). -ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf, - const size_t size) +ptrdiff_t os_read(const int fd, bool *const ret_eof, char *const ret_buf, + const size_t size, const bool non_blocking) FUNC_ATTR_WARN_UNUSED_RESULT { *ret_eof = false; @@ -468,7 +492,9 @@ ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf, if (cur_read_bytes < 0) { const int error = os_translate_sys_error(errno); errno = 0; - if (error == UV_EINTR || error == UV_EAGAIN) { + if (non_blocking && error == UV_EAGAIN) { + break; + } else if (error == UV_EINTR || error == UV_EAGAIN) { continue; } else if (error == UV_ENOMEM && !did_try_to_free) { try_to_free_memory(); @@ -498,7 +524,11 @@ ptrdiff_t os_read(const int fd, bool *ret_eof, char *const ret_buf, /// may change, it is incorrect to use data it points to after /// os_readv(). /// @param[in] iov_size Number of buffers in iov. -ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size) +/// @param[in] non_blocking Do not restart syscall if EAGAIN was encountered. +/// +/// @return Number of bytes read or libuv error code (< 0). +ptrdiff_t os_readv(const int fd, bool *const ret_eof, struct iovec *iov, + size_t iov_size, const bool non_blocking) FUNC_ATTR_NONNULL_ALL { *ret_eof = false; @@ -531,7 +561,9 @@ ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size) } else if (cur_read_bytes < 0) { const int error = os_translate_sys_error(errno); errno = 0; - if (error == UV_EINTR || error == UV_EAGAIN) { + if (non_blocking && error == UV_EAGAIN) { + break; + } else if (error == UV_EINTR || error == UV_EAGAIN) { continue; } else if (error == UV_ENOMEM && !did_try_to_free) { try_to_free_memory(); @@ -551,9 +583,11 @@ ptrdiff_t os_readv(int fd, bool *ret_eof, struct iovec *iov, size_t iov_size) /// @param[in] fd File descriptor to write to. /// @param[in] buf Data to write. May be NULL if size is zero. /// @param[in] size Amount of bytes to write. +/// @param[in] non_blocking Do not restart syscall if EAGAIN was encountered. /// /// @return Number of bytes written or libuv error code (< 0). -ptrdiff_t os_write(const int fd, const char *const buf, const size_t size) +ptrdiff_t os_write(const int fd, const char *const buf, const size_t size, + const bool non_blocking) FUNC_ATTR_WARN_UNUSED_RESULT { if (buf == NULL) { @@ -571,7 +605,9 @@ ptrdiff_t os_write(const int fd, const char *const buf, const size_t size) if (cur_written_bytes < 0) { const int error = os_translate_sys_error(errno); errno = 0; - if (error == UV_EINTR || error == UV_EAGAIN) { + if (non_blocking && error == UV_EAGAIN) { + break; + } else if (error == UV_EINTR || error == UV_EAGAIN) { continue; } else { return error; @@ -593,6 +629,7 @@ int os_fsync(int fd) { int r; RUN_UV_FS_FUNC(r, uv_fs_fsync, fd, NULL); + g_stats.fsync++; return r; } diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c index 63eea9022b..a67e7487eb 100644 --- a/src/nvim/os/process.c +++ b/src/nvim/os/process.c @@ -18,7 +18,7 @@ # include <sys/user.h> #endif -#if defined(__NetBSD__) +#if defined(__NetBSD__) || defined(__OpenBSD__) # include <sys/param.h> #endif diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index f650a51fe7..04f59d7522 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -136,7 +136,7 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) xfree(input.data); if (output) { - (void)write_output(output, nread, true, true); + (void)write_output(output, nread, true); xfree(output); } @@ -388,10 +388,10 @@ static bool out_data_decide_throttle(size_t size) pulse_msg[1] = (tick == 0 || 1 == tick) ? ' ' : '.'; pulse_msg[2] = (tick == 0 || 1 == tick || 2 == tick) ? ' ' : '.'; if (visit == 1) { - screen_del_lines(0, 0, 1, (int)Rows, NULL); + msg_putchar('\n'); } - int lastrow = (int)Rows - 1; - screen_puts_len((char_u *)pulse_msg, ARRAY_SIZE(pulse_msg), lastrow, 0, 0); + msg_putchar('\r'); // put cursor at start of line + msg_puts(pulse_msg); ui_flush(); return true; } @@ -609,28 +609,20 @@ static void read_input(DynamicBuffer *buf) } } -static size_t write_output(char *output, size_t remaining, bool to_buffer, - bool eof) +static size_t write_output(char *output, size_t remaining, bool eof) { if (!output) { return 0; } - char replacement_NUL = to_buffer ? NL : 1; char *start = output; size_t off = 0; - int lastrow = (int)Rows - 1; while (off < remaining) { if (output[off] == NL) { // Insert the line - if (to_buffer) { - 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); - } + output[off] = NUL; + ml_append(curwin->w_cursor.lnum++, (char_u *)output, (int)off + 1, + false); size_t skip = off + 1; output += skip; remaining -= skip; @@ -640,24 +632,19 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer, if (output[off] == NUL) { // Translate NUL to NL - output[off] = replacement_NUL; + output[off] = NL; } off++; } if (eof) { if (remaining) { - if (to_buffer) { - // append unfinished line - ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false); - // remember that the NL was missing - curbuf->b_no_eol_lnum = curwin->w_cursor.lnum; - } else { - screen_del_lines(0, 0, 1, (int)Rows, NULL); - screen_puts_len((char_u *)output, (int)remaining, lastrow, 0, 0); - } + // append unfinished line + ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false); + // remember that the NL was missing + curbuf->b_no_eol_lnum = curwin->w_cursor.lnum; output += remaining; - } else if (to_buffer) { + } else { curbuf->b_no_eol_lnum = 0; } } diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index 732be072e1..fc7f9cefd1 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -145,7 +145,7 @@ static void on_signal(SignalWatcher *handle, int signum, void *data) case SIGPWR: // Signal of a power failure(eg batteries low), flush the swap files to // be safe - ml_sync_all(false, false); + ml_sync_all(false, false, true); break; #endif #ifdef SIGPIPE diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c index a41fb7c621..866a005228 100644 --- a/src/nvim/os/stdpaths.c +++ b/src/nvim/os/stdpaths.c @@ -88,7 +88,7 @@ char *stdpaths_get_xdg_var(const XDGVarType idx) /// /// In WIN32 get_xdg_home(kXDGDataHome) returns `{xdg_directory}/nvim-data` to /// avoid storing configuration and data files in the same path. -static char *get_xdg_home(const XDGVarType idx) +char *get_xdg_home(const XDGVarType idx) FUNC_ATTR_WARN_UNUSED_RESULT { char *dir = stdpaths_get_xdg_var(idx); diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt index d01eff6384..94cc63baea 100644 --- a/src/nvim/po/CMakeLists.txt +++ b/src/nvim/po/CMakeLists.txt @@ -139,17 +139,17 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) endmacro() # Create some translations from others. - if("ja" IN_LIST LANGUAGES) + if(";${LANGUAGES};" MATCHES ";ja;") BuildPoIconv(ja utf-8 euc-jp) BuildMo(ja.euc-jp) endif() - if("cs" IN_LIST LANGUAGES) + if(";${LANGUAGES};" MATCHES ";cs;") BuildPoIconv(cs ISO-8859-2 cp1250) BuildMo(cs.cp1250) endif() - if("sk" IN_LIST LANGUAGES) + if(";${LANGUAGES};" MATCHES ";sk;") BuildPoIconv(sk ISO-8859-2 cp1250) BuildMo(sk.cp1250) endif() @@ -159,7 +159,7 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) ${CMAKE_CURRENT_SOURCE_DIR}/no.po ${CMAKE_CURRENT_SOURCE_DIR}/nb.po DEPENDS no.po) list(APPEND UPDATE_PO_TARGETS update-po-nb) - if("nb" IN_LIST LANGUAGES) + if(";${LANGUAGES};" MATCHES ";no;") CheckPo(nb) BuildMo(nb) endif() diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index fb7a9ecf5f..1c3cb5d6b2 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -3029,7 +3029,7 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) /* * Return TRUE if "buf" is the quickfix buffer. */ -int bt_quickfix(buf_T *buf) +int bt_quickfix(const buf_T *const buf) { return buf != NULL && buf->b_p_bt[0] == 'q'; } diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 6dbba30cf2..2e64eb864f 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -2040,7 +2040,7 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T } screen_line(row + wp->w_winrow, wp->w_wincol, wp->w_width, - wp->w_width, false, wp); + wp->w_width, false, wp, 0); /* * Update w_cline_height and w_cline_folded if the cursor line was @@ -2460,10 +2460,6 @@ win_line ( line_attr = win_hl_attr(wp, HLF_QFL); } - if (wp->w_hl_attr_normal != 0) { - line_attr = hl_combine_attr(wp->w_hl_attr_normal, line_attr); - } - if (line_attr != 0) { area_highlighting = true; } @@ -2920,7 +2916,8 @@ win_line ( && lnum == wp->w_cursor.lnum && vcol >= (long)wp->w_virtcol && filler_todo <= 0 ) { - screen_line(screen_row, wp->w_wincol, col, -wp->w_width, wp->w_p_rl, wp); + screen_line(screen_row, wp->w_wincol, col, -wp->w_width, wp->w_p_rl, wp, + wp->w_hl_attr_normal); // Pretend we have finished updating the window. Except when // 'cursorcolumn' is set. if (wp->w_p_cuc) { @@ -4012,7 +4009,8 @@ win_line ( col++; } } - screen_line(screen_row, wp->w_wincol, col, wp->w_width, wp->w_p_rl, wp); + screen_line(screen_row, wp->w_wincol, col, wp->w_width, wp->w_p_rl, wp, + wp->w_hl_attr_normal); row++; /* @@ -4230,7 +4228,7 @@ win_line ( || (n_extra != 0 && (c_extra != NUL || *p_extra != NUL))) ) { screen_line(screen_row, wp->w_wincol, col - boguscols, - wp->w_width, wp->w_p_rl, wp); + wp->w_width, wp->w_p_rl, wp, wp->w_hl_attr_normal); boguscols = 0; ++row; ++screen_row; @@ -4391,7 +4389,7 @@ static int char_needs_redraw(int off_from, int off_to, int cols) * When FALSE and "clear_width" > 0, clear columns "endcol" to "clear_width" */ static void screen_line(int row, int coloff, int endcol, - int clear_width, int rlflag, win_T *wp) + int clear_width, int rlflag, win_T *wp, int bg_attr) { unsigned off_from; unsigned off_to; @@ -4424,15 +4422,16 @@ static void screen_line(int row, int coloff, int endcol, /* Clear rest first, because it's left of the text. */ if (clear_width > 0) { while (col <= endcol && ScreenLines[off_to] == ' ' - && ScreenAttrs[off_to] == 0 + && ScreenAttrs[off_to] == bg_attr && (!enc_utf8 || ScreenLinesUC[off_to] == 0) ) { ++off_to; ++col; } - if (col <= endcol) - screen_fill(row, row + 1, col + coloff, - endcol + coloff + 1, ' ', ' ', 0); + if (col <= endcol) { + screen_fill(row, row + 1, col + coloff, endcol + coloff + 1, ' ', ' ', + bg_attr); + } } col = endcol + 1; off_to = LineOffset[row] + col + coloff; @@ -4440,6 +4439,13 @@ static void screen_line(int row, int coloff, int endcol, endcol = (clear_width > 0 ? clear_width : -clear_width); } + if (bg_attr) { + for (int c = col; c < endcol; c++) { + ScreenAttrs[off_from+c] = hl_combine_attr(bg_attr, + ScreenAttrs[off_from+c]); + } + } + redraw_next = char_needs_redraw(off_from, off_to, endcol - col); while (col < endcol) { @@ -4535,15 +4541,15 @@ static void screen_line(int row, int coloff, int endcol, /* blank out the rest of the line */ while (col < clear_width && ScreenLines[off_to] == ' ' - && ScreenAttrs[off_to] == 0 + && ScreenAttrs[off_to] == bg_attr && (!enc_utf8 || ScreenLinesUC[off_to] == 0) ) { ++off_to; ++col; } if (col < clear_width) { - screen_fill(row, row + 1, col + coloff, clear_width + coloff, - ' ', ' ', 0); + screen_fill(row, row + 1, col + coloff, clear_width + coloff, ' ', ' ', + bg_attr); off_to += clear_width - col; col = clear_width; } diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 605d9c30a6..f4454504a4 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -16,12 +16,14 @@ #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/vim.h" +#include "nvim/pos.h" #include "nvim/ascii.h" #include "nvim/shada.h" #include "nvim/message.h" #include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/mark.h" +#include "nvim/macros.h" #include "nvim/ops.h" #include "nvim/garray.h" #include "nvim/option.h" @@ -375,7 +377,8 @@ KHASH_MAP_INIT_STR(file_marks, FileMarks) /// Before actually writing most of the data is read to this structure. typedef struct { HistoryMergerState hms[HIST_COUNT]; ///< Structures for history merging. - PossiblyFreedShadaEntry global_marks[NGLOBALMARKS]; ///< All global marks. + PossiblyFreedShadaEntry global_marks[NMARKS]; ///< Named global marks. + PossiblyFreedShadaEntry numbered_marks[EXTRA_MARKS]; ///< Numbered marks. PossiblyFreedShadaEntry registers[NUM_SAVED_REGISTERS]; ///< All registers. PossiblyFreedShadaEntry jumps[JUMPLISTSIZE]; ///< All dumped jumps. size_t jumps_size; ///< Number of jumps occupied. @@ -808,7 +811,7 @@ static int open_shada_file_for_reading(const char *const fname, /// Wrapper for closing file descriptors static void close_file(void *cookie) { - const int error = file_free(cookie, true); + const int error = file_free(cookie, !!p_fs); if (error != 0) { emsgf(_(SERR "System error while closing ShaDa file: %s"), os_strerror(error)); @@ -2020,6 +2023,113 @@ shada_parse_msgpack_extra_bytes: return ret; } +/// Format shada entry for debugging purposes +/// +/// @param[in] entry ShaDa entry to format. +/// +/// @return string representing ShaDa entry in a static buffer. +static const char *shada_format_entry(const ShadaEntry entry) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_UNUSED FUNC_ATTR_NONNULL_RET +{ + static char ret[1024]; + ret[0] = 0; + vim_snprintf(S_LEN(ret), "[ ] ts=%" PRIu64 " "); + // ^ Space for `can_free_entry` + switch (entry.type) { + case kSDItemMissing: { + vim_snprintf_add(S_LEN(ret), "Missing"); + break; + } + case kSDItemHeader: { + vim_snprintf_add(S_LEN(ret), "Header { TODO }"); + break; + } + case kSDItemBufferList: { + vim_snprintf_add(S_LEN(ret), "BufferList { TODO }"); + break; + } + case kSDItemUnknown: { + vim_snprintf_add(S_LEN(ret), "Unknown { TODO }"); + break; + } + case kSDItemSearchPattern: { + vim_snprintf_add(S_LEN(ret), "SearchPattern { TODO }"); + break; + } + case kSDItemSubString: { + vim_snprintf_add(S_LEN(ret), "SubString { TODO }"); + break; + } + case kSDItemHistoryEntry: { + vim_snprintf_add(S_LEN(ret), "HistoryEntry { TODO }"); + break; + } + case kSDItemRegister: { + vim_snprintf_add(S_LEN(ret), "Register { TODO }"); + break; + } + case kSDItemVariable: { + vim_snprintf_add(S_LEN(ret), "Variable { TODO }"); + break; + } +#define FORMAT_MARK_ENTRY(entry_name, name_fmt, name_fmt_arg) \ + do { \ + typval_T ad_tv = { \ + .v_type = VAR_DICT, \ + .vval.v_dict = entry.data.filemark.additional_data \ + }; \ + size_t ad_len; \ + char *const ad = encode_tv2string(&ad_tv, &ad_len); \ + vim_snprintf_add( \ + S_LEN(ret), \ + entry_name " {" name_fmt " file=[%zu]\"%.512s\", " \ + "pos={l=%" PRIdLINENR ",c=%" PRIdCOLNR ",a=%" PRIdCOLNR "}, " \ + "ad={%p:[%zu]%.64s} }", \ + name_fmt_arg, \ + strlen(entry.data.filemark.fname), \ + entry.data.filemark.fname, \ + entry.data.filemark.mark.lnum, \ + entry.data.filemark.mark.col, \ + entry.data.filemark.mark.coladd, \ + entry.data.filemark.additional_data, \ + ad_len, \ + ad); \ + } while (0) + case kSDItemGlobalMark: { + FORMAT_MARK_ENTRY("GlobalMark", " name='%c',", entry.data.filemark.name); + break; + } + case kSDItemChange: { + FORMAT_MARK_ENTRY("Change", "%s", ""); + break; + } + case kSDItemLocalMark: { + FORMAT_MARK_ENTRY("LocalMark", " name='%c',", entry.data.filemark.name); + break; + } + case kSDItemJump: { + FORMAT_MARK_ENTRY("Jump", "%s", ""); + break; + } +#undef FORMAT_MARK_ENTRY + } + return ret; +} + +/// Format possibly freed shada entry for debugging purposes +/// +/// @param[in] entry ShaDa entry to format. +/// +/// @return string representing ShaDa entry in a static buffer. +static const char *shada_format_pfreed_entry( + const PossiblyFreedShadaEntry pfs_entry) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_UNUSED FUNC_ATTR_NONNULL_RET +{ + char *ret = (char *)shada_format_entry(pfs_entry.data); + ret[1] = (pfs_entry.can_free_entry ? 'T' : 'F'); + return ret; +} + /// Read and merge in ShaDa file, used when writing /// /// @param[in] sd_reader Structure containing file reader definition. @@ -2071,9 +2181,12 @@ static inline ShaDaWriteResult shada_read_when_writing( shada_free_shada_entry(&wms_entry->data); \ } \ } \ - wms_entry->can_free_entry = true; \ - wms_entry->data = (entry); \ + *wms_entry = pfs_entry; \ } while (0) + const PossiblyFreedShadaEntry pfs_entry = { + .can_free_entry = true, + .data = entry, + }; switch (entry.type) { case kSDItemMissing: { break; @@ -2125,13 +2238,49 @@ static inline ShaDaWriteResult shada_read_when_writing( break; } case kSDItemGlobalMark: { - const int idx = mark_global_index(entry.data.filemark.name); - if (idx < 0) { - ret = shada_pack_entry(packer, entry, 0); - shada_free_shada_entry(&entry); - break; + if (ascii_isdigit(entry.data.filemark.name)) { + bool processed_mark = false; + // Completely ignore numbered mark names, make a list sorted by + // timestamp. + for (size_t i = ARRAY_SIZE(wms->numbered_marks); i > 0; i--) { + ShadaEntry wms_entry = wms->numbered_marks[i - 1].data; + if (wms_entry.type != kSDItemGlobalMark) { + continue; + } + // Ignore duplicates. + if (wms_entry.timestamp == entry.timestamp + && (wms_entry.data.filemark.additional_data == NULL + && entry.data.filemark.additional_data == NULL) + && marks_equal(wms_entry.data.filemark.mark, + entry.data.filemark.mark) + && strcmp(wms_entry.data.filemark.fname, + entry.data.filemark.fname) == 0) { + shada_free_shada_entry(&entry); + processed_mark = true; + break; + } + if (wms_entry.timestamp >= entry.timestamp) { + processed_mark = true; + if (i < ARRAY_SIZE(wms->numbered_marks)) { + replace_numbered_mark(wms, i, pfs_entry); + } else { + shada_free_shada_entry(&entry); + } + break; + } + } + if (!processed_mark) { + replace_numbered_mark(wms, 0, pfs_entry); + } + } else { + const int idx = mark_global_index(entry.data.filemark.name); + if (idx < 0) { + ret = shada_pack_entry(packer, entry, 0); + shada_free_shada_entry(&entry); + break; + } + COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry); } - COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry); break; } case kSDItemChange: @@ -2175,8 +2324,7 @@ static inline ShaDaWriteResult shada_read_when_writing( shada_free_shada_entry(&wms_entry->data); } } - wms_entry->can_free_entry = true; - wms_entry->data = entry; + *wms_entry = pfs_entry; } } else { #define FREE_POSSIBLY_FREED_SHADA_ENTRY(entry) \ @@ -2216,6 +2364,20 @@ static inline ShaDaWriteResult shada_read_when_writing( return ret; } +/// Check whether buffer should be ignored +/// +/// @param[in] buf buf_T* to check. +/// @param[in] removable_bufs Cache of buffers ignored due to their location. +/// +/// @return true or false. +static inline bool ignore_buf(const buf_T *const buf, + khash_t(bufset) *const removable_bufs) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE +{ + return (buf->b_ffname == NULL || !buf->b_p_bl || bt_quickfix(buf) \ + || in_bufset(removable_bufs, buf)); +} + /// Get list of buffers to write to the shada file /// /// @param[in] removable_bufs Buffers which are ignored @@ -2227,11 +2389,9 @@ static inline ShadaEntry shada_get_buflist( { int max_bufs = get_shada_parameter('%'); size_t buf_count = 0; -#define IGNORE_BUF(buf)\ - (buf->b_ffname == NULL || !buf->b_p_bl || bt_quickfix(buf) \ - || in_bufset(removable_bufs, buf)) // NOLINT(whitespace/indent) FOR_ALL_BUFFERS(buf) { - if (!IGNORE_BUF(buf) && (max_bufs < 0 || buf_count < (size_t)max_bufs)) { + if (!ignore_buf(buf, removable_bufs) + && (max_bufs < 0 || buf_count < (size_t)max_bufs)) { buf_count++; } } @@ -2249,7 +2409,7 @@ static inline ShadaEntry shada_get_buflist( }; size_t i = 0; FOR_ALL_BUFFERS(buf) { - if (IGNORE_BUF(buf)) { + if (ignore_buf(buf, removable_bufs)) { continue; } if (i >= buf_count) { @@ -2263,7 +2423,6 @@ static inline ShadaEntry shada_get_buflist( i++; } -#undef IGNORE_BUF return buflist_entry; } @@ -2365,6 +2524,34 @@ static inline void shada_initialize_registers(WriteMergerState *const wms, } while (reg_iter != NULL); } +/// Replace numbered mark in WriteMergerState +/// +/// Frees the last mark, moves (including adjusting mark names) marks from idx +/// to the last-but-one one and saves the new mark at given index. +/// +/// @param[out] wms Merger state to adjust. +/// @param[in] idx Index at which new mark should be placed. +/// @param[in] entry New mark. +static inline void replace_numbered_mark(WriteMergerState *const wms, + const size_t idx, + const PossiblyFreedShadaEntry entry) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + if (ARRAY_LAST_ENTRY(wms->numbered_marks).can_free_entry) { + shada_free_shada_entry(&ARRAY_LAST_ENTRY(wms->numbered_marks).data); + } + for (size_t i = idx; i < ARRAY_SIZE(wms->numbered_marks) - 1; i++) { + if (wms->numbered_marks[i].data.type == kSDItemGlobalMark) { + wms->numbered_marks[i].data.data.filemark.name = (char)('0' + (int)i + 1); + } + } + memmove(wms->numbered_marks + idx + 1, wms->numbered_marks + idx, + sizeof(wms->numbered_marks[0]) + * (ARRAY_SIZE(wms->numbered_marks) - 1 - idx)); + wms->numbered_marks[idx] = entry; + wms->numbered_marks[idx].data.data.filemark.name = (char)('0' + (int)idx); +} + /// Write ShaDa file /// /// @param[in] sd_writer Structure containing file writer definition. @@ -2597,6 +2784,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, // Initialize global marks if (dump_global_marks) { const void *global_mark_iter = NULL; + size_t digit_mark_idx = 0; do { char name = NUL; xfmark_T fm; @@ -2619,7 +2807,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } fname = (const char *) buf->b_ffname; } - wms->global_marks[mark_global_index(name)] = (PossiblyFreedShadaEntry) { + const PossiblyFreedShadaEntry pf_entry = { .can_free_entry = false, .data = { .type = kSDItemGlobalMark, @@ -2629,11 +2817,16 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, .mark = fm.fmark.mark, .name = name, .additional_data = fm.fmark.additional_data, - .fname = (char *) fname, + .fname = (char *)fname, } } }, }; + if (ascii_isdigit(name)) { + replace_numbered_mark(wms, digit_mark_idx++, pf_entry); + } else { + wms->global_marks[mark_global_index(name)] = pf_entry; + } } while (global_mark_iter != NULL); } @@ -2715,6 +2908,26 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } } + // Update numbered marks: '0' should be replaced with the current position, + // '9' should be removed and all other marks shifted. + if (!ignore_buf(curbuf, &removable_bufs) && curwin->w_cursor.lnum != 0) { + replace_numbered_mark(wms, 0, (PossiblyFreedShadaEntry) { + .can_free_entry = false, + .data = { + .type = kSDItemGlobalMark, + .timestamp = os_time(), + .data = { + .filemark = { + .mark = curwin->w_cursor, + .name = '0', + .additional_data = NULL, + .fname = (char *)curbuf->b_ffname, + } + } + }, + }); + } + // Write the rest #define PACK_WMS_ARRAY(wms_array) \ do { \ @@ -2729,6 +2942,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } \ } while (0) PACK_WMS_ARRAY(wms->global_marks); + PACK_WMS_ARRAY(wms->numbered_marks); PACK_WMS_ARRAY(wms->registers); for (size_t i = 0; i < wms->jumps_size; i++) { if (shada_pack_pfreed_entry(packer, wms->jumps[i], max_kbyte) @@ -2823,6 +3037,7 @@ shada_write_exit: return ret; } +#undef IGNORE_BUF #undef PACK_STATIC_STR /// Write ShaDa file to a given location diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index 276b47536f..d006477c80 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -359,6 +359,15 @@ void terminal_resize(Terminal *term, uint16_t width, uint16_t height) return; } + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer && wp->w_buffer->terminal == term) { + const uint16_t win_width = + (uint16_t)(MAX(0, wp->w_width - win_col_off(wp))); + width = MAX(width, win_width); + height = (uint16_t)MAX(height, wp->w_height); + } + } + vterm_set_size(term->vt, height, width); vterm_screen_flush_damage(term->vts); term->pending_resize = true; @@ -1088,7 +1097,6 @@ static void refresh_terminal(Terminal *term) refresh_size(term, buf); refresh_scrollback(term, buf); refresh_screen(term, buf); - redraw_buf_later(buf, NOT_VALID); }); long ml_added = buf->b_ml.ml_line_count - ml_before; adjust_topline(term, buf, ml_added); @@ -1098,6 +1106,7 @@ static void refresh_timer_cb(TimeWatcher *watcher, void *data) { refresh_pending = false; if (exiting // Cannot redraw (requires event loop) during teardown/exit. + || (State & CMDPREVIEW) // WM_LIST (^D) is not redrawn, unlike the normal wildmenu. So we must // skip redraws to keep it visible. || wild_menu_showing == WM_LIST) { diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index a31e1843fc..4bfcbf8e79 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -20,13 +20,13 @@ SCRIPTS_DEFAULT = \ test40.out \ test42.out \ test48.out \ - test49.out \ test52.out \ test64.out \ ifneq ($(OS),Windows_NT) SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \ test17.out \ + test49.out \ endif diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 7090be7726..5c98455909 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -61,7 +61,7 @@ set nomore lang mess C " Always use forward slashes. -set shellslash +" set shellslash " Prepare for calling test_garbagecollect_now(). let v:testing = 1 diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index 7d6dd0c7ce..aac9fefef4 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -17,3 +17,11 @@ let &packpath = &rtp " Make sure $HOME does not get read or written. let $HOME = '/does/not/exist' + +" Use default shell on Windows to avoid segfault, caused by TUI +if has('win32') + let $SHELL = '' + let $TERM = '' + let &shell = empty($COMSPEC) ? exepath('cmd.exe') : $COMSPEC + set shellcmdflag=/s/c shellxquote=\" shellredir=>%s\ 2>&1 +endif diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 673246e1fb..be68e9ff9d 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -154,7 +154,7 @@ func Test_getcompletion() call assert_equal([], l) let l = getcompletion('', 'dir') - call assert_true(index(l, 'sautest/') >= 0) + call assert_true(index(l, expand('sautest/')) >= 0) let l = getcompletion('NoMatch', 'dir') call assert_equal([], l) @@ -246,7 +246,7 @@ func Test_getcompletion() " Command line completion tests let l = getcompletion('cd ', 'cmdline') - call assert_true(index(l, 'sautest/') >= 0) + call assert_true(index(l, expand('sautest/')) >= 0) let l = getcompletion('cd NoMatch', 'cmdline') call assert_equal([], l) let l = getcompletion('let v:n', 'cmdline') @@ -288,7 +288,7 @@ func Test_expand_star_star() call mkdir('a/b', 'p') call writefile(['asdfasdf'], 'a/b/fileXname') call feedkeys(":find **/fileXname\<Tab>\<CR>", 'xt') - call assert_equal('find a/b/fileXname', getreg(':')) + call assert_equal('find '.expand('a/b/fileXname'), getreg(':')) bwipe! call delete('a', 'rf') endfunc diff --git a/src/nvim/testdir/test_find_complete.vim b/src/nvim/testdir/test_find_complete.vim index 4732109ed0..1019246404 100644 --- a/src/nvim/testdir/test_find_complete.vim +++ b/src/nvim/testdir/test_find_complete.vim @@ -3,6 +3,8 @@ " Do all the tests in a separate window to avoid E211 when we recursively " delete the Xfind directory during cleanup func Test_find_complete() + let shellslash = &shellslash + set shellslash set belloff=all " On windows a stale "Xfind" directory may exist, remove it so that @@ -154,4 +156,5 @@ func Test_find_complete() exe 'cd ' . cwd call delete('Xfind', 'rf') set path& + let &shellslash = shellslash endfunc diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim index 06c48d8e76..4d4a902031 100644 --- a/src/nvim/testdir/test_help_tagjump.vim +++ b/src/nvim/testdir/test_help_tagjump.vim @@ -30,7 +30,7 @@ func Test_help_tagjump() help sp?it call assert_equal("help", &filetype) - call assert_true(getline('.') =~ '\*:split\*') + call assert_true(getline('.') =~ '\*'.(has('win32') ? 'split()' : ':split').'\*') helpclose help :? diff --git a/src/nvim/testdir/test_makeencoding.vim b/src/nvim/testdir/test_makeencoding.vim index a3d5538a47..6e4c7af821 100644 --- a/src/nvim/testdir/test_makeencoding.vim +++ b/src/nvim/testdir/test_makeencoding.vim @@ -13,12 +13,19 @@ endif let s:script = 'test_makeencoding.py' -let s:message_tbl = { +if has('iconv') + let s:message_tbl = { \ 'utf-8': 'ÀÈÌÒÙ こんにちは 你好', \ 'latin1': 'ÀÈÌÒÙ', \ 'cp932': 'こんにちは', \ 'cp936': '你好', \} +else + let s:message_tbl = { + \ 'utf-8': 'ÀÈÌÒÙ こんにちは 你好', + \ 'latin1': 'ÀÈÌÒÙ', + \} +endif " Tests for :cgetfile and :lgetfile. diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index a15d15213a..f8c3161b40 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -158,6 +158,8 @@ func Test_set_completion() call assert_equal('"set fileencodings:ucs-bom,utf-8,default,latin1', @:) " Expand directories. + let shellslash = &shellslash + set shellslash call feedkeys(":set cdpath=./\<C-A>\<C-B>\"\<CR>", 'tx') call assert_match('./samples/ ', @:) call assert_notmatch('./small.vim ', @:) @@ -168,6 +170,7 @@ func Test_set_completion() call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:) + let &shellslash = shellslash endfunc func Test_set_errors() diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index dd177fd633..85f93cf3da 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -850,17 +850,17 @@ func s:dir_stack_tests(cchar) let qf = g:Xgetlist() - call assert_equal('dir1/a/habits2.txt', bufname(qf[1].bufnr)) + call assert_equal(expand('dir1/a/habits2.txt'), bufname(qf[1].bufnr)) call assert_equal(1, qf[1].lnum) - call assert_equal('dir1/a/b/habits3.txt', bufname(qf[3].bufnr)) + call assert_equal(expand('dir1/a/b/habits3.txt'), bufname(qf[3].bufnr)) call assert_equal(2, qf[3].lnum) - call assert_equal('dir1/a/habits2.txt', bufname(qf[4].bufnr)) + call assert_equal(expand('dir1/a/habits2.txt'), bufname(qf[4].bufnr)) call assert_equal(7, qf[4].lnum) - call assert_equal('dir1/c/habits4.txt', bufname(qf[6].bufnr)) + call assert_equal(expand('dir1/c/habits4.txt'), bufname(qf[6].bufnr)) call assert_equal(3, qf[6].lnum) call assert_equal('habits1.txt', bufname(qf[9].bufnr)) call assert_equal(4, qf[9].lnum) - call assert_equal('dir2/habits5.txt', bufname(qf[11].bufnr)) + call assert_equal(expand('dir2/habits5.txt'), bufname(qf[11].bufnr)) call assert_equal(5, qf[11].lnum) let &efm=save_efm @@ -1065,7 +1065,7 @@ func Test_efm2() call assert_equal(8, len(l)) call assert_equal(89, l[4].lnum) call assert_equal(1, l[4].valid) - call assert_equal('unittests/dbfacadeTest.py', bufname(l[4].bufnr)) + call assert_equal(expand('unittests/dbfacadeTest.py'), bufname(l[4].bufnr)) " The following sequence of commands used to crash Vim set efm=%W%m @@ -1609,11 +1609,11 @@ func Test_two_windows() laddexpr 'one.txt:3:one one one' let loc_one = getloclist(one_id) - call assert_equal('Xone/a/one.txt', bufname(loc_one[1].bufnr)) + call assert_equal(expand('Xone/a/one.txt'), bufname(loc_one[1].bufnr)) call assert_equal(3, loc_one[1].lnum) let loc_two = getloclist(two_id) - call assert_equal('Xtwo/a/two.txt', bufname(loc_two[1].bufnr)) + call assert_equal(expand('Xtwo/a/two.txt'), bufname(loc_two[1].bufnr)) call assert_equal(5, loc_two[1].lnum) call win_gotoid(one_id) diff --git a/src/nvim/testdir/test_recover.vim b/src/nvim/testdir/test_recover.vim index 46d884a97c..beecb4cd0d 100644 --- a/src/nvim/testdir/test_recover.vim +++ b/src/nvim/testdir/test_recover.vim @@ -6,11 +6,6 @@ func Test_recover_root_dir() set dir=/ call assert_fails('recover', 'E305:') close! - - if has('win32') || filewritable('/') == 2 - " can write in / directory on MS-Windows - set dir=/notexist/ - endif call assert_fails('split Xtest', 'E303:') set dir& endfunc diff --git a/src/nvim/testdir/test_stat.vim b/src/nvim/testdir/test_stat.vim index 1239fe9427..0a09130b0c 100644 --- a/src/nvim/testdir/test_stat.vim +++ b/src/nvim/testdir/test_stat.vim @@ -86,7 +86,7 @@ func Test_win32_symlink_dir() let res = system('dir C:\Users /a') if match(res, '\C<SYMLINKD> *All Users') >= 0 " Get the filetype of the symlink. - call assert_equal('dir', getftype('C:\Users\All Users')) + call assert_equal('link', getftype('C:\Users\All Users')) endif endif endfunc diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim index ce9d110d82..d3c0594c03 100644 --- a/src/nvim/testdir/test_system.vim +++ b/src/nvim/testdir/test_system.vim @@ -5,14 +5,12 @@ function! Test_System() return endif let out = system('echo 123') - " On Windows we may get a trailing space. - if out != "123 \n" - call assert_equal("123\n", out) - endif + call assert_equal("123\n", out) let out = systemlist('echo 123') - " On Windows we may get a trailing space and CR. - if out != ["123 \r"] + if &shell =~# 'cmd.exe$' + call assert_equal(["123\r"], out) + else call assert_equal(['123'], out) endif diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index b04a6ce4f9..0362820687 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -170,11 +170,21 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) char buf[64]; size_t len = 0; int button, row, col; + static int last_pressed_button = 0; TermKeyMouseEvent ev; termkey_interpret_mouse(input->tk, key, &ev, &button, &row, &col); - if (ev != TERMKEY_MOUSE_PRESS && ev != TERMKEY_MOUSE_DRAG - && ev != TERMKEY_MOUSE_RELEASE) { + if ((ev == TERMKEY_MOUSE_RELEASE || ev == TERMKEY_MOUSE_DRAG) + && button == 0) { + // Some terminals (like urxvt) don't report which button was released. + // libtermkey reports button 0 in this case. + // For drag and release, we can reasonably infer the button to be the last + // pressed one. + button = last_pressed_button; + } + + if (button == 0 || (ev != TERMKEY_MOUSE_PRESS && ev != TERMKEY_MOUSE_DRAG + && ev != TERMKEY_MOUSE_RELEASE)) { return; } @@ -210,6 +220,7 @@ static void forward_mouse_event(TermInput *input, TermKeyKey *key) "ScrollWheelDown"); } else { len += (size_t)snprintf(buf + len, sizeof(buf) - len, "Mouse"); + last_pressed_button = button; } break; case TERMKEY_MOUSE_DRAG: diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 87f3cc2354..65957626cb 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -829,7 +829,7 @@ static void tui_cursor_goto(UI *ui, Integer row, Integer col) CursorShape tui_cursor_decode_shape(const char *shape_str) { - CursorShape shape = 0; + CursorShape shape; if (strequal(shape_str, "block")) { shape = SHAPE_BLOCK; } else if (strequal(shape_str, "vertical")) { @@ -837,7 +837,8 @@ CursorShape tui_cursor_decode_shape(const char *shape_str) } else if (strequal(shape_str, "horizontal")) { shape = SHAPE_HOR; } else { - EMSG2(_(e_invarg2), shape_str); + WLOG("Unknown shape value '%s'", shape_str); + shape = SHAPE_BLOCK; } return shape; } @@ -923,7 +924,6 @@ static void tui_set_mode(UI *ui, ModeShape mode) } TUIData *data = ui->data; cursorentry_T c = data->cursor_shapes[mode]; - int shape = c.shape; if (c.id != 0 && ui->rgb) { int attr = syn_id2attr(c.id); @@ -934,11 +934,12 @@ static void tui_set_mode(UI *ui, ModeShape mode) } } - switch (shape) { + int shape; + switch (c.shape) { + default: abort(); break; case SHAPE_BLOCK: shape = 1; break; case SHAPE_HOR: shape = 3; break; case SHAPE_VER: shape = 5; break; - default: WLOG("Unknown shape value %d", shape); break; } UNIBI_SET_NUM_VAR(data->params[0], shape + (int)(c.blinkon == 0)); unibi_out_ext(ui, data->unibi_ext.set_cursor_style); diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index 90fee146ab..1a7e55c11e 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -779,7 +779,8 @@ const char *viml_pexpr_repr_token(const ParserState *const pstate, eltkn_opt_scope_tab[token.data.opt.scope], (int)token.data.opt.len, token.data.opt.name) TKNARGS(kExprLexPlainIdentifier, "(scope=%s,autoload=%i)", - intchar2str(token.data.var.scope), (int)token.data.var.autoload) + intchar2str((int)token.data.var.scope), + (int)token.data.var.autoload) TKNARGS(kExprLexNumber, "(is_float=%i,base=%i,val=%lg)", (int)token.data.num.is_float, (int)token.data.num.base, |