diff options
Diffstat (limited to 'src')
181 files changed, 9208 insertions, 5282 deletions
diff --git a/src/clint.py b/src/clint.py index 862fdbc06b..675b67ccef 100755 --- a/src/clint.py +++ b/src/clint.py @@ -7,15 +7,14 @@ # modification, are permitted provided that the following conditions are # met: # -# * Redistributions of source code must retain the above copyright -# notice, this list of conditions and the following disclaimer. -# * Redistributions in binary form must reproduce the above -# copyright notice, this list of conditions and the following disclaimer -# in the documentation and/or other materials provided with the -# distribution. -# * Neither the name of Google Inc. nor the names of its -# contributors may be used to endorse or promote products derived from -# this software without specific prior written permission. +# * Redistributions of source code must retain the above copyright notice, +# this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# * Neither the name of Google Inc. nor the names of its contributors may be +# used to endorse or promote products derived from this software without +# specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT @@ -49,7 +48,6 @@ from __future__ import unicode_literals import codecs import copy -import fileinput import getopt import math # for log import os @@ -567,6 +565,7 @@ class _CppLintState(object): return self.record_errors_file = open(fname, 'w') + _cpplint_state = _CppLintState() @@ -2124,7 +2123,7 @@ def CheckExpressionAlignment(filename, clean_lines, linenum, error, startpos=0): 'Inner expression indentation should be 4') else: if (pos != level_starts[depth][0] + 1 - + (level_starts[depth][2] == '{')): + + (level_starts[depth][2] == '{')): if depth not in ignore_error_levels: error(filename, linenum, 'whitespace/alignment', 2, ('Inner expression should be aligned ' @@ -2297,7 +2296,7 @@ def CheckSpacing(filename, clean_lines, linenum, nesting_state, error): line = clean_lines.elided[linenum] # get rid of comments and strings # Don't try to do spacing checks for operator methods - line = re.sub(r'operator(==|!=|<|<<|<=|>=|>>|>)\(', 'operator\(', line) + line = re.sub(r'operator(==|!=|<|<<|<=|>=|>>|>)\(', r'operator\(', line) # We allow no-spaces around = within an if: "if ( (a=Foo()) == 0 )". # Otherwise not. Note we only check for non-spaces on *both* sides; @@ -2598,8 +2597,8 @@ def CheckBraces(filename, clean_lines, linenum, error): if (not Search(r'[,;:}{(]\s*$', prevline) and not Match(r'\s*#', prevline)): error(filename, linenum, 'whitespace/braces', 4, - '{ should almost always be at the end' - ' of the previous line') + '{ should almost always be at the end' + ' of the previous line') # Brace must appear after function signature, but on the *next* line if Match(r'^(?:\w+(?: ?\*+)? )+\w+\(', line): @@ -2611,9 +2610,13 @@ def CheckBraces(filename, clean_lines, linenum, error): 'Brace starting function body must be placed on its own line') else: func_start_linenum = end_linenum + 1 - while not clean_lines.lines[func_start_linenum] == '{': - attrline = Match(r'^((?!# *define).*?)(?:FUNC_ATTR|FUNC_API|REAL_FATTR)_\w+(?:\(\d+(, \d+)*\))?', - clean_lines.lines[func_start_linenum]) + while not clean_lines.lines[func_start_linenum] == "{": + attrline = Match( + r'^((?!# *define).*?)' + r'(?:FUNC_ATTR|FUNC_API|REAL_FATTR)_\w+' + r'(?:\(\d+(, \d+)*\))?', + clean_lines.lines[func_start_linenum], + ) if attrline: if len(attrline.group(1)) != 2: error(filename, func_start_linenum, @@ -3179,11 +3182,12 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, if not Search(r'eval/typval\.[ch]$', filename): match = Search(r'(?:\.|->)' r'(?:lv_(?:first|last|refcount|len|watch|idx(?:_item)?' - r'|copylist|lock)' - r'|li_(?:next|prev|tv))\b', line) + r'|copylist|lock)' + r'|li_(?:next|prev|tv))\b', line) if match: error(filename, linenum, 'runtime/deprecated', 4, - 'Accessing list_T internals directly is prohibited (hint: see commit d46e37cb4c71)') + 'Accessing list_T internals directly is prohibited ' + '(hint: see commit d46e37cb4c71)') # Check for suspicious usage of "if" like # } if (a == b) { diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index 8abc43c2aa..0fc7c780ca 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -7,6 +7,7 @@ if(USE_GCOV) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} --coverage") set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage") + add_definitions(-DUSE_GCOV) endif() endif() @@ -138,7 +139,6 @@ set(CONV_SOURCES eval.c ex_cmds.c ex_docmd.c - ex_getln.c fileio.c mbyte.c memline.c @@ -163,8 +163,8 @@ if(NOT MSVC) set_source_files_properties( ${CONV_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion") # gperf generates ANSI-C with incorrect linkage, ignore it. - check_c_compiler_flag(-Wno-static-in-inline HAS_WNO_STATIC_IN_INLINE_FLAG) - if(HAS_WNO_STATIC_IN_INLINE_FLAG) + check_c_compiler_flag(-Wstatic-in-inline HAS_WSTATIC_IN_INLINE) + if(HAS_WSTATIC_IN_INLINE) set_source_files_properties( eval.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-static-in-inline -Wno-conversion") else() @@ -383,6 +383,7 @@ endif() # Put these last on the link line, since multiple things may depend on them. list(APPEND NVIM_LINK_LIBRARIES + ${LIBLUV_LIBRARIES} ${LIBUV_LIBRARIES} ${MSGPACK_LIBRARIES} ${LIBVTERM_LIBRARIES} diff --git a/src/nvim/README.md b/src/nvim/README.md index 02464c2500..3c956cb2e9 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -60,9 +60,9 @@ Enable the sanitizer(s) via these environment variables: # Change to detect_leaks=1 to detect memory leaks (slower). export ASAN_OPTIONS="detect_leaks=0:log_path=$HOME/logs/asan" - export ASAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer + export ASAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer - export MSAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/bin/llvm-symbolizer + export MSAN_SYMBOLIZER_PATH=/usr/bin/llvm-symbolizer export TSAN_OPTIONS="external_symbolizer_path=/usr/lib/llvm-5.0/bin/llvm-symbolizer log_path=${HOME}/logs/tsan" Logs will be written to `${HOME}/logs/*san.PID`. diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 81b3851c53..41d7d8ba6b 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -12,8 +12,10 @@ #include "nvim/api/buffer.h" #include "nvim/api/private/helpers.h" #include "nvim/api/private/defs.h" +#include "nvim/lua/executor.h" #include "nvim/vim.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/getchar.h" @@ -108,9 +110,11 @@ String buffer_get_line(Buffer buffer, Integer index, Error *err) /// `nvim_buf_lines_event`. Otherwise, the first notification will be /// a `nvim_buf_changedtick_event`. Not used for lua callbacks. /// @param opts Optional parameters. -/// `on_lines`: lua callback received on change. +/// `on_lines`: lua callback received on change. /// `on_changedtick`: lua callback received on changedtick /// increment without text change. +/// `utf_sizes`: include UTF-32 and UTF-16 size of +/// the replaced region. /// See |api-buffer-updates-lua| for more information /// @param[out] err Error details, if any /// @return False when updates couldn't be enabled because the buffer isn't @@ -137,24 +141,44 @@ Boolean nvim_buf_attach(uint64_t channel_id, if (is_lua && strequal("on_lines", k.data)) { if (v->type != kObjectTypeLuaRef) { api_set_error(err, kErrorTypeValidation, "callback is not a function"); - return false; + goto error; } cb.on_lines = v->data.luaref; v->data.integer = LUA_NOREF; } else if (is_lua && strequal("on_changedtick", k.data)) { if (v->type != kObjectTypeLuaRef) { api_set_error(err, kErrorTypeValidation, "callback is not a function"); - return false; + goto error; } cb.on_changedtick = v->data.luaref; v->data.integer = LUA_NOREF; + } else if (is_lua && strequal("on_detach", k.data)) { + if (v->type != kObjectTypeLuaRef) { + api_set_error(err, kErrorTypeValidation, "callback is not a function"); + goto error; + } + cb.on_detach = v->data.luaref; + v->data.integer = LUA_NOREF; + } else if (is_lua && strequal("utf_sizes", k.data)) { + if (v->type != kObjectTypeBoolean) { + api_set_error(err, kErrorTypeValidation, "utf_sizes must be boolean"); + goto error; + } + cb.utf_sizes = v->data.boolean; } else { api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data); - return false; + goto error; } } return buf_updates_register(buf, channel_id, cb, send_buffer); + +error: + // TODO(bfredl): ASAN build should check that the ref table is empty? + executor_free_luaref(cb.on_lines); + executor_free_luaref(cb.on_changedtick); + executor_free_luaref(cb.on_detach); + return false; } /// Deactivates buffer-update events on the channel. @@ -1161,6 +1185,30 @@ free_exit: return 0; } +Dictionary nvim__buf_stats(Buffer buffer, Error *err) +{ + Dictionary rv = ARRAY_DICT_INIT; + + buf_T *buf = find_buffer_by_handle(buffer, err); + if (!buf) { + return rv; + } + + // Number of times the cached line was flushed. + // This should generally not increase while editing the same + // line in the same mode. + PUT(rv, "flush_count", INTEGER_OBJ(buf->flush_count)); + // lnum of current line + PUT(rv, "current_lnum", INTEGER_OBJ(buf->b_ml.ml_line_lnum)); + // whether the line has unflushed changes. + PUT(rv, "line_dirty", BOOLEAN_OBJ(buf->b_ml.ml_flags & ML_LINE_DIRTY)); + // NB: this should be zero at any time API functions are called, + // this exists to debug issues + PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes)); + + return rv; +} + // Check if deleting lines made the cursor position invalid. // Changed lines from `lo` to `hi`; added `extra` lines (negative if deleted). static void fix_cursor(linenr_T lo, linenr_T hi, linenr_T extra) diff --git a/src/nvim/api/private/dispatch.h b/src/nvim/api/private/dispatch.h index 39aabd708a..bad5a13934 100644 --- a/src/nvim/api/private/dispatch.h +++ b/src/nvim/api/private/dispatch.h @@ -11,8 +11,10 @@ typedef Object (*ApiDispatchWrapper)(uint64_t channel_id, /// functions of this type. typedef struct { ApiDispatchWrapper fn; - bool async; // function is always safe to run immediately instead of being - // put in a request queue for handling when nvim waits for input. + bool fast; // Function is safe to be executed immediately while running the + // uv loop (the loop is run very frequently due to breakcheck). + // If "fast" is false, the function is deferred, i e the call will + // be put in the event queue, for safe handling later. } MsgpackRpcRequestHandler; #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index 4f28ea5af3..20ed77afad 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -123,6 +123,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->mode_change = remote_ui_mode_change; ui->grid_scroll = remote_ui_grid_scroll; ui->hl_attr_define = remote_ui_hl_attr_define; + ui->hl_group_set = remote_ui_hl_group_set; ui->raw_line = remote_ui_raw_line; ui->bell = remote_ui_bell; ui->visual_bell = remote_ui_visual_bell; diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index a1d25766fe..41bf0af65b 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -73,6 +73,8 @@ void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, void hl_attr_define(Integer id, HlAttrs rgb_attrs, HlAttrs cterm_attrs, Array info) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_BRIDGE_IMPL; +void hl_group_set(String name, Integer id) + FUNC_API_SINCE(6) FUNC_API_BRIDGE_IMPL; void grid_resize(Integer grid, Integer width, Integer height) FUNC_API_SINCE(5) FUNC_API_REMOTE_IMPL FUNC_API_COMPOSITOR_IMPL; void grid_clear(Integer grid) diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index 2e8ca384b4..ed6a28bcda 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -21,6 +21,7 @@ #include "nvim/lua/executor.h" #include "nvim/vim.h" #include "nvim/buffer.h" +#include "nvim/context.h" #include "nvim/file_search.h" #include "nvim/highlight.h" #include "nvim/window.h" @@ -30,6 +31,7 @@ #include "nvim/memline.h" #include "nvim/memory.h" #include "nvim/message.h" +#include "nvim/popupmnu.h" #include "nvim/edit.h" #include "nvim/eval.h" #include "nvim/eval/typval.h" @@ -208,7 +210,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_csi) /// @return Number of bytes actually written (can be fewer than /// requested if the buffer becomes full). Integer nvim_input(String keys) - FUNC_API_SINCE(1) FUNC_API_ASYNC + FUNC_API_SINCE(1) FUNC_API_FAST { return (Integer)input_enqueue(keys); } @@ -237,7 +239,7 @@ Integer nvim_input(String keys) /// @param[out] err Error details, if any void nvim_input_mouse(String button, String action, String modifier, Integer grid, Integer row, Integer col, Error *err) - FUNC_API_SINCE(6) FUNC_API_ASYNC + FUNC_API_SINCE(6) FUNC_API_FAST { if (button.data == NULL || action.data == NULL) { goto error; @@ -1063,6 +1065,19 @@ fail: /// - `external`: GUI should display the window as an external /// top-level window. Currently accepts no other positioning /// configuration together with this. +/// - `style`: Configure the apparance of the window. Currently only takes +/// one non-empty value: +/// - "minimal" Nvim will display the window with many UI options +/// disabled. This is useful when displaing a temporary +/// float where the text should not be edited. Disables +/// 'number', 'relativenumber', 'cursorline', 'cursorcolumn', +/// 'spell' and 'list' options. 'signcolumn' is changed to +/// `auto`. The end-of-buffer region is hidden by setting +/// `eob` flag of 'fillchars' to a space char, and clearing +/// the |EndOfBuffer| region in 'winhighlight'. +/// +/// top-level window. Currently accepts no other positioning +/// configuration together with this. /// @param[out] err Error details, if any /// /// @return Window handle, or 0 on error @@ -1084,6 +1099,11 @@ Window nvim_open_win(Buffer buffer, Boolean enter, Dictionary config, if (buffer > 0) { nvim_win_set_buf(wp->handle, buffer, err); } + + if (fconfig.style == kWinStyleMinimal) { + win_set_minimal_style(wp); + didset_window_options(wp); + } return wp->handle; } @@ -1249,13 +1269,74 @@ Dictionary nvim_get_color_map(void) return colors; } +/// Gets a map of the current editor state. +/// +/// @param types Context types ("regs", "jumps", "buflist", "gvars", ...) +/// to gather, or NIL for all. +/// +/// @return map of global context +Dictionary nvim_get_context(Array types) + FUNC_API_SINCE(6) +{ + int int_types = 0; + if (types.size == 1 && types.items[0].type == kObjectTypeNil) { + int_types = kCtxAll; + } else { + for (size_t i = 0; i < types.size; i++) { + if (types.items[i].type == kObjectTypeString) { + const char *const current = types.items[i].data.string.data; + if (strequal(current, "regs")) { + int_types |= kCtxRegs; + } else if (strequal(current, "jumps")) { + int_types |= kCtxJumps; + } else if (strequal(current, "buflist")) { + int_types |= kCtxBuflist; + } else if (strequal(current, "gvars")) { + int_types |= kCtxGVars; + } else if (strequal(current, "sfuncs")) { + int_types |= kCtxSFuncs; + } else if (strequal(current, "funcs")) { + int_types |= kCtxFuncs; + } + } + } + } + + Context ctx = CONTEXT_INIT; + ctx_save(&ctx, int_types); + Dictionary dict = ctx_to_dict(&ctx); + ctx_free(&ctx); + return dict; +} + +/// Sets the current editor state to that in given context dictionary. +/// +/// @param ctx_dict Context dictionary. +Object nvim_load_context(Dictionary dict) + FUNC_API_SINCE(6) +{ + Context ctx = CONTEXT_INIT; + + int save_did_emsg = did_emsg; + did_emsg = false; + + ctx_from_dict(dict, &ctx); + if (!did_emsg) { + ctx_restore(&ctx, kCtxAll); + } + + ctx_free(&ctx); + + did_emsg = save_did_emsg; + return (Object)OBJECT_INIT; +} /// Gets the current mode. |mode()| /// "blocking" is true if Nvim is waiting for input. /// /// @returns Dictionary { "mode": String, "blocking": Boolean } Dictionary nvim_get_mode(void) - FUNC_API_SINCE(2) FUNC_API_ASYNC + FUNC_API_SINCE(2) FUNC_API_FAST { Dictionary rv = ARRAY_DICT_INIT; char *modestr = get_mode(); @@ -1341,7 +1422,7 @@ Dictionary nvim_get_commands(Dictionary opts, Error *err) /// /// @returns 2-tuple [{channel-id}, {api-metadata}] Array nvim_get_api_info(uint64_t channel_id) - FUNC_API_SINCE(1) FUNC_API_ASYNC FUNC_API_REMOTE_ONLY + FUNC_API_SINCE(1) FUNC_API_FAST FUNC_API_REMOTE_ONLY { Array rv = ARRAY_DICT_INIT; @@ -1651,7 +1732,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; /// @param[out] err Error details, if any Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, Error *err) - FUNC_API_SINCE(4) FUNC_API_ASYNC + FUNC_API_SINCE(4) FUNC_API_FAST { int pflags = 0; for (size_t i = 0 ; i < flags.size ; i++) { @@ -2239,16 +2320,33 @@ void nvim_select_popupmenu_item(Integer item, Boolean insert, Boolean finish, } /// NB: if your UI doesn't use hlstate, this will not return hlstate first time -Array nvim__inspect_cell(Integer row, Integer col, Error *err) +Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err) { Array ret = ARRAY_DICT_INIT; - if (row < 0 || row >= default_grid.Rows - || col < 0 || col >= default_grid.Columns) { + + // TODO(bfredl): if grid == 0 we should read from the compositor's buffer. + // The only problem is that it does not yet exist. + ScreenGrid *g = &default_grid; + if (grid == pum_grid.handle) { + g = &pum_grid; + } else if (grid > 1) { + win_T *wp = get_win_by_grid_handle((handle_T)grid); + if (wp != NULL && wp->w_grid.chars != NULL) { + g = &wp->w_grid; + } else { + api_set_error(err, kErrorTypeValidation, + "No grid with the given handle"); + return ret; + } + } + + if (row < 0 || row >= g->Rows + || col < 0 || col >= g->Columns) { return ret; } - size_t off = default_grid.line_offset[(size_t)row] + (size_t)col; - ADD(ret, STRING_OBJ(cstr_to_string((char *)default_grid.chars[off]))); - int attr = default_grid.attrs[off]; + size_t off = g->line_offset[(size_t)row] + (size_t)col; + ADD(ret, STRING_OBJ(cstr_to_string((char *)g->chars[off]))); + int attr = g->attrs[off]; ADD(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, err))); // will not work first time if (!highlight_use_hlstate()) { diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c index 9fd1818a5c..e279edebde 100644 --- a/src/nvim/api/window.c +++ b/src/nvim/api/window.c @@ -6,6 +6,8 @@ #include <stdlib.h> #include <limits.h> +#include "nvim/ascii.h" +#include "nvim/globals.h" #include "nvim/api/window.h" #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" @@ -13,11 +15,11 @@ #include "nvim/vim.h" #include "nvim/buffer.h" #include "nvim/cursor.h" +#include "nvim/option.h" #include "nvim/window.h" #include "nvim/screen.h" #include "nvim/move.h" - /// Gets the current buffer in a window /// /// @param window Window handle @@ -475,6 +477,10 @@ void nvim_win_set_config(Window window, Dictionary config, Error *err) win_config_float(win, fconfig); win->w_pos_changed = true; } + if (fconfig.style == kWinStyleMinimal) { + win_set_minimal_style(win); + didset_window_options(win); + } } /// Return window configuration. @@ -521,9 +527,7 @@ Dictionary nvim_win_get_config(Window window, Error *err) return rv; } -/// Close a window. -/// -/// This is equivalent to |:close| with count except that it takes a window id. +/// Closes the window (like |:close| with a |window-ID|). /// /// @param window Window handle /// @param force Behave like `:close!` The last window of a buffer with @@ -537,8 +541,17 @@ void nvim_win_close(Window window, Boolean force, Error *err) if (!win) { return; } - tabpage_T *tabpage = win_find_tabpage(win); + if (cmdwin_type != 0) { + if (win == curwin) { + cmdwin_result = Ctrl_C; + } else { + api_set_error(err, kErrorTypeException, "%s", _(e_cmdwin)); + } + return; + } + + tabpage_T *tabpage = win_find_tabpage(win); TryState tstate; try_enter(&tstate); ex_win_close(force, win, tabpage == curtab ? NULL : tabpage); diff --git a/src/nvim/assert.h b/src/nvim/assert.h index 34734f294d..1361879876 100644 --- a/src/nvim/assert.h +++ b/src/nvim/assert.h @@ -133,8 +133,10 @@ /// Alternative for compilers without __builtin_xx_overflow ? /// https://stackoverflow.com/a/44830670/152142 /// -/// @param MAX Maximum value of the narrowest type of operand. -/// Not used if compiler supports __builtin_add_overflow. +/// @param a Operand 1. +/// @param b Operand 2. +/// @param c Where to store the result. +/// @param t Result type. Not used if compiler supports __builtin_add_overflow. #ifdef HAVE_BUILTIN_ADD_OVERFLOW # define STRICT_ADD(a, b, c, t) \ do { \ @@ -150,6 +152,7 @@ /// @def STRICT_SUB /// @brief Subtracts (a - b) and stores result in `c`. Aborts on overflow. +/// @see STRICT_ADD #ifdef HAVE_BUILTIN_ADD_OVERFLOW # define STRICT_SUB(a, b, c, t) \ do { \ diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua index ef528f72b8..12fc8fd02a 100644 --- a/src/nvim/auevents.lua +++ b/src/nvim/auevents.lua @@ -77,6 +77,7 @@ return { 'Signal', -- after nvim process received a signal 'SourceCmd', -- sourcing a Vim script using command 'SourcePre', -- before sourcing a Vim script + 'SourcePost', -- after sourcing a Vim script 'SpellFileMissing', -- spell file missing 'StdinReadPost', -- after reading from stdin 'StdinReadPre', -- before reading from stdin diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 52dc359716..a545112360 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -29,8 +29,10 @@ #include "nvim/api/vim.h" #include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/channel.h" #include "nvim/vim.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" @@ -1425,7 +1427,7 @@ do_buffer( } } if (bufIsChanged(curbuf)) { - EMSG(_(e_nowrtmsg)); + no_write_message(); return FAIL; } } @@ -1626,6 +1628,16 @@ void do_autochdir(void) } } +void no_write_message(void) +{ + EMSG(_("E37: No write since last change (add ! to override)")); +} + +void no_write_message_nobang(void) +{ + EMSG(_("E37: No write since last change")); +} + // // functions for dealing with the buffer list // @@ -2532,6 +2544,11 @@ void get_winopts(buf_T *buf) } else copy_winopt(&curwin->w_allbuf_opt, &curwin->w_onebuf_opt); + if (curwin->w_float_config.style == kWinStyleMinimal) { + didset_window_options(curwin); + win_set_minimal_style(curwin); + } + // Set 'foldlevel' to 'foldlevelstart' if it's not negative. if (p_fdls >= 0) { curwin->w_p_fdl = p_fdls; @@ -2595,14 +2612,23 @@ void buflist_list(exarg_T *eap) continue; } + const int changed_char = (buf->b_flags & BF_READERR) + ? 'x' + : (bufIsChanged(buf) ? '+' : ' '); + int ro_char = !MODIFIABLE(buf) ? '-' : (buf->b_p_ro ? '=' : ' '); + if (buf->terminal) { + ro_char = channel_job_running((uint64_t)buf->b_p_channel) ? 'R' : 'F'; + } + msg_putchar('\n'); - len = vim_snprintf((char *)IObuff, IOSIZE - 20, "%3d%c%c%c%c%c \"%s\"", + len = vim_snprintf( + (char *)IObuff, IOSIZE - 20, "%3d%c%c%c%c%c \"%s\"", buf->b_fnum, buf->b_p_bl ? ' ' : 'u', buf == curbuf ? '%' : (curwin->w_alt_fnum == buf->b_fnum ? '#' : ' '), buf->b_ml.ml_mfp == NULL ? ' ' : (buf->b_nwindows == 0 ? 'h' : 'a'), - !MODIFIABLE(buf) ? '-' : (buf->b_p_ro ? '=' : ' '), - (buf->b_flags & BF_READERR) ? 'x' : (bufIsChanged(buf) ? '+' : ' '), + ro_char, + changed_char, NameBuff); if (len > IOSIZE - 20) { @@ -3341,7 +3367,6 @@ int build_stl_str_hl( char_u *usefmt = fmt; const int save_must_redraw = must_redraw; const int save_redr_type = curwin->w_redr_type; - const int save_highlight_shcnaged = need_highlight_changed; // When the format starts with "%!" then evaluate it as an expression and // use the result as the actual format string. @@ -4405,12 +4430,12 @@ int build_stl_str_hl( cur_tab_rec->def.func = NULL; } - // We do not want redrawing a stausline, ruler, title, etc. to trigger - // another redraw, it may cause an endless loop. This happens when a - // statusline changes a highlight group. - must_redraw = save_must_redraw; - curwin->w_redr_type = save_redr_type; - need_highlight_changed = save_highlight_shcnaged; + // When inside update_screen we do not want redrawing a stausline, ruler, + // title, etc. to trigger another redraw, it may cause an endless loop. + if (updating_screen) { + must_redraw = save_must_redraw; + curwin->w_redr_type = save_redr_type; + } return width; } @@ -5147,18 +5172,21 @@ chk_modeline( // Return true if "buf" is a help buffer. bool bt_help(const buf_T *const buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && buf->b_help; } // Return true if "buf" is the quickfix buffer. bool bt_quickfix(const buf_T *const buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && buf->b_p_bt[0] == 'q'; } // Return true if "buf" is a terminal buffer. bool bt_terminal(const buf_T *const buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && buf->b_p_bt[0] == 't'; } @@ -5166,6 +5194,7 @@ bool bt_terminal(const buf_T *const buf) // Return true if "buf" is a "nofile", "acwrite" or "terminal" buffer. // This means the buffer name is not a file name. bool bt_nofile(const buf_T *const buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && ((buf->b_p_bt[0] == 'n' && buf->b_p_bt[2] == 'f') || buf->b_p_bt[0] == 'a' || buf->terminal); @@ -5173,11 +5202,13 @@ bool bt_nofile(const buf_T *const buf) // Return true if "buf" is a "nowrite", "nofile" or "terminal" buffer. bool bt_dontwrite(const buf_T *const buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { return buf != NULL && (buf->b_p_bt[0] == 'n' || buf->terminal); } bool bt_dontwrite_msg(const buf_T *const buf) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { if (bt_dontwrite(buf)) { EMSG(_("E382: Cannot write, 'buftype' option is set")); @@ -5223,8 +5254,8 @@ char_u *buf_spname(buf_T *buf) // There is no _file_ when 'buftype' is "nofile", b_sfname // contains the name as specified by the user. if (bt_nofile(buf)) { - if (buf->b_sfname != NULL) { - return buf->b_sfname; + if (buf->b_fname != NULL) { + return buf->b_fname; } return (char_u *)_("[Scratch]"); } diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index 117a9183a4..b11eaefdd0 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -252,6 +252,8 @@ typedef struct { # define w_p_fcs w_onebuf_opt.wo_fcs // 'fillchars' char_u *wo_lcs; # define w_p_lcs w_onebuf_opt.wo_lcs // 'listchars' + long wo_winbl; +# define w_p_winbl w_onebuf_opt.wo_winbl // 'winblend' LastSet wo_scriptID[WV_COUNT]; // SIDs for window-local options # define w_p_scriptID w_onebuf_opt.wo_scriptID @@ -456,8 +458,10 @@ typedef TV_DICTITEM_STRUCT(sizeof("changedtick")) ChangedtickDictItem; typedef struct { LuaRef on_lines; LuaRef on_changedtick; + LuaRef on_detach; + bool utf_sizes; } BufUpdateCallbacks; -#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF } +#define BUF_UPDATE_CALLBACKS_INIT { LUA_NOREF, LUA_NOREF, LUA_NOREF, false } #define BUF_HAS_QF_ENTRY 1 #define BUF_HAS_LL_ENTRY 2 @@ -799,11 +803,26 @@ struct file_buffer { kvec_t(BufhlLine *) b_bufhl_move_space; // temporary space for highlights - // array of channelids which have asked to receive updates for this + // array of channel_id:s which have asked to receive updates for this // buffer. kvec_t(uint64_t) update_channels; + // array of lua callbacks for buffer updates. kvec_t(BufUpdateCallbacks) update_callbacks; + // whether an update callback has requested codepoint size of deleted regions. + bool update_need_codepoints; + + // Measurements of the deleted or replaced region since the last update + // event. Some consumers of buffer changes need to know the byte size (like + // tree-sitter) or the corresponding UTF-32/UTF-16 size (like LSP) of the + // deleted text. + size_t deleted_bytes; + size_t deleted_codepoints; + size_t deleted_codeunits; + + // The number for times the current line has been flushed in the memline. + int flush_count; + int b_diff_failed; // internal diff failed for this buffer }; @@ -969,7 +988,6 @@ struct matchitem { }; typedef int FloatAnchor; -typedef int FloatRelative; enum { kFloatAnchorEast = 1, @@ -982,15 +1000,20 @@ enum { // SE -> kFloatAnchorSouth | kFloatAnchorEast EXTERN const char *const float_anchor_str[] INIT(= { "NW", "NE", "SW", "SE" }); -enum { +typedef enum { kFloatRelativeEditor = 0, kFloatRelativeWindow = 1, kFloatRelativeCursor = 2, -}; +} FloatRelative; EXTERN const char *const float_relative_str[] INIT(= { "editor", "window", "cursor" }); +typedef enum { + kWinStyleUnused = 0, + kWinStyleMinimal, /// Minimal UI: no number column, eob markers, etc +} WinStyle; + typedef struct { Window window; int height, width; @@ -999,12 +1022,14 @@ typedef struct { FloatRelative relative; bool external; bool focusable; + WinStyle style; } FloatConfig; #define FLOAT_CONFIG_INIT ((FloatConfig){ .height = 0, .width = 0, \ .row = 0, .col = 0, .anchor = 0, \ .relative = 0, .external = false, \ - .focusable = true }) + .focusable = true, \ + .style = kWinStyleUnused }) // Structure to store last cursor position and topline. Used by check_lnums() // and reset_lnums(). diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c index 2515e3f8aa..3604578b50 100644 --- a/src/nvim/buffer_updates.c +++ b/src/nvim/buffer_updates.c @@ -26,6 +26,9 @@ bool buf_updates_register(buf_T *buf, uint64_t channel_id, if (channel_id == LUA_INTERNAL_CALL) { kv_push(buf->update_callbacks, cb); + if (cb.utf_sizes) { + buf->update_need_codepoints = true; + } return true; } @@ -143,7 +146,21 @@ void buf_updates_unregister_all(buf_T *buf) } for (size_t i = 0; i < kv_size(buf->update_callbacks); i++) { - free_update_callbacks(kv_A(buf->update_callbacks, i)); + BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i); + if (cb.on_detach != LUA_NOREF) { + Array args = ARRAY_DICT_INIT; + Object items[1]; + args.size = 1; + args.items = items; + + // the first argument is always the buffer handle + args.items[0] = BUFFER_OBJ(buf->handle); + + textlock++; + executor_exec_lua_cb(cb.on_detach, "detach", args, false); + textlock--; + } + free_update_callbacks(cb); } kv_destroy(buf->update_callbacks); kv_init(buf->update_callbacks); @@ -155,6 +172,10 @@ void buf_updates_send_changes(buf_T *buf, int64_t num_removed, bool send_tick) { + size_t deleted_codepoints, deleted_codeunits; + size_t deleted_bytes = ml_flush_deleted_bytes(buf, &deleted_codepoints, + &deleted_codeunits); + if (!buf_updates_active(buf)) { return; } @@ -217,8 +238,8 @@ void buf_updates_send_changes(buf_T *buf, bool keep = true; if (cb.on_lines != LUA_NOREF) { Array args = ARRAY_DICT_INIT; - Object items[5]; - args.size = 5; + Object items[8]; + args.size = 6; // may be increased to 8 below args.items = items; // the first argument is always the buffer handle @@ -236,14 +257,22 @@ void buf_updates_send_changes(buf_T *buf, // the last line in the updated range args.items[4] = INTEGER_OBJ(firstline - 1 + num_added); + // byte count of previous contents + args.items[5] = INTEGER_OBJ((Integer)deleted_bytes); + if (cb.utf_sizes) { + args.size = 8; + args.items[6] = INTEGER_OBJ((Integer)deleted_codepoints); + args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits); + } textlock++; - Object res = executor_exec_lua_cb(cb.on_lines, "lines", args); + Object res = executor_exec_lua_cb(cb.on_lines, "lines", args, true); textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { free_update_callbacks(cb); keep = false; } + api_free_object(res); } if (keep) { kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i); @@ -276,13 +305,15 @@ void buf_updates_changedtick(buf_T *buf) args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf)); textlock++; - Object res = executor_exec_lua_cb(cb.on_changedtick, "changedtick", args); + Object res = executor_exec_lua_cb(cb.on_changedtick, "changedtick", + args, true); textlock--; if (res.type == kObjectTypeBoolean && res.data.boolean == true) { free_update_callbacks(cb); keep = false; } + api_free_object(res); } if (keep) { kv_A(buf->update_callbacks, j++) = kv_A(buf->update_callbacks, i); diff --git a/src/nvim/change.c b/src/nvim/change.c new file mode 100644 index 0000000000..2363578139 --- /dev/null +++ b/src/nvim/change.c @@ -0,0 +1,1798 @@ +// 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 + +/// change.c: functions related to changing text + +#include "nvim/assert.h" +#include "nvim/buffer.h" +#include "nvim/buffer_updates.h" +#include "nvim/change.h" +#include "nvim/charset.h" +#include "nvim/cursor.h" +#include "nvim/diff.h" +#include "nvim/edit.h" +#include "nvim/eval.h" +#include "nvim/fileio.h" +#include "nvim/fold.h" +#include "nvim/indent.h" +#include "nvim/indent_c.h" +#include "nvim/mark.h" +#include "nvim/memline.h" +#include "nvim/misc1.h" +#include "nvim/move.h" +#include "nvim/option.h" +#include "nvim/screen.h" +#include "nvim/search.h" +#include "nvim/state.h" +#include "nvim/ui.h" +#include "nvim/undo.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "change.c.generated.h" +#endif + +/// If the file is readonly, give a warning message with the first change. +/// Don't do this for autocommands. +/// Doesn't use emsg(), because it flushes the macro buffer. +/// If we have undone all changes b_changed will be false, but "b_did_warn" +/// will be true. +/// "col" is the column for the message; non-zero when in insert mode and +/// 'showmode' is on. +/// Careful: may trigger autocommands that reload the buffer. +void change_warning(int col) +{ + static char *w_readonly = N_("W10: Warning: Changing a readonly file"); + + if (curbuf->b_did_warn == false + && curbufIsChanged() == 0 + && !autocmd_busy + && curbuf->b_p_ro) { + curbuf_lock++; + apply_autocmds(EVENT_FILECHANGEDRO, NULL, NULL, false, curbuf); + curbuf_lock--; + if (!curbuf->b_p_ro) { + return; + } + // Do what msg() does, but with a column offset if the warning should + // be after the mode message. + msg_start(); + if (msg_row == Rows - 1) { + msg_col = col; + } + msg_source(HL_ATTR(HLF_W)); + msg_ext_set_kind("wmsg"); + MSG_PUTS_ATTR(_(w_readonly), HL_ATTR(HLF_W) | MSG_HIST); + set_vim_var_string(VV_WARNINGMSG, _(w_readonly), -1); + msg_clr_eos(); + (void)msg_end(); + if (msg_silent == 0 && !silent_mode && ui_active()) { + ui_flush(); + os_delay(1000L, true); // give the user time to think about it + } + curbuf->b_did_warn = true; + redraw_cmdline = false; // don't redraw and erase the message + if (msg_row < Rows - 1) { + showmode(); + } + } +} + +/// Call this function when something in the current buffer is changed. +/// +/// Most often called through changed_bytes() and changed_lines(), which also +/// mark the area of the display to be redrawn. +/// +/// Careful: may trigger autocommands that reload the buffer. +void changed(void) +{ + if (!curbuf->b_changed) { + int save_msg_scroll = msg_scroll; + + // Give a warning about changing a read-only file. This may also + // check-out the file, thus change "curbuf"! + change_warning(0); + + // Create a swap file if that is wanted. + // Don't do this for "nofile" and "nowrite" buffer types. + if (curbuf->b_may_swap + && !bt_dontwrite(curbuf) + ) { + int save_need_wait_return = need_wait_return; + + need_wait_return = false; + ml_open_file(curbuf); + + // The ml_open_file() can cause an ATTENTION message. + // Wait two seconds, to make sure the user reads this unexpected + // message. Since we could be anywhere, call wait_return() now, + // and don't let the emsg() set msg_scroll. + if (need_wait_return && emsg_silent == 0) { + ui_flush(); + os_delay(2000L, true); + wait_return(true); + msg_scroll = save_msg_scroll; + } else { + need_wait_return = save_need_wait_return; + } + } + changed_internal(); + } + buf_inc_changedtick(curbuf); + + // If a pattern is highlighted, the position may now be invalid. + highlight_match = false; +} + +/// Internal part of changed(), no user interaction. +/// Also used for recovery. +void changed_internal(void) +{ + curbuf->b_changed = true; + ml_setflags(curbuf); + check_status(curbuf); + redraw_tabline = true; + need_maketitle = true; // set window title later +} + +/// Common code for when a change was made. +/// See changed_lines() for the arguments. +/// Careful: may trigger autocommands that reload the buffer. +static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, + long xtra) +{ + int i; + int cols; + pos_T *p; + int add; + + // mark the buffer as modified + changed(); + + if (curwin->w_p_diff && diff_internal()) { + curtab->tp_diff_update = true; + } + + // set the '. mark + if (!cmdmod.keepjumps) { + RESET_FMARK(&curbuf->b_last_change, ((pos_T) { lnum, col, 0 }), 0); + + // Create a new entry if a new undo-able change was started or we + // don't have an entry yet. + if (curbuf->b_new_change || curbuf->b_changelistlen == 0) { + if (curbuf->b_changelistlen == 0) { + add = true; + } else { + // Don't create a new entry when the line number is the same + // as the last one and the column is not too far away. Avoids + // creating many entries for typing "xxxxx". + p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark; + if (p->lnum != lnum) { + add = true; + } else { + cols = comp_textwidth(false); + if (cols == 0) { + cols = 79; + } + add = (p->col + cols < col || col + cols < p->col); + } + } + if (add) { + // This is the first of a new sequence of undo-able changes + // and it's at some distance of the last change. Use a new + // position in the changelist. + curbuf->b_new_change = false; + + if (curbuf->b_changelistlen == JUMPLISTSIZE) { + // changelist is full: remove oldest entry + curbuf->b_changelistlen = JUMPLISTSIZE - 1; + memmove(curbuf->b_changelist, curbuf->b_changelist + 1, + sizeof(curbuf->b_changelist[0]) * (JUMPLISTSIZE - 1)); + FOR_ALL_TAB_WINDOWS(tp, wp) { + // Correct position in changelist for other windows on + // this buffer. + if (wp->w_buffer == curbuf && wp->w_changelistidx > 0) { + wp->w_changelistidx--; + } + } + } + FOR_ALL_TAB_WINDOWS(tp, wp) { + // For other windows, if the position in the changelist is + // at the end it stays at the end. + if (wp->w_buffer == curbuf + && wp->w_changelistidx == curbuf->b_changelistlen) { + wp->w_changelistidx++; + } + } + curbuf->b_changelistlen++; + } + } + curbuf->b_changelist[curbuf->b_changelistlen - 1] = + curbuf->b_last_change; + // The current window is always after the last change, so that "g," + // takes you back to it. + curwin->w_changelistidx = curbuf->b_changelistlen; + } + + FOR_ALL_TAB_WINDOWS(tp, wp) { + if (wp->w_buffer == curbuf) { + // Mark this window to be redrawn later. + if (wp->w_redr_type < VALID) { + wp->w_redr_type = VALID; + } + + // Check if a change in the buffer has invalidated the cached + // values for the cursor. + // Update the folds for this window. Can't postpone this, because + // a following operator might work on the whole fold: ">>dd". + foldUpdate(wp, lnum, lnume + xtra - 1); + + // The change may cause lines above or below the change to become + // included in a fold. Set lnum/lnume to the first/last line that + // might be displayed differently. + // Set w_cline_folded here as an efficient way to update it when + // inserting lines just above a closed fold. */ + bool folded = hasFoldingWin(wp, lnum, &lnum, NULL, false, NULL); + if (wp->w_cursor.lnum == lnum) { + wp->w_cline_folded = folded; + } + folded = hasFoldingWin(wp, lnume, NULL, &lnume, false, NULL); + if (wp->w_cursor.lnum == lnume) { + wp->w_cline_folded = folded; + } + + // If the changed line is in a range of previously folded lines, + // compare with the first line in that range. + if (wp->w_cursor.lnum <= lnum) { + i = find_wl_entry(wp, lnum); + if (i >= 0 && wp->w_cursor.lnum > wp->w_lines[i].wl_lnum) { + changed_line_abv_curs_win(wp); + } + } + + if (wp->w_cursor.lnum > lnum) { + changed_line_abv_curs_win(wp); + } else if (wp->w_cursor.lnum == lnum && wp->w_cursor.col >= col) { + changed_cline_bef_curs_win(wp); + } + if (wp->w_botline >= lnum) { + // Assume that botline doesn't change (inserted lines make + // other lines scroll down below botline). + approximate_botline_win(wp); + } + + // Check if any w_lines[] entries have become invalid. + // For entries below the change: Correct the lnums for + // inserted/deleted lines. Makes it possible to stop displaying + // after the change. + for (i = 0; i < wp->w_lines_valid; i++) { + if (wp->w_lines[i].wl_valid) { + if (wp->w_lines[i].wl_lnum >= lnum) { + if (wp->w_lines[i].wl_lnum < lnume) { + // line included in change + wp->w_lines[i].wl_valid = false; + } else if (xtra != 0) { + // line below change + wp->w_lines[i].wl_lnum += xtra; + wp->w_lines[i].wl_lastlnum += xtra; + } + } else if (wp->w_lines[i].wl_lastlnum >= lnum) { + // change somewhere inside this range of folded lines, + // may need to be redrawn + wp->w_lines[i].wl_valid = false; + } + } + } + + // Take care of side effects for setting w_topline when folds have + // changed. Esp. when the buffer was changed in another window. + if (hasAnyFolding(wp)) { + set_topline(wp, wp->w_topline); + } + + // relative numbering may require updating more + if (wp->w_p_rnu) { + redraw_win_later(wp, SOME_VALID); + } + } + } + + // Call update_screen() later, which checks out what needs to be redrawn, + // since it notices b_mod_set and then uses b_mod_*. + if (must_redraw < VALID) { + must_redraw = VALID; + } + + // when the cursor line is changed always trigger CursorMoved + if (lnum <= curwin->w_cursor.lnum + && lnume + (xtra < 0 ? -xtra : xtra) > curwin->w_cursor.lnum) { + curwin->w_last_cursormoved.lnum = 0; + } +} + +static void changedOneline(buf_T *buf, linenr_T lnum) +{ + if (buf->b_mod_set) { + // find the maximum area that must be redisplayed + if (lnum < buf->b_mod_top) { + buf->b_mod_top = lnum; + } else if (lnum >= buf->b_mod_bot) { + buf->b_mod_bot = lnum + 1; + } + } else { + // set the area that must be redisplayed to one line + buf->b_mod_set = true; + buf->b_mod_top = lnum; + buf->b_mod_bot = lnum + 1; + buf->b_mod_xlines = 0; + } +} + +/// Changed bytes within a single line for the current buffer. +/// - marks the windows on this buffer to be redisplayed +/// - marks the buffer changed by calling changed() +/// - invalidates cached values +/// Careful: may trigger autocommands that reload the buffer. +void changed_bytes(linenr_T lnum, colnr_T col) +{ + changedOneline(curbuf, lnum); + changed_common(lnum, col, lnum + 1, 0L); + // notify any channels that are watching + buf_updates_send_changes(curbuf, lnum, 1, 1, true); + + // Diff highlighting in other diff windows may need to be updated too. + if (curwin->w_p_diff) { + linenr_T wlnum; + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_p_diff && wp != curwin) { + redraw_win_later(wp, VALID); + wlnum = diff_lnum_win(lnum, wp); + if (wlnum > 0) { + changedOneline(wp->w_buffer, wlnum); + } + } + } + } +} + +/// Appended "count" lines below line "lnum" in the current buffer. +/// Must be called AFTER the change and after mark_adjust(). +/// Takes care of marking the buffer to be redrawn and sets the changed flag. +void appended_lines(linenr_T lnum, long count) +{ + changed_lines(lnum + 1, 0, lnum + 1, count, true); +} + +/// Like appended_lines(), but adjust marks first. +void appended_lines_mark(linenr_T lnum, long count) +{ + // Skip mark_adjust when adding a line after the last one, there can't + // be marks there. But it's still needed in diff mode. + if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { + mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false); + } + changed_lines(lnum + 1, 0, lnum + 1, count, true); +} + +/// Deleted "count" lines at line "lnum" in the current buffer. +/// Must be called AFTER the change and after mark_adjust(). +/// Takes care of marking the buffer to be redrawn and sets the changed flag. +void deleted_lines(linenr_T lnum, long count) +{ + changed_lines(lnum, 0, lnum + count, -count, true); +} + +/// Like deleted_lines(), but adjust marks first. +/// Make sure the cursor is on a valid line before calling, a GUI callback may +/// be triggered to display the cursor. +void deleted_lines_mark(linenr_T lnum, long count) +{ + mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false); + changed_lines(lnum, 0, lnum + count, -count, true); +} + +/// Marks the area to be redrawn after a change. +/// +/// @param buf the buffer where lines were changed +/// @param lnum first line with change +/// @param lnume line below last changed line +/// @param xtra number of extra lines (negative when deleting) +void changed_lines_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, long xtra) +{ + if (buf->b_mod_set) { + // find the maximum area that must be redisplayed + if (lnum < buf->b_mod_top) { + buf->b_mod_top = lnum; + } + if (lnum < buf->b_mod_bot) { + // adjust old bot position for xtra lines + buf->b_mod_bot += xtra; + if (buf->b_mod_bot < lnum) { + buf->b_mod_bot = lnum; + } + } + if (lnume + xtra > buf->b_mod_bot) { + buf->b_mod_bot = lnume + xtra; + } + buf->b_mod_xlines += xtra; + } else { + // set the area that must be redisplayed + buf->b_mod_set = true; + buf->b_mod_top = lnum; + buf->b_mod_bot = lnume + xtra; + buf->b_mod_xlines = xtra; + } +} + +/// Changed lines for the current buffer. +/// Must be called AFTER the change and after mark_adjust(). +/// - mark the buffer changed by calling changed() +/// - mark the windows on this buffer to be redisplayed +/// - invalidate cached values +/// "lnum" is the first line that needs displaying, "lnume" the first line +/// below the changed lines (BEFORE the change). +/// When only inserting lines, "lnum" and "lnume" are equal. +/// Takes care of calling changed() and updating b_mod_*. +/// Careful: may trigger autocommands that reload the buffer. +void +changed_lines( + linenr_T lnum, // first line with change + colnr_T col, // column in first line with change + linenr_T lnume, // line below last changed line + long xtra, // number of extra lines (negative when deleting) + bool do_buf_event // some callers like undo/redo call changed_lines() + // and then increment changedtick *again*. This flag + // allows these callers to send the nvim_buf_lines_event + // events after they're done modifying changedtick. +) +{ + changed_lines_buf(curbuf, lnum, lnume, xtra); + + if (xtra == 0 && curwin->w_p_diff && !diff_internal()) { + // When the number of lines doesn't change then mark_adjust() isn't + // called and other diff buffers still need to be marked for + // displaying. + linenr_T wlnum; + + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if (wp->w_p_diff && wp != curwin) { + redraw_win_later(wp, VALID); + wlnum = diff_lnum_win(lnum, wp); + if (wlnum > 0) { + changed_lines_buf(wp->w_buffer, wlnum, + lnume - lnum + wlnum, 0L); + } + } + } + } + + changed_common(lnum, col, lnume, xtra); + + if (do_buf_event) { + int64_t num_added = (int64_t)(lnume + xtra - lnum); + int64_t num_removed = lnume - lnum; + buf_updates_send_changes(curbuf, lnum, num_added, num_removed, true); + } +} + +/// Called when the changed flag must be reset for buffer "buf". +/// When "ff" is true also reset 'fileformat'. +void unchanged(buf_T *buf, int ff) +{ + if (buf->b_changed || (ff && file_ff_differs(buf, false))) { + buf->b_changed = false; + ml_setflags(buf); + if (ff) { + save_file_ff(buf); + } + check_status(buf); + redraw_tabline = true; + need_maketitle = true; // set window title later + } + buf_inc_changedtick(buf); +} + +/// Insert string "p" at the cursor position. Stops at a NUL byte. +/// Handles Replace mode and multi-byte characters. +void ins_bytes(char_u *p) +{ + ins_bytes_len(p, STRLEN(p)); +} + +/// Insert string "p" with length "len" at the cursor position. +/// Handles Replace mode and multi-byte characters. +void ins_bytes_len(char_u *p, size_t len) +{ + size_t n; + for (size_t i = 0; i < len; i += n) { + if (enc_utf8) { + // avoid reading past p[len] + n = (size_t)utfc_ptr2len_len(p + i, (int)(len - i)); + } else { + n = (size_t)(*mb_ptr2len)(p + i); + } + ins_char_bytes(p + i, n); + } +} + +/// Insert or replace a single character at the cursor position. +/// When in REPLACE or VREPLACE mode, replace any existing character. +/// Caller must have prepared for undo. +/// For multi-byte characters we get the whole character, the caller must +/// convert bytes to a character. +void ins_char(int c) +{ + char_u buf[MB_MAXBYTES + 1]; + size_t n = (size_t)utf_char2bytes(c, buf); + + // When "c" is 0x100, 0x200, etc. we don't want to insert a NUL byte. + // Happens for CTRL-Vu9900. + if (buf[0] == 0) { + buf[0] = '\n'; + } + ins_char_bytes(buf, n); +} + +void ins_char_bytes(char_u *buf, size_t charlen) +{ + // Break tabs if needed. + if (virtual_active() && curwin->w_cursor.coladd > 0) { + coladvance_force(getviscol()); + } + + size_t col = (size_t)curwin->w_cursor.col; + linenr_T lnum = curwin->w_cursor.lnum; + char_u *oldp = ml_get(lnum); + size_t linelen = STRLEN(oldp) + 1; // length of old line including NUL + + // The lengths default to the values for when not replacing. + size_t oldlen = 0; // nr of bytes inserted + size_t newlen = charlen; // nr of bytes deleted (0 when not replacing) + + if (State & REPLACE_FLAG) { + if (State & VREPLACE_FLAG) { + // Disable 'list' temporarily, unless 'cpo' contains the 'L' flag. + // Returns the old value of list, so when finished, + // curwin->w_p_list should be set back to this. + int old_list = curwin->w_p_list; + if (old_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) { + curwin->w_p_list = false; + } + // In virtual replace mode each character may replace one or more + // characters (zero if it's a TAB). Count the number of bytes to + // be deleted to make room for the new character, counting screen + // cells. May result in adding spaces to fill a gap. + colnr_T vcol; + getvcol(curwin, &curwin->w_cursor, NULL, &vcol, NULL); + colnr_T new_vcol = vcol + chartabsize(buf, vcol); + while (oldp[col + oldlen] != NUL && vcol < new_vcol) { + vcol += chartabsize(oldp + col + oldlen, vcol); + // Don't need to remove a TAB that takes us to the right + // position. + if (vcol > new_vcol && oldp[col + oldlen] == TAB) { + break; + } + oldlen += (size_t)(*mb_ptr2len)(oldp + col + oldlen); + // Deleted a bit too much, insert spaces. + if (vcol > new_vcol) { + newlen += (size_t)(vcol - new_vcol); + } + } + curwin->w_p_list = old_list; + } else if (oldp[col] != NUL) { + // normal replace + oldlen = (size_t)(*mb_ptr2len)(oldp + col); + } + + + // Push the replaced bytes onto the replace stack, so that they can be + // put back when BS is used. The bytes of a multi-byte character are + // done the other way around, so that the first byte is popped off + // first (it tells the byte length of the character). + replace_push(NUL); + for (size_t i = 0; i < oldlen; i++) { + i += (size_t)replace_push_mb(oldp + col + i) - 1; + } + } + + char_u *newp = xmalloc((size_t)(linelen + newlen - oldlen)); + + // Copy bytes before the cursor. + if (col > 0) { + memmove(newp, oldp, (size_t)col); + } + + // Copy bytes after the changed character(s). + char_u *p = newp + col; + if (linelen > col + oldlen) { + memmove(p + newlen, oldp + col + oldlen, + (size_t)(linelen - col - oldlen)); + } + + // Insert or overwrite the new character. + memmove(p, buf, charlen); + + // Fill with spaces when necessary. + for (size_t i = charlen; i < newlen; i++) { + p[i] = ' '; + } + + // Replace the line in the buffer. + ml_replace(lnum, newp, false); + + // mark the buffer as changed and prepare for displaying + changed_bytes(lnum, (colnr_T)col); + + // If we're in Insert or Replace mode and 'showmatch' is set, then briefly + // show the match for right parens and braces. + if (p_sm && (State & INSERT) + && msg_silent == 0 + && !ins_compl_active() + ) { + showmatch(utf_ptr2char(buf)); + } + + if (!p_ri || (State & REPLACE_FLAG)) { + // Normal insert: move cursor right + curwin->w_cursor.col += (int)charlen; + } + // TODO(Bram): should try to update w_row here, to avoid recomputing it later. +} + +/// Insert a string at the cursor position. +/// Note: Does NOT handle Replace mode. +/// Caller must have prepared for undo. +void ins_str(char_u *s) +{ + char_u *oldp, *newp; + int newlen = (int)STRLEN(s); + int oldlen; + colnr_T col; + linenr_T lnum = curwin->w_cursor.lnum; + + if (virtual_active() && curwin->w_cursor.coladd > 0) { + coladvance_force(getviscol()); + } + + col = curwin->w_cursor.col; + oldp = ml_get(lnum); + oldlen = (int)STRLEN(oldp); + + newp = (char_u *)xmalloc((size_t)oldlen + (size_t)newlen + 1); + if (col > 0) { + memmove(newp, oldp, (size_t)col); + } + memmove(newp + col, s, (size_t)newlen); + int bytes = oldlen - col + 1; + assert(bytes >= 0); + memmove(newp + col + newlen, oldp + col, (size_t)bytes); + ml_replace(lnum, newp, false); + changed_bytes(lnum, col); + curwin->w_cursor.col += newlen; +} + +// Delete one character under the cursor. +// If "fixpos" is true, don't leave the cursor on the NUL after the line. +// Caller must have prepared for undo. +// +// return FAIL for failure, OK otherwise +int del_char(bool fixpos) +{ + // Make sure the cursor is at the start of a character. + mb_adjust_cursor(); + if (*get_cursor_pos_ptr() == NUL) { + return FAIL; + } + return del_chars(1L, fixpos); +} + +/// Like del_bytes(), but delete characters instead of bytes. +int del_chars(long count, int fixpos) +{ + int bytes = 0; + long i; + char_u *p; + int l; + + p = get_cursor_pos_ptr(); + for (i = 0; i < count && *p != NUL; i++) { + l = (*mb_ptr2len)(p); + bytes += l; + p += l; + } + return del_bytes(bytes, fixpos, true); +} + +/// Delete "count" bytes under the cursor. +/// If "fixpos" is true, don't leave the cursor on the NUL after the line. +/// Caller must have prepared for undo. +/// +/// @param count number of bytes to be deleted +/// @param fixpos_arg leave the cursor on the NUL after the line +/// @param use_delcombine 'delcombine' option applies +/// +/// @return FAIL for failure, OK otherwise +int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) +{ + linenr_T lnum = curwin->w_cursor.lnum; + colnr_T col = curwin->w_cursor.col; + bool fixpos = fixpos_arg; + char_u *oldp = ml_get(lnum); + colnr_T oldlen = (colnr_T)STRLEN(oldp); + + // Can't do anything when the cursor is on the NUL after the line. + if (col >= oldlen) { + return FAIL; + } + // If "count" is zero there is nothing to do. + if (count == 0) { + return OK; + } + // If "count" is negative the caller must be doing something wrong. + if (count < 1) { + IEMSGN("E950: Invalid count for del_bytes(): %ld", count); + return FAIL; + } + + // If 'delcombine' is set and deleting (less than) one character, only + // delete the last combining character. + if (p_deco && use_delcombine && enc_utf8 + && utfc_ptr2len(oldp + col) >= count) { + int cc[MAX_MCO]; + int n; + + (void)utfc_ptr2char(oldp + col, cc); + if (cc[0] != NUL) { + // Find the last composing char, there can be several. + n = col; + do { + col = n; + count = utf_ptr2len(oldp + n); + n += count; + } while (UTF_COMPOSINGLIKE(oldp + col, oldp + n)); + fixpos = false; + } + } + + // When count is too big, reduce it. + int movelen = oldlen - col - count + 1; // includes trailing NUL + if (movelen <= 1) { + // If we just took off the last character of a non-blank line, and + // fixpos is TRUE, we don't want to end up positioned at the NUL, + // unless "restart_edit" is set or 'virtualedit' contains "onemore". + if (col > 0 && fixpos && restart_edit == 0 + && (ve_flags & VE_ONEMORE) == 0 + ) { + curwin->w_cursor.col--; + curwin->w_cursor.coladd = 0; + curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col); + } + count = oldlen - col; + movelen = 1; + } + + // If the old line has been allocated the deletion can be done in the + // existing line. Otherwise a new line has to be allocated. + bool was_alloced = ml_line_alloced(); // check if oldp was allocated + char_u *newp; + if (was_alloced) { + ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, oldlen); + newp = oldp; // use same allocated memory + } else { // need to allocate a new line + newp = xmalloc((size_t)(oldlen + 1 - count)); + memmove(newp, oldp, (size_t)col); + } + memmove(newp + col, oldp + col + count, (size_t)movelen); + if (!was_alloced) { + ml_replace(lnum, newp, false); + } + + // mark the buffer as changed and prepare for displaying + changed_bytes(lnum, curwin->w_cursor.col); + + return OK; +} + +/// Copy the indent from ptr to the current line (and fill to size). +/// Leaves the cursor on the first non-blank in the line. +/// @return true if the line was changed. +int copy_indent(int size, char_u *src) +{ + char_u *p = NULL; + char_u *line = NULL; + char_u *s; + int todo; + int ind_len; + int line_len = 0; + int tab_pad; + int ind_done; + int round; + + // Round 1: compute the number of characters needed for the indent + // Round 2: copy the characters. + for (round = 1; round <= 2; round++) { + todo = size; + ind_len = 0; + ind_done = 0; + s = src; + + // Count/copy the usable portion of the source line. + while (todo > 0 && ascii_iswhite(*s)) { + if (*s == TAB) { + tab_pad = (int)curbuf->b_p_ts + - (ind_done % (int)curbuf->b_p_ts); + + // Stop if this tab will overshoot the target. + if (todo < tab_pad) { + break; + } + todo -= tab_pad; + ind_done += tab_pad; + } else { + todo--; + ind_done++; + } + ind_len++; + + if (p != NULL) { + *p++ = *s; + } + s++; + } + + // Fill to next tabstop with a tab, if possible. + tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts); + + if ((todo >= tab_pad) && !curbuf->b_p_et) { + todo -= tab_pad; + ind_len++; + + if (p != NULL) { + *p++ = TAB; + } + } + + // Add tabs required for indent. + while (todo >= (int)curbuf->b_p_ts && !curbuf->b_p_et) { + todo -= (int)curbuf->b_p_ts; + ind_len++; + + if (p != NULL) { + *p++ = TAB; + } + } + + // Count/add spaces required for indent. + while (todo > 0) { + todo--; + ind_len++; + + if (p != NULL) { + *p++ = ' '; + } + } + + if (p == NULL) { + // Allocate memory for the result: the copied indent, new indent + // and the rest of the line. + line_len = (int)STRLEN(get_cursor_line_ptr()) + 1; + assert(ind_len + line_len >= 0); + size_t line_size; + STRICT_ADD(ind_len, line_len, &line_size, size_t); + line = xmalloc(line_size); + p = line; + } + } + + // Append the original line + memmove(p, get_cursor_line_ptr(), (size_t)line_len); + + // Replace the line + ml_replace(curwin->w_cursor.lnum, line, false); + + // Put the cursor after the indent. + curwin->w_cursor.col = ind_len; + return true; +} + +/// open_line: Add a new line below or above the current line. +/// +/// For VREPLACE mode, we only add a new line when we get to the end of the +/// file, otherwise we just start replacing the next line. +/// +/// Caller must take care of undo. Since VREPLACE may affect any number of +/// lines however, it may call u_save_cursor() again when starting to change a +/// new line. +/// "flags": OPENLINE_DELSPACES delete spaces after cursor +/// OPENLINE_DO_COM format comments +/// OPENLINE_KEEPTRAIL keep trailing spaces +/// OPENLINE_MARKFIX adjust mark positions after the line break +/// OPENLINE_COM_LIST format comments with list or 2nd line indent +/// +/// "second_line_indent": indent for after ^^D in Insert mode or if flag +/// OPENLINE_COM_LIST +/// +/// @return true on success, false on failure +int open_line( + int dir, // FORWARD or BACKWARD + int flags, + int second_line_indent +) +{ + char_u *next_line = NULL; // copy of the next line + char_u *p_extra = NULL; // what goes to next line + colnr_T less_cols = 0; // less columns for mark in new line + colnr_T less_cols_off = 0; // columns to skip for mark adjust + pos_T old_cursor; // old cursor position + colnr_T newcol = 0; // new cursor column + int newindent = 0; // auto-indent of the new line + bool trunc_line = false; // truncate current line afterwards + bool retval = false; // return value + int extra_len = 0; // length of p_extra string + int lead_len; // length of comment leader + char_u *lead_flags; // position in 'comments' for comment leader + char_u *leader = NULL; // copy of comment leader + char_u *allocated = NULL; // allocated memory + char_u *p; + char_u saved_char = NUL; // init for GCC + pos_T *pos; + bool do_si = (!p_paste && curbuf->b_p_si && !curbuf->b_p_cin + && *curbuf->b_p_inde == NUL); + bool no_si = false; // reset did_si afterwards + int first_char = NUL; // init for GCC + int vreplace_mode; + bool did_append; // appended a new line + int saved_pi = curbuf->b_p_pi; // copy of preserveindent setting + + // make a copy of the current line so we can mess with it + char_u *saved_line = vim_strsave(get_cursor_line_ptr()); + + if (State & VREPLACE_FLAG) { + // With VREPLACE we make a copy of the next line, which we will be + // starting to replace. First make the new line empty and let vim play + // with the indenting and comment leader to its heart's content. Then + // we grab what it ended up putting on the new line, put back the + // original line, and call ins_char() to put each new character onto + // the line, replacing what was there before and pushing the right + // stuff onto the replace stack. -- webb. + if (curwin->w_cursor.lnum < orig_line_count) { + next_line = vim_strsave(ml_get(curwin->w_cursor.lnum + 1)); + } else { + next_line = vim_strsave((char_u *)""); + } + + // In VREPLACE mode, a NL replaces the rest of the line, and starts + // replacing the next line, so push all of the characters left on the + // line onto the replace stack. We'll push any other characters that + // might be replaced at the start of the next line (due to autoindent + // etc) a bit later. + replace_push(NUL); // Call twice because BS over NL expects it + replace_push(NUL); + p = saved_line + curwin->w_cursor.col; + while (*p != NUL) { + p += replace_push_mb(p); + } + saved_line[curwin->w_cursor.col] = NUL; + } + + if ((State & INSERT) + && !(State & VREPLACE_FLAG) + ) { + p_extra = saved_line + curwin->w_cursor.col; + if (do_si) { // need first char after new line break + p = skipwhite(p_extra); + first_char = *p; + } + extra_len = (int)STRLEN(p_extra); + saved_char = *p_extra; + *p_extra = NUL; + } + + u_clearline(); // cannot do "U" command when adding lines + did_si = false; + ai_col = 0; + + // If we just did an auto-indent, then we didn't type anything on + // the prior line, and it should be truncated. Do this even if 'ai' is not + // set because automatically inserting a comment leader also sets did_ai. + if (dir == FORWARD && did_ai) { + trunc_line = true; + } + + // If 'autoindent' and/or 'smartindent' is set, try to figure out what + // indent to use for the new line. + if (curbuf->b_p_ai + || do_si + ) { + // count white space on current line + newindent = get_indent_str(saved_line, (int)curbuf->b_p_ts, false); + if (newindent == 0 && !(flags & OPENLINE_COM_LIST)) { + newindent = second_line_indent; // for ^^D command in insert mode + } + + // Do smart indenting. + // In insert/replace mode (only when dir == FORWARD) + // we may move some text to the next line. If it starts with '{' + // don't add an indent. Fixes inserting a NL before '{' in line + // "if (condition) {" + if (!trunc_line && do_si && *saved_line != NUL + && (p_extra == NULL || first_char != '{')) { + char_u *ptr; + char_u last_char; + + old_cursor = curwin->w_cursor; + ptr = saved_line; + if (flags & OPENLINE_DO_COM) { + lead_len = get_leader_len(ptr, NULL, false, true); + } else { + lead_len = 0; + } + if (dir == FORWARD) { + // Skip preprocessor directives, unless they are + // recognised as comments. + if (lead_len == 0 && ptr[0] == '#') { + while (ptr[0] == '#' && curwin->w_cursor.lnum > 1) { + ptr = ml_get(--curwin->w_cursor.lnum); + } + newindent = get_indent(); + } + if (flags & OPENLINE_DO_COM) { + lead_len = get_leader_len(ptr, NULL, false, true); + } else { + lead_len = 0; + } + if (lead_len > 0) { + // This case gets the following right: + // \* + // * A comment (read '\' as '/'). + // */ + // #define IN_THE_WAY + // This should line up here; + p = skipwhite(ptr); + if (p[0] == '/' && p[1] == '*') { + p++; + } + if (p[0] == '*') { + for (p++; *p; p++) { + if (p[0] == '/' && p[-1] == '*') { + // End of C comment, indent should line up + // with the line containing the start of + // the comment + curwin->w_cursor.col = (colnr_T)(p - ptr); + if ((pos = findmatch(NULL, NUL)) != NULL) { + curwin->w_cursor.lnum = pos->lnum; + newindent = get_indent(); + } + } + } + } + } else { // Not a comment line + // Find last non-blank in line + p = ptr + STRLEN(ptr) - 1; + while (p > ptr && ascii_iswhite(*p)) { + p--; + } + last_char = *p; + + // find the character just before the '{' or ';' + if (last_char == '{' || last_char == ';') { + if (p > ptr) { + p--; + } + while (p > ptr && ascii_iswhite(*p)) { + p--; + } + } + // Try to catch lines that are split over multiple + // lines. eg: + // if (condition && + // condition) { + // Should line up here! + // } + if (*p == ')') { + curwin->w_cursor.col = (colnr_T)(p - ptr); + if ((pos = findmatch(NULL, '(')) != NULL) { + curwin->w_cursor.lnum = pos->lnum; + newindent = get_indent(); + ptr = get_cursor_line_ptr(); + } + } + // If last character is '{' do indent, without + // checking for "if" and the like. + if (last_char == '{') { + did_si = true; // do indent + no_si = true; // don't delete it when '{' typed + // Look for "if" and the like, use 'cinwords'. + // Don't do this if the previous line ended in ';' or + // '}'. + } else if (last_char != ';' && last_char != '}' + && cin_is_cinword(ptr)) { + did_si = true; + } + } + } else { // dir == BACKWARD + // Skip preprocessor directives, unless they are + // recognised as comments. + if (lead_len == 0 && ptr[0] == '#') { + bool was_backslashed = false; + + while ((ptr[0] == '#' || was_backslashed) + && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { + if (*ptr && ptr[STRLEN(ptr) - 1] == '\\') { + was_backslashed = true; + } else { + was_backslashed = false; + } + ptr = ml_get(++curwin->w_cursor.lnum); + } + if (was_backslashed) { + newindent = 0; // Got to end of file + } else { + newindent = get_indent(); + } + } + p = skipwhite(ptr); + if (*p == '}') { // if line starts with '}': do indent + did_si = true; + } else { // can delete indent when '{' typed + can_si_back = true; + } + } + curwin->w_cursor = old_cursor; + } + if (do_si) { + can_si = true; + } + + did_ai = true; + } + + // Find out if the current line starts with a comment leader. + // This may then be inserted in front of the new line. + end_comment_pending = NUL; + if (flags & OPENLINE_DO_COM) { + lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, true); + } else { + lead_len = 0; + } + if (lead_len > 0) { + char_u *lead_repl = NULL; // replaces comment leader + int lead_repl_len = 0; // length of *lead_repl + char_u lead_middle[COM_MAX_LEN]; // middle-comment string + char_u lead_end[COM_MAX_LEN]; // end-comment string + char_u *comment_end = NULL; // where lead_end has been found + int extra_space = false; // append extra space + int current_flag; + int require_blank = false; // requires blank after middle + char_u *p2; + + // If the comment leader has the start, middle or end flag, it may not + // be used or may be replaced with the middle leader. + for (p = lead_flags; *p && *p != ':'; p++) { + if (*p == COM_BLANK) { + require_blank = true; + continue; + } + if (*p == COM_START || *p == COM_MIDDLE) { + current_flag = *p; + if (*p == COM_START) { + // Doing "O" on a start of comment does not insert leader. + if (dir == BACKWARD) { + lead_len = 0; + break; + } + + // find start of middle part + (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ","); + require_blank = false; + } + + // Isolate the strings of the middle and end leader. + while (*p && p[-1] != ':') { // find end of middle flags + if (*p == COM_BLANK) { + require_blank = true; + } + p++; + } + (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ","); + + while (*p && p[-1] != ':') { // find end of end flags + // Check whether we allow automatic ending of comments + if (*p == COM_AUTO_END) { + end_comment_pending = -1; // means we want to set it + } + p++; + } + size_t n = copy_option_part(&p, lead_end, COM_MAX_LEN, ","); + + if (end_comment_pending == -1) { // we can set it now + end_comment_pending = lead_end[n - 1]; + } + + // If the end of the comment is in the same line, don't use + // the comment leader. + if (dir == FORWARD) { + for (p = saved_line + lead_len; *p; p++) { + if (STRNCMP(p, lead_end, n) == 0) { + comment_end = p; + lead_len = 0; + break; + } + } + } + + // Doing "o" on a start of comment inserts the middle leader. + if (lead_len > 0) { + if (current_flag == COM_START) { + lead_repl = lead_middle; + lead_repl_len = (int)STRLEN(lead_middle); + } + + // If we have hit RETURN immediately after the start + // comment leader, then put a space after the middle + // comment leader on the next line. + if (!ascii_iswhite(saved_line[lead_len - 1]) + && ((p_extra != NULL + && (int)curwin->w_cursor.col == lead_len) + || (p_extra == NULL + && saved_line[lead_len] == NUL) + || require_blank)) { + extra_space = true; + } + } + break; + } + if (*p == COM_END) { + // Doing "o" on the end of a comment does not insert leader. + // Remember where the end is, might want to use it to find the + // start (for C-comments). + if (dir == FORWARD) { + comment_end = skipwhite(saved_line); + lead_len = 0; + break; + } + + // Doing "O" on the end of a comment inserts the middle leader. + // Find the string for the middle leader, searching backwards. + while (p > curbuf->b_p_com && *p != ',') { + p--; + } + for (lead_repl = p; lead_repl > curbuf->b_p_com + && lead_repl[-1] != ':'; lead_repl--) { + } + lead_repl_len = (int)(p - lead_repl); + + // We can probably always add an extra space when doing "O" on + // the comment-end + extra_space = true; + + // Check whether we allow automatic ending of comments + for (p2 = p; *p2 && *p2 != ':'; p2++) { + if (*p2 == COM_AUTO_END) { + end_comment_pending = -1; // means we want to set it + } + } + if (end_comment_pending == -1) { + // Find last character in end-comment string + while (*p2 && *p2 != ',') { + p2++; + } + end_comment_pending = p2[-1]; + } + break; + } + if (*p == COM_FIRST) { + // Comment leader for first line only: Don't repeat leader + // when using "O", blank out leader when using "o". + if (dir == BACKWARD) { + lead_len = 0; + } else { + lead_repl = (char_u *)""; + lead_repl_len = 0; + } + break; + } + } + if (lead_len > 0) { + // allocate buffer (may concatenate p_extra later) + int bytes = lead_len + + lead_repl_len + + extra_space + + extra_len + + (second_line_indent > 0 ? second_line_indent : 0) + + 1; + assert(bytes >= 0); + leader = xmalloc((size_t)bytes); + allocated = leader; // remember to free it later + + STRLCPY(leader, saved_line, lead_len + 1); + + // Replace leader with lead_repl, right or left adjusted + if (lead_repl != NULL) { + int c = 0; + int off = 0; + + for (p = lead_flags; *p != NUL && *p != ':'; ) { + if (*p == COM_RIGHT || *p == COM_LEFT) { + c = *p++; + } else if (ascii_isdigit(*p) || *p == '-') { + off = getdigits_int(&p); + } else { + p++; + } + } + if (c == COM_RIGHT) { // right adjusted leader + // find last non-white in the leader to line up with + for (p = leader + lead_len - 1; p > leader + && ascii_iswhite(*p); p--) { + } + p++; + + // Compute the length of the replaced characters in + // screen characters, not bytes. + { + int repl_size = vim_strnsize(lead_repl, + lead_repl_len); + int old_size = 0; + char_u *endp = p; + int l; + + while (old_size < repl_size && p > leader) { + MB_PTR_BACK(leader, p); + old_size += ptr2cells(p); + } + l = lead_repl_len - (int)(endp - p); + if (l != 0) { + memmove(endp + l, endp, + (size_t)((leader + lead_len) - endp)); + } + lead_len += l; + } + memmove(p, lead_repl, (size_t)lead_repl_len); + if (p + lead_repl_len > leader + lead_len) { + p[lead_repl_len] = NUL; + } + + // blank-out any other chars from the old leader. + while (--p >= leader) { + int l = utf_head_off(leader, p); + + if (l > 1) { + p -= l; + if (ptr2cells(p) > 1) { + p[1] = ' '; + l--; + } + memmove(p + 1, p + l + 1, + (size_t)((leader + lead_len) - (p + l + 1))); + lead_len -= l; + *p = ' '; + } else if (!ascii_iswhite(*p)) { + *p = ' '; + } + } + } else { // left adjusted leader + p = skipwhite(leader); + // Compute the length of the replaced characters in + // screen characters, not bytes. Move the part that is + // not to be overwritten. + { + int repl_size = vim_strnsize(lead_repl, + lead_repl_len); + int i; + int l; + + for (i = 0; i < lead_len && p[i] != NUL; i += l) { + l = (*mb_ptr2len)(p + i); + if (vim_strnsize(p, i + l) > repl_size) { + break; + } + } + if (i != lead_repl_len) { + memmove(p + lead_repl_len, p + i, + (size_t)(lead_len - i - (p - leader))); + lead_len += lead_repl_len - i; + } + } + memmove(p, lead_repl, (size_t)lead_repl_len); + + // Replace any remaining non-white chars in the old + // leader by spaces. Keep Tabs, the indent must + // remain the same. + for (p += lead_repl_len; p < leader + lead_len; p++) { + if (!ascii_iswhite(*p)) { + // Don't put a space before a TAB. + if (p + 1 < leader + lead_len && p[1] == TAB) { + lead_len--; + memmove(p, p + 1, (size_t)(leader + lead_len - p)); + } else { + int l = (*mb_ptr2len)(p); + + if (l > 1) { + if (ptr2cells(p) > 1) { + // Replace a double-wide char with + // two spaces + l--; + *p++ = ' '; + } + memmove(p + 1, p + l, (size_t)(leader + lead_len - p)); + lead_len -= l - 1; + } + *p = ' '; + } + } + } + *p = NUL; + } + + // Recompute the indent, it may have changed. + if (curbuf->b_p_ai + || do_si + ) { + newindent = get_indent_str(leader, (int)curbuf->b_p_ts, false); + } + + // Add the indent offset + if (newindent + off < 0) { + off = -newindent; + newindent = 0; + } else { + newindent += off; + } + + // Correct trailing spaces for the shift, so that + // alignment remains equal. + while (off > 0 && lead_len > 0 + && leader[lead_len - 1] == ' ') { + // Don't do it when there is a tab before the space + if (vim_strchr(skipwhite(leader), '\t') != NULL) { + break; + } + lead_len--; + off--; + } + + // If the leader ends in white space, don't add an + // extra space + if (lead_len > 0 && ascii_iswhite(leader[lead_len - 1])) { + extra_space = false; + } + leader[lead_len] = NUL; + } + + if (extra_space) { + leader[lead_len++] = ' '; + leader[lead_len] = NUL; + } + + newcol = lead_len; + + // if a new indent will be set below, remove the indent that + // is in the comment leader + if (newindent + || did_si + ) { + while (lead_len && ascii_iswhite(*leader)) { + lead_len--; + newcol--; + leader++; + } + } + + did_si = can_si = false; + } else if (comment_end != NULL) { + // We have finished a comment, so we don't use the leader. + // If this was a C-comment and 'ai' or 'si' is set do a normal + // indent to align with the line containing the start of the + // comment. + if (comment_end[0] == '*' && comment_end[1] == '/' + && (curbuf->b_p_ai || do_si)) { + old_cursor = curwin->w_cursor; + curwin->w_cursor.col = (colnr_T)(comment_end - saved_line); + if ((pos = findmatch(NULL, NUL)) != NULL) { + curwin->w_cursor.lnum = pos->lnum; + newindent = get_indent(); + } + curwin->w_cursor = old_cursor; + } + } + } + + // (State == INSERT || State == REPLACE), only when dir == FORWARD + if (p_extra != NULL) { + *p_extra = saved_char; // restore char that NUL replaced + + // When 'ai' set or "flags" has OPENLINE_DELSPACES, skip to the first + // non-blank. + // + // When in REPLACE mode, put the deleted blanks on the replace stack, + // preceded by a NUL, so they can be put back when a BS is entered. + if (REPLACE_NORMAL(State)) { + replace_push(NUL); // end of extra blanks + } + if (curbuf->b_p_ai || (flags & OPENLINE_DELSPACES)) { + while ((*p_extra == ' ' || *p_extra == '\t') + && !utf_iscomposing(utf_ptr2char(p_extra + 1))) { + if (REPLACE_NORMAL(State)) { + replace_push(*p_extra); + } + p_extra++; + less_cols_off++; + } + } + + // columns for marks adjusted for removed columns + less_cols = (int)(p_extra - saved_line); + } + + if (p_extra == NULL) { + p_extra = (char_u *)""; // append empty line + } + + // concatenate leader and p_extra, if there is a leader + if (lead_len > 0) { + if (flags & OPENLINE_COM_LIST && second_line_indent > 0) { + int i; + int padding = second_line_indent + - (newindent + (int)STRLEN(leader)); + + // Here whitespace is inserted after the comment char. + // Below, set_indent(newindent, SIN_INSERT) will insert the + // whitespace needed before the comment char. + for (i = 0; i < padding; i++) { + STRCAT(leader, " "); + less_cols--; + newcol++; + } + } + STRCAT(leader, p_extra); + p_extra = leader; + did_ai = true; // So truncating blanks works with comments + less_cols -= lead_len; + } else { + end_comment_pending = NUL; // turns out there was no leader + } + + old_cursor = curwin->w_cursor; + if (dir == BACKWARD) { + curwin->w_cursor.lnum--; + } + if (!(State & VREPLACE_FLAG) || old_cursor.lnum >= orig_line_count) { + if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, false) == FAIL) { + goto theend; + } + // Postpone calling changed_lines(), because it would mess up folding + // with markers. + // Skip mark_adjust when adding a line after the last one, there can't + // be marks there. But still needed in diff mode. + if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count + || curwin->w_p_diff) { + mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false); + } + did_append = true; + } else { + // In VREPLACE mode we are starting to replace the next line. + curwin->w_cursor.lnum++; + if (curwin->w_cursor.lnum >= Insstart.lnum + vr_lines_changed) { + // In case we NL to a new line, BS to the previous one, and NL + // again, we don't want to save the new line for undo twice. + (void)u_save_cursor(); // errors are ignored! + vr_lines_changed++; + } + ml_replace(curwin->w_cursor.lnum, p_extra, true); + changed_bytes(curwin->w_cursor.lnum, 0); + curwin->w_cursor.lnum--; + did_append = false; + } + + inhibit_delete_count++; + if (newindent + || did_si + ) { + curwin->w_cursor.lnum++; + if (did_si) { + int sw = get_sw_value(curbuf); + + if (p_sr) { + newindent -= newindent % sw; + } + newindent += sw; + } + // Copy the indent + if (curbuf->b_p_ci) { + (void)copy_indent(newindent, saved_line); + + // Set the 'preserveindent' option so that any further screwing + // with the line doesn't entirely destroy our efforts to preserve + // it. It gets restored at the function end. + curbuf->b_p_pi = true; + } else { + (void)set_indent(newindent, SIN_INSERT); + } + less_cols -= curwin->w_cursor.col; + + ai_col = curwin->w_cursor.col; + + // In REPLACE mode, for each character in the new indent, there must + // be a NUL on the replace stack, for when it is deleted with BS + if (REPLACE_NORMAL(State)) { + for (colnr_T n = 0; n < curwin->w_cursor.col; n++) { + replace_push(NUL); + } + } + newcol += curwin->w_cursor.col; + if (no_si) { + did_si = false; + } + } + inhibit_delete_count--; + + // In REPLACE mode, for each character in the extra leader, there must be + // a NUL on the replace stack, for when it is deleted with BS. + if (REPLACE_NORMAL(State)) { + while (lead_len-- > 0) { + replace_push(NUL); + } + } + + curwin->w_cursor = old_cursor; + + if (dir == FORWARD) { + if (trunc_line || (State & INSERT)) { + // truncate current line at cursor + saved_line[curwin->w_cursor.col] = NUL; + // Remove trailing white space, unless OPENLINE_KEEPTRAIL used. + if (trunc_line && !(flags & OPENLINE_KEEPTRAIL)) { + truncate_spaces(saved_line); + } + ml_replace(curwin->w_cursor.lnum, saved_line, false); + saved_line = NULL; + if (did_append) { + changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col, + curwin->w_cursor.lnum + 1, 1L, true); + did_append = false; + + // Move marks after the line break to the new line. + if (flags & OPENLINE_MARKFIX) { + mark_col_adjust(curwin->w_cursor.lnum, + curwin->w_cursor.col + less_cols_off, + 1L, (long)-less_cols, 0); + } + } else { + changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); + } + } + + // Put the cursor on the new line. Careful: the scrollup() above may + // have moved w_cursor, we must use old_cursor. + curwin->w_cursor.lnum = old_cursor.lnum + 1; + } + if (did_append) { + changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true); + } + + curwin->w_cursor.col = newcol; + curwin->w_cursor.coladd = 0; + + // In VREPLACE mode, we are handling the replace stack ourselves, so stop + // fixthisline() from doing it (via change_indent()) by telling it we're in + // normal INSERT mode. + if (State & VREPLACE_FLAG) { + vreplace_mode = State; // So we know to put things right later + State = INSERT; + } else { + vreplace_mode = 0; + } + // May do lisp indenting. + if (!p_paste + && leader == NULL + && curbuf->b_p_lisp + && curbuf->b_p_ai) { + fixthisline(get_lisp_indent); + ai_col = (colnr_T)getwhitecols_curline(); + } + // May do indenting after opening a new line. + if (!p_paste + && (curbuf->b_p_cin + || *curbuf->b_p_inde != NUL + ) + && in_cinkeys(dir == FORWARD + ? KEY_OPEN_FORW + : KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum))) { + do_c_expr_indent(); + ai_col = (colnr_T)getwhitecols_curline(); + } + if (vreplace_mode != 0) { + State = vreplace_mode; + } + + // Finally, VREPLACE gets the stuff on the new line, then puts back the + // original line, and inserts the new stuff char by char, pushing old stuff + // onto the replace stack (via ins_char()). + if (State & VREPLACE_FLAG) { + // Put new line in p_extra + p_extra = vim_strsave(get_cursor_line_ptr()); + + // Put back original line + ml_replace(curwin->w_cursor.lnum, next_line, false); + + // Insert new stuff into line again + curwin->w_cursor.col = 0; + curwin->w_cursor.coladd = 0; + ins_bytes(p_extra); // will call changed_bytes() + xfree(p_extra); + next_line = NULL; + } + + retval = true; // success! +theend: + curbuf->b_p_pi = saved_pi; + xfree(saved_line); + xfree(next_line); + xfree(allocated); + return retval; +} // NOLINT(readability/fn_size) + +/// Delete from cursor to end of line. +/// Caller must have prepared for undo. +/// If "fixpos" is true fix the cursor position when done. +void truncate_line(int fixpos) +{ + char_u *newp; + linenr_T lnum = curwin->w_cursor.lnum; + colnr_T col = curwin->w_cursor.col; + + if (col == 0) { + newp = vim_strsave((char_u *)""); + } else { + newp = vim_strnsave(ml_get(lnum), (size_t)col); + } + ml_replace(lnum, newp, false); + + // mark the buffer as changed and prepare for displaying + changed_bytes(lnum, curwin->w_cursor.col); + + // If "fixpos" is true we don't want to end up positioned at the NUL. + if (fixpos && curwin->w_cursor.col > 0) { + curwin->w_cursor.col--; + } +} + +/// Delete "nlines" lines at the cursor. +/// Saves the lines for undo first if "undo" is true. +void del_lines(long nlines, int undo) +{ + long n; + linenr_T first = curwin->w_cursor.lnum; + + if (nlines <= 0) { + return; + } + + // save the deleted lines for undo + if (undo && u_savedel(first, nlines) == FAIL) { + return; + } + + for (n = 0; n < nlines; ) { + if (curbuf->b_ml.ml_flags & ML_EMPTY) { // nothing to delete + break; + } + + ml_delete(first, true); + n++; + + // If we delete the last line in the file, stop + if (first > curbuf->b_ml.ml_line_count) { + break; + } + } + + // Correct the cursor position before calling deleted_lines_mark(), it may + // trigger a callback to display the cursor. + curwin->w_cursor.col = 0; + check_cursor_lnum(); + + // adjust marks, mark the buffer as changed and prepare for displaying + deleted_lines_mark(first, n); +} diff --git a/src/nvim/change.h b/src/nvim/change.h new file mode 100644 index 0000000000..e1a1bfba17 --- /dev/null +++ b/src/nvim/change.h @@ -0,0 +1,11 @@ +#ifndef NVIM_CHANGE_H +#define NVIM_CHANGE_H + +#include "nvim/buffer_defs.h" // for buf_T +#include "nvim/pos.h" // for linenr_T + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "change.h.generated.h" +#endif + +#endif // NVIM_CHANGE_H diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 8b8d27affd..104c79efd9 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -22,19 +22,10 @@ PMap(uint64_t) *channels = NULL; /// 2 is reserved for stderr channel static uint64_t next_chan_id = CHAN_STDERR+1; - -typedef struct { - Channel *chan; - Callback *callback; - const char *type; - // if reader is set, status is ignored. - CallbackReader *reader; - int status; -} ChannelEvent; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "channel.c.generated.h" #endif + /// Teardown the module void channel_teardown(void) { @@ -179,6 +170,7 @@ static Channel *channel_alloc(ChannelStreamType type) } chan->events = multiqueue_new_child(main_loop.events); chan->refcount = 1; + chan->exit_status = -1; chan->streamtype = type; pmap_put(uint64_t)(channels, chan->id, chan); return chan; @@ -234,9 +226,10 @@ void callback_reader_free(CallbackReader *reader) ga_clear(&reader->buffer); } -void callback_reader_start(CallbackReader *reader) +void callback_reader_start(CallbackReader *reader, const char *type) { ga_init(&reader->buffer, sizeof(char *), 32); + reader->type = type; } static void free_channel_event(void **argv) @@ -246,7 +239,7 @@ static void free_channel_event(void **argv) rpc_free(chan); } - callback_reader_free(&chan->on_stdout); + callback_reader_free(&chan->on_data); callback_reader_free(&chan->on_stderr); callback_free(&chan->on_exit); @@ -286,7 +279,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, assert(cwd == NULL || os_isdir_executable(cwd)); Channel *chan = channel_alloc(kChannelStreamProc); - chan->on_stdout = on_stdout; + chan->on_data = on_stdout; chan->on_stderr = on_stderr; chan->on_exit = on_exit; @@ -326,7 +319,7 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, has_out = true; has_err = false; } else { - has_out = rpc || callback_reader_set(chan->on_stdout); + has_out = rpc || callback_reader_set(chan->on_data); has_err = callback_reader_set(chan->on_stderr); } int status = process_spawn(proc, true, has_out, has_err); @@ -352,13 +345,13 @@ Channel *channel_job_start(char **argv, CallbackReader on_stdout, rpc_start(chan); } else { if (has_out) { - callback_reader_start(&chan->on_stdout); - rstream_start(&proc->out, on_job_stdout, chan); + callback_reader_start(&chan->on_data, "stdout"); + rstream_start(&proc->out, on_channel_data, chan); } } if (has_err) { - callback_reader_start(&chan->on_stderr); + callback_reader_start(&chan->on_stderr, "stderr"); rstream_init(&proc->err, 0); rstream_start(&proc->err, on_job_stderr, chan); } @@ -402,9 +395,9 @@ uint64_t channel_connect(bool tcp, const char *address, if (rpc) { rpc_start(channel); } else { - channel->on_stdout = on_output; - callback_reader_start(&channel->on_stdout); - rstream_start(&channel->stream.socket, on_socket_output, channel); + channel->on_data = on_output; + callback_reader_start(&channel->on_data, "data"); + rstream_start(&channel->stream.socket, on_channel_data, channel); } end: @@ -452,9 +445,9 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, if (rpc) { rpc_start(channel); } else { - channel->on_stdout = on_output; - callback_reader_start(&channel->on_stdout); - rstream_start(&channel->stream.stdio.in, on_stdio_input, channel); + channel->on_data = on_output; + callback_reader_start(&channel->on_data, "stdin"); + rstream_start(&channel->stream.stdio.in, on_channel_data, channel); } return channel->id; @@ -519,55 +512,22 @@ static inline list_T *buffer_to_tv_list(const char *const buf, const size_t len) return l; } -// vimscript job callbacks must be executed on Nvim main loop -static inline void process_channel_event(Channel *chan, Callback *callback, - const char *type, - CallbackReader *reader, int status) -{ - assert(callback); - ChannelEvent *event_data = xmalloc(sizeof(*event_data)); - event_data->reader = reader; - event_data->status = status; - channel_incref(chan); // Hold on ref to callback - event_data->chan = chan; - event_data->callback = callback; - event_data->type = type; - - multiqueue_put(chan->events, on_channel_event, 1, event_data); -} - -void on_job_stdout(Stream *stream, RBuffer *buf, size_t count, - void *data, bool eof) +void on_channel_data(Stream *stream, RBuffer *buf, size_t count, + void *data, bool eof) { Channel *chan = data; - on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "stdout"); + on_channel_output(stream, chan, buf, count, eof, &chan->on_data); } void on_job_stderr(Stream *stream, RBuffer *buf, size_t count, void *data, bool eof) { Channel *chan = data; - on_channel_output(stream, chan, buf, count, eof, &chan->on_stderr, "stderr"); -} - -static void on_socket_output(Stream *stream, RBuffer *buf, size_t count, - void *data, bool eof) -{ - Channel *chan = data; - on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "data"); -} - -static void on_stdio_input(Stream *stream, RBuffer *buf, size_t count, - void *data, bool eof) -{ - Channel *chan = data; - on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "stdin"); + on_channel_output(stream, chan, buf, count, eof, &chan->on_stderr); } -/// @param type must have static lifetime static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, - size_t count, bool eof, CallbackReader *reader, - const char *type) + size_t count, bool eof, CallbackReader *reader) { // stub variable, to keep reading consistent with the order of events, only // consider the count parameter. @@ -575,57 +535,93 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, char *ptr = rbuffer_read_ptr(buf, &r); if (eof) { - if (reader->buffered) { - if (reader->cb.type != kCallbackNone) { - process_channel_event(chan, &reader->cb, type, reader, 0); - } else if (reader->self) { - if (tv_dict_find(reader->self, type, -1) == NULL) { - list_T *data = buffer_to_tv_list(reader->buffer.ga_data, - (size_t)reader->buffer.ga_len); - tv_dict_add_list(reader->self, type, strlen(type), data); - } else { - // can't display error message now, defer it. - channel_incref(chan); - multiqueue_put(chan->events, on_buffered_error, 2, chan, type); - } - ga_clear(&reader->buffer); - } else { - abort(); - } - } else if (reader->cb.type != kCallbackNone) { - process_channel_event(chan, &reader->cb, type, reader, 0); + reader->eof = true; + } else { + if (chan->term) { + terminal_receive(chan->term, ptr, count); + terminal_flush_output(chan->term); } - return; - } - // The order here matters, the terminal must receive the data first because - // process_channel_event will modify the read buffer(convert NULs into NLs) - if (chan->term) { - terminal_receive(chan->term, ptr, count); - terminal_flush_output(chan->term); + rbuffer_consumed(buf, count); + + if (callback_reader_set(*reader)) { + ga_concat_len(&reader->buffer, ptr, count); + } } - rbuffer_consumed(buf, count); + if (callback_reader_set(*reader)) { + schedule_channel_event(chan); + } +} - if (callback_reader_set(*reader) || reader->buffered) { - // if buffer wasn't consumed, a pending callback is stalled. Aggregate the - // received data and avoid a "burst" of multiple callbacks. - bool buffer_set = reader->buffer.ga_len > 0; - ga_concat_len(&reader->buffer, ptr, count); - if (callback_reader_set(*reader) && !reader->buffered && !buffer_set) { - process_channel_event(chan, &reader->cb, type, reader, 0); +/// schedule the necessary callbacks to be invoked as a deferred event +static void schedule_channel_event(Channel *chan) +{ + if (!chan->callback_scheduled) { + if (!chan->callback_busy) { + multiqueue_put(chan->events, on_channel_event, 1, chan); + channel_incref(chan); } + chan->callback_scheduled = true; } } -static void on_buffered_error(void **args) +static void on_channel_event(void **args) { Channel *chan = (Channel *)args[0]; - const char *stream = (const char *)args[1]; - EMSG3(_(e_streamkey), stream, chan->id); + + chan->callback_busy = true; + chan->callback_scheduled = false; + + int exit_status = chan->exit_status; + channel_reader_callbacks(chan, &chan->on_data); + channel_reader_callbacks(chan, &chan->on_stderr); + if (exit_status > -1) { + channel_callback_call(chan, NULL); + chan->exit_status = -1; + } + + chan->callback_busy = false; + if (chan->callback_scheduled) { + // further callback was deferred to avoid recursion. + multiqueue_put(chan->events, on_channel_event, 1, chan); + channel_incref(chan); + } + channel_decref(chan); } +void channel_reader_callbacks(Channel *chan, CallbackReader *reader) +{ + if (reader->buffered) { + if (reader->eof) { + if (reader->self) { + if (tv_dict_find(reader->self, reader->type, -1) == NULL) { + list_T *data = buffer_to_tv_list(reader->buffer.ga_data, + (size_t)reader->buffer.ga_len); + tv_dict_add_list(reader->self, reader->type, strlen(reader->type), + data); + } else { + EMSG3(_(e_streamkey), reader->type, chan->id); + } + } else { + channel_callback_call(chan, reader); + } + reader->eof = false; + } + } else { + bool is_eof = reader->eof; + if (reader->buffer.ga_len > 0) { + channel_callback_call(chan, reader); + } + // if the stream reached eof, invoke extra callback with no data + if (is_eof) { + channel_callback_call(chan, reader); + reader->eof = false; + } + } +} + static void channel_process_exit_cb(Process *proc, int status, void *data) { Channel *chan = data; @@ -637,45 +633,46 @@ static void channel_process_exit_cb(Process *proc, int status, void *data) // 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, status); + if (exited && chan->on_exit.type != kCallbackNone) { + schedule_channel_event(chan); + chan->exit_status = status; } channel_decref(chan); } -static void on_channel_event(void **args) +static void channel_callback_call(Channel *chan, CallbackReader *reader) { - ChannelEvent *ev = (ChannelEvent *)args[0]; - + Callback *cb; typval_T argv[4]; argv[0].v_type = VAR_NUMBER; argv[0].v_lock = VAR_UNLOCKED; - argv[0].vval.v_number = (varnumber_T)ev->chan->id; + argv[0].vval.v_number = (varnumber_T)chan->id; - if (ev->reader) { + if (reader) { argv[1].v_type = VAR_LIST; argv[1].v_lock = VAR_UNLOCKED; - argv[1].vval.v_list = buffer_to_tv_list(ev->reader->buffer.ga_data, - (size_t)ev->reader->buffer.ga_len); + argv[1].vval.v_list = buffer_to_tv_list(reader->buffer.ga_data, + (size_t)reader->buffer.ga_len); tv_list_ref(argv[1].vval.v_list); - ga_clear(&ev->reader->buffer); + ga_clear(&reader->buffer); + cb = &reader->cb; + argv[2].vval.v_string = (char_u *)reader->type; } else { argv[1].v_type = VAR_NUMBER; argv[1].v_lock = VAR_UNLOCKED; - argv[1].vval.v_number = ev->status; + argv[1].vval.v_number = chan->exit_status; + cb = &chan->on_exit; + argv[2].vval.v_string = (char_u *)"exit"; } argv[2].v_type = VAR_STRING; argv[2].v_lock = VAR_UNLOCKED; - argv[2].vval.v_string = (uint8_t *)ev->type; typval_T rettv = TV_INITIAL_VALUE; - callback_call(ev->callback, 3, argv, &rettv); + callback_call(cb, 3, argv, &rettv); tv_clear(&rettv); - channel_decref(ev->chan); - xfree(ev); } @@ -765,6 +762,14 @@ static void set_info_event(void **argv) channel_decref(chan); } +bool channel_job_running(uint64_t id) +{ + Channel *chan = find_channel(id); + return (chan + && chan->streamtype == kChannelStreamProc + && !process_is_stopped(&chan->stream.proc)); +} + Dictionary channel_info(uint64_t id) { Channel *chan = find_channel(id); diff --git a/src/nvim/channel.h b/src/nvim/channel.h index b856d197f1..c733e276be 100644 --- a/src/nvim/channel.h +++ b/src/nvim/channel.h @@ -42,13 +42,16 @@ typedef struct { Callback cb; dict_T *self; garray_T buffer; + bool eof; bool buffered; + const char *type; } CallbackReader; #define CALLBACK_READER_INIT ((CallbackReader){ .cb = CALLBACK_NONE, \ .self = NULL, \ .buffer = GA_EMPTY_INIT_VALUE, \ - .buffered = false }) + .buffered = false, \ + .type = NULL }) static inline bool callback_reader_set(CallbackReader reader) { return reader.cb.type != kCallbackNone || reader.self; @@ -73,9 +76,13 @@ struct Channel { RpcState rpc; Terminal *term; - CallbackReader on_stdout; + CallbackReader on_data; CallbackReader on_stderr; Callback on_exit; + int exit_status; + + bool callback_busy; + bool callback_scheduled; }; EXTERN PMap(uint64_t) *channels; diff --git a/src/nvim/context.c b/src/nvim/context.c new file mode 100644 index 0000000000..b2a2fd3fd9 --- /dev/null +++ b/src/nvim/context.c @@ -0,0 +1,383 @@ +// 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 + +// Context: snapshot of the entire editor state as one big object/map + +#include "nvim/context.h" +#include "nvim/eval/encode.h" +#include "nvim/ex_docmd.h" +#include "nvim/option.h" +#include "nvim/shada.h" +#include "nvim/api/vim.h" +#include "nvim/api/private/helpers.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "context.c.generated.h" +#endif + +int kCtxAll = (kCtxRegs | kCtxJumps | kCtxBuflist | kCtxGVars | kCtxSFuncs + | kCtxFuncs); + +static ContextVec ctx_stack = KV_INITIAL_VALUE; + +/// Clears and frees the context stack +void ctx_free_all(void) +{ + for (size_t i = 0; i < kv_size(ctx_stack); i++) { + ctx_free(&kv_A(ctx_stack, i)); + } + kv_destroy(ctx_stack); +} + +/// Returns the size of the context stack. +size_t ctx_size(void) +{ + return kv_size(ctx_stack); +} + +/// Returns pointer to Context object with given zero-based index from the top +/// of context stack or NULL if index is out of bounds. +Context *ctx_get(size_t index) +{ + if (index < kv_size(ctx_stack)) { + return &kv_Z(ctx_stack, index); + } + return NULL; +} + +/// Free resources used by Context object. +/// +/// param[in] ctx pointer to Context object to free. +void ctx_free(Context *ctx) + FUNC_ATTR_NONNULL_ALL +{ + if (ctx->regs.data) { + msgpack_sbuffer_destroy(&ctx->regs); + } + if (ctx->jumps.data) { + msgpack_sbuffer_destroy(&ctx->jumps); + } + if (ctx->buflist.data) { + msgpack_sbuffer_destroy(&ctx->buflist); + } + if (ctx->gvars.data) { + msgpack_sbuffer_destroy(&ctx->gvars); + } + if (ctx->funcs.items) { + api_free_array(ctx->funcs); + } +} + +/// Saves the editor state to a context. +/// +/// If "context" is NULL, pushes context on context stack. +/// Use "flags" to select particular types of context. +/// +/// @param ctx Save to this context, or push on context stack if NULL. +/// @param flags Flags, see ContextTypeFlags enum. +void ctx_save(Context *ctx, const int flags) +{ + if (ctx == NULL) { + kv_push(ctx_stack, CONTEXT_INIT); + ctx = &kv_last(ctx_stack); + } + + if (flags & kCtxRegs) { + ctx_save_regs(ctx); + } + + if (flags & kCtxJumps) { + ctx_save_jumps(ctx); + } + + if (flags & kCtxBuflist) { + ctx_save_buflist(ctx); + } + + if (flags & kCtxGVars) { + ctx_save_gvars(ctx); + } + + if (flags & kCtxFuncs) { + ctx_save_funcs(ctx, false); + } else if (flags & kCtxSFuncs) { + ctx_save_funcs(ctx, true); + } +} + +/// Restores the editor state from a context. +/// +/// If "context" is NULL, pops context from context stack. +/// Use "flags" to select particular types of context. +/// +/// @param ctx Restore from this context. Pop from context stack if NULL. +/// @param flags Flags, see ContextTypeFlags enum. +/// +/// @return true on success, false otherwise (i.e.: empty context stack). +bool ctx_restore(Context *ctx, const int flags) +{ + bool free_ctx = false; + if (ctx == NULL) { + if (ctx_stack.size == 0) { + return false; + } + ctx = &kv_pop(ctx_stack); + free_ctx = true; + } + + char_u *op_shada; + get_option_value((char_u *)"shada", NULL, &op_shada, OPT_GLOBAL); + set_option_value("shada", 0L, "!,'100,%", OPT_GLOBAL); + + if (flags & kCtxRegs) { + ctx_restore_regs(ctx); + } + + if (flags & kCtxJumps) { + ctx_restore_jumps(ctx); + } + + if (flags & kCtxBuflist) { + ctx_restore_buflist(ctx); + } + + if (flags & kCtxGVars) { + ctx_restore_gvars(ctx); + } + + if (flags & kCtxFuncs) { + ctx_restore_funcs(ctx); + } + + if (free_ctx) { + ctx_free(ctx); + } + + set_option_value("shada", 0L, (char *)op_shada, OPT_GLOBAL); + xfree(op_shada); + + return true; +} + +/// Saves the global registers to a context. +/// +/// @param ctx Save to this context. +static inline void ctx_save_regs(Context *ctx) + FUNC_ATTR_NONNULL_ALL +{ + msgpack_sbuffer_init(&ctx->regs); + shada_encode_regs(&ctx->regs); +} + +/// Restores the global registers from a context. +/// +/// @param ctx Restore from this context. +static inline void ctx_restore_regs(Context *ctx) + FUNC_ATTR_NONNULL_ALL +{ + shada_read_sbuf(&ctx->regs, kShaDaWantInfo | kShaDaForceit); +} + +/// Saves the jumplist to a context. +/// +/// @param ctx Save to this context. +static inline void ctx_save_jumps(Context *ctx) + FUNC_ATTR_NONNULL_ALL +{ + msgpack_sbuffer_init(&ctx->jumps); + shada_encode_jumps(&ctx->jumps); +} + +/// Restores the jumplist from a context. +/// +/// @param ctx Restore from this context. +static inline void ctx_restore_jumps(Context *ctx) + FUNC_ATTR_NONNULL_ALL +{ + shada_read_sbuf(&ctx->jumps, kShaDaWantInfo | kShaDaForceit); +} + +/// Saves the buffer list to a context. +/// +/// @param ctx Save to this context. +static inline void ctx_save_buflist(Context *ctx) + FUNC_ATTR_NONNULL_ALL +{ + msgpack_sbuffer_init(&ctx->buflist); + shada_encode_buflist(&ctx->buflist); +} + +/// Restores the buffer list from a context. +/// +/// @param ctx Restore from this context. +static inline void ctx_restore_buflist(Context *ctx) + FUNC_ATTR_NONNULL_ALL +{ + shada_read_sbuf(&ctx->buflist, kShaDaWantInfo | kShaDaForceit); +} + +/// Saves global variables to a context. +/// +/// @param ctx Save to this context. +static inline void ctx_save_gvars(Context *ctx) + FUNC_ATTR_NONNULL_ALL +{ + msgpack_sbuffer_init(&ctx->gvars); + shada_encode_gvars(&ctx->gvars); +} + +/// Restores global variables from a context. +/// +/// @param ctx Restore from this context. +static inline void ctx_restore_gvars(Context *ctx) + FUNC_ATTR_NONNULL_ALL +{ + shada_read_sbuf(&ctx->gvars, kShaDaWantInfo | kShaDaForceit); +} + +/// Saves functions to a context. +/// +/// @param ctx Save to this context. +/// @param scriptonly Save script-local (s:) functions only. +static inline void ctx_save_funcs(Context *ctx, bool scriptonly) + FUNC_ATTR_NONNULL_ALL +{ + ctx->funcs = (Array)ARRAY_DICT_INIT; + Error err = ERROR_INIT; + + HASHTAB_ITER(&func_hashtab, hi, { + const char_u *const name = hi->hi_key; + bool islambda = (STRNCMP(name, "<lambda>", 8) == 0); + bool isscript = (name[0] == K_SPECIAL); + + if (!islambda && (!scriptonly || isscript)) { + size_t cmd_len = sizeof("func! ") + STRLEN(name); + char *cmd = xmalloc(cmd_len); + snprintf(cmd, cmd_len, "func! %s", name); + String func_body = nvim_command_output(cstr_as_string(cmd), &err); + xfree(cmd); + if (!ERROR_SET(&err)) { + ADD(ctx->funcs, STRING_OBJ(func_body)); + } + api_clear_error(&err); + } + }); +} + +/// Restores functions from a context. +/// +/// @param ctx Restore from this context. +static inline void ctx_restore_funcs(Context *ctx) + FUNC_ATTR_NONNULL_ALL +{ + for (size_t i = 0; i < ctx->funcs.size; i++) { + do_cmdline_cmd(ctx->funcs.items[i].data.string.data); + } +} + +/// Convert msgpack_sbuffer to readfile()-style array. +/// +/// @param[in] sbuf msgpack_sbuffer to convert. +/// +/// @return readfile()-style array representation of "sbuf". +static inline Array sbuf_to_array(msgpack_sbuffer sbuf) +{ + list_T *const list = tv_list_alloc(kListLenMayKnow); + tv_list_append_string(list, "", 0); + if (sbuf.size > 0) { + encode_list_write(list, sbuf.data, sbuf.size); + } + + typval_T list_tv = (typval_T) { + .v_lock = VAR_UNLOCKED, + .v_type = VAR_LIST, + .vval.v_list = list + }; + + Array array = vim_to_object(&list_tv).data.array; + tv_clear(&list_tv); + return array; +} + +/// Convert readfile()-style array to msgpack_sbuffer. +/// +/// @param[in] array readfile()-style array to convert. +/// +/// @return msgpack_sbuffer with conversion result. +static inline msgpack_sbuffer array_to_sbuf(Array array) +{ + msgpack_sbuffer sbuf; + msgpack_sbuffer_init(&sbuf); + + typval_T list_tv; + Error err = ERROR_INIT; + object_to_vim(ARRAY_OBJ(array), &list_tv, &err); + + if (!encode_vim_list_to_buf(list_tv.vval.v_list, &sbuf.size, &sbuf.data)) { + EMSG(_("E474: Failed to convert list to msgpack string buffer")); + } + sbuf.alloc = sbuf.size; + + tv_clear(&list_tv); + api_clear_error(&err); + return sbuf; +} + +/// Converts Context to Dictionary representation. +/// +/// @param[in] ctx Context to convert. +/// +/// @return Dictionary representing "ctx". +Dictionary ctx_to_dict(Context *ctx) + FUNC_ATTR_NONNULL_ALL +{ + assert(ctx != NULL); + + Dictionary rv = ARRAY_DICT_INIT; + + PUT(rv, "regs", ARRAY_OBJ(sbuf_to_array(ctx->regs))); + PUT(rv, "jumps", ARRAY_OBJ(sbuf_to_array(ctx->jumps))); + PUT(rv, "buflist", ARRAY_OBJ(sbuf_to_array(ctx->buflist))); + PUT(rv, "gvars", ARRAY_OBJ(sbuf_to_array(ctx->gvars))); + PUT(rv, "funcs", ARRAY_OBJ(copy_array(ctx->funcs))); + + return rv; +} + +/// Converts Dictionary representation of Context back to Context object. +/// +/// @param[in] dict Context Dictionary representation. +/// @param[out] ctx Context object to store conversion result into. +/// +/// @return types of included context items. +int ctx_from_dict(Dictionary dict, Context *ctx) + FUNC_ATTR_NONNULL_ALL +{ + assert(ctx != NULL); + + int types = 0; + for (size_t i = 0; i < dict.size; i++) { + KeyValuePair item = dict.items[i]; + if (item.value.type != kObjectTypeArray) { + continue; + } + if (strequal(item.key.data, "regs")) { + types |= kCtxRegs; + ctx->regs = array_to_sbuf(item.value.data.array); + } else if (strequal(item.key.data, "jumps")) { + types |= kCtxJumps; + ctx->jumps = array_to_sbuf(item.value.data.array); + } else if (strequal(item.key.data, "buflist")) { + types |= kCtxBuflist; + ctx->buflist = array_to_sbuf(item.value.data.array); + } else if (strequal(item.key.data, "gvars")) { + types |= kCtxGVars; + ctx->gvars = array_to_sbuf(item.value.data.array); + } else if (strequal(item.key.data, "funcs")) { + types |= kCtxFuncs; + ctx->funcs = copy_object(item.value).data.array; + } + } + + return types; +} diff --git a/src/nvim/context.h b/src/nvim/context.h new file mode 100644 index 0000000000..328e12c6a6 --- /dev/null +++ b/src/nvim/context.h @@ -0,0 +1,46 @@ +#ifndef NVIM_CONTEXT_H +#define NVIM_CONTEXT_H + +#include <msgpack.h> +#include "nvim/api/private/defs.h" +#include "nvim/lib/kvec.h" + +typedef struct { + msgpack_sbuffer regs; ///< Registers. + msgpack_sbuffer jumps; ///< Jumplist. + msgpack_sbuffer buflist; ///< Buffer list. + msgpack_sbuffer gvars; ///< Global variables. + Array funcs; ///< Functions. +} Context; +typedef kvec_t(Context) ContextVec; + +#define MSGPACK_SBUFFER_INIT (msgpack_sbuffer) { \ + .size = 0, \ + .data = NULL, \ + .alloc = 0, \ +} + +#define CONTEXT_INIT (Context) { \ + .regs = MSGPACK_SBUFFER_INIT, \ + .jumps = MSGPACK_SBUFFER_INIT, \ + .buflist = MSGPACK_SBUFFER_INIT, \ + .gvars = MSGPACK_SBUFFER_INIT, \ + .funcs = ARRAY_DICT_INIT, \ +} + +typedef enum { + kCtxRegs = 1, ///< Registers + kCtxJumps = 2, ///< Jumplist + kCtxBuflist = 4, ///< Buffer list + kCtxGVars = 8, ///< Global variables + kCtxSFuncs = 16, ///< Script functions + kCtxFuncs = 32, ///< Functions +} ContextTypeFlags; + +extern int kCtxAll; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "context.h.generated.h" +#endif + +#endif // NVIM_CONTEXT_H diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c index bc14761877..f2b3cfe690 100644 --- a/src/nvim/cursor.c +++ b/src/nvim/cursor.c @@ -5,6 +5,7 @@ #include <inttypes.h> #include "nvim/assert.h" +#include "nvim/change.h" #include "nvim/cursor.h" #include "nvim/charset.h" #include "nvim/fold.h" diff --git a/src/nvim/diff.c b/src/nvim/diff.c index f720e702a4..7328b88a40 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -18,6 +18,7 @@ #include "nvim/ascii.h" #include "nvim/diff.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/eval.h" @@ -951,7 +952,7 @@ static int check_external_diff(diffio_T *diffio) TriState ok = kFalse; for (;;) { ok = kFalse; - FILE *fd = mch_fopen((char *)diffio->dio_orig.din_fname, "w"); + FILE *fd = os_fopen((char *)diffio->dio_orig.din_fname, "w"); if (fd == NULL) { io_error = true; @@ -960,7 +961,7 @@ static int check_external_diff(diffio_T *diffio) io_error = true; } fclose(fd); - fd = mch_fopen((char *)diffio->dio_new.din_fname, "w"); + fd = os_fopen((char *)diffio->dio_new.din_fname, "w"); if (fd == NULL) { io_error = true; @@ -971,7 +972,7 @@ static int check_external_diff(diffio_T *diffio) fclose(fd); fd = NULL; if (diff_file(diffio) == OK) { - fd = mch_fopen((char *)diffio->dio_diff.dout_fname, "r"); + fd = os_fopen((char *)diffio->dio_diff.dout_fname, "r"); } if (fd == NULL) { @@ -1505,7 +1506,7 @@ static void diff_read(int idx_orig, int idx_new, diffout_T *dout) if (dout->dout_fname == NULL) { diffstyle = DIFF_UNIFIED; } else { - fd = mch_fopen((char *)dout->dout_fname, "r"); + fd = os_fopen((char *)dout->dout_fname, "r"); if (fd == NULL) { EMSG(_("E98: Cannot read diff output")); return; @@ -2190,7 +2191,7 @@ int diffopt_changed(void) } diff_flags = diff_flags_new; - diff_context = diff_context_new; + diff_context = diff_context_new == 0 ? 1 : diff_context_new; diff_foldcolumn = diff_foldcolumn_new; diff_algorithm = diff_algorithm_new; diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index 3329290634..d47ad3f1c0 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -18,6 +18,7 @@ #include "nvim/ex_docmd.h" #include "nvim/ex_getln.h" #include "nvim/getchar.h" +#include "nvim/misc1.h" #include "nvim/mbyte.h" #include "nvim/message.h" #include "nvim/memory.h" @@ -1496,8 +1497,8 @@ int get_digraph(int cmdline) } if (cmdline) { - if ((char2cells(c) == 1) && (cmdline_star == 0)) { - putcmdline(c, TRUE); + if ((char2cells(c) == 1) && c < 128 && (cmdline_star == 0)) { + putcmdline((char)c, true); } } else { add_to_showcmd(c); @@ -1663,13 +1664,13 @@ void listdigraphs(void) printdigraph(&tmp); } dp++; - os_breakcheck(); + fast_breakcheck(); } dp = (digr_T *)user_digraphs.ga_data; for (int i = 0; i < user_digraphs.ga_len && !got_int; ++i) { printdigraph(dp); - os_breakcheck(); + fast_breakcheck(); dp++; } } diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 9af003f140..2ac429cf9e 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -14,6 +14,7 @@ #include "nvim/ascii.h" #include "nvim/edit.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/digraph.h" @@ -64,6 +65,7 @@ #define CTRL_X_WANT_IDENT 0x100 +#define CTRL_X_NORMAL 0 ///< CTRL-N CTRL-P completion, default #define CTRL_X_NOT_DEFINED_YET 1 #define CTRL_X_SCROLL 2 #define CTRL_X_WHOLE_LINE 3 @@ -78,11 +80,12 @@ #define CTRL_X_FUNCTION 12 #define CTRL_X_OMNI 13 #define CTRL_X_SPELL 14 -#define CTRL_X_LOCAL_MSG 15 /* only used in "ctrl_x_msgs" */ +#define CTRL_X_LOCAL_MSG 15 ///< only used in "ctrl_x_msgs" #define CTRL_X_EVAL 16 ///< for builtin function complete() #define CTRL_X_MSG(i) ctrl_x_msgs[(i) & ~CTRL_X_WANT_IDENT] -#define CTRL_X_MODE_LINE_OR_EVAL(m) (m == CTRL_X_WHOLE_LINE || m == CTRL_X_EVAL) +#define CTRL_X_MODE_LINE_OR_EVAL(m) \ + ((m) == CTRL_X_WHOLE_LINE || (m) == CTRL_X_EVAL) // Message for CTRL-X mode, index is ctrl_x_mode. static char *ctrl_x_msgs[] = @@ -192,6 +195,9 @@ static int compl_restarting = FALSE; /* don't insert match */ * FALSE the word to be completed must be located. */ static int compl_started = FALSE; +// Which Ctrl-X mode are we in? +static int ctrl_x_mode = CTRL_X_NORMAL; + static int compl_matches = 0; static char_u *compl_pattern = NULL; static int compl_direction = FORWARD; @@ -276,7 +282,7 @@ static int ins_need_undo; /* call u_save() before inserting a static bool did_add_space = false; // auto_format() added an extra space // under the cursor static TriState dont_sync_undo = kFalse; // CTRL-G U prevents syncing undo - // for the next left/right cursor + // for the next left/right cursor key static linenr_T o_lnum = 0; @@ -509,7 +515,7 @@ static int insert_check(VimState *state) // If typed something may trigger CursorHoldI again. if (s->c != K_EVENT // but not in CTRL-X mode, a script can't restore the state - && ctrl_x_mode == 0) { + && ctrl_x_mode == CTRL_X_NORMAL) { did_cursorhold = false; } @@ -518,7 +524,10 @@ static int insert_check(VimState *state) s->inserted_space = false; } - if (can_cindent && cindent_on() && ctrl_x_mode == 0 && !compl_started) { + if (can_cindent + && cindent_on() + && ctrl_x_mode == CTRL_X_NORMAL + && !compl_started) { insert_do_cindent(s); } @@ -641,7 +650,9 @@ static int insert_execute(VimState *state, int key) s->c = key; // Don't want K_EVENT with cursorhold for the second key, e.g., after CTRL-V. - did_cursorhold = true; + if (key != K_EVENT) { + did_cursorhold = true; + } if (p_hkmap && KeyTyped) { s->c = hkmap(s->c); // Hebrew mode mapping @@ -1037,7 +1048,7 @@ check_pum: if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) { ins_s_left(); } else { - ins_left(dont_sync_undo == kFalse); + ins_left(); } break; @@ -1050,7 +1061,7 @@ check_pum: if (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL)) { ins_s_right(); } else { - ins_right(dont_sync_undo == kFalse); + ins_right(); } break; @@ -1204,7 +1215,8 @@ check_pum: // if 'complete' is empty then plain ^P is no longer special, // but it is under other ^X modes if (*curbuf->b_p_cpt == NUL - && ctrl_x_mode != 0 + && (ctrl_x_mode == CTRL_X_NORMAL + || ctrl_x_mode == CTRL_X_WHOLE_LINE) && !(compl_cont_status & CONT_LOCAL)) { goto normalchar; } @@ -1921,6 +1933,19 @@ static void ins_ctrl_x(void) } } +// Whether other than default completion has been selected. +bool ctrl_x_mode_not_default(void) +{ + return ctrl_x_mode != CTRL_X_NORMAL; +} + +// Whether CTRL-X was typed without a following character. +bool ctrl_x_mode_not_defined_yet(void) +{ + return ctrl_x_mode == CTRL_X_NOT_DEFINED_YET; +} + + /// Check that the "dict" or "tsr" option can be used. /// /// @param dict_opt check "dict" when true, "tsr" when false. @@ -1929,7 +1954,7 @@ static bool check_compl_option(bool dict_opt) if (dict_opt ? (*curbuf->b_p_dict == NUL && *p_dict == NUL && !curwin->w_p_spell) : (*curbuf->b_p_tsr == NUL && *p_tsr == NUL)) { - ctrl_x_mode = 0; + ctrl_x_mode = CTRL_X_NORMAL; edit_submode = NULL; msg_attr((dict_opt ? _("'dictionary' option is empty") @@ -2460,7 +2485,7 @@ void completeopt_was_set(void) void set_completion(colnr_T startcol, list_T *list) { // If already doing completions stop it. - if (ctrl_x_mode != 0) { + if (ctrl_x_mode != CTRL_X_NORMAL) { ins_compl_prep(' '); } ins_compl_clear(); @@ -2556,10 +2581,35 @@ static bool pum_enough_matches(void) return i >= 2; } -/* - * Show the popup menu for the list of matches. - * Also adjusts "compl_shown_match" to an entry that is actually displayed. - */ +static void trigger_complete_changed_event(int cur) +{ + static bool recursive = false; + + if (recursive) { + return; + } + + dict_T *v_event = get_vim_var_dict(VV_EVENT); + if (cur < 0) { + tv_dict_add_dict(v_event, S_LEN("completed_item"), tv_dict_alloc()); + } else { + dict_T *item = ins_compl_dict_alloc(compl_curr_match); + tv_dict_add_dict(v_event, S_LEN("completed_item"), item); + } + pum_set_event_info(v_event); + tv_dict_set_keys_readonly(v_event); + + recursive = true; + textlock++; + apply_autocmds(EVENT_COMPLETECHANGED, NULL, NULL, false, curbuf); + textlock--; + recursive = false; + + tv_dict_clear(v_event); +} + +/// Show the popup menu for the list of matches. +/// Also adjusts "compl_shown_match" to an entry that is actually displayed. void ins_compl_show_pum(void) { compl_T *compl; @@ -2691,22 +2741,9 @@ void ins_compl_show_pum(void) pum_display(compl_match_array, compl_match_arraysize, cur, array_changed, 0); curwin->w_cursor.col = col; - if (!has_event(EVENT_COMPLETECHANGED)) { - return; - } - dict_T *dict = get_vim_var_dict(VV_EVENT); - if (cur < 0) { - tv_dict_add_dict(dict, S_LEN("completed_item"), tv_dict_alloc()); - } else { - dict_T *item = ins_compl_dict_alloc(compl_curr_match); - tv_dict_add_dict(dict, S_LEN("completed_item"), item); + if (has_event(EVENT_COMPLETECHANGED)) { + trigger_complete_changed_event(cur); } - pum_set_boundings(dict); - tv_dict_set_keys_readonly(dict); - textlock++; - apply_autocmds(EVENT_COMPLETECHANGED, NULL, NULL, false, curbuf); - textlock--; - tv_dict_clear(dict); } #define DICT_FIRST (1) /* use just first element in "dict" */ @@ -2820,7 +2857,7 @@ static void ins_compl_files(int count, char_u **files, int thesaurus, int flags, int add_r; for (i = 0; i < count && !got_int && !compl_interrupted; i++) { - fp = mch_fopen((char *)files[i], "r"); /* open dictionary file */ + fp = os_fopen((char *)files[i], "r"); // open dictionary file if (flags != DICT_EXACT) { vim_snprintf((char *)IObuff, IOSIZE, _("Scanning dictionary: %s"), (char *)files[i]); @@ -3086,6 +3123,7 @@ void get_complete_info(list_T *what_list, dict_T *retdict) ? compl_curr_match->cp_number - 1 : -1); } + (void)ret; // TODO(vim): // if (ret == OK && (what_flag & CI_WHAT_INSERTED)) } @@ -3331,7 +3369,7 @@ static bool ins_compl_prep(int c) /* Set "compl_get_longest" when finding the first matches. */ if (ctrl_x_mode == CTRL_X_NOT_DEFINED_YET - || (ctrl_x_mode == 0 && !compl_started)) { + || (ctrl_x_mode == CTRL_X_NORMAL && !compl_started)) { compl_get_longest = (strstr((char *)p_cot, "longest") != NULL); compl_used_match = TRUE; @@ -3426,18 +3464,19 @@ static bool ins_compl_prep(int c) else compl_cont_mode = CTRL_X_NOT_DEFINED_YET; } - ctrl_x_mode = 0; + ctrl_x_mode = CTRL_X_NORMAL; edit_submode = NULL; showmode(); break; } - } else if (ctrl_x_mode != 0) { - /* We're already in CTRL-X mode, do we stay in it? */ + } else if (ctrl_x_mode != CTRL_X_NORMAL) { + // We're already in CTRL-X mode, do we stay in it? if (!vim_is_ctrl_x_key(c)) { - if (ctrl_x_mode == CTRL_X_SCROLL) - ctrl_x_mode = 0; - else + if (ctrl_x_mode == CTRL_X_SCROLL) { + ctrl_x_mode = CTRL_X_NORMAL; + } else { ctrl_x_mode = CTRL_X_FINISHED; + } edit_submode = NULL; } showmode(); @@ -3448,7 +3487,10 @@ static bool ins_compl_prep(int c) * 'Pattern not found') until another key is hit, then go back to * showing what mode we are in. */ showmode(); - if ((ctrl_x_mode == 0 && c != Ctrl_N && c != Ctrl_P && c != Ctrl_R + if ((ctrl_x_mode == CTRL_X_NORMAL + && c != Ctrl_N + && c != Ctrl_P + && c != Ctrl_R && !ins_compl_pum_key(c)) || ctrl_x_mode == CTRL_X_FINISHED) { /* Get here when we have finished typing a sequence of ^N and @@ -3526,8 +3568,8 @@ static bool ins_compl_prep(int c) if (!shortmess(SHM_COMPLETIONMENU)) { msg_clr_cmdline(); // necessary for "noshowmode" } - ctrl_x_mode = 0; - compl_enter_selects = FALSE; + ctrl_x_mode = CTRL_X_NORMAL; + compl_enter_selects = false; if (edit_submode != NULL) { edit_submode = NULL; showmode(); @@ -3654,14 +3696,19 @@ expand_by_function ( return; // Call 'completefunc' to obtain the list of matches. - const char_u *const args[2] = { (char_u *)"0", base }; + typval_T args[3]; + args[0].v_type = VAR_NUMBER; + args[1].v_type = VAR_STRING; + args[2].v_type = VAR_UNKNOWN; + args[0].vval.v_number = 0; + args[1].vval.v_string = base != NULL ? base : (char_u *)""; pos = curwin->w_cursor; curwin_save = curwin; curbuf_save = curbuf; - /* Call a function, which returns a list or dict. */ - if (call_vim_function(funcname, 2, args, FALSE, FALSE, &rettv) == OK) { + // Call a function, which returns a list or dict. + if (call_vim_function(funcname, 2, args, &rettv, false) == OK) { switch (rettv.v_type) { case VAR_LIST: matchlist = rettv.vval.v_list; @@ -3838,11 +3885,14 @@ static int ins_compl_get_exp(pos_T *ini) e_cpt = (compl_cont_status & CONT_LOCAL) ? (char_u *)"." : curbuf->b_p_cpt; last_match_pos = first_match_pos = *ini; + } else if (ins_buf != curbuf && !buf_valid(ins_buf)) { + ins_buf = curbuf; // In case the buffer was wiped out. } compl_old_match = compl_curr_match; // remember the last current match pos = (compl_direction == FORWARD) ? &last_match_pos : &first_match_pos; - /* For ^N/^P loop over all the flags/windows/buffers in 'complete' */ + + // For ^N/^P loop over all the flags/windows/buffers in 'complete' for (;; ) { found_new_match = FAIL; set_match_pos = FALSE; @@ -3852,7 +3902,8 @@ static int ins_compl_get_exp(pos_T *ini) /* For ^N/^P pick a new entry from e_cpt if compl_started is off, * or if found_all says this entry is done. For ^X^L only use the * entries from 'complete' that look in loaded buffers. */ - if ((l_ctrl_x_mode == 0 || CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode)) + if ((l_ctrl_x_mode == CTRL_X_NORMAL + || CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode)) && (!compl_started || found_all)) { found_all = FALSE; while (*e_cpt == ',' || *e_cpt == ' ') @@ -3862,7 +3913,7 @@ static int ins_compl_get_exp(pos_T *ini) first_match_pos = *ini; // Move the cursor back one character so that ^N can match the // word immediately after the cursor. - if (ctrl_x_mode == 0 && dec(&first_match_pos) < 0) { + if (ctrl_x_mode == CTRL_X_NORMAL && dec(&first_match_pos) < 0) { // Move the cursor to after the last character in the // buffer, so that word at start of buffer is found // correctly. @@ -3980,9 +4031,9 @@ static int ins_compl_get_exp(pos_T *ini) /* Find up to TAG_MANY matches. Avoids that an enormous number * of matches is found when compl_pattern is empty */ if (find_tags(compl_pattern, &num_matches, &matches, - TAG_REGEXP | TAG_NAMES | TAG_NOIC | - TAG_INS_COMP | (l_ctrl_x_mode ? TAG_VERBOSE : 0), - TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) { + TAG_REGEXP | TAG_NAMES | TAG_NOIC | TAG_INS_COMP + | (l_ctrl_x_mode != CTRL_X_NORMAL ? TAG_VERBOSE : 0), + TAG_MANY, curbuf->b_ffname) == OK && num_matches > 0) { ins_compl_add_matches(num_matches, matches, p_ic); } p_ic = save_p_ic; @@ -4156,9 +4207,10 @@ static int ins_compl_get_exp(pos_T *ini) found_new_match = OK; } - /* break the loop for specialized modes (use 'complete' just for the - * generic l_ctrl_x_mode == 0) or when we've found a new match */ - if ((l_ctrl_x_mode != 0 && !CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode)) + // break the loop for specialized modes (use 'complete' just for the + // generic l_ctrl_x_mode == CTRL_X_NORMAL) or when we've found a new match + if ((l_ctrl_x_mode != CTRL_X_NORMAL + && !CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode)) || found_new_match != FAIL) { if (got_int) break; @@ -4166,7 +4218,8 @@ static int ins_compl_get_exp(pos_T *ini) if (type != -1) ins_compl_check_keys(0, false); - if ((l_ctrl_x_mode != 0 && !CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode)) + if ((l_ctrl_x_mode != CTRL_X_NORMAL + && !CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode)) || compl_interrupted) { break; } @@ -4183,14 +4236,16 @@ static int ins_compl_get_exp(pos_T *ini) } compl_started = TRUE; - if ((l_ctrl_x_mode == 0 || CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode)) + if ((l_ctrl_x_mode == CTRL_X_NORMAL + || CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode)) && *e_cpt == NUL) { // Got to end of 'complete' found_new_match = FAIL; } i = -1; /* total of matches, unknown */ if (found_new_match == FAIL - || (l_ctrl_x_mode != 0 && !CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode))) { + || (l_ctrl_x_mode != CTRL_X_NORMAL + && !CTRL_X_MODE_LINE_OR_EVAL(l_ctrl_x_mode))) { i = ins_compl_make_cyclic(); } @@ -4505,12 +4560,12 @@ void ins_compl_check_keys(int frequency, int in_compl_func) { static int count = 0; - int c; - - /* Don't check when reading keys from a script. That would break the test - * scripts */ - if (using_script()) + // Don't check when reading keys from a script, :normal or feedkeys(). + // That would break the test scripts. But do check for keys when called + // from complete_check(). + if (!in_compl_func && (using_script() || ex_normal_busy)) { return; + } /* Only do this at regular intervals */ if (++count < frequency) @@ -4519,7 +4574,7 @@ void ins_compl_check_keys(int frequency, int in_compl_func) /* Check for a typed key. Do use mappings, otherwise vim_is_ctrl_x_key() * can't do its work correctly. */ - c = vpeekc_any(); + int c = vpeekc_any(); if (c != NUL) { if (vim_is_ctrl_x_key(c) && c != Ctrl_X && c != Ctrl_R) { c = safe_vgetc(); /* Eat the character */ @@ -4668,8 +4723,9 @@ static int ins_complete(int c, bool enable_pum) /* * it is a continued search */ - compl_cont_status &= ~CONT_INTRPT; /* remove INTRPT */ - if (ctrl_x_mode == 0 || ctrl_x_mode == CTRL_X_PATH_PATTERNS + compl_cont_status &= ~CONT_INTRPT; // remove INTRPT + if (ctrl_x_mode == CTRL_X_NORMAL + || ctrl_x_mode == CTRL_X_PATH_PATTERNS || ctrl_x_mode == CTRL_X_PATH_DEFINES) { if (compl_startpos.lnum != curwin->w_cursor.lnum) { /* line (probably) wrapped, set compl_startpos to the @@ -4713,16 +4769,18 @@ static int ins_complete(int c, bool enable_pum) if (!(compl_cont_status & CONT_ADDING)) { /* normal expansion */ compl_cont_mode = ctrl_x_mode; - if (ctrl_x_mode != 0) /* Remove LOCAL if ctrl_x_mode != 0 */ + if (ctrl_x_mode != CTRL_X_NORMAL) { + // Remove LOCAL if ctrl_x_mode != CTRL_X_NORMAL compl_cont_status = 0; + } compl_cont_status |= CONT_N_ADDS; compl_startpos = curwin->w_cursor; startcol = (int)curs_col; compl_col = 0; } - /* Work out completion pattern and original text -- webb */ - if (ctrl_x_mode == 0 || (ctrl_x_mode & CTRL_X_WANT_IDENT)) { + // Work out completion pattern and original text -- webb + if (ctrl_x_mode == CTRL_X_NORMAL || (ctrl_x_mode & CTRL_X_WANT_IDENT)) { if ((compl_cont_status & CONT_SOL) || ctrl_x_mode == CTRL_X_PATH_DEFINES) { if (!(compl_cont_status & CONT_ADDING)) { @@ -4853,7 +4911,13 @@ static int ins_complete(int c, bool enable_pum) return FAIL; } - const char_u *const args[2] = { (char_u *)"1", NULL }; + typval_T args[3]; + args[0].v_type = VAR_NUMBER; + args[1].v_type = VAR_STRING; + args[2].v_type = VAR_UNKNOWN; + args[0].vval.v_number = 1; + args[1].vval.v_string = (char_u *)""; + pos = curwin->w_cursor; curwin_save = curwin; curbuf_save = curbuf; @@ -4877,7 +4941,7 @@ static int ins_complete(int c, bool enable_pum) if (col == -2) return FAIL; if (col == -3) { - ctrl_x_mode = 0; + ctrl_x_mode = CTRL_X_NORMAL; edit_submode = NULL; if (!shortmess(SHM_COMPLETIONMENU)) { msg_clr_cmdline(); @@ -5008,12 +5072,13 @@ static int ins_complete(int c, bool enable_pum) * because we couldn't expand anything at first place, but if we used * ^P, ^N, ^X^I or ^X^D we might want to add-expand a single-char-word * (such as M in M'exico) if not tried already. -- Acevedo */ - if ( compl_length > 1 - || (compl_cont_status & CONT_ADDING) - || (ctrl_x_mode != 0 - && ctrl_x_mode != CTRL_X_PATH_PATTERNS - && ctrl_x_mode != CTRL_X_PATH_DEFINES)) + if (compl_length > 1 + || (compl_cont_status & CONT_ADDING) + || (ctrl_x_mode != CTRL_X_NORMAL + && ctrl_x_mode != CTRL_X_PATH_PATTERNS + && ctrl_x_mode != CTRL_X_PATH_DEFINES)) { compl_cont_status &= ~CONT_N_ADDS; + } } if (compl_curr_match->cp_flags & CONT_S_IPOS) @@ -8100,9 +8165,10 @@ static void ins_mousescroll(int dir) -static void ins_left(bool end_change) +static void ins_left(void) { pos_T tpos; + const bool end_change = dont_sync_undo == kFalse; // end undoable change if ((fdo_flags & FDO_HOR) && KeyTyped) foldOpenCursor(); @@ -8164,23 +8230,31 @@ static void ins_end(int c) static void ins_s_left(void) { - if ((fdo_flags & FDO_HOR) && KeyTyped) + const bool end_change = dont_sync_undo == kFalse; // end undoable change + if ((fdo_flags & FDO_HOR) && KeyTyped) { foldOpenCursor(); + } undisplay_dollar(); if (curwin->w_cursor.lnum > 1 || curwin->w_cursor.col > 0) { - start_arrow(&curwin->w_cursor); + start_arrow_with_change(&curwin->w_cursor, end_change); + if (!end_change) { + AppendCharToRedobuff(K_S_LEFT); + } (void)bck_word(1L, false, false); curwin->w_set_curswant = true; } else { vim_beep(BO_CRSR); } + dont_sync_undo = kFalse; } /// @param end_change end undoable change -static void ins_right(bool end_change) +static void ins_right(void) { - if ((fdo_flags & FDO_HOR) && KeyTyped) + const bool end_change = dont_sync_undo == kFalse; // end undoable change + if ((fdo_flags & FDO_HOR) && KeyTyped) { foldOpenCursor(); + } undisplay_dollar(); if (gchar_cursor() != NUL || virtual_active()) { start_arrow_with_change(&curwin->w_cursor, end_change); @@ -8217,17 +8291,23 @@ static void ins_right(bool end_change) static void ins_s_right(void) { - if ((fdo_flags & FDO_HOR) && KeyTyped) + const bool end_change = dont_sync_undo == kFalse; // end undoable change + if ((fdo_flags & FDO_HOR) && KeyTyped) { foldOpenCursor(); + } undisplay_dollar(); if (curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count || gchar_cursor() != NUL) { - start_arrow(&curwin->w_cursor); + start_arrow_with_change(&curwin->w_cursor, end_change); + if (!end_change) { + AppendCharToRedobuff(K_S_RIGHT); + } (void)fwd_word(1L, false, 0); curwin->w_set_curswant = true; } else { vim_beep(BO_CRSR); } + dont_sync_undo = kFalse; } static void diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 942f8d5e09..a3a66a5764 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -24,8 +24,10 @@ #endif #include "nvim/eval.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/channel.h" #include "nvim/charset.h" +#include "nvim/context.h" #include "nvim/cursor.h" #include "nvim/diff.h" #include "nvim/edit.h" @@ -176,6 +178,7 @@ static char *e_funcref = N_("E718: Funcref required"); static char *e_dictrange = N_("E719: Cannot use [:] with a Dictionary"); static char *e_nofunc = N_("E130: Unknown function: %s"); static char *e_illvar = N_("E461: Illegal variable name: %s"); +static char *e_cannot_mod = N_("E995: Cannot modify existing variable"); static const char *e_readonlyvar = N_( "E46: Cannot change read-only variable \"%.*s\""); @@ -302,15 +305,6 @@ typedef struct { list_T *fi_list; /* list being used */ } forinfo_T; -/* - * enum used by var_flavour() - */ -typedef enum { - VAR_FLAVOUR_DEFAULT, /* doesn't start with uppercase */ - VAR_FLAVOUR_SESSION, /* starts with uppercase, some lower */ - VAR_FLAVOUR_SHADA /* all uppercase */ -} var_flavour_T; - /* values for vv_flags: */ #define VV_COMPAT 1 /* compatible, also used without "v:" */ #define VV_RO 2 /* read-only */ @@ -533,6 +527,35 @@ const list_T *eval_msgpack_type_lists[] = { [kMPExt] = NULL, }; +// Return "n1" divided by "n2", taking care of dividing by zero. +varnumber_T num_divide(varnumber_T n1, varnumber_T n2) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + varnumber_T result; + + if (n2 == 0) { // give an error message? + if (n1 == 0) { + result = VARNUMBER_MIN; // similar to NaN + } else if (n1 < 0) { + result = -VARNUMBER_MAX; + } else { + result = VARNUMBER_MAX; + } + } else { + result = n1 / n2; + } + + return result; +} + +// Return "n1" modulus "n2", taking care of dividing by zero. +varnumber_T num_modulus(varnumber_T n1, varnumber_T n2) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + // Give an error when n2 is 0? + return (n2 == 0) ? 0 : (n1 % n2); +} + /* * Initialize the global and v: variables. */ @@ -776,10 +799,11 @@ var_redir_start( did_emsg = FALSE; tv.v_type = VAR_STRING; tv.vval.v_string = (char_u *)""; - if (append) - set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"."); - else - set_var_lval(redir_lval, redir_endp, &tv, TRUE, (char_u *)"="); + if (append) { + set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"."); + } else { + set_var_lval(redir_lval, redir_endp, &tv, true, false, (char_u *)"="); + } clear_lval(redir_lval); err = did_emsg; did_emsg |= save_emsg; @@ -837,7 +861,7 @@ void var_redir_stop(void) redir_endp = (char_u *)get_lval(redir_varname, NULL, redir_lval, false, false, 0, FNE_CHECK_START); if (redir_endp != NULL && redir_lval->ll_name != NULL) { - set_var_lval(redir_lval, redir_endp, &tv, false, (char_u *)"."); + set_var_lval(redir_lval, redir_endp, &tv, false, false, (char_u *)"."); } clear_lval(redir_lval); } @@ -955,6 +979,89 @@ eval_to_bool( return retval; } +// Call eval1() and give an error message if not done at a lower level. +static int eval1_emsg(char_u **arg, typval_T *rettv, bool evaluate) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + const char_u *const start = *arg; + const int did_emsg_before = did_emsg; + const int called_emsg_before = called_emsg; + + const int ret = eval1(arg, rettv, evaluate); + if (ret == FAIL) { + // Report the invalid expression unless the expression evaluation has + // been cancelled due to an aborting error, an interrupt, or an + // exception, or we already gave a more specific error. + // Also check called_emsg for when using assert_fails(). + if (!aborting() + && did_emsg == did_emsg_before + && called_emsg == called_emsg_before) { + emsgf(_(e_invexpr2), start); + } + } + return ret; +} + +static int eval_expr_typval(const typval_T *expr, typval_T *argv, + int argc, typval_T *rettv) + FUNC_ATTR_NONNULL_ARG(1, 2, 4) +{ + int dummy; + + if (expr->v_type == VAR_FUNC) { + const char_u *const s = expr->vval.v_string; + if (s == NULL || *s == NUL) { + return FAIL; + } + if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL, + 0L, 0L, &dummy, true, NULL, NULL) == FAIL) { + return FAIL; + } + } else if (expr->v_type == VAR_PARTIAL) { + partial_T *const partial = expr->vval.v_partial; + const char_u *const s = partial_name(partial); + if (s == NULL || *s == NUL) { + return FAIL; + } + if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL, + 0L, 0L, &dummy, true, partial, NULL) == FAIL) { + return FAIL; + } + } else { + char buf[NUMBUFLEN]; + char_u *s = (char_u *)tv_get_string_buf_chk(expr, buf); + if (s == NULL) { + return FAIL; + } + s = skipwhite(s); + if (eval1_emsg(&s, rettv, true) == FAIL) { + return FAIL; + } + if (*s != NUL) { // check for trailing chars after expr + tv_clear(rettv); + emsgf(_(e_invexpr2), s); + return FAIL; + } + } + return OK; +} + +/// Like eval_to_bool() but using a typval_T instead of a string. +/// Works for string, funcref and partial. +static bool eval_expr_to_bool(const typval_T *expr, bool *error) + FUNC_ATTR_NONNULL_ARG(1, 2) +{ + typval_T argv, rettv; + + if (eval_expr_typval(expr, &argv, 0, &rettv) == FAIL) { + *error = true; + return false; + } + const bool res = (tv_get_number_chk(&rettv, error) != 0); + tv_clear(&rettv); + return res; +} + /// Top level evaluation function, returning a string /// /// @param[in] arg String to evaluate. @@ -1190,68 +1297,35 @@ int get_spellword(list_T *const list, const char **ret_word) // Call some vim script function and return the result in "*rettv". -// Uses argv[argc] for the function arguments. Only Number and String -// arguments are currently supported. +// Uses argv[0] to argv[argc-1] for the function arguments. argv[argc] +// should have type VAR_UNKNOWN. // // Return OK or FAIL. int call_vim_function( const char_u *func, int argc, - const char_u *const *const argv, - bool safe, // use the sandbox - int str_arg_only, // all arguments are strings - typval_T *rettv + typval_T *argv, + typval_T *rettv, + bool safe // use the sandbox ) { - varnumber_T n; - int len; int doesrange; void *save_funccalp = NULL; int ret; - typval_T *argvars = xmalloc((argc + 1) * sizeof(typval_T)); - - for (int i = 0; i < argc; i++) { - // Pass a NULL or empty argument as an empty string - if (argv[i] == NULL || *argv[i] == NUL) { - argvars[i].v_type = VAR_STRING; - argvars[i].vval.v_string = (char_u *)""; - continue; - } - - if (str_arg_only) { - len = 0; - } else { - // Recognize a number argument, the others must be strings. A dash - // is a string too. - vim_str2nr(argv[i], NULL, &len, STR2NR_ALL, &n, NULL, 0); - if (len == 1 && *argv[i] == '-') { - len = 0; - } - } - if (len != 0 && len == (int)STRLEN(argv[i])) { - argvars[i].v_type = VAR_NUMBER; - argvars[i].vval.v_number = n; - } else { - argvars[i].v_type = VAR_STRING; - argvars[i].vval.v_string = (char_u *)argv[i]; - } - } - if (safe) { save_funccalp = save_funccal(); ++sandbox; } rettv->v_type = VAR_UNKNOWN; // tv_clear() uses this. - ret = call_func(func, (int)STRLEN(func), rettv, argc, argvars, NULL, + ret = call_func(func, (int)STRLEN(func), rettv, argc, argv, NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &doesrange, true, NULL, NULL); if (safe) { --sandbox; restore_funccal(save_funccalp); } - xfree(argvars); if (ret == FAIL) { tv_clear(rettv); @@ -1259,47 +1333,44 @@ int call_vim_function( return ret; } - /// Call Vim script function and return the result as a number /// /// @param[in] func Function name. /// @param[in] argc Number of arguments. -/// @param[in] argv Array with string arguments. +/// @param[in] argv Array with typval_T arguments. /// @param[in] safe Use with sandbox. /// /// @return -1 when calling function fails, result of function otherwise. varnumber_T call_func_retnr(char_u *func, int argc, - const char_u *const *const argv, int safe) + typval_T *argv, int safe) { typval_T rettv; varnumber_T retval; - /* All arguments are passed as strings, no conversion to number. */ - if (call_vim_function(func, argc, argv, safe, TRUE, &rettv) == FAIL) + if (call_vim_function(func, argc, argv, &rettv, safe) == FAIL) { return -1; - + } retval = tv_get_number_chk(&rettv, NULL); tv_clear(&rettv); return retval; } - /// Call Vim script function and return the result as a string /// /// @param[in] func Function name. /// @param[in] argc Number of arguments. -/// @param[in] argv Array with string arguments. +/// @param[in] argv Array with typval_T arguments. /// @param[in] safe Use the sandbox. /// /// @return [allocated] NULL when calling function fails, allocated string /// otherwise. char *call_func_retstr(const char *const func, int argc, - const char_u *const *argv, + typval_T *argv, bool safe) FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC { typval_T rettv; // All arguments are passed as strings, no conversion to number. - if (call_vim_function((const char_u *)func, argc, argv, safe, true, &rettv) + if (call_vim_function((const char_u *)func, argc, argv, &rettv, safe) == FAIL) { return NULL; } @@ -1308,24 +1379,24 @@ char *call_func_retstr(const char *const func, int argc, tv_clear(&rettv); return retval; } - /// Call Vim script function and return the result as a List /// /// @param[in] func Function name. /// @param[in] argc Number of arguments. -/// @param[in] argv Array with string arguments. +/// @param[in] argv Array with typval_T arguments. /// @param[in] safe Use the sandbox. /// /// @return [allocated] NULL when calling function fails or return tv is not a /// List, allocated List otherwise. -void *call_func_retlist(char_u *func, int argc, const char_u *const *argv, +void *call_func_retlist(char_u *func, int argc, typval_T *argv, bool safe) { typval_T rettv; - /* All arguments are passed as strings, no conversion to number. */ - if (call_vim_function(func, argc, argv, safe, TRUE, &rettv) == FAIL) + // All arguments are passed as strings, no conversion to number. + if (call_vim_function(func, argc, argv, &rettv, safe) == FAIL) { return NULL; + } if (rettv.v_type != VAR_LIST) { tv_clear(&rettv); @@ -1436,21 +1507,33 @@ int eval_foldexpr(char_u *arg, int *cp) return (int)retval; } -/* - * ":let" list all variable values - * ":let var1 var2" list variable values - * ":let var = expr" assignment command. - * ":let var += expr" assignment command. - * ":let var -= expr" assignment command. - * ":let var *= expr" assignment command. - * ":let var /= expr" assignment command. - * ":let var %= expr" assignment command. - * ":let var .= expr" assignment command. - * ":let var ..= expr" assignment command. - * ":let [var1, var2] = expr" unpack list. - */ +// ":cons[t] var = expr1" define constant +// ":cons[t] [name1, name2, ...] = expr1" define constants unpacking list +// ":cons[t] [name, ..., ; lastname] = expr" define constants unpacking list +void ex_const(exarg_T *eap) +{ + ex_let_const(eap, true); +} + +// ":let" list all variable values +// ":let var1 var2" list variable values +// ":let var = expr" assignment command. +// ":let var += expr" assignment command. +// ":let var -= expr" assignment command. +// ":let var *= expr" assignment command. +// ":let var /= expr" assignment command. +// ":let var %= expr" assignment command. +// ":let var .= expr" assignment command. +// ":let var ..= expr" assignment command. +// ":let [var1, var2] = expr" unpack list. +// ":let [name, ..., ; lastname] = expr" unpack list. void ex_let(exarg_T *eap) { + ex_let_const(eap, false); +} + +static void ex_let_const(exarg_T *eap, const bool is_const) +{ char_u *arg = eap->arg; char_u *expr = NULL; typval_T rettv; @@ -1512,7 +1595,8 @@ void ex_let(exarg_T *eap) } emsg_skip--; } else if (i != FAIL) { - (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, op); + (void)ex_let_vars(eap->arg, &rettv, false, semicolon, var_count, + is_const, op); tv_clear(&rettv); } } @@ -1530,9 +1614,10 @@ static int ex_let_vars( char_u *arg_start, typval_T *tv, - int copy, /* copy values from "tv", don't move */ - int semicolon, /* from skip_var_list() */ - int var_count, /* from skip_var_list() */ + int copy, // copy values from "tv", don't move + int semicolon, // from skip_var_list() + int var_count, // from skip_var_list() + int is_const, // lock variables for :const char_u *nextchars ) { @@ -1543,8 +1628,9 @@ ex_let_vars( /* * ":let var = expr" or ":for var in list" */ - if (ex_let_one(arg, tv, copy, nextchars, nextchars) == NULL) + if (ex_let_one(arg, tv, copy, is_const, nextchars, nextchars) == NULL) { return FAIL; + } return OK; } @@ -1572,8 +1658,8 @@ ex_let_vars( size_t rest_len = tv_list_len(l); while (*arg != ']') { arg = skipwhite(arg + 1); - arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", - nextchars); + arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, is_const, + (const char_u *)",;]", nextchars); if (arg == NULL) { return FAIL; } @@ -1595,7 +1681,7 @@ ex_let_vars( ltv.vval.v_list = rest_list; tv_list_ref(rest_list); - arg = ex_let_one(skipwhite(arg + 1), <v, false, + arg = ex_let_one(skipwhite(arg + 1), <v, false, is_const, (char_u *)"]", nextchars); tv_clear(<v); if (arg == NULL) { @@ -1683,6 +1769,15 @@ static void list_hashtable_vars(hashtab_T *ht, const char *prefix, int empty, if (!HASHITEM_EMPTY(hi)) { todo--; di = TV_DICT_HI2DI(hi); + char buf[IOSIZE]; + + // apply :filter /pat/ to variable name + xstrlcpy(buf, prefix, IOSIZE - 1); + xstrlcat(buf, (char *)di->di_key, IOSIZE); + if (message_filtered((char_u *)buf)) { + continue; + } + if (empty || di->di_tv.v_type != VAR_STRING || di->di_tv.vval.v_string != NULL) { list_one_var(di, prefix, first); @@ -1851,8 +1946,8 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first) /// @return a pointer to the char just after the var name or NULL in case of /// error. static char_u *ex_let_one(char_u *arg, typval_T *const tv, - const bool copy, const char_u *const endchars, - const char_u *const op) + const bool copy, const bool is_const, + const char_u *const endchars, const char_u *const op) FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT { char_u *arg_end = NULL; @@ -1864,6 +1959,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, * ":let $VAR = expr": Set environment variable. */ if (*arg == '$') { + if (is_const) { + EMSG(_("E996: Cannot lock an environment variable")); + return NULL; + } // Find the end of the name. arg++; char *name = (char *)arg; @@ -1909,6 +2008,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, // ":let &l:option = expr": Set local option value. // ":let &g:option = expr": Set global option value. } else if (*arg == '&') { + if (is_const) { + EMSG(_("E996: Cannot lock an option")); + return NULL; + } // Find the end of the name. char *const p = (char *)find_option_end((const char **)&arg, &opt_flags); if (p == NULL @@ -1938,8 +2041,8 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, case '+': n = numval + n; break; case '-': n = numval - n; break; case '*': n = numval * n; break; - case '/': n = numval / n; break; - case '%': n = numval % n; break; + case '/': n = num_divide(numval, n); break; + case '%': n = num_modulus(numval, n); break; } } else if (opt_type == 0 && stringval != NULL) { // string char *const oldstringval = stringval; @@ -1959,6 +2062,10 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, } // ":let @r = expr": Set register contents. } else if (*arg == '@') { + if (is_const) { + EMSG(_("E996: Cannot lock a register")); + return NULL; + } arg++; if (op != NULL && vim_strchr((char_u *)"+-*/%", *op) != NULL) { emsgf(_(e_letwrong), op); @@ -1998,7 +2105,7 @@ static char_u *ex_let_one(char_u *arg, typval_T *const tv, if (endchars != NULL && vim_strchr(endchars, *skipwhite(p)) == NULL) { EMSG(_(e_letunexp)); } else { - set_var_lval(&lv, p, tv, copy, op); + set_var_lval(&lv, p, tv, copy, is_const, op); arg_end = p; } } @@ -2249,6 +2356,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, /* Can't add "v:" variable. */ if (lp->ll_dict == &vimvardict) { EMSG2(_(e_illvar), name); + tv_clear(&var1); return NULL; } @@ -2363,7 +2471,7 @@ static void clear_lval(lval_T *lp) * "%" for "%=", "." for ".=" or "=" for "=". */ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, - int copy, const char_u *op) + int copy, const bool is_const, const char_u *op) { int cc; listitem_T *ri; @@ -2375,6 +2483,12 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, if (op != NULL && *op != '=') { typval_T tv; + if (is_const) { + EMSG(_(e_cannot_mod)); + *endp = cc; + return; + } + // handle +=, -=, *=, /=, %= and .= di = NULL; if (get_var_tv((const char *)lp->ll_name, (int)STRLEN(lp->ll_name), @@ -2390,7 +2504,7 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, tv_clear(&tv); } } else { - set_var(lp->ll_name, lp->ll_name_len, rettv, copy); + set_var_const(lp->ll_name, lp->ll_name_len, rettv, copy, is_const); } *endp = cc; } else if (tv_check_lock(lp->ll_newkey == NULL @@ -2401,6 +2515,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, listitem_T *ll_li = lp->ll_li; int ll_n1 = lp->ll_n1; + if (is_const) { + EMSG(_("E996: Cannot lock a range")); + return; + } + // Check whether any of the list items is locked for (ri = tv_list_first(rettv->vval.v_list); ri != NULL && ll_li != NULL; ) { @@ -2456,6 +2575,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, dict_T *dict = lp->ll_dict; bool watched = tv_dict_is_watched(dict); + if (is_const) { + EMSG(_("E996: Cannot lock a list or dict")); + return; + } + // Assign to a List or Dictionary item. if (lp->ll_newkey != NULL) { if (op != NULL && *op != '=') { @@ -2579,7 +2703,7 @@ bool next_for_item(void *fi_void, char_u *arg) } else { fi->fi_lw.lw_item = TV_LIST_ITEM_NEXT(fi->fi_list, item); return (ex_let_vars(arg, TV_LIST_ITEM_TV(item), true, - fi->fi_semicolon, fi->fi_varcount, NULL) == OK); + fi->fi_semicolon, fi->fi_varcount, false, NULL) == OK); } } @@ -4048,22 +4172,9 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string) if (op == '*') { n1 = n1 * n2; } else if (op == '/') { - if (n2 == 0) { // give an error message? - if (n1 == 0) { - n1 = VARNUMBER_MIN; // similar to NaN - } else if (n1 < 0) { - n1 = -VARNUMBER_MAX; - } else { - n1 = VARNUMBER_MAX; - } - } else { - n1 = n1 / n2; - } + n1 = num_divide(n1, n2); } else { - if (n2 == 0) /* give an error message? */ - n1 = 0; - else - n1 = n1 % n2; + n1 = num_modulus(n1, n2); } rettv->v_type = VAR_NUMBER; rettv->vval.v_number = n1; @@ -4199,7 +4310,7 @@ static int eval7( // Dictionary: {key: val, key: val} case '{': ret = get_lambda_tv(arg, rettv, evaluate); if (ret == NOTDONE) { - ret = get_dict_tv(arg, rettv, evaluate); + ret = dict_get_tv(arg, rettv, evaluate); } break; @@ -5123,7 +5234,7 @@ bool garbage_collect(bool testing) yankreg_T reg; char name = NUL; bool is_unnamed = false; - reg_iter = op_register_iter(reg_iter, &name, ®, &is_unnamed); + reg_iter = op_global_reg_iter(reg_iter, &name, ®, &is_unnamed); if (name != NUL) { ABORTING(set_ref_dict)(reg.additional_data, copyID); } @@ -5165,7 +5276,7 @@ bool garbage_collect(bool testing) { Channel *data; map_foreach_value(channels, data, { - set_ref_in_callback_reader(&data->on_stdout, copyID, NULL, NULL); + set_ref_in_callback_reader(&data->on_data, copyID, NULL, NULL); set_ref_in_callback_reader(&data->on_stderr, copyID, NULL, NULL); set_ref_in_callback(&data->on_exit, copyID, NULL, NULL); }) @@ -5583,7 +5694,7 @@ static bool set_ref_in_funccal(funccall_T *fc, int copyID) * Allocate a variable for a Dictionary and fill it from "*arg". * Return OK or FAIL. Returns NOTDONE for {expr}. */ -static int get_dict_tv(char_u **arg, typval_T *rettv, int evaluate) +static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate) { dict_T *d = NULL; typval_T tvkey; @@ -5871,10 +5982,6 @@ static int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate) fp->uf_scoped = NULL; } - fp->uf_tml_count = NULL; - fp->uf_tml_total = NULL; - fp->uf_tml_self = NULL; - fp->uf_profiling = false; if (prof_def_func()) { func_do_profile(fp); } @@ -6312,6 +6419,7 @@ call_func( partial_T *partial, // optional, can be NULL dict_T *selfdict_in // Dictionary for "self" ) + FUNC_ATTR_NONNULL_ARG(1, 3, 5, 9) { int ret = FAIL; int error = ERROR_NONE; @@ -6545,6 +6653,10 @@ static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr) { + if (check_restricted() || check_secure()) { + return; + } + ApiDispatchWrapper fn = (ApiDispatchWrapper)fptr; Array args = ARRAY_DICT_INIT; @@ -6969,10 +7081,14 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *const cmd = tv_get_string_chk(&argvars[0]); garray_T ga; + int save_trylevel = trylevel; + // trylevel must be zero for a ":throw" command to be considered failed + trylevel = 0; called_emsg = false; suppress_errthrow = true; emsg_silent = true; + do_cmdline_cmd(cmd); if (!called_emsg) { prepare_assert_error(&ga); @@ -6994,6 +7110,7 @@ static void f_assert_fails(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } + trylevel = save_trylevel; called_emsg = false; suppress_errthrow = false; emsg_silent = false; @@ -7161,6 +7278,14 @@ static buf_T *find_buffer(typval_T *avar) return buf; } +// "bufadd(expr)" function +static void f_bufadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *name = (char_u *)tv_get_string(&argvars[0]); + + rettv->vval.v_number = buflist_add(*name == NUL ? NULL : name, 0); +} + /* * "bufexists(expr)" function */ @@ -7180,6 +7305,21 @@ static void f_buflisted(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = (buf != NULL && buf->b_p_bl); } +// "bufload(expr)" function +static void f_bufload(typval_T *argvars, typval_T *unused, FunPtr fptr) +{ + buf_T *buf = get_buf_arg(&argvars[0]); + + if (buf != NULL && buf->b_ml.ml_mfp == NULL) { + aco_save_T aco; + + aucmd_prepbuf(&aco, buf); + swap_exists_action = SEA_NONE; + open_buffer(false, NULL, 0); + aucmd_restbuf(&aco); + } +} + /* * "bufloaded(expr)" function */ @@ -7856,6 +7996,116 @@ static void f_cscope_connection(typval_T *argvars, typval_T *rettv, FunPtr fptr) (char_u *)prepend); } +/// "ctxget([{index}])" function +static void f_ctxget(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + size_t index = 0; + if (argvars[0].v_type == VAR_NUMBER) { + index = argvars[0].vval.v_number; + } else if (argvars[0].v_type != VAR_UNKNOWN) { + EMSG2(_(e_invarg2), "expected nothing or a Number as an argument"); + return; + } + + Context *ctx = ctx_get(index); + if (ctx == NULL) { + EMSG3(_(e_invargNval), "index", "out of bounds"); + return; + } + + Dictionary ctx_dict = ctx_to_dict(ctx); + Error err = ERROR_INIT; + object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err); + api_free_dictionary(ctx_dict); + api_clear_error(&err); +} + +/// "ctxpop()" function +static void f_ctxpop(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (!ctx_restore(NULL, kCtxAll)) { + EMSG(_("Context stack is empty")); + } +} + +/// "ctxpush([{types}])" function +static void f_ctxpush(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + int types = kCtxAll; + if (argvars[0].v_type == VAR_LIST) { + types = 0; + TV_LIST_ITER(argvars[0].vval.v_list, li, { + typval_T *tv_li = TV_LIST_ITEM_TV(li); + if (tv_li->v_type == VAR_STRING) { + if (strequal((char *)tv_li->vval.v_string, "regs")) { + types |= kCtxRegs; + } else if (strequal((char *)tv_li->vval.v_string, "jumps")) { + types |= kCtxJumps; + } else if (strequal((char *)tv_li->vval.v_string, "buflist")) { + types |= kCtxBuflist; + } else if (strequal((char *)tv_li->vval.v_string, "gvars")) { + types |= kCtxGVars; + } else if (strequal((char *)tv_li->vval.v_string, "sfuncs")) { + types |= kCtxSFuncs; + } else if (strequal((char *)tv_li->vval.v_string, "funcs")) { + types |= kCtxFuncs; + } + } + }); + } else if (argvars[0].v_type != VAR_UNKNOWN) { + EMSG2(_(e_invarg2), "expected nothing or a List as an argument"); + return; + } + ctx_save(NULL, types); +} + +/// "ctxset({context}[, {index}])" function +static void f_ctxset(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + if (argvars[0].v_type != VAR_DICT) { + EMSG2(_(e_invarg2), "expected dictionary as first argument"); + return; + } + + size_t index = 0; + if (argvars[1].v_type == VAR_NUMBER) { + index = argvars[1].vval.v_number; + } else if (argvars[1].v_type != VAR_UNKNOWN) { + EMSG2(_(e_invarg2), "expected nothing or a Number as second argument"); + return; + } + + Context *ctx = ctx_get(index); + if (ctx == NULL) { + EMSG3(_(e_invargNval), "index", "out of bounds"); + return; + } + + int save_did_emsg = did_emsg; + did_emsg = false; + + Dictionary dict = vim_to_object(&argvars[0]).data.dictionary; + Context tmp = CONTEXT_INIT; + ctx_from_dict(dict, &tmp); + + if (did_emsg) { + ctx_free(&tmp); + } else { + ctx_free(ctx); + *ctx = tmp; + } + + api_free_dictionary(dict); + did_emsg = save_did_emsg; +} + +/// "ctxsize()" function +static void f_ctxsize(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = ctx_size(); +} + /// "cursor(lnum, col)" function, or /// "cursor(list)" /// @@ -8246,6 +8496,25 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = n; } +/// "environ()" function +static void f_environ(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + tv_dict_alloc_ret(rettv); + + for (int i = 0; ; i++) { + // TODO(justinmk): use os_copyfullenv from #7202 ? + char *envname = os_getenvname_at_index((size_t)i); + if (envname == NULL) { + break; + } + const char *value = os_getenv(envname); + tv_dict_add_str(rettv->vval.v_dict, + (char *)envname, STRLEN((char *)envname), + value == NULL ? "" : value); + xfree(envname); + } +} + /* * "escape({string}, {chars})" function */ @@ -8259,6 +8528,20 @@ static void f_escape(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; } +/// "getenv()" function +static void f_getenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char_u *p = (char_u *)vim_getenv(tv_get_string(&argvars[0])); + + if (p == NULL) { + rettv->v_type = VAR_SPECIAL; + rettv->vval.v_number = kSpecialVarNull; + return; + } + rettv->vval.v_string = p; + rettv->v_type = VAR_STRING; +} + /* * "eval()" function */ @@ -8298,10 +8581,7 @@ static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *name = tv_get_string(&argvars[0]); // Check in $PATH and also check directly if there is a directory name - rettv->vval.v_number = ( - os_can_exe((const char_u *)name, NULL, true) - || (gettail_dir(name) != name - && os_can_exe((const char_u *)name, NULL, false))); + rettv->vval.v_number = os_can_exe(name, NULL, true); } typedef struct { @@ -8406,12 +8686,12 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_exepath(typval_T *argvars, typval_T *rettv, FunPtr fptr) { const char *arg = tv_get_string(&argvars[0]); - char_u *path = NULL; + char *path = NULL; - (void)os_can_exe((const char_u *)arg, &path, true); + (void)os_can_exe(arg, &path, true); rettv->v_type = VAR_STRING; - rettv->vval.v_string = path; + rettv->vval.v_string = (char_u *)path; } /// Find a window: When using a Window ID in any tab page, when using a number @@ -8438,7 +8718,7 @@ static void f_exists(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *p = tv_get_string(&argvars[0]); if (*p == '$') { // Environment variable. // First try "normal" environment variables (fast). - if (os_getenv(p + 1) != NULL) { + if (os_env_exists(p + 1)) { n = true; } else { // Try expanding things like $VIM and ${HOME}. @@ -8834,6 +9114,7 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) } hash_unlock(ht); } else { + assert(argvars[0].v_type == VAR_LIST); vimvars[VV_KEY].vv_type = VAR_NUMBER; for (listitem_T *li = tv_list_first(l); li != NULL;) { @@ -8864,44 +9145,17 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) } static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) + FUNC_ATTR_NONNULL_ARG(1, 2) { typval_T rettv; typval_T argv[3]; int retval = FAIL; - int dummy; tv_copy(tv, &vimvars[VV_VAL].vv_tv); argv[0] = vimvars[VV_KEY].vv_tv; argv[1] = vimvars[VV_VAL].vv_tv; - if (expr->v_type == VAR_FUNC) { - const char_u *const s = expr->vval.v_string; - if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, - 0L, 0L, &dummy, true, NULL, NULL) == FAIL) { - goto theend; - } - } else if (expr->v_type == VAR_PARTIAL) { - partial_T *partial = expr->vval.v_partial; - - const char_u *const s = partial_name(partial); - if (call_func(s, (int)STRLEN(s), &rettv, 2, argv, NULL, - 0L, 0L, &dummy, true, partial, NULL) == FAIL) { - goto theend; - } - } else { - char buf[NUMBUFLEN]; - const char *s = tv_get_string_buf_chk(expr, buf); - if (s == NULL) { - goto theend; - } - s = (const char *)skipwhite((const char_u *)s); - if (eval1((char_u **)&s, &rettv, true) == FAIL) { - goto theend; - } - - if (*s != NUL) { // check for trailing chars after expr - emsgf(_(e_invexpr2), s); - goto theend; - } + if (eval_expr_typval(expr, argv, 2, &rettv) == FAIL) { + goto theend; } if (map) { // map(): replace the list item value. @@ -9360,6 +9614,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) dictitem_T *di; dict_T *d; typval_T *tv = NULL; + bool what_is_dict = false; if (argvars[0].v_type == VAR_LIST) { if ((l = argvars[0].vval.v_list) != NULL) { @@ -9401,7 +9656,10 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) func_ref(rettv->vval.v_string); } } else if (strcmp(what, "dict") == 0) { - tv_dict_set_ret(rettv, pt->pt_dict); + what_is_dict = true; + if (pt->pt_dict != NULL) { + tv_dict_set_ret(rettv, pt->pt_dict); + } } else if (strcmp(what, "args") == 0) { rettv->v_type = VAR_LIST; if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) { @@ -9412,7 +9670,12 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { EMSG2(_(e_invarg2), what); } - return; + + // When {what} == "dict" and pt->pt_dict == NULL, evaluate the + // third argument + if (!what_is_dict) { + return; + } } } else { EMSG2(_(e_listdictarg), "get()"); @@ -9886,13 +10149,13 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (strcmp(tv_get_string(&argvars[1]), "cmdline") == 0) { set_one_cmd_context(&xpc, tv_get_string(&argvars[0])); - xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); goto theend; } ExpandInit(&xpc); xpc.xp_pattern = (char_u *)tv_get_string(&argvars[0]); - xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); xpc.xp_context = cmdcomplete_str_to_type( (char_u *)tv_get_string(&argvars[1])); if (xpc.xp_context == EXPAND_NOTHING) { @@ -9902,17 +10165,17 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (xpc.xp_context == EXPAND_MENUS) { set_context_in_menu_cmd(&xpc, (char_u *)"menu", xpc.xp_pattern, false); - xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); } if (xpc.xp_context == EXPAND_CSCOPE) { set_context_in_cscope_cmd(&xpc, (const char *)xpc.xp_pattern, CMD_cscope); - xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); } if (xpc.xp_context == EXPAND_SIGN) { set_context_in_sign_cmd(&xpc, xpc.xp_pattern); - xpc.xp_pattern_len = (int)STRLEN(xpc.xp_pattern); + xpc.xp_pattern_len = STRLEN(xpc.xp_pattern); } theend: @@ -10619,6 +10882,7 @@ static dict_T *get_win_info(win_T *wp, int16_t tpnr, int16_t winnr) tv_dict_add_nr(dict, S_LEN("bufnr"), wp->w_buffer->b_fnum); tv_dict_add_nr(dict, S_LEN("wincol"), wp->w_wincol); + tv_dict_add_nr(dict, S_LEN("terminal"), bt_terminal(wp->w_buffer)); tv_dict_add_nr(dict, S_LEN("quickfix"), bt_quickfix(wp->w_buffer)); tv_dict_add_nr(dict, S_LEN("loclist"), (bt_quickfix(wp->w_buffer) && wp->w_llist_ref != NULL)); @@ -10996,7 +11260,7 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "fork", #endif "gettext", -#if defined(HAVE_ICONV_H) && defined(USE_ICONV) +#if defined(HAVE_ICONV) "iconv", #endif "insert_expand", @@ -11109,10 +11373,6 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) n = stdout_isatty; } else if (STRICMP(name, "multi_byte_encoding") == 0) { n = has_mbyte != 0; -#if defined(USE_ICONV) && defined(DYNAMIC_ICONV) - } else if (STRICMP(name, "iconv") == 0) { - n = iconv_enabled(false); -#endif } else if (STRICMP(name, "syntax_items") == 0) { n = syntax_present(curwin); #ifdef UNIX @@ -11935,9 +12195,18 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_number = 1; } +/// Builds a process argument vector from a VimL object (typval_T). +/// +/// @param[in] cmd_tv VimL object +/// @param[out] cmd Returns the command or executable name. +/// @param[out] executable Returns `false` if argv[0] is not executable. +/// +/// @returns Result of `shell_build_argv()` if `cmd_tv` is a String. +/// Else, string values of `cmd_tv` copied to a (char **) list with +/// argv[0] resolved to full path ($PATHEXT-resolved on Windows). static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) { - if (cmd_tv->v_type == VAR_STRING) { + if (cmd_tv->v_type == VAR_STRING) { // String => "shell semantics". const char *cmd_str = tv_get_string(cmd_tv); if (cmd) { *cmd = cmd_str; @@ -11957,16 +12226,17 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) return NULL; } - const char *exe = tv_get_string_chk(TV_LIST_ITEM_TV(tv_list_first(argl))); - if (!exe || !os_can_exe((const char_u *)exe, NULL, true)) { - if (exe && executable) { + const char *arg0 = tv_get_string_chk(TV_LIST_ITEM_TV(tv_list_first(argl))); + char *exe_resolved = NULL; + if (!arg0 || !os_can_exe(arg0, &exe_resolved, true)) { + if (arg0 && executable) { *executable = false; } return NULL; } if (cmd) { - *cmd = exe; + *cmd = exe_resolved; } // Build the argument vector @@ -11977,10 +12247,15 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) if (!a) { // Did emsg in tv_get_string_chk; just deallocate argv. shell_free_argv(argv); + xfree(exe_resolved); return NULL; } argv[i++] = xstrdup(a); }); + // Replace argv[0] with absolute path. The only reason for this is to make + // $PATHEXT work on Windows with jobstart([…]). #9569 + xfree(argv[0]); + argv[0] = exe_resolved; return argv; } @@ -12080,14 +12355,21 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - Channel *data = find_job(argvars[0].vval.v_number, true); if (!data) { return; } + const char *error = NULL; + if (data->is_rpc) { + // Ignore return code, but show error later. + (void)channel_close(data->id, kChannelPartRpc, &error); + } process_stop((Process *)&data->stream.proc); rettv->vval.v_number = 1; + if (error) { + EMSG(error); + } } // "jobwait(ids[, timeout])" function @@ -13454,7 +13736,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) // Always open the file in binary mode, library functions have a mind of // their own about CR-LF conversion. const char *const fname = tv_get_string(&argvars[0]); - if (*fname == NUL || (fd = mch_fopen(fname, READBIN)) == NULL) { + if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) { EMSG2(_(e_notopen), *fname == NUL ? _("<empty>") : fname); return; } @@ -13686,11 +13968,7 @@ static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_list_append_number(rettv->vval.v_list, u.split.low); } -/// f_reltimestr - return a string that represents the value of {time} -/// -/// @return The string representation of the argument, the format is the -/// number of seconds followed by a dot, followed by the number -/// of microseconds. +/// "reltimestr()" function static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) FUNC_ATTR_NONNULL_ALL { @@ -13699,7 +13977,7 @@ static void f_reltimestr(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_STRING; rettv->vval.v_string = NULL; if (list2proftime(&argvars[0], &tm) == OK) { - rettv->vval.v_string = (char_u *) xstrdup(profile_msg(tm)); + rettv->vval.v_string = (char_u *)xstrdup(profile_msg(tm)); } } @@ -14501,10 +14779,10 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) long lnum_stop = 0; long time_limit = 0; - // Get the three pattern arguments: start, middle, end. + // Get the three pattern arguments: start, middle, end. Will result in an + // error if not a valid argument. char nbuf1[NUMBUFLEN]; char nbuf2[NUMBUFLEN]; - char nbuf3[NUMBUFLEN]; const char *spat = tv_get_string_chk(&argvars[0]); const char *mpat = tv_get_string_buf_chk(&argvars[1], nbuf1); const char *epat = tv_get_string_buf_chk(&argvars[2], nbuf2); @@ -14532,23 +14810,28 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) } // Optional fifth argument: skip expression. - const char *skip; + const typval_T *skip; if (argvars[3].v_type == VAR_UNKNOWN || argvars[4].v_type == VAR_UNKNOWN) { - skip = ""; + skip = NULL; } else { - skip = tv_get_string_buf_chk(&argvars[4], nbuf3); - if (skip == NULL) { + skip = &argvars[4]; + if (skip->v_type != VAR_FUNC + && skip->v_type != VAR_PARTIAL + && skip->v_type != VAR_STRING) { + emsgf(_(e_invarg2), tv_get_string(&argvars[4])); goto theend; // Type error. } if (argvars[5].v_type != VAR_UNKNOWN) { lnum_stop = tv_get_number_chk(&argvars[5], NULL); if (lnum_stop < 0) { + emsgf(_(e_invarg2), tv_get_string(&argvars[5])); goto theend; } if (argvars[6].v_type != VAR_UNKNOWN) { time_limit = tv_get_number_chk(&argvars[6], NULL); if (time_limit < 0) { + emsgf(_(e_invarg2), tv_get_string(&argvars[6])); goto theend; } } @@ -14556,7 +14839,7 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos) } retval = do_searchpair( - (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, (char_u *)skip, + (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip, flags, match_pos, lnum_stop, time_limit); theend: @@ -14604,7 +14887,7 @@ do_searchpair( char_u *mpat, // middle pattern char_u *epat, // end pattern int dir, // BACKWARD or FORWARD - char_u *skip, // skip expression + const typval_T *skip, // skip expression int flags, // SP_SETPCMARK and other SP_ values pos_T *match_pos, linenr_T lnum_stop, // stop at this line if not zero @@ -14620,8 +14903,8 @@ do_searchpair( pos_T save_cursor; pos_T save_pos; int n; - int r; int nest = 1; + bool use_skip = false; int options = SEARCH_KEEP; proftime_T tm; size_t pat2_len; @@ -14651,6 +14934,13 @@ do_searchpair( options |= SEARCH_START; } + if (skip != NULL) { + // Empty string means to not use the skip expression. + if (skip->v_type == VAR_STRING || skip->v_type == VAR_FUNC) { + use_skip = skip->vval.v_string != NULL && *skip->vval.v_string != NUL; + } + } + save_cursor = curwin->w_cursor; pos = curwin->w_cursor; clearpos(&firstpos); @@ -14680,12 +14970,12 @@ do_searchpair( /* clear the start flag to avoid getting stuck here */ options &= ~SEARCH_START; - /* If the skip pattern matches, ignore this match. */ - if (*skip != NUL) { + // If the skip pattern matches, ignore this match. + if (use_skip) { save_pos = curwin->w_cursor; curwin->w_cursor = pos; - bool err; - r = eval_to_bool(skip, &err, NULL, false); + bool err = false; + const bool r = eval_expr_to_bool(skip, &err); curwin->w_cursor = save_pos; if (err) { /* Evaluating {skip} caused an error, break here. */ @@ -14846,6 +15136,123 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +/// Set line or list of lines in buffer "buf". +static void set_buffer_lines(buf_T *buf, linenr_T lnum, typval_T *lines, + typval_T *rettv) +{ + list_T *l = NULL; + listitem_T *li = NULL; + long added = 0; + linenr_T lcount; + buf_T *curbuf_save = NULL; + win_T *curwin_save = NULL; + int is_curbuf = buf == curbuf; + + // When using the current buffer ml_mfp will be set if needed. Useful when + // setline() is used on startup. For other buffers the buffer must be + // loaded. + if (buf == NULL || (!is_curbuf && buf->b_ml.ml_mfp == NULL) || lnum < 1) { + rettv->vval.v_number = 1; // FAIL + return; + } + + if (!is_curbuf) { + wininfo_T *wip; + + curbuf_save = curbuf; + curwin_save = curwin; + curbuf = buf; + for (wip = buf->b_wininfo; wip != NULL; wip = wip->wi_next) { + if (wip->wi_win != NULL) { + curwin = wip->wi_win; + break; + } + } + } + + lcount = curbuf->b_ml.ml_line_count; + + const char *line = NULL; + + if (lines->v_type == VAR_LIST) { + l = lines->vval.v_list; + li = tv_list_first(l); + } else { + line = tv_get_string_chk(lines); + } + + // Default result is zero == OK. + for (;; ) { + if (lines->v_type == VAR_LIST) { + // List argument, get next string. + if (li == NULL) { + break; + } + line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); + li = TV_LIST_ITEM_NEXT(l, li); + } + + rettv->vval.v_number = 1; // FAIL + if (line == NULL || lnum > curbuf->b_ml.ml_line_count + 1) { + break; + } + + // When coming here from Insert mode, sync undo, so that this can be + // undone separately from what was previously inserted. + if (u_sync_once == 2) { + u_sync_once = 1; // notify that u_sync() was called + u_sync(true); + } + + if (lnum <= curbuf->b_ml.ml_line_count) { + // Existing line, replace it. + if (u_savesub(lnum) == OK + && ml_replace(lnum, (char_u *)line, true) == OK) { + changed_bytes(lnum, 0); + if (is_curbuf && lnum == curwin->w_cursor.lnum) { + check_cursor_col(); + } + rettv->vval.v_number = 0; // OK + } + } else if (added > 0 || u_save(lnum - 1, lnum) == OK) { + // lnum is one past the last line, append the line. + added++; + if (ml_append(lnum - 1, (char_u *)line, 0, false) == OK) { + rettv->vval.v_number = 0; // OK + } + } + + if (l == NULL) /* only one string argument */ + break; + ++lnum; + } + + if (added > 0) { + appended_lines_mark(lcount, added); + } + + if (!is_curbuf) { + curbuf = curbuf_save; + curwin = curwin_save; + } +} + +/// "setbufline()" function +static void f_setbufline(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + linenr_T lnum; + buf_T *buf; + + buf = tv_get_buf(&argvars[0], false); + if (buf == NULL) { + rettv->vval.v_number = 1; // FAIL + } else { + lnum = tv_get_lnum_buf(&argvars[1], buf); + + set_buffer_lines(buf, lnum, &argvars[2], rettv); + } +} + /* * "setbufvar()" function */ @@ -14941,6 +15348,20 @@ static void f_setcmdpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } +/// "setenv()" function +static void f_setenv(typval_T *argvars, typval_T *rettv, FunPtr fptr) +{ + char namebuf[NUMBUFLEN]; + char valbuf[NUMBUFLEN]; + const char *name = tv_get_string_buf(&argvars[0], namebuf); + + if (argvars[1].v_type == VAR_SPECIAL + && argvars[1].vval.v_number == kSpecialVarNull) { + os_unsetenv(name); + } else { + os_setenv(name, tv_get_string_buf(&argvars[1], valbuf), 1); + } +} /// "setfperm({fname}, {mode})" function static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) @@ -14978,67 +15399,8 @@ static void f_setfperm(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l = NULL; - listitem_T *li = NULL; - long added = 0; - linenr_T lcount = curbuf->b_ml.ml_line_count; - linenr_T lnum = tv_get_lnum(&argvars[0]); - const char *line = NULL; - if (argvars[1].v_type == VAR_LIST) { - l = argvars[1].vval.v_list; - li = tv_list_first(l); - } else { - line = tv_get_string_chk(&argvars[1]); - } - - // Default result is zero == OK. - for (;; ) { - if (argvars[1].v_type == VAR_LIST) { - // List argument, get next string. - if (li == NULL) { - break; - } - line = tv_get_string_chk(TV_LIST_ITEM_TV(li)); - li = TV_LIST_ITEM_NEXT(l, li); - } - - rettv->vval.v_number = 1; // FAIL - if (line == NULL || lnum < 1 || lnum > curbuf->b_ml.ml_line_count + 1) { - break; - } - - /* When coming here from Insert mode, sync undo, so that this can be - * undone separately from what was previously inserted. */ - if (u_sync_once == 2) { - u_sync_once = 1; /* notify that u_sync() was called */ - u_sync(TRUE); - } - - if (lnum <= curbuf->b_ml.ml_line_count) { - // Existing line, replace it. - if (u_savesub(lnum) == OK - && ml_replace(lnum, (char_u *)line, true) == OK) { - changed_bytes(lnum, 0); - if (lnum == curwin->w_cursor.lnum) - check_cursor_col(); - rettv->vval.v_number = 0; /* OK */ - } - } else if (added > 0 || u_save(lnum - 1, lnum) == OK) { - // lnum is one past the last line, append the line. - added++; - if (ml_append(lnum - 1, (char_u *)line, 0, false) == OK) { - rettv->vval.v_number = 0; // OK - } - } - - if (l == NULL) /* only one string argument */ - break; - ++lnum; - } - - if (added > 0) - appended_lines_mark(lcount, added); + set_buffer_lines(curbuf, lnum, &argvars[1], rettv); } /// Create quickfix/location list from VimL values @@ -15390,7 +15752,7 @@ free_lstval: if (set_unnamed) { // Discard the result. We already handle the error case. - if (op_register_set_previous(regname)) { } + if (op_reg_set_previous(regname)) { } } } @@ -15503,7 +15865,7 @@ static void f_setwinvar(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void setwinvar(typval_T *argvars, typval_T *rettv, int off) { - if (check_restricted() || check_secure()) { + if (check_secure()) { return; } @@ -15681,6 +16043,7 @@ static void f_sign_getplaced(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (notanum) { return; } + (void)lnum; lnum = tv_get_lnum(&di->di_tv); } if ((di = tv_dict_find(dict, "id", -1)) != NULL) { @@ -15735,9 +16098,6 @@ static void f_sign_jump(typval_T *argvars, typval_T *rettv, FunPtr fptr) sign_group = NULL; // global sign group } else { sign_group = xstrdup(sign_group_chk); - if (sign_group == NULL) { - return; - } } // Buffer to place the sign @@ -15786,9 +16146,6 @@ static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) group = NULL; // global sign group } else { group = vim_strsave((const char_u *)group_chk); - if (group == NULL) { - return; - } } // Sign name @@ -15816,6 +16173,7 @@ static void f_sign_place(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (notanum) { goto cleanup; } + (void)lnum; lnum = tv_get_lnum(&di->di_tv); } if ((di = tv_dict_find(dict, "priority", -1)) != NULL) { @@ -15881,9 +16239,6 @@ static void f_sign_unplace(typval_T *argvars, typval_T *rettv, FunPtr fptr) group = NULL; // global sign group } else { group = vim_strsave((const char_u *)group_chk); - if (group == NULL) { - return; - } } if (argvars[1].v_type != VAR_UNKNOWN) { @@ -16352,9 +16707,7 @@ static void f_uniq(typval_T *argvars, typval_T *rettv, FunPtr fptr) do_sort_uniq(argvars, rettv, false); } -// // "reltimefloat()" function -// static void f_reltimefloat(typval_T *argvars , typval_T *rettv, FunPtr fptr) FUNC_ATTR_NONNULL_ALL { @@ -16363,7 +16716,7 @@ static void f_reltimefloat(typval_T *argvars , typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_FLOAT; rettv->vval.v_float = 0; if (list2proftime(&argvars[0], &tm) == OK) { - rettv->vval.v_float = ((float_T)tm) / 1000000000; + rettv->vval.v_float = (float_T)profile_signed(tm) / 1000000000.0; } } @@ -16406,6 +16759,8 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; } str += len; + capcol -= len; + len = 0; } } } @@ -17776,7 +18131,6 @@ static void add_timer_info(typval_T *rettv, timer_T *timer) di->di_tv.v_type = VAR_FUNC; di->di_tv.vval.v_string = vim_strsave(timer->callback.data.funcref); } - di->di_tv.v_lock = 0; } static void add_timer_info_all(typval_T *rettv) @@ -19033,8 +19387,11 @@ static int get_name_len(const char **const arg, } len += get_id_len(arg); - if (len == 0 && verbose) + // Only give an error when there is something, otherwise it will be + // reported at a higher level. + if (len == 0 && verbose && **arg != NUL) { EMSG2(_(e_invexpr2), *arg); + } return len; } @@ -20047,6 +20404,24 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, const bool copy) FUNC_ATTR_NONNULL_ALL { + set_var_const(name, name_len, tv, copy, false); +} + +/// Set variable to the given value +/// +/// If the variable already exists, the value is updated. Otherwise the variable +/// is created. +/// +/// @param[in] name Variable name to set. +/// @param[in] name_len Length of the variable name. +/// @param tv Variable value. +/// @param[in] copy True if value in tv is to be copied. +/// @param[in] is_const True if value in tv is to be locked. +static void set_var_const(const char *name, const size_t name_len, + typval_T *const tv, const bool copy, + const bool is_const) + FUNC_ATTR_NONNULL_ALL +{ dictitem_T *v; hashtab_T *ht; dict_T *dict; @@ -20072,6 +20447,11 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, typval_T oldtv = TV_INITIAL_VALUE; if (v != NULL) { + if (is_const) { + EMSG(_(e_cannot_mod)); + return; + } + // existing variable, need to clear the value if (var_check_ro(v->di_flags, name, name_len) || tv_check_lock(v->di_tv.v_lock, name, name_len)) { @@ -20138,6 +20518,9 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, return; } v->di_flags = DI_FLAGS_ALLOC; + if (is_const) { + v->di_flags |= DI_FLAGS_LOCK; + } } if (copy || tv->v_type == VAR_NUMBER || tv->v_type == VAR_FLOAT) { @@ -20156,6 +20539,10 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, tv_clear(&oldtv); } } + + if (is_const) { + v->di_tv.v_lock |= VAR_LOCKED; + } } /// Check whether variable is read-only (DI_FLAGS_RO, DI_FLAGS_RO_SBX) @@ -20480,29 +20867,19 @@ void ex_echohl(exarg_T *eap) */ void ex_execute(exarg_T *eap) { - char_u *arg = eap->arg; + char_u *arg = eap->arg; typval_T rettv; int ret = OK; - char_u *p; garray_T ga; - int save_did_emsg = did_emsg; + int save_did_emsg; ga_init(&ga, 1, 80); if (eap->skip) ++emsg_skip; while (*arg != NUL && *arg != '|' && *arg != '\n') { - p = arg; - if (eval1(&arg, &rettv, !eap->skip) == FAIL) { - /* - * Report the invalid expression unless the expression evaluation - * has been cancelled due to an aborting error, an interrupt, or an - * exception. - */ - if (!aborting() && did_emsg == save_did_emsg) { - EMSG2(_(e_invexpr2), p); - } - ret = FAIL; + ret = eval1_emsg(&arg, &rettv, !eap->skip); + if (ret == FAIL) { break; } @@ -20632,8 +21009,11 @@ void ex_function(exarg_T *eap) if (!HASHITEM_EMPTY(hi)) { --todo; fp = HI2UF(hi); + if (message_filtered(fp->uf_name)) { + continue; + } if (!func_name_refcount(fp->uf_name)) { - list_func_head(fp, false); + list_func_head(fp, false, false); } } } @@ -20664,7 +21044,7 @@ void ex_function(exarg_T *eap) fp = HI2UF(hi); if (!isdigit(*fp->uf_name) && vim_regexec(®match, fp->uf_name, 0)) - list_func_head(fp, FALSE); + list_func_head(fp, false, false); } } vim_regfree(regmatch.regprog); @@ -20714,9 +21094,12 @@ void ex_function(exarg_T *eap) saved_did_emsg = did_emsg; did_emsg = FALSE; - /* - * ":function func" with only function name: list function. - */ + // + // ":function func" with only function name: list function. + // If bang is given: + // - include "!" in function head + // - exclude line numbers from function body + // if (!paren) { if (!ends_excmd(*skipwhite(p))) { EMSG(_(e_trailing)); @@ -20728,17 +21111,20 @@ void ex_function(exarg_T *eap) if (!eap->skip && !got_int) { fp = find_func(name); if (fp != NULL) { - list_func_head(fp, TRUE); - for (int j = 0; j < fp->uf_lines.ga_len && !got_int; ++j) { - if (FUNCLINE(fp, j) == NULL) + list_func_head(fp, !eap->forceit, eap->forceit); + for (int j = 0; j < fp->uf_lines.ga_len && !got_int; j++) { + if (FUNCLINE(fp, j) == NULL) { continue; - msg_putchar('\n'); - msg_outnum((long)j + 1); - if (j < 9) { - msg_putchar(' '); } - if (j < 99) { - msg_putchar(' '); + msg_putchar('\n'); + if (!eap->forceit) { + msg_outnum((long)j + 1); + if (j < 9) { + msg_putchar(' '); + } + if (j < 99) { + msg_putchar(' '); + } } msg_prt_line(FUNCLINE(fp, j), false); ui_flush(); // show a line at a time @@ -20746,7 +21132,7 @@ void ex_function(exarg_T *eap) } if (!got_int) { msg_putchar('\n'); - msg_puts(" endfunction"); + msg_puts(eap->forceit ? "endfunction" : " endfunction"); } } else emsg_funcname(N_("E123: Undefined function: %s"), name); @@ -20895,7 +21281,8 @@ void ex_function(exarg_T *eap) goto erret; } if (show_block) { - ui_ext_cmdline_block_append(indent, (const char *)theline); + assert(indent >= 0); + ui_ext_cmdline_block_append((size_t)indent, (const char *)theline); } /* Detect line continuation: sourcing_lnum increased more than one. */ @@ -21064,9 +21451,10 @@ void ex_function(exarg_T *eap) overwrite = true; } else { // redefine existing function - ga_clear_strings(&(fp->uf_args)); - ga_clear_strings(&(fp->uf_lines)); XFREE_CLEAR(name); + func_clear_items(fp); + fp->uf_profiling = false; + fp->uf_prof_initialized = false; } } } else { @@ -21137,7 +21525,6 @@ void ex_function(exarg_T *eap) tv_clear(&fudi.fd_di->di_tv); } fudi.fd_di->di_tv.v_type = VAR_FUNC; - fudi.fd_di->di_tv.v_lock = 0; fudi.fd_di->di_tv.vval.v_string = vim_strsave(name); /* behave like "dict" was used */ @@ -21162,12 +21549,9 @@ void ex_function(exarg_T *eap) } else { fp->uf_scoped = NULL; } - fp->uf_tml_count = NULL; - fp->uf_tml_total = NULL; - fp->uf_tml_self = NULL; - fp->uf_profiling = FALSE; - if (prof_def_func()) + if (prof_def_func()) { func_do_profile(fp); + } fp->uf_varargs = varargs; if (sandbox) { flags |= FC_SANDBOX; @@ -21438,15 +21822,17 @@ static inline bool eval_fname_sid(const char *const name) return *name == 's' || TOUPPER_ASC(name[2]) == 'I'; } -/* - * List the head of the function: "name(arg1, arg2)". - */ -static void list_func_head(ufunc_T *fp, int indent) +/// List the head of the function: "name(arg1, arg2)". +/// +/// @param[in] fp Function pointer. +/// @param[in] indent Indent line. +/// @param[in] force Include bang "!" (i.e.: "function!"). +static void list_func_head(ufunc_T *fp, int indent, bool force) { msg_start(); if (indent) MSG_PUTS(" "); - MSG_PUTS("function "); + MSG_PUTS(force ? "function! " : "function "); if (fp->uf_name[0] == K_SPECIAL) { MSG_PUTS_ATTR("<SNR>", HL_ATTR(HLF_8)); msg_puts((const char *)fp->uf_name + 3); @@ -21628,25 +22014,29 @@ static void func_do_profile(ufunc_T *fp) { int len = fp->uf_lines.ga_len; - if (len == 0) - len = 1; /* avoid getting error for allocating zero bytes */ - fp->uf_tm_count = 0; - fp->uf_tm_self = profile_zero(); - fp->uf_tm_total = profile_zero(); + if (!fp->uf_prof_initialized) { + if (len == 0) { + len = 1; // avoid getting error for allocating zero bytes + } + fp->uf_tm_count = 0; + fp->uf_tm_self = profile_zero(); + fp->uf_tm_total = profile_zero(); - if (fp->uf_tml_count == NULL) { - fp->uf_tml_count = xcalloc(len, sizeof(int)); - } + if (fp->uf_tml_count == NULL) { + fp->uf_tml_count = xcalloc(len, sizeof(int)); + } - if (fp->uf_tml_total == NULL) { - fp->uf_tml_total = xcalloc(len, sizeof(proftime_T)); - } + if (fp->uf_tml_total == NULL) { + fp->uf_tml_total = xcalloc(len, sizeof(proftime_T)); + } - if (fp->uf_tml_self == NULL) { - fp->uf_tml_self = xcalloc(len, sizeof(proftime_T)); - } + if (fp->uf_tml_self == NULL) { + fp->uf_tml_self = xcalloc(len, sizeof(proftime_T)); + } - fp->uf_tml_idx = -1; + fp->uf_tml_idx = -1; + fp->uf_prof_initialized = true; + } fp->uf_profiling = TRUE; } @@ -21672,7 +22062,7 @@ void func_dump_profile(FILE *fd) if (!HASHITEM_EMPTY(hi)) { --todo; fp = HI2UF(hi); - if (fp->uf_profiling) { + if (fp->uf_prof_initialized) { sorttab[st_len++] = fp; if (fp->uf_name[0] == K_SPECIAL) @@ -21832,6 +22222,7 @@ static bool script_autoload(const char *const name, const size_t name_len, } /// Return the autoload script name for a function or variable name +/// Caller must make sure that "name" contains AUTOLOAD_CHAR. /// /// @param[in] name Variable/function name. /// @param[in] name_len Name length. @@ -22015,6 +22406,16 @@ static bool func_remove(ufunc_T *fp) return false; } +static void func_clear_items(ufunc_T *fp) +{ + ga_clear_strings(&(fp->uf_args)); + ga_clear_strings(&(fp->uf_lines)); + + XFREE_CLEAR(fp->uf_tml_count); + XFREE_CLEAR(fp->uf_tml_total); + XFREE_CLEAR(fp->uf_tml_self); +} + /// Free all things that a function contains. Does not free the function /// itself, use func_free() for that. /// @@ -22027,11 +22428,7 @@ static void func_clear(ufunc_T *fp, bool force) fp->uf_cleared = true; // clear this function - ga_clear_strings(&(fp->uf_args)); - ga_clear_strings(&(fp->uf_lines)); - xfree(fp->uf_tml_count); - xfree(fp->uf_tml_total); - xfree(fp->uf_tml_self); + func_clear_items(fp); funccal_unref(fp->uf_scoped, fp, force); } @@ -22174,6 +22571,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, char_u *name; proftime_T wait_start; proftime_T call_start; + int started_profiling = false; bool did_save_redo = false; save_redo_T save_redo; @@ -22393,8 +22791,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, do_profiling_yes && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL); - if (func_not_yet_profiling_but_should) + if (func_not_yet_profiling_but_should) { + started_profiling = true; func_do_profile(fp); + } bool func_or_func_caller_profiling = do_profiling_yes @@ -22442,6 +22842,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, fc->caller->func->uf_tml_children = profile_add(fc->caller->func->uf_tml_children, call_start); } + if (started_profiling) { + // make a ":profdel func" stop profiling the function + fp->uf_profiling = false; + } } /* when being verbose, mention the return value */ @@ -23012,7 +23416,7 @@ dictitem_T *find_var_in_scoped_ht(const char *name, const size_t namelen, /// @return Pointer that needs to be passed to next `var_shada_iter` invocation /// or NULL to indicate that iteration is over. const void *var_shada_iter(const void *const iter, const char **const name, - typval_T *rettv) + typval_T *rettv, var_flavour_T flavour) FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(2, 3) { const hashitem_T *hi; @@ -23023,7 +23427,7 @@ const void *var_shada_iter(const void *const iter, const char **const name, hi = globvarht.ht_array; while ((size_t) (hi - hifirst) < hinum && (HASHITEM_EMPTY(hi) - || var_flavour(hi->hi_key) != VAR_FLAVOUR_SHADA)) { + || !(var_flavour(hi->hi_key) & flavour))) { hi++; } if ((size_t) (hi - hifirst) == hinum) { @@ -23035,7 +23439,7 @@ const void *var_shada_iter(const void *const iter, const char **const name, *name = (char *)TV_DICT_HI2DI(hi)->di_key; tv_copy(&TV_DICT_HI2DI(hi)->di_tv, rettv); while ((size_t)(++hi - hifirst) < hinum) { - if (!HASHITEM_EMPTY(hi) && var_flavour(hi->hi_key) == VAR_FLAVOUR_SHADA) { + if (!HASHITEM_EMPTY(hi) && (var_flavour(hi->hi_key) & flavour)) { return hi; } } @@ -23609,50 +24013,57 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) return rettv; } +/// Checks if a named provider is enabled. bool eval_has_provider(const char *name) { -#define CHECK_PROVIDER(name) \ - if (has_##name == -1) { \ - has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \ - if (!has_##name) { \ - script_autoload("provider#" #name "#Call", \ - sizeof("provider#" #name "#Call") - 1, \ - false); \ - has_##name = !!find_func((char_u *)"provider#" #name "#Call"); \ - } \ - } - - static int has_clipboard = -1; - static int has_python = -1; - static int has_python3 = -1; - static int has_ruby = -1; - - if (strequal(name, "clipboard")) { - CHECK_PROVIDER(clipboard); - return has_clipboard; - } else if (strequal(name, "python3")) { - CHECK_PROVIDER(python3); - return has_python3; - } else if (strequal(name, "python")) { - CHECK_PROVIDER(python); - return has_python; - } else if (strequal(name, "ruby")) { - bool need_check_ruby = (has_ruby == -1); - CHECK_PROVIDER(ruby); - if (need_check_ruby && has_ruby == 1) { - char *rubyhost = call_func_retstr("provider#ruby#Detect", 0, NULL, true); - if (rubyhost) { - if (*rubyhost == NUL) { - // Invalid rubyhost executable. Gem is probably not installed. - has_ruby = 0; - } - xfree(rubyhost); + if (!strequal(name, "clipboard") + && !strequal(name, "python") + && !strequal(name, "python3") + && !strequal(name, "ruby") + && !strequal(name, "node")) { + // Avoid autoload for non-provider has() features. + return false; + } + + char buf[256]; + int len; + typval_T tv; + + // Get the g:loaded_xx_provider variable. + len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name); + if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) { + // Trigger autoload once. + len = snprintf(buf, sizeof(buf), "provider#%s#bogus", name); + script_autoload(buf, len, false); + + // Retry the (non-autoload-style) variable. + len = snprintf(buf, sizeof(buf), "g:loaded_%s_provider", name); + if (get_var_tv(buf, len, &tv, NULL, false, true) == FAIL) { + // Show a hint if Call() is defined but g:loaded_xx_provider is missing. + snprintf(buf, sizeof(buf), "provider#%s#Call", name); + if (!!find_func((char_u *)buf) && p_lpl) { + emsgf("provider: %s: missing required variable g:loaded_%s_provider", + name, name); } + return false; } - return has_ruby; } - return false; + bool ok = (tv.v_type == VAR_NUMBER) + ? 2 == tv.vval.v_number // Value of 2 means "loaded and working". + : false; + + if (ok) { + // Call() must be defined if provider claims to be working. + snprintf(buf, sizeof(buf), "provider#%s#Call", name); + if (!find_func((char_u *)buf)) { + emsgf("provider: %s: g:loaded_%s_provider=2 but %s is not defined", + name, name, buf); + ok = false; + } + } + + return ok; } /// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`. diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 149dae688e..abe032a96e 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -24,6 +24,13 @@ EXTERN ufunc_T dumuf; #define HIKEY2UF(p) ((ufunc_T *)(p - offsetof(ufunc_T, uf_name))) #define HI2UF(hi) HIKEY2UF((hi)->hi_key) +/// enum used by var_flavour() +typedef enum { + VAR_FLAVOUR_DEFAULT = 1, // doesn't start with uppercase + VAR_FLAVOUR_SESSION = 2, // starts with uppercase, some lower + VAR_FLAVOUR_SHADA = 4 // all uppercase +} var_flavour_T; + /// Defines for Vim variables typedef enum { VV_COUNT, diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index f36a7ea6c0..db45409e77 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -20,10 +20,10 @@ return { ['and']={args=2}, api_info={}, append={args=2}, - argc={}, + argc={args={0, 1}}, argidx={}, arglistid={args={0, 2}}, - argv={args={0, 1}}, + argv={args={0, 2}}, asin={args=1, func="float_op_wrapper", data="&asin"}, -- WJMc assert_beeps={args={1, 2}}, assert_equal={args={2, 3}}, @@ -40,11 +40,13 @@ return { atan2={args=2}, browse={args=4}, browsedir={args=2}, + bufadd={args=1}, bufexists={args=1}, buffer_exists={args=1, func='f_bufexists'}, -- obsolete buffer_name={args=1, func='f_bufname'}, -- obsolete buffer_number={args=1, func='f_bufnr'}, -- obsolete buflisted={args=1}, + bufload={args=1}, bufloaded={args=1}, bufname={args=1}, bufnr={args={1, 2}}, @@ -72,6 +74,11 @@ return { cosh={args=1, func="float_op_wrapper", data="&cosh"}, count={args={2, 4}}, cscope_connection={args={0, 3}}, + ctxget={args={0, 1}}, + ctxpop={}, + ctxpush={args={0, 1}}, + ctxset={args={1, 2}}, + ctxsize={}, cursor={args={1, 3}}, deepcopy={args={1, 2}}, delete={args={1,2}}, @@ -82,6 +89,7 @@ return { diff_filler={args=1}, diff_hlID={args=2}, empty={args=1}, + environ={}, escape={args=2}, eval={args=1}, eventhandler={}, @@ -128,6 +136,7 @@ return { getcompletion={args={2, 3}}, getcurpos={}, getcwd={args={0,2}}, + getenv={args={1}}, getfontname={args={0, 1}}, getfperm={args=1}, getfsize={args=1}, @@ -263,9 +272,11 @@ return { serverlist={}, serverstart={args={0, 1}}, serverstop={args=1}, + setbufline={args=3}, setbufvar={args=3}, setcharsearch={args=1}, setcmdpos={args=1}, + setenv={args=2}, setfperm={args=2}, setline={args=2}, setloclist={args={2, 4}}, diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c index e972c506dd..8cd21f8d62 100644 --- a/src/nvim/eval/executor.c +++ b/src/nvim/eval/executor.c @@ -74,8 +74,8 @@ int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, case '+': n += tv_get_number(tv2); break; case '-': n -= tv_get_number(tv2); break; case '*': n *= tv_get_number(tv2); break; - case '/': n /= tv_get_number(tv2); break; - case '%': n %= tv_get_number(tv2); break; + case '/': n = num_divide(n, tv_get_number(tv2)); break; + case '%': n = num_modulus(n, tv_get_number(tv2)); break; } tv_clear(tv1); tv1->v_type = VAR_NUMBER; diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index ffb46abfea..91a1d083c7 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -1221,7 +1221,8 @@ void tv_dict_watcher_notify(dict_T *const dict, const char *const key, /// Allocate a dictionary item /// -/// @note that the value of the item (->di_tv) still needs to be initialized. +/// @note that the type and value of the item (->di_tv) still needs to +/// be initialized. /// /// @param[in] key Key, is copied to the new item. /// @param[in] key_len Key length. @@ -1235,12 +1236,14 @@ dictitem_T *tv_dict_item_alloc_len(const char *const key, const size_t key_len) memcpy(di->di_key, key, key_len); di->di_key[key_len] = NUL; di->di_flags = DI_FLAGS_ALLOC; + di->di_tv.v_lock = VAR_UNLOCKED; return di; } /// Allocate a dictionary item /// -/// @note that the value of the item (->di_tv) still needs to be initialized. +/// @note that the type and value of the item (->di_tv) still needs to +/// be initialized. /// /// @param[in] key Key, is copied to the new item. /// @@ -1572,7 +1575,6 @@ int tv_dict_add_list(dict_T *const d, const char *const key, { dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); - item->di_tv.v_lock = VAR_UNLOCKED; item->di_tv.v_type = VAR_LIST; item->di_tv.vval.v_list = list; tv_list_ref(list); @@ -1597,7 +1599,6 @@ int tv_dict_add_dict(dict_T *const d, const char *const key, { dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); - item->di_tv.v_lock = VAR_UNLOCKED; item->di_tv.v_type = VAR_DICT; item->di_tv.vval.v_dict = dict; dict->dv_refcount++; @@ -1621,7 +1622,6 @@ int tv_dict_add_nr(dict_T *const d, const char *const key, { dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); - item->di_tv.v_lock = VAR_UNLOCKED; item->di_tv.v_type = VAR_NUMBER; item->di_tv.vval.v_number = nr; if (tv_dict_add(d, item) == FAIL) { @@ -1644,7 +1644,6 @@ int tv_dict_add_special(dict_T *const d, const char *const key, { dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); - item->di_tv.v_lock = VAR_UNLOCKED; item->di_tv.v_type = VAR_SPECIAL; item->di_tv.vval.v_special = val; if (tv_dict_add(d, item) == FAIL) { @@ -1706,7 +1705,6 @@ int tv_dict_add_allocated_str(dict_T *const d, { dictitem_T *const item = tv_dict_item_alloc_len(key, key_len); - item->di_tv.v_lock = VAR_UNLOCKED; item->di_tv.v_type = VAR_STRING; item->di_tv.vval.v_string = (char_u *)val; if (tv_dict_add(d, item) == FAIL) { diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index e99289c430..5c0f872b38 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -260,6 +260,7 @@ struct ufunc { garray_T uf_args; ///< arguments garray_T uf_lines; ///< function lines int uf_profiling; ///< true when func is being profiled + int uf_prof_initialized; // Profiling the function as a whole. int uf_tm_count; ///< nr of calls proftime_T uf_tm_total; ///< time spent in function + children diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index 623bdfc93b..289c3ee99c 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -382,7 +382,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( case VAR_SPECIAL: { switch (tv->vval.v_special) { case kSpecialVarNull: { - TYPVAL_ENCODE_CONV_NIL(tv); + TYPVAL_ENCODE_CONV_NIL(tv); // -V1037 break; } case kSpecialVarTrue: diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index ffe2db9b76..63efee59a8 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -101,6 +101,10 @@ static void close_cb(uv_handle_t *handle) static void exit_cb(uv_process_t *handle, int64_t status, int term_signal) { Process *proc = handle->data; - proc->status = (int)status; +#if defined(WIN32) + // Use stored/expected signal. + term_signal = proc->exit_signal; +#endif + proc->status = term_signal ? 128 + term_signal : (int)status; proc->internal_exit_cb(proc); } diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 7a8a39dbcf..c31ecdaddf 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -26,6 +26,11 @@ // For PTY processes SIGTERM is sent first (in case SIGHUP was not enough). #define KILL_TIMEOUT_MS 2000 +/// Externally defined with gcov. +#ifdef USE_GCOV +void __gcov_flush(void); +#endif + static bool process_is_tearing_down = false; /// @returns zero on success, or negative error code @@ -50,6 +55,11 @@ int process_spawn(Process *proc, bool in, bool out, bool err) proc->err.closed = true; } +#ifdef USE_GCOV + // Flush coverage data before forking, to avoid "Merge mismatch" errors. + __gcov_flush(); +#endif + int status; switch (proc->type) { case kProcessTypeUv: @@ -149,7 +159,7 @@ void process_close_streams(Process *proc) FUNC_ATTR_NONNULL_ALL /// 0 for no wait. -1 to wait until the process quits. /// @return Exit code of the process. proc->status will have the same value. /// -1 if the timeout expired while the process is still running. -/// -2 if the user interruped the wait. +/// -2 if the user interrupted the wait. int process_wait(Process *proc, int ms, MultiQueue *events) FUNC_ATTR_NONNULL_ARG(1) { @@ -210,13 +220,10 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL return; } proc->stopped_time = os_hrtime(); + proc->exit_signal = SIGTERM; switch (proc->type) { case kProcessTypeUv: - // Close the process's stdin. If the process doesn't close its own - // stdout/stderr, they will be closed when it exits(possibly due to being - // terminated after a timeout) - stream_may_close(&proc->in); os_proc_tree_kill(proc->pid, SIGTERM); break; case kProcessTypePty: @@ -247,8 +254,10 @@ static void children_kill_cb(uv_timer_t *handle) } uint64_t term_sent = UINT64_MAX == proc->stopped_time; if (kProcessTypePty != proc->type || term_sent) { + proc->exit_signal = SIGKILL; os_proc_tree_kill(proc->pid, SIGKILL); } else { + proc->exit_signal = SIGTERM; os_proc_tree_kill(proc->pid, SIGTERM); proc->stopped_time = UINT64_MAX; // Flag: SIGTERM was sent. // Restart timer. @@ -397,4 +406,3 @@ static void on_process_stream_close(Stream *stream, void *data) Process *proc = data; decref(proc); } - diff --git a/src/nvim/event/process.h b/src/nvim/event/process.h index ba2c2a6a11..ef9d953ab7 100644 --- a/src/nvim/event/process.h +++ b/src/nvim/event/process.h @@ -19,6 +19,7 @@ struct process { Loop *loop; void *data; int pid, status, refcount; + uint8_t exit_signal; // Signal used when killing (on Windows). uint64_t stopped_time; // process_stop() timestamp const char *cwd; char **argv; @@ -56,7 +57,8 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data) static inline bool process_is_stopped(Process *proc) { - return proc->stopped_time != 0; + bool exited = (proc->status >= 0); + return exited || (proc->stopped_time != 0); } #ifdef INCLUDE_GENERATED_DECLARATIONS diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c index 7aaac0b03b..7c8014dead 100644 --- a/src/nvim/event/stream.c +++ b/src/nvim/event/stream.c @@ -113,6 +113,11 @@ void stream_close_handle(Stream *stream) FUNC_ATTR_NONNULL_ALL { if (stream->uvstream) { + if (uv_stream_get_write_queue_size(stream->uvstream) > 0) { + WLOG("closed Stream (%p) with %zu unwritten bytes", + (void *)stream, + uv_stream_get_write_queue_size(stream->uvstream)); + } uv_close((uv_handle_t *)stream->uvstream, close_cb); } else { uv_close((uv_handle_t *)&stream->uv.idle, close_cb); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index abed909008..a0fbde008b 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -20,6 +20,7 @@ #include "nvim/ascii.h" #include "nvim/ex_cmds.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" @@ -335,7 +336,7 @@ static int linelen(int *has_tab) len = linetabsize(line); // Check for embedded TAB. if (has_tab != NULL) { - *has_tab = STRRCHR(first, TAB) != NULL; + *has_tab = vim_strchr(first, TAB) != NULL; } *last = save; @@ -1230,7 +1231,7 @@ static void do_filter( /* Create the shell command in allocated memory. */ cmd_buf = make_filter_cmd(cmd, itmp, otmp); - ui_cursor_goto((int)Rows - 1, 0); + ui_cursor_goto(Rows - 1, 0); if (do_out) { if (u_save((linenr_T)(line2), (linenr_T)(line2 + 1)) == FAIL) { @@ -1931,11 +1932,12 @@ void do_wqall(exarg_T *eap) int error = 0; int save_forceit = eap->forceit; - if (eap->cmdidx == CMD_xall || eap->cmdidx == CMD_wqall) - exiting = TRUE; + if (eap->cmdidx == CMD_xall || eap->cmdidx == CMD_wqall) { + exiting = true; + } FOR_ALL_BUFFERS(buf) { - if (!bufIsChanged(buf)) { + if (!bufIsChanged(buf) || bt_dontwrite(buf)) { continue; } /* @@ -2073,7 +2075,7 @@ int getfile(int fnum, char_u *ffname, char_u *sfname, int setpm, linenr_T lnum, } if (curbufIsChanged()) { no_wait_return--; - EMSG(_(e_nowrtmsg)); + no_write_message(); retval = GETFILE_NOT_WRITTEN; // File has been changed. goto theend; } @@ -4553,7 +4555,7 @@ void ex_help(exarg_T *eap) } else { // There is no help window yet. // Try to open the file specified by the "helpfile" option. - if ((helpfd = mch_fopen((char *)p_hf, READBIN)) == NULL) { + if ((helpfd = os_fopen((char *)p_hf, READBIN)) == NULL) { smsg(_("Sorry, help file \"%s\" not found"), p_hf); goto erret; } @@ -5083,7 +5085,7 @@ void fix_help_buffer(void) continue; } - FILE *const fd = mch_fopen((char *)fnames[fi], "r"); + FILE *const fd = os_fopen((char *)fnames[fi], "r"); if (fd == NULL) { continue; } @@ -5219,17 +5221,15 @@ static void helptags_one(char_u *const dir, const char_u *const ext, return; } - FILE *const fd_tags = mch_fopen((char *)NameBuff, "w"); + FILE *const fd_tags = os_fopen((char *)NameBuff, "w"); if (fd_tags == NULL) { EMSG2(_("E152: Cannot open %s for writing"), NameBuff); FreeWild(filecount, files); return; } - /* - * If using the "++t" argument or generating tags for "$VIMRUNTIME/doc" - * add the "help-tags" tag. - */ + // If using the "++t" argument or generating tags for "$VIMRUNTIME/doc" + // add the "help-tags" tag. ga_init(&ga, (int)sizeof(char_u *), 100); if (add_help_tags || path_full_compare((char_u *)"$VIMRUNTIME/doc", @@ -5239,11 +5239,9 @@ static void helptags_one(char_u *const dir, const char_u *const ext, GA_APPEND(char_u *, &ga, s); } - /* - * Go over all the files and extract the tags. - */ + // Go over all the files and extract the tags. for (int fi = 0; fi < filecount && !got_int; fi++) { - FILE *const fd = mch_fopen((char *)files[fi], "r"); + FILE *const fd = os_fopen((char *)files[fi], "r"); if (fd == NULL) { EMSG2(_("E153: Unable to open %s for reading"), files[fi]); continue; @@ -5281,21 +5279,19 @@ static void helptags_one(char_u *const dir, const char_u *const ext, } firstline = false; } - p1 = vim_strchr(IObuff, '*'); /* find first '*' */ + p1 = vim_strchr(IObuff, '*'); // find first '*' while (p1 != NULL) { p2 = (char_u *)strchr((const char *)p1 + 1, '*'); // Find second '*'. - if (p2 != NULL && p2 > p1 + 1) { // Skip "*" and "**". + if (p2 != NULL && p2 > p1 + 1) { // Skip "*" and "**". for (s = p1 + 1; s < p2; s++) { if (*s == ' ' || *s == '\t' || *s == '|') { break; } } - /* - * Only accept a *tag* when it consists of valid - * characters, there is white space before it and is - * followed by a white character or end-of-line. - */ + // Only accept a *tag* when it consists of valid + // characters, there is white space before it and is + // followed by a white character or end-of-line. if (s == p2 && (p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t') && (vim_strchr((char_u *)" \t\n\r", s[1]) != NULL @@ -5306,7 +5302,7 @@ static void helptags_one(char_u *const dir, const char_u *const ext, GA_APPEND(char_u *, &ga, s); sprintf((char *)s, "%s\t%s", p1, fname); - /* find next '*' */ + // find next '*' p2 = vim_strchr(p2 + 1, '*'); } } @@ -5320,18 +5316,12 @@ static void helptags_one(char_u *const dir, const char_u *const ext, FreeWild(filecount, files); - if (!got_int) { - /* - * Sort the tags. - */ - if (ga.ga_data != NULL) { - sort_strings((char_u **)ga.ga_data, ga.ga_len); - } + if (!got_int && ga.ga_data != NULL) { + // Sort the tags. + sort_strings((char_u **)ga.ga_data, ga.ga_len); - /* - * Check for duplicates. - */ - for (int i = 1; i < ga.ga_len; ++i) { + // Check for duplicates. + for (int i = 1; i < ga.ga_len; i++) { p1 = ((char_u **)ga.ga_data)[i - 1]; p2 = ((char_u **)ga.ga_data)[i]; while (*p1 == *p2) { @@ -5353,31 +5343,31 @@ static void helptags_one(char_u *const dir, const char_u *const ext, fprintf(fd_tags, "!_TAG_FILE_ENCODING\tutf-8\t//\n"); } - /* - * Write the tags into the file. - */ - for (int i = 0; i < ga.ga_len; ++i) { + // Write the tags into the file. + for (int i = 0; i < ga.ga_len; i++) { s = ((char_u **)ga.ga_data)[i]; - if (STRNCMP(s, "help-tags\t", 10) == 0) - /* help-tags entry was added in formatted form */ + if (STRNCMP(s, "help-tags\t", 10) == 0) { + // help-tags entry was added in formatted form fputs((char *)s, fd_tags); - else { - fprintf(fd_tags, "%s\t/*", s); - for (p1 = s; *p1 != '\t'; ++p1) { - /* insert backslash before '\\' and '/' */ - if (*p1 == '\\' || *p1 == '/') + } else { + fprintf(fd_tags, "%s\t/" "*", s); + for (p1 = s; *p1 != '\t'; p1++) { + // insert backslash before '\\' and '/' + if (*p1 == '\\' || *p1 == '/') { putc('\\', fd_tags); + } putc(*p1, fd_tags); } fprintf(fd_tags, "*\n"); } } } - if (mix) - got_int = FALSE; /* continue with other languages */ + if (mix) { + got_int = false; // continue with other languages + } GA_DEEP_CLEAR_PTR(&ga); - fclose(fd_tags); /* there is no check for an error... */ + fclose(fd_tags); // there is no check for an error... } /// Generate tags in one help directory, taking care of translations. diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index 0f69d476f9..8c0d22809f 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -601,6 +601,12 @@ return { func='ex_wrongmodifier', }, { + command='const', + flags=bit.bor(NEEDARG, EXTRA, NOTRLCOM, CMDWIN), + addr_type=ADDR_LINES, + func='ex_const', + }, + { command='copen', flags=bit.bor(RANGE, NOTADR, COUNT, TRLBAR), addr_type=ADDR_LINES, @@ -2145,6 +2151,12 @@ return { func='ex_redrawstatus', }, { + command='redrawtabline', + flags=bit.bor(TRLBAR, CMDWIN), + addr_type=ADDR_LINES, + func='ex_redrawtabline', + }, + { command='registers', flags=bit.bor(EXTRA, NOTRLCOM, TRLBAR, CMDWIN), addr_type=ADDR_LINES, @@ -2793,6 +2805,12 @@ return { func='ex_tag', }, { + command='tmenu', + flags=bit.bor(RANGE, NOTADR, ZEROR, EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN), + addr_type=ADDR_LINES, + func='ex_menu', + }, + { command='tmap', flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN), addr_type=ADDR_LINES, @@ -2805,12 +2823,6 @@ return { func='ex_mapclear', }, { - command='tmenu', - flags=bit.bor(RANGE, NOTADR, ZEROR, EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN), - addr_type=ADDR_LINES, - func='ex_menu', - }, - { command='tnext', flags=bit.bor(RANGE, NOTADR, BANG, TRLBAR, ZEROR), addr_type=ADDR_LINES, @@ -2853,16 +2865,16 @@ return { func='ex_tag', }, { - command='tunmap', + command='tunmenu', flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN), addr_type=ADDR_LINES, - func='ex_unmap', + func='ex_menu', }, { - command='tunmenu', + command='tunmap', flags=bit.bor(EXTRA, TRLBAR, NOTRLCOM, USECTRLV, CMDWIN), addr_type=ADDR_LINES, - func='ex_menu', + func='ex_unmap', }, { command='undo', diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index 5ad285c387..408c6dce79 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -18,6 +18,7 @@ #endif #include "nvim/ex_cmds2.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/charset.h" #include "nvim/eval.h" #include "nvim/ex_cmds.h" @@ -43,6 +44,7 @@ #include "nvim/screen.h" #include "nvim/strings.h" #include "nvim/undo.h" +#include "nvim/version.h" #include "nvim/window.h" #include "nvim/profile.h" #include "nvim/os/os.h" @@ -611,7 +613,7 @@ static int dbg_parsearg(char_u *arg, garray_T *gap) return OK; } -/// ":breakadd". +/// ":breakadd". Also used for ":profile". void ex_breakadd(exarg_T *eap) { struct debuggy *bp; @@ -989,7 +991,7 @@ void profile_dump(void) FILE *fd; if (profile_fname != NULL) { - fd = mch_fopen((char *)profile_fname, "w"); + fd = os_fopen((char *)profile_fname, "w"); if (fd == NULL) { EMSG2(_(e_notopen), profile_fname); } else { @@ -1138,7 +1140,7 @@ static void script_dump_profile(FILE *fd) fprintf(fd, "\n"); fprintf(fd, "count total (s) self (s)\n"); - sfd = mch_fopen((char *)si->sn_name, "r"); + sfd = os_fopen((char *)si->sn_name, "r"); if (sfd == NULL) { fprintf(fd, "Cannot open file!\n"); } else { @@ -1274,9 +1276,9 @@ bool check_changed(buf_T *buf, int flags) return bufIsChanged(buf); } if (flags & CCGD_EXCMD) { - EMSG(_(e_nowrtmsg)); + no_write_message(); } else { - EMSG(_(e_nowrtmsg_nobang)); + no_write_message_nobang(); } return true; } @@ -1791,19 +1793,15 @@ void ex_args(exarg_T *eap) } else if (eap->cmdidx == CMD_args) { // ":args": list arguments. if (ARGCOUNT > 0) { + char_u **items = xmalloc(sizeof(char_u *) * (size_t)ARGCOUNT); // Overwrite the command, for a short list there is no scrolling // required and no wait_return(). gotocmdline(true); for (int i = 0; i < ARGCOUNT; i++) { - if (i == curwin->w_arg_idx) { - msg_putchar('['); - } - msg_outtrans(alist_name(&ARGLIST[i])); - if (i == curwin->w_arg_idx) { - msg_putchar(']'); - } - msg_putchar(' '); + items[i] = alist_name(&ARGLIST[i]); } + list_in_columns(items, ARGCOUNT, curwin->w_arg_idx); + xfree(items); } } else if (eap->cmdidx == CMD_arglocal) { garray_T *gap = &curwin->w_alist->al_ga; @@ -2858,7 +2856,7 @@ static int requires_py_version(char_u *filename) lines = 5; } - file = mch_fopen((char *)filename, "r"); + file = os_fopen((char *)filename, "r"); if (file != NULL) { for (i = 0; i < lines; i++) { if (vim_fgets(IObuff, IOSIZE, file)) { @@ -3039,6 +3037,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc) int save_debug_break_level = debug_break_level; scriptitem_T *si = NULL; proftime_T wait_start; + bool trigger_source_post = false; p = expand_env_save(fname); if (p == NULL) { @@ -3059,6 +3058,10 @@ int do_source(char_u *fname, int check_other, int is_vimrc) && apply_autocmds(EVENT_SOURCECMD, fname_exp, fname_exp, false, curbuf)) { retval = aborting() ? FAIL : OK; + if (retval == OK) { + // Apply SourcePost autocommands. + apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf); + } goto theend; } @@ -3181,7 +3184,7 @@ int do_source(char_u *fname, int check_other, int is_vimrc) } si = &SCRIPT_ITEM(current_SID); si->sn_name = fname_exp; - fname_exp = NULL; + fname_exp = vim_strsave(si->sn_name); // used for autocmd if (file_id_ok) { si->file_id_valid = true; si->file_id = file_id; @@ -3261,6 +3264,10 @@ int do_source(char_u *fname, int check_other, int is_vimrc) time_pop(rel_time); } + if (!got_int) { + trigger_source_post = true; + } + // After a "finish" in debug mode, need to break at first command of next // sourced file. if (save_debug_break_level > ex_nesting_level @@ -3278,6 +3285,10 @@ int do_source(char_u *fname, int check_other, int is_vimrc) xfree(firstline); convert_setup(&cookie.conv, NULL, NULL); + if (trigger_source_post) { + apply_autocmds(EVENT_SOURCEPOST, fname_exp, fname_exp, false, curbuf); + } + theend: xfree(fname_exp); return retval; diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h index 6c36922c09..bc7e1e9b59 100644 --- a/src/nvim/ex_cmds_defs.h +++ b/src/nvim/ex_cmds_defs.h @@ -143,7 +143,7 @@ struct exarg { struct expand { int xp_context; // type of expansion char_u *xp_pattern; // start of item to expand - int xp_pattern_len; // bytes in xp_pattern before cursor + size_t xp_pattern_len; // bytes in xp_pattern before cursor char_u *xp_arg; // completion function int xp_scriptID; // SID for completion function int xp_backslash; // one of the XP_BS_ values diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index fb15bd4e66..0f345df22b 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -16,6 +16,7 @@ #include "nvim/ascii.h" #include "nvim/ex_docmd.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" @@ -1108,9 +1109,8 @@ static int current_tab_nr(tabpage_T *tab) #define CURRENT_TAB_NR current_tab_nr(curtab) #define LAST_TAB_NR current_tab_nr(NULL) -/* -* Figure out the address type for ":wincmd". -*/ + +/// Figure out the address type for ":wincmd". static void get_wincmd_addr_type(char_u *arg, exarg_T *eap) { switch (*arg) { @@ -1156,13 +1156,13 @@ static void get_wincmd_addr_type(char_u *arg, exarg_T *eap) case Ctrl_I: case 'd': case Ctrl_D: - /* window size or any count */ - eap->addr_type = ADDR_LINES; + // window size or any count + eap->addr_type = ADDR_LINES; // -V1037 break; case Ctrl_HAT: case '^': - /* buffer number */ + // buffer number eap->addr_type = ADDR_BUFFERS; break; @@ -1177,7 +1177,7 @@ static void get_wincmd_addr_type(char_u *arg, exarg_T *eap) case 'W': case 'x': case Ctrl_X: - /* window number */ + // window number eap->addr_type = ADDR_WINDOWS; break; @@ -1192,7 +1192,7 @@ static void get_wincmd_addr_type(char_u *arg, exarg_T *eap) case Ctrl_P: case '=': case CAR: - /* no count */ + // no count eap->addr_type = 0; break; } @@ -2452,7 +2452,7 @@ static char_u *find_command(exarg_T *eap, int *full) if (ASCII_ISLOWER(eap->cmd[0])) { const int c1 = eap->cmd[0]; - const int c2 = eap->cmd[1]; + const int c2 = len == 1 ? NUL : eap->cmd[1]; if (command_count != (int)CMD_SIZE) { iemsg((char *)_("E943: Command table needs to be updated, run 'make'")); @@ -2503,10 +2503,10 @@ static char_u *find_command(exarg_T *eap, int *full) static char_u * find_ucmd ( exarg_T *eap, - char_u *p, /* end of the command (possibly including count) */ - int *full, /* set to TRUE for a full match */ - expand_T *xp, /* used for completion, NULL otherwise */ - int *compl /* completion flags or NULL */ + char_u *p, // end of the command (possibly including count) + int *full, // set to TRUE for a full match + expand_T *xp, // used for completion, NULL otherwise + int *complp // completion flags or NULL ) { int len = (int)(p - eap->cmd); @@ -2559,8 +2559,9 @@ find_ucmd ( eap->useridx = j; eap->addr_type = uc->uc_addr_type; - if (compl != NULL) - *compl = uc->uc_compl; + if (complp != NULL) { + *complp = uc->uc_compl; + } if (xp != NULL) { xp->xp_arg = uc->uc_compl_arg; xp->xp_scriptID = uc->uc_scriptID; @@ -4060,10 +4061,9 @@ static char_u *replace_makeprg(exarg_T *eap, char_u *p, char_u **cmdlinep) return p; } -/* - * Expand file name in Ex command argument. - * Return FAIL for failure, OK otherwise. - */ +// Expand file name in Ex command argument. +// When an error is detected, "errormsgp" is set to a non-NULL pointer. +// Return FAIL for failure, OK otherwise. int expand_filename(exarg_T *eap, char_u **cmdlinep, char_u **errormsgp) { int has_wildcards; /* need to expand wildcards */ @@ -4614,7 +4614,7 @@ static void ex_doautocmd(exarg_T *eap) int call_do_modelines = check_nomodeline(&arg); bool did_aucmd; - (void)do_doautocmd(arg, true, &did_aucmd); + (void)do_doautocmd(arg, false, &did_aucmd); // Only when there is no <nomodeline>. if (call_do_modelines && did_aucmd) { do_modelines(0); @@ -5088,7 +5088,7 @@ static void uc_list(char_u *name, size_t name_len) } static int uc_scan_attr(char_u *attr, size_t len, uint32_t *argt, long *def, - int *flags, int * compl, char_u **compl_arg, + int *flags, int *complp, char_u **compl_arg, int *addr_type_arg) { char_u *p; @@ -5185,9 +5185,10 @@ invalid_count: return FAIL; } - if (parse_compl_arg(val, (int)vallen, compl, argt, compl_arg) - == FAIL) + if (parse_compl_arg(val, (int)vallen, complp, argt, compl_arg) + == FAIL) { return FAIL; + } } else if (STRNICMP(attr, "addr", attrlen) == 0) { *argt |= RANGE; if (val == NULL) { @@ -6018,7 +6019,7 @@ static void ex_highlight(exarg_T *eap) */ void not_exiting(void) { - exiting = FALSE; + exiting = false; } static bool before_quit_autocmds(win_T *wp, bool quit_all, int forceit) @@ -6216,7 +6217,7 @@ ex_win_close( } need_hide = false; } else { - EMSG(_(e_nowrtmsg)); + no_write_message(); return; } } @@ -6418,7 +6419,7 @@ static void ex_stop(exarg_T *eap) apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL); // TODO(bfredl): the TUI should do this on suspend - ui_cursor_goto((int)Rows - 1, 0); + ui_cursor_goto(Rows - 1, 0); ui_call_grid_scroll(1, 0, Rows, 0, Columns, 1, 0); ui_flush(); ui_call_suspend(); // call machine specific function @@ -6762,8 +6763,9 @@ void ex_splitview(exarg_T *eap) if (*eap->arg != NUL ) { RESET_BINDING(curwin); - } else - do_check_scrollbind(FALSE); + } else { + do_check_scrollbind(false); + } do_exedit(eap, old_curwin); } @@ -6922,16 +6924,17 @@ static void ex_resize(exarg_T *eap) n = atol((char *)eap->arg); if (cmdmod.split & WSP_VERT) { - if (*eap->arg == '-' || *eap->arg == '+') + if (*eap->arg == '-' || *eap->arg == '+') { n += curwin->w_width; - else if (n == 0 && eap->arg[0] == NUL) /* default is very wide */ - n = 9999; + } else if (n == 0 && eap->arg[0] == NUL) { // default is very wide + n = Columns; + } win_setwidth_win(n, wp); } else { if (*eap->arg == '-' || *eap->arg == '+') { n += curwin->w_height; } else if (n == 0 && eap->arg[0] == NUL) { // default is very high - n = 9999; + n = Rows-1; } win_setheight_win(n, wp); } @@ -7520,7 +7523,7 @@ static void ex_operators(exarg_T *eap) case CMD_yank: oa.op_type = OP_YANK; - (void)op_yank(&oa, true); + (void)op_yank(&oa, true, false); break; default: /* CMD_rshift or CMD_lshift */ @@ -7874,6 +7877,22 @@ static void ex_redrawstatus(exarg_T *eap) ui_flush(); } +// ":redrawtabline": force redraw of the tabline +static void ex_redrawtabline(exarg_T *eap FUNC_ATTR_UNUSED) +{ + const int r = RedrawingDisabled; + const int p = p_lz; + + RedrawingDisabled = 0; + p_lz = false; + + draw_tabline(); + + RedrawingDisabled = r; + p_lz = p; + ui_flush(); +} + static void close_redir(void) { if (redir_fd != NULL) { @@ -8090,8 +8109,9 @@ open_exfile ( return NULL; } - if ((fd = mch_fopen((char *)fname, mode)) == NULL) + if ((fd = os_fopen((char *)fname, mode)) == NULL) { EMSG2(_("E190: Cannot open \"%s\" for writing"), fname); + } return fd; } @@ -8452,24 +8472,23 @@ static void ex_tag_cmd(exarg_T *eap, char_u *name) int cmd; switch (name[1]) { - case 'j': cmd = DT_JUMP; /* ":tjump" */ + case 'j': cmd = DT_JUMP; // ":tjump" break; - case 's': cmd = DT_SELECT; /* ":tselect" */ + case 's': cmd = DT_SELECT; // ":tselect" break; - case 'p': cmd = DT_PREV; /* ":tprevious" */ + case 'p': // ":tprevious" + case 'N': cmd = DT_PREV; // ":tNext" break; - case 'N': cmd = DT_PREV; /* ":tNext" */ + case 'n': cmd = DT_NEXT; // ":tnext" break; - case 'n': cmd = DT_NEXT; /* ":tnext" */ + case 'o': cmd = DT_POP; // ":pop" break; - case 'o': cmd = DT_POP; /* ":pop" */ + case 'f': // ":tfirst" + case 'r': cmd = DT_FIRST; // ":trewind" break; - case 'f': /* ":tfirst" */ - case 'r': cmd = DT_FIRST; /* ":trewind" */ + case 'l': cmd = DT_LAST; // ":tlast" break; - case 'l': cmd = DT_LAST; /* ":tlast" */ - break; - default: /* ":tag" */ + default: // ":tag" if (p_cst && *eap->arg != NUL) { ex_cstag(eap); return; @@ -9322,26 +9341,30 @@ static frame_T *ses_skipframe(frame_T *fr) { frame_T *frc; - for (frc = fr; frc != NULL; frc = frc->fr_next) - if (ses_do_frame(frc)) + FOR_ALL_FRAMES(frc, fr) { + if (ses_do_frame(frc)) { break; + } + } return frc; } -/* - * Return TRUE if frame "fr" has a window somewhere that we want to save in - * the Session. - */ -static int ses_do_frame(frame_T *fr) +// Return true if frame "fr" has a window somewhere that we want to save in +// the Session. +static bool ses_do_frame(const frame_T *fr) + FUNC_ATTR_NONNULL_ARG(1) { - frame_T *frc; + const frame_T *frc; - if (fr->fr_layout == FR_LEAF) + if (fr->fr_layout == FR_LEAF) { return ses_do_win(fr->fr_win); - for (frc = fr->fr_child; frc != NULL; frc = frc->fr_next) - if (ses_do_frame(frc)) - return TRUE; - return FALSE; + } + FOR_ALL_FRAMES(frc, fr->fr_child) { + if (ses_do_frame(frc)) { + return true; + } + } + return false; } /// Return non-zero if window "wp" is to be stored in the Session. @@ -9584,7 +9607,7 @@ ses_arglist( if (fputs(cmd, fd) < 0 || put_eol(fd) == FAIL) { return FAIL; } - if (put_line(fd, "silent! argdel *") == FAIL) { + if (put_line(fd, "%argdel") == FAIL) { return FAIL; } for (int i = 0; i < gap->ga_len; ++i) { @@ -10153,7 +10176,7 @@ Dictionary commands_array(buf_T *buf) Dictionary rv = ARRAY_DICT_INIT; Object obj = NIL; (void)obj; // Avoid "dead assignment" warning. - char str[10]; + char str[20]; garray_T *gap = (buf == NULL) ? &ucmds : &buf->b_ucmds; for (int i = 0; i < gap->ga_len; i++) { diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index 479d195966..38432a34db 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -142,12 +142,12 @@ typedef struct command_line_state { long count; int indent; int c; - int i; - int j; int gotesc; // TRUE when <ESC> just typed int do_abbr; // when TRUE check for abbr. char_u *lookfor; // string to match int hiscnt; // current history line in use + int save_hiscnt; // history line before attempting + // to jump to next match int histype; // history type to be used pos_T search_start; // where 'incsearch' starts searching pos_T save_cursor; @@ -200,7 +200,7 @@ static Array cmdline_block = ARRAY_DICT_INIT; */ typedef void *(*user_expand_func_T)(const char_u *, int, - const char_u * const *, + typval_T *, bool); static histentry_T *(history[HIST_COUNT]) = {NULL, NULL, NULL, NULL, NULL}; @@ -279,6 +279,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) s->old_topfill = curwin->w_topfill; s->old_botline = curwin->w_botline; + assert(indent >= 0); + // set some variables for redrawcmd() ccline.cmdfirstc = (s->firstc == '@' ? 0 : s->firstc); ccline.cmdindent = (s->firstc > 0 ? s->indent : 0); @@ -294,7 +296,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) // autoindent for :insert and :append if (s->firstc <= 0) { - memset(ccline.cmdbuff, ' ', s->indent); + memset(ccline.cmdbuff, ' ', (size_t)s->indent); ccline.cmdbuff[s->indent] = NUL; ccline.cmdpos = s->indent; ccline.cmdspos = s->indent; @@ -315,7 +317,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) if (!cmd_silent) { gotocmdline(true); redrawcmdprompt(); // draw prompt or indent - set_cmdspos(); + ccline.cmdspos = cmd_startcol(); if (!msg_scroll) { msg_ext_clear(false); } @@ -382,7 +384,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) bool tl_ret = true; dict_T *dict = get_vim_var_dict(VV_EVENT); char firstcbuf[2]; - firstcbuf[0] = firstc > 0 ? firstc : '-'; + firstcbuf[0] = (char)(firstc > 0 ? firstc : '-'); firstcbuf[1] = 0; if (has_event(EVENT_CMDLINEENTER)) { @@ -676,37 +678,37 @@ static int command_line_execute(VimState *state, int key) // Hitting <Down> after "emenu Name.": complete submenu if (s->c == K_DOWN && ccline.cmdpos > 0 && ccline.cmdbuff[ccline.cmdpos - 1] == '.') { - s->c = p_wc; + s->c = (int)p_wc; } else if (s->c == K_UP) { // Hitting <Up>: Remove one submenu name in front of the // cursor int found = false; - s->j = (int)(s->xpc.xp_pattern - ccline.cmdbuff); - s->i = 0; - while (--s->j > 0) { + int j = (int)(s->xpc.xp_pattern - ccline.cmdbuff); + int i = 0; + while (--j > 0) { // check for start of menu name - if (ccline.cmdbuff[s->j] == ' ' - && ccline.cmdbuff[s->j - 1] != '\\') { - s->i = s->j + 1; + if (ccline.cmdbuff[j] == ' ' + && ccline.cmdbuff[j - 1] != '\\') { + i = j + 1; break; } // check for start of submenu name - if (ccline.cmdbuff[s->j] == '.' - && ccline.cmdbuff[s->j - 1] != '\\') { + if (ccline.cmdbuff[j] == '.' + && ccline.cmdbuff[j - 1] != '\\') { if (found) { - s->i = s->j + 1; + i = j + 1; break; } else { found = true; } } } - if (s->i > 0) { - cmdline_del(s->i); + if (i > 0) { + cmdline_del(i); } - s->c = p_wc; + s->c = (int)p_wc; s->xpc.xp_context = EXPAND_NOTHING; } } @@ -728,44 +730,44 @@ static int command_line_execute(VimState *state, int key) || ccline.cmdbuff[ccline.cmdpos - 2] != '.' || ccline.cmdbuff[ccline.cmdpos - 3] != '.')) { // go down a directory - s->c = p_wc; + s->c = (int)p_wc; } else if (STRNCMP(s->xpc.xp_pattern, upseg + 1, 3) == 0 && s->c == K_DOWN) { // If in a direct ancestor, strip off one ../ to go down int found = false; - s->j = ccline.cmdpos; - s->i = (int)(s->xpc.xp_pattern - ccline.cmdbuff); - while (--s->j > s->i) { - s->j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + s->j); - if (vim_ispathsep(ccline.cmdbuff[s->j])) { + int j = ccline.cmdpos; + int i = (int)(s->xpc.xp_pattern - ccline.cmdbuff); + while (--j > i) { + j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + j); + if (vim_ispathsep(ccline.cmdbuff[j])) { found = true; break; } } if (found - && ccline.cmdbuff[s->j - 1] == '.' - && ccline.cmdbuff[s->j - 2] == '.' - && (vim_ispathsep(ccline.cmdbuff[s->j - 3]) || s->j == s->i + 2)) { - cmdline_del(s->j - 2); - s->c = p_wc; + && ccline.cmdbuff[j - 1] == '.' + && ccline.cmdbuff[j - 2] == '.' + && (vim_ispathsep(ccline.cmdbuff[j - 3]) || j == i + 2)) { + cmdline_del(j - 2); + s->c = (int)p_wc; } } else if (s->c == K_UP) { // go up a directory int found = false; - s->j = ccline.cmdpos - 1; - s->i = (int)(s->xpc.xp_pattern - ccline.cmdbuff); - while (--s->j > s->i) { - s->j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + s->j); - if (vim_ispathsep(ccline.cmdbuff[s->j]) + int j = ccline.cmdpos - 1; + int i = (int)(s->xpc.xp_pattern - ccline.cmdbuff); + while (--j > i) { + j -= utf_head_off(ccline.cmdbuff, ccline.cmdbuff + j); + if (vim_ispathsep(ccline.cmdbuff[j]) #ifdef BACKSLASH_IN_FILENAME - && vim_strchr((const char_u *)" *?[{`$%#", ccline.cmdbuff[s->j + 1]) + && vim_strchr((const char_u *)" *?[{`$%#", ccline.cmdbuff[j + 1]) == NULL #endif ) { if (found) { - s->i = s->j + 1; + i = j + 1; break; } else { found = true; @@ -774,28 +776,28 @@ static int command_line_execute(VimState *state, int key) } if (!found) { - s->j = s->i; - } else if (STRNCMP(ccline.cmdbuff + s->j, upseg, 4) == 0) { - s->j += 4; - } else if (STRNCMP(ccline.cmdbuff + s->j, upseg + 1, 3) == 0 - && s->j == s->i) { - s->j += 3; + j = i; + } else if (STRNCMP(ccline.cmdbuff + j, upseg, 4) == 0) { + j += 4; + } else if (STRNCMP(ccline.cmdbuff + j, upseg + 1, 3) == 0 + && j == i) { + j += 3; } else { - s->j = 0; + j = 0; } - if (s->j > 0) { + if (j > 0) { // TODO(tarruda): this is only for DOS/Unix systems - need to put in // machine-specific stuff here and in upseg init - cmdline_del(s->j); + cmdline_del(j); put_on_cmdline(upseg + 1, 3, false); - } else if (ccline.cmdpos > s->i) { - cmdline_del(s->i); + } else if (ccline.cmdpos > i) { + cmdline_del(i); } // Now complete in the new directory. Set KeyTyped in case the // Up key came from a mapping. - s->c = p_wc; + s->c = (int)p_wc; KeyTyped = true; } } @@ -943,7 +945,7 @@ static int command_line_execute(VimState *state, int key) } } else { // typed p_wc first time s->wim_index = 0; - s->j = ccline.cmdpos; + int j = ccline.cmdpos; // if 'wildmode' first contains "longest", get longest // common part @@ -970,7 +972,7 @@ static int command_line_execute(VimState *state, int key) if (s->res == OK && s->xpc.xp_numfiles > 1) { // a "longest" that didn't do anything is skipped (but not // "list:longest") - if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == s->j) { + if (wim_flags[0] == WIM_LONGEST && ccline.cmdpos == j) { s->wim_index = 1; } if ((wim_flags[s->wim_index] & WIM_LIST) @@ -1069,13 +1071,13 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) search_flags += SEARCH_KEEP; } emsg_off++; - s->i = searchit(curwin, curbuf, &t, NULL, - next_match ? FORWARD : BACKWARD, - pat, s->count, search_flags, - RE_SEARCH, 0, NULL, NULL); + int found = searchit(curwin, curbuf, &t, NULL, + next_match ? FORWARD : BACKWARD, + pat, s->count, search_flags, + RE_SEARCH, 0, NULL, NULL); emsg_off--; ui_busy_stop(); - if (s->i) { + if (found) { s->search_start = s->match_start; s->match_end = t; s->match_start = t; @@ -1124,7 +1126,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) static void command_line_next_histidx(CommandLineState *s, bool next_match) { - s->j = (int)STRLEN(s->lookfor); + int j = (int)STRLEN(s->lookfor); for (;; ) { // one step backwards if (!next_match) { @@ -1137,7 +1139,7 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match) s->hiscnt--; } else { // at top of list - s->hiscnt = s->i; + s->hiscnt = s->save_hiscnt; break; } } else { // one step forwards @@ -1161,14 +1163,14 @@ static void command_line_next_histidx(CommandLineState *s, bool next_match) } if (s->hiscnt < 0 || history[s->histype][s->hiscnt].hisstr == NULL) { - s->hiscnt = s->i; + s->hiscnt = s->save_hiscnt; break; } if ((s->c != K_UP && s->c != K_DOWN) - || s->hiscnt == s->i + || s->hiscnt == s->save_hiscnt || STRNCMP(history[s->histype][s->hiscnt].hisstr, - s->lookfor, (size_t)s->j) == 0) { + s->lookfor, (size_t)j) == 0) { break; } } @@ -1193,7 +1195,7 @@ static int command_line_handle_key(CommandLineState *s) ++ccline.cmdpos; } - if (has_mbyte && s->c == K_DEL) { + if (s->c == K_DEL) { ccline.cmdpos += mb_off_next(ccline.cmdbuff, ccline.cmdbuff + ccline.cmdpos); } @@ -1201,43 +1203,30 @@ static int command_line_handle_key(CommandLineState *s) if (ccline.cmdpos > 0) { char_u *p; - s->j = ccline.cmdpos; - p = ccline.cmdbuff + s->j; - if (has_mbyte) { - p = mb_prevptr(ccline.cmdbuff, p); - - if (s->c == Ctrl_W) { - while (p > ccline.cmdbuff && ascii_isspace(*p)) { - p = mb_prevptr(ccline.cmdbuff, p); - } + int j = ccline.cmdpos; + p = mb_prevptr(ccline.cmdbuff, ccline.cmdbuff + j); - s->i = mb_get_class(p); - while (p > ccline.cmdbuff && mb_get_class(p) == s->i) - p = mb_prevptr(ccline.cmdbuff, p); - - if (mb_get_class(p) != s->i) { - p += (*mb_ptr2len)(p); - } + if (s->c == Ctrl_W) { + while (p > ccline.cmdbuff && ascii_isspace(*p)) { + p = mb_prevptr(ccline.cmdbuff, p); } - } else if (s->c == Ctrl_W) { - while (p > ccline.cmdbuff && ascii_isspace(p[-1])) { - --p; + + int i = mb_get_class(p); + while (p > ccline.cmdbuff && mb_get_class(p) == i) { + p = mb_prevptr(ccline.cmdbuff, p); } - s->i = vim_iswordc(p[-1]); - while (p > ccline.cmdbuff && !ascii_isspace(p[-1]) - && vim_iswordc(p[-1]) == s->i) - --p; - } else { - --p; + if (mb_get_class(p) != i) { + p += utfc_ptr2len(p); + } } ccline.cmdpos = (int)(p - ccline.cmdbuff); - ccline.cmdlen -= s->j - ccline.cmdpos; - s->i = ccline.cmdpos; + ccline.cmdlen -= j - ccline.cmdpos; + int i = ccline.cmdpos; - while (s->i < ccline.cmdlen) { - ccline.cmdbuff[s->i++] = ccline.cmdbuff[s->j++]; + while (i < ccline.cmdlen) { + ccline.cmdbuff[i++] = ccline.cmdbuff[j++]; } // Truncate at the end, required for multi-byte chars. @@ -1307,13 +1296,13 @@ static int command_line_handle_key(CommandLineState *s) status_redraw_curbuf(); return command_line_not_changed(s); - case Ctrl_U: + case Ctrl_U: { // delete all characters left of the cursor - s->j = ccline.cmdpos; - ccline.cmdlen -= s->j; - s->i = ccline.cmdpos = 0; - while (s->i < ccline.cmdlen) { - ccline.cmdbuff[s->i++] = ccline.cmdbuff[s->j++]; + int j = ccline.cmdpos; + ccline.cmdlen -= j; + int i = ccline.cmdpos = 0; + while (i < ccline.cmdlen) { + ccline.cmdbuff[i++] = ccline.cmdbuff[j++]; } // Truncate at the end, required for multi-byte chars. @@ -1323,6 +1312,7 @@ static int command_line_handle_key(CommandLineState *s) } redrawcmd(); return command_line_changed(s); + } case ESC: // get here if p_wc != ESC or when ESC typed twice case Ctrl_C: @@ -1339,15 +1329,15 @@ static int command_line_handle_key(CommandLineState *s) // putting it in history return 0; // back to cmd mode - case Ctrl_R: // insert register + case Ctrl_R: { // insert register putcmdline('"', true); - ++no_mapping; - s->i = s->c = plain_vgetc(); // CTRL-R <char> - if (s->i == Ctrl_O) { - s->i = Ctrl_R; // CTRL-R CTRL-O == CTRL-R CTRL-R + no_mapping++; + int i = s->c = plain_vgetc(); // CTRL-R <char> + if (i == Ctrl_O) { + i = Ctrl_R; // CTRL-R CTRL-O == CTRL-R CTRL-R } - if (s->i == Ctrl_R) { + if (i == Ctrl_R) { s->c = plain_vgetc(); // CTRL-R CTRL-R <char> } --no_mapping; @@ -1369,7 +1359,7 @@ static int command_line_handle_key(CommandLineState *s) } if (s->c != ESC) { // use ESC to cancel inserting register - cmdline_paste(s->c, s->i == Ctrl_R, false); + cmdline_paste(s->c, i == Ctrl_R, false); // When there was a serious error abort getting the // command line. @@ -1391,6 +1381,7 @@ static int command_line_handle_key(CommandLineState *s) ccline.special_char = NUL; redrawcmd(); return command_line_changed(s); + } case Ctrl_D: if (showmatches(&s->xpc, false) == EXPAND_NOTHING) { @@ -1409,24 +1400,17 @@ static int command_line_handle_key(CommandLineState *s) break; } - s->i = cmdline_charsize(ccline.cmdpos); - if (KeyTyped && ccline.cmdspos + s->i >= Columns * Rows) { + int cells = cmdline_charsize(ccline.cmdpos); + if (KeyTyped && ccline.cmdspos + cells >= Columns * Rows) { break; } - ccline.cmdspos += s->i; - if (has_mbyte) { - ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff - + ccline.cmdpos); - } else { - ++ccline.cmdpos; - } + ccline.cmdspos += cells; + ccline.cmdpos += utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos); } while ((s->c == K_S_RIGHT || s->c == K_C_RIGHT || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) && ccline.cmdbuff[ccline.cmdpos] != ' '); - if (has_mbyte) { - set_cmdspos_cursor(); - } + ccline.cmdspos = cmd_screencol(ccline.cmdpos); return command_line_not_changed(s); case K_LEFT: @@ -1446,7 +1430,7 @@ static int command_line_handle_key(CommandLineState *s) || (mod_mask & (MOD_MASK_SHIFT|MOD_MASK_CTRL))) && ccline.cmdbuff[ccline.cmdpos - 1] != ' '); - set_cmdspos_cursor(); + ccline.cmdspos = cmd_screencol(ccline.cmdpos); if (ccline.special_char != NUL) { putcmdline(ccline.special_char, ccline.special_shift); } @@ -1493,22 +1477,19 @@ static int command_line_handle_key(CommandLineState *s) return command_line_not_changed(s); // Ignore mouse } - set_cmdspos(); + ccline.cmdspos = cmd_startcol(); for (ccline.cmdpos = 0; ccline.cmdpos < ccline.cmdlen; - ++ccline.cmdpos) { - s->i = cmdline_charsize(ccline.cmdpos); + ccline.cmdpos++) { + int cells = cmdline_charsize(ccline.cmdpos); if (mouse_row <= cmdline_row + ccline.cmdspos / Columns - && mouse_col < ccline.cmdspos % Columns + s->i) { + && mouse_col < ccline.cmdspos % Columns + cells) { break; } - if (has_mbyte) { - // Count ">" for double-wide char that doesn't fit. - correct_cmdspos(ccline.cmdpos, s->i); - ccline.cmdpos += (*mb_ptr2len)(ccline.cmdbuff - + ccline.cmdpos) - 1; - } - ccline.cmdspos += s->i; + // Count ">" for double-wide char that doesn't fit. + correct_screencol(ccline.cmdpos, cells, &ccline.cmdspos); + ccline.cmdpos += utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos) - 1; + ccline.cmdspos += cells; } return command_line_not_changed(s); @@ -1537,7 +1518,7 @@ static int command_line_handle_key(CommandLineState *s) case K_S_HOME: case K_C_HOME: ccline.cmdpos = 0; - set_cmdspos(); + ccline.cmdspos = cmd_startcol(); return command_line_not_changed(s); case Ctrl_E: // end of command line @@ -1546,7 +1527,7 @@ static int command_line_handle_key(CommandLineState *s) case K_S_END: case K_C_END: ccline.cmdpos = ccline.cmdlen; - set_cmdspos_cursor(); + ccline.cmdspos = cmd_screencol(ccline.cmdpos); return command_line_not_changed(s); case Ctrl_A: // all matches @@ -1613,7 +1594,7 @@ static int command_line_handle_key(CommandLineState *s) return command_line_not_changed(s); } - s->i = s->hiscnt; + s->save_hiscnt = s->hiscnt; // save current command string so it can be restored later if (s->lookfor == NULL) { @@ -1625,7 +1606,7 @@ static int command_line_handle_key(CommandLineState *s) || s->c == K_PAGEDOWN || s->c == K_KPAGEDOWN); command_line_next_histidx(s, next_match); - if (s->hiscnt != s->i) { + if (s->hiscnt != s->save_hiscnt) { // jumped to other entry char_u *p; int len = 0; @@ -1646,35 +1627,35 @@ static int command_line_handle_key(CommandLineState *s) // adding the history entry vs the one used now. // First loop: count length. // Second loop: copy the characters. - for (s->i = 0; s->i <= 1; ++s->i) { + for (int i = 0; i <= 1; i++) { len = 0; - for (s->j = 0; p[s->j] != NUL; ++s->j) { + for (int j = 0; p[j] != NUL; j++) { // Replace old sep with new sep, unless it is // escaped. - if (p[s->j] == old_firstc - && (s->j == 0 || p[s->j - 1] != '\\')) { - if (s->i > 0) { - ccline.cmdbuff[len] = s->firstc; + if (p[j] == old_firstc + && (j == 0 || p[j - 1] != '\\')) { + if (i > 0) { + ccline.cmdbuff[len] = (char_u)s->firstc; } } else { // Escape new sep, unless it is already // escaped. - if (p[s->j] == s->firstc - && (s->j == 0 || p[s->j - 1] != '\\')) { - if (s->i > 0) { + if (p[j] == s->firstc + && (j == 0 || p[j - 1] != '\\')) { + if (i > 0) { ccline.cmdbuff[len] = '\\'; } ++len; } - if (s->i > 0) { - ccline.cmdbuff[len] = p[s->j]; + if (i > 0) { + ccline.cmdbuff[len] = p[j]; } } ++len; } - if (s->i == 0) { + if (i == 0) { alloc_cmdbuff(len); } } @@ -1766,9 +1747,9 @@ static int command_line_handle_key(CommandLineState *s) if (IS_SPECIAL(s->c) || mod_mask != 0) { put_on_cmdline(get_special_key_name(s->c, mod_mask), -1, true); } else { - s->j = utf_char2bytes(s->c, IObuff); - IObuff[s->j] = NUL; // exclude composing chars - put_on_cmdline(IObuff, s->j, true); + int j = utf_char2bytes(s->c, IObuff); + IObuff[j] = NUL; // exclude composing chars + put_on_cmdline(IObuff, j, true); } return command_line_changed(s); } @@ -1810,7 +1791,7 @@ static int command_line_changed(CommandLineState *s) dict_T *dict = get_vim_var_dict(VV_EVENT); char firstcbuf[2]; - firstcbuf[0] = s->firstc > 0 ? s->firstc : '-'; + firstcbuf[0] = (char)(s->firstc > 0 ? s->firstc : '-'); firstcbuf[1] = 0; // set v:event to a dictionary with information about the commandline @@ -1845,10 +1826,11 @@ static int command_line_changed(CommandLineState *s) s->incsearch_postponed = false; curwin->w_cursor = s->search_start; // start at old position save_last_search_pattern(); + int i; // If there is no command line, don't do anything if (ccline.cmdlen == 0) { - s->i = 0; + i = 0; SET_NO_HLSEARCH(true); // turn off previous highlight redraw_all_later(SOME_VALID); } else { @@ -1861,15 +1843,14 @@ static int command_line_changed(CommandLineState *s) if (!p_hls) { search_flags += SEARCH_KEEP; } - s->i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count, - search_flags, - &tm, NULL); + i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count, + search_flags, &tm, NULL); emsg_off--; // if interrupted while searching, behave like it failed if (got_int) { (void)vpeekc(); // remove <C-C> from input stream got_int = false; // don't abandon the command line - s->i = 0; + i = 0; } else if (char_avail()) { // cancelled searching because a char was typed s->incsearch_postponed = true; @@ -1877,7 +1858,7 @@ static int command_line_changed(CommandLineState *s) ui_busy_stop(); } - if (s->i != 0) { + if (i != 0) { highlight_match = true; // highlight position } else { highlight_match = false; // remove highlight @@ -1892,7 +1873,7 @@ static int command_line_changed(CommandLineState *s) changed_cline_bef_curs(); update_topline(); - if (s->i != 0) { + if (i != 0) { pos_T save_pos = curwin->w_cursor; s->match_start = curwin->w_cursor; @@ -1921,7 +1902,7 @@ static int command_line_changed(CommandLineState *s) restore_last_search_pattern(); // Leave it at the end to make CTRL-R CTRL-W work. - if (s->i != 0) { + if (i != 0) { curwin->w_cursor = end_pos; } @@ -1978,6 +1959,7 @@ static int command_line_changed(CommandLineState *s) static void abandon_cmdline(void) { XFREE_CLEAR(ccline.cmdbuff); + ccline.redraw_state = kCmdRedrawNone; if (msg_scrolled == 0) { compute_cmdrow(); } @@ -2131,58 +2113,52 @@ static int cmdline_charsize(int idx) return ptr2cells(ccline.cmdbuff + idx); } -/* - * Compute the offset of the cursor on the command line for the prompt and - * indent. - */ -static void set_cmdspos(void) +/// Compute the offset of the cursor on the command line for the prompt and +/// indent. +static int cmd_startcol(void) { - if (ccline.cmdfirstc != NUL) - ccline.cmdspos = 1 + ccline.cmdindent; - else - ccline.cmdspos = 0 + ccline.cmdindent; + return ccline.cmdindent + ((ccline.cmdfirstc != NUL) ? 1 : 0); } -/* - * Compute the screen position for the cursor on the command line. - */ -static void set_cmdspos_cursor(void) + +/// Compute the column position for a byte position on the command line. +static int cmd_screencol(int bytepos) { - int i, m, c; + int m; // maximum column - set_cmdspos(); + int col = cmd_startcol(); if (KeyTyped) { m = Columns * Rows; if (m < 0) /* overflow, Columns or Rows at weird value */ m = MAXCOL; - } else + } else { m = MAXCOL; - for (i = 0; i < ccline.cmdlen && i < ccline.cmdpos; ++i) { - c = cmdline_charsize(i); - /* Count ">" for double-wide multi-byte char that doesn't fit. */ - if (has_mbyte) - correct_cmdspos(i, c); - /* If the cmdline doesn't fit, show cursor on last visible char. - * Don't move the cursor itself, so we can still append. */ - if ((ccline.cmdspos += c) >= m) { - ccline.cmdspos -= c; + } + + for (int i = 0; i < ccline.cmdlen && i < bytepos; + i += utfc_ptr2len(ccline.cmdbuff + i)) { + int c = cmdline_charsize(i); + // Count ">" for double-wide multi-byte char that doesn't fit. + correct_screencol(i, c, &col); + + // If the cmdline doesn't fit, show cursor on last visible char. + // Don't move the cursor itself, so we can still append. + if ((col += c) >= m) { + col -= c; break; } - if (has_mbyte) - i += (*mb_ptr2len)(ccline.cmdbuff + i) - 1; } + return col; } -/* - * Check if the character at "idx", which is "cells" wide, is a multi-byte - * character that doesn't fit, so that a ">" must be displayed. - */ -static void correct_cmdspos(int idx, int cells) +/// Check if the character at "idx", which is "cells" wide, is a multi-byte +/// character that doesn't fit, so that a ">" must be displayed. +static void correct_screencol(int idx, int cells, int *col) { if (utfc_ptr2len(ccline.cmdbuff + idx) > 1 && utf_ptr2cells(ccline.cmdbuff + idx) > 1 - && ccline.cmdspos % Columns + cells > Columns) { - ccline.cmdspos++; + && (*col) % Columns + cells > Columns) { + (*col)++; } } @@ -2323,8 +2299,10 @@ add_indent: char_u *s = skipwhite(p); // Insert spaces after leading whitespaces. - memmove(s + num_spaces, s, line_ga.ga_len - (s - p) + 1); - memset(s, ' ', num_spaces); + long move_len = line_ga.ga_len - (s - p) + 1; + assert(move_len >= 0); + memmove(s + num_spaces, s, (size_t)move_len); + memset(s, ' ', (size_t)num_spaces); line_ga.ga_len += num_spaces; } @@ -2377,8 +2355,10 @@ redraw: while ((old_indent = get_indent_str(p, 8, FALSE)) > indent) { *--to = NUL; } - memmove(to, from, line_ga.ga_len - (from - p) + 1); - line_ga.ga_len -= from - to; + long move_len = line_ga.ga_len - (from - p) + 1; + assert(move_len > 0); + memmove(to, from, (size_t)move_len); + line_ga.ga_len -= (int)(from - to); // Removed to much indentation, fix it before redrawing. num_spaces = indent - old_indent; @@ -2484,7 +2464,7 @@ static void alloc_cmdbuff(int len) else len += 20; - ccline.cmdbuff = xmalloc(len); + ccline.cmdbuff = xmalloc((size_t)len); ccline.cmdbufflen = len; } @@ -2521,7 +2501,7 @@ static void realloc_cmdbuff(int len) static char_u *arshape_buf = NULL; # if defined(EXITFREE) -void free_cmdline_buf(void) +void free_arshape_buf(void) { xfree(arshape_buf); } @@ -2561,26 +2541,28 @@ static void color_expr_cmdline(const CmdlineInfo *const colored_ccline, size_t prev_end = 0; for (size_t i = 0 ; i < kv_size(colors) ; i++) { const ParserHighlightChunk chunk = kv_A(colors, i); + assert(chunk.start.col < INT_MAX); + assert(chunk.end_col < INT_MAX); if (chunk.start.col != prev_end) { kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { - .start = prev_end, - .end = chunk.start.col, + .start = (int)prev_end, + .end = (int)chunk.start.col, .attr = 0, })); } const int id = syn_name2id((const char_u *)chunk.group); const int attr = (id == 0 ? 0 : syn_id2attr(id)); kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { - .start = chunk.start.col, - .end = chunk.end_col, + .start = (int)chunk.start.col, + .end = (int)chunk.end_col, .attr = attr, })); prev_end = chunk.end_col; } if (prev_end < (size_t)colored_ccline->cmdlen) { kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { - .start = prev_end, - .end = (size_t)colored_ccline->cmdlen, + .start = (int)prev_end, + .end = colored_ccline->cmdlen, .attr = 0, })); } @@ -2745,8 +2727,8 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) } if (start != prev_end) { kv_push(ccline_colors->colors, ((CmdlineColorChunk) { - .start = prev_end, - .end = start, + .start = (int)prev_end, + .end = (int)start, .attr = 0, })); } @@ -2775,15 +2757,15 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) const int id = syn_name2id((char_u *)group); const int attr = (id == 0 ? 0 : syn_id2attr(id)); kv_push(ccline_colors->colors, ((CmdlineColorChunk) { - .start = start, - .end = end, + .start = (int)start, + .end = (int)end, .attr = attr, })); i++; }); if (prev_end < colored_ccline->cmdlen) { kv_push(ccline_colors->colors, ((CmdlineColorChunk) { - .start = prev_end, + .start = (int)prev_end, .end = colored_ccline->cmdlen, .attr = 0, })); @@ -2861,15 +2843,16 @@ static void draw_cmdline(int start, int len) goto draw_cmdline_no_arabicshape; } - static int buflen = 0; + static size_t buflen = 0; + assert(len >= 0); // Do arabic shaping into a temporary buffer. This is very // inefficient! - if (len * 2 + 2 > buflen) { + if ((size_t)len * 2 + 2 > buflen) { // Re-allocate the buffer. We keep it around to avoid a lot of // alloc()/free() calls. xfree(arshape_buf); - buflen = len * 2 + 2; + buflen = (size_t)len * 2 + 2; arshape_buf = xmalloc(buflen); } @@ -2927,7 +2910,7 @@ static void draw_cmdline(int start, int len) } } else { prev_c = u8c; - memmove(arshape_buf + newlen, p, mb_l); + memmove(arshape_buf + newlen, p, (size_t)mb_l); newlen += mb_l; } } @@ -2972,8 +2955,9 @@ static void ui_ext_cmdline_show(CmdlineInfo *line) Array item = ARRAY_DICT_INIT; ADD(item, INTEGER_OBJ(chunk.attr)); + assert(chunk.end >= chunk.start); ADD(item, STRING_OBJ(cbuf_to_string((char *)line->cmdbuff + chunk.start, - chunk.end-chunk.start))); + (size_t)(chunk.end-chunk.start)))); ADD(content, ARRAY_OBJ(item)); } } else { @@ -2994,7 +2978,7 @@ static void ui_ext_cmdline_show(CmdlineInfo *line) } } -void ui_ext_cmdline_block_append(int indent, const char *line) +void ui_ext_cmdline_block_append(size_t indent, const char *line) { char *buf = xmallocz(indent + strlen(line)); memset(buf, ' ', indent); @@ -3073,7 +3057,7 @@ void cmdline_ui_flush(void) * right when "shift" is TRUE. Used for CTRL-V, CTRL-K, etc. * "c" must be printable (fit in one display cell)! */ -void putcmdline(int c, int shift) +void putcmdline(char c, int shift) { if (cmd_silent) { return; @@ -3215,7 +3199,7 @@ void put_on_cmdline(char_u *str, int len, int redraw) c = cmdline_charsize(ccline.cmdpos); // count ">" for a double-wide char that doesn't fit. if (has_mbyte) { - correct_cmdspos(ccline.cmdpos, c); + correct_screencol(ccline.cmdpos, c, &ccline.cmdspos); } // Stop cursor at the end of the screen, but do increment the // insert position, so that entering a very long command @@ -3392,8 +3376,9 @@ void cmdline_paste_str(char_u *s, int literally) /// Delete characters on the command line, from "from" to the current position. static void cmdline_del(int from) { + assert(ccline.cmdpos <= ccline.cmdlen); memmove(ccline.cmdbuff + from, ccline.cmdbuff + ccline.cmdpos, - (size_t)ccline.cmdlen - ccline.cmdpos + 1); + (size_t)ccline.cmdlen - (size_t)ccline.cmdpos + 1); ccline.cmdlen -= ccline.cmdpos - from; ccline.cmdpos = from; } @@ -3428,7 +3413,7 @@ static void redrawcmdprompt(void) if (ccline.cmdprompt != NULL) { msg_puts_attr((const char *)ccline.cmdprompt, ccline.cmdattr); ccline.cmdindent = msg_col + (msg_row - cmdline_row) * Columns; - // do the reverse of set_cmdspos() + // do the reverse of cmd_startcol() if (ccline.cmdfirstc != NUL) { ccline.cmdindent--; } @@ -3470,7 +3455,7 @@ void redrawcmd(void) msg_clr_eos(); msg_no_more = FALSE; - set_cmdspos_cursor(); + ccline.cmdspos = cmd_screencol(ccline.cmdpos); if (ccline.special_char != NUL) { putcmdline(ccline.special_char, ccline.special_shift); @@ -3514,15 +3499,17 @@ static void cursorcmd(void) } if (cmdmsg_rl) { - msg_row = cmdline_row + (ccline.cmdspos / (int)(Columns - 1)); - msg_col = (int)Columns - (ccline.cmdspos % (int)(Columns - 1)) - 1; - if (msg_row <= 0) + msg_row = cmdline_row + (ccline.cmdspos / (Columns - 1)); + msg_col = Columns - (ccline.cmdspos % (Columns - 1)) - 1; + if (msg_row <= 0) { msg_row = Rows - 1; + } } else { - msg_row = cmdline_row + (ccline.cmdspos / (int)Columns); - msg_col = ccline.cmdspos % (int)Columns; - if (msg_row >= Rows) + msg_row = cmdline_row + (ccline.cmdspos / Columns); + msg_col = ccline.cmdspos % Columns; + if (msg_row >= Rows) { msg_row = Rows - 1; + } } ui_cursor_goto(msg_row, msg_col); @@ -3624,7 +3611,8 @@ nextwild ( } i = (int)(xp->xp_pattern - ccline.cmdbuff); - xp->xp_pattern_len = ccline.cmdpos - i; + assert(ccline.cmdpos >= i); + xp->xp_pattern_len = (size_t)ccline.cmdpos - (size_t)i; if (type == WILD_NEXT || type == WILD_PREV) { // Get next/previous match for a previous expanded pattern. @@ -3644,7 +3632,7 @@ nextwild ( xfree(p1); // Longest match: make sure it is not shorter, happens with :help. if (p2 != NULL && type == WILD_LONGEST) { - for (j = 0; j < xp->xp_pattern_len; j++) { + for (j = 0; (size_t)j < xp->xp_pattern_len; j++) { if (ccline.cmdbuff[i + j] == '*' || ccline.cmdbuff[i + j] == '?') { break; @@ -3657,14 +3645,15 @@ nextwild ( } if (p2 != NULL && !got_int) { - difflen = (int)STRLEN(p2) - xp->xp_pattern_len; + difflen = (int)STRLEN(p2) - (int)xp->xp_pattern_len; if (ccline.cmdlen + difflen + 4 > ccline.cmdbufflen) { realloc_cmdbuff(ccline.cmdlen + difflen + 4); xp->xp_pattern = ccline.cmdbuff + i; } + assert(ccline.cmdpos <= ccline.cmdlen); memmove(&ccline.cmdbuff[ccline.cmdpos + difflen], &ccline.cmdbuff[ccline.cmdpos], - (size_t)ccline.cmdlen - ccline.cmdpos + 1); + (size_t)ccline.cmdlen - (size_t)ccline.cmdpos + 1); memmove(&ccline.cmdbuff[i], p2, STRLEN(p2)); ccline.cmdlen += difflen; ccline.cmdpos += difflen; @@ -3861,7 +3850,7 @@ ExpandOne ( size_t len = 0; for (size_t mb_len; xp->xp_files[0][len]; len += mb_len) { - mb_len = utfc_ptr2len(&xp->xp_files[0][len]); + mb_len = (size_t)utfc_ptr2len(&xp->xp_files[0][len]); int c0 = utf_ptr2char(&xp->xp_files[0][len]); for (i = 1; i < xp->xp_numfiles; i++) { int ci = utf_ptr2char(&xp->xp_files[i][len]); @@ -4100,7 +4089,8 @@ void cmdline_pum_display(bool changed_array) */ static int showmatches(expand_T *xp, int wildmenu) { -#define L_SHOWFILE(m) (showtail ? sm_gettail(files_found[m]) : files_found[m]) +#define L_SHOWFILE(m) (showtail \ + ? sm_gettail(files_found[m], false) : files_found[m]) int num_files; char_u **files_found; int i, j, k; @@ -4132,19 +4122,19 @@ static int showmatches(expand_T *xp, int wildmenu) || ui_has(kUIWildmenu); if (compl_use_pum) { + assert(num_files >= 0); compl_match_arraysize = num_files; - compl_match_array = xcalloc(compl_match_arraysize, sizeof(pumitem_T)); + compl_match_array = xcalloc((size_t)compl_match_arraysize, + sizeof(pumitem_T)); for (i = 0; i < num_files; i++) { compl_match_array[i].pum_text = L_SHOWFILE(i); } - ssize_t offset = showtail ? sm_gettail(xp->xp_pattern)-xp->xp_pattern : 0; + char_u *endpos = (showtail + ? sm_gettail(xp->xp_pattern, true) : xp->xp_pattern); if (ui_has(kUICmdline)) { - compl_startcol = ccline.cmdpos - strnlen((char *)xp->xp_pattern+offset, - xp->xp_pattern_len-offset); + compl_startcol = (int)(endpos - ccline.cmdbuff); } else { - compl_startcol = ccline.cmdspos - - mb_string2cells_len(xp->xp_pattern+offset, - xp->xp_pattern_len-offset); + compl_startcol = cmd_screencol((int)(endpos - ccline.cmdbuff)); } compl_selected = -1; cmdline_pum_display(true); @@ -4180,14 +4170,15 @@ static int showmatches(expand_T *xp, int wildmenu) maxlen = j; } - if (xp->xp_context == EXPAND_TAGS_LISTFILES) + if (xp->xp_context == EXPAND_TAGS_LISTFILES) { lines = num_files; - else { - /* compute the number of columns and lines for the listing */ - maxlen += 2; /* two spaces between file names */ - columns = ((int)Columns + 2) / maxlen; - if (columns < 1) + } else { + // compute the number of columns and lines for the listing + maxlen += 2; // two spaces between file names + columns = (Columns + 2) / maxlen; + if (columns < 1) { columns = 1; + } lines = (num_files + columns - 1) / columns; } @@ -4276,7 +4267,7 @@ static int showmatches(expand_T *xp, int wildmenu) * Private path_tail for showmatches() (and win_redr_status_matches()): * Find tail of file name path, but ignore trailing "/". */ -char_u *sm_gettail(char_u *s) +char_u *sm_gettail(char_u *s, bool eager) { char_u *p; char_u *t = s; @@ -4287,9 +4278,13 @@ char_u *sm_gettail(char_u *s) #ifdef BACKSLASH_IN_FILENAME && !rem_backslash(p) #endif - ) - had_sep = TRUE; - else if (had_sep) { + ) { + if (eager) { + t = p+1; + } else { + had_sep = true; + } + } else if (had_sep) { t = p; had_sep = FALSE; } @@ -4329,24 +4324,20 @@ static int expand_showtail(expand_T *xp) return TRUE; } -/* - * Prepare a string for expansion. - * When expanding file names: The string will be used with expand_wildcards(). - * Copy "fname[len]" into allocated memory and add a '*' at the end. - * When expanding other names: The string will be used with regcomp(). Copy - * the name into allocated memory and prepend "^". - */ -char_u * -addstar ( - char_u *fname, - int len, - int context /* EXPAND_FILES etc. */ -) +/// Prepare a string for expansion. +/// +/// When expanding file names: The string will be used with expand_wildcards(). +/// Copy "fname[len]" into allocated memory and add a '*' at the end. +/// When expanding other names: The string will be used with regcomp(). Copy +/// the name into allocated memory and prepend "^". +/// +/// @param context EXPAND_FILES etc. +char_u *addstar(char_u *fname, size_t len, int context) FUNC_ATTR_NONNULL_RET { char_u *retval; - int i, j; - int new_len; + size_t i, j; + size_t new_len; char_u *tail; int ends_in_star; @@ -4437,9 +4428,10 @@ addstar ( tail = path_tail(retval); ends_in_star = (len > 0 && retval[len - 1] == '*'); #ifndef BACKSLASH_IN_FILENAME - for (i = len - 2; i >= 0; --i) { - if (retval[i] != '\\') + for (ssize_t k = (ssize_t)len - 2; k >= 0; k--) { + if (retval[k] != '\\') { break; + } ends_in_star = !ends_in_star; } #endif @@ -4520,7 +4512,7 @@ set_cmd_context ( int use_ccline // use ccline for info ) { - int old_char = NUL; + char_u old_char = NUL; /* * Avoid a UMR warning from Purify, only save the character if it has been @@ -4584,8 +4576,9 @@ expand_cmdline ( return EXPAND_NOTHING; } - /* add star to file name, or convert to regexp if not exp. files. */ - xp->xp_pattern_len = (int)(str + col - xp->xp_pattern); + // add star to file name, or convert to regexp if not exp. files. + assert((str + col) - xp->xp_pattern >= 0); + xp->xp_pattern_len = (size_t)((str + col) - xp->xp_pattern); file_str = addstar(xp->xp_pattern, xp->xp_pattern_len, xp->xp_context); if (p_wic) @@ -4875,7 +4868,7 @@ void ExpandGeneric( ) { int i; - int count = 0; + size_t count = 0; char_u *str; // count the number of matching names @@ -4891,7 +4884,8 @@ void ExpandGeneric( } if (count == 0) return; - *num_file = count; + assert(count < INT_MAX); + *num_file = (int)count; *file = (char_u **)xmalloc(count * sizeof(char_u *)); // copy the matching names into allocated memory @@ -4952,7 +4946,7 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, { char_u *pat; int i; - char_u *path; + char_u *path = NULL; garray_T ga; char_u *buf = xmalloc(MAXPATHL); size_t l; @@ -4971,15 +4965,14 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, flags |= EW_FILE | EW_EXEC | EW_SHELLCMD; bool mustfree = false; // Track memory allocation for *path. - // For an absolute name we don't use $PATH. - if (path_is_absolute(pat)) { - path = (char_u *)" "; - } else if (pat[0] == '.' && (vim_ispathsep(pat[1]) - || (pat[1] == '.' - && vim_ispathsep(pat[2])))) { + if (pat[0] == '.' && (vim_ispathsep(pat[1]) + || (pat[1] == '.' && vim_ispathsep(pat[2])))) { path = (char_u *)"."; } else { - path = (char_u *)vim_getenv("PATH"); + // For an absolute name we don't use $PATH. + if (!path_is_absolute(pat)) { + path = (char_u *)vim_getenv("PATH"); + } if (path == NULL) { path = (char_u *)""; } else { @@ -4993,6 +4986,8 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, * current directory, to find "subdir/cmd". */ ga_init(&ga, (int)sizeof(char *), 10); + hashtab_T found_ht; + hash_init(&found_ht); for (s = path; ; s = e) { if (*s == NUL) { if (did_curdir) { @@ -5004,17 +4999,15 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, did_curdir = true; } - if (*s == ' ') { - s++; // Skip space used for absolute path name. - } - - e = vim_strchr(s, ':'); - if (e == NULL) + e = vim_strchr(s, ENV_SEPCHAR); + if (e == NULL) { e = s + STRLEN(s); + } - l = e - s; - if (l > MAXPATHL - 5) + l = (size_t)(e - s); + if (l > MAXPATHL - 5) { break; + } STRLCPY(buf, s, l + 1); add_pathsep((char *)buf); l = STRLEN(buf); @@ -5025,14 +5018,24 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, if (ret == OK) { ga_grow(&ga, *num_file); { - for (i = 0; i < *num_file; ++i) { - s = (*file)[i]; - if (STRLEN(s) > l) { - /* Remove the path again. */ - STRMOVE(s, s + l); - ((char_u **)ga.ga_data)[ga.ga_len++] = s; - } else - xfree(s); + for (i = 0; i < *num_file; i++) { + char_u *name = (*file)[i]; + + if (STRLEN(name) > l) { + // Check if this name was already found. + hash_T hash = hash_hash(name + l); + hashitem_T *hi = + hash_lookup(&found_ht, (const char *)(name + l), + STRLEN(name + l), hash); + if (HASHITEM_EMPTY(hi)) { + // Remove the path that was prepended. + STRMOVE(name, name + l); + ((char_u **)ga.ga_data)[ga.ga_len++] = name; + hash_add_item(&found_ht, hi, name, hash); + name = NULL; + } + } + xfree(name); } xfree(*file); } @@ -5048,6 +5051,7 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, if (mustfree) { xfree(path); } + hash_clear(&found_ht); } /// Call "user_expand_func()" to invoke a user defined Vim script function and @@ -5055,9 +5059,9 @@ static void expand_shellcmd(char_u *filepat, int *num_file, char_u ***file, static void * call_user_expand_func(user_expand_func_T user_expand_func, expand_T *xp, int *num_file, char_u ***file) { - int keep = 0; - char_u num[50]; - char_u *args[3]; + char_u keep = 0; + typval_T args[4]; + char_u *pat = NULL; int save_current_SID = current_SID; void *ret; struct cmdline_info save_ccline; @@ -5072,10 +5076,14 @@ static void * call_user_expand_func(user_expand_func_T user_expand_func, ccline.cmdbuff[ccline.cmdlen] = 0; } - args[0] = vim_strnsave(xp->xp_pattern, xp->xp_pattern_len); - args[1] = xp->xp_line; - sprintf((char *)num, "%d", xp->xp_col); - args[2] = num; + pat = vim_strnsave(xp->xp_pattern, xp->xp_pattern_len); + args[0].v_type = VAR_STRING; + args[1].v_type = VAR_STRING; + args[2].v_type = VAR_NUMBER; + args[3].v_type = VAR_UNKNOWN; + args[0].vval.v_string = pat; + args[1].vval.v_string = xp->xp_line; + args[2].vval.v_number = xp->xp_col; /* Save the cmdline, we don't know what the function may do. */ save_ccline = ccline; @@ -5085,7 +5093,7 @@ static void * call_user_expand_func(user_expand_func_T user_expand_func, ret = user_expand_func(xp->xp_arg, 3, - (const char_u * const *)args, + args, false); ccline = save_ccline; @@ -5093,7 +5101,7 @@ static void * call_user_expand_func(user_expand_func_T user_expand_func, if (ccline.cmdbuff != NULL) ccline.cmdbuff[ccline.cmdlen] = keep; - xfree(args[0]); + xfree(pat); return ret; } @@ -5117,14 +5125,14 @@ static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, e = vim_strchr(s, '\n'); if (e == NULL) e = s + STRLEN(s); - const int keep = *e; + const char_u keep = *e; *e = NUL; const bool skip = xp->xp_pattern[0] && vim_regexec(regmatch, s, (colnr_T)0) == 0; *e = keep; if (!skip) { - GA_APPEND(char_u *, &ga, vim_strnsave(s, (int)(e - s))); + GA_APPEND(char_u *, &ga, vim_strnsave(s, (size_t)(e - s))); } if (*e != NUL) { @@ -5226,7 +5234,8 @@ static int ExpandRTDir(char_u *pat, int flags, int *num_file, char_u ***file, } s++; *e = NUL; - memmove(match, s, e - s + 1); + assert((e - s) + 1 >= 0); + memmove(match, s, (size_t)(e - s) + 1); } } @@ -5262,8 +5271,7 @@ static int ExpandPackAddDir(char_u *pat, int *num_file, char_u ***file) for (int i = 0; i < ga.ga_len; i++) { char_u *match = ((char_u **)ga.ga_data)[i]; s = path_tail(match); - char_u *e = s + STRLEN(s); - memmove(match, s, e - s + 1); + memmove(match, s, STRLEN(s)+1); } if (GA_EMPTY(&ga)) { @@ -5407,7 +5415,9 @@ void init_history(void) // On copying them to the new arrays, we take the chance to reorder them. if (newlen != oldlen) { for (int type = 0; type < HIST_COUNT; type++) { - histentry_T *temp = newlen ? xmalloc(newlen * sizeof(*temp)) : NULL; + histentry_T *temp = (newlen + ? xmalloc((size_t)newlen * sizeof(*temp)) + : NULL); int j = hisidx[type]; if (j >= 0) { @@ -5572,7 +5582,6 @@ add_to_history ( ) { histentry_T *hisptr; - int len; if (hislen == 0 || histype == HIST_INVALID) { // no history return; @@ -5604,12 +5613,12 @@ add_to_history ( hisptr = &history[histype][hisidx[histype]]; hist_free_entry(hisptr); - /* Store the separator after the NUL of the string. */ - len = (int)STRLEN(new_entry); + // Store the separator after the NUL of the string. + size_t len = STRLEN(new_entry); hisptr->hisstr = vim_strnsave(new_entry, len + 2); hisptr->timestamp = os_time(); hisptr->additional_elements = NULL; - hisptr->hisstr[len + 1] = sep; + hisptr->hisstr[len + 1] = (char_u)sep; hisptr->hisnum = ++hisnum[histype]; if (histype == HIST_SEARCH && in_map) @@ -5663,7 +5672,7 @@ char_u *get_cmdline_str(void) if (p == NULL) return NULL; - return vim_strnsave(p->cmdbuff, p->cmdlen); + return vim_strnsave(p->cmdbuff, (size_t)p->cmdlen); } /* @@ -5932,7 +5941,7 @@ void ex_history(exarg_T *eap) while (ASCII_ISALPHA(*end) || vim_strchr((char_u *)":=@>/?", *end) != NULL) end++; - histype1 = get_histtype((const char *)arg, end - arg, false); + histype1 = get_histtype((const char *)arg, (size_t)(end - arg), false); if (histype1 == HIST_INVALID) { if (STRNICMP(arg, "all", end - arg) == 0) { histype1 = 0; @@ -5971,13 +5980,14 @@ void ex_history(exarg_T *eap) if (hist[i].hisstr != NULL && hist[i].hisnum >= j && hist[i].hisnum <= k) { msg_putchar('\n'); - sprintf((char *)IObuff, "%c%6d ", i == idx ? '>' : ' ', - hist[i].hisnum); - if (vim_strsize(hist[i].hisstr) > (int)Columns - 10) + snprintf((char *)IObuff, IOSIZE, "%c%6d ", i == idx ? '>' : ' ', + hist[i].hisnum); + if (vim_strsize(hist[i].hisstr) > Columns - 10) { trunc_string(hist[i].hisstr, IObuff + STRLEN(IObuff), - (int)Columns - 10, IOSIZE - (int)STRLEN(IObuff)); - else + Columns - 10, IOSIZE - (int)STRLEN(IObuff)); + } else { STRCAT(IObuff, hist[i].hisstr); + } msg_outtrans(IObuff); ui_flush(); } @@ -6138,8 +6148,8 @@ static int open_cmdwin(void) State = NORMAL; setmouse(); - /* Trigger CmdwinEnter autocommands. */ - typestr[0] = cmdwin_type; + // Trigger CmdwinEnter autocommands. + typestr[0] = (char_u)cmdwin_type; typestr[1] = NUL; apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, FALSE, curbuf); if (restart_edit != 0) /* autocmd with ":startinsert" */ @@ -6217,7 +6227,7 @@ static int open_cmdwin(void) if (ccline.cmdpos > ccline.cmdlen) ccline.cmdpos = ccline.cmdlen; if (cmdwin_result == K_IGNORE) { - set_cmdspos_cursor(); + ccline.cmdspos = cmd_screencol(ccline.cmdpos); redrawcmd(); } } diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index f2a664288b..d2620376c6 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -15,6 +15,7 @@ #include "nvim/ascii.h" #include "nvim/fileio.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" @@ -58,10 +59,6 @@ #include "nvim/os/time.h" #include "nvim/os/input.h" -#if defined(HAVE_UTIME) && defined(HAVE_UTIME_H) -# include <utime.h> /* for struct utimbuf */ -#endif - #define BUFSIZE 8192 /* size of normal write buffer */ #define SMBUFSIZE 256 /* size of emergency write buffer */ @@ -167,22 +164,22 @@ typedef struct AutoPatCmd { * Structure to pass arguments from buf_write() to buf_write_bytes(). */ struct bw_info { - int bw_fd; /* file descriptor */ - char_u *bw_buf; /* buffer with data to be written */ - int bw_len; /* length of data */ + int bw_fd; // file descriptor + char_u *bw_buf; // buffer with data to be written + int bw_len; // length of data #ifdef HAS_BW_FLAGS - int bw_flags; /* FIO_ flags */ + int bw_flags; // FIO_ flags #endif - char_u bw_rest[CONV_RESTLEN]; /* not converted bytes */ - int bw_restlen; /* nr of bytes in bw_rest[] */ - int bw_first; /* first write call */ - char_u *bw_conv_buf; /* buffer for writing converted chars */ - int bw_conv_buflen; /* size of bw_conv_buf */ - int bw_conv_error; /* set for conversion error */ - linenr_T bw_conv_error_lnum; /* first line with error or zero */ - linenr_T bw_start_lnum; /* line number at start of buffer */ -# ifdef USE_ICONV - iconv_t bw_iconv_fd; /* descriptor for iconv() or -1 */ + char_u bw_rest[CONV_RESTLEN]; // not converted bytes + int bw_restlen; // nr of bytes in bw_rest[] + int bw_first; // first write call + char_u *bw_conv_buf; // buffer for writing converted chars + int bw_conv_buflen; // size of bw_conv_buf + int bw_conv_error; // set for conversion error + linenr_T bw_conv_error_lnum; // first line with error or zero + linenr_T bw_start_lnum; // line number at start of buffer +# ifdef HAVE_ICONV + iconv_t bw_iconv_fd; // descriptor for iconv() or -1 # endif }; @@ -190,10 +187,6 @@ struct bw_info { # include "fileio.c.generated.h" #endif -#ifdef UNIX -#endif - - static char *e_auchangedbuf = N_( "E812: Autocommands changed buffer or buffer name"); @@ -266,8 +259,8 @@ static AutoPat *last_autopat[NUM_EVENTS] = { * * return FAIL for failure, NOTDONE for directory (failure), or OK */ -int -readfile ( +int +readfile( char_u *fname, char_u *sfname, linenr_T from, @@ -297,7 +290,7 @@ readfile ( int wasempty; /* buffer was empty before reading */ colnr_T len; long size = 0; - char_u *p = NULL; + uint8_t *p = NULL; off_T filesize = 0; int skip_read = false; context_sha256_T sha_ctx; @@ -335,10 +328,10 @@ readfile ( char_u *fenc_next = NULL; // next item in 'fencs' or NULL bool advance_fenc = false; long real_size = 0; -# ifdef USE_ICONV - iconv_t iconv_fd = (iconv_t)-1; /* descriptor for iconv() or -1 */ - int did_iconv = FALSE; /* TRUE when iconv() failed and trying - 'charconvert' next */ +# ifdef HAVE_ICONV + iconv_t iconv_fd = (iconv_t)-1; // descriptor for iconv() or -1 + int did_iconv = false; // TRUE when iconv() failed and trying + // 'charconvert' next # endif int converted = FALSE; /* TRUE if conversion done */ int notconverted = FALSE; /* TRUE if conversion wanted but it @@ -849,7 +842,7 @@ retry: fileformat = EOL_UNKNOWN; /* detect from file */ } -# ifdef USE_ICONV +# ifdef HAVE_ICONV if (iconv_fd != (iconv_t)-1) { /* aborted conversion with iconv(), close the descriptor */ iconv_close(iconv_fd); @@ -916,15 +909,14 @@ retry: -# ifdef USE_ICONV - /* - * Try using iconv() if we can't convert internally. - */ +# ifdef HAVE_ICONV + // Try using iconv() if we can't convert internally. if (fio_flags == 0 && !did_iconv - ) + ) { iconv_fd = (iconv_t)my_iconv_open( enc_utf8 ? (char_u *)"utf-8" : p_enc, fenc); + } # endif /* @@ -933,12 +925,12 @@ retry: */ if (fio_flags == 0 && !read_stdin && !read_buffer && *p_ccv != NUL && !read_fifo -# ifdef USE_ICONV +# ifdef HAVE_ICONV && iconv_fd == (iconv_t)-1 # endif ) { -# ifdef USE_ICONV - did_iconv = FALSE; +# ifdef HAVE_ICONV + did_iconv = false; # endif /* Skip conversion when it's already done (retry for wrong * "fileformat"). */ @@ -958,7 +950,7 @@ retry: } } else { if (fio_flags == 0 -# ifdef USE_ICONV +# ifdef HAVE_ICONV && iconv_fd == (iconv_t)-1 # endif ) { @@ -1031,20 +1023,23 @@ retry: * ucs-4 to utf-8: 4 bytes become up to 6 bytes, size must be * multiple of 4 */ real_size = (int)size; -# ifdef USE_ICONV - if (iconv_fd != (iconv_t)-1) +# ifdef HAVE_ICONV + if (iconv_fd != (iconv_t)-1) { size = size / ICONV_MULT; - else + } else { # endif - if (fio_flags & FIO_LATIN1) + if (fio_flags & FIO_LATIN1) { size = size / 2; - else if (fio_flags & (FIO_UCS2 | FIO_UTF16)) + } else if (fio_flags & (FIO_UCS2 | FIO_UTF16)) { size = (size * 2 / 3) & ~1; - else if (fio_flags & FIO_UCS4) + } else if (fio_flags & FIO_UCS4) { size = (size * 2 / 3) & ~3; - else if (fio_flags == FIO_UCSBOM) - size = size / ICONV_MULT; /* worst case */ - + } else if (fio_flags == FIO_UCSBOM) { + size = size / ICONV_MULT; // worst case + } +# ifdef HAVE_ICONV + } +# endif if (conv_restlen > 0) { // Insert unconverted bytes from previous line. memmove(ptr, conv_rest, conv_restlen); // -V614 @@ -1120,7 +1115,7 @@ retry: /* When we did a conversion report an error. */ if (fio_flags != 0 -# ifdef USE_ICONV +# ifdef HAVE_ICONV || iconv_fd != (iconv_t)-1 # endif ) { @@ -1143,7 +1138,7 @@ retry: * leave the UTF8 checking code to do it, as it * works slightly differently. */ if (bad_char_behavior != BAD_KEEP && (fio_flags != 0 -# ifdef USE_ICONV +# ifdef HAVE_ICONV || iconv_fd != (iconv_t)-1 # endif )) { @@ -1152,8 +1147,8 @@ retry: --conv_restlen; } } - fio_flags = 0; /* don't convert this */ -# ifdef USE_ICONV + fio_flags = 0; // don't convert this +# ifdef HAVE_ICONV if (iconv_fd != (iconv_t)-1) { iconv_close(iconv_fd); iconv_fd = (iconv_t)-1; @@ -1224,7 +1219,7 @@ retry: if (size <= 0) break; -# ifdef USE_ICONV +# ifdef HAVE_ICONV if (iconv_fd != (iconv_t)-1) { /* * Attempt conversion of the read bytes to 'encoding' using @@ -1283,7 +1278,7 @@ retry: # endif if (fio_flags != 0) { - int u8c; + unsigned int u8c; char_u *dest; char_u *tail = NULL; @@ -1431,33 +1426,13 @@ retry: } } } - if (enc_utf8) { /* produce UTF-8 */ - dest -= utf_char2len(u8c); - (void)utf_char2bytes(u8c, dest); - } else { /* produce Latin1 */ - --dest; - if (u8c >= 0x100) { - /* character doesn't fit in latin1, retry with - * another fenc when possible, otherwise just - * report the error. */ - if (can_retry) - goto rewind_retry; - if (conv_error == 0) - conv_error = readfile_linenr(linecnt, ptr, p); - if (bad_char_behavior == BAD_DROP) - ++dest; - else if (bad_char_behavior == BAD_KEEP) - *dest = u8c; - else if (eap != NULL && eap->bad_char != 0) - *dest = bad_char_behavior; - else - *dest = 0xBF; - } else - *dest = u8c; - } + assert(u8c <= INT_MAX); + // produce UTF-8 + dest -= utf_char2len((int)u8c); + (void)utf_char2bytes((int)u8c, dest); } - /* move the linerest to before the converted characters */ + // move the linerest to before the converted characters line_start = dest - linerest; memmove(line_start, buffer, (size_t)linerest); size = (long)((ptr + real_size) - dest); @@ -1465,18 +1440,19 @@ retry: } else if (enc_utf8 && !curbuf->b_p_bin) { int incomplete_tail = FALSE; - /* Reading UTF-8: Check if the bytes are valid UTF-8. */ - for (p = ptr;; ++p) { + // Reading UTF-8: Check if the bytes are valid UTF-8. + for (p = ptr;; p++) { int todo = (int)((ptr + size) - p); int l; - if (todo <= 0) + if (todo <= 0) { break; + } if (*p >= 0x80) { - /* A length of 1 means it's an illegal byte. Accept - * an incomplete character at the end though, the next - * read() will get the next bytes, we'll check it - * then. */ + // A length of 1 means it's an illegal byte. Accept + // an incomplete character at the end though, the next + // read() will get the next bytes, we'll check it + // then. l = utf_ptr2len_len(p, todo); if (l > todo && !incomplete_tail) { /* Avoid retrying with a different encoding when @@ -1501,10 +1477,11 @@ retry: * file is more likely than a conversion error. */ if (can_retry && !incomplete_tail) break; -# ifdef USE_ICONV - /* When we did a conversion report an error. */ - if (iconv_fd != (iconv_t)-1 && conv_error == 0) +# ifdef HAVE_ICONV + // When we did a conversion report an error. + if (iconv_fd != (iconv_t)-1 && conv_error == 0) { conv_error = readfile_linenr(linecnt, ptr, p); + } # endif /* Remember the first linenr with an illegal byte */ if (conv_error == 0 && illegal_byte == 0) @@ -1524,15 +1501,18 @@ retry: if (p < ptr + size && !incomplete_tail) { /* Detected a UTF-8 error. */ rewind_retry: - /* Retry reading with another conversion. */ -# ifdef USE_ICONV - if (*p_ccv != NUL && iconv_fd != (iconv_t)-1) - /* iconv() failed, try 'charconvert' */ - did_iconv = TRUE; - else + // Retry reading with another conversion. +# ifdef HAVE_ICONV + if (*p_ccv != NUL && iconv_fd != (iconv_t)-1) { + // iconv() failed, try 'charconvert' + did_iconv = true; + } else { # endif // use next item from 'fileencodings' advance_fenc = true; +# ifdef HAVE_ICONV + } +# endif file_rewind = true; goto retry; } @@ -1731,13 +1711,13 @@ failed: // Remember the current file format. save_file_ff(curbuf); // If editing a new file: set 'fenc' for the current buffer. - // Also for ":read ++edit file". + // Also for ":read ++edit file". set_string_option_direct((char_u *)"fenc", -1, fenc, OPT_FREE | OPT_LOCAL, 0); } if (fenc_alloced) xfree(fenc); -# ifdef USE_ICONV +# ifdef HAVE_ICONV if (iconv_fd != (iconv_t)-1) { iconv_close(iconv_fd); # ifndef __clang_analyzer__ @@ -1782,6 +1762,9 @@ failed: ml_delete(curbuf->b_ml.ml_line_count, false); linecnt--; } + curbuf->deleted_bytes = 0; + curbuf->deleted_codepoints = 0; + curbuf->deleted_codeunits = 0; linecnt = curbuf->b_ml.ml_line_count - linecnt; if (filesize == 0) linecnt = 0; @@ -2030,11 +2013,11 @@ bool is_dev_fd_file(char_u *fname) * line number where we are now. * Used for error messages that include a line number. */ -static linenr_T -readfile_linenr ( - linenr_T linecnt, /* line count before reading more bytes */ - char_u *p, /* start of more bytes read */ - char_u *endp /* end of more bytes read */ +static linenr_T +readfile_linenr( + linenr_T linecnt, // line count before reading more bytes + char_u *p, // start of more bytes read + char_u *endp // end of more bytes read ) { char_u *s; @@ -2198,34 +2181,6 @@ static void check_marks_read(void) curbuf->b_marks_read = true; } -#ifdef UNIX -static void -set_file_time ( - char_u *fname, - time_t atime, /* access time */ - time_t mtime /* modification time */ -) -{ -# if defined(HAVE_UTIME) && defined(HAVE_UTIME_H) - struct utimbuf buf; - - buf.actime = atime; - buf.modtime = mtime; - (void)utime((char *)fname, &buf); -# else -# if defined(HAVE_UTIMES) - struct timeval tvp[2]; - - tvp[0].tv_sec = atime; - tvp[0].tv_usec = 0; - tvp[1].tv_sec = mtime; - tvp[1].tv_usec = 0; - (void)utimes((char *)fname, (const struct timeval *)&tvp); -# endif -# endif -} -#endif /* UNIX */ - /* * buf_write() - write to file "fname" lines "start" through "end" * @@ -2242,8 +2197,8 @@ set_file_time ( * * return FAIL for failure, OK otherwise */ -int -buf_write ( +int +buf_write( buf_T *buf, char_u *fname, char_u *sfname, @@ -2346,7 +2301,7 @@ buf_write ( write_info.bw_conv_error = FALSE; write_info.bw_conv_error_lnum = 0; write_info.bw_restlen = 0; -# ifdef USE_ICONV +# ifdef HAVE_ICONV write_info.bw_iconv_fd = (iconv_t)-1; # endif @@ -2887,9 +2842,9 @@ buf_write ( } #ifdef UNIX - set_file_time(backup, - file_info_old.stat.st_atim.tv_sec, - file_info_old.stat.st_mtim.tv_sec); + os_file_settime((char *)backup, + file_info_old.stat.st_atim.tv_sec, + file_info_old.stat.st_mtim.tv_sec); #endif #ifdef HAVE_ACL mch_set_acl(backup, acl); @@ -3067,7 +3022,7 @@ nobackup: if (converted && wb_flags == 0) { -# ifdef USE_ICONV +# ifdef HAVE_ICONV // Use iconv() conversion when conversion is needed and it's not done // internally. write_info.bw_iconv_fd = (iconv_t)my_iconv_open(fenc, @@ -3097,7 +3052,7 @@ nobackup: } } if (converted && wb_flags == 0 -# ifdef USE_ICONV +# ifdef HAVE_ICONV && write_info.bw_iconv_fd == (iconv_t)-1 # endif && wfname == fname @@ -3572,9 +3527,9 @@ restore_backup: vim_rename(backup, (char_u *)org); XFREE_CLEAR(backup); // don't delete the file #ifdef UNIX - set_file_time((char_u *)org, - file_info_old.stat.st_atim.tv_sec, - file_info_old.stat.st_mtim.tv_sec); + os_file_settime(org, + file_info_old.stat.st_atim.tv_sec, + file_info_old.stat.st_mtim.tv_sec); #endif } } @@ -3625,7 +3580,7 @@ nofail: xfree(buffer); xfree(fenc_tofree); xfree(write_info.bw_conv_buf); -# ifdef USE_ICONV +# ifdef HAVE_ICONV if (write_info.bw_iconv_fd != (iconv_t)-1) { iconv_close(write_info.bw_iconv_fd); write_info.bw_iconv_fd = (iconv_t)-1; @@ -4000,7 +3955,7 @@ static int buf_write_bytes(struct bw_info *ip) } } -# ifdef USE_ICONV +# ifdef HAVE_ICONV if (ip->bw_iconv_fd != (iconv_t)-1) { const char *from; size_t fromlen; @@ -4303,15 +4258,13 @@ void shorten_buf_fname(buf_T *buf, char_u *dirname, int force) buf->b_sfname = vim_strsave(p); buf->b_fname = buf->b_sfname; } - if (p == NULL || buf->b_fname == NULL) { + if (p == NULL) { buf->b_fname = buf->b_ffname; } } } -/* - * Shorten filenames for all buffers. - */ +/// Shorten filenames for all buffers. void shorten_fnames(int force) { char_u dirname[MAXPATHL]; @@ -4320,8 +4273,8 @@ void shorten_fnames(int force) FOR_ALL_BUFFERS(buf) { shorten_buf_fname(buf, dirname, force); - /* Always make the swap file name a full path, a "nofile" buffer may - * also have a swap file. */ + // Always make the swap file name a full path, a "nofile" buffer may + // also have a swap file. mf_fullname(buf->b_ml.ml_mfp); } status_redraw_all(); @@ -4743,17 +4696,15 @@ int vim_rename(const char_u *from, const char_u *to) static int already_warned = FALSE; -/* - * Check if any not hidden buffer has been changed. - * Postpone the check if there are characters in the stuff buffer, a global - * command is being executed, a mapping is being executed or an autocommand is - * busy. - * Returns TRUE if some message was written (screen should be redrawn and - * cursor positioned). - */ -int -check_timestamps ( - int focus /* called for GUI focus event */ +// Check if any not hidden buffer has been changed. +// Postpone the check if there are characters in the stuff buffer, a global +// command is being executed, a mapping is being executed or an autocommand is +// busy. +// Returns TRUE if some message was written (screen should be redrawn and +// cursor positioned). +int +check_timestamps( + int focus // called for GUI focus event ) { int didit = 0; @@ -4855,8 +4806,8 @@ static int move_lines(buf_T *frombuf, buf_T *tobuf) * return 2 if a message has been displayed. * return 0 otherwise. */ -int -buf_check_timestamp ( +int +buf_check_timestamp( buf_T *buf, int focus /* called for GUI focus event */ ) @@ -4866,13 +4817,12 @@ buf_check_timestamp ( char_u *path; char *mesg = NULL; char *mesg2 = ""; - int helpmesg = FALSE; - int reload = FALSE; - int can_reload = FALSE; + bool helpmesg = false; + bool reload = false; + bool can_reload = false; uint64_t orig_size = buf->b_orig_size; int orig_mode = buf->b_orig_mode; - static int busy = FALSE; - int n; + static bool busy = false; char_u *s; char *reason; @@ -4897,16 +4847,16 @@ buf_check_timestamp ( && buf->b_mtime != 0 && (!(file_info_ok = os_fileinfo((char *)buf->b_ffname, &file_info)) || time_differs(file_info.stat.st_mtim.tv_sec, buf->b_mtime) - || (int)file_info.stat.st_mode != buf->b_orig_mode - )) { + || (int)file_info.stat.st_mode != buf->b_orig_mode)) { + const long prev_b_mtime = buf->b_mtime; + retval = 1; // set b_mtime to stop further warnings (e.g., when executing // FileChangedShell autocmd) if (!file_info_ok) { - // When 'autoread' is set we'll check the file again to see if it - // re-appears. - buf->b_mtime = buf->b_p_ar; + // Check the file again later to see if it re-appears. + buf->b_mtime = -1; buf->b_orig_size = 0; buf->b_orig_mode = 0; } else { @@ -4915,28 +4865,25 @@ buf_check_timestamp ( /* Don't do anything for a directory. Might contain the file * explorer. */ - if (os_isdir(buf->b_fname)) - ; - - /* - * If 'autoread' is set, the buffer has no changes and the file still - * exists, reload the buffer. Use the buffer-local option value if it - * was set, the global option value otherwise. - */ - else if ((buf->b_p_ar >= 0 ? buf->b_p_ar : p_ar) - && !bufIsChanged(buf) && file_info_ok) - reload = TRUE; - else { - if (!file_info_ok) + if (os_isdir(buf->b_fname)) { + } else if ((buf->b_p_ar >= 0 ? buf->b_p_ar : p_ar) + && !bufIsChanged(buf) && file_info_ok) { + // If 'autoread' is set, the buffer has no changes and the file still + // exists, reload the buffer. Use the buffer-local option value if it + // was set, the global option value otherwise. + reload = true; + } else { + if (!file_info_ok) { reason = "deleted"; - else if (bufIsChanged(buf)) + } else if (bufIsChanged(buf)) { reason = "conflict"; - else if (orig_size != buf->b_orig_size || buf_contents_changed(buf)) + } else if (orig_size != buf->b_orig_size || buf_contents_changed(buf)) { reason = "changed"; - else if (orig_mode != buf->b_orig_mode) + } else if (orig_mode != buf->b_orig_mode) { reason = "mode"; - else + } else { reason = "time"; + } // Only give the warning if there are no FileChangedShell // autocommands. @@ -4945,8 +4892,8 @@ buf_check_timestamp ( set_vim_var_string(VV_FCS_REASON, reason, -1); set_vim_var_string(VV_FCS_CHOICE, "", -1); allbuf_lock++; - n = apply_autocmds(EVENT_FILECHANGEDSHELL, - buf->b_fname, buf->b_fname, false, buf); + bool n = apply_autocmds(EVENT_FILECHANGEDSHELL, + buf->b_fname, buf->b_fname, false, buf); allbuf_lock--; busy = false; if (n) { @@ -4954,25 +4901,28 @@ buf_check_timestamp ( EMSG(_("E246: FileChangedShell autocommand deleted buffer")); } s = get_vim_var_str(VV_FCS_CHOICE); - if (STRCMP(s, "reload") == 0 && *reason != 'd') - reload = TRUE; - else if (STRCMP(s, "ask") == 0) - n = FALSE; - else + if (STRCMP(s, "reload") == 0 && *reason != 'd') { + reload = true; + } else if (STRCMP(s, "ask") == 0) { + n = false; + } else { return 2; + } } if (!n) { - if (*reason == 'd') - mesg = _("E211: File \"%s\" no longer available"); - else { - helpmesg = TRUE; - can_reload = TRUE; - /* - * Check if the file contents really changed to avoid - * giving a warning when only the timestamp was set (e.g., - * checked out of CVS). Always warn when the buffer was - * changed. - */ + if (*reason == 'd') { + // Only give the message once. + if (prev_b_mtime != -1) { + mesg = _("E211: File \"%s\" no longer available"); + } + } else { + helpmesg = true; + can_reload = true; + + // Check if the file contents really changed to avoid + // giving a warning when only the timestamp was set (e.g., + // checked out of CVS). Always warn when the buffer was + // changed. if (reason[2] == 'n') { mesg = _( "W12: Warning: File \"%s\" has changed and the buffer was changed in Vim as well"); @@ -4998,7 +4948,7 @@ buf_check_timestamp ( retval = 1; mesg = _("W13: Warning: File \"%s\" has been created after editing started"); buf->b_flags |= BF_NEW_W; - can_reload = TRUE; + can_reload = true; } if (mesg != NULL) { @@ -6304,12 +6254,10 @@ static int do_autocmd_event(event_T event, char_u *pat, bool once, int nested, return OK; } -/* - * Implementation of ":doautocmd [group] event [fname]". - * Return OK for success, FAIL for failure; - */ -int -do_doautocmd ( +// Implementation of ":doautocmd [group] event [fname]". +// Return OK for success, FAIL for failure; +int +do_doautocmd( char_u *arg, int do_msg, // give message for no matching autocmds? bool *did_something @@ -7093,11 +7041,9 @@ void unblock_autocmds(void) apply_autocmds(EVENT_TERMRESPONSE, NULL, NULL, FALSE, curbuf); } -/* - * Find next autocommand pattern that matches. - */ -static void -auto_next_pat ( +// Find next autocommand pattern that matches. +static void +auto_next_pat( AutoPatCmd *apc, int stop_at_last /* stop when 'last' flag is set */ ) diff --git a/src/nvim/fold.c b/src/nvim/fold.c index 72d8c14468..ad0bfe29e2 100644 --- a/src/nvim/fold.c +++ b/src/nvim/fold.c @@ -13,6 +13,7 @@ #include "nvim/vim.h" #include "nvim/ascii.h" #include "nvim/fold.h" +#include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" diff --git a/src/nvim/func_attr.h b/src/nvim/func_attr.h index d3b600a40c..14b8158c7f 100644 --- a/src/nvim/func_attr.h +++ b/src/nvim/func_attr.h @@ -205,8 +205,8 @@ #endif #ifdef DEFINE_FUNC_ATTRIBUTES -/// Non-deferred API function. -# define FUNC_API_ASYNC +/// Fast (non-deferred) API function. +# define FUNC_API_FAST /// Internal C function not exposed in the RPC API. # define FUNC_API_NOEXPORT /// API function not exposed in VimL/eval. diff --git a/src/nvim/garray.c b/src/nvim/garray.c index 74fd9d89cb..1cfc2b6176 100644 --- a/src/nvim/garray.c +++ b/src/nvim/garray.c @@ -89,6 +89,14 @@ void ga_grow(garray_T *gap, int n) if (n < gap->ga_growsize) { n = gap->ga_growsize; } + + // A linear growth is very inefficient when the array grows big. This + // is a compromise between allocating memory that won't be used and too + // many copy operations. A factor of 1.5 seems reasonable. + if (n < gap->ga_len / 2) { + n = gap->ga_len / 2; + } + int new_maxlen = gap->ga_len + n; size_t new_size = (size_t)gap->ga_itemsize * (size_t)new_maxlen; diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua index 40bdc3e30c..1aa8223da0 100644 --- a/src/nvim/generators/c_grammar.lua +++ b/src/nvim/generators/c_grammar.lua @@ -1,4 +1,4 @@ -lpeg = require('lpeg') +local lpeg = require('lpeg') -- lpeg grammar for building api metadata from a set of header files. It -- ignores comments and preprocessor commands and parses a very small subset @@ -35,11 +35,11 @@ local c_params = Ct(c_void + c_param_list) local c_proto = Ct( Cg(c_type, 'return_type') * Cg(c_id, 'name') * fill * P('(') * fill * Cg(c_params, 'parameters') * fill * P(')') * - Cg(Cc(false), 'async') * + Cg(Cc(false), 'fast') * (fill * Cg((P('FUNC_API_SINCE(') * C(num ^ 1)) * P(')'), 'since') ^ -1) * (fill * Cg((P('FUNC_API_DEPRECATED_SINCE(') * C(num ^ 1)) * P(')'), 'deprecated_since') ^ -1) * - (fill * Cg((P('FUNC_API_ASYNC') * Cc(true)), 'async') ^ -1) * + (fill * Cg((P('FUNC_API_FAST') * Cc(true)), 'fast') ^ -1) * (fill * Cg((P('FUNC_API_NOEXPORT') * Cc(true)), 'noexport') ^ -1) * (fill * Cg((P('FUNC_API_REMOTE_ONLY') * Cc(true)), 'remote_only') ^ -1) * (fill * Cg((P('FUNC_API_REMOTE_IMPL') * Cc(true)), 'remote_impl') ^ -1) * diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua index f52d05a4a5..76dcf849d1 100644 --- a/src/nvim/generators/gen_api_dispatch.lua +++ b/src/nvim/generators/gen_api_dispatch.lua @@ -1,4 +1,4 @@ -mpack = require('mpack') +local mpack = require('mpack') -- we need at least 4 arguments since the last two are output files if arg[1] == '--help' then @@ -11,27 +11,27 @@ if arg[1] == '--help' then print(' rest: C files where API functions are defined') end assert(#arg >= 4) -functions = {} +local functions = {} local nvimdir = arg[1] package.path = nvimdir .. '/?.lua;' .. package.path -- names of all headers relative to the source root (for inclusion in the -- generated file) -headers = {} +local headers = {} -- output h file with generated dispatch functions -dispatch_outputf = arg[2] +local dispatch_outputf = arg[2] -- output h file with packed metadata -funcs_metadata_outputf = arg[3] +local funcs_metadata_outputf = arg[3] -- output metadata mpack file, for use by other build scripts -mpack_outputf = arg[4] -lua_c_bindings_outputf = arg[5] +local mpack_outputf = arg[4] +local lua_c_bindings_outputf = arg[5] -- set of function names, used to detect duplicates -function_names = {} +local function_names = {} -c_grammar = require('generators.c_grammar') +local c_grammar = require('generators.c_grammar') -- read each input file, parse and append to the api metadata for i = 6, #arg do @@ -45,10 +45,10 @@ for i = 6, #arg do local input = io.open(full_path, 'rb') local tmp = c_grammar.grammar:match(input:read('*all')) - for i = 1, #tmp do - local fn = tmp[i] + for j = 1, #tmp do + local fn = tmp[j] if not fn.noexport then - functions[#functions + 1] = tmp[i] + functions[#functions + 1] = tmp[j] function_names[fn.name] = true if #fn.parameters ~= 0 and fn.parameters[1][2] == 'channel_id' then -- this function should receive the channel id @@ -83,7 +83,7 @@ end -- Export functions under older deprecated names. -- These will be removed eventually. local deprecated_aliases = require("api.dispatch_deprecated") -for i,f in ipairs(shallowcopy(functions)) do +for _,f in ipairs(shallowcopy(functions)) do local ismethod = false if startswith(f.name, "nvim_") then if startswith(f.name, "nvim__") then @@ -135,9 +135,9 @@ for i,f in ipairs(shallowcopy(functions)) do end -- don't expose internal attributes like "impl_name" in public metadata -exported_attributes = {'name', 'return_type', 'method', - 'since', 'deprecated_since'} -exported_functions = {} +local exported_attributes = {'name', 'return_type', 'method', + 'since', 'deprecated_since'} +local exported_functions = {} for _,f in ipairs(functions) do if not startswith(f.name, "nvim__") then local f_exported = {} @@ -158,14 +158,14 @@ end -- serialize the API metadata using msgpack and embed into the resulting -- binary for easy querying by clients -funcs_metadata_output = io.open(funcs_metadata_outputf, 'wb') -packed = mpack.pack(exported_functions) -dump_bin_array = require("generators.dump_bin_array") +local funcs_metadata_output = io.open(funcs_metadata_outputf, 'wb') +local packed = mpack.pack(exported_functions) +local dump_bin_array = require("generators.dump_bin_array") dump_bin_array(funcs_metadata_output, 'funcs_metadata', packed) funcs_metadata_output:close() -- start building the dispatch wrapper output -output = io.open(dispatch_outputf, 'wb') +local output = io.open(dispatch_outputf, 'wb') local function real_type(type) local rv = type @@ -198,7 +198,8 @@ for i = 1, #functions do output:write('Object handle_'..fn.name..'(uint64_t channel_id, Array args, Error *error)') output:write('\n{') output:write('\n#if MIN_LOG_LEVEL <= DEBUG_LOG_LEVEL') - output:write('\n logmsg(DEBUG_LOG_LEVEL, "RPC: ", NULL, -1, true, "invoke '..fn.name..'");') + output:write('\n logmsg(DEBUG_LOG_LEVEL, "RPC: ", NULL, -1, true, "ch %" PRIu64 ": invoke ' + ..fn.name..'", channel_id);') output:write('\n#endif') output:write('\n Object ret = NIL;') -- Declare/initialize variables that will hold converted arguments @@ -209,20 +210,22 @@ for i = 1, #functions do end output:write('\n') output:write('\n if (args.size != '..#fn.parameters..') {') - output:write('\n api_set_error(error, kErrorTypeException, "Wrong number of arguments: expecting '..#fn.parameters..' but got %zu", args.size);') + output:write('\n api_set_error(error, kErrorTypeException, \ + "Wrong number of arguments: expecting '..#fn.parameters..' but got %zu", args.size);') output:write('\n goto cleanup;') output:write('\n }\n') -- Validation/conversion for each argument for j = 1, #fn.parameters do - local converted, convert_arg, param, arg + local converted, param param = fn.parameters[j] converted = 'arg_'..j local rt = real_type(param[1]) if rt ~= 'Object' then if rt:match('^Buffer$') or rt:match('^Window$') or rt:match('^Tabpage$') then -- Buffer, Window, and Tabpage have a specific type, but are stored in integer - output:write('\n if (args.items['..(j - 1)..'].type == kObjectType'..rt..' && args.items['..(j - 1)..'].data.integer >= 0) {') + output:write('\n if (args.items['.. + (j - 1)..'].type == kObjectType'..rt..' && args.items['..(j - 1)..'].data.integer >= 0) {') output:write('\n '..converted..' = (handle_T)args.items['..(j - 1)..'].data.integer;') else output:write('\n if (args.items['..(j - 1)..'].type == kObjectType'..rt..') {') @@ -230,16 +233,18 @@ for i = 1, #functions do end if rt:match('^Buffer$') or rt:match('^Window$') or rt:match('^Tabpage$') or rt:match('^Boolean$') then -- accept nonnegative integers for Booleans, Buffers, Windows and Tabpages - output:write('\n } else if (args.items['..(j - 1)..'].type == kObjectTypeInteger && args.items['..(j - 1)..'].data.integer >= 0) {') + output:write('\n } else if (args.items['.. + (j - 1)..'].type == kObjectTypeInteger && args.items['..(j - 1)..'].data.integer >= 0) {') output:write('\n '..converted..' = (handle_T)args.items['..(j - 1)..'].data.integer;') end -- accept empty lua tables as empty dictionarys if rt:match('^Dictionary') then - output:write('\n } else if (args.items['..(j - 1)..'].type == kObjectTypeArray && args.items['..(j - 1)..'].data.array.size == 0) {') + output:write('\n } else if (args.items['..(j - 1)..'].type == kObjectTypeArray && args.items['..(j - 1)..'].data.array.size == 0) {') --luacheck: ignore 631 output:write('\n '..converted..' = (Dictionary)ARRAY_DICT_INIT;') end output:write('\n } else {') - output:write('\n api_set_error(error, kErrorTypeException, "Wrong type for argument '..j..', expecting '..param[1]..'");') + output:write('\n api_set_error(error, kErrorTypeException, \ + "Wrong type for argument '..j..', expecting '..param[1]..'");') output:write('\n goto cleanup;') output:write('\n }\n') else @@ -309,26 +314,26 @@ for i = 1, #functions do '(String) {.data = "'..fn.name..'", '.. '.size = sizeof("'..fn.name..'") - 1}, '.. '(MsgpackRpcRequestHandler) {.fn = handle_'.. (fn.impl_name or fn.name).. - ', .async = '..tostring(fn.async)..'});\n') + ', .fast = '..tostring(fn.fast)..'});\n') end output:write('\n}\n\n') output:close() -mpack_output = io.open(mpack_outputf, 'wb') +local mpack_output = io.open(mpack_outputf, 'wb') mpack_output:write(mpack.pack(functions)) mpack_output:close() -local function include_headers(output, headers) - for i = 1, #headers do - if headers[i]:sub(-12) ~= '.generated.h' then - output:write('\n#include "nvim/'..headers[i]..'"') +local function include_headers(output_handle, headers_to_include) + for i = 1, #headers_to_include do + if headers_to_include[i]:sub(-12) ~= '.generated.h' then + output_handle:write('\n#include "nvim/'..headers_to_include[i]..'"') end end end -local function write_shifted_output(output, str) +local function write_shifted_output(_, str) str = str:gsub('\n ', '\n') str = str:gsub('^ ', '') str = str:gsub(' +$', '') @@ -349,14 +354,15 @@ output:write([[ #include "nvim/api/private/defs.h" #include "nvim/api/private/helpers.h" #include "nvim/lua/converter.h" +#include "nvim/lua/executor.h" ]]) include_headers(output, headers) output:write('\n') -lua_c_functions = {} +local lua_c_functions = {} local function process_function(fn) - lua_c_function_name = ('nlua_msgpack_%s'):format(fn.name) + local lua_c_function_name = ('nlua_msgpack_%s'):format(fn.name) write_shifted_output(output, string.format([[ static int %s(lua_State *lstate) @@ -372,14 +378,22 @@ local function process_function(fn) binding=lua_c_function_name, api=fn.name } + + if not fn.fast then + write_shifted_output(output, string.format([[ + if (!nlua_is_deferred_safe(lstate)) { + return luaL_error(lstate, e_luv_api_disabled, "%s"); + } + ]], fn.name)) + end local cparams = '' local free_code = {} for j = #fn.parameters,1,-1 do - param = fn.parameters[j] - cparam = string.format('arg%u', j) - param_type = real_type(param[1]) - lc_param_type = real_type(param[1]):lower() - extra = ((param_type == "Object" or param_type == "Dictionary") and "false, ") or "" + local param = fn.parameters[j] + local cparam = string.format('arg%u', j) + local param_type = real_type(param[1]) + local lc_param_type = real_type(param[1]):lower() + local extra = ((param_type == "Object" or param_type == "Dictionary") and "false, ") or "" if param[1] == "DictionaryOf(LuaRef)" then extra = "true, " end @@ -424,6 +438,7 @@ local function process_function(fn) return lua_error(lstate); } ]] + local return_type if fn.return_type ~= 'void' then if fn.return_type:match('^ArrayOf') then return_type = 'Array' @@ -432,7 +447,7 @@ local function process_function(fn) end write_shifted_output(output, string.format([[ const %s ret = %s(%s); - nlua_push_%s(lstate, ret); + nlua_push_%s(lstate, ret, true); api_free_%s(ret); %s %s diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index c8ab310b02..3cb117d8b5 100644 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -1,20 +1,20 @@ -mpack = require('mpack') +local mpack = require('mpack') local nvimdir = arg[1] package.path = nvimdir .. '/?.lua;' .. package.path assert(#arg == 7) -input = io.open(arg[2], 'rb') -proto_output = io.open(arg[3], 'wb') -call_output = io.open(arg[4], 'wb') -remote_output = io.open(arg[5], 'wb') -bridge_output = io.open(arg[6], 'wb') -metadata_output = io.open(arg[7], 'wb') - -c_grammar = require('generators.c_grammar') +local input = io.open(arg[2], 'rb') +local proto_output = io.open(arg[3], 'wb') +local call_output = io.open(arg[4], 'wb') +local remote_output = io.open(arg[5], 'wb') +local bridge_output = io.open(arg[6], 'wb') +local metadata_output = io.open(arg[7], 'wb') + +local c_grammar = require('generators.c_grammar') local events = c_grammar.grammar:match(input:read('*all')) -function write_signature(output, ev, prefix, notype) +local function write_signature(output, ev, prefix, notype) output:write('('..prefix) if prefix == "" and #ev.parameters == 0 then output:write('void') @@ -32,7 +32,7 @@ function write_signature(output, ev, prefix, notype) output:write(')') end -function write_arglist(output, ev, need_copy) +local function write_arglist(output, ev, need_copy) output:write(' Array args = ARRAY_DICT_INIT;\n') for j = 1, #ev.parameters do local param = ev.parameters[j] @@ -51,7 +51,7 @@ function write_arglist(output, ev, need_copy) end for i = 1, #events do - ev = events[i] + local ev = events[i] assert(ev.return_type == 'void') if ev.since == nil and not ev.noexport then @@ -75,11 +75,11 @@ for i = 1, #events do end if not ev.bridge_impl and not ev.noexport then - send, argv, recv, recv_argv, recv_cleanup = '', '', '', '', '' - argc = 1 + local send, argv, recv, recv_argv, recv_cleanup = '', '', '', '', '' + local argc = 1 for j = 1, #ev.parameters do local param = ev.parameters[j] - copy = 'copy_'..param[2] + local copy = 'copy_'..param[2] if param[1] == 'String' then send = send..' String copy_'..param[2]..' = copy_string('..param[2]..');\n' argv = argv..', '..copy..'.data, INT2PTR('..copy..'.size)' @@ -160,7 +160,6 @@ for i = 1, #events do call_output:write(";\n") call_output:write("}\n\n") end - end proto_output:close() @@ -169,9 +168,9 @@ remote_output:close() bridge_output:close() -- don't expose internal attributes like "impl_name" in public metadata -exported_attributes = {'name', 'parameters', +local exported_attributes = {'name', 'parameters', 'since', 'deprecated_since'} -exported_events = {} +local exported_events = {} for _,ev in ipairs(events) do local ev_exported = {} for _,attr in ipairs(exported_attributes) do @@ -187,7 +186,7 @@ for _,ev in ipairs(events) do end end -packed = mpack.pack(exported_events) -dump_bin_array = require("generators.dump_bin_array") +local packed = mpack.pack(exported_events) +local dump_bin_array = require("generators.dump_bin_array") dump_bin_array(metadata_output, 'ui_events_metadata', packed) metadata_output:close() diff --git a/src/nvim/generators/gen_char_blob.lua b/src/nvim/generators/gen_char_blob.lua index d860375e26..1702add2e4 100644 --- a/src/nvim/generators/gen_char_blob.lua +++ b/src/nvim/generators/gen_char_blob.lua @@ -13,16 +13,17 @@ local source_file = arg[1] local target_file = arg[2] local varname = arg[3] -source = io.open(source_file, 'r') -target = io.open(target_file, 'w') +local source = io.open(source_file, 'r') +local target = io.open(target_file, 'w') target:write('#include <stdint.h>\n\n') target:write(('static const uint8_t %s[] = {\n'):format(varname)) -num_bytes = 0 -MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line +local num_bytes = 0 +local MAX_NUM_BYTES = 15 -- 78 / 5: maximum number of bytes on one line target:write(' ') +local increase_num_bytes increase_num_bytes = function() num_bytes = num_bytes + 1 if num_bytes == MAX_NUM_BYTES then @@ -33,7 +34,7 @@ end for line in source:lines() do for i = 1,string.len(line) do - byte = string.byte(line, i) + local byte = string.byte(line, i) assert(byte ~= 0) target:write(string.format(' %3u,', byte)) increase_num_bytes() diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua index c40c37bb3e..ad44613f42 100755 --- a/src/nvim/generators/gen_declarations.lua +++ b/src/nvim/generators/gen_declarations.lua @@ -10,7 +10,7 @@ local lpeg = require('lpeg') local fold = function (func, ...) local result = nil - for i, v in ipairs({...}) do + for _, v in ipairs({...}) do if result == nil then result = v else @@ -107,7 +107,7 @@ local typ_part = concat( )), spaces ) -local typ = one_or_more(typ_part) + local typ_id = two_or_more(typ_part) local arg = typ_id -- argument name is swallowed by typ local pattern = concat( @@ -222,7 +222,6 @@ local non_static = header local static = header local filepattern = '^#%a* (%d+) "([^"]-)/?([^"/]+)"' -local curfile local init = 1 local curfile = nil @@ -248,14 +247,14 @@ while init ~= nil do else declline = declline - 1 end - elseif init < declendpos then + elseif init < declendpos then -- luacheck: ignore 542 -- Skipping over declaration elseif is_needed_file then s = init - e = pattern:match(text, init) + local e = pattern:match(text, init) if e ~= nil then local declaration = text:sub(s, e - 1) - -- Comments are really handled by preprocessor, so the following is not + -- Comments are really handled by preprocessor, so the following is not -- needed declaration = declaration:gsub('/%*.-%*/', '') declaration = declaration:gsub('//.-\n', '\n') diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua index 23435a1d0b..2c6f8f2603 100644 --- a/src/nvim/generators/gen_eval.lua +++ b/src/nvim/generators/gen_eval.lua @@ -1,4 +1,4 @@ -mpack = require('mpack') +local mpack = require('mpack') local nvimsrcdir = arg[1] local autodir = arg[2] @@ -8,9 +8,9 @@ local funcs_file = arg[4] if nvimsrcdir == '--help' then print([[ Usage: - lua geneval.lua src/nvim build/src/nvim/auto + lua gen_eval.lua src/nvim build/src/nvim/auto -Will generate build/src/nvim/auto/funcs.generated.h with definition of functions +Will generate build/src/nvim/auto/funcs.generated.h with definition of functions static const array. ]]) os.exit(0) @@ -23,8 +23,8 @@ local funcsfname = autodir .. '/funcs.generated.h' local gperfpipe = io.open(funcsfname .. '.gperf', 'wb') local funcs = require('eval').funcs -local metadata = mpack.unpack(io.open(arg[3], 'rb'):read("*all")) -for i,fun in ipairs(metadata) do +local metadata = mpack.unpack(io.open(metadata_file, 'rb'):read("*all")) +for _,fun in ipairs(metadata) do if not fun.remote_only then funcs[fun.name] = { args=#fun.parameters, @@ -53,14 +53,14 @@ VimLFuncDef; ]]) for name, def in pairs(funcs) do - args = def.args or 0 + local args = def.args or 0 if type(args) == 'number' then args = {args, args} elseif #args == 1 then args[2] = 'MAX_FUNC_ARGS' end - func = def.func or ('f_' .. name) - data = def.data or "NULL" + local func = def.func or ('f_' .. name) + local data = def.data or "NULL" gperfpipe:write(('%s, %s, %s, &%s, (FunPtr)%s\n') :format(name, args[1], args[2], func, data)) end diff --git a/src/nvim/generators/gen_events.lua b/src/nvim/generators/gen_events.lua index d03c787b2b..98c3254e7a 100644 --- a/src/nvim/generators/gen_events.lua +++ b/src/nvim/generators/gen_events.lua @@ -13,8 +13,8 @@ local auevents = require('auevents') local events = auevents.events local aliases = auevents.aliases -enum_tgt = io.open(fileio_enum_file, 'w') -names_tgt = io.open(names_file, 'w') +local enum_tgt = io.open(fileio_enum_file, 'w') +local names_tgt = io.open(names_file, 'w') enum_tgt:write('typedef enum auto_event {') names_tgt:write([[ @@ -42,8 +42,8 @@ enum_tgt:write('\n} event_T;\n') names_tgt:write('\n};\n') names_tgt:write('\nstatic AutoPat *first_autopat[NUM_EVENTS] = {\n ') -line_len = 1 -for i = 1,((#events) - 1) do +local line_len = 1 +for _ = 1,((#events) - 1) do line_len = line_len + #(' NULL,') if line_len > 80 then names_tgt:write('\n ') diff --git a/src/nvim/generators/gen_ex_cmds.lua b/src/nvim/generators/gen_ex_cmds.lua index 7859d7c71a..075d8ba9cc 100644 --- a/src/nvim/generators/gen_ex_cmds.lua +++ b/src/nvim/generators/gen_ex_cmds.lua @@ -7,8 +7,8 @@ if nvimsrcdir == '--help' then Usage: lua genex_cmds.lua src/nvim build/include build/src/nvim/auto -Will generate files build/include/ex_cmds_enum.generated.h with cmdidx_T -enum and build/src/nvim/auto/ex_cmds_defs.generated.h with main Ex commands +Will generate files build/include/ex_cmds_enum.generated.h with cmdidx_T +enum and build/src/nvim/auto/ex_cmds_defs.generated.h with main Ex commands definitions. ]]) os.exit(0) @@ -23,10 +23,7 @@ local enumfile = io.open(enumfname, 'w') local defsfile = io.open(defsfname, 'w') local defs = require('ex_cmds') -local lastchar = nil -local i -local cmd local first = true local byte_a = string.byte('a') @@ -58,7 +55,7 @@ defsfile:write(string.format([[ static CommandDefinition cmdnames[%u] = { ]], #defs)) local cmds, cmdidxs1, cmdidxs2 = {}, {}, {} -for i, cmd in ipairs(defs) do +for _, cmd in ipairs(defs) do local enumname = cmd.enum or ('CMD_' .. cmd.command) local byte_cmd = cmd.command:sub(1, 1):byte() if byte_a <= byte_cmd and byte_cmd <= byte_z then diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index fdc00d5dc0..a8cf496cb9 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -20,7 +20,7 @@ end local options = require('options') -cstr = options.cstr +local cstr = options.cstr local type_flags={ bool='P_BOOL', @@ -79,6 +79,7 @@ local get_flags = function(o) {'pri_mkrc'}, {'deny_in_modelines', 'P_NO_ML'}, {'deny_duplicates', 'P_NODUP'}, + {'modelineexpr', 'P_MLE'}, }) do local key_name = flag_desc[1] local def_name = flag_desc[2] or ('P_' .. key_name:upper()) @@ -107,12 +108,12 @@ get_cond = function(c, base_string) return cond_string end -value_dumpers = { +local value_dumpers = { ['function']=function(v) return v() end, string=cstr, boolean=function(v) return v and 'true' or 'false' end, number=function(v) return ('%iL'):format(v) end, - ['nil']=function(v) return '0L' end, + ['nil']=function(_) return '0L' end, } local get_value = function(v) diff --git a/src/nvim/generators/gen_unicode_tables.lua b/src/nvim/generators/gen_unicode_tables.lua index 3130cecd82..aa96c97bc1 100644 --- a/src/nvim/generators/gen_unicode_tables.lua +++ b/src/nvim/generators/gen_unicode_tables.lua @@ -1,16 +1,16 @@ -- Script creates the following tables in unicode_tables.generated.h: -- --- 1. doublewidth and ambiguous tables: sorted list of non-overlapping closed --- intervals. Codepoints in these intervals have double (W or F) or ambiguous +-- 1. doublewidth and ambiguous tables: sorted list of non-overlapping closed +-- intervals. Codepoints in these intervals have double (W or F) or ambiguous -- (A) east asian width respectively. --- 2. combining table: same as the above, but characters inside are combining +-- 2. combining table: same as the above, but characters inside are combining -- characters (i.e. have general categories equal to Mn, Mc or Me). --- 3. foldCase, toLower and toUpper tables used to convert characters to --- folded/lower/upper variants. In these tables first two values are --- character ranges: like in previous tables they are sorted and must be --- non-overlapping. Third value means step inside the range: e.g. if it is --- 2 then interval applies only to first, third, fifth, … character in range. --- Fourth value is number that should be added to the codepoint to yield +-- 3. foldCase, toLower and toUpper tables used to convert characters to +-- folded/lower/upper variants. In these tables first two values are +-- character ranges: like in previous tables they are sorted and must be +-- non-overlapping. Third value means step inside the range: e.g. if it is +-- 2 then interval applies only to first, third, fifth, … character in range. +-- Fourth value is number that should be added to the codepoint to yield -- folded/lower/upper codepoint. -- 4. emoji_width and emoji_all tables: sorted lists of non-overlapping closed -- intervals of Emoji characters. emoji_width contains all the characters @@ -38,7 +38,7 @@ local split_on_semicolons = function(s) local ret = {} local idx = 1 while idx <= #s + 1 do - item = s:match('^[^;]*', idx) + local item = s:match('^[^;]*', idx) idx = idx + #item + 1 if idx <= #s + 1 then assert(s:sub(idx - 1, idx - 1) == ';') @@ -208,7 +208,7 @@ local build_width_table = function(ut_fp, dataprops, widthprops, widths, -- But use all chars from a range. local dp = dataprops[dataidx] if (n_last > n) or (not (({Mn=true, Mc=true, Me=true})[dp[3]])) then - if start >= 0 and end_ + 1 == n then + if start >= 0 and end_ + 1 == n then -- luacheck: ignore 542 -- Continue with the same range. else if start >= 0 then @@ -235,6 +235,8 @@ local build_emoji_table = function(ut_fp, emojiprops, doublewidth, ambiwidth) for _, p in ipairs(emojiprops) do if p[2]:match('Emoji%s+#') then local rng_start, rng_end = p[1]:find('%.%.') + local n + local n_last if rng_start then n = tonumber(p[1]:sub(1, rng_start - 1), 16) n_last = tonumber(p[1]:sub(rng_end + 1), 16) diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index 7e4a0e1321..03f64c2019 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -15,6 +15,7 @@ #include <string.h> #include <inttypes.h> +#include "nvim/assert.h" #include "nvim/vim.h" #include "nvim/ascii.h" #include "nvim/getchar.h" @@ -82,10 +83,10 @@ static buffheader_T old_redobuff = { { NULL, { NUL } }, NULL, 0, 0 }; static buffheader_T recordbuff = { { NULL, { NUL } }, NULL, 0, 0 }; // First read ahead buffer. Used for translated commands. -static buffheader_T readbuf1 = {{NULL, {NUL}}, NULL, 0, 0}; +static buffheader_T readbuf1 = { { NULL, { NUL } }, NULL, 0, 0 }; // Second read ahead buffer. Used for redo. -static buffheader_T readbuf2 = {{NULL, {NUL}}, NULL, 0, 0}; +static buffheader_T readbuf2 = { { NULL, { NUL } }, NULL, 0, 0 }; static int typeahead_char = 0; /* typeahead char that's not flushed */ @@ -181,18 +182,22 @@ static char_u *get_buffcont(buffheader_T *buffer, size_t count = 0; char_u *p = NULL; char_u *p2; - char_u *str; - /* compute the total length of the string */ - for (buffblock_T *bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next) + // compute the total length of the string + for (const buffblock_T *bp = buffer->bh_first.b_next; + bp != NULL; bp = bp->b_next) { count += STRLEN(bp->b_str); + } if (count || dozero) { p = xmalloc(count + 1); p2 = p; - for (buffblock_T *bp = buffer->bh_first.b_next; bp != NULL; bp = bp->b_next) - for (str = bp->b_str; *str; ) + for (const buffblock_T *bp = buffer->bh_first.b_next; + bp != NULL; bp = bp->b_next) { + for (const char_u *str = bp->b_str; *str;) { *p2++ = *str++; + } + } *p2 = NUL; } return p; @@ -356,12 +361,12 @@ static int read_readbuffers(int advance) static int read_readbuf(buffheader_T *buf, int advance) { char_u c; - buffblock_T *curr; - if (buf->bh_first.b_next == NULL) /* buffer is empty */ + if (buf->bh_first.b_next == NULL) { // buffer is empty return NUL; + } - curr = buf->bh_first.b_next; + buffblock_T *const curr = buf->bh_first.b_next; c = curr->b_str[buf->bh_index]; if (advance) { @@ -668,12 +673,10 @@ static int read_redo(bool init, bool old_redo) int i; if (init) { - if (old_redo) - bp = old_redobuff.bh_first.b_next; - else - bp = redobuff.bh_first.b_next; - if (bp == NULL) + bp = old_redo ? old_redobuff.bh_first.b_next : redobuff.bh_first.b_next; + if (bp == NULL) { return FAIL; + } p = bp->b_str; return OK; } @@ -896,18 +899,19 @@ int ins_typebuf(char_u *str, int noremap, int offset, int nottyped, bool silent) s2 = xmalloc((size_t)newlen); typebuf.tb_buflen = newlen; - /* copy the old chars, before the insertion point */ - memmove(s1 + newoff, typebuf.tb_buf + typebuf.tb_off, - (size_t)offset); - /* copy the new chars */ + // copy the old chars, before the insertion point + memmove(s1 + newoff, typebuf.tb_buf + typebuf.tb_off, (size_t)offset); + // copy the new chars memmove(s1 + newoff + offset, str, (size_t)addlen); - /* copy the old chars, after the insertion point, including the NUL at - * the end */ + // copy the old chars, after the insertion point, including the NUL at + // the end + int bytes = typebuf.tb_len - offset + 1; + assert(bytes > 0); memmove(s1 + newoff + offset + addlen, - typebuf.tb_buf + typebuf.tb_off + offset, - (size_t)(typebuf.tb_len - offset + 1)); - if (typebuf.tb_buf != typebuf_init) + typebuf.tb_buf + typebuf.tb_off + offset, (size_t)bytes); + if (typebuf.tb_buf != typebuf_init) { xfree(typebuf.tb_buf); + } typebuf.tb_buf = s1; memmove(s2 + newoff, typebuf.tb_noremap + typebuf.tb_off, @@ -1053,11 +1057,12 @@ void del_typebuf(int len, int offset) typebuf.tb_noremap + typebuf.tb_off, (size_t)offset); typebuf.tb_off = MAXMAPLEN; } - /* adjust typebuf.tb_buf (include the NUL at the end) */ + // adjust typebuf.tb_buf (include the NUL at the end) + int bytes = typebuf.tb_len - offset + 1; + assert(bytes > 0); memmove(typebuf.tb_buf + typebuf.tb_off + offset, - typebuf.tb_buf + i + len, - (size_t)(typebuf.tb_len - offset + 1)); - /* adjust typebuf.tb_noremap[] */ + typebuf.tb_buf + i + len, (size_t)bytes); + // adjust typebuf.tb_noremap[] memmove(typebuf.tb_noremap + typebuf.tb_off + offset, typebuf.tb_noremap + i + len, (size_t)(typebuf.tb_len - offset)); @@ -1244,9 +1249,17 @@ openscript ( EMSG(_(e_nesting)); return; } - if (ignore_script) - /* Not reading from script, also don't open one. Warning message? */ + + // Disallow sourcing a file in the sandbox, the commands would be executed + // later, possibly outside of the sandbox. + if (check_secure()) { + return; + } + + if (ignore_script) { + // Not reading from script, also don't open one. Warning message? return; + } if (scriptin[curscript] != NULL) /* already reading script */ ++curscript; @@ -1761,7 +1774,7 @@ static int vgetorpeek(int advance) && !(State == HITRETURN && (c1 == CAR || c1 == ' ')) && State != ASKMORE && State != CONFIRM - && !((ctrl_x_mode != 0 && vim_is_ctrl_x_key(c1)) + && !((ctrl_x_mode_not_default() && vim_is_ctrl_x_key(c1)) || ((compl_cont_status & CONT_LOCAL) && (c1 == Ctrl_N || c1 == Ctrl_P))) ) { @@ -1908,7 +1921,7 @@ static int vgetorpeek(int advance) set_option_value("paste", !p_paste, NULL, 0); if (!(State & INSERT)) { msg_col = 0; - msg_row = (int)Rows - 1; + msg_row = Rows - 1; msg_clr_eos(); // clear ruler } status_redraw_all(); @@ -1927,10 +1940,12 @@ static int vgetorpeek(int advance) } if ((mp == NULL || max_mlen >= mp_match_len) - && keylen != KEYLEN_PART_MAP) { + && keylen != KEYLEN_PART_MAP + && !(keylen == KEYLEN_PART_KEY && c1 == ui_toggle[0])) { // No matching mapping found or found a non-matching mapping that // matches at least what the matching mapping matched keylen = 0; + (void)keylen; // suppress clang/dead assignment // If there was no mapping, use the character from the typeahead // buffer right here. Otherwise, use the mapping (loop around). if (mp == NULL) { @@ -2252,14 +2267,13 @@ static int vgetorpeek(int advance) curwin->w_wrow = old_wrow; } - /* this looks nice when typing a dead character map */ - if ((State & CMDLINE) - && cmdline_star == 0 - && ptr2cells(typebuf.tb_buf + typebuf.tb_off - + typebuf.tb_len - 1) == 1) { - putcmdline(typebuf.tb_buf[typebuf.tb_off - + typebuf.tb_len - 1], FALSE); - c1 = 1; + // this looks nice when typing a dead character map + if ((State & CMDLINE) && cmdline_star == 0) { + char_u *p = typebuf.tb_buf + typebuf.tb_off + typebuf.tb_len - 1; + if (ptr2cells(p) == 1 && *p < 128) { + putcmdline((char)(*p), false); + c1 = 1; + } } } @@ -2342,6 +2356,17 @@ static int vgetorpeek(int advance) } } + if (timedout && c == ESC) { + char_u nop_buf[3]; + + // When recording there will be no timeout. Add a <Nop> after the ESC + // to avoid that it forms a key code with following characters. + nop_buf[0] = K_SPECIAL; + nop_buf[1] = KS_EXTRA; + nop_buf[2] = KE_NOP; + gotchars(nop_buf, 3); + } + --vgetc_busy; return c; @@ -3264,29 +3289,36 @@ char *map_mode_to_chars(int mode) ga_init(&mapmode, 1, 7); - if ((mode & (INSERT + CMDLINE)) == INSERT + CMDLINE) - ga_append(&mapmode, '!'); /* :map! */ - else if (mode & INSERT) - ga_append(&mapmode, 'i'); /* :imap */ - else if (mode & LANGMAP) - ga_append(&mapmode, 'l'); /* :lmap */ - else if (mode & CMDLINE) - ga_append(&mapmode, 'c'); /* :cmap */ - else if ((mode & (NORMAL + VISUAL + SELECTMODE + OP_PENDING)) - == NORMAL + VISUAL + SELECTMODE + OP_PENDING) - ga_append(&mapmode, ' '); /* :map */ - else { - if (mode & NORMAL) - ga_append(&mapmode, 'n'); /* :nmap */ - if (mode & OP_PENDING) - ga_append(&mapmode, 'o'); /* :omap */ - if ((mode & (VISUAL + SELECTMODE)) == VISUAL + SELECTMODE) - ga_append(&mapmode, 'v'); /* :vmap */ - else { - if (mode & VISUAL) - ga_append(&mapmode, 'x'); /* :xmap */ - if (mode & SELECTMODE) - ga_append(&mapmode, 's'); /* :smap */ + if ((mode & (INSERT + CMDLINE)) == INSERT + CMDLINE) { + ga_append(&mapmode, '!'); // :map! + } else if (mode & INSERT) { + ga_append(&mapmode, 'i'); // :imap + } else if (mode & LANGMAP) { + ga_append(&mapmode, 'l'); // :lmap + } else if (mode & CMDLINE) { + ga_append(&mapmode, 'c'); // :cmap + } else if ((mode & (NORMAL + VISUAL + SELECTMODE + OP_PENDING)) + == NORMAL + VISUAL + SELECTMODE + OP_PENDING) { + ga_append(&mapmode, ' '); // :map + } else { + if (mode & NORMAL) { + ga_append(&mapmode, 'n'); // :nmap + } + if (mode & OP_PENDING) { + ga_append(&mapmode, 'o'); // :omap + } + if (mode & TERM_FOCUS) { + ga_append(&mapmode, 't'); // :tmap + } + if ((mode & (VISUAL + SELECTMODE)) == VISUAL + SELECTMODE) { + ga_append(&mapmode, 'v'); // :vmap + } else { + if (mode & VISUAL) { + ga_append(&mapmode, 'x'); // :xmap + } + if (mode & SELECTMODE) { + ga_append(&mapmode, 's'); // :smap + } } } @@ -3417,7 +3449,7 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr) { mapblock_T *mp; int hash; - bool expand_buffer = false; + bool exp_buffer = false; validate_maphash(); @@ -3428,12 +3460,12 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr) if (hash > 0) { // There is only one abbr list. break; } - if (expand_buffer) { + if (exp_buffer) { mp = curbuf->b_first_abbr; } else { mp = first_abbr; } - } else if (expand_buffer) { + } else if (exp_buffer) { mp = curbuf->b_maphash[hash]; } else { mp = maphash[hash]; @@ -3445,10 +3477,10 @@ int map_to_exists_mode(const char *const rhs, const int mode, const bool abbr) } } } - if (expand_buffer) { + if (exp_buffer) { break; } - expand_buffer = true; + exp_buffer = true; } return false; @@ -3527,11 +3559,9 @@ set_context_in_map_cmd ( return NULL; } -/* - * Find all mapping/abbreviation names that match regexp 'prog'. - * For command line expansion of ":[un]map" and ":[un]abbrev" in all modes. - * Return OK if matches found, FAIL otherwise. - */ +// Find all mapping/abbreviation names that match regexp "regmatch". +// For command line expansion of ":[un]map" and ":[un]abbrev" in all modes. +// Return OK if matches found, FAIL otherwise. int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) { mapblock_T *mp; @@ -3591,7 +3621,7 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) mp = maphash[hash]; for (; mp; mp = mp->m_next) { if (mp->m_mode & expand_mapmodes) { - p = translate_mapping(mp->m_keys, true, CPO_TO_CPO_FLAGS); + p = translate_mapping(mp->m_keys, CPO_TO_CPO_FLAGS); if (p != NULL && vim_regexec(regmatch, p, (colnr_T)0)) { if (round == 1) ++count; @@ -3642,7 +3672,9 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) /* * Check for an abbreviation. - * Cursor is at ptr[col]. When inserting, mincol is where insert started. + * Cursor is at ptr[col]. + * When inserting, mincol is where insert started. + * For the command line, mincol is what is to be skipped over. * "c" is the character typed before check_abbr was called. It may have * ABBR_OFF added to avoid prepending a CTRL-V to it. * @@ -3921,10 +3953,10 @@ void vim_unescape_csi(char_u *p) * Write map commands for the current mappings to an .exrc file. * Return FAIL on error, OK otherwise. */ -int -makemap ( +int +makemap( FILE *fd, - buf_T *buf /* buffer for local mappings or NULL */ + buf_T *buf // buffer for local mappings or NULL ) { mapblock_T *mp; @@ -3937,50 +3969,56 @@ makemap ( validate_maphash(); - /* - * Do the loop twice: Once for mappings, once for abbreviations. - * Then loop over all map hash lists. - */ - for (abbr = 0; abbr < 2; ++abbr) - for (hash = 0; hash < 256; ++hash) { + // Do the loop twice: Once for mappings, once for abbreviations. + // Then loop over all map hash lists. + for (abbr = 0; abbr < 2; abbr++) { + for (hash = 0; hash < 256; hash++) { if (abbr) { - if (hash > 0) /* there is only one abbr list */ + if (hash > 0) { // there is only one abbr list break; - if (buf != NULL) + } + if (buf != NULL) { mp = buf->b_first_abbr; - else + } else { mp = first_abbr; + } } else { - if (buf != NULL) + if (buf != NULL) { mp = buf->b_maphash[hash]; - else + } else { mp = maphash[hash]; + } } for (; mp; mp = mp->m_next) { - /* skip script-local mappings */ - if (mp->m_noremap == REMAP_SCRIPT) + // skip script-local mappings + if (mp->m_noremap == REMAP_SCRIPT) { continue; + } - /* skip mappings that contain a <SNR> (script-local thing), - * they probably don't work when loaded again */ - for (p = mp->m_str; *p != NUL; ++p) + // skip mappings that contain a <SNR> (script-local thing), + // they probably don't work when loaded again + for (p = mp->m_str; *p != NUL; p++) { if (p[0] == K_SPECIAL && p[1] == KS_EXTRA - && p[2] == (int)KE_SNR) + && p[2] == (int)KE_SNR) { break; - if (*p != NUL) + } + } + if (*p != NUL) { continue; + } - /* It's possible to create a mapping and then ":unmap" certain - * modes. We recreate this here by mapping the individual - * modes, which requires up to three of them. */ + // It's possible to create a mapping and then ":unmap" certain + // modes. We recreate this here by mapping the individual + // modes, which requires up to three of them. c1 = NUL; c2 = NUL; c3 = NUL; - if (abbr) + if (abbr) { cmd = "abbr"; - else + } else { cmd = "map"; + } switch (mp->m_mode) { case NORMAL + VISUAL + SELECTMODE + OP_PENDING: break; @@ -4038,8 +4076,9 @@ makemap ( c2 = 'o'; break; case CMDLINE + INSERT: - if (!abbr) + if (!abbr) { cmd = "map!"; + } break; case CMDLINE: c1 = 'c'; @@ -4057,9 +4096,10 @@ makemap ( IEMSG(_("E228: makemap: Illegal mode")); return FAIL; } - do { /* do this twice if c2 is set, 3 times with c3 */ - /* When outputting <> form, need to make sure that 'cpo' - * is set to the Vim default. */ + do { + // do this twice if c2 is set, 3 times with c3 */ + // When outputting <> form, need to make sure that 'cpo' + // is set to the Vim default. if (!did_cpo) { if (*mp->m_str == NUL) { // Will use <Nop>. did_cpo = true; @@ -4074,63 +4114,69 @@ makemap ( if (fprintf(fd, "let s:cpo_save=&cpo") < 0 || put_eol(fd) < 0 || fprintf(fd, "set cpo&vim") < 0 - || put_eol(fd) < 0) + || put_eol(fd) < 0) { return FAIL; + } } } - if (c1 && putc(c1, fd) < 0) + if (c1 && putc(c1, fd) < 0) { return FAIL; - if (mp->m_noremap != REMAP_YES && fprintf(fd, "nore") < 0) - return FAIL; - if (fputs(cmd, fd) < 0) + } + if (mp->m_noremap != REMAP_YES && fprintf(fd, "nore") < 0) { return FAIL; - if (buf != NULL && fputs(" <buffer>", fd) < 0) + } + if (fputs(cmd, fd) < 0) { return FAIL; - if (mp->m_nowait && fputs(" <nowait>", fd) < 0) + } + if (buf != NULL && fputs(" <buffer>", fd) < 0) { return FAIL; - if (mp->m_silent && fputs(" <silent>", fd) < 0) + } + if (mp->m_nowait && fputs(" <nowait>", fd) < 0) { return FAIL; - if (mp->m_noremap == REMAP_SCRIPT - && fputs("<script>", fd) < 0) + } + if (mp->m_silent && fputs(" <silent>", fd) < 0) { return FAIL; - if (mp->m_expr && fputs(" <expr>", fd) < 0) + } + if (mp->m_expr && fputs(" <expr>", fd) < 0) { return FAIL; + } - if ( putc(' ', fd) < 0 - || put_escstr(fd, mp->m_keys, 0) == FAIL - || putc(' ', fd) < 0 - || put_escstr(fd, mp->m_str, 1) == FAIL - || put_eol(fd) < 0) + if (putc(' ', fd) < 0 + || put_escstr(fd, mp->m_keys, 0) == FAIL + || putc(' ', fd) < 0 + || put_escstr(fd, mp->m_str, 1) == FAIL + || put_eol(fd) < 0) { return FAIL; + } c1 = c2; c2 = c3; c3 = NUL; } while (c1 != NUL); } } - - if (did_cpo) + } + if (did_cpo) { if (fprintf(fd, "let &cpo=s:cpo_save") < 0 || put_eol(fd) < 0 || fprintf(fd, "unlet s:cpo_save") < 0 - || put_eol(fd) < 0) + || put_eol(fd) < 0) { return FAIL; + } + } return OK; } -/* - * write escape string to file - * "what": 0 for :map lhs, 1 for :map rhs, 2 for :set - * - * return FAIL for failure, OK otherwise - */ +// write escape string to file +// "what": 0 for :map lhs, 1 for :map rhs, 2 for :set +// +// return FAIL for failure, OK otherwise int put_escstr(FILE *fd, char_u *strstart, int what) { char_u *str = strstart; int c; int modifiers; - /* :map xx <Nop> */ + // :map xx <Nop> if (*str == NUL && what == 1) { if (fprintf(fd, "<Nop>") < 0) return FAIL; @@ -4299,16 +4345,15 @@ void add_map(char_u *map, int mode) // corresponding external one recognized by :map/:abbrev commands. // // This function is called when expanding mappings/abbreviations on the -// command-line, and for building the "Ambiguous mapping..." error message. +// command-line. // -// It uses a growarray to build the translation string since the -// latter can be wider than the original description. The caller has to -// free the string afterwards. +// It uses a growarray to build the translation string since the latter can be +// wider than the original description. The caller has to free the string +// afterwards. // // Returns NULL when there is a problem. static char_u * translate_mapping ( char_u *str, - int expmap, // True when expanding mappings on command-line int cpo_flags // Value of various flags present in &cpo ) { @@ -4326,12 +4371,8 @@ static char_u * translate_mapping ( modifiers = *++str; c = *++str; } - + if (c == K_SPECIAL && str[1] != NUL && str[2] != NUL) { - if (expmap) { - ga_clear(&ga); - return NULL; - } c = TO_SPECIAL(str[1], str[2]); if (c == K_ZERO) { // display <Nul> as ^@ @@ -4340,10 +4381,6 @@ static char_u * translate_mapping ( str += 2; } if (IS_SPECIAL(c) || modifiers) { // special key - if (expmap) { - ga_clear(&ga); - return NULL; - } ga_concat(&ga, get_special_key_name(c, modifiers)); continue; /* for (str) */ } diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 6d602d2712..3bdbff79b4 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -88,18 +88,15 @@ EXTERN struct nvim_stats_s { #define NO_BUFFERS 1 // not all buffers loaded yet // 0 not starting anymore -/* - * Number of Rows and Columns in the screen. - * Must be long to be able to use them as options in option.c. - * Note: Use default_grid.Rows and default_grid.Columns to access items in - * default_grid.chars[]. They may have different values when the screen - * wasn't (re)allocated yet after setting Rows or Columns (e.g., when starting - * up). - */ +// Number of Rows and Columns in the screen. +// Note: Use default_grid.Rows and default_grid.Columns to access items in +// default_grid.chars[]. They may have different values when the screen +// wasn't (re)allocated yet after setting Rows or Columns (e.g., when starting +// up). #define DFLT_COLS 80 // default value for 'columns' #define DFLT_ROWS 24 // default value for 'lines' -EXTERN long Rows INIT(= DFLT_ROWS); // nr of rows in the screen -EXTERN long Columns INIT(= DFLT_COLS); // nr of columns in the screen +EXTERN int Rows INIT(= DFLT_ROWS); // nr of rows in the screen +EXTERN int Columns INIT(= DFLT_COLS); // nr of columns in the screen // We use 64-bit file functions here, if available. E.g. ftello() returns // off_t instead of long, which helps if long is 32 bit and off_t is 64 bit. @@ -435,10 +432,11 @@ EXTERN win_T *firstwin; /* first window */ EXTERN win_T *lastwin; /* last window */ EXTERN win_T *prevwin INIT(= NULL); /* previous window */ # define ONE_WINDOW (firstwin == lastwin) -/* - * When using this macro "break" only breaks out of the inner loop. Use "goto" - * to break out of the tabpage loop. - */ +# define FOR_ALL_FRAMES(frp, first_frame) \ + for (frp = first_frame; frp != NULL; frp = frp->fr_next) // NOLINT + +// When using this macro "break" only breaks out of the inner loop. Use "goto" +// to break out of the tabpage loop. # define FOR_ALL_TAB_WINDOWS(tp, wp) \ FOR_ALL_TABS(tp) \ FOR_ALL_WINDOWS_IN_TAB(wp, tp) @@ -510,7 +508,7 @@ EXTERN int sc_col; /* column for shown command */ // First NO_SCREEN, then NO_BUFFERS, then 0 when startup finished. EXTERN int starting INIT(= NO_SCREEN); // true when planning to exit. Might keep running if there is a changed buffer. -EXTERN int exiting INIT(= false); +EXTERN bool exiting INIT(= false); // is stdin a terminal? EXTERN int stdin_isatty INIT(= true); // is stdout a terminal? @@ -629,6 +627,8 @@ EXTERN pos_T Insstart_orig; EXTERN int orig_line_count INIT(= 0); /* Line count when "gR" started */ EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */ +// increase around internal delete/replace +EXTERN int inhibit_delete_count INIT(= 0); /* * These flags are set based upon 'fileencoding'. @@ -655,16 +655,6 @@ EXTERN int vr_lines_changed INIT(= 0); /* #Lines changed by "gR" so far */ /// Encoding used when 'fencs' is set to "default" EXTERN char_u *fenc_default INIT(= NULL); -# if defined(USE_ICONV) && defined(DYNAMIC_ICONV) -/* Pointers to functions and variables to be loaded at runtime */ -EXTERN size_t (*iconv)(iconv_t cd, const char **inbuf, size_t *inbytesleft, - char **outbuf, size_t *outbytesleft); -EXTERN iconv_t (*iconv_open)(const char *tocode, const char *fromcode); -EXTERN int (*iconv_close)(iconv_t cd); -EXTERN int (*iconvctl)(iconv_t cd, int request, void *argument); -EXTERN int* (*iconv_errno)(void); -# endif - /// "State" is the main state of Vim. /// There are other variables that modify the state: /// Visual_mode: When State is NORMAL or INSERT. @@ -700,11 +690,10 @@ EXTERN int arrow_used; /* Normally FALSE, set to TRUE after * to call u_sync() */ EXTERN int ins_at_eol INIT(= FALSE); /* put cursor after eol when restarting edit after CTRL-O */ -EXTERN char_u *edit_submode INIT(= NULL); /* msg for CTRL-X submode */ -EXTERN char_u *edit_submode_pre INIT(= NULL); /* prepended to edit_submode */ -EXTERN char_u *edit_submode_extra INIT(= NULL); /* appended to edit_submode */ -EXTERN hlf_T edit_submode_highl; /* highl. method for extra info */ -EXTERN int ctrl_x_mode INIT(= 0); /* Which Ctrl-X mode are we in? */ +EXTERN char_u *edit_submode INIT(= NULL); // msg for CTRL-X submode +EXTERN char_u *edit_submode_pre INIT(= NULL); // prepended to edit_submode +EXTERN char_u *edit_submode_extra INIT(= NULL); // appended to edit_submode +EXTERN hlf_T edit_submode_highl; // highl. method for extra info EXTERN int no_abbr INIT(= TRUE); /* TRUE when no abbreviations loaded */ @@ -757,9 +746,11 @@ EXTERN bool KeyTyped; // true if user typed current char EXTERN int KeyStuffed; // TRUE if current char from stuffbuf EXTERN int maptick INIT(= 0); // tick for each non-mapped char -EXTERN int must_redraw INIT(= 0); /* type of redraw necessary */ -EXTERN int skip_redraw INIT(= FALSE); /* skip redraw once */ -EXTERN int do_redraw INIT(= FALSE); /* extra redraw once */ +EXTERN int must_redraw INIT(= 0); // type of redraw necessary +EXTERN bool skip_redraw INIT(= false); // skip redraw once +EXTERN bool do_redraw INIT(= false); // extra redraw once +EXTERN bool must_redraw_pum INIT(= false); // redraw pum. NB: must_redraw + // should also be set. EXTERN int need_highlight_changed INIT(= true); @@ -933,6 +924,9 @@ EXTERN char_u e_interr[] INIT(= N_("Interrupted")); EXTERN char_u e_invaddr[] INIT(= N_("E14: Invalid address")); EXTERN char_u e_invarg[] INIT(= N_("E474: Invalid argument")); EXTERN char_u e_invarg2[] INIT(= N_("E475: Invalid argument: %s")); +EXTERN char_u e_invargval[] INIT(= N_("E475: Invalid value for argument %s")); +EXTERN char_u e_invargNval[] INIT(= N_( + "E475: Invalid value for argument %s: %s")); EXTERN char_u e_duparg2[] INIT(= N_("E983: Duplicate argument: %s")); EXTERN char_u e_invexpr2[] INIT(= N_("E15: Invalid expression: %s")); EXTERN char_u e_invrange[] INIT(= N_("E16: Invalid range")); @@ -980,9 +974,6 @@ 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)")); -EXTERN char_u e_nowrtmsg_nobang[] INIT(= N_("E37: No write since last change")); EXTERN char_u e_null[] INIT(= N_("E38: Null argument")); EXTERN char_u e_number_exp[] INIT(= N_("E39: Number expected")); EXTERN char_u e_openerrf[] INIT(= N_("E40: Can't open errorfile %s")); @@ -1055,6 +1046,9 @@ EXTERN char_u e_cmdmap_key[] INIT(=N_( EXTERN char_u e_api_error[] INIT(=N_( "E5555: API call: %s")); +EXTERN char e_luv_api_disabled[] INIT(=N_( + "E5560: %s must not be called in a lua loop callback")); + EXTERN char_u e_floatonly[] INIT(=N_( "E5601: Cannot close window, only floating window would remain")); EXTERN char_u e_floatexchange[] INIT(=N_( diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h index 38fc513baa..e4021c033b 100644 --- a/src/nvim/grid_defs.h +++ b/src/nvim/grid_defs.h @@ -54,6 +54,9 @@ typedef struct { int row_offset; int col_offset; + // whether the compositor should blend the grid with the background grid + bool blending; + // state owned by the compositor. int comp_row; int comp_col; @@ -61,7 +64,7 @@ typedef struct { bool comp_disabled; } ScreenGrid; -#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, 0, 0, false, 0, 0, 0, \ - 0, 0, false } +#define SCREEN_GRID_INIT { 0, NULL, NULL, NULL, NULL, 0, 0, false, 0, 0, \ + false, 0, 0, 0, false } #endif // NVIM_GRID_DEFS_H diff --git a/src/nvim/hardcopy.c b/src/nvim/hardcopy.c index bf2ac35554..3e0e438434 100644 --- a/src/nvim/hardcopy.c +++ b/src/nvim/hardcopy.c @@ -582,9 +582,9 @@ static void prt_header(prt_settings_T *const psettings, const int pagenum, */ static void prt_message(char_u *s) { - grid_fill(&default_grid, (int)Rows - 1, (int)Rows, 0, (int)Columns, ' ', ' ', - 0); - grid_puts(&default_grid, s, (int)Rows - 1, 0, HL_ATTR(HLF_R)); + // TODO(bfredl): delete this + grid_fill(&default_grid, Rows - 1, Rows, 0, Columns, ' ', ' ', 0); + grid_puts(&default_grid, s, Rows - 1, 0, HL_ATTR(HLF_R)); ui_flush(); } @@ -1670,7 +1670,7 @@ static int prt_open_resource(struct prt_ps_resource_S *resource) FILE *fd_resource; struct prt_dsc_line_S dsc_line; - fd_resource = mch_fopen((char *)resource->filename, READBIN); + fd_resource = os_fopen((char *)resource->filename, READBIN); if (fd_resource == NULL) { EMSG2(_("E624: Can't open file \"%s\""), resource->filename); return FALSE; @@ -2343,11 +2343,11 @@ int mch_print_init(prt_settings_T *psettings, char_u *jobname, int forceit) EMSG(_(e_notmp)); return FAIL; } - prt_ps_fd = mch_fopen((char *)prt_ps_file_name, WRITEBIN); + prt_ps_fd = os_fopen((char *)prt_ps_file_name, WRITEBIN); } else { p = expand_env_save(psettings->outfile); if (p != NULL) { - prt_ps_fd = mch_fopen((char *)p, WRITEBIN); + prt_ps_fd = os_fopen((char *)p, WRITEBIN); xfree(p); } } @@ -2382,7 +2382,7 @@ static int prt_add_resource(struct prt_ps_resource_S *resource) char_u resource_buffer[512]; size_t bytes_read; - fd_resource = mch_fopen((char *)resource->filename, READBIN); + fd_resource = os_fopen((char *)resource->filename, READBIN); if (fd_resource == NULL) { EMSG2(_("E456: Can't open file \"%s\""), resource->filename); return FALSE; diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c index 3ba02be32d..f11880cb2b 100644 --- a/src/nvim/highlight.c +++ b/src/nvim/highlight.c @@ -7,6 +7,7 @@ #include "nvim/highlight.h" #include "nvim/highlight_defs.h" #include "nvim/map.h" +#include "nvim/popupmnu.h" #include "nvim/screen.h" #include "nvim/syntax.h" #include "nvim/ui.h" @@ -105,14 +106,19 @@ static int get_attr_entry(HlEntry entry) /// When a UI connects, we need to send it the table of highlights used so far. void ui_send_all_hls(UI *ui) { - if (!ui->hl_attr_define) { - return; + if (ui->hl_attr_define) { + for (size_t i = 1; i < kv_size(attr_entries); i++) { + Array inspect = hl_inspect((int)i); + ui->hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr, + kv_A(attr_entries, i).attr, inspect); + api_free_array(inspect); + } } - for (size_t i = 1; i < kv_size(attr_entries); i++) { - Array inspect = hl_inspect((int)i); - ui->hl_attr_define(ui, (Integer)i, kv_A(attr_entries, i).attr, - kv_A(attr_entries, i).attr, inspect); - api_free_array(inspect); + if (ui->hl_group_set) { + for (size_t hlf = 0; hlf < HLF_COUNT; hlf++) { + ui->hl_group_set(ui, cstr_as_string((char *)hlf_names[hlf]), + highlight_attr[hlf]); + } } } @@ -140,11 +146,23 @@ int hl_get_ui_attr(int idx, int final_id, bool optional) HlAttrs attrs = HLATTRS_INIT; bool available = false; - int syn_attr = syn_id2attr(final_id); - if (syn_attr != 0) { - attrs = syn_attr2entry(syn_attr); - available = true; + if (final_id > 0) { + int syn_attr = syn_id2attr(final_id); + if (syn_attr != 0) { + attrs = syn_attr2entry(syn_attr); + available = true; + } + } + + if (HLF_PNI <= idx && idx <= HLF_PST) { + if (attrs.hl_blend == -1 && p_pb > 0) { + attrs.hl_blend = (int)p_pb; + } + if (pum_drawn()) { + must_redraw_pum = true; + } } + if (optional && !available) { return 0; } @@ -159,28 +177,42 @@ void update_window_hl(win_T *wp, bool invalid) } wp->w_hl_needs_update = false; + // If a floating window is blending it always have a named + // wp->w_hl_attr_normal group. HL_ATTR(HLF_NFLOAT) is always named. + bool has_blend = wp->w_floating && wp->w_p_winbl != 0; + // determine window specific background set in 'winhighlight' bool float_win = wp->w_floating && !wp->w_float_config.external; - if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] > 0) { + if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] != 0) { wp->w_hl_attr_normal = hl_get_ui_attr(HLF_INACTIVE, - wp->w_hl_ids[HLF_INACTIVE], true); - } else if (float_win && wp->w_hl_ids[HLF_NFLOAT] > 0) { + wp->w_hl_ids[HLF_INACTIVE], + !has_blend); + } else if (float_win && wp->w_hl_ids[HLF_NFLOAT] != 0) { wp->w_hl_attr_normal = hl_get_ui_attr(HLF_NFLOAT, - wp->w_hl_ids[HLF_NFLOAT], true); - } else if (wp->w_hl_id_normal > 0) { - wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, true); + wp->w_hl_ids[HLF_NFLOAT], !has_blend); + } else if (wp->w_hl_id_normal != 0) { + wp->w_hl_attr_normal = hl_get_ui_attr(-1, wp->w_hl_id_normal, !has_blend); } else { wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0; } - if (wp != curwin) { + // if blend= attribute is not set, 'winblend' value overrides it. + if (wp->w_floating && wp->w_p_winbl > 0) { + HlEntry entry = kv_A(attr_entries, wp->w_hl_attr_normal); + if (entry.attr.hl_blend == -1) { + entry.attr.hl_blend = (int)wp->w_p_winbl; + wp->w_hl_attr_normal = get_attr_entry(entry); + } + } + + if (wp != curwin && wp->w_hl_ids[HLF_INACTIVE] == 0) { wp->w_hl_attr_normal = hl_combine_attr(HL_ATTR(HLF_INACTIVE), wp->w_hl_attr_normal); } for (int hlf = 0; hlf < (int)HLF_COUNT; hlf++) { int attr; - if (wp->w_hl_ids[hlf] > 0) { + if (wp->w_hl_ids[hlf] != 0) { attr = hl_get_ui_attr(hlf, wp->w_hl_ids[hlf], false); } else { attr = HL_ATTR(hlf); @@ -224,6 +256,7 @@ void clear_hl_tables(bool reinit) map_clear(int, int)(combine_attr_entries); map_clear(int, int)(blend_attr_entries); map_clear(int, int)(blendthrough_attr_entries); + memset(highlight_attr_last, -1, sizeof(highlight_attr_last)); highlight_attr_set_all(); highlight_changed(); screen_invalidate_highlights(); @@ -240,6 +273,8 @@ void hl_invalidate_blends(void) { map_clear(int, int)(blend_attr_entries); map_clear(int, int)(blendthrough_attr_entries); + highlight_changed(); + update_window_hl(curwin, true); } // Combine special attributes (e.g., for spelling) with other attributes @@ -292,6 +327,10 @@ int hl_combine_attr(int char_attr, int prim_attr) new_en.rgb_sp_color = spell_aep.rgb_sp_color; } + if (spell_aep.hl_blend >= 0) { + new_en.hl_blend = spell_aep.hl_blend; + } + id = get_attr_entry((HlEntry){ .attr = new_en, .kind = kHlCombine, .id1 = char_attr, .id2 = prim_attr }); if (id > 0) { @@ -336,50 +375,59 @@ static HlAttrs get_colors_force(int attr) /// This is called per-cell, so cache the result. /// /// @return the resulting attributes. -int hl_blend_attrs(int back_attr, int front_attr, bool through) +int hl_blend_attrs(int back_attr, int front_attr, bool *through) { + HlAttrs fattrs = get_colors_force(front_attr); + int ratio = fattrs.hl_blend; + if (ratio <= 0) { + *through = false; + return front_attr; + } + int combine_tag = (back_attr << 16) + front_attr; - Map(int, int) *map = through ? blendthrough_attr_entries : blend_attr_entries; + Map(int, int) *map = (*through + ? blendthrough_attr_entries + : blend_attr_entries); int id = map_get(int, int)(map, combine_tag); if (id > 0) { return id; } HlAttrs battrs = get_colors_force(back_attr); - HlAttrs fattrs = get_colors_force(front_attr); HlAttrs cattrs; - if (through) { + + if (*through) { cattrs = battrs; - cattrs.rgb_fg_color = rgb_blend((int)p_pb, battrs.rgb_fg_color, + cattrs.rgb_fg_color = rgb_blend(ratio, battrs.rgb_fg_color, fattrs.rgb_bg_color); if (cattrs.rgb_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)) { - cattrs.rgb_sp_color = rgb_blend((int)p_pb, battrs.rgb_sp_color, + cattrs.rgb_sp_color = rgb_blend(ratio, battrs.rgb_sp_color, fattrs.rgb_bg_color); } else { cattrs.rgb_sp_color = -1; } cattrs.cterm_bg_color = fattrs.cterm_bg_color; - cattrs.cterm_fg_color = cterm_blend((int)p_pb, battrs.cterm_fg_color, + cattrs.cterm_fg_color = cterm_blend(ratio, battrs.cterm_fg_color, fattrs.cterm_bg_color); } else { cattrs = fattrs; - if (p_pb >= 50) { + if (ratio >= 50) { cattrs.rgb_ae_attr |= battrs.rgb_ae_attr; } - cattrs.rgb_fg_color = rgb_blend((int)p_pb/2, battrs.rgb_fg_color, + cattrs.rgb_fg_color = rgb_blend(ratio/2, battrs.rgb_fg_color, fattrs.rgb_fg_color); if (cattrs.rgb_ae_attr & (HL_UNDERLINE|HL_UNDERCURL)) { - cattrs.rgb_sp_color = rgb_blend((int)p_pb/2, battrs.rgb_bg_color, + cattrs.rgb_sp_color = rgb_blend(ratio/2, battrs.rgb_bg_color, fattrs.rgb_sp_color); } else { cattrs.rgb_sp_color = -1; } } - cattrs.rgb_bg_color = rgb_blend((int)p_pb, battrs.rgb_bg_color, + cattrs.rgb_bg_color = rgb_blend(ratio, battrs.rgb_bg_color, fattrs.rgb_bg_color); - HlKind kind = through ? kHlBlendThrough : kHlBlend; + HlKind kind = *through ? kHlBlendThrough : kHlBlend; id = get_attr_entry((HlEntry){ .attr = cattrs, .kind = kind, .id1 = back_attr, .id2 = front_attr }); if (id > 0) { diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 746d2c2dfc..afccf9e6f6 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -25,6 +25,7 @@ typedef struct attr_entry { int16_t rgb_ae_attr, cterm_ae_attr; ///< HlAttrFlags RgbValue rgb_fg_color, rgb_bg_color, rgb_sp_color; int cterm_fg_color, cterm_bg_color; + int hl_blend; } HlAttrs; #define HLATTRS_INIT (HlAttrs) { \ @@ -35,6 +36,7 @@ typedef struct attr_entry { .rgb_sp_color = -1, \ .cterm_fg_color = 0, \ .cterm_bg_color = 0, \ + .hl_blend = -1, \ } /// Values for index in highlight_attr[]. @@ -148,6 +150,7 @@ EXTERN const char *hlf_names[] INIT(= { EXTERN int highlight_attr[HLF_COUNT]; // Highl. attr for each context. +EXTERN int highlight_attr_last[HLF_COUNT]; // copy for detecting changed groups EXTERN int highlight_user[9]; // User[1-9] attributes EXTERN int highlight_stlnc[9]; // On top of user EXTERN int cterm_normal_fg_color INIT(= 0); diff --git a/src/nvim/iconv.h b/src/nvim/iconv.h index d7234090c4..a7c9ad4040 100644 --- a/src/nvim/iconv.h +++ b/src/nvim/iconv.h @@ -1,52 +1,20 @@ #ifndef NVIM_ICONV_H #define NVIM_ICONV_H -// iconv can be linked at compile-time as well as loaded at runtime. In the -// latter case, some function pointers need to be initialized after loading -// the library (see `iconv_enabled()` in mbyte.c). These function pointers -// are stored in globals.h. Since globals.h includes iconv.h to get the -// definition of USE_ICONV, we can't include it from iconv.h. One way to -// solve this conundrum would be perhaps to let cmake decide the value of -// USE_ICONV, or to put the USE_ICONV definition in config.h.in directly. As -// it stands, globals.h needs to be included alongside iconv.h. - #include "auto/config.h" -// Use iconv() when it's available, either by linking to the library at -// compile time or by loading it at runtime. -#if (defined(HAVE_ICONV_H) && defined(HAVE_ICONV)) || defined(DYNAMIC_ICONV) -# define USE_ICONV -#endif - -// If we don't have the actual iconv header files present but USE_ICONV was -// defined, we provide a type shim (pull in errno.h and define iconv_t). -// This enables us to still load and use iconv dynamically at runtime. -#ifdef USE_ICONV +#ifdef HAVE_ICONV # include <errno.h> -# ifdef HAVE_ICONV_H -# include <iconv.h> -# else -typedef void *iconv_t; -# endif -#endif +# include <iconv.h> // define some missing constants if necessary -# ifdef USE_ICONV # ifndef EILSEQ # define EILSEQ 123 # endif -# ifdef DYNAMIC_ICONV -// on win32 iconv.dll is dynamically loaded -# define ICONV_ERRNO (*iconv_errno()) -# define ICONV_E2BIG 7 -# define ICONV_EINVAL 22 -# define ICONV_EILSEQ 42 -# else -# define ICONV_ERRNO errno -# define ICONV_E2BIG E2BIG -# define ICONV_EINVAL EINVAL -# define ICONV_EILSEQ EILSEQ -# endif -# endif +# define ICONV_ERRNO errno +# define ICONV_E2BIG E2BIG +# define ICONV_EINVAL EINVAL +# define ICONV_EILSEQ EILSEQ +#endif #endif // NVIM_ICONV_H diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index a3eabed8a0..0f9984ec38 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -1008,10 +1008,10 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, qf_info_T *qi = NULL; win_T *wp = NULL; - f = mch_fopen((char *)tmp, "w"); - if (f == NULL) + f = os_fopen((char *)tmp, "w"); + if (f == NULL) { EMSG2(_(e_notopen), tmp); - else { + } else { cs_file_results(f, nummatches); fclose(f); if (use_ll) /* Use location list */ @@ -1648,7 +1648,7 @@ static void cs_print_tags_priv(char **matches, char **cntxts, assert(buf_len >= 0); // Print the context only if it fits on the same line. - if (msg_col + buf_len >= (int)Columns) { + if (msg_col + buf_len >= Columns) { msg_putchar('\n'); } msg_advance(12); diff --git a/src/nvim/indent.c b/src/nvim/indent.c index 8e20aa5be4..efbfea33aa 100644 --- a/src/nvim/indent.c +++ b/src/nvim/indent.c @@ -7,6 +7,7 @@ #include "nvim/ascii.h" #include "nvim/assert.h" +#include "nvim/change.h" #include "nvim/indent.h" #include "nvim/eval.h" #include "nvim/charset.h" @@ -316,109 +317,6 @@ int set_indent(int size, int flags) } -// Copy the indent from ptr to the current line (and fill to size). -// Leaves the cursor on the first non-blank in the line. -// @return true if the line was changed. -int copy_indent(int size, char_u *src) -{ - char_u *p = NULL; - char_u *line = NULL; - char_u *s; - int todo; - int ind_len; - int line_len = 0; - int tab_pad; - int ind_done; - int round; - - // Round 1: compute the number of characters needed for the indent - // Round 2: copy the characters. - for (round = 1; round <= 2; ++round) { - todo = size; - ind_len = 0; - ind_done = 0; - s = src; - - // Count/copy the usable portion of the source line. - while (todo > 0 && ascii_iswhite(*s)) { - if (*s == TAB) { - tab_pad = (int)curbuf->b_p_ts - - (ind_done % (int)curbuf->b_p_ts); - - // Stop if this tab will overshoot the target. - if (todo < tab_pad) { - break; - } - todo -= tab_pad; - ind_done += tab_pad; - } else { - todo--; - ind_done++; - } - ind_len++; - - if (p != NULL) { - *p++ = *s; - } - s++; - } - - // Fill to next tabstop with a tab, if possible. - tab_pad = (int)curbuf->b_p_ts - (ind_done % (int)curbuf->b_p_ts); - - if ((todo >= tab_pad) && !curbuf->b_p_et) { - todo -= tab_pad; - ind_len++; - - if (p != NULL) { - *p++ = TAB; - } - } - - // Add tabs required for indent. - while (todo >= (int)curbuf->b_p_ts && !curbuf->b_p_et) { - todo -= (int)curbuf->b_p_ts; - ind_len++; - - if (p != NULL) { - *p++ = TAB; - } - } - - // Count/add spaces required for indent. - while (todo > 0) { - todo--; - ind_len++; - - if (p != NULL) { - *p++ = ' '; - } - } - - if (p == NULL) { - // Allocate memory for the result: the copied indent, new indent - // and the rest of the line. - line_len = (int)STRLEN(get_cursor_line_ptr()) + 1; - assert(ind_len + line_len >= 0); - size_t line_size; - STRICT_ADD(ind_len, line_len, &line_size, size_t); - line = xmalloc(line_size); - p = line; - } - } - - // Append the original line - memmove(p, get_cursor_line_ptr(), (size_t)line_len); - - // Replace the line - ml_replace(curwin->w_cursor.lnum, line, false); - - // Put the cursor after the indent. - curwin->w_cursor.col = ind_len; - return true; -} - - // Return the indent of the current line after a number. Return -1 if no // number was found. Used for 'n' in 'formatoptions': numbered list. // Since a pattern is used it can actually handle more than numbers. diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index 3729e42b99..9665655e74 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -615,7 +615,7 @@ static inline void nlua_create_typed_table(lua_State *lstate, /// Convert given String to lua string /// /// Leaves converted string on top of the stack. -void nlua_push_String(lua_State *lstate, const String s) +void nlua_push_String(lua_State *lstate, const String s, bool special) FUNC_ATTR_NONNULL_ALL { lua_pushlstring(lstate, s.data, s.size); @@ -624,7 +624,7 @@ void nlua_push_String(lua_State *lstate, const String s) /// Convert given Integer to lua number /// /// Leaves converted number on top of the stack. -void nlua_push_Integer(lua_State *lstate, const Integer n) +void nlua_push_Integer(lua_State *lstate, const Integer n, bool special) FUNC_ATTR_NONNULL_ALL { lua_pushnumber(lstate, (lua_Number)n); @@ -633,19 +633,23 @@ void nlua_push_Integer(lua_State *lstate, const Integer n) /// Convert given Float to lua table /// /// Leaves converted table on top of the stack. -void nlua_push_Float(lua_State *lstate, const Float f) +void nlua_push_Float(lua_State *lstate, const Float f, bool special) FUNC_ATTR_NONNULL_ALL { - nlua_create_typed_table(lstate, 0, 1, kObjectTypeFloat); - nlua_push_val_idx(lstate); - lua_pushnumber(lstate, (lua_Number)f); - lua_rawset(lstate, -3); + if (special) { + nlua_create_typed_table(lstate, 0, 1, kObjectTypeFloat); + nlua_push_val_idx(lstate); + lua_pushnumber(lstate, (lua_Number)f); + lua_rawset(lstate, -3); + } else { + lua_pushnumber(lstate, (lua_Number)f); + } } /// Convert given Float to lua boolean /// /// Leaves converted value on top of the stack. -void nlua_push_Boolean(lua_State *lstate, const Boolean b) +void nlua_push_Boolean(lua_State *lstate, const Boolean b, bool special) FUNC_ATTR_NONNULL_ALL { lua_pushboolean(lstate, b); @@ -654,17 +658,18 @@ void nlua_push_Boolean(lua_State *lstate, const Boolean b) /// Convert given Dictionary to lua table /// /// Leaves converted table on top of the stack. -void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict) +void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict, + bool special) FUNC_ATTR_NONNULL_ALL { - if (dict.size == 0) { + if (dict.size == 0 && special) { nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); } else { lua_createtable(lstate, 0, (int)dict.size); } for (size_t i = 0; i < dict.size; i++) { - nlua_push_String(lstate, dict.items[i].key); - nlua_push_Object(lstate, dict.items[i].value); + nlua_push_String(lstate, dict.items[i].key, special); + nlua_push_Object(lstate, dict.items[i].value, special); lua_rawset(lstate, -3); } } @@ -672,18 +677,18 @@ void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict) /// Convert given Array to lua table /// /// Leaves converted table on top of the stack. -void nlua_push_Array(lua_State *lstate, const Array array) +void nlua_push_Array(lua_State *lstate, const Array array, bool special) FUNC_ATTR_NONNULL_ALL { lua_createtable(lstate, (int)array.size, 0); for (size_t i = 0; i < array.size; i++) { - nlua_push_Object(lstate, array.items[i]); + nlua_push_Object(lstate, array.items[i], special); lua_rawseti(lstate, -2, (int)i + 1); } } #define GENERATE_INDEX_FUNCTION(type) \ -void nlua_push_##type(lua_State *lstate, const type item) \ +void nlua_push_##type(lua_State *lstate, const type item, bool special) \ FUNC_ATTR_NONNULL_ALL \ { \ lua_pushnumber(lstate, (lua_Number)(item)); \ @@ -698,7 +703,7 @@ GENERATE_INDEX_FUNCTION(Tabpage) /// Convert given Object to lua value /// /// Leaves converted value on top of the stack. -void nlua_push_Object(lua_State *lstate, const Object obj) +void nlua_push_Object(lua_State *lstate, const Object obj, bool special) FUNC_ATTR_NONNULL_ALL { switch (obj.type) { @@ -712,7 +717,7 @@ void nlua_push_Object(lua_State *lstate, const Object obj) } #define ADD_TYPE(type, data_key) \ case kObjectType##type: { \ - nlua_push_##type(lstate, obj.data.data_key); \ + nlua_push_##type(lstate, obj.data.data_key, special); \ break; \ } ADD_TYPE(Boolean, boolean) @@ -724,7 +729,7 @@ void nlua_push_Object(lua_State *lstate, const Object obj) #undef ADD_TYPE #define ADD_REMOTE_TYPE(type) \ case kObjectType##type: { \ - nlua_push_##type(lstate, (type)obj.data.integer); \ + nlua_push_##type(lstate, (type)obj.data.integer, special); \ break; \ } ADD_REMOTE_TYPE(Buffer) diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index df08a9dd87..3de1b531e6 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -23,6 +23,7 @@ #include "nvim/cursor.h" #include "nvim/undo.h" #include "nvim/ascii.h" +#include "nvim/change.h" #ifdef WIN32 #include "nvim/os/os.h" @@ -31,6 +32,10 @@ #include "nvim/lua/executor.h" #include "nvim/lua/converter.h" +#include "luv/luv.h" + +static int in_fast_callback = 0; + typedef struct { Error err; String lua_err_str; @@ -108,6 +113,109 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL return 1; } +/// convert byte index to UTF-32 and UTF-16 indicies +/// +/// Expects a string and an optional index. If no index is supplied, the length +/// of the string is returned. +/// +/// Returns two values: the UTF-32 and UTF-16 indicies. +static int nlua_str_utfindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +{ + size_t s1_len; + const char *s1 = luaL_checklstring(lstate, 1, &s1_len); + intptr_t idx; + if (lua_gettop(lstate) >= 2) { + idx = luaL_checkinteger(lstate, 2); + if (idx < 0 || idx > (intptr_t)s1_len) { + return luaL_error(lstate, "index out of range"); + } + } else { + idx = (intptr_t)s1_len; + } + + size_t codepoints = 0, codeunits = 0; + mb_utflen((const char_u *)s1, (size_t)idx, &codepoints, &codeunits); + + lua_pushinteger(lstate, (long)codepoints); + lua_pushinteger(lstate, (long)codeunits); + + return 2; +} + +/// convert UTF-32 or UTF-16 indicies to byte index. +/// +/// Expects up to three args: string, index and use_utf16. +/// If use_utf16 is not supplied it defaults to false (use UTF-32) +/// +/// Returns the byte index. +static int nlua_str_byteindex(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL +{ + size_t s1_len; + const char *s1 = luaL_checklstring(lstate, 1, &s1_len); + intptr_t idx = luaL_checkinteger(lstate, 2); + if (idx < 0) { + return luaL_error(lstate, "index out of range"); + } + bool use_utf16 = false; + if (lua_gettop(lstate) >= 3) { + use_utf16 = lua_toboolean(lstate, 3); + } + + ssize_t byteidx = mb_utf_index_to_bytes((const char_u *)s1, s1_len, + (size_t)idx, use_utf16); + if (byteidx == -1) { + return luaL_error(lstate, "index out of range"); + } + + lua_pushinteger(lstate, (long)byteidx); + + return 1; +} + +static void nlua_luv_error_event(void **argv) +{ + char *error = (char *)argv[0]; + msg_ext_set_kind("lua_error"); + emsgf_multiline("Error executing luv callback:\n%s", error); + xfree(error); +} + +static int nlua_luv_cfpcall(lua_State *lstate, int nargs, int nresult, + int flags) + FUNC_ATTR_NONNULL_ALL +{ + int retval; + + // luv callbacks might be executed at any os_breakcheck/line_breakcheck + // call, so using the API directly here is not safe. + in_fast_callback++; + + int top = lua_gettop(lstate); + int status = lua_pcall(lstate, nargs, nresult, 0); + if (status) { + if (status == LUA_ERRMEM && !(flags & LUVF_CALLBACK_NOEXIT)) { + // consider out of memory errors unrecoverable, just like xmalloc() + mch_errmsg(e_outofmem); + mch_errmsg("\n"); + preserve_exit(); + } + const char *error = lua_tostring(lstate, -1); + + multiqueue_put(main_loop.events, nlua_luv_error_event, + 1, xstrdup(error)); + lua_pop(lstate, 1); // error mesage + retval = -status; + } else { // LUA_OK + if (nresult == LUA_MULTRET) { + nresult = lua_gettop(lstate) - top + nargs + 1; + } + retval = nresult; + } + + in_fast_callback--; + return retval; +} + static void nlua_schedule_event(void **argv) { LuaRef cb = (LuaRef)(ptrdiff_t)argv[0]; @@ -172,9 +280,33 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL // stricmp lua_pushcfunction(lstate, &nlua_stricmp); lua_setfield(lstate, -2, "stricmp"); + // str_utfindex + lua_pushcfunction(lstate, &nlua_str_utfindex); + lua_setfield(lstate, -2, "str_utfindex"); + // str_byteindex + lua_pushcfunction(lstate, &nlua_str_byteindex); + lua_setfield(lstate, -2, "str_byteindex"); // schedule lua_pushcfunction(lstate, &nlua_schedule); lua_setfield(lstate, -2, "schedule"); + // in_fast_event + lua_pushcfunction(lstate, &nlua_in_fast_event); + lua_setfield(lstate, -2, "in_fast_event"); + + // vim.loop + luv_set_loop(lstate, &main_loop.uv); + luv_set_callback(lstate, nlua_luv_cfpcall); + luaopen_luv(lstate); + lua_pushvalue(lstate, -1); + lua_setfield(lstate, -3, "loop"); + + // package.loaded.luv = vim.loop + // otherwise luv will be reinitialized when require'luv' + lua_getglobal(lstate, "package"); + lua_getfield(lstate, -1, "loaded"); + lua_pushvalue(lstate, -3); + lua_setfield(lstate, -2, "luv"); + lua_pop(lstate, 3); lua_setglobal(lstate, "vim"); return 0; @@ -260,6 +392,42 @@ void executor_exec_lua(const String str, typval_T *const ret_tv) nlua_pop_typval(lstate, ret_tv); } +static void nlua_print_event(void **argv) +{ + char *str = argv[0]; + const size_t len = (size_t)(intptr_t)argv[1]-1; // exclude final NUL + + for (size_t i = 0; i < len;) { + const size_t start = i; + while (i < len) { + switch (str[i]) { + case NUL: { + str[i] = NL; + i++; + continue; + } + case NL: { + // TODO(bfredl): use proper multiline msg? Probably should implement + // print() in lua in terms of nvim_message(), when it is available. + str[i] = NUL; + i++; + break; + } + default: { + i++; + continue; + } + } + break; + } + msg((char_u *)str + start); + } + if (len && str[len - 1] == NUL) { // Last was newline + msg((char_u *)""); + } + xfree(str); +} + /// Print as a Vim message /// /// @param lstate Lua interpreter state. @@ -299,47 +467,24 @@ static int nlua_print(lua_State *const lstate) lua_pop(lstate, 1); } #undef PRINT_ERROR - lua_pop(lstate, nargs + 1); ga_append(&msg_ga, NUL); - { - const size_t len = (size_t)msg_ga.ga_len - 1; - char *const str = (char *)msg_ga.ga_data; - - for (size_t i = 0; i < len;) { - const size_t start = i; - while (i < len) { - switch (str[i]) { - case NUL: { - str[i] = NL; - i++; - continue; - } - case NL: { - str[i] = NUL; - i++; - break; - } - default: { - i++; - continue; - } - } - break; - } - msg((char_u *)str + start); - } - if (len && str[len - 1] == NUL) { // Last was newline - msg((char_u *)""); - } + + if (in_fast_callback) { + multiqueue_put(main_loop.events, nlua_print_event, + 2, msg_ga.ga_data, msg_ga.ga_len); + } else { + nlua_print_event((void *[]){ msg_ga.ga_data, + (void *)(intptr_t)msg_ga.ga_len }); } - ga_clear(&msg_ga); return 0; + nlua_print_error: - emsgf(_("E5114: Error while converting print argument #%i: %.*s"), - curargidx, (int)errmsg_len, errmsg); ga_clear(&msg_ga); - lua_pop(lstate, lua_gettop(lstate)); - return 0; + const char *fmt = _("E5114: Error while converting print argument #%i: %.*s"); + size_t len = (size_t)vim_snprintf((char *)IObuff, IOSIZE, fmt, curargidx, + (int)errmsg_len, errmsg); + lua_pushlstring(lstate, (char *)IObuff, len); + return lua_error(lstate); } /// debug.debug: interaction with user while debugging. @@ -373,15 +518,20 @@ int nlua_debug(lua_State *lstate) if (luaL_loadbuffer(lstate, (const char *)input.vval.v_string, STRLEN(input.vval.v_string), "=(debug command)")) { nlua_error(lstate, _("E5115: Error while loading debug string: %.*s")); - } - tv_clear(&input); - if (lua_pcall(lstate, 0, 0, 0)) { + } else if (lua_pcall(lstate, 0, 0, 0)) { nlua_error(lstate, _("E5116: Error while calling debug string: %.*s")); } + tv_clear(&input); } return 0; } +int nlua_in_fast_event(lua_State *lstate) +{ + lua_pushboolean(lstate, in_fast_callback > 0); + return 1; +} + #ifdef WIN32 /// os.getenv: override os.getenv to maintain coherency. #9681 /// @@ -499,7 +649,7 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err) } for (size_t i = 0; i < args.size; i++) { - nlua_push_Object(lstate, args.items[i]); + nlua_push_Object(lstate, args.items[i], false); } if (lua_pcall(lstate, (int)args.size, 1, 0)) { @@ -513,16 +663,17 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err) return nlua_pop_Object(lstate, false, err); } -Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args) +Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args, + bool retval) { lua_State *const lstate = nlua_enter(); nlua_pushref(lstate, ref); lua_pushstring(lstate, name); for (size_t i = 0; i < args.size; i++) { - nlua_push_Object(lstate, args.items[i]); + nlua_push_Object(lstate, args.items[i], false); } - if (lua_pcall(lstate, (int)args.size+1, 1, 0)) { + if (lua_pcall(lstate, (int)args.size+1, retval ? 1 : 0, 0)) { // TODO(bfredl): callbacks:s might not always be msg-safe, for instance // lua callbacks for redraw events. Later on let the caller deal with the // error instead. @@ -531,7 +682,18 @@ Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args) } Error err = ERROR_INIT; - return nlua_pop_Object(lstate, false, &err); + if (retval) { + return nlua_pop_Object(lstate, false, &err); + } else { + return NIL; + } +} + +/// check if the current execution context is safe for calling deferred API +/// methods. Luv callbacks are unsafe as they are called inside the uv loop. +bool nlua_is_deferred_safe(lua_State *lstate) +{ + return in_fast_callback == 0; } /// Run lua string diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index 848bccaae6..46c96b455f 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -172,11 +172,22 @@ local function __index(t, key) end end +--- Defers the wrapped callback until when the nvim API is safe to call. +--- +--- See |vim-loop-callbacks| +local function schedule_wrap(cb) + return (function (...) + local args = {...} + vim.schedule(function() cb(unpack(args)) end) + end) +end + local module = { _update_package_paths = _update_package_paths, _os_proc_children = _os_proc_children, _os_proc_info = _os_proc_info, _system = _system, + schedule_wrap = schedule_wrap, } setmetatable(module, { diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 61009528a8..018985fad2 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -86,8 +86,6 @@ #define READBIN "rb" #define APPENDBIN "ab" -# define mch_fopen(n, p) fopen((n), (p)) - /* mch_open_rw(): invoke os_open() with third argument for user R/W. */ #if defined(UNIX) /* open in rw------- mode */ # define mch_open_rw(n, f) os_open((n), (f), (mode_t)0600) @@ -129,7 +127,11 @@ # define MB_CHAR2LEN(c) mb_char2len(c) # define PTR2CHAR(p) utf_ptr2char(p) -# define RESET_BINDING(wp) (wp)->w_p_scb = FALSE; (wp)->w_p_crb = FALSE +# define RESET_BINDING(wp) \ + do { \ + (wp)->w_p_scb = false; \ + (wp)->w_p_crb = false; \ + } while (0) /// Calculate the length of a C array /// @@ -165,7 +167,8 @@ # define NVIM_HAS_ATTRIBUTE __has_attribute #endif -#if NVIM_HAS_ATTRIBUTE(fallthrough) +#if NVIM_HAS_ATTRIBUTE(fallthrough) \ + && (!defined(__apple_build_version__) || __apple_build_version__ >= 7000000) # define FALLTHROUGH __attribute__((fallthrough)) #else # define FALLTHROUGH diff --git a/src/nvim/main.c b/src/nvim/main.c index 28ef8e04ea..5aa7ed42d3 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -54,6 +54,7 @@ #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" +#include "nvim/ui_compositor.h" #include "nvim/version.h" #include "nvim/window.h" #include "nvim/shada.h" @@ -141,15 +142,12 @@ static const char *err_extra_cmd = void event_init(void) { + log_init(); loop_init(&main_loop, NULL); // early msgpack-rpc initialization msgpack_rpc_init_method_table(); msgpack_rpc_helpers_init(); - // Initialize input events input_init(); - // Timer to wake the event loop if a timeout argument is passed to - // `event_poll` - // Signals signal_init(); // finish mspgack-rpc initialization channel_init(); @@ -185,7 +183,6 @@ bool event_teardown(void) /// Needed for unit tests. Must be called after `time_init()`. void early_init(void) { - log_init(); env_init(); fs_init(); handle_init(); @@ -222,6 +219,9 @@ void early_init(void) TIME_MSG("inits 1"); set_lang_var(); // set v:lang and v:ctype + + init_signs(); + ui_comp_syn_init(); } #ifdef MAKE_LIB @@ -257,12 +257,13 @@ int main(int argc, char **argv) init_startuptime(¶ms); + event_init(); + early_init(); // Check if we have an interactive window. check_and_set_isatty(¶ms); - event_init(); // Process the command line arguments. File names are put in the global // argument list "global_alist". command_line_scan(¶ms); @@ -343,10 +344,8 @@ int main(int argc, char **argv) p_lpl = false; } - // give embedders a chance to set up nvim, by processing a request before - // startup. This allows an external UI to show messages and prompts from - // --cmd and buffer loading (e.g. swap files) - bool early_ui = false; + // Wait for UIs to set up Nvim or show early messages + // and prompts (--cmd, swapfile dialog, …). bool use_remote_ui = (embedded_mode && !headless_mode); bool use_builtin_ui = (!headless_mode && !embedded_mode && !silent_mode); if (use_remote_ui || use_builtin_ui) { @@ -361,7 +360,6 @@ int main(int argc, char **argv) // prepare screen now, so external UIs can display messages starting = NO_BUFFERS; screenclear(); - early_ui = true; TIME_MSG("initialized screen early for UI"); } @@ -458,7 +456,7 @@ int main(int argc, char **argv) setmouse(); // may start using the mouse - if (exmode_active || early_ui) { + if (exmode_active || use_remote_ui || use_builtin_ui) { // Don't clear the screen when starting in Ex mode, or when a UI might have // displayed messages. redraw_later(VALID); @@ -581,9 +579,7 @@ int main(int argc, char **argv) void getout(int exitval) FUNC_ATTR_NORETURN { - tabpage_T *tp, *next_tp; - - exiting = TRUE; + exiting = true; /* When running in Ex mode an error causes us to exit with a non-zero exit * code. POSIX requires this, although it's not 100% clear from the @@ -593,15 +589,17 @@ void getout(int exitval) set_vim_var_nr(VV_EXITING, exitval); - /* Position the cursor on the last screen line, below all the text */ - ui_cursor_goto((int)Rows - 1, 0); + // Position the cursor on the last screen line, below all the text + ui_cursor_goto(Rows - 1, 0); /* Optionally print hashtable efficiency. */ hash_debug_results(); if (get_vim_var_nr(VV_DYING) <= 1) { - /* Trigger BufWinLeave for all windows, but only once per buffer. */ - for (tp = first_tabpage; tp != NULL; tp = next_tp) { + const tabpage_T *next_tp; + + // Trigger BufWinLeave for all windows, but only once per buffer. + for (const tabpage_T *tp = first_tabpage; tp != NULL; tp = next_tp) { next_tp = tp->tp_next; FOR_ALL_WINDOWS_IN_TAB(wp, tp) { if (wp->w_buffer == NULL) { @@ -658,17 +656,14 @@ void getout(int exitval) wait_return(FALSE); } - /* Position the cursor again, the autocommands may have moved it */ - ui_cursor_goto((int)Rows - 1, 0); + // Position the cursor again, the autocommands may have moved it + ui_cursor_goto(Rows - 1, 0); // Apply 'titleold'. if (p_title && *p_titleold != NUL) { ui_call_set_title(cstr_as_string((char *)p_titleold)); } -#if defined(USE_ICONV) && defined(DYNAMIC_ICONV) - iconv_end(); -#endif cs_end(); if (garbage_collect_at_exit) { garbage_collect(false); @@ -1168,8 +1163,8 @@ scripterror: if (scriptout != NULL) { goto scripterror; } - if ((scriptout = mch_fopen(argv[0], - c == 'w' ? APPENDBIN : WRITEBIN)) == NULL) { + if ((scriptout = os_fopen(argv[0], c == 'w' ? APPENDBIN : WRITEBIN)) + == NULL) { mch_errmsg(_("Cannot open for script output: \"")); mch_errmsg(argv[0]); mch_errmsg("\"\n"); @@ -1257,8 +1252,9 @@ static void init_params(mparm_T *paramp, int argc, char **argv) static void init_startuptime(mparm_T *paramp) { for (int i = 1; i < paramp->argc; i++) { - if (STRICMP(paramp->argv[i], "--startuptime") == 0 && i + 1 < paramp->argc) { - time_fd = mch_fopen(paramp->argv[i + 1], "a"); + if (STRICMP(paramp->argv[i], "--startuptime") == 0 + && i + 1 < paramp->argc) { + time_fd = os_fopen(paramp->argv[i + 1], "a"); time_start("--- NVIM STARTING ---"); break; } @@ -1304,8 +1300,6 @@ static void init_path(const char *exename) // shipped with Windows package. This also mimics SearchPath(). os_setenv_append_path(exepath); #endif - - init_signs(); } /// Get filename from command line, if any. diff --git a/src/nvim/map.c b/src/nvim/map.c index 90da27cf9f..cdade5ee71 100644 --- a/src/nvim/map.c +++ b/src/nvim/map.c @@ -179,7 +179,7 @@ MAP_IMPL(cstr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(ptr_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(uint64_t, ptr_t, DEFAULT_INITIALIZER) MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER) -#define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .async = false } +#define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .fast = false } MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER) #define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL } MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER) diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 2f2f2a7d74..9f357575d0 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -656,48 +656,51 @@ show_one_mark( int c, char_u *arg, pos_T *p, - char_u *name, - int current /* in current file */ + char_u *name_arg, + int current // in current file ) { - static int did_title = FALSE; - int mustfree = FALSE; - - if (c == -1) { /* finish up */ - if (did_title) - did_title = FALSE; - else { - if (arg == NULL) + static bool did_title = false; + bool mustfree = false; + char_u *name = name_arg; + + if (c == -1) { // finish up + if (did_title) { + did_title = false; + } else { + if (arg == NULL) { MSG(_("No marks set")); - else + } else { EMSG2(_("E283: No marks matching \"%s\""), arg); + } } - } - /* don't output anything if 'q' typed at --more-- prompt */ - else if (!got_int - && (arg == NULL || vim_strchr(arg, c) != NULL) - && p->lnum != 0) { - if (!did_title) { - /* Highlight title */ - MSG_PUTS_TITLE(_("\nmark line col file/text")); - did_title = TRUE; + } else if (!got_int + && (arg == NULL || vim_strchr(arg, c) != NULL) + && p->lnum != 0) { + // don't output anything if 'q' typed at --more-- prompt + if (name == NULL && current) { + name = mark_line(p, 15); + mustfree = true; } - msg_putchar('\n'); - if (!got_int) { - sprintf((char *)IObuff, " %c %6ld %4d ", c, p->lnum, p->col); - msg_outtrans(IObuff); - if (name == NULL && current) { - name = mark_line(p, 15); - mustfree = TRUE; + if (!message_filtered(name)) { + if (!did_title) { + // Highlight title + msg_puts_title(_("\nmark line col file/text")); + did_title = true; } - if (name != NULL) { - msg_outtrans_attr(name, current ? HL_ATTR(HLF_D) : 0); - if (mustfree) { - xfree(name); + msg_putchar('\n'); + if (!got_int) { + snprintf((char *)IObuff, IOSIZE, " %c %6ld %4d ", c, p->lnum, p->col); + msg_outtrans(IObuff); + if (name != NULL) { + msg_outtrans_attr(name, current ? HL_ATTR(HLF_D) : 0); } } + ui_flush(); // show one line at a time + } + if (mustfree) { + xfree(name); } - ui_flush(); /* show one line at a time */ } } @@ -786,8 +789,12 @@ void ex_jumps(exarg_T *eap) for (i = 0; i < curwin->w_jumplistlen && !got_int; ++i) { if (curwin->w_jumplist[i].fmark.mark.lnum != 0) { name = fm_getname(&curwin->w_jumplist[i].fmark, 16); - if (name == NULL) /* file name not available */ + + // apply :filter /pat/ or file name not available + if (name == NULL || message_filtered(name)) { + xfree(name); continue; + } msg_putchar('\n'); if (got_int) { @@ -1206,8 +1213,8 @@ void cleanup_jumplist(win_T *wp, bool loadfiles) // When pointer is below last jump, remove the jump if it matches the current // line. This avoids useless/phantom jumps. #9805 - if (wp->w_jumplistlen - && wp->w_jumplistidx == wp->w_jumplistlen) { + if (loadfiles // otherwise (i.e.: Shada), last entry should be kept + && wp->w_jumplistlen && wp->w_jumplistidx == wp->w_jumplistlen) { const xfmark_T *fm_last = &wp->w_jumplist[wp->w_jumplistlen - 1]; if (fm_last->fmark.fnum == curbuf->b_fnum && fm_last->fmark.mark.lnum == wp->w_cursor.lnum) { diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index 683087bd7b..fae7635d34 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -456,43 +456,50 @@ static bool intable(const struct interval *table, size_t n_items, int c) return false; } -/* - * For UTF-8 character "c" return 2 for a double-width character, 1 for others. - * Returns 4 or 6 for an unprintable character. - * Is only correct for characters >= 0x80. - * When p_ambw is "double", return 2 for a character with East Asian Width - * class 'A'(mbiguous). - */ +/// For UTF-8 character "c" return 2 for a double-width character, 1 for others. +/// Returns 4 or 6 for an unprintable character. +/// Is only correct for characters >= 0x80. +/// When p_ambw is "double", return 2 for a character with East Asian Width +/// class 'A'(mbiguous). +/// +/// @note Tables `doublewidth` and `ambiguous` are generated by +/// gen_unicode_tables.lua, which must be manually invoked as needed. int utf_char2cells(int c) { if (c >= 0x100) { #ifdef USE_WCHAR_FUNCTIONS - /* - * Assume the library function wcwidth() works better than our own - * stuff. It should return 1 for ambiguous width chars! - */ + // + // Assume the library function wcwidth() works better than our own + // stuff. It should return 1 for ambiguous width chars! + // int n = wcwidth(c); - if (n < 0) - return 6; /* unprintable, displays <xxxx> */ - if (n > 1) + if (n < 0) { + return 6; // unprintable, displays <xxxx> + } + if (n > 1) { return n; + } #else - if (!utf_printable(c)) - return 6; /* unprintable, displays <xxxx> */ - if (intable(doublewidth, ARRAY_SIZE(doublewidth), c)) + if (!utf_printable(c)) { + return 6; // unprintable, displays <xxxx> + } + if (intable(doublewidth, ARRAY_SIZE(doublewidth), c)) { return 2; + } #endif if (p_emoji && intable(emoji_width, ARRAY_SIZE(emoji_width), c)) { return 2; } + } else if (c >= 0x80 && !vim_isprintc(c)) { + // Characters below 0x100 are influenced by 'isprint' option. + return 4; // unprintable, displays <xx> } - /* Characters below 0x100 are influenced by 'isprint' option */ - else if (c >= 0x80 && !vim_isprintc(c)) - return 4; /* unprintable, displays <xx> */ - if (c >= 0x80 && *p_ambw == 'd' && intable(ambiguous, ARRAY_SIZE(ambiguous), c)) + if (c >= 0x80 && *p_ambw == 'd' + && intable(ambiguous, ARRAY_SIZE(ambiguous), c)) { return 2; + } return 1; } @@ -1431,6 +1438,64 @@ int utf16_to_utf8(const wchar_t *strw, char **str) #endif +/// Measure the length of a string in corresponding UTF-32 and UTF-16 units. +/// +/// Invalid UTF-8 bytes, or embedded surrogates, count as one code point/unit +/// each. +/// +/// The out parameters are incremented. This is used to measure the size of +/// a buffer region consisting of multiple line segments. +/// +/// @param s the string +/// @param len maximum length (an earlier NUL terminates) +/// @param[out] codepoints incremented with UTF-32 code point size +/// @param[out] codeunits incremented with UTF-16 code unit size +void mb_utflen(const char_u *s, size_t len, size_t *codepoints, + size_t *codeunits) + FUNC_ATTR_NONNULL_ALL +{ + size_t count = 0, extra = 0; + size_t clen; + for (size_t i = 0; i < len && s[i] != NUL; i += clen) { + clen = utf_ptr2len_len(s+i, len-i); + // NB: gets the byte value of invalid sequence bytes. + // we only care whether the char fits in the BMP or not + int c = (clen > 1) ? utf_ptr2char(s+i) : s[i]; + count++; + if (c > 0xFFFF) { + extra++; + } + } + *codepoints += count; + *codeunits += count + extra; +} + +ssize_t mb_utf_index_to_bytes(const char_u *s, size_t len, + size_t index, bool use_utf16_units) + FUNC_ATTR_NONNULL_ALL +{ + size_t count = 0; + size_t clen, i; + if (index == 0) { + return 0; + } + for (i = 0; i < len && s[i] != NUL; i += clen) { + clen = utf_ptr2len_len(s+i, len-i); + // NB: gets the byte value of invalid sequence bytes. + // we only care whether the char fits in the BMP or not + int c = (clen > 1) ? utf_ptr2char(s+i) : s[i]; + count++; + if (use_utf16_units && c > 0xFFFF) { + count++; + } + if (count >= index) { + return i+clen; + } + } + return -1; +} + + /* * Version of strnicmp() that handles multi-byte characters. * Needed for Big5, Shift-JIS and UTF-8 encoding. Other DBCS encodings can @@ -1997,7 +2062,7 @@ enc_locale_copy_enc: return enc_canonize((char_u *)buf); } -# if defined(USE_ICONV) +# if defined(HAVE_ICONV) /* @@ -2018,13 +2083,6 @@ void * my_iconv_open(char_u *to, char_u *from) if (iconv_working == kBroken) return (void *)-1; /* detected a broken iconv() previously */ -#ifdef DYNAMIC_ICONV - // Check if the iconv.dll can be found. - if (!iconv_enabled(true)) { - return (void *)-1; - } -#endif - fd = iconv_open((char *)enc_skip(to), (char *)enc_skip(from)); if (fd != (iconv_t)-1 && iconv_working == kUnknown) { @@ -2131,152 +2189,7 @@ static char_u *iconv_string(const vimconv_T *const vcp, char_u *str, return result; } -# if defined(DYNAMIC_ICONV) -// Dynamically load the "iconv.dll" on Win32. - -#ifndef DYNAMIC_ICONV // just generating prototypes -# define HINSTANCE int -#endif -static HINSTANCE hIconvDLL = 0; -static HINSTANCE hMsvcrtDLL = 0; - -# ifndef DYNAMIC_ICONV_DLL -# define DYNAMIC_ICONV_DLL "iconv.dll" -# define DYNAMIC_ICONV_DLL_ALT "libiconv-2.dll" -# endif -# ifndef DYNAMIC_MSVCRT_DLL -# define DYNAMIC_MSVCRT_DLL "msvcrt.dll" -# endif - -/* - * Get the address of 'funcname' which is imported by 'hInst' DLL. - */ -static void * get_iconv_import_func(HINSTANCE hInst, - const char *funcname) -{ - PBYTE pImage = (PBYTE)hInst; - PIMAGE_DOS_HEADER pDOS = (PIMAGE_DOS_HEADER)hInst; - PIMAGE_NT_HEADERS pPE; - PIMAGE_IMPORT_DESCRIPTOR pImpDesc; - PIMAGE_THUNK_DATA pIAT; /* Import Address Table */ - PIMAGE_THUNK_DATA pINT; /* Import Name Table */ - PIMAGE_IMPORT_BY_NAME pImpName; - - if (pDOS->e_magic != IMAGE_DOS_SIGNATURE) - return NULL; - pPE = (PIMAGE_NT_HEADERS)(pImage + pDOS->e_lfanew); - if (pPE->Signature != IMAGE_NT_SIGNATURE) - return NULL; - pImpDesc = (PIMAGE_IMPORT_DESCRIPTOR)(pImage - + pPE->OptionalHeader.DataDirectory[ - IMAGE_DIRECTORY_ENTRY_IMPORT] - .VirtualAddress); - for (; pImpDesc->FirstThunk; ++pImpDesc) { - if (!pImpDesc->OriginalFirstThunk) - continue; - pIAT = (PIMAGE_THUNK_DATA)(pImage + pImpDesc->FirstThunk); - pINT = (PIMAGE_THUNK_DATA)(pImage + pImpDesc->OriginalFirstThunk); - for (; pIAT->u1.Function; ++pIAT, ++pINT) { - if (IMAGE_SNAP_BY_ORDINAL(pINT->u1.Ordinal)) - continue; - pImpName = (PIMAGE_IMPORT_BY_NAME)(pImage - + (UINT_PTR)(pINT->u1.AddressOfData)); - if (strcmp(pImpName->Name, funcname) == 0) - return (void *)pIAT->u1.Function; - } - } - return NULL; -} - -// Load library "name". -HINSTANCE vimLoadLib(char *name) -{ - HINSTANCE dll = NULL; - - // NOTE: Do not use mch_dirname() and mch_chdir() here, they may call - // vimLoadLib() recursively, which causes a stack overflow. - wchar_t old_dirw[MAXPATHL]; - - // Path to exe dir. - char *buf = xstrdup((char *)get_vim_var_str(VV_PROGPATH)); - // ptrdiff_t len = ; - // assert(len > 0); - buf[path_tail_with_sep(buf) - buf] = '\0'; - - if (GetCurrentDirectoryW(MAXPATHL, old_dirw) != 0) { - // Change directory to where the executable is, both to make - // sure we find a .dll there and to avoid looking for a .dll - // in the current directory. - SetCurrentDirectory((LPCSTR)buf); - // TODO(justinmk): use uv_dlopen instead. see os_libcall - dll = LoadLibrary(name); - SetCurrentDirectoryW(old_dirw); - } - - return dll; -} - - -/* - * Try opening the iconv.dll and return TRUE if iconv() can be used. - */ -bool iconv_enabled(bool verbose) -{ - if (hIconvDLL != 0 && hMsvcrtDLL != 0) - return true; - hIconvDLL = vimLoadLib(DYNAMIC_ICONV_DLL); - if (hIconvDLL == 0) /* sometimes it's called libiconv.dll */ - hIconvDLL = vimLoadLib(DYNAMIC_ICONV_DLL_ALT); - if (hIconvDLL != 0) - hMsvcrtDLL = vimLoadLib(DYNAMIC_MSVCRT_DLL); - if (hIconvDLL == 0 || hMsvcrtDLL == 0) { - /* Only give the message when 'verbose' is set, otherwise it might be - * done whenever a conversion is attempted. */ - if (verbose && p_verbose > 0) { - verbose_enter(); - EMSG2(_(e_loadlib), - hIconvDLL == 0 ? DYNAMIC_ICONV_DLL : DYNAMIC_MSVCRT_DLL); - verbose_leave(); - } - iconv_end(); - return false; - } - - iconv = (void *)GetProcAddress(hIconvDLL, "libiconv"); - iconv_open = (void *)GetProcAddress(hIconvDLL, "libiconv_open"); - iconv_close = (void *)GetProcAddress(hIconvDLL, "libiconv_close"); - iconvctl = (void *)GetProcAddress(hIconvDLL, "libiconvctl"); - iconv_errno = get_iconv_import_func(hIconvDLL, "_errno"); - if (iconv_errno == NULL) - iconv_errno = (void *)GetProcAddress(hMsvcrtDLL, "_errno"); - if (iconv == NULL || iconv_open == NULL || iconv_close == NULL - || iconvctl == NULL || iconv_errno == NULL) { - iconv_end(); - if (verbose && p_verbose > 0) { - verbose_enter(); - EMSG2(_(e_loadfunc), "for libiconv"); - verbose_leave(); - } - return false; - } - return true; -} - -void iconv_end(void) -{ - if (hIconvDLL != 0) { - // TODO(justinmk): use uv_dlclose instead. - FreeLibrary(hIconvDLL); - } - if (hMsvcrtDLL != 0) { - FreeLibrary(hMsvcrtDLL); - } - hIconvDLL = 0; - hMsvcrtDLL = 0; -} - -# endif /* DYNAMIC_ICONV */ -# endif /* USE_ICONV */ +# endif // HAVE_ICONV @@ -2307,10 +2220,11 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8, int from_is_utf8; int to_is_utf8; - /* Reset to no conversion. */ -# ifdef USE_ICONV - if (vcp->vc_type == CONV_ICONV && vcp->vc_fd != (iconv_t)-1) + // Reset to no conversion. +# ifdef HAVE_ICONV + if (vcp->vc_type == CONV_ICONV && vcp->vc_fd != (iconv_t)-1) { iconv_close(vcp->vc_fd); + } # endif *vcp = (vimconv_T)MBYTE_NONE_CONV; @@ -2345,9 +2259,9 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8, /* Internal utf-8 -> latin9 conversion. */ vcp->vc_type = CONV_TO_LATIN9; } -# ifdef USE_ICONV - else { - /* Use iconv() for conversion. */ +# ifdef HAVE_ICONV + else { // NOLINT(readability/braces) + // Use iconv() for conversion. vcp->vc_fd = (iconv_t)my_iconv_open( to_is_utf8 ? (char_u *)"utf-8" : to, from_is_utf8 ? (char_u *)"utf-8" : from); @@ -2499,8 +2413,8 @@ char_u * string_convert_ext(const vimconv_T *const vcp, char_u *ptr, *lenp = (size_t)(d - retval); break; -# ifdef USE_ICONV - case CONV_ICONV: /* conversion with vcp->vc_fd */ +# ifdef HAVE_ICONV + case CONV_ICONV: // conversion with vcp->vc_fd retval = iconv_string(vcp, ptr, len, unconvlenp, lenp); break; # endif diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index ed48705c6d..536d58be1f 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -63,7 +63,7 @@ typedef enum { typedef struct { int vc_type; ///< Zero or more ConvFlags. int vc_factor; ///< Maximal expansion factor. -# ifdef USE_ICONV +# ifdef HAVE_ICONV iconv_t vc_fd; ///< Value for CONV_ICONV. # endif bool vc_fail; ///< What to do with invalid characters: if true, fail, diff --git a/src/nvim/memline.c b/src/nvim/memline.c index a69669f680..34774055c1 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -1,9 +1,9 @@ // 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 -/* for debugging */ -/* #define CHECK(c, s) if (c) EMSG(s) */ -#define CHECK(c, s) +// for debugging +// #define CHECK(c, s) do { if (c) EMSG(s); } while (0) +#define CHECK(c, s) do { } while (0) /* * memline.c: Contains the functions for appending, deleting and changing the @@ -47,6 +47,7 @@ #include "nvim/vim.h" #include "nvim/memline.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/cursor.h" #include "nvim/eval.h" #include "nvim/getchar.h" @@ -1178,7 +1179,7 @@ void ml_recover(void) /* Recovering an empty file results in two lines and the first line is * empty. Don't set the modified flag then. */ if (!(curbuf->b_ml.ml_line_count == 2 && *ml_get(1) == NUL)) { - changed_int(); + changed_internal(); buf_inc_changedtick(curbuf); } } else { @@ -1188,7 +1189,7 @@ void ml_recover(void) i = STRCMP(p, ml_get(idx + lnum)); xfree(p); if (i != 0) { - changed_int(); + changed_internal(); buf_inc_changedtick(curbuf); break; } @@ -2383,6 +2384,23 @@ static int ml_append_int( return OK; } +void ml_add_deleted_len(char_u *ptr, ssize_t len) +{ + if (inhibit_delete_count) { + return; + } + if (len == -1) { + len = STRLEN(ptr); + } + curbuf->deleted_bytes += len+1; + if (curbuf->update_need_codepoints) { + mb_utflen(ptr, len, &curbuf->deleted_codepoints, + &curbuf->deleted_codeunits); + curbuf->deleted_codepoints++; // NL char + curbuf->deleted_codeunits++; + } +} + /* * Replace line lnum, with buffering, in current buffer. * @@ -2403,13 +2421,24 @@ int ml_replace(linenr_T lnum, char_u *line, bool copy) if (curbuf->b_ml.ml_mfp == NULL && open_buffer(FALSE, NULL, 0) == FAIL) return FAIL; + bool readlen = true; + if (copy) { line = vim_strsave(line); } - if (curbuf->b_ml.ml_line_lnum != lnum) /* other line buffered */ - ml_flush_line(curbuf); /* flush it */ - else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) /* same line allocated */ - xfree(curbuf->b_ml.ml_line_ptr); /* free it */ + if (curbuf->b_ml.ml_line_lnum != lnum) { // other line buffered + ml_flush_line(curbuf); // flush it + } else if (curbuf->b_ml.ml_flags & ML_LINE_DIRTY) { // same line allocated + ml_add_deleted_len(curbuf->b_ml.ml_line_ptr, -1); + readlen = false; // already added the length + + xfree(curbuf->b_ml.ml_line_ptr); // free it + } + + if (readlen && kv_size(curbuf->update_callbacks)) { + ml_add_deleted_len(ml_get_buf(curbuf, lnum, false), -1); + } + curbuf->b_ml.ml_line_ptr = line; curbuf->b_ml.ml_line_lnum = lnum; curbuf->b_ml.ml_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY; @@ -2491,6 +2520,10 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, bool message) else line_size = ((dp->db_index[idx - 1]) & DB_INDEX_MASK) - line_start; + // Line should always have an NL char internally (represented as NUL), + // even if 'noeol' is set. + assert(line_size >= 1); + ml_add_deleted_len((char_u *)dp + line_start, line_size-1); /* * special case: If there is only one line in the data block it becomes empty. @@ -2676,6 +2709,17 @@ void ml_clearmarked(void) return; } +size_t ml_flush_deleted_bytes(buf_T *buf, size_t *codepoints, size_t *codeunits) +{ + size_t ret = buf->deleted_bytes; + *codepoints = buf->deleted_codepoints; + *codeunits = buf->deleted_codeunits; + buf->deleted_bytes = 0; + buf->deleted_codepoints = 0; + buf->deleted_codeunits = 0; + return ret; +} + /* * flush ml_line if necessary */ @@ -2704,6 +2748,8 @@ static void ml_flush_line(buf_T *buf) return; entered = TRUE; + buf->flush_count++; + lnum = buf->b_ml.ml_line_lnum; new_line = buf->b_ml.ml_line_ptr; diff --git a/src/nvim/memory.c b/src/nvim/memory.c index dced03f3d5..64aae71433 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -9,6 +9,7 @@ #include <stdbool.h> #include "nvim/vim.h" +#include "nvim/context.h" #include "nvim/eval.h" #include "nvim/highlight.h" #include "nvim/memfile.h" @@ -648,7 +649,7 @@ void free_all_mem(void) // Free all option values. Must come after closing windows. free_all_options(); - free_cmdline_buf(); + free_arshape_buf(); /* Clear registers. */ clear_registers(); @@ -671,6 +672,7 @@ void free_all_mem(void) eval_clear(); api_vim_free_all_mem(); + ctx_free_all(); // Free all buffers. Reset 'autochdir' to avoid accessing things that // were freed already. diff --git a/src/nvim/message.c b/src/nvim/message.c index e1164ad326..b043df17d3 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -116,6 +116,7 @@ static const char *msg_ext_kind = NULL; static Array msg_ext_chunks = ARRAY_DICT_INIT; static garray_T msg_ext_last_chunk = GA_INIT(sizeof(char), 40); static sattr_T msg_ext_last_attr = -1; +static size_t msg_ext_cur_len = 0; static bool msg_ext_overwrite = false; ///< will overwrite last message static int msg_ext_visible = 0; ///< number of messages currently visible @@ -1877,8 +1878,9 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, msg_ext_last_attr = attr; } // Concat pieces with the same highlight - ga_concat_len(&msg_ext_last_chunk, (char *)str, - strnlen((char *)str, maxlen)); // -V781 + size_t len = strnlen((char *)str, maxlen); // -V781 + ga_concat_len(&msg_ext_last_chunk, (char *)str, len); + msg_ext_cur_len += len; return; } @@ -2323,8 +2325,8 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen) } } - // primitive way to compute the current column int cw = utf_char2cells(utf_ptr2char((const char_u *)s)); + // primitive way to compute the current column if (cmdmsg_rl) { if (*s == '\r' || *s == '\n') { msg_col = Columns - 1; @@ -2567,6 +2569,7 @@ void mch_errmsg(char *str) } } +// Give a message. To be used when the UI is not initialized yet. void mch_msg(char *str) { wchar_t *utf16str; @@ -2722,6 +2725,7 @@ void msg_ext_ui_flush(void) } msg_ext_kind = NULL; msg_ext_chunks = (Array)ARRAY_DICT_INIT; + msg_ext_cur_len = 0; msg_ext_overwrite = false; } } @@ -2734,6 +2738,7 @@ void msg_ext_flush_showmode(void) msg_ext_emit_chunk(); ui_call_msg_showmode(msg_ext_chunks); msg_ext_chunks = (Array)ARRAY_DICT_INIT; + msg_ext_cur_len = 0; } } @@ -2940,7 +2945,7 @@ int verbose_open(void) /* Only give the error message once. */ verbose_did_open = TRUE; - verbose_fd = mch_fopen((char *)p_vfile, "a"); + verbose_fd = os_fopen((char *)p_vfile, "a"); if (verbose_fd == NULL) { EMSG2(_(e_notopen), p_vfile); return FAIL; @@ -2970,7 +2975,10 @@ void give_warning(char_u *message, bool hl) FUNC_ATTR_NONNULL_ARG(1) } else { keep_msg_attr = 0; } - msg_ext_set_kind("wmsg"); + + if (msg_ext_kind == NULL) { + msg_ext_set_kind("wmsg"); + } if (msg_attr((const char *)message, keep_msg_attr) && msg_scrolled == 0) { set_keep_msg(message, keep_msg_attr); @@ -2997,6 +3005,14 @@ void msg_advance(int col) msg_col = col; /* for redirection, may fill it up later */ return; } + if (ui_has(kUIMessages)) { + // TODO(bfredl): use byte count as a basic proxy. + // later on we might add proper support for formatted messages. + while (msg_ext_cur_len < (size_t)col) { + msg_putchar(' '); + } + return; + } if (col >= Columns) /* not enough room */ col = Columns - 1; if (cmdmsg_rl) diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index 45e03681eb..44e2c7df5f 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -65,872 +65,6 @@ static garray_T ga_users = GA_EMPTY_INIT_VALUE; /* - * open_line: Add a new line below or above the current line. - * - * For VREPLACE mode, we only add a new line when we get to the end of the - * file, otherwise we just start replacing the next line. - * - * Caller must take care of undo. Since VREPLACE may affect any number of - * lines however, it may call u_save_cursor() again when starting to change a - * new line. - * "flags": OPENLINE_DELSPACES delete spaces after cursor - * OPENLINE_DO_COM format comments - * OPENLINE_KEEPTRAIL keep trailing spaces - * OPENLINE_MARKFIX adjust mark positions after the line break - * OPENLINE_COM_LIST format comments with list or 2nd line indent - * - * "second_line_indent": indent for after ^^D in Insert mode or if flag - * OPENLINE_COM_LIST - * - * Return TRUE for success, FALSE for failure - */ -int -open_line ( - int dir, /* FORWARD or BACKWARD */ - int flags, - int second_line_indent -) -{ - char_u *next_line = NULL; // copy of the next line - char_u *p_extra = NULL; // what goes to next line - colnr_T less_cols = 0; // less columns for mark in new line - colnr_T less_cols_off = 0; // columns to skip for mark adjust - pos_T old_cursor; // old cursor position - colnr_T newcol = 0; // new cursor column - int newindent = 0; // auto-indent of the new line - bool trunc_line = false; // truncate current line afterwards - bool retval = false; // return value - int extra_len = 0; // length of p_extra string - int lead_len; // length of comment leader - char_u *lead_flags; // position in 'comments' for comment leader - char_u *leader = NULL; // copy of comment leader - char_u *allocated = NULL; // allocated memory - char_u *p; - char_u saved_char = NUL; // init for GCC - pos_T *pos; - bool do_si = (!p_paste && curbuf->b_p_si && !curbuf->b_p_cin - && *curbuf->b_p_inde == NUL); - bool no_si = false; // reset did_si afterwards - int first_char = NUL; // init for GCC - int vreplace_mode; - bool did_append; // appended a new line - int saved_pi = curbuf->b_p_pi; // copy of preserveindent setting - - // make a copy of the current line so we can mess with it - char_u *saved_line = vim_strsave(get_cursor_line_ptr()); - - if (State & VREPLACE_FLAG) { - /* - * With VREPLACE we make a copy of the next line, which we will be - * starting to replace. First make the new line empty and let vim play - * with the indenting and comment leader to its heart's content. Then - * we grab what it ended up putting on the new line, put back the - * original line, and call ins_char() to put each new character onto - * the line, replacing what was there before and pushing the right - * stuff onto the replace stack. -- webb. - */ - if (curwin->w_cursor.lnum < orig_line_count) - next_line = vim_strsave(ml_get(curwin->w_cursor.lnum + 1)); - else - next_line = vim_strsave((char_u *)""); - - /* - * In VREPLACE mode, a NL replaces the rest of the line, and starts - * replacing the next line, so push all of the characters left on the - * line onto the replace stack. We'll push any other characters that - * might be replaced at the start of the next line (due to autoindent - * etc) a bit later. - */ - replace_push(NUL); /* Call twice because BS over NL expects it */ - replace_push(NUL); - p = saved_line + curwin->w_cursor.col; - while (*p != NUL) { - if (has_mbyte) - p += replace_push_mb(p); - else - replace_push(*p++); - } - saved_line[curwin->w_cursor.col] = NUL; - } - - if ((State & INSERT) - && !(State & VREPLACE_FLAG) - ) { - p_extra = saved_line + curwin->w_cursor.col; - if (do_si) { /* need first char after new line break */ - p = skipwhite(p_extra); - first_char = *p; - } - extra_len = (int)STRLEN(p_extra); - saved_char = *p_extra; - *p_extra = NUL; - } - - u_clearline(); // cannot do "U" command when adding lines - did_si = false; - ai_col = 0; - - /* - * If we just did an auto-indent, then we didn't type anything on - * the prior line, and it should be truncated. Do this even if 'ai' is not - * set because automatically inserting a comment leader also sets did_ai. - */ - if (dir == FORWARD && did_ai) - trunc_line = TRUE; - - /* - * If 'autoindent' and/or 'smartindent' is set, try to figure out what - * indent to use for the new line. - */ - if (curbuf->b_p_ai - || do_si - ) { - // count white space on current line - newindent = get_indent_str(saved_line, (int)curbuf->b_p_ts, false); - if (newindent == 0 && !(flags & OPENLINE_COM_LIST)) { - newindent = second_line_indent; // for ^^D command in insert mode - } - - /* - * Do smart indenting. - * In insert/replace mode (only when dir == FORWARD) - * we may move some text to the next line. If it starts with '{' - * don't add an indent. Fixes inserting a NL before '{' in line - * "if (condition) {" - */ - if (!trunc_line && do_si && *saved_line != NUL - && (p_extra == NULL || first_char != '{')) { - char_u *ptr; - char_u last_char; - - old_cursor = curwin->w_cursor; - ptr = saved_line; - if (flags & OPENLINE_DO_COM) - lead_len = get_leader_len(ptr, NULL, FALSE, TRUE); - else - lead_len = 0; - if (dir == FORWARD) { - // Skip preprocessor directives, unless they are - // recognised as comments. - if (lead_len == 0 && ptr[0] == '#') { - while (ptr[0] == '#' && curwin->w_cursor.lnum > 1) { - ptr = ml_get(--curwin->w_cursor.lnum); - } - newindent = get_indent(); - } - if (flags & OPENLINE_DO_COM) - lead_len = get_leader_len(ptr, NULL, FALSE, TRUE); - else - lead_len = 0; - if (lead_len > 0) { - /* - * This case gets the following right: - * \* - * * A comment (read '\' as '/'). - * *\ - * #define IN_THE_WAY - * This should line up here; - */ - p = skipwhite(ptr); - if (p[0] == '/' && p[1] == '*') - p++; - if (p[0] == '*') { - for (p++; *p; p++) { - if (p[0] == '/' && p[-1] == '*') { - /* - * End of C comment, indent should line up - * with the line containing the start of - * the comment - */ - curwin->w_cursor.col = (colnr_T)(p - ptr); - if ((pos = findmatch(NULL, NUL)) != NULL) { - curwin->w_cursor.lnum = pos->lnum; - newindent = get_indent(); - } - } - } - } - } else { /* Not a comment line */ - /* Find last non-blank in line */ - p = ptr + STRLEN(ptr) - 1; - while (p > ptr && ascii_iswhite(*p)) - --p; - last_char = *p; - - /* - * find the character just before the '{' or ';' - */ - if (last_char == '{' || last_char == ';') { - if (p > ptr) - --p; - while (p > ptr && ascii_iswhite(*p)) - --p; - } - /* - * Try to catch lines that are split over multiple - * lines. eg: - * if (condition && - * condition) { - * Should line up here! - * } - */ - if (*p == ')') { - curwin->w_cursor.col = (colnr_T)(p - ptr); - if ((pos = findmatch(NULL, '(')) != NULL) { - curwin->w_cursor.lnum = pos->lnum; - newindent = get_indent(); - ptr = get_cursor_line_ptr(); - } - } - /* - * If last character is '{' do indent, without - * checking for "if" and the like. - */ - if (last_char == '{') { - did_si = true; // do indent - no_si = true; // don't delete it when '{' typed - } - /* - * Look for "if" and the like, use 'cinwords'. - * Don't do this if the previous line ended in ';' or - * '}'. - */ - else if (last_char != ';' && last_char != '}' - && cin_is_cinword(ptr)) - did_si = true; - } - } else { // dir == BACKWARD - // Skip preprocessor directives, unless they are - // recognised as comments. - if (lead_len == 0 && ptr[0] == '#') { - bool was_backslashed = false; - - while ((ptr[0] == '#' || was_backslashed) - && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - if (*ptr && ptr[STRLEN(ptr) - 1] == '\\') { - was_backslashed = true; - } else { - was_backslashed = false; - } - ptr = ml_get(++curwin->w_cursor.lnum); - } - if (was_backslashed) { - newindent = 0; // Got to end of file - } else { - newindent = get_indent(); - } - } - p = skipwhite(ptr); - if (*p == '}') { // if line starts with '}': do indent - did_si = true; - } else { // can delete indent when '{' typed - can_si_back = true; - } - } - curwin->w_cursor = old_cursor; - } - if (do_si) { - can_si = true; - } - - did_ai = true; - } - - /* - * Find out if the current line starts with a comment leader. - * This may then be inserted in front of the new line. - */ - end_comment_pending = NUL; - if (flags & OPENLINE_DO_COM) - lead_len = get_leader_len(saved_line, &lead_flags, dir == BACKWARD, TRUE); - else - lead_len = 0; - if (lead_len > 0) { - char_u *lead_repl = NULL; /* replaces comment leader */ - int lead_repl_len = 0; /* length of *lead_repl */ - char_u lead_middle[COM_MAX_LEN]; /* middle-comment string */ - char_u lead_end[COM_MAX_LEN]; /* end-comment string */ - char_u *comment_end = NULL; /* where lead_end has been found */ - int extra_space = FALSE; /* append extra space */ - int current_flag; - int require_blank = FALSE; /* requires blank after middle */ - char_u *p2; - - /* - * If the comment leader has the start, middle or end flag, it may not - * be used or may be replaced with the middle leader. - */ - for (p = lead_flags; *p && *p != ':'; ++p) { - if (*p == COM_BLANK) { - require_blank = TRUE; - continue; - } - if (*p == COM_START || *p == COM_MIDDLE) { - current_flag = *p; - if (*p == COM_START) { - /* - * Doing "O" on a start of comment does not insert leader. - */ - if (dir == BACKWARD) { - lead_len = 0; - break; - } - - /* find start of middle part */ - (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ","); - require_blank = FALSE; - } - - /* - * Isolate the strings of the middle and end leader. - */ - while (*p && p[-1] != ':') { /* find end of middle flags */ - if (*p == COM_BLANK) - require_blank = TRUE; - ++p; - } - (void)copy_option_part(&p, lead_middle, COM_MAX_LEN, ","); - - while (*p && p[-1] != ':') { /* find end of end flags */ - /* Check whether we allow automatic ending of comments */ - if (*p == COM_AUTO_END) - end_comment_pending = -1; /* means we want to set it */ - ++p; - } - size_t n = copy_option_part(&p, lead_end, COM_MAX_LEN, ","); - - if (end_comment_pending == -1) /* we can set it now */ - end_comment_pending = lead_end[n - 1]; - - /* - * If the end of the comment is in the same line, don't use - * the comment leader. - */ - if (dir == FORWARD) { - for (p = saved_line + lead_len; *p; ++p) - if (STRNCMP(p, lead_end, n) == 0) { - comment_end = p; - lead_len = 0; - break; - } - } - - /* - * Doing "o" on a start of comment inserts the middle leader. - */ - if (lead_len > 0) { - if (current_flag == COM_START) { - lead_repl = lead_middle; - lead_repl_len = (int)STRLEN(lead_middle); - } - - /* - * If we have hit RETURN immediately after the start - * comment leader, then put a space after the middle - * comment leader on the next line. - */ - if (!ascii_iswhite(saved_line[lead_len - 1]) - && ((p_extra != NULL - && (int)curwin->w_cursor.col == lead_len) - || (p_extra == NULL - && saved_line[lead_len] == NUL) - || require_blank)) - extra_space = TRUE; - } - break; - } - if (*p == COM_END) { - /* - * Doing "o" on the end of a comment does not insert leader. - * Remember where the end is, might want to use it to find the - * start (for C-comments). - */ - if (dir == FORWARD) { - comment_end = skipwhite(saved_line); - lead_len = 0; - break; - } - - /* - * Doing "O" on the end of a comment inserts the middle leader. - * Find the string for the middle leader, searching backwards. - */ - while (p > curbuf->b_p_com && *p != ',') - --p; - for (lead_repl = p; lead_repl > curbuf->b_p_com - && lead_repl[-1] != ':'; --lead_repl) - ; - lead_repl_len = (int)(p - lead_repl); - - /* We can probably always add an extra space when doing "O" on - * the comment-end */ - extra_space = TRUE; - - /* Check whether we allow automatic ending of comments */ - for (p2 = p; *p2 && *p2 != ':'; p2++) { - if (*p2 == COM_AUTO_END) - end_comment_pending = -1; /* means we want to set it */ - } - if (end_comment_pending == -1) { - /* Find last character in end-comment string */ - while (*p2 && *p2 != ',') - p2++; - end_comment_pending = p2[-1]; - } - break; - } - if (*p == COM_FIRST) { - /* - * Comment leader for first line only: Don't repeat leader - * when using "O", blank out leader when using "o". - */ - if (dir == BACKWARD) - lead_len = 0; - else { - lead_repl = (char_u *)""; - lead_repl_len = 0; - } - break; - } - } - if (lead_len > 0) { - // allocate buffer (may concatenate p_extra later) - leader = xmalloc((size_t)(lead_len + lead_repl_len + extra_space - + extra_len + (second_line_indent > 0 - ? second_line_indent : 0) + 1)); - allocated = leader; // remember to free it later - - STRLCPY(leader, saved_line, lead_len + 1); - - /* - * Replace leader with lead_repl, right or left adjusted - */ - if (lead_repl != NULL) { - int c = 0; - int off = 0; - - for (p = lead_flags; *p != NUL && *p != ':'; ) { - if (*p == COM_RIGHT || *p == COM_LEFT) - c = *p++; - else if (ascii_isdigit(*p) || *p == '-') - off = getdigits_int(&p); - else - ++p; - } - if (c == COM_RIGHT) { /* right adjusted leader */ - /* find last non-white in the leader to line up with */ - for (p = leader + lead_len - 1; p > leader - && ascii_iswhite(*p); --p) - ; - ++p; - - /* Compute the length of the replaced characters in - * screen characters, not bytes. */ - { - int repl_size = vim_strnsize(lead_repl, - lead_repl_len); - int old_size = 0; - char_u *endp = p; - int l; - - while (old_size < repl_size && p > leader) { - MB_PTR_BACK(leader, p); - old_size += ptr2cells(p); - } - l = lead_repl_len - (int)(endp - p); - if (l != 0) - memmove(endp + l, endp, - (size_t)((leader + lead_len) - endp)); - lead_len += l; - } - memmove(p, lead_repl, (size_t)lead_repl_len); - if (p + lead_repl_len > leader + lead_len) - p[lead_repl_len] = NUL; - - /* blank-out any other chars from the old leader. */ - while (--p >= leader) { - int l = utf_head_off(leader, p); - - if (l > 1) { - p -= l; - if (ptr2cells(p) > 1) { - p[1] = ' '; - --l; - } - memmove(p + 1, p + l + 1, - (size_t)((leader + lead_len) - (p + l + 1))); - lead_len -= l; - *p = ' '; - } else if (!ascii_iswhite(*p)) - *p = ' '; - } - } else { /* left adjusted leader */ - p = skipwhite(leader); - /* Compute the length of the replaced characters in - * screen characters, not bytes. Move the part that is - * not to be overwritten. */ - { - int repl_size = vim_strnsize(lead_repl, - lead_repl_len); - int i; - int l; - - for (i = 0; i < lead_len && p[i] != NUL; i += l) { - l = (*mb_ptr2len)(p + i); - if (vim_strnsize(p, i + l) > repl_size) - break; - } - if (i != lead_repl_len) { - memmove(p + lead_repl_len, p + i, - (size_t)(lead_len - i - (p - leader))); - lead_len += lead_repl_len - i; - } - } - memmove(p, lead_repl, (size_t)lead_repl_len); - - /* Replace any remaining non-white chars in the old - * leader by spaces. Keep Tabs, the indent must - * remain the same. */ - for (p += lead_repl_len; p < leader + lead_len; ++p) - if (!ascii_iswhite(*p)) { - /* Don't put a space before a TAB. */ - if (p + 1 < leader + lead_len && p[1] == TAB) { - lead_len--; - memmove(p, p + 1, (size_t)(leader + lead_len - p)); - } else { - int l = (*mb_ptr2len)(p); - - if (l > 1) { - if (ptr2cells(p) > 1) { - /* Replace a double-wide char with - * two spaces */ - --l; - *p++ = ' '; - } - memmove(p + 1, p + l, (size_t)(leader + lead_len - p)); - lead_len -= l - 1; - } - *p = ' '; - } - } - *p = NUL; - } - - /* Recompute the indent, it may have changed. */ - if (curbuf->b_p_ai - || do_si - ) - newindent = get_indent_str(leader, (int)curbuf->b_p_ts, false); - - /* Add the indent offset */ - if (newindent + off < 0) { - off = -newindent; - newindent = 0; - } else - newindent += off; - - /* Correct trailing spaces for the shift, so that - * alignment remains equal. */ - while (off > 0 && lead_len > 0 - && leader[lead_len - 1] == ' ') { - /* Don't do it when there is a tab before the space */ - if (vim_strchr(skipwhite(leader), '\t') != NULL) - break; - --lead_len; - --off; - } - - /* If the leader ends in white space, don't add an - * extra space */ - if (lead_len > 0 && ascii_iswhite(leader[lead_len - 1])) - extra_space = FALSE; - leader[lead_len] = NUL; - } - - if (extra_space) { - leader[lead_len++] = ' '; - leader[lead_len] = NUL; - } - - newcol = lead_len; - - /* - * if a new indent will be set below, remove the indent that - * is in the comment leader - */ - if (newindent - || did_si - ) { - while (lead_len && ascii_iswhite(*leader)) { - --lead_len; - --newcol; - ++leader; - } - } - - did_si = can_si = false; - } else if (comment_end != NULL) { - // We have finished a comment, so we don't use the leader. - // If this was a C-comment and 'ai' or 'si' is set do a normal - // indent to align with the line containing the start of the - // comment. - if (comment_end[0] == '*' && comment_end[1] == '/' - && (curbuf->b_p_ai || do_si)) { - old_cursor = curwin->w_cursor; - curwin->w_cursor.col = (colnr_T)(comment_end - saved_line); - if ((pos = findmatch(NULL, NUL)) != NULL) { - curwin->w_cursor.lnum = pos->lnum; - newindent = get_indent(); - } - curwin->w_cursor = old_cursor; - } - } - } - - /* (State == INSERT || State == REPLACE), only when dir == FORWARD */ - if (p_extra != NULL) { - *p_extra = saved_char; /* restore char that NUL replaced */ - - /* - * When 'ai' set or "flags" has OPENLINE_DELSPACES, skip to the first - * non-blank. - * - * When in REPLACE mode, put the deleted blanks on the replace stack, - * preceded by a NUL, so they can be put back when a BS is entered. - */ - if (REPLACE_NORMAL(State)) - replace_push(NUL); /* end of extra blanks */ - if (curbuf->b_p_ai || (flags & OPENLINE_DELSPACES)) { - while ((*p_extra == ' ' || *p_extra == '\t') - && !utf_iscomposing(utf_ptr2char(p_extra + 1))) { - if (REPLACE_NORMAL(State)) { - replace_push(*p_extra); - } - p_extra++; - less_cols_off++; - } - } - - /* columns for marks adjusted for removed columns */ - less_cols = (int)(p_extra - saved_line); - } - - if (p_extra == NULL) - p_extra = (char_u *)""; /* append empty line */ - - /* concatenate leader and p_extra, if there is a leader */ - if (lead_len > 0) { - if (flags & OPENLINE_COM_LIST && second_line_indent > 0) { - int i; - int padding = second_line_indent - - (newindent + (int)STRLEN(leader)); - - /* Here whitespace is inserted after the comment char. - * Below, set_indent(newindent, SIN_INSERT) will insert the - * whitespace needed before the comment char. */ - for (i = 0; i < padding; i++) { - STRCAT(leader, " "); - less_cols--; - newcol++; - } - } - STRCAT(leader, p_extra); - p_extra = leader; - did_ai = true; // So truncating blanks works with comments - less_cols -= lead_len; - } else - end_comment_pending = NUL; /* turns out there was no leader */ - - old_cursor = curwin->w_cursor; - if (dir == BACKWARD) - --curwin->w_cursor.lnum; - if (!(State & VREPLACE_FLAG) || old_cursor.lnum >= orig_line_count) { - if (ml_append(curwin->w_cursor.lnum, p_extra, (colnr_T)0, false) == FAIL) { - goto theend; - } - // Postpone calling changed_lines(), because it would mess up folding - // with markers. - // Skip mark_adjust when adding a line after the last one, there can't - // be marks there. But still needed in diff mode. - if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count - || curwin->w_p_diff) { - mark_adjust(curwin->w_cursor.lnum + 1, (linenr_T)MAXLNUM, 1L, 0L, false); - } - did_append = true; - } else { - /* - * In VREPLACE mode we are starting to replace the next line. - */ - curwin->w_cursor.lnum++; - if (curwin->w_cursor.lnum >= Insstart.lnum + vr_lines_changed) { - /* In case we NL to a new line, BS to the previous one, and NL - * again, we don't want to save the new line for undo twice. - */ - (void)u_save_cursor(); /* errors are ignored! */ - vr_lines_changed++; - } - ml_replace(curwin->w_cursor.lnum, p_extra, true); - changed_bytes(curwin->w_cursor.lnum, 0); - curwin->w_cursor.lnum--; - did_append = FALSE; - } - - if (newindent - || did_si - ) { - ++curwin->w_cursor.lnum; - if (did_si) { - int sw = get_sw_value(curbuf); - - if (p_sr) - newindent -= newindent % sw; - newindent += sw; - } - /* Copy the indent */ - if (curbuf->b_p_ci) { - (void)copy_indent(newindent, saved_line); - - /* - * Set the 'preserveindent' option so that any further screwing - * with the line doesn't entirely destroy our efforts to preserve - * it. It gets restored at the function end. - */ - curbuf->b_p_pi = TRUE; - } else - (void)set_indent(newindent, SIN_INSERT); - less_cols -= curwin->w_cursor.col; - - ai_col = curwin->w_cursor.col; - - /* - * In REPLACE mode, for each character in the new indent, there must - * be a NUL on the replace stack, for when it is deleted with BS - */ - if (REPLACE_NORMAL(State)) { - for (colnr_T n = 0; n < curwin->w_cursor.col; n++) { - replace_push(NUL); - } - } - newcol += curwin->w_cursor.col; - if (no_si) { - did_si = false; - } - } - - /* - * In REPLACE mode, for each character in the extra leader, there must be - * a NUL on the replace stack, for when it is deleted with BS. - */ - if (REPLACE_NORMAL(State)) - while (lead_len-- > 0) - replace_push(NUL); - - curwin->w_cursor = old_cursor; - - if (dir == FORWARD) { - if (trunc_line || (State & INSERT)) { - // truncate current line at cursor - saved_line[curwin->w_cursor.col] = NUL; - // Remove trailing white space, unless OPENLINE_KEEPTRAIL used. - if (trunc_line && !(flags & OPENLINE_KEEPTRAIL)) { - truncate_spaces(saved_line); - } - ml_replace(curwin->w_cursor.lnum, saved_line, false); - saved_line = NULL; - if (did_append) { - changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col, - curwin->w_cursor.lnum + 1, 1L, true); - did_append = false; - - /* Move marks after the line break to the new line. */ - if (flags & OPENLINE_MARKFIX) - mark_col_adjust(curwin->w_cursor.lnum, - curwin->w_cursor.col + less_cols_off, - 1L, (long)-less_cols, 0); - } else { - changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col); - } - } - - /* - * Put the cursor on the new line. Careful: the scrollup() above may - * have moved w_cursor, we must use old_cursor. - */ - curwin->w_cursor.lnum = old_cursor.lnum + 1; - } - if (did_append) { - changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true); - } - - curwin->w_cursor.col = newcol; - curwin->w_cursor.coladd = 0; - - /* - * In VREPLACE mode, we are handling the replace stack ourselves, so stop - * fixthisline() from doing it (via change_indent()) by telling it we're in - * normal INSERT mode. - */ - if (State & VREPLACE_FLAG) { - vreplace_mode = State; /* So we know to put things right later */ - State = INSERT; - } else - vreplace_mode = 0; - /* - * May do lisp indenting. - */ - if (!p_paste - && leader == NULL - && curbuf->b_p_lisp - && curbuf->b_p_ai) { - fixthisline(get_lisp_indent); - ai_col = (colnr_T)getwhitecols_curline(); - } - /* - * May do indenting after opening a new line. - */ - if (!p_paste - && (curbuf->b_p_cin - || *curbuf->b_p_inde != NUL - ) - && in_cinkeys(dir == FORWARD - ? KEY_OPEN_FORW - : KEY_OPEN_BACK, ' ', linewhite(curwin->w_cursor.lnum))) { - do_c_expr_indent(); - ai_col = (colnr_T)getwhitecols_curline(); - } - if (vreplace_mode != 0) - State = vreplace_mode; - - /* - * Finally, VREPLACE gets the stuff on the new line, then puts back the - * original line, and inserts the new stuff char by char, pushing old stuff - * onto the replace stack (via ins_char()). - */ - if (State & VREPLACE_FLAG) { - /* Put new line in p_extra */ - p_extra = vim_strsave(get_cursor_line_ptr()); - - // Put back original line - ml_replace(curwin->w_cursor.lnum, next_line, false); - - /* Insert new stuff into line again */ - curwin->w_cursor.col = 0; - curwin->w_cursor.coladd = 0; - ins_bytes(p_extra); /* will call changed_bytes() */ - xfree(p_extra); - next_line = NULL; - } - - retval = true; // success! -theend: - curbuf->b_p_pi = saved_pi; - xfree(saved_line); - xfree(next_line); - xfree(allocated); - return retval; -} - -/* * get_leader_len() returns the length in bytes of the prefix of the given * string which introduces a comment. If this string is not a comment then * 0 is returned. @@ -1373,398 +507,8 @@ int plines_m_win(win_T *wp, linenr_T first, linenr_T last) return count; } -/* - * Insert string "p" at the cursor position. Stops at a NUL byte. - * Handles Replace mode and multi-byte characters. - */ -void ins_bytes(char_u *p) -{ - ins_bytes_len(p, STRLEN(p)); -} - -/// Insert string "p" with length "len" at the cursor position. -/// Handles Replace mode and multi-byte characters. -void ins_bytes_len(char_u *p, size_t len) -{ - if (has_mbyte) { - size_t n; - for (size_t i = 0; i < len; i += n) { - if (enc_utf8) { - // avoid reading past p[len] - n = (size_t)utfc_ptr2len_len(p + i, (int)(len - i)); - } else { - n = (size_t)(*mb_ptr2len)(p + i); - } - ins_char_bytes(p + i, n); - } - } else { - for (size_t i = 0; i < len; i++) { - ins_char(p[i]); - } - } -} - -/// Insert or replace a single character at the cursor position. -/// When in REPLACE or VREPLACE mode, replace any existing character. -/// Caller must have prepared for undo. -/// For multi-byte characters we get the whole character, the caller must -/// convert bytes to a character. -void ins_char(int c) -{ - char_u buf[MB_MAXBYTES + 1]; - size_t n = (size_t)utf_char2bytes(c, buf); - - // When "c" is 0x100, 0x200, etc. we don't want to insert a NUL byte. - // Happens for CTRL-Vu9900. - if (buf[0] == 0) { - buf[0] = '\n'; - } - ins_char_bytes(buf, n); -} - -void ins_char_bytes(char_u *buf, size_t charlen) -{ - // Break tabs if needed. - if (virtual_active() && curwin->w_cursor.coladd > 0) { - coladvance_force(getviscol()); - } - - size_t col = (size_t)curwin->w_cursor.col; - linenr_T lnum = curwin->w_cursor.lnum; - char_u *oldp = ml_get(lnum); - size_t linelen = STRLEN(oldp) + 1; // length of old line including NUL - - // The lengths default to the values for when not replacing. - size_t oldlen = 0; // nr of bytes inserted - size_t newlen = charlen; // nr of bytes deleted (0 when not replacing) - - if (State & REPLACE_FLAG) { - if (State & VREPLACE_FLAG) { - // Disable 'list' temporarily, unless 'cpo' contains the 'L' flag. - // Returns the old value of list, so when finished, - // curwin->w_p_list should be set back to this. - int old_list = curwin->w_p_list; - if (old_list && vim_strchr(p_cpo, CPO_LISTWM) == NULL) { - curwin->w_p_list = false; - } - // In virtual replace mode each character may replace one or more - // characters (zero if it's a TAB). Count the number of bytes to - // be deleted to make room for the new character, counting screen - // cells. May result in adding spaces to fill a gap. - colnr_T vcol; - getvcol(curwin, &curwin->w_cursor, NULL, &vcol, NULL); - colnr_T new_vcol = vcol + chartabsize(buf, vcol); - while (oldp[col + oldlen] != NUL && vcol < new_vcol) { - vcol += chartabsize(oldp + col + oldlen, vcol); - // Don't need to remove a TAB that takes us to the right - // position. - if (vcol > new_vcol && oldp[col + oldlen] == TAB) { - break; - } - oldlen += (size_t)(*mb_ptr2len)(oldp + col + oldlen); - // Deleted a bit too much, insert spaces. - if (vcol > new_vcol) { - newlen += (size_t)(vcol - new_vcol); - } - } - curwin->w_p_list = old_list; - } else if (oldp[col] != NUL) { - // normal replace - oldlen = (size_t)(*mb_ptr2len)(oldp + col); - } - - - /* Push the replaced bytes onto the replace stack, so that they can be - * put back when BS is used. The bytes of a multi-byte character are - * done the other way around, so that the first byte is popped off - * first (it tells the byte length of the character). */ - replace_push(NUL); - for (size_t i = 0; i < oldlen; i++) { - if (has_mbyte) { - i += (size_t)replace_push_mb(oldp + col + i) - 1; - } else { - replace_push(oldp[col + i]); - } - } - } - - char_u *newp = xmalloc((size_t)(linelen + newlen - oldlen)); - - // Copy bytes before the cursor. - if (col > 0) { - memmove(newp, oldp, (size_t)col); - } - - // Copy bytes after the changed character(s). - char_u *p = newp + col; - if (linelen > col + oldlen) { - memmove(p + newlen, oldp + col + oldlen, - (size_t)(linelen - col - oldlen)); - } - - // Insert or overwrite the new character. - memmove(p, buf, charlen); - - // Fill with spaces when necessary. - for (size_t i = charlen; i < newlen; i++) { - p[i] = ' '; - } - - // Replace the line in the buffer. - ml_replace(lnum, newp, false); - - // mark the buffer as changed and prepare for displaying - changed_bytes(lnum, (colnr_T)col); - - /* - * If we're in Insert or Replace mode and 'showmatch' is set, then briefly - * show the match for right parens and braces. - */ - if (p_sm && (State & INSERT) - && msg_silent == 0 - && !ins_compl_active() - ) { - showmatch(utf_ptr2char(buf)); - } - - if (!p_ri || (State & REPLACE_FLAG)) { - // Normal insert: move cursor right - curwin->w_cursor.col += (int)charlen; - } - /* - * TODO: should try to update w_row here, to avoid recomputing it later. - */ -} - -/* - * Insert a string at the cursor position. - * Note: Does NOT handle Replace mode. - * Caller must have prepared for undo. - */ -void ins_str(char_u *s) -{ - char_u *oldp, *newp; - int newlen = (int)STRLEN(s); - int oldlen; - colnr_T col; - linenr_T lnum = curwin->w_cursor.lnum; - - if (virtual_active() && curwin->w_cursor.coladd > 0) - coladvance_force(getviscol()); - - col = curwin->w_cursor.col; - oldp = ml_get(lnum); - oldlen = (int)STRLEN(oldp); - - newp = (char_u *) xmalloc((size_t)(oldlen + newlen + 1)); - if (col > 0) - memmove(newp, oldp, (size_t)col); - memmove(newp + col, s, (size_t)newlen); - memmove(newp + col + newlen, oldp + col, (size_t)(oldlen - col + 1)); - ml_replace(lnum, newp, false); - changed_bytes(lnum, col); - curwin->w_cursor.col += newlen; -} - -// Delete one character under the cursor. -// If "fixpos" is true, don't leave the cursor on the NUL after the line. -// Caller must have prepared for undo. -// -// return FAIL for failure, OK otherwise -int del_char(bool fixpos) -{ - if (has_mbyte) { - /* Make sure the cursor is at the start of a character. */ - mb_adjust_cursor(); - if (*get_cursor_pos_ptr() == NUL) - return FAIL; - return del_chars(1L, fixpos); - } - return del_bytes(1, fixpos, true); -} - -/* - * Like del_bytes(), but delete characters instead of bytes. - */ -int del_chars(long count, int fixpos) -{ - int bytes = 0; - long i; - char_u *p; - int l; - - p = get_cursor_pos_ptr(); - for (i = 0; i < count && *p != NUL; ++i) { - l = (*mb_ptr2len)(p); - bytes += l; - p += l; - } - return del_bytes(bytes, fixpos, TRUE); -} - -/// Delete "count" bytes under the cursor. -/// If "fixpos" is true, don't leave the cursor on the NUL after the line. -/// Caller must have prepared for undo. -/// -/// @param count number of bytes to be deleted -/// @param fixpos_arg leave the cursor on the NUL after the line -/// @param use_delcombine 'delcombine' option applies -/// -/// @return FAIL for failure, OK otherwise -int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine) -{ - linenr_T lnum = curwin->w_cursor.lnum; - colnr_T col = curwin->w_cursor.col; - bool fixpos = fixpos_arg; - char_u *oldp = ml_get(lnum); - colnr_T oldlen = (colnr_T)STRLEN(oldp); - - // Can't do anything when the cursor is on the NUL after the line. - if (col >= oldlen) { - return FAIL; - } - // If "count" is zero there is nothing to do. - if (count == 0) { - return OK; - } - // If "count" is negative the caller must be doing something wrong. - if (count < 1) { - IEMSGN("E950: Invalid count for del_bytes(): %ld", count); - return FAIL; - } - - /* If 'delcombine' is set and deleting (less than) one character, only - * delete the last combining character. */ - if (p_deco && use_delcombine && enc_utf8 - && utfc_ptr2len(oldp + col) >= count) { - int cc[MAX_MCO]; - int n; - - (void)utfc_ptr2char(oldp + col, cc); - if (cc[0] != NUL) { - /* Find the last composing char, there can be several. */ - n = col; - do { - col = n; - count = utf_ptr2len(oldp + n); - n += count; - } while (UTF_COMPOSINGLIKE(oldp + col, oldp + n)); - fixpos = false; - } - } - - // When count is too big, reduce it. - int movelen = oldlen - col - count + 1; // includes trailing NUL - if (movelen <= 1) { - /* - * If we just took off the last character of a non-blank line, and - * fixpos is TRUE, we don't want to end up positioned at the NUL, - * unless "restart_edit" is set or 'virtualedit' contains "onemore". - */ - if (col > 0 && fixpos && restart_edit == 0 - && (ve_flags & VE_ONEMORE) == 0 - ) { - --curwin->w_cursor.col; - curwin->w_cursor.coladd = 0; - curwin->w_cursor.col -= utf_head_off(oldp, oldp + curwin->w_cursor.col); - } - count = oldlen - col; - movelen = 1; - } - - // If the old line has been allocated the deletion can be done in the - // existing line. Otherwise a new line has to be allocated. - bool was_alloced = ml_line_alloced(); // check if oldp was allocated - char_u *newp; - if (was_alloced) { - newp = oldp; // use same allocated memory - } else { // need to allocate a new line - newp = xmalloc((size_t)(oldlen + 1 - count)); - memmove(newp, oldp, (size_t)col); - } - memmove(newp + col, oldp + col + count, (size_t)movelen); - if (!was_alloced) { - ml_replace(lnum, newp, false); - } - - /* mark the buffer as changed and prepare for displaying */ - changed_bytes(lnum, curwin->w_cursor.col); - - return OK; -} - -/* - * Delete from cursor to end of line. - * Caller must have prepared for undo. - */ -void -truncate_line ( - int fixpos /* if TRUE fix the cursor position when done */ -) -{ - char_u *newp; - linenr_T lnum = curwin->w_cursor.lnum; - colnr_T col = curwin->w_cursor.col; - - if (col == 0) { - newp = vim_strsave((char_u *)""); - } else { - newp = vim_strnsave(ml_get(lnum), (size_t)col); - } - ml_replace(lnum, newp, false); - - /* mark the buffer as changed and prepare for displaying */ - changed_bytes(lnum, curwin->w_cursor.col); - - /* - * If "fixpos" is TRUE we don't want to end up positioned at the NUL. - */ - if (fixpos && curwin->w_cursor.col > 0) - --curwin->w_cursor.col; -} - -/* - * Delete "nlines" lines at the cursor. - * Saves the lines for undo first if "undo" is TRUE. - */ -void -del_lines ( - long nlines, /* number of lines to delete */ - int undo /* if TRUE, prepare for undo */ -) -{ - long n; - linenr_T first = curwin->w_cursor.lnum; - - if (nlines <= 0) - return; - - /* save the deleted lines for undo */ - if (undo && u_savedel(first, nlines) == FAIL) - return; - - for (n = 0; n < nlines; ) { - if (curbuf->b_ml.ml_flags & ML_EMPTY) /* nothing to delete */ - break; - - ml_delete(first, true); - n++; - - /* If we delete the last line in the file, stop */ - if (first > curbuf->b_ml.ml_line_count) - break; - } - - /* Correct the cursor position before calling deleted_lines_mark(), it may - * trigger a callback to display the cursor. */ - curwin->w_cursor.col = 0; - check_cursor_lnum(); - - /* adjust marks, mark the buffer as changed and prepare for displaying */ - deleted_lines_mark(first, n); -} - int gchar_pos(pos_T *pos) + FUNC_ATTR_NONNULL_ARG(1) { // When searching columns is sometimes put at the end of a line. if (pos->col == MAXCOL) { @@ -1774,430 +518,6 @@ int gchar_pos(pos_T *pos) } /* - * Call this function when something in the current buffer is changed. - * - * Most often called through changed_bytes() and changed_lines(), which also - * mark the area of the display to be redrawn. - * - * Careful: may trigger autocommands that reload the buffer. - */ -void changed(void) -{ - - if (!curbuf->b_changed) { - int save_msg_scroll = msg_scroll; - - /* Give a warning about changing a read-only file. This may also - * check-out the file, thus change "curbuf"! */ - change_warning(0); - - /* Create a swap file if that is wanted. - * Don't do this for "nofile" and "nowrite" buffer types. */ - if (curbuf->b_may_swap - && !bt_dontwrite(curbuf) - ) { - int save_need_wait_return = need_wait_return; - - need_wait_return = false; - ml_open_file(curbuf); - - /* The ml_open_file() can cause an ATTENTION message. - * Wait two seconds, to make sure the user reads this unexpected - * message. Since we could be anywhere, call wait_return() now, - * and don't let the emsg() set msg_scroll. */ - if (need_wait_return && emsg_silent == 0) { - ui_flush(); - os_delay(2000L, true); - wait_return(TRUE); - msg_scroll = save_msg_scroll; - } else { - need_wait_return = save_need_wait_return; - } - } - changed_int(); - } - buf_inc_changedtick(curbuf); - - // If a pattern is highlighted, the position may now be invalid. - highlight_match = false; -} - -/* - * Internal part of changed(), no user interaction. - */ -void changed_int(void) -{ - curbuf->b_changed = true; - ml_setflags(curbuf); - check_status(curbuf); - redraw_tabline = TRUE; - need_maketitle = TRUE; /* set window title later */ -} - - -/* - * Changed bytes within a single line for the current buffer. - * - marks the windows on this buffer to be redisplayed - * - marks the buffer changed by calling changed() - * - invalidates cached values - * Careful: may trigger autocommands that reload the buffer. - */ -void changed_bytes(linenr_T lnum, colnr_T col) -{ - changedOneline(curbuf, lnum); - changed_common(lnum, col, lnum + 1, 0L); - // notify any channels that are watching - buf_updates_send_changes(curbuf, lnum, 1, 1, true); - - /* Diff highlighting in other diff windows may need to be updated too. */ - if (curwin->w_p_diff) { - linenr_T wlnum; - - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_p_diff && wp != curwin) { - redraw_win_later(wp, VALID); - wlnum = diff_lnum_win(lnum, wp); - if (wlnum > 0) - changedOneline(wp->w_buffer, wlnum); - } - } - } -} - -static void changedOneline(buf_T *buf, linenr_T lnum) -{ - if (buf->b_mod_set) { - /* find the maximum area that must be redisplayed */ - if (lnum < buf->b_mod_top) - buf->b_mod_top = lnum; - else if (lnum >= buf->b_mod_bot) - buf->b_mod_bot = lnum + 1; - } else { - /* set the area that must be redisplayed to one line */ - buf->b_mod_set = true; - buf->b_mod_top = lnum; - buf->b_mod_bot = lnum + 1; - buf->b_mod_xlines = 0; - } -} - -/* - * Appended "count" lines below line "lnum" in the current buffer. - * Must be called AFTER the change and after mark_adjust(). - * Takes care of marking the buffer to be redrawn and sets the changed flag. - */ -void appended_lines(linenr_T lnum, long count) -{ - changed_lines(lnum + 1, 0, lnum + 1, count, true); -} - -/* - * Like appended_lines(), but adjust marks first. - */ -void appended_lines_mark(linenr_T lnum, long count) -{ - // Skip mark_adjust when adding a line after the last one, there can't - // be marks there. But it's still needed in diff mode. - if (lnum + count < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { - mark_adjust(lnum + 1, (linenr_T)MAXLNUM, count, 0L, false); - } - changed_lines(lnum + 1, 0, lnum + 1, count, true); -} - -/* - * Deleted "count" lines at line "lnum" in the current buffer. - * Must be called AFTER the change and after mark_adjust(). - * Takes care of marking the buffer to be redrawn and sets the changed flag. - */ -void deleted_lines(linenr_T lnum, long count) -{ - changed_lines(lnum, 0, lnum + count, -count, true); -} - -/* - * Like deleted_lines(), but adjust marks first. - * Make sure the cursor is on a valid line before calling, a GUI callback may - * be triggered to display the cursor. - */ -void deleted_lines_mark(linenr_T lnum, long count) -{ - mark_adjust(lnum, (linenr_T)(lnum + count - 1), (long)MAXLNUM, -count, false); - changed_lines(lnum, 0, lnum + count, -count, true); -} - -/* - * Changed lines for the current buffer. - * Must be called AFTER the change and after mark_adjust(). - * - mark the buffer changed by calling changed() - * - mark the windows on this buffer to be redisplayed - * - invalidate cached values - * "lnum" is the first line that needs displaying, "lnume" the first line - * below the changed lines (BEFORE the change). - * When only inserting lines, "lnum" and "lnume" are equal. - * Takes care of calling changed() and updating b_mod_*. - * Careful: may trigger autocommands that reload the buffer. - */ -void -changed_lines( - linenr_T lnum, // first line with change - colnr_T col, // column in first line with change - linenr_T lnume, // line below last changed line - long xtra, // number of extra lines (negative when deleting) - bool do_buf_event // some callers like undo/redo call changed_lines() - // and then increment changedtick *again*. This flag - // allows these callers to send the nvim_buf_lines_event - // events after they're done modifying changedtick. -) -{ - changed_lines_buf(curbuf, lnum, lnume, xtra); - - if (xtra == 0 && curwin->w_p_diff && !diff_internal()) { - // When the number of lines doesn't change then mark_adjust() isn't - // called and other diff buffers still need to be marked for - // displaying. - linenr_T wlnum; - - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { - if (wp->w_p_diff && wp != curwin) { - redraw_win_later(wp, VALID); - wlnum = diff_lnum_win(lnum, wp); - if (wlnum > 0) { - changed_lines_buf(wp->w_buffer, wlnum, - lnume - lnum + wlnum, 0L); - } - } - } - } - - changed_common(lnum, col, lnume, xtra); - - if (do_buf_event) { - int64_t num_added = (int64_t)(lnume + xtra - lnum); - int64_t num_removed = lnume - lnum; - buf_updates_send_changes(curbuf, lnum, num_added, num_removed, true); - } -} - -/// Mark line range in buffer as changed. -/// -/// @param buf the buffer where lines were changed -/// @param lnum first line with change -/// @param lnume line below last changed line -/// @param xtra number of extra lines (negative when deleting) -void changed_lines_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, long xtra) -{ - if (buf->b_mod_set) { - /* find the maximum area that must be redisplayed */ - if (lnum < buf->b_mod_top) - buf->b_mod_top = lnum; - if (lnum < buf->b_mod_bot) { - /* adjust old bot position for xtra lines */ - buf->b_mod_bot += xtra; - if (buf->b_mod_bot < lnum) - buf->b_mod_bot = lnum; - } - if (lnume + xtra > buf->b_mod_bot) - buf->b_mod_bot = lnume + xtra; - buf->b_mod_xlines += xtra; - } else { - /* set the area that must be redisplayed */ - buf->b_mod_set = true; - buf->b_mod_top = lnum; - buf->b_mod_bot = lnume + xtra; - buf->b_mod_xlines = xtra; - } -} - -/* - * Common code for when a change is was made. - * See changed_lines() for the arguments. - * Careful: may trigger autocommands that reload the buffer. - */ -static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume, long xtra) -{ - int i; - int cols; - pos_T *p; - int add; - - /* mark the buffer as modified */ - changed(); - - if (curwin->w_p_diff && diff_internal()) { - curtab->tp_diff_update = true; - } - - /* set the '. mark */ - if (!cmdmod.keepjumps) { - RESET_FMARK(&curbuf->b_last_change, ((pos_T) {lnum, col, 0}), 0); - - /* Create a new entry if a new undo-able change was started or we - * don't have an entry yet. */ - if (curbuf->b_new_change || curbuf->b_changelistlen == 0) { - if (curbuf->b_changelistlen == 0) - add = TRUE; - else { - /* Don't create a new entry when the line number is the same - * as the last one and the column is not too far away. Avoids - * creating many entries for typing "xxxxx". */ - p = &curbuf->b_changelist[curbuf->b_changelistlen - 1].mark; - if (p->lnum != lnum) - add = TRUE; - else { - cols = comp_textwidth(FALSE); - if (cols == 0) - cols = 79; - add = (p->col + cols < col || col + cols < p->col); - } - } - if (add) { - /* This is the first of a new sequence of undo-able changes - * and it's at some distance of the last change. Use a new - * position in the changelist. */ - curbuf->b_new_change = false; - - if (curbuf->b_changelistlen == JUMPLISTSIZE) { - /* changelist is full: remove oldest entry */ - curbuf->b_changelistlen = JUMPLISTSIZE - 1; - memmove(curbuf->b_changelist, curbuf->b_changelist + 1, - sizeof(curbuf->b_changelist[0]) * (JUMPLISTSIZE - 1)); - FOR_ALL_TAB_WINDOWS(tp, wp) { - /* Correct position in changelist for other windows on - * this buffer. */ - if (wp->w_buffer == curbuf && wp->w_changelistidx > 0) { - --wp->w_changelistidx; - } - } - } - FOR_ALL_TAB_WINDOWS(tp, wp) { - /* For other windows, if the position in the changelist is - * at the end it stays at the end. */ - if (wp->w_buffer == curbuf - && wp->w_changelistidx == curbuf->b_changelistlen) { - ++wp->w_changelistidx; - } - } - ++curbuf->b_changelistlen; - } - } - curbuf->b_changelist[curbuf->b_changelistlen - 1] = - curbuf->b_last_change; - /* The current window is always after the last change, so that "g," - * takes you back to it. */ - curwin->w_changelistidx = curbuf->b_changelistlen; - } - - FOR_ALL_TAB_WINDOWS(tp, wp) { - if (wp->w_buffer == curbuf) { - /* Mark this window to be redrawn later. */ - if (wp->w_redr_type < VALID) - wp->w_redr_type = VALID; - - /* Check if a change in the buffer has invalidated the cached - * values for the cursor. */ - /* - * Update the folds for this window. Can't postpone this, because - * a following operator might work on the whole fold: ">>dd". - */ - foldUpdate(wp, lnum, lnume + xtra - 1); - - /* The change may cause lines above or below the change to become - * included in a fold. Set lnum/lnume to the first/last line that - * might be displayed differently. - * Set w_cline_folded here as an efficient way to update it when - * inserting lines just above a closed fold. */ - bool folded = hasFoldingWin(wp, lnum, &lnum, NULL, false, NULL); - if (wp->w_cursor.lnum == lnum) - wp->w_cline_folded = folded; - folded = hasFoldingWin(wp, lnume, NULL, &lnume, false, NULL); - if (wp->w_cursor.lnum == lnume) - wp->w_cline_folded = folded; - - /* If the changed line is in a range of previously folded lines, - * compare with the first line in that range. */ - if (wp->w_cursor.lnum <= lnum) { - i = find_wl_entry(wp, lnum); - if (i >= 0 && wp->w_cursor.lnum > wp->w_lines[i].wl_lnum) - changed_line_abv_curs_win(wp); - } - - if (wp->w_cursor.lnum > lnum) - changed_line_abv_curs_win(wp); - else if (wp->w_cursor.lnum == lnum && wp->w_cursor.col >= col) - changed_cline_bef_curs_win(wp); - if (wp->w_botline >= lnum) { - /* Assume that botline doesn't change (inserted lines make - * other lines scroll down below botline). */ - approximate_botline_win(wp); - } - - /* Check if any w_lines[] entries have become invalid. - * For entries below the change: Correct the lnums for - * inserted/deleted lines. Makes it possible to stop displaying - * after the change. */ - for (i = 0; i < wp->w_lines_valid; ++i) - if (wp->w_lines[i].wl_valid) { - if (wp->w_lines[i].wl_lnum >= lnum) { - if (wp->w_lines[i].wl_lnum < lnume) { - /* line included in change */ - wp->w_lines[i].wl_valid = FALSE; - } else if (xtra != 0) { - /* line below change */ - wp->w_lines[i].wl_lnum += xtra; - wp->w_lines[i].wl_lastlnum += xtra; - } - } else if (wp->w_lines[i].wl_lastlnum >= lnum) { - /* change somewhere inside this range of folded lines, - * may need to be redrawn */ - wp->w_lines[i].wl_valid = FALSE; - } - } - - /* Take care of side effects for setting w_topline when folds have - * changed. Esp. when the buffer was changed in another window. */ - if (hasAnyFolding(wp)) - set_topline(wp, wp->w_topline); - - // relative numbering may require updating more - if (wp->w_p_rnu) { - redraw_win_later(wp, SOME_VALID); - } - } - } - - /* Call update_screen() later, which checks out what needs to be redrawn, - * since it notices b_mod_set and then uses b_mod_*. */ - if (must_redraw < VALID) - must_redraw = VALID; - - /* when the cursor line is changed always trigger CursorMoved */ - if (lnum <= curwin->w_cursor.lnum - && lnume + (xtra < 0 ? -xtra : xtra) > curwin->w_cursor.lnum) - curwin->w_last_cursormoved.lnum = 0; -} - -/* - * unchanged() is called when the changed flag must be reset for buffer 'buf' - */ -void -unchanged ( - buf_T *buf, - int ff /* also reset 'fileformat' */ -) -{ - if (buf->b_changed || (ff && file_ff_differs(buf, false))) { - buf->b_changed = false; - ml_setflags(buf); - if (ff) - save_file_ff(buf); - check_status(buf); - redraw_tabline = TRUE; - need_maketitle = TRUE; /* set window title later */ - } - buf_inc_changedtick(buf); -} - -/* * check_status: called when the status bars for the buffer 'buf' * need to be updated */ @@ -2213,55 +533,6 @@ void check_status(buf_T *buf) } } -/* - * If the file is readonly, give a warning message with the first change. - * Don't do this for autocommands. - * Don't use emsg(), because it flushes the macro buffer. - * If we have undone all changes b_changed will be false, but "b_did_warn" - * will be true. - * Careful: may trigger autocommands that reload the buffer. - */ -void -change_warning ( - int col /* column for message; non-zero when in insert - mode and 'showmode' is on */ -) -{ - static char *w_readonly = N_("W10: Warning: Changing a readonly file"); - - if (curbuf->b_did_warn == false - && curbufIsChanged() == 0 - && !autocmd_busy - && curbuf->b_p_ro) { - ++curbuf_lock; - apply_autocmds(EVENT_FILECHANGEDRO, NULL, NULL, FALSE, curbuf); - --curbuf_lock; - if (!curbuf->b_p_ro) - return; - /* - * Do what msg() does, but with a column offset if the warning should - * be after the mode message. - */ - msg_start(); - if (msg_row == Rows - 1) - msg_col = col; - msg_source(HL_ATTR(HLF_W)); - msg_ext_set_kind("wmsg"); - MSG_PUTS_ATTR(_(w_readonly), HL_ATTR(HLF_W) | MSG_HIST); - set_vim_var_string(VV_WARNINGMSG, _(w_readonly), -1); - msg_clr_eos(); - (void)msg_end(); - if (msg_silent == 0 && !silent_mode && ui_active()) { - ui_flush(); - os_delay(1000L, true); /* give the user time to think about it */ - } - curbuf->b_did_warn = true; - redraw_cmdline = FALSE; /* don't redraw and erase the message */ - if (msg_row < Rows - 1) - showmode(); - } -} - /// Ask for a reply from the user, 'y' or 'n' /// /// No other characters are accepted, the message is repeated until a valid @@ -2503,18 +774,24 @@ int prompt_for_number(int *mouse_used) cmdline_row = 0; save_State = State; State = ASKMORE; // prevents a screen update when using a timer + // May show different mouse shape. + setmouse(); i = get_number(TRUE, mouse_used); if (KeyTyped) { - /* don't call wait_return() now */ - /* msg_putchar('\n'); */ - cmdline_row = msg_row - 1; - need_wait_return = FALSE; - msg_didany = FALSE; - msg_didout = FALSE; - } else + // don't call wait_return() now + if (msg_row > 0) { + cmdline_row = msg_row - 1; + } + need_wait_return = false; + msg_didany = false; + msg_didout = false; + } else { cmdline_row = save_cmdline_row; + } State = save_State; + // May need to restore mouse shape. + setmouse(); return i; } @@ -2720,7 +997,7 @@ void preserve_exit(void) */ #ifndef BREAKCHECK_SKIP -# define BREAKCHECK_SKIP 32 +# define BREAKCHECK_SKIP 1000 #endif static int breakcheck_count = 0; @@ -2821,7 +1098,7 @@ char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags, xfree(command); // read the names from the file into memory - FILE *fd = mch_fopen((char *)tempname, READBIN); + FILE *fd = os_fopen((char *)tempname, READBIN); if (fd == NULL) { EMSG2(_(e_notopen), tempname); diff --git a/src/nvim/move.c b/src/nvim/move.c index e076543614..4a87f82eb7 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -103,7 +103,8 @@ void reset_cursorline(void) } // Redraw when w_cline_row changes and 'relativenumber' or 'cursorline' is set. -static void redraw_for_cursorline(win_T *wp) +void redraw_for_cursorline(win_T *wp) + FUNC_ATTR_NONNULL_ALL { if ((wp->w_p_rnu || win_cursorline_standout(wp)) && (wp->w_valid & VALID_CROW) == 0 @@ -119,11 +120,9 @@ static void redraw_for_cursorline(win_T *wp) // the current window. redrawWinline(wp, wp->w_last_cursorline); redrawWinline(wp, wp->w_cursor.lnum); - redraw_win_later(wp, VALID); } else { redraw_win_later(wp, SOME_VALID); } - wp->w_last_cursorline = wp->w_cursor.lnum; } } } @@ -847,11 +846,11 @@ void curs_columns( prev_skipcol = curwin->w_skipcol; - int p_lines = 0; + int plines = 0; if ((curwin->w_wrow >= curwin->w_height_inner || ((prev_skipcol > 0 || curwin->w_wrow + p_so >= curwin->w_height_inner) - && (p_lines = + && (plines = plines_win_nofill(curwin, curwin->w_cursor.lnum, false)) - 1 >= curwin->w_height_inner)) && curwin->w_height_inner != 0 @@ -870,20 +869,21 @@ void curs_columns( extra = 1; /* Compute last display line of the buffer line that we want at the * bottom of the window. */ - if (p_lines == 0) - p_lines = plines_win(curwin, curwin->w_cursor.lnum, false); - --p_lines; - if (p_lines > curwin->w_wrow + p_so) { + if (plines == 0) { + plines = plines_win(curwin, curwin->w_cursor.lnum, false); + } + plines--; + if (plines > curwin->w_wrow + p_so) { assert(p_so <= INT_MAX); n = curwin->w_wrow + (int)p_so; + } else { + n = plines; } - else - n = p_lines; if ((colnr_T)n >= curwin->w_height_inner + curwin->w_skipcol / width) { extra += 2; } - if (extra == 3 || p_lines < p_so * 2) { + if (extra == 3 || plines < p_so * 2) { // not enough room for 'scrolloff', put cursor in the middle n = curwin->w_virtcol / width; if (n > curwin->w_height_inner / 2) { @@ -892,8 +892,8 @@ void curs_columns( n = 0; } // don't skip more than necessary - if (n > p_lines - curwin->w_height_inner + 1) { - n = p_lines - curwin->w_height_inner + 1; + if (n > plines - curwin->w_height_inner + 1) { + n = plines - curwin->w_height_inner + 1; } curwin->w_skipcol = n * width; } else if (extra == 1) { @@ -1245,12 +1245,12 @@ static void botline_forw(lineoff_T *lp) } else { ++lp->lnum; lp->fill = 0; - if (lp->lnum > curbuf->b_ml.ml_line_count) + if (lp->lnum > curbuf->b_ml.ml_line_count) { lp->height = MAXCOL; - else if (hasFolding(lp->lnum, NULL, &lp->lnum)) - /* Add a closed fold */ + } else if (hasFolding(lp->lnum, NULL, &lp->lnum)) { + // Add a closed fold lp->height = 1; - else { + } else { lp->height = plines_nofill(lp->lnum); } } diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c index c1ad3bd829..81c9f1e3f4 100644 --- a/src/nvim/msgpack_rpc/channel.c +++ b/src/nvim/msgpack_rpc/channel.c @@ -344,7 +344,7 @@ static void handle_request(Channel *channel, msgpack_object *request) evdata->args = args; evdata->request_id = request_id; channel_incref(channel); - if (handler.async) { + if (handler.fast) { bool is_get_mode = handler.fn == handle_nvim_get_mode; if (is_get_mode && !input_blocking()) { diff --git a/src/nvim/msgpack_rpc/helpers.c b/src/nvim/msgpack_rpc/helpers.c index 3f768dcc0c..18b0bf3c16 100644 --- a/src/nvim/msgpack_rpc/helpers.c +++ b/src/nvim/msgpack_rpc/helpers.c @@ -384,10 +384,7 @@ void msgpack_rpc_from_object(const Object result, msgpack_packer *const res) && kObjectTypeTabpage == kObjectTypeWindow + 1, "Buffer, window and tabpage enum items are in order"); switch (cur.aobj->type) { - case kObjectTypeNil: { - msgpack_pack_nil(res); - break; - } + case kObjectTypeNil: case kObjectTypeLuaRef: { // TODO(bfredl): could also be an error. Though kObjectTypeLuaRef // should only appear when the caller has opted in to handle references, diff --git a/src/nvim/normal.c b/src/nvim/normal.c index af2d24e9db..73841cf449 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -18,6 +18,7 @@ #include "nvim/ascii.h" #include "nvim/normal.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/diff.h" @@ -1376,9 +1377,8 @@ static void set_vcount_ca(cmdarg_T *cap, bool *set_prevcount) *set_prevcount = false; /* only set v:prevcount once */ } -/* - * Handle an operator after visual mode or when the movement is finished - */ +// Handle an operator after Visual mode or when the movement is finished. +// "gui_yank" is true when yanking text for the clipboard. void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) { oparg_T *oap = cap->oap; @@ -1402,8 +1402,12 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) * If an operation is pending, handle it... */ if ((finish_op - || VIsual_active - ) && oap->op_type != OP_NOP) { + || VIsual_active) + && oap->op_type != OP_NOP) { + // Yank can be redone when 'y' is in 'cpoptions', but not when yanking + // for the clipboard. + const bool redo_yank = vim_strchr(p_cpo, CPO_YANK) != NULL && !gui_yank; + // Avoid a problem with unwanted linebreaks in block mode if (curwin->w_p_lbr) { curwin->w_valid &= ~VALID_VIRTCOL; @@ -1433,9 +1437,9 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) VIsual_reselect = false; } - /* Only redo yank when 'y' flag is in 'cpoptions'. */ - /* Never redo "zf" (define fold). */ - if ((vim_strchr(p_cpo, CPO_YANK) != NULL || oap->op_type != OP_YANK) + // Only redo yank when 'y' flag is in 'cpoptions'. + // Never redo "zf" (define fold). + if ((redo_yank || oap->op_type != OP_YANK) && ((!VIsual_active || oap->motion_force) // Also redo Operator-pending Visual mode mappings. || (cap->cmdchar == ':' && oap->op_type != OP_COLON)) @@ -1608,8 +1612,8 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) resel_VIsual_line_count = oap->line_count; } - /* can't redo yank (unless 'y' is in 'cpoptions') and ":" */ - if ((vim_strchr(p_cpo, CPO_YANK) != NULL || oap->op_type != OP_YANK) + // can't redo yank (unless 'y' is in 'cpoptions') and ":" + if ((redo_yank || oap->op_type != OP_YANK) && oap->op_type != OP_COLON && oap->op_type != OP_FOLD && oap->op_type != OP_FOLDOPEN @@ -1814,7 +1818,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) } } else { curwin->w_p_lbr = lbr_saved; - (void)op_yank(oap, !gui_yank); + (void)op_yank(oap, !gui_yank, false); } check_cursor_col(); break; @@ -2093,13 +2097,15 @@ static void op_function(oparg_T *oap) decl(&curbuf->b_op_end); } - const char_u *const argv[1] = { - (const char_u *)(((const char *const[]) { + typval_T argv[2]; + argv[0].v_type = VAR_STRING; + argv[1].v_type = VAR_UNKNOWN; + argv[0].vval.v_string = + (char_u *)(((const char *const[]) { [kMTBlockWise] = "block", [kMTLineWise] = "line", [kMTCharWise] = "char", - })[oap->motion_type]), - }; + })[oap->motion_type]); // Reset virtual_op so that 'virtualedit' can be changed in the // function. @@ -3386,8 +3392,8 @@ bool add_to_showcmd(int c) void add_to_showcmd_c(int c) { - if (!add_to_showcmd(c)) - setcursor(); + add_to_showcmd(c); + setcursor(); } /* @@ -3448,18 +3454,18 @@ static void display_showcmd(void) return; } + int showcmd_row = Rows - 1; + grid_puts_line_start(&default_grid, showcmd_row); + if (!showcmd_is_clear) { - grid_puts(&default_grid, showcmd_buf, (int)Rows - 1, sc_col, 0); + grid_puts(&default_grid, showcmd_buf, showcmd_row, sc_col, 0); } - /* - * clear the rest of an old message by outputting up to SHOWCMD_COLS - * spaces - */ - grid_puts(&default_grid, (char_u *)" " + len, (int)Rows - 1, + // clear the rest of an old message by outputting up to SHOWCMD_COLS spaces + grid_puts(&default_grid, (char_u *)" " + len, showcmd_row, sc_col + len, 0); - setcursor(); /* put cursor back where it belongs */ + grid_puts_line_flush(false); } /* @@ -4035,6 +4041,9 @@ static void nv_mousescroll(cmdarg_T *cap) } else { mouse_scroll_horiz(cap->arg); } + if (curwin != old_curwin && curwin->w_p_cul) { + redraw_for_cursorline(curwin); + } curwin->w_redr_status = true; @@ -5227,12 +5236,8 @@ static void nv_down(cmdarg_T *cap) cap->arg = FORWARD; nv_page(cap); } else if (bt_quickfix(curbuf) && cap->cmdchar == CAR) { - // In a quickfix window a <CR> jumps to the error under the cursor. - if (curwin->w_llist_ref == NULL) { - do_cmdline_cmd(".cc"); // quickfix window - } else { - do_cmdline_cmd(".ll"); // location list window - } + // Quickfix window only: view the result under the cursor. + qf_view_result(false); } else { // In the cmdline window a <CR> executes the command. if (cmdwin_type != 0 && cap->cmdchar == CAR) { @@ -7463,8 +7468,12 @@ static void nv_esc(cmdarg_T *cap) && cmdwin_type == 0 && !VIsual_active && no_reason) { - MSG(_("Type :qa! and press <Enter> to abandon all changes" - " and exit Nvim")); + if (anyBufIsChanged()) { + MSG(_("Type :qa! and press <Enter> to abandon all changes" + " and exit Nvim")); + } else { + MSG(_("Type :qa and press <Enter> to exit Nvim")); + } } /* Don't reset "restart_edit" when 'insertmode' is set, it won't be @@ -7987,8 +7996,8 @@ static void nv_event(cmdarg_T *cap) multiqueue_process_events(main_loop.events); finish_op = false; if (may_restart) { - // Tricky: if restart_edit was set before the handler we are in ctrl-o mode - // but if not, the event should be allow to trigger :startinsert + // Tricky: if restart_edit was set before the handler we are in ctrl-o mode, + // but if not, the event should be allowed to trigger :startinsert. cap->retval |= CA_COMMAND_BUSY; // don't call edit() now } } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 2a3b7beb8e..4f1709bb1f 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -15,6 +15,7 @@ #include "nvim/ascii.h" #include "nvim/ops.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/assert.h" @@ -379,8 +380,10 @@ static void shift_block(oparg_T *oap, int amount) /* if we're splitting a TAB, allow for it */ bd.textcol -= bd.pre_whitesp_c - (bd.startspaces != 0); const int len = (int)STRLEN(bd.textstart) + 1; - newp = (char_u *)xmalloc((size_t)(bd.textcol + i + j + len)); - memset(newp, NUL, (size_t)(bd.textcol + i + j + len)); + int col = bd.textcol + i +j + len; + assert(col >= 0); + newp = (char_u *)xmalloc((size_t)col); + memset(newp, NUL, (size_t)col); memmove(newp, oldp, (size_t)bd.textcol); memset(newp + bd.textcol, TAB, (size_t)i); memset(newp + bd.textcol + i, ' ', (size_t)j); @@ -1398,9 +1401,11 @@ int op_delete(oparg_T *oap) */ if (oap->regname != '_') { yankreg_T *reg = NULL; + int did_yank = false; if (oap->regname != 0) { - //yank without message - if (!op_yank(oap, false)) { + // yank without message + did_yank = op_yank(oap, false, true); + if (!did_yank) { // op_yank failed, don't do anything return OK; } @@ -1421,6 +1426,7 @@ int op_delete(oparg_T *oap) y_regs[1].y_array = NULL; // set register "1 to empty reg = &y_regs[1]; op_yank_reg(oap, false, reg, false); + did_yank = true; } /* Yank into small delete register when no named register specified @@ -1429,13 +1435,14 @@ int op_delete(oparg_T *oap) && oap->line_count == 1) { reg = get_yank_register('-', YREG_YANK); op_yank_reg(oap, false, reg, false); + did_yank = true; } - if (oap->regname == 0) { + if (did_yank || oap->regname == 0) { if (reg == NULL) { abort(); } - set_clipboard(0, reg); + set_clipboard(oap->regname, reg); do_autocmd_textyankpost(oap, reg); } @@ -1471,7 +1478,8 @@ int op_delete(oparg_T *oap) // copy up to deleted part memmove(newp, oldp, (size_t)bd.textcol); // insert spaces - memset(newp + bd.textcol, ' ', (size_t)(bd.startspaces + bd.endspaces)); + memset(newp + bd.textcol, ' ', (size_t)bd.startspaces + + (size_t)bd.endspaces); // copy the part after the deleted part oldp += bd.textcol + bd.textlen; STRMOVE(newp + bd.textcol + bd.startspaces + bd.endspaces, oldp); @@ -1638,12 +1646,25 @@ static void mb_adjust_opend(oparg_T *oap) /* * Put character 'c' at position 'lp' */ -static inline void pchar(pos_T lp, int c) +static inline void pbyte(pos_T lp, int c) { assert(c <= UCHAR_MAX); *(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char_u)c; } +// Replace the character under the cursor with "c". +// This takes care of multi-byte characters. +static void replace_character(int c) +{ + const int n = State; + + State = REPLACE; + ins_char(c); + State = n; + // Backup to the replaced character. + dec_cursor(); +} + /* * Replace a whole area with one character. */ @@ -1730,7 +1751,7 @@ int op_replace(oparg_T *oap, int c) oldp = get_cursor_line_ptr(); oldlen = (int)STRLEN(oldp); - size_t newp_size = (size_t)(bd.textcol + bd.startspaces); + size_t newp_size = (size_t)bd.textcol + (size_t)bd.startspaces; if (had_ctrl_v_cr || (c != '\r' && c != '\n')) { newp_size += (size_t)numc; if (!bd.is_short) { @@ -1747,6 +1768,8 @@ int op_replace(oparg_T *oap, int c) // insert replacement chars CHECK FOR ALLOCATED SPACE // REPLACE_CR_NCHAR/REPLACE_NL_NCHAR is used for entering CR literally. size_t after_p_len = 0; + int col = oldlen - bd.textcol - bd.textlen + 1; + assert(col >= 0); if (had_ctrl_v_cr || (c != '\r' && c != '\n')) { // strlen(newp) at this point int newp_len = bd.textcol + bd.startspaces; @@ -1758,12 +1781,11 @@ int op_replace(oparg_T *oap, int c) memset(newp + newp_len, ' ', (size_t)bd.endspaces); newp_len += bd.endspaces; // copy the part after the changed part - memmove(newp + newp_len, oldp, - (size_t)(oldlen - bd.textcol - bd.textlen + 1)); + memmove(newp + newp_len, oldp, (size_t)col); } } else { // Replacing with \r or \n means splitting the line. - after_p_len = (size_t)(oldlen - bd.textcol - bd.textlen + 1); + after_p_len = (size_t)col; after_p = (char_u *)xmalloc(after_p_len); memmove(after_p, oldp, after_p_len); } @@ -1795,12 +1817,7 @@ int op_replace(oparg_T *oap, int c) * with a multi-byte and the other way around. */ if (curwin->w_cursor.lnum == oap->end.lnum) oap->end.col += (*mb_char2len)(c) - (*mb_char2len)(n); - n = State; - State = REPLACE; - ins_char(c); - State = n; - /* Backup to the replaced character. */ - dec_cursor(); + replace_character(c); } else { if (n == TAB) { int end_vcol = 0; @@ -1815,7 +1832,7 @@ int op_replace(oparg_T *oap, int c) if (curwin->w_cursor.lnum == oap->end.lnum) getvpos(&oap->end, end_vcol); } - pchar(curwin->w_cursor, c); + pbyte(curwin->w_cursor, c); } } else if (virtual_op && curwin->w_cursor.lnum == oap->end.lnum) { int virtcols = oap->end.coladd; @@ -1830,9 +1847,14 @@ int op_replace(oparg_T *oap, int c) coladvance_force(getviscol2(oap->end.col, oap->end.coladd) + 1); curwin->w_cursor.col -= (virtcols + 1); for (; virtcols >= 0; virtcols--) { - pchar(curwin->w_cursor, c); - if (inc(&curwin->w_cursor) == -1) + if (utf_char2len(c) > 1) { + replace_character(c); + } else { + pbyte(curwin->w_cursor, c); + } + if (inc(&curwin->w_cursor) == -1) { break; + } } } @@ -1953,23 +1975,20 @@ static int swapchars(int op_type, pos_T *pos, int length) return did_change; } -/* - * If op_type == OP_UPPER: make uppercase, - * if op_type == OP_LOWER: make lowercase, - * if op_type == OP_ROT13: do rot13 encoding, - * else swap case of character at 'pos' - * returns TRUE when something actually changed. - */ -int swapchar(int op_type, pos_T *pos) +// If op_type == OP_UPPER: make uppercase, +// if op_type == OP_LOWER: make lowercase, +// if op_type == OP_ROT13: do rot13 encoding, +// else swap case of character at 'pos' +// returns true when something actually changed. +bool swapchar(int op_type, pos_T *pos) + FUNC_ATTR_NONNULL_ARG(2) { - int c; - int nc; - - c = gchar_pos(pos); + const int c = gchar_pos(pos); - /* Only do rot13 encoding for ASCII characters. */ - if (c >= 0x80 && op_type == OP_ROT13) - return FALSE; + // Only do rot13 encoding for ASCII characters. + if (c >= 0x80 && op_type == OP_ROT13) { + return false; + } if (op_type == OP_UPPER && c == 0xdf) { pos_T sp = curwin->w_cursor; @@ -1983,7 +2002,7 @@ int swapchar(int op_type, pos_T *pos) inc(pos); } - nc = c; + int nc = c; if (mb_islower(c)) { if (op_type == OP_ROT13) { nc = ROT13(c, 'a'); @@ -1998,7 +2017,7 @@ int swapchar(int op_type, pos_T *pos) } } if (nc != c) { - if (enc_utf8 && (c >= 0x80 || nc >= 0x80)) { + if (c >= 0x80 || nc >= 0x80) { pos_T sp = curwin->w_cursor; curwin->w_cursor = *pos; @@ -2006,11 +2025,12 @@ int swapchar(int op_type, pos_T *pos) del_bytes(utf_ptr2len(get_cursor_pos_ptr()), FALSE, FALSE); ins_char(nc); curwin->w_cursor = sp; - } else - pchar(*pos, nc); - return TRUE; + } else { + pbyte(*pos, nc); + } + return true; } - return FALSE; + return false; } /* @@ -2361,8 +2381,9 @@ void free_register(yankreg_T *reg) /// /// @param oap operator arguments /// @param message show message when more than `&report` lines are yanked. +/// @param deleting whether the function was called from a delete operation. /// @returns whether the operation register was writable. -bool op_yank(oparg_T *oap, bool message) +bool op_yank(oparg_T *oap, bool message, int deleting) FUNC_ATTR_NONNULL_ALL { // check for read-only register @@ -2376,8 +2397,11 @@ bool op_yank(oparg_T *oap, bool message) yankreg_T *reg = get_yank_register(oap->regname, YREG_YANK); op_yank_reg(oap, message, reg, is_append_register(oap->regname)); - set_clipboard(oap->regname, reg); - do_autocmd_textyankpost(oap, reg); + // op_delete will set_clipboard and do_autocmd + if (!deleting) { + set_clipboard(oap->regname, reg); + do_autocmd_textyankpost(oap, reg); + } return true; } @@ -2496,9 +2520,9 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) endcol = (colnr_T)STRLEN(p); if (startcol > endcol || is_oneChar - ) + ) { bd.textlen = 0; - else { + } else { bd.textlen = endcol - startcol + oap->inclusive; } bd.textstart = p + startcol; @@ -2591,8 +2615,9 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append) static void yank_copy_line(yankreg_T *reg, struct block_def *bd, size_t y_idx) { - char_u *pnew = xmallocz((size_t)(bd->startspaces + bd->endspaces - + bd->textlen)); + int size = bd->startspaces + bd->endspaces + bd->textlen; + assert(size >= 0); + char_u *pnew = xmallocz((size_t)size); reg->y_array[y_idx] = pnew; memset(pnew, ' ', (size_t)bd->startspaces); pnew += bd->startspaces; @@ -3074,8 +3099,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) memset(ptr, ' ', (size_t)bd.endspaces); ptr += bd.endspaces; // move the text after the cursor to the end of the line. - memmove(ptr, oldp + bd.textcol + delcount, - (size_t)((int)oldlen - bd.textcol - delcount + 1)); + int columns = (int)oldlen - bd.textcol - delcount + 1; + assert(columns >= 0); + memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns); ml_replace(curwin->w_cursor.lnum, newp, false); ++curwin->w_cursor.lnum; @@ -3198,11 +3224,11 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) xfree(newp); oldp = ml_get(lnum); - newp = (char_u *) xmalloc((size_t)(col + yanklen + 1)); - /* copy first part of line */ + newp = (char_u *)xmalloc((size_t)col + (size_t)yanklen + 1); + // copy first part of line memmove(newp, oldp, (size_t)col); - /* append to first line */ - memmove(newp + col, y_array[0], (size_t)(yanklen + 1)); + // append to first line + memmove(newp + col, y_array[0], (size_t)yanklen + 1); ml_replace(lnum, newp, false); curwin->w_cursor.lnum = lnum; @@ -3416,13 +3442,13 @@ void ex_display(exarg_T *eap) msg_putchar(name); MSG_PUTS(" "); - int n = (int)Columns - 6; + int n = Columns - 6; for (size_t j = 0; j < yb->y_size && n > 1; j++) { if (j) { MSG_PUTS_ATTR("^J", attr); n -= 2; } - for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; ++p) { + for (p = yb->y_array[j]; *p && (n -= ptr2cells(p)) >= 0; p++) { // -V1019 NOLINT(whitespace/line_length) clen = (*mb_ptr2len)(p); msg_outtrans_len(p, clen); p += clen - 1; @@ -3499,8 +3525,8 @@ void ex_display(exarg_T *eap) * display a string for do_dis() * truncate at end of screen line */ -static void -dis_msg ( +static void +dis_msg( char_u *p, int skip_esc /* if TRUE, ignore trailing ESC */ ) @@ -3508,7 +3534,7 @@ dis_msg ( int n; int l; - n = (int)Columns - 6; + n = Columns - 6; while (*p != NUL && !(*p == ESC && skip_esc && *(p + 1) == NUL) && (n -= ptr2cells(p)) >= 0) { @@ -3694,11 +3720,11 @@ int do_join(size_t count, } } - /* store the column position before last line */ + // store the column position before last line col = sumsize - currsize - spaces[count - 1]; - /* allocate the space for the new line */ - newp = (char_u *) xmalloc((size_t)(sumsize + 1)); + // allocate the space for the new line + newp = (char_u *)xmalloc((size_t)sumsize + 1); cend = newp + sumsize; *cend = 0; @@ -3738,7 +3764,7 @@ int do_join(size_t count, if (setmark) { // Set the '] mark. curwin->w_buffer->b_op_end.lnum = curwin->w_cursor.lnum; - curwin->w_buffer->b_op_end.col = (colnr_T)STRLEN(newp); + curwin->w_buffer->b_op_end.col = sumsize; } /* Only report the change in the first line here, del_lines() will report @@ -3840,8 +3866,8 @@ static int same_leader(linenr_T lnum, int leader1_len, char_u *leader1_flags, in /* * Implementation of the format operator 'gq'. */ -void -op_format ( +void +op_format( oparg_T *oap, int keep_cursor /* keep cursor on same text char */ ) @@ -3920,8 +3946,8 @@ void op_formatexpr(oparg_T *oap) op_format(oap, FALSE); } -int -fex_format ( +int +fex_format( linenr_T lnum, long count, int c /* character to be inserted */ @@ -3963,8 +3989,8 @@ fex_format ( * Lines after the cursor line are saved for undo, caller must have saved the * first line. */ -void -format_lines ( +void +format_lines( linenr_T line_count, int avoid_fex /* don't use 'formatexpr' */ ) @@ -5461,7 +5487,7 @@ void cursor_pos_info(dict_T *dict) byte_count_cursor = byte_count + line_count_info(ml_get(lnum), &word_count_cursor, &char_count_cursor, - (varnumber_T)(curwin->w_cursor.col + 1), + (varnumber_T)curwin->w_cursor.col + 1, eol_size); } } @@ -5479,8 +5505,10 @@ void cursor_pos_info(dict_T *dict) if (l_VIsual_active) { if (l_VIsual_mode == Ctrl_V && curwin->w_curswant < MAXCOL) { getvcols(curwin, &min_pos, &max_pos, &min_pos.col, &max_pos.col); + int64_t cols; + STRICT_SUB(oparg.end_vcol + 1, oparg.start_vcol, &cols, int64_t); vim_snprintf((char *)buf1, sizeof(buf1), _("%" PRId64 " Cols; "), - (int64_t)(oparg.end_vcol - oparg.start_vcol + 1)); + cols); } else { buf1[0] = NUL; } @@ -5873,33 +5901,45 @@ static inline bool reg_empty(const yankreg_T *const reg) && *(reg->y_array[0]) == NUL)); } -/// Iterate over registerrs +/// Iterate over global registers. +/// +/// @see op_register_iter +const void *op_global_reg_iter(const void *const iter, char *const name, + yankreg_T *const reg, bool *is_unnamed) + FUNC_ATTR_NONNULL_ARG(2, 3, 4) FUNC_ATTR_WARN_UNUSED_RESULT +{ + return op_reg_iter(iter, y_regs, name, reg, is_unnamed); +} + +/// Iterate over registers `regs`. /// /// @param[in] iter Iterator. Pass NULL to start iteration. +/// @param[in] regs Registers list to be iterated. /// @param[out] name Register name. /// @param[out] reg Register contents. /// -/// @return Pointer that needs to be passed to next `op_register_iter` call or +/// @return Pointer that must be passed to next `op_register_iter` call or /// NULL if iteration is over. -const void *op_register_iter(const void *const iter, char *const name, - yankreg_T *const reg, bool *is_unnamed) - FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT +const void *op_reg_iter(const void *const iter, const yankreg_T *const regs, + char *const name, yankreg_T *const reg, + bool *is_unnamed) + FUNC_ATTR_NONNULL_ARG(3, 4, 5) FUNC_ATTR_WARN_UNUSED_RESULT { *name = NUL; const yankreg_T *iter_reg = (iter == NULL - ? &(y_regs[0]) - : (const yankreg_T *const) iter); - while (iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS && reg_empty(iter_reg)) { + ? &(regs[0]) + : (const yankreg_T *const)iter); + while (iter_reg - &(regs[0]) < NUM_SAVED_REGISTERS && reg_empty(iter_reg)) { iter_reg++; } - if (iter_reg - &(y_regs[0]) == NUM_SAVED_REGISTERS || reg_empty(iter_reg)) { + if (iter_reg - &(regs[0]) == NUM_SAVED_REGISTERS || reg_empty(iter_reg)) { return NULL; } - int iter_off = (int)(iter_reg - &(y_regs[0])); + int iter_off = (int)(iter_reg - &(regs[0])); *name = (char)get_register_name(iter_off); *reg = *iter_reg; *is_unnamed = (iter_reg == y_previous); - while (++iter_reg - &(y_regs[0]) < NUM_SAVED_REGISTERS) { + while (++iter_reg - &(regs[0]) < NUM_SAVED_REGISTERS) { if (!reg_empty(iter_reg)) { return (void *) iter_reg; } @@ -5908,7 +5948,7 @@ const void *op_register_iter(const void *const iter, char *const name, } /// Get a number of non-empty registers -size_t op_register_amount(void) +size_t op_reg_amount(void) FUNC_ATTR_WARN_UNUSED_RESULT { size_t ret = 0; @@ -5927,7 +5967,7 @@ size_t op_register_amount(void) /// @param[in] is_unnamed Whether to set the unnamed regiseter to reg /// /// @return true on success, false on failure. -bool op_register_set(const char name, const yankreg_T reg, bool is_unnamed) +bool op_reg_set(const char name, const yankreg_T reg, bool is_unnamed) { int i = op_reg_index(name); if (i == -1) { @@ -5947,7 +5987,7 @@ bool op_register_set(const char name, const yankreg_T reg, bool is_unnamed) /// @param[in] name Register name. /// /// @return Pointer to the register contents or NULL. -const yankreg_T *op_register_get(const char name) +const yankreg_T *op_reg_get(const char name) { int i = op_reg_index(name); if (i == -1) { @@ -5961,7 +6001,7 @@ const yankreg_T *op_register_get(const char name) /// @param[in] name Register name. /// /// @return true on success, false on failure. -bool op_register_set_previous(const char name) +bool op_reg_set_previous(const char name) FUNC_ATTR_WARN_UNUSED_RESULT { int i = op_reg_index(name); diff --git a/src/nvim/option.c b/src/nvim/option.c index 8ff62d5b12..55b69edba0 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -74,6 +74,7 @@ #include "nvim/strings.h" #include "nvim/syntax.h" #include "nvim/ui.h" +#include "nvim/ui_compositor.h" #include "nvim/undo.h" #include "nvim/window.h" #include "nvim/os/os.h" @@ -253,6 +254,7 @@ typedef struct vimoption { #define P_RWINONLY 0x10000000U ///< only redraw current window #define P_NDNAME 0x20000000U ///< only normal dir name chars allowed #define P_UI_OPTION 0x40000000U ///< send option to remote ui +#define P_MLE 0x80000000U ///< under control of 'modelineexpr' #define HIGHLIGHT_INIT \ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText," \ @@ -626,7 +628,11 @@ void set_init_1(void) char *p; # ifdef UNIX if (*names[n] == NUL) { +# ifdef __APPLE__ + p = "/private/tmp"; +# else p = "/tmp"; +# endif mustfree = false; } else # endif @@ -1323,6 +1329,11 @@ int do_set( errmsg = (char_u *)_("E520: Not allowed in a modeline"); goto skip; } + if ((flags & P_MLE) && !p_mle) { + errmsg = (char_u *)_( + "E992: Not allowed in a modeline when 'modelineexpr' is off"); + goto skip; + } // In diff mode some options are overruled. This avoids that // 'foldmethod' becomes "marker" instead of "diff" and that // "wrap" gets set. @@ -2166,10 +2177,8 @@ static char_u *option_expand(int opt_idx, char_u *val) return NameBuff; } -/* - * After setting various option values: recompute variables that depend on - * option values. - */ +// After setting various option values: recompute variables that depend on +// option values. static void didset_options(void) { // initialize the table for 'iskeyword' et.al. @@ -2182,8 +2191,10 @@ static void didset_options(void) (void)opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true); (void)opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true); (void)opt_strings_flags(p_dy, p_dy_values, &dy_flags, true); + (void)opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true); (void)opt_strings_flags(p_tc, p_tc_values, &tc_flags, false); (void)opt_strings_flags(p_ve, p_ve_values, &ve_flags, true); + (void)opt_strings_flags(p_wop, p_wop_values, &wop_flags, true); (void)spell_check_msm(); (void)spell_check_sps(); (void)compile_cap_prog(curwin->w_s); @@ -2640,6 +2651,10 @@ did_set_string_option( if (opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true) != OK) { errmsg = e_invarg; } + } else if (varp == &p_rdb) { // 'redrawdebug' + if (opt_strings_flags(p_rdb, p_rdb_values, &rdb_flags, true) != OK) { + errmsg = e_invarg; + } } else if (varp == &p_sbo) { // 'scrollopt' if (check_opt_strings(p_sbo, p_scbopt_values, true) != OK) { errmsg = e_invarg; @@ -3493,7 +3508,9 @@ static char_u *set_chars_option(win_T *wp, char_u **varp) { int round, i, len, entries; char_u *p, *s; - int c1 = 0, c2 = 0, c3 = 0; + int c1; + int c2 = 0; + int c3 = 0; struct chars_tab { int *cp; ///< char value @@ -3560,7 +3577,7 @@ static char_u *set_chars_option(win_T *wp, char_u **varp) if (STRNCMP(p, tab[i].name, len) == 0 && p[len] == ':' && p[len + 1] != NUL) { - c1 = c2 = c3 = 0; + c2 = c3 = 0; s = p + len + 1; // TODO(bfredl): use schar_T representation and utfc_ptr2len @@ -3752,7 +3769,8 @@ static bool parse_winhl_opt(win_T *wp) size_t nlen = (size_t)(colon-p); char *hi = colon+1; char *commap = xstrchrnul(hi, ','); - int hl_id = syn_check_group((char_u *)hi, (int)(commap-hi)); + int len = (int)(commap-hi); + int hl_id = len ? syn_check_group((char_u *)hi, len) : -1; if (strncmp("Normal", p, nlen) == 0) { w_hl_id_normal = hl_id; @@ -4149,7 +4167,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, char_u *errmsg = NULL; long old_value = *(long *)varp; long old_Rows = Rows; // remember old Rows - long old_Columns = Columns; // remember old Columns long *pp = (long *)varp; // Disallow changing some options from secure mode. @@ -4264,7 +4281,7 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } else if (pp == &curwin->w_p_nuw || pp == &curwin->w_allbuf_opt.wo_nuw) { if (value < 1) { errmsg = e_positive; - } else if (value > 10) { + } else if (value > 20) { errmsg = e_invarg; } } else if (pp == &curbuf->b_p_iminsert || pp == &p_iminsert) { @@ -4381,11 +4398,10 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } } else if (pp == &p_pb) { p_pb = MAX(MIN(p_pb, 100), 0); - if (old_value != 0) { - hl_invalidate_blends(); - } + hl_invalidate_blends(); + pum_grid.blending = (p_pb > 0); if (pum_drawn()) { - pum_recompose(); + pum_redraw(); } } else if (pp == &p_pyx) { if (p_pyx != 0 && p_pyx != 2 && p_pyx != 3) { @@ -4408,40 +4424,50 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } } else if (pp == &curwin->w_p_nuw) { curwin->w_nrwidth_line_count = 0; + } else if (pp == &curwin->w_p_winbl && value != old_value) { + // 'floatblend' + curwin->w_p_winbl = MAX(MIN(curwin->w_p_winbl, 100), 0); + curwin->w_hl_needs_update = true; + curwin->w_grid.blending = curwin->w_p_winbl > 0; } // Check the (new) bounds for Rows and Columns here. - if (Rows < min_rows() && full_screen) { + if (p_lines < min_rows() && full_screen) { if (errbuf != NULL) { vim_snprintf((char *)errbuf, errbuflen, _("E593: Need at least %d lines"), min_rows()); errmsg = errbuf; } - Rows = min_rows(); + p_lines = min_rows(); } - if (Columns < MIN_COLUMNS && full_screen) { + if (p_columns < MIN_COLUMNS && full_screen) { if (errbuf != NULL) { vim_snprintf((char *)errbuf, errbuflen, _("E594: Need at least %d columns"), MIN_COLUMNS); errmsg = errbuf; } - Columns = MIN_COLUMNS; + p_columns = MIN_COLUMNS; } - limit_screen_size(); + // True max size is defined by check_shellsize() + p_lines = MIN(p_lines, INT_MAX); + p_columns = MIN(p_columns, INT_MAX); // If the screen (shell) height has been changed, assume it is the // physical screenheight. - if (old_Rows != Rows || old_Columns != Columns) { + if (p_lines != Rows || p_columns != Columns) { // Changing the screen size is not allowed while updating the screen. if (updating_screen) { *pp = old_value; } else if (full_screen) { - screen_resize((int)Columns, (int)Rows); + screen_resize((int)p_columns, (int)p_lines); } else { + // TODO(bfredl): is this branch ever needed? // Postpone the resizing; check the size and cmdline position for // messages. + Rows = (int)p_lines; + Columns = (int)p_columns; check_shellsize(); if (cmdline_row > Rows - p_ch && Rows > p_ch) { assert(p_ch >= 0 && Rows - p_ch <= INT_MAX); @@ -5027,6 +5053,11 @@ showoptions( // collect the items in items[] item_count = 0; for (p = &options[0]; p->fullname != NULL; p++) { + // apply :filter /pat/ + if (message_filtered((char_u *)p->fullname)) { + continue; + } + varp = NULL; if (opt_flags != 0) { if (p->indir != PV_NONE) { @@ -5054,8 +5085,8 @@ showoptions( * display the items */ if (run == 1) { - assert(Columns <= LONG_MAX - GAP - && Columns + GAP >= LONG_MIN + 3 + assert(Columns <= INT_MAX - GAP + && Columns + GAP >= INT_MIN + 3 && (Columns + GAP - 3) / INC >= INT_MIN && (Columns + GAP - 3) / INC <= INT_MAX); cols = (int)((Columns + GAP - 3) / INC); @@ -5703,6 +5734,7 @@ static char_u *get_varp(vimoption_T *p) case PV_WINHL: return (char_u *)&(curwin->w_p_winhl); case PV_FCS: return (char_u *)&(curwin->w_p_fcs); case PV_LCS: return (char_u *)&(curwin->w_p_lcs); + case PV_WINBL: return (char_u *)&(curwin->w_p_winbl); default: IEMSG(_("E356: get_varp ERROR")); } // always return a valid pointer to avoid a crash! @@ -5782,6 +5814,7 @@ void copy_winopt(winopt_T *from, winopt_T *to) to->wo_winhl = vim_strsave(from->wo_winhl); to->wo_fcs = vim_strsave(from->wo_fcs); to->wo_lcs = vim_strsave(from->wo_lcs); + to->wo_winbl = from->wo_winbl; check_winopt(to); // don't want NULL pointers } @@ -5844,7 +5877,8 @@ void didset_window_options(win_T *wp) briopt_check(wp); set_chars_option(wp, &wp->w_p_fcs); set_chars_option(wp, &wp->w_p_lcs); - parse_winhl_opt(wp); + parse_winhl_opt(wp); // sets w_hl_needs_update also for w_p_winbl + wp->w_grid.blending = wp->w_p_winbl > 0; } diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index a9f44976c9..fa6ebc70e5 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -181,28 +181,29 @@ enum { 0, \ }) -/* characters for p_go: */ -#define GO_ASEL 'a' /* autoselect */ -#define GO_ASELML 'A' /* autoselect modeless selection */ -#define GO_BOT 'b' /* use bottom scrollbar */ -#define GO_CONDIALOG 'c' /* use console dialog */ -#define GO_TABLINE 'e' /* may show tabline */ -#define GO_FORG 'f' /* start GUI in foreground */ -#define GO_GREY 'g' /* use grey menu items */ -#define GO_HORSCROLL 'h' /* flexible horizontal scrolling */ -#define GO_ICON 'i' /* use Vim icon */ -#define GO_LEFT 'l' /* use left scrollbar */ -#define GO_VLEFT 'L' /* left scrollbar with vert split */ -#define GO_MENUS 'm' /* use menu bar */ -#define GO_NOSYSMENU 'M' /* don't source system menu */ -#define GO_POINTER 'p' /* pointer enter/leave callbacks */ -#define GO_ASELPLUS 'P' /* autoselectPlus */ -#define GO_RIGHT 'r' /* use right scrollbar */ -#define GO_VRIGHT 'R' /* right scrollbar with vert split */ -#define GO_TOOLBAR 'T' /* add toolbar */ -#define GO_FOOTER 'F' /* add footer */ -#define GO_VERTICAL 'v' /* arrange dialog buttons vertically */ -#define GO_ALL "aAbcefFghilmMprTv" /* all possible flags for 'go' */ +// characters for p_go: +#define GO_ASEL 'a' // autoselect +#define GO_ASELML 'A' // autoselect modeless selection +#define GO_BOT 'b' // use bottom scrollbar +#define GO_CONDIALOG 'c' // use console dialog +#define GO_TABLINE 'e' // may show tabline +#define GO_FORG 'f' // start GUI in foreground +#define GO_GREY 'g' // use grey menu items +#define GO_HORSCROLL 'h' // flexible horizontal scrolling +#define GO_ICON 'i' // use Vim icon +#define GO_LEFT 'l' // use left scrollbar +#define GO_VLEFT 'L' // left scrollbar with vert split +#define GO_MENUS 'm' // use menu bar +#define GO_NOSYSMENU 'M' // don't source system menu +#define GO_POINTER 'p' // pointer enter/leave callbacks +#define GO_ASELPLUS 'P' // autoselectPlus +#define GO_RIGHT 'r' // use right scrollbar +#define GO_VRIGHT 'R' // right scrollbar with vert split +#define GO_TOOLBAR 'T' // add toolbar +#define GO_FOOTER 'F' // add footer +#define GO_VERTICAL 'v' // arrange dialog buttons vertically +#define GO_KEEPWINSIZE 'k' // keep GUI window size +#define GO_ALL "aAbcefFghilmMprTvk" // all possible flags for 'go' /* flags for 'comments' option */ #define COM_NEST 'n' /* comments strings nest */ @@ -365,6 +366,7 @@ static char *(p_cb_values[]) = {"unnamed", "unnamedplus", NULL}; # define CB_UNNAMEDMASK (CB_UNNAMED | CB_UNNAMEDPLUS) EXTERN long p_cwh; // 'cmdwinheight' EXTERN long p_ch; // 'cmdheight' +EXTERN long p_columns; // 'columns' EXTERN int p_confirm; // 'confirm' EXTERN int p_cp; // 'compatible' EXTERN char_u *p_cot; // 'completeopt' @@ -475,7 +477,8 @@ EXTERN char_u *p_langmap; // 'langmap' EXTERN int p_lnr; // 'langnoremap' EXTERN int p_lrm; // 'langremap' EXTERN char_u *p_lm; // 'langmenu' -EXTERN long *p_linespace; // 'linespace' +EXTERN long p_lines; // 'lines' +EXTERN long p_linespace; // 'linespace' EXTERN char_u *p_lispwords; // 'lispwords' EXTERN long p_ls; // 'laststatus' EXTERN long p_stal; // 'showtabline' @@ -495,6 +498,7 @@ EXTERN long p_mmd; // 'maxmapdepth' EXTERN long p_mmp; // 'maxmempattern' EXTERN long p_mis; // 'menuitems' EXTERN char_u *p_msm; // 'mkspellmem' +EXTERN long p_mle; // 'modelineexpr' EXTERN long p_mls; // 'modelines' EXTERN char_u *p_mouse; // 'mouse' EXTERN char_u *p_mousem; // 'mousemodel' @@ -509,6 +513,13 @@ EXTERN char_u *p_pm; // 'patchmode' EXTERN char_u *p_path; // 'path' EXTERN char_u *p_cdpath; // 'cdpath' EXTERN long p_pyx; // 'pyxversion' +EXTERN char_u *p_rdb; // 'redrawdebug' +EXTERN unsigned rdb_flags; +# ifdef IN_OPTION_C +static char *(p_rdb_values[]) = { "compositor", NULL }; +# endif +# define RDB_COMPOSITOR 0x001 + EXTERN long p_rdt; // 'redrawtime' EXTERN int p_remap; // 'remap' EXTERN long p_re; // 'regexpengine' @@ -814,6 +825,7 @@ enum { , WV_WINHL , WV_FCS , WV_LCS + , WV_WINBL , WV_COUNT // must be the last one }; diff --git a/src/nvim/options.lua b/src/nvim/options.lua index 96e098778c..3280c92b2d 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -8,6 +8,7 @@ -- defaults={condition=nil, if_true={vi=224, vim=0}, if_false=nil}, -- secure=nil, gettext=nil, noglob=nil, normal_fname_chars=nil, -- pri_mkrc=nil, deny_in_modelines=nil, normal_dname_chars=nil, +-- modelineexpr=nil, -- expand=nil, nodefault=nil, no_mkrc=nil, vi_def=true, vim=true, -- alloced=nil, -- save_pv_indir=nil, @@ -39,7 +40,7 @@ local imacros=function(s) return '(intptr_t)' .. s end end -local N_=function(s) +local N_=function(s) -- luacheck: ignore 211 (currently unused) return function() return 'N_(' .. cstr(s) .. ')' end @@ -188,7 +189,6 @@ return { }, { full_name='belloff', abbreviation='bo', - deny_duplicates=true, type='string', list='comma', scope={'global'}, deny_duplicates=true, vi_def=true, @@ -283,6 +283,7 @@ return { deny_duplicates=true, vi_def=true, expand=true, + secure=true, varname='p_cdpath', defaults={if_true={vi=",,"}} }, @@ -378,10 +379,9 @@ return { full_name='columns', abbreviation='co', type='number', scope={'global'}, no_mkrc=true, - nodefault=true, vi_def=true, redraw={'everything'}, - varname='Columns', + varname='p_columns', defaults={if_true={vi=macros('DFLT_COLS')}} }, { @@ -543,7 +543,7 @@ return { full_name='cursorcolumn', abbreviation='cuc', type='bool', scope={'window'}, vi_def=true, - redraw={'current_window'}, + redraw={'current_window_only'}, defaults={if_true={vi=false}} }, { @@ -847,6 +847,7 @@ return { type='string', scope={'window'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, redraw={'current_window'}, defaults={if_true={vi="0"}} @@ -922,6 +923,7 @@ return { type='string', scope={'window'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, redraw={'current_window'}, defaults={if_true={vi="foldtext()"}} @@ -931,6 +933,7 @@ return { type='string', scope={'buffer'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, varname='p_fex', defaults={if_true={vi=""}} @@ -1045,6 +1048,7 @@ return { full_name='guitablabel', abbreviation='gtl', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, redraw={'current_window'}, enable_if=false, }, @@ -1136,6 +1140,7 @@ return { full_name='iconstring', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, varname='p_iconstring', defaults={if_true={vi=""}} }, @@ -1198,6 +1203,7 @@ return { full_name='includeexpr', abbreviation='inex', type='string', scope={'buffer'}, vi_def=true, + modelineexpr=true, alloced=true, varname='p_inex', defaults={if_true={vi=""}} @@ -1214,6 +1220,7 @@ return { type='string', scope={'buffer'}, vi_def=true, vim=true, + modelineexpr=true, alloced=true, varname='p_inde', defaults={if_true={vi=""}} @@ -1376,10 +1383,9 @@ return { full_name='lines', type='number', scope={'global'}, no_mkrc=true, - nodefault=true, vi_def=true, redraw={'everything'}, - varname='Rows', + varname='p_lines', defaults={if_true={vi=macros('DFLT_ROWS')}} }, { @@ -1528,6 +1534,14 @@ return { defaults={if_true={vi=false, vim=true}} }, { + full_name='modelineexpr', abbreviation='mle', + type='bool', scope={'global'}, + vi_def=true, + secure=true, + varname='p_mle', + defaults={if_true={vi=false}} + }, + { full_name='modelines', abbreviation='mls', type='number', scope={'global'}, vi_def=true, @@ -1832,6 +1846,13 @@ return { defaults={if_true={vi=false}} }, { + full_name='redrawdebug', abbreviation='rdb', + type='string', list='onecomma', scope={'global'}, + vi_def=true, + varname='p_rdb', + defaults={if_true={vi=''}} + }, + { full_name='redrawtime', abbreviation='rdt', type='number', scope={'global'}, vi_def=true, @@ -1903,6 +1924,7 @@ return { type='string', scope={'global'}, vi_def=true, alloced=true, + modelineexpr=true, redraw={'statuslines'}, varname='p_ruf', defaults={if_true={vi=""}} @@ -2310,6 +2332,7 @@ return { type='string', scope={'global', 'window'}, vi_def=true, alloced=true, + modelineexpr=true, redraw={'statuslines'}, varname='p_stl', defaults={if_true={vi=""}} @@ -2369,6 +2392,7 @@ return { full_name='tabline', abbreviation='tal', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, redraw={'all_windows'}, varname='p_tal', defaults={if_true={vi=""}} @@ -2528,6 +2552,7 @@ return { full_name='titlestring', type='string', scope={'global'}, vi_def=true, + modelineexpr=true, varname='p_titlestring', defaults={if_true={vi=""}} }, @@ -2729,9 +2754,9 @@ return { full_name='wildoptions', abbreviation='wop', type='string', list='onecomma', scope={'global'}, deny_duplicates=true, - vi_def=true, + vim=true, varname='p_wop', - defaults={if_true={vi=""}} + defaults={if_true={vi='', vim='pum,tagfile'}} }, { full_name='winaltkeys', abbreviation='wak', @@ -2741,6 +2766,13 @@ return { defaults={if_true={vi="menu"}} }, { + full_name='winblend', abbreviation='winbl', + type='number', scope={'window'}, + vi_def=true, + redraw={'current_window'}, + defaults={if_true={vi=0}} + }, + { full_name='winhighlight', abbreviation='winhl', type='string', scope={'window'}, vi_def=true, diff --git a/src/nvim/os/dl.c b/src/nvim/os/dl.c index bbd0424a82..f0fadb16f2 100644 --- a/src/nvim/os/dl.c +++ b/src/nvim/os/dl.c @@ -54,6 +54,7 @@ bool os_libcall(const char *libname, // open the dynamic loadable library if (uv_dlopen(libname, &lib)) { EMSG2(_("dlerror = \"%s\""), uv_dlerror(&lib)); + uv_dlclose(&lib); return false; } diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index 871298f415..62457e155c 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -45,6 +45,7 @@ void env_init(void) } /// Like getenv(), but returns NULL if the variable is empty. +/// @see os_env_exists const char *os_getenv(const char *name) FUNC_ATTR_NONNULL_ALL { @@ -101,10 +102,19 @@ bool os_env_exists(const char *name) assert(r != UV_EINVAL); if (r != 0 && r != UV_ENOENT && r != UV_ENOBUFS) { ELOG("uv_os_getenv(%s) failed: %d %s", name, r, uv_err_name(r)); +#ifdef WIN32 + return (r == UV_UNKNOWN); +#endif } return (r == 0 || r == UV_ENOBUFS); } +/// Sets an environment variable. +/// +/// Windows (Vim-compat): Empty string (:let $FOO="") undefines the env var. +/// +/// @warning Existing pointers to the result of os_getenv("foo") are +/// INVALID after os_setenv("foo", …). int os_setenv(const char *name, const char *value, int overwrite) FUNC_ATTR_NONNULL_ALL { @@ -115,15 +125,21 @@ int os_setenv(const char *name, const char *value, int overwrite) if (!overwrite && os_getenv(name) != NULL) { return 0; } + if (value[0] == '\0') { + // Windows (Vim-compat): Empty string undefines the env var. + return os_unsetenv(name); + } #else if (!overwrite && os_env_exists(name)) { return 0; } #endif uv_mutex_lock(&mutex); - pmap_del2(envmap, name); int r = uv_os_setenv(name, value); assert(r != UV_EINVAL); + // Destroy the old map item. Do this AFTER uv_os_setenv(), because `value` + // could be a previous os_getenv() result. + pmap_del2(envmap, name); if (r != 0) { ELOG("uv_os_setenv(%s) failed: %d %s", name, r, uv_err_name(r)); } @@ -294,6 +310,30 @@ void init_homedir(void) if (var == NULL) { var = os_getenv("USERPROFILE"); } + + // Weird but true: $HOME may contain an indirect reference to another + // variable, esp. "%USERPROFILE%". Happens when $USERPROFILE isn't set + // when $HOME is being set. + if (var != NULL && *var == '%') { + const char *p = strchr(var + 1, '%'); + if (p != NULL) { + vim_snprintf(os_buf, (size_t)(p - var), "%s", var + 1); + const char *exp = os_getenv(os_buf); + if (exp != NULL && *exp != NUL + && STRLEN(exp) + STRLEN(p) < MAXPATHL) { + vim_snprintf(os_buf, MAXPATHL, "%s%s", exp, p + 1); + var = os_buf; + } + } + } + + // Default home dir is C:/ + // Best assumption we can make in such a situation. + if (var == NULL + // Empty means "undefined" + || *var == NUL) { + var = "C:/"; + } #endif if (var != NULL) { @@ -695,17 +735,17 @@ char *vim_getenv(const char *name) // init_path() should have been called before now. assert(get_vim_var_str(VV_PROGPATH)[0] != NUL); - const char *kos_env_path = os_getenv(name); - if (kos_env_path != NULL) { - return xstrdup(kos_env_path); - } - #ifdef WIN32 if (strcmp(name, "HOME") == 0) { return xstrdup(homedir); } #endif + const char *kos_env_path = os_getenv(name); + if (kos_env_path != NULL) { + return xstrdup(kos_env_path); + } + bool vimruntime = (strcmp(name, "VIMRUNTIME") == 0); if (!vimruntime && strcmp(name, "VIM") != 0) { return NULL; diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index d5500b230c..dcb3ef7c4a 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -24,6 +24,7 @@ #include "nvim/message.h" #include "nvim/assert.h" #include "nvim/misc1.h" +#include "nvim/option.h" #include "nvim/path.h" #include "nvim/strings.h" @@ -229,7 +230,7 @@ int os_exepath(char *buffer, size_t *size) /// Checks if the file `name` is executable. /// /// @param[in] name Filename to check. -/// @param[out] abspath Returns resolved executable path, if not NULL. +/// @param[out,allocated] abspath Returns resolved exe path, if not NULL. /// @param[in] use_path Also search $PATH. /// /// @return true if `name` is executable and @@ -238,27 +239,16 @@ int os_exepath(char *buffer, size_t *size) /// - is absolute. /// /// @return `false` otherwise. -bool os_can_exe(const char_u *name, char_u **abspath, bool use_path) +bool os_can_exe(const char *name, char **abspath, bool use_path) FUNC_ATTR_NONNULL_ARG(1) { - bool no_path = !use_path || path_is_absolute(name); - // If the filename is "qualified" (relative or absolute) do not check $PATH. -#ifdef WIN32 - no_path |= (name[0] == '.' - && ((name[1] == '/' || name[1] == '\\') - || (name[1] == '.' && (name[2] == '/' || name[2] == '\\')))); -#else - no_path |= (name[0] == '.' - && (name[1] == '/' || (name[1] == '.' && name[2] == '/'))); -#endif - - if (no_path) { + if (!use_path || gettail_dir(name) != name) { #ifdef WIN32 - if (is_executable_ext((char *)name, abspath)) { + if (is_executable_ext(name, abspath)) { #else // Must have path separator, cannot execute files in the current directory. - if ((const char_u *)gettail_dir((const char *)name) != name - && is_executable((char *)name, abspath)) { + if ((use_path || gettail_dir(name) != name) + && is_executable(name, abspath)) { #endif return true; } else { @@ -270,10 +260,13 @@ bool os_can_exe(const char_u *name, char_u **abspath, bool use_path) } /// Returns true if `name` is an executable file. -static bool is_executable(const char *name, char_u **abspath) +/// +/// @param[in] name Filename to check. +/// @param[out,allocated] abspath Returns full exe path, if not NULL. +static bool is_executable(const char *name, char **abspath) FUNC_ATTR_NONNULL_ARG(1) { - int32_t mode = os_getperm((const char *)name); + int32_t mode = os_getperm(name); if (mode < 0) { return false; @@ -291,7 +284,7 @@ static bool is_executable(const char *name, char_u **abspath) const bool ok = (r == 0); #endif if (ok && abspath != NULL) { - *abspath = save_abs_path((char_u *)name); + *abspath = save_abs_path(name); } return ok; } @@ -300,7 +293,7 @@ static bool is_executable(const char *name, char_u **abspath) /// Checks if file `name` is executable under any of these conditions: /// - extension is in $PATHEXT and `name` is executable /// - result of any $PATHEXT extension appended to `name` is executable -static bool is_executable_ext(char *name, char_u **abspath) +static bool is_executable_ext(char *name, char **abspath) FUNC_ATTR_NONNULL_ARG(1) { const bool is_unix_shell = strstr((char *)path_tail(p_sh), "sh") != NULL; @@ -312,7 +305,8 @@ static bool is_executable_ext(char *name, char_u **abspath) if (!pathext) { pathext = ".com;.exe;.bat;.cmd"; } - for (const char *ext = pathext; *ext; ext++) { + const char *ext = pathext; + while (*ext) { // If $PATHEXT itself contains dot: if (ext[0] == '.' && (ext[1] == '\0' || ext[1] == ENV_SEPCHAR)) { if (is_executable(name, abspath)) { @@ -320,13 +314,17 @@ static bool is_executable_ext(char *name, char_u **abspath) } // Skip it. ext++; + if (*ext) { + ext++; + } continue; } - const char *ext_end = xstrchrnul(ext, ENV_SEPCHAR); - size_t ext_len = (size_t)(ext_end - ext); + const char *ext_end = ext; + size_t ext_len = + copy_option_part((char_u **)&ext_end, (char_u *)buf_end, + sizeof(os_buf) - (size_t)(buf_end - os_buf), ENV_SEPSTR); if (ext_len != 0) { - STRLCPY(buf_end, ext, ext_len + 1); bool in_pathext = nameext_len == ext_len && 0 == mb_strnicmp((char_u *)nameext, (char_u *)ext, ext_len); @@ -347,7 +345,7 @@ static bool is_executable_ext(char *name, char_u **abspath) /// @param[out] abspath Returns resolved executable path, if not NULL. /// /// @return `true` if `name` is an executable inside `$PATH`. -static bool is_executable_in_path(const char_u *name, char_u **abspath) +static bool is_executable_in_path(const char *name, char **abspath) FUNC_ATTR_NONNULL_ARG(1) { const char *path_env = os_getenv("PATH"); @@ -358,13 +356,13 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath) #ifdef WIN32 // Prepend ".;" to $PATH. size_t pathlen = strlen(path_env); - char *path = memcpy(xmallocz(pathlen + 3), "." ENV_SEPSTR, 2); + char *path = memcpy(xmallocz(pathlen + 2), "." ENV_SEPSTR, 2); memcpy(path + 2, path_env, pathlen); #else char *path = xstrdup(path_env); #endif - size_t buf_len = STRLEN(name) + strlen(path) + 2; + size_t buf_len = strlen(name) + strlen(path) + 2; char *buf = xmalloc(buf_len); // Walk through all entries in $PATH to check if "name" exists there and @@ -376,7 +374,7 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath) // Combine the $PATH segment with `name`. STRLCPY(buf, p, e - p + 1); - append_path(buf, (char *)name, buf_len); + append_path(buf, name, buf_len); #ifdef WIN32 if (is_executable_ext(buf, abspath)) { @@ -406,10 +404,11 @@ end: /// calls (read, write, lseek, fcntl, etc.). If the operation fails, a libuv /// error code is returned, and no file is created or modified. /// +/// @param path Filename /// @param flags Bitwise OR of flags defined in <fcntl.h> /// @param mode Permissions for the newly-created file (IGNORED if 'flags' is /// not `O_CREAT` or `O_TMPFILE`), subject to the current umask -/// @return file descriptor, or libuv error code on failure +/// @return file descriptor, or negative error code on failure int os_open(const char *path, int flags, int mode) { if (path == NULL) { // uv_fs_open asserts on NULL. #7561 @@ -420,6 +419,68 @@ int os_open(const char *path, int flags, int mode) return r; } +/// Compatibility wrapper conforming to fopen(3). +/// +/// Windows: works with UTF-16 filepaths by delegating to libuv (os_open). +/// +/// Future: remove this, migrate callers to os/fileio.c ? +/// But file_open_fd does not support O_RDWR yet. +/// +/// @param path Filename +/// @param flags String flags, one of { r w a r+ w+ a+ rb wb ab } +/// @return FILE pointer, or NULL on error. +FILE *os_fopen(const char *path, const char *flags) +{ + assert(flags != NULL && strlen(flags) > 0 && strlen(flags) <= 2); + int iflags = 0; + // Per table in fopen(3) manpage. + if (flags[1] == '\0' || flags[1] == 'b') { + switch (flags[0]) { + case 'r': + iflags = O_RDONLY; + break; + case 'w': + iflags = O_WRONLY | O_CREAT | O_TRUNC; + break; + case 'a': + iflags = O_WRONLY | O_CREAT | O_APPEND; + break; + default: + abort(); + } +#ifdef WIN32 + if (flags[1] == 'b') { + iflags |= O_BINARY; + } +#endif + } else { + // char 0 must be one of ('r','w','a'). + // char 1 is always '+' ('b' is handled above). + assert(flags[1] == '+'); + switch (flags[0]) { + case 'r': + iflags = O_RDWR; + break; + case 'w': + iflags = O_RDWR | O_CREAT | O_TRUNC; + break; + case 'a': + iflags = O_RDWR | O_CREAT | O_APPEND; + break; + default: + abort(); + } + } + // Per open(2) manpage. + assert((iflags|O_RDONLY) || (iflags|O_WRONLY) || (iflags|O_RDWR)); + // Per fopen(3) manpage: default to 0666, it will be umask-adjusted. + int fd = os_open(path, iflags, 0666); + if (fd < 0) { + return NULL; + } + return fdopen(fd, flags); +} + /// Sets file descriptor `fd` to close-on-exec. // // @return -1 if failed to set, 0 otherwise. @@ -746,6 +807,22 @@ bool os_path_exists(const char_u *path) return os_stat((char *)path, &statbuf) == kLibuvSuccess; } +/// Sets file access and modification times. +/// +/// @see POSIX utime(2) +/// +/// @param path File path. +/// @param atime Last access time. +/// @param mtime Last modification time. +/// +/// @return 0 on success, or negative error code. +int os_file_settime(const char *path, double atime, double mtime) +{ + int r; + RUN_UV_FS_FUNC(r, uv_fs_utime, path, atime, mtime, NULL); + return r; +} + /// Check if a file is readable. /// /// @return true if `name` is readable, otherwise false. @@ -815,12 +892,12 @@ int os_mkdir_recurse(const char *const dir, int32_t mode, // We're done when it's "/" or "c:/". const size_t dirlen = strlen(dir); char *const curdir = xmemdupz(dir, dirlen); - char *const past_head = (char *) get_past_head((char_u *) curdir); + char *const past_head = (char *)get_past_head((char_u *)curdir); char *e = curdir + dirlen; const char *const real_end = e; const char past_head_save = *past_head; - while (!os_isdir((char_u *) curdir)) { - e = (char *) path_tail_with_sep((char_u *) curdir); + while (!os_isdir((char_u *)curdir)) { + e = (char *)path_tail_with_sep((char_u *)curdir); if (e <= past_head) { *past_head = NUL; break; @@ -972,7 +1049,7 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info) /// /// @return `true` if the two FileInfos represent the same file. bool os_fileinfo_id_equal(const FileInfo *file_info_1, - const FileInfo *file_info_2) + const FileInfo *file_info_2) FUNC_ATTR_NONNULL_ALL { return file_info_1->stat.st_ino == file_info_2->stat.st_ino diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 8070f4c420..95e9e8e414 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -50,6 +50,11 @@ void input_init(void) input_buffer = rbuffer_new(INPUT_BUFFER_SIZE + MAX_KEY_CODE_LEN); } +void input_global_fd_init(int fd) +{ + global_fd = fd; +} + /// Global TTY (or pipe for "-es") input stream, before UI starts. int input_global_fd(void) { @@ -62,7 +67,7 @@ void input_start(int fd) return; } - global_fd = fd; + input_global_fd_init(fd); rstream_init_fd(&main_loop, &read_stream, fd, READ_BUFFER_SIZE); rstream_start(&read_stream, input_read_cb, NULL); } @@ -316,10 +321,10 @@ static unsigned int handle_mouse_event(char **ptr, uint8_t *buf, // Make sure the mouse position is valid. Some terminals may // return weird values. if (col >= Columns) { - col = (int)Columns - 1; + col = Columns - 1; } if (row >= Rows) { - row = (int)Rows - 1; + row = Rows - 1; } mouse_grid = 0; mouse_row = row; diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c index a1020be215..c7b473a012 100644 --- a/src/nvim/os/process.c +++ b/src/nvim/os/process.c @@ -89,21 +89,12 @@ bool os_proc_tree_kill(int pid, int sig) bool os_proc_tree_kill(int pid, int sig) { assert(sig == SIGTERM || sig == SIGKILL); - int pgid = getpgid(pid); - if (pgid > 0) { // Ignore error. Never kill self (pid=0). - if (pgid == pid) { - ILOG("sending %s to process group: -%d", - sig == SIGTERM ? "SIGTERM" : "SIGKILL", pgid); - int rv = uv_kill(-pgid, sig); - return rv == 0; - } else { - // Should never happen, because process_spawn() did setsid() in the child. - ELOG("pgid %d != pid %d", pgid, pid); - } - } else { - ELOG("getpgid(%d) returned %d", pid, pgid); + if (pid == 0) { + // Never kill self (pid=0). + return false; } - return false; + ILOG("sending %s to PID %d", sig == SIGTERM ? "SIGTERM" : "SIGKILL", -pid); + return uv_kill(-pid, sig) == 0; } #endif diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 5fdf0e6181..f0bc13783c 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -288,7 +288,7 @@ static void chld_handler(uv_signal_t *handle, int signum) if (WIFEXITED(stat)) { proc->status = WEXITSTATUS(stat); } else if (WIFSIGNALED(stat)) { - proc->status = WTERMSIG(stat); + proc->status = 128 + WTERMSIG(stat); } proc->internal_exit_cb(proc); } diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index c5f8efadff..290668bca3 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -252,7 +252,7 @@ static void pty_process_finish2(PtyProcess *ptyproc) DWORD exit_code = 0; GetExitCodeProcess(ptyproc->process_handle, &exit_code); - proc->status = (int)exit_code; + proc->status = proc->exit_signal ? 128 + proc->exit_signal : (int)exit_code; CloseHandle(ptyproc->process_handle); ptyproc->process_handle = NULL; diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index 31ef1a0cd6..4dd0614fe2 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -9,6 +9,7 @@ #include <uv.h> +#include "nvim/assert.h" #include "nvim/os/time.h" #include "nvim/os/input.h" #include "nvim/event/loop.h" @@ -22,6 +23,7 @@ static uv_cond_t delay_cond; #ifdef INCLUDE_GENERATED_DECLARATIONS # include "os/time.c.generated.h" #endif + /// Initializes the time module void time_init(void) { @@ -29,15 +31,32 @@ void time_init(void) uv_cond_init(&delay_cond); } -/// Obtain a high-resolution timer value +/// Gets a high-resolution (nanosecond), monotonically-increasing time relative +/// to an arbitrary time in the past. +/// +/// Not related to the time of day and therefore not subject to clock drift. /// -/// @return a timer value, not related to the time of day and not subject -/// to clock drift. The value is expressed in nanoseconds. +/// @return Relative time value with nanosecond precision. uint64_t os_hrtime(void) + FUNC_ATTR_WARN_UNUSED_RESULT { return uv_hrtime(); } +/// Gets a millisecond-resolution, monotonically-increasing time relative to an +/// arbitrary time in the past. +/// +/// Not related to the time of day and therefore not subject to clock drift. +/// The value is cached by the loop, it will not change until the next +/// loop-tick (unless uv_update_time is called). +/// +/// @return Relative time value with millisecond precision. +uint64_t os_now(void) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + return uv_now(&main_loop.uv); +} + /// Sleeps for `ms` milliseconds. /// /// @param ms Number of milliseconds to sleep diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index 35a7942059..ded575529f 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -369,10 +369,7 @@ int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file, // With interactive completion, the error message is not printed. if (!(flags & EW_SILENT)) { msg_putchar('\n'); // clear bottom line quickly -#if SIZEOF_LONG > SIZEOF_INT - assert(Rows <= (long)INT_MAX + 1); -#endif - cmdline_row = (int)(Rows - 1); // continue on last line + cmdline_row = Rows - 1; // continue on last line MSG(_(e_wildexpand)); msg_start(); // don't overwrite this message } @@ -538,7 +535,7 @@ int mch_expand_wildcards(int num_pat, char_u **pat, int *num_file, // Skip files that are not executable if we check for that. if (!dir && (flags & EW_EXEC) - && !os_can_exe((*file)[i], NULL, !(flags & EW_SHELLCMD))) { + && !os_can_exe((char *)(*file)[i], NULL, !(flags & EW_SHELLCMD))) { continue; } diff --git a/src/nvim/path.c b/src/nvim/path.c index b43a172991..75a26d88c1 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -453,13 +453,13 @@ char *FullName_save(const char *fname, bool force) /// Saves the absolute path. /// @param name An absolute or relative path. /// @return The absolute path of `name`. -char_u *save_abs_path(const char_u *name) +char *save_abs_path(const char *name) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { - if (!path_is_absolute(name)) { - return (char_u *)FullName_save((char *)name, true); + if (!path_is_absolute((char_u *)name)) { + return FullName_save(name, true); } - return vim_strsave((char_u *) name); + return (char *)vim_strsave((char_u *)name); } /// Checks if a path has a wildcard character including '~', unless at the end. @@ -1401,7 +1401,7 @@ void addfile( // If the file isn't executable, may not add it. Do accept directories. // When invoked from expand_shellcmd() do not use $PATH. if (!isdir && (flags & EW_EXEC) - && !os_can_exe(f, NULL, !(flags & EW_SHELLCMD))) { + && !os_can_exe((char *)f, NULL, !(flags & EW_SHELLCMD))) { return; } @@ -2306,7 +2306,7 @@ void path_guess_exepath(const char *argv0, char *buf, size_t bufsize) xstrlcpy((char *)NameBuff, dir, dir_len + 1); xstrlcat((char *)NameBuff, PATHSEPSTR, sizeof(NameBuff)); xstrlcat((char *)NameBuff, argv0, sizeof(NameBuff)); - if (os_can_exe(NameBuff, NULL, false)) { + if (os_can_exe((char *)NameBuff, NULL, false)) { xstrlcpy(buf, (char *)NameBuff, bufsize); return; } diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 58a0008e04..ce40bc15e0 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -376,6 +376,7 @@ void pum_redraw(void) pum_height, grid_width, false, true); bool invalid_grid = moved || pum_invalid; pum_invalid = false; + must_redraw_pum = false; if (!pum_grid.chars || pum_grid.Rows != pum_height || pum_grid.Columns != grid_width) { @@ -412,7 +413,7 @@ void pum_redraw(void) idx = i + pum_first; attr = (idx == pum_selected) ? attr_select : attr_norm; - screen_puts_line_start(row); + grid_puts_line_start(&pum_grid, row); // prepend a space if there is room if (extra_space) { @@ -564,7 +565,7 @@ void pum_redraw(void) ? attr_thumb : attr_scroll); } } - grid_puts_line_flush(&pum_grid, false); + grid_puts_line_flush(false); row++; } } @@ -790,6 +791,7 @@ void pum_undisplay(bool immediate) { pum_is_visible = false; pum_array = NULL; + must_redraw_pum = false; if (immediate) { pum_check_clear(); @@ -853,7 +855,8 @@ int pum_get_height(void) return pum_height; } -void pum_set_boundings(dict_T *dict) +/// Add size information about the pum to "dict". +void pum_set_event_info(dict_T *dict) { if (!pum_visible()) { return; diff --git a/src/nvim/profile.c b/src/nvim/profile.c index 8fb8e92add..0a5030edae 100644 --- a/src/nvim/profile.c +++ b/src/nvim/profile.c @@ -5,6 +5,7 @@ #include <math.h> #include <assert.h> +#include "nvim/assert.h" #include "nvim/profile.h" #include "nvim/os/time.h" #include "nvim/func_attr.h" @@ -16,11 +17,9 @@ # include "profile.c.generated.h" #endif -/// functions for profiling - static proftime_T prof_wait_time; -/// profile_start - return the current time +/// Gets the current time. /// /// @return the current time proftime_T profile_start(void) FUNC_ATTR_WARN_UNUSED_RESULT @@ -28,31 +27,29 @@ proftime_T profile_start(void) FUNC_ATTR_WARN_UNUSED_RESULT return os_hrtime(); } -/// profile_end - compute the time elapsed +/// Computes the time elapsed. /// -/// @return the elapsed time from `tm` until now. +/// @return Elapsed time from `tm` until now. proftime_T profile_end(proftime_T tm) FUNC_ATTR_WARN_UNUSED_RESULT { - return os_hrtime() - tm; + return profile_sub(os_hrtime(), tm); } -/// profile_msg - return a string that represents the time in `tm` +/// Gets a string representing time `tm`. /// /// @warning Do not modify or free this string, not multithread-safe. /// -/// @param tm The time to be represented -/// @return a static string representing `tm` in the -/// form "seconds.microseconds". +/// @param tm Time +/// @return Static string representing `tm` in the form "seconds.microseconds". const char *profile_msg(proftime_T tm) FUNC_ATTR_WARN_UNUSED_RESULT { static char buf[50]; - - snprintf(buf, sizeof(buf), "%10.6lf", (double)tm / 1000000000.0); - + snprintf(buf, sizeof(buf), "%10.6lf", + (double)profile_signed(tm) / 1000000000.0); return buf; } -/// profile_setlimit - return the time `msec` into the future +/// Gets the time `msec` into the future. /// /// @param msec milliseconds, the maximum number of milliseconds is /// (2^63 / 10^6) - 1 = 9.223372e+12. @@ -64,14 +61,12 @@ proftime_T profile_setlimit(int64_t msec) FUNC_ATTR_WARN_UNUSED_RESULT // no limit return profile_zero(); } - assert(msec <= (INT64_MAX / 1000000LL) - 1); - - proftime_T nsec = (proftime_T) msec * 1000000ULL; + proftime_T nsec = (proftime_T)msec * 1000000ULL; return os_hrtime() + nsec; } -/// profile_passed_limit - check if current time has passed `tm` +/// Checks if current time has passed `tm`. /// /// @return true if the current time is past `tm`, false if not or if the /// timer was not set. @@ -85,7 +80,7 @@ bool profile_passed_limit(proftime_T tm) FUNC_ATTR_WARN_UNUSED_RESULT return profile_cmp(os_hrtime(), tm) < 0; } -/// profile_zero - obtain the zero time +/// Gets the zero time. /// /// @return the zero time proftime_T profile_zero(void) FUNC_ATTR_CONST @@ -93,7 +88,7 @@ proftime_T profile_zero(void) FUNC_ATTR_CONST return 0; } -/// profile_divide - divide the time `tm` by `count`. +/// Divides time `tm` by `count`. /// /// @return 0 if count <= 0, otherwise tm / count proftime_T profile_divide(proftime_T tm, int count) FUNC_ATTR_CONST @@ -105,7 +100,7 @@ proftime_T profile_divide(proftime_T tm, int count) FUNC_ATTR_CONST return (proftime_T) round((double) tm / (double) count); } -/// profile_add - add the time `tm2` to `tm1` +/// Adds time `tm2` to `tm1`. /// /// @return `tm1` + `tm2` proftime_T profile_add(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST @@ -113,7 +108,12 @@ proftime_T profile_add(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST return tm1 + tm2; } -/// profile_sub - subtract `tm2` from `tm1` +/// Subtracts time `tm2` from `tm1`. +/// +/// Unsigned overflow (wraparound) occurs if `tm2` is greater than `tm1`. +/// Use `profile_signed()` to get the signed integer value. +/// +/// @see profile_signed /// /// @return `tm1` - `tm2` proftime_T profile_sub(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST @@ -121,8 +121,7 @@ proftime_T profile_sub(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST return tm1 - tm2; } -/// profile_self - add the `self` time from the total time and the -/// children's time +/// Adds the `self` time from the total time and the `children` time. /// /// @return if `total` <= `children`, then self, otherwise `self` + `total` - /// `children` @@ -139,7 +138,7 @@ proftime_T profile_self(proftime_T self, proftime_T total, proftime_T children) return profile_sub(profile_add(self, total), children); } -/// profile_get_wait - get the current waittime +/// Gets the current waittime. /// /// @return the current waittime proftime_T profile_get_wait(void) FUNC_ATTR_PURE @@ -147,13 +146,13 @@ proftime_T profile_get_wait(void) FUNC_ATTR_PURE return prof_wait_time; } -/// profile_set_wait - set the current waittime +/// Sets the current waittime. void profile_set_wait(proftime_T wait) { prof_wait_time = wait; } -/// profile_sub_wait - subtract the passed waittime since `tm` +/// Subtracts the passed waittime since `tm`. /// /// @return `tma` - (waittime - `tm`) proftime_T profile_sub_wait(proftime_T tm, proftime_T tma) FUNC_ATTR_PURE @@ -162,7 +161,7 @@ proftime_T profile_sub_wait(proftime_T tm, proftime_T tma) FUNC_ATTR_PURE return profile_sub(tma, tm3); } -/// profile_equal - check if `tm1` is equal to `tm2` +/// Checks if time `tm1` is equal to `tm2`. /// /// @return true if `tm1` == `tm2` bool profile_equal(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST @@ -170,30 +169,38 @@ bool profile_equal(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST return tm1 == tm2; } -/// sgn64 - calculates the sign of a 64-bit integer +/// Converts time duration `tm` (`profile_sub` result) to a signed integer. /// -/// @return -1, 0, or +1 -static inline int sgn64(int64_t x) FUNC_ATTR_CONST +/// @return signed representation of the given time value +int64_t profile_signed(proftime_T tm) + FUNC_ATTR_CONST { - return (int) ((x > 0) - (x < 0)); + // (tm > INT64_MAX) is >=150 years, so we can assume it was produced by + // arithmetic of two proftime_T values. For human-readable representation + // (and Vim-compat) we want the difference after unsigned wraparound. #10452 + return (tm <= INT64_MAX) ? (int64_t)tm : -(int64_t)(UINT64_MAX - tm); } -/// profile_cmp - compare profiling times +/// Compares profiling times. /// -/// Only guarantees correct results if both input times are not more than -/// 150 years apart. +/// Times `tm1` and `tm2` must be less than 150 years apart. /// -/// @return <0, 0 or >0 if `tm2` < `tm1`, `tm2` == `tm1` or `tm2` > `tm1` +/// @return <0: `tm2` < `tm1` +/// 0: `tm2` == `tm1` +/// >0: `tm2` > `tm1` int profile_cmp(proftime_T tm1, proftime_T tm2) FUNC_ATTR_CONST { - return sgn64((int64_t)(tm2 - tm1)); + if (tm1 == tm2) { + return 0; + } + return profile_signed(tm2 - tm1) < 0 ? -1 : 1; } /// globals for use in the startuptime related functionality (time_*). static proftime_T g_start_time; static proftime_T g_prev_time; -/// time_push - save the previous time before doing something that could nest +/// Saves the previous time before doing something that could nest. /// /// After calling this function, the static global `g_prev_time` will /// contain the current time. @@ -212,7 +219,7 @@ void time_push(proftime_T *rel, proftime_T *start) g_prev_time = now; } -/// time_pop - compute the prev time after doing something that could nest +/// Computes the prev time after doing something that could nest. /// /// Subtracts `tp` from the static global `g_prev_time`. /// @@ -222,18 +229,18 @@ void time_pop(proftime_T tp) g_prev_time -= tp; } -/// time_diff - print the difference between `then` and `now` +/// Prints the difference between `then` and `now`. /// /// the format is "msec.usec". static void time_diff(proftime_T then, proftime_T now) { proftime_T diff = profile_sub(now, then); - fprintf(time_fd, "%07.3lf", (double) diff / 1.0E6); + fprintf(time_fd, "%07.3lf", (double)diff / 1.0E6); } -/// time_start - initialize the startuptime code +/// Initializes the startuptime code. /// -/// Needs to be called once before calling other startuptime code (such as +/// Must be called once before calling other startuptime code (such as /// time_{push,pop,msg,...}). /// /// @param message the message that will be displayed @@ -253,7 +260,7 @@ void time_start(const char *message) time_msg(message, NULL); } -/// time_msg - print out timing info +/// Prints out timing info. /// /// @warning don't forget to call `time_start()` once before calling this. /// diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index ced0cf0f80..1eb616bca7 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -863,7 +863,7 @@ qf_init_ext( fields.errmsg = xmalloc(fields.errmsglen); fields.pattern = xmalloc(CMDBUFFSIZE + 1); - if (efile != NULL && (state.fd = mch_fopen((char *)efile, "r")) == NULL) { + if (efile != NULL && (state.fd = os_fopen((char *)efile, "r")) == NULL) { EMSG2(_(e_openerrf), efile); goto qf_init_end; } @@ -1326,7 +1326,7 @@ static int qf_parse_multiline_pfx(qf_info_T *qi, int qf_idx, int idx, if (qfprev == NULL) { return QF_FAIL; } - if (*fields->errmsg && !qfl->qf_multiignore) { + if (*fields->errmsg) { size_t textlen = strlen((char *)qfprev->qf_text); size_t errlen = strlen((char *)fields->errmsg); qfprev->qf_text = xrealloc(qfprev->qf_text, textlen + errlen + 2); @@ -2056,7 +2056,7 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window) win_T *usable_win_ptr = NULL; int usable_win; int flags; - win_T *win = NULL; + win_T *win; win_T *altwin; usable_win = 0; @@ -2079,7 +2079,6 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window) // Locate a window showing a normal buffer FOR_ALL_WINDOWS_IN_TAB(win2, curtab) { if (win2->w_buffer->b_p_bt[0] == NUL) { - win = win2; usable_win = 1; break; } @@ -2204,7 +2203,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, // Open help file (do_ecmd() will set b_help flag, readfile() will // set b_p_ro flag). if (!can_abandon(curbuf, forceit)) { - EMSG(_(e_nowrtmsg)); + no_write_message(); retval = false; } else { retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1, @@ -2212,7 +2211,6 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, oldwin == curwin ? curwin : NULL); } } else { - int old_qf_curlist = qi->qf_curlist; unsigned save_qfid = qi->qf_lists[qi->qf_curlist].qf_id; retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1, @@ -2229,8 +2227,7 @@ static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit, EMSG(_(e_loc_list_changed)); *abort = true; } - } else if (old_qf_curlist != qi->qf_curlist - || !is_qf_entry_present(qi, qf_ptr)) { + } else if (!is_qf_entry_present(qi, qf_ptr)) { if (IS_QF_STACK(qi)) { EMSG(_("E925: Current quickfix was changed")); } else { @@ -2528,9 +2525,9 @@ void qf_list(exarg_T *eap) qfp = qi->qf_lists[qi->qf_curlist].qf_start; for (i = 1; !got_int && i <= qi->qf_lists[qi->qf_curlist].qf_count; ) { if ((qfp->qf_valid || all) && idx1 <= i && i <= idx2) { - msg_putchar('\n'); - if (got_int) + if (got_int) { break; + } fname = NULL; if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { @@ -2549,6 +2546,27 @@ void qf_list(exarg_T *eap) vim_snprintf((char *)IObuff, IOSIZE, "%2d %s", i, (char *)fname); } } + + // Support for filtering entries using :filter /pat/ clist + // Match against the module name, file name, search pattern and + // text of the entry. + bool filter_entry = true; + if (qfp->qf_module != NULL && *qfp->qf_module != NUL) { + filter_entry &= message_filtered(qfp->qf_module); + } + if (filter_entry && fname != NULL) { + filter_entry &= message_filtered(fname); + } + if (filter_entry && qfp->qf_pattern != NULL) { + filter_entry &= message_filtered(qfp->qf_pattern); + } + if (filter_entry) { + filter_entry &= message_filtered(qfp->qf_text); + } + if (filter_entry) { + goto next_entry; + } + msg_putchar('\n'); msg_outtrans_attr(IObuff, i == qi->qf_lists[qi->qf_curlist].qf_index ? HL_ATTR(HLF_QFL) : HL_ATTR(HLF_D)); if (qfp->qf_lnum == 0) { @@ -2579,6 +2597,7 @@ void qf_list(exarg_T *eap) ui_flush(); /* show one line at a time */ } +next_entry: qfp = qfp->qf_next; if (qfp == NULL) { break; @@ -2632,7 +2651,7 @@ static void qf_msg(qf_info_T *qi, int which, char *lead) } xstrlcat((char *)buf, title, IOSIZE); } - trunc_string(buf, buf, (int)Columns - 1, IOSIZE); + trunc_string(buf, buf, Columns - 1, IOSIZE); msg(buf); } @@ -2845,6 +2864,39 @@ static char_u *qf_types(int c, int nr) return buf; } +// When "split" is false: Open the entry/result under the cursor. +// When "split" is true: Open the entry/result under the cursor in a new window. +void qf_view_result(bool split) +{ + qf_info_T *qi = &ql_info; + + if (!bt_quickfix(curbuf)) { + return; + } + if (IS_LL_WINDOW(curwin)) { + qi = GET_LOC_LIST(curwin); + } + if (qi == NULL + || qi->qf_lists[qi->qf_curlist].qf_count == 0) { + EMSG(_(e_quickfix)); + return; + } + + if (split) { + char cmd[32]; + + snprintf(cmd, sizeof(cmd), "split +%" PRId64 "%s", + (int64_t)curwin->w_cursor.lnum, + IS_LL_WINDOW(curwin) ? "ll" : "cc"); + if (do_cmdline_cmd(cmd) == OK) { + do_cmdline_cmd("clearjumps"); + } + return; + } + + do_cmdline_cmd((IS_LL_WINDOW(curwin) ? ".ll" : ".cc")); +} + /* * ":cwindow": open the quickfix window if we have errors to display, * close it if not. @@ -4688,11 +4740,8 @@ static int qf_getprop_defaults(qf_info_T *qi, int flags, dict_T *retdict) /// Return the quickfix list title as 'title' in retdict static int qf_getprop_title(qf_info_T *qi, int qf_idx, dict_T *retdict) { - char_u *t = qi->qf_lists[qf_idx].qf_title; - if (t == NULL) { - t = (char_u *)""; - } - return tv_dict_add_str(retdict, S_LEN("title"), (const char *)t); + return tv_dict_add_str(retdict, S_LEN("title"), + (const char *)qi->qf_lists[qf_idx].qf_title); } /// Return the quickfix list items/entries as 'items' in retdict @@ -5335,8 +5384,11 @@ void ex_cexpr(exarg_T *eap) apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, curbuf->b_fname, true, curbuf); } - if (res > 0 && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr)) { - qf_jump(qi, 0, 0, eap->forceit); // display first error + if (res > 0 + && (eap->cmdidx == CMD_cexpr || eap->cmdidx == CMD_lexpr) + && qi == GET_LOC_LIST(curwin)) { + // Jump to the first error if autocmds didn't free the list. + qf_jump(qi, 0, 0, eap->forceit); } } else { EMSG(_("E777: String or List expected")); @@ -5440,7 +5492,7 @@ void ex_helpgrep(exarg_T *eap) + STRLEN(fnames[fi]) - 3, 3) == 0)) { continue; } - fd = mch_fopen((char *)fnames[fi], "r"); + fd = os_fopen((char *)fnames[fi], "r"); if (fd != NULL) { lnum = 1; while (!vim_fgets(IObuff, IOSIZE, fd) && !got_int) { diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index 8598da6376..06b99d0b75 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -247,9 +247,9 @@ #define BRACE_COMPLEX 140 /* -149 node Match nodes between m & n times */ -#define NOPEN 150 /* Mark this point in input as start of - \%( subexpr. */ -#define NCLOSE 151 /* Analogous to NOPEN. */ +#define NOPEN 150 // Mark this point in input as start of + // \%( subexpr. +#define NCLOSE 151 // Analogous to NOPEN. #define MULTIBYTECODE 200 /* mbc Match one multi-byte character */ #define RE_BOF 201 /* Match "" at beginning of file. */ @@ -348,13 +348,13 @@ typedef enum regstate_E { * more things. */ typedef struct regitem_S { - regstate_T rs_state; /* what we are doing, one of RS_ above */ - char_u *rs_scan; /* current node in program */ + regstate_T rs_state; // what we are doing, one of RS_ above + uint16_t rs_no; // submatch nr or BEHIND/NOBEHIND + char_u *rs_scan; // current node in program union { save_se_T sesave; regsave_T regsave; - } rs_un; /* room for saving reginput */ - short rs_no; /* submatch nr or BEHIND/NOBEHIND */ + } rs_un; // room for saving reginput } regitem_T; @@ -2058,10 +2058,14 @@ static char_u *regatom(int *flagp) EMSG2_RET_NULL(_(e_missing_sb), reg_magic == MAGIC_ALL); br = regnode(BRANCH); - if (ret == NULL) + if (ret == NULL) { ret = br; - else + } else { regtail(lastnode, br); + if (reg_toolong) { + return NULL; + } + } ungetchr(); one_exactly = TRUE; @@ -2083,6 +2087,9 @@ static char_u *regatom(int *flagp) for (br = ret; br != lastnode; ) { if (OP(br) == BRANCH) { regtail(br, lastbranch); + if (reg_toolong) { + return NULL; + } br = OPERAND(br); } else br = regnext(br); @@ -4316,8 +4323,10 @@ static int regmatch( /* Still at same position as last time, fail. */ status = RA_NOMATCH; - if (status != RA_FAIL && status != RA_NOMATCH) + assert(status != RA_FAIL); + if (status != RA_NOMATCH) { reg_save(&bp[i].bp_pos, &backpos); + } } break; diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index dc1ab971ab..c0129a00fb 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -1263,8 +1263,8 @@ static int nfa_regatom(void) IEMSGN("INTERNAL: Unknown character class char: %" PRId64, c); return FAIL; } - /* When '.' is followed by a composing char ignore the dot, so that - * the composing char is matched here. */ + // When '.' is followed by a composing char ignore the dot, so that + // the composing char is matched here. if (enc_utf8 && c == Magic('.') && utf_iscomposing(peekchr())) { old_regparse = regparse; c = getchr(); @@ -1279,25 +1279,26 @@ static int nfa_regatom(void) break; case Magic('n'): - if (reg_string) - /* In a string "\n" matches a newline character. */ + if (reg_string) { + // In a string "\n" matches a newline character. EMIT(NL); - else { - /* In buffer text "\n" matches the end of a line. */ + } else { + // In buffer text "\n" matches the end of a line. EMIT(NFA_NEWL); regflags |= RF_HASNL; } break; case Magic('('): - if (nfa_reg(REG_PAREN) == FAIL) - return FAIL; /* cascaded error */ + if (nfa_reg(REG_PAREN) == FAIL) { + return FAIL; // cascaded error + } break; case Magic('|'): case Magic('&'): case Magic(')'): - EMSGN(_(e_misplaced), no_Magic(c)); + EMSGN(_(e_misplaced), no_Magic(c)); // -V1037 return FAIL; case Magic('='): @@ -1306,7 +1307,7 @@ static int nfa_regatom(void) case Magic('@'): case Magic('*'): case Magic('{'): - /* these should follow an atom, not form an atom */ + // these should follow an atom, not form an atom EMSGN(_(e_misplaced), no_Magic(c)); return FAIL; @@ -1314,8 +1315,8 @@ static int nfa_regatom(void) { char_u *lp; - /* Previous substitute pattern. - * Generated as "\%(pattern\)". */ + // Previous substitute pattern. + // Generated as "\%(pattern\)". if (reg_prev_sub == NULL) { EMSG(_(e_nopresub)); return FAIL; @@ -1492,16 +1493,22 @@ static int nfa_regatom(void) default: { - long n = 0; - int cmp = c; + int64_t n = 0; + const int cmp = c; if (c == '<' || c == '>') c = getchr(); while (ascii_isdigit(c)) { + if (n > (INT32_MAX - (c - '0')) / 10) { + EMSG(_("E951: \\% value too large")); + return FAIL; + } n = n * 10 + (c - '0'); c = getchr(); } if (c == 'l' || c == 'c' || c == 'v') { + int32_t limit = INT32_MAX; + if (c == 'l') { // \%{n}l \%{n}<l \%{n}>l EMIT(cmp == '<' ? NFA_LNUM_LT : @@ -1517,13 +1524,12 @@ static int nfa_regatom(void) // \%{n}v \%{n}<v \%{n}>v EMIT(cmp == '<' ? NFA_VCOL_LT : cmp == '>' ? NFA_VCOL_GT : NFA_VCOL); + limit = INT32_MAX / MB_MAXBYTES; } -#if SIZEOF_INT < SIZEOF_LONG - if (n > INT_MAX) { + if (n >= limit) { EMSG(_("E951: \\% value too large")); return FAIL; } -#endif EMIT((int)n); break; } else if (c == '\'' && n == 0) { @@ -3002,8 +3008,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size) return NULL; \ } - if (nfa_calc_size == FALSE) { - /* Allocate space for the stack. Max states on the stack : nstate */ + if (nfa_calc_size == false) { + // Allocate space for the stack. Max states on the stack: "nstate". stack = xmalloc((nstate + 1) * sizeof(Frag_T)); stackp = stack; stack_end = stack + (nstate + 1); @@ -3925,13 +3931,14 @@ state_in_list ( // Add "state" and possibly what follows to state list ".". // Returns "subs_arg", possibly copied into temp_subs. -static regsubs_T * -addstate ( - nfa_list_T *l, /* runtime state list */ - nfa_state_T *state, /* state to update */ - regsubs_T *subs_arg, /* pointers to subexpressions */ - nfa_pim_T *pim, /* postponed look-behind match */ - int off_arg) /* byte offset, when -1 go to next line */ +// Returns NULL when recursiveness is too deep. +static regsubs_T *addstate( + nfa_list_T *l, // runtime state list + nfa_state_T *state, // state to update + regsubs_T *subs_arg, // pointers to subexpressions + nfa_pim_T *pim, // postponed look-behind match + int off_arg) // byte offset, when -1 go to next line + FUNC_ATTR_NONNULL_ARG(1, 2) FUNC_ATTR_WARN_UNUSED_RESULT { int subidx; int off = off_arg; @@ -3950,6 +3957,14 @@ addstate ( #ifdef REGEXP_DEBUG int did_print = FALSE; #endif + static int depth = 0; + + // This function is called recursively. When the depth is too much we run + // out of stack and crash, limit recursiveness here. + if (++depth >= 5000 || subs == NULL) { + depth--; + return NULL; + } if (off_arg <= -ADDSTATE_HERE_OFFSET) { add_here = true; @@ -4053,6 +4068,7 @@ skip_add: abs(state->id), l->id, state->c, code, pim == NULL ? "NULL" : "yes", l->has_pim, found); #endif + depth--; return subs; } } @@ -4063,11 +4079,17 @@ skip_add: goto skip_add; } - /* When there are backreferences or PIMs the number of states may - * be (a lot) bigger than anticipated. */ + // When there are backreferences or PIMs the number of states may + // be (a lot) bigger than anticipated. if (l->n == l->len) { - int newlen = l->len * 3 / 2 + 50; + const int newlen = l->len * 3 / 2 + 50; + const size_t newsize = newlen * sizeof(nfa_thread_T); + if ((long)(newsize >> 10) >= p_mmp) { + EMSG(_(e_maxmempat)); + depth--; + return NULL; + } if (subs != &temp_subs) { /* "subs" may point into the current array, need to make a * copy before it becomes invalid. */ @@ -4077,7 +4099,8 @@ skip_add: subs = &temp_subs; } - l->t = xrealloc(l->t, newlen * sizeof(nfa_thread_T)); + nfa_thread_T *const newt = xrealloc(l->t, newsize); + l->t = newt; l->len = newlen; } @@ -4196,6 +4219,9 @@ skip_add: } subs = addstate(l, state->out, subs, pim, off_arg); + if (subs == NULL) { + break; + } // "subs" may have changed, need to set "sub" again. if (state->c >= NFA_ZOPEN && state->c <= NFA_ZOPEN9) { // -V560 sub = &subs->synt; @@ -4217,7 +4243,7 @@ skip_add: if (nfa_has_zend && (REG_MULTI ? subs->norm.list.multi[0].end_lnum >= 0 : subs->norm.list.line[0].end != NULL)) { - /* Do not overwrite the position set by \ze. */ + // Do not overwrite the position set by \ze. subs = addstate(l, state->out, subs, pim, off_arg); break; } @@ -4278,6 +4304,9 @@ skip_add: } subs = addstate(l, state->out, subs, pim, off_arg); + if (subs == NULL) { + break; + } // "subs" may have changed, need to set "sub" again. if (state->c >= NFA_ZCLOSE && state->c <= NFA_ZCLOSE9) { // -V560 sub = &subs->synt; @@ -4293,6 +4322,7 @@ skip_add: sub->in_use = save_in_use; break; } + depth--; return subs; } @@ -4302,14 +4332,14 @@ skip_add: * This makes sure the order of states to be tried does not change, which * matters for alternatives. */ -static void -addstate_here ( - nfa_list_T *l, /* runtime state list */ - nfa_state_T *state, /* state to update */ - regsubs_T *subs, /* pointers to subexpressions */ - nfa_pim_T *pim, /* postponed look-behind match */ +static regsubs_T *addstate_here( + nfa_list_T *l, // runtime state list + nfa_state_T *state, // state to update + regsubs_T *subs, // pointers to subexpressions + nfa_pim_T *pim, // postponed look-behind match int *ip ) + FUNC_ATTR_NONNULL_ARG(1, 2, 5) FUNC_ATTR_WARN_UNUSED_RESULT { int tlen = l->n; int count; @@ -4318,26 +4348,37 @@ addstate_here ( /* First add the state(s) at the end, so that we know how many there are. * Pass the listidx as offset (avoids adding another argument to * addstate(). */ - addstate(l, state, subs, pim, -listidx - ADDSTATE_HERE_OFFSET); + regsubs_T *r = addstate(l, state, subs, pim, -listidx - ADDSTATE_HERE_OFFSET); + if (r == NULL) { + return NULL; + } - /* when "*ip" was at the end of the list, nothing to do */ - if (listidx + 1 == tlen) - return; + // when "*ip" was at the end of the list, nothing to do + if (listidx + 1 == tlen) { + return r; + } - /* re-order to put the new state at the current position */ + // re-order to put the new state at the current position count = l->n - tlen; - if (count == 0) - return; /* no state got added */ + if (count == 0) { + return r; // no state got added + } if (count == 1) { - /* overwrite the current state */ + // overwrite the current state l->t[listidx] = l->t[l->n - 1]; } else if (count > 1) { if (l->n + count - 1 >= l->len) { /* not enough space to move the new states, reallocate the list * and move the states to the right position */ + const int newlen = l->len * 3 / 2 + 50; + const size_t newsize = newlen * sizeof(nfa_thread_T); - l->len = l->len * 3 / 2 + 50; - nfa_thread_T *newl = xmalloc(l->len * sizeof(nfa_thread_T)); + if ((long)(newsize >> 10) >= p_mmp) { + EMSG(_(e_maxmempat)); + return NULL; + } + nfa_thread_T *const newl = xmalloc(newsize); + l->len = newlen; memmove(&(newl[0]), &(l->t[0]), sizeof(nfa_thread_T) * listidx); @@ -4362,6 +4403,8 @@ addstate_here ( } --l->n; *ip = listidx - 1; + + return r; } /* @@ -4991,6 +5034,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, int add_count; int add_off = 0; int toplevel = start->c == NFA_MOPEN; + regsubs_T *r; #ifdef NFA_REGEXP_DEBUG_LOG FILE *debug = fopen(NFA_REGEXP_DEBUG_LOG, "a"); @@ -5058,9 +5102,14 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, } else m->norm.list.line[0].start = reginput; m->norm.in_use = 1; - addstate(thislist, start->out, m, NULL, 0); - } else - addstate(thislist, start, m, NULL, 0); + r = addstate(thislist, start->out, m, NULL, 0); + } else { + r = addstate(thislist, start, m, NULL, 0); + } + if (r == NULL) { + nfa_match = NFA_TOO_EXPENSIVE; + goto theend; + } #define ADD_STATE_IF_MATCH(state) \ if (result) { \ @@ -5327,8 +5376,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, // t->state->out1 is the corresponding END_INVISIBLE // node; Add its out to the current list (zero-width // match). - addstate_here(thislist, t->state->out1->out, &t->subs, - &pim, &listidx); + if (addstate_here(thislist, t->state->out1->out, &t->subs, + &pim, &listidx) == NULL) { + nfa_match = NFA_TOO_EXPENSIVE; + goto theend; + } } } break; @@ -6144,12 +6196,17 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, pim = &pim_copy; } - if (add_here) - addstate_here(thislist, add_state, &t->subs, pim, &listidx); - else { - addstate(nextlist, add_state, &t->subs, pim, add_off); - if (add_count > 0) + if (add_here) { + r = addstate_here(thislist, add_state, &t->subs, pim, &listidx); + } else { + r = addstate(nextlist, add_state, &t->subs, pim, add_off); + if (add_count > 0) { nextlist->t[nextlist->n - 1].count = add_count; + } + } + if (r == NULL) { + nfa_match = NFA_TOO_EXPENSIVE; + goto theend; } } } // for (thislist = thislist; thislist->state; thislist++) @@ -6219,10 +6276,17 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, (colnr_T)(reginput - regline) + clen; else m->norm.list.line[0].start = reginput + clen; - addstate(nextlist, start->out, m, NULL, clen); + if (addstate(nextlist, start->out, m, NULL, clen) == NULL) { + nfa_match = NFA_TOO_EXPENSIVE; + goto theend; + } } - } else - addstate(nextlist, start, m, NULL, clen); + } else { + if (addstate(nextlist, start, m, NULL, clen) == NULL) { + nfa_match = NFA_TOO_EXPENSIVE; + goto theend; + } + } } #ifdef REGEXP_DEBUG diff --git a/src/nvim/screen.c b/src/nvim/screen.c index 84c3f169ef..99ccce1793 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -153,6 +153,8 @@ static bool conceal_cursor_used = false; static bool redraw_popupmenu = false; +static bool resizing = false; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "screen.c.generated.h" #endif @@ -169,6 +171,7 @@ void redraw_later(int type) } void redraw_win_later(win_T *wp, int type) + FUNC_ATTR_NONNULL_ALL { if (!exiting && wp->w_redr_type < type) { wp->w_redr_type = type; @@ -241,6 +244,7 @@ redrawWinline( win_T *wp, linenr_T lnum ) + FUNC_ATTR_NONNULL_ALL { if (lnum >= wp->w_topline && lnum < wp->w_botline) { @@ -269,14 +273,16 @@ void update_curbuf(int type) /// and redraw_all_later() to mark parts of the screen as needing a redraw. /// /// @param type set to a NOT_VALID to force redraw of entire screen -void update_screen(int type) +int update_screen(int type) { static int did_intro = FALSE; int did_one; // Don't do anything if the screen structures are (not yet) valid. - if (!default_grid.chars) { - return; + // A VimResized autocmd can invoke redrawing in the middle of a resize, + // which would bypass the checks in screen_resize for popupmenu etc. + if (!default_grid.chars || resizing) { + return FAIL; } if (must_redraw) { @@ -299,9 +305,10 @@ void update_screen(int type) if (!redrawing() || updating_screen) { redraw_later(type); /* remember type for next time */ must_redraw = type; - if (type > INVERTED_ALL) - curwin->w_lines_valid = 0; /* don't use w_lines[].wl_size now */ - return; + if (type > INVERTED_ALL) { + curwin->w_lines_valid = 0; // don't use w_lines[].wl_size now + } + return FAIL; } updating_screen = TRUE; @@ -336,8 +343,7 @@ void update_screen(int type) type = CLEAR; } else if (type != CLEAR) { check_for_delay(false); - grid_ins_lines(&default_grid, 0, msg_scrolled, (int)Rows, - 0, (int)Columns); + grid_ins_lines(&default_grid, 0, msg_scrolled, Rows, 0, Columns); FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_floating) { continue; @@ -482,13 +488,13 @@ void update_screen(int type) } end_search_hl(); + // May need to redraw the popup menu. - if (pum_drawn() && redraw_popupmenu) { + if (pum_drawn() && must_redraw_pum) { pum_redraw(); } send_grid_resize = false; - redraw_popupmenu = false; /* Reset b_mod_set flags. Going through all windows is probably faster * than going through all buffers (there could be many buffers). */ @@ -511,28 +517,30 @@ void update_screen(int type) // either cmdline is cleared, not drawn or mode is last drawn cmdline_was_last_drawn = false; + return OK; } -/* - * Return TRUE if the cursor line in window "wp" may be concealed, according - * to the 'concealcursor' option. - */ -int conceal_cursor_line(win_T *wp) +// Return true if the cursor line in window "wp" may be concealed, according +// to the 'concealcursor' option. +bool conceal_cursor_line(const win_T *wp) + FUNC_ATTR_NONNULL_ALL { int c; - if (*wp->w_p_cocu == NUL) - return FALSE; - if (get_real_state() & VISUAL) + if (*wp->w_p_cocu == NUL) { + return false; + } + if (get_real_state() & VISUAL) { c = 'v'; - else if (State & INSERT) + } else if (State & INSERT) { c = 'i'; - else if (State & NORMAL) + } else if (State & NORMAL) { c = 'n'; - else if (State & CMDLINE) + } else if (State & CMDLINE) { c = 'c'; - else - return FALSE; + } else { + return false; + } return vim_strchr(wp->w_p_cocu, c) != NULL; } @@ -554,7 +562,8 @@ void conceal_check_cursor_line(void) /// /// If true, both old and new cursorline will need /// need to be redrawn when moving cursor within windows. -bool win_cursorline_standout(win_T *wp) +bool win_cursorline_standout(const win_T *wp) + FUNC_ATTR_NONNULL_ALL { return wp->w_p_cul || (wp->w_p_cole > 0 && !conceal_cursor_line(wp)); } @@ -600,8 +609,7 @@ static void win_update(win_T *wp) updating. 999 when no bot area updating */ int scrolled_down = FALSE; /* TRUE when scrolled down when w_topline got smaller a bit */ - matchitem_T *cur; /* points to the match list */ - int top_to_mod = FALSE; /* redraw above mod_top */ + bool top_to_mod = false; // redraw above mod_top int row; /* current window row to display */ linenr_T lnum; /* current buffer lnum to display */ @@ -696,21 +704,20 @@ static void win_update(win_T *wp) if (mod_bot == 0 || mod_bot < buf->b_mod_bot) mod_bot = buf->b_mod_bot; - /* When 'hlsearch' is on and using a multi-line search pattern, a - * change in one line may make the Search highlighting in a - * previous line invalid. Simple solution: redraw all visible - * lines above the change. - * Same for a match pattern. - */ + // When 'hlsearch' is on and using a multi-line search pattern, a + // change in one line may make the Search highlighting in a + // previous line invalid. Simple solution: redraw all visible + // lines above the change. + // Same for a match pattern. if (search_hl.rm.regprog != NULL - && re_multiline(search_hl.rm.regprog)) - top_to_mod = TRUE; - else { - cur = wp->w_match_head; + && re_multiline(search_hl.rm.regprog)) { + top_to_mod = true; + } else { + const matchitem_T *cur = wp->w_match_head; while (cur != NULL) { if (cur->match.regprog != NULL && re_multiline(cur->match.regprog)) { - top_to_mod = TRUE; + top_to_mod = true; break; } cur = cur->next; @@ -1457,7 +1464,7 @@ static void win_update(win_T *wp) // Last line isn't finished: Display "@@@" in the last screen line. grid_puts_len(&wp->w_grid, (char_u *)"@@", 2, scr_row, 0, at_attr); - grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, (int)wp->w_grid.Columns, + grid_fill(&wp->w_grid, scr_row, scr_row + 1, 2, wp->w_grid.Columns, '@', ' ', at_attr); set_empty_rows(wp, srow); wp->w_botline = lnum; @@ -1468,7 +1475,7 @@ static void win_update(win_T *wp) set_empty_rows(wp, srow); wp->w_botline = lnum; } else { - win_draw_end(wp, '@', ' ', true, srow, wp->w_grid.Rows, at_attr); + win_draw_end(wp, '@', ' ', true, srow, wp->w_grid.Rows, HLF_AT); wp->w_botline = lnum; } } else { @@ -1584,6 +1591,7 @@ static int win_fill_end(win_T *wp, int c1, int c2, int off, int width, int row, static void win_draw_end(win_T *wp, int c1, int c2, bool draw_margin, int row, int endrow, hlf_T hl) { + assert(hl >= 0 && hl < HLF_COUNT); int n = 0; if (draw_margin) { @@ -1709,7 +1717,6 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T int col; int txtcol; int off; - int ri; /* Build the fold line: * 1. Add the cmdwin_type for the command-line window @@ -1753,15 +1760,18 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T col += fdc; } -# define RL_MEMSET(p, v, l) if (wp->w_p_rl) { \ - for (ri = 0; ri < l; ri++) { \ - linebuf_attr[off + (wp->w_grid.Columns - (p) - (l)) + ri] = v; \ - } \ - } else { \ - for (ri = 0; ri < l; ri++) { \ - linebuf_attr[off + (p) + ri] = v; \ +# define RL_MEMSET(p, v, l) \ + do { \ + if (wp->w_p_rl) { \ + for (int ri = 0; ri < l; ri++) { \ + linebuf_attr[off + (wp->w_grid.Columns - (p) - (l)) + ri] = v; \ + } \ + } else { \ + for (int ri = 0; ri < l; ri++) { \ + linebuf_attr[off + (p) + ri] = v; \ + } \ } \ - } + } while (0) /* Set all attributes of the 'number' or 'relativenumber' column and the * text */ @@ -1775,7 +1785,9 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T if (len > len_max) { len = len_max; } - copy_text_attr(off + col, (char_u *)" ", len, + char_u space_buf[18] = " "; + assert((size_t)len_max <= sizeof(space_buf)); + copy_text_attr(off + col, space_buf, len, win_hl_attr(wp, HLF_FL)); col += len; } @@ -2061,7 +2073,8 @@ win_line ( int row; // row in the window, excl w_winrow ScreenGrid *grid = &wp->w_grid; // grid specfic to the window - char_u extra[18]; // line number and 'fdc' must fit in here + char_u extra[57]; // sign, line number and 'fdc' must + // fit in here int n_extra = 0; // number of extra chars char_u *p_extra = NULL; // string of extra chars, plus NUL char_u *p_extra_free = NULL; // p_extra needs to be freed @@ -2074,7 +2087,7 @@ win_line ( int lcs_eol_one = wp->w_p_lcs_chars.eol; // 'eol' until it's been used int lcs_prec_todo = wp->w_p_lcs_chars.prec; // 'prec' until it's been used - /* saved "extra" items for when draw_state becomes WL_LINE (again) */ + // saved "extra" items for when draw_state becomes WL_LINE (again) int saved_n_extra = 0; char_u *saved_p_extra = NULL; int saved_c_extra = 0; @@ -2712,15 +2725,24 @@ win_line ( sign_idx, count); if (text_sign != 0) { p_extra = sign_get_text(text_sign); - int symbol_blen = (int)STRLEN(p_extra); if (p_extra != NULL) { + int symbol_blen = (int)STRLEN(p_extra); + c_extra = NUL; c_final = NUL; + + // TODO(oni-link): Is sign text already extended to + // full cell width? + assert((size_t)win_signcol_width(wp) + >= mb_string2cells(p_extra)); // symbol(s) bytes + (filling spaces) (one byte each) n_extra = symbol_blen + (win_signcol_width(wp) - mb_string2cells(p_extra)); + + assert(sizeof(extra) > (size_t)symbol_blen); memset(extra, ' ', sizeof(extra)); - STRNCPY(extra, p_extra, STRLEN(p_extra)); + memcpy(extra, p_extra, symbol_blen); + p_extra = extra; p_extra[n_extra] = NUL; } @@ -3121,7 +3143,7 @@ win_line ( c = '>'; mb_c = c; mb_l = 1; - mb_utf8 = false; + (void)mb_l; multi_attr = win_hl_attr(wp, HLF_AT); // put the pointer back to output the double-width @@ -3132,9 +3154,9 @@ win_line ( n_extra -= mb_l - 1; p_extra += mb_l - 1; } - ++p_extra; + p_extra++; } - --n_extra; + n_extra--; } else { int c0; @@ -3753,15 +3775,14 @@ win_line ( n_attr3 = 1; } - /* - * At end of the text line or just after the last character. - */ + // At end of the text line or just after the last character. if (c == NUL) { - long prevcol = (long)(ptr - line) - (c == NUL); + long prevcol = (long)(ptr - line) - 1; - /* we're not really at that column when skipping some text */ - if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) - ++prevcol; + // we're not really at that column when skipping some text + if ((long)(wp->w_p_wrap ? wp->w_skipcol : wp->w_leftcol) > prevcol) { + prevcol++; + } // Invert at least one char, used for Visual and empty line or // highlight match at end of line. If it's beyond the last @@ -3784,8 +3805,7 @@ win_line ( && ((area_attr != 0 && vcol == fromcol && (VIsual_mode != Ctrl_V || lnum == VIsual.lnum - || lnum == curwin->w_cursor.lnum) - && c == NUL) + || lnum == curwin->w_cursor.lnum)) // highlight 'hlsearch' match at end of line || prevcol_hl_flag)) { int n = 0; @@ -4012,7 +4032,7 @@ win_line ( && filler_todo <= 0 && (wp->w_p_rl ? col == 0 : col == grid->Columns - 1) && (*ptr != NUL - || (wp->w_p_list && lcs_eol_one > 0) + || lcs_eol_one > 0 || (n_extra && (c_extra != NUL || *p_extra != NUL)))) { c = wp->w_p_lcs_chars.ext; char_attr = win_hl_attr(wp, HLF_AT); @@ -4026,14 +4046,15 @@ win_line ( } } - /* advance to the next 'colorcolumn' */ - if (draw_color_col) + // advance to the next 'colorcolumn' + if (draw_color_col) { draw_color_col = advance_color_col(VCOL_HLC, &color_cols); + } - /* Highlight the cursor column if 'cursorcolumn' is set. But don't - * highlight the cursor position itself. - * Also highlight the 'colorcolumn' if it is different than - * 'cursorcolumn' */ + // Highlight the cursor column if 'cursorcolumn' is set. But don't + // highlight the cursor position itself. + // Also highlight the 'colorcolumn' if it is different than + // 'cursorcolumn' vcol_save_attr = -1; if (draw_state == WL_LINE && !lnum_in_visual_area && search_attr == 0 && area_attr == 0) { @@ -4052,10 +4073,8 @@ win_line ( char_attr = hl_combine_attr(line_attr_lowprio, char_attr); } - /* - * Store character to be displayed. - * Skip characters that are left of the screen for 'nowrap'. - */ + // Store character to be displayed. + // Skip characters that are left of the screen for 'nowrap'. vcol_prev = vcol; if (draw_state < WL_LINE || n_skip <= 0) { // @@ -4355,6 +4374,12 @@ static void grid_put_linebuf(ScreenGrid *grid, int row, int coloff, int endcol, screen_adjust_grid(&grid, &row, &coloff); + // Safety check. Avoids clang warnings down the call stack. + if (grid->chars == NULL || row >= grid->Rows || col >= grid->Columns) { + DLOG("invalid state, skipped"); + return; + } + off_from = 0; off_to = grid->line_offset[row] + coloff; max_off_from = linebuf_size; @@ -4550,17 +4575,21 @@ void redraw_statuslines(void) /* * Redraw all status lines at the bottom of frame "frp". */ -void win_redraw_last_status(frame_T *frp) +void win_redraw_last_status(const frame_T *frp) + FUNC_ATTR_NONNULL_ARG(1) { - if (frp->fr_layout == FR_LEAF) - frp->fr_win->w_redr_status = TRUE; - else if (frp->fr_layout == FR_ROW) { - for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) + if (frp->fr_layout == FR_LEAF) { + frp->fr_win->w_redr_status = true; + } else if (frp->fr_layout == FR_ROW) { + FOR_ALL_FRAMES(frp, frp->fr_child) { win_redraw_last_status(frp); - } else { /* frp->fr_layout == FR_COL */ + } + } else { + assert(frp->fr_layout == FR_COL); frp = frp->fr_child; - while (frp->fr_next != NULL) + while (frp->fr_next != NULL) { frp = frp->fr_next; + } win_redraw_last_status(frp); } } @@ -4641,7 +4670,7 @@ win_redr_status_matches ( int showtail ) { -#define L_MATCH(m) (showtail ? sm_gettail(matches[m]) : matches[m]) +#define L_MATCH(m) (showtail ? sm_gettail(matches[m], false) : matches[m]) int row; char_u *buf; int len; @@ -4798,7 +4827,7 @@ win_redr_status_matches ( grid_puts(&default_grid, selstart, row, selstart_col, HL_ATTR(HLF_WM)); } - grid_fill(&default_grid, row, row + 1, clen, (int)Columns, + grid_fill(&default_grid, row, row + 1, clen, Columns, fillchar, fillchar, attr); } @@ -5141,7 +5170,7 @@ win_redr_custom ( /* * Draw each snippet with the specified highlighting. */ - screen_puts_line_start(row); + grid_puts_line_start(&default_grid, row); curattr = attr; p = buf; @@ -5164,7 +5193,7 @@ win_redr_custom ( grid_puts(&default_grid, p >= buf + len ? (char_u *)"" : p, row, col, curattr); - grid_puts_line_flush(&default_grid, false); + grid_puts_line_flush(false); if (wp == NULL) { // Fill the tab_page_click_defs array for clicking in the tab pages line. @@ -5310,18 +5339,20 @@ void grid_puts(ScreenGrid *grid, char_u *text, int row, int col, int attr) grid_puts_len(grid, text, -1, row, col, attr); } +static ScreenGrid *put_dirty_grid = NULL; static int put_dirty_row = -1; static int put_dirty_first = INT_MAX; static int put_dirty_last = 0; -/// Start a group of screen_puts_len calls that builds a single screen line. +/// Start a group of grid_puts_len calls that builds a single grid line. /// -/// Must be matched with a screen_puts_line_flush call before moving to +/// Must be matched with a grid_puts_line_flush call before moving to /// another line. -void screen_puts_line_start(int row) +void grid_puts_line_start(ScreenGrid *grid, int row) { assert(put_dirty_row == -1); put_dirty_row = row; + put_dirty_grid = grid; } /// like grid_puts(), but output "text[len]". When "len" is -1 output up to @@ -5347,16 +5378,19 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, screen_adjust_grid(&grid, &row, &col); - // safety check - if (grid->chars == NULL || row >= grid->Rows || col >= grid->Columns) { + // Safety check. The check for negative row and column is to fix issue + // vim/vim#4102. TODO: find out why row/col could be negative. + if (grid->chars == NULL + || row >= grid->Rows || row < 0 + || col >= grid->Columns || col < 0) { return; } if (put_dirty_row == -1) { - screen_puts_line_start(row); + grid_puts_line_start(grid, row); do_flush = true; } else { - if (row != put_dirty_row) { + if (grid != put_dirty_grid || row != put_dirty_row) { abort(); } } @@ -5459,31 +5493,31 @@ void grid_puts_len(ScreenGrid *grid, char_u *text, int textlen, int row, } if (do_flush) { - grid_puts_line_flush(grid, true); + grid_puts_line_flush(true); } } -/// End a group of screen_puts_len calls and send the screen buffer to the UI +/// End a group of grid_puts_len calls and send the screen buffer to the UI /// layer. /// -/// @param grid The grid which contains the buffer. /// @param set_cursor Move the visible cursor to the end of the changed region. /// This is a workaround for not yet refactored code paths /// and shouldn't be used in new code. -void grid_puts_line_flush(ScreenGrid *grid, bool set_cursor) +void grid_puts_line_flush(bool set_cursor) { assert(put_dirty_row != -1); if (put_dirty_first < put_dirty_last) { if (set_cursor) { - ui_grid_cursor_goto(grid->handle, put_dirty_row, - MIN(put_dirty_last, grid->Columns-1)); + ui_grid_cursor_goto(put_dirty_grid->handle, put_dirty_row, + MIN(put_dirty_last, put_dirty_grid->Columns-1)); } - ui_line(grid, put_dirty_row, put_dirty_first, put_dirty_last, + ui_line(put_dirty_grid, put_dirty_row, put_dirty_first, put_dirty_last, put_dirty_last, 0, false); put_dirty_first = INT_MAX; put_dirty_last = 0; } put_dirty_row = -1; + put_dirty_grid = NULL; } /* @@ -5971,7 +6005,14 @@ void grid_assign_handle(ScreenGrid *grid) /// needed. void screenalloc(void) { - static bool entered = false; // avoid recursiveness + // It's possible that we produce an out-of-memory message below, which + // will cause this function to be called again. To break the loop, just + // return here. + if (resizing) { + return; + } + resizing = true; + int retry_count = 0; retry: @@ -5985,19 +6026,11 @@ retry: || Rows == 0 || Columns == 0 || (!full_screen && default_grid.chars == NULL)) { + resizing = false; return; } /* - * It's possible that we produce an out-of-memory message below, which - * will cause this function to be called again. To break the loop, just - * return here. - */ - if (entered) - return; - entered = TRUE; - - /* * Note that the window sizes are updated before reallocating the arrays, * thus we must not redraw here! */ @@ -6037,8 +6070,7 @@ retry: must_redraw = CLEAR; // need to clear the screen later - entered = FALSE; - --RedrawingDisabled; + RedrawingDisabled--; /* * Do not apply autocommands more than 3 times to avoid an endless loop @@ -6050,14 +6082,16 @@ retry: * jump back to check if we need to allocate the screen again. */ goto retry; } + + resizing = false; } void grid_alloc(ScreenGrid *grid, int rows, int columns, bool copy, bool valid) { int new_row; ScreenGrid new = *grid; - - size_t ncells = (size_t)((rows+1) * columns); + assert(rows >= 0 && columns >= 0); + size_t ncells = (size_t)rows * columns; new.chars = xmalloc(ncells * sizeof(schar_T)); new.attrs = xmalloc(ncells * sizeof(sattr_T)); new.line_offset = xmalloc((size_t)(rows * sizeof(unsigned))); @@ -6623,7 +6657,7 @@ static void recording_mode(int attr) /* * Draw the tab pages line at the top of the Vim window. */ -static void draw_tabline(void) +void draw_tabline(void) { int tabcount = 0; int tabwidth = 0; @@ -6781,13 +6815,11 @@ static void draw_tabline(void) c = '_'; else c = ' '; - grid_fill(&default_grid, 0, 1, col, (int)Columns, c, c, - attr_fill); + grid_fill(&default_grid, 0, 1, col, Columns, c, c, attr_fill); /* Put an "X" for closing the current tab if there are several. */ if (first_tabpage->tp_next != NULL) { - grid_putchar(&default_grid, 'X', 0, (int)Columns - 1, - attr_nosel); + grid_putchar(&default_grid, 'X', 0, Columns - 1, attr_nosel); tab_page_click_defs[Columns - 1] = (StlClickDefinition) { .type = kStlClickTabClose, .tabnr = 999, @@ -7150,6 +7182,8 @@ void screen_resize(int width, int height) check_shellsize(); height = Rows; width = Columns; + p_lines = Rows; + p_columns = Columns; ui_call_grid_resize(1, width, height); send_grid_resize = true; diff --git a/src/nvim/search.c b/src/nvim/search.c index 4d02a07cbd..fe4fdf57ba 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -352,7 +352,7 @@ int ignorecase_opt(char_u *pat, int ic_in, int scs) { int ic = ic_in; if (ic && !no_smartcase && scs - && !(ctrl_x_mode && curbuf->b_p_inf) + && !(ctrl_x_mode_not_default() && curbuf->b_p_inf) ) { ic = !pat_has_uppercase(pat); } @@ -864,13 +864,11 @@ int searchit( } at_first_line = FALSE; - /* - * Stop the search if wrapscan isn't set, "stop_lnum" is - * specified, after an interrupt, after a match and after looping - * twice. - */ + // Stop the search if wrapscan isn't set, "stop_lnum" is + // specified, after an interrupt, after a match and after looping + // twice. if (!p_ws || stop_lnum != 0 || got_int || called_emsg - || (timed_out != NULL && timed_out) + || (timed_out != NULL && *timed_out) || break_loop || found || loop) { break; @@ -1132,14 +1130,14 @@ int do_search( && !cmd_silent && msg_silent == 0) { char_u *trunc; char_u off_buf[40]; - int off_len = 0; + size_t off_len = 0; // Compute msg_row early. msg_start(); // Get the offset, so we know how long it is. if (spats[0].off.line || spats[0].off.end || spats[0].off.off) { - p = off_buf; + p = off_buf; // -V507 *p++ = dirc; if (spats[0].off.end) { *p++ = 'e'; @@ -1168,12 +1166,14 @@ int do_search( // search stat. Use all the space available, so that the // search state is right aligned. If there is not enough space // msg_strtrunc() will shorten in the middle. - if (msg_scrolled != 0) { + if (ui_has(kUIMessages)) { + len = 0; // adjusted below + } else if (msg_scrolled != 0) { // Use all the columns. - len = (int)(Rows - msg_row) * Columns - 1; + len = (Rows - msg_row) * Columns - 1; } else { // Use up to 'showcmd' column. - len = (int)(Rows - msg_row - 1) * Columns + sc_col - 1; + len = (Rows - msg_row - 1) * Columns + sc_col - 1; } if (len < STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3) { len = STRLEN(p) + off_len + SEARCH_STAT_BUF_LEN + 3; @@ -1183,7 +1183,7 @@ int do_search( len = STRLEN(p) + off_len + 3; } - msgbuf = xmalloc((int)len); + msgbuf = xmalloc(len); { memset(msgbuf, ' ', len); msgbuf[0] = dirc; @@ -1215,7 +1215,7 @@ int do_search( xfree(msgbuf); msgbuf = r; // move reversed text to beginning of buffer - while (*r != NUL && *r == ' ') { + while (*r == ' ') { r++; } size_t pat_len = msgbuf + STRLEN(msgbuf) - r; @@ -3368,7 +3368,6 @@ current_tagblock( ) { long count = count_arg; - long n; pos_T old_pos; pos_T start_pos; pos_T end_pos; @@ -3377,7 +3376,6 @@ current_tagblock( char_u *p; char_u *cp; int len; - int r; bool do_include = include; bool save_p_ws = p_ws; int retval = FAIL; @@ -3426,12 +3424,12 @@ again: * Search backwards for unclosed "<aaa>". * Put this position in start_pos. */ - for (n = 0; n < count; ++n) { - if (do_searchpair((char_u *) - "<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)", - (char_u *)"", - (char_u *)"</[^>]*>", BACKWARD, (char_u *)"", 0, - NULL, (linenr_T)0, 0L) <= 0) { + for (long n = 0; n < count; n++) { + if (do_searchpair( + (char_u *)"<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)", + (char_u *)"", + (char_u *)"</[^>]*>", BACKWARD, NULL, 0, + NULL, (linenr_T)0, 0L) <= 0) { curwin->w_cursor = old_pos; goto theend; } @@ -3457,8 +3455,8 @@ again: sprintf((char *)spat, "<%.*s\\>\\%%(\\s\\_[^>]\\{-}[^/]>\\|>\\)\\c", len, p); sprintf((char *)epat, "</%.*s>\\c", len, p); - r = do_searchpair(spat, (char_u *)"", epat, FORWARD, (char_u *)"", - 0, NULL, (linenr_T)0, 0L); + const int r = do_searchpair(spat, (char_u *)"", epat, FORWARD, NULL, + 0, NULL, (linenr_T)0, 0L); xfree(spat); xfree(epat); @@ -4244,6 +4242,7 @@ static void search_stat(int dirc, pos_T *pos, // STRNICMP ignores case, but we should not ignore case. // Unfortunately, there is no STRNICMP function. if (!(chgtick == buf_get_changedtick(curbuf) + && lastpat != NULL // supress clang/NULL passed as nonnull parameter && STRNICMP(lastpat, spats[last_idx].pat, STRLEN(lastpat)) == 0 && STRLEN(lastpat) == STRLEN(spats[last_idx].pat) && equalpos(lastpos, curwin->w_cursor) @@ -4289,7 +4288,7 @@ static void search_stat(int dirc, pos_T *pos, if (curwin->w_p_rl && *curwin->w_p_rlc == 's') { if (cur == OUT_OF_TIME) { - vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?/??]"); + vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[?\?/?]"); } else if (cnt > 99 && cur > 99) { vim_snprintf(t, SEARCH_STAT_BUF_LEN, "[>99/>99]"); } else if (cnt > 99) { @@ -4328,6 +4327,7 @@ static void search_stat(int dirc, pos_T *pos, // keep the message even after redraw, but don't put in history msg_hist_off = true; + msg_ext_set_kind("search_count"); give_warning(msgbuf, false); msg_hist_off = false; } @@ -4343,8 +4343,8 @@ find_pattern_in_path( char_u *ptr, // pointer to search pattern int dir, // direction of expansion size_t len, // length of search pattern - int whole, // match whole words only - int skip_comments, // don't match inside comments + bool whole, // match whole words only + bool skip_comments, // don't match inside comments int type, // Type of search; are we looking for a type? // a macro? long count, @@ -4574,10 +4574,9 @@ find_pattern_in_path( xfree(files); files = bigger; } - if ((files[depth + 1].fp = mch_fopen((char *)new_fname, "r")) - == NULL) + if ((files[depth + 1].fp = os_fopen((char *)new_fname, "r")) == NULL) { xfree(new_fname); - else { + } else { if (++depth == old_files) { // Something wrong. We will forget one of our already visited files // now. diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 4aafc669dc..3ebe75b980 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -151,15 +151,6 @@ KHASH_SET_INIT_STR(strset) /// Callback function for add_search_pattern typedef void (*SearchPatternGetter)(SearchPattern *); -/// Flags for shada_read_file and children -typedef enum { - kShaDaWantInfo = 1, ///< Load non-mark information - kShaDaWantMarks = 2, ///< Load local file marks and change list - kShaDaForceit = 4, ///< Overwrite info already read - kShaDaGetOldfiles = 8, ///< Load v:oldfiles. - kShaDaMissingError = 16, ///< Error out when os_open returns -ENOENT. -} ShaDaReadFileFlags; - /// Possible ShaDa entry types /// /// @warning Enum values are part of the API and must not be altered. @@ -283,7 +274,7 @@ typedef struct { char sep; list_T *additional_elements; } history_item; - struct reg { + struct reg { // yankreg_T char name; MotionType type; char **contents; @@ -1328,13 +1319,13 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) break; } if (!force) { - const yankreg_T *const reg = op_register_get(cur_entry.data.reg.name); + const yankreg_T *const reg = op_reg_get(cur_entry.data.reg.name); if (reg == NULL || reg->timestamp >= cur_entry.timestamp) { shada_free_shada_entry(&cur_entry); break; } } - if (!op_register_set(cur_entry.data.reg.name, (yankreg_T) { + if (!op_reg_set(cur_entry.data.reg.name, (yankreg_T) { .y_array = (char_u **)cur_entry.data.reg.contents, .y_size = cur_entry.data.reg.contents_size, .y_type = cur_entry.data.reg.type, @@ -2496,7 +2487,7 @@ static inline void shada_initialize_registers(WriteMergerState *const wms, yankreg_T reg; char name = NUL; bool is_unnamed = false; - reg_iter = op_register_iter(reg_iter, &name, ®, &is_unnamed); + reg_iter = op_global_reg_iter(reg_iter, &name, ®, &is_unnamed); if (name == NUL) { break; } @@ -2552,6 +2543,19 @@ static inline void replace_numbered_mark(WriteMergerState *const wms, wms->numbered_marks[idx].data.data.filemark.name = (char)('0' + (int)idx); } +/// Find buffers ignored due to their location. +/// +/// @param[out] removable_bufs Cache of buffers ignored due to their location. +static inline void find_removable_bufs(khash_t(bufset) *removable_bufs) +{ + FOR_ALL_BUFFERS(buf) { + if (buf->b_ffname != NULL && shada_removable((char *)buf->b_ffname)) { + int kh_ret; + (void)kh_put(bufset, removable_bufs, (uintptr_t)buf, &kh_ret); + } + } +} + /// Write ShaDa file /// /// @param[in] sd_writer Structure containing file writer definition. @@ -2621,12 +2625,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, set_last_cursor(wp); } - FOR_ALL_BUFFERS(buf) { - if (buf->b_ffname != NULL && shada_removable((char *) buf->b_ffname)) { - int kh_ret; - (void) kh_put(bufset, &removable_bufs, (uintptr_t) buf, &kh_ret); - } - } + find_removable_bufs(&removable_bufs); // Write header if (shada_pack_entry(packer, (ShadaEntry) { @@ -2673,7 +2672,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, do { typval_T vartv; const char *name = NULL; - var_iter = var_shada_iter(var_iter, &name, &vartv); + var_iter = var_shada_iter(var_iter, &name, &vartv, VAR_FLAVOUR_SHADA); if (name == NULL) { break; } @@ -2737,49 +2736,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } // Initialize jump list - const void *jump_iter = NULL; - cleanup_jumplist(curwin, false); - setpcmark(); - do { - xfmark_T fm; - jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm); - - if (fm.fmark.mark.lnum == 0) { - iemsgf("ShaDa: mark lnum zero (ji:%p, js:%p, len:%i)", - (void *)jump_iter, (void *)&curwin->w_jumplist[0], - curwin->w_jumplistlen); - continue; - } - const buf_T *const buf = (fm.fmark.fnum == 0 - ? NULL - : buflist_findnr(fm.fmark.fnum)); - if (buf != NULL - ? in_bufset(&removable_bufs, buf) - : fm.fmark.fnum != 0) { - continue; - } - const char *const fname = (char *) (fm.fmark.fnum == 0 - ? (fm.fname == NULL ? NULL : fm.fname) - : buf->b_ffname); - if (fname == NULL) { - continue; - } - wms->jumps[wms->jumps_size++] = (PossiblyFreedShadaEntry) { - .can_free_entry = false, - .data = { - .type = kSDItemJump, - .timestamp = fm.fmark.timestamp, - .data = { - .filemark = { - .name = NUL, - .mark = fm.fmark.mark, - .fname = (char *) fname, - .additional_data = fm.fmark.additional_data, - } - } - } - }; - } while (jump_iter != NULL); + wms->jumps_size = shada_init_jumps(wms->jumps, &removable_bufs); // Initialize global marks if (dump_global_marks) { @@ -4117,3 +4074,236 @@ static bool shada_removable(const char *name) xfree(new_name); return retval; } + +/// Initialize ShaDa jumplist entries. +/// +/// @param[in,out] jumps Array of ShaDa entries to set. +/// @param[in] removable_bufs Cache of buffers ignored due to their +/// location. +/// +/// @return number of jumplist entries +static inline size_t shada_init_jumps( + PossiblyFreedShadaEntry *jumps, khash_t(bufset) *const removable_bufs) +{ + // Initialize jump list + size_t jumps_size = 0; + const void *jump_iter = NULL; + setpcmark(); + cleanup_jumplist(curwin, false); + do { + xfmark_T fm; + jump_iter = mark_jumplist_iter(jump_iter, curwin, &fm); + + if (fm.fmark.mark.lnum == 0) { + iemsgf("ShaDa: mark lnum zero (ji:%p, js:%p, len:%i)", + (void *)jump_iter, (void *)&curwin->w_jumplist[0], + curwin->w_jumplistlen); + continue; + } + const buf_T *const buf = (fm.fmark.fnum == 0 + ? NULL + : buflist_findnr(fm.fmark.fnum)); + if (buf != NULL + ? in_bufset(removable_bufs, buf) + : fm.fmark.fnum != 0) { + continue; + } + const char *const fname = (char *) (fm.fmark.fnum == 0 + ? (fm.fname == NULL ? NULL : fm.fname) + : buf->b_ffname); + if (fname == NULL) { + continue; + } + jumps[jumps_size++] = (PossiblyFreedShadaEntry) { + .can_free_entry = false, + .data = { + .type = kSDItemJump, + .timestamp = fm.fmark.timestamp, + .data = { + .filemark = { + .name = NUL, + .mark = fm.fmark.mark, + .fname = (char *) fname, + .additional_data = fm.fmark.additional_data, + } + } + } + }; + } while (jump_iter != NULL); + return jumps_size; +} + +/// Write registers ShaDa entries in given msgpack_sbuffer. +/// +/// @param[in] sbuf target msgpack_sbuffer to write to. +void shada_encode_regs(msgpack_sbuffer *const sbuf) + FUNC_ATTR_NONNULL_ALL +{ + WriteMergerState *const wms = xcalloc(1, sizeof(*wms)); + shada_initialize_registers(wms, -1); + msgpack_packer packer; + msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write); + for (size_t i = 0; i < ARRAY_SIZE(wms->registers); i++) { + if (wms->registers[i].data.type == kSDItemRegister) { + if (kSDWriteFailed + == shada_pack_pfreed_entry(&packer, wms->registers[i], 0)) { + abort(); + } + } + } + xfree(wms); +} + +/// Write jumplist ShaDa entries in given msgpack_sbuffer. +/// +/// @param[in] sbuf target msgpack_sbuffer to write to. +void shada_encode_jumps(msgpack_sbuffer *const sbuf) + FUNC_ATTR_NONNULL_ALL +{ + khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset); + find_removable_bufs(&removable_bufs); + PossiblyFreedShadaEntry jumps[JUMPLISTSIZE]; + size_t jumps_size = shada_init_jumps(jumps, &removable_bufs); + msgpack_packer packer; + msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write); + for (size_t i = 0; i < jumps_size; i++) { + if (kSDWriteFailed == shada_pack_pfreed_entry(&packer, jumps[i], 0)) { + abort(); + } + } +} + +/// Write buffer list ShaDa entry in given msgpack_sbuffer. +/// +/// @param[in] sbuf target msgpack_sbuffer to write to. +void shada_encode_buflist(msgpack_sbuffer *const sbuf) + FUNC_ATTR_NONNULL_ALL +{ + khash_t(bufset) removable_bufs = KHASH_EMPTY_TABLE(bufset); + find_removable_bufs(&removable_bufs); + ShadaEntry buflist_entry = shada_get_buflist(&removable_bufs); + msgpack_packer packer; + msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write); + if (kSDWriteFailed == shada_pack_entry(&packer, buflist_entry, 0)) { + abort(); + } + xfree(buflist_entry.data.buffer_list.buffers); +} + +/// Write global variables ShaDa entries in given msgpack_sbuffer. +/// +/// @param[in] sbuf target msgpack_sbuffer to write to. +void shada_encode_gvars(msgpack_sbuffer *const sbuf) + FUNC_ATTR_NONNULL_ALL +{ + msgpack_packer packer; + msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write); + const void *var_iter = NULL; + const Timestamp cur_timestamp = os_time(); + do { + typval_T vartv; + const char *name = NULL; + var_iter = var_shada_iter( + var_iter, &name, &vartv, + VAR_FLAVOUR_DEFAULT | VAR_FLAVOUR_SESSION | VAR_FLAVOUR_SHADA); + if (name == NULL) { + break; + } + if (vartv.v_type != VAR_FUNC && vartv.v_type != VAR_PARTIAL) { + typval_T tgttv; + tv_copy(&vartv, &tgttv); + ShaDaWriteResult r = shada_pack_entry(&packer, (ShadaEntry) { + .type = kSDItemVariable, + .timestamp = cur_timestamp, + .data = { + .global_var = { + .name = (char *)name, + .value = tgttv, + .additional_elements = NULL, + } + } + }, 0); + if (kSDWriteFailed == r) { + abort(); + } + tv_clear(&tgttv); + } + tv_clear(&vartv); + } while (var_iter != NULL); +} + +/// Wrapper for reading from msgpack_sbuffer. +/// +/// @return number of bytes read. +static ptrdiff_t read_sbuf(ShaDaReadDef *const sd_reader, void *const dest, + const size_t size) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + msgpack_sbuffer *sbuf = (msgpack_sbuffer *)sd_reader->cookie; + const uintmax_t bytes_read = MIN(size, sbuf->size - sd_reader->fpos); + if (bytes_read < size) { + sd_reader->eof = true; + } + memcpy(dest, sbuf->data + sd_reader->fpos, (size_t)bytes_read); + sd_reader->fpos += bytes_read; + return (ptrdiff_t)bytes_read; +} + +/// Wrapper for read that ignores bytes read from msgpack_sbuffer. +/// +/// Used for skipping. +/// +/// @param[in,out] sd_reader ShaDaReadDef with msgpack_sbuffer. +/// @param[in] offset Amount of bytes to skip. +/// +/// @return FAIL in case of failure, OK in case of success. May set +/// sd_reader->eof. +static int sd_sbuf_reader_skip_read(ShaDaReadDef *const sd_reader, + const size_t offset) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT +{ + msgpack_sbuffer *sbuf = (msgpack_sbuffer *)sd_reader->cookie; + assert(sbuf->size >= sd_reader->fpos); + const uintmax_t skip_bytes = MIN(offset, sbuf->size - sd_reader->fpos); + if (skip_bytes < offset) { + sd_reader->eof = true; + return FAIL; + } + sd_reader->fpos += offset; + return OK; +} + +/// Prepare ShaDaReadDef with msgpack_sbuffer for reading. +/// +/// @param[in] sbuf msgpack_sbuffer to read from. +/// @param[out] sd_reader Location where reader structure will be saved. +static void open_shada_sbuf_for_reading(const msgpack_sbuffer *const sbuf, + ShaDaReadDef *sd_reader) + FUNC_ATTR_NONNULL_ALL +{ + *sd_reader = (ShaDaReadDef) { + .read = &read_sbuf, + .close = NULL, + .skip = &sd_sbuf_reader_skip_read, + .error = NULL, + .eof = false, + .fpos = 0, + .cookie = (void *)sbuf, + }; +} + +/// Read ShaDa from msgpack_sbuffer. +/// +/// @param[in] file msgpack_sbuffer to read from. +/// @param[in] flags Flags, see ShaDaReadFileFlags enum. +void shada_read_sbuf(msgpack_sbuffer *const sbuf, const int flags) + FUNC_ATTR_NONNULL_ALL +{ + assert(sbuf != NULL); + if (sbuf->data == NULL) { + return; + } + ShaDaReadDef sd_reader; + open_shada_sbuf_for_reading(sbuf, &sd_reader); + shada_read(&sd_reader, flags); +} diff --git a/src/nvim/shada.h b/src/nvim/shada.h index 49986ac1c1..2a945a06bc 100644 --- a/src/nvim/shada.h +++ b/src/nvim/shada.h @@ -1,6 +1,17 @@ #ifndef NVIM_SHADA_H #define NVIM_SHADA_H +#include <msgpack.h> + +/// Flags for shada_read_file and children +typedef enum { + kShaDaWantInfo = 1, ///< Load non-mark information + kShaDaWantMarks = 2, ///< Load local file marks and change list + kShaDaForceit = 4, ///< Overwrite info already read + kShaDaGetOldfiles = 8, ///< Load v:oldfiles. + kShaDaMissingError = 16, ///< Error out when os_open returns -ENOENT. +} ShaDaReadFileFlags; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "shada.h.generated.h" #endif diff --git a/src/nvim/sign.c b/src/nvim/sign.c index 8c85fbdaa7..16ab00a52b 100644 --- a/src/nvim/sign.c +++ b/src/nvim/sign.c @@ -28,9 +28,6 @@ struct sign int sn_typenr; // type number of sign char_u *sn_name; // name of sign char_u *sn_icon; // name of pixmap -# ifdef FEAT_SIGN_ICONS - void *sn_image; // icon image -# endif char_u *sn_text; // text used instead of pixmap int sn_line_hl; // highlight ID for line int sn_text_hl; // highlight ID for text @@ -83,11 +80,8 @@ static signgroup_T * sign_group_ref(const char_u *groupname) hi = hash_lookup(&sg_table, (char *)groupname, STRLEN(groupname), hash); if (HASHITEM_EMPTY(hi)) { // new group - group = (signgroup_T *)xmalloc( - (unsigned)(sizeof(signgroup_T) + STRLEN(groupname))); - if (group == NULL) { - return NULL; - } + group = xmalloc((unsigned)(sizeof(signgroup_T) + STRLEN(groupname))); + STRCPY(group->sg_name, groupname); group->refcount = 1; group->next_sign_id = 1; @@ -188,10 +182,6 @@ static void insert_sign( newsign->typenr = typenr; if (group != NULL) { newsign->group = sign_group_ref(group); - if (newsign->group == NULL) { - xfree(newsign); - return; - } } else { newsign->group = NULL; } @@ -263,11 +253,7 @@ char_u * sign_typenr2name(int typenr) /// Return information about a sign in a Dict dict_T * sign_get_info(signlist_T *sign) { - dict_T *d; - - if ((d = tv_dict_alloc()) == NULL) { - return NULL; - } + dict_T *d = tv_dict_alloc(); tv_dict_add_nr(d, S_LEN("id"), sign->id); tv_dict_add_str(d, S_LEN("group"), ((sign->group == NULL) ? (char *)"" @@ -696,15 +682,6 @@ static void sign_define_init_icon(sign_T *sp, char_u *icon) xfree(sp->sn_icon); sp->sn_icon = vim_strsave(icon); backslash_halve(sp->sn_icon); -# ifdef FEAT_SIGN_ICONS - if (gui.in_use) { - out_flush(); - if (sp->sn_image != NULL) { - gui_mch_destroy_sign(sp->sn_image); - } - sp->sn_image = gui_mch_register_sign(sp->sn_icon); - } -# endif } /// Initialize the text for a new sign @@ -1347,8 +1324,8 @@ static void sign_getinfo(sign_T *sp, dict_T *retdict) /// Otherwise, return information about the specified sign. void sign_getlist(const char_u *name, list_T *retlist) { - sign_T *sp = first_sign; - dict_T *dict; + sign_T *sp = first_sign; + dict_T *dict; if (name != NULL) { sp = sign_find(name, NULL); @@ -1358,9 +1335,7 @@ void sign_getlist(const char_u *name, list_T *retlist) } for (; sp != NULL && !got_int; sp = sp->sn_next) { - if ((dict = tv_dict_alloc()) == NULL) { - return; - } + dict = tv_dict_alloc(); tv_list_append_dict(retlist, dict); sign_getinfo(sp, dict); @@ -1374,14 +1349,13 @@ void sign_getlist(const char_u *name, list_T *retlist) list_T *get_buffer_signs(buf_T *buf) FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { - signlist_T *sign; - dict_T *d; + signlist_T *sign; + dict_T *d; list_T *const l = tv_list_alloc(kListLenMayKnow); FOR_ALL_SIGNS_IN_BUF(buf, sign) { - if ((d = sign_get_info(sign)) != NULL) { - tv_list_append_dict(l, d); - } + d = sign_get_info(sign); + tv_list_append_dict(l, d); } return l; } @@ -1394,21 +1368,16 @@ static void sign_get_placed_in_buf( const char_u *sign_group, list_T *retlist) { - dict_T *d; - list_T *l; - signlist_T *sign; - dict_T *sdict; + dict_T *d; + list_T *l; + signlist_T *sign; - if ((d = tv_dict_alloc()) == NULL) { - return; - } + d = tv_dict_alloc(); tv_list_append_dict(retlist, d); tv_dict_add_nr(d, S_LEN("bufnr"), (long)buf->b_fnum); - if ((l = tv_list_alloc(kListLenMayKnow)) == NULL) { - return; - } + l = tv_list_alloc(kListLenMayKnow); tv_dict_add_list(d, S_LEN("signs"), l); FOR_ALL_SIGNS_IN_BUF(buf, sign) { @@ -1419,9 +1388,7 @@ static void sign_get_placed_in_buf( || (sign_id == 0 && lnum == sign->lnum) || (lnum == 0 && sign_id == sign->id) || (lnum == sign->lnum && sign_id == sign->id)) { - if ((sdict = sign_get_info(sign)) != NULL) { - tv_list_append_dict(l, sdict); - } + tv_list_append_dict(l, sign_get_info(sign)); } } } @@ -1448,21 +1415,6 @@ void sign_get_placed( } } -# if defined(FEAT_SIGN_ICONS) || defined(PROTO) -/// Allocate the icons. Called when the GUI has started. Allows defining -/// signs before it starts. -void sign_gui_started(void) -{ - sign_T *sp; - - for (sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (sp->sn_icon != NULL) { - sp->sn_image = gui_mch_register_sign(sp->sn_icon); - } - } -} -# endif - /// List one sign. static void sign_list_defined(sign_T *sp) { @@ -1566,22 +1518,6 @@ char_u * sign_get_text(int typenr) return NULL; } -# if defined(FEAT_SIGN_ICONS) || defined(PROTO) -void * sign_get_image( - int typenr // the attribute which may have a sign -) -{ - sign_T *sp; - - for (sp = first_sign; sp != NULL; sp = sp->sn_next) { - if (sp->sn_typenr == typenr) { - return sp->sn_image; - } - } - return NULL; -} -# endif - /// Undefine/free all signs. void free_signs(void) { diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 6fd22a6537..8d800843f8 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -84,6 +84,7 @@ #include "nvim/ascii.h" #include "nvim/spell.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/charset.h" #include "nvim/cursor.h" #include "nvim/edit.h" @@ -1626,7 +1627,7 @@ static void spell_load_lang(char_u *lang) if (starting) { // Prompt the user at VimEnter if spell files are missing. #3027 // Plugins aren't loaded yet, so spellfile.vim cannot handle this case. - char autocmd_buf[128] = { 0 }; + char autocmd_buf[512] = { 0 }; snprintf(autocmd_buf, sizeof(autocmd_buf), "autocmd VimEnter * call spellfile#LoadFile('%s')|set spell", lang); @@ -1807,9 +1808,11 @@ void count_common_word(slang_T *lp, char_u *word, int len, int count) char_u buf[MAXWLEN]; char_u *p; - if (len == -1) + if (len == -1) { p = word; - else { + } else if (len >= MAXWLEN) { + return; + } else { STRLCPY(buf, word, len + 1); p = buf; } @@ -2616,7 +2619,7 @@ static bool spell_mb_isword_class(int cl, win_T *wp) if (wp->w_s->b_cjk) // East Asian characters are not considered word characters. return cl == 2 || cl == 0x2800; - return cl >= 2 && cl != 0x2070 && cl != 0x2080; + return cl >= 2 && cl != 0x2070 && cl != 0x2080 && cl != 3; } // Returns true if "p" points to a word character. @@ -3262,7 +3265,7 @@ static void spell_suggest_file(suginfo_T *su, char_u *fname) char_u cword[MAXWLEN]; // Open the file. - fd = mch_fopen((char *)fname, "r"); + fd = os_fopen((char *)fname, "r"); if (fd == NULL) { EMSG2(_(e_notopen), fname); return; @@ -4500,7 +4503,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so sp->ts_state = STATE_SWAP3; break; } - if (c2 != NUL && TRY_DEEPER(su, stack, depth, SCORE_SWAP)) { + if (TRY_DEEPER(su, stack, depth, SCORE_SWAP)) { go_deeper(stack, depth, SCORE_SWAP); #ifdef DEBUG_TRIEWALK snprintf(changename[depth], sizeof(changename[0]), @@ -5283,7 +5286,7 @@ add_sound_suggest ( } // Go over the list of good words that produce this soundfold word - nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)(sfwordnr + 1), FALSE); + nrline = ml_get_buf(slang->sl_sugbuf, (linenr_T)sfwordnr + 1, false); orgnr = 0; while (*nrline != NUL) { // The wordnr was stored in a minimal nr of bytes as an offset to the diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 5f5f74cf2e..405e390589 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -586,7 +586,7 @@ spell_load_file ( int c = 0; int res; - fd = mch_fopen((char *)fname, "r"); + fd = os_fopen((char *)fname, "r"); if (fd == NULL) { if (!silent) EMSG2(_(e_notopen), fname); @@ -885,9 +885,10 @@ void suggest_load_files(void) continue; } STRCPY(dotp, ".sug"); - fd = mch_fopen((char *)slang->sl_fname, "r"); - if (fd == NULL) + fd = os_fopen((char *)slang->sl_fname, "r"); + if (fd == NULL) { goto nextone; + } // <SUGHEADER>: <fileID> <versionnr> <timestamp> for (i = 0; i < VIMSUGMAGICL; ++i) @@ -1984,7 +1985,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char_u *fname) char_u *sofoto = NULL; // SOFOTO value // Open the file. - fd = mch_fopen((char *)fname, "r"); + fd = os_fopen((char *)fname, "r"); if (fd == NULL) { EMSG2(_(e_notopen), fname); return NULL; @@ -3019,7 +3020,7 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile) int duplicate = 0; // Open the file. - fd = mch_fopen((char *)fname, "r"); + fd = os_fopen((char *)fname, "r"); if (fd == NULL) { EMSG2(_(e_notopen), fname); return FAIL; @@ -3539,7 +3540,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) int regionmask; // Open the file. - fd = mch_fopen((char *)fname, "r"); + fd = os_fopen((char *)fname, "r"); if (fd == NULL) { EMSG2(_(e_notopen), fname); return FAIL; @@ -4185,7 +4186,7 @@ static int write_vim_spell(spellinfo_T *spin, char_u *fname) int retval = OK; int regionmask; - FILE *fd = mch_fopen((char *)fname, "w"); + FILE *fd = os_fopen((char *)fname, "w"); if (fd == NULL) { EMSG2(_(e_notopen), fname); return FAIL; @@ -4986,7 +4987,7 @@ static int offset2bytes(int nr, char_u *buf) static void sug_write(spellinfo_T *spin, char_u *fname) { // Create the file. Note that an existing file is silently overwritten! - FILE *fd = mch_fopen((char *)fname, "w"); + FILE *fd = os_fopen((char *)fname, "w"); if (fd == NULL) { EMSG2(_(e_notopen), fname); return; @@ -5365,7 +5366,7 @@ spell_add_word ( if (bad || undo) { // When the word appears as good word we need to remove that one, // since its flags sort before the one with WF_BANNED. - fd = mch_fopen((char *)fname, "r"); + fd = os_fopen((char *)fname, "r"); if (fd != NULL) { while (!vim_fgets(line, MAXWLEN * 2, fd)) { fpos = fpos_next; @@ -5376,7 +5377,7 @@ spell_add_word ( // the start of the line. Mixing reading and writing // doesn't work for all systems, close the file first. fclose(fd); - fd = mch_fopen((char *)fname, "r+"); + fd = os_fopen((char *)fname, "r+"); if (fd == NULL) { break; } @@ -5399,7 +5400,7 @@ spell_add_word ( } if (!undo) { - fd = mch_fopen((char *)fname, "a"); + fd = os_fopen((char *)fname, "a"); if (fd == NULL && new_spf) { char_u *p; @@ -5415,7 +5416,7 @@ spell_add_word ( *p = NUL; os_mkdir((char *)fname, 0755); *p = c; - fd = mch_fopen((char *)fname, "a"); + fd = os_fopen((char *)fname, "a"); } } diff --git a/src/nvim/state.c b/src/nvim/state.c index e6eeaac8d0..7c7d035366 100644 --- a/src/nvim/state.c +++ b/src/nvim/state.c @@ -143,7 +143,7 @@ char *get_mode(void) } if (ins_compl_active()) { buf[1] = 'c'; - } else if (ctrl_x_mode == 1) { + } else if (ctrl_x_mode_not_defined_yet()) { buf[1] = 'x'; } } diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index 418756abc4..4fa70c0684 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -14,6 +14,7 @@ #include "nvim/vim.h" #include "nvim/ascii.h" +#include "nvim/api/private/helpers.h" #include "nvim/syntax.h" #include "nvim/charset.h" #include "nvim/cursor_shape.h" @@ -75,6 +76,8 @@ struct hl_group { uint8_t *sg_rgb_fg_name; ///< RGB foreground color name uint8_t *sg_rgb_bg_name; ///< RGB background color name uint8_t *sg_rgb_sp_name; ///< RGB special color name + + int sg_blend; ///< blend level (0-100 inclusive), -1 if unset }; /// \addtogroup SG_SET @@ -348,7 +351,7 @@ static reg_extmatch_T *next_match_extmatch = NULL; /* * A state stack is an array of integers or stateitem_T, stored in a - * garray_T. A state stack is invalid if it's itemsize entry is zero. + * garray_T. A state stack is invalid if its itemsize entry is zero. */ #define INVALID_STATE(ssp) ((ssp)->ga_itemsize == 0) #define VALID_STATE(ssp) ((ssp)->ga_itemsize != 0) @@ -997,6 +1000,7 @@ static void syn_stack_free_block(synblock_T *block) clear_syn_state(p); } XFREE_CLEAR(block->b_sst_array); + block->b_sst_first = NULL; block->b_sst_len = 0; } } @@ -1108,9 +1112,6 @@ static void syn_stack_apply_changes_block(synblock_T *block, buf_T *buf) synstate_T *p, *prev, *np; linenr_T n; - if (block->b_sst_array == NULL) /* nothing to do */ - return; - prev = NULL; for (p = block->b_sst_first; p != NULL; ) { if (p->sst_lnum + block->b_syn_sync_linebreaks > buf->b_mod_top) { @@ -1158,8 +1159,9 @@ static int syn_stack_cleanup(void) int dist; int retval = FALSE; - if (syn_block->b_sst_array == NULL || syn_block->b_sst_first == NULL) + if (syn_block->b_sst_first == NULL) { return retval; + } /* Compute normal distance between non-displayed entries. */ if (syn_block->b_sst_len <= Rows) @@ -2923,8 +2925,9 @@ static int syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T if (r > 0) ++st->match; } - if (timed_out) { + if (timed_out && !syn_win->w_s->b_syn_slow) { syn_win->w_s->b_syn_slow = true; + MSG(_("'redrawtime' exceeded, syntax highlighting disabled")); } if (r > 0) { @@ -3124,11 +3127,11 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) arg = skipwhite(arg); if (*arg == NUL) { MSG_PUTS("\n"); - MSG_PUTS(_("syntax iskeyword ")); if (curwin->w_s->b_syn_isk != empty_option) { + MSG_PUTS(_("syntax iskeyword ")); msg_outtrans(curwin->w_s->b_syn_isk); } else { - msg_outtrans((char_u *)"not set"); + msg_outtrans((char_u *)_("syntax iskeyword not set")); } } else { if (STRNICMP(arg, "clear", 5) == 0) { @@ -3606,7 +3609,7 @@ syn_list_one( continue; } - (void)syn_list_header(did_header, 999, id); + (void)syn_list_header(did_header, 0, id, true); did_header = true; last_matchgroup = 0; if (spp->sp_type == SPTYPE_MATCH) { @@ -3655,7 +3658,7 @@ syn_list_one( /* list the link, if there is one */ if (HL_TABLE()[id - 1].sg_link && (did_header || link_only) && !got_int) { - (void)syn_list_header(did_header, 999, id); + (void)syn_list_header(did_header, 0, id, true); msg_puts_attr("links to", attr); msg_putchar(' '); msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name); @@ -3797,7 +3800,6 @@ static bool syn_list_keywords( const int attr ) { - int outlen; int prev_contained = 0; const int16_t *prev_next_list = NULL; const int16_t *prev_cont_in_list = NULL; @@ -3815,17 +3817,20 @@ static bool syn_list_keywords( todo--; for (keyentry_T *kp = HI2KE(hi); kp != NULL && !got_int; kp = kp->ke_next) { if (kp->k_syn.id == id) { + int outlen = 0; + bool force_newline = false; if (prev_contained != (kp->flags & HL_CONTAINED) || prev_skipnl != (kp->flags & HL_SKIPNL) || prev_skipwhite != (kp->flags & HL_SKIPWHITE) || prev_skipempty != (kp->flags & HL_SKIPEMPTY) || prev_cont_in_list != kp->k_syn.cont_in_list - || prev_next_list != kp->next_list) - outlen = 9999; - else + || prev_next_list != kp->next_list) { + force_newline = true; + } else { outlen = (int)STRLEN(kp->keyword); - /* output "contained" and "nextgroup" on each line */ - if (syn_list_header(did_header, outlen, id)) { + } + // output "contained" and "nextgroup" on each line + if (syn_list_header(did_header, outlen, id, force_newline)) { prev_contained = 0; prev_next_list = NULL; prev_cont_in_list = NULL; @@ -5968,6 +5973,10 @@ static const char *highlight_init_both[] = { "default link Whitespace NonText", "default link MsgSeparator StatusLine", "default link NormalFloat Pmenu", + "RedrawDebugNormal cterm=reverse gui=reverse", + "RedrawDebugClear ctermbg=Yellow guibg=Yellow", + "RedrawDebugComposed ctermbg=Green guibg=Green", + "RedrawDebugRecompose ctermbg=Red guibg=Red", NULL }; @@ -6465,6 +6474,7 @@ void do_highlight(const char *line, const bool forceit, const bool init) int id; int idx; struct hl_group item_before; + bool did_change = false; bool dodefault = false; bool doclear = false; bool dolink = false; @@ -6821,18 +6831,23 @@ void do_highlight(const char *line, const bool forceit, const bool init) } } } else if (strcmp(key, "GUIFG") == 0) { + char_u **const namep = &HL_TABLE()[idx].sg_rgb_fg_name; + if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { if (!init) { HL_TABLE()[idx].sg_set |= SG_GUI; } - xfree(HL_TABLE()[idx].sg_rgb_fg_name); - if (strcmp(arg, "NONE")) { - HL_TABLE()[idx].sg_rgb_fg_name = (char_u *)xstrdup((char *)arg); - HL_TABLE()[idx].sg_rgb_fg = name_to_color((const char_u *)arg); - } else { - HL_TABLE()[idx].sg_rgb_fg_name = NULL; - HL_TABLE()[idx].sg_rgb_fg = -1; + if (*namep == NULL || STRCMP(*namep, arg) != 0) { + xfree(*namep); + if (strcmp(arg, "NONE") != 0) { + *namep = (char_u *)xstrdup(arg); + HL_TABLE()[idx].sg_rgb_fg = name_to_color((char_u *)arg); + } else { + *namep = NULL; + HL_TABLE()[idx].sg_rgb_fg = -1; + } + did_change = true; } } @@ -6840,18 +6855,23 @@ void do_highlight(const char *line, const bool forceit, const bool init) normal_fg = HL_TABLE()[idx].sg_rgb_fg; } } else if (STRCMP(key, "GUIBG") == 0) { + char_u **const namep = &HL_TABLE()[idx].sg_rgb_bg_name; + if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { if (!init) { HL_TABLE()[idx].sg_set |= SG_GUI; } - xfree(HL_TABLE()[idx].sg_rgb_bg_name); - if (STRCMP(arg, "NONE") != 0) { - HL_TABLE()[idx].sg_rgb_bg_name = (char_u *)xstrdup((char *)arg); - HL_TABLE()[idx].sg_rgb_bg = name_to_color((const char_u *)arg); - } else { - HL_TABLE()[idx].sg_rgb_bg_name = NULL; - HL_TABLE()[idx].sg_rgb_bg = -1; + if (*namep == NULL || STRCMP(*namep, arg) != 0) { + xfree(*namep); + if (STRCMP(arg, "NONE") != 0) { + *namep = (char_u *)xstrdup(arg); + HL_TABLE()[idx].sg_rgb_bg = name_to_color((char_u *)arg); + } else { + *namep = NULL; + HL_TABLE()[idx].sg_rgb_bg = -1; + } + did_change = true; } } @@ -6859,18 +6879,23 @@ void do_highlight(const char *line, const bool forceit, const bool init) normal_bg = HL_TABLE()[idx].sg_rgb_bg; } } else if (strcmp(key, "GUISP") == 0) { + char_u **const namep = &HL_TABLE()[idx].sg_rgb_sp_name; + if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { if (!init) { HL_TABLE()[idx].sg_set |= SG_GUI; } - xfree(HL_TABLE()[idx].sg_rgb_sp_name); - if (strcmp(arg, "NONE") != 0) { - HL_TABLE()[idx].sg_rgb_sp_name = (char_u *)xstrdup((char *)arg); - HL_TABLE()[idx].sg_rgb_sp = name_to_color((const char_u *)arg); - } else { - HL_TABLE()[idx].sg_rgb_sp_name = NULL; - HL_TABLE()[idx].sg_rgb_sp = -1; + if (*namep == NULL || STRCMP(*namep, arg) != 0) { + xfree(*namep); + if (strcmp(arg, "NONE") != 0) { + *namep = (char_u *)xstrdup(arg); + HL_TABLE()[idx].sg_rgb_sp = name_to_color((char_u *)arg); + } else { + *namep = NULL; + HL_TABLE()[idx].sg_rgb_sp = -1; + } + did_change = true; } } @@ -6879,6 +6904,12 @@ void do_highlight(const char *line, const bool forceit, const bool init) } } else if (strcmp(key, "START") == 0 || strcmp(key, "STOP") == 0) { // Ignored for now + } else if (strcmp(key, "BLEND") == 0) { + if (strcmp(arg, "NONE") != 0) { + HL_TABLE()[idx].sg_blend = strtol(arg, NULL, 10); + } else { + HL_TABLE()[idx].sg_blend = -1; + } } else { emsgf(_("E423: Illegal argument: %s"), key_start); error = true; @@ -6926,9 +6957,15 @@ void do_highlight(const char *line, const bool forceit, const bool init) // Only call highlight_changed() once, after a sequence of highlight // commands, and only if an attribute actually changed - if (memcmp(&HL_TABLE()[idx], &item_before, sizeof(item_before)) != 0 + if ((did_change + || memcmp(&HL_TABLE()[idx], &item_before, sizeof(item_before)) != 0) && !did_highlight_changed) { - redraw_all_later(NOT_VALID); + // Do not trigger a redraw when highlighting is changed while + // redrawing. This may happen when evaluating 'statusline' changes the + // StatusLine group. + if (!updating_screen) { + redraw_all_later(NOT_VALID); + } need_highlight_changed = true; } } @@ -6993,6 +7030,7 @@ static void highlight_clear(int idx) XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_fg_name); XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_bg_name); XFREE_CLEAR(HL_TABLE()[idx].sg_rgb_sp_name); + HL_TABLE()[idx].sg_blend = -1; // Clear the script ID only when there is no link, since that is not // cleared. if (HL_TABLE()[idx].sg_link == 0) { @@ -7013,6 +7051,10 @@ static void highlight_list_one(const int id) struct hl_group *const sgp = &HL_TABLE()[id - 1]; // index is ID minus one bool didh = false; + if (message_filtered(sgp->sg_name)) { + return; + } + didh = highlight_list_arg(id, didh, LIST_ATTR, sgp->sg_cterm, NULL, "cterm"); didh = highlight_list_arg(id, didh, LIST_INT, @@ -7029,8 +7071,11 @@ static void highlight_list_one(const int id) didh = highlight_list_arg(id, didh, LIST_STRING, 0, sgp->sg_rgb_sp_name, "guisp"); + didh = highlight_list_arg(id, didh, LIST_INT, + sgp->sg_blend+1, NULL, "blend"); + if (sgp->sg_link && !got_int) { - (void)syn_list_header(didh, 9999, id); + (void)syn_list_header(didh, 0, id, true); didh = true; msg_puts_attr("links to", HL_ATTR(HLF_D)); msg_putchar(' '); @@ -7075,7 +7120,8 @@ static bool highlight_list_arg( } } - (void)syn_list_header(didh, (int)(vim_strsize(ts) + STRLEN(name) + 1), id); + (void)syn_list_header(didh, (int)(vim_strsize(ts) + STRLEN(name) + 1), id, + false); didh = true; if (!got_int) { if (*name != NUL) { @@ -7193,12 +7239,14 @@ const char *highlight_color(const int id, const char *const what, /// @param did_header did header already /// @param outlen length of string that comes /// @param id highlight group id +/// @param force_newline always start a new line /// @return true when started a new line. static bool syn_list_header(const bool did_header, const int outlen, - const int id) + const int id, bool force_newline) { int endcol = 19; bool newline = true; + bool adjust = true; if (!did_header) { msg_putchar('\n'); @@ -7207,7 +7255,10 @@ static bool syn_list_header(const bool did_header, const int outlen, } msg_outtrans(HL_TABLE()[id - 1].sg_name); endcol = 15; - } else if (msg_col + outlen + 1 >= Columns) { + } else if ((ui_has(kUIMessages) || msg_silent) && !force_newline) { + msg_putchar(' '); + adjust = false; + } else if (msg_col + outlen + 1 >= Columns || force_newline) { msg_putchar('\n'); if (got_int) { return true; @@ -7218,12 +7269,14 @@ static bool syn_list_header(const bool did_header, const int outlen, } } - if (msg_col >= endcol) /* output at least one space */ - endcol = msg_col + 1; - if (Columns <= endcol) /* avoid hang for tiny window */ - endcol = Columns - 1; + if (adjust) { + if (msg_col >= endcol) { + // output at least one space + endcol = msg_col + 1; + } - msg_advance(endcol); + msg_advance(endcol); + } /* Show "xxx" with the attributes. */ if (!did_header) { @@ -7252,6 +7305,7 @@ static void set_hl_attr(int idx) at_en.rgb_fg_color = sgp->sg_rgb_fg_name ? sgp->sg_rgb_fg : -1; at_en.rgb_bg_color = sgp->sg_rgb_bg_name ? sgp->sg_rgb_bg : -1; at_en.rgb_sp_color = sgp->sg_rgb_sp_name ? sgp->sg_rgb_sp : -1; + at_en.hl_blend = sgp->sg_blend; sgp->sg_attr = hl_get_syn_attr(idx+1, at_en); @@ -7377,6 +7431,7 @@ static int syn_add_group(char_u *name) hlgp->sg_rgb_bg = -1; hlgp->sg_rgb_fg = -1; hlgp->sg_rgb_sp = -1; + hlgp->sg_blend = -1; hlgp->sg_name_u = vim_strsave_up(name); return highlight_ga.ga_len; /* ID is index plus one */ @@ -7476,6 +7531,12 @@ void highlight_changed(void) highlight_attr[hlf] = hl_get_ui_attr(hlf, final_id, hlf == (int)HLF_INACTIVE); + + if (highlight_attr[hlf] != highlight_attr_last[hlf]) { + ui_call_hl_group_set(cstr_as_string((char *)hlf_names[hlf]), + highlight_attr[hlf]); + highlight_attr_last[hlf] = highlight_attr[hlf]; + } } /* Setup the user highlights diff --git a/src/nvim/tag.c b/src/nvim/tag.c index 6e883a1c3d..88676abc2e 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -1303,8 +1303,9 @@ find_tags ( } } - if ((fp = mch_fopen((char *)tag_fname, "r")) == NULL) + if ((fp = os_fopen((char *)tag_fname, "r")) == NULL) { continue; + } if (p_verbose >= 5) { verbose_enter(); @@ -2235,8 +2236,13 @@ parse_match ( p = tagp->command; if (find_extra(&p) == OK) { tagp->command_end = p; - p += 2; /* skip ";\"" */ - if (*p++ == TAB) + if (p > tagp->command && p[-1] == '|') { + tagp->command_end = p - 1; // drop trailing bar + } else { + tagp->command_end = p; + } + p += 2; // skip ";\"" + if (*p++ == TAB) { while (ASCII_ISALPHA(*p)) { if (STRNCMP(p, "kind:", 5) == 0) { tagp->tagkind = p + 5; @@ -2252,6 +2258,7 @@ parse_match ( break; p = pt + 1; } + } } if (tagp->tagkind != NULL) { for (p = tagp->tagkind; @@ -2676,22 +2683,30 @@ static int find_extra(char_u **pp) { char_u *str = *pp; - /* Repeat for addresses separated with ';' */ + // Repeat for addresses separated with ';' for (;; ) { - if (ascii_isdigit(*str)) + if (ascii_isdigit(*str)) { str = skipdigits(str); - else if (*str == '/' || *str == '?') { - str = skip_regexp(str + 1, *str, FALSE, NULL); - if (*str != **pp) + } else if (*str == '/' || *str == '?') { + str = skip_regexp(str + 1, *str, false, NULL); + if (*str != **pp) { str = NULL; - else - ++str; - } else - str = NULL; + } else { + str++; + } + } else { + // not a line number or search string, look for terminator. + str = (char_u *)strstr((char *)str, "|;\""); + if (str != NULL) { + str++; + break; + } + } if (str == NULL || *str != ';' - || !(ascii_isdigit(str[1]) || str[1] == '/' || str[1] == '?')) + || !(ascii_isdigit(str[1]) || str[1] == '/' || str[1] == '?')) { break; - ++str; /* skip ';' */ + } + str++; // skip ';' } if (str != NULL && STRNCMP(str, ";\"", 2) == 0) { diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index d8d529d0f6..5aeea3223b 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -53,6 +53,7 @@ #include "nvim/macros.h" #include "nvim/mbyte.h" #include "nvim/buffer.h" +#include "nvim/change.h" #include "nvim/ascii.h" #include "nvim/getchar.h" #include "nvim/ui.h" @@ -1306,7 +1307,7 @@ static void refresh_screen(Terminal *term, buf_T *buf) static void adjust_topline(Terminal *term, buf_T *buf, long added) { - FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_buffer == buf) { linenr_T ml_end = buf->b_ml.ml_line_count; bool following = ml_end == wp->w_cursor.lnum + added; // cursor at end? diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 7b1f0f59cc..8b43d91e25 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -32,14 +32,12 @@ NEW_TESTS_ALOT := test_alot_utf8 test_alot NEW_TESTS_IN_ALOT := $(shell sed '/^source/ s/^source //;s/\.vim$$//' test_alot*.vim) # Ignored tests. # test_alot_latin: Nvim does not allow setting encoding. -# test_arglist: ported to Lua, but kept for easier merging. # test_autochdir: ported to Lua, but kept for easier merging. # test_eval_func: used as include in old-style test (test_eval.in). # test_listlbr: Nvim does not allow setting encoding. # test_largefile: uses too much resources to run on CI. NEW_TESTS_IGNORE := $(NEW_TESTS_IN_ALOT) $(NEW_TESTS_ALOT) \ test_alot_latin \ - test_arglist \ test_autochdir \ test_eval_func \ test_listlbr \ @@ -101,23 +99,24 @@ RM_ON_RUN := test.out X* viminfo RM_ON_START := test.ok RUN_VIM := $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE -i viminfo --headless --noplugin -s dotest.in +CLEAN_FILES := *.out \ + *.failed \ + *.res \ + *.rej \ + *.orig \ + *.tlog \ + test.log \ + messages \ + $(RM_ON_RUN) \ + $(RM_ON_START) \ + valgrind.* \ + .*.swp \ + .*.swo \ + .gdbinit \ + $(TMPDIR) \ + del clean: - -rm -rf *.out \ - *.failed \ - *.res \ - *.rej \ - *.orig \ - *.tlog \ - test.log \ - messages \ - $(RM_ON_RUN) \ - $(RM_ON_START) \ - valgrind.* \ - .*.swp \ - .*.swo \ - .gdbinit \ - $(TMPDIR) \ - del + $(RM) -rf $(CLEAN_FILES) test1.out: .gdbinit test1.in @echo "[OLDTEST-PREP] Running test1" diff --git a/src/nvim/testdir/runnvim.vim b/src/nvim/testdir/runnvim.vim index 396a3a6477..52e05cfbeb 100644 --- a/src/nvim/testdir/runnvim.vim +++ b/src/nvim/testdir/runnvim.vim @@ -20,7 +20,7 @@ function Main() let results = jobwait([job], 5 * 60 * 1000) " TODO(ZyX-I): Get colors let screen = getline(1, '$') - bwipeout! + bwipeout! " kills the job always. let stringified_events = map(s:logger.d_events, \'v:val[0] . ": " . ' . \'join(map(v:val[1], '. @@ -43,9 +43,6 @@ function Main() \]) write if results[0] != 0 - if results[0] != -1 - call jobstop(job) - endif cquit else qall diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index aaddc90c99..c8161b1f9b 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -35,8 +35,11 @@ if &lines < 24 || &columns < 80 echoerr error split test.log $put =error - w - cquit + write + split messages + call append(line('$'), error) + write + qa! endif " Common with all tests on all systems. @@ -121,7 +124,10 @@ func RunTheTest(test) exe 'call ' . a:test else try + let s:test = a:test + au VimLeavePre * call EarlyExit(s:test) exe 'call ' . a:test + au! VimLeavePre catch /^\cskipped/ call add(s:messages, ' Skipped') call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '', '')) @@ -175,6 +181,15 @@ func AfterTheTest() endif endfunc +func EarlyExit(test) + " It's OK for the test we use to test the quit detection. + if a:test != 'Test_zz_quit_detected()' + call add(v:errors, 'Test caused Vim to exit: ' . a:test) + endif + + call FinishTesting() +endfunc + " This function can be called by a test if it wants to abort testing. func FinishTesting() call AfterTheTest() @@ -200,7 +215,11 @@ func FinishTesting() write endif - let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test') + if s:done == 0 + let message = 'NO tests executed' + else + let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test') + endif echo message call add(s:messages, message) if s:fail > 0 @@ -244,7 +263,8 @@ else endif " Names of flaky tests. -let s:flaky = [ +let s:flaky_tests = [ + \ 'Test_cursorhold_insert()', \ 'Test_exit_callback_interval()', \ 'Test_oneshot()', \ 'Test_out_cb()', @@ -258,9 +278,11 @@ let s:flaky = [ \ 'Test_terminal_redir_file()', \ 'Test_terminal_tmap()', \ 'Test_with_partial_callback()', - \ 'Test_lambda_with_timer()', \ ] +" Pattern indicating a common flaky test failure. +let s:flaky_errors_re = 'StopVimInTerminal\|VerifyScreenDump' + " Locate Test_ functions and execute them. redir @q silent function /^Test_ @@ -285,7 +307,9 @@ for s:test in sort(s:tests) " Repeat a flaky test. Give up when: " - it fails again with the same message " - it fails five times (with a different mesage) - if len(v:errors) > 0 && index(s:flaky, s:test) >= 0 + if len(v:errors) > 0 + \ && (index(s:flaky_tests, s:test) >= 0 + \ || v:errors[0] =~ s:flaky_errors_re) while 1 call add(s:messages, 'Found errors in ' . s:test . ':') call extend(s:messages, v:errors) diff --git a/src/nvim/testdir/samples/memfile_test.c b/src/nvim/testdir/samples/memfile_test.c index 3c8f108255..c71a5c8f40 100644 --- a/src/nvim/testdir/samples/memfile_test.c +++ b/src/nvim/testdir/samples/memfile_test.c @@ -70,7 +70,7 @@ test_mf_hash(void) assert(mf_hash_find(&ht, key) == NULL); /* allocate and add new item */ - item = (mf_hashitem_T *)lalloc_clear(sizeof(mf_hashtab_T), FALSE); + item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE); assert(item != NULL); item->mhi_key = key; mf_hash_add_item(&ht, item); diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index c75b00d5de..f79fb9e518 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -7,16 +7,19 @@ endif let s:did_load = 1 " Align Nvim defaults to Vim. -set sidescroll=0 -set directory^=. -set undodir^=. set backspace= -set nrformats+=octal -set nohidden smarttab noautoindent noautoread complete-=i noruler noshowcmd -set listchars=eol:$ +set directory^=. set fillchars=vert:\|,fold:- -set shortmess-=F set laststatus=1 +set listchars=eol:$ +set nohidden smarttab noautoindent noautoread complete-=i noruler noshowcmd +set nrformats+=octal +set shortmess-=F +set sidescroll=0 +set tags=./tags,tags +set undodir^=. +set wildoptions= + " Prevent Nvim log from writing to stderr. let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index eb6798f353..6cc2d06a36 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -133,7 +133,11 @@ func s:kill_server(cmd) endif endfunc -" Wait for up to a second for "expr" to become true. +" Wait for up to a second for "expr" to become true. "expr" can be a +" stringified expression to evaluate, or a funcref without arguments. +" +" A second argument can be used to specify a different timeout in msec. +" " Return time slept in milliseconds. With the +reltime feature this can be " more than the actual waiting time. Without +reltime it can also be less. func WaitFor(expr, ...) @@ -144,8 +148,13 @@ func WaitFor(expr, ...) else let slept = 0 endif + if type(a:expr) == v:t_func + let Test = a:expr + else + let Test = {-> eval(a:expr) } + endif for i in range(timeout / 10) - if eval(a:expr) + if Test() if has('reltime') return float2nr(reltimefloat(reltime(start)) * 1000) endif @@ -156,7 +165,7 @@ func WaitFor(expr, ...) endif sleep 10m endfor - return timeout + throw 'WaitFor() timed out after ' . timeout . ' msec' endfunc " Wait for up to a given milliseconds. @@ -188,12 +197,18 @@ func s:feedkeys(timer) endfunc " Get the command to run Vim, with -u NONE and --headless arguments. +" If there is an argument use it instead of "NONE". " Returns an empty string on error. -func GetVimCommand() +func GetVimCommand(...) + if a:0 == 0 + let name = 'NONE' + else + let name = a:1 + endif let cmd = v:progpath - let cmd = substitute(cmd, '-u \f\+', '-u NONE', '') - if cmd !~ '-u NONE' - let cmd = cmd . ' -u NONE' + let cmd = substitute(cmd, '-u \f\+', '-u ' . name, '') + if cmd !~ '-u '. name + let cmd = cmd . ' -u ' . name endif let cmd .= ' --headless -i NONE' let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '') @@ -246,3 +261,7 @@ func! Screenline(lnum) let line = join(chars, '') return matchstr(line, '^.\{-}\ze\s*$') endfunc + +func CanRunGui() + return has('gui') && ($DISPLAY != "" || has('gui_running')) +endfunc diff --git a/src/nvim/testdir/test49.vim b/src/nvim/testdir/test49.vim index 467abcd9b9..837e55ebca 100644 --- a/src/nvim/testdir/test49.vim +++ b/src/nvim/testdir/test49.vim @@ -1,6 +1,6 @@ " Vim script language tests " Author: Servatius Brandt <Servatius.Brandt@fujitsu-siemens.com> -" Last Change: 2016 Feb 07 +" Last Change: 2019 May 24 "------------------------------------------------------------------------------- " Test environment {{{1 @@ -9005,5 +9005,4 @@ Xcheck 50443995 "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=4 tw=80 fdm=marker -" vim: fdt=substitute(substitute(foldtext(),\ '\\%(^+--\\)\\@<=\\(\\s*\\)\\(.\\{-}\\)\:\ \\%(\"\ \\)\\=\\(Test\ \\d*\\)\:\\s*',\ '\\3\ (\\2)\:\ \\1',\ \"\"),\ '\\(Test\\s*\\)\\(\\d\\)\\D\\@=',\ '\\1\ \\2',\ "") "------------------------------------------------------------------------------- diff --git a/src/nvim/testdir/test_arabic.vim b/src/nvim/testdir/test_arabic.vim index 17e925ee7f..d67f875f97 100644 --- a/src/nvim/testdir/test_arabic.vim +++ b/src/nvim/testdir/test_arabic.vim @@ -524,54 +524,6 @@ func Test_shape_final() bwipe! endfunc -func Test_shape_final_to_medial() - new - set arabicshape - - " Shaping arabic {testchar} arabic Tests chg_c_f2m(). - " This does not test much... - " pair[0] = testchar, pair[1] = current-result - for pair in [[s:a_f_YEH_HAMZA, s:a_f_BEH], - \[s:a_f_WAW_HAMZA, s:a_s_BEH], - \[s:a_f_ALEF, s:a_s_BEH], - \[s:a_f_TEH_MARBUTA, s:a_s_BEH], - \[s:a_f_DAL, s:a_s_BEH], - \[s:a_f_THAL, s:a_s_BEH], - \[s:a_f_REH, s:a_s_BEH], - \[s:a_f_ZAIN, s:a_s_BEH], - \[s:a_f_WAW, s:a_s_BEH], - \[s:a_f_ALEF_MAKSURA, s:a_s_BEH], - \[s:a_f_BEH, s:a_f_BEH], - \[s:a_f_TEH, s:a_f_BEH], - \[s:a_f_THEH, s:a_f_BEH], - \[s:a_f_JEEM, s:a_f_BEH], - \[s:a_f_HAH, s:a_f_BEH], - \[s:a_f_KHAH, s:a_f_BEH], - \[s:a_f_SEEN, s:a_f_BEH], - \[s:a_f_SHEEN, s:a_f_BEH], - \[s:a_f_SAD, s:a_f_BEH], - \[s:a_f_DAD, s:a_f_BEH], - \[s:a_f_TAH, s:a_f_BEH], - \[s:a_f_ZAH, s:a_f_BEH], - \[s:a_f_AIN, s:a_f_BEH], - \[s:a_f_GHAIN, s:a_f_BEH], - \[s:a_f_FEH, s:a_f_BEH], - \[s:a_f_QAF, s:a_f_BEH], - \[s:a_f_KAF, s:a_f_BEH], - \[s:a_f_LAM, s:a_f_BEH], - \[s:a_f_MEEM, s:a_f_BEH], - \[s:a_f_NOON, s:a_f_BEH], - \[s:a_f_HEH, s:a_f_BEH], - \[s:a_f_YEH, s:a_f_BEH], - \ ] - call setline(1, ' ' . s:a_BEH . pair[0]) - call assert_equal([' ' . pair[1] . pair[0]], ScreenLines(1, 3)) - endfor - - set arabicshape& - bwipe! -endfunc - func Test_shape_combination_final() new set arabicshape diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim index 6468819198..df72ff0a32 100644 --- a/src/nvim/testdir/test_arglist.vim +++ b/src/nvim/testdir/test_arglist.vim @@ -74,7 +74,6 @@ func Test_argadd() call assert_equal(1, len(argv())) call assert_equal('some file', get(argv(), 0, '')) - call delete('Xargadd') %argd new arga @@ -122,10 +121,7 @@ func Test_argument() call assert_equal(['d', 'c', 'b', 'a', 'c'], g:buffers) - redir => result - ar - redir END - call assert_true(result =~# 'a b \[c] d') + call assert_equal("\na b [c] d ", execute(':args')) .argd call assert_equal(['a', 'b', 'd'], argv()) @@ -154,6 +150,25 @@ func Test_argument() let &hidden = save_hidden + let save_columns = &columns + let &columns = 79 + exe 'args ' .. join(range(1, 81)) + call assert_equal(join([ + \ '', + \ '[1] 6 11 16 21 26 31 36 41 46 51 56 61 66 71 76 81 ', + \ '2 7 12 17 22 27 32 37 42 47 52 57 62 67 72 77 ', + \ '3 8 13 18 23 28 33 38 43 48 53 58 63 68 73 78 ', + \ '4 9 14 19 24 29 34 39 44 49 54 59 64 69 74 79 ', + \ '5 10 15 20 25 30 35 40 45 50 55 60 65 70 75 80 ', + \ ], "\n"), + \ execute('args')) + + " No trailing newline with one item per row. + let long_arg = repeat('X', 81) + exe 'args ' .. long_arg + call assert_equal("\n[".long_arg.']', execute('args')) + let &columns = save_columns + " Setting argument list should fail when the current buffer has unsaved " changes %argd @@ -171,6 +186,34 @@ func Test_argument() call assert_fails('argument', 'E163:') endfunc +func Test_list_arguments() + " Clean the argument list + arga a | %argd + + " four args half the screen width makes two lines with two columns + let aarg = repeat('a', &columns / 2 - 4) + let barg = repeat('b', &columns / 2 - 4) + let carg = repeat('c', &columns / 2 - 4) + let darg = repeat('d', &columns / 2 - 4) + exe 'argadd ' aarg barg carg darg + + redir => result + args + redir END + call assert_match('\[' . aarg . '] \+' . carg . '\n' . barg . ' \+' . darg, trim(result)) + + " if one arg is longer than half the screen make one column + exe 'argdel' aarg + let aarg = repeat('a', &columns / 2 + 2) + exe '0argadd' aarg + redir => result + args + redir END + call assert_match(aarg . '\n\[' . barg . ']\n' . carg . '\n' . darg, trim(result)) + + %argdelete +endfunc + func Test_args_with_quote() " Only on Unix can a file name include a double quote. if has('unix') @@ -427,3 +470,19 @@ func Test_arg_all_expand() call assert_equal('notexist Xx\ x runtest.vim', expand('##')) call delete('Xx x') endfunc + +func Test_large_arg() + " Argument longer or equal to the number of columns used to cause + " access to invalid memory. + exe 'argadd ' .repeat('x', &columns) + args +endfunc + +func Test_argdo() + next! Xa.c Xb.c Xc.c + new + let l = [] + argdo call add(l, expand('%')) + call assert_equal(['Xa.c', 'Xb.c', 'Xc.c'], l) + bwipe Xa.c Xb.c Xc.c +endfunc diff --git a/src/nvim/testdir/test_assert.vim b/src/nvim/testdir/test_assert.vim new file mode 100644 index 0000000000..a4c8ce7e43 --- /dev/null +++ b/src/nvim/testdir/test_assert.vim @@ -0,0 +1,13 @@ +" Test that the methods used for testing work. + +func Test_assert_fails_in_try_block() + try + call assert_equal(0, assert_fails('throw "error"')) + endtry +endfunc + +" Must be last. +func Test_zz_quit_detected() + " Verify that if a test function ends Vim the test script detects this. + quit +endfunc diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index d66e237e45..b686d0292f 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -635,7 +635,8 @@ func Test_OptionSet() " Cleanup au! OptionSet - for opt in ['nu', 'ai', 'acd', 'ar', 'bs', 'backup', 'cul', 'cp'] + " set tags& + for opt in ['nu', 'ai', 'acd', 'ar', 'bs', 'backup', 'cul', 'cp', 'tags'] exe printf(":set %s&vim", opt) endfor call test_override('starting', 0) @@ -1672,6 +1673,25 @@ func Test_ReadWrite_Autocmds() call delete('test.out') endfunc +func Test_throw_in_BufWritePre() + new + call setline(1, ['one', 'two', 'three']) + call assert_false(filereadable('Xthefile')) + augroup throwing + au BufWritePre X* throw 'do not write' + augroup END + try + w Xthefile + catch + let caught = 1 + endtry + call assert_equal(1, caught) + call assert_false(filereadable('Xthefile')) + + bwipe! + au! throwing +endfunc + func Test_FileChangedShell_reload() if !has('unix') return diff --git a/src/nvim/testdir/test_bufline.vim b/src/nvim/testdir/test_bufline.vim new file mode 100644 index 0000000000..b886e99506 --- /dev/null +++ b/src/nvim/testdir/test_bufline.vim @@ -0,0 +1,67 @@ +" Tests for setbufline() and getbufline() + +source shared.vim + +func Test_setbufline_getbufline() + new + let b = bufnr('%') + hide + call assert_equal(0, setbufline(b, 1, ['foo', 'bar'])) + call assert_equal(['foo'], getbufline(b, 1)) + call assert_equal(['bar'], getbufline(b, 2)) + call assert_equal(['foo', 'bar'], getbufline(b, 1, 2)) + exe "bd!" b + call assert_equal([], getbufline(b, 1, 2)) + + split Xtest + call setline(1, ['a', 'b', 'c']) + let b = bufnr('%') + wincmd w + call assert_equal(1, setbufline(b, 5, ['x'])) + call assert_equal(1, setbufline(1234, 1, ['x'])) + call assert_equal(0, setbufline(b, 4, ['d', 'e'])) + call assert_equal(['c'], getbufline(b, 3)) + call assert_equal(['d'], getbufline(b, 4)) + call assert_equal(['e'], getbufline(b, 5)) + call assert_equal([], getbufline(b, 6)) + exe "bwipe! " . b +endfunc + +func Test_setbufline_getbufline_fold() + split Xtest + setlocal foldmethod=expr foldexpr=0 + let b = bufnr('%') + new + call assert_equal(0, setbufline(b, 1, ['foo', 'bar'])) + call assert_equal(['foo'], getbufline(b, 1)) + call assert_equal(['bar'], getbufline(b, 2)) + call assert_equal(['foo', 'bar'], getbufline(b, 1, 2)) + exe "bwipe!" b + bwipe! +endfunc + +func Test_setbufline_getbufline_fold_tab() + split Xtest + setlocal foldmethod=expr foldexpr=0 + let b = bufnr('%') + tab new + call assert_equal(0, setbufline(b, 1, ['foo', 'bar'])) + call assert_equal(['foo'], getbufline(b, 1)) + call assert_equal(['bar'], getbufline(b, 2)) + call assert_equal(['foo', 'bar'], getbufline(b, 1, 2)) + exe "bwipe!" b + bwipe! +endfunc + +func Test_setline_startup() + let cmd = GetVimCommand('Xscript') + if cmd == '' + return + endif + call writefile(['call setline(1, "Hello")', 'silent w Xtest', 'q!'], 'Xscript') + call system(cmd) + call assert_equal(['Hello'], readfile('Xtest')) + + call delete('Xscript') + call delete('Xtest') +endfunc diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index 75832a798c..4b333e444a 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -44,6 +44,42 @@ func Test_map_completion() call assert_equal('"map <special> <nowait>', getreg(':')) call feedkeys(":map <silent> <sp\<Tab>\<Home>\"\<CR>", 'xt') call assert_equal('"map <silent> <special>', getreg(':')) + + map ,f commaf + map ,g commaf + call feedkeys(":map ,\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map ,f', getreg(':')) + call feedkeys(":map ,\<Tab>\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map ,g', getreg(':')) + unmap ,f + unmap ,g + + set cpo-=< cpo-=B cpo-=k + map <Left> left + call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <Left>', getreg(':')) + unmap <Left> + + " set cpo+=< + map <Left> left + call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <Left>', getreg(':')) + unmap <Left> + set cpo-=< + + set cpo+=B + map <Left> left + call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <Left>', getreg(':')) + unmap <Left> + set cpo-=B + + " set cpo+=k + map <Left> left + call feedkeys(":map <L\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <Left>', getreg(':')) + unmap <Left> + " set cpo-=k endfunc func Test_match_completion() @@ -232,6 +268,15 @@ func Test_getcompletion() let l = getcompletion('not', 'mapclear') call assert_equal([], l) + let l = getcompletion('.', 'shellcmd') + call assert_equal(['./', '../'], l[0:1]) + call assert_equal(-1, match(l[2:], '^\.\.\?/$')) + let root = has('win32') ? 'C:\\' : '/' + let l = getcompletion(root, 'shellcmd') + let expected = map(filter(glob(root . '*', 0, 1), + \ 'isdirectory(v:val) || executable(v:val)'), 'isdirectory(v:val) ? v:val . ''/'' : v:val') + call assert_equal(expected, l) + if has('cscope') let l = getcompletion('', 'cscope') let cmds = ['add', 'find', 'help', 'kill', 'reset', 'show'] @@ -273,8 +318,7 @@ func Test_getcompletion() call assert_equal([], l) " For others test if the name is recognized. - let names = ['buffer', 'environment', 'file_in_path', - \ 'mapping', 'shellcmd', 'tag', 'tag_listfiles', 'user'] + let names = ['buffer', 'environment', 'file_in_path', 'mapping', 'tag', 'tag_listfiles', 'user'] if has('cmdline_hist') call add(names, 'history') endif @@ -294,6 +338,7 @@ func Test_getcompletion() endfor call delete('Xtags') + set tags& call assert_fails('call getcompletion("", "burp")', 'E475:') endfunc diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim index 4600a28da5..46c14d8bc3 100644 --- a/src/nvim/testdir/test_compiler.vim +++ b/src/nvim/testdir/test_compiler.vim @@ -33,9 +33,9 @@ endfunc func Test_compiler_without_arg() let a=split(execute('compiler')) - call assert_match(expand('^.*runtime/compiler/ant.vim$'), a[0]) - call assert_match(expand('^.*runtime/compiler/bcc.vim$'), a[1]) - call assert_match(expand('^.*runtime/compiler/xmlwf.vim$'), a[-1]) + call assert_match('^.*runtime/compiler/ant.vim$', a[0]) + call assert_match('^.*runtime/compiler/bcc.vim$', a[1]) + call assert_match('^.*runtime/compiler/xmlwf.vim$', a[-1]) endfunc func Test_compiler_completion() diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim new file mode 100644 index 0000000000..06062c5e58 --- /dev/null +++ b/src/nvim/testdir/test_const.vim @@ -0,0 +1,237 @@ + +" Test for :const + +func s:noop() +endfunc + +func Test_define_var_with_lock() + const i = 1 + const f = 1.1 + const s = 'vim' + const F = funcref('s:noop') + const l = [1, 2, 3] + const d = {'foo': 10} + if has('channel') + const j = test_null_job() + const c = test_null_channel() + endif + const b = v:true + const n = v:null + + call assert_true(exists('i')) + call assert_true(exists('f')) + call assert_true(exists('s')) + call assert_true(exists('F')) + call assert_true(exists('l')) + call assert_true(exists('d')) + if has('channel') + call assert_true(exists('j')) + call assert_true(exists('c')) + endif + call assert_true(exists('b')) + call assert_true(exists('n')) + + call assert_fails('let i = 1', 'E741:') + call assert_fails('let f = 1.1', 'E741:') + call assert_fails('let s = "vim"', 'E741:') + call assert_fails('let F = funcref("s:noop")', 'E741:') + call assert_fails('let l = [1, 2, 3]', 'E741:') + call assert_fails('let d = {"foo": 10}', 'E741:') + if has('channel') + call assert_fails('let j = test_null_job()', 'E741:') + call assert_fails('let c = test_null_channel()', 'E741:') + endif + call assert_fails('let b = v:true', 'E741:') + call assert_fails('let n = v:null', 'E741:') + + " Unlet + unlet i + unlet f + unlet s + unlet F + unlet l + unlet d + if has('channel') + unlet j + unlet c + endif + unlet b + unlet n +endfunc + +func Test_define_l_var_with_lock() + " With l: prefix + const l:i = 1 + const l:f = 1.1 + const l:s = 'vim' + const l:F = funcref('s:noop') + const l:l = [1, 2, 3] + const l:d = {'foo': 10} + if has('channel') + const l:j = test_null_job() + const l:c = test_null_channel() + endif + const l:b = v:true + const l:n = v:null + + call assert_fails('let l:i = 1', 'E741:') + call assert_fails('let l:f = 1.1', 'E741:') + call assert_fails('let l:s = "vim"', 'E741:') + call assert_fails('let l:F = funcref("s:noop")', 'E741:') + call assert_fails('let l:l = [1, 2, 3]', 'E741:') + call assert_fails('let l:d = {"foo": 10}', 'E741:') + if has('channel') + call assert_fails('let l:j = test_null_job()', 'E741:') + call assert_fails('let l:c = test_null_channel()', 'E741:') + endif + call assert_fails('let l:b = v:true', 'E741:') + call assert_fails('let l:n = v:null', 'E741:') + + " Unlet + unlet l:i + unlet l:f + unlet l:s + unlet l:F + unlet l:l + unlet l:d + if has('channel') + unlet l:j + unlet l:c + endif + unlet l:b + unlet l:n +endfunc + +func Test_define_script_var_with_lock() + const s:x = 0 + call assert_fails('let s:x = 1', 'E741:') + unlet s:x +endfunc + +func Test_descructuring_with_lock() + const [a, b, c] = [1, 1.1, 'vim'] + + call assert_fails('let a = 1', 'E741:') + call assert_fails('let b = 1.1', 'E741:') + call assert_fails('let c = "vim"', 'E741:') + + const [d; e] = [1, 1.1, 'vim'] + call assert_fails('let d = 1', 'E741:') + call assert_fails('let e = [2.2, "a"]', 'E741:') +endfunc + +func Test_cannot_modify_existing_variable() + let i = 1 + let f = 1.1 + let s = 'vim' + let F = funcref('s:noop') + let l = [1, 2, 3] + let d = {'foo': 10} + if has('channel') + let j = test_null_job() + let c = test_null_channel() + endif + let b = v:true + let n = v:null + + call assert_fails('const i = 1', 'E995:') + call assert_fails('const f = 1.1', 'E995:') + call assert_fails('const s = "vim"', 'E995:') + call assert_fails('const F = funcref("s:noop")', 'E995:') + call assert_fails('const l = [1, 2, 3]', 'E995:') + call assert_fails('const d = {"foo": 10}', 'E995:') + if has('channel') + call assert_fails('const j = test_null_job()', 'E995:') + call assert_fails('const c = test_null_channel()', 'E995:') + endif + call assert_fails('const b = v:true', 'E995:') + call assert_fails('const n = v:null', 'E995:') + call assert_fails('const [i, f, s] = [1, 1.1, "vim"]', 'E995:') + + const i2 = 1 + const f2 = 1.1 + const s2 = 'vim' + const F2 = funcref('s:noop') + const l2 = [1, 2, 3] + const d2 = {'foo': 10} + if has('channel') + const j2 = test_null_job() + const c2 = test_null_channel() + endif + const b2 = v:true + const n2 = v:null + + call assert_fails('const i2 = 1', 'E995:') + call assert_fails('const f2 = 1.1', 'E995:') + call assert_fails('const s2 = "vim"', 'E995:') + call assert_fails('const F2 = funcref("s:noop")', 'E995:') + call assert_fails('const l2 = [1, 2, 3]', 'E995:') + call assert_fails('const d2 = {"foo": 10}', 'E995:') + if has('channel') + call assert_fails('const j2 = test_null_job()', 'E995:') + call assert_fails('const c2 = test_null_channel()', 'E995:') + endif + call assert_fails('const b2 = v:true', 'E995:') + call assert_fails('const n2 = v:null', 'E995:') + call assert_fails('const [i2, f2, s2] = [1, 1.1, "vim"]', 'E995:') +endfunc + +func Test_const_with_index_access() + let l = [1, 2, 3] + call assert_fails('const l[0] = 4', 'E996:') + call assert_fails('const l[0:1] = [1, 2]', 'E996:') + + let d = {'aaa': 0} + call assert_fails("const d['aaa'] = 4", 'E996:') + call assert_fails("const d.aaa = 4", 'E996:') +endfunc + +func Test_const_with_compound_assign() + let i = 0 + call assert_fails('const i += 4', 'E995:') + call assert_fails('const i -= 4', 'E995:') + call assert_fails('const i *= 4', 'E995:') + call assert_fails('const i /= 4', 'E995:') + call assert_fails('const i %= 4', 'E995:') + + let s = 'a' + call assert_fails('const s .= "b"', 'E995:') + + let [a, b, c] = [1, 2, 3] + call assert_fails('const [a, b, c] += [4, 5, 6]', 'E995:') + + let [d; e] = [1, 2, 3] + call assert_fails('const [d; e] += [4, 5, 6]', 'E995:') +endfunc + +func Test_const_with_special_variables() + call assert_fails('const $FOO = "hello"', 'E996:') + call assert_fails('const @a = "hello"', 'E996:') + call assert_fails('const &filetype = "vim"', 'E996:') + call assert_fails('const &l:filetype = "vim"', 'E996:') + call assert_fails('const &g:encoding = "utf-8"', 'E996:') +endfunc + +func Test_const_with_eval_name() + let s = 'foo' + + " eval name with :const should work + const abc_{s} = 1 + const {s}{s} = 1 + + let s2 = 'abc_foo' + call assert_fails('const {s2} = "bar"', 'E995:') +endfunc + +func Test_lock_depth_is_1() + const l = [1, 2, 3] + const d = {'foo': 10} + + " Modify list + call add(l, 4) + let l[0] = 42 + + " Modify dict + let d['bar'] = 'hello' + let d.foo = 44 +endfunc diff --git a/src/nvim/testdir/test_cscope.vim b/src/nvim/testdir/test_cscope.vim index bcb0d0dec5..e5dab05511 100644 --- a/src/nvim/testdir/test_cscope.vim +++ b/src/nvim/testdir/test_cscope.vim @@ -36,7 +36,7 @@ func Test_cscopeWithCscopeConnections() " Test 1: Find this C-Symbol for cmd in ['cs find s main', 'cs find 0 main'] - let a=execute(cmd) + let a = execute(cmd) " Test 1.1 test where it moves the cursor call assert_equal('main(void)', getline('.')) " Test 1.2 test the output of the :cs command @@ -51,21 +51,21 @@ func Test_cscopeWithCscopeConnections() " Test 3: Find functions called by this function for cmd in ['cs find d test_mf_hash', 'cs find 2 test_mf_hash'] - let a=execute(cmd) + let a = execute(cmd) call assert_match('\n(1 of 42): <<mf_hash_init>> mf_hash_init(&ht);', a) call assert_equal(' mf_hash_init(&ht);', getline('.')) endfor " Test 4: Find functions calling this function for cmd in ['cs find c test_mf_hash', 'cs find 3 test_mf_hash'] - let a=execute(cmd) + let a = execute(cmd) call assert_match('\n(1 of 1): <<main>> test_mf_hash();', a) call assert_equal(' test_mf_hash();', getline('.')) endfor " Test 5: Find this text string for cmd in ['cs find t Bram', 'cs find 4 Bram'] - let a=execute(cmd) + let a = execute(cmd) call assert_match('(1 of 1): <<<unknown>>> \* VIM - Vi IMproved^Iby Bram Moolenaar', a) call assert_equal(' * VIM - Vi IMproved by Bram Moolenaar', getline('.')) endfor @@ -73,7 +73,7 @@ func Test_cscopeWithCscopeConnections() " Test 6: Find this egrep pattern " test all matches returned by cscope for cmd in ['cs find e ^\#includ.', 'cs find 6 ^\#includ.'] - let a=execute(cmd) + let a = execute(cmd) call assert_match('\n(1 of 3): <<<unknown>>> #include <assert.h>', a) call assert_equal('#include <assert.h>', getline('.')) cnext @@ -84,7 +84,7 @@ func Test_cscopeWithCscopeConnections() endfor " Test 7: Find the same egrep pattern using lcscope this time. - let a=execute('lcs find e ^\#includ.') + let a = execute('lcs find e ^\#includ.') call assert_match('\n(1 of 3): <<<unknown>>> #include <assert.h>', a) call assert_equal('#include <assert.h>', getline('.')) lnext @@ -96,7 +96,7 @@ func Test_cscopeWithCscopeConnections() " Test 8: Find this file for cmd in ['cs find f Xmemfile_test.c', 'cs find 7 Xmemfile_test.c'] enew - let a=execute(cmd) + let a = execute(cmd) call assert_true(a =~ '"Xmemfile_test.c" \d\+L, \d\+C') call assert_equal('Xmemfile_test.c', @%) endfor @@ -104,7 +104,7 @@ func Test_cscopeWithCscopeConnections() " Test 9: Find files #including this file for cmd in ['cs find i assert.h', 'cs find 8 assert.h'] enew - let a=execute(cmd) + let a = execute(cmd) let alines = split(a, '\n', 1) call assert_equal('', alines[0]) call assert_true(alines[1] =~ '"Xmemfile_test.c" \d\+L, \d\+C') @@ -118,13 +118,13 @@ func Test_cscopeWithCscopeConnections() " Test 11: Find places where this symbol is assigned a value " this needs a cscope >= 15.8 " unfortunately, Travis has cscope version 15.7 - let cscope_version=systemlist('cscope --version')[0] - let cs_version=str2float(matchstr(cscope_version, '\d\+\(\.\d\+\)\?')) + let cscope_version = systemlist('cscope --version')[0] + let cs_version = str2float(matchstr(cscope_version, '\d\+\(\.\d\+\)\?')) if cs_version >= 15.8 for cmd in ['cs find a item', 'cs find 9 item'] - let a=execute(cmd) - call assert_equal(['', '(1 of 4): <<test_mf_hash>> item = (mf_hashitem_T *)lalloc_clear(sizeof(mf_hashtab_T), FALSE);'], split(a, '\n', 1)) - call assert_equal(' item = (mf_hashitem_T *)lalloc_clear(sizeof(mf_hashtab_T), FALSE);', getline('.')) + let a = execute(cmd) + call assert_equal(['', '(1 of 4): <<test_mf_hash>> item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);'], split(a, '\n', 1)) + call assert_equal(' item = (mf_hashitem_T *)lalloc_clear(sizeof(*item), FALSE);', getline('.')) cnext call assert_equal(' item = mf_hash_find(&ht, key);', getline('.')) cnext @@ -135,18 +135,18 @@ func Test_cscopeWithCscopeConnections() endif " Test 12: leading whitespace is not removed for cscope find text - let a=execute('cscope find t test_mf_hash') + let a = execute('cscope find t test_mf_hash') call assert_equal(['', '(1 of 1): <<<unknown>>> test_mf_hash();'], split(a, '\n', 1)) call assert_equal(' test_mf_hash();', getline('.')) " Test 13: test with scscope - let a=execute('scs find t Bram') + let a = execute('scs find t Bram') call assert_match('(1 of 1): <<<unknown>>> \* VIM - Vi IMproved^Iby Bram Moolenaar', a) call assert_equal(' * VIM - Vi IMproved by Bram Moolenaar', getline('.')) " Test 14: cscope help for cmd in ['cs', 'cs help', 'cs xxx'] - let a=execute(cmd) + let a = execute(cmd) call assert_match('^cscope commands:\n', a) call assert_match('\nadd :', a) call assert_match('\nfind :', a) @@ -155,25 +155,25 @@ func Test_cscopeWithCscopeConnections() call assert_match('\nreset: Reinit all connections', a) call assert_match('\nshow : Show connections', a) endfor - let a=execute('scscope help') + let a = execute('scscope help') call assert_match('This cscope command does not support splitting the window\.', a) " Test 15: reset connections - let a=execute('cscope reset') + let a = execute('cscope reset') call assert_match('\nAdded cscope database.*Xcscope.out (#0)', a) call assert_match('\nAll cscope databases reset', a) " Test 16: cscope show - let a=execute('cscope show') + let a = execute('cscope show') call assert_match('\n 0 \d\+.*Xcscope.out\s*<none>', a) " Test 17: cstag and 'csto' option set csto=0 - let a=execute('cstag TEST_COUNT') + let a = execute('cstag TEST_COUNT') call assert_match('(1 of 1): <<TEST_COUNT>> #define TEST_COUNT 50000', a) call assert_equal('#define TEST_COUNT 50000', getline('.')) set csto=1 - let a=execute('cstag index_to_key') + let a = execute('cstag index_to_key') call assert_match('(1 of 1): <<index_to_key>> #define index_to_key(i) ((i) ^ 15167)', a) call assert_equal('#define index_to_key(i) ((i) ^ 15167)', getline('.')) call assert_fails('cstag xxx', 'E257:') @@ -183,10 +183,10 @@ func Test_cscopeWithCscopeConnections() set nocst call assert_fails('tag TEST_COUNT', 'E426:') set cst - let a=execute('tag TEST_COUNT') + let a = execute('tag TEST_COUNT') call assert_match('(1 of 1): <<TEST_COUNT>> #define TEST_COUNT 50000', a) call assert_equal('#define TEST_COUNT 50000', getline('.')) - let a=execute('tags') + let a = execute('tags') call assert_match('1 1 TEST_COUNT\s\+\d\+\s\+#define index_to_key', a) " Test 19: this should trigger call to cs_print_tags() @@ -198,17 +198,17 @@ func Test_cscopeWithCscopeConnections() call assert_fails('cscope kill 2', 'E261:') call assert_fails('cscope kill xxx', 'E261:') - let a=execute('cscope kill 0') + let a = execute('cscope kill 0') call assert_match('cscope connection 0 closed', a) cscope add Xcscope.out - let a=execute('cscope kill Xcscope.out') + let a = execute('cscope kill Xcscope.out') call assert_match('cscope connection Xcscope.out closed', a) cscope add Xcscope.out . - let a=execute('cscope kill -1') + let a = execute('cscope kill -1') call assert_match('cscope connection .*Xcscope.out closed', a) - let a=execute('cscope kill -1') + let a = execute('cscope kill -1') call assert_equal('', a) " Test 21: 'csprg' option @@ -220,7 +220,7 @@ func Test_cscopeWithCscopeConnections() " Test 22: multiple cscope connections cscope add Xcscope.out cscope add Xcscope2.out . -C - let a=execute('cscope show') + let a = execute('cscope show') call assert_match('\n 0 \d\+.*Xcscope.out\s*<none>', a) call assert_match('\n 1 \d\+.*Xcscope2.out\s*\.', a) @@ -294,7 +294,7 @@ func Test_withoutCscopeConnection() call assert_equal(cscope_connection(), 0) call assert_fails('cscope find s main', 'E567:') - let a=execute('cscope show') + let a = execute('cscope show') call assert_match('no cscope connections', a) endfunc diff --git a/src/nvim/testdir/test_cursor_func.vim b/src/nvim/testdir/test_cursor_func.vim index e1b9651c84..6bc9535aaf 100644 --- a/src/nvim/testdir/test_cursor_func.vim +++ b/src/nvim/testdir/test_cursor_func.vim @@ -44,3 +44,23 @@ func Test_curswant_with_autocommand() quit! endfunc +" Tests for behavior of curswant with cursorcolumn/line +func Test_curswant_with_cursorcolumn() + new + call setline(1, ['01234567', '']) + exe "normal! ggf6j" + call assert_equal(6, winsaveview().curswant) + set cursorcolumn + call assert_equal(6, winsaveview().curswant) + quit! +endfunc + +func Test_curswant_with_cursorline() + new + call setline(1, ['01234567', '']) + exe "normal! ggf6j" + call assert_equal(6, winsaveview().curswant) + set cursorline + call assert_equal(6, winsaveview().curswant) + quit! +endfunc diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim index c923b22836..bb87ef9c58 100644 --- a/src/nvim/testdir/test_debugger.vim +++ b/src/nvim/testdir/test_debugger.vim @@ -52,16 +52,16 @@ func Test_Debugger() let buf = RunVimInTerminal('-S Xtest.vim', {}) " Start the Vim debugger - call RunDbgCmd(buf, ':debug echo Foo()') + call RunDbgCmd(buf, ':debug echo Foo()', ['cmd: echo Foo()']) " Create a few stack frames by stepping through functions - call RunDbgCmd(buf, 'step') - call RunDbgCmd(buf, 'step') - call RunDbgCmd(buf, 'step') - call RunDbgCmd(buf, 'step') - call RunDbgCmd(buf, 'step') - call RunDbgCmd(buf, 'step') - call RunDbgCmd(buf, 'step') + call RunDbgCmd(buf, 'step', ['line 1: let var1 = 1']) + call RunDbgCmd(buf, 'step', ['line 2: let var2 = Bar(var1) + 9']) + call RunDbgCmd(buf, 'step', ['line 1: let var1 = 2 + a:var']) + call RunDbgCmd(buf, 'step', ['line 2: let var2 = Bazz(var1) + 4']) + call RunDbgCmd(buf, 'step', ['line 1: try']) + call RunDbgCmd(buf, 'step', ['line 2: let var1 = 3 + a:var']) + call RunDbgCmd(buf, 'step', ['line 3: let var3 = "another var"']) " check backtrace call RunDbgCmd(buf, 'backtrace', [ diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 00f4563f3d..1ba36ca8e9 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -752,6 +752,9 @@ func Test_diff_of_diff() if !CanRunVimInTerminal() return endif + if !has("rightleft") + throw 'Skipped: rightleft not supported' + endif call writefile([ \ 'call setline(1, ["aa","bb","cc","@@ -3,2 +5,7 @@","dd","ee","ff"])', diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim index 7f3994300f..48376d7922 100644 --- a/src/nvim/testdir/test_edit.vim +++ b/src/nvim/testdir/test_edit.vim @@ -643,11 +643,11 @@ func! Test_edit_CTRL_L() call feedkeys("cct\<c-x>\<c-l>\<c-n>\<esc>", 'tnix') call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$')) call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<esc>", 'tnix') - call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$')) - call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix') call assert_equal(['one', 'two', 'three', 'two', '', '', ''], getline(1, '$')) - call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix') + call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix') call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$')) + call feedkeys("cct\<c-x>\<c-l>\<c-n>\<c-n>\<c-n>\<c-n>\<esc>", 'tnix') + call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$')) call feedkeys("cct\<c-x>\<c-l>\<c-p>\<esc>", 'tnix') call assert_equal(['one', 'two', 'three', 'two', '', '', ''], getline(1, '$')) call feedkeys("cct\<c-x>\<c-l>\<c-p>\<c-p>\<esc>", 'tnix') @@ -1408,7 +1408,6 @@ func Test_edit_complete_very_long_name() let save_columns = &columns " Need at least about 1100 columns to reproduce the problem. set columns=2000 - call assert_equal(2000, &columns) set noswapfile let longfilename = longdirname . '/' . repeat('a', 255) diff --git a/src/nvim/testdir/test_environ.vim b/src/nvim/testdir/test_environ.vim new file mode 100644 index 0000000000..21bb09a690 --- /dev/null +++ b/src/nvim/testdir/test_environ.vim @@ -0,0 +1,44 @@ +scriptencoding utf-8 + +func Test_environ() + unlet! $TESTENV + call assert_equal(0, has_key(environ(), 'TESTENV')) + let $TESTENV = 'foo' + call assert_equal(1, has_key(environ(), 'TESTENV')) + let $TESTENV = 'こんにちわ' + call assert_equal('こんにちわ', environ()['TESTENV']) +endfunc + +func Test_getenv() + unlet! $TESTENV + call assert_equal(v:null, getenv('TESTENV')) + let $TESTENV = 'foo' + call assert_equal('foo', getenv('TESTENV')) +endfunc + +func Test_setenv() + unlet! $TESTENV + call setenv('TEST ENV', 'foo') + call assert_equal('foo', getenv('TEST ENV')) + call setenv('TEST ENV', v:null) + call assert_equal(v:null, getenv('TEST ENV')) +endfunc + +func Test_external_env() + call setenv('FOO', 'HelloWorld') + if has('win32') + let result = system('echo %FOO%') + else + let result = system('echo $FOO') + endif + let result = substitute(result, '[ \r\n]', '', 'g') + call assert_equal('HelloWorld', result) + + call setenv('FOO', v:null) + if has('win32') + let result = system('set | findstr "^FOO="') + else + let result = system('env | grep ^FOO=') + endif + call assert_equal('', result) +endfunc diff --git a/src/nvim/testdir/test_excmd.vim b/src/nvim/testdir/test_excmd.vim new file mode 100644 index 0000000000..f5ce979208 --- /dev/null +++ b/src/nvim/testdir/test_excmd.vim @@ -0,0 +1,10 @@ +" Tests for various Ex commands. + +func Test_ex_delete() + new + call setline(1, ['a', 'b', 'c']) + 2 + " :dl is :delete with the "l" flag, not :dlist + .dl + call assert_equal(['a', 'c'], getline(1, 2)) +endfunc diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim index 7a99a37be4..a9ade9155a 100644 --- a/src/nvim/testdir/test_filetype.vim +++ b/src/nvim/testdir/test_filetype.vim @@ -44,8 +44,10 @@ endfunc " Filetypes detected just from matching the file name. let s:filename_checks = { + \ '8th': ['file.8th'], \ 'a2ps': ['/etc/a2ps.cfg', '/etc/a2ps/file.cfg', 'a2psrc', '.a2psrc'], \ 'a65': ['file.a65'], + \ 'aap': ['file.aap'], \ 'abap': ['file.abap'], \ 'abc': ['file.abc'], \ 'abel': ['file.abl'], @@ -56,7 +58,8 @@ let s:filename_checks = { \ 'aml': ['file.aml'], \ 'ampl': ['file.run'], \ 'ant': ['build.xml'], - \ 'apache': ['.htaccess', '/etc/httpd/file.conf'], + \ 'apache': ['.htaccess', '/etc/httpd/file.conf', '/etc/apache2/sites-2/file.com', '/etc/apache2/some.config', '/etc/apache2/conf.file/conf', '/etc/apache2/mods-some/file', '/etc/apache2/sites-some/file', '/etc/httpd/conf.d/file.config'], + \ 'apachestyle': ['/etc/proftpd/file.config,/etc/proftpd/conf.file/file'], \ 'applescript': ['file.scpt'], \ 'aptconf': ['apt.conf', '/.aptitude/config'], \ 'arch': ['.arch-inventory'], @@ -81,7 +84,7 @@ let s:filename_checks = { \ 'c': ['enlightenment/file.cfg', 'file.qc', 'file.c'], \ 'cabal': ['file.cabal'], \ 'calendar': ['calendar'], - \ 'catalog': ['catalog'], + \ 'catalog': ['catalog', 'sgml.catalogfile'], \ 'cdl': ['file.cdl'], \ 'cdrdaoconf': ['/etc/cdrdao.conf', '/etc/defaults/cdrdao', '/etc/default/cdrdao', '.cdrdao'], \ 'cdrtoc': ['file.toc'], @@ -102,7 +105,7 @@ let s:filename_checks = { \ 'coco': ['file.atg'], \ 'conaryrecipe': ['file.recipe'], \ 'conf': ['auto.master'], - \ 'config': ['configure.in', 'configure.ac'], + \ 'config': ['configure.in', 'configure.ac', 'Pipfile'], \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi'], \ 'cpp': ['file.cxx', 'file.c++', 'file.hh', 'file.hxx', 'file.hpp', 'file.ipp', 'file.moc', 'file.tcc', 'file.inl', 'file.tlh'], \ 'crm': ['file.crm'], @@ -223,7 +226,7 @@ let s:filename_checks = { \ 'jgraph': ['file.jgr'], \ 'jovial': ['file.jov', 'file.j73', 'file.jovial'], \ 'jproperties': ['file.properties', 'file.properties_xx', 'file.properties_xx_xx'], - \ 'json': ['file.json', 'file.jsonp', 'file.webmanifest'], + \ 'json': ['file.json', 'file.jsonp', 'file.webmanifest', 'Pipfile.lock'], \ 'jsp': ['file.jsp'], \ 'kconfig': ['Kconfig', 'Kconfig.debug'], \ 'kivy': ['file.kv'], @@ -424,7 +427,7 @@ let s:filename_checks = { \ 'svg': ['file.svg'], \ 'svn': ['svn-commitfile.tmp'], \ 'sysctl': ['/etc/sysctl.conf', '/etc/sysctl.d/file.conf'], - \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.mount', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer'], + \ 'systemd': ['any/systemd/file.automount', 'any/systemd/file.mount', 'any/systemd/file.path', 'any/systemd/file.service', 'any/systemd/file.socket', 'any/systemd/file.swap', 'any/systemd/file.target', 'any/systemd/file.timer', '/etc/systemd/system/some.d/file.conf', '/etc/systemd/system/some.d/.#file'], \ 'systemverilog': ['file.sv', 'file.svh'], \ 'tags': ['tags'], \ 'tak': ['file.tak'], @@ -467,13 +470,14 @@ let s:filename_checks = { \ 'verilog': ['file.v'], \ 'verilogams': ['file.va', 'file.vams'], \ 'vgrindefs': ['vgrindefs'], - \ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst'], + \ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst', 'file.vhdl_123'], \ 'vim': ['file.vim', 'file.vba', '.exrc', '_exrc'], \ 'viminfo': ['.viminfo', '_viminfo'], \ 'vmasm': ['file.mar'], \ 'voscm': ['file.cm'], \ 'vrml': ['file.wrl'], \ 'vroom': ['file.vroom'], + \ 'vue': ['file.vue'], \ 'wast': ['file.wast', 'file.wat'], \ 'webmacro': ['file.wm'], \ 'wget': ['.wgetrc', 'wgetrc'], @@ -501,7 +505,6 @@ let s:filename_checks = { \ 'zimbutempl': ['file.zut'], \ 'zsh': ['.zprofile', '/etc/zprofile', '.zfbfmarks', 'file.zsh'], \ - \ 'aap': ['file.aap'], \ 'help': [$VIMRUNTIME . '/doc/help.txt'], \ 'xpm': ['file.xpm'], \ } @@ -515,11 +518,15 @@ func CheckItems(checks) for i in range(0, len(names) - 1) new try - exe 'edit ' . names[i] + exe 'edit ' . fnameescape(names[i]) catch - call assert_report('cannot edit "' . names[i] . '": ' . v:errmsg) + call assert_report('cannot edit "' . names[i] . '": ' . v:exception) endtry - call assert_equal(ft, &filetype, 'with file name: ' . names[i]) + if &filetype == '' && &readonly + " File exists but not able to edit it (permission denied) + else + call assert_equal(ft, &filetype, 'with file name: ' . names[i]) + endif bwipe! endfor endfor @@ -581,6 +588,8 @@ let s:script_checks = { \ 'cfengine': [['#!/path/cfengine']], \ 'erlang': [['#!/path/escript']], \ 'haskell': [['#!/path/haskell']], + \ 'cpp': [['// Standard iostream objects -*- C++ -*-'], + \ ['// -*- C++ -*-']], \ } func Test_script_detection() diff --git a/src/nvim/testdir/test_filter_cmd.vim b/src/nvim/testdir/test_filter_cmd.vim index 86347ab77f..0c45db049b 100644 --- a/src/nvim/testdir/test_filter_cmd.vim +++ b/src/nvim/testdir/test_filter_cmd.vim @@ -87,3 +87,61 @@ func Test_filter_cmd_with_filter() call assert_equal('a|b', out) set shelltemp& endfunction + +func Test_filter_commands() + let g:test_filter_a = 1 + let b:test_filter_b = 2 + let test_filter_c = 3 + + " Test filtering :let command + let res = split(execute("filter /^test_filter/ let"), "\n") + call assert_equal(["test_filter_a #1"], res) + + let res = split(execute("filter /\\v^(b:)?test_filter/ let"), "\n") + call assert_equal(["test_filter_a #1", "b:test_filter_b #2"], res) + + unlet g:test_filter_a + unlet b:test_filter_b + unlet test_filter_c + + " Test filtering :set command + let helplang=&helplang + set helplang=en + let res = join(split(execute("filter /^help/ set"), "\n")[1:], " ") + call assert_match('^\s*helplang=\w*$', res) + let &helplang=helplang + + " Test filtering :llist command + call setloclist(0, [{"filename": "/path/vim.c"}, {"filename": "/path/vim.h"}, {"module": "Main.Test"}]) + let res = split(execute("filter /\\.c$/ llist"), "\n") + call assert_equal([" 1 /path/vim.c: "], res) + + let res = split(execute("filter /\\.Test$/ llist"), "\n") + call assert_equal([" 3 Main.Test: "], res) + + " Test filtering :jump command + e file.c + e file.h + e file.hs + let res = split(execute("filter /\.c$/ jumps"), "\n")[1:] + call assert_equal([" 2 1 0 file.c", ">"], res) + + " Test filtering :marks command + b file.c + mark A + b file.h + mark B + let res = split(execute("filter /\.c$/ marks"), "\n")[1:] + call assert_equal([" A 1 0 file.c"], res) + + call setline(1, ['one', 'two', 'three']) + 1mark a + 2mark b + 3mark c + let res = split(execute("filter /two/ marks abc"), "\n")[1:] + call assert_equal([" b 2 0 two"], res) + + bwipe! file.c + bwipe! file.h + bwipe! file.hs +endfunc diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim index c8d64ce0a4..1dd3a5b29f 100644 --- a/src/nvim/testdir/test_filter_map.vim +++ b/src/nvim/testdir/test_filter_map.vim @@ -79,3 +79,8 @@ func Test_filter_map_dict_expr_funcref() endfunc call assert_equal({"foo": "f", "bar": "b", "baz": "b"}, map(copy(dict), function('s:filter4'))) endfunc + +func Test_map_fails() + call assert_fails('call map([1], "42 +")', 'E15:') + call assert_fails('call filter([1], "42 +")', 'E15:') +endfunc diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index ed9c70403e..fab1d7790d 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -1,4 +1,5 @@ " Tests for various functions. +source shared.vim " Must be done first, since the alternate buffer must be unset. func Test_00_bufexists() @@ -886,6 +887,14 @@ func Test_Executable() elseif has('unix') call assert_equal(1, executable('cat')) call assert_equal(0, executable('nodogshere')) + + " get "cat" path and remove the leading / + let catcmd = exepath('cat')[1:] + new + lcd / + call assert_equal(1, executable(catcmd)) + call assert_equal('/' .. catcmd, exepath(catcmd)) + bwipe endif endfunc @@ -1140,3 +1149,92 @@ func Test_reg_executing_and_recording() delfunc s:save_reg_stat unlet s:reg_stat endfunc + +func Test_libcall_libcallnr() + if !has('libcall') + return + endif + + if has('win32') + let libc = 'msvcrt.dll' + elseif has('mac') + let libc = 'libSystem.B.dylib' + elseif system('uname -s') =~ 'SunOS' + " Set the path to libc.so according to the architecture. + let test_bits = system('file ' . GetVimProg()) + let test_arch = system('uname -p') + if test_bits =~ '64-bit' && test_arch =~ 'sparc' + let libc = '/usr/lib/sparcv9/libc.so' + elseif test_bits =~ '64-bit' && test_arch =~ 'i386' + let libc = '/usr/lib/amd64/libc.so' + else + let libc = '/usr/lib/libc.so' + endif + else + " On Unix, libc.so can be in various places. + " Interestingly, using an empty string for the 1st argument of libcall + " allows to call functions from libc which is not documented. + let libc = '' + endif + + if has('win32') + call assert_equal($USERPROFILE, libcall(libc, 'getenv', 'USERPROFILE')) + else + call assert_equal($HOME, libcall(libc, 'getenv', 'HOME')) + endif + + " If function returns NULL, libcall() should return an empty string. + call assert_equal('', libcall(libc, 'getenv', 'X_ENV_DOES_NOT_EXIT')) + + " Test libcallnr() with string and integer argument. + call assert_equal(4, libcallnr(libc, 'strlen', 'abcd')) + call assert_equal(char2nr('A'), libcallnr(libc, 'toupper', char2nr('a'))) + + call assert_fails("call libcall(libc, 'Xdoesnotexist_', '')", 'E364:') + call assert_fails("call libcallnr(libc, 'Xdoesnotexist_', '')", 'E364:') + + call assert_fails("call libcall('Xdoesnotexist_', 'getenv', 'HOME')", 'E364:') + call assert_fails("call libcallnr('Xdoesnotexist_', 'strlen', 'abcd')", 'E364:') +endfunc + +func Test_bufadd_bufload() + call assert_equal(0, bufexists('someName')) + let buf = bufadd('someName') + call assert_notequal(0, buf) + call assert_equal(1, bufexists('someName')) + call assert_equal(0, getbufvar(buf, '&buflisted')) + call assert_equal(0, bufloaded(buf)) + call bufload(buf) + call assert_equal(1, bufloaded(buf)) + call assert_equal([''], getbufline(buf, 1, '$')) + + let curbuf = bufnr('') + call writefile(['some', 'text'], 'otherName') + let buf = bufadd('otherName') + call assert_notequal(0, buf) + call assert_equal(1, bufexists('otherName')) + call assert_equal(0, getbufvar(buf, '&buflisted')) + call assert_equal(0, bufloaded(buf)) + call bufload(buf) + call assert_equal(1, bufloaded(buf)) + call assert_equal(['some', 'text'], getbufline(buf, 1, '$')) + call assert_equal(curbuf, bufnr('')) + + let buf1 = bufadd('') + let buf2 = bufadd('') + call assert_notequal(0, buf1) + call assert_notequal(0, buf2) + call assert_notequal(buf1, buf2) + call assert_equal(1, bufexists(buf1)) + call assert_equal(1, bufexists(buf2)) + call assert_equal(0, bufloaded(buf1)) + exe 'bwipe ' .. buf1 + call assert_equal(0, bufexists(buf1)) + call assert_equal(1, bufexists(buf2)) + exe 'bwipe ' .. buf2 + call assert_equal(0, bufexists(buf2)) + + bwipe someName + bwipe otherName + call assert_equal(0, bufexists('someName')) +endfunc diff --git a/src/nvim/testdir/test_getvar.vim b/src/nvim/testdir/test_getvar.vim index d6b6b69aa8..3b61d68ebc 100644 --- a/src/nvim/testdir/test_getvar.vim +++ b/src/nvim/testdir/test_getvar.vim @@ -1,4 +1,5 @@ -" Tests for getwinvar(), gettabvar() and gettabwinvar(). +" Tests for getwinvar(), gettabvar(), gettabwinvar() and get(). + func Test_var() " Use strings to test for memory leaks. First, check that in an empty " window, gettabvar() returns the correct value @@ -102,3 +103,44 @@ func Test_gettabvar_in_tabline() close redrawstatus! endfunc + +" Test get() function using default value. + +" get({dict}, {key} [, {default}]) +func Test_get_dict() + let d = {'foo': 42} + call assert_equal(42, get(d, 'foo', 99)) + call assert_equal(999, get(d, 'bar', 999)) +endfunc + +" get({list}, {idx} [, {default}]) +func Test_get_list() + let l = [1,2,3] + call assert_equal(1, get(l, 0, 999)) + call assert_equal(3, get(l, -1, 999)) + call assert_equal(999, get(l, 3, 999)) +endfunc + +" get({blob}, {idx} [, {default}]) - in test_blob.vim + +" get({lambda}, {what} [, {default}]) +func Test_get_lambda() + let l:L = {-> 42} + call assert_match('^<lambda>', get(l:L, 'name')) + call assert_equal(l:L, get(l:L, 'func')) + call assert_equal({'lambda has': 'no dict'}, get(l:L, 'dict', {'lambda has': 'no dict'})) + call assert_equal(0, get(l:L, 'dict')) + call assert_equal([], get(l:L, 'args')) +endfunc + +" get({func}, {what} [, {default}]) +func Test_get_func() + let l:F = function('tr') + call assert_equal('tr', get(l:F, 'name')) + call assert_equal(l:F, get(l:F, 'func')) + call assert_equal({'func has': 'no dict'}, get(l:F, 'dict', {'func has': 'no dict'})) + call assert_equal(0, get(l:F, 'dict')) + call assert_equal([], get(l:F, 'args')) +endfunc + +" get({partial}, {what} [, {default}]) - in test_partial.vim diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim index d3429617d0..7f52481ba8 100644 --- a/src/nvim/testdir/test_ins_complete.vim +++ b/src/nvim/testdir/test_ins_complete.vim @@ -249,3 +249,39 @@ func Test_omni_dash() delfunc Omni set omnifunc= endfunc + +func Test_completefunc_args() + let s:args = [] + func! CompleteFunc(findstart, base) + let s:args += [[a:findstart, empty(a:base)]] + endfunc + new + + set completefunc=CompleteFunc + call feedkeys("i\<C-X>\<C-U>\<Esc>", 'x') + call assert_equal([1, 1], s:args[0]) + call assert_equal(0, s:args[1][0]) + set completefunc= + + let s:args = [] + set omnifunc=CompleteFunc + call feedkeys("i\<C-X>\<C-O>\<Esc>", 'x') + call assert_equal([1, 1], s:args[0]) + call assert_equal(0, s:args[1][0]) + set omnifunc= + + bwipe! + unlet s:args + delfunc CompleteFunc +endfunc + +" Check that when using feedkeys() typeahead does not interrupt searching for +" completions. +func Test_compl_feedkeys() + new + set completeopt=menuone,noselect + call feedkeys("ajump ju\<C-X>\<C-N>\<C-P>\<ESC>", "tx") + call assert_equal("jump jump", getline(1)) + bwipe! + set completeopt& +endfunc diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim index bc7817cef8..bfbb3e5c5b 100644 --- a/src/nvim/testdir/test_lambda.vim +++ b/src/nvim/testdir/test_lambda.vim @@ -23,30 +23,36 @@ function! Test_lambda_with_timer() return endif - source load.vim - let s:n = 0 let s:timer_id = 0 - function! s:Foo() - "let n = 0 - let s:timer_id = timer_start(50, {-> execute("let s:n += 1 | echo s:n", "")}, {"repeat": -1}) - endfunction + func! s:Foo() + let s:timer_id = timer_start(10, {-> execute("let s:n += 1 | echo s:n", "")}, {"repeat": -1}) + endfunc call s:Foo() - sleep 210m + " check timer works + for i in range(0, 10) + if s:n > 0 + break + endif + sleep 10m + endfor + " do not collect lambda call test_garbagecollect_now() - let m = LoadAdjust(s:n) - sleep 230m - call timer_stop(s:timer_id) - let n = LoadAdjust(s:n) - let nine = LoadAdjust(9) + " check timer still works + let m = s:n + for i in range(0, 10) + if s:n > m + break + endif + sleep 10m + endfor - call assert_true(m > 1) - call assert_true(n > m + 1) - call assert_true(n < nine) -endfunction + call timer_stop(s:timer_id) + call assert_true(s:n > m) +endfunc function! Test_lambda_with_partial() let l:Cb = function({... -> ['zero', a:1, a:2, a:3]}, ['one', 'two']) diff --git a/src/nvim/testdir/test_makeencoding.py b/src/nvim/testdir/test_makeencoding.py index 041edadc0a..f6dc0f8d1c 100644 --- a/src/nvim/testdir/test_makeencoding.py +++ b/src/nvim/testdir/test_makeencoding.py @@ -8,6 +8,7 @@ import locale import io import sys + def set_output_encoding(enc=None): """Set the encoding of stdout and stderr @@ -20,7 +21,7 @@ def set_output_encoding(enc=None): def get_text_writer(fo, **kwargs): kw = dict(kwargs) - kw.setdefault('errors', 'backslashreplace') # use \uXXXX style + kw.setdefault('errors', 'backslashreplace') # use \uXXXX style kw.setdefault('closefd', False) if sys.version_info[0] < 3: @@ -29,6 +30,7 @@ def set_output_encoding(enc=None): writer = io.open(fo.fileno(), mode='w', newline='', **kw) write = writer.write # save the original write() function enc = locale.getpreferredencoding() + def convwrite(s): if isinstance(s, bytes): write(s.decode(enc)) # convert to unistr diff --git a/src/nvim/testdir/test_maparg.vim b/src/nvim/testdir/test_maparg.vim index 0fb878b04a..ee16a22398 100644 --- a/src/nvim/testdir/test_maparg.vim +++ b/src/nvim/testdir/test_maparg.vim @@ -27,6 +27,10 @@ function Test_maparg() call assert_equal({'silent': 0, 'noremap': 0, 'lhs': 'foo', 'mode': ' ', \ 'nowait': 1, 'expr': 0, 'sid': sid, 'rhs': 'bar', 'buffer': 1}, \ maparg('foo', '', 0, 1)) + tmap baz foo + call assert_equal({'silent': 0, 'noremap': 0, 'lhs': 'baz', 'mode': 't', + \ 'nowait': 0, 'expr': 0, 'sid': sid, 'rhs': 'foo', 'buffer': 0}, + \ maparg('baz', 't', 0, 1)) map abc x<char-114>x call assert_equal("xrx", maparg('abc')) diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim index 84a118aef2..9f253604ed 100644 --- a/src/nvim/testdir/test_mapping.vim +++ b/src/nvim/testdir/test_mapping.vim @@ -187,9 +187,32 @@ func Test_map_cursor() imapclear endfunc +func Test_map_cursor_ctrl_gU() + " <c-g>U<cursor> works only within a single line + nnoremap c<* *Ncgn<C-r>"<C-G>U<S-Left> + call setline(1, ['foo', 'foobar', '', 'foo']) + call cursor(1,2) + call feedkeys("c<*PREFIX\<esc>.", 'xt') + call assert_equal(['PREFIXfoo', 'foobar', '', 'PREFIXfoo'], getline(1,'$')) + " break undo manually + set ul=1000 + exe ":norm! uu" + call assert_equal(['foo', 'foobar', '', 'foo'], getline(1,'$')) + + " Test that it does not work if the cursor moves to the previous line + " 2 times <S-Left> move to the previous line + nnoremap c<* *Ncgn<C-r>"<C-G>U<S-Left><C-G>U<S-Left> + call setline(1, ['', ' foo', 'foobar', '', 'foo']) + call cursor(2,3) + call feedkeys("c<*PREFIX\<esc>.", 'xt') + call assert_equal(['PREFIXPREFIX', ' foo', 'foobar', '', 'foo'], getline(1,'$')) + nmapclear +endfunc + + " This isn't actually testing a mapping, but similar use of CTRL-G U as above. func Test_break_undo() - :set whichwrap=<,>,[,] + set whichwrap=<,>,[,] call feedkeys("G4o2k", "xt") exe ":norm! iTest3: text with a (parenthesis here\<C-G>U\<Right>new line here\<esc>\<up>\<up>." call assert_equal('new line here', getline(line('$') - 3)) diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim index 7c5b95ae96..d77dac69c7 100644 --- a/src/nvim/testdir/test_mksession.vim +++ b/src/nvim/testdir/test_mksession.vim @@ -238,6 +238,17 @@ func Test_mkview_no_file_name() %bwipe endfunc +" A clean session (one empty buffer, one window, and one tab) should not +" set any error messages when sourced because no commands should fail. +func Test_mksession_no_errmsg() + let v:errmsg = '' + %bwipe! + mksession! Xtest_mks.out + source Xtest_mks.out + call assert_equal('', v:errmsg) + call delete('Xtest_mks.out') +endfunc + func Test_mksession_quote_in_filename() if !has('unix') " only Unix can handle this weird filename diff --git a/src/nvim/testdir/test_modeline.vim b/src/nvim/testdir/test_modeline.vim index 091a833774..1e196e07f0 100644 --- a/src/nvim/testdir/test_modeline.vim +++ b/src/nvim/testdir/test_modeline.vim @@ -60,14 +60,17 @@ func Test_modeline_keymap() set keymap= iminsert=0 imsearch=-1 endfunc -func s:modeline_fails(what, text) +func s:modeline_fails(what, text, error) + if !exists('+' . a:what) + return + endif let fname = "Xmodeline_fails_" . a:what call writefile(['vim: set ' . a:text . ' :', 'nothing'], fname) let modeline = &modeline set modeline filetype plugin on syntax enable - call assert_fails('split ' . fname, 'E474:') + call assert_fails('split ' . fname, a:error) call assert_equal("", &filetype) call assert_equal("", &syntax) @@ -79,16 +82,92 @@ func s:modeline_fails(what, text) endfunc func Test_modeline_filetype_fails() - call s:modeline_fails('filetype', 'ft=evil$CMD') + call s:modeline_fails('filetype', 'ft=evil$CMD', 'E474:') endfunc func Test_modeline_syntax_fails() - call s:modeline_fails('syntax', 'syn=evil$CMD') + call s:modeline_fails('syntax', 'syn=evil$CMD', 'E474:') endfunc func Test_modeline_keymap_fails() - if !has('keymap') - return - endif - call s:modeline_fails('keymap', 'keymap=evil$CMD') + call s:modeline_fails('keymap', 'keymap=evil$CMD', 'E474:') +endfunc + +func Test_modeline_fails_always() + call s:modeline_fails('backupdir', 'backupdir=Something()', 'E520:') + call s:modeline_fails('cdpath', 'cdpath=Something()', 'E520:') + call s:modeline_fails('charconvert', 'charconvert=Something()', 'E520:') + call s:modeline_fails('completefunc', 'completefunc=Something()', 'E520:') + call s:modeline_fails('cscopeprg', 'cscopeprg=Something()', 'E520:') + call s:modeline_fails('diffexpr', 'diffexpr=Something()', 'E520:') + call s:modeline_fails('directory', 'directory=Something()', 'E520:') + call s:modeline_fails('equalprg', 'equalprg=Something()', 'E520:') + call s:modeline_fails('errorfile', 'errorfile=Something()', 'E520:') + call s:modeline_fails('exrc', 'exrc=Something()', 'E520:') + call s:modeline_fails('formatprg', 'formatprg=Something()', 'E520:') + call s:modeline_fails('fsync', 'fsync=Something()', 'E520:') + call s:modeline_fails('grepprg', 'grepprg=Something()', 'E520:') + call s:modeline_fails('helpfile', 'helpfile=Something()', 'E520:') + call s:modeline_fails('imactivatefunc', 'imactivatefunc=Something()', 'E520:') + call s:modeline_fails('imstatusfunc', 'imstatusfunc=Something()', 'E520:') + call s:modeline_fails('imstyle', 'imstyle=Something()', 'E520:') + call s:modeline_fails('keywordprg', 'keywordprg=Something()', 'E520:') + call s:modeline_fails('langmap', 'langmap=Something()', 'E520:') + call s:modeline_fails('luadll', 'luadll=Something()', 'E520:') + call s:modeline_fails('makeef', 'makeef=Something()', 'E520:') + call s:modeline_fails('makeprg', 'makeprg=Something()', 'E520:') + call s:modeline_fails('mkspellmem', 'mkspellmem=Something()', 'E520:') + call s:modeline_fails('mzschemedll', 'mzschemedll=Something()', 'E520:') + call s:modeline_fails('mzschemegcdll', 'mzschemegcdll=Something()', 'E520:') + call s:modeline_fails('modelineexpr', 'modelineexpr=Something()', 'E520:') + call s:modeline_fails('omnifunc', 'omnifunc=Something()', 'E520:') + call s:modeline_fails('operatorfunc', 'operatorfunc=Something()', 'E520:') + call s:modeline_fails('perldll', 'perldll=Something()', 'E520:') + call s:modeline_fails('printdevice', 'printdevice=Something()', 'E520:') + call s:modeline_fails('patchexpr', 'patchexpr=Something()', 'E520:') + call s:modeline_fails('printexpr', 'printexpr=Something()', 'E520:') + call s:modeline_fails('pythondll', 'pythondll=Something()', 'E520:') + call s:modeline_fails('pythonhome', 'pythonhome=Something()', 'E520:') + call s:modeline_fails('pythonthreedll', 'pythonthreedll=Something()', 'E520:') + call s:modeline_fails('pythonthreehome', 'pythonthreehome=Something()', 'E520:') + call s:modeline_fails('pyxversion', 'pyxversion=Something()', 'E520:') + call s:modeline_fails('rubydll', 'rubydll=Something()', 'E520:') + call s:modeline_fails('runtimepath', 'runtimepath=Something()', 'E520:') + call s:modeline_fails('secure', 'secure=Something()', 'E520:') + call s:modeline_fails('shell', 'shell=Something()', 'E520:') + call s:modeline_fails('shellcmdflag', 'shellcmdflag=Something()', 'E520:') + call s:modeline_fails('shellpipe', 'shellpipe=Something()', 'E520:') + call s:modeline_fails('shellquote', 'shellquote=Something()', 'E520:') + call s:modeline_fails('shellredir', 'shellredir=Something()', 'E520:') + call s:modeline_fails('shellxquote', 'shellxquote=Something()', 'E520:') + call s:modeline_fails('spellfile', 'spellfile=Something()', 'E520:') + call s:modeline_fails('spellsuggest', 'spellsuggest=Something()', 'E520:') + call s:modeline_fails('tcldll', 'tcldll=Something()', 'E520:') + call s:modeline_fails('titleold', 'titleold=Something()', 'E520:') + call s:modeline_fails('viewdir', 'viewdir=Something()', 'E520:') + call s:modeline_fails('viminfo', 'viminfo=Something()', 'E520:') + call s:modeline_fails('viminfofile', 'viminfofile=Something()', 'E520:') + call s:modeline_fails('winptydll', 'winptydll=Something()', 'E520:') + call s:modeline_fails('undodir', 'undodir=Something()', 'E520:') + " only check a few terminal options + " Skip these since nvim doesn't support termcodes as options + "call s:modeline_fails('t_AB', 't_AB=Something()', 'E520:') + "call s:modeline_fails('t_ce', 't_ce=Something()', 'E520:') + "call s:modeline_fails('t_sr', 't_sr=Something()', 'E520:') + "call s:modeline_fails('t_8b', 't_8b=Something()', 'E520:') +endfunc + +func Test_modeline_fails_modelineexpr() + call s:modeline_fails('balloonexpr', 'balloonexpr=Something()', 'E992:') + call s:modeline_fails('foldexpr', 'foldexpr=Something()', 'E992:') + call s:modeline_fails('foldtext', 'foldtext=Something()', 'E992:') + call s:modeline_fails('formatexpr', 'formatexpr=Something()', 'E992:') + call s:modeline_fails('guitablabel', 'guitablabel=Something()', 'E992:') + call s:modeline_fails('iconstring', 'iconstring=Something()', 'E992:') + call s:modeline_fails('includeexpr', 'includeexpr=Something()', 'E992:') + call s:modeline_fails('indentexpr', 'indentexpr=Something()', 'E992:') + call s:modeline_fails('rulerformat', 'rulerformat=Something()', 'E992:') + call s:modeline_fails('statusline', 'statusline=Something()', 'E992:') + call s:modeline_fails('tabline', 'tabline=Something()', 'E992:') + call s:modeline_fails('titlestring', 'titlestring=Something()', 'E992:') endfunc diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index ef17209f74..945cd5a617 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -2552,6 +2552,21 @@ func Test_delete_until_paragraph() bwipe! endfunc +func Test_message_when_using_ctrl_c() + " Make sure no buffers are changed. + %bwipe! + + exe "normal \<C-C>" + call assert_match("Type :qa and press <Enter> to exit Nvim", Screenline(&lines)) + + new + cal setline(1, 'hi!') + exe "normal \<C-C>" + call assert_match("Type :qa! and press <Enter> to abandon all changes and exit Nvim", Screenline(&lines)) + + bwipe! +endfunc + " Test for '[m', ']m', '[M' and ']M' " Jumping to beginning and end of methods in Java-like languages func Test_java_motion() diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 78afa929d0..a6ebd7b023 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -92,9 +92,6 @@ function! Test_path_keep_commas() endfunction func Test_filetype_valid() - if !has('autocmd') - return - endif set ft=valid_name call assert_equal("valid_name", &filetype) set ft=valid-name @@ -180,6 +177,15 @@ func Test_thesaurus() call Check_dir_option('thesaurus') endfun +func Test_complete() + " Trailing single backslash used to cause invalid memory access. + set complete=s\ + new + call feedkeys("i\<C-N>\<Esc>", 'xt') + bwipe! + set complete& +endfun + func Test_set_completion() call feedkeys(":set di\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set dictionary diff diffexpr diffopt digraph directory display', @:) @@ -216,6 +222,7 @@ func Test_set_completion() call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:) + set tags& let &shellslash = shellslash endfunc @@ -224,7 +231,7 @@ func Test_set_errors() call assert_fails('set backupcopy=', 'E474:') call assert_fails('set regexpengine=3', 'E474:') call assert_fails('set history=10001', 'E474:') - call assert_fails('set numberwidth=11', 'E474:') + call assert_fails('set numberwidth=21', 'E474:') call assert_fails('set colorcolumn=-a') call assert_fails('set colorcolumn=a') call assert_fails('set colorcolumn=1,') @@ -305,14 +312,23 @@ func Test_set_ttytype() endif endfunc -func Test_complete() - " Trailing single backslash used to cause invalid memory access. - set complete=s\ - new - call feedkeys("i\<C-N>\<Esc>", 'xt') - bwipe! - set complete& -endfun +func Test_set_all() + set tw=75 + set iskeyword=a-z,A-Z + set nosplitbelow + let out = execute('set all') + call assert_match('textwidth=75', out) + call assert_match('iskeyword=a-z,A-Z', out) + call assert_match('nosplitbelow', out) + set tw& iskeyword& splitbelow& +endfunc + +func Test_set_values() + " The file is only generated when running "make test" in the src directory. + if filereadable('opt_test.vim') + source opt_test.vim + endif +endfunc func ResetIndentexpr() set indentexpr= @@ -327,6 +343,65 @@ func Test_set_indentexpr() bwipe! endfunc +func Test_backupskip() + " Option 'backupskip' may contain several comma-separated path + " specifications if one or more of the environment variables TMPDIR, TMP, + " or TEMP is defined. To simplify testing, convert the string value into a + " list. + let bsklist = split(&bsk, ',') + + if has("mac") + let found = (index(bsklist, '/private/tmp/*') >= 0) + call assert_true(found, '/private/tmp not in option bsk: ' . &bsk) + elseif has("unix") + let found = (index(bsklist, '/tmp/*') >= 0) + call assert_true(found, '/tmp not in option bsk: ' . &bsk) + endif + + " If our test platform is Windows, the path(s) in option bsk will use + " backslash for the path separator and the components could be in short + " (8.3) format. As such, we need to replace the backslashes with forward + " slashes and convert the path components to long format. The expand() + " function will do this but it cannot handle comma-separated paths. This is + " why bsk was converted from a string into a list of strings above. + " + " One final complication is that the wildcard "/*" is at the end of each + " path and so expand() might return a list of matching files. To prevent + " this, we need to remove the wildcard before calling expand() and then + " append it afterwards. + if has('win32') + let item_nbr = 0 + while item_nbr < len(bsklist) + let path_spec = bsklist[item_nbr] + let path_spec = strcharpart(path_spec, 0, strlen(path_spec)-2) + let path_spec = substitute(expand(path_spec), '\\', '/', 'g') + let bsklist[item_nbr] = path_spec . '/*' + let item_nbr += 1 + endwhile + endif + + " Option bsk will also include these environment variables if defined. + " If they're defined, verify they appear in the option value. + for var in ['$TMPDIR', '$TMP', '$TEMP'] + if exists(var) + let varvalue = substitute(expand(var), '\\', '/', 'g') + let varvalue = substitute(varvalue, '/$', '', '') + let varvalue .= '/*' + let found = (index(bsklist, varvalue) >= 0) + call assert_true(found, var . ' (' . varvalue . ') not in option bsk: ' . &bsk) + endif + endfor + + " Duplicates should be filtered out (option has P_NODUP) + let backupskip = &backupskip + set backupskip= + set backupskip+=/test/dir + set backupskip+=/other/dir + set backupskip+=/test/dir + call assert_equal('/test/dir,/other/dir', &backupskip) + let &backupskip = backupskip +endfunc + func Test_copy_winopt() set hidden @@ -395,24 +470,6 @@ func Test_shortmess_F() bwipe endfunc -func Test_set_all() - set tw=75 - set iskeyword=a-z,A-Z - set nosplitbelow - let out = execute('set all') - call assert_match('textwidth=75', out) - call assert_match('iskeyword=a-z,A-Z', out) - call assert_match('nosplitbelow', out) - set tw& iskeyword& splitbelow& -endfunc - -func Test_set_values() - " The file is only generated when running "make test" in the src directory. - if filereadable('opt_test.vim') - source opt_test.vim - endif -endfunc - func Test_shortmess_F2() e file1 e file2 diff --git a/src/nvim/testdir/test_partial.vim b/src/nvim/testdir/test_partial.vim index de5c26c2dd..590e18e024 100644 --- a/src/nvim/testdir/test_partial.vim +++ b/src/nvim/testdir/test_partial.vim @@ -285,6 +285,11 @@ func Test_get_partial_items() call assert_equal('MyDictFunc', get(Func, 'name')) call assert_equal([], get(Func, 'args')) call assert_true(empty( get(Func, 'dict'))) + + let P = function('substitute', ['hello there', 'there']) + let dict = {'partial has': 'no dict'} + call assert_equal(dict, get(P, 'dict', dict)) + call assert_equal(0, get(l:P, 'dict')) endfunc func Test_compare_partials() diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index 96f4bfc71b..98e9de9ffb 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -641,7 +641,7 @@ func Test_popup_and_preview_autocommand() norm! gt call assert_equal(0, &previewwindow) norm! gT - call assert_equal(12, tabpagenr('$')) + call assert_equal(10, tabpagenr('$')) tabonly pclose augroup MyBufAdd @@ -841,4 +841,38 @@ func Test_popup_complete_info_02() bwipe! endfunc +func Test_CompleteChanged() + new + call setline(1, ['foo', 'bar', 'foobar', '']) + set complete=. completeopt=noinsert,noselect,menuone + function! OnPumChange() + let g:event = copy(v:event) + let g:item = get(v:event, 'completed_item', {}) + let g:word = get(g:item, 'word', v:null) + endfunction + augroup AAAAA_Group + au! + autocmd CompleteChanged * :call OnPumChange() + augroup END + call cursor(4, 1) + + call feedkeys("Sf\<C-N>", 'tx') + call assert_equal({'completed_item': {}, 'width': 15, + \ 'height': 2, 'size': 2, + \ 'col': 0, 'row': 4, 'scrollbar': v:false}, g:event) + call feedkeys("a\<C-N>\<C-N>\<C-E>", 'tx') + call assert_equal('foo', g:word) + call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-E>", 'tx') + call assert_equal('foobar', g:word) + call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-N>\<C-E>", 'tx') + call assert_equal(v:null, g:word) + call feedkeys("a\<C-N>\<C-N>\<C-N>\<C-N>\<C-P>", 'tx') + call assert_equal('foobar', g:word) + + autocmd! AAAAA_Group + set complete& completeopt& + delfunc! OnPumchange + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_profile.vim b/src/nvim/testdir/test_profile.vim index 8996e86b43..1aa4d5eaf8 100644 --- a/src/nvim/testdir/test_profile.vim +++ b/src/nvim/testdir/test_profile.vim @@ -5,6 +5,8 @@ endif func Test_profile_func() let lines = [ + \ 'profile start Xprofile_func.log', + \ 'profile func Foo*"', \ "func! Foo1()", \ "endfunc", \ "func! Foo2()", @@ -33,9 +35,7 @@ func Test_profile_func() call writefile(lines, 'Xprofile_func.vim') call system(v:progpath - \ . ' -es -u NONE -U NONE -i NONE --noplugin' - \ . ' -c "profile start Xprofile_func.log"' - \ . ' -c "profile func Foo*"' + \ . ' -es --clean' \ . ' -c "so Xprofile_func.vim"' \ . ' -c "qall!"') call assert_equal(0, v:shell_error) @@ -97,7 +97,7 @@ func Test_profile_file() call writefile(lines, 'Xprofile_file.vim') call system(v:progpath - \ . ' -es -u NONE -U NONE -i NONE --noplugin' + \ . ' -es --clean' \ . ' -c "profile start Xprofile_file.log"' \ . ' -c "profile file Xprofile_file.vim"' \ . ' -c "so Xprofile_file.vim"' @@ -152,17 +152,17 @@ func Test_profile_file_with_cont() let lines = readfile('Xprofile_file.log') call assert_equal(11, len(lines)) - call assert_match('^SCRIPT .*Xprofile_file.vim$', lines[0]) - call assert_equal('Sourced 1 time', lines[1]) - call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[2]) - call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[3]) - call assert_equal('', lines[4]) - call assert_equal('count total (s) self (s)', lines[5]) - call assert_match(' 1 0.\d\+ echo "hello', lines[6]) - call assert_equal(' \ world"', lines[7]) - call assert_match(' 1 0.\d\+ echo "foo ', lines[8]) - call assert_equal(' \bar"', lines[9]) - call assert_equal('', lines[10]) + call assert_match('^SCRIPT .*Xprofile_file.vim$', lines[0]) + call assert_equal('Sourced 1 time', lines[1]) + call assert_match('^Total time:\s\+\d\+\.\d\+$', lines[2]) + call assert_match('^ Self time:\s\+\d\+\.\d\+$', lines[3]) + call assert_equal('', lines[4]) + call assert_equal('count total (s) self (s)', lines[5]) + call assert_match(' 1 0.\d\+ echo "hello', lines[6]) + call assert_equal(' \ world"', lines[7]) + call assert_match(' 1 0.\d\+ echo "foo ', lines[8]) + call assert_equal(' \bar"', lines[9]) + call assert_equal('', lines[10]) call delete('Xprofile_file.vim') call delete('Xprofile_file.log') @@ -222,3 +222,73 @@ func Test_profile_truncate_mbyte() call delete('Xprofile_file.vim') call delete('Xprofile_file.log') endfunc + +func Test_profdel_func() + let lines = [ + \ 'profile start Xprofile_file.log', + \ 'func! Foo1()', + \ 'endfunc', + \ 'func! Foo2()', + \ 'endfunc', + \ 'func! Foo3()', + \ 'endfunc', + \ '', + \ 'profile func Foo1', + \ 'profile func Foo2', + \ 'call Foo1()', + \ 'call Foo2()', + \ '', + \ 'profile func Foo3', + \ 'profdel func Foo2', + \ 'profdel func Foo3', + \ 'call Foo1()', + \ 'call Foo2()', + \ 'call Foo3()' ] + call writefile(lines, 'Xprofile_file.vim') + call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q') + call assert_equal(0, v:shell_error) + + let lines = readfile('Xprofile_file.log') + call assert_equal(24, len(lines)) + + " Check that: + " - Foo1() is called twice (profdel not invoked) + " - Foo2() is called once (profdel invoked after it was called) + " - Foo3() is not called (profdel invoked before it was called) + call assert_equal('FUNCTION Foo1()', lines[0]) + call assert_equal('Called 2 times', lines[1]) + call assert_equal('FUNCTION Foo2()', lines[7]) + call assert_equal('Called 1 time', lines[8]) + call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[14]) + call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[19]) + + call delete('Xprofile_file.vim') + call delete('Xprofile_file.log') +endfunc + +func Test_profdel_star() + " Foo() is invoked once before and once after 'profdel *'. + " So profiling should report it only once. + let lines = [ + \ 'profile start Xprofile_file.log', + \ 'func! Foo()', + \ 'endfunc', + \ 'profile func Foo', + \ 'call Foo()', + \ 'profdel *', + \ 'call Foo()' ] + call writefile(lines, 'Xprofile_file.vim') + call system(v:progpath . ' -es --clean -c "so Xprofile_file.vim" -c q') + call assert_equal(0, v:shell_error) + + let lines = readfile('Xprofile_file.log') + call assert_equal(15, len(lines)) + + call assert_equal('FUNCTION Foo()', lines[0]) + call assert_equal('Called 1 time', lines[1]) + call assert_equal('FUNCTIONS SORTED ON TOTAL TIME', lines[7]) + call assert_equal('FUNCTIONS SORTED ON SELF TIME', lines[11]) + + call delete('Xprofile_file.vim') + call delete('Xprofile_file.log') +endfunc diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index 16fb86ea08..ce0b8f1be8 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -3372,3 +3372,49 @@ func Test_lbuffer_with_bwipe() au! augroup END endfunc + +" Tests for the ':filter /pat/ clist' command +func Test_filter_clist() + cexpr ['Xfile1:10:10:Line 10', 'Xfile2:15:15:Line 15'] + call assert_equal([' 2 Xfile2:15 col 15: Line 15'], + \ split(execute('filter /Line 15/ clist'), "\n")) + call assert_equal([' 1 Xfile1:10 col 10: Line 10'], + \ split(execute('filter /Xfile1/ clist'), "\n")) + call assert_equal([], split(execute('filter /abc/ clist'), "\n")) + + call setqflist([{'module' : 'abc', 'pattern' : 'pat1'}, + \ {'module' : 'pqr', 'pattern' : 'pat2'}], ' ') + call assert_equal([' 2 pqr:pat2: '], + \ split(execute('filter /pqr/ clist'), "\n")) + call assert_equal([' 1 abc:pat1: '], + \ split(execute('filter /pat1/ clist'), "\n")) +endfunc + +func Test_setloclist_in_aucmd() + " This was using freed memory. + augroup nasty + au * * call setloclist(0, [], 'f') + augroup END + lexpr "x" + augroup nasty + au! + augroup END +endfunc + +" Tests for the "CTRL-W <CR>" command. +func Xview_result_split_tests(cchar) + call s:setup_commands(a:cchar) + + " Test that "CTRL-W <CR>" in a qf/ll window fails with empty list. + call g:Xsetlist([]) + Xopen + let l:win_count = winnr('$') + call assert_fails('execute "normal! \<C-W>\<CR>"', 'E42') + call assert_equal(l:win_count, winnr('$')) + Xclose +endfunc + +func Test_view_result_split() + call Xview_result_split_tests('c') + call Xview_result_split_tests('l') +endfunc diff --git a/src/nvim/testdir/test_recover.vim b/src/nvim/testdir/test_recover.vim index beecb4cd0d..09c8d1cda6 100644 --- a/src/nvim/testdir/test_recover.vim +++ b/src/nvim/testdir/test_recover.vim @@ -6,6 +6,13 @@ func Test_recover_root_dir() set dir=/ call assert_fails('recover', 'E305:') close! + + if has('win32') + " can write in / directory on MS-Windows + let &directory = 'F:\\' + elseif filewritable('/') == 2 + set dir=/notexist/ + endif call assert_fails('split Xtest', 'E303:') set dir& endfunc diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim index de209fa9ec..b5e99b0ed3 100644 --- a/src/nvim/testdir/test_regexp_latin.vim +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -85,3 +85,16 @@ func Test_multi_failure() call assert_fails('/a\{a}', 'E870:') set re=0 endfunc + +func Test_recursive_addstate() + " This will call addstate() recursively until it runs into the limit. + let lnum = search('\v((){328}){389}') + call assert_equal(0, lnum) +endfunc + +func Test_out_of_memory() + new + s/^/,n + " This will be slow... + call assert_fails('call search("\\v((n||<)+);")', 'E363:') +endfunc diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim index 97638e9aac..e06c7d6368 100644 --- a/src/nvim/testdir/test_regexp_utf8.vim +++ b/src/nvim/testdir/test_regexp_utf8.vim @@ -183,3 +183,12 @@ func Test_large_class() call assert_equal(1, "\u3042" =~# '[\u3000-\u4000]') set re=0 endfunc + +func Test_optmatch_toolong() + set re=1 + " Can only handle about 8000 characters. + let pat = '\\%[' .. repeat('x', 9000) .. ']' + call assert_fails('call match("abc def", "' .. pat .. '")', 'E339:') + set re=0 +endfunc + diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim index d7b6de5652..298268a994 100644 --- a/src/nvim/testdir/test_registers.vim +++ b/src/nvim/testdir/test_registers.vim @@ -63,3 +63,22 @@ func Test_display_registers() bwipe! endfunc + +" Check that replaying a typed sequence does not use an Esc and following +" characters as an escape sequence. +func Test_recording_esc_sequence() + new + try + let save_F2 = &t_F2 + catch + endtry + let t_F2 = "\<Esc>OQ" + call feedkeys("qqiTest\<Esc>", "xt") + call feedkeys("OQuirk\<Esc>q", "xt") + call feedkeys("Go\<Esc>@q", "xt") + call assert_equal(['Quirk', 'Test', 'Quirk', 'Test'], getline(1, 4)) + bwipe! + if exists('save_F2') + let &t_F2 = save_F2 + endif +endfunc diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index 87cad241e2..8e284ba042 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -1,5 +1,7 @@ " Test for the search command +source shared.vim + func Test_search_cmdline() " See test/functional/legacy/search_spec.lua throw 'skipped: Nvim does not support test_override()' @@ -288,16 +290,53 @@ func Test_searchpair() new call setline(1, ['other code here', '', '[', '" cursor here', ']']) 4 - let a=searchpair('\[','',']','bW') + let a = searchpair('\[','',']','bW') call assert_equal(3, a) set nomagic 4 - let a=searchpair('\[','',']','bW') + let a = searchpair('\[','',']','bW') call assert_equal(3, a) set magic q! endfunc +func Test_searchpair_errors() + call assert_fails("call searchpair([0], 'middle', 'end', 'bW', 'skip', 99, 100)", 'E730: using List as a String') + call assert_fails("call searchpair('start', {-> 0}, 'end', 'bW', 'skip', 99, 100)", 'E729: using Funcref as a String') + call assert_fails("call searchpair('start', 'middle', {'one': 1}, 'bW', 'skip', 99, 100)", 'E731: using Dictionary as a String') + call assert_fails("call searchpair('start', 'middle', 'end', 'flags', 'skip', 99, 100)", 'E475: Invalid argument: flags') + call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 0, 99, 100)", 'E475: Invalid argument: 0') + call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', -99, 100)", 'E475: Invalid argument: -99') + call assert_fails("call searchpair('start', 'middle', 'end', 'bW', 'func', 99, -100)", 'E475: Invalid argument: -100') +endfunc + +func Test_searchpair_skip() + func Zero() + return 0 + endfunc + func Partial(x) + return a:x + endfunc + new + call setline(1, ['{', 'foo', 'foo', 'foo', '}']) + 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', '')) + 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', '0')) + 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', {-> 0})) + 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', function('Zero'))) + 3 | call assert_equal(1, searchpair('{', '', '}', 'bWn', function('Partial', [0]))) + bw! +endfunc + +func Test_searchpair_leak() + new + call setline(1, 'if one else another endif') + + " The error in the skip expression caused memory to leak. + call assert_fails("call searchpair('\\<if\\>', '\\<else\\>', '\\<endif\\>', '', '\"foo\" 2')", 'E15:') + + bwipe! +endfunc + func Test_searchc() " These commands used to cause memory overflow in searchc(). new diff --git a/src/nvim/testdir/test_source.vim b/src/nvim/testdir/test_source.vim new file mode 100644 index 0000000000..09baec0b7d --- /dev/null +++ b/src/nvim/testdir/test_source.vim @@ -0,0 +1,48 @@ +" Tests for the :source command. + +func Test_source_autocmd() + call writefile([ + \ 'let did_source = 1', + \ ], 'Xsourced') + au SourcePre *source* let did_source_pre = 1 + au SourcePost *source* let did_source_post = 1 + + source Xsourced + + call assert_equal(g:did_source, 1) + call assert_equal(g:did_source_pre, 1) + call assert_equal(g:did_source_post, 1) + + call delete('Xsourced') + au! SourcePre + au! SourcePost + unlet g:did_source + unlet g:did_source_pre + unlet g:did_source_post +endfunc + +func Test_source_cmd() + au SourceCmd *source* let did_source = expand('<afile>') + au SourcePre *source* let did_source_pre = 2 + au SourcePost *source* let did_source_post = 2 + + source Xsourced + + call assert_equal(g:did_source, 'Xsourced') + call assert_false(exists('g:did_source_pre')) + call assert_equal(g:did_source_post, 2) + + au! SourceCmd + au! SourcePre + au! SourcePost +endfunc + +func Test_source_sandbox() + new + call writefile(["Ohello\<Esc>"], 'Xsourcehello') + source! Xsourcehello | echo + call assert_equal('hello', getline(1)) + call assert_fails('sandbox source! Xsourcehello', 'E48:') + bwipe! + call delete('Xsourcehello') +endfunc diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim index b3438cc649..230cb72335 100644 --- a/src/nvim/testdir/test_spell.vim +++ b/src/nvim/testdir/test_spell.vim @@ -1,4 +1,5 @@ " Test spell checking +" Note: this file uses latin1 encoding, but is used with utf-8 encoding. if !has('spell') finish @@ -68,6 +69,47 @@ func Test_z_equal_on_invalid_utf8_word() bwipe! endfunc +" Test spellbadword() with argument +func Test_spellbadword() + set spell + + call assert_equal(['bycycle', 'bad'], spellbadword('My bycycle.')) + call assert_equal(['another', 'caps'], spellbadword('A sentence. another sentence')) + + set spelllang=en + call assert_equal(['', ''], spellbadword('centre')) + call assert_equal(['', ''], spellbadword('center')) + set spelllang=en_us + call assert_equal(['centre', 'local'], spellbadword('centre')) + call assert_equal(['', ''], spellbadword('center')) + set spelllang=en_gb + call assert_equal(['', ''], spellbadword('centre')) + call assert_equal(['center', 'local'], spellbadword('center')) + + " Create a small word list to test that spellbadword('...') + " can return ['...', 'rare']. + e Xwords + insert +foo +foobar/? +. + w! + mkspell! Xwords.spl Xwords + set spelllang=Xwords.spl + call assert_equal(['foobar', 'rare'], spellbadword('foo foobar')) + + " Typo should not be detected without the 'spell' option. + set spelllang=en_gb nospell + call assert_equal(['', ''], spellbadword('centre')) + call assert_equal(['', ''], spellbadword('My bycycle.')) + call assert_equal(['', ''], spellbadword('A sentence. another sentence')) + + call delete('Xwords.spl') + call delete('Xwords') + set spelllang& + set spell& +endfunc + func Test_spellreall() new set spell @@ -351,6 +393,18 @@ func Test_zeq_crash() bwipe! endfunc +" Check handling a word longer than MAXWLEN. +func Test_spell_long_word() + set enc=utf-8 + new + call setline(1, "d\xCC\xB4\xCC\xBD\xCD\x88\xCD\x94a\xCC\xB5\xCD\x84\xCD\x84\xCC\xA8\xCD\x9Cr\xCC\xB5\xCC\x8E\xCD\x85\xCD\x85k\xCC\xB6\xCC\x89\xCC\x9D \xCC\xB6\xCC\x83\xCC\x8F\xCC\xA4\xCD\x8Ef\xCC\xB7\xCC\x81\xCC\x80\xCC\xA9\xCC\xB0\xCC\xAC\xCC\xA2\xCD\x95\xCD\x87\xCD\x8D\xCC\x9E\xCD\x99\xCC\xAD\xCC\xAB\xCC\x97\xCC\xBBo\xCC\xB6\xCC\x84\xCC\x95\xCC\x8C\xCC\x8B\xCD\x9B\xCD\x9C\xCC\xAFr\xCC\xB7\xCC\x94\xCD\x83\xCD\x97\xCC\x8C\xCC\x82\xCD\x82\xCD\x80\xCD\x91\xCC\x80\xCC\xBE\xCC\x82\xCC\x8F\xCC\xA3\xCD\x85\xCC\xAE\xCD\x8D\xCD\x99\xCC\xBC\xCC\xAB\xCC\xA7\xCD\x88c\xCC\xB7\xCD\x83\xCC\x84\xCD\x92\xCC\x86\xCC\x83\xCC\x88\xCC\x92\xCC\x94\xCC\xBE\xCC\x9D\xCC\xAF\xCC\x98\xCC\x9D\xCC\xBB\xCD\x8E\xCC\xBB\xCC\xB3\xCC\xA3\xCD\x8E\xCD\x99\xCC\xA5\xCC\xAD\xCC\x99\xCC\xB9\xCC\xAE\xCC\xA5\xCC\x9E\xCD\x88\xCC\xAE\xCC\x9E\xCC\xA9\xCC\x97\xCC\xBC\xCC\x99\xCC\xA5\xCD\x87\xCC\x97\xCD\x8E\xCD\x94\xCC\x99\xCC\x9D\xCC\x96\xCD\x94\xCC\xAB\xCC\xA7\xCC\xA5\xCC\x98\xCC\xBB\xCC\xAF\xCC\xABe\xCC\xB7\xCC\x8E\xCC\x82\xCD\x86\xCD\x9B\xCC\x94\xCD\x83\xCC\x85\xCD\x8A\xCD\x8C\xCC\x8B\xCD\x92\xCD\x91\xCC\x8F\xCC\x81\xCD\x95\xCC\xA2\xCC\xB9\xCC\xB2\xCD\x9C\xCC\xB1\xCC\xA6\xCC\xB3\xCC\xAF\xCC\xAE\xCC\x9C\xCD\x99s\xCC\xB8\xCC\x8C\xCC\x8E\xCC\x87\xCD\x81\xCD\x82\xCC\x86\xCD\x8C\xCD\x8C\xCC\x8B\xCC\x84\xCC\x8C\xCD\x84\xCD\x9B\xCD\x86\xCC\x93\xCD\x90\xCC\x85\xCC\x94\xCD\x98\xCD\x84\xCD\x92\xCD\x8B\xCC\x90\xCC\x83\xCC\x8F\xCD\x84\xCD\x81\xCD\x9B\xCC\x90\xCD\x81\xCC\x8F\xCC\xBD\xCC\x88\xCC\xBF\xCC\x88\xCC\x84\xCC\x8E\xCD\x99\xCD\x94\xCC\x99\xCD\x99\xCC\xB0\xCC\xA8\xCC\xA3\xCC\xA8\xCC\x96\xCC\x99\xCC\xAE\xCC\xBC\xCC\x99\xCD\x9A\xCC\xB2\xCC\xB1\xCC\x9F\xCC\xBB\xCC\xA6\xCD\x85\xCC\xAA\xCD\x89\xCC\x9D\xCC\x99\xCD\x96\xCC\xB1\xCC\xB1\xCC\x99\xCC\xA6\xCC\xA5\xCD\x95\xCC\xB2\xCC\xA0\xCD\x99 within") + set spell spelllang=en + redraw + redraw! + bwipe! + set nospell +endfunc + func LoadAffAndDic(aff_contents, dic_contents) throw 'skipped: Nvim does not support enc=latin1' set enc=latin1 diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim index 873f2e8731..b5647b610d 100644 --- a/src/nvim/testdir/test_startup.vim +++ b/src/nvim/testdir/test_startup.vim @@ -339,6 +339,88 @@ func Test_A_F_H_arg() call delete('Xtestout') endfunc +func Test_invalid_args() + if !has('unix') || has('gui_running') + " can't get output of Vim. + return + endif + + for opt in ['-Y', '--does-not-exist'] + let out = split(system(GetVimCommand() .. ' ' .. opt), "\n") + call assert_equal(1, v:shell_error) + call assert_equal('nvim: Unknown option argument: "' .. opt .. '"', out[0]) + call assert_equal('More info with "nvim -h"', out[1]) + endfor + + for opt in ['-c', '-i', '-s', '-t', '-u', '-U', '-w', '-W', '--cmd', '--startuptime'] + let out = split(system(GetVimCommand() .. ' ' .. opt), "\n") + call assert_equal(1, v:shell_error) + call assert_equal('nvim: Argument missing after: "' .. opt .. '"', out[0]) + call assert_equal('More info with "nvim -h"', out[1]) + endfor + + if has('clientserver') + " FIXME: need to add --servername to this list + " but it causes vim-8.1.1282 to crash! + for opt in ['--remote', '--remote-send', '--remote-silent', '--remote-expr', + \ '--remote-tab', '--remote-tab-wait', + \ '--remote-tab-wait-silent', '--remote-tab-silent', + \ '--remote-wait', '--remote-wait-silent', + \ ] + let out = split(system(GetVimCommand() .. ' ' .. opt), "\n") + call assert_equal(1, v:shell_error) + call assert_match('^VIM - Vi IMproved .* (.*)$', out[0]) + call assert_equal('Argument missing after: "' .. opt .. '"', out[1]) + call assert_equal('More info with: "vim -h"', out[2]) + endfor + endif + + " FIXME: commented out as this causes vim-8.1.1282 to crash! + "if has('clipboard') + " let out = split(system(GetVimCommand() .. ' --display'), "\n") + " call assert_equal(1, v:shell_error) + " call assert_match('^VIM - Vi IMproved .* (.*)$', out[0]) + " call assert_equal('Argument missing after: "--display"', out[1]) + " call assert_equal('More info with: "vim -h"', out[2]) + "endif + + let out = split(system(GetVimCommand() .. ' -ix'), "\n") + call assert_equal(1, v:shell_error) + call assert_equal('nvim: Garbage after option argument: "-ix"', out[0]) + call assert_equal('More info with "nvim -h"', out[1]) + + " Not an error in Nvim. The "-" file is allowed with -t, -q, or [file]. + let out = split(system(GetVimCommand() .. ' - xxx -cq'), "\n") + call assert_equal(0, v:shell_error) + + " Detect invalid repeated arguments '-t foo -t foo", '-q foo -q foo'. + for opt in ['-t', '-q'] + let out = split(system(GetVimCommand() .. repeat(' ' .. opt .. ' foo', 2)), "\n") + call assert_equal(1, v:shell_error) + call assert_equal('nvim: Too many edit arguments: "' .. opt .. '"', out[0]) + call assert_equal('More info with "nvim -h"', out[1]) + endfor + + for opt in [' -cq', ' --cmd q', ' +', ' -S foo'] + let out = split(system(GetVimCommand() .. repeat(opt, 11)), "\n") + call assert_equal(1, v:shell_error) + " FIXME: The error message given by Vim is not ideal in case of repeated + " -S foo since it does not mention -S. + call assert_equal('nvim: Too many "+command", "-c command" or "--cmd command" arguments', out[0]) + call assert_equal('More info with "nvim -h"', out[1]) + endfor + + if has('gui_gtk') + for opt in ['--socketid x', '--socketid 0xg'] + let out = split(system(GetVimCommand() .. ' ' .. opt), "\n") + call assert_equal(1, v:shell_error) + call assert_match('^VIM - Vi IMproved .* (.*)$', out[0]) + call assert_equal('Invalid argument for: "--socketid"', out[1]) + call assert_equal('More info with: "vim -h"', out[2]) + endfor + endif +endfunc + func Test_file_args() let after = [ \ 'call writefile(argv(), "Xtestout")', diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index b86340a23a..ea29da0262 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -86,8 +86,11 @@ func Test_statusline() call assert_match('^Xstatusline\s*$', s:get_statusline()) " %F: Full path to the file in the buffer. + let shellslash = &shellslash + set shellslash set statusline=%F call assert_match('/testdir/Xstatusline\s*$', s:get_statusline()) + let &shellslash = shellslash " %h: Help buffer flag, text is "[help]". " %H: Help buffer flag, text is ",HLP". diff --git a/src/nvim/testdir/test_suspend.vim b/src/nvim/testdir/test_suspend.vim index e569e49055..ef5a96bd72 100644 --- a/src/nvim/testdir/test_suspend.vim +++ b/src/nvim/testdir/test_suspend.vim @@ -2,6 +2,20 @@ source shared.vim +func CheckSuspended(buf, fileExists) + call WaitForAssert({-> assert_match('[$#] $', term_getline(a:buf, '.'))}) + + if a:fileExists + call assert_equal(['foo'], readfile('Xfoo')) + else + " Without 'autowrite', buffer should not be written. + call assert_equal(0, filereadable('Xfoo')) + endif + + call term_sendkeys(a:buf, "fg\<CR>\<C-L>") + call WaitForAssert({-> assert_equal(' 1 foo', term_getline(a:buf, '.'))}) +endfunc + func Test_suspend() if !has('terminal') || !executable('/bin/sh') return @@ -26,13 +40,7 @@ func Test_suspend() \ "\<C-Z>"] " Suspend and wait for shell prompt. call term_sendkeys(buf, suspend_cmd) - call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))}) - - " Without 'autowrite', buffer should not be written. - call assert_equal(0, filereadable('Xfoo')) - - call term_sendkeys(buf, "fg\<CR>") - call WaitForAssert({-> assert_equal(' 1 foo', term_getline(buf, '.'))}) + call CheckSuspended(buf, 0) endfor " Test that :suspend! with 'autowrite' writes content of buffers if modified. @@ -40,14 +48,13 @@ func Test_suspend() call assert_equal(0, filereadable('Xfoo')) call term_sendkeys(buf, ":suspend\<CR>") " Wait for shell prompt. - call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))}) - call assert_equal(['foo'], readfile('Xfoo')) - call term_sendkeys(buf, "fg\<CR>") - call WaitForAssert({-> assert_equal(' 1 foo', term_getline(buf, '.'))}) + call CheckSuspended(buf, 1) " Quit gracefully to dump coverage information. call term_sendkeys(buf, ":qall!\<CR>") call term_wait(buf) + " Wait until Vim actually exited and shell shows a prompt + call WaitForAssert({-> assert_match('[$#] $', term_getline(buf, '.'))}) call Stop_shell_in_terminal(buf) exe buf . 'bwipe!' diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 850947f89f..a3de879b2a 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -466,6 +466,8 @@ func Test_bg_detection() set bg=dark hi Normal ctermbg=12 call assert_equal('dark', &bg) + + hi Normal ctermbg=NONE endfunc fun Test_synstack_synIDtrans() diff --git a/src/nvim/testdir/test_tabline.vim b/src/nvim/testdir/test_tabline.vim index 6c7a02d650..f24552088b 100644 --- a/src/nvim/testdir/test_tabline.vim +++ b/src/nvim/testdir/test_tabline.vim @@ -1,19 +1,22 @@ -function! TablineWithCaughtError() + +source shared.vim + +func TablineWithCaughtError() let s:func_in_tabline_called = 1 try call eval('unknown expression') catch endtry return '' -endfunction +endfunc -function! TablineWithError() +func TablineWithError() let s:func_in_tabline_called = 1 call eval('unknown expression') return '' -endfunction +endfunc -function! Test_caught_error_in_tabline() +func Test_caught_error_in_tabline() let showtabline_save = &showtabline set showtabline=2 let s:func_in_tabline_called = 0 @@ -24,9 +27,9 @@ function! Test_caught_error_in_tabline() call assert_equal(tabline, &tabline) set tabline= let &showtabline = showtabline_save -endfunction +endfunc -function! Test_tabline_will_be_disabled_with_error() +func Test_tabline_will_be_disabled_with_error() let showtabline_save = &showtabline set showtabline=2 let s:func_in_tabline_called = 0 @@ -40,4 +43,24 @@ function! Test_tabline_will_be_disabled_with_error() call assert_equal('', &tabline) set tabline= let &showtabline = showtabline_save -endfunction +endfunc + +func Test_redrawtabline() + if has('gui') + set guioptions-=e + endif + let showtabline_save = &showtabline + set showtabline=2 + set tabline=%{bufnr('$')} + edit Xtabline1 + edit Xtabline2 + redraw + call assert_match(bufnr('$') . '', Screenline(1)) + au BufAdd * redrawtabline + badd Xtabline3 + call assert_match(bufnr('$') . '', Screenline(1)) + + set tabline= + let &showtabline = showtabline_save + au! Bufadd +endfunc diff --git a/src/nvim/testdir/test_tagcase.vim b/src/nvim/testdir/test_tagcase.vim index 833cb9f990..53c08ccf1e 100644 --- a/src/nvim/testdir/test_tagcase.vim +++ b/src/nvim/testdir/test_tagcase.vim @@ -44,6 +44,7 @@ func Test_tagcase() endfor call delete('Xtags') + set tags& set ic& setg tc& setl tc& diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 21ea00cab7..ce527a5e1d 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -20,6 +20,7 @@ func Test_cancel_ptjump() call assert_equal(2, winnr('$')) call delete('Xtags') + set tags& quit endfunc @@ -104,6 +105,7 @@ func Test_tagjump_switchbuf() enew | only call delete('Xfile1') call delete('Xtags') + set tags& set switchbuf&vim endfunc @@ -424,7 +426,7 @@ func Test_tagnr_recall() tag call assert_equal(bufname('%'), 'Xtest.h') - set tag& + set tags& call delete('Xtags') bwipe Xtest.h bwipe Xtest.c @@ -460,6 +462,7 @@ func Test_tag_line_toolong() endtry call assert_equal('Ignoring long line in tags file', split(execute('messages'), '\n')[-1]) call delete('Xtags') + set tags& let &verbose = old_vbs endfunc diff --git a/src/nvim/testdir/test_taglist.vim b/src/nvim/testdir/test_taglist.vim index ea0a6b9678..eb64e59509 100644 --- a/src/nvim/testdir/test_taglist.vim +++ b/src/nvim/testdir/test_taglist.vim @@ -5,7 +5,9 @@ func Test_taglist() \ "FFoo\tXfoo\t1", \ "FBar\tXfoo\t2", \ "BFoo\tXbar\t1", - \ "BBar\tXbar\t2" + \ "BBar\tXbar\t2", + \ "Kindly\tXbar\t3;\"\tv\tfile:", + \ "Command\tXbar\tcall cursor(3, 4)|;\"\td", \ ], 'Xtags') set tags=Xtags split Xtext @@ -15,7 +17,20 @@ func Test_taglist() call assert_equal(['FFoo', 'BFoo'], map(taglist("Foo", "Xfoo"), {i, v -> v.name})) call assert_equal(['BFoo', 'FFoo'], map(taglist("Foo", "Xbar"), {i, v -> v.name})) + let kind = taglist("Kindly") + call assert_equal(1, len(kind)) + call assert_equal('v', kind[0]['kind']) + call assert_equal('3', kind[0]['cmd']) + call assert_equal(1, kind[0]['static']) + call assert_equal('Xbar', kind[0]['filename']) + + let cmd = taglist("Command") + call assert_equal(1, len(cmd)) + call assert_equal('d', cmd[0]['kind']) + call assert_equal('call cursor(3, 4)', cmd[0]['cmd']) + call delete('Xtags') + set tags& bwipe endfunc @@ -36,6 +51,7 @@ func Test_taglist_native_etags() \ map(taglist('set_signals'), {i, v -> [v.name, v.cmd]})) call delete('Xtags') + set tags& endfunc func Test_taglist_ctags_etags() @@ -55,6 +71,7 @@ func Test_taglist_ctags_etags() \ map(taglist('set_signals'), {i, v -> [v.name, v.cmd]})) call delete('Xtags') + set tags& endfunc func Test_tags_too_long() @@ -62,19 +79,6 @@ func Test_tags_too_long() tags endfunc -" For historical reasons we support a tags file where the last line is missing -" the newline. -func Test_tagsfile_without_trailing_newline() - call writefile(["Foo\tfoo\t1"], 'Xtags', 'b') - set tags=Xtags - - let tl = taglist('.*') - call assert_equal(1, len(tl)) - call assert_equal('Foo', tl[0].name) - - call delete('Xtags') -endfunc - func Test_tagfiles() call assert_equal([], tagfiles()) @@ -90,10 +94,25 @@ func Test_tagfiles() \ fnamemodify(tf[0], ':p:gs?\\?/?')) helpclose call assert_equal(['Xtags1', 'Xtags2'], tagfiles()) - set tags& + " Nvim: defaults to "./tags;,tags", which might cause false positives. + set tags=./tags,tags call assert_equal([], tagfiles()) call delete('Xtags1') call delete('Xtags2') bd endfunc + +" For historical reasons we support a tags file where the last line is missing +" the newline. +func Test_tagsfile_without_trailing_newline() + call writefile(["Foo\tfoo\t1"], 'Xtags', 'b') + set tags=Xtags + + let tl = taglist('.*') + call assert_equal(1, len(tl)) + call assert_equal('Foo', tl[0].name) + + call delete('Xtags') + set tags& +endfunc diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim index 1520c2f32a..b23a4aa62f 100644 --- a/src/nvim/testdir/test_usercommands.vim +++ b/src/nvim/testdir/test_usercommands.vim @@ -73,6 +73,97 @@ function Test_cmdmods() unlet g:mods endfunction +func SaveCmdArgs(...) + let g:args = a:000 +endfunc + +func Test_f_args() + command -nargs=* TestFArgs call SaveCmdArgs(<f-args>) + + TestFArgs + call assert_equal([], g:args) + + TestFArgs one two three + call assert_equal(['one', 'two', 'three'], g:args) + + TestFArgs one\\two three + call assert_equal(['one\two', 'three'], g:args) + + TestFArgs one\ two three + call assert_equal(['one two', 'three'], g:args) + + TestFArgs one\"two three + call assert_equal(['one\"two', 'three'], g:args) + + delcommand TestFArgs +endfunc + +func Test_q_args() + command -nargs=* TestQArgs call SaveCmdArgs(<q-args>) + + TestQArgs + call assert_equal([''], g:args) + + TestQArgs one two three + call assert_equal(['one two three'], g:args) + + TestQArgs one\\two three + call assert_equal(['one\\two three'], g:args) + + TestQArgs one\ two three + call assert_equal(['one\ two three'], g:args) + + TestQArgs one\"two three + call assert_equal(['one\"two three'], g:args) + + delcommand TestQArgs +endfunc + +func Test_reg_arg() + command -nargs=* -reg TestRegArg call SaveCmdArgs("<reg>", "<register>") + + TestRegArg + call assert_equal(['', ''], g:args) + + TestRegArg x + call assert_equal(['x', 'x'], g:args) + + delcommand TestRegArg +endfunc + +func Test_no_arg() + command -nargs=* TestNoArg call SaveCmdArgs("<args>", "<>", "<x>", "<lt>") + + TestNoArg + call assert_equal(['', '<>', '<x>', '<'], g:args) + + TestNoArg one + call assert_equal(['one', '<>', '<x>', '<'], g:args) + + delcommand TestNoArg +endfunc + +func Test_range_arg() + command -range TestRangeArg call SaveCmdArgs(<range>, <line1>, <line2>) + new + call setline(1, range(100)) + let lnum = line('.') + + TestRangeArg + call assert_equal([0, lnum, lnum], g:args) + + 99TestRangeArg + call assert_equal([1, 99, 99], g:args) + + 88,99TestRangeArg + call assert_equal([2, 88, 99], g:args) + + call assert_fails('102TestRangeArg', 'E16:') + + bwipe! + delcommand TestRangeArg +endfunc + func Test_Ambiguous() command Doit let g:didit = 'yes' command Dothat let g:didthat = 'also' @@ -88,6 +179,9 @@ func Test_Ambiguous() Do call assert_equal('also', g:didthat) delcommand Dothat + + " Nvim removed the ":Ni!" easter egg in 87e107d92. + call assert_fails("\x4ei\041", 'E492: Not an editor command: Ni!') endfunc func Test_CmdUndefined() @@ -111,6 +205,7 @@ func Test_CmdErrors() call assert_fails('com! - DoCmd :', 'E175:') call assert_fails('com! -xxx DoCmd :', 'E181:') call assert_fails('com! -addr DoCmd :', 'E179:') + call assert_fails('com! -addr=asdf DoCmd :', 'E180:') call assert_fails('com! -complete DoCmd :', 'E179:') call assert_fails('com! -complete=xxx DoCmd :', 'E180:') call assert_fails('com! -complete=custom DoCmd :', 'E467:') diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index 05abf04d65..f39e53d6dd 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1,4 +1,4 @@ -" Test various aspects of the Vim language. +" Test various aspects of the Vim script language. " Most of this was formerly in test49. "------------------------------------------------------------------------------- @@ -21,7 +21,7 @@ com! -nargs=1 Xout call Xout(<args>) " " Create a script that consists of the body of the function a:funcname. " Replace any ":return" by a ":finish", any argument variable by a global -" variable, and and every ":call" by a ":source" for the next following argument +" variable, and every ":call" by a ":source" for the next following argument " in the variable argument list. This function is useful if similar tests are " to be made for a ":return" from a function call or a ":finish" in a script " file. @@ -1310,6 +1310,43 @@ func Test_compound_assignment_operators() let x .= 'n' call assert_equal('2n', x) + " Test special cases: division or modulus with 0. + let x = 1 + let x /= 0 + if has('num64') + call assert_equal(0x7FFFFFFFFFFFFFFF, x) + else + call assert_equal(0x7fffffff, x) + endif + + let x = -1 + let x /= 0 + if has('num64') + call assert_equal(-0x7FFFFFFFFFFFFFFF, x) + else + call assert_equal(-0x7fffffff, x) + endif + + let x = 0 + let x /= 0 + if has('num64') + call assert_equal(-0x7FFFFFFFFFFFFFFF - 1, x) + else + call assert_equal(-0x7FFFFFFF - 1, x) + endif + + let x = 1 + let x %= 0 + call assert_equal(0, x) + + let x = -1 + let x %= 0 + call assert_equal(0, x) + + let x = 0 + let x %= 0 + call assert_equal(0, x) + " Test for string let x = 'str' let x .= 'ing' @@ -1375,5 +1412,4 @@ endfunc "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=4 tw=80 fdm=marker -" vim: fdt=substitute(substitute(foldtext(),\ '\\%(^+--\\)\\@<=\\(\\s*\\)\\(.\\{-}\\)\:\ \\%(\"\ \\)\\=\\(Test\ \\d*\\)\:\\s*',\ '\\3\ (\\2)\:\ \\1',\ \"\"),\ '\\(Test\\s*\\)\\(\\d\\)\\D\\@=',\ '\\1\ \\2',\ "") "------------------------------------------------------------------------------- diff --git a/src/nvim/testdir/test_virtualedit.vim b/src/nvim/testdir/test_virtualedit.vim index d49025237b..abe79f6a4a 100644 --- a/src/nvim/testdir/test_virtualedit.vim +++ b/src/nvim/testdir/test_virtualedit.vim @@ -42,6 +42,22 @@ func Test_paste_end_of_line() set virtualedit= endfunc +func Test_replace_end_of_line() + new + set virtualedit=all + call setline(1, range(20)) + exe "normal! gg2jv10lr-" + call assert_equal(["1", "-----------", "3"], getline(2,4)) + if has('multi_byte') + call setline(1, range(20)) + exe "normal! gg2jv10lr\<c-k>hh" + call assert_equal(["1", "───────────", "3"], getline(2,4)) + endif + + bwipe! + set virtualedit= +endfunc + func Test_edit_CTRL_G() new set virtualedit=insert diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 003a23ea7b..2a07a04401 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -518,6 +518,43 @@ func Test_winrestcmd() only endfunc +function! Fun_RenewFile() + sleep 2 + silent execute '!echo "1" > tmp.txt' + sp + wincmd p + edit! tmp.txt +endfunction + +func Test_window_prevwin() + " Can we make this work on MS-Windows? + if !has('unix') + return + endif + + set hidden autoread + call writefile(['2'], 'tmp.txt') + new tmp.txt + q + " Need to wait a bit for the timestamp to be older. + call Fun_RenewFile() + call assert_equal(2, winnr()) + wincmd p + call assert_equal(1, winnr()) + wincmd p + q + call Fun_RenewFile() + call assert_equal(2, winnr()) + wincmd p + call assert_equal(1, winnr()) + wincmd p + " reset + q + call delete('tmp.txt') + set hidden&vim autoread&vim + delfunc Fun_RenewFile +endfunc + func Test_relative_cursor_position_in_one_line_window() new only diff --git a/src/nvim/testdir/test_windows_home.vim b/src/nvim/testdir/test_windows_home.vim index bbcbf96050..2e311b9aa5 100644 --- a/src/nvim/testdir/test_windows_home.vim +++ b/src/nvim/testdir/test_windows_home.vim @@ -86,7 +86,7 @@ func Test_WindowsHome() let $HOME = '%USERPROFILE%\bar' let $HOMEDRIVE = 'unused' let $HOMEPATH = 'unused' - " call CheckHome('C:\foo\bar', '%USERPROFILE%\bar') + call CheckHome('C:\foo\bar', '%USERPROFILE%\bar') " Invalid $HOME is kept let $USERPROFILE = 'C:\foo' diff --git a/src/nvim/testdir/test_writefile.vim b/src/nvim/testdir/test_writefile.vim index 9da9df2150..6d88c0d8cd 100644 --- a/src/nvim/testdir/test_writefile.vim +++ b/src/nvim/testdir/test_writefile.vim @@ -33,7 +33,7 @@ func Test_writefile_fails_gently() endfunc func Test_writefile_fails_conversion() - if !has('multi_byte') || !has('iconv') + if !has('multi_byte') || !has('iconv') || system('uname -s') =~ 'SunOS' return endif " Without a backup file the write won't happen if there is a conversion diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 7a725df0a1..fe8ffee8e0 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -32,23 +32,6 @@ void tinput_init(TermInput *input, Loop *loop) uv_mutex_init(&input->key_buffer_mutex); uv_cond_init(&input->key_buffer_cond); - const char *term = os_getenv("TERM"); - if (!term) { - term = ""; // termkey_new_abstract assumes non-null (#2745) - } - -#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 - input->tk = termkey_new_abstract(term, - TERMKEY_FLAG_UTF8 | TERMKEY_FLAG_NOSTART); - termkey_hook_terminfo_getstr(input->tk, input->tk_ti_hook_fn, NULL); - termkey_start(input->tk); -#else - input->tk = termkey_new_abstract(term, TERMKEY_FLAG_UTF8); -#endif - - int curflags = termkey_get_canonflags(input->tk); - termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS); - // If stdin is not a pty, switch to stderr. For cases like: // echo q | nvim -es // ls *.md | xargs nvim @@ -67,6 +50,24 @@ void tinput_init(TermInput *input, Loop *loop) input->in_fd = 2; } #endif + input_global_fd_init(input->in_fd); + + const char *term = os_getenv("TERM"); + if (!term) { + term = ""; // termkey_new_abstract assumes non-null (#2745) + } + +#if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 + input->tk = termkey_new_abstract(term, + TERMKEY_FLAG_UTF8 | TERMKEY_FLAG_NOSTART); + termkey_hook_terminfo_getstr(input->tk, input->tk_ti_hook_fn, NULL); + termkey_start(input->tk); +#else + input->tk = termkey_new_abstract(term, TERMKEY_FLAG_UTF8); +#endif + + int curflags = termkey_get_canonflags(input->tk); + termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS); // setup input handle rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff); @@ -387,17 +388,20 @@ static void set_bg_deferred(void **argv) // During startup, tui.c requests the background color (see `ext.get_bg`). // // Here in input.c, we watch for the terminal response `\e]11;COLOR\a`. If -// COLOR matches `rgb:RRRR/GGGG/BBBB` where R, G, and B are hex digits, then -// compute the luminance[1] of the RGB color and classify it as light/dark +// COLOR matches `rgb:RRRR/GGGG/BBBB/AAAA` where R, G, B, and A are hex digits, +// then compute the luminance[1] of the RGB color and classify it as light/dark // accordingly. Note that the color components may have anywhere from one to // four hex digits, and require scaling accordingly as values out of 4, 8, 12, -// or 16 bits. +// or 16 bits. Also note the A(lpha) component is optional, and is parsed but +// ignored in the calculations. // // [1] https://en.wikipedia.org/wiki/Luma_%28video%29 static bool handle_background_color(TermInput *input) { size_t count = 0; size_t component = 0; + size_t header_size = 0; + size_t num_components = 0; uint16_t rgb[] = { 0, 0, 0 }; uint16_t rgb_max[] = { 0, 0, 0 }; bool eat_backslash = false; @@ -405,48 +409,54 @@ static bool handle_background_color(TermInput *input) bool bad = false; if (rbuffer_size(input->read_stream.buffer) >= 9 && !rbuffer_cmp(input->read_stream.buffer, "\x1b]11;rgb:", 9)) { - rbuffer_consumed(input->read_stream.buffer, 9); - RBUFFER_EACH(input->read_stream.buffer, c, i) { - count = i + 1; - if (eat_backslash) { - done = true; - break; - } else if (c == '\x07') { - done = true; - break; - } else if (c == '\x1b') { - eat_backslash = true; - } else if (bad) { - // ignore - } else if (c == '/') { - if (component < 3) { - component++; - } - } else if (ascii_isxdigit(c)) { - if (component < 3 && rgb_max[component] != 0xffff) { - rgb_max[component] = (uint16_t)((rgb_max[component] << 4) | 0xf); - rgb[component] = (uint16_t)((rgb[component] << 4) | hex2nr(c)); - } - } else { - bad = true; + header_size = 9; + num_components = 3; + } else if (rbuffer_size(input->read_stream.buffer) >= 10 + && !rbuffer_cmp(input->read_stream.buffer, "\x1b]11;rgba:", 10)) { + header_size = 10; + num_components = 4; + } else { + return false; + } + rbuffer_consumed(input->read_stream.buffer, header_size); + RBUFFER_EACH(input->read_stream.buffer, c, i) { + count = i + 1; + if (eat_backslash) { + done = true; + break; + } else if (c == '\x07') { + done = true; + break; + } else if (c == '\x1b') { + eat_backslash = true; + } else if (bad) { + // ignore + } else if ((c == '/') && (++component < num_components)) { + // work done in condition + } else if (ascii_isxdigit(c)) { + if (component < 3 && rgb_max[component] != 0xffff) { + rgb_max[component] = (uint16_t)((rgb_max[component] << 4) | 0xf); + rgb[component] = (uint16_t)((rgb[component] << 4) | hex2nr(c)); } - } - rbuffer_consumed(input->read_stream.buffer, count); - if (done && !bad && rgb_max[0] && rgb_max[1] && rgb_max[2]) { - double r = (double)rgb[0] / (double)rgb_max[0]; - double g = (double)rgb[1] / (double)rgb_max[1]; - double b = (double)rgb[2] / (double)rgb_max[2]; - double luminance = (0.299 * r) + (0.587 * g) + (0.114 * b); // CCIR 601 - char *bgvalue = luminance < 0.5 ? "dark" : "light"; - DLOG("bg response: %s", bgvalue); - loop_schedule_deferred(&main_loop, - event_create(set_bg_deferred, 1, bgvalue)); } else { - DLOG("failed to parse bg response"); + bad = true; } - return true; } - return false; + rbuffer_consumed(input->read_stream.buffer, count); + if (done && !bad && rgb_max[0] && rgb_max[1] && rgb_max[2]) { + double r = (double)rgb[0] / (double)rgb_max[0]; + double g = (double)rgb[1] / (double)rgb_max[1]; + double b = (double)rgb[2] / (double)rgb_max[2]; + double luminance = (0.299 * r) + (0.587 * g) + (0.114 * b); // CCIR 601 + char *bgvalue = luminance < 0.5 ? "dark" : "light"; + DLOG("bg response: %s", bgvalue); + loop_schedule_deferred(&main_loop, + event_create(set_bg_deferred, 1, bgvalue)); + } else { + DLOG("failed to parse bg response"); + return false; + } + return true; } static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 42e5b9b270..519ef1cccd 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -222,15 +222,20 @@ static void terminfo_start(UI *ui) #ifdef WIN32 os_tty_guess_term(&term, data->out_fd); os_setenv("TERM", term, 1); + // Old os_getenv() pointer is invalid after os_setenv(), fetch it again. + term = os_getenv("TERM"); #endif // Set up unibilium/terminfo. - data->ut = unibi_from_env(); char *termname = NULL; - if (!term || !data->ut) { + if (term) { + data->ut = unibi_from_term(term); + if (data->ut) { + termname = xstrdup(term); + } + } + if (!data->ut) { data->ut = terminfo_from_builtin(term, &termname); - } else { - termname = xstrdup(term); } // Update 'term' option. loop_schedule_deferred(&main_loop, @@ -1585,6 +1590,11 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, unibi_set_if_empty(ut, unibi_set_left_margin_parm, "\x1b[%i%p1%ds"); unibi_set_if_empty(ut, unibi_set_right_margin_parm, "\x1b[%i;%p2%ds"); } + +#ifdef WIN32 + // XXX: workaround libuv implicit LF => CRLF conversion. #10558 + unibi_set_str(ut, unibi_cursor_down, "\x1b[B"); +#endif } else if (rxvt) { // 2017-04 terminfo.src lacks these. Unicode rxvt has them. unibi_set_if_empty(ut, unibi_enter_italics_mode, "\x1b[3m"); diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c index 8adb421ee1..9e4aaff878 100644 --- a/src/nvim/ugrid.c +++ b/src/nvim/ugrid.c @@ -6,6 +6,7 @@ #include <stdio.h> #include <limits.h> +#include "nvim/assert.h" #include "nvim/vim.h" #include "nvim/ui.h" #include "nvim/ugrid.h" @@ -72,8 +73,9 @@ void ugrid_scroll(UGrid *grid, int top, int bot, int left, int right, int count) for (i = start; i != stop; i += step) { UCell *target_row = grid->cells[i] + left; UCell *source_row = grid->cells[i + count] + left; + assert(right >= left && left >= 0); memcpy(target_row, source_row, - sizeof(UCell) * (size_t)(right - left + 1)); + sizeof(UCell) * ((size_t)right - (size_t)left + 1)); } } diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 7dbb8ec790..fc4a3a403d 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -341,15 +341,15 @@ void ui_line(ScreenGrid *grid, int row, int startcol, int endcol, int clearcol, flags, (const schar_T *)grid->chars + off, (const sattr_T *)grid->attrs + off); - if (p_wd) { // 'writedelay': flush & delay each time. - int old_row = cursor_row, old_col = cursor_col; - handle_T old_grid = cursor_grid_handle; + // 'writedelay': flush & delay each time. + if (p_wd && !(rdb_flags & RDB_COMPOSITOR)) { // If 'writedelay' is active, set the cursor to indicate what was drawn. - ui_grid_cursor_goto(grid->handle, row, MIN(clearcol, (int)Columns-1)); - ui_flush(); + ui_call_grid_cursor_goto(grid->handle, row, + MIN(clearcol, (int)grid->Columns-1)); + ui_call_flush(); uint64_t wd = (uint64_t)labs(p_wd); os_microdelay(wd * 1000u, true); - ui_grid_cursor_goto(old_grid, old_row, old_col); + pending_cursor_update = true; // restore the cursor later } } @@ -372,6 +372,14 @@ void ui_grid_cursor_goto(handle_T grid_handle, int new_row, int new_col) pending_cursor_update = true; } +/// moving the cursor grid will implicitly move the cursor +void ui_check_cursor_grid(handle_T grid_handle) +{ + if (cursor_grid_handle == grid_handle) { + pending_cursor_update = true; + } +} + void ui_mode_info_set(void) { pending_mode_info_update = true; diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c index f6573e7488..2cb3cf7ee7 100644 --- a/src/nvim/ui_compositor.c +++ b/src/nvim/ui_compositor.c @@ -49,6 +49,8 @@ static bool valid_screen = true; static bool msg_scroll_mode = false; static int msg_first_invalid = 0; +static int dbghl_normal, dbghl_clear, dbghl_composed, dbghl_recompose; + void ui_comp_init(void) { if (compositor != NULL) { @@ -81,6 +83,13 @@ void ui_comp_init(void) ui_attach_impl(compositor); } +void ui_comp_syn_init(void) +{ + dbghl_normal = syn_check_group((char_u *)S_LEN("RedrawDebugNormal")); + dbghl_clear = syn_check_group((char_u *)S_LEN("RedrawDebugClear")); + dbghl_composed = syn_check_group((char_u *)S_LEN("RedrawDebugComposed")); + dbghl_recompose = syn_check_group((char_u *)S_LEN("RedrawDebugRecompose")); +} void ui_comp_attach(UI *ui) { @@ -290,10 +299,14 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, { // in case we start on the right half of a double-width char, we need to // check the left half. But skip it in output if it wasn't doublewidth. - int skip = 0; + int skipstart = 0, skipend = 0; if (startcol > 0 && (flags & kLineFlagInvalid)) { startcol--; - skip = 1; + skipstart = 1; + } + if (endcol < default_grid.Columns && (flags & kLineFlagInvalid)) { + endcol++; + skipend = 1; } int col = (int)startcol; @@ -329,13 +342,24 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, memcpy(linebuf+(col-startcol), grid->chars+off, n * sizeof(*linebuf)); memcpy(attrbuf+(col-startcol), grid->attrs+off, n * sizeof(*attrbuf)); - // 'pumblend' - if (grid == &pum_grid && p_pb) { - for (int i = col-(int)startcol; i < until-startcol; i++) { - bool thru = strequal((char *)linebuf[i], " "); // negative space - attrbuf[i] = (sattr_T)hl_blend_attrs(bg_attrs[i], attrbuf[i], thru); + // 'pumblend' and 'winblend' + if (grid->blending) { + int width; + for (int i = col-(int)startcol; i < until-startcol; i += width) { + width = 1; + // negative space + bool thru = strequal((char *)linebuf[i], " ") && bg_line[i][0] != NUL; + if (i+1 < endcol-startcol && bg_line[i+1][0] == NUL) { + width = 2; + thru &= strequal((char *)linebuf[i+1], " "); + } + attrbuf[i] = (sattr_T)hl_blend_attrs(bg_attrs[i], attrbuf[i], &thru); + if (width == 2) { + attrbuf[i+1] = (sattr_T)hl_blend_attrs(bg_attrs[i+1], + attrbuf[i+1], &thru); + } if (thru) { - memcpy(linebuf[i], bg_line[i], sizeof(linebuf[i])); + memcpy(linebuf + i, bg_line + i, (size_t)width * sizeof(linebuf[i])); } } } @@ -345,20 +369,27 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, if (linebuf[col-startcol][0] == NUL) { linebuf[col-startcol][0] = ' '; linebuf[col-startcol][1] = NUL; + if (col == endcol-1) { + skipend = 0; + } } else if (n > 1 && linebuf[col-startcol+1][0] == NUL) { - skip = 0; + skipstart = 0; } if (grid->comp_col+grid->Columns > until && grid->chars[off+n][0] == NUL) { linebuf[until-1-startcol][0] = ' '; linebuf[until-1-startcol][1] = '\0'; if (col == startcol && n == 1) { - skip = 0; + skipstart = 0; } } col = until; } + if (linebuf[endcol-startcol-1][0] == NUL) { + skipend = 0; + } + assert(endcol <= chk_width); assert(row < chk_height); @@ -368,14 +399,48 @@ static void compose_line(Integer row, Integer startcol, Integer endcol, flags = flags & ~kLineFlagWrap; } - ui_composed_call_raw_line(1, row, startcol+skip, endcol, endcol, 0, flags, - (const schar_T *)linebuf+skip, - (const sattr_T *)attrbuf+skip); + ui_composed_call_raw_line(1, row, startcol+skipstart, + endcol-skipend, endcol-skipend, 0, flags, + (const schar_T *)linebuf+skipstart, + (const sattr_T *)attrbuf+skipstart); } +static void compose_debug(Integer startrow, Integer endrow, Integer startcol, + Integer endcol, int syn_id, bool delay) +{ + if (!(rdb_flags & RDB_COMPOSITOR)) { + return; + } + + endrow = MIN(endrow, default_grid.Rows); + endcol = MIN(endcol, default_grid.Columns); + int attr = syn_id2attr(syn_id); + + for (int row = (int)startrow; row < endrow; row++) { + ui_composed_call_raw_line(1, row, startcol, startcol, endcol, attr, false, + (const schar_T *)linebuf, + (const sattr_T *)attrbuf); + } + + + if (delay) { + debug_delay(endrow-startrow); + } +} + +static void debug_delay(Integer lines) +{ + ui_call_flush(); + uint64_t wd = (uint64_t)labs(p_wd); + uint64_t factor = (uint64_t)MAX(MIN(lines, 5), 1); + os_microdelay(factor * wd * 1000u, true); +} + + static void compose_area(Integer startrow, Integer endrow, Integer startcol, Integer endcol) { + compose_debug(startrow, endrow, startcol, endcol, dbghl_recompose, true); endrow = MIN(endrow, default_grid.Rows); endcol = MIN(endcol, default_grid.Columns); if (endcol <= startcol) { @@ -419,9 +484,12 @@ static void ui_comp_raw_line(UI *ui, Integer grid, Integer row, assert(clearcol <= default_grid.Columns); if (flags & kLineFlagInvalid || kv_size(layers) > curgrid->comp_index+1 - || (p_pb && curgrid == &pum_grid)) { + || curgrid->blending) { + compose_debug(row, row+1, startcol, clearcol, dbghl_composed, true); compose_line(row, startcol, clearcol, flags); } else { + compose_debug(row, row+1, startcol, endcol, dbghl_normal, false); + compose_debug(row, row+1, endcol, clearcol, dbghl_clear, true); ui_composed_call_raw_line(1, row, startcol, endcol, clearcol, clearattr, flags, chunk, attrs); } @@ -467,7 +535,8 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, bot += curgrid->comp_row; left += curgrid->comp_col; right += curgrid->comp_col; - if (!msg_scroll_mode && kv_size(layers) > curgrid->comp_index+1) { + bool covered = kv_size(layers) > curgrid->comp_index+1 || curgrid->blending; + if (!msg_scroll_mode && covered) { // TODO(bfredl): // 1. check if rectangles actually overlap // 2. calulate subareas that can scroll. @@ -480,6 +549,9 @@ static void ui_comp_grid_scroll(UI *ui, Integer grid, Integer top, } else { msg_first_invalid = MIN(msg_first_invalid, (int)top); ui_composed_call_grid_scroll(1, top, bot, left, right, rows, cols); + if (rdb_flags & RDB_COMPOSITOR) { + debug_delay(2); + } } } diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 116bdd02b8..1a31f6a6c7 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -85,6 +85,7 @@ #include "nvim/buffer.h" #include "nvim/ascii.h" +#include "nvim/change.h" #include "nvim/undo.h" #include "nvim/cursor.h" #include "nvim/edit.h" @@ -1299,7 +1300,7 @@ void u_read_undo(char *name, char_u *hash, char_u *orig_name) verbose_leave(); } - FILE *fp = mch_fopen(file_name, "r"); + FILE *fp = os_fopen(file_name, "r"); if (fp == NULL) { if (name != NULL || p_verbose > 0) { EMSG2(_("E822: Cannot open undo file for reading: %s"), file_name); @@ -2961,10 +2962,23 @@ static char_u *u_save_line(linenr_T lnum) /// /// @return true if the buffer has changed bool bufIsChanged(buf_T *buf) + FUNC_ATTR_WARN_UNUSED_RESULT { return !bt_dontwrite(buf) && (buf->b_changed || file_ff_differs(buf, true)); } +// Return true if any buffer has changes. Also buffers that are not written. +bool anyBufIsChanged(void) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + FOR_ALL_BUFFERS(buf) { + if (bufIsChanged(buf)) { + return true; + } + } + return false; +} + /// Check if the 'modified' flag is set, or 'ff' has changed (only need to /// check the first character, because it can only be "dos", "unix" or "mac"). /// "nofile" and "scratch" type buffers are considered to always be unchanged. diff --git a/src/nvim/version.c b/src/nvim/version.c index be7a2ffcad..74a4852def 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -52,12 +52,8 @@ static char *features[] = { "-acl", #endif -#if (defined(HAVE_ICONV_H) && defined(USE_ICONV)) || defined(DYNAMIC_ICONV) -# ifdef DYNAMIC_ICONV -"+iconv/dyn", -# else +#if defined(HAVE_ICONV) "+iconv", -# endif #else "-iconv", #endif @@ -76,7 +72,7 @@ static const int included_patches[] = { // 1848, 1847, // 1846, - // 1845, + 1845, // 1844, 1843, // 1842, @@ -137,7 +133,7 @@ static const int included_patches[] = { 1787, // 1786, 1785, - // 1784, + 1784, // 1783, 1782, 1781, @@ -165,7 +161,7 @@ static const int included_patches[] = { // 1759, 1758, 1757, - // 1756, + 1756, 1755, // 1754, // 1753, @@ -186,7 +182,7 @@ static const int included_patches[] = { // 1738, 1737, 1736, - // 1735, + 1735, 1734, // 1733, // 1732, @@ -195,10 +191,10 @@ static const int included_patches[] = { // 1729, // 1728, 1727, - // 1726, + 1726, // 1725, // 1724, - // 1723, + 1723, // 1722, // 1721, // 1720, @@ -217,9 +213,9 @@ static const int included_patches[] = { 1707, // 1706, 1705, - // 1704, + 1704, // 1703, - // 1702, + 1702, 1701, 1700, 1699, @@ -232,7 +228,7 @@ static const int included_patches[] = { 1692, // 1691, // 1690, - // 1689, + 1689, // 1688, 1687, 1686, @@ -252,7 +248,7 @@ static const int included_patches[] = { 1672, // 1671, // 1670, - // 1669, + 1669, // 1668, // 1667, // 1666, @@ -372,7 +368,7 @@ static const int included_patches[] = { // 1552, // 1551, // 1550, - // 1549, + 1549, 1548, 1547, // 1546, @@ -403,9 +399,9 @@ static const int included_patches[] = { // 1521, // 1520, 1519, - // 1518, + 1518, 1517, - // 1516, + 1516, // 1515, 1514, 1513, @@ -507,9 +503,9 @@ static const int included_patches[] = { 1417, 1416, 1415, - // 1414, + 1414, 1413, - // 1412, + 1412, 1411, 1410, 1409, @@ -537,7 +533,7 @@ static const int included_patches[] = { 1387, // 1386, 1385, - // 1384, + 1384, 1383, // 1382, // 1381, @@ -616,7 +612,7 @@ static const int included_patches[] = { 1308, // 1307, 1306, - // 1305, + 1305, 1304, 1303, 1302, @@ -643,7 +639,7 @@ static const int included_patches[] = { 1281, 1280, 1279, - // 1278, + 1278, // 1277, // 1276, 1275, @@ -676,7 +672,7 @@ static const int included_patches[] = { 1248, 1247, // 1246, - // 1245, + 1245, // 1244, 1243, 1242, @@ -968,7 +964,7 @@ static const int included_patches[] = { // 956, 955, 954, - // 953, + 953, 952, 951, 950, @@ -988,9 +984,9 @@ static const int included_patches[] = { // 936, // 935, // 934, - // 933, + 933, // 932, - // 931, + 931, // 930, // 929, 928, @@ -1008,7 +1004,7 @@ static const int included_patches[] = { // 916, 915, // 914, - // 913, + 913, // 912, 911, // 910, @@ -1022,43 +1018,43 @@ static const int included_patches[] = { // 902, 901, 900, - // 899, - // 898, + 899, + 898, 897, - // 896, + 896, 895, 894, - // 893, - // 892, + 893, + 892, 891, 890, 889, 888, - // 887, + 887, 886, - // 885, - // 884, + 885, + 884, 883, - // 882, + 882, 881, 880, 879, 878, - // 877, + 877, 876, 875, - // 874, + 874, 873, 872, 871, - // 870, - // 869, + 870, + 869, 868, - // 867, + 867, 866, 865, - // 864, - // 863, + 864, + 863, 862, 861, 860, @@ -1100,12 +1096,12 @@ static const int included_patches[] = { 824, 823, 822, - // 821, + 821, 820, 819, - // 818, - // 817, - // 816, + 818, + 817, + 816, 815, 814, 813, @@ -1118,7 +1114,7 @@ static const int included_patches[] = { 806, 805, 804, - // 803, + 803, 802, 801, 800, @@ -2002,30 +1998,78 @@ void ex_version(exarg_T *eap) } } -/// List all features aligned in columns, dictionary style. +/// Output a string for the version message. If it's going to wrap, output a +/// newline, unless the message is too long to fit on the screen anyway. +/// When "wrap" is TRUE wrap the string in []. +/// @param s +/// @param wrap +static void version_msg_wrap(char_u *s, int wrap) +{ + int len = (int)vim_strsize(s) + (wrap ? 2 : 0); + + if (!got_int + && (len < Columns) + && (msg_col + len >= Columns) + && (*s != '\n')) { + msg_putchar('\n'); + } + + if (!got_int) { + if (wrap) { + msg_puts("["); + } + msg_puts((char *)s); + if (wrap) { + msg_puts("]"); + } + } +} + +static void version_msg(char *s) +{ + version_msg_wrap((char_u *)s, false); +} + +/// List all features. +/// This does not use list_in_columns (as in Vim), because there are only a +/// few, and we do not start at a new line. static void list_features(void) { - int nfeat = 0; + version_msg(_("\n\nFeatures: ")); + for (int i = 0; features[i] != NULL; i++) { + version_msg(features[i]); + if (features[i+1] != NULL) { + version_msg(" "); + } + } + version_msg("\nSee \":help feature-compile\"\n\n"); +} + +/// List string items nicely aligned in columns. +/// When "size" is < 0 then the last entry is marked with NULL. +/// The entry with index "current" is inclosed in []. +void list_in_columns(char_u **items, int size, int current) +{ + int item_count = 0; int width = 0; - // Find the length of the longest feature name, use that + 1 as the column - // width + // Find the length of the longest item, use that + 1 as the column width. int i; - for (i = 0; features[i] != NULL; ++i) { - int l = (int)STRLEN(features[i]); + for (i = 0; size < 0 ? items[i] != NULL : i < size; i++) { + int l = (int)vim_strsize(items[i]) + (i == current ? 2 : 0); if (l > width) { width = l; } - nfeat++; + item_count++; } width += 1; if (Columns < width) { // Not enough screen columns - show one per line - for (i = 0; features[i] != NULL; ++i) { - version_msg(features[i]); - if (msg_col > 0) { + for (i = 0; i < item_count; i++) { + version_msg_wrap(items[i], i == current); + if (msg_col > 0 && i < item_count - 1) { msg_putchar('\n'); } } @@ -2035,28 +2079,41 @@ static void list_features(void) // The rightmost column doesn't need a separator. // Sacrifice it to fit in one more column if possible. int ncol = (int)(Columns + 1) / width; - int nrow = nfeat / ncol + (nfeat % ncol ? 1 : 0); + int nrow = item_count / ncol + (item_count % ncol ? 1 : 0); + int cur_row = 1; - // i counts columns then rows. idx counts rows then columns. - for (i = 0; !got_int && i < nrow * ncol; ++i) { + // "i" counts columns then rows. "idx" counts rows then columns. + for (i = 0; !got_int && i < nrow * ncol; i++) { int idx = (i / ncol) + (i % ncol) * nrow; - if (idx < nfeat) { + if (idx < item_count) { int last_col = (i + 1) % ncol == 0; - msg_puts(features[idx]); + if (idx == current) { + msg_putchar('['); + } + msg_puts((char *)items[idx]); + if (idx == current) { + msg_putchar(']'); + } if (last_col) { - if (msg_col > 0) { + if (msg_col > 0 && cur_row < nrow) { msg_putchar('\n'); } + cur_row++; } else { - msg_putchar(' '); + while (msg_col % width) { + msg_putchar(' '); + } } } else { + // this row is out of items, thus at the end of the row if (msg_col > 0) { - msg_putchar('\n'); + if (cur_row < nrow) { + msg_putchar('\n'); + } + cur_row++; } } } - MSG_PUTS("See \":help feature-compile\"\n\n"); } void list_lua_version(void) @@ -2094,8 +2151,6 @@ void list_version(void) } #endif // ifdef HAVE_PATHDEF - version_msg(_("\n\nFeatures: ")); - list_features(); #ifdef SYS_VIMRC_FILE @@ -2121,26 +2176,6 @@ void list_version(void) version_msg("\nRun :checkhealth for more info"); } -/// Output a string for the version message. If it's going to wrap, output a -/// newline, unless the message is too long to fit on the screen anyway. -/// -/// @param s -static void version_msg(char *s) -{ - int len = (int)STRLEN(s); - - if (!got_int - && (len < (int)Columns) - && (msg_col + len >= (int)Columns) - && (*s != '\n')) { - msg_putchar('\n'); - } - - if (!got_int) { - MSG_PUTS(s); - } -} - /// Show the intro message when not editing a file. void maybe_intro_message(void) diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c index dcc64db8a0..b4a0f57e99 100644 --- a/src/nvim/viml/parser/expressions.c +++ b/src/nvim/viml/parser/expressions.c @@ -2330,7 +2330,7 @@ viml_pexpr_parse_invalid_comma: break; } else if (eastnode_type == kExprNodeSubscript) { is_subscript = true; - can_be_ternary = false; + // can_be_ternary = false; assert(!is_ternary); break; } else if (eastnode_type == kExprNodeColon) { diff --git a/src/nvim/window.c b/src/nvim/window.c index e6b19cf88d..22a8969b88 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -90,36 +90,47 @@ do_window ( else Prenum1 = Prenum; -# define CHECK_CMDWIN if (cmdwin_type != 0) { EMSG(_(e_cmdwin)); break; } +# define CHECK_CMDWIN \ + do { \ + if (cmdwin_type != 0) { \ + EMSG(_(e_cmdwin)); \ + return; \ + } \ + } while (0) switch (nchar) { /* split current window in two parts, horizontally */ case 'S': case Ctrl_S: case 's': - CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */ - /* When splitting the quickfix window open a new buffer in it, - * don't replicate the quickfix buffer. */ - if (bt_quickfix(curbuf)) + CHECK_CMDWIN; + reset_VIsual_and_resel(); // stop Visual mode + // When splitting the quickfix window open a new buffer in it, + // don't replicate the quickfix buffer. + if (bt_quickfix(curbuf)) { goto newwindow; + } (void)win_split((int)Prenum, 0); break; /* split current window in two parts, vertically */ case Ctrl_V: case 'v': - CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */ - /* When splitting the quickfix window open a new buffer in it, - * don't replicate the quickfix buffer. */ - if (bt_quickfix(curbuf)) + CHECK_CMDWIN; + reset_VIsual_and_resel(); // stop Visual mode + // When splitting the quickfix window open a new buffer in it, + // don't replicate the quickfix buffer. + if (bt_quickfix(curbuf)) { goto newwindow; + } (void)win_split((int)Prenum, WSP_VERT); break; /* split current window and edit alternate file */ case Ctrl_HAT: case '^': - CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */ + CHECK_CMDWIN; + reset_VIsual_and_resel(); // stop Visual mode cmd_with_count("split #", (char_u *)cbuf, sizeof(cbuf), Prenum); do_cmdline_cmd(cbuf); break; @@ -127,7 +138,8 @@ do_window ( /* open new window */ case Ctrl_N: case 'n': - CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */ + CHECK_CMDWIN; + reset_VIsual_and_resel(); // stop Visual mode newwindow: if (Prenum) /* window height */ @@ -160,7 +172,8 @@ newwindow: /* close preview window */ case Ctrl_Z: case 'z': - CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */ + CHECK_CMDWIN; + reset_VIsual_and_resel(); // stop Visual mode do_cmdline_cmd("pclose"); break; @@ -183,7 +196,8 @@ newwindow: /* close all but current window */ case Ctrl_O: case 'o': - CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */ + CHECK_CMDWIN; + reset_VIsual_and_resel(); // stop Visual mode cmd_with_count("only", (char_u *)cbuf, sizeof(cbuf), Prenum); do_cmdline_cmd(cbuf); break; @@ -193,11 +207,11 @@ newwindow: case 'w': /* cursor to previous window with wrap around */ case 'W': - CHECK_CMDWIN - if (ONE_WINDOW && Prenum != 1) /* just one window */ + CHECK_CMDWIN; + if (ONE_WINDOW && Prenum != 1) { // just one window beep_flush(); - else { - if (Prenum) { /* go to specified window */ + } else { + if (Prenum) { // go to specified window for (wp = firstwin; --Prenum > 0; ) { if (wp->w_next == NULL) break; @@ -233,14 +247,16 @@ newwindow: case 'j': case K_DOWN: case Ctrl_J: - CHECK_CMDWIN win_goto_ver(FALSE, Prenum1); + CHECK_CMDWIN; + win_goto_ver(false, Prenum1); break; /* cursor to window above */ case 'k': case K_UP: case Ctrl_K: - CHECK_CMDWIN win_goto_ver(TRUE, Prenum1); + CHECK_CMDWIN; + win_goto_ver(true, Prenum1); break; /* cursor to left window */ @@ -248,14 +264,16 @@ newwindow: case K_LEFT: case Ctrl_H: case K_BS: - CHECK_CMDWIN win_goto_hor(TRUE, Prenum1); + CHECK_CMDWIN; + win_goto_hor(true, Prenum1); break; /* cursor to right window */ case 'l': case K_RIGHT: case Ctrl_L: - CHECK_CMDWIN win_goto_hor(FALSE, Prenum1); + CHECK_CMDWIN; + win_goto_hor(false, Prenum1); break; /* move window to new tab page */ @@ -309,20 +327,23 @@ newwindow: /* exchange current and next window */ case 'x': case Ctrl_X: - CHECK_CMDWIN win_exchange(Prenum); + CHECK_CMDWIN; + win_exchange(Prenum); break; /* rotate windows downwards */ case Ctrl_R: case 'r': - CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */ - win_rotate(FALSE, (int)Prenum1); /* downwards */ + CHECK_CMDWIN; + reset_VIsual_and_resel(); // stop Visual mode + win_rotate(false, (int)Prenum1); // downwards break; /* rotate windows upwards */ case 'R': - CHECK_CMDWIN reset_VIsual_and_resel(); /* stop Visual mode */ - win_rotate(TRUE, (int)Prenum1); /* upwards */ + CHECK_CMDWIN; + reset_VIsual_and_resel(); // stop Visual mode + win_rotate(true, (int)Prenum1); // upwards break; /* move window to the very top/bottom/left/right */ @@ -330,9 +351,10 @@ newwindow: case 'J': case 'H': case 'L': - CHECK_CMDWIN win_totop((int)Prenum, - ((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0) - | ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT)); + CHECK_CMDWIN; + win_totop((int)Prenum, + ((nchar == 'H' || nchar == 'L') ? WSP_VERT : 0) + | ((nchar == 'H' || nchar == 'K') ? WSP_TOP : WSP_BOT)); break; /* make all windows the same height */ @@ -353,7 +375,7 @@ newwindow: /* set current window height */ case Ctrl__: case '_': - win_setheight(Prenum ? (int)Prenum : 9999); + win_setheight(Prenum ? (int)Prenum : Rows-1); break; /* increase current window width */ @@ -368,20 +390,21 @@ newwindow: /* set current window width */ case '|': - win_setwidth(Prenum != 0 ? (int)Prenum : 9999); + win_setwidth(Prenum != 0 ? (int)Prenum : Columns); break; /* jump to tag and split window if tag exists (in preview window) */ case '}': - CHECK_CMDWIN - if (Prenum) + CHECK_CMDWIN; + if (Prenum) { g_do_tagpreview = Prenum; - else + } else { g_do_tagpreview = p_pvh; + } FALLTHROUGH; case ']': case Ctrl_RSB: - CHECK_CMDWIN + CHECK_CMDWIN; // Keep visual mode, can select words to use as a tag. if (Prenum) postponed_split = Prenum; @@ -402,7 +425,7 @@ newwindow: case 'F': case Ctrl_F: wingotofile: - CHECK_CMDWIN + CHECK_CMDWIN; ptr = grab_file_name(Prenum1, &lnum); if (ptr != NULL) { @@ -434,26 +457,20 @@ wingotofile: FALLTHROUGH; case 'd': // Go to definition, using 'define' case Ctrl_D: - CHECK_CMDWIN - if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) + CHECK_CMDWIN; + if ((len = find_ident_under_cursor(&ptr, FIND_IDENT)) == 0) { break; - find_pattern_in_path(ptr, 0, len, TRUE, - Prenum == 0 ? TRUE : FALSE, + } + find_pattern_in_path(ptr, 0, len, true, Prenum == 0, type, Prenum1, ACTION_SPLIT, 1, MAXLNUM); curwin->w_set_curswant = TRUE; break; + // Quickfix window only: view the result under the cursor in a new split. case K_KENTER: case CAR: - /* - * In a quickfix window a <CR> jumps to the error under the - * cursor in a new window. - */ if (bt_quickfix(curbuf)) { - sprintf(cbuf, "split +%" PRId64 "%s", - (int64_t)curwin->w_cursor.lnum, - (curwin->w_llist_ref == NULL) ? "cc" : "ll"); - do_cmdline_cmd(cbuf); + qf_view_result(true); } break; @@ -461,7 +478,7 @@ wingotofile: /* CTRL-W g extended commands */ case 'g': case Ctrl_G: - CHECK_CMDWIN + CHECK_CMDWIN; no_mapping++; if (xchar == NUL) { xchar = plain_vgetc(); @@ -567,15 +584,43 @@ win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err) wp->w_status_height = 0; wp->w_vsep_width = 0; - // TODO(bfredl): use set_option_to() after merging #9110 ? - wp->w_p_nu = false; - wp->w_allbuf_opt.wo_nu = false; win_config_float(wp, fconfig); wp->w_pos_changed = true; redraw_win_later(wp, VALID); return wp; } +void win_set_minimal_style(win_T *wp) +{ + wp->w_p_nu = false; + wp->w_p_rnu = false; + wp->w_p_cul = false; + wp->w_p_cuc = false; + wp->w_p_spell = false; + wp->w_p_list = false; + + // Hide EOB region: use " " fillchar and cleared highlighting + if (wp->w_p_fcs_chars.eob != ' ') { + char_u *old = wp->w_p_fcs; + wp->w_p_fcs = ((*old == NUL) + ? (char_u *)xstrdup("eob: ") + : concat_str(old, (char_u *)",eob: ")); + xfree(old); + } + if (wp->w_hl_ids[HLF_EOB] != -1) { + char_u *old = wp->w_p_winhl; + wp->w_p_winhl = ((*old == NUL) + ? (char_u *)xstrdup("EndOfBuffer:") + : concat_str(old, (char_u *)",EndOfBuffer:")); + xfree(old); + } + + if (wp->w_p_scl[0] != 'a') { + xfree(wp->w_p_scl); + wp->w_p_scl = (char_u *)xstrdup("auto"); + } +} + void win_config_float(win_T *wp, FloatConfig fconfig) { wp->w_width = MAX(fconfig.width, 1); @@ -649,6 +694,7 @@ static void ui_ext_win_position(win_T *wp) bool on_top = (curwin == wp) || !curwin->w_floating; ui_comp_put_grid(&wp->w_grid, comp_row, comp_col, wp->w_height, wp->w_width, valid, on_top); + ui_check_cursor_grid(wp->w_grid.handle); if (!valid) { wp->w_grid.valid = false; redraw_win_later(wp, NOT_VALID); @@ -804,6 +850,20 @@ bool parse_float_config(Dictionary config, FloatConfig *fconfig, bool reconf, "'focusable' key must be Boolean"); return false; } + } else if (!strcmp(key, "style")) { + if (val.type != kObjectTypeString) { + api_set_error(err, kErrorTypeValidation, + "'style' key must be String"); + return false; + } + if (val.data.string.data[0] == NUL) { + fconfig->style = kWinStyleUnused; + } else if (striequal(val.data.string.data, "minimal")) { + fconfig->style = kWinStyleMinimal; + } else { + api_set_error(err, kErrorTypeValidation, + "Invalid value of 'style' key"); + } } else { api_set_error(err, kErrorTypeValidation, "Invalid key '%s'", key); @@ -964,7 +1024,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) for (frp = oldwin->w_frame->fr_parent; frp != NULL; frp = frp->fr_parent) { if (frp->fr_layout == FR_ROW) { - for (frp2 = frp->fr_child; frp2 != NULL; frp2 = frp2->fr_next) { + FOR_ALL_FRAMES(frp2, frp->fr_child) { if (frp2 != prevfrp) { minwidth += frame_minwidth(frp2, NOWIN); } @@ -1042,7 +1102,7 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) for (frp = oldwin->w_frame->fr_parent; frp != NULL; frp = frp->fr_parent) { if (frp->fr_layout == FR_COL) { - for (frp2 = frp->fr_child; frp2 != NULL; frp2 = frp2->fr_next) { + FOR_ALL_FRAMES(frp2, frp->fr_child) { if (frp2 != prevfrp) { minheight += frame_minheight(frp2, NOWIN); } @@ -1187,11 +1247,13 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) curfrp->fr_child = frp; curfrp->fr_win = NULL; curfrp = frp; - if (frp->fr_win != NULL) + if (frp->fr_win != NULL) { oldwin->w_frame = frp; - else - for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) + } else { + FOR_ALL_FRAMES(frp, frp->fr_child) { frp->fr_parent = curfrp; + } + } } if (new_wp == NULL) @@ -1637,11 +1699,9 @@ static void win_exchange(long Prenum) redraw_win_later(wp, NOT_VALID); } -/* - * rotate windows: if upwards TRUE the second window becomes the first one - * if upwards FALSE the first window becomes the second one - */ -static void win_rotate(int upwards, int count) +// rotate windows: if upwards true the second window becomes the first one +// if upwards false the first window becomes the second one +static void win_rotate(bool upwards, int count) { win_T *wp1; win_T *wp2; @@ -1653,19 +1713,19 @@ static void win_rotate(int upwards, int count) return; } - if (firstwin == curwin && lastwin_nofloating() == curwin) { + if (count <= 0 || (firstwin == curwin && lastwin_nofloating() == curwin)) { // nothing to do beep_flush(); return; } - /* Check if all frames in this row/col have one window. */ - for (frp = curwin->w_frame->fr_parent->fr_child; frp != NULL; - frp = frp->fr_next) + // Check if all frames in this row/col have one window. + FOR_ALL_FRAMES(frp, curwin->w_frame->fr_parent->fr_child) { if (frp->fr_win == NULL) { EMSG(_("E443: Cannot rotate when another window is split")); return; } + } while (count--) { if (upwards) { /* first window becomes last window */ @@ -1832,8 +1892,8 @@ void win_equal( if (dir == 0) dir = *p_ead; win_equal_rec(next_curwin == NULL ? curwin : next_curwin, current, - topframe, dir, 0, tabline_height(), - (int)Columns, topframe->fr_height); + topframe, dir, 0, tabline_height(), + Columns, topframe->fr_height); } /* @@ -1903,10 +1963,10 @@ static void win_equal_rec( room = 0; } else { next_curwin_size = -1; - for (fr = topfr->fr_child; fr != NULL; fr = fr->fr_next) { - /* If 'winfixwidth' set keep the window width if - * possible. - * Watch out for this window being the next_curwin. */ + FOR_ALL_FRAMES(fr, topfr->fr_child) { + // If 'winfixwidth' set keep the window width if + // possible. + // Watch out for this window being the next_curwin. if (!frame_fixed_width(fr)) { continue; } @@ -1949,7 +2009,7 @@ static void win_equal_rec( --totwincount; /* don't count curwin */ } - for (fr = topfr->fr_child; fr != NULL; fr = fr->fr_next) { + FOR_ALL_FRAMES(fr, topfr->fr_child) { wincount = 1; if (fr->fr_next == NULL) /* last frame gets all that remains (avoid roundoff error) */ @@ -2024,10 +2084,10 @@ static void win_equal_rec( room = 0; } else { next_curwin_size = -1; - for (fr = topfr->fr_child; fr != NULL; fr = fr->fr_next) { - /* If 'winfixheight' set keep the window height if - * possible. - * Watch out for this window being the next_curwin. */ + FOR_ALL_FRAMES(fr, topfr->fr_child) { + // If 'winfixheight' set keep the window height if + // possible. + // Watch out for this window being the next_curwin. if (!frame_fixed_height(fr)) { continue; } @@ -2070,7 +2130,7 @@ static void win_equal_rec( --totwincount; /* don't count curwin */ } - for (fr = topfr->fr_child; fr != NULL; fr = fr->fr_next) { + FOR_ALL_FRAMES(fr, topfr->fr_child) { wincount = 1; if (fr->fr_next == NULL) /* last frame gets all that remains (avoid roundoff error) */ @@ -2757,8 +2817,9 @@ winframe_remove ( * and remove it. */ frp2->fr_parent->fr_layout = frp2->fr_layout; frp2->fr_parent->fr_child = frp2->fr_child; - for (frp = frp2->fr_child; frp != NULL; frp = frp->fr_next) + FOR_ALL_FRAMES(frp, frp2->fr_child) { frp->fr_parent = frp2->fr_parent; + } frp2->fr_parent->fr_win = frp2->fr_win; if (frp2->fr_win != NULL) frp2->fr_win->w_frame = frp2->fr_parent; @@ -2879,13 +2940,14 @@ static win_T *frame2win(frame_T *frp) /// /// @param frp frame /// @param wp window -static bool frame_has_win(frame_T *frp, win_T *wp) +static bool frame_has_win(const frame_T *frp, const win_T *wp) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1) { if (frp->fr_layout == FR_LEAF) { return frp->fr_win == wp; } - for (frame_T *p = frp->fr_child; p != NULL; p = p->fr_next) { + const frame_T *p; + FOR_ALL_FRAMES(p, frp->fr_child) { if (frame_has_win(p, wp)) { return true; } @@ -2916,8 +2978,8 @@ frame_new_height ( height - topfrp->fr_win->w_status_height); } else if (topfrp->fr_layout == FR_ROW) { do { - /* All frames in this row get the same new height. */ - for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) { + // All frames in this row get the same new height. + FOR_ALL_FRAMES(frp, topfrp->fr_child) { frame_new_height(frp, height, topfirst, wfh); if (frp->fr_height > height) { /* Could not fit the windows, make the whole row higher. */ @@ -2998,7 +3060,7 @@ static bool frame_fixed_height(frame_T *frp) if (frp->fr_layout == FR_ROW) { // The frame is fixed height if one of the frames in the row is fixed // height. - for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) { + FOR_ALL_FRAMES(frp, frp->fr_child) { if (frame_fixed_height(frp)) { return true; } @@ -3008,7 +3070,7 @@ static bool frame_fixed_height(frame_T *frp) // frp->fr_layout == FR_COL: The frame is fixed height if all of the // frames in the row are fixed height. - for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) { + FOR_ALL_FRAMES(frp, frp->fr_child) { if (!frame_fixed_height(frp)) { return false; } @@ -3032,7 +3094,7 @@ static bool frame_fixed_width(frame_T *frp) if (frp->fr_layout == FR_COL) { // The frame is fixed width if one of the frames in the row is fixed // width. - for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) { + FOR_ALL_FRAMES(frp, frp->fr_child) { if (frame_fixed_width(frp)) { return true; } @@ -3042,7 +3104,7 @@ static bool frame_fixed_width(frame_T *frp) // frp->fr_layout == FR_ROW: The frame is fixed width if all of the // frames in the row are fixed width. - for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) { + FOR_ALL_FRAMES(frp, frp->fr_child) { if (!frame_fixed_width(frp)) { return false; } @@ -3066,13 +3128,15 @@ static void frame_add_statusline(frame_T *frp) wp->w_status_height = STATUS_HEIGHT; } } else if (frp->fr_layout == FR_ROW) { - /* Handle all the frames in the row. */ - for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) + // Handle all the frames in the row. + FOR_ALL_FRAMES(frp, frp->fr_child) { frame_add_statusline(frp); - } else { /* frp->fr_layout == FR_COL */ - /* Only need to handle the last frame in the column. */ - for (frp = frp->fr_child; frp->fr_next != NULL; frp = frp->fr_next) - ; + } + } else { + assert(frp->fr_layout == FR_COL); + // Only need to handle the last frame in the column. + for (frp = frp->fr_child; frp->fr_next != NULL; frp = frp->fr_next) { + } frame_add_statusline(frp); } } @@ -3107,8 +3171,8 @@ frame_new_width ( win_new_width(wp, width - wp->w_vsep_width); } else if (topfrp->fr_layout == FR_COL) { do { - /* All frames in this column get the same new width. */ - for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) { + // All frames in this column get the same new width. + FOR_ALL_FRAMES(frp, topfrp->fr_child) { frame_new_width(frp, width, leftfirst, wfw); if (frp->fr_width > width) { /* Could not fit the windows, make whole column wider. */ @@ -3177,7 +3241,8 @@ frame_new_width ( * Add the vertical separator to windows at the right side of "frp". * Note: Does not check if there is room! */ -static void frame_add_vsep(frame_T *frp) +static void frame_add_vsep(const frame_T *frp) + FUNC_ATTR_NONNULL_ARG(1) { win_T *wp; @@ -3189,11 +3254,13 @@ static void frame_add_vsep(frame_T *frp) wp->w_vsep_width = 1; } } else if (frp->fr_layout == FR_COL) { - /* Handle all the frames in the column. */ - for (frp = frp->fr_child; frp != NULL; frp = frp->fr_next) + // Handle all the frames in the column. + FOR_ALL_FRAMES(frp, frp->fr_child) { frame_add_vsep(frp); - } else { /* frp->fr_layout == FR_ROW */ - /* Only need to handle the last frame in the row. */ + } + } else { + assert(frp->fr_layout == FR_ROW); + // Only need to handle the last frame in the row. frp = frp->fr_child; while (frp->fr_next != NULL) frp = frp->fr_next; @@ -3243,7 +3310,7 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin) } else if (topfrp->fr_layout == FR_ROW) { /* get the minimal height from each frame in this row */ m = 0; - for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) { + FOR_ALL_FRAMES(frp, topfrp->fr_child) { n = frame_minheight(frp, next_curwin); if (n > m) m = n; @@ -3251,8 +3318,9 @@ static int frame_minheight(frame_T *topfrp, win_T *next_curwin) } else { /* Add up the minimal heights for all frames in this column. */ m = 0; - for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) + FOR_ALL_FRAMES(frp, topfrp->fr_child) { m += frame_minheight(frp, next_curwin); + } } return m; @@ -3286,7 +3354,7 @@ frame_minwidth ( } else if (topfrp->fr_layout == FR_COL) { /* get the minimal width from each frame in this column */ m = 0; - for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) { + FOR_ALL_FRAMES(frp, topfrp->fr_child) { n = frame_minwidth(frp, next_curwin); if (n > m) m = n; @@ -3294,8 +3362,9 @@ frame_minwidth ( } else { /* Add up the minimal widths for all frames in this row. */ m = 0; - for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) + FOR_ALL_FRAMES(frp, topfrp->fr_child) { m += frame_minwidth(frp, next_curwin); + } } return m; @@ -4673,9 +4742,10 @@ void shell_new_columns(void) /* First try setting the widths of windows with 'winfixwidth'. If that * doesn't result in the right width, forget about that option. */ - frame_new_width(topframe, (int)Columns, FALSE, TRUE); - if (!frame_check_width(topframe, Columns)) - frame_new_width(topframe, (int)Columns, FALSE, FALSE); + frame_new_width(topframe, Columns, false, true); + if (!frame_check_width(topframe, Columns)) { + frame_new_width(topframe, Columns, false, false); + } (void)win_comp_pos(); /* recompute w_winrow and w_wincol */ } @@ -4772,11 +4842,12 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col) } else { startrow = *row; startcol = *col; - for (frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) { - if (topfrp->fr_layout == FR_ROW) - *row = startrow; /* all frames are at the same row */ - else - *col = startcol; /* all frames are at the same col */ + FOR_ALL_FRAMES(frp, topfrp->fr_child) { + if (topfrp->fr_layout == FR_ROW) { + *row = startrow; // all frames are at the same row + } else { + *col = startcol; // all frames are at the same col + } frame_comp_pos(frp, row, col); } } @@ -4808,13 +4879,9 @@ void win_setheight_win(int height, win_T *win) } if (win->w_floating) { - if (win->w_float_config.external) { - win->w_float_config.height = height; - win_config_float(win, win->w_float_config); - } else { - beep_flush(); - return; - } + win->w_float_config.height = height; + win_config_float(win, win->w_float_config); + redraw_win_later(win, NOT_VALID); } else { frame_setheight(win->w_frame, height + win->w_status_height); @@ -4824,14 +4891,14 @@ void win_setheight_win(int height, win_T *win) // If there is extra space created between the last window and the command // line, clear it. if (full_screen && msg_scrolled == 0 && row < cmdline_row) { - grid_fill(&default_grid, row, cmdline_row, 0, (int)Columns, ' ', ' ', 0); + grid_fill(&default_grid, row, cmdline_row, 0, Columns, ' ', ' ', 0); } cmdline_row = row; msg_row = row; msg_col = 0; + redraw_all_later(NOT_VALID); } - redraw_all_later(NOT_VALID); } @@ -4889,15 +4956,16 @@ static void frame_setheight(frame_T *curfrp, int height) for (run = 1; run <= 2; ++run) { room = 0; room_reserved = 0; - for (frp = curfrp->fr_parent->fr_child; frp != NULL; - frp = frp->fr_next) { + FOR_ALL_FRAMES(frp, curfrp->fr_parent->fr_child) { if (frp != curfrp && frp->fr_win != NULL - && frp->fr_win->w_p_wfh) + && frp->fr_win->w_p_wfh) { room_reserved += frp->fr_height; + } room += frp->fr_height; - if (frp != curfrp) + if (frp != curfrp) { room -= frame_minheight(frp, NULL); + } } if (curfrp->fr_width != Columns) { room_cmdline = 0; @@ -5014,21 +5082,17 @@ void win_setwidth_win(int width, win_T *wp) width = 1; } if (wp->w_floating) { - if (wp->w_float_config.external) { - wp->w_float_config.width = width; - win_config_float(wp, wp->w_float_config); - } else { - beep_flush(); - return; - } + wp->w_float_config.width = width; + win_config_float(wp, wp->w_float_config); + redraw_win_later(wp, NOT_VALID); } else { frame_setwidth(wp->w_frame, width + wp->w_vsep_width); // recompute the window positions (void)win_comp_pos(); + redraw_all_later(NOT_VALID); } - redraw_all_later(NOT_VALID); } /* @@ -5074,15 +5138,16 @@ static void frame_setwidth(frame_T *curfrp, int width) for (run = 1; run <= 2; ++run) { room = 0; room_reserved = 0; - for (frp = curfrp->fr_parent->fr_child; frp != NULL; - frp = frp->fr_next) { + FOR_ALL_FRAMES(frp, curfrp->fr_parent->fr_child) { if (frp != curfrp && frp->fr_win != NULL - && frp->fr_win->w_p_wfw) + && frp->fr_win->w_p_wfw) { room_reserved += frp->fr_width; + } room += frp->fr_width; - if (frp != curfrp) + if (frp != curfrp) { room -= frame_minwidth(frp, NULL); + } } if (width <= room) @@ -5246,10 +5311,11 @@ void win_drag_status_line(win_T *dragwin, int offset) room -= p_ch; if (room < 0) room = 0; - /* sum up the room of frames below of the current one */ - for (fr = curfr->fr_next; fr != NULL; fr = fr->fr_next) + // sum up the room of frames below of the current one + FOR_ALL_FRAMES(fr, curfr->fr_next) { room += fr->fr_height - frame_minheight(fr, NULL); - fr = curfr; /* put fr at window that grows */ + } + fr = curfr; // put fr at window that grows } if (room < offset) /* Not enough room */ @@ -5287,7 +5353,7 @@ void win_drag_status_line(win_T *dragwin, int offset) fr = fr->fr_next; } row = win_comp_pos(); - grid_fill(&default_grid, row, cmdline_row, 0, (int)Columns, ' ', ' ', 0); + grid_fill(&default_grid, row, cmdline_row, 0, Columns, ' ', ' ', 0); cmdline_row = row; p_ch = Rows - cmdline_row; if (p_ch < 1) @@ -5349,9 +5415,10 @@ void win_drag_vsep_line(win_T *dragwin, int offset) left = FALSE; /* sum up the room of frames right of the current one */ room = 0; - for (fr = curfr->fr_next; fr != NULL; fr = fr->fr_next) + FOR_ALL_FRAMES(fr, curfr->fr_next) { room += fr->fr_width - frame_minwidth(fr, NULL); - fr = curfr; /* put fr at window that grows */ + } + fr = curfr; // put fr at window that grows } assert(fr); @@ -5655,8 +5722,7 @@ void command_height(void) // clear the lines added to cmdline if (full_screen) { - grid_fill(&default_grid, cmdline_row, (int)Rows, 0, (int)Columns, ' ', - ' ', 0); + grid_fill(&default_grid, cmdline_row, Rows, 0, Columns, ' ', ' ', 0); } msg_row = cmdline_row; redraw_cmdline = TRUE; @@ -5883,9 +5949,10 @@ static void last_status_rec(frame_T *fr, int statusline) redraw_all_later(SOME_VALID); } } else if (fr->fr_layout == FR_ROW) { - /* vertically split windows, set status line for each one */ - for (fp = fr->fr_child; fp != NULL; fp = fp->fr_next) + // vertically split windows, set status line for each one + FOR_ALL_FRAMES(fp, fr->fr_child) { last_status_rec(fp, statusline); + } } else { /* horizontally split window, set status line for last one */ for (fp = fr->fr_child; fp->fr_next != NULL; fp = fp->fr_next) @@ -6501,14 +6568,15 @@ matchitem_T *get_match(win_T *wp, int id) /// /// @param topfrp top frame pointer /// @param height expected height -static bool frame_check_height(frame_T *topfrp, int height) +static bool frame_check_height(const frame_T *topfrp, int height) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { if (topfrp->fr_height != height) { return false; } if (topfrp->fr_layout == FR_ROW) { - for (frame_T *frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) { + const frame_T *frp; + FOR_ALL_FRAMES(frp, topfrp->fr_child) { if (frp->fr_height != height) { return false; } @@ -6521,14 +6589,15 @@ static bool frame_check_height(frame_T *topfrp, int height) /// /// @param topfrp top frame pointer /// @param width expected width -static bool frame_check_width(frame_T *topfrp, int width) +static bool frame_check_width(const frame_T *topfrp, int width) FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { if (topfrp->fr_width != width) { return false; } if (topfrp->fr_layout == FR_COL) { - for (frame_T *frp = topfrp->fr_child; frp != NULL; frp = frp->fr_next) { + const frame_T *frp; + FOR_ALL_FRAMES(frp, topfrp->fr_child) { if (frp->fr_width != width) { return false; } |
