diff options
Diffstat (limited to 'src')
227 files changed, 24348 insertions, 7581 deletions
diff --git a/src/.asan-blacklist b/src/.asan-blacklist index 928d81bd5a..9d7f721a88 100644 --- a/src/.asan-blacklist +++ b/src/.asan-blacklist @@ -1,3 +1,7 @@ # multiqueue.h pointer arithmetic is not accepted by asan fun:multiqueue_node_data fun:tv_dict_watcher_node_data + +# Allocation in loop_schedule_deferred() is freed by loop_deferred_event(), but +# this sometimes does not happen during teardown. +func:loop_schedule_deferred diff --git a/src/clint.py b/src/clint.py index e63175a69b..79ab91ebe1 100755 --- a/src/clint.py +++ b/src/clint.py @@ -201,6 +201,7 @@ _ERROR_CATEGORIES = [ 'runtime/printf', 'runtime/printf_format', 'runtime/threadsafe_fn', + 'runtime/deprecated', 'syntax/parenthesis', 'whitespace/alignment', 'whitespace/blank_line', @@ -2123,8 +2124,10 @@ def CheckExpressionAlignment(filename, clean_lines, linenum, error, startpos=0): + (level_starts[depth][2] == '{')): if depth not in ignore_error_levels: error(filename, linenum, 'whitespace/alignment', 2, - 'Inner expression should be aligned ' - 'as opening brace + 1 (+ 2 in case of {)') + ('Inner expression should be aligned ' + 'as opening brace + 1 (+ 2 in case of {{). ' + 'Relevant opening is on line {0!r}').format( + level_starts[depth][3])) prev_line_start = pos elif brace == 'e': pass @@ -2141,7 +2144,8 @@ def CheckExpressionAlignment(filename, clean_lines, linenum, error, startpos=0): ignore_error_levels.add(depth) line_ended_with_opening = ( pos == len(line) - 2 * (line.endswith(' \\')) - 1) - level_starts[depth] = (pos, line_ended_with_opening, brace) + level_starts[depth] = (pos, line_ended_with_opening, brace, + linenum) if line_ended_with_opening: depth_line_starts[depth] = (prev_line_start, brace) else: @@ -3200,6 +3204,14 @@ def CheckLanguage(filename, clean_lines, linenum, file_extension, if match: error(filename, linenum, 'runtime/printf', 4, 'Use xstrlcat or snprintf instead of %s' % match.group(1)) + 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) + if match: + error(filename, linenum, 'runtime/deprecated', 4, + 'Accessing list_T internals directly is prohibited') # Check for suspicious usage of "if" like # } if (a == b) { diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt index a166ee6c02..2d803792c8 100644 --- a/src/nvim/CMakeLists.txt +++ b/src/nvim/CMakeLists.txt @@ -13,6 +13,9 @@ endif() if(WIN32) # tell MinGW compiler to enable wmain set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -municode") +elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -framework CoreFoundation") + set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -framework CoreFoundation") endif() set(TOUCHES_DIR ${PROJECT_BINARY_DIR}/touches) @@ -86,6 +89,8 @@ foreach(subdir event eval lua + viml + viml/parser ) if(${subdir} MATCHES "tui" AND NOT FEAT_TUI) continue() @@ -170,7 +175,7 @@ endif() get_directory_property(gen_cdefs COMPILE_DEFINITIONS) foreach(gen_cdef ${gen_cdefs} DO_NOT_DEFINE_EMPTY_ATTRIBUTES) - if(NOT "${gen_cdef}" MATCHES "INCLUDE_GENERATED_DECLARATIONS") + if(NOT ${gen_cdef} MATCHES "INCLUDE_GENERATED_DECLARATIONS") list(APPEND gen_cflags "-D${gen_cdef}") endif() endforeach() @@ -182,6 +187,10 @@ get_directory_property(gen_includes INCLUDE_DIRECTORIES) foreach(gen_include ${gen_includes} ${LUA_PREFERRED_INCLUDE_DIRS}) list(APPEND gen_cflags "-I${gen_include}") endforeach() +if(CMAKE_SYSTEM_NAME STREQUAL "Darwin" AND CMAKE_OSX_SYSROOT) + list(APPEND gen_cflags "-isysroot") + list(APPEND gen_cflags "${CMAKE_OSX_SYSROOT}") +endif() string(TOUPPER "${CMAKE_BUILD_TYPE}" build_type) separate_arguments(C_FLAGS_ARRAY UNIX_COMMAND ${CMAKE_C_FLAGS}) separate_arguments(C_FLAGS_${build_type}_ARRAY UNIX_COMMAND ${CMAKE_C_FLAGS_${build_type}}) @@ -350,6 +359,10 @@ endforeach() # Our dependencies come first. +if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + list(APPEND NVIM_LINK_LIBRARIES pthread c++abi) +endif() + if (LibIntl_FOUND) list(APPEND NVIM_LINK_LIBRARIES ${LibIntl_LIBRARY}) endif() @@ -428,6 +441,7 @@ if(WIN32) COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/tidy.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/win32yank.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/winpty-agent.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ + COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/xxd.exe" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/D3Dcompiler_47.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/libEGL.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/ @@ -446,6 +460,8 @@ if(WIN32) COMMAND ${CMAKE_COMMAND} -E copy "${DEPS_PREFIX}/bin/platforms/qwindows.dll" ${PROJECT_BINARY_DIR}/windows_runtime_deps/platforms/ ) add_dependencies(nvim_runtime_deps external_blobs) +else() + add_custom_target(nvim_runtime_deps) # Stub target to avoid CMP0046. endif() add_library( diff --git a/src/nvim/README.md b/src/nvim/README.md index 0caf71e2c5..d668db0cdc 100644 --- a/src/nvim/README.md +++ b/src/nvim/README.md @@ -1,10 +1,22 @@ -Nvim core source -================ +Nvim core +========= Module-specific details are documented at the top of each module (`terminal.c`, -`screen.c`, ...). +`screen.c`, …). -See `:help development` for more guidelines. +See `:help dev` for guidelines. + +Filename conventions +-------------------- + +The source files use extensions to hint about their purpose. + +- `*.c`, `*.generated.c` - full C files, with all includes, etc. +- `*.c.h` - parametrized C files, contain all necessary includes, but require + defining macros before actually using. Example: `typval_encode.c.h` +- `*.h` - full headers, with all includes. Does *not* apply to `*.generated.h`. +- `*.h.generated.h` - exported functions’ declarations. +- `*.c.generated.h` - static functions’ declarations. Logs ---- @@ -20,17 +32,75 @@ UI events are logged at level 0 (`DEBUG_LOG_LEVEL`). rm -rf build/ make CMAKE_EXTRA_FLAGS="-DMIN_LOG_LEVEL=0" -Filename conventions --------------------- +Build with ASAN +--------------- -The source files use extensions to hint about their purpose. +Building Nvim with Clang sanitizers (Address Sanitizer: ASan, Undefined +Behavior Sanitizer: UBSan, Memory Sanitizer: MSan, Thread Sanitizer: TSan) is +a good way to catch undefined behavior, leaks and other errors as soon as they +happen. It's significantly faster than Valgrind. -- `*.c`, `*.generated.c` - full C files, with all includes, etc. -- `*.c.h` - parametrized C files, contain all necessary includes, but require - defining macros before actually using. Example: `typval_encode.c.h` -- `*.h` - full headers, with all includes. Does *not* apply to `*.generated.h`. -- `*.h.generated.h` - exported functions’ declarations. -- `*.c.generated.h` - static functions’ declarations. +Requires clang 3.4 or later: + + clang --version + +Build Nvim with sanitizer instrumentation: + + CC=clang make CMAKE_EXTRA_FLAGS="-DCLANG_ASAN_UBSAN=ON" + +Create a directory to store logs: + + mkdir -p "$HOME/logs" + +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 MSAN_SYMBOLIZER_PATH=/usr/lib/llvm-5.0/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`. + +TUI debugging +------------- + +### TUI troubleshoot + +Nvim logs its internal terminfo state at 'verbose' level 3. This makes it +possible to see exactly what terminfo values Nvim is using on any system. + + nvim -V3log + +### TUI trace + +The ancient `script` command is still the "state of the art" for tracing +terminal behavior. The libvterm `vterm-dump` utility formats the result for +human-readability. + +Record a Nvim terminal session and format it with `vterm-dump`: + + script foo + ./build/bin/nvim -u NONE + # Exit the script session with CTRL-d + + # Use `vterm-dump` utility to format the result. + ./.deps/usr/bin/vterm-dump foo > bar + +Then you can compare `bar` with another session, to debug TUI behavior. + +### TUI redraw + +Set the 'writedelay' option to see where and when the UI is painted. + + :set writedelay=1 + +### Terminal reference + +- `man terminfo` +- http://bazaar.launchpad.net/~libvterm/libvterm/trunk/view/head:/doc/seqs.txt +- http://invisible-island.net/xterm/ctlseqs/ctlseqs.html Nvim lifecycle -------------- @@ -39,7 +109,7 @@ Following describes how Nvim processes input. Consider a typical Vim-like editing session: -01. Vim dispays the welcome screen +01. Vim displays the welcome screen 02. User types: `:` 03. Vim enters command-line mode 04. User types: `edit README.txt<CR>` diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c index 4b6a88e5fa..af723639c5 100644 --- a/src/nvim/api/buffer.c +++ b/src/nvim/api/buffer.c @@ -399,7 +399,11 @@ void nvim_buf_set_lines(uint64_t channel_id, // Only adjust marks if we managed to switch to a window that holds // the buffer, otherwise line numbers will be invalid. if (save_curbuf.br_buf == NULL) { - mark_adjust((linenr_T)start, (linenr_T)(end - 1), MAXLNUM, extra, false); + mark_adjust((linenr_T)start, + (linenr_T)(end - 1), + MAXLNUM, + (long)extra, + false); } changed_lines((linenr_T)start, 0, (linenr_T)end, (long)extra); @@ -763,8 +767,8 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err) /// or -1 for ungrouped highlight /// @param hl_group Name of the highlight group to use /// @param line Line to highlight (zero-indexed) -/// @param col_start Start of range of columns to highlight -/// @param col_end End of range of columns to highlight, +/// @param col_start Start of (byte-indexed) column range to highlight +/// @param col_end End of (byte-indexed) column range to highlight, /// or -1 to highlight to end of line /// @param[out] err Error details, if any /// @return The src_id that was used @@ -806,7 +810,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, /// Clears highlights from a given source group and a range of lines /// -/// To clear a source group in the entire buffer, pass in 1 and -1 to +/// To clear a source group in the entire buffer, pass in 0 and -1 to /// line_start and line_end respectively. /// /// @param buffer Buffer handle diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h index 2144c80d6a..390b7e8363 100644 --- a/src/nvim/api/private/defs.h +++ b/src/nvim/api/private/defs.h @@ -47,7 +47,7 @@ typedef enum { /// Internal call from lua code #define LUA_INTERNAL_CALL (VIML_INTERNAL_CALL + 1) -static inline bool is_internal_call(uint64_t channel_id) +static inline bool is_internal_call(const uint64_t channel_id) REAL_FATTR_ALWAYS_INLINE REAL_FATTR_CONST; /// Check whether call is internal diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c index 629873998e..12a4279dd7 100644 --- a/src/nvim/api/private/helpers.c +++ b/src/nvim/api/private/helpers.c @@ -26,6 +26,7 @@ #include "nvim/version.h" #include "nvim/lib/kvec.h" #include "nvim/getchar.h" +#include "nvim/ui.h" /// Helper structure for vim_to_object typedef struct { @@ -46,13 +47,15 @@ typedef struct { /// @param[out] tstate Location where try state should be saved. void try_enter(TryState *const tstate) { + // TODO(ZyX-I): Check whether try_enter()/try_leave() may use + // enter_cleanup()/leave_cleanup(). Or + // save_dbg_stuff()/restore_dbg_stuff(). *tstate = (TryState) { .current_exception = current_exception, .msg_list = (const struct msglist *const *)msg_list, .private_msg_list = NULL, .trylevel = trylevel, .got_int = got_int, - .did_throw = did_throw, .need_rethrow = need_rethrow, .did_emsg = did_emsg, }; @@ -60,7 +63,6 @@ void try_enter(TryState *const tstate) current_exception = NULL; trylevel = 1; got_int = false; - did_throw = false; need_rethrow = false; did_emsg = false; } @@ -81,7 +83,6 @@ bool try_leave(const TryState *const tstate, Error *const err) assert(trylevel == 0); assert(!need_rethrow); assert(!got_int); - assert(!did_throw); assert(!did_emsg); assert(msg_list == &tstate->private_msg_list); assert(*msg_list == NULL); @@ -90,7 +91,6 @@ bool try_leave(const TryState *const tstate, Error *const err) current_exception = tstate->current_exception; trylevel = tstate->trylevel; got_int = tstate->got_int; - did_throw = tstate->did_throw; need_rethrow = tstate->need_rethrow; did_emsg = tstate->did_emsg; return ret; @@ -126,7 +126,7 @@ bool try_end(Error *err) did_emsg = false; if (got_int) { - if (did_throw) { + if (current_exception) { // If we got an interrupt, discard the current exception discard_current_exception(); } @@ -145,7 +145,7 @@ bool try_end(Error *err) if (should_free) { xfree(msg); } - } else if (did_throw) { + } else if (current_exception) { api_set_error(err, kErrorTypeException, "%s", current_exception->value); discard_current_exception(); } @@ -566,7 +566,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata) typval_encode_dict_end(edata) #define TYPVAL_ENCODE_CONV_RECURSE(val, conv_type) \ - TYPVAL_ENCODE_CONV_NIL() + TYPVAL_ENCODE_CONV_NIL(val) #define TYPVAL_ENCODE_SCOPE static #define TYPVAL_ENCODE_NAME object @@ -783,22 +783,20 @@ bool object_to_vim(Object obj, typval_T *tv, Error *err) break; case kObjectTypeArray: { - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)obj.data.array.size); for (uint32_t i = 0; i < obj.data.array.size; i++) { Object item = obj.data.array.items[i]; - listitem_T *li = tv_list_item_alloc(); + typval_T li_tv; - if (!object_to_vim(item, &li->li_tv, err)) { - // cleanup - tv_list_item_free(li); + if (!object_to_vim(item, &li_tv, err)) { tv_list_free(list); return false; } - tv_list_append(list, li); + tv_list_append_owned_tv(list, li_tv); } - list->lv_refcount++; + tv_list_ref(list); tv->v_type = VAR_LIST; tv->vval.v_list = list; @@ -957,6 +955,12 @@ static void init_ui_event_metadata(Dictionary *metadata) msgpack_rpc_to_object(&unpacked.data, &ui_events); msgpack_unpacked_destroy(&unpacked); PUT(*metadata, "ui_events", ui_events); + Array ui_options = ARRAY_DICT_INIT; + ADD(ui_options, STRING_OBJ(cstr_to_string("rgb"))); + for (UIExtension i = 0; i < kUIExtCount; i++) { + ADD(ui_options, STRING_OBJ(cstr_to_string(ui_ext_names[i]))); + } + PUT(*metadata, "ui_options", ARRAY_OBJ(ui_options)); } static void init_error_type_metadata(Dictionary *metadata) diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h index 87f334ac30..0634764f13 100644 --- a/src/nvim/api/private/helpers.h +++ b/src/nvim/api/private/helpers.h @@ -93,7 +93,6 @@ typedef struct { const struct msglist *const *msg_list; int trylevel; int got_int; - int did_throw; int need_rethrow; int did_emsg; } TryState; diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c index a9eaccfac5..4cd2657561 100644 --- a/src/nvim/api/ui.c +++ b/src/nvim/api/ui.c @@ -43,10 +43,10 @@ void remote_ui_disconnect(uint64_t channel_id) return; } UIData *data = ui->data; - // destroy pending screen updates - api_free_array(data->buffer); + api_free_array(data->buffer); // Destroy pending screen updates. pmap_del(uint64_t)(connected_uis, channel_id); xfree(ui->data); + ui->data = NULL; // Flag UI as "stopped". ui_detach_impl(ui); xfree(ui); } @@ -56,7 +56,8 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, kErrorTypeException, "UI already attached for channel"); + api_set_error(err, kErrorTypeException, + "UI already attached to channel: %" PRId64, channel_id); return; } @@ -86,6 +87,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->put = remote_ui_put; ui->bell = remote_ui_bell; ui->visual_bell = remote_ui_visual_bell; + ui->default_colors_set = remote_ui_default_colors_set; ui->update_fg = remote_ui_update_fg; ui->update_bg = remote_ui_update_bg; ui->update_sp = remote_ui_update_sp; @@ -93,6 +95,7 @@ void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, ui->suspend = remote_ui_suspend; ui->set_title = remote_ui_set_title; ui->set_icon = remote_ui_set_icon; + ui->option_set = remote_ui_option_set; ui->event = remote_ui_event; memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); @@ -128,7 +131,8 @@ void nvim_ui_detach(uint64_t channel_id, Error *err) FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, kErrorTypeException, "UI is not attached for channel"); + api_set_error(err, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } remote_ui_disconnect(channel_id); @@ -140,7 +144,8 @@ void nvim_ui_try_resize(uint64_t channel_id, Integer width, FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(err, kErrorTypeException, "UI is not attached for channel"); + api_set_error(err, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } @@ -161,7 +166,8 @@ void nvim_ui_set_option(uint64_t channel_id, String name, FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY { if (!pmap_has(uint64_t)(connected_uis, channel_id)) { - api_set_error(error, kErrorTypeException, "UI is not attached for channel"); + api_set_error(error, kErrorTypeException, + "UI not attached to channel: %" PRId64, channel_id); return; } UI *ui = pmap_get(uint64_t)(connected_uis, channel_id); @@ -174,18 +180,6 @@ void nvim_ui_set_option(uint64_t channel_id, String name, static void ui_set_option(UI *ui, String name, Object value, Error *error) { -#define UI_EXT_OPTION(o, e) \ - do { \ - if (strequal(name.data, #o)) { \ - if (value.type != kObjectTypeBoolean) { \ - api_set_error(error, kErrorTypeValidation, #o " must be a Boolean"); \ - return; \ - } \ - ui->ui_ext[(e)] = value.data.boolean; \ - return; \ - } \ - } while (0) - if (strequal(name.data, "rgb")) { if (value.type != kObjectTypeBoolean) { api_set_error(error, kErrorTypeValidation, "rgb must be a Boolean"); @@ -195,13 +189,21 @@ static void ui_set_option(UI *ui, String name, Object value, Error *error) return; } - UI_EXT_OPTION(ext_cmdline, kUICmdline); - UI_EXT_OPTION(ext_popupmenu, kUIPopupmenu); - UI_EXT_OPTION(ext_tabline, kUITabline); - UI_EXT_OPTION(ext_wildmenu, kUIWildmenu); + for (UIExtension i = 0; i < kUIExtCount; i++) { + if (strequal(name.data, ui_ext_names[i])) { + if (value.type != kObjectTypeBoolean) { + snprintf((char *)IObuff, IOSIZE, "%s must be a Boolean", + ui_ext_names[i]); + api_set_error(error, kErrorTypeValidation, (char *)IObuff); + return; + } + ui->ui_ext[i] = value.data.boolean; + return; + } + } if (strequal(name.data, "popupmenu_external")) { - // LEGACY: Deprecated option, use `ui_ext` instead. + // LEGACY: Deprecated option, use `ext_cmdline` instead. if (value.type != kObjectTypeBoolean) { api_set_error(error, kErrorTypeValidation, "popupmenu_external must be a Boolean"); @@ -211,7 +213,8 @@ static void ui_set_option(UI *ui, String name, Object value, Error *error) return; } - api_set_error(error, kErrorTypeValidation, "No such ui option"); + api_set_error(error, kErrorTypeValidation, "No such UI option: %s", + name.data); #undef UI_EXT_OPTION } @@ -242,7 +245,7 @@ static void push_call(UI *ui, char *name, Array args) static void remote_ui_highlight_set(UI *ui, HlAttrs attrs) { Array args = ARRAY_DICT_INIT; - Dictionary hl = hlattrs2dict(attrs); + Dictionary hl = hlattrs2dict(&attrs, ui->rgb); ADD(args, DICTIONARY_OBJ(hl)); push_call(ui, "highlight_set", args); diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h index 847b21072a..c599b0ce72 100644 --- a/src/nvim/api/ui_events.in.h +++ b/src/nvim/api/ui_events.in.h @@ -47,17 +47,22 @@ void visual_bell(void) void flush(void) FUNC_API_SINCE(3) FUNC_API_REMOTE_IMPL; void update_fg(Integer fg) - FUNC_API_SINCE(3); + FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; void update_bg(Integer bg) - FUNC_API_SINCE(3); + FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; void update_sp(Integer sp) - FUNC_API_SINCE(3); + FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; +void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) + FUNC_API_SINCE(4); void suspend(void) FUNC_API_SINCE(3) FUNC_API_BRIDGE_IMPL; void set_title(String title) FUNC_API_SINCE(3); void set_icon(String icon) FUNC_API_SINCE(3); +void option_set(String name, Object value) + FUNC_API_SINCE(4); void popupmenu_show(Array items, Integer selected, Integer row, Integer col) FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY; diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c index f4ccf07bec..07ec6e8c27 100644 --- a/src/nvim/api/vim.c +++ b/src/nvim/api/vim.c @@ -33,6 +33,10 @@ #include "nvim/syntax.h" #include "nvim/getchar.h" #include "nvim/os/input.h" +#include "nvim/os/process.h" +#include "nvim/viml/parser/expressions.h" +#include "nvim/viml/parser/parser.h" +#include "nvim/ui.h" #define LINE_BUFFER_SIZE 4096 @@ -41,14 +45,15 @@ #endif /// Executes an ex-command. -/// On VimL error: Returns the VimL error; v:errmsg is not updated. +/// +/// On parse error: forwards the Vim error; does not update v:errmsg. +/// On runtime error: forwards the Vim error; does not update v:errmsg. /// /// @param command Ex-command string -/// @param[out] err Error details (including actual VimL error), if any +/// @param[out] err Error details (Vim error), if any void nvim_command(String command, Error *err) FUNC_API_SINCE(1) { - // Run the command try_start(); do_cmdline_cmd(command.data); update_screen(VALID); @@ -205,18 +210,51 @@ String nvim_replace_termcodes(String str, Boolean from_part, Boolean do_lt, return cstr_as_string(ptr); } -String nvim_command_output(String str, Error *err) +/// Executes an ex-command and returns its (non-error) output. +/// Shell |:!| output is not captured. +/// +/// On parse error: forwards the Vim error; does not update v:errmsg. +/// On runtime error: forwards the Vim error; does not update v:errmsg. +/// +/// @param command Ex-command string +/// @param[out] err Error details (Vim error), if any +String nvim_command_output(String command, Error *err) FUNC_API_SINCE(1) { - do_cmdline_cmd("redir => v:command_output"); - nvim_command(str, err); - do_cmdline_cmd("redir END"); + const int save_msg_silent = msg_silent; + garray_T *const save_capture_ga = capture_ga; + garray_T capture_local; + ga_init(&capture_local, 1, 80); + + try_start(); + msg_silent++; + capture_ga = &capture_local; + do_cmdline_cmd(command.data); + capture_ga = save_capture_ga; + msg_silent = save_msg_silent; + try_end(err); if (ERROR_SET(err)) { - return (String)STRING_INIT; + goto theend; } - return cstr_to_string((char *)get_vim_var_str(VV_COMMAND_OUTPUT)); + if (capture_local.ga_len > 1) { + String s = (String){ + .data = capture_local.ga_data, + .size = (size_t)capture_local.ga_len, + }; + // redir usually (except :echon) prepends a newline. + if (s.data[0] == '\n') { + memmove(s.data, s.data + 1, s.size); + s.data[s.size - 1] = '\0'; + s.size = s.size - 1; + } + return s; // Caller will free the memory. + } + +theend: + ga_clear(&capture_local); + return (String)STRING_INIT; } /// Evaluates a VimL expression (:help expression). @@ -787,6 +825,10 @@ ArrayOf(Dictionary) nvim_get_keymap(String mode) return keymap_array(mode, NULL); } +/// Returns a 2-tuple (Array), where item 0 is the current channel id and item +/// 1 is the |api-metadata| map (Dictionary). +/// +/// @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 { @@ -889,6 +931,460 @@ theend: return rv; } +typedef struct { + ExprASTNode **node_p; + Object *ret_node_p; +} ExprASTConvStackItem; + +/// @cond DOXYGEN_NOT_A_FUNCTION +typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack; +/// @endcond + +/// Parse a VimL expression +/// +/// @param[in] expr Expression to parse. Is always treated as a single line. +/// @param[in] flags Flags: +/// +/// - "m" if multiple expressions in a row are allowed (only +/// the first one will be parsed), +/// - "E" if EOC tokens are not allowed (determines whether +/// they will stop parsing process or be recognized as an +/// operator/space, though also yielding an error). +/// - "l" when needing to start parsing with lvalues for +/// ":let" or ":for". +/// +/// Common flag sets: +/// - "m" to parse like for ":echo". +/// - "E" to parse like for "<C-r>=". +/// - empty string for ":call". +/// - "lm" to parse for ":let". +/// @param[in] highlight If true, return value will also include "highlight" +/// key containing array of 4-tuples (arrays) (Integer, +/// Integer, Integer, String), where first three numbers +/// define the highlighted region and represent line, +/// starting column and ending column (latter exclusive: +/// one should highlight region [start_col, end_col)). +/// +/// @return AST: top-level dictionary with these keys: +/// +/// "error": Dictionary with error, present only if parser saw some +/// error. Contains the following keys: +/// +/// "message": String, error message in printf format, translated. +/// Must contain exactly one "%.*s". +/// "arg": String, error message argument. +/// +/// "len": Amount of bytes successfully parsed. With flags equal to "" +/// that should be equal to the length of expr string. +/// +/// @note: “Sucessfully parsed” here means “participated in AST +/// creation”, not “till the first error”. +/// +/// "ast": AST, either nil or a dictionary with these keys: +/// +/// "type": node type, one of the value names from ExprASTNodeType +/// stringified without "kExprNode" prefix. +/// "start": a pair [line, column] describing where node is “started” +/// where "line" is always 0 (will not be 0 if you will be +/// using nvim_parse_viml() on e.g. ":let", but that is not +/// present yet). Both elements are Integers. +/// "len": “length” of the node. This and "start" are there for +/// debugging purposes primary (debugging parser and providing +/// debug information). +/// "children": a list of nodes described in top/"ast". There always +/// is zero, one or two children, key will not be present +/// if node has no children. Maximum number of children +/// may be found in node_maxchildren array. +/// +/// Local values (present only for certain nodes): +/// +/// "scope": a single Integer, specifies scope for "Option" and +/// "PlainIdentifier" nodes. For "Option" it is one of +/// ExprOptScope values, for "PlainIdentifier" it is one of +/// ExprVarScope values. +/// "ident": identifier (without scope, if any), present for "Option", +/// "PlainIdentifier", "PlainKey" and "Environment" nodes. +/// "name": Integer, register name (one character) or -1. Only present +/// for "Register" nodes. +/// "cmp_type": String, comparison type, one of the value names from +/// ExprComparisonType, stringified without "kExprCmp" +/// prefix. Only present for "Comparison" nodes. +/// "ccs_strategy": String, case comparison strategy, one of the +/// value names from ExprCaseCompareStrategy, +/// stringified without "kCCStrategy" prefix. Only +/// present for "Comparison" nodes. +/// "augmentation": String, augmentation type for "Assignment" nodes. +/// Is either an empty string, "Add", "Subtract" or +/// "Concat" for "=", "+=", "-=" or ".=" respectively. +/// "invert": Boolean, true if result of comparison needs to be +/// inverted. Only present for "Comparison" nodes. +/// "ivalue": Integer, integer value for "Integer" nodes. +/// "fvalue": Float, floating-point value for "Float" nodes. +/// "svalue": String, value for "SingleQuotedString" and +/// "DoubleQuotedString" nodes. +Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, + Error *err) + FUNC_API_SINCE(4) FUNC_API_ASYNC +{ + int pflags = 0; + for (size_t i = 0 ; i < flags.size ; i++) { + switch (flags.data[i]) { + case 'm': { pflags |= kExprFlagsMulti; break; } + case 'E': { pflags |= kExprFlagsDisallowEOC; break; } + case 'l': { pflags |= kExprFlagsParseLet; break; } + case NUL: { + api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)", + (unsigned)flags.data[i]); + return (Dictionary)ARRAY_DICT_INIT; + } + default: { + api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)", + flags.data[i], (unsigned)flags.data[i]); + return (Dictionary)ARRAY_DICT_INIT; + } + } + } + ParserLine plines[] = { + { + .data = expr.data, + .size = expr.size, + .allocated = false, + }, + { NULL, 0, false }, + }; + ParserLine *plines_p = plines; + ParserHighlight colors; + kvi_init(colors); + ParserHighlight *const colors_p = (highlight ? &colors : NULL); + ParserState pstate; + viml_parser_init( + &pstate, parser_simple_get_line, &plines_p, colors_p); + ExprAST east = viml_pexpr_parse(&pstate, pflags); + + const size_t ret_size = ( + 2 // "ast", "len" + + (size_t)(east.err.msg != NULL) // "error" + + (size_t)highlight // "highlight" + + 0); + Dictionary ret = { + .items = xmalloc(ret_size * sizeof(ret.items[0])), + .size = 0, + .capacity = ret_size, + }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ast"), + .value = NIL, + }; + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("len"), + .value = INTEGER_OBJ((Integer)(pstate.pos.line == 1 + ? plines[0].size + : pstate.pos.col)), + }; + if (east.err.msg != NULL) { + Dictionary err_dict = { + .items = xmalloc(2 * sizeof(err_dict.items[0])), + .size = 2, + .capacity = 2, + }; + err_dict.items[0] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("message"), + .value = STRING_OBJ(cstr_to_string(east.err.msg)), + }; + if (east.err.arg == NULL) { + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("arg"), + .value = STRING_OBJ(STRING_INIT), + }; + } else { + err_dict.items[1] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("arg"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(east.err.arg, (size_t)east.err.arg_len), + .size = (size_t)east.err.arg_len, + })), + }; + } + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("error"), + .value = DICTIONARY_OBJ(err_dict), + }; + } + if (highlight) { + Array hl = (Array) { + .items = xmalloc(kv_size(colors) * sizeof(hl.items[0])), + .capacity = kv_size(colors), + .size = kv_size(colors), + }; + for (size_t i = 0 ; i < kv_size(colors) ; i++) { + const ParserHighlightChunk chunk = kv_A(colors, i); + Array chunk_arr = (Array) { + .items = xmalloc(4 * sizeof(chunk_arr.items[0])), + .capacity = 4, + .size = 4, + }; + chunk_arr.items[0] = INTEGER_OBJ((Integer)chunk.start.line); + chunk_arr.items[1] = INTEGER_OBJ((Integer)chunk.start.col); + chunk_arr.items[2] = INTEGER_OBJ((Integer)chunk.end_col); + chunk_arr.items[3] = STRING_OBJ(cstr_to_string(chunk.group)); + hl.items[i] = ARRAY_OBJ(chunk_arr); + } + ret.items[ret.size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("highlight"), + .value = ARRAY_OBJ(hl), + }; + } + kvi_destroy(colors); + + // Walk over the AST, freeing nodes in process. + ExprASTConvStack ast_conv_stack; + kvi_init(ast_conv_stack); + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &east.root, + .ret_node_p = &ret.items[0].value, + })); + while (kv_size(ast_conv_stack)) { + ExprASTConvStackItem cur_item = kv_last(ast_conv_stack); + ExprASTNode *const node = *cur_item.node_p; + if (node == NULL) { + assert(kv_size(ast_conv_stack) == 1); + kv_drop(ast_conv_stack, 1); + } else { + if (cur_item.ret_node_p->type == kObjectTypeNil) { + const size_t ret_node_items_size = (size_t)( + 3 // "type", "start" and "len" + + (node->children != NULL) // "children" + + (node->type == kExprNodeOption + || node->type == kExprNodePlainIdentifier) // "scope" + + (node->type == kExprNodeOption + || node->type == kExprNodePlainIdentifier + || node->type == kExprNodePlainKey + || node->type == kExprNodeEnvironment) // "ident" + + (node->type == kExprNodeRegister) // "name" + + (3 // "cmp_type", "ccs_strategy", "invert" + * (node->type == kExprNodeComparison)) + + (node->type == kExprNodeInteger) // "ivalue" + + (node->type == kExprNodeFloat) // "fvalue" + + (node->type == kExprNodeDoubleQuotedString + || node->type == kExprNodeSingleQuotedString) // "svalue" + + (node->type == kExprNodeAssignment) // "augmentation" + + 0); + Dictionary ret_node = { + .items = xmalloc(ret_node_items_size * sizeof(ret_node.items[0])), + .capacity = ret_node_items_size, + .size = 0, + }; + *cur_item.ret_node_p = DICTIONARY_OBJ(ret_node); + } + Dictionary *ret_node = &cur_item.ret_node_p->data.dictionary; + if (node->children != NULL) { + const size_t num_children = 1 + (node->children->next != NULL); + Array children_array = { + .items = xmalloc(num_children * sizeof(children_array.items[0])), + .capacity = num_children, + .size = num_children, + }; + for (size_t i = 0; i < num_children; i++) { + children_array.items[i] = NIL; + } + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("children"), + .value = ARRAY_OBJ(children_array), + }; + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &node->children, + .ret_node_p = &children_array.items[0], + })); + } else if (node->next != NULL) { + kvi_push(ast_conv_stack, ((ExprASTConvStackItem) { + .node_p = &node->next, + .ret_node_p = cur_item.ret_node_p + 1, + })); + } else if (node != NULL) { + kv_drop(ast_conv_stack, 1); + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("type"), + .value = STRING_OBJ(cstr_to_string(east_node_type_tab[node->type])), + }; + Array start_array = { + .items = xmalloc(2 * sizeof(start_array.items[0])), + .capacity = 2, + .size = 2, + }; + start_array.items[0] = INTEGER_OBJ((Integer)node->start.line); + start_array.items[1] = INTEGER_OBJ((Integer)node->start.col); + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("start"), + .value = ARRAY_OBJ(start_array), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("len"), + .value = INTEGER_OBJ((Integer)node->len), + }; + switch (node->type) { + case kExprNodeDoubleQuotedString: + case kExprNodeSingleQuotedString: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("svalue"), + .value = STRING_OBJ(((String) { + .data = node->data.str.value, + .size = node->data.str.size, + })), + }; + break; + } + case kExprNodeOption: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("scope"), + .value = INTEGER_OBJ(node->data.opt.scope), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.opt.ident, + node->data.opt.ident_len), + .size = node->data.opt.ident_len, + })), + }; + break; + } + case kExprNodePlainIdentifier: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("scope"), + .value = INTEGER_OBJ(node->data.var.scope), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.var.ident, + node->data.var.ident_len), + .size = node->data.var.ident_len, + })), + }; + break; + } + case kExprNodePlainKey: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.var.ident, + node->data.var.ident_len), + .size = node->data.var.ident_len, + })), + }; + break; + } + case kExprNodeEnvironment: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ident"), + .value = STRING_OBJ(((String) { + .data = xmemdupz(node->data.env.ident, + node->data.env.ident_len), + .size = node->data.env.ident_len, + })), + }; + break; + } + case kExprNodeRegister: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("name"), + .value = INTEGER_OBJ(node->data.reg.name), + }; + break; + } + case kExprNodeComparison: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("cmp_type"), + .value = STRING_OBJ(cstr_to_string( + eltkn_cmp_type_tab[node->data.cmp.type])), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ccs_strategy"), + .value = STRING_OBJ(cstr_to_string( + ccs_tab[node->data.cmp.ccs])), + }; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("invert"), + .value = BOOLEAN_OBJ(node->data.cmp.inv), + }; + break; + } + case kExprNodeFloat: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("fvalue"), + .value = FLOAT_OBJ(node->data.flt.value), + }; + break; + } + case kExprNodeInteger: { + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("ivalue"), + .value = INTEGER_OBJ((Integer)( + node->data.num.value > API_INTEGER_MAX + ? API_INTEGER_MAX + : (Integer)node->data.num.value)), + }; + break; + } + case kExprNodeAssignment: { + const ExprAssignmentType asgn_type = node->data.ass.type; + ret_node->items[ret_node->size++] = (KeyValuePair) { + .key = STATIC_CSTR_TO_STRING("augmentation"), + .value = STRING_OBJ( + asgn_type == kExprAsgnPlain + ? (String)STRING_INIT + : cstr_to_string(expr_asgn_type_tab[asgn_type])), + }; + break; + } + case kExprNodeMissing: + case kExprNodeOpMissing: + case kExprNodeTernary: + case kExprNodeTernaryValue: + case kExprNodeSubscript: + case kExprNodeListLiteral: + case kExprNodeUnaryPlus: + case kExprNodeBinaryPlus: + case kExprNodeNested: + case kExprNodeCall: + case kExprNodeComplexIdentifier: + case kExprNodeUnknownFigure: + case kExprNodeLambda: + case kExprNodeDictLiteral: + case kExprNodeCurlyBracesIdentifier: + case kExprNodeComma: + case kExprNodeColon: + case kExprNodeArrow: + case kExprNodeConcat: + case kExprNodeConcatOrSubscript: + case kExprNodeOr: + case kExprNodeAnd: + case kExprNodeUnaryMinus: + case kExprNodeBinaryMinus: + case kExprNodeNot: + case kExprNodeMultiplication: + case kExprNodeDivision: + case kExprNodeMod: { + break; + } + } + assert(cur_item.ret_node_p->data.dictionary.size + == cur_item.ret_node_p->data.dictionary.capacity); + xfree(*cur_item.node_p); + *cur_item.node_p = NULL; + } + } + } + kvi_destroy(ast_conv_stack); + + assert(ret.size == ret.capacity); + // Should be a no-op actually, leaving it in case non-nodes will need to be + // freed later. + viml_pexpr_free_ast(east); + viml_parser_destroy(&pstate); + return ret; +} + /// Writes a message to vim output or error buffer. The string is split /// and flushed after each newline. Incomplete lines are kept for writing @@ -976,3 +1472,95 @@ Float nvim__id_float(Float flt) { return flt; } + +/// Gets a list of dictionaries representing attached UIs. +/// +/// @return Array of UI dictionaries +Array nvim_list_uis(void) + FUNC_API_SINCE(4) +{ + return ui_array(); +} + +/// Gets the immediate children of process `pid`. +/// +/// @return Array of child process ids, empty if process not found. +Array nvim_get_proc_children(Integer pid, Error *err) + FUNC_API_SINCE(4) +{ + Array rvobj = ARRAY_DICT_INIT; + int *proc_list = NULL; + + if (pid <= 0 || pid > INT_MAX) { + api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); + goto end; + } + + size_t proc_count; + int rv = os_proc_children((int)pid, &proc_list, &proc_count); + if (rv != 0) { + // syscall failed (possibly because of kernel options), try shelling out. + DLOG("fallback to vim._os_proc_children()"); + Array a = ARRAY_DICT_INIT; + ADD(a, INTEGER_OBJ(pid)); + String s = cstr_to_string("return vim._os_proc_children(select(1, ...))"); + Object o = nvim_execute_lua(s, a, err); + api_free_string(s); + api_free_array(a); + if (o.type == kObjectTypeArray) { + rvobj = o.data.array; + } else if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, + "Failed to get process children. pid=%" PRId64 " error=%d", + pid, rv); + } + goto end; + } + + for (size_t i = 0; i < proc_count; i++) { + ADD(rvobj, INTEGER_OBJ(proc_list[i])); + } + +end: + xfree(proc_list); + return rvobj; +} + +/// Gets info describing process `pid`. +/// +/// @return Map of process properties, or NIL if process not found. +Object nvim_get_proc(Integer pid, Error *err) + FUNC_API_SINCE(4) +{ + Object rvobj = OBJECT_INIT; + rvobj.data.dictionary = (Dictionary)ARRAY_DICT_INIT; + rvobj.type = kObjectTypeDictionary; + + if (pid <= 0 || pid > INT_MAX) { + api_set_error(err, kErrorTypeException, "Invalid pid: %" PRId64, pid); + return NIL; + } +#ifdef WIN32 + rvobj.data.dictionary = os_proc_info((int)pid); + if (rvobj.data.dictionary.size == 0) { // Process not found. + return NIL; + } +#else + // Cross-platform process info APIs are miserable, so use `ps` instead. + Array a = ARRAY_DICT_INIT; + ADD(a, INTEGER_OBJ(pid)); + String s = cstr_to_string("return vim._os_proc_info(select(1, ...))"); + Object o = nvim_execute_lua(s, a, err); + api_free_string(s); + api_free_array(a); + if (o.type == kObjectTypeArray && o.data.array.size == 0) { + return NIL; // Process not found. + } else if (o.type == kObjectTypeDictionary) { + rvobj.data.dictionary = o.data.dictionary; + } else if (!ERROR_SET(err)) { + api_set_error(err, kErrorTypeException, + "Failed to get process info. pid=%" PRId64, pid); + } +#endif + return rvobj; +} diff --git a/src/nvim/arabic.c b/src/nvim/arabic.c index 1ef51d2a2a..e120e6d492 100644 --- a/src/nvim/arabic.c +++ b/src/nvim/arabic.c @@ -393,491 +393,141 @@ static bool A_is_f(int cur_c) // Change shape - from ISO-8859-6/Isolated to Form-B Isolated static int chg_c_a2s(int cur_c) { - int tempc; - switch (cur_c) { - case a_HAMZA: - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: - tempc = a_s_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_s_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: - tempc = a_s_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_s_ALEF_HAMZA_BELOW; - break; - - case a_YEH_HAMZA: - tempc = a_s_YEH_HAMZA; - break; - - case a_ALEF: - tempc = a_s_ALEF; - break; - - case a_TEH_MARBUTA: - tempc = a_s_TEH_MARBUTA; - break; - - case a_DAL: - tempc = a_s_DAL; - break; - - case a_THAL: - tempc = a_s_THAL; - break; - - case a_REH: - tempc = a_s_REH; - break; - - case a_ZAIN: - tempc = a_s_ZAIN; - break; - - case a_TATWEEL: // exceptions - tempc = cur_c; - break; - - case a_WAW: - tempc = a_s_WAW; - break; - - case a_ALEF_MAKSURA: - tempc = a_s_ALEF_MAKSURA; - break; - - case a_BEH: - tempc = a_s_BEH; - break; - - case a_TEH: - tempc = a_s_TEH; - break; - - case a_THEH: - tempc = a_s_THEH; - break; - - case a_JEEM: - tempc = a_s_JEEM; - break; - - case a_HAH: - tempc = a_s_HAH; - break; - - case a_KHAH: - tempc = a_s_KHAH; - break; - - case a_SEEN: - tempc = a_s_SEEN; - break; - - case a_SHEEN: - tempc = a_s_SHEEN; - break; - - case a_SAD: - tempc = a_s_SAD; - break; - - case a_DAD: - tempc = a_s_DAD; - break; - - case a_TAH: - tempc = a_s_TAH; - break; - - case a_ZAH: - tempc = a_s_ZAH; - break; - - case a_AIN: - tempc = a_s_AIN; - break; - - case a_GHAIN: - tempc = a_s_GHAIN; - break; - - case a_FEH: - tempc = a_s_FEH; - break; - - case a_QAF: - tempc = a_s_QAF; - break; - - case a_KAF: - tempc = a_s_KAF; - break; - - case a_LAM: - tempc = a_s_LAM; - break; - - case a_MEEM: - tempc = a_s_MEEM; - break; - - case a_NOON: - tempc = a_s_NOON; - break; - - case a_HEH: - tempc = a_s_HEH; - break; - - case a_YEH: - tempc = a_s_YEH; - break; - - default: - tempc = 0; + case a_HAMZA: return a_s_HAMZA; + case a_ALEF_MADDA: return a_s_ALEF_MADDA; + case a_ALEF_HAMZA_ABOVE: return a_s_ALEF_HAMZA_ABOVE; + case a_WAW_HAMZA: return a_s_WAW_HAMZA; + case a_ALEF_HAMZA_BELOW: return a_s_ALEF_HAMZA_BELOW; + case a_YEH_HAMZA: return a_s_YEH_HAMZA; + case a_ALEF: return a_s_ALEF; + case a_TEH_MARBUTA: return a_s_TEH_MARBUTA; + case a_DAL: return a_s_DAL; + case a_THAL: return a_s_THAL; + case a_REH: return a_s_REH; + case a_ZAIN: return a_s_ZAIN; + case a_TATWEEL: return cur_c; // exceptions + case a_WAW: return a_s_WAW; + case a_ALEF_MAKSURA: return a_s_ALEF_MAKSURA; + case a_BEH: return a_s_BEH; + case a_TEH: return a_s_TEH; + case a_THEH: return a_s_THEH; + case a_JEEM: return a_s_JEEM; + case a_HAH: return a_s_HAH; + case a_KHAH: return a_s_KHAH; + case a_SEEN: return a_s_SEEN; + case a_SHEEN: return a_s_SHEEN; + case a_SAD: return a_s_SAD; + case a_DAD: return a_s_DAD; + case a_TAH: return a_s_TAH; + case a_ZAH: return a_s_ZAH; + case a_AIN: return a_s_AIN; + case a_GHAIN: return a_s_GHAIN; + case a_FEH: return a_s_FEH; + case a_QAF: return a_s_QAF; + case a_KAF: return a_s_KAF; + case a_LAM: return a_s_LAM; + case a_MEEM: return a_s_MEEM; + case a_NOON: return a_s_NOON; + case a_HEH: return a_s_HEH; + case a_YEH: return a_s_YEH; } - - return tempc; + return 0; } // Change shape - from ISO-8859-6/Isolated to Initial static int chg_c_a2i(int cur_c) { - int tempc; - switch (cur_c) { - case a_YEH_HAMZA: - tempc = a_i_YEH_HAMZA; - break; - - case a_HAMZA: // exceptions - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: // exceptions - tempc = a_s_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: // exceptions - tempc = a_s_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: // exceptions - tempc = a_s_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: // exceptions - tempc = a_s_ALEF_HAMZA_BELOW; - break; - - case a_ALEF: // exceptions - tempc = a_s_ALEF; - break; - - case a_TEH_MARBUTA: // exceptions - tempc = a_s_TEH_MARBUTA; - break; - - case a_DAL: // exceptions - tempc = a_s_DAL; - break; - - case a_THAL: // exceptions - tempc = a_s_THAL; - break; - - case a_REH: // exceptions - tempc = a_s_REH; - break; - - case a_ZAIN: // exceptions - tempc = a_s_ZAIN; - break; - - case a_TATWEEL: // exceptions - tempc = cur_c; - break; - - case a_WAW: // exceptions - tempc = a_s_WAW; - break; - - case a_ALEF_MAKSURA: // exceptions - tempc = a_s_ALEF_MAKSURA; - break; - - case a_BEH: - tempc = a_i_BEH; - break; - - case a_TEH: - tempc = a_i_TEH; - break; - - case a_THEH: - tempc = a_i_THEH; - break; - - case a_JEEM: - tempc = a_i_JEEM; - break; - - case a_HAH: - tempc = a_i_HAH; - break; - - case a_KHAH: - tempc = a_i_KHAH; - break; - - case a_SEEN: - tempc = a_i_SEEN; - break; - - case a_SHEEN: - tempc = a_i_SHEEN; - break; - - case a_SAD: - tempc = a_i_SAD; - break; - - case a_DAD: - tempc = a_i_DAD; - break; - - case a_TAH: - tempc = a_i_TAH; - break; - - case a_ZAH: - tempc = a_i_ZAH; - break; - - case a_AIN: - tempc = a_i_AIN; - break; - - case a_GHAIN: - tempc = a_i_GHAIN; - break; - - case a_FEH: - tempc = a_i_FEH; - break; - - case a_QAF: - tempc = a_i_QAF; - break; - - case a_KAF: - tempc = a_i_KAF; - break; - - case a_LAM: - tempc = a_i_LAM; - break; - - case a_MEEM: - tempc = a_i_MEEM; - break; - - case a_NOON: - tempc = a_i_NOON; - break; - - case a_HEH: - tempc = a_i_HEH; - break; - - case a_YEH: - tempc = a_i_YEH; - break; - - default: - tempc = 0; + case a_YEH_HAMZA: return a_i_YEH_HAMZA; + case a_HAMZA: return a_s_HAMZA; // exceptions + case a_ALEF_MADDA: return a_s_ALEF_MADDA; // exceptions + case a_ALEF_HAMZA_ABOVE: return a_s_ALEF_HAMZA_ABOVE; // exceptions + case a_WAW_HAMZA: return a_s_WAW_HAMZA; // exceptions + case a_ALEF_HAMZA_BELOW: return a_s_ALEF_HAMZA_BELOW; // exceptions + case a_ALEF: return a_s_ALEF; // exceptions + case a_TEH_MARBUTA: return a_s_TEH_MARBUTA; // exceptions + case a_DAL: return a_s_DAL; // exceptions + case a_THAL: return a_s_THAL; // exceptions + case a_REH: return a_s_REH; // exceptions + case a_ZAIN: return a_s_ZAIN; // exceptions + case a_TATWEEL: return cur_c; // exceptions + case a_WAW: return a_s_WAW; // exceptions + case a_ALEF_MAKSURA: return a_s_ALEF_MAKSURA; // exceptions + case a_BEH: return a_i_BEH; + case a_TEH: return a_i_TEH; + case a_THEH: return a_i_THEH; + case a_JEEM: return a_i_JEEM; + case a_HAH: return a_i_HAH; + case a_KHAH: return a_i_KHAH; + case a_SEEN: return a_i_SEEN; + case a_SHEEN: return a_i_SHEEN; + case a_SAD: return a_i_SAD; + case a_DAD: return a_i_DAD; + case a_TAH: return a_i_TAH; + case a_ZAH: return a_i_ZAH; + case a_AIN: return a_i_AIN; + case a_GHAIN: return a_i_GHAIN; + case a_FEH: return a_i_FEH; + case a_QAF: return a_i_QAF; + case a_KAF: return a_i_KAF; + case a_LAM: return a_i_LAM; + case a_MEEM: return a_i_MEEM; + case a_NOON: return a_i_NOON; + case a_HEH: return a_i_HEH; + case a_YEH: return a_i_YEH; } - - return tempc; + return 0; } // Change shape - from ISO-8859-6/Isolated to Medial static int chg_c_a2m(int cur_c) { - int tempc; - switch (cur_c) { - case a_HAMZA: // exception - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: // exception - tempc = a_f_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: // exception - tempc = a_f_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: // exception - tempc = a_f_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: // exception - tempc = a_f_ALEF_HAMZA_BELOW; - break; - - case a_YEH_HAMZA: - tempc = a_m_YEH_HAMZA; - break; - - case a_ALEF: // exception - tempc = a_f_ALEF; - break; - - case a_BEH: - tempc = a_m_BEH; - break; - - case a_TEH_MARBUTA: // exception - tempc = a_f_TEH_MARBUTA; - break; - - case a_TEH: - tempc = a_m_TEH; - break; - - case a_THEH: - tempc = a_m_THEH; - break; - - case a_JEEM: - tempc = a_m_JEEM; - break; - - case a_HAH: - tempc = a_m_HAH; - break; - - case a_KHAH: - tempc = a_m_KHAH; - break; - - case a_DAL: // exception - tempc = a_f_DAL; - break; - - case a_THAL: // exception - tempc = a_f_THAL; - break; - - case a_REH: // exception - tempc = a_f_REH; - break; - - case a_ZAIN: // exception - tempc = a_f_ZAIN; - break; - - case a_SEEN: - tempc = a_m_SEEN; - break; - - case a_SHEEN: - tempc = a_m_SHEEN; - break; - - case a_SAD: - tempc = a_m_SAD; - break; - - case a_DAD: - tempc = a_m_DAD; - break; - - case a_TAH: - tempc = a_m_TAH; - break; - - case a_ZAH: - tempc = a_m_ZAH; - break; - - case a_AIN: - tempc = a_m_AIN; - break; - - case a_GHAIN: - tempc = a_m_GHAIN; - break; - - case a_TATWEEL: // exception - tempc = cur_c; - break; - - case a_FEH: - tempc = a_m_FEH; - break; - - case a_QAF: - tempc = a_m_QAF; - break; - - case a_KAF: - tempc = a_m_KAF; - break; - - case a_LAM: - tempc = a_m_LAM; - break; - - case a_MEEM: - tempc = a_m_MEEM; - break; - - case a_NOON: - tempc = a_m_NOON; - break; - - case a_HEH: - tempc = a_m_HEH; - break; - - case a_WAW: // exception - tempc = a_f_WAW; - break; - - case a_ALEF_MAKSURA: // exception - tempc = a_f_ALEF_MAKSURA; - break; - - case a_YEH: - tempc = a_m_YEH; - break; - - default: - tempc = 0; + case a_HAMZA: return a_s_HAMZA; // exception + case a_ALEF_MADDA: return a_f_ALEF_MADDA; // exception + case a_ALEF_HAMZA_ABOVE: return a_f_ALEF_HAMZA_ABOVE; // exception + case a_WAW_HAMZA: return a_f_WAW_HAMZA; // exception + case a_ALEF_HAMZA_BELOW: return a_f_ALEF_HAMZA_BELOW; // exception + case a_YEH_HAMZA: return a_m_YEH_HAMZA; + case a_ALEF: return a_f_ALEF; // exception + case a_BEH: return a_m_BEH; + case a_TEH_MARBUTA: return a_f_TEH_MARBUTA; // exception + case a_TEH: return a_m_TEH; + case a_THEH: return a_m_THEH; + case a_JEEM: return a_m_JEEM; + case a_HAH: return a_m_HAH; + case a_KHAH: return a_m_KHAH; + case a_DAL: return a_f_DAL; // exception + case a_THAL: return a_f_THAL; // exception + case a_REH: return a_f_REH; // exception + case a_ZAIN: return a_f_ZAIN; // exception + case a_SEEN: return a_m_SEEN; + case a_SHEEN: return a_m_SHEEN; + case a_SAD: return a_m_SAD; + case a_DAD: return a_m_DAD; + case a_TAH: return a_m_TAH; + case a_ZAH: return a_m_ZAH; + case a_AIN: return a_m_AIN; + case a_GHAIN: return a_m_GHAIN; + case a_TATWEEL: return cur_c; // exception + case a_FEH: return a_m_FEH; + case a_QAF: return a_m_QAF; + case a_KAF: return a_m_KAF; + case a_LAM: return a_m_LAM; + case a_MEEM: return a_m_MEEM; + case a_NOON: return a_m_NOON; + case a_HEH: return a_m_HEH; + case a_WAW: return a_f_WAW; // exception + case a_ALEF_MAKSURA: return a_f_ALEF_MAKSURA; // exception + case a_YEH: return a_m_YEH; } - - return tempc; + return 0; } // Change shape - from ISO-8859-6/Isolated to final static int chg_c_a2f(int cur_c) { - int tempc; - // NOTE: these encodings need to be accounted for // // a_f_ALEF_MADDA; @@ -888,280 +538,87 @@ static int chg_c_a2f(int cur_c) // a_f_LAM_ALEF_HAMZA_BELOW; switch (cur_c) { - case a_HAMZA: // exception - tempc = a_s_HAMZA; - break; - - case a_ALEF_MADDA: - tempc = a_f_ALEF_MADDA; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_f_ALEF_HAMZA_ABOVE; - break; - - case a_WAW_HAMZA: - tempc = a_f_WAW_HAMZA; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_f_ALEF_HAMZA_BELOW; - break; - - case a_YEH_HAMZA: - tempc = a_f_YEH_HAMZA; - break; - - case a_ALEF: - tempc = a_f_ALEF; - break; - - case a_BEH: - tempc = a_f_BEH; - break; - - case a_TEH_MARBUTA: - tempc = a_f_TEH_MARBUTA; - break; - - case a_TEH: - tempc = a_f_TEH; - break; - - case a_THEH: - tempc = a_f_THEH; - break; - - case a_JEEM: - tempc = a_f_JEEM; - break; - - case a_HAH: - tempc = a_f_HAH; - break; - - case a_KHAH: - tempc = a_f_KHAH; - break; - - case a_DAL: - tempc = a_f_DAL; - break; - - case a_THAL: - tempc = a_f_THAL; - break; - - case a_REH: - tempc = a_f_REH; - break; - - case a_ZAIN: - tempc = a_f_ZAIN; - break; - - case a_SEEN: - tempc = a_f_SEEN; - break; - - case a_SHEEN: - tempc = a_f_SHEEN; - break; - - case a_SAD: - tempc = a_f_SAD; - break; - - case a_DAD: - tempc = a_f_DAD; - break; - - case a_TAH: - tempc = a_f_TAH; - break; - - case a_ZAH: - tempc = a_f_ZAH; - break; - - case a_AIN: - tempc = a_f_AIN; - break; - - case a_GHAIN: - tempc = a_f_GHAIN; - break; - - case a_TATWEEL: // exception - tempc = cur_c; - break; - - case a_FEH: - tempc = a_f_FEH; - break; - - case a_QAF: - tempc = a_f_QAF; - break; - - case a_KAF: - tempc = a_f_KAF; - break; - - case a_LAM: - tempc = a_f_LAM; - break; - - case a_MEEM: - tempc = a_f_MEEM; - break; - - case a_NOON: - tempc = a_f_NOON; - break; - - case a_HEH: - tempc = a_f_HEH; - break; - - case a_WAW: - tempc = a_f_WAW; - break; - - case a_ALEF_MAKSURA: - tempc = a_f_ALEF_MAKSURA; - break; - - case a_YEH: - tempc = a_f_YEH; - break; - - default: - tempc = 0; + case a_HAMZA: return a_s_HAMZA; // exception + case a_ALEF_MADDA: return a_f_ALEF_MADDA; + case a_ALEF_HAMZA_ABOVE: return a_f_ALEF_HAMZA_ABOVE; + case a_WAW_HAMZA: return a_f_WAW_HAMZA; + case a_ALEF_HAMZA_BELOW: return a_f_ALEF_HAMZA_BELOW; + case a_YEH_HAMZA: return a_f_YEH_HAMZA; + case a_ALEF: return a_f_ALEF; + case a_BEH: return a_f_BEH; + case a_TEH_MARBUTA: return a_f_TEH_MARBUTA; + case a_TEH: return a_f_TEH; + case a_THEH: return a_f_THEH; + case a_JEEM: return a_f_JEEM; + case a_HAH: return a_f_HAH; + case a_KHAH: return a_f_KHAH; + case a_DAL: return a_f_DAL; + case a_THAL: return a_f_THAL; + case a_REH: return a_f_REH; + case a_ZAIN: return a_f_ZAIN; + case a_SEEN: return a_f_SEEN; + case a_SHEEN: return a_f_SHEEN; + case a_SAD: return a_f_SAD; + case a_DAD: return a_f_DAD; + case a_TAH: return a_f_TAH; + case a_ZAH: return a_f_ZAH; + case a_AIN: return a_f_AIN; + case a_GHAIN: return a_f_GHAIN; + case a_TATWEEL: return cur_c; // exception + case a_FEH: return a_f_FEH; + case a_QAF: return a_f_QAF; + case a_KAF: return a_f_KAF; + case a_LAM: return a_f_LAM; + case a_MEEM: return a_f_MEEM; + case a_NOON: return a_f_NOON; + case a_HEH: return a_f_HEH; + case a_WAW: return a_f_WAW; + case a_ALEF_MAKSURA: return a_f_ALEF_MAKSURA; + case a_YEH: return a_f_YEH; } - - return tempc; + return 0; } // Change shape - from Initial to Medial static int chg_c_i2m(int cur_c) { - int tempc; - switch (cur_c) { - case a_i_YEH_HAMZA: - tempc = a_m_YEH_HAMZA; - break; - - case a_i_BEH: - tempc = a_m_BEH; - break; - - case a_i_TEH: - tempc = a_m_TEH; - break; - - case a_i_THEH: - tempc = a_m_THEH; - break; - - case a_i_JEEM: - tempc = a_m_JEEM; - break; - - case a_i_HAH: - tempc = a_m_HAH; - break; - - case a_i_KHAH: - tempc = a_m_KHAH; - break; - - case a_i_SEEN: - tempc = a_m_SEEN; - break; - - case a_i_SHEEN: - tempc = a_m_SHEEN; - break; - - case a_i_SAD: - tempc = a_m_SAD; - break; - - case a_i_DAD: - tempc = a_m_DAD; - break; - - case a_i_TAH: - tempc = a_m_TAH; - break; - - case a_i_ZAH: - tempc = a_m_ZAH; - break; - - case a_i_AIN: - tempc = a_m_AIN; - break; - - case a_i_GHAIN: - tempc = a_m_GHAIN; - break; - - case a_i_FEH: - tempc = a_m_FEH; - break; - - case a_i_QAF: - tempc = a_m_QAF; - break; - - case a_i_KAF: - tempc = a_m_KAF; - break; - - case a_i_LAM: - tempc = a_m_LAM; - break; - - case a_i_MEEM: - tempc = a_m_MEEM; - break; - - case a_i_NOON: - tempc = a_m_NOON; - break; - - case a_i_HEH: - tempc = a_m_HEH; - break; - - case a_i_YEH: - tempc = a_m_YEH; - break; - - default: - tempc = 0; + case a_i_YEH_HAMZA: return a_m_YEH_HAMZA; + case a_i_BEH: return a_m_BEH; + case a_i_TEH: return a_m_TEH; + case a_i_THEH: return a_m_THEH; + case a_i_JEEM: return a_m_JEEM; + case a_i_HAH: return a_m_HAH; + case a_i_KHAH: return a_m_KHAH; + case a_i_SEEN: return a_m_SEEN; + case a_i_SHEEN: return a_m_SHEEN; + case a_i_SAD: return a_m_SAD; + case a_i_DAD: return a_m_DAD; + case a_i_TAH: return a_m_TAH; + case a_i_ZAH: return a_m_ZAH; + case a_i_AIN: return a_m_AIN; + case a_i_GHAIN: return a_m_GHAIN; + case a_i_FEH: return a_m_FEH; + case a_i_QAF: return a_m_QAF; + case a_i_KAF: return a_m_KAF; + case a_i_LAM: return a_m_LAM; + case a_i_MEEM: return a_m_MEEM; + case a_i_NOON: return a_m_NOON; + case a_i_HEH: return a_m_HEH; + case a_i_YEH: return a_m_YEH; } - - return tempc; + return 0; } // Change shape - from Final to Medial static int chg_c_f2m(int cur_c) { - int tempc; - switch (cur_c) { // NOTE: these encodings are multi-positional, no ? // case a_f_ALEF_MADDA: // case a_f_ALEF_HAMZA_ABOVE: // case a_f_ALEF_HAMZA_BELOW: - case a_f_YEH_HAMZA: - tempc = a_m_YEH_HAMZA; - break; - + case a_f_YEH_HAMZA: return a_m_YEH_HAMZA; case a_f_WAW_HAMZA: // exceptions case a_f_ALEF: case a_f_TEH_MARBUTA: @@ -1171,165 +628,60 @@ static int chg_c_f2m(int cur_c) case a_f_ZAIN: case a_f_WAW: case a_f_ALEF_MAKSURA: - tempc = cur_c; - break; - - case a_f_BEH: - tempc = a_m_BEH; - break; - - case a_f_TEH: - tempc = a_m_TEH; - break; - - case a_f_THEH: - tempc = a_m_THEH; - break; - - case a_f_JEEM: - tempc = a_m_JEEM; - break; - - case a_f_HAH: - tempc = a_m_HAH; - break; - - case a_f_KHAH: - tempc = a_m_KHAH; - break; - - case a_f_SEEN: - tempc = a_m_SEEN; - break; - - case a_f_SHEEN: - tempc = a_m_SHEEN; - break; - - case a_f_SAD: - tempc = a_m_SAD; - break; - - case a_f_DAD: - tempc = a_m_DAD; - break; - - case a_f_TAH: - tempc = a_m_TAH; - break; - - case a_f_ZAH: - tempc = a_m_ZAH; - break; - - case a_f_AIN: - tempc = a_m_AIN; - break; - - case a_f_GHAIN: - tempc = a_m_GHAIN; - break; - - case a_f_FEH: - tempc = a_m_FEH; - break; - - case a_f_QAF: - tempc = a_m_QAF; - break; - - case a_f_KAF: - tempc = a_m_KAF; - break; - - case a_f_LAM: - tempc = a_m_LAM; - break; - - case a_f_MEEM: - tempc = a_m_MEEM; - break; - - case a_f_NOON: - tempc = a_m_NOON; - break; - - case a_f_HEH: - tempc = a_m_HEH; - break; - - case a_f_YEH: - tempc = a_m_YEH; - break; - + return cur_c; + case a_f_BEH: return a_m_BEH; + case a_f_TEH: return a_m_TEH; + case a_f_THEH: return a_m_THEH; + case a_f_JEEM: return a_m_JEEM; + case a_f_HAH: return a_m_HAH; + case a_f_KHAH: return a_m_KHAH; + case a_f_SEEN: return a_m_SEEN; + case a_f_SHEEN: return a_m_SHEEN; + case a_f_SAD: return a_m_SAD; + case a_f_DAD: return a_m_DAD; + case a_f_TAH: return a_m_TAH; + case a_f_ZAH: return a_m_ZAH; + case a_f_AIN: return a_m_AIN; + case a_f_GHAIN: return a_m_GHAIN; + case a_f_FEH: return a_m_FEH; + case a_f_QAF: return a_m_QAF; + case a_f_KAF: return a_m_KAF; + case a_f_LAM: return a_m_LAM; + case a_f_MEEM: return a_m_MEEM; + case a_f_NOON: return a_m_NOON; + case a_f_HEH: return a_m_HEH; + case a_f_YEH: return a_m_YEH; // NOTE: these encodings are multi-positional, no ? // case a_f_LAM_ALEF_MADDA_ABOVE: // case a_f_LAM_ALEF_HAMZA_ABOVE: // case a_f_LAM_ALEF_HAMZA_BELOW: // case a_f_LAM_ALEF: - default: - tempc = 0; } - - return tempc; + return 0; } // Change shape - from Combination (2 char) to an Isolated. static int chg_c_laa2i(int hid_c) { - int tempc; - switch (hid_c) { - case a_ALEF_MADDA: - tempc = a_s_LAM_ALEF_MADDA_ABOVE; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_s_LAM_ALEF_HAMZA_ABOVE; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_s_LAM_ALEF_HAMZA_BELOW; - break; - - case a_ALEF: - tempc = a_s_LAM_ALEF; - break; - - default: - tempc = 0; + case a_ALEF_MADDA: return a_s_LAM_ALEF_MADDA_ABOVE; + case a_ALEF_HAMZA_ABOVE: return a_s_LAM_ALEF_HAMZA_ABOVE; + case a_ALEF_HAMZA_BELOW: return a_s_LAM_ALEF_HAMZA_BELOW; + case a_ALEF: return a_s_LAM_ALEF; } - - return tempc; + return 0; } // Change shape - from Combination-Isolated to Final. static int chg_c_laa2f(int hid_c) { - int tempc; - switch (hid_c) { - case a_ALEF_MADDA: - tempc = a_f_LAM_ALEF_MADDA_ABOVE; - break; - - case a_ALEF_HAMZA_ABOVE: - tempc = a_f_LAM_ALEF_HAMZA_ABOVE; - break; - - case a_ALEF_HAMZA_BELOW: - tempc = a_f_LAM_ALEF_HAMZA_BELOW; - break; - - case a_ALEF: - tempc = a_f_LAM_ALEF; - break; - - default: - tempc = 0; + case a_ALEF_MADDA: return a_f_LAM_ALEF_MADDA_ABOVE; + case a_ALEF_HAMZA_ABOVE: return a_f_LAM_ALEF_HAMZA_ABOVE; + case a_ALEF_HAMZA_BELOW: return a_f_LAM_ALEF_HAMZA_BELOW; + case a_ALEF: return a_f_LAM_ALEF; } - - return tempc; + return 0; } // Do "half-shaping" on character "c". Return zero if no shaping. diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h index adde91f9ec..ff6840d690 100644 --- a/src/nvim/ascii.h +++ b/src/nvim/ascii.h @@ -3,6 +3,7 @@ #include <stdbool.h> +#include "nvim/macros.h" #include "nvim/func_attr.h" #include "nvim/os/os_defs.h" @@ -98,6 +99,10 @@ static inline bool ascii_isxdigit(int) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; +static inline bool ascii_isident(int) + REAL_FATTR_CONST + REAL_FATTR_ALWAYS_INLINE; + static inline bool ascii_isbdigit(int) REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE; @@ -138,6 +143,14 @@ static inline bool ascii_isxdigit(int c) || (c >= 'A' && c <= 'F'); } +/// Checks if `c` is an “identifier” character +/// +/// That is, whether it is alphanumeric character or underscore. +static inline bool ascii_isident(int c) +{ + return ASCII_ISALNUM(c) || c == '_'; +} + /// Checks if `c` is a binary digit, that is, 0-1. /// /// @see {ascii_isdigit} diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c index 766003a021..0cd6f628b5 100644 --- a/src/nvim/buffer.c +++ b/src/nvim/buffer.c @@ -129,7 +129,7 @@ read_buffer( if (read_stdin) { // Set or reset 'modified' before executing autocommands, so that // it can be changed there. - if (!readonlymode && !bufempty()) { + if (!readonlymode && !BUFEMPTY()) { changed(); } else if (retval != FAIL) { unchanged(curbuf, false); @@ -1387,31 +1387,40 @@ void set_curbuf(buf_T *buf, int action) /* Don't restart Select mode after switching to another buffer. */ VIsual_reselect = FALSE; - /* close_windows() or apply_autocmds() may change curbuf */ + // close_windows() or apply_autocmds() may change curbuf and wipe out "buf" prevbuf = curbuf; - bufref_T bufref; - set_bufref(&bufref, prevbuf); + bufref_T newbufref; + bufref_T prevbufref; + set_bufref(&prevbufref, prevbuf); + set_bufref(&newbufref, buf); + // Autocommands may delete the curren buffer and/or the buffer we wan to go + // to. In those cases don't close the buffer. if (!apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf) - || (bufref_valid(&bufref) && !aborting())) { + || (bufref_valid(&prevbufref) && bufref_valid(&newbufref) + && !aborting())) { if (prevbuf == curwin->w_buffer) { reset_synblock(curwin); } if (unload) { close_windows(prevbuf, false); } - if (bufref_valid(&bufref) && !aborting()) { + if (bufref_valid(&prevbufref) && !aborting()) { win_T *previouswin = curwin; - if (prevbuf == curbuf) - u_sync(FALSE); - close_buffer(prevbuf == curwin->w_buffer ? curwin : NULL, prevbuf, - unload ? action : (action == DOBUF_GOTO - && !P_HID(prevbuf) - && !bufIsChanged( - prevbuf)) ? DOBUF_UNLOAD : 0, FALSE); - if (curwin != previouswin && win_valid(previouswin)) - /* autocommands changed curwin, Grr! */ + if (prevbuf == curbuf) { + u_sync(false); + } + close_buffer(prevbuf == curwin->w_buffer ? curwin : NULL, + prevbuf, + unload + ? action + : (action == DOBUF_GOTO && !buf_hide(prevbuf) + && !bufIsChanged(prevbuf)) ? DOBUF_UNLOAD : 0, + false); + if (curwin != previouswin && win_valid(previouswin)) { + // autocommands changed curwin, Grr! curwin = previouswin; + } } } /* An autocommand may have deleted "buf", already entered it (e.g., when @@ -1616,7 +1625,7 @@ buf_T * buflist_new(char_u *ffname, char_u *sfname, linenr_T lnum, int flags) && curbuf != NULL && curbuf->b_ffname == NULL && curbuf->b_nwindows <= 1 - && (curbuf->b_ml.ml_mfp == NULL || bufempty())) { + && (curbuf->b_ml.ml_mfp == NULL || BUFEMPTY())) { buf = curbuf; /* It's like this buffer is deleted. Watch out for autocommands that * change curbuf! If that happens, allocate a new buffer anyway. */ @@ -1775,6 +1784,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) clear_string_option(&buf->b_p_flp); clear_string_option(&buf->b_p_isk); clear_string_option(&buf->b_p_keymap); + keymap_ga_clear(&buf->b_kmap_ga); ga_clear(&buf->b_kmap_ga); clear_string_option(&buf->b_p_com); clear_string_option(&buf->b_p_cms); @@ -1808,6 +1818,7 @@ void free_buf_options(buf_T *buf, int free_p_ff) buf->b_p_ul = NO_LOCAL_UNDOLEVEL; clear_string_option(&buf->b_p_lw); clear_string_option(&buf->b_p_bkc); + clear_string_option(&buf->b_p_menc); } @@ -1870,7 +1881,7 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit) // If 'switchbuf' contains "split", "vsplit" or "newtab" and the // current buffer isn't empty: open new tab or window if (wp == NULL && (swb_flags & (SWB_VSPLIT | SWB_SPLIT | SWB_NEWTAB)) - && !bufempty()) { + && !BUFEMPTY()) { if (swb_flags & SWB_NEWTAB) { tabpage_new(); } else if (win_split(0, (swb_flags & SWB_VSPLIT) ? WSP_VERT : 0) @@ -2672,7 +2683,7 @@ void buflist_altfpos(win_T *win) } /// Check that "ffname" is not the same file as current file. -/// Fname must have a full path (expanded by path_get_absolute_path()). +/// Fname must have a full path (expanded by path_to_absolute()). /// /// @param ffname full path name to check bool otherfile(char_u *ffname) @@ -2682,7 +2693,7 @@ bool otherfile(char_u *ffname) } /// Check that "ffname" is not the same file as the file loaded in "buf". -/// Fname must have a full path (expanded by path_get_absolute_path()). +/// Fname must have a full path (expanded by path_to_absolute()). /// /// @param buf buffer to check /// @param ffname full path name to check @@ -4406,12 +4417,12 @@ do_arg_all ( } wp->w_arg_idx = i; - if (i == opened_len && !keep_tabs) { /* close this window */ - if (P_HID(buf) || forceit || buf->b_nwindows > 1 + if (i == opened_len && !keep_tabs) { // close this window + if (buf_hide(buf) || forceit || buf->b_nwindows > 1 || !bufIsChanged(buf)) { /* If the buffer was changed, and we would like to hide it, * try autowriting. */ - if (!P_HID(buf) && buf->b_nwindows <= 1 && bufIsChanged(buf)) { + if (!buf_hide(buf) && buf->b_nwindows <= 1 && bufIsChanged(buf)) { bufref_T bufref; set_bufref(&bufref, buf); (void)autowrite(buf, false); @@ -4426,7 +4437,7 @@ do_arg_all ( && (first_tabpage->tp_next == NULL || !had_tab)) { use_firstwin = true; } else { - win_close(wp, !P_HID(buf) && !bufIsChanged(buf)); + win_close(wp, !buf_hide(buf) && !bufIsChanged(buf)); // check if autocommands removed the next window if (!win_valid(wpnext)) { // start all over... @@ -4460,11 +4471,12 @@ do_arg_all ( last_curwin = curwin; last_curtab = curtab; win_enter(lastwin, false); - /* ":drop all" should re-use an empty window to avoid "--remote-tab" - * leaving an empty tab page when executed locally. */ - if (keep_tabs && bufempty() && curbuf->b_nwindows == 1 - && curbuf->b_ffname == NULL && !curbuf->b_changed) - use_firstwin = TRUE; + // ":drop all" should re-use an empty window to avoid "--remote-tab" + // leaving an empty tab page when executed locally. + if (keep_tabs && BUFEMPTY() && curbuf->b_nwindows == 1 + && curbuf->b_ffname == NULL && !curbuf->b_changed) { + use_firstwin = true; + } for (i = 0; i < count && i < opened_len && !got_int; ++i) { if (alist == &global_alist && i == global_alist.al_ga.ga_len - 1) @@ -4503,14 +4515,15 @@ do_arg_all ( new_curwin = curwin; new_curtab = curtab; } - (void)do_ecmd(0, alist_name(&AARGLIST(alist)[i]), NULL, NULL, - ECMD_ONE, - ((P_HID(curwin->w_buffer) - || bufIsChanged(curwin->w_buffer)) ? ECMD_HIDE : 0) - + ECMD_OLDBUF, curwin); - if (use_firstwin) - ++autocmd_no_leave; - use_firstwin = FALSE; + (void)do_ecmd(0, alist_name(&AARGLIST(alist)[i]), NULL, NULL, ECMD_ONE, + ((buf_hide(curwin->w_buffer) + || bufIsChanged(curwin->w_buffer)) + ? ECMD_HIDE : 0) + ECMD_OLDBUF, + curwin); + if (use_firstwin) { + autocmd_no_leave++; + } + use_firstwin = false; } os_breakcheck(); @@ -4697,14 +4710,14 @@ void ex_buffer_all(exarg_T *eap) * Close superfluous windows. */ for (wp = lastwin; open_wins > count; ) { - r = (P_HID(wp->w_buffer) || !bufIsChanged(wp->w_buffer) - || autowrite(wp->w_buffer, FALSE) == OK); + r = (buf_hide(wp->w_buffer) || !bufIsChanged(wp->w_buffer) + || autowrite(wp->w_buffer, false) == OK); if (!win_valid(wp)) { /* BufWrite Autocommands made the window invalid, start over */ wp = lastwin; } else if (r) { - win_close(wp, !P_HID(wp->w_buffer)); - --open_wins; + win_close(wp, !buf_hide(wp->w_buffer)); + open_wins--; wp = lastwin; } else { wp = wp->w_prev; diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h index f1cbcb2627..8de4286216 100644 --- a/src/nvim/buffer_defs.h +++ b/src/nvim/buffer_defs.h @@ -637,6 +637,7 @@ struct file_buffer { uint32_t b_p_fex_flags; ///< flags for 'formatexpr' char_u *b_p_kp; ///< 'keywordprg' int b_p_lisp; ///< 'lisp' + char_u *b_p_menc; ///< 'makeencoding' char_u *b_p_mps; ///< 'matchpairs' int b_p_ml; ///< 'modeline' int b_p_ml_nobin; ///< b_p_ml saved for binary mode @@ -717,6 +718,7 @@ struct file_buffer { int b_ind_hash_comment; int b_ind_cpp_namespace; int b_ind_if_for_while; + int b_ind_cpp_extern_c; linenr_T b_no_eol_lnum; /* non-zero lnum when last line of next binary * write should not have an end-of-line */ diff --git a/src/nvim/channel.c b/src/nvim/channel.c index 40af470bde..776e2bfa86 100644 --- a/src/nvim/channel.c +++ b/src/nvim/channel.c @@ -4,6 +4,7 @@ #include "nvim/api/ui.h" #include "nvim/channel.h" #include "nvim/eval.h" +#include "nvim/eval/encode.h" #include "nvim/event/socket.h" #include "nvim/msgpack_rpc/channel.h" #include "nvim/msgpack_rpc/server.h" @@ -179,10 +180,12 @@ static Channel *channel_alloc(ChannelStreamType type) } /// Not implemented, only logging for now -void channel_create_event(Channel *chan, char *ext_source) +void channel_create_event(Channel *chan, const char *ext_source) { #if MIN_LOG_LEVEL <= INFO_LOG_LEVEL - char *stream_desc, *mode_desc, *source; + const char *stream_desc; + const char *mode_desc; + const char *source; switch (chan->streamtype) { case kChannelStreamProc: @@ -222,8 +225,8 @@ void channel_create_event(Channel *chan, char *ext_source) // external events should be included. source = ext_source; } else { - eval_format_source_name_line((char *)IObuff, sizeof(IObuff)); - source = (char *)IObuff; + eval_fmt_source_name_line((char *)IObuff, sizeof(IObuff)); + source = (const char *)IObuff; } ILOG("new channel %" PRIu64 " (%s%s): %s", chan->id, stream_desc, @@ -234,15 +237,16 @@ void channel_create_event(Channel *chan, char *ext_source) #endif } -void channel_incref(Channel *channel) +void channel_incref(Channel *chan) { - channel->refcount++; + chan->refcount++; } -void channel_decref(Channel *channel) +void channel_decref(Channel *chan) { - if (!(--channel->refcount)) { - multiqueue_put(main_loop.fast_events, free_channel_event, 1, channel); + if (!(--chan->refcount)) { + // delay free, so that libuv is done with the handles + multiqueue_put(main_loop.events, free_channel_event, 1, chan); } } @@ -264,18 +268,18 @@ void callback_reader_start(CallbackReader *reader) static void free_channel_event(void **argv) { - Channel *channel = argv[0]; - if (channel->is_rpc) { - rpc_free(channel); + Channel *chan = argv[0]; + if (chan->is_rpc) { + rpc_free(chan); } - callback_reader_free(&channel->on_stdout); - callback_reader_free(&channel->on_stderr); - callback_free(&channel->on_exit); + callback_reader_free(&chan->on_stdout); + callback_reader_free(&chan->on_stderr); + callback_free(&chan->on_exit); - pmap_del(uint64_t)(channels, channel->id); - multiqueue_free(channel->events); - xfree(channel); + pmap_del(uint64_t)(channels, chan->id); + multiqueue_free(chan->events); + xfree(chan); } static void channel_destroy_early(Channel *chan) @@ -283,12 +287,15 @@ static void channel_destroy_early(Channel *chan) if ((chan->id != --next_chan_id)) { abort(); } + pmap_del(uint64_t)(channels, chan->id); + chan->id = 0; if ((--chan->refcount != 0)) { abort(); } - free_channel_event((void **)&chan); + // uv will keep a reference to handles until next loop tick, so delay free + multiqueue_put(main_loop.events, free_channel_event, 1, chan); } @@ -391,17 +398,22 @@ uint64_t channel_connect(bool tcp, const char *address, bool rpc, CallbackReader on_output, int timeout, const char **error) { + Channel *channel; + if (!tcp && rpc) { char *path = fix_fname(address); - if (server_owns_pipe_address(path)) { - // avoid deadlock - xfree(path); - return channel_create_internal_rpc(); - } + bool loopback = server_owns_pipe_address(path); xfree(path); + if (loopback) { + // Create a loopback channel. This avoids deadlock if nvim connects to + // its own named pipe. + channel = channel_alloc(kChannelStreamInternal); + rpc_start(channel); + goto end; + } } - Channel *channel = channel_alloc(kChannelStreamSocket); + channel = channel_alloc(kChannelStreamSocket); if (!socket_connect(&main_loop, &channel->stream.socket, tcp, address, timeout, error)) { channel_destroy_early(channel); @@ -421,7 +433,8 @@ uint64_t channel_connect(bool tcp, const char *address, rstream_start(&channel->stream.socket, on_socket_output, channel); } - channel_create_event(channel, NULL); +end: + channel_create_event(channel, address); return channel->id; } @@ -440,15 +453,6 @@ void channel_from_connection(SocketWatcher *watcher) channel_create_event(channel, watcher->addr); } -/// Creates a loopback channel. This is used to avoid deadlock -/// when an instance connects to its own named pipe. -static uint64_t channel_create_internal_rpc(void) -{ - Channel *channel = channel_alloc(kChannelStreamInternal); - rpc_start(channel); - return channel->id; -} - /// Creates an API channel from stdin/stdout. This is used when embedding /// Neovim uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, @@ -522,32 +526,21 @@ err: return 0; } -/// NB: mutates buf in place! -static list_T *buffer_to_tv_list(char *buf, size_t count) +/// Convert binary byte array to a readfile()-style list +/// +/// @param[in] buf Array to convert. +/// @param[in] len Array length. +/// +/// @return [allocated] Converted list. +static inline list_T *buffer_to_tv_list(const char *const buf, const size_t len) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE { - list_T *ret = tv_list_alloc(); - char *ptr = buf; - size_t remaining = count; - size_t off = 0; - - while (off < remaining) { - // append the line - if (ptr[off] == NL) { - tv_list_append_string(ret, ptr, (ssize_t)off); - size_t skip = off + 1; - ptr += skip; - remaining -= skip; - off = 0; - continue; - } - if (ptr[off] == NUL) { - // Translate NUL to NL - ptr[off] = NL; - } - off++; - } - tv_list_append_string(ret, ptr, (ssize_t)off); - return ret; + list_T *const l = tv_list_alloc(kListLenMayKnow); + // Empty buffer should be represented by [''], encode_list_write() thinks + // empty list is fine for the case. + tv_list_append_string(l, "", 0); + encode_list_write(l, buf, len); + return l; } // vimscript job callbacks must be executed on Nvim main loop @@ -599,6 +592,7 @@ static void on_stdio_input(Stream *stream, RBuffer *buf, size_t count, on_channel_output(stream, chan, buf, count, eof, &chan->on_stdout, "stdin"); } +/// @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) @@ -613,14 +607,20 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, if (reader->cb.type != kCallbackNone) { process_channel_event(chan, &reader->cb, type, reader->buffer.ga_data, (size_t)reader->buffer.ga_len, 0); - ga_clear(&reader->buffer); } else if (reader->self) { - 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); + 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); + } } else { abort(); } + ga_clear(&reader->buffer); } else if (reader->cb.type != kCallbackNone) { process_channel_event(chan, &reader->cb, type, ptr, 0, 0); } @@ -641,6 +641,14 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, } } +static void on_buffered_error(void **args) +{ + Channel *chan = (Channel *)args[0]; + const char *stream = (const char *)args[1]; + EMSG3(_(e_streamkey), stream, chan->id); + channel_decref(chan); +} + static void channel_process_exit_cb(Process *proc, int status, void *data) { Channel *chan = data; @@ -673,7 +681,7 @@ static void on_channel_event(void **args) argv[1].v_type = VAR_LIST; argv[1].v_lock = VAR_UNLOCKED; argv[1].vval.v_list = ev->received; - argv[1].vval.v_list->lv_refcount++; + tv_list_ref(argv[1].vval.v_list); } else { argv[1].v_type = VAR_NUMBER; argv[1].v_lock = VAR_UNLOCKED; diff --git a/src/nvim/charset.c b/src/nvim/charset.c index 577fc13a31..980b4ed426 100644 --- a/src/nvim/charset.c +++ b/src/nvim/charset.c @@ -1611,141 +1611,145 @@ bool vim_isblankline(char_u *lbuf) /// If maxlen > 0, check at a maximum maxlen chars. /// /// @param start -/// @param prep Returns type of number 0 = decimal, 'x' or 'X' is hex, -/// '0' = octal, 'b' or 'B' is bin +/// @param prep Returns guessed type of number 0 = decimal, 'x' or 'X' is +/// hexadecimal, '0' = octal, 'b' or 'B' is binary. When using +/// STR2NR_FORCE is always zero. /// @param len Returns the detected length of number. -/// @param what Recognizes what number passed. +/// @param what Recognizes what number passed, @see ChStr2NrFlags. /// @param nptr Returns the signed result. /// @param unptr Returns the unsigned result. /// @param maxlen Max length of string to check. void vim_str2nr(const char_u *const start, int *const prep, int *const len, const int what, varnumber_T *const nptr, uvarnumber_T *const unptr, const int maxlen) + FUNC_ATTR_NONNULL_ARG(1) { - const char_u *ptr = start; + const char *ptr = (const char *)start; +#define STRING_ENDED(ptr) \ + (!(maxlen == 0 || (int)((ptr) - (const char *)start) < maxlen)) int pre = 0; // default is decimal - bool negative = false; + const bool negative = (ptr[0] == '-'); uvarnumber_T un = 0; - if (ptr[0] == '-') { - negative = true; + if (negative) { ptr++; } - // Recognize hex, octal and bin. - if ((ptr[0] == '0') && (ptr[1] != '8') && (ptr[1] != '9') - && (maxlen == 0 || maxlen > 1)) { + if (what & STR2NR_FORCE) { + // When forcing main consideration is skipping the prefix. Octal and decimal + // numbers have no prefixes to skip. pre is not set. + switch ((unsigned)what & (~(unsigned)STR2NR_FORCE)) { + case STR2NR_HEX: { + if (!STRING_ENDED(ptr + 2) + && ptr[0] == '0' + && (ptr[1] == 'x' || ptr[1] == 'X') + && ascii_isxdigit(ptr[2])) { + ptr += 2; + } + goto vim_str2nr_hex; + } + case STR2NR_BIN: { + if (!STRING_ENDED(ptr + 2) + && ptr[0] == '0' + && (ptr[1] == 'b' || ptr[1] == 'B') + && ascii_isbdigit(ptr[2])) { + ptr += 2; + } + goto vim_str2nr_bin; + } + case STR2NR_OCT: { + goto vim_str2nr_oct; + } + case 0: { + goto vim_str2nr_dec; + } + default: { + assert(false); + } + } + } else if ((what & (STR2NR_HEX|STR2NR_OCT|STR2NR_BIN)) + && !STRING_ENDED(ptr + 1) + && ptr[0] == '0' && ptr[1] != '8' && ptr[1] != '9') { pre = ptr[1]; - + // Detect hexadecimal: 0x or 0X follwed by hex digit if ((what & STR2NR_HEX) - && ((pre == 'X') || (pre == 'x')) - && ascii_isxdigit(ptr[2]) - && (maxlen == 0 || maxlen > 2)) { - // hexadecimal + && !STRING_ENDED(ptr + 2) + && (pre == 'X' || pre == 'x') + && ascii_isxdigit(ptr[2])) { ptr += 2; - } else if ((what & STR2NR_BIN) - && ((pre == 'B') || (pre == 'b')) - && ascii_isbdigit(ptr[2]) - && (maxlen == 0 || maxlen > 2)) { - // binary + goto vim_str2nr_hex; + } + // Detect binary: 0b or 0B follwed by 0 or 1 + if ((what & STR2NR_BIN) + && !STRING_ENDED(ptr + 2) + && (pre == 'B' || pre == 'b') + && ascii_isbdigit(ptr[2])) { ptr += 2; - } else { - // decimal or octal, default is decimal - pre = 0; - - if (what & STR2NR_OCT) { - // Don't interpret "0", "08" or "0129" as octal. - for (int n = 1; ascii_isdigit(ptr[n]); ++n) { - if (ptr[n] > '7') { - // can't be octal - pre = 0; - break; - } - if (ptr[n] >= '0') { - // assume octal - pre = '0'; - } - if (n == maxlen) { - break; - } - } + goto vim_str2nr_bin; + } + // Detect octal number: zero followed by octal digits without '8' or '9' + pre = 0; + if (!(what & STR2NR_OCT) + || !('0' <= ptr[1] && ptr[1] <= '7')) { + goto vim_str2nr_dec; + } + for (int i = 2; !STRING_ENDED(ptr + i) && ascii_isdigit(ptr[i]); i++) { + if (ptr[i] > '7') { + goto vim_str2nr_dec; } } + pre = '0'; + goto vim_str2nr_oct; + } else { + goto vim_str2nr_dec; } // Do the string-to-numeric conversion "manually" to avoid sscanf quirks. - int n = 1; - if ((pre == 'B') || (pre == 'b') || what == STR2NR_BIN + STR2NR_FORCE) { - // bin - if (pre != 0) { - n += 2; // skip over "0b" + assert(false); // Should’ve used goto earlier. +#define PARSE_NUMBER(base, cond, conv) \ + do { \ + while (!STRING_ENDED(ptr) && (cond)) { \ + /* avoid ubsan error for overflow */ \ + if (un < UVARNUMBER_MAX / base) { \ + un = base * un + (uvarnumber_T)(conv); \ + } else { \ + un = UVARNUMBER_MAX; \ + } \ + ptr++; \ + } \ + } while (0) + switch (pre) { + case 'b': + case 'B': { +vim_str2nr_bin: + PARSE_NUMBER(2, (*ptr == '0' || *ptr == '1'), (*ptr - '0')); + break; } - while ('0' <= *ptr && *ptr <= '1') { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 2) { - un = 2 * un + (uvarnumber_T)(*ptr - '0'); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - if (n++ == maxlen) { - break; - } - } - } else if ((pre == '0') || what == STR2NR_OCT + STR2NR_FORCE) { - // octal - while ('0' <= *ptr && *ptr <= '7') { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 8) { - un = 8 * un + (uvarnumber_T)(*ptr - '0'); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - if (n++ == maxlen) { - break; - } + case '0': { +vim_str2nr_oct: + PARSE_NUMBER(8, ('0' <= *ptr && *ptr <= '7'), (*ptr - '0')); + break; } - } else if ((pre == 'X') || (pre == 'x') - || what == STR2NR_HEX + STR2NR_FORCE) { - // hex - if (pre != 0) { - n += 2; // skip over "0x" - } - while (ascii_isxdigit(*ptr)) { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 16) { - un = 16 * un + (uvarnumber_T)hex2nr(*ptr); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - if (n++ == maxlen) { - break; - } + case 0: { +vim_str2nr_dec: + PARSE_NUMBER(10, (ascii_isdigit(*ptr)), (*ptr - '0')); + break; } - } else { - // decimal - while (ascii_isdigit(*ptr)) { - // avoid ubsan error for overflow - if (un < UVARNUMBER_MAX / 10) { - un = 10 * un + (uvarnumber_T)(*ptr - '0'); - } else { - un = UVARNUMBER_MAX; - } - ptr++; - if (n++ == maxlen) { - break; - } + case 'x': + case 'X': { +vim_str2nr_hex: + PARSE_NUMBER(16, (ascii_isxdigit(*ptr)), (hex2nr(*ptr))); + break; } } +#undef PARSE_NUMBER if (prep != NULL) { *prep = pre; } if (len != NULL) { - *len = (int)(ptr - start); + *len = (int)(ptr - (const char *)start); } if (nptr != NULL) { @@ -1767,6 +1771,7 @@ void vim_str2nr(const char_u *const start, int *const prep, int *const len, if (unptr != NULL) { *unptr = un; } +#undef STRING_ENDED } /// Return the value of a single hex character. diff --git a/src/nvim/charset.h b/src/nvim/charset.h index c69582c4c6..eb64b6128a 100644 --- a/src/nvim/charset.h +++ b/src/nvim/charset.h @@ -4,6 +4,7 @@ #include "nvim/types.h" #include "nvim/pos.h" #include "nvim/buffer_defs.h" +#include "nvim/eval/typval.h" /// Return the folded-case equivalent of the given character /// @@ -15,6 +16,21 @@ ?((int)(uint8_t)(c)) \ :((int)(c))) +/// Flags for vim_str2nr() +typedef enum { + STR2NR_DEC = 0, + STR2NR_BIN = (1 << 0), ///< Allow binary numbers. + STR2NR_OCT = (1 << 1), ///< Allow octal numbers. + STR2NR_HEX = (1 << 2), ///< Allow hexadecimal numbers. + /// Force one of the above variants. + /// + /// STR2NR_FORCE|STR2NR_DEC is actually not different from supplying zero + /// as flags, but still present for completeness. + STR2NR_FORCE = (1 << 3), + /// Recognize all formats vim_str2nr() can recognize. + STR2NR_ALL = STR2NR_BIN | STR2NR_OCT | STR2NR_HEX, +} ChStr2NrFlags; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "charset.h.generated.h" #endif diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c index 97fc3a3ca3..b45e7002f7 100644 --- a/src/nvim/cursor_shape.c +++ b/src/nvim/cursor_shape.c @@ -102,11 +102,14 @@ char_u *parse_shape_opt(int what) } while (*modep != NUL) { colonp = vim_strchr(modep, ':'); - if (colonp == NULL) + commap = vim_strchr(modep, ','); + + if (colonp == NULL || (commap != NULL && commap < colonp)) { return (char_u *)N_("E545: Missing colon"); - if (colonp == modep) + } + if (colonp == modep) { return (char_u *)N_("E546: Illegal mode"); - commap = vim_strchr(modep, ','); + } // Repeat for all modes before the colon. // For the 'a' mode, we loop to handle all the modes. diff --git a/src/nvim/diff.c b/src/nvim/diff.c index cc0f3b2629..0ee1c3815d 100644 --- a/src/nvim/diff.c +++ b/src/nvim/diff.c @@ -855,12 +855,13 @@ void ex_diffpatch(exarg_T *eap) { char_u *buf = NULL; win_T *old_curwin = curwin; - char_u *newname = NULL; // name of patched file buffer + char_u *newname = NULL; // name of patched file buffer + char_u *esc_name = NULL; #ifdef UNIX char_u dirbuf[MAXPATHL]; char_u *fullname = NULL; -#endif // ifdef UNIX +#endif // We need two temp file names. // Name of original temp file. char_u *tmp_orig = vim_tempname(); @@ -880,21 +881,21 @@ void ex_diffpatch(exarg_T *eap) #ifdef UNIX // Get the absolute path of the patchfile, changing directory below. - fullname = (char_u *)FullName_save((char *)eap->arg, FALSE); -#endif // ifdef UNIX + fullname = (char_u *)FullName_save((char *)eap->arg, false); +#endif + esc_name = vim_strsave_shellescape( #ifdef UNIX - size_t buflen = STRLEN(tmp_orig) - + (fullname != NULL ? STRLEN(fullname) : STRLEN(eap->arg)) - + STRLEN(tmp_new) + 16; -#else - size_t buflen = STRLEN(tmp_orig) + (STRLEN(eap->arg)) + STRLEN(tmp_new) + 16; -#endif // ifdef UNIX - + fullname != NULL ? fullname : +#endif + eap->arg, true, true); + if (esc_name == NULL) { + goto theend; + } + size_t buflen = STRLEN(tmp_orig) + STRLEN(esc_name) + STRLEN(tmp_new) + 16; buf = xmalloc(buflen); #ifdef UNIX - // Temporarily chdir to /tmp, to avoid patching files in the current // directory when the patch file contains more than one patch. When we // have our own temp dir use that instead, it will be cleaned up when we @@ -911,26 +912,21 @@ void ex_diffpatch(exarg_T *eap) os_chdir(tempdir); shorten_fnames(TRUE); } -#endif // ifdef UNIX +#endif if (*p_pex != NUL) { // Use 'patchexpr' to generate the new file. #ifdef UNIX - eval_patch((char *) tmp_orig, - (char *) (fullname != NULL ? fullname : eap->arg), - (char *) tmp_new); + eval_patch((char *)tmp_orig, + (char *)(fullname != NULL ? fullname : eap->arg), + (char *)tmp_new); #else - eval_patch((char *) tmp_orig, (char *) eap->arg, (char *) tmp_new); -#endif // ifdef UNIX + eval_patch((char *)tmp_orig, (char *)eap->arg, (char *)tmp_new); +#endif } else { // Build the patch command and execute it. Ignore errors. -#ifdef UNIX - vim_snprintf((char *)buf, buflen, "patch -o %s %s < \"%s\"", - tmp_new, tmp_orig, fullname != NULL ? fullname : eap->arg); -#else - vim_snprintf((char *)buf, buflen, "patch -o %s %s < \"%s\"", - tmp_new, tmp_orig, eap->arg); -#endif // ifdef UNIX + vim_snprintf((char *)buf, buflen, "patch -o %s %s < %s", + tmp_new, tmp_orig, esc_name); block_autocmds(); // Avoid ShellCmdPost stuff (void)call_shell(buf, kShellOptFilter, NULL); unblock_autocmds(); @@ -943,10 +939,7 @@ void ex_diffpatch(exarg_T *eap) } shorten_fnames(TRUE); } -#endif // ifdef UNIX - - // patch probably has written over the screen - redraw_later(CLEAR); +#endif // Delete any .orig or .rej file created. STRCPY(buf, tmp_new); @@ -1012,7 +1005,8 @@ theend: xfree(buf); #ifdef UNIX xfree(fullname); -#endif // ifdef UNIX +#endif + xfree(esc_name); } /// Split the window and edit another file, setting options to show the diffs. @@ -1064,6 +1058,20 @@ void ex_diffthis(exarg_T *eap) diff_win_options(curwin, TRUE); } +static void set_diff_option(win_T *wp, int value) +{ + win_T *old_curwin = curwin; + + curwin = wp; + curbuf = curwin->w_buffer; + curbuf_lock++; + set_option_value("diff", (long)value, NULL, OPT_LOCAL); + curbuf_lock--; + curwin = old_curwin; + curbuf = curwin->w_buffer; +} + + /// Set options in window "wp" for diff mode. /// /// @param addbuf Add buffer to diff. @@ -1121,10 +1129,10 @@ void diff_win_options(win_T *wp, int addbuf) do_cmdline_cmd("set sbo+=hor"); } - // Saved the current values, to be restored in ex_diffoff(). - wp->w_p_diff_saved = TRUE; + // Save the current values, to be restored in ex_diffoff(). + wp->w_p_diff_saved = true; - wp->w_p_diff = true; + set_diff_option(wp, true); if (addbuf) { diff_buf_add(wp->w_buffer); @@ -1145,7 +1153,7 @@ void ex_diffoff(exarg_T *eap) // Set 'diff' off. If option values were saved in // diff_win_options(), restore the ones whose settings seem to have // been left over from diff mode. - wp->w_p_diff = false; + set_diff_option(wp, false); if (wp->w_p_diff_saved) { if (wp->w_p_scb) { @@ -1161,7 +1169,9 @@ void ex_diffoff(exarg_T *eap) } free_string_option(wp->w_p_fdm); - wp->w_p_fdm = vim_strsave(wp->w_p_fdm_save); + wp->w_p_fdm = vim_strsave(*wp->w_p_fdm_save + ? wp->w_p_fdm_save + : (char_u *)"manual"); if (wp->w_p_fdc == diff_foldcolumn) { wp->w_p_fdc = wp->w_p_fdc_save; } @@ -2271,7 +2281,7 @@ void ex_diffgetput(exarg_T *eap) } } - buf_empty = bufempty(); + buf_empty = BUFEMPTY(); added = 0; for (i = 0; i < count; ++i) { diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c index dbcc8db109..bc4c12e0b7 100644 --- a/src/nvim/digraph.c +++ b/src/nvim/digraph.c @@ -1841,6 +1841,16 @@ void ex_loadkeymap(exarg_T *eap) status_redraw_curbuf(); } +/// Frees the buf_T.b_kmap_ga field of a buffer. +void keymap_ga_clear(garray_T *kmap_ga) +{ + kmap_T *kp = (kmap_T *)kmap_ga->ga_data; + for (int i = 0; i < kmap_ga->ga_len; i++) { + xfree(kp[i].from); + xfree(kp[i].to); + } +} + /// Stop using 'keymap'. static void keymap_unload(void) { @@ -1858,12 +1868,11 @@ static void keymap_unload(void) // clear the ":lmap"s kp = (kmap_T *)curbuf->b_kmap_ga.ga_data; - for (int i = 0; i < curbuf->b_kmap_ga.ga_len; ++i) { + for (int i = 0; i < curbuf->b_kmap_ga.ga_len; i++) { vim_snprintf((char *)buf, sizeof(buf), "<buffer> %s", kp[i].from); - (void)do_map(1, buf, LANGMAP, FALSE); - xfree(kp[i].from); - xfree(kp[i].to); + (void)do_map(1, buf, LANGMAP, false); } + keymap_ga_clear(&curbuf->b_kmap_ga); p_cpo = save_cpo; diff --git a/src/nvim/edit.c b/src/nvim/edit.c index 2bafb77fef..a0f6ce152b 100644 --- a/src/nvim/edit.c +++ b/src/nvim/edit.c @@ -847,7 +847,7 @@ static int insert_handle_key(InsertState *s) case ' ': - if (mod_mask != 4) { + if (mod_mask != MOD_MASK_CTRL) { goto normalchar; } // FALLTHROUGH @@ -974,6 +974,10 @@ static int insert_handle_key(InsertState *s) multiqueue_process_events(main_loop.events); break; + case K_COMMAND: // some command + do_cmdline(NULL, getcmdkeycmd, NULL, 0); + break; + case K_HOME: // <Home> case K_KHOME: case K_S_HOME: @@ -1176,6 +1180,14 @@ static int insert_handle_key(InsertState *s) normalchar: // Insert a normal character. + + if (mod_mask == MOD_MASK_ALT || mod_mask == MOD_MASK_META) { + // Unmapped ALT/META chord behaves like ESC+c. #8213 + stuffcharReadbuff(ESC); + stuffcharReadbuff(s->c); + break; + } + if (!p_paste) { // Trigger InsertCharPre. char_u *str = do_insert_char_pre(s->c); @@ -1428,7 +1440,7 @@ static void ins_ctrl_v(void) * line and will not removed by the redraw */ edit_unputchar(); clear_showcmd(); - insert_special(c, FALSE, TRUE); + insert_special(c, true, true); revins_chars++; revins_legal++; } @@ -1927,7 +1939,7 @@ bool vim_is_ctrl_x_key(int c) case CTRL_X_EVAL: return (c == Ctrl_P || c == Ctrl_N); } - EMSG(_(e_internal)); + internal_error("vim_is_ctrl_x_key()"); return false; } @@ -3536,19 +3548,19 @@ theend: /* * Add completions from a list. */ -static void ins_compl_add_list(list_T *list) +static void ins_compl_add_list(list_T *const list) { - listitem_T *li; int dir = compl_direction; - /* Go through the List with matches and add each of them. */ - for (li = list->lv_first; li != NULL; li = li->li_next) { - if (ins_compl_add_tv(&li->li_tv, dir) == OK) - /* if dir was BACKWARD then honor it just once */ + // Go through the List with matches and add each of them. + TV_LIST_ITER(list, li, { + if (ins_compl_add_tv(TV_LIST_ITEM_TV(li), dir) == OK) { + // If dir was BACKWARD then honor it just once. dir = FORWARD; - else if (did_emsg) + } else if (did_emsg) { break; - } + } + }); } /* @@ -3600,6 +3612,8 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir) cptext[CPT_MENU] = tv_dict_get_string(tv->vval.v_dict, "menu", true); cptext[CPT_KIND] = tv_dict_get_string(tv->vval.v_dict, "kind", true); cptext[CPT_INFO] = tv_dict_get_string(tv->vval.v_dict, "info", true); + cptext[CPT_USER_DATA] = tv_dict_get_string(tv->vval.v_dict, + "user_data", true); icase = (bool)tv_dict_get_number(tv->vval.v_dict, "icase"); adup = (bool)tv_dict_get_number(tv->vval.v_dict, "dup"); @@ -3609,6 +3623,9 @@ int ins_compl_add_tv(typval_T *const tv, const Direction dir) memset(cptext, 0, sizeof(cptext)); } if (word == NULL || (!aempty && *word == NUL)) { + for (size_t i = 0; i < CPT_COUNT; i++) { + xfree(cptext[i]); + } return FAIL; } return ins_compl_add((char_u *)word, -1, icase, NULL, @@ -4043,8 +4060,9 @@ static void ins_compl_insert(int in_compl_func) // Set completed item. // { word, abbr, menu, kind, info } dict_T *dict = tv_dict_alloc(); - tv_dict_add_str(dict, S_LEN("word"), - (const char *)EMPTY_IF_NULL(compl_shown_match->cp_str)); + tv_dict_add_str( + dict, S_LEN("word"), + (const char *)EMPTY_IF_NULL(compl_shown_match->cp_str)); tv_dict_add_str( dict, S_LEN("abbr"), (const char *)EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_ABBR])); @@ -4057,6 +4075,9 @@ static void ins_compl_insert(int in_compl_func) tv_dict_add_str( dict, S_LEN("info"), (const char *)EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_INFO])); + tv_dict_add_str( + dict, S_LEN("user_data"), + (const char *)EMPTY_IF_NULL(compl_shown_match->cp_text[CPT_USER_DATA])); set_vim_var_dict(VV_COMPLETED_ITEM, dict); if (!in_compl_func) { compl_curr_match = compl_shown_match; @@ -4681,7 +4702,7 @@ static int ins_complete(int c, bool enable_pum) line = ml_get(curwin->w_cursor.lnum); compl_pattern = vim_strnsave(line + compl_col, compl_length); } else { - EMSG2(_(e_intern2), "ins_complete()"); + internal_error("ins_complete()"); return FAIL; } @@ -5044,13 +5065,11 @@ static void insert_special(int c, int allow_modmask, int ctrlv) char_u *p; int len; - /* - * Special function key, translate into "<Key>". Up to the last '>' is - * inserted with ins_str(), so as not to replace characters in replace - * mode. - * Only use mod_mask for special keys, to avoid things like <S-Space>, - * unless 'allow_modmask' is TRUE. - */ + // Special function key, translate into "<Key>". Up to the last '>' is + // inserted with ins_str(), so as not to replace characters in replace + // mode. + // Only use mod_mask for special keys, to avoid things like <S-Space>, + // unless 'allow_modmask' is TRUE. if (mod_mask & MOD_MASK_CMD) { // Command-key never produces a normal key. allow_modmask = true; } @@ -5235,28 +5254,27 @@ insertchar ( buf[0] = c; i = 1; - if (textwidth > 0) + if (textwidth > 0) { virtcol = get_nolist_virtcol(); - /* - * Stop the string when: - * - no more chars available - * - finding a special character (command key) - * - buffer is full - * - running into the 'textwidth' boundary - * - need to check for abbreviation: A non-word char after a word-char - */ - while ( (c = vpeekc()) != NUL - && !ISSPECIAL(c) - && (!has_mbyte || MB_BYTE2LEN_CHECK(c) == 1) - && i < INPUT_BUFLEN - && (textwidth == 0 - || (virtcol += byte2cells(buf[i - 1])) < (colnr_T)textwidth) - && !(!no_abbr && !vim_iswordc(c) && vim_iswordc(buf[i - 1]))) { + } + // Stop the string when: + // - no more chars available + // - finding a special character (command key) + // - buffer is full + // - running into the 'textwidth' boundary + // - need to check for abbreviation: A non-word char after a word-char + while ((c = vpeekc()) != NUL + && !ISSPECIAL(c) + && (!has_mbyte || MB_BYTE2LEN_CHECK(c) == 1) + && i < INPUT_BUFLEN + && !(p_fkmap && KeyTyped) // Farsi mode mapping moves cursor + && (textwidth == 0 + || (virtcol += byte2cells(buf[i - 1])) < (colnr_T)textwidth) + && !(!no_abbr && !vim_iswordc(c) && vim_iswordc(buf[i - 1]))) { c = vgetc(); - if (p_hkmap && KeyTyped) - c = hkmap(c); /* Hebrew mode mapping */ - if (p_fkmap && KeyTyped) - c = fkmap(c); /* Farsi mode mapping */ + if (p_hkmap && KeyTyped) { + c = hkmap(c); // Hebrew mode mapping + } buf[i++] = c; } @@ -6066,27 +6084,30 @@ void free_last_insert(void) #endif -/* - * Add character "c" to buffer "s". Escape the special meaning of K_SPECIAL - * and CSI. Handle multi-byte characters. - * Returns a pointer to after the added bytes. - */ +/// Add character "c" to buffer "s" +/// +/// Escapes the special meaning of K_SPECIAL and CSI, handles multi-byte +/// characters. +/// +/// @param[in] c Character to add. +/// @param[out] s Buffer to add to. Must have at least MB_MAXBYTES + 1 bytes. +/// +/// @return Pointer to after the added bytes. char_u *add_char2buf(int c, char_u *s) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { char_u temp[MB_MAXBYTES + 1]; - int i; - int len; - - len = (*mb_char2bytes)(c, temp); - for (i = 0; i < len; ++i) { + const int len = utf_char2bytes(c, temp); + for (int i = 0; i < len; i++) { c = temp[i]; - /* Need to escape K_SPECIAL and CSI like in the typeahead buffer. */ + // Need to escape K_SPECIAL and CSI like in the typeahead buffer. if (c == K_SPECIAL) { *s++ = K_SPECIAL; *s++ = KS_SPECIAL; *s++ = KE_FILLER; - } else + } else { *s++ = c; + } } return s; } @@ -6904,7 +6925,9 @@ bool in_cinkeys(int keytyped, int when, bool line_is_empty) if (try_match && *look == keytyped) { return true; } - look++; + if (*look != NUL) { + look++; + } } /* @@ -7470,13 +7493,11 @@ static bool ins_bs(int c, int mode, int *inserted_space_p) int oldState; int cpc[MAX_MCO]; /* composing characters */ - /* - * can't delete anything in an empty file - * can't backup past first character in buffer - * can't backup past starting point unless 'backspace' > 1 - * can backup to a previous line if 'backspace' == 0 - */ - if (bufempty() + // can't delete anything in an empty file + // can't backup past first character in buffer + // can't backup past starting point unless 'backspace' > 1 + // can backup to a previous line if 'backspace' == 0 + if (BUFEMPTY() || (!revins_on && ((curwin->w_cursor.lnum == 1 && curwin->w_cursor.col == 0) || (!can_bs(BS_START) diff --git a/src/nvim/edit.h b/src/nvim/edit.h index 0d61f26bcc..433a941295 100644 --- a/src/nvim/edit.h +++ b/src/nvim/edit.h @@ -6,11 +6,12 @@ /* * Array indexes used for cptext argument of ins_compl_add(). */ -#define CPT_ABBR 0 /* "abbr" */ -#define CPT_MENU 1 /* "menu" */ -#define CPT_KIND 2 /* "kind" */ -#define CPT_INFO 3 /* "info" */ -#define CPT_COUNT 4 /* Number of entries */ +#define CPT_ABBR 0 // "abbr" +#define CPT_MENU 1 // "menu" +#define CPT_KIND 2 // "kind" +#define CPT_INFO 3 // "info" +#define CPT_USER_DATA 4 // "user data" +#define CPT_COUNT 5 // Number of entries typedef int (*IndentGetter)(void); diff --git a/src/nvim/eval.c b/src/nvim/eval.c index 577aa67c60..284185083e 100644 --- a/src/nvim/eval.c +++ b/src/nvim/eval.c @@ -110,9 +110,6 @@ #define DICT_MAXNEST 100 /* maximum nesting of lists and dicts */ -#define DO_NOT_FREE_CNT 99999 /* refcount for dict or list that should not - be freed. */ - #define AUTOLOAD_CHAR '#' /* Character used as separator in autoload function/variable names. */ @@ -218,6 +215,15 @@ static garray_T ga_scripts = {0, 0, sizeof(scriptvar_T *), 4, NULL}; static int echo_attr = 0; /* attributes used for ":echo" */ +/// Describe data to return from find_some_match() +typedef enum { + kSomeMatch, ///< Data for match(). + kSomeMatchEnd, ///< Data for matchend(). + kSomeMatchList, ///< Data for matchlist(). + kSomeMatchStr, ///< Data for matchstr(). + kSomeMatchStrPos, ///< Data for matchstrpos(). +} SomeMatchType; + /// trans_function_name() flags typedef enum { TFN_INT = 1, ///< May use internal function name @@ -396,7 +402,6 @@ static struct vimvar { VV(VV_OLDFILES, "oldfiles", VAR_LIST, 0), VV(VV_WINDOWID, "windowid", VAR_NUMBER, VV_RO_SBX), VV(VV_PROGPATH, "progpath", VAR_STRING, VV_RO), - VV(VV_COMMAND_OUTPUT, "command_output", VAR_STRING, 0), VV(VV_COMPLETED_ITEM, "completed_item", VAR_DICT, VV_RO), VV(VV_OPTION_NEW, "option_new", VAR_STRING, VV_RO), VV(VV_OPTION_OLD, "option_old", VAR_STRING, VV_RO), @@ -563,9 +568,9 @@ void eval_init(void) dict_T *const msgpack_types_dict = tv_dict_alloc(); for (size_t i = 0; i < ARRAY_SIZE(msgpack_type_names); i++) { - list_T *const type_list = tv_list_alloc(); - type_list->lv_lock = VAR_FIXED; - type_list->lv_refcount = 1; + list_T *const type_list = tv_list_alloc(0); + tv_list_set_lock(type_list, VAR_FIXED); + tv_list_ref(type_list); dictitem_T *const di = tv_dict_item_alloc(msgpack_type_names[i]); di->di_flags |= DI_FLAGS_RO|DI_FLAGS_FIX; di->di_tv = (typval_T) { @@ -586,7 +591,7 @@ void eval_init(void) dict_T *v_event = tv_dict_alloc(); v_event->dv_lock = VAR_FIXED; set_vim_var_dict(VV_EVENT, v_event); - set_vim_var_list(VV_ERRORS, tv_list_alloc()); + set_vim_var_list(VV_ERRORS, tv_list_alloc(kListLenUnknown)); set_vim_var_nr(VV_STDERR, CHAN_STDERR); set_vim_var_nr(VV_SEARCHFORWARD, 1L); set_vim_var_nr(VV_HLSEARCH, 1L); @@ -1014,7 +1019,7 @@ char_u *eval_to_string(char_u *arg, char_u **nextcmd, int convert) ga_init(&ga, (int)sizeof(char), 80); if (tv.vval.v_list != NULL) { tv_list_join(&ga, tv.vval.v_list, "\n"); - if (tv.vval.v_list->lv_len > 0) { + if (tv_list_len(tv.vval.v_list) > 0) { ga_append(&ga, NL); } } @@ -1102,10 +1107,11 @@ static void restore_vimvar(int idx, typval_T *save_tv) vimvars[idx].vv_tv = *save_tv; if (vimvars[idx].vv_type == VAR_UNKNOWN) { hi = hash_find(&vimvarht, vimvars[idx].vv_di.di_key); - if (HASHITEM_EMPTY(hi)) - EMSG2(_(e_intern2), "restore_vimvar()"); - else + if (HASHITEM_EMPTY(hi)) { + internal_error("restore_vimvar()"); + } else { hash_remove(&vimvarht, hi); + } } } @@ -1143,27 +1149,31 @@ list_T *eval_spell_expr(char_u *badword, char_u *expr) return list; } -/* - * "list" is supposed to contain two items: a word and a number. Return the - * word in "pp" and the number as the return value. - * Return -1 if anything isn't right. - * Used to get the good word and score from the eval_spell_expr() result. - */ -int get_spellword(list_T *list, const char **pp) +/// Get spell word from an entry from spellsuggest=expr: +/// +/// Entry in question is supposed to be a list (to be checked by the caller) +/// with two items: a word and a score represented as an unsigned number +/// (whether it actually is unsigned is not checked). +/// +/// Used to get the good word and score from the eval_spell_expr() result. +/// +/// @param[in] list List to get values from. +/// @param[out] ret_word Suggested word. Not initialized if return value is +/// -1. +/// +/// @return -1 in case of error, score otherwise. +int get_spellword(list_T *const list, const char **ret_word) { - listitem_T *li; - - li = list->lv_first; - if (li == NULL) { + if (tv_list_len(list) != 2) { + EMSG(_("E5700: Expression from 'spellsuggest' must yield lists with " + "exactly two values")); return -1; } - *pp = tv_get_string(&li->li_tv); - - li = li->li_next; - if (li == NULL) { + *ret_word = tv_list_find_str(list, 0); + if (*ret_word == NULL) { return -1; } - return tv_get_number(&li->li_tv); + return tv_list_find_nr(list, -1, NULL); } @@ -1269,7 +1279,7 @@ varnumber_T call_func_retnr(char_u *func, int argc, char *call_func_retstr(const char *const func, const int argc, const char_u *const *const argv, const bool safe) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC + 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. @@ -1504,9 +1514,6 @@ ex_let_vars ( ) { char_u *arg = arg_start; - list_T *l; - int i; - listitem_T *item; typval_T ltv; if (*arg != '[') { @@ -1518,46 +1525,52 @@ ex_let_vars ( return OK; } - /* - * ":let [v1, v2] = list" or ":for [v1, v2] in listlist" - */ - if (tv->v_type != VAR_LIST || (l = tv->vval.v_list) == NULL) { + // ":let [v1, v2] = list" or ":for [v1, v2] in listlist" + if (tv->v_type != VAR_LIST) { EMSG(_(e_listreq)); return FAIL; } + list_T *const l = tv->vval.v_list; - i = tv_list_len(l); - if (semicolon == 0 && var_count < i) { + const int len = tv_list_len(l); + if (semicolon == 0 && var_count < len) { EMSG(_("E687: Less targets than List items")); return FAIL; } - if (var_count - semicolon > i) { + if (var_count - semicolon > len) { EMSG(_("E688: More targets than List items")); return FAIL; } + // List l may actually be NULL, but it should fail with E688 or even earlier + // if you try to do ":let [] = v:_null_list". + assert(l != NULL); - item = l->lv_first; + listitem_T *item = tv_list_first(l); + size_t rest_len = tv_list_len(l); while (*arg != ']') { arg = skipwhite(arg + 1); - arg = ex_let_one(arg, &item->li_tv, TRUE, (char_u *)",;]", nextchars); - item = item->li_next; - if (arg == NULL) + arg = ex_let_one(arg, TV_LIST_ITEM_TV(item), true, (const char_u *)",;]", + nextchars); + if (arg == NULL) { return FAIL; + } + rest_len--; + item = TV_LIST_ITEM_NEXT(l, item); arg = skipwhite(arg); if (*arg == ';') { /* Put the rest of the list (may be empty) in the var after ';'. * Create a new list for this. */ - l = tv_list_alloc(); + list_T *const rest_list = tv_list_alloc(rest_len); while (item != NULL) { - tv_list_append_tv(l, &item->li_tv); - item = item->li_next; + tv_list_append_tv(rest_list, TV_LIST_ITEM_TV(item)); + item = TV_LIST_ITEM_NEXT(l, item); } ltv.v_type = VAR_LIST; - ltv.v_lock = 0; - ltv.vval.v_list = l; - l->lv_refcount = 1; + ltv.v_lock = VAR_UNLOCKED; + ltv.vval.v_list = rest_list; + tv_list_ref(rest_list); arg = ex_let_one(skipwhite(arg + 1), <v, false, (char_u *)"]", nextchars); @@ -1567,7 +1580,7 @@ ex_let_vars ( } break; } else if (*arg != ',' && *arg != ']') { - EMSG2(_(e_intern2), "ex_let_vars()"); + internal_error("ex_let_vars()"); return FAIL; } } @@ -2072,6 +2085,8 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, * Loop until no more [idx] or .key is following. */ lp->ll_tv = &v->di_tv; + var1.v_type = VAR_UNKNOWN; + var2.v_type = VAR_UNKNOWN; while (*p == '[' || (*p == '.' && lp->ll_tv->v_type == VAR_DICT)) { if (!(lp->ll_tv->v_type == VAR_LIST && lp->ll_tv->vval.v_list != NULL) && !(lp->ll_tv->v_type == VAR_DICT @@ -2122,9 +2137,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { EMSG(_(e_dictrange)); } - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } if (rettv != NULL && (rettv->v_type != VAR_LIST @@ -2132,9 +2145,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { emsgf(_("E709: [:] requires a List value")); } - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } p = skipwhite(p + 1); @@ -2143,16 +2154,12 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } else { lp->ll_empty2 = false; if (eval1(&p, &var2, true) == FAIL) { // Recursive! - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } if (!tv_check_str(&var2)) { // Not a number or string. - if (!empty1) { - tv_clear(&var1); - } + tv_clear(&var1); tv_clear(&var2); return NULL; } @@ -2165,12 +2172,8 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { emsgf(_(e_missbrac)); } - if (!empty1) { - tv_clear(&var1); - } - if (lp->ll_range && !lp->ll_empty2) { - tv_clear(&var2); - } + tv_clear(&var1); + tv_clear(&var2); return NULL; } @@ -2224,9 +2227,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, if (!quiet) { emsgf(_(e_dictkey), key); } - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } if (len == -1) { @@ -2234,32 +2235,28 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } else { lp->ll_newkey = vim_strnsave(key, len); } - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); break; // existing variable, need to check if it can be changed } else if (!(flags & GLV_READ_ONLY) && var_check_ro(lp->ll_di->di_flags, (const char *)name, (size_t)(p - name))) { - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); return NULL; } - if (len == -1) { - tv_clear(&var1); - } + tv_clear(&var1); lp->ll_tv = &lp->ll_di->di_tv; } else { // Get the number and item for the only or first index of the List. if (empty1) { lp->ll_n1 = 0; } else { - lp->ll_n1 = (long)tv_get_number(&var1); // Is number or string. - tv_clear(&var1); + // Is number or string. + lp->ll_n1 = (long)tv_get_number(&var1); } + tv_clear(&var1); + lp->ll_dict = NULL; lp->ll_list = lp->ll_tv->vval.v_list; lp->ll_li = tv_list_find(lp->ll_list, lp->ll_n1); @@ -2270,9 +2267,7 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } } if (lp->ll_li == NULL) { - if (lp->ll_range && !lp->ll_empty2) { - tv_clear(&var2); - } + tv_clear(&var2); if (!quiet) { EMSGN(_(e_listidx), lp->ll_n1); } @@ -2310,10 +2305,11 @@ static char_u *get_lval(char_u *const name, typval_T *const rettv, } } - lp->ll_tv = &lp->ll_li->li_tv; + lp->ll_tv = TV_LIST_ITEM_TV(lp->ll_li); } } + tv_clear(&var1); return p; } @@ -2375,47 +2371,55 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, int ll_n1 = lp->ll_n1; // Check whether any of the list items is locked - for (listitem_T *ri = rettv->vval.v_list->lv_first; + for (listitem_T *ri = tv_list_first(rettv->vval.v_list); ri != NULL && ll_li != NULL; ) { - if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name, + if (tv_check_lock(TV_LIST_ITEM_TV(ll_li)->v_lock, + (const char *)lp->ll_name, TV_CSTRING)) { return; } - ri = ri->li_next; + ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri); if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == ll_n1)) { break; } - ll_li = ll_li->li_next; + ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, ll_li); ll_n1++; } /* * Assign the List values to the list items. */ - for (ri = rettv->vval.v_list->lv_first; ri != NULL; ) { + for (ri = tv_list_first(rettv->vval.v_list); ri != NULL; ) { if (op != NULL && *op != '=') { - eexe_mod_op(&lp->ll_li->li_tv, &ri->li_tv, (const char *)op); + eexe_mod_op(TV_LIST_ITEM_TV(lp->ll_li), TV_LIST_ITEM_TV(ri), + (const char *)op); } else { - tv_clear(&lp->ll_li->li_tv); - tv_copy(&ri->li_tv, &lp->ll_li->li_tv); + tv_clear(TV_LIST_ITEM_TV(lp->ll_li)); + tv_copy(TV_LIST_ITEM_TV(ri), TV_LIST_ITEM_TV(lp->ll_li)); } - ri = ri->li_next; - if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) + ri = TV_LIST_ITEM_NEXT(rettv->vval.v_list, ri); + if (ri == NULL || (!lp->ll_empty2 && lp->ll_n2 == lp->ll_n1)) { break; - if (lp->ll_li->li_next == NULL) { + } + assert(lp->ll_li != NULL); + if (TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) == NULL) { // Need to add an empty item. tv_list_append_number(lp->ll_list, 0); - assert(lp->ll_li->li_next); + // ll_li may have become invalid after append, don’t use it. + lp->ll_li = tv_list_last(lp->ll_list); // Valid again. + } else { + lp->ll_li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); } - lp->ll_li = lp->ll_li->li_next; - ++lp->ll_n1; + lp->ll_n1++; } - if (ri != NULL) + if (ri != NULL) { EMSG(_("E710: List value has more items than target")); - else if (lp->ll_empty2 - ? (lp->ll_li != NULL && lp->ll_li->li_next != NULL) - : lp->ll_n1 != lp->ll_n2) + } else if (lp->ll_empty2 + ? (lp->ll_li != NULL + && TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li) != NULL) + : lp->ll_n1 != lp->ll_n2) { EMSG(_("E711: List value has not enough items")); + } } else { typval_T oldtv = TV_INITIAL_VALUE; dict_T *dict = lp->ll_dict; @@ -2460,9 +2464,11 @@ static void set_var_lval(lval_T *lp, char_u *endp, typval_T *rettv, notify: if (watched) { if (oldtv.v_type == VAR_UNKNOWN) { + assert(lp->ll_newkey != NULL); tv_dict_watcher_notify(dict, (char *)lp->ll_newkey, lp->ll_tv, NULL); } else { dictitem_T *di = lp->ll_di; + assert(di->di_key != NULL); tv_dict_watcher_notify(dict, (char *)di->di_key, lp->ll_tv, &oldtv); tv_clear(&oldtv); } @@ -2514,7 +2520,7 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip) * list being used in "tv". */ fi->fi_list = l; tv_list_watch_add(l, &fi->fi_lw); - fi->fi_lw.lw_item = l->lv_first; + fi->fi_lw.lw_item = tv_list_first(l); } } } @@ -2532,21 +2538,18 @@ void *eval_for_line(const char_u *arg, bool *errp, char_u **nextcmdp, int skip) * Return TRUE when a valid item was found, FALSE when at end of list or * something wrong. */ -int next_for_item(void *fi_void, char_u *arg) +bool next_for_item(void *fi_void, char_u *arg) { - forinfo_T *fi = (forinfo_T *)fi_void; - int result; - listitem_T *item; + forinfo_T *fi = (forinfo_T *)fi_void; - item = fi->fi_lw.lw_item; - if (item == NULL) - result = FALSE; - else { - fi->fi_lw.lw_item = item->li_next; - result = (ex_let_vars(arg, &item->li_tv, TRUE, - fi->fi_semicolon, fi->fi_varcount, NULL) == OK); + listitem_T *item = fi->fi_lw.lw_item; + if (item == NULL) { + return false; + } 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); } - return result; } // TODO(ZyX-I): move to eval/ex_cmds @@ -2870,7 +2873,10 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) } *name_end = cc; } else if ((lp->ll_list != NULL - && tv_check_lock(lp->ll_list->lv_lock, (const char *)lp->ll_name, + // ll_list is not NULL when lvalue is not in a list, NULL lists + // yield E689. + && tv_check_lock(tv_list_locked(lp->ll_list), + (const char *)lp->ll_name, lp->ll_name_len)) || (lp->ll_dict != NULL && tv_check_lock(lp->ll_dict->dv_lock, @@ -2878,27 +2884,26 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) lp->ll_name_len))) { return FAIL; } else if (lp->ll_range) { - listitem_T *li; - listitem_T *ll_li = lp->ll_li; - int ll_n1 = lp->ll_n1; - - while (ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= ll_n1)) { - li = ll_li->li_next; - if (tv_check_lock(ll_li->li_tv.v_lock, (const char *)lp->ll_name, + assert(lp->ll_list != NULL); + // Delete a range of List items. + listitem_T *const first_li = lp->ll_li; + listitem_T *last_li = first_li; + for (;;) { + listitem_T *const li = TV_LIST_ITEM_NEXT(lp->ll_list, lp->ll_li); + if (tv_check_lock(TV_LIST_ITEM_TV(lp->ll_li)->v_lock, + (const char *)lp->ll_name, lp->ll_name_len)) { return false; } - ll_li = li; - ll_n1++; - } - - /* Delete a range of List items. */ - while (lp->ll_li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { - li = lp->ll_li->li_next; - tv_list_item_remove(lp->ll_list, lp->ll_li); lp->ll_li = li; - ++lp->ll_n1; + lp->ll_n1++; + if (lp->ll_li == NULL || (!lp->ll_empty2 && lp->ll_n2 < lp->ll_n1)) { + break; + } else { + last_li = lp->ll_li; + } } + tv_list_remove_items(lp->ll_list, first_li, last_li); } else { if (lp->ll_list != NULL) { // unlet a List item. @@ -2906,6 +2911,7 @@ static int do_unlet_var(lval_T *const lp, char_u *const name_end, int forceit) } else { // unlet a Dictionary item. dict_T *d = lp->ll_dict; + assert(d != NULL); dictitem_T *di = lp->ll_di; bool watched = tv_dict_is_watched(d); char *key = NULL; @@ -2960,7 +2966,7 @@ int do_unlet(const char *const name, const size_t name_len, const int forceit) d = di->di_tv.vval.v_dict; } if (d == NULL) { - EMSG2(_(e_intern2), "do_unlet()"); + internal_error("do_unlet()"); return FAIL; } hashitem_T *hi = hash_find(ht, (const char_u *)varname); @@ -3043,13 +3049,13 @@ static int do_lock_var(lval_T *lp, char_u *const name_end, const int deep, /* (un)lock a range of List items. */ while (li != NULL && (lp->ll_empty2 || lp->ll_n2 >= lp->ll_n1)) { - tv_item_lock(&li->li_tv, deep, lock); - li = li->li_next; - ++lp->ll_n1; + tv_item_lock(TV_LIST_ITEM_TV(li), deep, lock); + li = TV_LIST_ITEM_NEXT(lp->ll_list, li); + lp->ll_n1++; } } else if (lp->ll_list != NULL) { // (un)lock a List item. - tv_item_lock(&lp->ll_li->li_tv, deep, lock); + tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock); } else { // (un)lock a Dictionary item. tv_item_lock(&lp->ll_di->di_tv, deep, lock); @@ -4508,18 +4514,18 @@ eval_index ( if (!empty2 && (n2 < 0 || n2 + 1 < n1)) { n2 = -1; } - l = tv_list_alloc(); + l = tv_list_alloc(n2 - n1 + 1); item = tv_list_find(rettv->vval.v_list, n1); while (n1++ <= n2) { - tv_list_append_tv(l, &item->li_tv); - item = item->li_next; + tv_list_append_tv(l, TV_LIST_ITEM_TV(item)); + item = TV_LIST_ITEM_NEXT(rettv->vval.v_list, item); } tv_clear(rettv); rettv->v_type = VAR_LIST; rettv->vval.v_list = l; - l->lv_refcount++; + tv_list_ref(l); } else { - tv_copy(&tv_list_find(rettv->vval.v_list, n1)->li_tv, &var1); + tv_copy(TV_LIST_ITEM_TV(tv_list_find(rettv->vval.v_list, n1)), &var1); tv_clear(rettv); *rettv = var1; } @@ -4864,35 +4870,34 @@ void partial_unref(partial_T *pt) static int get_list_tv(char_u **arg, typval_T *rettv, int evaluate) { list_T *l = NULL; - typval_T tv; - listitem_T *item; if (evaluate) { - l = tv_list_alloc(); + l = tv_list_alloc(kListLenShouldKnow); } *arg = skipwhite(*arg + 1); while (**arg != ']' && **arg != NUL) { - if (eval1(arg, &tv, evaluate) == FAIL) /* recursive! */ + typval_T tv; + if (eval1(arg, &tv, evaluate) == FAIL) { // Recursive! goto failret; + } if (evaluate) { - item = tv_list_item_alloc(); - item->li_tv = tv; - item->li_tv.v_lock = 0; - tv_list_append(l, item); + tv.v_lock = VAR_UNLOCKED; + tv_list_append_owned_tv(l, tv); } - if (**arg == ']') + if (**arg == ']') { break; + } if (**arg != ',') { - EMSG2(_("E696: Missing comma in List: %s"), *arg); + emsgf(_("E696: Missing comma in List: %s"), *arg); goto failret; } *arg = skipwhite(*arg + 1); } if (**arg != ']') { - EMSG2(_("E697: Missing end of List ']': %s"), *arg); + emsgf(_("E697: Missing end of List ']': %s"), *arg); failret: if (evaluate) { tv_list_free(l); @@ -4904,7 +4909,7 @@ failret: if (evaluate) { rettv->v_type = VAR_LIST; rettv->vval.v_list = l; - ++l->lv_refcount; + tv_list_ref(l); } return OK; @@ -5172,6 +5177,8 @@ bool garbage_collect(bool testing) ABORTING(set_ref_list)(sub.additional_elements, copyID); } + ABORTING(set_ref_in_quickfix)(copyID); + bool did_free = false; if (!abort) { // 2. Free lists and dictionaries that are not referenced. @@ -5239,8 +5246,8 @@ static int free_unref_items(int copyID) // But don't free a list that has a watcher (used in a for loop), these // are not referenced anywhere. for (list_T *ll = gc_first_list; ll != NULL; ll = ll->lv_used_next) { - if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) - && ll->lv_watch == NULL) { + if ((tv_list_copyid(ll) & COPYID_MASK) != (copyID & COPYID_MASK) + && !tv_list_has_watchers(ll)) { // Free the List and ordinary items it contains, but don't recurse // into Lists and Dictionaries, they will be in the list of dicts // or list of lists. @@ -5260,7 +5267,7 @@ static int free_unref_items(int copyID) for (ll = gc_first_list; ll != NULL; ll = ll_next) { ll_next = ll->lv_used_next; if ((ll->lv_copyID & COPYID_MASK) != (copyID & COPYID_MASK) - && ll->lv_watch == NULL) { + && !tv_list_has_watchers(ll)) { // Free the List and ordinary items it contains, but don't recurse // into Lists and Dictionaries, they will be in the list of dicts // or list of lists. @@ -5325,15 +5332,16 @@ bool set_ref_in_list(list_T *l, int copyID, ht_stack_T **ht_stack) list_T *cur_l = l; for (;;) { - if (!abort) { - // Mark each item in the list. If the item contains a hashtab - // it is added to ht_stack, if it contains a list it is added to - // list_stack. - for (listitem_T *li = cur_l->lv_first; !abort && li != NULL; - li = li->li_next) { - abort = set_ref_in_item(&li->li_tv, copyID, ht_stack, &list_stack); + // Mark each item in the list. If the item contains a hashtab + // it is added to ht_stack, if it contains a list it is added to + // list_stack. + TV_LIST_ITER(cur_l, li, { + if (abort) { + break; } - } + abort = set_ref_in_item(TV_LIST_ITEM_TV(li), copyID, ht_stack, + &list_stack); + }); if (list_stack == NULL) { break; @@ -5928,6 +5936,14 @@ static int get_env_tv(char_u **arg, typval_T *rettv, int evaluate) } #ifdef INCLUDE_GENERATED_DECLARATIONS + +#ifdef _MSC_VER +// This prevents MSVC from replacing the functions with intrinsics, +// and causing errors when trying to get their addresses in funcs.generated.h +#pragma function (ceil) +#pragma function (floor) +#endif + # include "funcs.generated.h" #endif @@ -6530,12 +6546,10 @@ static void f_abs(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_add(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - - rettv->vval.v_number = 1; /* Default: Failed */ + rettv->vval.v_number = 1; // Default: failed. if (argvars[0].v_type == VAR_LIST) { - if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, "add() argument", TV_TRANSLATE)) { + list_T *const l = argvars[0].vval.v_list; + if (!tv_check_lock(tv_list_locked(l), N_("add() argument"), TV_TRANSLATE)) { tv_list_append_tv(l, &argvars[1]); tv_copy(&argvars[0], rettv); } @@ -6586,9 +6600,10 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) && u_save(lnum, lnum + 1) == OK) { if (argvars[1].v_type == VAR_LIST) { l = argvars[1].vval.v_list; - if (l == NULL) + if (l == NULL) { return; - li = l->lv_first; + } + li = tv_list_first(l); } for (;; ) { if (l == NULL) { @@ -6596,7 +6611,7 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else if (li == NULL) { break; // End of list. } else { - tv = &li->li_tv; // Append item from list. + tv = TV_LIST_ITEM_TV(li); // Append item from list. } const char *const line = tv_get_string_chk(tv); if (line == NULL) { // Type error. @@ -6608,7 +6623,7 @@ static void f_append(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (l == NULL) { break; } - li = li->li_next; + li = TV_LIST_ITEM_NEXT(l, li); } appended_lines_mark(lnum, added); @@ -6661,7 +6676,7 @@ static void f_argv(typval_T *argvars, typval_T *rettv, FunPtr fptr) } rettv->v_type = VAR_STRING; } else { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, ARGCOUNT); for (idx = 0; idx < ARGCOUNT; idx++) { tv_list_append_string(rettv->vval.v_list, (const char *)alist_name(&ARGLIST[idx]), -1); @@ -6771,7 +6786,7 @@ static void assert_error(garray_T *gap) if (vp->vv_type != VAR_LIST || vimvars[VV_ERRORS].vv_list == NULL) { // Make sure v:errors is a list. - set_vim_var_list(VV_ERRORS, tv_list_alloc()); + set_vim_var_list(VV_ERRORS, tv_list_alloc(1)); } tv_list_append_string(vimvars[VV_ERRORS].vv_list, (const char *)gap->ga_data, (ptrdiff_t)gap->ga_len); @@ -7247,30 +7262,26 @@ static void f_byteidxcomp(typval_T *argvars, typval_T *rettv, FunPtr fptr) int func_call(char_u *name, typval_T *args, partial_T *partial, dict_T *selfdict, typval_T *rettv) { - listitem_T *item; typval_T argv[MAX_FUNC_ARGS + 1]; int argc = 0; int dummy; int r = 0; - for (item = args->vval.v_list->lv_first; item != NULL; - item = item->li_next) { + TV_LIST_ITER(args->vval.v_list, item, { if (argc == MAX_FUNC_ARGS - (partial == NULL ? 0 : partial->pt_argc)) { EMSG(_("E699: Too many arguments")); - break; + goto func_call_skip_call; } - /* Make a copy of each argument. This is needed to be able to set - * v_lock to VAR_FIXED in the copy without changing the original list. - */ - tv_copy(&item->li_tv, &argv[argc++]); - } + // Make a copy of each argument. This is needed to be able to set + // v_lock to VAR_FIXED in the copy without changing the original list. + tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]); + }); - if (item == NULL) { - r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, - curwin->w_cursor.lnum, curwin->w_cursor.lnum, - &dummy, true, partial, selfdict); - } + r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL, + curwin->w_cursor.lnum, curwin->w_cursor.lnum, + &dummy, true, partial, selfdict); +func_call_skip_call: // Free the arguments. while (argc > 0) { tv_clear(&argv[--argc]); @@ -7487,7 +7498,7 @@ static void f_complete(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (!undo_allowed()) return; - if (argvars[1].v_type != VAR_LIST || argvars[1].vval.v_list == NULL) { + if (argvars[1].v_type != VAR_LIST) { EMSG(_(e_invarg)); return; } @@ -7595,7 +7606,7 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) long idx; if ((l = argvars[0].vval.v_list) != NULL) { - li = l->lv_first; + li = tv_list_first(l); if (argvars[2].v_type != VAR_UNKNOWN) { bool error = false; @@ -7613,9 +7624,11 @@ static void f_count(typval_T *argvars, typval_T *rettv, FunPtr fptr) li = NULL; } - for (; li != NULL; li = li->li_next) - if (tv_equal(&li->li_tv, &argvars[1], ic, FALSE)) - ++n; + for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + if (tv_equal(TV_LIST_ITEM_TV(li), &argvars[1], ic, false)) { + n++; + } + } } } else if (argvars[0].v_type == VAR_DICT) { int todo; @@ -7933,34 +7946,51 @@ static void f_empty(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool n = true; switch (argvars[0].v_type) { - case VAR_STRING: - case VAR_FUNC: - n = argvars[0].vval.v_string == NULL - || *argvars[0].vval.v_string == NUL; - break; - case VAR_PARTIAL: - n = false; - break; - case VAR_NUMBER: - n = argvars[0].vval.v_number == 0; - break; - case VAR_FLOAT: - n = argvars[0].vval.v_float == 0.0; - break; - case VAR_LIST: - n = argvars[0].vval.v_list == NULL - || argvars[0].vval.v_list->lv_first == NULL; - break; - case VAR_DICT: - n = argvars[0].vval.v_dict == NULL - || argvars[0].vval.v_dict->dv_hashtab.ht_used == 0; - break; - case VAR_SPECIAL: - n = argvars[0].vval.v_special != kSpecialVarTrue; - break; - case VAR_UNKNOWN: - EMSG2(_(e_intern2), "f_empty(UNKNOWN)"); - break; + case VAR_STRING: + case VAR_FUNC: { + n = argvars[0].vval.v_string == NULL + || *argvars[0].vval.v_string == NUL; + break; + } + case VAR_PARTIAL: { + n = false; + break; + } + case VAR_NUMBER: { + n = argvars[0].vval.v_number == 0; + break; + } + case VAR_FLOAT: { + n = argvars[0].vval.v_float == 0.0; + break; + } + case VAR_LIST: { + n = (tv_list_len(argvars[0].vval.v_list) == 0); + break; + } + case VAR_DICT: { + n = (tv_dict_len(argvars[0].vval.v_dict) == 0); + break; + } + case VAR_SPECIAL: { + // Using switch to get warning if SpecialVarValue receives more values. + switch (argvars[0].vval.v_special) { + case kSpecialVarTrue: { + n = false; + break; + } + case kSpecialVarFalse: + case kSpecialVarNull: { + n = true; + break; + } + } + break; + } + case VAR_UNKNOWN: { + internal_error("f_empty(UNKNOWN)"); + break; + } } rettv->vval.v_number = n; @@ -8024,17 +8054,22 @@ static void f_executable(typval_T *argvars, typval_T *rettv, FunPtr fptr) && os_can_exe((const char_u *)name, NULL, false))); } +typedef struct { + const list_T *const l; + const listitem_T *li; +} GetListLineCookie; + static char_u *get_list_line(int c, void *cookie, int indent) { - const listitem_T **const p = (const listitem_T **)cookie; - const listitem_T *item = *p; + GetListLineCookie *const p = (GetListLineCookie *)cookie; + const listitem_T *const item = p->li; if (item == NULL) { return NULL; } char buf[NUMBUFLEN]; - const char *const s = tv_get_string_buf_chk(&item->li_tv, buf); - *p = item->li_next; + const char *const s = tv_get_string_buf_chk(TV_LIST_ITEM_TV(item), buf); + p->li = TV_LIST_ITEM_NEXT(p->l, item); return (char_u *)(s == NULL ? NULL : xstrdup(s)); } @@ -8076,11 +8111,14 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) do_cmdline_cmd(tv_get_string(&argvars[0])); } else if (argvars[0].vval.v_list != NULL) { list_T *const list = argvars[0].vval.v_list; - list->lv_refcount++; - listitem_T *const item = list->lv_first; - do_cmdline(NULL, get_list_line, (void *)&item, + tv_list_ref(list); + GetListLineCookie cookie = { + .l = list, + .li = tv_list_first(list), + }; + do_cmdline(NULL, get_list_line, (void *)&cookie, DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT|DOCMD_KEYTYPED); - list->lv_refcount--; + tv_list_unref(list); } msg_silent = save_msg_silent; emsg_silent = save_emsg_silent; @@ -8088,8 +8126,7 @@ static void f_execute(typval_T *argvars, typval_T *rettv, FunPtr fptr) ga_append(capture_ga, NUL); rettv->v_type = VAR_STRING; - rettv->vval.v_string = vim_strsave(capture_ga->ga_data); - ga_clear(capture_ga); + rettv->vval.v_string = capture_ga->ga_data; capture_ga = save_capture_ga; } @@ -8198,7 +8235,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) result = eval_vars((char_u *)s, (char_u *)s, &len, NULL, &errormsg, NULL); emsg_off--; if (rettv->v_type == VAR_LIST) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (result != NULL)); if (result != NULL) { tv_list_append_string(rettv->vval.v_list, (const char *)result, -1); } @@ -8221,8 +8258,8 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL); } else { - tv_list_alloc_ret(rettv); ExpandOne(&xpc, (char_u *)s, NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], -1); @@ -8239,7 +8276,7 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "menu_get(path [, modes])" function static void f_menu_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); int modes = MENU_ALL_MODES; if (argvars[1].v_type == VAR_STRING) { const char_u *const strmodes = (char_u *)tv_get_string(&argvars[1]); @@ -8262,14 +8299,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *const l1 = argvars[0].vval.v_list; list_T *const l2 = argvars[1].vval.v_list; - if (l1 == NULL) { - const bool locked = tv_check_lock(VAR_FIXED, arg_errmsg, TV_TRANSLATE); - (void)locked; - assert(locked == true); - } else if (l2 == NULL) { - // Do nothing - tv_copy(&argvars[0], rettv); - } else if (!tv_check_lock(l1->lv_lock, arg_errmsg, TV_TRANSLATE)) { + if (!tv_check_lock(tv_list_locked(l1), arg_errmsg, TV_TRANSLATE)) { listitem_T *item; if (argvars[2].v_type != VAR_UNKNOWN) { before = (long)tv_get_number_chk(&argvars[2], &error); @@ -8277,7 +8307,7 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; // Type error; errmsg already given. } - if (before == l1->lv_len) { + if (before == tv_list_len(l1)) { item = NULL; } else { item = tv_list_find(l1, before); @@ -8286,8 +8316,9 @@ static void f_extend(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } } - } else + } else { item = NULL; + } tv_list_extend(l1, l2, item); tv_copy(&argvars[0], rettv); @@ -8406,7 +8437,7 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) } if (count < 0) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenUnknown); } if (*fname != NUL && !error) { @@ -8439,7 +8470,6 @@ static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what) static void filter_map(typval_T *argvars, typval_T *rettv, int map) { typval_T *expr; - listitem_T *li, *nli; list_T *l = NULL; dictitem_T *di; hashtab_T *ht; @@ -8457,11 +8487,14 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) int idx = 0; if (argvars[0].v_type == VAR_LIST) { + tv_copy(&argvars[0], rettv); if ((l = argvars[0].vval.v_list) == NULL - || (!map && tv_check_lock(l->lv_lock, arg_errmsg, TV_TRANSLATE))) { + || (!map && tv_check_lock(tv_list_locked(l), arg_errmsg, + TV_TRANSLATE))) { return; } } else if (argvars[0].v_type == VAR_DICT) { + tv_copy(&argvars[0], rettv); if ((d = argvars[0].vval.v_dict) == NULL || (!map && tv_check_lock(d->dv_lock, arg_errmsg, TV_TRANSLATE))) { return; @@ -8520,18 +8553,21 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) } else { vimvars[VV_KEY].vv_type = VAR_NUMBER; - for (li = l->lv_first; li != NULL; li = nli) { + for (listitem_T *li = tv_list_first(l); li != NULL;) { if (map - && tv_check_lock(li->li_tv.v_lock, arg_errmsg, TV_TRANSLATE)) { + && tv_check_lock(TV_LIST_ITEM_TV(li)->v_lock, arg_errmsg, + TV_TRANSLATE)) { break; } - nli = li->li_next; vimvars[VV_KEY].vv_nr = idx; - if (filter_map_one(&li->li_tv, expr, map, &rem) == FAIL - || did_emsg) + if (filter_map_one(TV_LIST_ITEM_TV(li), expr, map, &rem) == FAIL + || did_emsg) { break; + } if (!map && rem) { - tv_list_item_remove(l, li); + li = tv_list_item_remove(l, li); + } else { + li = TV_LIST_ITEM_NEXT(l, li); } idx++; } @@ -8542,8 +8578,6 @@ static void filter_map(typval_T *argvars, typval_T *rettv, int map) did_emsg |= save_did_emsg; } - - tv_copy(&argvars[0], rettv); } static int filter_map_one(typval_T *tv, typval_T *expr, int map, int *remp) @@ -8935,7 +8969,7 @@ static void common_function(typval_T *argvars, typval_T *rettv, goto theend; } list = argvars[arg_idx].vval.v_list; - if (list == NULL || list->lv_len == 0) { + if (tv_list_len(list) == 0) { arg_idx = 0; } } @@ -8946,7 +8980,7 @@ static void common_function(typval_T *argvars, typval_T *rettv, // result is a VAR_PARTIAL if (arg_idx > 0 || (arg_pt != NULL && arg_pt->pt_argc > 0)) { const int arg_len = (arg_pt == NULL ? 0 : arg_pt->pt_argc); - const int lv_len = (list == NULL ? 0 : list->lv_len); + const int lv_len = tv_list_len(list); pt->pt_argc = arg_len + lv_len; pt->pt_argv = xmalloc(sizeof(pt->pt_argv[0]) * pt->pt_argc); @@ -8955,11 +8989,9 @@ static void common_function(typval_T *argvars, typval_T *rettv, tv_copy(&arg_pt->pt_argv[i], &pt->pt_argv[i]); } if (lv_len > 0) { - for (listitem_T *li = list->lv_first; - li != NULL; - li = li->li_next) { - tv_copy(&li->li_tv, &pt->pt_argv[i++]); - } + TV_LIST_ITER(list, li, { + tv_copy(TV_LIST_ITEM_TV(li), &pt->pt_argv[i++]); + }); } } @@ -9045,7 +9077,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) li = tv_list_find(l, tv_get_number_chk(&argvars[1], &error)); if (!error && li != NULL) { - tv = &li->li_tv; + tv = TV_LIST_ITEM_TV(li); } } } else if (argvars[0].v_type == VAR_DICT) { @@ -9086,7 +9118,7 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } else if (strcmp(what, "args") == 0) { rettv->v_type = VAR_LIST; - if (tv_list_alloc_ret(rettv) != NULL) { + if (tv_list_alloc_ret(rettv, pt->pt_argc) != NULL) { for (int i = 0; i < pt->pt_argc; i++) { tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]); } @@ -9110,8 +9142,10 @@ static void f_get(typval_T *argvars, typval_T *rettv, FunPtr fptr) } /// Returns information about signs placed in a buffer as list of dicts. -static void get_buffer_signs(buf_T *buf, list_T *l) +static list_T *get_buffer_signs(buf_T *buf) + FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { + list_T *const l = tv_list_alloc(kListLenMayKnow); for (signlist_T *sign = buf->b_signlist; sign; sign = sign->next) { dict_T *const d = tv_dict_alloc(); @@ -9122,6 +9156,7 @@ static void get_buffer_signs(buf_T *buf, list_T *l) tv_list_append_dict(l, d); } + return l; } /// Returns buffer options, variables and other attributes in a dictionary. @@ -9145,7 +9180,7 @@ static dict_T *get_buffer_info(buf_T *buf) tv_dict_add_dict(dict, S_LEN("variables"), buf->b_vars); // List of windows displaying this buffer - list_T *const windows = tv_list_alloc(); + list_T *const windows = tv_list_alloc(kListLenMayKnow); FOR_ALL_TAB_WINDOWS(tp, wp) { if (wp->w_buffer == buf) { tv_list_append_number(windows, (varnumber_T)wp->handle); @@ -9155,9 +9190,7 @@ static dict_T *get_buffer_info(buf_T *buf) if (buf->b_signlist != NULL) { // List of signs placed in this buffer - list_T *const signs = tv_list_alloc(); - get_buffer_signs(buf, signs); - tv_dict_add_list(dict, S_LEN("signs"), signs); + tv_dict_add_list(dict, S_LEN("signs"), get_buffer_signs(buf)); } return dict; @@ -9171,7 +9204,7 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) bool sel_buflisted = false; bool sel_bufloaded = false; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); // List of all the buffers or selected buffers if (argvars[0].v_type == VAR_DICT) { @@ -9230,35 +9263,33 @@ static void f_getbufinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void get_buffer_lines(buf_T *buf, linenr_T start, linenr_T end, int retlist, typval_T *rettv) { - char_u *p; - - rettv->v_type = VAR_STRING; + rettv->v_type = (retlist ? VAR_LIST : VAR_STRING); rettv->vval.v_string = NULL; - if (retlist) { - tv_list_alloc_ret(rettv); - } - if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0) + if (buf == NULL || buf->b_ml.ml_mfp == NULL || start < 0 || end < start) { + if (retlist) { + tv_list_alloc_ret(rettv, 0); + } return; + } - if (!retlist) { - if (start >= 1 && start <= buf->b_ml.ml_line_count) - p = ml_get_buf(buf, start, FALSE); - else - p = (char_u *)""; - rettv->vval.v_string = vim_strsave(p); - } else { - if (end < start) - return; - - if (start < 1) + if (retlist) { + if (start < 1) { start = 1; - if (end > buf->b_ml.ml_line_count) + } + if (end > buf->b_ml.ml_line_count) { end = buf->b_ml.ml_line_count; + } + tv_list_alloc_ret(rettv, end - start + 1); while (start <= end) { tv_list_append_string(rettv->vval.v_list, (const char *)ml_get_buf(buf, start++, false), -1); } + } else { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = ((start >= 1 && start <= buf->b_ml.ml_line_count) + ? vim_strsave(ml_get_buf(buf, start, false)) + : NULL); } } @@ -9583,8 +9614,8 @@ static void f_getcompletion(typval_T *argvars, typval_T *rettv, FunPtr fptr) theend: pat = addstar(xpc.xp_pattern, xpc.xp_pattern_len, xpc.xp_context); - tv_list_alloc_ret(rettv); ExpandOne(&xpc, pat, NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], @@ -9878,7 +9909,7 @@ static void f_getline(typval_T *argvars, typval_T *rettv, FunPtr fptr) const linenr_T lnum = tv_get_lnum(argvars); if (argvars[1].v_type == VAR_UNKNOWN) { - end = 0; + end = lnum; retlist = false; } else { end = tv_get_lnum(&argvars[1]); @@ -9892,7 +9923,7 @@ static void get_qf_loc_list(int is_qf, win_T *wp, typval_T *what_arg, typval_T *rettv) { if (what_arg->v_type == VAR_UNKNOWN) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); if (is_qf || wp != NULL) { (void)get_errorlist(wp, -1, rettv->vval.v_list); } @@ -9927,7 +9958,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) matchitem_T *cur = curwin->w_match_head; int i; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); while (cur != NULL) { dict_T *dict = tv_dict_alloc(); if (cur->match.regprog == NULL) { @@ -9940,7 +9971,7 @@ static void f_getmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (llpos->lnum == 0) { break; } - list_T *l = tv_list_alloc(); + list_T *const l = tv_list_alloc(1 + (llpos->col > 0 ? 2 : 0)); tv_list_append_number(l, (varnumber_T)llpos->lnum); if (llpos->col > 0) { tv_list_append_number(l, (varnumber_T)llpos->col); @@ -9989,7 +10020,7 @@ static void getpos_both(typval_T *argvars, typval_T *rettv, bool getcurpos) fp = var2fpos(&argvars[0], true, &fnum); } - list_T *l = tv_list_alloc_ret(rettv); + list_T *const l = tv_list_alloc_ret(rettv, 4 + (!!getcurpos)); tv_list_append_number(l, (fnum != -1) ? (varnumber_T)fnum : (varnumber_T)0); tv_list_append_number(l, ((fp != NULL) ? (varnumber_T)fp->lnum @@ -10062,12 +10093,12 @@ static void f_getreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (return_list) { rettv->v_type = VAR_LIST; - rettv->vval.v_list = + rettv->vval.v_list = get_reg_contents(regname, (arg2 ? kGRegExprSrc : 0) | kGRegList); if (rettv->vval.v_list == NULL) { - rettv->vval.v_list = tv_list_alloc(); + rettv->vval.v_list = tv_list_alloc(0); } - rettv->vval.v_list->lv_refcount++; + tv_list_ref(rettv->vval.v_list); } else { rettv->v_type = VAR_STRING; rettv->vval.v_string = get_reg_contents(regname, arg2 ? kGRegExprSrc : 0); @@ -10115,7 +10146,7 @@ static dict_T *get_tabpage_info(tabpage_T *tp, int tp_idx) tv_dict_add_nr(dict, S_LEN("tabnr"), tp_idx); - list_T *const l = tv_list_alloc(); + list_T *const l = tv_list_alloc(kListLenMayKnow); FOR_ALL_WINDOWS_IN_TAB(wp, tp) { tv_list_append_number(l, (varnumber_T)wp->handle); } @@ -10132,7 +10163,9 @@ static void f_gettabinfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) { tabpage_T *tparg = NULL; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (argvars[0].v_type == VAR_UNKNOWN + ? 1 + : kListLenMayKnow)); if (argvars[0].v_type != VAR_UNKNOWN) { // Information about one tab page @@ -10231,7 +10264,7 @@ static void f_getwininfo(typval_T *argvars, typval_T *rettv, FunPtr fptr) { win_T *wparg = NULL; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); if (argvars[0].v_type != VAR_UNKNOWN) { wparg = win_id2wp(argvars); @@ -10456,9 +10489,9 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->vval.v_string = ExpandOne( &xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL); } else { - tv_list_alloc_ret(rettv); ExpandOne(&xpc, (char_u *)tv_get_string(&argvars[0]), NULL, options, WILD_ALL_KEEP); + tv_list_alloc_ret(rettv, xpc.xp_numfiles); for (int i = 0; i < xpc.xp_numfiles; i++) { tv_list_append_string(rettv->vval.v_list, (const char *)xpc.xp_files[i], -1); @@ -10507,7 +10540,7 @@ static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (rettv->v_type == VAR_STRING) { rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n"); } else { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, ga.ga_len); for (int i = 0; i < ga.ga_len; i++) { tv_list_append_string(rettv->vval.v_list, ((const char **)(ga.ga_data))[i], -1); @@ -10646,6 +10679,9 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) "windows", "winaltkeys", "writebackup", +#if defined(HAVE_WSL) + "wsl", +#endif "nvim", }; @@ -10705,6 +10741,17 @@ static void f_has(typval_T *argvars, typval_T *rettv, FunPtr fptr) n = true; } + if (STRICMP(name, "ruby") == 0 && n == true) { + char *rubyhost = call_func_retstr("provider#ruby#Detect", 0, NULL, true); + if (rubyhost) { + if (*rubyhost == NUL) { + // Invalid rubyhost executable. Gem is probably not installed. + n = false; + } + xfree(rubyhost); + } + } + rettv->vval.v_number = n; } @@ -11024,39 +11071,42 @@ static void f_indent(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_index(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - listitem_T *item; long idx = 0; - int ic = FALSE; + bool ic = false; rettv->vval.v_number = -1; if (argvars[0].v_type != VAR_LIST) { EMSG(_(e_listreq)); return; } - l = argvars[0].vval.v_list; + list_T *const l = argvars[0].vval.v_list; if (l != NULL) { - item = l->lv_first; + listitem_T *item = tv_list_first(l); if (argvars[2].v_type != VAR_UNKNOWN) { bool error = false; - // Start at specified item. Use the cached index that tv_list_find() - // sets, so that a negative number also works. - item = tv_list_find(l, tv_get_number_chk(&argvars[2], &error)); - idx = l->lv_idx; - if (argvars[3].v_type != VAR_UNKNOWN) { - ic = tv_get_number_chk(&argvars[3], &error); - } - if (error) { + // Start at specified item. + idx = tv_list_uidx(l, tv_get_number_chk(&argvars[2], &error)); + if (error || idx == -1) { item = NULL; + } else { + item = tv_list_find(l, idx); + assert(item != NULL); + } + if (argvars[3].v_type != VAR_UNKNOWN) { + ic = !!tv_get_number_chk(&argvars[3], &error); + if (error) { + item = NULL; + } } } - for (; item != NULL; item = item->li_next, ++idx) - if (tv_equal(&item->li_tv, &argvars[1], ic, FALSE)) { + for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) { + if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) { rettv->vval.v_number = idx; break; } + } } } @@ -11085,6 +11135,7 @@ void get_user_input(const typval_T *const argvars, char defstr_buf[NUMBUFLEN]; char cancelreturn_buf[NUMBUFLEN]; char xp_name_buf[NUMBUFLEN]; + char def[1] = { 0 }; if (argvars[0].v_type == VAR_DICT) { if (argvars[1].v_type != VAR_UNKNOWN) { emsgf(_("E5050: {opts} must be the only argument")); @@ -11099,7 +11150,6 @@ void get_user_input(const typval_T *const argvars, if (defstr == NULL) { return; } - char def[1] = { 0 }; cancelreturn = tv_dict_get_string_buf_chk(dict, S_LEN("cancelreturn"), cancelreturn_buf, def); if (cancelreturn == NULL) { // error @@ -11220,11 +11270,10 @@ static void f_inputdialog(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - listitem_T *li; int selected; int mouse_used; - if (argvars[0].v_type != VAR_LIST || argvars[0].vval.v_list == NULL) { + if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "inputlist()"); return; } @@ -11235,15 +11284,16 @@ static void f_inputlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) msg_scroll = TRUE; msg_clr_eos(); - for (li = argvars[0].vval.v_list->lv_first; li != NULL; li = li->li_next) { - msg_puts(tv_get_string(&li->li_tv)); + TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, { + msg_puts(tv_get_string(TV_LIST_ITEM_TV(li))); msg_putchar('\n'); - } + }); - /* Ask for choice. */ + // Ask for choice. selected = prompt_for_number(&mouse_used); - if (mouse_used) + if (mouse_used) { selected -= lines_left; + } rettv->vval.v_number = selected; } @@ -11299,9 +11349,8 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "insert()"); - } else if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, N_("insert() argument"), - TV_TRANSLATE)) { + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("insert() argument"), TV_TRANSLATE)) { long before = 0; if (argvars[2].v_type != VAR_UNKNOWN) { before = tv_get_number_chk(&argvars[2], &error); @@ -11312,7 +11361,7 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr) } listitem_T *item = NULL; - if (before != l->lv_len) { + if (before != tv_list_len(l)) { item = tv_list_find(l, before); if (item == NULL) { EMSGN(_(e_listidx), before); @@ -11376,7 +11425,7 @@ static void f_islocked(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_dictkey), lv.ll_newkey); } else if (lv.ll_list != NULL) { // List item. - rettv->vval.v_number = tv_islocked(&lv.ll_li->li_tv); + rettv->vval.v_number = tv_islocked(TV_LIST_ITEM_TV(lv.ll_li)); } else { // Dictionary item. rettv->vval.v_number = tv_islocked(&lv.ll_di->di_tv); @@ -11405,43 +11454,41 @@ static void dict_list(typval_T *const tv, typval_T *const rettv, return; } - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict)); TV_DICT_ITER(tv->vval.v_dict, di, { - listitem_T *const li = tv_list_item_alloc(); - tv_list_append(rettv->vval.v_list, li); + typval_T tv = { .v_lock = VAR_UNLOCKED }; switch (what) { case kDictListKeys: { - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_string = vim_strsave(di->di_key); + tv.v_type = VAR_STRING; + tv.vval.v_string = vim_strsave(di->di_key); break; } case kDictListValues: { - tv_copy(&di->di_tv, &li->li_tv); + tv_copy(&di->di_tv, &tv); break; } case kDictListItems: { // items() - list_T *const sub_l = tv_list_alloc(); - li->li_tv.v_type = VAR_LIST; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_list = sub_l; - sub_l->lv_refcount++; - - listitem_T *sub_li = tv_list_item_alloc(); - tv_list_append(sub_l, sub_li); - sub_li->li_tv.v_type = VAR_STRING; - sub_li->li_tv.v_lock = VAR_UNLOCKED; - sub_li->li_tv.vval.v_string = vim_strsave(di->di_key); - - sub_li = tv_list_item_alloc(); - tv_list_append(sub_l, sub_li); - tv_copy(&di->di_tv, &sub_li->li_tv); + list_T *const sub_l = tv_list_alloc(2); + tv.v_type = VAR_LIST; + tv.vval.v_list = sub_l; + tv_list_ref(sub_l); + + tv_list_append_owned_tv(sub_l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = (char_u *)xstrdup((const char *)di->di_key), + }); + + tv_list_append_tv(sub_l, &di->di_tv); + break; } } + + tv_list_append_owned_tv(rettv->vval.v_list, tv); }); } @@ -11536,15 +11583,13 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) } list_T *argl = cmd_tv->vval.v_list; - int argc = argl->lv_len; + int argc = tv_list_len(argl); if (!argc) { EMSG(_(e_invarg)); // List must have at least one item. return NULL; } - assert(argl->lv_first); - - const char *exe = tv_get_string_chk(&argl->lv_first->li_tv); + 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) { *executable = false; @@ -11559,15 +11604,15 @@ static char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable) // Build the argument vector int i = 0; char **argv = xcalloc(argc + 1, sizeof(char *)); - for (listitem_T *arg = argl->lv_first; arg != NULL; arg = arg->li_next) { - const char *a = tv_get_string_chk(&arg->li_tv); + TV_LIST_ITER_CONST(argl, arg, { + const char *a = tv_get_string_chk(TV_LIST_ITEM_TV(arg)); if (!a) { // Did emsg in tv_get_string_chk; just deallocate argv. shell_free_argv(argv); return NULL; } argv[i++] = xstrdup(a); - } + }); return argv; } @@ -11695,7 +11740,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *args = argvars[0].vval.v_list; - Channel **jobs = xcalloc(args->lv_len, sizeof(*jobs)); + Channel **jobs = xcalloc(tv_list_len(args), sizeof(*jobs)); ui_busy_start(); MultiQueue *waiting_jobs = multiqueue_new_parent(loop_on_put, &main_loop); @@ -11704,10 +11749,10 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) // -1 for jobs that were skipped or timed out. int i = 0; - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next, i++) { + TV_LIST_ITER_CONST(args, arg, { Channel *chan = NULL; - if (arg->li_tv.v_type != VAR_NUMBER - || !(chan = find_job(arg->li_tv.vval.v_number, false))) { + if (TV_LIST_ITEM_TV(arg)->v_type != VAR_NUMBER + || !(chan = find_job(TV_LIST_ITEM_TV(arg)->vval.v_number, false))) { jobs[i] = NULL; } else { jobs[i] = chan; @@ -11719,7 +11764,8 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) multiqueue_replace_parent(chan->events, waiting_jobs); } } - } + i++; + }); int remaining = -1; uint64_t before = 0; @@ -11728,7 +11774,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) before = os_hrtime(); } - for (i = 0; i < args->lv_len; i++) { + for (i = 0; i < tv_list_len(args); i++) { if (remaining == 0) { // timed out break; @@ -11755,10 +11801,10 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - list_T *rv = tv_list_alloc(); + list_T *const rv = tv_list_alloc(tv_list_len(args)); // restore the parent queue for any jobs still alive - for (i = 0; i < args->lv_len; i++) { + for (i = 0; i < tv_list_len(args); i++) { if (jobs[i] == NULL) { tv_list_append_number(rv, -3); continue; @@ -11774,7 +11820,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr) multiqueue_free(waiting_jobs); xfree(jobs); ui_busy_stop(); - rv->lv_refcount++; + tv_list_ref(rv); rettv->v_type = VAR_LIST; rettv->vval.v_list = rv; } @@ -11788,9 +11834,6 @@ static void f_join(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG(_(e_listreq)); return; } - if (argvars[0].vval.v_list == NULL) { - return; - } const char *const sep = (argvars[1].v_type == VAR_UNKNOWN ? " " : tv_get_string_chk(&argvars[1])); @@ -12161,7 +12204,8 @@ static void f_mapcheck(typval_T *argvars, typval_T *rettv, FunPtr fptr) } -static void find_some_match(typval_T *argvars, typval_T *rettv, int type) +static void find_some_match(typval_T *const argvars, typval_T *const rettv, + const SomeMatchType type) { char_u *str = NULL; long len = 0; @@ -12182,25 +12226,38 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) p_cpo = (char_u *)""; rettv->vval.v_number = -1; - if (type == 3 || type == 4) { - // type 3: return empty list when there are no matches. - // type 4: return ["", -1, -1, -1] - tv_list_alloc_ret(rettv); - if (type == 4) { + switch (type) { + // matchlist(): return empty list when there are no matches. + case kSomeMatchList: { + tv_list_alloc_ret(rettv, kListLenMayKnow); + break; + } + // matchstrpos(): return ["", -1, -1, -1] + case kSomeMatchStrPos: { + tv_list_alloc_ret(rettv, 4); tv_list_append_string(rettv->vval.v_list, "", 0); tv_list_append_number(rettv->vval.v_list, -1); tv_list_append_number(rettv->vval.v_list, -1); tv_list_append_number(rettv->vval.v_list, -1); + break; + } + case kSomeMatchStr: { + rettv->v_type = VAR_STRING; + rettv->vval.v_string = NULL; + break; + } + case kSomeMatch: + case kSomeMatchEnd: { + // Do nothing: zero is default. + break; } - } else if (type == 2) { - rettv->v_type = VAR_STRING; - rettv->vval.v_string = NULL; } if (argvars[0].v_type == VAR_LIST) { - if ((l = argvars[0].vval.v_list) == NULL) + if ((l = argvars[0].vval.v_list) == NULL) { goto theend; - li = l->lv_first; + } + li = tv_list_first(l); } else { expr = str = (char_u *)tv_get_string(&argvars[0]); len = (long)STRLEN(str); @@ -12220,11 +12277,11 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) goto theend; } if (l != NULL) { - li = tv_list_find(l, start); - if (li == NULL) { + idx = tv_list_uidx(l, start); + if (idx == -1) { goto theend; } - idx = l->lv_idx; // Use the cached index. + li = tv_list_find(l, idx); } else { if (start < 0) start = 0; @@ -12260,7 +12317,8 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) break; } xfree(tofree); - tofree = expr = str = (char_u *)encode_tv2echo(&li->li_tv, NULL); + tofree = expr = str = (char_u *)encode_tv2echo(TV_LIST_ITEM_TV(li), + NULL); if (str == NULL) { break; } @@ -12275,8 +12333,8 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) /* Advance to just after the match. */ if (l != NULL) { - li = li->li_next; - ++idx; + li = TV_LIST_ITEM_NEXT(l, li); + idx++; } else { startcol = (colnr_T)(regmatch.startp[0] + (*mb_ptr2len)(regmatch.startp[0]) - str); @@ -12288,62 +12346,76 @@ static void find_some_match(typval_T *argvars, typval_T *rettv, int type) } if (match) { - if (type == 4) { - listitem_T *li1 = rettv->vval.v_list->lv_first; - listitem_T *li2 = li1->li_next; - listitem_T *li3 = li2->li_next; - listitem_T *li4 = li3->li_next; - xfree(li1->li_tv.vval.v_string); - - int rd = (int)(regmatch.endp[0] - regmatch.startp[0]); - li1->li_tv.vval.v_string = vim_strnsave(regmatch.startp[0], rd); - li3->li_tv.vval.v_number = (varnumber_T)(regmatch.startp[0] - expr); - li4->li_tv.vval.v_number = (varnumber_T)(regmatch.endp[0] - expr); - if (l != NULL) { - li2->li_tv.vval.v_number = (varnumber_T)idx; + switch (type) { + case kSomeMatchStrPos: { + list_T *const ret_l = rettv->vval.v_list; + listitem_T *li1 = tv_list_first(ret_l); + listitem_T *li2 = TV_LIST_ITEM_NEXT(ret_l, li1); + listitem_T *li3 = TV_LIST_ITEM_NEXT(ret_l, li2); + listitem_T *li4 = TV_LIST_ITEM_NEXT(ret_l, li3); + xfree(TV_LIST_ITEM_TV(li1)->vval.v_string); + + const size_t rd = (size_t)(regmatch.endp[0] - regmatch.startp[0]); + TV_LIST_ITEM_TV(li1)->vval.v_string = xmemdupz( + (const char *)regmatch.startp[0], rd); + TV_LIST_ITEM_TV(li3)->vval.v_number = (varnumber_T)( + regmatch.startp[0] - expr); + TV_LIST_ITEM_TV(li4)->vval.v_number = (varnumber_T)( + regmatch.endp[0] - expr); + if (l != NULL) { + TV_LIST_ITEM_TV(li2)->vval.v_number = (varnumber_T)idx; + } + break; } - } else if (type == 3) { - int i; - - /* return list with matched string and submatches */ - for (i = 0; i < NSUBEXP; ++i) { - if (regmatch.endp[i] == NULL) { - tv_list_append_string(rettv->vval.v_list, NULL, 0); - } else { - tv_list_append_string(rettv->vval.v_list, - (const char *)regmatch.startp[i], - (regmatch.endp[i] - regmatch.startp[i])); + case kSomeMatchList: { + // Return list with matched string and submatches. + for (int i = 0; i < NSUBEXP; i++) { + if (regmatch.endp[i] == NULL) { + tv_list_append_string(rettv->vval.v_list, NULL, 0); + } else { + tv_list_append_string(rettv->vval.v_list, + (const char *)regmatch.startp[i], + (regmatch.endp[i] - regmatch.startp[i])); + } } + break; } - } else if (type == 2) { - // Return matched string. - if (l != NULL) { - tv_copy(&li->li_tv, rettv); - } else { - rettv->vval.v_string = (char_u *)xmemdupz( - (const char *)regmatch.startp[0], - (size_t)(regmatch.endp[0] - regmatch.startp[0])); + case kSomeMatchStr: { + // Return matched string. + if (l != NULL) { + tv_copy(TV_LIST_ITEM_TV(li), rettv); + } else { + rettv->vval.v_string = (char_u *)xmemdupz( + (const char *)regmatch.startp[0], + (size_t)(regmatch.endp[0] - regmatch.startp[0])); + } + break; } - } else if (l != NULL) { - rettv->vval.v_number = idx; - } else { - if (type != 0) { - rettv->vval.v_number = - (varnumber_T)(regmatch.startp[0] - str); - } else { - rettv->vval.v_number = - (varnumber_T)(regmatch.endp[0] - str); + case kSomeMatch: + case kSomeMatchEnd: { + if (l != NULL) { + rettv->vval.v_number = idx; + } else { + if (type == kSomeMatch) { + rettv->vval.v_number = + (varnumber_T)(regmatch.startp[0] - str); + } else { + rettv->vval.v_number = + (varnumber_T)(regmatch.endp[0] - str); + } + rettv->vval.v_number += (varnumber_T)(str - expr); + } + break; } - rettv->vval.v_number += (varnumber_T)(str - expr); } } vim_regfree(regmatch.regprog); } - if (type == 4 && l == NULL) { + if (type == kSomeMatchStrPos && l == NULL) { // matchstrpos() without a list: drop the second item - tv_list_item_remove(rettv->vval.v_list, - rettv->vval.v_list->lv_first->li_next); + list_T *const ret_l = rettv->vval.v_list; + tv_list_item_remove(ret_l, TV_LIST_ITEM_NEXT(ret_l, tv_list_first(ret_l))); } theend: @@ -12356,7 +12428,7 @@ theend: */ static void f_match(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 1); + find_some_match(argvars, rettv, kSomeMatch); } /* @@ -12409,56 +12481,56 @@ static void f_matchadd(typval_T *argvars, typval_T *rettv, FunPtr fptr) static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - rettv->vval.v_number = -1; + rettv->vval.v_number = -1; - char buf[NUMBUFLEN]; - const char *const group = tv_get_string_buf_chk(&argvars[0], buf); - if (group == NULL) { - return; - } + char buf[NUMBUFLEN]; + const char *const group = tv_get_string_buf_chk(&argvars[0], buf); + if (group == NULL) { + return; + } - if (argvars[1].v_type != VAR_LIST) { - EMSG2(_(e_listarg), "matchaddpos()"); - return; - } + if (argvars[1].v_type != VAR_LIST) { + EMSG2(_(e_listarg), "matchaddpos()"); + return; + } - list_T *l; - l = argvars[1].vval.v_list; - if (l == NULL) { - return; - } + list_T *l; + l = argvars[1].vval.v_list; + if (l == NULL) { + return; + } - bool error = false; - int prio = 10; - int id = -1; - const char *conceal_char = NULL; + bool error = false; + int prio = 10; + int id = -1; + const char *conceal_char = NULL; - if (argvars[2].v_type != VAR_UNKNOWN) { - prio = tv_get_number_chk(&argvars[2], &error); - if (argvars[3].v_type != VAR_UNKNOWN) { - id = tv_get_number_chk(&argvars[3], &error); - if (argvars[4].v_type != VAR_UNKNOWN) { - if (argvars[4].v_type != VAR_DICT) { - EMSG(_(e_dictreq)); - return; - } - dictitem_T *di; - if ((di = tv_dict_find(argvars[4].vval.v_dict, S_LEN("conceal"))) - != NULL) { - conceal_char = tv_get_string(&di->di_tv); - } + if (argvars[2].v_type != VAR_UNKNOWN) { + prio = tv_get_number_chk(&argvars[2], &error); + if (argvars[3].v_type != VAR_UNKNOWN) { + id = tv_get_number_chk(&argvars[3], &error); + if (argvars[4].v_type != VAR_UNKNOWN) { + if (argvars[4].v_type != VAR_DICT) { + EMSG(_(e_dictreq)); + return; + } + dictitem_T *di; + if ((di = tv_dict_find(argvars[4].vval.v_dict, S_LEN("conceal"))) + != NULL) { + conceal_char = tv_get_string(&di->di_tv); } } } - if (error == true) { - return; - } + } + if (error == true) { + return; + } - // id == 3 is ok because matchaddpos() is supposed to substitute :3match - if (id == 1 || id == 2) { - EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id); - return; - } + // id == 3 is ok because matchaddpos() is supposed to substitute :3match + if (id == 1 || id == 2) { + EMSGN(_("E798: ID is reserved for \"match\": %" PRId64), id); + return; + } rettv->vval.v_number = match_add(curwin, group, NULL, prio, id, l, conceal_char); @@ -12469,14 +12541,16 @@ static void f_matchaddpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matcharg(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + const int id = tv_get_number(&argvars[0]); - int id = tv_get_number(&argvars[0]); + tv_list_alloc_ret(rettv, (id >= 1 && id <= 3 + ? 2 + : 0)); if (id >= 1 && id <= 3) { - matchitem_T *m; + matchitem_T *const m = (matchitem_T *)get_match(curwin, id); - if ((m = (matchitem_T *)get_match(curwin, id)) != NULL) { + if (m != NULL) { tv_list_append_string(rettv->vval.v_list, (const char *)syn_id2name(m->hlg_id), -1); tv_list_append_string(rettv->vval.v_list, (const char *)m->pattern, -1); @@ -12501,7 +12575,7 @@ static void f_matchdelete(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 0); + find_some_match(argvars, rettv, kSomeMatchEnd); } /* @@ -12509,7 +12583,7 @@ static void f_matchend(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 3); + find_some_match(argvars, rettv, kSomeMatchList); } /* @@ -12517,13 +12591,13 @@ static void f_matchlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_matchstr(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 2); + find_some_match(argvars, rettv, kSomeMatchStr); } /// "matchstrpos()" function static void f_matchstrpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - find_some_match(argvars, rettv, 4); + find_some_match(argvars, rettv, kSomeMatchStrPos); } /// Get maximal/minimal number value in a list or dictionary @@ -12539,41 +12613,41 @@ static void max_min(const typval_T *const tv, typval_T *const rettv, const bool domax) FUNC_ATTR_NONNULL_ALL { - varnumber_T n = 0; bool error = false; + rettv->vval.v_number = 0; + varnumber_T n = (domax ? VARNUMBER_MIN : VARNUMBER_MAX); if (tv->v_type == VAR_LIST) { - const list_T *const l = tv->vval.v_list; - if (tv_list_len(l) != 0) { - n = tv_get_number_chk(&l->lv_first->li_tv, &error); - for (const listitem_T *li = l->lv_first->li_next; li != NULL && !error; - li = li->li_next) { - const varnumber_T i = tv_get_number_chk(&li->li_tv, &error); - if (domax ? i > n : i < n) { - n = i; - } - } + if (tv_list_len(tv->vval.v_list) == 0) { + return; } + TV_LIST_ITER_CONST(tv->vval.v_list, li, { + const varnumber_T i = tv_get_number_chk(TV_LIST_ITEM_TV(li), &error); + if (error) { + return; + } + if (domax ? i > n : i < n) { + n = i; + } + }); } else if (tv->v_type == VAR_DICT) { - if (tv->vval.v_dict != NULL) { - bool first = true; - TV_DICT_ITER(tv->vval.v_dict, di, { - const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); - if (error) { - break; - } - if (first) { - n = i; - first = true; - } else if (domax ? i > n : i < n) { - n = i; - } - }); + if (tv_dict_len(tv->vval.v_dict) == 0) { + return; } + TV_DICT_ITER(tv->vval.v_dict, di, { + const varnumber_T i = tv_get_number_chk(&di->di_tv, &error); + if (error) { + return; + } + if (domax ? i > n : i < n) { + n = i; + } + }); } else { EMSG2(_(e_listdictarg), domax ? "max()" : "min()"); + return; } - rettv->vval.v_number = error ? 0 : n; + rettv->vval.v_number = n; } /* @@ -12658,23 +12732,20 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "msgpackdump()"); return; } - list_T *ret_list = tv_list_alloc_ret(rettv); - const list_T *list = argvars[0].vval.v_list; - if (list == NULL) { - return; - } + list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); + list_T *const list = argvars[0].vval.v_list; msgpack_packer *lpacker = msgpack_packer_new(ret_list, &encode_list_write); const char *const msg = _("msgpackdump() argument, index %i"); // Assume that translation will not take more then 4 times more space char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN]; int idx = 0; - for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - vim_snprintf(msgbuf, sizeof(msgbuf), (char *) msg, idx); + TV_LIST_ITER(list, li, { + vim_snprintf(msgbuf, sizeof(msgbuf), (char *)msg, idx); idx++; - if (encode_vim_to_msgpack(lpacker, &li->li_tv, msgbuf) == FAIL) { + if (encode_vim_to_msgpack(lpacker, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) { break; } - } + }); msgpack_packer_free(lpacker); } @@ -12686,12 +12757,12 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "msgpackparse()"); return; } - list_T *ret_list = tv_list_alloc_ret(rettv); - const list_T *list = argvars[0].vval.v_list; - if (list == NULL || list->lv_first == NULL) { + list_T *const ret_list = tv_list_alloc_ret(rettv, kListLenMayKnow); + const list_T *const list = argvars[0].vval.v_list; + if (tv_list_len(list) == 0) { return; } - if (list->lv_first->li_tv.v_type != VAR_STRING) { + if (TV_LIST_ITEM_TV(tv_list_first(list))->v_type != VAR_STRING) { EMSG2(_(e_invarg2), "List item is not a string"); return; } @@ -12731,13 +12802,12 @@ static void f_msgpackparse(typval_T *argvars, typval_T *rettv, FunPtr fptr) goto f_msgpackparse_exit; } if (result == MSGPACK_UNPACK_SUCCESS) { - listitem_T *li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_UNKNOWN; - tv_list_append(ret_list, li); - if (msgpack_to_vim(unpacked.data, &li->li_tv) == FAIL) { + typval_T tv = { .v_type = VAR_UNKNOWN }; + if (msgpack_to_vim(unpacked.data, &tv) == FAIL) { EMSG2(_(e_invarg2), "Failed to convert msgpack string"); goto f_msgpackparse_exit; } + tv_list_append_owned_tv(ret_list, tv); } if (result == MSGPACK_UNPACK_CONTINUE) { if (rlret == OK) { @@ -12943,7 +13013,7 @@ static void f_range(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else if (stride > 0 ? end + 1 < start : end - 1 > start) { emsgf(_("E727: Start past end")); } else { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (end - start) / stride); for (i = start; stride > 0 ? i <= end : i >= end; i += stride) { tv_list_append_number(rettv->vval.v_list, (varnumber_T)i); } @@ -12964,9 +13034,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) long prevlen = 0; /* length of data in prev */ long prevsize = 0; /* size of prev buffer */ long maxline = MAXLNUM; - long cnt = 0; - char_u *p; /* position in buf */ - char_u *start; /* start of current line */ if (argvars[1].v_type != VAR_UNKNOWN) { if (strcmp(tv_get_string(&argvars[1]), "b") == 0) { @@ -12977,7 +13044,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } - tv_list_alloc_ret(rettv); + list_T *const l = tv_list_alloc_ret(rettv, kListLenUnknown); // Always open the file in binary mode, library functions have a mind of // their own about CR-LF conversion. @@ -12987,19 +13054,20 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } - while (cnt < maxline || maxline < 0) { + while (maxline < 0 || tv_list_len(l) < maxline) { readlen = (int)fread(buf, 1, io_size, fd); - /* This for loop processes what was read, but is also entered at end - * of file so that either: - * - an incomplete line gets written - * - a "binary" file gets an empty line at the end if it ends in a - * newline. */ + // This for loop processes what was read, but is also entered at end + // of file so that either: + // - an incomplete line gets written + // - a "binary" file gets an empty line at the end if it ends in a + // newline. + char_u *p; // Position in buf. + char_u *start; // Start of current line. for (p = buf, start = buf; p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary)); - ++p) { + p++) { if (*p == '\n' || readlen <= 0) { - listitem_T *li; char_u *s = NULL; size_t len = p - start; @@ -13026,22 +13094,32 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) prevlen = prevsize = 0; } - li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = 0; - li->li_tv.vval.v_string = s; - tv_list_append(rettv->vval.v_list, li); - - start = p + 1; /* step over newline */ - if ((++cnt >= maxline && maxline >= 0) || readlen <= 0) + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = s, + }); + + start = p + 1; // Step over newline. + if (maxline < 0) { + if (tv_list_len(l) > -maxline) { + assert(tv_list_len(l) == 1 + (-maxline)); + tv_list_item_remove(l, tv_list_first(l)); + } + } else if (tv_list_len(l) >= maxline) { + assert(tv_list_len(l) == maxline); + break; + } + if (readlen <= 0) { break; - } else if (*p == NUL) + } + } else if (*p == NUL) { *p = '\n'; - /* Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this - * when finding the BF and check the previous two bytes. */ - else if (*p == 0xbf && enc_utf8 && !binary) { - /* Find the two bytes before the 0xbf. If p is at buf, or buf - * + 1, these may be in the "prev" string. */ + // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this + // when finding the BF and check the previous two bytes. + } else if (*p == 0xbf && !binary) { + // Find the two bytes before the 0xbf. If p is at buf, or buf + 1, + // these may be in the "prev" string. char_u back1 = p >= buf + 1 ? p[-1] : prevlen >= 1 ? prev[prevlen - 1] : NUL; char_u back2 = p >= buf + 2 ? p[-2] @@ -13075,8 +13153,9 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } /* for */ - if ((cnt >= maxline && maxline >= 0) || readlen <= 0) + if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) { break; + } if (start < p) { /* There's part of a line in buf, store it in "prev". */ if (p - start + prevlen >= prevsize) { @@ -13100,16 +13179,6 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } /* while */ - /* - * For a negative line count use only the lines at the end of the file, - * free the rest. - */ - if (maxline < 0) - while (cnt > -maxline) { - tv_list_item_remove(rettv->vval.v_list, rettv->vval.v_list->lv_first); - cnt--; - } - xfree(prev); fclose(fd); } @@ -13123,9 +13192,7 @@ static void f_readfile(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// @return OK In case of success, FAIL in case of error static int list2proftime(typval_T *arg, proftime_T *tm) FUNC_ATTR_NONNULL_ALL { - if (arg->v_type != VAR_LIST - || arg->vval.v_list == NULL - || arg->vval.v_list->lv_len != 2) { + if (arg->v_type != VAR_LIST || tv_list_len(arg->vval.v_list) != 2) { return FAIL; } @@ -13190,7 +13257,7 @@ static void f_reltime(typval_T *argvars, typval_T *rettv, FunPtr fptr) STATIC_ASSERT(sizeof(u.prof) == sizeof(u) && sizeof(u.split) == sizeof(u), "type punning will produce incorrect results on this platform"); - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 2); tv_list_append_number(rettv->vval.v_list, u.split.high); tv_list_append_number(rettv->vval.v_list, u.split.low); } @@ -13249,8 +13316,8 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } else if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listdictarg), "remove()"); - } else if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, arg_errmsg, TV_TRANSLATE)) { + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + arg_errmsg, TV_TRANSLATE)) { bool error = false; idx = tv_get_number_chk(&argvars[1], &error); @@ -13261,8 +13328,8 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { if (argvars[2].v_type == VAR_UNKNOWN) { // Remove one item, return its value. - tv_list_remove_items(l, item, item); - *rettv = item->li_tv; + tv_list_drop_items(l, item, item); + *rettv = *TV_LIST_ITEM_TV(item); xfree(item); } else { // Remove range of items, return list with values. @@ -13274,21 +13341,17 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { int cnt = 0; - for (li = item; li != NULL; li = li->li_next) { - ++cnt; - if (li == item2) + for (li = item; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) { + cnt++; + if (li == item2) { break; + } } if (li == NULL) { // Didn't find "item2" after "item". emsgf(_(e_invrange)); } else { - tv_list_remove_items(l, item, item2); - l = tv_list_alloc_ret(rettv); - l->lv_first = item; - l->lv_last = item2; - item->li_prev = NULL; - item2->li_next = NULL; - l->lv_len = cnt; + tv_list_move_items(l, item, item2, tv_list_alloc_ret(rettv, cnt), + cnt); } } } @@ -13318,7 +13381,7 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, FunPtr fptr) { varnumber_T n = tv_get_number(&argvars[1]); if (argvars[0].v_type == VAR_LIST) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (n > 0) * n * tv_list_len(argvars[0].vval.v_list)); while (n-- > 0) { tv_list_extend(rettv->vval.v_list, argvars[0].vval.v_list, NULL); } @@ -13433,7 +13496,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr) q[-1] = NUL; q = (char *)path_tail((char_u *)p); } - if (q > p && !path_is_absolute_path((const char_u *)buf)) { + if (q > p && !path_is_absolute((const char_u *)buf)) { // Symlink is relative to directory of argument. Replace the // symlink with the resolved name in the same directory. const size_t p_len = strlen(p); @@ -13524,21 +13587,12 @@ static void f_reverse(typval_T *argvars, typval_T *rettv, FunPtr fptr) list_T *l; if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), "reverse()"); - } else if ((l = argvars[0].vval.v_list) != NULL - && !tv_check_lock(l->lv_lock, N_("reverse() argument"), - TV_TRANSLATE)) { - listitem_T *li = l->lv_last; - l->lv_first = l->lv_last = NULL; - l->lv_len = 0; - while (li != NULL) { - listitem_T *const ni = li->li_prev; - tv_list_append(l, li); - li = ni; - } + } else if (!tv_check_lock(tv_list_locked((l = argvars[0].vval.v_list)), + N_("reverse() argument"), TV_TRANSLATE)) { + tv_list_reverse(l); rettv->vval.v_list = l; rettv->v_type = VAR_LIST; - l->lv_refcount++; - l->lv_idx = l->lv_len - l->lv_idx - 1; + tv_list_ref(l); } } @@ -13760,7 +13814,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) scid_T save_current_SID; uint8_t *save_sourcing_name, *save_autocmd_fname, *save_autocmd_match; linenr_T save_sourcing_lnum; - int save_autocmd_fname_full, save_autocmd_bufnr; + int save_autocmd_bufnr; void *save_funccalp; if (l_provider_call_nesting) { @@ -13771,16 +13825,14 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) save_sourcing_lnum = sourcing_lnum; save_autocmd_fname = autocmd_fname; save_autocmd_match = autocmd_match; - save_autocmd_fname_full = autocmd_fname_full; save_autocmd_bufnr = autocmd_bufnr; save_funccalp = save_funccal(); - // + current_SID = provider_caller_scope.SID; sourcing_name = provider_caller_scope.sourcing_name; sourcing_lnum = provider_caller_scope.sourcing_lnum; autocmd_fname = provider_caller_scope.autocmd_fname; autocmd_match = provider_caller_scope.autocmd_match; - autocmd_fname_full = provider_caller_scope.autocmd_fname_full; autocmd_bufnr = provider_caller_scope.autocmd_bufnr; restore_funccal(provider_caller_scope.funccalp); } @@ -13796,7 +13848,6 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr) sourcing_lnum = save_sourcing_lnum; autocmd_fname = save_autocmd_fname; autocmd_match = save_autocmd_match; - autocmd_fname_full = save_autocmd_fname_full; autocmd_bufnr = save_autocmd_bufnr; restore_funccal(save_funccalp); } @@ -13836,14 +13887,17 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) int argsl = 0; if (argvars[1].v_type == VAR_LIST) { args = argvars[1].vval.v_list; - argsl = args->lv_len; + argsl = tv_list_len(args); // Assert that all list items are strings - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - if (arg->li_tv.v_type != VAR_STRING) { - EMSG(_(e_invarg)); + int i = 0; + TV_LIST_ITER_CONST(args, arg, { + if (TV_LIST_ITEM_TV(arg)->v_type != VAR_STRING) { + emsgf(_("E5010: List item %d of the second argument is not a string"), + i); return; } - } + i++; + }); } if (argvars[0].vval.v_string == NULL || argvars[0].vval.v_string[0] == NUL) { @@ -13861,9 +13915,9 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr) int i = 1; // Copy arguments to the vector if (argsl > 0) { - for (listitem_T *arg = args->lv_first; arg != NULL; arg = arg->li_next) { - argv[i++] = xstrdup(tv_get_string(&arg->li_tv)); - } + TV_LIST_ITER_CONST(args, arg, { + argv[i++] = xstrdup(tv_get_string(TV_LIST_ITEM_TV(arg))); + }); } // The last item of argv must be NULL @@ -14094,7 +14148,7 @@ static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) int lnum = 0; int col = 0; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 2); if (searchpair_cmn(argvars, &match_pos) > 0) { lnum = match_pos.lnum; @@ -14262,18 +14316,14 @@ do_searchpair ( static void f_searchpos(typval_T *argvars, typval_T *rettv, FunPtr fptr) { pos_T match_pos; - int lnum = 0; - int col = 0; - int n; int flags = 0; - tv_list_alloc_ret(rettv); + const int n = search_cmn(argvars, &match_pos, &flags); - n = search_cmn(argvars, &match_pos, &flags); - if (n > 0) { - lnum = match_pos.lnum; - col = match_pos.col; - } + tv_list_alloc_ret(rettv, 2 + (!!(flags & SP_SUBPAT))); + + const int lnum = (n > 0 ? match_pos.lnum : 0); + const int col = (n > 0 ? match_pos.col : 0); tv_list_append_number(rettv->vval.v_list, (varnumber_T)lnum); tv_list_append_number(rettv->vval.v_list, (varnumber_T)col); @@ -14289,13 +14339,9 @@ static void f_serverlist(typval_T *argvars, typval_T *rettv, FunPtr fptr) char **addrs = server_address_list(&n); // Copy addrs into a linked list. - list_T *l = tv_list_alloc_ret(rettv); + list_T *const l = tv_list_alloc_ret(rettv, n); for (size_t i = 0; i < n; i++) { - listitem_T *li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = 0; - li->li_tv.vval.v_string = (char_u *)addrs[i]; - tv_list_append(l, li); + tv_list_append_allocated_string(l, addrs[i]); } xfree(addrs); } @@ -14357,8 +14403,11 @@ static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr) return; } + rettv->v_type = VAR_NUMBER; + rettv->vval.v_number = 0; if (argvars[0].vval.v_string) { - server_stop((char *) argvars[0].vval.v_string); + bool rv = server_stop((char *)argvars[0].vval.v_string); + rettv->vval.v_number = (rv ? 1 : 0); } } @@ -14503,25 +14552,26 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr) const char *line = NULL; if (argvars[1].v_type == VAR_LIST) { l = argvars[1].vval.v_list; - li = l->lv_first; + li = tv_list_first(l); } else { line = tv_get_string_chk(&argvars[1]); } - /* default result is zero == OK */ + // Default result is zero == OK. for (;; ) { - if (l != NULL) { + if (argvars[1].v_type == VAR_LIST) { // List argument, get next string. if (li == NULL) { break; } - line = tv_get_string_chk(&li->li_tv); - li = li->li_next; + 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) + 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. */ @@ -14593,7 +14643,8 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv) return; } const char *const act = tv_get_string_chk(action_arg); - if ((*act == 'a' || *act == 'r' || *act == ' ') && act[1] == NUL) { + if ((*act == 'a' || *act == 'r' || *act == ' ' || *act == 'f') + && act[1] == NUL) { action = *act; } else { EMSG2(_(e_invact), act); @@ -14622,8 +14673,8 @@ skip_args: title = (wp ? "setloclist()" : "setqflist()"); } - list_T *l = list_arg->vval.v_list; - if (l && set_errorlist(wp, l, action, (char_u *)title, d) == OK) { + list_T *const l = list_arg->vval.v_list; + if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) { rettv->vval.v_number = 0; } } @@ -14648,8 +14699,6 @@ static void f_setloclist(typval_T *argvars, typval_T *rettv, FunPtr fptr) */ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - list_T *l; - listitem_T *li; dict_T *d; list_T *s = NULL; @@ -14658,92 +14707,89 @@ static void f_setmatches(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG(_(e_listreq)); return; } - if ((l = argvars[0].vval.v_list) != NULL) { - - /* To some extent make sure that we are dealing with a list from - * "getmatches()". */ - li = l->lv_first; - while (li != NULL) { - if (li->li_tv.v_type != VAR_DICT - || (d = li->li_tv.vval.v_dict) == NULL) { - EMSG(_(e_invarg)); - return; - } - if (!(tv_dict_find(d, S_LEN("group")) != NULL - && (tv_dict_find(d, S_LEN("pattern")) != NULL - || tv_dict_find(d, S_LEN("pos1")) != NULL) - && tv_dict_find(d, S_LEN("priority")) != NULL - && tv_dict_find(d, S_LEN("id")) != NULL)) { - EMSG(_(e_invarg)); - return; - } - li = li->li_next; + list_T *const l = argvars[0].vval.v_list; + // To some extent make sure that we are dealing with a list from + // "getmatches()". + int i = 0; + TV_LIST_ITER_CONST(l, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT + || (d = TV_LIST_ITEM_TV(li)->vval.v_dict) == NULL) { + emsgf(_("E474: List item %d is either not a dictionary " + "or an empty one"), i); + return; } + if (!(tv_dict_find(d, S_LEN("group")) != NULL + && (tv_dict_find(d, S_LEN("pattern")) != NULL + || tv_dict_find(d, S_LEN("pos1")) != NULL) + && tv_dict_find(d, S_LEN("priority")) != NULL + && tv_dict_find(d, S_LEN("id")) != NULL)) { + emsgf(_("E474: List item %d is missing one of the required keys"), i); + return; + } + i++; + }); - clear_matches(curwin); - li = l->lv_first; - bool match_add_failed = false; - while (li != NULL) { - int i = 0; - - d = li->li_tv.vval.v_dict; - dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); - if (di == NULL) { - if (s == NULL) { - s = tv_list_alloc(); - } + clear_matches(curwin); + bool match_add_failed = false; + TV_LIST_ITER_CONST(l, li, { + int i = 0; - // match from matchaddpos() - for (i = 1; i < 9; i++) { - char buf[5]; - snprintf(buf, sizeof(buf), "pos%d", i); - dictitem_T *const pos_di = tv_dict_find(d, buf, -1); - if (pos_di != NULL) { - if (pos_di->di_tv.v_type != VAR_LIST) { - return; - } + d = TV_LIST_ITEM_TV(li)->vval.v_dict; + dictitem_T *const di = tv_dict_find(d, S_LEN("pattern")); + if (di == NULL) { + if (s == NULL) { + s = tv_list_alloc(9); + } - tv_list_append_tv(s, &pos_di->di_tv); - s->lv_refcount++; - } else { - break; + // match from matchaddpos() + for (i = 1; i < 9; i++) { + char buf[5]; + snprintf(buf, sizeof(buf), "pos%d", i); + dictitem_T *const pos_di = tv_dict_find(d, buf, -1); + if (pos_di != NULL) { + if (pos_di->di_tv.v_type != VAR_LIST) { + return; } - } - } - // Note: there are three number buffers involved: - // - group_buf below. - // - numbuf in tv_dict_get_string(). - // - mybuf in tv_get_string(). - // - // If you change this code make sure that buffers will not get - // accidentally reused. - char group_buf[NUMBUFLEN]; - const char *const group = tv_dict_get_string_buf(d, "group", group_buf); - const int priority = (int)tv_dict_get_number(d, "priority"); - const int id = (int)tv_dict_get_number(d, "id"); - dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); - const char *const conceal = (conceal_di != NULL - ? tv_get_string(&conceal_di->di_tv) - : NULL); - if (i == 0) { - if (match_add(curwin, group, - tv_dict_get_string(d, "pattern", false), - priority, id, NULL, conceal) != id) { - match_add_failed = true; - } - } else { - if (match_add(curwin, group, NULL, priority, id, s, conceal) != id) { - match_add_failed = true; + tv_list_append_tv(s, &pos_di->di_tv); + tv_list_ref(s); + } else { + break; } - tv_list_unref(s); - s = NULL; } - li = li->li_next; } - if (!match_add_failed) { - rettv->vval.v_number = 0; + + // Note: there are three number buffers involved: + // - group_buf below. + // - numbuf in tv_dict_get_string(). + // - mybuf in tv_get_string(). + // + // If you change this code make sure that buffers will not get + // accidentally reused. + char group_buf[NUMBUFLEN]; + const char *const group = tv_dict_get_string_buf(d, "group", group_buf); + const int priority = (int)tv_dict_get_number(d, "priority"); + const int id = (int)tv_dict_get_number(d, "id"); + dictitem_T *const conceal_di = tv_dict_find(d, S_LEN("conceal")); + const char *const conceal = (conceal_di != NULL + ? tv_get_string(&conceal_di->di_tv) + : NULL); + if (i == 0) { + if (match_add(curwin, group, + tv_dict_get_string(d, "pattern", false), + priority, id, NULL, conceal) != id) { + match_add_failed = true; + } + } else { + if (match_add(curwin, group, NULL, priority, id, s, conceal) != id) { + match_add_failed = true; + } + tv_list_unref(s); + s = NULL; } + }); + if (!match_add_failed) { + rettv->vval.v_number = 0; } } @@ -14860,7 +14906,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type == VAR_LIST) { list_T *ll = argvars[1].vval.v_list; // If the list is NULL handle like an empty list. - int len = ll == NULL ? 0 : ll->lv_len; + const int len = tv_list_len(ll); // First half: use for pointers to result lines; second half: use for // pointers to allocated copies. @@ -14869,11 +14915,9 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) char **allocval = lstval + len + 2; char **curallocval = allocval; - for (listitem_T *li = ll == NULL ? NULL : ll->lv_first; - li != NULL; - li = li->li_next) { + TV_LIST_ITER_CONST(ll, li, { char buf[NUMBUFLEN]; - *curval = tv_get_string_buf_chk(&li->li_tv, buf); + *curval = tv_get_string_buf_chk(TV_LIST_ITEM_TV(li), buf); if (*curval == NULL) { goto free_lstval; } @@ -14885,7 +14929,7 @@ static void f_setreg(typval_T *argvars, typval_T *rettv, FunPtr fptr) curallocval++; } curval++; - } + }); *curval++ = NULL; write_reg_contents_lst(regname, (char_u **)lstval, append, yank_type, @@ -15106,12 +15150,6 @@ static void f_sockconnect(typval_T *argvars, typval_T *rettv, FunPtr fptr) rettv->v_type = VAR_NUMBER; } -/// struct used in the array that's given to qsort() -typedef struct { - listitem_T *item; - int idx; -} sortItem_T; - /// struct storing information about current sort typedef struct { int item_compare_ic; @@ -15132,11 +15170,11 @@ static sortinfo_T *sortinfo = NULL; */ static int item_compare(const void *s1, const void *s2, bool keep_zero) { - sortItem_T *const si1 = (sortItem_T *)s1; - sortItem_T *const si2 = (sortItem_T *)s2; + ListSortItem *const si1 = (ListSortItem *)s1; + ListSortItem *const si2 = (ListSortItem *)s2; - typval_T *const tv1 = &si1->item->li_tv; - typval_T *const tv2 = &si2->item->li_tv; + typval_T *const tv1 = TV_LIST_ITEM_TV(si1->item); + typval_T *const tv2 = TV_LIST_ITEM_TV(si2->item); int res; @@ -15208,6 +15246,8 @@ item_compare_end: // When the result would be zero, compare the item indexes. Makes the // sort stable. if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. res = si1->idx > si2->idx ? 1 : -1; } return res; @@ -15225,7 +15265,7 @@ static int item_compare_not_keeping_zero(const void *s1, const void *s2) static int item_compare2(const void *s1, const void *s2, bool keep_zero) { - sortItem_T *si1, *si2; + ListSortItem *si1, *si2; int res; typval_T rettv; typval_T argv[3]; @@ -15238,8 +15278,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) return 0; } - si1 = (sortItem_T *)s1; - si2 = (sortItem_T *)s2; + si1 = (ListSortItem *)s1; + si2 = (ListSortItem *)s2; if (partial == NULL) { func_name = sortinfo->item_compare_func; @@ -15249,8 +15289,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) // Copy the values. This is needed to be able to set v_lock to VAR_FIXED // in the copy without changing the original list items. - tv_copy(&si1->item->li_tv, &argv[0]); - tv_copy(&si2->item->li_tv, &argv[1]); + tv_copy(TV_LIST_ITEM_TV(si1->item), &argv[0]); + tv_copy(TV_LIST_ITEM_TV(si2->item), &argv[1]); rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this res = call_func((const char_u *)func_name, @@ -15273,6 +15313,8 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero) // When the result would be zero, compare the pointers themselves. Makes // the sort stable. if (res == 0 && !keep_zero) { + // WARNING: When using uniq si1 and si2 are actually listitem_T **, no + // indexes are there. res = si1->idx > si2->idx ? 1 : -1; } @@ -15294,9 +15336,7 @@ static int item_compare2_not_keeping_zero(const void *s1, const void *s2) */ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) { - list_T *l; - listitem_T *li; - sortItem_T *ptrs; + ListSortItem *ptrs; long len; long i; @@ -15313,13 +15353,13 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) if (argvars[0].v_type != VAR_LIST) { EMSG2(_(e_listarg), sort ? "sort()" : "uniq()"); } else { - l = argvars[0].vval.v_list; - if (l == NULL || tv_check_lock(l->lv_lock, arg_errmsg, TV_TRANSLATE)) { - goto theend; + list_T *const l = argvars[0].vval.v_list; + if (tv_check_lock(tv_list_locked(l), arg_errmsg, TV_TRANSLATE)) { + goto theend; } rettv->vval.v_list = l; rettv->v_type = VAR_LIST; - ++l->lv_refcount; + tv_list_ref(l); len = tv_list_len(l); if (len <= 1) { @@ -15385,80 +15425,45 @@ static void do_sort_uniq(typval_T *argvars, typval_T *rettv, bool sort) } } - /* Make an array with each entry pointing to an item in the List. */ - ptrs = xmalloc((size_t)(len * sizeof (sortItem_T))); + // Make an array with each entry pointing to an item in the List. + ptrs = xmalloc((size_t)(len * sizeof(ListSortItem))); - i = 0; if (sort) { - // sort(): ptrs will be the list to sort. - for (li = l->lv_first; li != NULL; li = li->li_next) { - ptrs[i].item = li; - ptrs[i].idx = i; - i++; - } - info.item_compare_func_err = false; - // Test the compare function. - if ((info.item_compare_func != NULL - || info.item_compare_partial != NULL) - && item_compare2_not_keeping_zero(&ptrs[0], &ptrs[1]) - == ITEM_COMPARE_FAIL) { + tv_list_item_sort(l, ptrs, + ((info.item_compare_func == NULL + && info.item_compare_partial == NULL) + ? item_compare_not_keeping_zero + : item_compare2_not_keeping_zero), + &info.item_compare_func_err); + if (info.item_compare_func_err) { EMSG(_("E702: Sort compare function failed")); - } else { - // Sort the array with item pointers. - qsort(ptrs, (size_t)len, sizeof (sortItem_T), - (info.item_compare_func == NULL - && info.item_compare_partial == NULL ? - item_compare_not_keeping_zero : - item_compare2_not_keeping_zero)); - - if (!info.item_compare_func_err) { - // Clear the list and append the items in the sorted order. - l->lv_first = NULL; - l->lv_last = NULL; - l->lv_idx_item = NULL; - l->lv_len = 0; - - for (i = 0; i < len; i++) { - tv_list_append(l, ptrs[i].item); - } - } } } else { - int (*item_compare_func_ptr)(const void *, const void *); + ListSorter item_compare_func_ptr; // f_uniq(): ptrs will be a stack of items to remove. info.item_compare_func_err = false; if (info.item_compare_func != NULL || info.item_compare_partial != NULL) { - item_compare_func_ptr = item_compare2_keeping_zero; + item_compare_func_ptr = item_compare2_keeping_zero; } else { - item_compare_func_ptr = item_compare_keeping_zero; + item_compare_func_ptr = item_compare_keeping_zero; } - for (li = l->lv_first; li != NULL && li->li_next != NULL; li = li->li_next) { - if (item_compare_func_ptr(&li, &li->li_next) == 0) { - ptrs[i++].item = li; - } - if (info.item_compare_func_err) { - EMSG(_("E882: Uniq compare function failed")); - break; - } - } - - if (!info.item_compare_func_err) { - while (--i >= 0) { - assert(ptrs[i].item->li_next); - li = ptrs[i].item->li_next; - ptrs[i].item->li_next = li->li_next; - if (li->li_next != NULL) { - li->li_next->li_prev = ptrs[i].item; - } else { - l->lv_last = ptrs[i].item; + int idx = 0; + for (listitem_T *li = TV_LIST_ITEM_NEXT(l, tv_list_first(l)) + ; li != NULL;) { + listitem_T *const prev_li = TV_LIST_ITEM_PREV(l, li); + if (item_compare_func_ptr(&prev_li, &li) == 0) { + if (info.item_compare_func_err) { + EMSG(_("E882: Uniq compare function failed")); + break; } - tv_list_watch_fix(l, li); - tv_list_item_free(li); - l->lv_len--; + li = tv_list_item_remove(l, li); + } else { + idx++; + li = TV_LIST_ITEM_NEXT(l, li); } } } @@ -15549,13 +15554,12 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) hlf_T attr = HLF_COUNT; size_t len = 0; - tv_list_alloc_ret(rettv); - if (argvars[0].v_type == VAR_UNKNOWN) { // Find the start and length of the badly spelled word. len = spell_move_to(curwin, FORWARD, true, true, &attr); if (len != 0) { word = (char *)get_cursor_pos_ptr(); + curwin->w_set_curswant = true; } } else if (curwin->w_p_spell && *curbuf->b_s.b_p_spl != NUL) { const char *str = tv_get_string_chk(&argvars[0]); @@ -15575,6 +15579,7 @@ static void f_spellbadword(typval_T *argvars, typval_T *rettv, FunPtr fptr) } assert(len <= INT_MAX); + tv_list_alloc_ret(rettv, 2); tv_list_append_string(rettv->vval.v_list, word, len); tv_list_append_string(rettv->vval.v_list, (attr == HLF_SPB ? "bad" @@ -15591,41 +15596,36 @@ static void f_spellsuggest(typval_T *argvars, typval_T *rettv, FunPtr fptr) { bool typeerr = false; int maxcount; - garray_T ga; - listitem_T *li; + garray_T ga = GA_EMPTY_INIT_VALUE; bool need_capital = false; - tv_list_alloc_ret(rettv); - if (curwin->w_p_spell && *curwin->w_s->b_p_spl != NUL) { const char *const str = tv_get_string(&argvars[0]); if (argvars[1].v_type != VAR_UNKNOWN) { maxcount = tv_get_number_chk(&argvars[1], &typeerr); if (maxcount <= 0) { - return; + goto f_spellsuggest_return; } if (argvars[2].v_type != VAR_UNKNOWN) { need_capital = tv_get_number_chk(&argvars[2], &typeerr); if (typeerr) { - return; + goto f_spellsuggest_return; } } - } else + } else { maxcount = 25; + } spell_suggest_list(&ga, (char_u *)str, maxcount, need_capital, false); + } - for (int i = 0; i < ga.ga_len; i++) { - char *p = ((char **)ga.ga_data)[i]; - - li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = 0; - li->li_tv.vval.v_string = (char_u *)p; - tv_list_append(rettv->vval.v_list, li); - } - ga_clear(&ga); +f_spellsuggest_return: + tv_list_alloc_ret(rettv, (ptrdiff_t)ga.ga_len); + for (int i = 0; i < ga.ga_len; i++) { + char *const p = ((char **)ga.ga_data)[i]; + tv_list_append_allocated_string(rettv->vval.v_list, p); } + ga_clear(&ga); } static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) @@ -15657,10 +15657,11 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) pat = "[\\x01- ]\\+"; } - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); - if (typeerr) + if (typeerr) { return; + } regmatch.regprog = vim_regcomp((char_u *)pat, RE_MAGIC + RE_STRING); if (regmatch.regprog != NULL) { @@ -15677,7 +15678,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr) } else { end = str + strlen(str); } - if (keepempty || end > str || (rettv->vval.v_list->lv_len > 0 + if (keepempty || end > str || (tv_list_len(rettv->vval.v_list) > 0 && *str != NUL && match && end < (const char *)regmatch.endp[0])) { @@ -16223,7 +16224,7 @@ static void f_synIDattr(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; } case 'n': { // name - p = get_highlight_name(NULL, id - 1); + p = get_highlight_name_ext(NULL, id - 1, false); break; } case 'r': { // reverse @@ -16287,7 +16288,6 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) memset(str, NUL, sizeof(str)); - tv_list_alloc_ret(rettv); if (lnum >= 1 && lnum <= curbuf->b_ml.ml_line_count && col >= 0 && (size_t)col <= STRLEN(ml_get(lnum)) && curwin->w_p_cole > 0) { (void)syn_get_id(curwin, lnum, col, false, NULL, false); @@ -16296,18 +16296,16 @@ static void f_synconcealed(typval_T *argvars, typval_T *rettv, FunPtr fptr) // get the conceal character if ((syntax_flags & HL_CONCEAL) && curwin->w_p_cole < 3) { cchar = syn_get_sub_char(); - if (cchar == NUL && curwin->w_p_cole == 1 && lcs_conceal != NUL) { - cchar = lcs_conceal; + if (cchar == NUL && curwin->w_p_cole == 1) { + cchar = (lcs_conceal == NUL) ? ' ' : lcs_conceal; } if (cchar != NUL) { - if (has_mbyte) - (*mb_char2bytes)(cchar, str); - else - str[0] = cchar; + utf_char2bytes(cchar, str); } } } + tv_list_alloc_ret(rettv, 3); tv_list_append_number(rettv->vval.v_list, (syntax_flags & HL_CONCEAL) != 0); // -1 to auto-determine strlen tv_list_append_string(rettv->vval.v_list, (const char *)str, -1); @@ -16330,7 +16328,7 @@ static void f_synstack(typval_T *argvars, typval_T *rettv, FunPtr fptr) && lnum <= curbuf->b_ml.ml_line_count && col >= 0 && (size_t)col <= STRLEN(ml_get(lnum))) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); (void)syn_get_id(curwin, lnum, col, false, NULL, true); int id; @@ -16346,7 +16344,7 @@ static list_T *string_to_list(const char *str, size_t len, const bool keepempty) if (!keepempty && str[len - 1] == NL) { len--; } - list_T *const list = tv_list_alloc(); + list_T *const list = tv_list_alloc(kListLenMayKnow); encode_list_write(list, str, len); return list; } @@ -16392,7 +16390,7 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, if (res == NULL) { if (retlist) { // return an empty list when there's no output - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, 0); } else { rettv->vval.v_string = (char_u *) xstrdup(""); } @@ -16405,7 +16403,7 @@ static void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv, keepempty = tv_get_number(&argvars[2]); } rettv->vval.v_list = string_to_list(res, nread, (bool)keepempty); - rettv->vval.v_list->lv_refcount++; + tv_list_ref(rettv->vval.v_list); rettv->v_type = VAR_LIST; xfree(res); @@ -16458,7 +16456,7 @@ static void f_tabpagebuflist(typval_T *argvars, typval_T *rettv, FunPtr fptr) } } if (wp != NULL) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); while (wp != NULL) { tv_list_append_number(rettv->vval.v_list, wp->w_buffer->b_fnum); wp = wp->w_next; @@ -16556,7 +16554,7 @@ static void f_tagfiles(typval_T *argvars, typval_T *rettv, FunPtr fptr) char *fname; tagname_T tn; - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenUnknown); fname = xmalloc(MAXPATHL); bool first = true; @@ -16585,8 +16583,8 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, FunPtr fptr) if (argvars[1].v_type != VAR_UNKNOWN) { fname = tv_get_string(&argvars[1]); } - (void)get_tags(tv_list_alloc_ret(rettv), (char_u *)tag_pattern, - (char_u *)fname); + (void)get_tags(tv_list_alloc_ret(rettv, kListLenUnknown), + (char_u *)tag_pattern, (char_u *)fname); } /* @@ -16692,6 +16690,18 @@ static void f_test_garbagecollect_now(typval_T *argvars, garbage_collect(true); } +// "test_write_list_log()" function +static void f_test_write_list_log(typval_T *const argvars, + typval_T *const rettv, + FunPtr fptr) +{ + const char *const fname = tv_get_string_chk(&argvars[0]); + if (fname == NULL) { + return; + } + list_write_log(fname); +} + bool callback_from_typval(Callback *const callback, typval_T *const arg) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT { @@ -16827,7 +16837,9 @@ static void add_timer_info_all(typval_T *rettv) /// "timer_info([timer])" function static void f_timer_info(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, (argvars[0].v_type != VAR_UNKNOWN + ? 1 + : timers->table->n_occupied)); if (argvars[0].v_type != VAR_UNKNOWN) { if (argvars[0].v_type != VAR_NUMBER) { EMSG(_(e_number_exp)); @@ -16852,6 +16864,12 @@ static void f_timer_pause(typval_T *argvars, typval_T *unused, FunPtr fptr) int paused = (bool)tv_get_number(&argvars[1]); timer_T *timer = pmap_get(uint64_t)(timers, tv_get_number(&argvars[0])); if (timer != NULL) { + if (!timer->paused && paused) { + time_watcher_stop(&timer->tw); + } else if (timer->paused && !paused) { + time_watcher_start(&timer->tw, timer_due_cb, timer->timeout, + timer->timeout); + } timer->paused = paused; } } @@ -17151,7 +17169,7 @@ static void f_type(typval_T *argvars, typval_T *rettv, FunPtr fptr) break; } case VAR_UNKNOWN: { - EMSG2(_(e_intern2), "f_type(UNKNOWN)"); + internal_error("f_type(UNKNOWN)"); break; } } @@ -17187,7 +17205,6 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_alloc_ret(rettv); dict_T *dict = rettv->vval.v_dict; - list_T *list; tv_dict_add_nr(dict, S_LEN("synced"), (varnumber_T)curbuf->b_u_synced); tv_dict_add_nr(dict, S_LEN("seq_last"), (varnumber_T)curbuf->b_u_seq_last); @@ -17197,9 +17214,7 @@ static void f_undotree(typval_T *argvars, typval_T *rettv, FunPtr fptr) tv_dict_add_nr(dict, S_LEN("time_cur"), (varnumber_T)curbuf->b_u_time_cur); tv_dict_add_nr(dict, S_LEN("save_cur"), (varnumber_T)curbuf->b_u_save_nr_cur); - list = tv_list_alloc(); - u_eval_tree(curbuf->b_u_oldhead, list); - tv_dict_add_list(dict, S_LEN("entries"), list); + tv_dict_add_list(dict, S_LEN("entries"), u_eval_tree(curbuf->b_u_oldhead)); } /* @@ -17258,7 +17273,7 @@ static void f_wildmenumode(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "win_findbuf()" function static void f_win_findbuf(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); + tv_list_alloc_ret(rettv, kListLenMayKnow); win_findbuf(argvars, rettv->vval.v_list); } @@ -17277,8 +17292,7 @@ static void f_win_gotoid(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// "win_id2tabwin()" function static void f_win_id2tabwin(typval_T *argvars, typval_T *rettv, FunPtr fptr) { - tv_list_alloc_ret(rettv); - win_id2tabwin(argvars, rettv->vval.v_list); + win_id2tabwin(argvars, rettv); } /// "win_id2win()" function @@ -17449,10 +17463,11 @@ static void f_winsaveview(typval_T *argvars, typval_T *rettv, FunPtr fptr) /// @return true in case of success, false otherwise. static bool write_list(FileDescriptor *const fp, const list_T *const list, const bool binary) + FUNC_ATTR_NONNULL_ARG(1) { int error = 0; - for (const listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - const char *const s = tv_get_string_chk(&li->li_tv); + TV_LIST_ITER_CONST(list, li, { + const char *const s = tv_get_string_chk(TV_LIST_ITEM_TV(li)); if (s == NULL) { return false; } @@ -17479,14 +17494,14 @@ static bool write_list(FileDescriptor *const fp, const list_T *const list, } } } - if (!binary || li->li_next != NULL) { + if (!binary || TV_LIST_ITEM_NEXT(list, li) != NULL) { const ptrdiff_t written = file_write(fp, "\n", 1); if (written < 0) { error = (int)written; goto write_list_error; } } - } + }); if ((error = file_flush(fp)) != 0) { goto write_list_error; } @@ -17496,56 +17511,29 @@ write_list_error: return false; } -/// Initializes a static list with 10 items. -void init_static_list(staticList10_T *sl) -{ - list_T *l = &sl->sl_list; - - memset(sl, 0, sizeof(staticList10_T)); - l->lv_first = &sl->sl_items[0]; - l->lv_last = &sl->sl_items[9]; - l->lv_refcount = DO_NOT_FREE_CNT; - l->lv_lock = VAR_FIXED; - sl->sl_list.lv_len = 10; - - for (int i = 0; i < 10; i++) { - listitem_T *li = &sl->sl_items[i]; - - if (i == 0) { - li->li_prev = NULL; - } else { - li->li_prev = li - 1; - } - if (i == 9) { - li->li_next = NULL; - } else { - li->li_next = li + 1; - } - } -} - /// Saves a typval_T as a string. /// -/// For lists, replaces NLs with NUL and separates items with NLs. +/// For lists or buffers, replaces NLs with NUL and separates items with NLs. /// -/// @param[in] tv A value to store as a string. -/// @param[out] len The length of the resulting string or -1 on error. +/// @param[in] tv Value to store as a string. +/// @param[out] len Length of the resulting string or -1 on error. /// @param[in] endnl If true, the output will end in a newline (if a list). /// @returns an allocated string if `tv` represents a VimL string, list, or /// number; NULL otherwise. static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { + *len = 0; if (tv->v_type == VAR_UNKNOWN) { - *len = 0; return NULL; } - // For types other than list, let tv_get_string_buf_chk() get the value or + // For other types, let tv_get_string_buf_chk() get the value or // print an error. - if (tv->v_type != VAR_LIST) { + if (tv->v_type != VAR_LIST && tv->v_type != VAR_NUMBER) { const char *ret = tv_get_string_chk(tv); - if (ret && (*len = strlen(ret))) { + if (ret) { + *len = strlen(ret); return xmemdupz(ret, (size_t)(*len)); } else { *len = -1; @@ -17553,12 +17541,44 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) } } + if (tv->v_type == VAR_NUMBER) { // Treat number as a buffer-id. + buf_T *buf = buflist_findnr(tv->vval.v_number); + if (buf) { + for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { + for (char_u *p = ml_get_buf(buf, lnum, false); *p != NUL; p++) { + *len += 1; + } + *len += 1; + } + } else { + EMSGN(_(e_nobufnr), tv->vval.v_number); + *len = -1; + return NULL; + } + + if (*len == 0) { + return NULL; + } + + char *ret = xmalloc(*len + 1); + char *end = ret; + for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count; lnum++) { + for (char_u *p = ml_get_buf(buf, lnum, false); *p != NUL; p++) { + *end++ = (*p == '\n') ? NUL : *p; + } + *end++ = '\n'; + } + *end = NUL; + *len = end - ret; + return ret; + } + + assert(tv->v_type == VAR_LIST); // Pre-calculate the resulting length. - *len = 0; list_T *list = tv->vval.v_list; - for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - *len += strlen(tv_get_string(&li->li_tv)) + 1; - } + TV_LIST_ITER_CONST(list, li, { + *len += strlen(tv_get_string(TV_LIST_ITEM_TV(li))) + 1; + }); if (*len == 0) { return NULL; @@ -17566,14 +17586,14 @@ static char *save_tv_as_string(typval_T *tv, ptrdiff_t *const len, bool endnl) char *ret = xmalloc(*len + endnl); char *end = ret; - for (listitem_T *li = list->lv_first; li != NULL; li = li->li_next) { - for (const char *s = tv_get_string(&li->li_tv); *s != NUL; s++) { + TV_LIST_ITER_CONST(list, li, { + for (const char *s = tv_get_string(TV_LIST_ITEM_TV(li)); *s != NUL; s++) { *end++ = (*s == '\n') ? NUL : *s; } - if (endnl || li->li_next != NULL) { + if (endnl || TV_LIST_ITEM_NEXT(list, li) != NULL) { *end++ = '\n'; } - } + }); *end = NUL; *len = end - ret; return ret; @@ -17613,9 +17633,6 @@ static void f_writefile(typval_T *argvars, typval_T *rettv, FunPtr fptr) EMSG2(_(e_listarg), "writefile()"); return; } - if (argvars[0].vval.v_list == NULL) { - return; - } bool binary = false; bool append = false; @@ -17720,9 +17737,9 @@ pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum, // We accept "$" for the column number: last column. li = tv_list_find(l, 1L); - if (li != NULL && li->li_tv.v_type == VAR_STRING - && li->li_tv.vval.v_string != NULL - && STRCMP(li->li_tv.vval.v_string, "$") == 0) { + if (li != NULL && TV_LIST_ITEM_TV(li)->v_type == VAR_STRING + && TV_LIST_ITEM_TV(li)->vval.v_string != NULL + && STRCMP(TV_LIST_ITEM_TV(li)->vval.v_string, "$") == 0) { pos.col = len + 1; } @@ -17799,17 +17816,18 @@ pos_T *var2fpos(const typval_T *const tv, const int dollar_lnum, */ static int list2fpos(typval_T *arg, pos_T *posp, int *fnump, colnr_T *curswantp) { - list_T *l = arg->vval.v_list; + list_T *l; long i = 0; long n; - /* List must be: [fnum, lnum, col, coladd, curswant], where "fnum" is only - * there when "fnump" isn't NULL; "coladd" and "curswant" are optional. */ + // List must be: [fnum, lnum, col, coladd, curswant], where "fnum" is only + // there when "fnump" isn't NULL; "coladd" and "curswant" are optional. if (arg->v_type != VAR_LIST - || l == NULL - || l->lv_len < (fnump == NULL ? 2 : 3) - || l->lv_len > (fnump == NULL ? 4 : 5)) + || (l = arg->vval.v_list) == NULL + || tv_list_len(l) < (fnump == NULL ? 2 : 3) + || tv_list_len(l) > (fnump == NULL ? 4 : 5)) { return FAIL; + } if (fnump != NULL) { n = tv_list_find_nr(l, i++, NULL); // fnum @@ -18234,7 +18252,7 @@ void set_vim_var_list(const VimVarIndex idx, list_T *const val) vimvars[idx].vv_type = VAR_LIST; vimvars[idx].vv_list = val; if (val != NULL) { - val->lv_refcount++; + tv_list_ref(val); } } @@ -19030,7 +19048,7 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, } return; } else if (v->di_tv.v_type != tv->v_type) { - EMSG2(_(e_intern2), "set_var()"); + internal_error("set_var()"); } } @@ -19050,6 +19068,9 @@ static void set_var(const char *name, const size_t name_len, typval_T *const tv, return; } + // Make sure dict is valid + assert(dict != NULL); + v = xmalloc(sizeof(dictitem_T) + strlen(varname)); STRCPY(v->di_key, varname); if (tv_dict_add(dict, v) == FAIL) { @@ -19267,12 +19288,12 @@ int var_item_copy(const vimconv_T *const conv, case VAR_LIST: to->v_type = VAR_LIST; to->v_lock = 0; - if (from->vval.v_list == NULL) + if (from->vval.v_list == NULL) { to->vval.v_list = NULL; - else if (copyID != 0 && from->vval.v_list->lv_copyID == copyID) { - /* use the copy made earlier */ - to->vval.v_list = from->vval.v_list->lv_copylist; - ++to->vval.v_list->lv_refcount; + } else if (copyID != 0 && tv_list_copyid(from->vval.v_list) == copyID) { + // Use the copy made earlier. + to->vval.v_list = tv_list_latest_copy(from->vval.v_list); + tv_list_ref(to->vval.v_list); } else { to->vval.v_list = tv_list_copy(conv, from->vval.v_list, deep, copyID); } @@ -19297,7 +19318,7 @@ int var_item_copy(const vimconv_T *const conv, } break; case VAR_UNKNOWN: - EMSG2(_(e_intern2), "var_item_copy(UNKNOWN)"); + internal_error("var_item_copy(UNKNOWN)"); ret = FAIL; } --recurse; @@ -19365,14 +19386,10 @@ void ex_echo(exarg_T *eap) } msg_putchar_attr((uint8_t)(*p), echo_attr); } else { - if (has_mbyte) { - int i = (*mb_ptr2len)((const char_u *)p); + int i = (*mb_ptr2len)((const char_u *)p); - (void)msg_outtrans_len_attr((char_u *)p, i, echo_attr); - p += i - 1; - } else { - (void)msg_outtrans_len_attr((char_u *)p, 1, echo_attr); - } + (void)msg_outtrans_len_attr((char_u *)p, i, echo_attr); + p += i - 1; } } } @@ -20871,7 +20888,9 @@ void ex_delfunction(exarg_T *eap) if (!eap->skip) { if (fp == NULL) { - EMSG2(_(e_nofunc), eap->arg); + if (!eap->forceit) { + EMSG2(_(e_nofunc), eap->arg); + } return; } if (fp->uf_calls > 0) { @@ -20985,11 +21004,11 @@ void func_unref(char_u *name) if (fp == NULL && isdigit(*name)) { #ifdef EXITFREE if (!entered_free_all_mem) { - EMSG2(_(e_intern2), "func_unref()"); + internal_error("func_unref()"); abort(); } #else - EMSG2(_(e_intern2), "func_unref()"); + internal_error("func_unref()"); abort(); #endif } @@ -21028,7 +21047,7 @@ void func_ref(char_u *name) } else if (isdigit(*name)) { // Only give an error for a numbered function. // Fail silently, when named or lambda function isn't found. - EMSG2(_(e_intern2), "func_ref()"); + internal_error("func_ref()"); } } @@ -21040,6 +21059,22 @@ void func_ptr_ref(ufunc_T *fp) } } +/// Check whether funccall is still referenced outside +/// +/// It is supposed to be referenced if either it is referenced itself or if l:, +/// a: or a:000 are referenced as all these are statically allocated within +/// funccall structure. +static inline bool fc_referenced(const funccall_T *const fc) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT + FUNC_ATTR_NONNULL_ALL +{ + return ((fc->l_varlist.lv_refcount // NOLINT(runtime/deprecated) + != DO_NOT_FREE_CNT) + || fc->l_vars.dv_refcount != DO_NOT_FREE_CNT + || fc->l_avars.dv_refcount != DO_NOT_FREE_CNT + || fc->fc_refcount > 0); +} + /// Call a user function /// /// @param fp Function to call. @@ -21153,9 +21188,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, v->di_tv.v_type = VAR_LIST; v->di_tv.v_lock = VAR_FIXED; v->di_tv.vval.v_list = &fc->l_varlist; - memset(&fc->l_varlist, 0, sizeof(list_T)); - fc->l_varlist.lv_refcount = DO_NOT_FREE_CNT; - fc->l_varlist.lv_lock = VAR_FIXED; + tv_list_init_static(&fc->l_varlist); + tv_list_set_lock(&fc->l_varlist, VAR_FIXED); // Set a:firstline to "firstline" and a:lastline to "lastline". // Set a:name to named arguments. @@ -21204,8 +21238,8 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, if (ai >= 0 && ai < MAX_FUNC_ARGS) { tv_list_append(&fc->l_varlist, &fc->l_listitems[ai]); - fc->l_listitems[ai].li_tv = argvars[i]; - fc->l_listitems[ai].li_tv.v_lock = VAR_FIXED; + *TV_LIST_ITEM_TV(&fc->l_listitems[ai]) = argvars[i]; + TV_LIST_ITEM_TV(&fc->l_listitems[ai])->v_lock = VAR_FIXED; } } @@ -21275,15 +21309,17 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, } } + const bool do_profiling_yes = do_profiling == PROF_YES; + bool func_not_yet_profiling_but_should = - do_profiling == PROF_YES - && !fp->uf_profiling && has_profiling(FALSE, fp->uf_name, NULL); + do_profiling_yes + && !fp->uf_profiling && has_profiling(false, fp->uf_name, NULL); if (func_not_yet_profiling_but_should) func_do_profile(fp); bool func_or_func_caller_profiling = - do_profiling == PROF_YES + do_profiling_yes && (fp->uf_profiling || (fc->caller != NULL && fc->caller->func->uf_profiling)); @@ -21293,7 +21329,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, fp->uf_tm_children = profile_zero(); } - if (do_profiling == PROF_YES) { + if (do_profiling_yes) { script_prof_save(&wait_start); } @@ -21369,8 +21405,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, sourcing_name = save_sourcing_name; sourcing_lnum = save_sourcing_lnum; current_SID = save_current_SID; - if (do_profiling == PROF_YES) + if (do_profiling_yes) { script_prof_restore(&wait_start); + } if (p_verbose >= 12 && sourcing_name != NULL) { ++no_wait_return; @@ -21389,10 +21426,7 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, // If the a:000 list and the l: and a: dicts are not referenced and there // is no closure using it, we can free the funccall_T and what's in it. - if (fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT - && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT - && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT - && fc->fc_refcount <= 0) { + if (!fc_referenced(fc)) { free_funccal(fc, false); } else { // "fc" is still in use. This can happen when returning "a:000", @@ -21407,10 +21441,9 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, }); // Make a copy of the a:000 items, since we didn't do that above. - for (listitem_T *li = fc->l_varlist.lv_first; li != NULL; - li = li->li_next) { - tv_copy(&li->li_tv, &li->li_tv); - } + TV_LIST_ITER(&fc->l_varlist, li, { + tv_copy(TV_LIST_ITEM_TV(li), TV_LIST_ITEM_TV(li)); + }); } if (--fp->uf_calls <= 0 && fp->uf_refcount <= 0) { @@ -21437,10 +21470,8 @@ static void funccal_unref(funccall_T *fc, ufunc_T *fp, bool force) return; } - if (--fc->fc_refcount <= 0 && (force || ( - fc->l_varlist.lv_refcount == DO_NOT_FREE_CNT - && fc->l_vars.dv_refcount == DO_NOT_FREE_CNT - && fc->l_avars.dv_refcount == DO_NOT_FREE_CNT))) { + fc->fc_refcount--; + if (force ? fc->fc_refcount <= 0 : !fc_referenced(fc)) { for (pfc = &previous_funccal; *pfc != NULL; pfc = &(*pfc)->caller) { if (fc == *pfc) { *pfc = fc->caller; @@ -21475,8 +21506,6 @@ free_funccal ( int free_val /* a: vars were allocated */ ) { - listitem_T *li; - for (int i = 0; i < fc->fc_funcs.ga_len; i++) { ufunc_T *fp = ((ufunc_T **)(fc->fc_funcs.ga_data))[i]; @@ -21494,14 +21523,14 @@ free_funccal ( // allocated variables. vars_clear_ext(&fc->l_avars.dv_hashtab, free_val); - /* free all l: variables */ + // Free all l: variables. vars_clear(&fc->l_vars.dv_hashtab); // Free the a:000 variables if they were allocated. if (free_val) { - for (li = fc->l_varlist.lv_first; li != NULL; li = li->li_next) { - tv_clear(&li->li_tv); - } + TV_LIST_ITER(&fc->l_varlist, li, { + tv_clear(TV_LIST_ITEM_TV(li)); + }); } func_ptr_unref(fc->func); @@ -22407,7 +22436,7 @@ static void script_host_eval(char *name, typval_T *argvars, typval_T *rettv) return; } - list_T *args = tv_list_alloc(); + list_T *args = tv_list_alloc(1); tv_list_append_string(args, (const char *)argvars[0].vval.v_string, -1); *rettv = eval_call_provider(name, "eval", args); } @@ -22425,7 +22454,6 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) .sourcing_lnum = sourcing_lnum, .autocmd_fname = autocmd_fname, .autocmd_match = autocmd_match, - .autocmd_fname_full = autocmd_fname_full, .autocmd_bufnr = autocmd_bufnr, .funccalp = save_funccal() }; @@ -22436,8 +22464,8 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) {.v_type = VAR_LIST, .vval.v_list = arguments, .v_lock = 0}, {.v_type = VAR_UNKNOWN} }; - typval_T rettv = {.v_type = VAR_UNKNOWN, .v_lock = 0}; - arguments->lv_refcount++; + typval_T rettv = { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED }; + tv_list_ref(arguments); int dummy; (void)call_func((const char_u *)func, @@ -22458,7 +22486,8 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments) restore_funccal(provider_caller_scope.funccalp); provider_caller_scope = saved_provider_caller_scope; provider_call_nesting--; - + assert(provider_call_nesting >= 0); + return rettv; } @@ -22498,11 +22527,13 @@ bool eval_has_provider(const char *name) } /// Writes "<sourcing_name>:<sourcing_lnum>" to `buf[bufsize]`. -void eval_format_source_name_line(char *buf, size_t bufsize) +void eval_fmt_source_name_line(char *buf, size_t bufsize) { - snprintf(buf, bufsize, "%s:%" PRIdLINENR, - (sourcing_name ? sourcing_name : (char_u *)"?"), - (sourcing_name ? sourcing_lnum : 0)); + if (sourcing_name) { + snprintf(buf, bufsize, "%s:%" PRIdLINENR, sourcing_name, sourcing_lnum); + } else { + snprintf(buf, bufsize, "?"); + } } /// ":checkhealth [plugins]" diff --git a/src/nvim/eval.h b/src/nvim/eval.h index 0c0a6881f6..b798eae187 100644 --- a/src/nvim/eval.h +++ b/src/nvim/eval.h @@ -86,7 +86,6 @@ typedef enum { VV_OLDFILES, VV_WINDOWID, VV_PROGPATH, - VV_COMMAND_OUTPUT, VV_COMPLETED_ITEM, VV_OPTION_NEW, VV_OPTION_OLD, diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua index 54cbc54d78..daa3b637a3 100644 --- a/src/nvim/eval.lua +++ b/src/nvim/eval.lua @@ -313,6 +313,7 @@ return { tempname={}, termopen={args={1, 2}}, test_garbagecollect_now={}, + test_write_list_log={args=1}, timer_info={args={0,1}}, timer_pause={args=2}, timer_start={args={2,3}}, diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c index 0933b1bf9c..17799b500c 100644 --- a/src/nvim/eval/decode.c +++ b/src/nvim/eval/decode.c @@ -60,8 +60,8 @@ static inline void create_special_dict(typval_T *const rettv, dictitem_T *const type_di = tv_dict_item_alloc_len(S_LEN("_TYPE")); type_di->di_tv.v_type = VAR_LIST; type_di->di_tv.v_lock = VAR_UNLOCKED; - type_di->di_tv.vval.v_list = (list_T *) eval_msgpack_type_lists[type]; - type_di->di_tv.vval.v_list->lv_refcount++; + type_di->di_tv.vval.v_list = (list_T *)eval_msgpack_type_lists[type]; + tv_list_ref(type_di->di_tv.vval.v_list); tv_dict_add(dict, type_di); dictitem_T *const val_di = tv_dict_item_alloc_len(S_LEN("_VAL")); val_di->di_tv = val; @@ -120,16 +120,14 @@ static inline int json_decoder_pop(ValuesStackItem obj, last_container = kv_last(*container_stack); } if (last_container.container.v_type == VAR_LIST) { - if (last_container.container.vval.v_list->lv_len != 0 + if (tv_list_len(last_container.container.vval.v_list) != 0 && !obj.didcomma) { EMSG2(_("E474: Expected comma before list item: %s"), val_location); tv_clear(&obj.val); return FAIL; } assert(last_container.special_val == NULL); - listitem_T *obj_li = tv_list_item_alloc(); - obj_li->li_tv = obj.val; - tv_list_append(last_container.container.vval.v_list, obj_li); + tv_list_append_owned_tv(last_container.container.vval.v_list, obj.val); } else if (last_container.stack_index == kv_size(*stack) - 2) { if (!obj.didcolon) { EMSG2(_("E474: Expected colon before dictionary value: %s"), @@ -152,14 +150,10 @@ static inline int json_decoder_pop(ValuesStackItem obj, } obj_di->di_tv = obj.val; } else { - list_T *const kv_pair = tv_list_alloc(); + list_T *const kv_pair = tv_list_alloc(2); tv_list_append_list(last_container.special_val, kv_pair); - listitem_T *const key_li = tv_list_item_alloc(); - key_li->li_tv = key.val; - tv_list_append(kv_pair, key_li); - listitem_T *const val_li = tv_list_item_alloc(); - val_li->li_tv = obj.val; - tv_list_append(kv_pair, val_li); + tv_list_append_owned_tv(kv_pair, key.val); + tv_list_append_owned_tv(kv_pair, obj.val); } } else { // Object with key only @@ -227,14 +221,19 @@ static inline int json_decoder_pop(ValuesStackItem obj, /// Create a new special dictionary that ought to represent a MAP /// /// @param[out] ret_tv Address where new special dictionary is saved. +/// @param[in] len Expected number of items to be populated before list +/// becomes accessible from VimL. It is still valid to +/// underpopulate a list, value only controls how many elements +/// will be allocated in advance. @see ListLenSpecials. /// /// @return [allocated] list which should contain key-value pairs. Return value /// may be safely ignored. -list_T *decode_create_map_special_dict(typval_T *const ret_tv) +list_T *decode_create_map_special_dict(typval_T *const ret_tv, + const ptrdiff_t len) FUNC_ATTR_NONNULL_ALL { - list_T *const list = tv_list_alloc(); - list->lv_refcount++; + list_T *const list = tv_list_alloc(len); + tv_list_ref(list); create_special_dict(ret_tv, kMPMap, ((typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -269,8 +268,8 @@ typval_T decode_string(const char *const s, const size_t len, ? ((s != NULL) && (memchr(s, NUL, len) != NULL)) : (bool)hasnul); if (really_hasnul) { - list_T *const list = tv_list_alloc(); - list->lv_refcount++; + list_T *const list = tv_list_alloc(kListLenMayKnow); + tv_list_ref(list); typval_T tv; create_special_dict(&tv, binary ? kMPBinary : kMPString, ((typval_T) { .v_type = VAR_LIST, @@ -291,7 +290,7 @@ typval_T decode_string(const char *const s, const size_t len, .v_type = VAR_STRING, .v_lock = VAR_UNLOCKED, .vval = { .v_string = (char_u *)( - s_allocated ? (char *)s : xmemdupz(s, len)) }, + (s == NULL || s_allocated) ? (char *)s : xmemdupz(s, len)) }, }; } } @@ -738,8 +737,9 @@ json_decode_string_cycle_start: } else if (last_container.special_val == NULL ? (last_container.container.v_type == VAR_DICT ? (DICT_LEN(last_container.container.vval.v_dict) == 0) - : (last_container.container.vval.v_list->lv_len == 0)) - : (last_container.special_val->lv_len == 0)) { + : (tv_list_len(last_container.container.vval.v_list) + == 0)) + : (tv_list_len(last_container.special_val) == 0)) { emsgf(_("E474: Leading comma: %.*s"), LENP(p, e)); goto json_decode_string_fail; } @@ -848,8 +848,8 @@ json_decode_string_cycle_start: break; } case '[': { - list_T *list = tv_list_alloc(); - list->lv_refcount++; + list_T *list = tv_list_alloc(kListLenMayKnow); + tv_list_ref(list); typval_T tv = { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -869,7 +869,7 @@ json_decode_string_cycle_start: list_T *val_list = NULL; if (next_map_special) { next_map_special = false; - val_list = decode_create_map_special_dict(&tv); + val_list = decode_create_map_special_dict(&tv, kListLenMayKnow); } else { dict_T *dict = tv_dict_alloc(); dict->dv_refcount++; @@ -969,8 +969,8 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) .vval = { .v_number = (varnumber_T) mobj.via.u64 }, }; } else { - list_T *const list = tv_list_alloc(); - list->lv_refcount++; + list_T *const list = tv_list_alloc(4); + tv_list_ref(list); create_special_dict(rettv, kMPInteger, ((typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -992,8 +992,8 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) .vval = { .v_number = (varnumber_T) mobj.via.i64 }, }; } else { - list_T *const list = tv_list_alloc(); - list->lv_refcount++; + list_T *const list = tv_list_alloc(4); + tv_list_ref(list); create_special_dict(rettv, kMPInteger, ((typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, @@ -1038,18 +1038,19 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) break; } case MSGPACK_OBJECT_ARRAY: { - list_T *const list = tv_list_alloc(); - list->lv_refcount++; + list_T *const list = tv_list_alloc((ptrdiff_t)mobj.via.array.size); + tv_list_ref(list); *rettv = (typval_T) { .v_type = VAR_LIST, .v_lock = VAR_UNLOCKED, .vval = { .v_list = list }, }; for (size_t i = 0; i < mobj.via.array.size; i++) { - listitem_T *const li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_UNKNOWN; - tv_list_append(list, li); - if (msgpack_to_vim(mobj.via.array.ptr[i], &li->li_tv) == FAIL) { + // Not populated yet, need to create list item to push. + tv_list_append_owned_tv(list, (typval_T) { .v_type = VAR_UNKNOWN }); + if (msgpack_to_vim(mobj.via.array.ptr[i], + TV_LIST_ITEM_TV(tv_list_last(list))) + == FAIL) { return FAIL; } } @@ -1089,30 +1090,33 @@ int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv) } break; msgpack_to_vim_generic_map: {} - list_T *const list = decode_create_map_special_dict(rettv); + list_T *const list = decode_create_map_special_dict( + rettv, (ptrdiff_t)mobj.via.map.size); for (size_t i = 0; i < mobj.via.map.size; i++) { - list_T *const kv_pair = tv_list_alloc(); + list_T *const kv_pair = tv_list_alloc(2); tv_list_append_list(list, kv_pair); - listitem_T *const key_li = tv_list_item_alloc(); - key_li->li_tv.v_type = VAR_UNKNOWN; - tv_list_append(kv_pair, key_li); - listitem_T *const val_li = tv_list_item_alloc(); - val_li->li_tv.v_type = VAR_UNKNOWN; - tv_list_append(kv_pair, val_li); - if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_li->li_tv) == FAIL) { + + typval_T key_tv = { .v_type = VAR_UNKNOWN }; + if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_tv) == FAIL) { + tv_clear(&key_tv); return FAIL; } - if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_li->li_tv) == FAIL) { + tv_list_append_owned_tv(kv_pair, key_tv); + + typval_T val_tv = { .v_type = VAR_UNKNOWN }; + if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_tv) == FAIL) { + tv_clear(&val_tv); return FAIL; } + tv_list_append_owned_tv(kv_pair, val_tv); } break; } case MSGPACK_OBJECT_EXT: { - list_T *const list = tv_list_alloc(); - list->lv_refcount++; + list_T *const list = tv_list_alloc(2); + tv_list_ref(list); tv_list_append_number(list, mobj.via.ext.type); - list_T *const ext_val_list = tv_list_alloc(); + list_T *const ext_val_list = tv_list_alloc(kListLenMayKnow); tv_list_append_list(list, ext_val_list); create_special_dict(rettv, kMPExt, ((typval_T) { .v_type = VAR_LIST, diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c index ef647b3ee4..9bae436e3d 100644 --- a/src/nvim/eval/encode.c +++ b/src/nvim/eval/encode.c @@ -28,6 +28,11 @@ #include "nvim/lib/kvec.h" #include "nvim/eval/typval_encode.h" +#ifdef __MINGW32__ +# undef fpclassify +# define fpclassify __fpclassify +#endif + #define ga_concat(a, b) ga_concat(a, (char_u *)b) #define utf_ptr2char(b) utf_ptr2char((char_u *)b) #define utf_ptr2len(b) ((size_t)utf_ptr2len((char_u *)b)) @@ -53,17 +58,18 @@ int encode_list_write(void *const data, const char *const buf, const size_t len) list_T *const list = (list_T *) data; const char *const end = buf + len; const char *line_end = buf; - listitem_T *li = list->lv_last; + listitem_T *li = tv_list_last(list); // Continue the last list element if (li != NULL) { line_end = xmemscan(buf, NL, len); if (line_end != buf) { const size_t line_length = (size_t)(line_end - buf); - char *str = (char *)li->li_tv.vval.v_string; + char *str = (char *)TV_LIST_ITEM_TV(li)->vval.v_string; const size_t li_len = (str == NULL ? 0 : strlen(str)); - li->li_tv.vval.v_string = xrealloc(str, li_len + line_length + 1); - str = (char *)li->li_tv.vval.v_string + li_len; + TV_LIST_ITEM_TV(li)->vval.v_string = xrealloc( + str, li_len + line_length + 1); + str = (char *)TV_LIST_ITEM_TV(li)->vval.v_string + li_len; memcpy(str, buf, line_length); str[line_length] = 0; memchrsub(str, NUL, NL, line_length); @@ -135,21 +141,27 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack, } case kMPConvPairs: case kMPConvList: { - int idx = 0; - const listitem_T *li; - for (li = v.data.l.list->lv_first; - li != NULL && li->li_next != v.data.l.li; - li = li->li_next) { - idx++; - } + const int idx = (v.data.l.li == tv_list_first(v.data.l.list) + ? 0 + : (v.data.l.li == NULL + ? tv_list_len(v.data.l.list) - 1 + : (int)tv_list_idx_of_item( + v.data.l.list, + TV_LIST_ITEM_PREV(v.data.l.list, + v.data.l.li)))); + const listitem_T *const li = (v.data.l.li == NULL + ? tv_list_last(v.data.l.list) + : TV_LIST_ITEM_PREV(v.data.l.list, + v.data.l.li)); if (v.type == kMPConvList || li == NULL - || (li->li_tv.v_type != VAR_LIST - && li->li_tv.vval.v_list->lv_len <= 0)) { - vim_snprintf((char *) IObuff, IOSIZE, idx_msg, idx); + || (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST + && tv_list_len(TV_LIST_ITEM_TV(li)->vval.v_list) <= 0)) { + vim_snprintf((char *)IObuff, IOSIZE, idx_msg, idx); ga_concat(&msg_ga, IObuff); } else { - typval_T key_tv = li->li_tv.vval.v_list->lv_first->li_tv; + typval_T key_tv = *TV_LIST_ITEM_TV( + tv_list_first(TV_LIST_ITEM_TV(li)->vval.v_list)); char *const key = encode_tv2echo(&key_tv, NULL); vim_snprintf((char *) IObuff, IOSIZE, key_pair_msg, key, idx); xfree(key); @@ -202,21 +214,17 @@ bool encode_vim_list_to_buf(const list_T *const list, size_t *const ret_len, FUNC_ATTR_NONNULL_ARG(2, 3) FUNC_ATTR_WARN_UNUSED_RESULT { size_t len = 0; - if (list != NULL) { - for (const listitem_T *li = list->lv_first; - li != NULL; - li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING) { - return false; - } - len++; - if (li->li_tv.vval.v_string != 0) { - len += STRLEN(li->li_tv.vval.v_string); - } + TV_LIST_ITER_CONST(list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) { + return false; } - if (len) { - len--; + len++; + if (TV_LIST_ITEM_TV(li)->vval.v_string != NULL) { + len += STRLEN(TV_LIST_ITEM_TV(li)->vval.v_string); } + }); + if (len) { + len--; } *ret_len = len; if (len == 0) { @@ -253,31 +261,34 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, char *const buf_end = buf + nbuf; char *p = buf; while (p < buf_end) { - assert(state->li_length == 0 || state->li->li_tv.vval.v_string != NULL); + assert(state->li_length == 0 + || TV_LIST_ITEM_TV(state->li)->vval.v_string != NULL); for (size_t i = state->offset; i < state->li_length && p < buf_end; i++) { - assert(state->li->li_tv.vval.v_string != NULL); - const char ch = (char)state->li->li_tv.vval.v_string[state->offset++]; + assert(TV_LIST_ITEM_TV(state->li)->vval.v_string != NULL); + const char ch = (char)( + TV_LIST_ITEM_TV(state->li)->vval.v_string[state->offset++]); *p++ = (char)((char)ch == (char)NL ? (char)NUL : (char)ch); } if (p < buf_end) { - state->li = state->li->li_next; + state->li = TV_LIST_ITEM_NEXT(state->list, state->li); if (state->li == NULL) { *read_bytes = (size_t) (p - buf); return OK; } *p++ = NL; - if (state->li->li_tv.v_type != VAR_STRING) { - *read_bytes = (size_t) (p - buf); + if (TV_LIST_ITEM_TV(state->li)->v_type != VAR_STRING) { + *read_bytes = (size_t)(p - buf); return FAIL; } state->offset = 0; - state->li_length = (state->li->li_tv.vval.v_string == NULL + state->li_length = (TV_LIST_ITEM_TV(state->li)->vval.v_string == NULL ? 0 - : STRLEN(state->li->li_tv.vval.v_string)); + : STRLEN(TV_LIST_ITEM_TV(state->li)->vval.v_string)); } } *read_bytes = nbuf; - return (state->offset < state->li_length || state->li->li_next != NULL + return ((state->offset < state->li_length + || TV_LIST_ITEM_NEXT(state->list, state->li) != NULL) ? NOTDONE : OK); } @@ -340,7 +351,7 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, do { \ const char *const fun_ = (const char *)(fun); \ if (fun_ == NULL) { \ - EMSG2(_(e_intern2), "string(): NULL function name"); \ + internal_error("string(): NULL function name"); \ ga_concat(gap, "function(NULL"); \ } else { \ ga_concat(gap, "function("); \ @@ -727,12 +738,11 @@ bool encode_check_json_key(const typval_T *const tv) if (val_di->di_tv.vval.v_list == NULL) { return true; } - for (const listitem_T *li = val_di->di_tv.vval.v_list->lv_first; - li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING) { + TV_LIST_ITER_CONST(val_di->di_tv.vval.v_list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) { return false; } - } + }); return true; } diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h index 9bc665253b..ccea245ab3 100644 --- a/src/nvim/eval/encode.h +++ b/src/nvim/eval/encode.h @@ -33,9 +33,10 @@ int encode_vim_to_echo(garray_T *const packer, /// Structure defining state for read_from_list() typedef struct { + const list_T *const list; ///< List being currently read. const listitem_T *li; ///< Item currently read. - size_t offset; ///< Byte offset inside the read item. - size_t li_length; ///< Length of the string inside the read item. + size_t offset; ///< Byte offset inside the read item. + size_t li_length; ///< Length of the string inside the read item. } ListReaderState; /// Initialize ListReaderState structure @@ -43,11 +44,13 @@ static inline ListReaderState encode_init_lrstate(const list_T *const list) FUNC_ATTR_NONNULL_ALL { return (ListReaderState) { - .li = list->lv_first, + .list = list, + .li = tv_list_first(list), .offset = 0, - .li_length = (list->lv_first->li_tv.vval.v_string == NULL + .li_length = (TV_LIST_ITEM_TV(tv_list_first(list))->vval.v_string == NULL ? 0 - : STRLEN(list->lv_first->li_tv.vval.v_string)), + : STRLEN(TV_LIST_ITEM_TV( + tv_list_first(list))->vval.v_string)), }; } diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c index 4bc3a85efb..c8b550f902 100644 --- a/src/nvim/eval/typval.c +++ b/src/nvim/eval/typval.c @@ -3,6 +3,7 @@ #include <stdio.h> #include <stddef.h> +#include <stdlib.h> #include <string.h> #include <assert.h> #include <stdbool.h> @@ -30,6 +31,7 @@ #include "nvim/message.h" // TODO(ZyX-I): Move line_breakcheck out of misc1 #include "nvim/misc1.h" // For line_breakcheck +#include "nvim/os/fileio.h" #ifdef INCLUDE_GENERATED_DECLARATIONS # include "eval/typval.c.generated.h" @@ -44,6 +46,70 @@ bool tv_in_free_unref_items = false; const char *const tv_empty_string = ""; //{{{1 Lists +//{{{2 List log +#ifdef LOG_LIST_ACTIONS +ListLog *list_log_first = NULL; +ListLog *list_log_last = NULL; + +/// Write list log to the given file +/// +/// @param[in] fname File to write log to. Will be appended to if already +/// present. +void list_write_log(const char *const fname) + FUNC_ATTR_NONNULL_ALL +{ + FileDescriptor fp; + const int fo_ret = file_open(&fp, fname, kFileCreate|kFileAppend, 0600); + if (fo_ret != 0) { + emsgf(_("E5142: Failed to open file %s: %s"), fname, os_strerror(fo_ret)); + return; + } + for (ListLog *chunk = list_log_first; chunk != NULL;) { + for (size_t i = 0; i < chunk->size; i++) { + char buf[10 + 1 + ((16 + 3) * 3) + (8 + 2) + 2]; + // act : hex " c:" len "[]" "\n\0" + const ListLogEntry entry = chunk->entries[i]; + const size_t snp_len = (size_t)snprintf( + buf, sizeof(buf), + "%-10.10s: l:%016" PRIxPTR "[%08d] 1:%016" PRIxPTR " 2:%016" PRIxPTR + "\n", + entry.action, entry.l, entry.len, entry.li1, entry.li2); + assert(snp_len + 1 == sizeof(buf)); + const ptrdiff_t fw_ret = file_write(&fp, buf, snp_len); + if (fw_ret != (ptrdiff_t)snp_len) { + assert(fw_ret < 0); + if (i) { + memmove(chunk->entries, chunk->entries + i, + sizeof(chunk->entries[0]) * (chunk->size - i)); + chunk->size -= i; + } + emsgf(_("E5143: Failed to write to file %s: %s"), + fname, os_strerror((int)fw_ret)); + return; + } + } + list_log_first = chunk->next; + xfree(chunk); + chunk = list_log_first; + } + const int fc_ret = file_close(&fp, true); + if (fc_ret != 0) { + emsgf(_("E5144: Failed to close file %s: %s"), fname, os_strerror(fc_ret)); + } +} + +#ifdef EXITFREE +/// Free list log +void list_free_log(void) +{ + for (ListLog *chunk = list_log_first; chunk != NULL;) { + list_log_first = chunk->next; + xfree(chunk); + chunk = list_log_first; + } +} +#endif +#endif //{{{2 List item /// Allocate a list item @@ -52,35 +118,29 @@ const char *const tv_empty_string = ""; /// and specifically set lv_lock. /// /// @return [allocated] new list item. -listitem_T *tv_list_item_alloc(void) +static listitem_T *tv_list_item_alloc(void) FUNC_ATTR_NONNULL_RET FUNC_ATTR_MALLOC { return xmalloc(sizeof(listitem_T)); } -/// Free a list item -/// -/// Also clears the value. Does not touch watchers. -/// -/// @param[out] item Item to free. -void tv_list_item_free(listitem_T *const item) - FUNC_ATTR_NONNULL_ALL -{ - tv_clear(&item->li_tv); - xfree(item); -} - /// Remove a list item from a List and free it /// /// Also clears the value. /// /// @param[out] l List to remove item from. /// @param[in,out] item Item to remove. -void tv_list_item_remove(list_T *const l, listitem_T *const item) +/// +/// @return Pointer to the list item just after removed one, NULL if removed +/// item was the last one. +listitem_T *tv_list_item_remove(list_T *const l, listitem_T *const item) FUNC_ATTR_NONNULL_ALL { - tv_list_remove_items(l, item, item); - tv_list_item_free(item); + listitem_T *const next_item = TV_LIST_ITEM_NEXT(l, item); + tv_list_drop_items(l, item, item); + tv_clear(TV_LIST_ITEM_TV(item)); + xfree(item); + return next_item; } //{{{2 List watchers @@ -137,8 +197,14 @@ void tv_list_watch_fix(list_T *const l, const listitem_T *const item) /// /// Caller should take care of the reference count. /// +/// @param[in] len Expected number of items to be populated before list +/// becomes accessible from VimL. It is still valid to +/// underpopulate a list, value only controls how many elements +/// will be allocated in advance. Currently does nothing. +/// @see ListLenSpecials. +/// /// @return [allocated] new list. -list_T *tv_list_alloc(void) +list_T *tv_list_alloc(const ptrdiff_t len) FUNC_ATTR_NONNULL_RET { list_T *const list = xcalloc(1, sizeof(list_T)); @@ -150,15 +216,59 @@ list_T *tv_list_alloc(void) list->lv_used_prev = NULL; list->lv_used_next = gc_first_list; gc_first_list = list; + list_log(list, NULL, (void *)(uintptr_t)len, "alloc"); return list; } +/// Initialize a static list with 10 items +/// +/// @param[out] sl Static list to initialize. +void tv_list_init_static10(staticList10_T *const sl) + FUNC_ATTR_NONNULL_ALL +{ +#define SL_SIZE ARRAY_SIZE(sl->sl_items) + list_T *const l = &sl->sl_list; + + memset(sl, 0, sizeof(staticList10_T)); + l->lv_first = &sl->sl_items[0]; + l->lv_last = &sl->sl_items[SL_SIZE - 1]; + l->lv_refcount = DO_NOT_FREE_CNT; + tv_list_set_lock(l, VAR_FIXED); + sl->sl_list.lv_len = 10; + + sl->sl_items[0].li_prev = NULL; + sl->sl_items[0].li_next = &sl->sl_items[1]; + sl->sl_items[SL_SIZE - 1].li_prev = &sl->sl_items[SL_SIZE - 2]; + sl->sl_items[SL_SIZE - 1].li_next = NULL; + + for (size_t i = 1; i < SL_SIZE - 1; i++) { + listitem_T *const li = &sl->sl_items[i]; + li->li_prev = li - 1; + li->li_next = li + 1; + } + list_log((const list_T *)sl, &sl->sl_items[0], &sl->sl_items[SL_SIZE - 1], + "s10init"); +#undef SL_SIZE +} + +/// Initialize static list with undefined number of elements +/// +/// @param[out] l List to initialize. +void tv_list_init_static(list_T *const l) + FUNC_ATTR_NONNULL_ALL +{ + memset(l, 0, sizeof(*l)); + l->lv_refcount = DO_NOT_FREE_CNT; + list_log(l, NULL, NULL, "sinit"); +} + /// Free items contained in a list /// /// @param[in,out] l List to clear. void tv_list_free_contents(list_T *const l) FUNC_ATTR_NONNULL_ALL { + list_log(l, NULL, NULL, "freecont"); for (listitem_T *item = l->lv_first; item != NULL; item = l->lv_first) { // Remove the item before deleting it. l->lv_first = item->li_next; @@ -188,6 +298,7 @@ void tv_list_free_list(list_T *const l) if (l->lv_used_next != NULL) { l->lv_used_next->lv_used_prev = l->lv_used_prev; } + list_log(l, NULL, NULL, "freelist"); xfree(l); } @@ -221,17 +332,18 @@ void tv_list_unref(list_T *const l) //{{{2 Add/remove -/// Remove items "item" to "item2" from list "l". +/// Remove items "item" to "item2" from list "l" /// /// @warning Does not free the listitem or the value! /// /// @param[out] l List to remove from. /// @param[in] item First item to remove. /// @param[in] item2 Last item to remove. -void tv_list_remove_items(list_T *const l, listitem_T *const item, - listitem_T *const item2) +void tv_list_drop_items(list_T *const l, listitem_T *const item, + listitem_T *const item2) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, item2, "drop"); // Notify watchers. for (listitem_T *ip = item; ip != item2->li_next; ip = ip->li_next) { l->lv_len--; @@ -249,6 +361,51 @@ void tv_list_remove_items(list_T *const l, listitem_T *const item, item->li_prev->li_next = item2->li_next; } l->lv_idx_item = NULL; + list_log(l, l->lv_first, l->lv_last, "afterdrop"); +} + +/// Like tv_list_drop_items, but also frees all removed items +void tv_list_remove_items(list_T *const l, listitem_T *const item, + listitem_T *const item2) + FUNC_ATTR_NONNULL_ALL +{ + list_log(l, item, item2, "remove"); + tv_list_drop_items(l, item, item2); + for (listitem_T *li = item;;) { + tv_clear(TV_LIST_ITEM_TV(li)); + listitem_T *const nli = li->li_next; + xfree(li); + if (li == item2) { + break; + } + li = nli; + } +} + +/// Move items "item" to "item2" from list "l" to the end of the list "tgt_l" +/// +/// @param[out] l List to move from. +/// @param[in] item First item to move. +/// @param[in] item2 Last item to move. +/// @param[out] tgt_l List to move to. +/// @param[in] cnt Number of items moved. +void tv_list_move_items(list_T *const l, listitem_T *const item, + listitem_T *const item2, list_T *const tgt_l, + const int cnt) + FUNC_ATTR_NONNULL_ALL +{ + list_log(l, item, item2, "move"); + tv_list_drop_items(l, item, item2); + item->li_prev = tgt_l->lv_last; + item2->li_next = NULL; + if (tgt_l->lv_last == NULL) { + tgt_l->lv_first = item; + } else { + tgt_l->lv_last->li_next = item; + } + tgt_l->lv_last = item2; + tgt_l->lv_len += cnt; + list_log(tgt_l, tgt_l->lv_first, tgt_l->lv_last, "movetgt"); } /// Insert list item @@ -277,6 +434,7 @@ void tv_list_insert(list_T *const l, listitem_T *const ni, } item->li_prev = ni; l->lv_len++; + list_log(l, ni, item, "insert"); } } @@ -303,6 +461,7 @@ void tv_list_insert_tv(list_T *const l, typval_T *const tv, void tv_list_append(list_T *const l, listitem_T *const item) FUNC_ATTR_NONNULL_ALL { + list_log(l, item, NULL, "append"); if (l->lv_last == NULL) { // empty list l->lv_first = item; @@ -326,7 +485,19 @@ void tv_list_append_tv(list_T *const l, typval_T *const tv) FUNC_ATTR_NONNULL_ALL { listitem_T *const li = tv_list_item_alloc(); - tv_copy(tv, &li->li_tv); + tv_copy(tv, TV_LIST_ITEM_TV(li)); + tv_list_append(l, li); +} + +/// Like tv_list_append_tv(), but tv is moved to a list +/// +/// This means that it is no longer valid to use contents of the typval_T after +/// function exits. +void tv_list_append_owned_tv(list_T *const l, typval_T tv) + FUNC_ATTR_NONNULL_ALL +{ + listitem_T *const li = tv_list_item_alloc(); + *TV_LIST_ITEM_TV(li) = tv; tv_list_append(l, li); } @@ -334,33 +505,29 @@ void tv_list_append_tv(list_T *const l, typval_T *const tv) /// /// @param[out] l List to append to. /// @param[in,out] itemlist List to append. Reference count is increased. -void tv_list_append_list(list_T *const list, list_T *const itemlist) +void tv_list_append_list(list_T *const l, list_T *const itemlist) FUNC_ATTR_NONNULL_ARG(1) { - listitem_T *const li = tv_list_item_alloc(); - - li->li_tv.v_type = VAR_LIST; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_list = itemlist; - tv_list_append(list, li); - if (itemlist != NULL) { - itemlist->lv_refcount++; - } + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_LIST, + .v_lock = VAR_UNLOCKED, + .vval.v_list = itemlist, + }); + tv_list_ref(itemlist); } /// Append a dictionary to a list /// /// @param[out] l List to append to. /// @param[in,out] dict Dictionary to append. Reference count is increased. -void tv_list_append_dict(list_T *const list, dict_T *const dict) +void tv_list_append_dict(list_T *const l, dict_T *const dict) FUNC_ATTR_NONNULL_ARG(1) { - listitem_T *const li = tv_list_item_alloc(); - - li->li_tv.v_type = VAR_DICT; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_dict = dict; - tv_list_append(list, li); + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_DICT, + .v_lock = VAR_UNLOCKED, + .vval.v_dict = dict, + }); if (dict != NULL) { dict->dv_refcount++; } @@ -377,14 +544,15 @@ void tv_list_append_string(list_T *const l, const char *const str, const ssize_t len) FUNC_ATTR_NONNULL_ARG(1) { - if (str == NULL) { - assert(len == 0 || len == -1); - tv_list_append_allocated_string(l, NULL); - } else { - tv_list_append_allocated_string(l, (len >= 0 - ? xmemdupz(str, (size_t)len) - : xstrdup(str))); - } + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = (str == NULL + ? NULL + : (len >= 0 + ? xmemdupz(str, (size_t)len) + : xstrdup(str))), + }); } /// Append given string to the list @@ -396,12 +564,11 @@ void tv_list_append_string(list_T *const l, const char *const str, void tv_list_append_allocated_string(list_T *const l, char *const str) FUNC_ATTR_NONNULL_ARG(1) { - listitem_T *const li = tv_list_item_alloc(); - - tv_list_append(l, li); - li->li_tv.v_type = VAR_STRING; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_string = (char_u *)str; + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_STRING, + .v_lock = VAR_UNLOCKED, + .vval.v_string = (char_u *)str, + }); } /// Append number to the list @@ -411,11 +578,11 @@ void tv_list_append_allocated_string(list_T *const l, char *const str) /// listitem_T. void tv_list_append_number(list_T *const l, const varnumber_T n) { - listitem_T *const li = tv_list_item_alloc(); - li->li_tv.v_type = VAR_NUMBER; - li->li_tv.v_lock = VAR_UNLOCKED; - li->li_tv.vval.v_number = n; - tv_list_append(l, li); + tv_list_append_owned_tv(l, (typval_T) { + .v_type = VAR_NUMBER, + .v_lock = VAR_UNLOCKED, + .vval.v_number = n, + }); } //{{{2 Operations on the whole list @@ -438,34 +605,36 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig, return NULL; } - list_T *copy = tv_list_alloc(); + list_T *copy = tv_list_alloc(tv_list_len(orig)); + tv_list_ref(copy); if (copyID != 0) { // Do this before adding the items, because one of the items may // refer back to this list. orig->lv_copyID = copyID; orig->lv_copylist = copy; } - listitem_T *item; - for (item = orig->lv_first; item != NULL && !got_int; - item = item->li_next) { + TV_LIST_ITER(orig, item, { + if (got_int) { + break; + } listitem_T *const ni = tv_list_item_alloc(); if (deep) { - if (var_item_copy(conv, &item->li_tv, &ni->li_tv, deep, copyID) == FAIL) { + if (var_item_copy(conv, TV_LIST_ITEM_TV(item), TV_LIST_ITEM_TV(ni), + deep, copyID) == FAIL) { xfree(ni); - break; + goto tv_list_copy_error; } } else { - tv_copy(&item->li_tv, &ni->li_tv); + tv_copy(TV_LIST_ITEM_TV(item), TV_LIST_ITEM_TV(ni)); } tv_list_append(copy, ni); - } - copy->lv_refcount++; - if (item != NULL) { - tv_list_unref(copy); - copy = NULL; - } + }); return copy; + +tv_list_copy_error: + tv_list_unref(copy); + return NULL; } /// Extend first list with the second @@ -475,17 +644,17 @@ list_T *tv_list_copy(const vimconv_T *const conv, list_T *const orig, /// @param[in] bef If not NULL, extends before this item. void tv_list_extend(list_T *const l1, list_T *const l2, listitem_T *const bef) - FUNC_ATTR_NONNULL_ARG(1, 2) + FUNC_ATTR_NONNULL_ARG(1) { - int todo = l2->lv_len; + int todo = tv_list_len(l2); listitem_T *const befbef = (bef == NULL ? NULL : bef->li_prev); listitem_T *const saved_next = (befbef == NULL ? NULL : befbef->li_next); // We also quit the loop when we have inserted the original item count of // the list, avoid a hang when we extend a list with itself. - for (listitem_T *item = l2->lv_first - ; item != NULL && --todo >= 0 + for (listitem_T *item = tv_list_first(l2) + ; item != NULL && todo-- ; item = (item == befbef ? saved_next : item->li_next)) { - tv_list_insert_tv(l1, &item->li_tv, bef); + tv_list_insert_tv(l1, TV_LIST_ITEM_TV(item), bef); } } @@ -540,13 +709,15 @@ static int list_join_inner(garray_T *const gap, list_T *const l, { size_t sumlen = 0; bool first = true; - listitem_T *item; // Stringify each item in the list. - for (item = l->lv_first; item != NULL && !got_int; item = item->li_next) { + TV_LIST_ITER(l, item, { + if (got_int) { + break; + } char *s; size_t len; - s = encode_tv2echo(&item->li_tv, &len); + s = encode_tv2echo(TV_LIST_ITEM_TV(item), &len); if (s == NULL) { return FAIL; } @@ -557,7 +728,7 @@ static int list_join_inner(garray_T *const gap, list_T *const l, p->tofree = p->s = (char_u *)s; line_breakcheck(); - } + }); // Allocate result buffer with its total size, avoid re-allocation and // multiple copy operations. Add 2 for a tailing ']' and NUL. @@ -591,16 +762,16 @@ static int list_join_inner(garray_T *const gap, list_T *const l, /// /// @return OK in case of success, FAIL otherwise. int tv_list_join(garray_T *const gap, list_T *const l, const char *const sep) - FUNC_ATTR_NONNULL_ALL + FUNC_ATTR_NONNULL_ARG(1) { - if (l->lv_len < 1) { + if (!tv_list_len(l)) { return OK; } garray_T join_ga; int retval; - ga_init(&join_ga, (int)sizeof(Join), l->lv_len); + ga_init(&join_ga, (int)sizeof(Join), tv_list_len(l)); retval = list_join_inner(gap, l, sep, &join_ga); #define FREE_JOIN_TOFREE(join) xfree((join)->tofree) @@ -632,11 +803,13 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, return false; } - listitem_T *item1 = l1->lv_first; - listitem_T *item2 = l2->lv_first; + listitem_T *item1 = tv_list_first(l1); + listitem_T *item2 = tv_list_first(l2); for (; item1 != NULL && item2 != NULL - ; item1 = item1->li_next, item2 = item2->li_next) { - if (!tv_equal(&item1->li_tv, &item2->li_tv, ic, recursive)) { + ; (item1 = TV_LIST_ITEM_NEXT(l1, item1), + item2 = TV_LIST_ITEM_NEXT(l2, item2))) { + if (!tv_equal(TV_LIST_ITEM_TV(item1), TV_LIST_ITEM_TV(item2), ic, + recursive)) { return false; } } @@ -644,6 +817,74 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, return true; } +/// Reverse list in-place +/// +/// @param[in,out] l List to reverse. +void tv_list_reverse(list_T *const l) +{ + if (tv_list_len(l) <= 1) { + return; + } + list_log(l, NULL, NULL, "reverse"); +#define SWAP(a, b) \ + do { \ + tmp = a; \ + a = b; \ + b = tmp; \ + } while (0) + listitem_T *tmp; + + SWAP(l->lv_first, l->lv_last); + for (listitem_T *li = l->lv_first; li != NULL; li = li->li_next) { + SWAP(li->li_next, li->li_prev); + } +#undef SWAP + + l->lv_idx = l->lv_len - l->lv_idx - 1; +} + +// FIXME Add unit tests for tv_list_item_sort(). + +/// Sort list using libc qsort +/// +/// @param[in,out] l List to sort, will be sorted in-place. +/// @param ptrs Preallocated array of items to sort, must have at least +/// tv_list_len(l) entries. Should not be initialized. +/// @param[in] item_compare_func Function used to compare list items. +/// @param errp Location where information about whether error occurred is +/// saved by item_compare_func. If boolean there appears to be +/// true list will not be modified. Must be initialized to false +/// by the caller. +void tv_list_item_sort(list_T *const l, ListSortItem *const ptrs, + const ListSorter item_compare_func, + bool *errp) + FUNC_ATTR_NONNULL_ARG(3, 4) +{ + const int len = tv_list_len(l); + if (len <= 1) { + return; + } + list_log(l, NULL, NULL, "sort"); + int i = 0; + TV_LIST_ITER(l, li, { + ptrs[i].item = li; + ptrs[i].idx = i; + i++; + }); + // Sort the array with item pointers. + qsort(ptrs, (size_t)len, sizeof(ListSortItem), item_compare_func); + if (!(*errp)) { + // Clear the list and append the items in the sorted order. + l->lv_first = NULL; + l->lv_last = NULL; + l->lv_idx_item = NULL; + l->lv_len = 0; + for (i = 0; i < len; i++) { + tv_list_append(l, ptrs[i].item); + } + } +} + //{{{2 Indexing/searching /// Locate item with a given index in a list and return it @@ -662,13 +903,8 @@ listitem_T *tv_list_find(list_T *const l, int n) return NULL; } - // Negative index is relative to the end. - if (n < 0) { - n = l->lv_len + n; - } - - // Check for index out of range. - if (n < 0 || n >= l->lv_len) { + n = tv_list_uidx(l, n); + if (n == -1) { return NULL; } @@ -717,6 +953,7 @@ listitem_T *tv_list_find(list_T *const l, int n) // Cache the used index. l->lv_idx = idx; l->lv_idx_item = item; + list_log(l, l->lv_idx_item, (void *)(uintptr_t)l->lv_idx, "find"); return item; } @@ -740,7 +977,7 @@ varnumber_T tv_list_find_nr(list_T *const l, const int n, bool *const ret_error) } return -1; } - return tv_get_number_chk(&li->li_tv, ret_error); + return tv_get_number_chk(TV_LIST_ITEM_TV(li), ret_error); } /// Get list item l[n] as a string @@ -757,7 +994,7 @@ const char *tv_list_find_str(list_T *const l, const int n) emsgf(_(e_listidx), (int64_t)n); return NULL; } - return tv_get_string(&li->li_tv); + return tv_get_string(TV_LIST_ITEM_TV(li)); } /// Locate item in a list and return its index @@ -772,15 +1009,14 @@ long tv_list_idx_of_item(const list_T *const l, const listitem_T *const item) if (l == NULL) { return -1; } - long idx = 0; - const listitem_T *li; - for (li = l->lv_first; li != NULL && li != item; li = li->li_next) { + int idx = 0; + TV_LIST_ITER_CONST(l, li, { + if (li == item) { + return idx; + } idx++; - } - if (li == NULL) { - return -1; - } - return idx; + }); + return -1; } //{{{1 Dictionaries @@ -1339,7 +1575,7 @@ int tv_dict_add_list(dict_T *const d, const char *const key, item->di_tv.v_lock = VAR_UNLOCKED; item->di_tv.v_type = VAR_LIST; item->di_tv.vval.v_list = list; - list->lv_refcount++; + tv_list_ref(list); if (tv_dict_add(d, item) == FAIL) { tv_dict_item_free(item); return FAIL; @@ -1668,16 +1904,20 @@ void tv_dict_set_keys_readonly(dict_T *const dict) /// Also sets reference count. /// /// @param[out] ret_tv Structure where list is saved. +/// @param[in] len Expected number of items to be populated before list +/// becomes accessible from VimL. It is still valid to +/// underpopulate a list, value only controls how many elements +/// will be allocated in advance. @see ListLenSpecials. /// /// @return [allocated] pointer to the created list. -list_T *tv_list_alloc_ret(typval_T *const ret_tv) +list_T *tv_list_alloc_ret(typval_T *const ret_tv, const ptrdiff_t len) FUNC_ATTR_NONNULL_ALL { - list_T *const l = tv_list_alloc(); + list_T *const l = tv_list_alloc(len); ret_tv->vval.v_list = l; ret_tv->v_type = VAR_LIST; ret_tv->v_lock = VAR_UNLOCKED; - l->lv_refcount++; + tv_list_ref(l); return l; } @@ -1794,14 +2034,20 @@ static inline void _nothing_conv_func_end(typval_T *const tv, const int copyID) tv->v_lock = VAR_UNLOCKED; \ } while (0) +static inline void _nothing_conv_empty_dict(typval_T *const tv, + dict_T **const dictp) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(2) +{ + tv_dict_unref(*dictp); + *dictp = NULL; + if (tv != NULL) { + tv->v_lock = VAR_UNLOCKED; + } +} #define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \ do { \ assert((void *)&dict != (void *)&TYPVAL_ENCODE_NODICT_VAR); \ - tv_dict_unref((dict_T *)dict); \ - *((dict_T **)&dict) = NULL; \ - if (tv != NULL) { \ - ((typval_T *)tv)->v_lock = VAR_UNLOCKED; \ - } \ + _nothing_conv_empty_dict(tv, ((dict_T **)&dict)); \ } while (0) static inline int _nothing_conv_real_list_after_start( @@ -1998,7 +2244,7 @@ void tv_free(typval_T *tv) /// /// @param[in] from Location to copy from. /// @param[out] to Location to copy to. -void tv_copy(typval_T *const from, typval_T *const to) +void tv_copy(const typval_T *const from, typval_T *const to) { to->v_type = from->v_type; to->v_lock = VAR_UNLOCKED; @@ -2026,9 +2272,7 @@ void tv_copy(typval_T *const from, typval_T *const to) break; } case VAR_LIST: { - if (from->vval.v_list != NULL) { - to->vval.v_list->lv_refcount++; - } + tv_list_ref(to->vval.v_list); break; } case VAR_DICT: { @@ -2084,9 +2328,9 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) CHANGE_LOCK(lock, l->lv_lock); if (deep < 0 || deep > 1) { // Recursive: lock/unlock the items the List contains. - for (listitem_T *li = l->lv_first; li != NULL; li = li->li_next) { - tv_item_lock(&li->li_tv, deep - 1, lock); - } + TV_LIST_ITER(l, li, { + tv_item_lock(TV_LIST_ITEM_TV(li), deep - 1, lock); + }); } } break; @@ -2122,6 +2366,8 @@ void tv_item_lock(typval_T *const tv, const int deep, const bool lock) /// Check whether VimL value is locked itself or refers to a locked container /// +/// @warning Fixed container is not the same as locked. +/// /// @param[in] tv Value to check. /// /// @return True if value is locked, false otherwise. @@ -2130,8 +2376,7 @@ bool tv_islocked(const typval_T *const tv) { return ((tv->v_lock == VAR_LOCKED) || (tv->v_type == VAR_LIST - && tv->vval.v_list != NULL - && (tv->vval.v_list->lv_lock == VAR_LOCKED)) + && (tv_list_locked(tv->vval.v_list) == VAR_LOCKED)) || (tv->v_type == VAR_DICT && tv->vval.v_dict != NULL && (tv->vval.v_dict->dv_lock == VAR_LOCKED))); diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h index c44b85644d..2272a580d6 100644 --- a/src/nvim/eval/typval.h +++ b/src/nvim/eval/typval.h @@ -6,6 +6,8 @@ #include <stdint.h> #include <string.h> #include <stdbool.h> +#include <assert.h> +#include <limits.h> #include "nvim/types.h" #include "nvim/hashtab.h" @@ -18,6 +20,9 @@ #include "nvim/gettext.h" #include "nvim/message.h" #include "nvim/macros.h" +#ifdef LOG_LIST_ACTIONS +# include "nvim/memory.h" +#endif /// Type used for VimL VAR_NUMBER values typedef int64_t varnumber_T; @@ -26,6 +31,28 @@ typedef uint64_t uvarnumber_T; /// Type used for VimL VAR_FLOAT values typedef double float_T; +/// Refcount for dict or list that should not be freed +enum { DO_NOT_FREE_CNT = (INT_MAX / 2) }; + +/// Additional values for tv_list_alloc() len argument +enum { + /// List length is not known in advance + /// + /// To be used when there is neither a way to know how many elements will be + /// needed nor are any educated guesses. + kListLenUnknown = -1, + /// List length *should* be known, but is actually not + /// + /// All occurrences of this value should be eventually removed. This is for + /// the case when the only reason why list length is not known is that it + /// would be hard to code without refactoring, but refactoring is needed. + kListLenShouldKnow = -2, + /// List length may be known in advance, but it requires too much effort + /// + /// To be used when it looks impractical to determine list length. + kListLenMayKnow = -3, +} ListLenSpecials; + /// Maximal possible value of varnumber_T variable #define VARNUMBER_MAX INT64_MAX #define UVARNUMBER_MAX UINT64_MAX @@ -150,12 +177,26 @@ struct listvar_S { list_T *lv_used_prev; ///< Previous list in used lists list. }; -// Static list with 10 items. Use init_static_list() to initialize. +// Static list with 10 items. Use tv_list_init_static10() to initialize. typedef struct { list_T sl_list; // must be first listitem_T sl_items[10]; } staticList10_T; +#define TV_LIST_STATIC10_INIT { \ + .sl_list = { \ + .lv_first = NULL, \ + .lv_last = NULL, \ + .lv_refcount = 0, \ + .lv_len = 0, \ + .lv_watch = NULL, \ + .lv_idx_item = NULL, \ + .lv_lock = VAR_FIXED, \ + .lv_used_next = NULL, \ + .lv_used_prev = NULL, \ + }, \ + } + // Structure to hold an item of a Dictionary. // Also used for a variable. // The key is copied into "di_key" to avoid an extra alloc/free for it. @@ -165,11 +206,11 @@ struct dictitem_S { char_u di_key[1]; ///< key (actually longer!) }; -#define TV_DICTITEM_STRUCT(KEY_LEN) \ +#define TV_DICTITEM_STRUCT(...) \ struct { \ typval_T di_tv; /* Structure that holds scope dictionary itself. */ \ uint8_t di_flags; /* Flags. */ \ - char_u di_key[KEY_LEN]; /* Key value. */ \ + char_u di_key[__VA_ARGS__]; /* Key value. */ \ } /// Structure to hold a scope dictionary @@ -277,6 +318,104 @@ typedef struct list_stack_S { struct list_stack_S *prev; } list_stack_T; +/// Structure representing one list item, used for sort array. +typedef struct { + listitem_T *item; ///< Sorted list item. + int idx; ///< Sorted list item index. +} ListSortItem; + +typedef int (*ListSorter)(const void *, const void *); + +#ifdef LOG_LIST_ACTIONS + +/// List actions log entry +typedef struct { + uintptr_t l; ///< List log entry belongs to. + uintptr_t li1; ///< First list item log entry belongs to, if applicable. + uintptr_t li2; ///< Second list item log entry belongs to, if applicable. + int len; ///< List length when log entry was created. + const char *action; ///< Logged action. +} ListLogEntry; + +typedef struct list_log ListLog; + +/// List actions log +struct list_log { + ListLog *next; ///< Next chunk or NULL. + size_t capacity; ///< Number of entries in current chunk. + size_t size; ///< Current chunk size. + ListLogEntry entries[]; ///< Actual log entries. +}; + +extern ListLog *list_log_first; ///< First list log chunk, NULL if missing +extern ListLog *list_log_last; ///< Last list log chunk + +static inline ListLog *list_log_alloc(const size_t size) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Allocate a new log chunk and update globals +/// +/// @param[in] size Number of entries in a new chunk. +/// +/// @return [allocated] Newly allocated chunk. +static inline ListLog *list_log_new(const size_t size) +{ + ListLog *ret = xmalloc(offsetof(ListLog, entries) + + size * sizeof(ret->entries[0])); + ret->size = 0; + ret->capacity = size; + ret->next = NULL; + if (list_log_first == NULL) { + list_log_first = ret; + } else { + list_log_last->next = ret; + } + list_log_last = ret; + return ret; +} + +static inline void list_log(const list_T *const l, + const listitem_T *const li1, + const listitem_T *const li2, + const char *const action) + REAL_FATTR_ALWAYS_INLINE; + +/// Add new entry to log +/// +/// If last chunk was filled it uses twice as much memory to allocate the next +/// chunk. +/// +/// @param[in] l List to which entry belongs. +/// @param[in] li1 List item 1. +/// @param[in] li2 List item 2, often used for integers and not list items. +/// @param[in] action Logged action. +static inline void list_log(const list_T *const l, + const listitem_T *const li1, + const listitem_T *const li2, + const char *const action) +{ + ListLog *tgt; + if (list_log_first == NULL) { + tgt = list_log_new(128); + } else if (list_log_last->size == list_log_last->capacity) { + tgt = list_log_new(list_log_last->capacity * 2); + } else { + tgt = list_log_last; + } + tgt->entries[tgt->size++] = (ListLogEntry) { + .l = (uintptr_t)l, + .li1 = (uintptr_t)li1, + .li2 = (uintptr_t)li2, + .len = (l == NULL ? 0 : l->lv_len), + .action = action, + }; +} +#else +# define list_log(...) +# define list_write_log(...) +# define list_free_log() +#endif + // In a hashtab item "hi_key" points to "di_key" in a dictitem. // This avoids adding a pointer to the hashtab item. @@ -284,20 +423,181 @@ typedef struct list_stack_S { #define TV_DICT_HI2DI(hi) \ ((dictitem_T *)((hi)->hi_key - offsetof(dictitem_T, di_key))) -static inline long tv_list_len(const list_T *const l) +/// Increase reference count for a given list +/// +/// Does nothing for NULL lists. +/// +/// @param[in] l List to modify. +static inline void tv_list_ref(list_T *const l) +{ + if (l == NULL) { + return; + } + l->lv_refcount++; +} + +static inline VarLockStatus tv_list_locked(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Get list lock status +/// +/// Returns VAR_FIXED for NULL lists. +/// +/// @param[in] l List to check. +static inline VarLockStatus tv_list_locked(const list_T *const l) +{ + if (l == NULL) { + return VAR_FIXED; + } + return l->lv_lock; +} + +/// Set list lock status +/// +/// May only “set” VAR_FIXED for NULL lists. +/// +/// @param[out] l List to modify. +/// @param[in] lock New lock status. +static inline void tv_list_set_lock(list_T *const l, + const VarLockStatus lock) +{ + if (l == NULL) { + assert(lock == VAR_FIXED); + return; + } + l->lv_lock = lock; +} + +/// Set list copyID +/// +/// Does not expect NULL list, be careful. +/// +/// @param[out] l List to modify. +/// @param[in] copyid New copyID. +static inline void tv_list_set_copyid(list_T *const l, + const int copyid) + FUNC_ATTR_NONNULL_ALL +{ + l->lv_copyID = copyid; +} + +static inline int tv_list_len(const list_T *const l) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; /// Get the number of items in a list /// /// @param[in] l List to check. -static inline long tv_list_len(const list_T *const l) +static inline int tv_list_len(const list_T *const l) { + list_log(l, NULL, NULL, "len"); if (l == NULL) { return 0; } return l->lv_len; } +static inline int tv_list_copyid(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; + +/// Get list copyID +/// +/// Does not expect NULL list, be careful. +/// +/// @param[in] l List to check. +static inline int tv_list_copyid(const list_T *const l) +{ + return l->lv_copyID; +} + +static inline list_T *tv_list_latest_copy(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; + +/// Get latest list copy +/// +/// Gets lv_copylist field assigned by tv_list_copy() earlier. +/// +/// Does not expect NULL list, be careful. +/// +/// @param[in] l List to check. +static inline list_T *tv_list_latest_copy(const list_T *const l) +{ + return l->lv_copylist; +} + +static inline int tv_list_uidx(const list_T *const l, int n) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Normalize index: that is, return either -1 or non-negative index +/// +/// @param[in] l List to index. Used to get length. +/// @param[in] n List index, possibly negative. +/// +/// @return -1 or list index in range [0, tv_list_len(l)). +static inline int tv_list_uidx(const list_T *const l, int n) +{ + // Negative index is relative to the end. + if (n < 0) { + n += tv_list_len(l); + } + + // Check for index out of range. + if (n < 0 || n >= tv_list_len(l)) { + return -1; + } + return n; +} + +static inline bool tv_list_has_watchers(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Check whether list has watchers +/// +/// E.g. is referenced by a :for loop. +/// +/// @param[in] l List to check. +/// +/// @return true if there are watchers, false otherwise. +static inline bool tv_list_has_watchers(const list_T *const l) +{ + return l && l->lv_watch; +} + +static inline listitem_T *tv_list_first(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Get first list item +/// +/// @param[in] l List to get item from. +/// +/// @return List item or NULL in case of an empty list. +static inline listitem_T *tv_list_first(const list_T *const l) +{ + if (l == NULL) { + list_log(l, NULL, NULL, "first"); + return NULL; + } + list_log(l, l->lv_first, NULL, "first"); + return l->lv_first; +} + +static inline listitem_T *tv_list_last(const list_T *const l) + REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; + +/// Get last list item +/// +/// @param[in] l List to get item from. +/// +/// @return List item or NULL in case of an empty list. +static inline listitem_T *tv_list_last(const list_T *const l) +{ + if (l == NULL) { + list_log(l, NULL, NULL, "last"); + return NULL; + } + list_log(l, l->lv_last, NULL, "last"); + return l->lv_last; +} + static inline long tv_dict_len(const dict_T *const d) REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT; @@ -352,6 +652,76 @@ extern const char *const tv_empty_string; /// Specifies that free_unref_items() function has (not) been entered extern bool tv_in_free_unref_items; +/// Iterate over a list +/// +/// @param modifier Modifier: expected to be const or nothing, volatile should +/// also work if you have any uses for the volatile list. +/// @param[in] l List to iterate over. +/// @param li Name of the variable with current listitem_T entry. +/// @param code Cycle body. +#define _TV_LIST_ITER_MOD(modifier, l, li, code) \ + do { \ + modifier list_T *const l_ = (l); \ + list_log(l_, NULL, NULL, "iter" #modifier); \ + if (l_ != NULL) { \ + for (modifier listitem_T *li = l_->lv_first; \ + li != NULL; li = li->li_next) { \ + code \ + } \ + } \ + } while (0) + +/// Iterate over a list +/// +/// To be used when you need to modify list or values you iterate over, use +/// #TV_LIST_ITER_CONST if you don’t. +/// +/// @param[in] l List to iterate over. +/// @param li Name of the variable with current listitem_T entry. +/// @param code Cycle body. +#define TV_LIST_ITER(l, li, code) \ + _TV_LIST_ITER_MOD(, l, li, code) + +/// Iterate over a list +/// +/// To be used when you don’t need to modify list or values you iterate over, +/// use #TV_LIST_ITER if you do. +/// +/// @param[in] l List to iterate over. +/// @param li Name of the variable with current listitem_T entry. +/// @param code Cycle body. +#define TV_LIST_ITER_CONST(l, li, code) \ + _TV_LIST_ITER_MOD(const, l, li, code) + +// Below macros are macros to avoid duplicating code for functionally identical +// const and non-const function variants. + +/// Get typval_T out of list item +/// +/// @param[in] li List item to get typval_T from, must not be NULL. +/// +/// @return Pointer to typval_T. +#define TV_LIST_ITEM_TV(li) (&(li)->li_tv) + +/// Get next list item given the current one +/// +/// @param[in] l List to get item from. +/// @param[in] li List item to get typval_T from. +/// +/// @return Pointer to the next item or NULL. +#define TV_LIST_ITEM_NEXT(l, li) ((li)->li_next) + +/// Get previous list item given the current one +/// +/// @param[in] l List to get item from. +/// @param[in] li List item to get typval_T from. +/// +/// @return Pointer to the previous item or NULL. +#define TV_LIST_ITEM_PREV(l, li) ((li)->li_prev) +// List argument is not used currently, but it is a must for lists implemented +// as a pair (size(in list), array) without terminator - basically for lists on +// top of kvec. + /// Iterate over a dictionary /// /// @param[in] d Dictionary to iterate over. diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h index a93ad2dbba..f2d0d7265f 100644 --- a/src/nvim/eval/typval_encode.c.h +++ b/src/nvim/eval/typval_encode.c.h @@ -355,14 +355,14 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( break; } case VAR_LIST: { - if (tv->vval.v_list == NULL || tv->vval.v_list->lv_len == 0) { + if (tv->vval.v_list == NULL || tv_list_len(tv->vval.v_list) == 0) { TYPVAL_ENCODE_CONV_EMPTY_LIST(tv); break; } - const int saved_copyID = tv->vval.v_list->lv_copyID; + const int saved_copyID = tv_list_copyid(tv->vval.v_list); _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(tv->vval.v_list, lv_copyID, copyID, kMPConvList); - TYPVAL_ENCODE_CONV_LIST_START(tv, tv->vval.v_list->lv_len); + TYPVAL_ENCODE_CONV_LIST_START(tv, tv_list_len(tv->vval.v_list)); assert(saved_copyID != copyID && saved_copyID != copyID - 1); _mp_push(*mpstack, ((MPConvStackVal) { .type = kMPConvList, @@ -371,7 +371,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .data = { .l = { .list = tv->vval.v_list, - .li = tv->vval.v_list->lv_first, + .li = tv_list_first(tv->vval.v_list), }, }, })); @@ -440,23 +440,43 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( // bits is not checked), other unsigned and have at most 31 // non-zero bits (number of bits is not checked). if (val_di->di_tv.v_type != VAR_LIST - || (val_list = val_di->di_tv.vval.v_list) == NULL - || val_list->lv_len != 4 - || val_list->lv_first->li_tv.v_type != VAR_NUMBER - || (sign = val_list->lv_first->li_tv.vval.v_number) == 0 - || val_list->lv_first->li_next->li_tv.v_type != VAR_NUMBER - || (highest_bits = - val_list->lv_first->li_next->li_tv.vval.v_number) < 0 - || val_list->lv_last->li_prev->li_tv.v_type != VAR_NUMBER - || (high_bits = - val_list->lv_last->li_prev->li_tv.vval.v_number) < 0 - || val_list->lv_last->li_tv.v_type != VAR_NUMBER - || (low_bits = val_list->lv_last->li_tv.vval.v_number) < 0) { + || tv_list_len(val_list = val_di->di_tv.vval.v_list) != 4) { goto _convert_one_value_regular_dict; } - uint64_t number = ((uint64_t)(((uint64_t)highest_bits) << 62) - | (uint64_t)(((uint64_t)high_bits) << 31) - | (uint64_t)low_bits); + + const listitem_T *const sign_li = tv_list_first(val_list); + if (TV_LIST_ITEM_TV(sign_li)->v_type != VAR_NUMBER + || (sign = TV_LIST_ITEM_TV(sign_li)->vval.v_number) == 0) { + goto _convert_one_value_regular_dict; + } + + const listitem_T *const highest_bits_li = ( + TV_LIST_ITEM_NEXT(val_list, sign_li)); + if (TV_LIST_ITEM_TV(highest_bits_li)->v_type != VAR_NUMBER + || ((highest_bits + = TV_LIST_ITEM_TV(highest_bits_li)->vval.v_number) + < 0)) { + goto _convert_one_value_regular_dict; + } + + const listitem_T *const high_bits_li = ( + TV_LIST_ITEM_NEXT(val_list, highest_bits_li)); + if (TV_LIST_ITEM_TV(high_bits_li)->v_type != VAR_NUMBER + || ((high_bits = TV_LIST_ITEM_TV(high_bits_li)->vval.v_number) + < 0)) { + goto _convert_one_value_regular_dict; + } + + const listitem_T *const low_bits_li = tv_list_last(val_list); + if (TV_LIST_ITEM_TV(low_bits_li)->v_type != VAR_NUMBER + || ((low_bits = TV_LIST_ITEM_TV(low_bits_li)->vval.v_number) + < 0)) { + goto _convert_one_value_regular_dict; + } + + const uint64_t number = ((uint64_t)(((uint64_t)highest_bits) << 62) + | (uint64_t)(((uint64_t)high_bits) << 31) + | (uint64_t)low_bits); if (sign > 0) { TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, number); } else { @@ -495,12 +515,12 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( if (val_di->di_tv.v_type != VAR_LIST) { goto _convert_one_value_regular_dict; } - const int saved_copyID = val_di->di_tv.vval.v_list->lv_copyID; + const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list); _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_di->di_tv.vval.v_list, lv_copyID, copyID, kMPConvList); - TYPVAL_ENCODE_CONV_LIST_START(tv, - val_di->di_tv.vval.v_list->lv_len); + TYPVAL_ENCODE_CONV_LIST_START( + tv, tv_list_len(val_di->di_tv.vval.v_list)); assert(saved_copyID != copyID && saved_copyID != copyID - 1); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, @@ -509,7 +529,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .data = { .l = { .list = val_di->di_tv.vval.v_list, - .li = val_di->di_tv.vval.v_list->lv_first, + .li = tv_list_first(val_di->di_tv.vval.v_list), }, }, })); @@ -520,22 +540,21 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( goto _convert_one_value_regular_dict; } list_T *const val_list = val_di->di_tv.vval.v_list; - if (val_list == NULL || val_list->lv_len == 0) { + if (val_list == NULL || tv_list_len(val_list) == 0) { TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, TYPVAL_ENCODE_NODICT_VAR); break; } - for (const listitem_T *li = val_list->lv_first; li != NULL; - li = li->li_next) { - if (li->li_tv.v_type != VAR_LIST - || li->li_tv.vval.v_list->lv_len != 2) { + TV_LIST_ITER_CONST(val_list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST + || tv_list_len(TV_LIST_ITEM_TV(li)->vval.v_list) != 2) { goto _convert_one_value_regular_dict; } - } - const int saved_copyID = val_di->di_tv.vval.v_list->lv_copyID; + }); + const int saved_copyID = tv_list_copyid(val_di->di_tv.vval.v_list); _TYPVAL_ENCODE_DO_CHECK_SELF_REFERENCE(val_list, lv_copyID, copyID, kMPConvPairs); TYPVAL_ENCODE_CONV_DICT_START(tv, TYPVAL_ENCODE_NODICT_VAR, - val_list->lv_len); + tv_list_len(val_list)); assert(saved_copyID != copyID && saved_copyID != copyID - 1); _mp_push(*mpstack, ((MPConvStackVal) { .tv = tv, @@ -544,7 +563,7 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( .data = { .l = { .list = val_list, - .li = val_list->lv_first, + .li = tv_list_first(val_list), }, }, })); @@ -554,18 +573,23 @@ static int _TYPVAL_ENCODE_CONVERT_ONE_VALUE( const list_T *val_list; varnumber_T type; if (val_di->di_tv.v_type != VAR_LIST - || (val_list = val_di->di_tv.vval.v_list) == NULL - || val_list->lv_len != 2 - || (val_list->lv_first->li_tv.v_type != VAR_NUMBER) - || (type = val_list->lv_first->li_tv.vval.v_number) > INT8_MAX + || tv_list_len((val_list = val_di->di_tv.vval.v_list)) != 2 + || (TV_LIST_ITEM_TV(tv_list_first(val_list))->v_type + != VAR_NUMBER) + || ((type + = TV_LIST_ITEM_TV(tv_list_first(val_list))->vval.v_number) + > INT8_MAX) || type < INT8_MIN - || (val_list->lv_last->li_tv.v_type != VAR_LIST)) { + || (TV_LIST_ITEM_TV(tv_list_last(val_list))->v_type + != VAR_LIST)) { goto _convert_one_value_regular_dict; } size_t len; char *buf; - if (!encode_vim_list_to_buf(val_list->lv_last->li_tv.vval.v_list, - &len, &buf)) { + if (!( + encode_vim_list_to_buf( + TV_LIST_ITEM_TV(tv_list_last(val_list))->vval.v_list, &len, + &buf))) { goto _convert_one_value_regular_dict; } TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type); @@ -600,7 +624,7 @@ _convert_one_value_regular_dict: {} break; } case VAR_UNKNOWN: { - EMSG2(_(e_intern2), STR(_TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()"); + internal_error(STR(_TYPVAL_ENCODE_CONVERT_ONE_VALUE) "()"); return FAIL; } } @@ -671,40 +695,45 @@ typval_encode_stop_converting_one_item: case kMPConvList: { if (cur_mpsv->data.l.li == NULL) { (void)_mp_pop(mpstack); - cur_mpsv->data.l.list->lv_copyID = cur_mpsv->saved_copyID; + tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID); TYPVAL_ENCODE_CONV_LIST_END(cur_mpsv->tv); continue; - } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { + } else if (cur_mpsv->data.l.li + != tv_list_first(cur_mpsv->data.l.list)) { TYPVAL_ENCODE_CONV_LIST_BETWEEN_ITEMS(cur_mpsv->tv); } - tv = &cur_mpsv->data.l.li->li_tv; - cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; + tv = TV_LIST_ITEM_TV(cur_mpsv->data.l.li); + cur_mpsv->data.l.li = TV_LIST_ITEM_NEXT(cur_mpsv->data.l.list, + cur_mpsv->data.l.li); break; } case kMPConvPairs: { if (cur_mpsv->data.l.li == NULL) { (void)_mp_pop(mpstack); - cur_mpsv->data.l.list->lv_copyID = cur_mpsv->saved_copyID; + tv_list_set_copyid(cur_mpsv->data.l.list, cur_mpsv->saved_copyID); TYPVAL_ENCODE_CONV_DICT_END(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); continue; - } else if (cur_mpsv->data.l.li != cur_mpsv->data.l.list->lv_first) { + } else if (cur_mpsv->data.l.li + != tv_list_first(cur_mpsv->data.l.list)) { TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS( cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); } - const list_T *const kv_pair = cur_mpsv->data.l.li->li_tv.vval.v_list; + const list_T *const kv_pair = ( + TV_LIST_ITEM_TV(cur_mpsv->data.l.li)->vval.v_list); TYPVAL_ENCODE_SPECIAL_DICT_KEY_CHECK( - encode_vim_to__error_ret, kv_pair->lv_first->li_tv); - if (_TYPVAL_ENCODE_CONVERT_ONE_VALUE(TYPVAL_ENCODE_FIRST_ARG_NAME, - &mpstack, cur_mpsv, - &kv_pair->lv_first->li_tv, - copyID, - objname) == FAIL) { + encode_vim_to__error_ret, *TV_LIST_ITEM_TV(tv_list_first(kv_pair))); + if ( + _TYPVAL_ENCODE_CONVERT_ONE_VALUE( + TYPVAL_ENCODE_FIRST_ARG_NAME, &mpstack, cur_mpsv, + TV_LIST_ITEM_TV(tv_list_first(kv_pair)), copyID, objname) + == FAIL) { goto encode_vim_to__error_ret; } TYPVAL_ENCODE_CONV_DICT_AFTER_KEY(cur_mpsv->tv, TYPVAL_ENCODE_NODICT_VAR); - tv = &kv_pair->lv_last->li_tv; - cur_mpsv->data.l.li = cur_mpsv->data.l.li->li_next; + tv = TV_LIST_ITEM_TV(tv_list_last(kv_pair)); + cur_mpsv->data.l.li = TV_LIST_ITEM_NEXT(cur_mpsv->data.l.list, + cur_mpsv->data.l.li); break; } case kMPConvPartial: { diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_process.c index c101cb1bb9..0c2bf397e5 100644 --- a/src/nvim/event/libuv_process.c +++ b/src/nvim/event/libuv_process.c @@ -26,15 +26,18 @@ int libuv_process_spawn(LibuvProcess *uvproc) uvproc->uvopts.file = proc->argv[0]; uvproc->uvopts.args = proc->argv; uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE; - if (proc->detach) { - uvproc->uvopts.flags |= UV_PROCESS_DETACHED; - } #ifdef WIN32 // libuv collapses the argv to a CommandLineToArgvW()-style string. cmd.exe // expects a different syntax (must be prepared by the caller before now). if (os_shell_is_cmdexe(proc->argv[0])) { uvproc->uvopts.flags |= UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS; } + if (proc->detach) { + uvproc->uvopts.flags |= UV_PROCESS_DETACHED; + } +#else + // Always setsid() on unix-likes. #8107 + uvproc->uvopts.flags |= UV_PROCESS_DETACHED; #endif uvproc->uvopts.exit_cb = exit_cb; uvproc->uvopts.cwd = proc->cwd; diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c index 5adf16c0f3..d92464f17b 100644 --- a/src/nvim/event/loop.c +++ b/src/nvim/event/loop.c @@ -30,19 +30,25 @@ void loop_init(Loop *loop, void *data) uv_signal_init(&loop->uv, &loop->children_watcher); uv_timer_init(&loop->uv, &loop->children_kill_timer); uv_timer_init(&loop->uv, &loop->poll_timer); + loop->poll_timer.data = xmalloc(sizeof(bool)); // "timeout expired" flag } -void loop_poll_events(Loop *loop, int ms) +/// Processes one `Loop.uv` event (at most). +/// Processes all `Loop.fast_events` events. +/// +/// @returns true if `ms` timeout was reached +bool loop_poll_events(Loop *loop, int ms) { if (loop->recursive++) { abort(); // Should not re-enter uv_run } uv_run_mode mode = UV_RUN_ONCE; + bool timeout_expired = false; if (ms > 0) { - // Use a repeating timeout of ms milliseconds to make sure - // we do not block indefinitely for I/O. + *((bool *)loop->poll_timer.data) = false; // reset "timeout expired" flag + // Dummy timer to ensure UV_RUN_ONCE does not block indefinitely for I/O. uv_timer_start(&loop->poll_timer, timer_cb, (uint64_t)ms, (uint64_t)ms); } else if (ms == 0) { // For ms == 0, do a non-blocking event poll. @@ -52,11 +58,13 @@ void loop_poll_events(Loop *loop, int ms) uv_run(&loop->uv, mode); if (ms > 0) { + timeout_expired = *((bool *)loop->poll_timer.data); uv_timer_stop(&loop->poll_timer); } loop->recursive--; // Can re-enter uv_run now multiqueue_process_events(loop->fast_events); + return timeout_expired; } /// Schedules an event from another thread. @@ -111,7 +119,7 @@ bool loop_close(Loop *loop, bool wait) uv_mutex_destroy(&loop->mutex); uv_close((uv_handle_t *)&loop->children_watcher, NULL); uv_close((uv_handle_t *)&loop->children_kill_timer, NULL); - uv_close((uv_handle_t *)&loop->poll_timer, NULL); + uv_close((uv_handle_t *)&loop->poll_timer, timer_close_cb); uv_close((uv_handle_t *)&loop->async, NULL); uint64_t start = wait ? os_hrtime() : 0; while (true) { @@ -163,5 +171,11 @@ static void async_cb(uv_async_t *handle) static void timer_cb(uv_timer_t *handle) { + bool *timeout_expired = handle->data; + *timeout_expired = true; } +static void timer_close_cb(uv_handle_t *handle) +{ + xfree(handle->data); +} diff --git a/src/nvim/event/process.c b/src/nvim/event/process.c index 4eb2dd0baf..60650344ce 100644 --- a/src/nvim/event/process.c +++ b/src/nvim/event/process.c @@ -12,6 +12,7 @@ #include "nvim/event/wstream.h" #include "nvim/event/process.h" #include "nvim/event/libuv_process.h" +#include "nvim/os/process.h" #include "nvim/os/pty_process.h" #include "nvim/globals.h" #include "nvim/macros.h" @@ -151,7 +152,6 @@ void process_close_streams(Process *proc) FUNC_ATTR_NONNULL_ALL int process_wait(Process *proc, int ms, MultiQueue *events) FUNC_ATTR_NONNULL_ARG(1) { - bool interrupted = false; if (!proc->refcount) { int status = proc->status; LOOP_PROCESS_EVENTS(proc->loop, proc->events, 0); @@ -173,7 +173,6 @@ int process_wait(Process *proc, int ms, MultiQueue *events) // we'll assume that a user frantically hitting interrupt doesn't like // the current job. Signal that it has to be killed. if (got_int) { - interrupted = true; got_int = false; process_stop(proc); if (ms == -1) { @@ -184,14 +183,13 @@ int process_wait(Process *proc, int ms, MultiQueue *events) } else { LOOP_PROCESS_EVENTS(proc->loop, events, 0); } + + proc->status = -2; } if (proc->refcount == 1) { // Job exited, collect status and manually invoke close_cb to free the job // resources - if (interrupted) { - proc->status = -2; - } decref(proc); if (events) { // the decref call created an exit event, process it now @@ -218,8 +216,7 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL // stdout/stderr, they will be closed when it exits(possibly due to being // terminated after a timeout) stream_may_close(&proc->in); - ILOG("Sending SIGTERM to pid %d", proc->pid); - uv_kill(proc->pid, SIGTERM); + os_proc_tree_kill(proc->pid, SIGTERM); break; case kProcessTypePty: // close all streams for pty processes to send SIGHUP to the process @@ -234,7 +231,7 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL if (!loop->children_stop_requests++) { // When there's at least one stop request pending, start a timer that // will periodically check if a signal should be send to the job. - ILOG("Starting job kill timer"); + ILOG("starting job kill timer"); uv_timer_start(&loop->children_kill_timer, children_kill_cb, KILL_TIMEOUT_MS, KILL_TIMEOUT_MS); } @@ -256,11 +253,9 @@ static void children_kill_cb(uv_timer_t *handle) if (elapsed >= KILL_TIMEOUT_MS) { int sig = proc->type == kProcessTypePty && elapsed < KILL_TIMEOUT_MS * 2 - ? SIGTERM - : SIGKILL; - ILOG("Sending %s to pid %d", sig == SIGTERM ? "SIGTERM" : "SIGKILL", - proc->pid); - uv_kill(proc->pid, sig); + ? SIGTERM + : SIGKILL; + os_proc_tree_kill(proc->pid, sig); } } } diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c index e0500ba828..2fbe7f6773 100644 --- a/src/nvim/event/rstream.c +++ b/src/nvim/event/rstream.c @@ -95,7 +95,7 @@ static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf) // `uv_buf_t.len` happens to have different size on Windows. size_t write_count; buf->base = rbuffer_write_ptr(stream->buffer, &write_count); - buf->len = write_count; + buf->len = UV_BUF_LEN(write_count); } // Callback invoked by libuv after it copies the data into the buffer provided @@ -146,7 +146,7 @@ static void fread_idle_cb(uv_idle_t *handle) // `uv_buf_t.len` happens to have different size on Windows. size_t write_count; stream->uvbuf.base = rbuffer_write_ptr(stream->buffer, &write_count); - stream->uvbuf.len = write_count; + stream->uvbuf.len = UV_BUF_LEN(write_count); // the offset argument to uv_fs_read is int64_t, could someone really try // to read more than 9 quintillion (9e18) bytes? diff --git a/src/nvim/event/wstream.c b/src/nvim/event/wstream.c index 320006890d..d2fb52243c 100644 --- a/src/nvim/event/wstream.c +++ b/src/nvim/event/wstream.c @@ -90,7 +90,7 @@ bool wstream_write(Stream *stream, WBuffer *buffer) uv_buf_t uvbuf; uvbuf.base = buffer->data; - uvbuf.len = buffer->size; + uvbuf.len = UV_BUF_LEN(buffer->size); if (uv_write(&data->uv_req, stream->uvstream, &uvbuf, 1, write_cb)) { xfree(data); diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c index 8616508d88..c396b5891a 100644 --- a/src/nvim/ex_cmds.c +++ b/src/nvim/ex_cmds.c @@ -1175,7 +1175,7 @@ static void do_filter( // to read the error messages. Otherwise errors are ignored, so you can see // the error messages from the command that appear on stdout; use 'u' to fix // the text. - // Pass on the kShellDoOut flag when the output is being redirected. + // Pass on the kShellOptDoOut flag when the output is being redirected. if (call_shell( cmd_buf, kShellOptFilter | shell_flags, @@ -1417,7 +1417,7 @@ char_u *make_filter_cmd(char_u *cmd, char_u *itmp, char_u *otmp) #else // For shells that don't understand braces around commands, at least allow // the use of commands in a pipe. - xstrlcpy(buf, cmd, len); + xstrlcpy(buf, (char *)cmd, len); if (itmp != NULL) { // If there is a pipe, we have to put the '<' in front of it. // Don't do this when 'shellquote' is not empty, otherwise the @@ -2013,9 +2013,10 @@ int getfile(int fnum, char_u *ffname, char_u *sfname, int setpm, linenr_T lnum, } else other = (fnum != curbuf->b_fnum); - if (other) - ++no_wait_return; /* don't wait for autowrite message */ - if (other && !forceit && curbuf->b_nwindows == 1 && !P_HID(curbuf) + if (other) { + no_wait_return++; // don't wait for autowrite message + } + if (other && !forceit && curbuf->b_nwindows == 1 && !buf_hide(curbuf) && curbufIsChanged() && autowrite(curbuf, forceit) == FAIL) { if (p_confirm && p_write) dialog_changed(curbuf, FALSE); @@ -2032,17 +2033,19 @@ int getfile(int fnum, char_u *ffname, char_u *sfname, int setpm, linenr_T lnum, if (setpm) setpcmark(); if (!other) { - if (lnum != 0) + if (lnum != 0) { curwin->w_cursor.lnum = lnum; + } check_cursor_lnum(); beginline(BL_SOL | BL_FIX); - retval = 0; /* it's in the same file */ + retval = 0; // it's in the same file } else if (do_ecmd(fnum, ffname, sfname, NULL, lnum, - (P_HID(curbuf) ? ECMD_HIDE : 0) + (forceit ? ECMD_FORCEIT : 0), - curwin) == OK) - retval = -1; /* opened another file */ - else - retval = 1; /* error encountered */ + (buf_hide(curbuf) ? ECMD_HIDE : 0) + + (forceit ? ECMD_FORCEIT : 0), curwin) == OK) { + retval = -1; // opened another file + } else { + retval = 1; // error encountered + } theend: xfree(free_me); @@ -3541,6 +3544,9 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout) getvcol(curwin, &curwin->w_cursor, &sc, NULL, NULL); curwin->w_cursor.col = regmatch.endpos[0].col - 1; + if (curwin->w_cursor.col < 0) { + curwin->w_cursor.col = 0; + } getvcol(curwin, &curwin->w_cursor, NULL, NULL, &ec); if (subflags.do_number || curwin->w_p_nu) { int numw = number_width(curwin) + 1; @@ -4617,18 +4623,20 @@ int find_help_tags(char_u *arg, int *num_matches, char_u ***matches, int keep_la static char *(mtable[]) = {"*", "g*", "[*", "]*", "/*", "/\\*", "\"*", "**", "/\\(\\)", "/\\%(\\)", - "?", ":?", "?<CR>", "g?", "g?g?", "g??", "z?", + "?", ":?", "?<CR>", "g?", "g?g?", "g??", "/\\?", "/\\z(\\)", "\\=", ":s\\=", - "[count]", "[quotex]", "[range]", + "[count]", "[quotex]", + "[range]", ":[range]", "[pattern]", "\\|", "\\%$", "s/\\~", "s/\\U", "s/\\L", "s/\\1", "s/\\2", "s/\\3", "s/\\9"}; static char *(rtable[]) = {"star", "gstar", "[star", "]star", "/star", "/\\\\star", "quotestar", "starstar", "/\\\\(\\\\)", "/\\\\%(\\\\)", - "?", ":?", "?<CR>", "g?", "g?g?", "g??", "z?", + "?", ":?", "?<CR>", "g?", "g?g?", "g??", "/\\\\?", "/\\\\z(\\\\)", "\\\\=", ":s\\\\=", - "\\[count]", "\\[quotex]", "\\[range]", + "\\[count]", "\\[quotex]", + "\\[range]", ":\\[range]", "\\[pattern]", "\\\\bar", "/\\\\%\\$", "s/\\\\\\~", "s/\\\\U", "s/\\\\L", "s/\\\\1", "s/\\\\2", "s/\\\\3", "s/\\\\9"}; @@ -4856,7 +4864,9 @@ void fix_help_buffer(void) char_u *rt; // Set filetype to "help". - set_option_value("ft", 0L, "help", OPT_LOCAL); + if (STRCMP(curbuf->b_p_ft, "help") != 0) { + set_option_value("ft", 0L, "help", OPT_LOCAL); + } if (!syntax_present(curwin)) { for (lnum = 1; lnum <= curbuf->b_ml.ml_line_count; ++lnum) { @@ -5818,7 +5828,8 @@ static void sign_list_defined(sign_T *sp) } if (sp->sn_line_hl > 0) { msg_puts(" linehl="); - const char *const p = get_highlight_name(NULL, sp->sn_line_hl - 1); + const char *const p = get_highlight_name_ext(NULL, + sp->sn_line_hl - 1, false); if (p == NULL) { msg_puts("NONE"); } else { @@ -5827,7 +5838,8 @@ static void sign_list_defined(sign_T *sp) } if (sp->sn_text_hl > 0) { msg_puts(" texthl="); - const char *const p = get_highlight_name(NULL, sp->sn_text_hl - 1); + const char *const p = get_highlight_name_ext(NULL, + sp->sn_text_hl - 1, false); if (p == NULL) { msg_puts("NONE"); } else { @@ -6334,7 +6346,6 @@ char_u *skip_vimgrep_pat(char_u *p, char_u **s, int *flags) void ex_oldfiles(exarg_T *eap) { list_T *l = get_vim_var_list(VV_OLDFILES); - listitem_T *li; long nr = 0; if (l == NULL) { @@ -6342,19 +6353,22 @@ void ex_oldfiles(exarg_T *eap) } else { msg_start(); msg_scroll = true; - for (li = l->lv_first; li != NULL && !got_int; li = li->li_next) { + TV_LIST_ITER(l, li, { + if (got_int) { + break; + } nr++; - const char *fname = tv_get_string(&li->li_tv); + const char *fname = tv_get_string(TV_LIST_ITEM_TV(li)); if (!message_filtered((char_u *)fname)) { msg_outnum(nr); MSG_PUTS(": "); - msg_outtrans((char_u *)tv_get_string(&li->li_tv)); + msg_outtrans((char_u *)tv_get_string(TV_LIST_ITEM_TV(li))); msg_clr_eos(); msg_putchar('\n'); ui_flush(); // output one line at a time os_breakcheck(); } - } + }); // Assume "got_int" was set to truncate the listing. got_int = false; @@ -6364,7 +6378,7 @@ void ex_oldfiles(exarg_T *eap) quit_more = false; nr = prompt_for_number(false); msg_starthere(); - if (nr > 0 && nr <= l->lv_len) { + if (nr > 0 && nr <= tv_list_len(l)) { const char *const p = tv_list_find_str(l, nr - 1); if (p == NULL) { return; diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua index e57e662039..ce02808ad3 100644 --- a/src/nvim/ex_cmds.lua +++ b/src/nvim/ex_cmds.lua @@ -108,7 +108,7 @@ return { }, { command='argedit', - flags=bit.bor(BANG, NEEDARG, RANGE, NOTADR, ZEROR, FILE1, EDITCMD, ARGOPT, TRLBAR), + flags=bit.bor(BANG, NEEDARG, RANGE, NOTADR, ZEROR, FILES, EDITCMD, ARGOPT, TRLBAR), addr_type=ADDR_ARGUMENTS, func='ex_argedit', }, @@ -692,7 +692,7 @@ return { }, { command='delcommand', - flags=bit.bor(NEEDARG, WORD1, TRLBAR, CMDWIN), + flags=bit.bor(BANG, NEEDARG, WORD1, TRLBAR, CMDWIN), addr_type=ADDR_LINES, func='ex_delcommand', }, diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c index ec4ce63e17..821c050c50 100644 --- a/src/nvim/ex_cmds2.c +++ b/src/nvim/ex_cmds2.c @@ -1343,7 +1343,7 @@ void dialog_changed(buf_T *buf, int checkall) /// hidden, autowriting it or unloading it. bool can_abandon(buf_T *buf, int forceit) { - return P_HID(buf) + return buf_hide(buf) || !bufIsChanged(buf) || buf->b_nwindows > 1 || autowrite(buf, forceit) == OK @@ -1558,12 +1558,17 @@ static char_u *do_one_arg(char_u *str) /// Separate the arguments in "str" and return a list of pointers in the /// growarray "gap". -void get_arglist(garray_T *gap, char_u *str) +static void get_arglist(garray_T *gap, char_u *str, int escaped) { ga_init(gap, (int)sizeof(char_u *), 20); while (*str != NUL) { GA_APPEND(char_u *, gap, str); + // If str is escaped, don't handle backslashes or spaces + if (!escaped) { + return; + } + // Isolate one argument, change it in-place, put a NUL after it. str = do_one_arg(str); } @@ -1578,7 +1583,7 @@ int get_arglist_exp(char_u *str, int *fcountp, char_u ***fnamesp, bool wig) garray_T ga; int i; - get_arglist(&ga, str); + get_arglist(&ga, str, true); if (wig) { i = expand_wildcards(ga.ga_len, (char_u **)ga.ga_data, @@ -1609,6 +1614,7 @@ static int do_arglist(char_u *str, int what, int after) char_u **exp_files; char_u *p; int match; + int arg_escaped = true; // Set default argument for ":argadd" command. if (what == AL_ADD && *str == NUL) { @@ -1616,10 +1622,11 @@ static int do_arglist(char_u *str, int what, int after) return FAIL; } str = curbuf->b_fname; + arg_escaped = false; } // Collect all file name arguments in "new_ga". - get_arglist(&new_ga, str); + get_arglist(&new_ga, str, arg_escaped); if (what == AL_DEL) { regmatch_T regmatch; @@ -1853,12 +1860,12 @@ void do_argfile(exarg_T *eap, int argn) // if 'hidden' set, only check for changed file when re-editing // the same buffer other = true; - if (P_HID(curbuf)) { + if (buf_hide(curbuf)) { p = (char_u *)fix_fname((char *)alist_name(&ARGLIST[argn])); other = otherfile(p); xfree(p); } - if ((!P_HID(curbuf) || !other) + if ((!buf_hide(curbuf) || !other) && check_changed(curbuf, CCGD_AW | (other ? 0 : CCGD_MULTWIN) | (eap->forceit ? CCGD_FORCEIT : 0) @@ -1878,7 +1885,7 @@ void do_argfile(exarg_T *eap, int argn) // argument index. if (do_ecmd(0, alist_name(&ARGLIST[curwin->w_arg_idx]), NULL, eap, ECMD_LAST, - (P_HID(curwin->w_buffer) ? ECMD_HIDE : 0) + (buf_hide(curwin->w_buffer) ? ECMD_HIDE : 0) + (eap->forceit ? ECMD_FORCEIT : 0), curwin) == FAIL) { curwin->w_arg_idx = old_arg_idx; } else if (eap->cmdidx != CMD_argdo) { @@ -1895,7 +1902,7 @@ void ex_next(exarg_T *eap) // check for changed buffer now, if this fails the argument list is not // redefined. - if (P_HID(curbuf) + if (buf_hide(curbuf) || eap->cmdidx == CMD_snext || !check_changed(curbuf, CCGD_AW | (eap->forceit ? CCGD_FORCEIT : 0) @@ -1915,31 +1922,21 @@ void ex_next(exarg_T *eap) /// ":argedit" void ex_argedit(exarg_T *eap) { - int fnum; - int i; - char_u *s; - - // Add the argument to the buffer list and get the buffer number. - fnum = buflist_add(eap->arg, BLN_LISTED); + int i = eap->addr_count ? (int)eap->line2 : curwin->w_arg_idx + 1; - // Check if this argument is already in the argument list. - for (i = 0; i < ARGCOUNT; i++) { - if (ARGLIST[i].ae_fnum == fnum) { - break; - } - } - if (i == ARGCOUNT) { - // Can't find it, add it to the argument list. - s = vim_strsave(eap->arg); - int after = eap->addr_count > 0 ? (int)eap->line2 : curwin->w_arg_idx + 1; - i = alist_add_list(1, &s, after); - curwin->w_arg_idx = i; + if (do_arglist(eap->arg, AL_ADD, i) == FAIL) { + return; } + maketitle(); - alist_check_arg_idx(); - + if (curwin->w_arg_idx == 0 && (curbuf->b_ml.ml_flags & ML_EMPTY) + && curbuf->b_ffname == NULL) { + i = 0; + } // Edit the argument. - do_argfile(eap, i); + if (i < ARGCOUNT) { + do_argfile(eap, i); + } } /// ":argadd" @@ -1959,8 +1956,14 @@ void ex_argdelete(exarg_T *eap) eap->line2 = ARGCOUNT; } linenr_T n = eap->line2 - eap->line1 + 1; - if (*eap->arg != NUL || n <= 0) { + if (*eap->arg != NUL) { + // Can't have both a range and an argument. EMSG(_(e_invarg)); + } else if (n <= 0) { + // Don't give an error for ":%argdel" if the list is empty. + if (eap->line1 != 1 || eap->line2 != 0) { + EMSG(_(e_invrange)); + } } else { for (linenr_T i = eap->line1; i <= eap->line2; i++) { xfree(ARGLIST[i - 1].ae_fname); @@ -2005,7 +2008,7 @@ void ex_listdo(exarg_T *eap) if (eap->cmdidx == CMD_windo || eap->cmdidx == CMD_tabdo - || P_HID(curbuf) + || buf_hide(curbuf) || !check_changed(curbuf, CCGD_AW | (eap->forceit ? CCGD_FORCEIT : 0) | CCGD_EXCMD)) { @@ -3758,7 +3761,7 @@ static void script_host_execute(char *name, exarg_T *eap) char *const script = script_get(eap, &len); if (script != NULL) { - list_T *const args = tv_list_alloc(); + list_T *const args = tv_list_alloc(3); // script tv_list_append_allocated_string(args, script); // current range @@ -3773,7 +3776,7 @@ static void script_host_execute_file(char *name, exarg_T *eap) uint8_t buffer[MAXPATHL]; vim_FullName((char *)eap->arg, (char *)buffer, sizeof(buffer), false); - list_T *args = tv_list_alloc(); + list_T *args = tv_list_alloc(3); // filename tv_list_append_string(args, (const char *)buffer, -1); // current range @@ -3784,7 +3787,7 @@ static void script_host_execute_file(char *name, exarg_T *eap) static void script_host_do_range(char *name, exarg_T *eap) { - list_T *args = tv_list_alloc(); + list_T *args = tv_list_alloc(3); tv_list_append_number(args, (int)eap->line1); tv_list_append_number(args, (int)eap->line2); tv_list_append_string(args, (const char *)eap->arg, -1); @@ -3837,7 +3840,7 @@ void ex_drop(exarg_T *eap) // to split the current window or data could be lost. // Skip the check if the 'hidden' option is set, as in this case the // buffer won't be lost. - if (!P_HID(curbuf)) { + if (!buf_hide(curbuf)) { emsg_off++; split = check_changed(curbuf, CCGD_AW | CCGD_EXCMD); emsg_off--; diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c index e11788531b..93cb0e50fa 100644 --- a/src/nvim/ex_docmd.c +++ b/src/nvim/ex_docmd.c @@ -133,7 +133,6 @@ struct dbg_stuff { char_u *vv_throwpoint; int did_emsg; int got_int; - int did_throw; int need_rethrow; int check_cstack; except_T *current_exception; @@ -165,12 +164,11 @@ static void save_dbg_stuff(struct dbg_stuff *dsp) dsp->vv_exception = v_exception(NULL); dsp->vv_throwpoint = v_throwpoint(NULL); - /* Necessary for debugging an inactive ":catch", ":finally", ":endtry" */ - dsp->did_emsg = did_emsg; did_emsg = FALSE; - dsp->got_int = got_int; got_int = FALSE; - dsp->did_throw = did_throw; did_throw = FALSE; - dsp->need_rethrow = need_rethrow; need_rethrow = FALSE; - dsp->check_cstack = check_cstack; check_cstack = FALSE; + // Necessary for debugging an inactive ":catch", ":finally", ":endtry". + dsp->did_emsg = did_emsg; did_emsg = false; + dsp->got_int = got_int; got_int = false; + dsp->need_rethrow = need_rethrow; need_rethrow = false; + dsp->check_cstack = check_cstack; check_cstack = false; dsp->current_exception = current_exception; current_exception = NULL; } @@ -184,7 +182,6 @@ static void restore_dbg_stuff(struct dbg_stuff *dsp) (void)v_throwpoint(dsp->vv_throwpoint); did_emsg = dsp->did_emsg; got_int = dsp->got_int; - did_throw = dsp->did_throw; need_rethrow = dsp->need_rethrow; check_cstack = dsp->check_cstack; current_exception = dsp->current_exception; @@ -400,16 +397,11 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, initial_trylevel = trylevel; - /* - * "did_throw" will be set to TRUE when an exception is being thrown. - */ - did_throw = FALSE; - /* - * "did_emsg" will be set to TRUE when emsg() is used, in which case we - * cancel the whole command line, and any if/endif or loop. - * If force_abort is set, we cancel everything. - */ - did_emsg = FALSE; + current_exception = NULL; + // "did_emsg" will be set to TRUE when emsg() is used, in which case we + // cancel the whole command line, and any if/endif or loop. + // If force_abort is set, we cancel everything. + did_emsg = false; /* * KeyTyped is only set when calling vgetc(). Reset it here when not @@ -659,7 +651,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * not to use a cs_line[] from an entry that isn't a ":while" * or ":for": It would make "current_line" invalid and can * cause a crash. */ - if (!did_emsg && !got_int && !did_throw + if (!did_emsg && !got_int && !current_exception && cstack.cs_idx >= 0 && (cstack.cs_flags[cstack.cs_idx] & (CSF_WHILE | CSF_FOR)) @@ -707,7 +699,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, } /* - * A ":finally" makes did_emsg, got_int, and did_throw pending for + * A ":finally" makes did_emsg, got_int and current_exception pending for * being restored at the ":endtry". Reset them here and set the * ACTIVE and FINALLY flags, so that the finally clause gets executed. * This includes the case where a missing ":endif", ":endwhile" or @@ -715,10 +707,11 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, */ if (cstack.cs_lflags & CSL_HAD_FINA) { cstack.cs_lflags &= ~CSL_HAD_FINA; - report_make_pending(cstack.cs_pending[cstack.cs_idx] - & (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW), - did_throw ? (void *)current_exception : NULL); - did_emsg = got_int = did_throw = FALSE; + report_make_pending((cstack.cs_pending[cstack.cs_idx] + & (CSTP_ERROR | CSTP_INTERRUPT | CSTP_THROW)), + current_exception); + did_emsg = got_int = false; + current_exception = NULL; cstack.cs_flags[cstack.cs_idx] |= CSF_ACTIVE | CSF_FINALLY; } @@ -726,15 +719,14 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * within this loop. */ trylevel = initial_trylevel + cstack.cs_trylevel; - /* - * If the outermost try conditional (across function calls and sourced - * files) is aborted because of an error, an interrupt, or an uncaught - * exception, cancel everything. If it is left normally, reset - * force_abort to get the non-EH compatible abortion behavior for - * the rest of the script. - */ - if (trylevel == 0 && !did_emsg && !got_int && !did_throw) - force_abort = FALSE; + // If the outermost try conditional (across function calls and sourced + // files) is aborted because of an error, an interrupt, or an uncaught + // exception, cancel everything. If it is left normally, reset + // force_abort to get the non-EH compatible abortion behavior for + // the rest of the script. + if (trylevel == 0 && !did_emsg && !got_int && !current_exception) { + force_abort = false; + } /* Convert an interrupt to an exception if appropriate. */ (void)do_intthrow(&cstack); @@ -749,11 +741,8 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * - there is a command after '|', inside a :if, :while, :for or :try, or * looping for ":source" command or function call. */ - while (!((got_int - || (did_emsg && force_abort) || did_throw - ) - && cstack.cs_trylevel == 0 - ) + while (!((got_int || (did_emsg && force_abort) || current_exception) + && cstack.cs_trylevel == 0) && !(did_emsg /* Keep going when inside try/catch, so that the error can be * deal with, except when it is a syntax error, it may cause @@ -775,7 +764,7 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, * If a sourced file or executed function ran to its end, report the * unclosed conditional. */ - if (!got_int && !did_throw + if (!got_int && !current_exception && ((getline_equal(fgetline, cookie, getsourceline) && !source_finished(fgetline, cookie)) || (getline_equal(fgetline, cookie, get_func_line) @@ -815,17 +804,16 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, ? (char_u *)"endfunction" : (char_u *)NULL); if (trylevel == 0) { - /* - * When an exception is being thrown out of the outermost try - * conditional, discard the uncaught exception, disable the conversion - * of interrupts or errors to exceptions, and ensure that no more - * commands are executed. - */ - if (did_throw) { - void *p = NULL; - char_u *saved_sourcing_name; + // When an exception is being thrown out of the outermost try + // conditional, discard the uncaught exception, disable the conversion + // of interrupts or errors to exceptions, and ensure that no more + // commands are executed. + if (current_exception) { + void *p = NULL; + char_u *saved_sourcing_name; int saved_sourcing_lnum; - struct msglist *messages = NULL, *next; + struct msglist *messages = NULL; + struct msglist *next; /* * If the uncaught exception is a user exception, report it as an @@ -885,22 +873,22 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline, suppress_errthrow = TRUE; } - /* - * The current cstack will be freed when do_cmdline() returns. An uncaught - * exception will have to be rethrown in the previous cstack. If a function - * has just returned or a script file was just finished and the previous - * cstack belongs to the same function or, respectively, script file, it - * will have to be checked for finally clauses to be executed due to the - * ":return" or ":finish". This is done in do_one_cmd(). - */ - if (did_throw) - need_rethrow = TRUE; + // The current cstack will be freed when do_cmdline() returns. An uncaught + // exception will have to be rethrown in the previous cstack. If a function + // has just returned or a script file was just finished and the previous + // cstack belongs to the same function or, respectively, script file, it + // will have to be checked for finally clauses to be executed due to the + // ":return" or ":finish". This is done in do_one_cmd(). + if (current_exception) { + need_rethrow = true; + } if ((getline_equal(fgetline, cookie, getsourceline) && ex_nesting_level > source_level(real_cookie)) || (getline_equal(fgetline, cookie, get_func_line) && ex_nesting_level > func_level(real_cookie) + 1)) { - if (!did_throw) - check_cstack = TRUE; + if (!current_exception) { + check_cstack = true; + } } else { /* When leaving a function, reduce nesting level. */ if (getline_equal(fgetline, cookie, get_func_line)) @@ -1480,10 +1468,11 @@ static char_u * do_one_cmd(char_u **cmdlinep, } char_u *after_modifier = ea.cmd; - ea.skip = did_emsg || got_int || did_throw || (cstack->cs_idx >= 0 - && !(cstack->cs_flags[cstack-> - cs_idx] - & CSF_ACTIVE)); + ea.skip = (did_emsg + || got_int + || current_exception + || (cstack->cs_idx >= 0 + && !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE))); /* Count this line for profiling if ea.skip is FALSE. */ if (do_profiling == PROF_YES && !ea.skip) { @@ -1782,13 +1771,14 @@ static char_u * do_one_cmd(char_u **cmdlinep, )); - /* forced commands */ + // Forced commands. if (*p == '!' && ea.cmdidx != CMD_substitute && ea.cmdidx != CMD_smagic && ea.cmdidx != CMD_snomagic) { - ++p; - ea.forceit = TRUE; - } else - ea.forceit = FALSE; + p++; + ea.forceit = true; + } else { + ea.forceit = false; + } /* * 6. Parse arguments. @@ -3442,6 +3432,10 @@ const char * set_one_cmd_context( case CMD_profile: set_context_in_profile_cmd(xp, arg); break; + case CMD_checkhealth: + xp->xp_context = EXPAND_CHECKHEALTH; + xp->xp_pattern = (char_u *)arg; + break; case CMD_behave: xp->xp_context = EXPAND_BEHAVE; xp->xp_pattern = (char_u *)arg; @@ -4871,6 +4865,7 @@ static struct { { EXPAND_AUGROUP, "augroup" }, { EXPAND_BEHAVE, "behave" }, { EXPAND_BUFFERS, "buffer" }, + { EXPAND_CHECKHEALTH, "checkhealth" }, { EXPAND_COLORS, "color" }, { EXPAND_COMMANDS, "command" }, { EXPAND_COMPILER, "compiler" }, @@ -5925,9 +5920,10 @@ static void ex_colorscheme(exarg_T *eap) static void ex_highlight(exarg_T *eap) { - if (*eap->arg == NUL && eap->cmd[2] == '!') + if (*eap->arg == NUL && eap->cmd[2] == '!') { MSG(_("Greetings, Vim user!")); - do_highlight(eap->arg, eap->forceit, FALSE); + } + do_highlight((const char *)eap->arg, eap->forceit, false); } @@ -5968,22 +5964,24 @@ static void ex_quit(exarg_T *eap) wp = curwin; } - apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, curbuf); + // Refuse to quit when locked. + if (curbuf_locked()) { + return; + } + apply_autocmds(EVENT_QUITPRE, NULL, NULL, false, wp->w_buffer); // Refuse to quit when locked or when the buffer in the last window is // being closed (can only happen in autocommands). - if (curbuf_locked() + if (!win_valid(wp) || (wp->w_buffer->b_nwindows == 1 && wp->w_buffer->b_locked > 0)) { return; } - - /* - * If there are more files or windows we won't exit. - */ - if (check_more(FALSE, eap->forceit) == OK && only_one_window()) - exiting = TRUE; - if ((!P_HID(curbuf) - && check_changed(curbuf, (p_awa ? CCGD_AW : 0) + // If there are more files or windows we won't exit. + if (check_more(false, eap->forceit) == OK && only_one_window()) { + exiting = true; + } + if ((!buf_hide(wp->w_buffer) + && check_changed(wp->w_buffer, (p_awa ? CCGD_AW : 0) | (eap->forceit ? CCGD_FORCEIT : 0) | CCGD_EXCMD)) || check_more(true, eap->forceit) == FAIL @@ -5999,8 +5997,8 @@ static void ex_quit(exarg_T *eap) if (only_one_window() && (ONE_WINDOW || eap->addr_count == 0)) { getout(0); } - /* close window; may free buffer */ - win_close(wp, !P_HID(wp->w_buffer) || eap->forceit); + // close window; may free buffer + win_close(wp, !buf_hide(wp->w_buffer) || eap->forceit); } } @@ -6099,7 +6097,7 @@ ex_win_close ( buf_T *buf = win->w_buffer; need_hide = (bufIsChanged(buf) && buf->b_nwindows <= 1); - if (need_hide && !P_HID(buf) && !forceit) { + if (need_hide && !buf_hide(buf) && !forceit) { if ((p_confirm || cmdmod.confirm) && p_write) { bufref_T bufref; set_bufref(&bufref, buf); @@ -6115,11 +6113,12 @@ ex_win_close ( } - /* free buffer when not hiding it or when it's a scratch buffer */ - if (tp == NULL) - win_close(win, !need_hide && !P_HID(buf)); - else - win_close_othertab(win, !need_hide && !P_HID(buf), tp); + // free buffer when not hiding it or when it's a scratch buffer + if (tp == NULL) { + win_close(win, !need_hide && !buf_hide(buf)); + } else { + win_close_othertab(win, !need_hide && !buf_hide(buf), tp); + } } /* @@ -6229,7 +6228,6 @@ void tabpage_close_other(tabpage_T *tp, int forceit) if (!valid_tabpage(tp) || tp->tp_firstwin == wp) break; } - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, FALSE, curbuf); redraw_tabline = TRUE; if (h != tabline_height()) @@ -6351,7 +6349,7 @@ static void ex_exit(exarg_T *eap) getout(0); } // Quit current window, may free the buffer. - win_close(curwin, !P_HID(curwin->w_buffer)); + win_close(curwin, !buf_hide(curwin->w_buffer)); } } @@ -6921,24 +6919,23 @@ do_exedit ( empty buffer */ setpcmark(); if (do_ecmd(0, (eap->cmdidx == CMD_enew ? NULL : eap->arg), - NULL, eap, - eap->do_ecmd_lnum, - (P_HID(curbuf) ? ECMD_HIDE : 0) - + (eap->forceit ? ECMD_FORCEIT : 0) - // After a split we can use an existing buffer. - + (old_curwin != NULL ? ECMD_OLDBUF : 0) - + (eap->cmdidx == CMD_badd ? ECMD_ADDBUF : 0 ) - , old_curwin == NULL ? curwin : NULL) == FAIL) { - /* Editing the file failed. If the window was split, close it. */ + NULL, eap, eap->do_ecmd_lnum, + (buf_hide(curbuf) ? ECMD_HIDE : 0) + + (eap->forceit ? ECMD_FORCEIT : 0) + // After a split we can use an existing buffer. + + (old_curwin != NULL ? ECMD_OLDBUF : 0) + + (eap->cmdidx == CMD_badd ? ECMD_ADDBUF : 0) + , old_curwin == NULL ? curwin : NULL) == FAIL) { + // Editing the file failed. If the window was split, close it. if (old_curwin != NULL) { need_hide = (curbufIsChanged() && curbuf->b_nwindows <= 1); - if (!need_hide || P_HID(curbuf)) { + if (!need_hide || buf_hide(curbuf)) { cleanup_T cs; /* Reset the error/interrupt/exception state here so that * aborting() returns FALSE when closing a window. */ enter_cleanup(&cs); - win_close(curwin, !need_hide && !P_HID(curbuf)); + win_close(curwin, !need_hide && !buf_hide(curbuf)); /* Restore the error/interrupt/exception state if not * discarded by a new aborting error, interrupt, or @@ -6978,12 +6975,10 @@ do_exedit ( ex_no_reprint = TRUE; } -/* - * ":gui" and ":gvim" when there is no GUI. - */ +/// ":gui" and ":gvim" when there is no GUI. static void ex_nogui(exarg_T *eap) { - eap->errmsg = e_nogvim; + eap->errmsg = (char_u *)N_("E25: Nvim does not have a built-in GUI"); } @@ -8149,6 +8144,10 @@ static void ex_startinsert(exarg_T *eap) restart_edit = 'i'; curwin->w_curswant = 0; /* avoid MAXCOL */ } + + if (VIsual_active) { + showmode(); + } } /* @@ -8539,22 +8538,22 @@ eval_vars ( resultbuf = result; /* remember allocated string */ break; - case SPEC_AFILE: /* file name for autocommand */ - result = autocmd_fname; - if (result != NULL && !autocmd_fname_full) { - /* Still need to turn the fname into a full path. It is - * postponed to avoid a delay when <afile> is not used. */ - autocmd_fname_full = TRUE; - result = (char_u *)FullName_save((char *)autocmd_fname, FALSE); - xfree(autocmd_fname); - autocmd_fname = result; + case SPEC_AFILE: // file name for autocommand + if (autocmd_fname != NULL && !path_is_absolute(autocmd_fname)) { + // Still need to turn the fname into a full path. It was + // postponed to avoid a delay when <afile> is not used. + result = (char_u *)FullName_save((char *)autocmd_fname, false); + // Copy into `autocmd_fname`, don't reassign it. #8165 + xstrlcpy((char *)autocmd_fname, (char *)result, MAXPATHL); + xfree(result); } + result = autocmd_fname; if (result == NULL) { *errormsg = (char_u *)_( "E495: no autocommand file name to substitute for \"<afile>\""); return NULL; } - result = path_shorten_fname_if_possible(result); + result = path_try_shorten_fname(result); break; case SPEC_ABUF: /* buffer number for autocommand */ @@ -9718,29 +9717,37 @@ static void ex_filetype(exarg_T *eap) EMSG2(_(e_invarg2), arg); } -/// Do ":filetype plugin indent on" if user did not already do some -/// permutation thereof. +/// Set all :filetype options ON if user did not explicitly set any to OFF. void filetype_maybe_enable(void) { - if (filetype_detect == kNone - && filetype_plugin == kNone - && filetype_indent == kNone) { + if (filetype_detect == kNone) { source_runtime((char_u *)FILETYPE_FILE, true); filetype_detect = kTrue; + } + if (filetype_plugin == kNone) { source_runtime((char_u *)FTPLUGIN_FILE, true); filetype_plugin = kTrue; + } + if (filetype_indent == kNone) { source_runtime((char_u *)INDENT_FILE, true); filetype_indent = kTrue; } } -/* - * ":setfiletype {name}" - */ +/// ":setfiletype [FALLBACK] {name}" static void ex_setfiletype(exarg_T *eap) { if (!did_filetype) { - set_option_value("filetype", 0L, (char *)eap->arg, OPT_LOCAL); + char_u *arg = eap->arg; + + if (STRNCMP(arg, "FALLBACK ", 9) == 0) { + arg += 9; + } + + set_option_value("filetype", 0L, (char *)arg, OPT_LOCAL); + if (arg != eap->arg) { + did_filetype = false; + } } } diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c index 9037b3c151..e23945c842 100644 --- a/src/nvim/ex_eval.c +++ b/src/nvim/ex_eval.c @@ -45,22 +45,21 @@ * there or even outside the try conditional. Try conditionals may be nested. */ -/* - * Configuration whether an exception is thrown on error or interrupt. When - * the preprocessor macros below evaluate to FALSE, an error (did_emsg) or - * interrupt (got_int) under an active try conditional terminates the script - * after the non-active finally clauses of all active try conditionals have been - * executed. Otherwise, errors and/or interrupts are converted into catchable - * exceptions (did_throw additionally set), which terminate the script only if - * not caught. For user exceptions, only did_throw is set. (Note: got_int can - * be set asynchronously afterwards by a SIGINT, so did_throw && got_int is not - * a reliant test that the exception currently being thrown is an interrupt - * exception. Similarly, did_emsg can be set afterwards on an error in an - * (unskipped) conditional command inside an inactive conditional, so did_throw - * && did_emsg is not a reliant test that the exception currently being thrown - * is an error exception.) - The macros can be defined as expressions checking - * for a variable that is allowed to be changed during execution of a script. - */ +// Configuration whether an exception is thrown on error or interrupt. When +// the preprocessor macros below evaluate to FALSE, an error (did_emsg) or +// interrupt (got_int) under an active try conditional terminates the script +// after the non-active finally clauses of all active try conditionals have been +// executed. Otherwise, errors and/or interrupts are converted into catchable +// exceptions, which terminate the script only if not caught. For user +// exceptions, only current_exception is set. (Note: got_int can be set +// asynchronously afterwards by a SIGINT, so current_exception && got_int is not +// a reliant test that the exception currently being thrown is an interrupt +// exception. Similarly, did_emsg can be set afterwards on an error in an +// (unskipped) conditional command inside an inactive conditional, so +// current_exception && did_emsg is not a reliant test that the exception +// currently being thrown is an error exception.) - The macros can be defined +// as expressions checking for a variable that is allowed to be changed during +// execution of a script. // Values used for the Vim release. #define THROW_ON_ERROR true @@ -68,6 +67,15 @@ #define THROW_ON_INTERRUPT true #define THROW_ON_INTERRUPT_TRUE +// Don't do something after an error, interrupt, or throw, or when +// there is a surrounding conditional and it was not active. +#define CHECK_SKIP \ + (did_emsg \ + || got_int \ + || current_exception \ + || (cstack->cs_idx > 0 \ + && !(cstack->cs_flags[cstack->cs_idx - 1] & CSF_ACTIVE))) + #define discard_pending_return(p) tv_free((typval_T *)(p)) /* @@ -94,7 +102,7 @@ static int cause_abort = FALSE; */ int aborting(void) { - return (did_emsg && force_abort) || got_int || did_throw; + return (did_emsg && force_abort) || got_int || current_exception; } /* @@ -178,8 +186,9 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore) * currently throwing an exception, do nothing. The message text will * then be stored to v:errmsg by emsg() without displaying it. */ - if (((trylevel == 0 && !cause_abort) || emsg_silent) && !did_throw) - return FALSE; + if (((trylevel == 0 && !cause_abort) || emsg_silent) && !current_exception) { + return false; + } /* * Ignore an interrupt message when inside a try conditional or when an @@ -208,12 +217,13 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore) * exception currently being thrown to prevent it from being caught. Just * execute finally clauses and terminate. */ - if (did_throw) { - /* When discarding an interrupt exception, reset got_int to prevent the - * same interrupt being converted to an exception again and discarding - * the error exception we are about to throw here. */ - if (current_exception->type == ET_INTERRUPT) - got_int = FALSE; + if (current_exception) { + // When discarding an interrupt exception, reset got_int to prevent the + // same interrupt being converted to an exception again and discarding + // the error exception we are about to throw here. + if (current_exception->type == ET_INTERRUPT) { + got_int = false; + } discard_current_exception(); } @@ -333,45 +343,44 @@ void do_errthrow(struct condstack *cstack, char_u *cmdname) */ int do_intthrow(struct condstack *cstack) { - /* - * If no interrupt occurred or no try conditional is active and no exception - * is being thrown, do nothing (for compatibility of non-EH scripts). - */ - if (!got_int || (trylevel == 0 && !did_throw)) - return FALSE; + // If no interrupt occurred or no try conditional is active and no exception + // is being thrown, do nothing (for compatibility of non-EH scripts). + if (!got_int || (trylevel == 0 && !current_exception)) { + return false; + } -#ifdef THROW_TEST /* avoid warning for condition always true */ +#ifdef THROW_TEST // avoid warning for condition always true if (!THROW_ON_INTERRUPT) { - /* - * The interrupt aborts everything except for executing finally clauses. - * Discard any user or error or interrupt exception currently being - * thrown. - */ - if (did_throw) + // The interrupt aborts everything except for executing finally clauses. + // Discard any user or error or interrupt exception currently being + // thrown. + if (current_exception) { discard_current_exception(); - } else + } + } else { #endif - { - /* - * Throw an interrupt exception, so that everything will be aborted - * (except for executing finally clauses), until the interrupt exception - * is caught; if still uncaught at the top level, the script processing - * will be terminated then. - If an interrupt exception is already - * being thrown, do nothing. - * - */ - if (did_throw) { - if (current_exception->type == ET_INTERRUPT) - return FALSE; + // Throw an interrupt exception, so that everything will be aborted + // (except for executing finally clauses), until the interrupt exception + // is caught; if still uncaught at the top level, the script processing + // will be terminated then. - If an interrupt exception is already + // being thrown, do nothing. + + if (current_exception) { + if (current_exception->type == ET_INTERRUPT) { + return false; + } - /* An interrupt exception replaces any user or error exception. */ + // An interrupt exception replaces any user or error exception. discard_current_exception(); } - if (throw_exception("Vim:Interrupt", ET_INTERRUPT, NULL) != FAIL) + if (throw_exception("Vim:Interrupt", ET_INTERRUPT, NULL) != FAIL) { do_throw(cstack); + } +#ifdef THROW_TEST } +#endif - return TRUE; + return true; } // Get an exception message that is to be stored in current_exception->value. @@ -517,7 +526,7 @@ static void discard_exception(except_T *excp, int was_finished) char_u *saved_IObuff; if (excp == NULL) { - EMSG(_(e_internal)); + internal_error("discard_exception()"); return; } @@ -565,8 +574,7 @@ void discard_current_exception(void) // Note: all globals manipulated here should be saved/restored in // try_enter/try_leave. current_exception = NULL; - did_throw = FALSE; - need_rethrow = FALSE; + need_rethrow = false; } /* @@ -619,8 +627,9 @@ static void catch_exception(except_T *excp) */ static void finish_exception(except_T *excp) { - if (excp != caught_stack) - EMSG(_(e_internal)); + if (excp != caught_stack) { + internal_error("finish_exception()"); + } caught_stack = caught_stack->caught; if (caught_stack != NULL) { set_vim_var_string(VV_EXCEPTION, (char *) caught_stack->value, -1); @@ -794,15 +803,7 @@ void ex_if(exarg_T *eap) ++cstack->cs_idx; cstack->cs_flags[cstack->cs_idx] = 0; - /* - * Don't do something after an error, interrupt, or throw, or when there - * is a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; bool error; result = eval_to_bool(eap->arg, &error, &eap->nextcmd, skip); @@ -853,15 +854,7 @@ void ex_else(exarg_T *eap) int result; struct condstack *cstack = eap->cstack; - /* - * Don't do something after an error, interrupt, or throw, or when there is - * a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; if (cstack->cs_idx < 0 || (cstack->cs_flags[cstack->cs_idx] @@ -951,15 +944,7 @@ void ex_while(exarg_T *eap) cstack->cs_flags[cstack->cs_idx] = eap->cmdidx == CMD_while ? CSF_WHILE : CSF_FOR; - /* - * Don't do something after an error, interrupt, or throw, or when - * there is a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; if (eap->cmdidx == CMD_while) { /* * ":while bool-expr" @@ -1232,8 +1217,6 @@ void do_throw(struct condstack *cstack) cstack->cs_flags[idx] &= ~CSF_ACTIVE; cstack->cs_exception[idx] = current_exception; } - - did_throw = TRUE; } /* @@ -1252,15 +1235,7 @@ void ex_try(exarg_T *eap) cstack->cs_flags[cstack->cs_idx] = CSF_TRY; cstack->cs_pending[cstack->cs_idx] = CSTP_NONE; - /* - * Don't do something after an error, interrupt, or throw, or when there - * is a surrounding conditional and it was not active. - */ - skip = did_emsg || got_int || did_throw || (cstack->cs_idx > 0 - && !(cstack->cs_flags[cstack-> - cs_idx - - 1] & - CSF_ACTIVE)); + skip = CHECK_SKIP; if (!skip) { /* Set ACTIVE and TRUE. TRUE means that the corresponding ":catch" @@ -1352,8 +1327,9 @@ void ex_catch(exarg_T *eap) * corresponding try block never got active (because of an inactive * surrounding conditional or after an error or interrupt or throw). */ - if (!did_throw || !(cstack->cs_flags[idx] & CSF_TRUE)) - skip = TRUE; + if (!current_exception || !(cstack->cs_flags[idx] & CSF_TRUE)) { + skip = true; + } /* * Check for a match only if an exception is thrown but not caught by @@ -1412,18 +1388,23 @@ void ex_catch(exarg_T *eap) } if (caught) { - /* Make this ":catch" clause active and reset did_emsg, got_int, - * and did_throw. Put the exception on the caught stack. */ + /* Make this ":catch" clause active and reset did_emsg and got_int. + * Put the exception on the caught stack. */ cstack->cs_flags[idx] |= CSF_ACTIVE | CSF_CAUGHT; - did_emsg = got_int = did_throw = FALSE; + did_emsg = got_int = false; catch_exception((except_T *)cstack->cs_exception[idx]); /* It's mandatory that the current exception is stored in the cstack * so that it can be discarded at the next ":catch", ":finally", or * ":endtry" or when the catch clause is left by a ":continue", * ":break", ":return", ":finish", error, interrupt, or another * exception. */ - if (cstack->cs_exception[cstack->cs_idx] != current_exception) - EMSG(_(e_internal)); + if (cstack->cs_exception[cstack->cs_idx] != current_exception) { + internal_error("ex_catch()"); + } + // Discarding current_exceptions happens based on what is stored in + // cstack->cs_exception, *all* calls to discard_current_exception() are + // (and must be) guarded by current_exception check. + current_exception = NULL; } else { /* * If there is a preceding catch clause and it caught the exception, @@ -1482,7 +1463,7 @@ void ex_finally(exarg_T *eap) * interrupt or throw) or for a ":finally" without ":try" or a multiple * ":finally". After every other error (did_emsg or the conditional * errors detected above) or after an interrupt (got_int) or an - * exception (did_throw), the finally clause must be executed. + * exception (current_exception), the finally clause must be executed. */ skip = !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE); @@ -1509,30 +1490,31 @@ void ex_finally(exarg_T *eap) cleanup_conditionals(cstack, CSF_TRY, FALSE); /* - * Make did_emsg, got_int, did_throw pending. If set, they overrule - * a pending ":continue", ":break", ":return", or ":finish". Then - * we have particularly to discard a pending return value (as done + * Make did_emsg, got_int, current_exception pending. If set, they + * overrule a pending ":continue", ":break", ":return", or ":finish". + * Then we have particularly to discard a pending return value (as done * by the call to cleanup_conditionals() above when did_emsg or * got_int is set). The pending values are restored by the * ":endtry", except if there is a new error, interrupt, exception, * ":continue", ":break", ":return", or ":finish" in the following * finally clause. A missing ":endwhile", ":endfor" or ":endif" - * detected here is treated as if did_emsg and did_throw had + * detected here is treated as if did_emsg and current_exception had * already been set, respectively in case that the error is not - * converted to an exception, did_throw had already been unset. + * converted to an exception, current_exception had already been unset. * We must not set did_emsg here since that would suppress the * error message. */ - if (pending == CSTP_ERROR || did_emsg || got_int || did_throw) { + if (pending == CSTP_ERROR || did_emsg || got_int || current_exception) { if (cstack->cs_pending[cstack->cs_idx] == CSTP_RETURN) { report_discard_pending(CSTP_RETURN, cstack->cs_rettv[cstack->cs_idx]); discard_pending_return(cstack->cs_rettv[cstack->cs_idx]); } - if (pending == CSTP_ERROR && !did_emsg) - pending |= (THROW_ON_ERROR) ? CSTP_THROW : 0; - else - pending |= did_throw ? CSTP_THROW : 0; + if (pending == CSTP_ERROR && !did_emsg) { + pending |= (THROW_ON_ERROR ? CSTP_THROW : 0); + } else { + pending |= (current_exception ? CSTP_THROW : 0); + } pending |= did_emsg ? CSTP_ERROR : 0; pending |= got_int ? CSTP_INTERRUPT : 0; assert(pending >= CHAR_MIN && pending <= CHAR_MAX); @@ -1545,14 +1527,15 @@ void ex_finally(exarg_T *eap) * exception. When emsg() is called for a missing ":endif" or * a missing ":endwhile"/":endfor" detected here, the * exception will be discarded. */ - if (did_throw && cstack->cs_exception[cstack->cs_idx] - != current_exception) - EMSG(_(e_internal)); + if (current_exception + && cstack->cs_exception[cstack->cs_idx] != current_exception) { + internal_error("ex_finally()"); + } } /* * Set CSL_HAD_FINA, so do_cmdline() will reset did_emsg, - * got_int, and did_throw and make the finally clause active. + * got_int, and current_exception and make the finally clause active. * This will happen after emsg() has been called for a missing * ":endif" or a missing ":endwhile"/":endfor" detected here, so * that the following finally clause will be executed even then. @@ -1587,7 +1570,7 @@ void ex_endtry(exarg_T *eap) // made inactive by a ":continue", ":break", ":return", or ":finish" in // the finally clause. The latter case need not be tested since then // anything pending has already been discarded. - skip = (did_emsg || got_int || did_throw + skip = (did_emsg || got_int || current_exception || !(cstack->cs_flags[cstack->cs_idx] & CSF_TRUE)); if (!(cstack->cs_flags[cstack->cs_idx] & CSF_TRY)) { @@ -1604,12 +1587,13 @@ void ex_endtry(exarg_T *eap) /* * If an exception is being thrown, discard it to prevent it from * being rethrown at the end of this function. It would be - * discarded by the error message, anyway. Resets did_throw. + * discarded by the error message, anyway. Resets current_exception. * This does not affect the script termination due to the error * since "trylevel" is decremented after emsg() has been called. */ - if (did_throw) + if (current_exception) { discard_current_exception(); + } } else { idx = cstack->cs_idx; @@ -1619,9 +1603,11 @@ void ex_endtry(exarg_T *eap) * a finally clause, we need to rethrow it after closing the try * conditional. */ - if (did_throw && (cstack->cs_flags[idx] & CSF_TRUE) - && !(cstack->cs_flags[idx] & CSF_FINALLY)) - rethrow = TRUE; + if (current_exception + && (cstack->cs_flags[idx] & CSF_TRUE) + && !(cstack->cs_flags[idx] & CSF_FINALLY)) { + rethrow = true; + } } /* If there was no finally clause, show the user when debugging or @@ -1642,11 +1628,12 @@ void ex_endtry(exarg_T *eap) if (got_int) { skip = TRUE; (void)do_intthrow(cstack); - /* The do_intthrow() call may have reset did_throw or - * cstack->cs_pending[idx].*/ - rethrow = FALSE; - if (did_throw && !(cstack->cs_flags[idx] & CSF_FINALLY)) - rethrow = TRUE; + // The do_intthrow() call may have reset current_exception or + // cstack->cs_pending[idx]. + rethrow = false; + if (current_exception && !(cstack->cs_flags[idx] & CSF_FINALLY)) { + rethrow = true; + } } } @@ -1708,26 +1695,30 @@ void ex_endtry(exarg_T *eap) do_finish(eap, FALSE); break; - /* When the finally clause was entered due to an error, - * interrupt or throw (as opposed to a ":continue", ":break", - * ":return", or ":finish"), restore the pending values of - * did_emsg, got_int, and did_throw. This is skipped, if there - * was a new error, interrupt, throw, ":continue", ":break", - * ":return", or ":finish". in the finally clause. */ + // When the finally clause was entered due to an error, + // interrupt or throw (as opposed to a ":continue", ":break", + // ":return", or ":finish"), restore the pending values of + // did_emsg, got_int, and current_exception. This is skipped, if there + // was a new error, interrupt, throw, ":continue", ":break", + // ":return", or ":finish". in the finally clause. default: - if (pending & CSTP_ERROR) - did_emsg = TRUE; - if (pending & CSTP_INTERRUPT) - got_int = TRUE; - if (pending & CSTP_THROW) - rethrow = TRUE; + if (pending & CSTP_ERROR) { + did_emsg = true; + } + if (pending & CSTP_INTERRUPT) { + got_int = true; + } + if (pending & CSTP_THROW) { + rethrow = true; + } break; } } - if (rethrow) - /* Rethrow the current exception (within this cstack). */ + if (rethrow) { + // Rethrow the current exception (within this cstack). do_throw(cstack); + } } } @@ -1757,33 +1748,34 @@ void enter_cleanup(cleanup_T *csp) int pending = CSTP_NONE; /* - * Postpone did_emsg, got_int, did_throw. The pending values will be + * Postpone did_emsg, got_int, current_exception. The pending values will be * restored by leave_cleanup() except if there was an aborting error, * interrupt, or uncaught exception after this function ends. */ - if (did_emsg || got_int || did_throw || need_rethrow) { - csp->pending = (did_emsg ? CSTP_ERROR : 0) - | (got_int ? CSTP_INTERRUPT : 0) - | (did_throw ? CSTP_THROW : 0) - | (need_rethrow ? CSTP_THROW : 0); - - /* If we are currently throwing an exception (did_throw), save it as - * well. On an error not yet converted to an exception, update - * "force_abort" and reset "cause_abort" (as do_errthrow() would do). - * This is needed for the do_cmdline() call that is going to be made - * for autocommand execution. We need not save *msg_list because - * there is an extra instance for every call of do_cmdline(), anyway. + if (did_emsg || got_int || current_exception || need_rethrow) { + csp->pending = (did_emsg ? CSTP_ERROR : 0) + | (got_int ? CSTP_INTERRUPT : 0) + | (current_exception ? CSTP_THROW : 0) + | (need_rethrow ? CSTP_THROW : 0); + + /* If we are currently throwing an exception, save it as well. On an error + * not yet converted to an exception, update "force_abort" and reset + * "cause_abort" (as do_errthrow() would do). This is needed for the + * do_cmdline() call that is going to be made for autocommand execution. We + * need not save *msg_list because there is an extra instance for every call + * of do_cmdline(), anyway. */ - if (did_throw || need_rethrow) + if (current_exception || need_rethrow) { csp->exception = current_exception; - else { + } else { csp->exception = NULL; if (did_emsg) { force_abort |= cause_abort; cause_abort = FALSE; } } - did_emsg = got_int = did_throw = need_rethrow = FALSE; + did_emsg = got_int = need_rethrow = false; + current_exception = NULL; /* Report if required by the 'verbose' option or when debugging. */ report_make_pending(pending, csp->exception); @@ -1855,19 +1847,20 @@ void leave_cleanup(cleanup_T *csp) force_abort = FALSE; } - /* - * Restore the pending values of did_emsg, got_int, and did_throw. - */ - if (pending & CSTP_ERROR) - did_emsg = TRUE; - if (pending & CSTP_INTERRUPT) - got_int = TRUE; - if (pending & CSTP_THROW) - need_rethrow = TRUE; /* did_throw will be set by do_one_cmd() */ + // Restore the pending values of did_emsg, got_int, and current_exception. + if (pending & CSTP_ERROR) { + did_emsg = true; + } + if (pending & CSTP_INTERRUPT) { + got_int = true; + } + if (pending & CSTP_THROW) { + need_rethrow = true; // current_exception will be set by do_one_cmd() + } - /* Report if required by the 'verbose' option or when debugging. */ - report_resume_pending(pending, - (pending & CSTP_THROW) ? (void *)current_exception : NULL); + // Report if required by the 'verbose' option or when debugging. + report_resume_pending( + pending, ((pending & CSTP_THROW) ? (void *)current_exception : NULL)); } } diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c index c1500e3121..698419405a 100644 --- a/src/nvim/ex_getln.c +++ b/src/nvim/ex_getln.c @@ -66,6 +66,8 @@ #include "nvim/lib/kvec.h" #include "nvim/api/private/helpers.h" #include "nvim/highlight_defs.h" +#include "nvim/viml/parser/parser.h" +#include "nvim/viml/parser/expressions.h" /// Command-line colors: one chunk /// @@ -192,7 +194,8 @@ static int cmd_showtail; /* Only show path tail in lists ? */ static int new_cmdpos; /* position set by set_cmdline_pos() */ -static Array cmdline_block; ///< currently displayed block of context +/// currently displayed block of context +static Array cmdline_block = ARRAY_DICT_INIT; /* * Type used by call_user_expand_func @@ -424,7 +427,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) curwin->w_botline = s->old_botline; highlight_match = false; validate_cursor(); // needed for TAB - redraw_later(SOME_VALID); + redraw_all_later(SOME_VALID); } if (ccline.cmdbuff != NULL) { @@ -441,14 +444,8 @@ static uint8_t *command_line_enter(int firstc, long count, int indent) } } - if (s->gotesc) { // abandon command line - xfree(ccline.cmdbuff); - ccline.cmdbuff = NULL; - if (msg_scrolled == 0) { - compute_cmdrow(); - } - MSG(""); - redraw_cmdline = true; + if (s->gotesc) { + abandon_cmdline(); } } @@ -515,8 +512,12 @@ static int command_line_execute(VimState *state, int key) CommandLineState *s = (CommandLineState *)state; s->c = key; - if (s->c == K_EVENT) { - multiqueue_process_events(main_loop.events); + if (s->c == K_EVENT || s->c == K_COMMAND) { + if (s->c == K_EVENT) { + multiqueue_process_events(main_loop.events); + } else { + do_cmdline(NULL, getcmdkeycmd, NULL, DOCMD_NOWAIT); + } redrawcmdline(); return 1; } @@ -736,7 +737,7 @@ static int command_line_execute(VimState *state, int key) } if (vim_ispathsep(ccline.cmdbuff[s->j]) #ifdef BACKSLASH_IN_FILENAME - && vim_strchr(" *?[{`$%#", ccline.cmdbuff[s->j + 1]) + && vim_strchr((const char_u *)" *?[{`$%#", ccline.cmdbuff[s->j + 1]) == NULL #endif ) { @@ -1017,17 +1018,36 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) ui_flush(); pos_T t; - int search_flags = SEARCH_KEEP + SEARCH_NOOF + SEARCH_PEEK; + char_u *pat; + int search_flags = SEARCH_NOOF; + + + if (s->firstc == ccline.cmdbuff[0]) { + pat = last_search_pattern(); + } else { + pat = ccline.cmdbuff; + } + + save_last_search_pattern(); + if (next_match) { t = s->match_end; + if (lt(s->match_start, s->match_end)) { + // start searching at the end of the match + // not at the beginning of the next column + (void)decl(&t); + } search_flags += SEARCH_COL; } else { t = s->match_start; } + if (!p_hls) { + search_flags += SEARCH_KEEP; + } emsg_off++; s->i = searchit(curwin, curbuf, &t, next_match ? FORWARD : BACKWARD, - ccline.cmdbuff, s->count, search_flags, + pat, s->count, search_flags, RE_SEARCH, 0, NULL); emsg_off--; ui_busy_stop(); @@ -1041,6 +1061,12 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) // put back on the match s->search_start = t; (void)decl(&s->search_start); + } else if (next_match && s->firstc == '?') { + // move just after the current match, so that + // when nv_search finishes the cursor will be + // put back on the match + s->search_start = t; + (void)incl(&s->search_start); } if (lt(t, s->search_start) && next_match) { // wrap around @@ -1068,6 +1094,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match) } else { vim_beep(BO_ERROR); } + restore_last_search_pattern(); return; } @@ -1527,7 +1554,7 @@ static int command_line_handle_key(CommandLineState *s) } if (s->c != NUL) { if (s->c == s->firstc - || vim_strchr((char_u *)(p_magic ? "\\^$.*[" : "\\^$"), s->c) + || vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), s->c) != NULL) { // put a backslash before special characters stuffcharReadbuff(s->c); @@ -1651,10 +1678,9 @@ static int command_line_handle_key(CommandLineState *s) case Ctrl_G: // next match case Ctrl_T: // previous match if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) { - if (char_avail()) { - return 1; + if (ccline.cmdlen != 0) { + command_line_next_incsearch(s, s->c == Ctrl_G); } - command_line_next_incsearch(s, s->c == Ctrl_G); return command_line_not_changed(s); } break; @@ -1757,6 +1783,20 @@ static int command_line_not_changed(CommandLineState *s) return command_line_changed(s); } +/// Guess that the pattern matches everything. Only finds specific cases, such +/// as a trailing \|, which can happen while typing a pattern. +static int empty_pattern(char_u *p) +{ + int n = STRLEN(p); + + // remove trailing \v and the like + while (n >= 2 && p[n - 2] == '\\' + && vim_strchr((char_u *)"mMvVcCZ", p[n - 1]) != NULL) { + n -= 2; + } + return n == 0 || (n >= 2 && p[n - 2] == '\\' && p[n - 1] == '|'); +} + static int command_line_changed(CommandLineState *s) { // 'incsearch' highlighting. @@ -1771,20 +1811,27 @@ 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(); // If there is no command line, don't do anything if (ccline.cmdlen == 0) { s->i = 0; + SET_NO_HLSEARCH(true); // turn off previous highlight + redraw_all_later(SOME_VALID); } else { + int search_flags = SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK; ui_busy_start(); ui_flush(); ++emsg_off; // So it doesn't beep if bad expr // Set the time limit to half a second. tm = profile_setlimit(500L); + if (!p_hls) { + search_flags += SEARCH_KEEP; + } s->i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count, - SEARCH_KEEP + SEARCH_OPT + SEARCH_NOOF + SEARCH_PEEK, - &tm); - --emsg_off; + search_flags, + &tm); + emsg_off--; // if interrupted while searching, behave like it failed if (got_int) { (void)vpeekc(); // remove <C-C> from input stream @@ -1825,6 +1872,12 @@ static int command_line_changed(CommandLineState *s) end_pos = curwin->w_cursor; // shutup gcc 4 } + // Disable 'hlsearch' highlighting if the pattern matches + // everything. Avoids a flash when typing "foo\|". + if (empty_pattern(ccline.cmdbuff)) { + SET_NO_HLSEARCH(true); + } + validate_cursor(); // May redraw the status line to show the cursor position. if (p_ru && curwin->w_status_height > 0) { @@ -1834,6 +1887,7 @@ static int command_line_changed(CommandLineState *s) save_cmdline(&s->save_ccline); update_screen(SOME_VALID); restore_cmdline(&s->save_ccline); + restore_last_search_pattern(); // Leave it at the end to make CTRL-R CTRL-W work. if (s->i != 0) { @@ -1889,6 +1943,18 @@ static int command_line_changed(CommandLineState *s) return 1; } +/// Abandon the command line. +static void abandon_cmdline(void) +{ + xfree(ccline.cmdbuff); + ccline.cmdbuff = NULL; + if (msg_scrolled == 0) { + compute_cmdrow(); + } + MSG(""); + redraw_cmdline = true; +} + /* * getcmdline() - accept a command line starting with firstc. * @@ -2162,7 +2228,13 @@ getexmodeline ( /* Get one character at a time. Don't use inchar(), it can't handle * special characters. */ prev_char = c1; - c1 = vgetc(); + + // Check for a ":normal" command and no more characters left. + if (ex_normal_busy > 0 && typebuf.tb_len == 0) { + c1 = '\n'; + } else { + c1 = vgetc(); + } /* * Handle line editing. @@ -2428,6 +2500,63 @@ void free_cmdline_buf(void) enum { MAX_CB_ERRORS = 1 }; +/// Color expression cmdline using built-in expressions parser +/// +/// @param[in] colored_ccline Command-line to color. +/// @param[out] ret_ccline_colors What should be colored. +/// +/// Always colors the whole cmdline. +static void color_expr_cmdline(const CmdlineInfo *const colored_ccline, + ColoredCmdline *const ret_ccline_colors) + FUNC_ATTR_NONNULL_ALL +{ + ParserLine plines[] = { + { + .data = (const char *)colored_ccline->cmdbuff, + .size = STRLEN(colored_ccline->cmdbuff), + .allocated = false, + }, + { NULL, 0, false }, + }; + ParserLine *plines_p = plines; + ParserHighlight colors; + kvi_init(colors); + ParserState pstate; + viml_parser_init( + &pstate, parser_simple_get_line, &plines_p, &colors); + ExprAST east = viml_pexpr_parse(&pstate, kExprFlagsDisallowEOC); + viml_pexpr_free_ast(east); + viml_parser_destroy(&pstate); + kv_resize(ret_ccline_colors->colors, kv_size(colors)); + size_t prev_end = 0; + for (size_t i = 0 ; i < kv_size(colors) ; i++) { + const ParserHighlightChunk chunk = kv_A(colors, i); + if (chunk.start.col != prev_end) { + kv_push(ret_ccline_colors->colors, ((CmdlineColorChunk) { + .start = prev_end, + .end = 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, + .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, + .attr = 0, + })); + } + kvi_destroy(colors); +} + /// Color command-line /// /// Should use built-in command parser or user-specified one. Currently only the @@ -2510,13 +2639,7 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) tl_ret = try_leave(&tstate, &err); can_free_cb = true; } else if (colored_ccline->cmdfirstc == '=') { - try_enter(&tstate); - err_errmsg = N_( - "E5409: Unable to get g:Nvim_color_expr callback: %s"); - dgc_ret = tv_dict_get_callback(&globvardict, S_LEN("Nvim_color_expr"), - &color_cb); - tl_ret = try_leave(&tstate, &err); - can_free_cb = true; + color_expr_cmdline(colored_ccline, ccline_colors); } if (!tl_ret || !dgc_ret) { goto color_cmdline_error; @@ -2565,20 +2688,20 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) } varnumber_T prev_end = 0; int i = 0; - for (const listitem_T *li = tv.vval.v_list->lv_first; - li != NULL; li = li->li_next, i++) { - if (li->li_tv.v_type != VAR_LIST) { + TV_LIST_ITER_CONST(tv.vval.v_list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_LIST) { PRINT_ERRMSG(_("E5401: List item %i is not a List"), i); goto color_cmdline_error; } - const list_T *const l = li->li_tv.vval.v_list; + const list_T *const l = TV_LIST_ITEM_TV(li)->vval.v_list; if (tv_list_len(l) != 3) { PRINT_ERRMSG(_("E5402: List item %i has incorrect length: %li /= 3"), i, tv_list_len(l)); goto color_cmdline_error; } bool error = false; - const varnumber_T start = tv_get_number_chk(&l->lv_first->li_tv, &error); + const varnumber_T start = ( + tv_get_number_chk(TV_LIST_ITEM_TV(tv_list_first(l)), &error)); if (error) { goto color_cmdline_error; } else if (!(prev_end <= start && start < colored_ccline->cmdlen)) { @@ -2598,8 +2721,8 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) .attr = 0, })); } - const varnumber_T end = tv_get_number_chk(&l->lv_first->li_next->li_tv, - &error); + const varnumber_T end = tv_get_number_chk( + TV_LIST_ITEM_TV(TV_LIST_ITEM_NEXT(l, tv_list_first(l))), &error); if (error) { goto color_cmdline_error; } else if (!(start < end && end <= colored_ccline->cmdlen)) { @@ -2615,7 +2738,8 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) goto color_cmdline_error; } prev_end = end; - const char *const group = tv_get_string_chk(&l->lv_last->li_tv); + const char *const group = tv_get_string_chk( + TV_LIST_ITEM_TV(tv_list_last(l))); if (group == NULL) { goto color_cmdline_error; } @@ -2626,7 +2750,8 @@ static bool color_cmdline(CmdlineInfo *colored_ccline) .end = end, .attr = attr, })); - } + i++; + }); if (prev_end < colored_ccline->cmdlen) { kv_push(ccline_colors->colors, ((CmdlineColorChunk) { .start = prev_end, @@ -2818,11 +2943,11 @@ static void ui_ext_cmdline_show(CmdlineInfo *line) Array item = ARRAY_DICT_INIT; if (chunk.attr) { - attrentry_T *aep = syn_cterm_attr2entry(chunk.attr); + HlAttrs *aep = syn_cterm_attr2entry(chunk.attr); // TODO(bfredl): this desicion could be delayed by making attr_code a // recognized type - HlAttrs rgb_attrs = attrentry2hlattrs(aep, true); - ADD(item, DICTIONARY_OBJ(hlattrs2dict(rgb_attrs))); + Dictionary rgb_attrs = hlattrs2dict(aep, true); + ADD(item, DICTIONARY_OBJ(rgb_attrs)); } else { ADD(item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT)); } @@ -2870,6 +2995,7 @@ void ui_ext_cmdline_block_append(int indent, const char *line) void ui_ext_cmdline_block_leave(void) { api_free_array(cmdline_block); + cmdline_block = (Array)ARRAY_DICT_INIT; ui_call_cmdline_block_hide(); } @@ -3455,8 +3581,10 @@ nextwild ( return FAIL; } - MSG_PUTS("..."); /* show that we are busy */ - ui_flush(); + if (!ui_is_external(kUIWildmenu)) { + MSG_PUTS("..."); // show that we are busy + ui_flush(); + } i = (int)(xp->xp_pattern - ccline.cmdbuff); xp->xp_pattern_len = ccline.cmdpos - i; @@ -3995,12 +4123,12 @@ static int showmatches(expand_T *xp, int wildmenu) msg_start(); /* prepare for paging */ } - if (got_int) - got_int = FALSE; /* only int. the completion, not the cmd line */ - else if (wildmenu) - win_redr_status_matches(xp, num_files, files_found, 0, showtail); - else { - /* find the length of the longest file name */ + if (got_int) { + got_int = false; // only int. the completion, not the cmd line + } else if (wildmenu) { + win_redr_status_matches(xp, num_files, files_found, -1, showtail); + } else { + // find the length of the longest file name maxlen = 0; for (i = 0; i < num_files; ++i) { if (!showtail && (xp->xp_context == EXPAND_FILES @@ -4194,20 +4322,20 @@ addstar ( * use with vim_regcomp(). First work out how long it will be: */ - /* For help tags the translation is done in find_help_tags(). - * For a tag pattern starting with "/" no translation is needed. */ + // For help tags the translation is done in find_help_tags(). + // For a tag pattern starting with "/" no translation is needed. if (context == EXPAND_HELP + || context == EXPAND_CHECKHEALTH || context == EXPAND_COLORS || context == EXPAND_COMPILER || context == EXPAND_OWNSYNTAX || context == EXPAND_FILETYPE || context == EXPAND_PACKADD - || ((context == EXPAND_TAGS_LISTFILES - || context == EXPAND_TAGS) - && fname[0] == '/')) + || ((context == EXPAND_TAGS_LISTFILES || context == EXPAND_TAGS) + && fname[0] == '/')) { retval = vim_strnsave(fname, len); - else { - new_len = len + 2; /* +2 for '^' at start, NUL at end */ + } else { + new_len = len + 2; // +2 for '^' at start, NUL at end for (i = 0; i < len; i++) { if (fname[i] == '*' || fname[i] == '~') new_len++; /* '*' needs to be replaced by ".*" @@ -4604,6 +4732,10 @@ ExpandFromContext ( char *directories[] = { "syntax", "indent", "ftplugin", NULL }; return ExpandRTDir(pat, 0, num_file, file, directories); } + if (xp->xp_context == EXPAND_CHECKHEALTH) { + char *directories[] = { "autoload/health", NULL }; + return ExpandRTDir(pat, 0, num_file, file, directories); + } if (xp->xp_context == EXPAND_USER_LIST) { return ExpandUserList(xp, num_file, file); } @@ -4795,13 +4927,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_path(pat)) + // 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]))))) + } else if (pat[0] == '.' && (vim_ispathsep(pat[1]) + || (pat[1] == '.' + && vim_ispathsep(pat[2])))) { path = (char_u *)"."; - else { + } else { path = (char_u *)vim_getenv("PATH"); if (path == NULL) { path = (char_u *)""; @@ -4966,24 +5099,24 @@ static int ExpandUserDefined(expand_T *xp, regmatch_T *regmatch, int *num_file, */ static int ExpandUserList(expand_T *xp, int *num_file, char_u ***file) { - list_T *retlist; - listitem_T *li; - garray_T ga; - - retlist = call_user_expand_func((user_expand_func_T)call_func_retlist, xp, - num_file, file); + list_T *const retlist = call_user_expand_func( + (user_expand_func_T)call_func_retlist, xp, num_file, file); if (retlist == NULL) { return FAIL; } + garray_T ga; ga_init(&ga, (int)sizeof(char *), 3); - /* Loop over the items in the list. */ - for (li = retlist->lv_first; li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING || li->li_tv.vval.v_string == NULL) - continue; /* Skip non-string items and empty strings */ + // Loop over the items in the list. + TV_LIST_ITER_CONST(retlist, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING + || TV_LIST_ITEM_TV(li)->vval.v_string == NULL) { + continue; // Skip non-string items and empty strings. + } - GA_APPEND(char_u *, &ga, vim_strsave(li->li_tv.vval.v_string)); - } + GA_APPEND(char *, &ga, xstrdup( + (const char *)TV_LIST_ITEM_TV(li)->vval.v_string)); + }); tv_list_unref(retlist); *file = ga.ga_data; diff --git a/src/nvim/farsi.c b/src/nvim/farsi.c index 1053cb3ac2..5801a2d8fb 100644 --- a/src/nvim/farsi.c +++ b/src/nvim/farsi.c @@ -596,78 +596,83 @@ static void chg_r_to_Xor_X_(void) int fkmap(int c) { int tempc; - static int revins; + int insert_mode = (State & INSERT); + static int revins = 0; if (IS_SPECIAL(c)) { return c; } - if (ascii_isdigit(c) - || ((c == '.' - || c == '+' - || c == '-' - || c == '^' - || c == '%' - || c == '#' - || c == '=') - && revins)) { - if (!revins) { - if (curwin->w_cursor.col) { - if (!p_ri) { - dec_cursor(); - } + if (insert_mode) { + if (ascii_isdigit(c) + || ((c == '.' + || c == '+' + || c == '-' + || c == '^' + || c == '%' + || c == '#' + || c == '=') + && revins)) { + // Numbers are entered left-to-right. + if (!revins) { + if (curwin->w_cursor.col) { + if (!p_ri) { + dec_cursor(); + } - chg_c_toX_orX(); - chg_l_toXor_X(); - if (!p_ri) { - inc_cursor(); + chg_c_toX_orX(); + chg_l_toXor_X(); + if (!p_ri) { + inc_cursor(); + } } } - } - arrow_used = TRUE; - (void)stop_arrow(); + arrow_used = true; + (void)stop_arrow(); - if (!curwin->w_p_rl && revins) { - inc_cursor(); - } + if (!curwin->w_p_rl && revins) { + inc_cursor(); + } - revins++; - p_ri = 1; - } else { - if (revins) { - arrow_used = TRUE; - (void)stop_arrow(); + revins++; + p_ri = 1; + } else { + if (revins) { + // Stop entering number. + arrow_used = true; + (void)stop_arrow(); - revins = 0; - if (curwin->w_p_rl) { - while ((F_isdigit(gchar_cursor()) - || (gchar_cursor() == F_PERIOD - || gchar_cursor() == F_PLUS - || gchar_cursor() == F_MINUS - || gchar_cursor() == F_MUL - || gchar_cursor() == F_DIVIDE - || gchar_cursor() == F_PERCENT - || gchar_cursor() == F_EQUALS)) - && gchar_cursor() != NUL) { - curwin->w_cursor.col++; - } - } else { - if (curwin->w_cursor.col) { + revins = 0; + if (curwin->w_p_rl) { while ((F_isdigit(gchar_cursor()) - || (gchar_cursor() == F_PERIOD - || gchar_cursor() == F_PLUS - || gchar_cursor() == F_MINUS - || gchar_cursor() == F_MUL - || gchar_cursor() == F_DIVIDE - || gchar_cursor() == F_PERCENT - || gchar_cursor() == F_EQUALS)) - && --curwin->w_cursor.col) { + || (gchar_cursor() == F_PERIOD + || gchar_cursor() == F_PLUS + || gchar_cursor() == F_MINUS + || gchar_cursor() == F_MUL + || gchar_cursor() == F_DIVIDE + || gchar_cursor() == F_PERCENT + || gchar_cursor() == F_EQUALS)) + && gchar_cursor() != NUL) { + curwin->w_cursor.col++; + } + } else { + if (curwin->w_cursor.col) { + while ((F_isdigit(gchar_cursor()) + || (gchar_cursor() == F_PERIOD + || gchar_cursor() == F_PLUS + || gchar_cursor() == F_MINUS + || gchar_cursor() == F_MUL + || gchar_cursor() == F_DIVIDE + || gchar_cursor() == F_PERCENT + || gchar_cursor() == F_EQUALS)) + && --curwin->w_cursor.col) { + } } - } - if (!F_isdigit(gchar_cursor())) { - ++curwin->w_cursor.col; + if (!F_isdigit(gchar_cursor())) { + curwin->w_cursor.col++; + } } } } @@ -755,7 +760,7 @@ int fkmap(int c) case 'Y': case NL: case TAB: - if (p_ri && (c == NL) && curwin->w_cursor.col) { + if (p_ri && (c == NL) && curwin->w_cursor.col && insert_mode) { // If the char before the cursor is _X_ or X_ do not change // the one under the cursor with X type. @@ -826,135 +831,137 @@ int fkmap(int c) } } - if (!p_ri) { - dec_cursor(); - } + if (insert_mode) { + if (!p_ri) { + dec_cursor(); + } - switch ((tempc = gchar_cursor())) { - case _BE: - case _PE: - case _TE: - case _SE: - case _JIM: - case _CHE: - case _HE_J: - case _XE: - case _SIN: - case _SHIN: - case _SAD: - case _ZAD: - case _FE: - case _GHAF: - case _KAF: - case _KAF_H: - case _GAF: - case _LAM: - case _MIM: - case _NOON: - case _HE: - case _HE_: - case _TA: - case _ZA: - put_curr_and_l_to_X(toF_TyA((char_u)tempc)); - break; + switch ((tempc = gchar_cursor())) { + case _BE: + case _PE: + case _TE: + case _SE: + case _JIM: + case _CHE: + case _HE_J: + case _XE: + case _SIN: + case _SHIN: + case _SAD: + case _ZAD: + case _FE: + case _GHAF: + case _KAF: + case _KAF_H: + case _GAF: + case _LAM: + case _MIM: + case _NOON: + case _HE: + case _HE_: + case _TA: + case _ZA: + put_curr_and_l_to_X(toF_TyA((char_u)tempc)); + break; - case _AYN: - case _AYN_: - if (!p_ri) { - if (!curwin->w_cursor.col) { - put_curr_and_l_to_X(AYN); - break; + case _AYN: + case _AYN_: + if (!p_ri) { + if (!curwin->w_cursor.col) { + put_curr_and_l_to_X(AYN); + break; + } } - } - if (p_ri) { - inc_cursor(); - } else { - dec_cursor(); - } + if (p_ri) { + inc_cursor(); + } else { + dec_cursor(); + } - if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) { - tempc = AYN_; - } else { - tempc = AYN; - } + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) { + tempc = AYN_; + } else { + tempc = AYN; + } - if (p_ri) { - dec_cursor(); - } else { - inc_cursor(); - } + if (p_ri) { + dec_cursor(); + } else { + inc_cursor(); + } - put_curr_and_l_to_X((char_u)tempc); - break; + put_curr_and_l_to_X((char_u)tempc); + break; - case _GHAYN: - case _GHAYN_: + case _GHAYN: + case _GHAYN_: - if (!p_ri) { - if (!curwin->w_cursor.col) { - put_curr_and_l_to_X(GHAYN); - break; + if (!p_ri) { + if (!curwin->w_cursor.col) { + put_curr_and_l_to_X(GHAYN); + break; + } } - } - if (p_ri) { - inc_cursor(); - } else { - dec_cursor(); - } + if (p_ri) { + inc_cursor(); + } else { + dec_cursor(); + } - if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) { - tempc = GHAYN_; - } else { - tempc = GHAYN; - } + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) { + tempc = GHAYN_; + } else { + tempc = GHAYN; + } - if (p_ri) { - dec_cursor(); - } else { - inc_cursor(); - } + if (p_ri) { + dec_cursor(); + } else { + inc_cursor(); + } - put_curr_and_l_to_X((char_u)tempc); - break; + put_curr_and_l_to_X((char_u)tempc); + break; - case _YE: - case _IE: - case _YEE: + case _YE: + case _IE: + case _YEE: - if (!p_ri) { - if (!curwin->w_cursor.col) { - put_curr_and_l_to_X( - (tempc == _YE ? YE : tempc == _IE ? IE : YEE)); - break; + if (!p_ri) { + if (!curwin->w_cursor.col) { + put_curr_and_l_to_X( + (tempc == _YE ? YE : tempc == _IE ? IE : YEE)); + break; + } } - } - if (p_ri) { - inc_cursor(); - } else { - dec_cursor(); - } + if (p_ri) { + inc_cursor(); + } else { + dec_cursor(); + } - if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) { - tempc = (tempc == _YE ? YE_ : tempc == _IE ? IE_ : YEE_); - } else { - tempc = (tempc == _YE ? YE : tempc == _IE ? IE : YEE); - } + if (F_is_TyB_TyC_TyD(SRC_EDT, AT_CURSOR)) { + tempc = (tempc == _YE ? YE_ : tempc == _IE ? IE_ : YEE_); + } else { + tempc = (tempc == _YE ? YE : tempc == _IE ? IE : YEE); + } - if (p_ri) { - dec_cursor(); - } else { - inc_cursor(); - } + if (p_ri) { + dec_cursor(); + } else { + inc_cursor(); + } - put_curr_and_l_to_X((char_u)tempc); - break; - } + put_curr_and_l_to_X((char_u)tempc); + break; + } - if (!p_ri) { - inc_cursor(); + if (!p_ri) { + inc_cursor(); + } } tempc = 0; diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c index 8094a1b266..3272ce81bc 100644 --- a/src/nvim/file_search.c +++ b/src/nvim/file_search.c @@ -1560,7 +1560,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope) tv_dict_set_keys_readonly(dict); apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false, - NULL); + curbuf); tv_dict_clear(dict); @@ -1586,7 +1586,7 @@ int vim_chdirfile(char_u *fname) } #ifdef BACKSLASH_IN_FILENAME - slash_adjust(dir); + slash_adjust((char_u *)dir); #endif if (!strequal(dir, (char *)NameBuff)) { do_autocmd_dirchanged(dir, kCdScopeWindow); diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c index 1f4cd22754..25653deb3e 100644 --- a/src/nvim/fileio.c +++ b/src/nvim/fileio.c @@ -1,9 +1,7 @@ // This is an open source non-commercial project. Dear PVS-Studio, please check // it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com -/* - * fileio.c: read from and write to a file - */ +// fileio.c: read from and write to a file #include <assert.h> #include <errno.h> @@ -65,57 +63,62 @@ #define BUFSIZE 8192 /* size of normal write buffer */ #define SMBUFSIZE 256 /* size of emergency write buffer */ -/* - * The autocommands are stored in a list for each event. - * Autocommands for the same pattern, that are consecutive, are joined - * together, to avoid having to match the pattern too often. - * The result is an array of Autopat lists, which point to AutoCmd lists: - * - * first_autopat[0] --> Autopat.next --> Autopat.next --> NULL - * Autopat.cmds Autopat.cmds - * | | - * V V - * AutoCmd.next AutoCmd.next - * | | - * V V - * AutoCmd.next NULL - * | - * V - * NULL - * - * first_autopat[1] --> Autopat.next --> NULL - * Autopat.cmds - * | - * V - * AutoCmd.next - * | - * V - * NULL - * etc. - * - * The order of AutoCmds is important, this is the order in which they were - * defined and will have to be executed. - */ +// +// The autocommands are stored in a list for each event. +// Autocommands for the same pattern, that are consecutive, are joined +// together, to avoid having to match the pattern too often. +// The result is an array of Autopat lists, which point to AutoCmd lists: +// +// last_autopat[0] -----------------------------+ +// V +// first_autopat[0] --> Autopat.next --> Autopat.next --> NULL +// Autopat.cmds Autopat.cmds +// | | +// V V +// AutoCmd.next AutoCmd.next +// | | +// V V +// AutoCmd.next NULL +// | +// V +// NULL +// +// last_autopat[1] --------+ +// V +// first_autopat[1] --> Autopat.next --> NULL +// Autopat.cmds +// | +// V +// AutoCmd.next +// | +// V +// NULL +// etc. +// +// The order of AutoCmds is important, this is the order in which they were +// defined and will have to be executed. +// typedef struct AutoCmd { - char_u *cmd; /* The command to be executed (NULL - when command has been removed) */ - char nested; /* If autocommands nest here */ - char last; /* last command in list */ - scid_T scriptID; /* script ID where defined */ - struct AutoCmd *next; /* Next AutoCmd in list */ + char_u *cmd; // The command to be executed (NULL + // when command has been removed) + char nested; // If autocommands nest here + char last; // last command in list + scid_T scriptID; // script ID where defined + struct AutoCmd *next; // Next AutoCmd in list } AutoCmd; typedef struct AutoPat { - char_u *pat; /* pattern as typed (NULL when pattern - has been removed) */ - regprog_T *reg_prog; /* compiled regprog for pattern */ - AutoCmd *cmds; /* list of commands to do */ - struct AutoPat *next; /* next AutoPat in AutoPat list */ - int group; /* group ID */ - int patlen; /* strlen() of pat */ - int buflocal_nr; /* !=0 for buffer-local AutoPat */ - char allow_dirs; /* Pattern may match whole path */ - char last; /* last pattern for apply_autocmds() */ + struct AutoPat *next; // next AutoPat in AutoPat list; MUST + // be the first entry + char_u *pat; // pattern as typed (NULL when pattern + // has been removed) + regprog_T *reg_prog; // compiled regprog for pattern + AutoCmd *cmds; // list of commands to do + int group; // group ID + int patlen; // strlen() of pat + int buflocal_nr; // !=0 for buffer-local AutoPat + char allow_dirs; // Pattern may match whole path + char last; // last pattern for apply_autocmds() } AutoPat; /* @@ -226,6 +229,15 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr) msg_scrolled_ign = FALSE; } +static AutoPat *last_autopat[NUM_EVENTS] = { + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL +}; + /* * Read lines from file "fname" into the buffer after line "from". * @@ -4318,7 +4330,7 @@ void shorten_fnames(int force) && !path_with_url((char *)buf->b_fname) && (force || buf->b_sfname == NULL - || path_is_absolute_path(buf->b_sfname))) { + || path_is_absolute(buf->b_sfname))) { xfree(buf->b_sfname); buf->b_sfname = NULL; p = path_shorten_fname(buf->b_ffname, dirname); @@ -4440,7 +4452,7 @@ char *modname(const char *fname, const char *ext, bool prepend_dot) /// @param size size of the buffer /// @param fp file to read from /// -/// @return true for end-of-file. +/// @return true for EOF or error bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL { char *retval; @@ -4451,7 +4463,7 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL do { errno = 0; retval = fgets((char *)buf, size, fp); - } while (retval == NULL && errno == EINTR); + } while (retval == NULL && errno == EINTR && ferror(fp)); if (buf[size - 2] != NUL && buf[size - 2] != '\n') { char tbuf[200]; @@ -4463,12 +4475,12 @@ bool vim_fgets(char_u *buf, int size, FILE *fp) FUNC_ATTR_NONNULL_ALL tbuf[sizeof(tbuf) - 2] = NUL; errno = 0; retval = fgets((char *)tbuf, sizeof(tbuf), fp); - if (retval == NULL && errno != EINTR) { + if (retval == NULL && (feof(fp) || errno != EINTR)) { break; } } while (tbuf[sizeof(tbuf) - 2] != NUL && tbuf[sizeof(tbuf) - 2] != '\n'); } - return retval ? false : feof(fp); + return retval == NULL; } /// Read 2 bytes from "fd" and turn them into an int, MSB first. @@ -5088,14 +5100,12 @@ void buf_reload(buf_T *buf, int orig_mode) flags |= READ_KEEP_UNDO; } - /* - * To behave like when a new file is edited (matters for - * BufReadPost autocommands) we first need to delete the current - * buffer contents. But if reading the file fails we should keep - * the old contents. Can't use memory only, the file might be - * too big. Use a hidden buffer to move the buffer contents to. - */ - if (bufempty() || saved == FAIL) { + // To behave like when a new file is edited (matters for + // BufReadPost autocommands) we first need to delete the current + // buffer contents. But if reading the file fails we should keep + // the old contents. Can't use memory only, the file might be + // too big. Use a hidden buffer to move the buffer contents to. + if (BUFEMPTY() || saved == FAIL) { savebuf = NULL; } else { // Allocate a buffer without putting it in the buffer list. @@ -5128,7 +5138,7 @@ void buf_reload(buf_T *buf, int orig_mode) if (savebuf != NULL && bufref_valid(&bufref) && buf == curbuf) { // Put the text back from the save buffer. First // delete any lines that readfile() added. - while (!bufempty()) { + while (!BUFEMPTY()) { if (ml_delete(buf->b_ml.ml_line_count, false) == FAIL) { break; } @@ -5530,6 +5540,15 @@ static void au_cleanup(void) /* remove the pattern if it has been marked for deletion */ if (ap->pat == NULL) { + if (ap->next == NULL) { + if (prev_ap == &(first_autopat[(int)event])) { + last_autopat[(int)event] = NULL; + } else { + // this depends on the "next" field being the first in + // the struct + last_autopat[(int)event] = (AutoPat *)prev_ap; + } + } *prev_ap = ap->next; vim_regfree(ap->reg_prog); xfree(ap); @@ -6122,10 +6141,13 @@ static int do_autocmd_event(event_T event, char_u *pat, int nested, char_u *cmd, patlen = (int)STRLEN(buflocal_pat); /* but not endpat */ } - /* - * Find AutoPat entries with this pattern. - */ - prev_ap = &first_autopat[(int)event]; + // Find AutoPat entries with this pattern. When adding a command it + // always goes at or after the last one, so start at the end. + if (!forceit && *cmd != NUL && last_autopat[(int)event] != NULL) { + prev_ap = &last_autopat[(int)event]; + } else { + prev_ap = &first_autopat[(int)event]; + } while ((ap = *prev_ap) != NULL) { if (ap->pat != NULL) { /* Accept a pattern when: @@ -6211,6 +6233,7 @@ static int do_autocmd_event(event_T event, char_u *pat, int nested, char_u *cmd, } ap->cmds = NULL; *prev_ap = ap; + last_autopat[(int)event] = ap; ap->next = NULL; if (group == AUGROUP_ALL) ap->group = current_augroup; @@ -6276,13 +6299,13 @@ do_doautocmd ( fname = skipwhite(fname); - /* - * Loop over the events. - */ - while (*arg && !ascii_iswhite(*arg)) - if (apply_autocmds_group(event_name2nr(arg, &arg), - fname, NULL, TRUE, group, curbuf, NULL)) - nothing_done = FALSE; + // Loop over the events. + while (*arg && !ends_excmd(*arg) && !ascii_iswhite(*arg)) { + if (apply_autocmds_group(event_name2nr(arg, &arg), fname, NULL, true, + group, curbuf, NULL)) { + nothing_done = false; + } + } if (nothing_done && do_msg) { MSG(_("No matching autocommands")); @@ -6657,7 +6680,6 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, char_u *save_sourcing_name; linenr_T save_sourcing_lnum; char_u *save_autocmd_fname; - int save_autocmd_fname_full; int save_autocmd_bufnr; char_u *save_autocmd_match; int save_autocmd_busy; @@ -6673,12 +6695,12 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, proftime_T wait_time; bool did_save_redobuff = false; - /* - * Quickly return if there are no autocommands for this event or - * autocommands are blocked. - */ - if (first_autopat[(int)event] == NULL || autocmd_blocked > 0) + // Quickly return if there are no autocommands for this event or + // autocommands are blocked. + if (event == NUM_EVENTS || first_autopat[(int)event] == NULL + || autocmd_blocked > 0) { goto BYPASS_AU; + } /* * When autocommands are busy, new autocommands are only executed when @@ -6730,7 +6752,6 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, * Save the autocmd_* variables and info about the current buffer. */ save_autocmd_fname = autocmd_fname; - save_autocmd_fname_full = autocmd_fname_full; save_autocmd_bufnr = autocmd_bufnr; save_autocmd_match = autocmd_match; save_autocmd_busy = autocmd_busy; @@ -6744,19 +6765,22 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, * invalid. */ if (fname_io == NULL) { - if (event == EVENT_COLORSCHEME || event == EVENT_OPTIONSET) + if (event == EVENT_COLORSCHEME || event == EVENT_OPTIONSET) { autocmd_fname = NULL; - else if (fname != NULL && *fname != NUL) + } else if (fname != NULL && !ends_excmd(*fname)) { autocmd_fname = fname; - else if (buf != NULL) + } else if (buf != NULL) { autocmd_fname = buf->b_ffname; - else + } else { autocmd_fname = NULL; - } else + } + } else { autocmd_fname = fname_io; - if (autocmd_fname != NULL) - autocmd_fname = vim_strsave(autocmd_fname); - autocmd_fname_full = FALSE; /* call FullName_save() later */ + } + if (autocmd_fname != NULL) { + // Allocate MAXPATHL for when eval_vars() resolves the fullpath. + autocmd_fname = vim_strnsave(autocmd_fname, MAXPATHL); + } /* * Set the buffer number to be used for <abuf>. @@ -6923,7 +6947,6 @@ static bool apply_autocmds_group(event_T event, char_u *fname, char_u *fname_io, sourcing_lnum = save_sourcing_lnum; xfree(autocmd_fname); autocmd_fname = save_autocmd_fname; - autocmd_fname_full = save_autocmd_fname_full; autocmd_bufnr = save_autocmd_bufnr; autocmd_match = save_autocmd_match; current_SID = save_current_SID; diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua index d2b90db707..2666ca6e6f 100644 --- a/src/nvim/generators/gen_api_ui_events.lua +++ b/src/nvim/generators/gen_api_ui_events.lua @@ -37,7 +37,7 @@ function write_arglist(output, ev, need_copy) for j = 1, #ev.parameters do local param = ev.parameters[j] local kind = string.upper(param[1]) - local do_copy = need_copy and (kind == "ARRAY" or kind == "DICTIONARY" or kind == "STRING") + local do_copy = need_copy and (kind == "ARRAY" or kind == "DICTIONARY" or kind == "STRING" or kind == "OBJECT") output:write(' ADD(args, ') if do_copy then output:write('copy_object(') @@ -91,7 +91,7 @@ for i = 1, #events do recv_cleanup = recv_cleanup..' api_free_string('..param[2]..');\n' argc = argc+2 elseif param[1] == 'Array' then - send = send..' Array copy_'..param[2]..' = copy_array('..param[2]..');\n' + send = send..' Array '..copy..' = copy_array('..param[2]..');\n' argv = argv..', '..copy..'.items, INT2PTR('..copy..'.size)' recv = (recv..' Array '..param[2].. ' = (Array){.items = argv['..argc..'],'.. @@ -99,6 +99,15 @@ for i = 1, #events do recv_argv = recv_argv..', '..param[2] recv_cleanup = recv_cleanup..' api_free_array('..param[2]..');\n' argc = argc+2 + elseif param[1] == 'Object' then + send = send..' Object *'..copy..' = xmalloc(sizeof(Object));\n' + send = send..' *'..copy..' = copy_object('..param[2]..');\n' + argv = argv..', '..copy + recv = recv..' Object '..param[2]..' = *(Object *)argv['..argc..'];\n' + recv_argv = recv_argv..', '..param[2] + recv_cleanup = (recv_cleanup..' api_free_object('..param[2]..');\n'.. + ' xfree(argv['..argc..']);\n') + argc = argc+1 elseif param[1] == 'Integer' or param[1] == 'Boolean' then argv = argv..', INT2PTR('..param[2]..')' recv_argv = recv_argv..', PTR2INT(argv['..argc..'])' @@ -119,7 +128,7 @@ for i = 1, #events do write_signature(bridge_output, ev, 'UI *ui') bridge_output:write('\n{\n') bridge_output:write(send) - bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n') + bridge_output:write(' UI_BRIDGE_CALL(ui, '..ev.name..', '..argc..', ui'..argv..');\n}\n\n') end end diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua index e999e53e4a..c40c37bb3e 100755 --- a/src/nvim/generators/gen_declarations.lua +++ b/src/nvim/generators/gen_declarations.lua @@ -164,9 +164,40 @@ local pattern = concat( ) if fname == '--help' then - print'Usage:' - print() - print' gendeclarations.lua definitions.c static.h non-static.h preprocessor.i' + print([[ +Usage: + + gendeclarations.lua definitions.c static.h non-static.h definitions.i + +Generates declarations for a C file definitions.c, putting declarations for +static functions into static.h and declarations for non-static functions into +non-static.h. File `definitions.i' should contain an already preprocessed +version of definitions.c and it is the only one which is actually parsed, +definitions.c is needed only to determine functions from which file out of all +functions found in definitions.i are needed. + +Additionally uses the following environment variables: + + NVIM_GEN_DECLARATIONS_LINE_NUMBERS: + If set to 1 then all generated declarations receive a comment with file + name and line number after the declaration. This may be useful for + debugging gen_declarations script, but not much beyond that with + configured development environment (i.e. with ctags/cscope/finding + definitions with clang/etc). + + WARNING: setting this to 1 will cause extensive rebuilds: declarations + generator script will not regenerate non-static.h file if its + contents did not change, but including line numbers will make + contents actually change. + + With contents changed timestamp of the file is regenerated even + when no real changes were made (e.g. a few lines were added to + a function which is not at the bottom of the file). + + With changed timestamp build system will assume that header + changed, triggering rebuilds of all C files which depend on the + "changed" header. +]]) os.exit() end @@ -193,23 +224,15 @@ local static = header local filepattern = '^#%a* (%d+) "([^"]-)/?([^"/]+)"' local curfile -local init = 0 +local init = 1 local curfile = nil local neededfile = fname:match('[^/]+$') local declline = 0 local declendpos = 0 local curdir = nil local is_needed_file = false +local init_is_nl = true while init ~= nil do - init = text:find('[\n;}]', init) - if init == nil then - break - end - local init_is_nl = text:sub(init, init) == '\n' - init = init + 1 - if init_is_nl and is_needed_file then - declline = declline + 1 - end if init_is_nl and text:sub(init, init) == '#' then local line, dir, file = text:match(filepattern, init) if file ~= nil then @@ -249,8 +272,10 @@ while init ~= nil do declaration = declaration:gsub(' $', '') declaration = declaration:gsub('^ ', '') declaration = declaration .. ';' - declaration = declaration .. (' // %s/%s:%u'):format( - curdir, curfile, declline) + if os.getenv('NVIM_GEN_DECLARATIONS_LINE_NUMBERS') == '1' then + declaration = declaration .. (' // %s/%s:%u'):format( + curdir, curfile, declline) + end declaration = declaration .. '\n' if declaration:sub(1, 6) == 'static' then static = static .. declaration @@ -260,6 +285,15 @@ while init ~= nil do declendpos = e end end + init = text:find('[\n;}]', init) + if init == nil then + break + end + init_is_nl = text:sub(init, init) == '\n' + init = init + 1 + if init_is_nl and is_needed_file then + declline = declline + 1 + end end non_static = non_static .. footer diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua index 36562c0be9..fdc00d5dc0 100644 --- a/src/nvim/generators/gen_options.lua +++ b/src/nvim/generators/gen_options.lua @@ -36,6 +36,7 @@ local redraw_flags={ all_windows='P_RALL', everything='P_RCLR', curswant='P_CURSWANT', + ui_option='P_UI_OPTION', } local list_flags={ diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c index ef152d638b..98164b2653 100644 --- a/src/nvim/getchar.c +++ b/src/nvim/getchar.c @@ -260,16 +260,17 @@ static void add_buff(buffheader_T *const buf, const char *const s, return; } - if (buf->bh_first.b_next == NULL) { /* first add to list */ + if (buf->bh_first.b_next == NULL) { // first add to list buf->bh_space = 0; buf->bh_curr = &(buf->bh_first); - } else if (buf->bh_curr == NULL) { /* buffer has already been read */ - EMSG(_("E222: Add to read buffer")); + } else if (buf->bh_curr == NULL) { // buffer has already been read + IEMSG(_("E222: Add to read buffer")); return; - } else if (buf->bh_index != 0) + } else if (buf->bh_index != 0) { memmove(buf->bh_first.b_next->b_str, - buf->bh_first.b_next->b_str + buf->bh_index, - STRLEN(buf->bh_first.b_next->b_str + buf->bh_index) + 1); + buf->bh_first.b_next->b_str + buf->bh_index, + STRLEN(buf->bh_first.b_next->b_str + buf->bh_index) + 1); + } buf->bh_index = 0; size_t len; @@ -1158,14 +1159,16 @@ void alloc_typebuf(void) */ void free_typebuf(void) { - if (typebuf.tb_buf == typebuf_init) - EMSG2(_(e_intern2), "Free typebuf 1"); - else + if (typebuf.tb_buf == typebuf_init) { + internal_error("Free typebuf 1"); + } else { xfree(typebuf.tb_buf); - if (typebuf.tb_noremap == noremapbuf_init) - EMSG2(_(e_intern2), "Free typebuf 2"); - else + } + if (typebuf.tb_noremap == noremapbuf_init) { + internal_error("Free typebuf 2"); + } else { xfree(typebuf.tb_noremap); + } } /* @@ -1583,7 +1586,7 @@ vungetc ( /* unget one character (can only be done once!) */ old_mouse_col = mouse_col; } -/// get a character: +/// Gets a character: /// 1. from the stuffbuffer /// This is used for abbreviated commands like "D" -> "d$". /// Also used to redo a command for ".". @@ -1601,7 +1604,7 @@ vungetc ( /* unget one character (can only be done once!) */ /// if "advance" is FALSE (vpeekc()): /// just look whether there is a character available. /// -/// When "no_mapping" is zero, checks for mappings in the current mode. +/// When `no_mapping` (global) is zero, checks for mappings in the current mode. /// Only returns one byte (of a multi-byte character). /// K_SPECIAL and CSI may be escaped, need to get two more bytes then. static int vgetorpeek(int advance) @@ -3361,6 +3364,10 @@ set_context_in_map_cmd ( arg = skipwhite(arg + 8); continue; } + if (STRNCMP(arg, "<special>", 9) == 0) { + arg = skipwhite(arg + 9); + continue; + } if (STRNCMP(arg, "<script>", 8) == 0) { arg = skipwhite(arg + 8); continue; @@ -3403,21 +3410,24 @@ int ExpandMappings(regmatch_T *regmatch, int *num_file, char_u ***file) for (round = 1; round <= 2; ++round) { count = 0; - for (i = 0; i < 6; ++i) { - if (i == 0) + for (i = 0; i < 7; i++) { + if (i == 0) { p = (char_u *)"<silent>"; - else if (i == 1) + } else if (i == 1) { p = (char_u *)"<unique>"; - else if (i == 2) + } else if (i == 2) { p = (char_u *)"<script>"; - else if (i == 3) + } else if (i == 3) { p = (char_u *)"<expr>"; - else if (i == 4 && !expand_buffer) + } else if (i == 4 && !expand_buffer) { p = (char_u *)"<buffer>"; - else if (i == 5) + } else if (i == 5) { p = (char_u *)"<nowait>"; - else + } else if (i == 6) { + p = (char_u *)"<special>"; + } else { continue; + } if (vim_regexec(regmatch, p, (colnr_T)0)) { if (round == 1) @@ -3903,7 +3913,7 @@ makemap ( c1 = 't'; break; default: - EMSG(_("E228: makemap: Illegal mode")); + IEMSG(_("E228: makemap: Illegal mode")); return FAIL; } do { /* do this twice if c2 is set, 3 times with c3 */ @@ -4235,3 +4245,70 @@ mapblock_T *get_maphash(int index, buf_T *buf) return (buf == NULL) ? maphash[index] : buf->b_maphash[index]; } + +/// Get command argument for <Cmd> key +char_u * getcmdkeycmd(int promptc, void *cookie, int indent) +{ + garray_T line_ga; + int c1 = -1, c2; + int cmod = 0; + bool aborted = false; + + ga_init(&line_ga, 1, 32); + + no_mapping++; + + got_int = false; + while (c1 != NUL && !aborted) { + ga_grow(&line_ga, 32); + + if (vgetorpeek(false) == NUL) { + // incomplete <Cmd> is an error, because there is not much the user + // could do in this state. + EMSG(e_cmdmap_err); + aborted = true; + break; + } + + // Get one character at a time. + c1 = vgetorpeek(true); + // Get two extra bytes for special keys + if (c1 == K_SPECIAL) { + c1 = vgetorpeek(true); // no mapping for these chars + c2 = vgetorpeek(true); + if (c1 == KS_MODIFIER) { + cmod = c2; + continue; + } + c1 = TO_SPECIAL(c1, c2); + } + + + if (got_int) { + aborted = true; + } else if (c1 == '\r' || c1 == '\n') { + c1 = NUL; // end the line + } else if (c1 == ESC) { + aborted = true; + } else if (c1 == K_COMMAND) { + // special case to give nicer error message + EMSG(e_cmdmap_repeated); + aborted = true; + } else if (IS_SPECIAL(c1)) { + EMSG2(e_cmdmap_key, get_special_key_name(c1, cmod)); + aborted = true; + } else { + ga_append(&line_ga, (char)c1); + } + + cmod = 0; + } + + no_mapping--; + + if (aborted) { + ga_clear(&line_ga); + } + + return (char_u *)line_ga.ga_data; +} diff --git a/src/nvim/globals.h b/src/nvim/globals.h index 9c8cefc68e..56790bc89b 100644 --- a/src/nvim/globals.h +++ b/src/nvim/globals.h @@ -317,17 +317,11 @@ EXTERN int do_profiling INIT(= PROF_NONE); /* PROF_ values */ /* * The exception currently being thrown. Used to pass an exception to * a different cstack. Also used for discarding an exception before it is - * caught or made pending. Only valid when did_throw is TRUE. + * caught or made pending. */ EXTERN except_T *current_exception; /* - * did_throw: An exception is being thrown. Reset when the exception is caught - * or as long as it is pending in a finally clause. - */ -EXTERN int did_throw INIT(= FALSE); - -/* * need_rethrow: set to TRUE when a throw that cannot be handled in do_cmdline() * must be propagated to the cstack of the previously called do_cmdline(). */ @@ -416,7 +410,7 @@ EXTERN struct caller_scope { scid_T SID; uint8_t *sourcing_name, *autocmd_fname, *autocmd_match; linenr_T sourcing_lnum; - int autocmd_fname_full, autocmd_bufnr; + int autocmd_bufnr; void *funccalp; } provider_caller_scope; EXTERN int provider_call_nesting INIT(= 0); @@ -729,29 +723,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); -// To speed up BYTELEN(); keep a lookup table to quickly get the length in -// bytes of a UTF-8 character from the first byte of a UTF-8 string. Bytes -// which are illegal when used as the first byte have a 1. The NUL byte has -// length 1. -EXTERN char utf8len_tab[256] INIT(= { - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, - 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1, -}); - # 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, @@ -892,7 +863,6 @@ EXTERN char_u *last_cmdline INIT(= NULL); // last command line (for ":) EXTERN char_u *repeat_cmdline INIT(= NULL); // command line for "." EXTERN char_u *new_last_cmdline INIT(= NULL); // new value for last_cmdline EXTERN char_u *autocmd_fname INIT(= NULL); // fname for <afile> on cmdline -EXTERN int autocmd_fname_full; // autocmd_fname is full path EXTERN int autocmd_bufnr INIT(= 0); // fnum for <abuf> on cmdline EXTERN char_u *autocmd_match INIT(= NULL); // name for <amatch> on cmdline EXTERN int did_cursorhold INIT(= false); // set when CursorHold t'gerd @@ -957,7 +927,7 @@ extern char_u *compiled_sys; * directory is not a local directory, globaldir is NULL. */ EXTERN char_u *globaldir INIT(= NULL); -/* Characters from 'listchars' option */ +// 'listchars' characters. Defaults are overridden in set_chars_option(). EXTERN int lcs_eol INIT(= '$'); EXTERN int lcs_ext INIT(= NUL); EXTERN int lcs_prec INIT(= NUL); @@ -968,12 +938,13 @@ EXTERN int lcs_tab2 INIT(= NUL); EXTERN int lcs_trail INIT(= NUL); EXTERN int lcs_conceal INIT(= ' '); -/* Characters from 'fillchars' option */ +// 'fillchars' characters. Defaults are overridden in set_chars_option(). EXTERN int fill_stl INIT(= ' '); EXTERN int fill_stlnc INIT(= ' '); -EXTERN int fill_vert INIT(= ' '); -EXTERN int fill_fold INIT(= '-'); +EXTERN int fill_vert INIT(= 9474); // │ +EXTERN int fill_fold INIT(= 183); // · EXTERN int fill_diff INIT(= '-'); +EXTERN int fill_msgsep INIT(= ' '); /* Whether 'keymodel' contains "stopsel" and "startsel". */ EXTERN int km_stopsel INIT(= FALSE); @@ -1063,6 +1034,7 @@ EXTERN char_u e_for[] INIT(= N_("E588: :endfor without :for")); EXTERN char_u e_exists[] INIT(= N_("E13: File exists (add ! to override)")); EXTERN char_u e_failed[] INIT(= N_("E472: Command failed")); EXTERN char_u e_internal[] INIT(= N_("E473: Internal error")); +EXTERN char_u e_intern2[] INIT(= N_("E685: Internal error: %s")); 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")); @@ -1082,6 +1054,9 @@ EXTERN char_u e_stdiochan2[] INIT(= N_( EXTERN char_u e_invstream[] INIT(= N_("E906: invalid stream for channel")); EXTERN char_u e_invstreamrpc[] INIT(= N_( "E906: invalid stream for rpc channel, use 'rpc'")); +EXTERN char_u e_streamkey[] INIT(= N_( + "E5210: dict key '%s' already set for buffered stream in channel %" + PRIu64)); EXTERN char_u e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\"")); EXTERN char_u e_mkdir[] INIT(= N_("E739: Cannot create directory %s: %s")); EXTERN char_u e_markinval[] INIT(= N_("E19: Mark has invalid line number")); @@ -1092,7 +1067,6 @@ EXTERN char_u e_nesting[] INIT(= N_("E22: Scripts nested too deep")); EXTERN char_u e_noalt[] INIT(= N_("E23: No alternate file")); EXTERN char_u e_noabbr[] INIT(= N_("E24: No such abbreviation")); EXTERN char_u e_nobang[] INIT(= N_("E477: No ! allowed")); -EXTERN char_u e_nogvim[] INIT(= N_("E25: Nvim does not have a built-in GUI")); EXTERN char_u e_nogroup[] INIT(= N_("E28: No such highlight group name: %s")); EXTERN char_u e_noinstext[] INIT(= N_("E29: No inserted text yet")); EXTERN char_u e_nolastcmd[] INIT(= N_("E30: No previous command line")); @@ -1155,7 +1129,6 @@ EXTERN char_u e_write[] INIT(= N_("E80: Error while writing")); EXTERN char_u e_zerocount[] INIT(= N_("E939: Positive count required")); EXTERN char_u e_usingsid[] INIT(= N_( "E81: Using <SID> not in a script context")); -EXTERN char_u e_intern2[] INIT(= N_("E685: Internal error: %s")); EXTERN char_u e_maxmempat[] INIT(= N_( "E363: pattern uses more memory than 'maxmempattern'")); EXTERN char_u e_emptybuf[] INIT(= N_("E749: empty buffer")); @@ -1173,6 +1146,12 @@ EXTERN char_u e_fnametoolong[] INIT(= N_("E856: Filename too long")); EXTERN char_u e_float_as_string[] INIT(= N_("E806: using Float as a String")); EXTERN char_u e_autocmd_err[] INIT(=N_( "E5500: autocmd has thrown an exception: %s")); +EXTERN char_u e_cmdmap_err[] INIT(=N_( + "E5520: <Cmd> mapping must end with <CR>")); +EXTERN char_u e_cmdmap_repeated[] INIT(=N_( + "E5521: <Cmd> mapping must end with <CR> before second <Cmd>")); +EXTERN char_u e_cmdmap_key[] INIT(=N_( + "E5522: <Cmd> mapping must not include %s key")); EXTERN char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM")); diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c index 3397788b00..526bc284a4 100644 --- a/src/nvim/hashtab.c +++ b/src/nvim/hashtab.c @@ -208,7 +208,7 @@ int hash_add(hashtab_T *ht, char_u *key) hash_T hash = hash_hash(key); hashitem_T *hi = hash_lookup(ht, (const char *)key, STRLEN(key), hash); if (!HASHITEM_EMPTY(hi)) { - EMSG2(_(e_intern2), "hash_add()"); + internal_error("hash_add()"); return FAIL; } hash_add_item(ht, hi, key, hash); diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h index 927fc94bbe..3518c8bdcc 100644 --- a/src/nvim/highlight_defs.h +++ b/src/nvim/highlight_defs.h @@ -7,6 +7,34 @@ typedef int32_t RgbValue; +/// Highlighting attribute bits. +typedef enum { + HL_INVERSE = 0x01, + HL_BOLD = 0x02, + HL_ITALIC = 0x04, + HL_UNDERLINE = 0x08, + HL_UNDERCURL = 0x10, + HL_STANDOUT = 0x20, +} HlAttrFlags; + +/// Stores a complete highlighting entry, including colors and attributes +/// for both TUI and GUI. +typedef struct attr_entry { + int16_t rgb_ae_attr, cterm_ae_attr; // HL_BOLD, etc. + RgbValue rgb_fg_color, rgb_bg_color, rgb_sp_color; + int cterm_fg_color, cterm_bg_color; +} HlAttrs; + +#define HLATTRS_INIT (HlAttrs) { \ + .rgb_ae_attr = 0, \ + .cterm_ae_attr = 0, \ + .rgb_fg_color = -1, \ + .rgb_bg_color = -1, \ + .rgb_sp_color = -1, \ + .cterm_fg_color = 0, \ + .cterm_bg_color = 0, \ +} + /// Values for index in highlight_attr[]. /// When making changes, also update hlf_names below! typedef enum { @@ -59,6 +87,7 @@ typedef enum { , HLF_QFL // selected quickfix line , HLF_0 // Whitespace , HLF_INACTIVE // NormalNC: Normal text in non-current windows + , HLF_MSGSEP // message separator line , HLF_COUNT // MUST be the last one } hlf_T; @@ -109,7 +138,8 @@ EXTERN const char *hlf_names[] INIT(= { [HLF_MC] = "ColorColumn", [HLF_QFL] = "QuickFixLine", [HLF_0] = "Whitespace", - [HLF_INACTIVE] = "NormalNC" + [HLF_INACTIVE] = "NormalNC", + [HLF_MSGSEP] = "MsgSeparator", }); @@ -117,7 +147,6 @@ EXTERN int highlight_attr[HLF_COUNT]; // Highl. attr for each context. 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); -EXTERN int cterm_normal_fg_bold INIT(= 0); EXTERN int cterm_normal_bg_color INIT(= 0); EXTERN RgbValue normal_fg INIT(= -1); EXTERN RgbValue normal_bg INIT(= -1); diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c index 3c02f5acbd..b78b56562c 100644 --- a/src/nvim/if_cscope.c +++ b/src/nvim/if_cscope.c @@ -549,7 +549,7 @@ static void cs_reading_emsg( static int cs_cnt_matches(size_t idx) { char *stok; - int nlines; + int nlines = 0; char *buf = xmalloc(CSREAD_BUFSIZE); for (;; ) { @@ -569,16 +569,20 @@ static int cs_cnt_matches(size_t idx) return CSCOPE_FAILURE; } - /* - * If the database is out of date, or there's some other problem, - * cscope will output error messages before the number-of-lines output. - * Display/discard any output that doesn't match what we want. - * Accept "\S*cscope: X lines", also matches "mlcscope". - */ - if ((stok = strtok(buf, (const char *)" ")) == NULL) + // If the database is out of date, or there's some other problem, + // cscope will output error messages before the number-of-lines output. + // Display/discard any output that doesn't match what we want. + // Accept "\S*cscope: X lines", also matches "mlcscope". + // Bail out for the "Unable to search" error. + if (strstr((const char *)buf, "Unable to search database") != NULL) { + break; + } + if ((stok = strtok(buf, (const char *)" ")) == NULL) { continue; - if (strstr((const char *)stok, "cscope:") == NULL) + } + if (strstr((const char *)stok, "cscope:") == NULL) { continue; + } if ((stok = strtok(NULL, (const char *)" ")) == NULL) continue; @@ -1012,9 +1016,9 @@ static int cs_find_common(char *opt, char *pat, int forceit, int verbose, fclose(f); if (use_ll) /* Use location list */ wp = curwin; - /* '-' starts a new error list */ + // '-' starts a new error list if (qf_init(wp, tmp, (char_u *)"%f%*\\t%l%*\\t%m", - *qfpos == '-', cmdline) > 0) { + *qfpos == '-', cmdline, NULL) > 0) { if (postponed_split != 0) { (void)win_split(postponed_split > 0 ? postponed_split : 0, postponed_split_flags); @@ -1369,8 +1373,8 @@ static char *cs_manage_matches(char **matches, char **contexts, case Print: cs_print_tags_priv(mp, cp, cnt); break; - default: /* should not reach here */ - (void)EMSG(_("E570: fatal error in cs_manage_matches")); + default: // should not reach here + IEMSG(_("E570: fatal error in cs_manage_matches")); return NULL; } @@ -1685,8 +1689,15 @@ static int cs_read_prompt(size_t i) assert(IOSIZE >= cs_emsg_len); size_t maxlen = IOSIZE - cs_emsg_len; - for (;; ) { - while ((ch = getc(csinfo[i].fr_fp)) != EOF && ch != CSCOPE_PROMPT[0]) { + while (1) { + while (1) { + do { + errno = 0; + ch = fgetc(csinfo[i].fr_fp); + } while (ch == EOF && errno == EINTR && ferror(csinfo[i].fr_fp)); + if (ch == EOF || ch == CSCOPE_PROMPT[0]) { + break; + } // if there is room and char is printable if (bufpos < maxlen - 1 && vim_isprintc(ch)) { // lazy buffer allocation @@ -1715,9 +1726,13 @@ static int cs_read_prompt(size_t i) } } - for (size_t n = 0; n < strlen(CSCOPE_PROMPT); ++n) { - if (n > 0) - ch = (char)getc(csinfo[i].fr_fp); + for (size_t n = 0; n < strlen(CSCOPE_PROMPT); n++) { + if (n > 0) { + do { + errno = 0; + ch = fgetc(csinfo[i].fr_fp); + } while (ch == EOF && errno == EINTR && ferror(csinfo[i].fr_fp)); + } if (ch == EOF) { PERROR("cs_read_prompt EOF"); if (buf != NULL && buf[0] != NUL) diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c index 279d45bb0a..2a215f854f 100644 --- a/src/nvim/indent_c.c +++ b/src/nvim/indent_c.c @@ -1314,6 +1314,43 @@ static int cin_starts_with(char_u *s, char *word) return STRNCMP(s, word, l) == 0 && !vim_isIDc(s[l]); } +/// Recognize a `extern "C"` or `extern "C++"` linkage specifications. +static int cin_is_cpp_extern_c(char_u *s) +{ + char_u *p; + int has_string_literal = false; + + s = cin_skipcomment(s); + if (STRNCMP(s, "extern", 6) == 0 && (s[6] == NUL || !vim_iswordc(s[6]))) { + p = cin_skipcomment(skipwhite(s + 6)); + while (*p != NUL) { + if (ascii_iswhite(*p)) { + p = cin_skipcomment(skipwhite(p)); + } else if (*p == '{') { + break; + } else if (p[0] == '"' && p[1] == 'C' && p[2] == '"') { + if (has_string_literal) { + return false; + } + has_string_literal = true; + p += 3; + } else if (p[0] == '"' && p[1] == 'C' && p[2] == '+' && p[3] == '+' + && p[4] == '"') { + if (has_string_literal) { + return false; + } + has_string_literal = true; + p += 5; + } else { + return false; + } + } + return has_string_literal ? true : false; + } + return false; +} + + /* * Skip strings, chars and comments until at or past "trypos". * Return the column found. @@ -1322,14 +1359,19 @@ static int cin_skip2pos(pos_T *trypos) { char_u *line; char_u *p; + char_u *new_p; p = line = ml_get(trypos->lnum); while (*p && (colnr_T)(p - line) < trypos->col) { - if (cin_iscomment(p)) + if (cin_iscomment(p)) { p = cin_skipcomment(p); - else { - p = skip_string(p); - ++p; + } else { + new_p = skip_string(p); + if (new_p == p) { + p++; + } else { + p = new_p; + } } } return (int)(p - line); @@ -1619,6 +1661,12 @@ void parse_cino(buf_T *buf) * while(). */ buf->b_ind_if_for_while = 0; + // indentation for # comments + buf->b_ind_hash_comment = 0; + + // Handle C++ extern "C" or "C++" + buf->b_ind_cpp_extern_c = 0; + for (p = buf->b_p_cino; *p; ) { l = p++; if (*p == '-') @@ -1687,6 +1735,7 @@ void parse_cino(buf_T *buf) case '#': buf->b_ind_hash_comment = n; break; case 'N': buf->b_ind_cpp_namespace = n; break; case 'k': buf->b_ind_if_for_while = n; break; + case 'E': buf->b_ind_cpp_extern_c = n; break; } if (*p == ',') ++p; @@ -2317,8 +2366,11 @@ int get_c_indent(void) amount += curbuf->b_ind_open_imag; l = skipwhite(get_cursor_line_ptr()); - if (cin_is_cpp_namespace(l)) + if (cin_is_cpp_namespace(l)) { amount += curbuf->b_ind_cpp_namespace; + } else if (cin_is_cpp_extern_c(l)) { + amount += curbuf->b_ind_cpp_extern_c; + } } else { /* Compensate for adding b_ind_open_extra later. */ amount -= curbuf->b_ind_open_extra; @@ -2517,6 +2569,9 @@ int get_c_indent(void) amount += curbuf->b_ind_cpp_namespace - added_to_amount; break; + } else if (cin_is_cpp_extern_c(l)) { + amount += curbuf->b_ind_cpp_extern_c - added_to_amount; + break; } if (cin_nocode(l)) diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c index a75fe793ac..628bfef221 100644 --- a/src/nvim/keymap.c +++ b/src/nvim/keymap.c @@ -24,23 +24,23 @@ * Some useful tables. */ -static struct modmasktable { - short mod_mask; /* Bit-mask for particular key modifier */ - short mod_flag; /* Bit(s) for particular key modifier */ - char_u name; /* Single letter name of modifier */ -} mod_mask_table[] = -{ - {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M'}, - {MOD_MASK_META, MOD_MASK_META, (char_u)'T'}, - {MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C'}, - {MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S'}, - {MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2'}, - {MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3'}, - {MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4'}, - {MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D'}, +static const struct modmasktable { + uint16_t mod_mask; ///< Bit-mask for particular key modifier. + uint16_t mod_flag; ///< Bit(s) for particular key modifier. + char_u name; ///< Single letter name of modifier. +} mod_mask_table[] = { + { MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'M' }, + { MOD_MASK_META, MOD_MASK_META, (char_u)'T' }, + { MOD_MASK_CTRL, MOD_MASK_CTRL, (char_u)'C' }, + { MOD_MASK_SHIFT, MOD_MASK_SHIFT, (char_u)'S' }, + { MOD_MASK_MULTI_CLICK, MOD_MASK_2CLICK, (char_u)'2' }, + { MOD_MASK_MULTI_CLICK, MOD_MASK_3CLICK, (char_u)'3' }, + { MOD_MASK_MULTI_CLICK, MOD_MASK_4CLICK, (char_u)'4' }, + { MOD_MASK_CMD, MOD_MASK_CMD, (char_u)'D' }, // 'A' must be the last one - {MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A'}, - {0, 0, NUL} + { MOD_MASK_ALT, MOD_MASK_ALT, (char_u)'A' }, + { 0, 0, NUL } + // NOTE: when adding an entry, update MAX_KEY_NAME_LEN! }; /* @@ -139,11 +139,10 @@ static char_u modifier_keys_table[] = NUL }; -static struct key_name_entry { +static const struct key_name_entry { int key; // Special key code or ascii value - char *name; // Name of key -} key_names_table[] = -{ + const char *name; // Name of key +} key_names_table[] = { { ' ', "Space" }, { TAB, "Tab" }, { K_TAB, "Tab" }, @@ -286,7 +285,9 @@ static struct key_name_entry { { K_SNR, "SNR" }, { K_PLUG, "Plug" }, { K_PASTE, "Paste" }, + { K_COMMAND, "Cmd" }, { 0, NULL } + // NOTE: When adding a long name update MAX_KEY_NAME_LEN. }; static struct mousetable { @@ -318,73 +319,73 @@ static struct mousetable { {0, 0, 0, 0}, }; -/* - * Return the modifier mask bit (MOD_MASK_*) which corresponds to the given - * modifier name ('S' for Shift, 'C' for Ctrl etc). - */ +/// Return the modifier mask bit (#MOD_MASK_*) corresponding to mod name +/// +/// E.g. 'S' for shift, 'C' for ctrl. int name_to_mod_mask(int c) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { - int i; - c = TOUPPER_ASC(c); - for (i = 0; mod_mask_table[i].mod_mask != 0; i++) - if (c == mod_mask_table[i].name) + for (size_t i = 0; mod_mask_table[i].mod_mask != 0; i++) { + if (c == mod_mask_table[i].name) { return mod_mask_table[i].mod_flag; + } + } return 0; } -/* - * Check if if there is a special key code for "key" that includes the - * modifiers specified. - */ -int simplify_key(int key, int *modifiers) +/// Check if there is a special key code for "key" with specified modifiers +/// +/// @param[in] key Initial key code. +/// @param[in,out] modifiers Initial modifiers, is adjusted to have simplified +/// modifiers. +/// +/// @return Simplified key code. +int simplify_key(const int key, int *modifiers) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int i; - int key0; - int key1; - if (*modifiers & (MOD_MASK_SHIFT | MOD_MASK_CTRL | MOD_MASK_ALT)) { - /* TAB is a special case */ + // TAB is a special case. if (key == TAB && (*modifiers & MOD_MASK_SHIFT)) { *modifiers &= ~MOD_MASK_SHIFT; return K_S_TAB; } - key0 = KEY2TERMCAP0(key); - key1 = KEY2TERMCAP1(key); - for (i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) + const int key0 = KEY2TERMCAP0(key); + const int key1 = KEY2TERMCAP1(key); + for (int i = 0; modifier_keys_table[i] != NUL; i += MOD_KEYS_ENTRY_SIZE) { if (key0 == modifier_keys_table[i + 3] && key1 == modifier_keys_table[i + 4] && (*modifiers & modifier_keys_table[i])) { *modifiers &= ~modifier_keys_table[i]; return TERMCAP2KEY(modifier_keys_table[i + 1], - modifier_keys_table[i + 2]); + modifier_keys_table[i + 2]); } + } } return key; } -/* - * Change <xHome> to <Home>, <xUp> to <Up>, etc. - */ -int handle_x_keys(int key) +/// Change <xKey> to <Key> +int handle_x_keys(const int key) + FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT { switch (key) { - case K_XUP: return K_UP; - case K_XDOWN: return K_DOWN; - case K_XLEFT: return K_LEFT; - case K_XRIGHT: return K_RIGHT; - case K_XHOME: return K_HOME; - case K_ZHOME: return K_HOME; - case K_XEND: return K_END; - case K_ZEND: return K_END; - case K_XF1: return K_F1; - case K_XF2: return K_F2; - case K_XF3: return K_F3; - case K_XF4: return K_F4; - case K_S_XF1: return K_S_F1; - case K_S_XF2: return K_S_F2; - case K_S_XF3: return K_S_F3; - case K_S_XF4: return K_S_F4; + case K_XUP: return K_UP; + case K_XDOWN: return K_DOWN; + case K_XLEFT: return K_LEFT; + case K_XRIGHT: return K_RIGHT; + case K_XHOME: return K_HOME; + case K_ZHOME: return K_HOME; + case K_XEND: return K_END; + case K_ZEND: return K_END; + case K_XF1: return K_F1; + case K_XF2: return K_F2; + case K_XF3: return K_F3; + case K_XF4: return K_F4; + case K_S_XF1: return K_S_F1; + case K_S_XF2: return K_S_F2; + case K_S_XF3: return K_S_F3; + case K_S_XF4: return K_S_F4; } return key; } @@ -474,16 +475,20 @@ char_u *get_special_key_name(int c, int modifiers) string[idx++] = *s++; } } - } else { /* use name of special key */ - STRCPY(string + idx, key_names_table[table_idx].name); - idx = (int)STRLEN(string); + } else { // use name of special key + size_t len = STRLEN(key_names_table[table_idx].name); + + if ((int)len + idx + 2 <= MAX_KEY_NAME_LEN) { + STRCPY(string + idx, key_names_table[table_idx].name); + idx += (int)len; + } } string[idx++] = '>'; string[idx] = NUL; return string; } -/// Try translating a <> name +/// Try translating a <> name ("keycode"). /// /// @param[in,out] srcp Source from which <> are translated. Is advanced to /// after the <> name if there is a match. @@ -508,7 +513,7 @@ unsigned int trans_special(const char_u **srcp, const size_t src_len, return 0; } - /* Put the appropriate modifier in a string */ + // Put the appropriate modifier in a string. if (modifiers != 0) { dst[dlen++] = K_SPECIAL; dst[dlen++] = KS_MODIFIER; @@ -569,15 +574,11 @@ int find_special_key(const char_u **srcp, const size_t src_len, int *const modp, // Find end of modifier list last_dash = src; - for (bp = src + 1; bp <= end && (*bp == '-' || vim_isIDc(*bp)); bp++) { + for (bp = src + 1; bp <= end && (*bp == '-' || ascii_isident(*bp)); bp++) { if (*bp == '-') { last_dash = bp; if (bp + 1 <= end) { - if (has_mbyte) { - l = mb_ptr2len_len(bp + 1, (int) (end - bp) + 1); - } else { - l = 1; - } + l = utfc_ptr2len_len(bp + 1, (int)(end - bp) + 1); // Anything accepted, like <C-?>. // <C-"> or <M-"> are not special in strings as " is // the string delimiter. With a backslash it works: <M-\"> @@ -625,9 +626,11 @@ int find_special_key(const char_u **srcp, const size_t src_len, int *const modp, // Modifier with single letter, or special key name. if (in_string && last_dash[1] == '\\' && last_dash[2] == '"') { - off = 2; + // Special case for a double-quoted string + off = l = 2; + } else { + l = mb_ptr2len(last_dash + 1); } - l = mb_ptr2len(last_dash + 1); if (modifiers != 0 && last_dash[l + 1] == '>') { key = PTR2CHAR(last_dash + off); } else { @@ -702,33 +705,39 @@ int find_special_key_in_table(int c) { int i; - for (i = 0; key_names_table[i].name != NULL; i++) - if (c == key_names_table[i].key) + for (i = 0; key_names_table[i].name != NULL; i++) { + if (c == key_names_table[i].key) { break; - if (key_names_table[i].name == NULL) + } + } + if (key_names_table[i].name == NULL) { i = -1; + } return i; } -/* - * Find the special key with the given name (the given string does not have to - * end with NUL, the name is assumed to end before the first non-idchar). - * If the name starts with "t_" the next two characters are interpreted as a - * termcap name. - * Return the key code, or 0 if not found. - */ +/// Find the special key with the given name +/// +/// @param[in] name Name of the special. Does not have to end with NUL, it is +/// assumed to end before the first non-idchar. If name starts +/// with "t_" the next two characters are interpreted as +/// a termcap name. +/// +/// @return Key code or 0 if not found. int get_special_key_code(const char_u *name) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - char *table_name; - int i, j; - - for (i = 0; key_names_table[i].name != NULL; i++) { - table_name = key_names_table[i].name; - for (j = 0; vim_isIDc(name[j]) && table_name[j] != NUL; j++) - if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) + for (int i = 0; key_names_table[i].name != NULL; i++) { + const char *const table_name = key_names_table[i].name; + int j; + for (j = 0; ascii_isident(name[j]) && table_name[j] != NUL; j++) { + if (TOLOWER_ASC(table_name[j]) != TOLOWER_ASC(name[j])) { break; - if (!vim_isIDc(name[j]) && table_name[j] == NUL) + } + } + if (!ascii_isident(name[j]) && table_name[j] == NUL) { return key_names_table[i].key; + } } return 0; @@ -834,7 +843,7 @@ char_u *replace_termcodes(const char_u *from, const size_t from_len, } slen = trans_special(&src, (size_t)(end - src) + 1, result + dlen, true, - true); + false); if (slen) { dlen += slen; continue; diff --git a/src/nvim/keymap.h b/src/nvim/keymap.h index ee64854c98..c64691e8ea 100644 --- a/src/nvim/keymap.h +++ b/src/nvim/keymap.h @@ -243,6 +243,7 @@ enum key_extra { , KE_EVENT // event , KE_PASTE // special key to toggle the 'paste' option. // sent only by UIs + , KE_COMMAND // special key to execute command in any mode }; /* @@ -431,6 +432,7 @@ enum key_extra { #define K_EVENT TERMCAP2KEY(KS_EXTRA, KE_EVENT) #define K_PASTE TERMCAP2KEY(KS_EXTRA, KE_PASTE) +#define K_COMMAND TERMCAP2KEY(KS_EXTRA, KE_COMMAND) /* Bits for modifier mask */ /* 0x01 cannot be used, because the modifier must be 0x02 or higher */ @@ -441,16 +443,17 @@ enum key_extra { #define MOD_MASK_2CLICK 0x20 // use MOD_MASK_MULTI_CLICK #define MOD_MASK_3CLICK 0x40 // use MOD_MASK_MULTI_CLICK #define MOD_MASK_4CLICK 0x60 // use MOD_MASK_MULTI_CLICK -#define MOD_MASK_CMD 0x80 // "super" key (OSX/Mac: command-key) +#define MOD_MASK_CMD 0x80 // "super" key (macOS: command-key) #define MOD_MASK_MULTI_CLICK (MOD_MASK_2CLICK|MOD_MASK_3CLICK| \ MOD_MASK_4CLICK) /* * The length of the longest special key name, including modifiers. - * Current longest is <M-C-S-T-4-MiddleRelease> (length includes '<' and '>'). + * Current longest is <M-C-S-T-D-A-4-ScrollWheelRight> (length includes '<' and + * '>'). */ -#define MAX_KEY_NAME_LEN 25 +#define MAX_KEY_NAME_LEN 32 // Maximum length of a special key event as tokens. This includes modifiers. // The longest event is something like <M-C-S-T-4-LeftDrag> which would be the diff --git a/src/nvim/lib/kbtree.h b/src/nvim/lib/kbtree.h index a3943054e6..e2688064a8 100644 --- a/src/nvim/lib/kbtree.h +++ b/src/nvim/lib/kbtree.h @@ -390,34 +390,14 @@ #define KBTREE_INIT(name, key_t, __cmp, T) \ KBTREE_INIT_IMPL(name, key_t, kbnode_##name##_t, __cmp, T, (sizeof(kbnode_##name##_t)+(2*T)*sizeof(void *))) -#if (!defined(__clang__) && !defined(__INTEL_COMPILER)) && (__GNUC__ > 4 ) - -// The index trickery shouldn't be UB anymore, -// still it is to much for gcc:s -Werror=array-bounds -# define __KB_PRAGMA_START \ - _Pragma("GCC diagnostic push") \ - _Pragma("GCC diagnostic ignored \"-Warray-bounds\"") - -# define __KB_PRAGMA_END \ - _Pragma("GCC diagnostic pop") \ - -#else - -# define __KB_PRAGMA_START -# define __KB_PRAGMA_END - -#endif - #define KBTREE_INIT_IMPL(name, key_t, kbnode_t, __cmp, T, ILEN) \ - __KB_PRAGMA_START \ __KB_TREE_T(name, key_t, T) \ __KB_GET_AUX1(name, key_t, kbnode_t, __cmp) \ __KB_GET(name, key_t, kbnode_t) \ __KB_INTERVAL(name, key_t, kbnode_t) \ __KB_PUT(name, key_t, kbnode_t, __cmp, T, ILEN) \ __KB_DEL(name, key_t, kbnode_t, T) \ - __KB_ITR(name, key_t, kbnode_t) \ - __KB_PRAGMA_END + __KB_ITR(name, key_t, kbnode_t) #define KB_DEFAULT_SIZE 512 diff --git a/src/nvim/lib/kvec.h b/src/nvim/lib/kvec.h index 584282d773..ad56c9237b 100644 --- a/src/nvim/lib/kvec.h +++ b/src/nvim/lib/kvec.h @@ -41,6 +41,7 @@ #include <string.h> #include "nvim/memory.h" +#include "nvim/os/os_defs.h" #define kv_roundup32(x) \ ((--(x)), \ @@ -62,7 +63,16 @@ #define kv_pop(v) ((v).items[--(v).size]) #define kv_size(v) ((v).size) #define kv_max(v) ((v).capacity) -#define kv_last(v) kv_A(v, kv_size(v) - 1) +#define kv_Z(v, i) kv_A(v, kv_size(v) - (i) - 1) +#define kv_last(v) kv_Z(v, 0) + +/// Drop last n items from kvec without resizing +/// +/// Previously spelled as `(void)kv_pop(v)`, repeated n times. +/// +/// @param[out] v Kvec to drop items from. +/// @param[in] n Number of elements to drop. +#define kv_drop(v, n) ((v).size -= (n)) #define kv_resize(v, s) \ ((v).capacity = (s), \ diff --git a/src/nvim/lib/ringbuf.h b/src/nvim/lib/ringbuf.h index 12b75ec65a..e63eae70b0 100644 --- a/src/nvim/lib/ringbuf.h +++ b/src/nvim/lib/ringbuf.h @@ -15,6 +15,7 @@ #ifndef NVIM_LIB_RINGBUF_H #define NVIM_LIB_RINGBUF_H +#include <stddef.h> #include <string.h> #include <assert.h> #include <stdint.h> @@ -73,6 +74,32 @@ typedef struct { \ RBType *buf_end; \ } TypeName##RingBuffer; +/// Dummy item free macros, for use in RINGBUF_INIT +/// +/// This macros actually does nothing. +/// +/// @param[in] item Item to be freed. +#define RINGBUF_DUMMY_FREE(item) + +/// Static ring buffer +/// +/// @warning Ring buffers created with this macros must neither be freed nor +/// deallocated. +/// +/// @param scope Ring buffer scope. +/// @param TypeName Ring buffer type name. +/// @param RBType Type of the single ring buffer element. +/// @param varname Variable name. +/// @param rbsize Ring buffer size. +#define RINGBUF_STATIC(scope, TypeName, RBType, varname, rbsize) \ +static RBType _##varname##_buf[rbsize]; \ +scope TypeName##RingBuffer varname = { \ + .buf = _##varname##_buf, \ + .next = _##varname##_buf, \ + .first = NULL, \ + .buf_end = _##varname##_buf + rbsize - 1, \ +}; + /// Initialize a new ring buffer /// /// @param TypeName Ring buffer type name. Actual type name will be diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c index cacba3ce87..5da6d2c0a0 100644 --- a/src/nvim/lua/converter.c +++ b/src/nvim/lua/converter.c @@ -211,20 +211,28 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) size_t len; const char *s = lua_tolstring(lstate, -2, &len); if (cur.special) { - list_T *const kv_pair = tv_list_alloc(); - tv_list_append_list(cur.tv->vval.v_list, kv_pair); - listitem_T *const key = tv_list_item_alloc(); - key->li_tv = decode_string(s, len, kTrue, false, false); - tv_list_append(kv_pair, key); - if (key->li_tv.v_type == VAR_UNKNOWN) { + list_T *const kv_pair = tv_list_alloc(2); + + typval_T s_tv = decode_string(s, len, kTrue, false, false); + if (s_tv.v_type == VAR_UNKNOWN) { ret = false; tv_list_unref(kv_pair); continue; } - listitem_T *const val = tv_list_item_alloc(); - tv_list_append(kv_pair, val); + tv_list_append_owned_tv(kv_pair, s_tv); + + // Value: not populated yet, need to create list item to push. + tv_list_append_owned_tv(kv_pair, (typval_T) { + .v_type = VAR_UNKNOWN, + }); kv_push(stack, cur); - cur = (TVPopStackItem) { &val->li_tv, false, false, 0 }; + tv_list_append_list(cur.tv->vval.v_list, kv_pair); + cur = (TVPopStackItem) { + .tv = TV_LIST_ITEM_TV(tv_list_last(kv_pair)), + .container = false, + .special = false, + .idx = 0, + }; } else { dictitem_T *const di = tv_dict_item_alloc_len(s, len); if (tv_dict_add(cur.tv->vval.v_dict, di) == FAIL) { @@ -239,15 +247,23 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) } } else { assert(cur.tv->v_type == VAR_LIST); - lua_rawgeti(lstate, -1, cur.tv->vval.v_list->lv_len + 1); + lua_rawgeti(lstate, -1, tv_list_len(cur.tv->vval.v_list) + 1); if (lua_isnil(lstate, -1)) { lua_pop(lstate, 2); continue; } - listitem_T *const li = tv_list_item_alloc(); - tv_list_append(cur.tv->vval.v_list, li); + // Not populated yet, need to create list item to push. + tv_list_append_owned_tv(cur.tv->vval.v_list, (typval_T) { + .v_type = VAR_UNKNOWN, + }); kv_push(stack, cur); - cur = (TVPopStackItem) { &li->li_tv, false, false, 0 }; + // TODO(ZyX-I): Use indexes, here list item *will* be reallocated. + cur = (TVPopStackItem) { + .tv = TV_LIST_ITEM_TV(tv_list_last(cur.tv->vval.v_list)), + .container = false, + .special = false, + .idx = 0, + }; } } assert(!cur.container); @@ -305,8 +321,8 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) switch (table_props.type) { case kObjectTypeArray: { cur.tv->v_type = VAR_LIST; - cur.tv->vval.v_list = tv_list_alloc(); - cur.tv->vval.v_list->lv_refcount++; + cur.tv->vval.v_list = tv_list_alloc((ptrdiff_t)table_props.maxidx); + tv_list_ref(cur.tv->vval.v_list); if (table_props.maxidx != 0) { cur.container = true; cur.idx = lua_gettop(lstate); @@ -322,7 +338,8 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv) } else { cur.special = table_props.has_string_with_nul; if (table_props.has_string_with_nul) { - decode_create_map_special_dict(cur.tv); + decode_create_map_special_dict( + cur.tv, (ptrdiff_t)table_props.string_keys_num); assert(cur.tv->v_type == VAR_DICT); dictitem_T *const val_di = tv_dict_find(cur.tv->vval.v_dict, S_LEN("_VAL")); @@ -406,7 +423,7 @@ nlua_pop_typval_table_processing_end: #define TYPVAL_ENCODE_CONV_STR_STRING TYPVAL_ENCODE_CONV_STRING #define TYPVAL_ENCODE_CONV_EXT_STRING(tv, str, len, type) \ - TYPVAL_ENCODE_CONV_NIL() + TYPVAL_ENCODE_CONV_NIL(tv) #define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \ do { \ diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c index 8e7b8a1824..02eabb9c89 100644 --- a/src/nvim/lua/executor.c +++ b/src/nvim/lua/executor.c @@ -40,66 +40,6 @@ typedef struct { /// Name of the run code for use in messages #define NLUA_EVAL_NAME "<VimL compiled string>" -/// Call C function which does not expect any arguments -/// -/// @param function Called function -/// @param numret Number of returned arguments -#define NLUA_CALL_C_FUNCTION_0(lstate, function, numret) \ - do { \ - lua_pushcfunction(lstate, &function); \ - lua_call(lstate, 0, numret); \ - } while (0) -/// Call C function which expects one argument -/// -/// @param function Called function -/// @param numret Number of returned arguments -/// @param a… Supplied argument (should be a void* pointer) -#define NLUA_CALL_C_FUNCTION_1(lstate, function, numret, a1) \ - do { \ - lua_pushcfunction(lstate, &function); \ - lua_pushlightuserdata(lstate, a1); \ - lua_call(lstate, 1, numret); \ - } while (0) -/// Call C function which expects two arguments -/// -/// @param function Called function -/// @param numret Number of returned arguments -/// @param a… Supplied argument (should be a void* pointer) -#define NLUA_CALL_C_FUNCTION_2(lstate, function, numret, a1, a2) \ - do { \ - lua_pushcfunction(lstate, &function); \ - lua_pushlightuserdata(lstate, a1); \ - lua_pushlightuserdata(lstate, a2); \ - lua_call(lstate, 2, numret); \ - } while (0) -/// Call C function which expects three arguments -/// -/// @param function Called function -/// @param numret Number of returned arguments -/// @param a… Supplied argument (should be a void* pointer) -#define NLUA_CALL_C_FUNCTION_3(lstate, function, numret, a1, a2, a3) \ - do { \ - lua_pushcfunction(lstate, &function); \ - lua_pushlightuserdata(lstate, a1); \ - lua_pushlightuserdata(lstate, a2); \ - lua_pushlightuserdata(lstate, a3); \ - lua_call(lstate, 3, numret); \ - } while (0) -/// Call C function which expects five arguments -/// -/// @param function Called function -/// @param numret Number of returned arguments -/// @param a… Supplied argument (should be a void* pointer) -#define NLUA_CALL_C_FUNCTION_4(lstate, function, numret, a1, a2, a3, a4) \ - do { \ - lua_pushcfunction(lstate, &function); \ - lua_pushlightuserdata(lstate, a1); \ - lua_pushlightuserdata(lstate, a2); \ - lua_pushlightuserdata(lstate, a3); \ - lua_pushlightuserdata(lstate, a4); \ - lua_call(lstate, 4, numret); \ - } while (0) - /// Convert lua error into a Vim error message /// /// @param lstate Lua interpreter state. @@ -163,123 +103,6 @@ static int nlua_stricmp(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL return 1; } -/// Evaluate lua string -/// -/// Expects two values on the stack: string to evaluate, pointer to the -/// location where result is saved. Always returns nothing (from the lua point -/// of view). -static int nlua_exec_lua_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL -{ - const String *const str = (const String *)lua_touserdata(lstate, 1); - typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 2); - lua_pop(lstate, 2); - - if (luaL_loadbuffer(lstate, str->data, str->size, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s")); - return 0; - } - if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s")); - return 0; - } - if (!nlua_pop_typval(lstate, ret_tv)) { - return 0; - } - return 0; -} - -/// Evaluate lua string for each line in range -/// -/// Expects two values on the stack: string to evaluate and pointer to integer -/// array with line range. Always returns nothing (from the lua point of view). -static int nlua_exec_luado_string(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL -{ - const String *const str = (const String *)lua_touserdata(lstate, 1); - const linenr_T *const range = (const linenr_T *)lua_touserdata(lstate, 2); - lua_pop(lstate, 2); - -#define DOSTART "return function(line, linenr) " -#define DOEND " end" - const size_t lcmd_len = (str->size - + (sizeof(DOSTART) - 1) - + (sizeof(DOEND) - 1)); - char *lcmd; - if (lcmd_len < IOSIZE) { - lcmd = (char *)IObuff; - } else { - lcmd = xmalloc(lcmd_len + 1); - } - memcpy(lcmd, DOSTART, sizeof(DOSTART) - 1); - memcpy(lcmd + sizeof(DOSTART) - 1, str->data, str->size); - memcpy(lcmd + sizeof(DOSTART) - 1 + str->size, DOEND, sizeof(DOEND) - 1); -#undef DOSTART -#undef DOEND - - if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { - nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s")); - if (lcmd_len >= IOSIZE) { - xfree(lcmd); - } - return 0; - } - if (lcmd_len >= IOSIZE) { - xfree(lcmd); - } - if (lua_pcall(lstate, 0, 1, 0)) { - nlua_error(lstate, _("E5110: Error while creating lua function: %.*s")); - return 0; - } - for (linenr_T l = range[0]; l <= range[1]; l++) { - if (l > curbuf->b_ml.ml_line_count) { - break; - } - lua_pushvalue(lstate, -1); - lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false)); - lua_pushnumber(lstate, (lua_Number)l); - if (lua_pcall(lstate, 2, 1, 0)) { - nlua_error(lstate, _("E5111: Error while calling lua function: %.*s")); - break; - } - if (lua_isstring(lstate, -1)) { - size_t new_line_len; - const char *const new_line = lua_tolstring(lstate, -1, &new_line_len); - char *const new_line_transformed = xmemdupz(new_line, new_line_len); - for (size_t i = 0; i < new_line_len; i++) { - if (new_line_transformed[i] == NUL) { - new_line_transformed[i] = '\n'; - } - } - ml_replace(l, (char_u *)new_line_transformed, false); - changed_bytes(l, 0); - } - lua_pop(lstate, 1); - } - lua_pop(lstate, 1); - check_cursor(); - update_screen(NOT_VALID); - return 0; -} - -/// Evaluate lua file -/// -/// Expects one value on the stack: file to evaluate. Always returns nothing -/// (from the lua point of view). -static int nlua_exec_lua_file(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL -{ - const char *const filename = (const char *)lua_touserdata(lstate, 1); - lua_pop(lstate, 1); - - if (luaL_loadfile(lstate, filename)) { - nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s")); - return 0; - } - if (lua_pcall(lstate, 0, 0, 0)) { - nlua_error(lstate, _("E5113: Error while calling lua chunk: %.*s")); - return 0; - } - return 0; -} - /// Initialize lua interpreter state /// /// Called by lua interpreter itself to initialize state. @@ -327,7 +150,7 @@ static lua_State *nlua_init(void) preserve_exit(); } luaL_openlibs(lstate); - NLUA_CALL_C_FUNCTION_0(lstate, nlua_state_init, 0); + nlua_state_init(lstate); return lstate; } @@ -378,108 +201,18 @@ static lua_State *nlua_enter(void) void executor_exec_lua(const String str, typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { - NLUA_CALL_C_FUNCTION_2(nlua_enter(), nlua_exec_lua_string, 0, - (void *)&str, ret_tv); -} + lua_State *const lstate = nlua_enter(); -/// Evaluate lua string -/// -/// Used for luaeval(). Expects three values on the stack: -/// -/// 1. String to evaluate. -/// 2. _A value. -/// 3. Pointer to location where result is saved. -/// -/// @param[in,out] lstate Lua interpreter state. -static int nlua_eval_lua_string(lua_State *const lstate) - FUNC_ATTR_NONNULL_ALL -{ - const String *const str = (const String *)lua_touserdata(lstate, 1); - typval_T *const arg = (typval_T *)lua_touserdata(lstate, 2); - typval_T *const ret_tv = (typval_T *)lua_touserdata(lstate, 3); - lua_pop(lstate, 3); - - garray_T str_ga; - ga_init(&str_ga, 1, 80); -#define EVALHEADER "local _A=select(1,...) return (" - const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str->size + 1; - char *lcmd; - if (lcmd_len < IOSIZE) { - lcmd = (char *)IObuff; - } else { - lcmd = xmalloc(lcmd_len); - } - memcpy(lcmd, EVALHEADER, sizeof(EVALHEADER) - 1); - memcpy(lcmd + sizeof(EVALHEADER) - 1, str->data, str->size); - lcmd[lcmd_len - 1] = ')'; -#undef EVALHEADER - if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { - nlua_error(lstate, - _("E5107: Error while creating lua chunk for luaeval(): %.*s")); - if (lcmd != (char *)IObuff) { - xfree(lcmd); - } - return 0; - } - if (lcmd != (char *)IObuff) { - xfree(lcmd); - } - - if (arg == NULL || arg->v_type == VAR_UNKNOWN) { - lua_pushnil(lstate); - } else { - nlua_push_typval(lstate, arg); - } - if (lua_pcall(lstate, 1, 1, 0)) { - nlua_error(lstate, - _("E5108: Error while calling lua chunk for luaeval(): %.*s")); - return 0; - } - if (!nlua_pop_typval(lstate, ret_tv)) { - return 0; - } - - return 0; -} - -/// Evaluate lua string -/// -/// Expects four values on the stack: string to evaluate, pointer to args array, -/// and locations where result and error are saved, respectively. Always -/// returns nothing (from the lua point of view). -static int nlua_exec_lua_string_api(lua_State *const lstate) - FUNC_ATTR_NONNULL_ALL -{ - const String *str = (const String *)lua_touserdata(lstate, 1); - const Array *args = (const Array *)lua_touserdata(lstate, 2); - Object *retval = (Object *)lua_touserdata(lstate, 3); - Error *err = (Error *)lua_touserdata(lstate, 4); - - lua_pop(lstate, 4); - - if (luaL_loadbuffer(lstate, str->data, str->size, "<nvim>")) { - size_t len; - const char *str = lua_tolstring(lstate, -1, &len); - api_set_error(err, kErrorTypeValidation, - "Error loading lua: %.*s", (int)len, str); - return 0; - } - - for (size_t i = 0; i < args->size; i++) { - nlua_push_Object(lstate, args->items[i]); + if (luaL_loadbuffer(lstate, str.data, str.size, NLUA_EVAL_NAME)) { + nlua_error(lstate, _("E5104: Error while creating lua chunk: %.*s")); + return; } - - if (lua_pcall(lstate, (int)args->size, 1, 0)) { - size_t len; - const char *str = lua_tolstring(lstate, -1, &len); - api_set_error(err, kErrorTypeException, - "Error executing lua: %.*s", (int)len, str); - return 0; + if (lua_pcall(lstate, 0, 1, 0)) { + nlua_error(lstate, _("E5105: Error while calling lua chunk: %.*s")); + return; } - *retval = nlua_pop_Object(lstate, err); - - return 0; + nlua_pop_typval(lstate, ret_tv); } /// Print as a Vim message @@ -617,8 +350,46 @@ void executor_eval_lua(const String str, typval_T *const arg, typval_T *const ret_tv) FUNC_ATTR_NONNULL_ALL { - NLUA_CALL_C_FUNCTION_3(nlua_enter(), nlua_eval_lua_string, 0, - (void *)&str, arg, ret_tv); + lua_State *const lstate = nlua_enter(); + + garray_T str_ga; + ga_init(&str_ga, 1, 80); +#define EVALHEADER "local _A=select(1,...) return (" + const size_t lcmd_len = sizeof(EVALHEADER) - 1 + str.size + 1; + char *lcmd; + if (lcmd_len < IOSIZE) { + lcmd = (char *)IObuff; + } else { + lcmd = xmalloc(lcmd_len); + } + memcpy(lcmd, EVALHEADER, sizeof(EVALHEADER) - 1); + memcpy(lcmd + sizeof(EVALHEADER) - 1, str.data, str.size); + lcmd[lcmd_len - 1] = ')'; +#undef EVALHEADER + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { + nlua_error(lstate, + _("E5107: Error while creating lua chunk for luaeval(): %.*s")); + if (lcmd != (char *)IObuff) { + xfree(lcmd); + } + return; + } + if (lcmd != (char *)IObuff) { + xfree(lcmd); + } + + if (arg->v_type == VAR_UNKNOWN) { + lua_pushnil(lstate); + } else { + nlua_push_typval(lstate, arg); + } + if (lua_pcall(lstate, 1, 1, 0)) { + nlua_error(lstate, + _("E5108: Error while calling lua chunk for luaeval(): %.*s")); + return; + } + + nlua_pop_typval(lstate, ret_tv); } /// Execute lua string @@ -632,10 +403,29 @@ void executor_eval_lua(const String str, typval_T *const arg, /// @return Return value of the execution. Object executor_exec_lua_api(const String str, const Array args, Error *err) { - Object retval = NIL; - NLUA_CALL_C_FUNCTION_4(nlua_enter(), nlua_exec_lua_string_api, 0, - (void *)&str, (void *)&args, &retval, err); - return retval; + lua_State *const lstate = nlua_enter(); + + if (luaL_loadbuffer(lstate, str.data, str.size, "<nvim>")) { + size_t len; + const char *errstr = lua_tolstring(lstate, -1, &len); + api_set_error(err, kErrorTypeValidation, + "Error loading lua: %.*s", (int)len, errstr); + return NIL; + } + + for (size_t i = 0; i < args.size; i++) { + nlua_push_Object(lstate, args.items[i]); + } + + if (lua_pcall(lstate, (int)args.size, 1, 0)) { + size_t len; + const char *errstr = lua_tolstring(lstate, -1, &len); + api_set_error(err, kErrorTypeException, + "Error executing lua: %.*s", (int)len, errstr); + return NIL; + } + + return nlua_pop_Object(lstate, err); } @@ -671,13 +461,70 @@ void ex_luado(exarg_T *const eap) EMSG(_("cannot save undo information")); return; } - const String cmd = { - .size = STRLEN(eap->arg), - .data = (char *)eap->arg, - }; - const linenr_T range[] = { eap->line1, eap->line2 }; - NLUA_CALL_C_FUNCTION_2(nlua_enter(), nlua_exec_luado_string, 0, - (void *)&cmd, (void *)range); + const char *const cmd = (const char *)eap->arg; + const size_t cmd_len = strlen(cmd); + + lua_State *const lstate = nlua_enter(); + +#define DOSTART "return function(line, linenr) " +#define DOEND " end" + const size_t lcmd_len = (cmd_len + + (sizeof(DOSTART) - 1) + + (sizeof(DOEND) - 1)); + char *lcmd; + if (lcmd_len < IOSIZE) { + lcmd = (char *)IObuff; + } else { + lcmd = xmalloc(lcmd_len + 1); + } + memcpy(lcmd, DOSTART, sizeof(DOSTART) - 1); + memcpy(lcmd + sizeof(DOSTART) - 1, cmd, cmd_len); + memcpy(lcmd + sizeof(DOSTART) - 1 + cmd_len, DOEND, sizeof(DOEND) - 1); +#undef DOSTART +#undef DOEND + + if (luaL_loadbuffer(lstate, lcmd, lcmd_len, NLUA_EVAL_NAME)) { + nlua_error(lstate, _("E5109: Error while creating lua chunk: %.*s")); + if (lcmd_len >= IOSIZE) { + xfree(lcmd); + } + return; + } + if (lcmd_len >= IOSIZE) { + xfree(lcmd); + } + if (lua_pcall(lstate, 0, 1, 0)) { + nlua_error(lstate, _("E5110: Error while creating lua function: %.*s")); + return; + } + for (linenr_T l = eap->line1; l <= eap->line2; l++) { + if (l > curbuf->b_ml.ml_line_count) { + break; + } + lua_pushvalue(lstate, -1); + lua_pushstring(lstate, (const char *)ml_get_buf(curbuf, l, false)); + lua_pushnumber(lstate, (lua_Number)l); + if (lua_pcall(lstate, 2, 1, 0)) { + nlua_error(lstate, _("E5111: Error while calling lua function: %.*s")); + break; + } + if (lua_isstring(lstate, -1)) { + size_t new_line_len; + const char *const new_line = lua_tolstring(lstate, -1, &new_line_len); + char *const new_line_transformed = xmemdupz(new_line, new_line_len); + for (size_t i = 0; i < new_line_len; i++) { + if (new_line_transformed[i] == NUL) { + new_line_transformed[i] = '\n'; + } + } + ml_replace(l, (char_u *)new_line_transformed, false); + changed_bytes(l, 0); + } + lua_pop(lstate, 1); + } + lua_pop(lstate, 1); + check_cursor(); + update_screen(NOT_VALID); } /// Run lua file @@ -688,6 +535,15 @@ void ex_luado(exarg_T *const eap) void ex_luafile(exarg_T *const eap) FUNC_ATTR_NONNULL_ALL { - NLUA_CALL_C_FUNCTION_1(nlua_enter(), nlua_exec_lua_file, 0, - (void *)eap->arg); + lua_State *const lstate = nlua_enter(); + + if (luaL_loadfile(lstate, (const char *)eap->arg)) { + nlua_error(lstate, _("E5112: Error while creating lua chunk: %.*s")); + return; + } + + if (lua_pcall(lstate, 0, 0, 0)) { + nlua_error(lstate, _("E5113: Error while calling lua chunk: %.*s")); + return; + } } diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua index c7952520b0..e1bbb03d3f 100644 --- a/src/nvim/lua/vim.lua +++ b/src/nvim/lua/vim.lua @@ -1,3 +1,62 @@ +-- Internal-only until comments in #8107 are addressed. +-- Returns: +-- {errcode}, {output} +local function _system(cmd) + local out = vim.api.nvim_call_function('system', { cmd }) + local err = vim.api.nvim_get_vvar('shell_error') + return err, out +end + +-- Gets process info from the `ps` command. +-- Used by nvim_get_proc() as a fallback. +local function _os_proc_info(pid) + if pid == nil or pid <= 0 or type(pid) ~= 'number' then + error('invalid pid') + end + local cmd = { 'ps', '-p', pid, '-o', 'ucomm=', } + local err, name = _system(cmd) + if 1 == err and string.gsub(name, '%s*', '') == '' then + return {} -- Process not found. + elseif 0 ~= err then + local args_str = vim.api.nvim_call_function('string', { cmd }) + error('command failed: '..args_str) + end + local _, ppid = _system({ 'ps', '-p', pid, '-o', 'ppid=', }) + -- Remove trailing whitespace. + name = string.gsub(name, '%s+$', '') + ppid = string.gsub(ppid, '%s+$', '') + ppid = tonumber(ppid) == nil and -1 or tonumber(ppid) + return { + name = name, + pid = pid, + ppid = ppid, + } +end + +-- Gets process children from the `pgrep` command. +-- Used by nvim_get_proc_children() as a fallback. +local function _os_proc_children(ppid) + if ppid == nil or ppid <= 0 or type(ppid) ~= 'number' then + error('invalid ppid') + end + local cmd = { 'pgrep', '-P', ppid, } + local err, rv = _system(cmd) + if 1 == err and string.gsub(rv, '%s*', '') == '' then + return {} -- Process not found. + elseif 0 ~= err then + local args_str = vim.api.nvim_call_function('string', { cmd }) + error('command failed: '..args_str) + end + local children = {} + for s in string.gmatch(rv, '%S+') do + local i = tonumber(s) + if i ~= nil then + table.insert(children, i) + end + end + return children +end + -- TODO(ZyX-I): Create compatibility layer. --{{{1 package.path updater function -- Last inserted paths. Used to clear out items from package.[c]path when they @@ -58,7 +117,12 @@ local function _update_package_paths() end last_nvim_paths = cur_nvim_paths end ---{{{1 Module definition -return { + +local module = { _update_package_paths = _update_package_paths, + _os_proc_children = _os_proc_children, + _os_proc_info = _os_proc_info, + _system = _system, } + +return module diff --git a/src/nvim/macros.h b/src/nvim/macros.h index 26d4f74b6a..348df2d9b6 100644 --- a/src/nvim/macros.h +++ b/src/nvim/macros.h @@ -28,15 +28,11 @@ /// @return `s, sizeof(s) - 1` #define S_LEN(s) (s), (sizeof(s) - 1) -/* - * lineempty() - return TRUE if the line is empty - */ -#define lineempty(p) (*ml_get(p) == NUL) +/// LINEEMPTY() - return TRUE if the line is empty +#define LINEEMPTY(p) (*ml_get(p) == NUL) -/* - * bufempty() - return TRUE if the current buffer is empty - */ -#define bufempty() (curbuf->b_ml.ml_line_count == 1 && *ml_get((linenr_T)1) == \ +/// BUFEMPTY() - return TRUE if the current buffer is empty +#define BUFEMPTY() (curbuf->b_ml.ml_line_count == 1 && *ml_get((linenr_T)1) == \ NUL) /* @@ -75,7 +71,7 @@ do { \ if (*p_langmap \ && (condition) \ - && (p_lrm || KeyTyped) \ + && (p_lrm || (vgetc_busy ? typebuf_maplen() == 0 : KeyTyped)) \ && !KeyStuffed \ && (c) >= 0) \ { \ @@ -140,15 +136,24 @@ # define RESET_BINDING(wp) (wp)->w_p_scb = FALSE; (wp)->w_p_crb = FALSE -/// Calculate the length of a C array. +/// Calculate the length of a C array +/// +/// This should be called with a real array. Calling this with a pointer is an +/// error. A mechanism to detect many (though not all) of those errors at +/// compile time is implemented. It works by the second division producing +/// a division by zero in those cases (-Wdiv-by-zero in GCC). +#define ARRAY_SIZE(arr) \ + ((sizeof(arr)/sizeof((arr)[0])) \ + / ((size_t)(!(sizeof(arr) % sizeof((arr)[0]))))) + +/// Get last array entry /// /// This should be called with a real array. Calling this with a pointer is an -/// error. A mechanism to detect many (though not all) of those errors at compile -/// time is implemented. It works by the second division producing a division by -/// zero in those cases (-Wdiv-by-zero in GCC). -#define ARRAY_SIZE(arr) ((sizeof(arr)/sizeof((arr)[0])) / ((size_t)(!(sizeof(arr) % sizeof((arr)[0]))))) +/// error. +#define ARRAY_LAST_ENTRY(arr) (arr)[ARRAY_SIZE(arr) - 1] -#define RGB(r, g, b) ((r << 16) | (g << 8) | b) +// Duplicated in os/win_defs.h to avoid include-order sensitivity. +#define RGB_(r, g, b) ((r << 16) | (g << 8) | b) #define STR_(x) #x #define STR(x) STR_(x) @@ -183,4 +188,19 @@ /// @return ((Type *)obj). #define STRUCT_CAST(Type, obj) ((Type *)(obj)) +// Type of uv_buf_t.len is platform-dependent. +// Related: https://github.com/libuv/libuv/pull/1236 +#if defined(WIN32) +# define UV_BUF_LEN(x) (ULONG)(x) +#else +# define UV_BUF_LEN(x) (x) +#endif + +// Type of read()/write() `count` param is platform-dependent. +#if defined(WIN32) +# define IO_COUNT(x) (unsigned)(x) +#else +# define IO_COUNT(x) (x) +#endif + #endif // NVIM_MACROS_H diff --git a/src/nvim/main.c b/src/nvim/main.c index 6e2359dcab..067184c8c7 100644 --- a/src/nvim/main.c +++ b/src/nvim/main.c @@ -7,11 +7,6 @@ #include <string.h> #include <stdbool.h> -#ifdef WIN32 -# include <wchar.h> -# include <winnls.h> -#endif - #include <msgpack.h> #include "nvim/ascii.h" @@ -77,30 +72,30 @@ # include "nvim/os/pty_process_unix.h" #endif -/* Maximum number of commands from + or -c arguments. */ +// Maximum number of commands from + or -c arguments. #define MAX_ARG_CMDS 10 -/* values for "window_layout" */ -#define WIN_HOR 1 /* "-o" horizontally split windows */ -#define WIN_VER 2 /* "-O" vertically split windows */ -#define WIN_TABS 3 /* "-p" windows on tab pages */ +// values for "window_layout" +#define WIN_HOR 1 // "-o" horizontally split windows +#define WIN_VER 2 // "-O" vertically split windows +#define WIN_TABS 3 // "-p" windows on tab pages -/* Struct for various parameters passed between main() and other functions. */ +// Struct for various parameters passed between main() and other functions. typedef struct { int argc; char **argv; char *use_vimrc; // vimrc from -u argument - int n_commands; /* no. of commands from + or -c */ + int n_commands; // no. of commands from + or -c char *commands[MAX_ARG_CMDS]; // commands from + or -c arg - char_u cmds_tofree[MAX_ARG_CMDS]; /* commands that need free() */ - int n_pre_commands; /* no. of commands from --cmd */ + char_u cmds_tofree[MAX_ARG_CMDS]; // commands that need free() + int n_pre_commands; // no. of commands from --cmd char *pre_commands[MAX_ARG_CMDS]; // commands from --cmd argument - int edit_type; /* type of editing to do */ - char_u *tagname; /* tag from -t argument */ - char_u *use_ef; /* 'errorfile' from -q argument */ + int edit_type; // type of editing to do + char_u *tagname; // tag from -t argument + char_u *use_ef; // 'errorfile' from -q argument int want_full_screen; bool input_isatty; // stdin is a terminal @@ -108,13 +103,15 @@ typedef struct { bool err_isatty; // stderr is a terminal int no_swap_file; // "-n" argument used int use_debug_break_level; - int window_count; /* number of windows to use */ - int window_layout; /* 0, WIN_HOR, WIN_VER or WIN_TABS */ + int window_count; // number of windows to use + int window_layout; // 0, WIN_HOR, WIN_VER or WIN_TABS #if !defined(UNIX) - int literal; /* don't expand file names */ + int literal; // don't expand file names #endif - int diff_mode; /* start with 'diff' set */ + int diff_mode; // start with 'diff' set + + char *listen_addr; // --listen {address} } mparm_T; /* Values for edit_type. */ @@ -155,7 +152,6 @@ void event_init(void) signal_init(); // finish mspgack-rpc initialization channel_init(); - server_init(); terminal_init(); } @@ -229,7 +225,7 @@ int main(int argc, char **argv) #endif { #if defined(WIN32) && !defined(MAKE_LIB) - char *argv[argc]; + char **argv = xmalloc((size_t)argc * sizeof(char *)); for (int i = 0; i < argc; i++) { char *buf = NULL; utf16_to_utf8(argv_w[i], &buf); @@ -246,9 +242,8 @@ int main(int argc, char **argv) char_u *cwd = NULL; // current workding dir on startup time_init(); - /* Many variables are in "params" so that we can pass them to invoked - * functions without a lot of arguments. "argc" and "argv" are also - * copied, so that they can be changed. */ + // Many variables are in `params` so that we can pass them around easily. + // `argc` and `argv` are also copied, so that they can be changed. init_params(¶ms, argc, argv); init_startuptime(¶ms); @@ -259,11 +254,10 @@ int main(int argc, char **argv) check_and_set_isatty(¶ms); event_init(); - /* - * Process the command line arguments. File names are put in the global - * argument list "global_alist". - */ + // Process the command line arguments. File names are put in the global + // argument list "global_alist". command_line_scan(¶ms); + server_init(params.listen_addr); if (GARGCOUNT > 0) { fname = get_fname(¶ms, cwd); @@ -412,7 +406,7 @@ int main(int argc, char **argv) } // It's better to make v:oldfiles an empty list than NULL. if (get_vim_var_list(VV_OLDFILES) == NULL) { - set_vim_var_list(VV_OLDFILES, tv_list_alloc()); + set_vim_var_list(VV_OLDFILES, tv_list_alloc(0)); } /* @@ -571,11 +565,15 @@ int main(int argc, char **argv) */ normal_enter(false, false); +#if defined(WIN32) && !defined(MAKE_LIB) + xfree(argv); +#endif return 0; } -/* Exit properly */ +/// Exit properly void getout(int exitval) + FUNC_ATTR_NORETURN { tabpage_T *tp, *next_tp; @@ -791,7 +789,7 @@ static void command_line_scan(mparm_T *parmp) mch_exit(0); } else if (STRICMP(argv[0] + argv_idx, "api-info") == 0) { FileDescriptor fp; - const int fof_ret = file_open_fd(&fp, OS_STDOUT_FILENO, + const int fof_ret = file_open_fd(&fp, STDOUT_FILENO, kFileWriteOnly); msgpack_packer *p = msgpack_packer_new(&fp, msgpack_file_write); @@ -821,6 +819,9 @@ static void command_line_scan(mparm_T *parmp) if (!channel_from_stdio(true, CALLBACK_READER_INIT, &err)) { abort(); } + } else if (STRNICMP(argv[0] + argv_idx, "listen", 6) == 0) { + want_argument = true; + argv_idx += 6; } else if (STRNICMP(argv[0] + argv_idx, "literal", 7) == 0) { #if !defined(UNIX) parmp->literal = TRUE; @@ -866,10 +867,6 @@ static void command_line_scan(mparm_T *parmp) case 'f': /* "-f" GUI: run in foreground. */ break; - case 'g': /* "-g" start GUI */ - main_start_gui(); - break; - case 'F': { // "-F" start in Farsi mode: rl + fkmap set. p_fkmap = true; set_option_value("rl", 1L, NULL, 0); @@ -908,18 +905,8 @@ static void command_line_scan(mparm_T *parmp) parmp->no_swap_file = TRUE; break; - case 'p': /* "-p[N]" open N tab pages */ -#ifdef TARGET_API_MAC_OSX - /* For some reason on MacOS X, an argument like: - -psn_0_10223617 is passed in when invoke from Finder - or with the 'open' command */ - if (argv[0][argv_idx] == 's') { - argv_idx = -1; /* bypass full -psn */ - main_start_gui(); - break; - } -#endif - /* default is 0: open window for each file */ + case 'p': // "-p[N]" open N tab pages + // default is 0: open window for each file parmp->window_count = get_number_arg(argv[0], &argv_idx, 0); parmp->window_layout = WIN_TABS; break; @@ -1032,15 +1019,12 @@ static void command_line_scan(mparm_T *parmp) mainerr(err_opt_unknown, argv[0]); } - /* - * Handle option arguments with argument. - */ + // Handle option arguments with argument. if (want_argument) { - /* - * Check for garbage immediately after the option letter. - */ - if (argv[0][argv_idx] != NUL) + // Check for garbage immediately after the option letter. + if (argv[0][argv_idx] != NUL) { mainerr(err_opt_garbage, argv[0]); + } --argc; if (argc < 1 && c != 'S') /* -S has an optional argument */ @@ -1079,13 +1063,17 @@ static void command_line_scan(mparm_T *parmp) break; case '-': - if (argv[-1][2] == 'c') { - /* "--cmd {command}" execute command */ - if (parmp->n_pre_commands >= MAX_ARG_CMDS) + if (strequal(argv[-1], "--cmd")) { + // "--cmd {command}" execute command + if (parmp->n_pre_commands >= MAX_ARG_CMDS) { mainerr(err_extra_cmd, NULL); + } parmp->pre_commands[parmp->n_pre_commands++] = argv[0]; + } else if (strequal(argv[-1], "--listen")) { + // "--listen {address}" + parmp->listen_addr = argv[0]; } - /* "--startuptime <file>" already handled */ + // "--startuptime <file>" already handled break; case 'q': /* "-q {errorfile}" QuickFix mode */ @@ -1107,7 +1095,7 @@ scripterror: } int error; if (STRCMP(argv[0], "-") == 0) { - const int stdin_dup_fd = os_dup(OS_STDIN_FILENO); + const int stdin_dup_fd = os_dup(STDIN_FILENO); FileDescriptor *const stdin_dup = file_open_fd_new( &error, stdin_dup_fd, kFileReadOnly|kFileNonBlocking); assert(stdin_dup != NULL); @@ -1234,11 +1222,10 @@ static void init_params(mparm_T *paramp, int argc, char **argv) paramp->want_full_screen = true; paramp->use_debug_break_level = -1; paramp->window_count = -1; + paramp->listen_addr = NULL; } -/* - * Initialize global startuptime file if "--startuptime" passed as an argument. - */ +/// Initialize global startuptime file if "--startuptime" passed as an argument. static void init_startuptime(mparm_T *paramp) { for (int i = 1; i < paramp->argc; i++) { @@ -1259,12 +1246,12 @@ static void check_and_set_isatty(mparm_T *paramp) stdout_isatty = paramp->output_isatty = os_isatty(fileno(stdout)); paramp->err_isatty = os_isatty(fileno(stderr)); +#ifndef WIN32 int tty_fd = paramp->input_isatty - ? OS_STDIN_FILENO + ? STDIN_FILENO : (paramp->output_isatty - ? OS_STDOUT_FILENO - : (paramp->err_isatty ? OS_STDERR_FILENO : -1)); -#ifndef WIN32 + ? STDOUT_FILENO + : (paramp->err_isatty ? STDERR_FILENO : -1)); pty_process_save_termios(tty_fd); #endif TIME_MSG("window checked"); @@ -1378,7 +1365,7 @@ static void handle_quickfix(mparm_T *paramp) set_string_option_direct((char_u *)"ef", -1, paramp->use_ef, OPT_FREE, SID_CARG); vim_snprintf((char *)IObuff, IOSIZE, "cfile %s", p_ef); - if (qf_init(NULL, p_ef, p_efm, true, IObuff) < 0) { + if (qf_init(NULL, p_ef, p_efm, true, IObuff, p_menc) < 0) { ui_linefeed(); mch_exit(3); } @@ -1844,17 +1831,6 @@ static void source_startup_scripts(const mparm_T *const parmp) TIME_MSG("sourcing vimrc file(s)"); } -/* - * Setup to start using the GUI. Exit with an error when not available. - */ -static void main_start_gui(void) -{ - mch_errmsg(_(e_nogvim)); - mch_errmsg("\n"); - mch_exit(2); -} - - /// Get an environment variable, and execute it as Ex commands. /// /// @param env environment variable to execute @@ -1978,6 +1954,7 @@ static void usage(void) mch_msg(_(" --api-info Write msgpack-encoded API metadata to stdout\n")); mch_msg(_(" --embed Use stdin/stdout as a msgpack-rpc channel\n")); mch_msg(_(" --headless Don't start a user interface\n")); + mch_msg(_(" --listen <address> Start RPC server at this address\n")); #if !defined(UNIX) mch_msg(_(" --literal Don't expand wildcards\n")); #endif diff --git a/src/nvim/map.h b/src/nvim/map.h index 047aa163ce..ac1239a548 100644 --- a/src/nvim/map.h +++ b/src/nvim/map.h @@ -8,6 +8,11 @@ #include "nvim/api/private/dispatch.h" #include "nvim/bufhl_defs.h" +#if defined(__NetBSD__) +# undef uint64_t +# define uint64_t uint64_t +#endif + #define MAP_DECLS(T, U) \ KHASH_DECLARE(T##_##U##_map, T, U) \ \ diff --git a/src/nvim/mark.c b/src/nvim/mark.c index 1ba400972c..3cd26a5bf7 100644 --- a/src/nvim/mark.c +++ b/src/nvim/mark.c @@ -171,6 +171,10 @@ void setpcmark(void) curwin->w_prev_pcmark = curwin->w_pcmark; curwin->w_pcmark = curwin->w_cursor; + if (curwin->w_pcmark.lnum == 0) { + curwin->w_pcmark.lnum = 1; + } + /* If jumplist is full: remove oldest entry */ if (++curwin->w_jumplistlen > JUMPLISTSIZE) { curwin->w_jumplistlen = JUMPLISTSIZE; diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c index b24770a409..008bce6df6 100644 --- a/src/nvim/mbyte.c +++ b/src/nvim/mbyte.c @@ -72,19 +72,49 @@ struct interval { # include "unicode_tables.generated.h" #endif -/* - * Like utf8len_tab above, but using a zero for illegal lead bytes. - */ -const uint8_t utf8len_tab_zero[256] = -{ - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, - 3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,4,4,4,4,4,4,4,4,5,5,5,5,6,6,0,0, +// To speed up BYTELEN(); keep a lookup table to quickly get the length in +// bytes of a UTF-8 character from the first byte of a UTF-8 string. Bytes +// which are illegal when used as the first byte have a 1. The NUL byte has +// length 1. +const uint8_t utf8len_tab[] = { + // ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 8? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 9? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // A? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // B? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D? + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E? + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 1, 1, // F? +}; + +// Like utf8len_tab above, but using a zero for illegal lead bytes. +const uint8_t utf8len_tab_zero[] = { + // ?1 ?2 ?3 ?4 ?5 ?6 ?7 ?8 ?9 ?A ?B ?C ?D ?E ?F + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 1? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 2? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 3? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 4? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 5? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 6? + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 7? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 8? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 9? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // A? + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // B? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // C? + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // D? + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // E? + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 0, 0, // F? }; /* @@ -528,45 +558,52 @@ int utf_off2cells(unsigned off, unsigned max_off) return (off + 1 < max_off && ScreenLines[off + 1] == 0) ? 2 : 1; } -/* - * Convert a UTF-8 byte sequence to a wide character. - * If the sequence is illegal or truncated by a NUL the first byte is - * returned. - * Does not include composing characters, of course. - */ -int utf_ptr2char(const char_u *p) +/// Convert a UTF-8 byte sequence to a wide character +/// +/// If the sequence is illegal or truncated by a NUL then the first byte is +/// returned. Does not include composing characters for obvious reasons. +/// +/// @param[in] p String to convert. +/// +/// @return Unicode codepoint or byte value. +int utf_ptr2char(const char_u *const p) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT { - uint8_t len; - - if (p[0] < 0x80) /* be quick for ASCII */ + if (p[0] < 0x80) { // Be quick for ASCII. return p[0]; + } - len = utf8len_tab_zero[p[0]]; + const uint8_t len = utf8len_tab_zero[p[0]]; if (len > 1 && (p[1] & 0xc0) == 0x80) { - if (len == 2) + if (len == 2) { return ((p[0] & 0x1f) << 6) + (p[1] & 0x3f); + } if ((p[2] & 0xc0) == 0x80) { - if (len == 3) - return ((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) - + (p[2] & 0x3f); + if (len == 3) { + return (((p[0] & 0x0f) << 12) + ((p[1] & 0x3f) << 6) + + (p[2] & 0x3f)); + } if ((p[3] & 0xc0) == 0x80) { - if (len == 4) - return ((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) - + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f); + if (len == 4) { + return (((p[0] & 0x07) << 18) + ((p[1] & 0x3f) << 12) + + ((p[2] & 0x3f) << 6) + (p[3] & 0x3f)); + } if ((p[4] & 0xc0) == 0x80) { - if (len == 5) - return ((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18) - + ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6) - + (p[4] & 0x3f); - if ((p[5] & 0xc0) == 0x80 && len == 6) - return ((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24) - + ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12) - + ((p[4] & 0x3f) << 6) + (p[5] & 0x3f); + if (len == 5) { + return (((p[0] & 0x03) << 24) + ((p[1] & 0x3f) << 18) + + ((p[2] & 0x3f) << 12) + ((p[3] & 0x3f) << 6) + + (p[4] & 0x3f)); + } + if ((p[5] & 0xc0) == 0x80 && len == 6) { + return (((p[0] & 0x01) << 30) + ((p[1] & 0x3f) << 24) + + ((p[2] & 0x3f) << 18) + ((p[3] & 0x3f) << 12) + + ((p[4] & 0x3f) << 6) + (p[5] & 0x3f)); + } } } } } - /* Illegal value, just return the first byte */ + // Illegal value: just return the first byte. return p[0]; } @@ -767,23 +804,24 @@ int utfc_char2bytes(int off, char_u *buf) return len; } -/* - * Get the length of a UTF-8 byte sequence, not including any following - * composing characters. - * Returns 0 for "". - * Returns 1 for an illegal byte sequence. - */ -int utf_ptr2len(const char_u *p) +/// Get the length of a UTF-8 byte sequence representing a single codepoint +/// +/// @param[in] p UTF-8 string. +/// +/// @return Sequence length, 0 for empty string and 1 for non-UTF-8 byte +/// sequence. +int utf_ptr2len(const char_u *const p) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int len; - int i; - - if (*p == NUL) + if (*p == NUL) { return 0; - len = utf8len_tab[*p]; - for (i = 1; i < len; ++i) - if ((p[i] & 0xc0) != 0x80) + } + const int len = utf8len_tab[*p]; + for (int i = 1; i < len; i++) { + if ((p[i] & 0xc0) != 0x80) { return 1; + } + } return len; } @@ -824,38 +862,38 @@ int utf_ptr2len_len(const char_u *p, int size) return len; } -/* - * Return the number of bytes the UTF-8 encoding of the character at "p" takes. - * This includes following composing characters. - */ -int utfc_ptr2len(const char_u *p) +/// Return the number of bytes occupied by a UTF-8 character in a string +/// +/// This includes following composing characters. +int utfc_ptr2len(const char_u *const p) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL { - int len; - int b0 = *p; - int prevlen; + uint8_t b0 = (uint8_t)(*p); - if (b0 == NUL) + if (b0 == NUL) { return 0; - if (b0 < 0x80 && p[1] < 0x80) /* be quick for ASCII */ + } + if (b0 < 0x80 && p[1] < 0x80) { // be quick for ASCII return 1; + } - /* Skip over first UTF-8 char, stopping at a NUL byte. */ - len = utf_ptr2len(p); + // Skip over first UTF-8 char, stopping at a NUL byte. + int len = utf_ptr2len(p); - /* Check for illegal byte. */ - if (len == 1 && b0 >= 0x80) + // Check for illegal byte. + if (len == 1 && b0 >= 0x80) { return 1; + } - /* - * Check for composing characters. We can handle only the first six, but - * skip all of them (otherwise the cursor would get stuck). - */ - prevlen = 0; - for (;; ) { - if (p[len] < 0x80 || !UTF_COMPOSINGLIKE(p + prevlen, p + len)) + // Check for composing characters. We can handle only the first six, but + // skip all of them (otherwise the cursor would get stuck). + int prevlen = 0; + for (;;) { + if (p[len] < 0x80 || !UTF_COMPOSINGLIKE(p + prevlen, p + len)) { return len; + } - /* Skip over composing char */ + // Skip over composing char. prevlen = len; len += utf_ptr2len(p + len); } @@ -913,23 +951,22 @@ int utfc_ptr2len_len(const char_u *p, int size) return len; } -/* - * Return the number of bytes the UTF-8 encoding of character "c" takes. - * This does not include composing characters. - */ -int utf_char2len(int c) +/// Determine how many bytes certain unicode codepoint will occupy +int utf_char2len(const int c) { - if (c < 0x80) + if (c < 0x80) { return 1; - if (c < 0x800) + } else if (c < 0x800) { return 2; - if (c < 0x10000) + } else if (c < 0x10000) { return 3; - if (c < 0x200000) + } else if (c < 0x200000) { return 4; - if (c < 0x4000000) + } else if (c < 0x4000000) { return 5; - return 6; + } else { + return 6; + } } /// Convert Unicode character to UTF-8 string @@ -937,46 +974,42 @@ int utf_char2len(int c) /// @param c character to convert to \p buf /// @param[out] buf UTF-8 string generated from \p c, does not add \0 /// @return Number of bytes (1-6). Does not include composing characters. -int utf_char2bytes(int c, char_u *const buf) +int utf_char2bytes(const int c, char_u *const buf) { - if (c < 0x80) { /* 7 bits */ + if (c < 0x80) { // 7 bits buf[0] = c; return 1; - } - if (c < 0x800) { /* 11 bits */ + } else if (c < 0x800) { // 11 bits buf[0] = 0xc0 + ((unsigned)c >> 6); buf[1] = 0x80 + (c & 0x3f); return 2; - } - if (c < 0x10000) { /* 16 bits */ + } else if (c < 0x10000) { // 16 bits buf[0] = 0xe0 + ((unsigned)c >> 12); buf[1] = 0x80 + (((unsigned)c >> 6) & 0x3f); buf[2] = 0x80 + (c & 0x3f); return 3; - } - if (c < 0x200000) { /* 21 bits */ + } else if (c < 0x200000) { // 21 bits buf[0] = 0xf0 + ((unsigned)c >> 18); buf[1] = 0x80 + (((unsigned)c >> 12) & 0x3f); buf[2] = 0x80 + (((unsigned)c >> 6) & 0x3f); buf[3] = 0x80 + (c & 0x3f); return 4; - } - if (c < 0x4000000) { /* 26 bits */ + } else if (c < 0x4000000) { // 26 bits buf[0] = 0xf8 + ((unsigned)c >> 24); buf[1] = 0x80 + (((unsigned)c >> 18) & 0x3f); buf[2] = 0x80 + (((unsigned)c >> 12) & 0x3f); buf[3] = 0x80 + (((unsigned)c >> 6) & 0x3f); buf[4] = 0x80 + (c & 0x3f); return 5; + } else { // 31 bits + buf[0] = 0xfc + ((unsigned)c >> 30); + buf[1] = 0x80 + (((unsigned)c >> 24) & 0x3f); + buf[2] = 0x80 + (((unsigned)c >> 18) & 0x3f); + buf[3] = 0x80 + (((unsigned)c >> 12) & 0x3f); + buf[4] = 0x80 + (((unsigned)c >> 6) & 0x3f); + buf[5] = 0x80 + (c & 0x3f); + return 6; } - /* 31 bits */ - buf[0] = 0xfc + ((unsigned)c >> 30); - buf[1] = 0x80 + (((unsigned)c >> 24) & 0x3f); - buf[2] = 0x80 + (((unsigned)c >> 18) & 0x3f); - buf[3] = 0x80 + (((unsigned)c >> 12) & 0x3f); - buf[4] = 0x80 + (((unsigned)c >> 6) & 0x3f); - buf[5] = 0x80 + (c & 0x3f); - return 6; } /* @@ -1513,14 +1546,15 @@ int utf_head_off(const char_u *base, const char_u *p) return (int)(p - q); } -/* - * Copy a character from "*fp" to "*tp" and advance the pointers. - */ -void mb_copy_char(const char_u **fp, char_u **tp) +/// Copy a character, advancing the pointers +/// +/// @param[in,out] fp Source of the character to copy. +/// @param[in,out] tp Destination to copy to. +void mb_copy_char(const char_u **const fp, char_u **const tp) { - int l = (*mb_ptr2len)(*fp); + const size_t l = (size_t)utfc_ptr2len(*fp); - memmove(*tp, *fp, (size_t)l); + memmove(*tp, *fp, l); *tp += l; *fp += l; } @@ -2262,9 +2296,7 @@ int convert_setup_ext(vimconv_T *vcp, char_u *from, bool from_unicode_is_utf8, if (vcp->vc_type == CONV_ICONV && vcp->vc_fd != (iconv_t)-1) iconv_close(vcp->vc_fd); # endif - vcp->vc_type = CONV_NONE; - vcp->vc_factor = 1; - vcp->vc_fail = false; + *vcp = (vimconv_T)MBYTE_NONE_CONV; /* No conversion when one of the names is empty or they are equal. */ if (from == NULL || *from == NUL || to == NULL || *to == NUL diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h index bf6ccb13a5..a5ce1b0a15 100644 --- a/src/nvim/mbyte.h +++ b/src/nvim/mbyte.h @@ -60,6 +60,12 @@ typedef enum { CONV_ICONV = 5, } ConvFlags; +#define MBYTE_NONE_CONV { \ + .vc_type = CONV_NONE, \ + .vc_factor = 1, \ + .vc_fail = false, \ +} + /// Structure used for string conversions typedef struct { int vc_type; ///< Zero or more ConvFlags. @@ -73,6 +79,8 @@ typedef struct { extern const uint8_t utf8len_tab_zero[256]; +extern const uint8_t utf8len_tab[256]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "mbyte.h.generated.h" #endif diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c index 4428dd42aa..4eeba12b87 100644 --- a/src/nvim/memfile.c +++ b/src/nvim/memfile.c @@ -376,8 +376,9 @@ void mf_put(memfile_T *mfp, bhdr_T *hp, bool dirty, bool infile) { unsigned flags = hp->bh_flags; - if ((flags & BH_LOCKED) == 0) - EMSG(_("E293: block was not locked")); + if ((flags & BH_LOCKED) == 0) { + IEMSG(_("E293: block was not locked")); + } flags &= ~BH_LOCKED; if (dirty) { flags |= BH_DIRTY; diff --git a/src/nvim/memline.c b/src/nvim/memline.c index f28a9e60f4..c11ca74f5c 100644 --- a/src/nvim/memline.c +++ b/src/nvim/memline.c @@ -293,7 +293,7 @@ int ml_open(buf_T *buf) */ hp = mf_new(mfp, false, 1); if (hp->bh_bnum != 0) { - EMSG(_("E298: Didn't get block nr 0?")); + IEMSG(_("E298: Didn't get block nr 0?")); goto error; } b0p = hp->bh_data; @@ -335,7 +335,7 @@ int ml_open(buf_T *buf) if ((hp = ml_new_ptr(mfp)) == NULL) goto error; if (hp->bh_bnum != 1) { - EMSG(_("E298: Didn't get block nr 1?")); + IEMSG(_("E298: Didn't get block nr 1?")); goto error; } pp = hp->bh_data; @@ -351,7 +351,7 @@ int ml_open(buf_T *buf) */ hp = ml_new_data(mfp, FALSE, 1); if (hp->bh_bnum != 2) { - EMSG(_("E298: Didn't get block nr 2?")); + IEMSG(_("E298: Didn't get block nr 2?")); goto error; } @@ -635,13 +635,14 @@ static void ml_upd_block0(buf_T *buf, upd_block0_T what) if (mfp == NULL || (hp = mf_get(mfp, 0, 1)) == NULL) return; b0p = hp->bh_data; - if (ml_check_b0_id(b0p) == FAIL) - EMSG(_("E304: ml_upd_block0(): Didn't get block 0??")); - else { - if (what == UB_FNAME) + if (ml_check_b0_id(b0p) == FAIL) { + IEMSG(_("E304: ml_upd_block0(): Didn't get block 0??")); + } else { + if (what == UB_FNAME) { set_b0_fname(b0p, buf); - else /* what == UB_SAME_DIR */ + } else { // what == UB_SAME_DIR set_b0_dir_flag(b0p, buf); + } } mf_put(mfp, hp, true, false); } @@ -1329,10 +1330,14 @@ recover_names ( names[2] = (char_u *)concat_fnames((char *)dir_name, ".sw?", TRUE); num_names = 3; } else { - p = dir_name + STRLEN(dir_name); - if (after_pathsep((char *)dir_name, (char *)p) && p[-1] == p[-2]) { - /* Ends with '//', Use Full path for swap name */ - tail = (char_u *)make_percent_swname((char *)dir_name, (char *)fname_res); + int len = (int)STRLEN(dir_name); + p = dir_name + len; + if (after_pathsep((char *)dir_name, (char *)p) + && len > 1 + && p[-1] == p[-2]) { + // Ends with '//', Use Full path for swap name + tail = (char_u *)make_percent_swname((char *)dir_name, + (char *)fname_res); } else { tail = path_tail(fname_res); tail = (char_u *)concat_fnames((char *)dir_name, (char *)tail, TRUE); @@ -1742,11 +1747,11 @@ ml_get_buf ( if (lnum > buf->b_ml.ml_line_count) { /* invalid line number */ if (recursive == 0) { - /* Avoid giving this message for a recursive call, may happen when - * the GUI redraws part of the text. */ - ++recursive; - EMSGN(_("E315: ml_get: invalid lnum: %" PRId64), lnum); - --recursive; + // Avoid giving this message for a recursive call, may happen when + // the GUI redraws part of the text. + recursive++; + IEMSGN(_("E315: ml_get: invalid lnum: %" PRId64), lnum); + recursive--; } errorret: STRCPY(IObuff, "???"); @@ -1774,11 +1779,11 @@ errorret: */ if ((hp = ml_find_line(buf, lnum, ML_FIND)) == NULL) { if (recursive == 0) { - /* Avoid giving this message for a recursive call, may happen - * when the GUI redraws part of the text. */ - ++recursive; - EMSGN(_("E316: ml_get: cannot find line %" PRId64), lnum); - --recursive; + // Avoid giving this message for a recursive call, may happen + // when the GUI redraws part of the text. + recursive++; + IEMSGN(_("E316: ml_get: cannot find line %" PRId64), lnum); + recursive--; } goto errorret; } @@ -2162,7 +2167,7 @@ ml_append_int ( return FAIL; pp = hp->bh_data; /* must be pointer block */ if (pp->pb_id != PTR_ID) { - EMSG(_("E317: pointer block id wrong 3")); + IEMSG(_("E317: pointer block id wrong 3")); mf_put(mfp, hp, false, false); return FAIL; } @@ -2295,8 +2300,8 @@ ml_append_int ( * Safety check: fallen out of for loop? */ if (stack_idx < 0) { - EMSG(_("E318: Updated too many blocks?")); - buf->b_ml.ml_stack_top = 0; /* invalidate stack */ + IEMSG(_("E318: Updated too many blocks?")); + buf->b_ml.ml_stack_top = 0; // invalidate stack } } @@ -2435,7 +2440,7 @@ static int ml_delete_int(buf_T *buf, linenr_T lnum, int message) return FAIL; pp = hp->bh_data; /* must be pointer block */ if (pp->pb_id != PTR_ID) { - EMSG(_("E317: pointer block id wrong 4")); + IEMSG(_("E317: pointer block id wrong 4")); mf_put(mfp, hp, false, false); return FAIL; } @@ -2630,9 +2635,9 @@ static void ml_flush_line(buf_T *buf) new_line = buf->b_ml.ml_line_ptr; hp = ml_find_line(buf, lnum, ML_FIND); - if (hp == NULL) - EMSGN(_("E320: Cannot find line %" PRId64), lnum); - else { + if (hp == NULL) { + IEMSGN(_("E320: Cannot find line %" PRId64), lnum); + } else { dp = hp->bh_data; idx = lnum - buf->b_ml.ml_locked_low; start = ((dp->db_index[idx]) & DB_INDEX_MASK); @@ -2841,7 +2846,7 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) pp = (PTR_BL *)(dp); /* must be pointer block */ if (pp->pb_id != PTR_ID) { - EMSG(_("E317: pointer block id wrong")); + IEMSG(_("E317: pointer block id wrong")); goto error_block; } @@ -2878,13 +2883,14 @@ static bhdr_T *ml_find_line(buf_T *buf, linenr_T lnum, int action) break; } } - if (idx >= (int)pp->pb_count) { /* past the end: something wrong! */ - if (lnum > buf->b_ml.ml_line_count) - EMSGN(_("E322: line number out of range: %" PRId64 " past the end"), - lnum - buf->b_ml.ml_line_count); + if (idx >= (int)pp->pb_count) { // past the end: something wrong! + if (lnum > buf->b_ml.ml_line_count) { + IEMSGN(_("E322: line number out of range: %" PRId64 " past the end"), + lnum - buf->b_ml.ml_line_count); - else - EMSGN(_("E323: line count wrong in block %" PRId64), bnum); + } else { + IEMSGN(_("E323: line count wrong in block %" PRId64), bnum); + } goto error_block; } if (action == ML_DELETE) { @@ -2960,7 +2966,7 @@ static void ml_lineadd(buf_T *buf, int count) pp = hp->bh_data; /* must be pointer block */ if (pp->pb_id != PTR_ID) { mf_put(mfp, hp, false, false); - EMSG(_("E317: pointer block id wrong 2")); + IEMSG(_("E317: pointer block id wrong 2")); break; } pp->pb_pointer[ip->ip_index].pe_line_count += count; @@ -3014,20 +3020,17 @@ int resolve_symlink(const char_u *fname, char_u *buf) } buf[ret] = NUL; - /* - * Check whether the symlink is relative or absolute. - * If it's relative, build a new path based on the directory - * portion of the filename (if any) and the path the symlink - * points to. - */ - if (path_is_absolute_path(buf)) + // Check whether the symlink is relative or absolute. + // If it's relative, build a new path based on the directory + // portion of the filename (if any) and the path the symlink + // points to. + if (path_is_absolute(buf)) { STRCPY(tmp, buf); - else { - char_u *tail; - - tail = path_tail(tmp); - if (STRLEN(tail) + STRLEN(buf) >= MAXPATHL) + } else { + char_u *tail = path_tail(tmp); + if (STRLEN(tail) + STRLEN(buf) >= MAXPATHL) { return FAIL; + } STRCPY(tail, buf); } } @@ -3052,9 +3055,12 @@ char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name #ifdef HAVE_READLINK char_u fname_buf[MAXPATHL]; #endif + int len = (int)STRLEN(dir_name); - s = dir_name + STRLEN(dir_name); - if (after_pathsep((char *)dir_name, (char *)s) && s[-1] == s[-2]) { /* Ends with '//', Use Full path */ + s = dir_name + len; + if (after_pathsep((char *)dir_name, (char *)s) + && len > 1 + && s[-1] == s[-2]) { // Ends with '//', Use Full path r = NULL; if ((s = (char_u *)make_percent_swname((char *)dir_name, (char *)fname)) != NULL) { r = (char_u *)modname((char *)s, ".swp", FALSE); diff --git a/src/nvim/memory.c b/src/nvim/memory.c index 328b96fd5c..a66ab6a3cc 100644 --- a/src/nvim/memory.c +++ b/src/nvim/memory.c @@ -559,6 +559,7 @@ void time_to_bytes(time_t time_, uint8_t buf[8]) #include "nvim/tag.h" #include "nvim/window.h" #include "nvim/os/os.h" +#include "nvim/eval/typval.h" /* * Free everything that we allocated. @@ -692,6 +693,7 @@ void free_all_mem(void) free_screenlines(); clear_hl_tables(); + list_free_log(); } #endif diff --git a/src/nvim/menu.c b/src/nvim/menu.c index 01c8e94bac..42417f75d5 100644 --- a/src/nvim/menu.c +++ b/src/nvim/menu.c @@ -714,7 +714,7 @@ static dict_T *menu_get_recursive(const vimmenu_T *menu, int modes) } } else { // visit recursively all children - list_T *children_list = tv_list_alloc(); + list_T *const children_list = tv_list_alloc(kListLenMayKnow); for (menu = menu->children; menu != NULL; menu = menu->next) { dict_T *dic = menu_get_recursive(menu, modes); if (tv_dict_len(dict) > 0) { diff --git a/src/nvim/message.c b/src/nvim/message.c index d9b3300758..3a3c9cb167 100644 --- a/src/nvim/message.c +++ b/src/nvim/message.c @@ -487,9 +487,6 @@ int emsg(const char_u *s_) } called_emsg = true; - if (emsg_silent == 0) { - ex_exitval = 1; - } // If "emsg_severe" is TRUE: When an error exception is to be thrown, // prefer this message over previous messages for the same command. @@ -540,6 +537,8 @@ int emsg(const char_u *s_) return true; } + ex_exitval = 1; + // Reset msg_silent, an error causes messages to be switched back on. msg_silent = 0; cmd_silent = FALSE; @@ -583,19 +582,60 @@ void emsg_invreg(int name) /// Print an error message with unknown number of arguments bool emsgf(const char *const fmt, ...) { + bool ret; + + va_list ap; + va_start(ap, fmt); + ret = emsgfv(fmt, ap); + va_end(ap); + + return ret; +} + +/// Print an error message with unknown number of arguments +static bool emsgfv(const char *fmt, va_list ap) +{ static char errbuf[IOSIZE]; if (emsg_not_now()) { return true; } - va_list ap; - va_start(ap, fmt); vim_vsnprintf(errbuf, sizeof(errbuf), fmt, ap, NULL); - va_end(ap); return emsg((const char_u *)errbuf); } +/// Same as emsg(...), but abort on error when ABORT_ON_INTERNAL_ERROR is +/// defined. It is used for internal errors only, so that they can be +/// detected when fuzzing vim. +void iemsg(const char *s) +{ + msg((char_u *)s); +#ifdef ABORT_ON_INTERNAL_ERROR + abort(); +#endif +} + +/// Same as emsgf(...) but abort on error when ABORT_ON_INTERNAL_ERROR is +/// defined. It is used for internal errors only, so that they can be +/// detected when fuzzing vim. +void iemsgf(const char *s, ...) +{ + va_list ap; + va_start(ap, s); + (void)emsgfv(s, ap); + va_end(ap); +#ifdef ABORT_ON_INTERNAL_ERROR + abort(); +#endif +} + +/// Give an "Internal error" message. +void internal_error(char *where) +{ + IEMSG2(_(e_intern2), where); +} + static void msg_emsgf_event(void **argv) { char *s = argv[0]; @@ -1237,31 +1277,30 @@ void msg_make(char_u *arg) } } -/* - * Output the string 'str' upto a NUL character. - * Return the number of characters it takes on the screen. - * - * If K_SPECIAL is encountered, then it is taken in conjunction with the - * following character and shown as <F1>, <S-Up> etc. Any other character - * which is not printable shown in <> form. - * If 'from' is TRUE (lhs of a mapping), a space is shown as <Space>. - * If a character is displayed in one of these special ways, is also - * highlighted (its highlight name is '8' in the p_hl variable). - * Otherwise characters are not highlighted. - * This function is used to show mappings, where we want to see how to type - * the character/string -- webb - */ -int -msg_outtrans_special ( - char_u *strstart, - int from /* TRUE for lhs of a mapping */ +/// Output the string 'str' upto a NUL character. +/// Return the number of characters it takes on the screen. +/// +/// If K_SPECIAL is encountered, then it is taken in conjunction with the +/// following character and shown as <F1>, <S-Up> etc. Any other character +/// which is not printable shown in <> form. +/// If 'from' is TRUE (lhs of a mapping), a space is shown as <Space>. +/// If a character is displayed in one of these special ways, is also +/// highlighted (its highlight name is '8' in the p_hl variable). +/// Otherwise characters are not highlighted. +/// This function is used to show mappings, where we want to see how to type +/// the character/string -- webb +int msg_outtrans_special( + const char_u *strstart, + int from ///< true for LHS of a mapping ) { - char_u *str = strstart; + if (strstart == NULL) { + return 0; // Do nothing. + } + const char_u *str = strstart; int retval = 0; - int attr; + int attr = hl_attr(HLF_8); - attr = hl_attr(HLF_8); while (*str != NUL) { const char *string; // Leading and trailing spaces need to be displayed in <> form. @@ -1307,7 +1346,7 @@ char *str2special_save(const char *const str, const bool replace_spaces, return (char *)ga.ga_data; } -/// Convert character, replacing key one key code with printable representation +/// Convert character, replacing key with printable representation. /// /// @param[in,out] sp String to convert. Is advanced to the next key code. /// @param[in] replace_spaces Convert spaces into <Space>, normally used for @@ -1392,7 +1431,7 @@ void str2specialbuf(const char *sp, char *buf, size_t len) while (*sp) { const char *s = str2special(&sp, false, false); const size_t s_len = strlen(s); - if (s_len <= len) { + if (len <= s_len) { break; } memcpy(buf, s, s_len); @@ -1785,17 +1824,13 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr, } while (msg_col & 7); } else if (*s == BELL) { // beep (from ":sh") vim_beep(BO_SH); - } else { - if (has_mbyte) { - cw = (*mb_ptr2cells)(s); - if (enc_utf8 && maxlen >= 0) - /* avoid including composing chars after the end */ - l = utfc_ptr2len_len(s, (int)((str + maxlen) - s)); - else - l = (*mb_ptr2len)(s); + } else if (*s >= 0x20) { // printable char + cw = mb_ptr2cells(s); + if (maxlen >= 0) { + // avoid including composing chars after the end + l = utfc_ptr2len_len(s, (int)((str + maxlen) - s)); } else { - cw = 1; - l = 1; + l = utfc_ptr2len(s); } // When drawing from right to left or when a double-wide character // doesn't fit, draw a single character here. Otherwise collect @@ -1840,13 +1875,29 @@ bool message_filtered(char_u *msg) return cmdmod.filter_force ? match : !match; } +/// including horizontal separator +int msg_scrollsize(void) +{ + return msg_scrolled + p_ch + 1; +} + /* * Scroll the screen up one line for displaying the next message line. */ static void msg_scroll_up(void) { - /* scrolling up always works */ - screen_del_lines(0, 0, 1, (int)Rows, NULL); + if (dy_flags & DY_MSGSEP) { + if (msg_scrolled == 0) { + screen_fill(Rows-p_ch-1, Rows-p_ch, 0, (int)Columns, + fill_msgsep, fill_msgsep, hl_attr(HLF_MSGSEP)); + } + int nscroll = MIN(msg_scrollsize()+1, Rows); + ui_call_set_scroll_region(Rows-nscroll, Rows-1, 0, Columns-1); + screen_del_lines(Rows-nscroll, 0, 1, nscroll, NULL); + ui_reset_scroll_region(); + } else { + screen_del_lines(0, 0, 1, (int)Rows, NULL); + } } /* diff --git a/src/nvim/message.h b/src/nvim/message.h index 904a9d3ca1..82935a36a9 100644 --- a/src/nvim/message.h +++ b/src/nvim/message.h @@ -49,6 +49,15 @@ /// Like #EMSG, but for messages with one "%" PRIu64 inside #define EMSGU(s, n) emsgf((const char *) (s), (uint64_t)(n)) +/// Like #EMSG, but for internal messages +#define IEMSG(s) iemsg((const char *)(s)) + +/// Like #EMSG2, but for internal messages +#define IEMSG2(s, p) iemsgf((const char *)(s), (p)) + +/// Like #EMSGN, but for internal messages +#define IEMSGN(s, n) iemsgf((const char *)(s), (int64_t)(n)) + /// Display message at the recorded position #define MSG_PUTS(s) msg_puts((const char *)(s)) diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c index f7ee2950ef..b0232b6516 100644 --- a/src/nvim/misc1.c +++ b/src/nvim/misc1.c @@ -749,8 +749,9 @@ open_line ( // 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. - if (curwin->w_cursor.lnum + 1 < curbuf->b_ml.ml_line_count) { + // 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; @@ -1468,7 +1469,7 @@ void ins_char_bytes(char_u *buf, size_t charlen) } } - char_u *newp = (char_u *) xmalloc((size_t)(linelen + newlen - oldlen)); + char_u *newp = xmalloc((size_t)(linelen + newlen - oldlen)); // Copy bytes before the cursor. if (col > 0) { @@ -1477,7 +1478,10 @@ void ins_char_bytes(char_u *buf, size_t charlen) // Copy bytes after the changed character(s). char_u *p = newp + col; - memmove(p + newlen, oldp + col + oldlen, (size_t)(linelen - col - oldlen)); + 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); @@ -1864,8 +1868,8 @@ void appended_lines(linenr_T lnum, long count) 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. - if (lnum + count < curbuf->b_ml.ml_line_count) { + // 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); @@ -2609,13 +2613,12 @@ int match_user(char_u *name) return result; } -/* - * Preserve files and exit. - * When called IObuff must contain a message. - * NOTE: This may be called from deathtrap() in a signal handler, avoid unsafe - * functions, such as allocating memory. - */ +/// Preserve files and exit. +/// @note IObuff must contain a message. +/// @note This may be called from deadly_signal() in a signal handler, avoid +/// unsafe functions, such as allocating memory. void preserve_exit(void) + FUNC_ATTR_NORETURN { // 'true' when we are sure to exit, e.g., after a deadly signal static bool really_exiting = false; diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c index d908a022f1..6f636f643a 100644 --- a/src/nvim/mouse.c +++ b/src/nvim/mouse.c @@ -608,8 +608,7 @@ bool mouse_scroll_horiz(int dir) return leftcol_changed(); } -// Adjust the clicked column position if there are concealed characters -// before the current column. But only when it's absolutely necessary. +/// Adjusts the clicked column position when 'conceallevel' > 0 static int mouse_adjust_click(win_T *wp, int row, int col) { if (!(wp->w_p_cole > 0 && curbuf->b_p_smc > 0 @@ -617,64 +616,102 @@ static int mouse_adjust_click(win_T *wp, int row, int col) return col; } - int end = (colnr_T)STRLEN(ml_get(wp->w_cursor.lnum)); - int vend = getviscol2(end, 0); + // `col` is the position within the current line that is highlighted by the + // cursor without consideration for concealed characters. The current line is + // scanned *up to* `col`, nudging it left or right when concealed characters + // are encountered. + // + // chartabsize() is used to keep track of the virtual column position relative + // to the line's bytes. For example: if col == 9 and the line starts with a + // tab that's 8 columns wide, we would want the cursor to be highlighting the + // second byte, not the ninth. + + linenr_T lnum = wp->w_cursor.lnum; + char_u *line = ml_get(lnum); + char_u *ptr = line; + char_u *ptr_end = line; + char_u *ptr_row_offset = line; // Where we begin adjusting `ptr_end` - if (col >= vend) { - return col; + // Find the offset where scanning should begin. + int offset = wp->w_leftcol; + if (row > 0) { + offset += row * (wp->w_width - win_col_off(wp) - win_col_off2(wp) - + wp->w_leftcol + wp->w_skipcol); } - int i = wp->w_leftcol; + int vcol; + + if (offset) { + // Skip everything up to an offset since nvim takes care of displaying the + // correct portion of the line when horizontally scrolling. + // When 'wrap' is enabled, only the row (of the wrapped line) needs to be + // checked for concealed characters. + vcol = 0; + while (vcol < offset && *ptr != NUL) { + vcol += chartabsize(ptr, vcol); + ptr += utfc_ptr2len(ptr); + } - if (row > 0) { - i += row * (wp->w_width - win_col_off(wp) - win_col_off2(wp) - - wp->w_leftcol) + wp->w_skipcol; + ptr_row_offset = ptr; } - int start_col = i; - int matchid; - int last_matchid; - int bcol = end - (vend - col); + // Align `ptr_end` with `col` + vcol = offset; + ptr_end = ptr_row_offset; + while (vcol < col && *ptr_end != NUL) { + vcol += chartabsize(ptr_end, vcol); + ptr_end += utfc_ptr2len(ptr_end); + } - while (i < bcol) { - matchid = syn_get_concealed_id(wp, wp->w_cursor.lnum, i); + int matchid; + int prev_matchid; + int nudge = 0; + int cwidth = 0; + + vcol = offset; + +#define incr() nudge++; ptr_end += utfc_ptr2len(ptr_end) +#define decr() nudge--; ptr_end -= utfc_ptr2len(ptr_end) + + while (ptr < ptr_end && *ptr != NUL) { + cwidth = chartabsize(ptr, vcol); + vcol += cwidth; + if (cwidth > 1 && *ptr == '\t' && nudge > 0) { + // A tab will "absorb" any previous adjustments. + cwidth = MIN(cwidth, nudge); + while (cwidth > 0) { + decr(); + cwidth--; + } + } + matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line)); if (matchid != 0) { if (wp->w_p_cole == 3) { - bcol++; + incr(); } else { - if (row > 0 && i == start_col) { - // Check if the current concealed character is actually part of - // the previous wrapped row's conceal group. - last_matchid = syn_get_concealed_id(wp, wp->w_cursor.lnum, - i - 1); - if (last_matchid == matchid) { - bcol++; - } - } else if (wp->w_p_cole == 1 - || (wp->w_p_cole == 2 - && (lcs_conceal != NUL - || syn_get_sub_char() != NUL))) { + if (!(row > 0 && ptr == ptr_row_offset) + && (wp->w_p_cole == 1 || (wp->w_p_cole == 2 + && (lcs_conceal != NUL + || syn_get_sub_char() != NUL)))) { // At least one placeholder character will be displayed. - bcol--; + decr(); } - last_matchid = matchid; + prev_matchid = matchid; - // Adjust for concealed text that spans more than one character. - do { - i++; - bcol++; - matchid = syn_get_concealed_id(wp, wp->w_cursor.lnum, i); - } while (last_matchid == matchid); + while (prev_matchid == matchid && *ptr != NUL) { + incr(); + ptr += utfc_ptr2len(ptr); + matchid = syn_get_concealed_id(wp, lnum, (colnr_T)(ptr - line)); + } continue; } } - i++; + ptr += utfc_ptr2len(ptr); } - return getviscol2(bcol, 0); + return col + nudge; } - diff --git a/src/nvim/move.c b/src/nvim/move.c index 134ffcd7dc..cb13a9e207 100644 --- a/src/nvim/move.c +++ b/src/nvim/move.c @@ -155,12 +155,11 @@ void update_topline(void) old_topline = curwin->w_topline; old_topfill = curwin->w_topfill; - /* - * If the buffer is empty, always set topline to 1. - */ - if (bufempty()) { /* special case - file is empty */ - if (curwin->w_topline != 1) + // If the buffer is empty, always set topline to 1. + if (BUFEMPTY()) { // special case - file is empty + if (curwin->w_topline != 1) { redraw_later(NOT_VALID); + } curwin->w_topline = 1; curwin->w_botline = 2; curwin->w_valid |= VALID_BOTLINE|VALID_BOTLINE_AP; @@ -673,7 +672,7 @@ int win_col_off(win_T *wp) return ((wp->w_p_nu || wp->w_p_rnu) ? number_width(wp) + 1 : 0) + (cmdwin_type == 0 || wp != curwin ? 0 : 1) + (int)wp->w_p_fdc - + (signcolumn_on(wp) ? 2 : 0); + + (signcolumn_on(wp) ? win_signcol_width(wp) : 0); } int curwin_col_off(void) diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c index 9bf122f4db..e5d80aea1d 100644 --- a/src/nvim/msgpack_rpc/server.c +++ b/src/nvim/msgpack_rpc/server.c @@ -32,26 +32,27 @@ static garray_T watchers = GA_EMPTY_INIT_VALUE; #endif /// Initializes the module -bool server_init(void) +bool server_init(const char *listen_addr) { ga_init(&watchers, sizeof(SocketWatcher *), 1); - bool must_free = false; - const char *listen_address = os_getenv(LISTEN_ADDRESS_ENV_VAR); - if (listen_address == NULL) { - must_free = true; - listen_address = server_address_new(); - } + // $NVIM_LISTEN_ADDRESS + const char *env_addr = os_getenv(LISTEN_ADDRESS_ENV_VAR); + int rv = listen_addr == NULL ? 1 : server_start(listen_addr); - if (!listen_address) { - return false; + if (0 != rv) { + rv = env_addr == NULL ? 1 : server_start(env_addr); + if (0 != rv) { + listen_addr = server_address_new(); + if (listen_addr == NULL) { + return false; + } + rv = server_start(listen_addr); + xfree((char *)listen_addr); + } } - bool ok = (server_start(listen_address) == 0); - if (must_free) { - xfree((char *) listen_address); - } - return ok; + return rv == 0; } /// Teardown a single server @@ -120,8 +121,8 @@ bool server_owns_pipe_address(const char *path) /// @param endpoint Address of the server. Either a 'ip:[port]' string or an /// arbitrary identifier (trimmed to 256 bytes) for the Unix /// socket or named pipe. -/// @returns 0 on success, 1 on a regular error, and negative errno -/// on failure to bind or listen. +/// @returns 0: success, 1: validation error, 2: already listening, +/// -errno: failed to bind or listen. int server_start(const char *endpoint) { if (endpoint == NULL || endpoint[0] == '\0') { @@ -145,7 +146,7 @@ int server_start(const char *endpoint) uv_freeaddrinfo(watcher->uv.tcp.addrinfo); } socket_watcher_close(watcher, free_server); - return 1; + return 2; } } @@ -177,7 +178,7 @@ int server_start(const char *endpoint) /// Stops listening on the address specified by `endpoint`. /// /// @param endpoint Address of the server. -void server_stop(char *endpoint) +bool server_stop(char *endpoint) { SocketWatcher *watcher; bool watcher_found = false; @@ -196,8 +197,8 @@ void server_stop(char *endpoint) } if (!watcher_found) { - ELOG("Not listening on %s", addr); - return; + WLOG("Not listening on %s", addr); + return false; } // Unset $NVIM_LISTEN_ADDRESS if it is the stopped address. @@ -219,6 +220,8 @@ void server_stop(char *endpoint) if (STRCMP(addr, get_vim_var_str(VV_SEND_SERVER)) == 0) { set_vservername(&watchers); } + + return true; } /// Returns an allocated array of server addresses. diff --git a/src/nvim/normal.c b/src/nvim/normal.c index 1103fe15d2..e4310de5d8 100644 --- a/src/nvim/normal.c +++ b/src/nvim/normal.c @@ -345,6 +345,7 @@ static const struct nv_cmd { { K_F8, farsi_f8, 0, 0 }, { K_F9, farsi_f9, 0, 0 }, { K_EVENT, nv_event, NV_KEEPREG, 0 }, + { K_COMMAND, nv_colon, 0, 0 }, }; /* Number of commands in nv_cmds[]. */ @@ -1473,13 +1474,13 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) AppendToRedobuffLit(cap->searchbuf, -1); } AppendToRedobuff(NL_STR); - } else if (cap->cmdchar == ':') { - /* do_cmdline() has stored the first typed line in - * "repeat_cmdline". When several lines are typed repeating - * won't be possible. */ - if (repeat_cmdline == NULL) + } else if (cap->cmdchar == ':' || cap->cmdchar == K_COMMAND) { + // do_cmdline() has stored the first typed line in + // "repeat_cmdline". When several lines are typed repeating + // won't be possible. + if (repeat_cmdline == NULL) { ResetRedobuff(); - else { + } else { AppendToRedobuffLit(repeat_cmdline, -1); AppendToRedobuff(NL_STR); xfree(repeat_cmdline); @@ -1637,16 +1638,22 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank) /* Prepare for redoing. Only use the nchar field for "r", * otherwise it might be the second char of the operator. */ if (cap->cmdchar == 'g' && (cap->nchar == 'n' - || cap->nchar == 'N')) + || cap->nchar == 'N')) { prep_redo(oap->regname, cap->count0, - get_op_char(oap->op_type), get_extra_op_char(oap->op_type), - oap->motion_force, cap->cmdchar, cap->nchar); - else if (cap->cmdchar != ':') - prep_redo(oap->regname, 0L, NUL, 'v', - get_op_char(oap->op_type), - get_extra_op_char(oap->op_type), - oap->op_type == OP_REPLACE - ? cap->nchar : NUL); + get_op_char(oap->op_type), get_extra_op_char(oap->op_type), + oap->motion_force, cap->cmdchar, cap->nchar); + } else if (cap->cmdchar != ':') { + int nchar = oap->op_type == OP_REPLACE ? cap->nchar : NUL; + + // reverse what nv_replace() did + if (nchar == REPLACE_CR_NCHAR) { + nchar = CAR; + } else if (nchar == REPLACE_NL_NCHAR) { + nchar = NL; + } + prep_redo(oap->regname, 0L, NUL, 'v', get_op_char(oap->op_type), + get_extra_op_char(oap->op_type), nchar); + } if (!redo_VIsual_busy) { redo_VIsual_mode = resel_VIsual_mode; redo_VIsual_vcol = resel_VIsual_vcol; @@ -4518,23 +4525,22 @@ static void nv_exmode(cmdarg_T *cap) } } -/* - * Handle a ":" command. - */ +/// Handle a ":" command and <Cmd>. static void nv_colon(cmdarg_T *cap) { int old_p_im; bool cmd_result; + bool is_cmdkey = cap->cmdchar == K_COMMAND; - if (VIsual_active) + if (VIsual_active && !is_cmdkey) { nv_operator(cap); - else { + } else { if (cap->oap->op_type != OP_NOP) { // Using ":" as a movement is characterwise exclusive. cap->oap->motion_type = kMTCharWise; cap->oap->inclusive = false; - } else if (cap->count0) { - /* translate "count:" into ":.,.+(count - 1)" */ + } else if (cap->count0 && !is_cmdkey) { + // translate "count:" into ":.,.+(count - 1)" stuffcharReadbuff('.'); if (cap->count0 > 1) { stuffReadbuff(",.+"); @@ -4548,9 +4554,9 @@ static void nv_colon(cmdarg_T *cap) old_p_im = p_im; - /* get a command line and execute it */ - cmd_result = do_cmdline(NULL, getexline, NULL, - cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); + // get a command line and execute it + cmd_result = do_cmdline(NULL, is_cmdkey ? getcmdkeycmd : getexline, NULL, + cap->oap->op_type != OP_NOP ? DOCMD_KEEPLINE : 0); /* If 'insertmode' changed, enter or exit Insert mode */ if (p_im != old_p_im) { @@ -5031,26 +5037,21 @@ static void nv_right(cmdarg_T *cap) if ((!PAST_LINE && oneright() == false) || (PAST_LINE && *get_cursor_pos_ptr() == NUL) ) { - /* - * <Space> wraps to next line if 'whichwrap' has 's'. - * 'l' wraps to next line if 'whichwrap' has 'l'. - * CURS_RIGHT wraps to next line if 'whichwrap' has '>'. - */ - if ( ((cap->cmdchar == ' ' - && vim_strchr(p_ww, 's') != NULL) - || (cap->cmdchar == 'l' - && vim_strchr(p_ww, 'l') != NULL) - || (cap->cmdchar == K_RIGHT - && vim_strchr(p_ww, '>') != NULL)) - && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - /* When deleting we also count the NL as a character. - * Set cap->oap->inclusive when last char in the line is - * included, move to next line after that */ - if ( cap->oap->op_type != OP_NOP - && !cap->oap->inclusive - && !lineempty(curwin->w_cursor.lnum)) + // <Space> wraps to next line if 'whichwrap' has 's'. + // 'l' wraps to next line if 'whichwrap' has 'l'. + // CURS_RIGHT wraps to next line if 'whichwrap' has '>'. + if (((cap->cmdchar == ' ' && vim_strchr(p_ww, 's') != NULL) + || (cap->cmdchar == 'l' && vim_strchr(p_ww, 'l') != NULL) + || (cap->cmdchar == K_RIGHT && vim_strchr(p_ww, '>') != NULL)) + && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { + // When deleting we also count the NL as a character. + // Set cap->oap->inclusive when last char in the line is + // included, move to next line after that + if (cap->oap->op_type != OP_NOP + && !cap->oap->inclusive + && !LINEEMPTY(curwin->w_cursor.lnum)) { cap->oap->inclusive = true; - else { + } else { ++curwin->w_cursor.lnum; curwin->w_cursor.col = 0; curwin->w_cursor.coladd = 0; @@ -5060,12 +5061,14 @@ static void nv_right(cmdarg_T *cap) continue; } if (cap->oap->op_type == OP_NOP) { - /* Only beep and flush if not moved at all */ - if (n == cap->count1) + // Only beep and flush if not moved at all + if (n == cap->count1) { beep_flush(); + } } else { - if (!lineempty(curwin->w_cursor.lnum)) + if (!LINEEMPTY(curwin->w_cursor.lnum)) { cap->oap->inclusive = true; + } } break; } else if (PAST_LINE) { @@ -5123,13 +5126,12 @@ static void nv_left(cmdarg_T *cap) coladvance((colnr_T)MAXCOL); curwin->w_set_curswant = true; - /* When the NL before the first char has to be deleted we - * put the cursor on the NUL after the previous line. - * This is a very special case, be careful! - * Don't adjust op_end now, otherwise it won't work. */ - if ( (cap->oap->op_type == OP_DELETE - || cap->oap->op_type == OP_CHANGE) - && !lineempty(curwin->w_cursor.lnum)) { + // When the NL before the first char has to be deleted we + // put the cursor on the NUL after the previous line. + // This is a very special case, be careful! + // Don't adjust op_end now, otherwise it won't work. + if ((cap->oap->op_type == OP_DELETE || cap->oap->op_type == OP_CHANGE) + && !LINEEMPTY(curwin->w_cursor.lnum)) { char_u *cp = get_cursor_pos_ptr(); if (*cp != NUL) { @@ -5228,12 +5230,12 @@ static void nv_gotofile(cmdarg_T *cap) if (ptr != NULL) { // do autowrite if necessary - if (curbufIsChanged() && curbuf->b_nwindows <= 1 && !P_HID(curbuf)) { + if (curbufIsChanged() && curbuf->b_nwindows <= 1 && !buf_hide(curbuf)) { (void)autowrite(curbuf, false); } setpcmark(); (void)do_ecmd(0, ptr, NULL, NULL, ECMD_LAST, - P_HID(curbuf) ? ECMD_HIDE : 0, curwin); + buf_hide(curbuf) ? ECMD_HIDE : 0, curwin); if (cap->nchar == 'F' && lnum >= 0) { curwin->w_cursor.lnum = lnum; check_cursor_lnum(); @@ -5687,6 +5689,8 @@ static void nv_brackets(cmdarg_T *cap) cap->nchar == 's', false, NULL) == 0) { clearopbeep(cap->oap); break; + } else { + curwin->w_set_curswant = true; } if (cap->oap->op_type == OP_NOP && (fdo_flags & FDO_SEARCH) && KeyTyped) foldOpenCursor(); @@ -5856,10 +5860,13 @@ static void nv_replace(cmdarg_T *cap) if (got_int) reset_VIsual(); if (had_ctrl_v) { - if (cap->nchar == '\r') - cap->nchar = -1; - else if (cap->nchar == '\n') - cap->nchar = -2; + // Use a special (negative) number to make a difference between a + // literal CR or NL and a line break. + if (cap->nchar == CAR) { + cap->nchar = REPLACE_CR_NCHAR; + } else if (cap->nchar == NL) { + cap->nchar = REPLACE_NL_NCHAR; + } } nv_operator(cap); return; @@ -6096,10 +6103,11 @@ static void n_swapchar(cmdarg_T *cap) pos_T startpos; int did_change = 0; - if (checkclearopq(cap->oap)) + if (checkclearopq(cap->oap)) { return; + } - if (lineempty(curwin->w_cursor.lnum) && vim_strchr(p_ww, '~') == NULL) { + if (LINEEMPTY(curwin->w_cursor.lnum) && vim_strchr(p_ww, '~') == NULL) { clearopbeep(cap->oap); return; } diff --git a/src/nvim/ops.c b/src/nvim/ops.c index 432c9a8b47..b421d81b7e 100644 --- a/src/nvim/ops.c +++ b/src/nvim/ops.c @@ -1629,13 +1629,18 @@ int op_replace(oparg_T *oap, int c) colnr_T oldlen; struct block_def bd; char_u *after_p = NULL; - int had_ctrl_v_cr = (c == -1 || c == -2); + int had_ctrl_v_cr = false; if ((curbuf->b_ml.ml_flags & ML_EMPTY ) || oap->empty) return OK; /* nothing to do */ - if (had_ctrl_v_cr) - c = (c == -1 ? '\r' : '\n'); + if (c == REPLACE_CR_NCHAR) { + had_ctrl_v_cr = true; + c = CAR; + } else if (c == REPLACE_NL_NCHAR) { + had_ctrl_v_cr = true; + c = NL; + } if (has_mbyte) mb_adjust_opend(oap); @@ -1713,7 +1718,7 @@ int op_replace(oparg_T *oap, int c) // insert pre-spaces memset(newp + bd.textcol, ' ', (size_t)bd.startspaces); // insert replacement chars CHECK FOR ALLOCATED SPACE - // -1/-2 is used for entering CR literally. + // REPLACE_CR_NCHAR/REPLACE_NL_NCHAR is used for entering CR literally. size_t after_p_len = 0; if (had_ctrl_v_cr || (c != '\r' && c != '\n')) { // strlen(newp) at this point @@ -2052,10 +2057,11 @@ void op_insert(oparg_T *oap, long count1) curwin->w_cursor = oap->end; check_cursor_col(); - /* Works just like an 'i'nsert on the next character. */ - if (!lineempty(curwin->w_cursor.lnum) - && oap->start_vcol != oap->end_vcol) + // Works just like an 'i'nsert on the next character. + if (!LINEEMPTY(curwin->w_cursor.lnum) + && oap->start_vcol != oap->end_vcol) { inc_cursor(); + } } } @@ -2180,9 +2186,10 @@ int op_change(oparg_T *oap) } else if (op_delete(oap) == FAIL) return FALSE; - if ((l > curwin->w_cursor.col) && !lineempty(curwin->w_cursor.lnum) - && !virtual_op) + if ((l > curwin->w_cursor.col) && !LINEEMPTY(curwin->w_cursor.lnum) + && !virtual_op) { inc_cursor(); + } // check for still on same line (<CR> in inserted text meaningless) // skip blank lines too @@ -2564,11 +2571,11 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg) dict_T *dict = get_vim_var_dict(VV_EVENT); // the yanked text - list_T *list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, (const char *)reg->y_array[i], -1); } - list->lv_lock = VAR_FIXED; + tv_list_set_lock(list, VAR_FIXED); tv_dict_add_list(dict, S_LEN("regcontents"), list); // the register type @@ -2855,25 +2862,30 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) } } else if (y_type == kMTLineWise) { lnum = curwin->w_cursor.lnum; - /* Correct line number for closed fold. Don't move the cursor yet, - * u_save() uses it. */ - if (dir == BACKWARD) + // Correct line number for closed fold. Don't move the cursor yet, + // u_save() uses it. + if (dir == BACKWARD) { (void)hasFolding(lnum, &lnum, NULL); - else + } else { (void)hasFolding(lnum, NULL, &lnum); - if (dir == FORWARD) - ++lnum; - /* In an empty buffer the empty line is going to be replaced, include - * it in the saved lines. */ - if ((bufempty() ? u_save(0, 2) : u_save(lnum - 1, lnum)) == FAIL) + } + if (dir == FORWARD) { + lnum++; + } + // In an empty buffer the empty line is going to be replaced, include + // it in the saved lines. + if ((BUFEMPTY() ? u_save(0, 2) : u_save(lnum - 1, lnum)) == FAIL) { goto end; - if (dir == FORWARD) + } + if (dir == FORWARD) { curwin->w_cursor.lnum = lnum - 1; - else + } else { curwin->w_cursor.lnum = lnum; - curbuf->b_op_start = curwin->w_cursor; /* for mark_adjust() */ - } else if (u_save_cursor() == FAIL) + } + curbuf->b_op_start = curwin->w_cursor; // for mark_adjust() + } else if (u_save_cursor() == FAIL) { goto end; + } yanklen = (int)STRLEN(y_array[0]); @@ -3066,15 +3078,26 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) --lnum; new_cursor = curwin->w_cursor; - /* - * simple case: insert into current line - */ + // simple case: insert into current line if (y_type == kMTCharWise && y_size == 1) { + linenr_T end_lnum = 0; // init for gcc + + if (VIsual_active) { + end_lnum = curbuf->b_visual.vi_end.lnum; + if (end_lnum < curbuf->b_visual.vi_start.lnum) { + end_lnum = curbuf->b_visual.vi_start.lnum; + } + } + do { totlen = (size_t)(count * yanklen); if (totlen > 0) { oldp = ml_get(lnum); - newp = (char_u *) xmalloc((size_t)(STRLEN(oldp) + totlen + 1)); + if (VIsual_active && col > (int)STRLEN(oldp)) { + lnum++; + continue; + } + newp = (char_u *)xmalloc((size_t)(STRLEN(oldp) + totlen + 1)); memmove(newp, oldp, (size_t)col); ptr = newp + col; for (i = 0; i < (size_t)count; i++) { @@ -3090,11 +3113,10 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags) curwin->w_cursor.col += (colnr_T)(totlen - 1); } } - if (VIsual_active) + if (VIsual_active) { lnum++; - } while (VIsual_active - && (lnum <= curbuf->b_visual.vi_end.lnum - || lnum <= curbuf->b_visual.vi_start.lnum)); + } + } while (VIsual_active && lnum <= end_lnum); if (VIsual_active) { /* reset lnum to the last visual line */ lnum--; @@ -3177,9 +3199,9 @@ error: curbuf->b_op_start.lnum++; } // Skip mark_adjust when adding lines after the last one, there - // can't be marks there. + // can't be marks there. But still needed in diff mode. if (curbuf->b_op_start.lnum + (y_type == kMTCharWise) - 1 + nr_lines - < curbuf->b_ml.ml_line_count) { + < curbuf->b_ml.ml_line_count || curwin->w_p_diff) { mark_adjust(curbuf->b_op_start.lnum + (y_type == kMTCharWise), (linenr_T)MAXLNUM, nr_lines, 0L, false); } @@ -3997,7 +4019,7 @@ format_lines ( && (do_second_indent || do_number_indent) && prev_is_end_par && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) { - if (do_second_indent && !lineempty(curwin->w_cursor.lnum + 1)) { + if (do_second_indent && !LINEEMPTY(curwin->w_cursor.lnum + 1)) { if (leader_len == 0 && next_leader_len == 0) { /* no comment found */ second_indent = @@ -4854,9 +4876,8 @@ static void *get_reg_wrap_one_line(char_u *s, int flags) if (!(flags & kGRegList)) { return s; } - list_T *list = tv_list_alloc(); - tv_list_append_string(list, NULL, 0); - list->lv_first->li_tv.vval.v_string = s; + list_T *const list = tv_list_alloc(1); + tv_list_append_allocated_string(list, (char *)s); return list; } @@ -4905,7 +4926,7 @@ void *get_reg_contents(int regname, int flags) return NULL; if (flags & kGRegList) { - list_T *list = tv_list_alloc(); + list_T *const list = tv_list_alloc((ptrdiff_t)reg->y_size); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(list, (const char *)reg->y_array[i], -1); } @@ -5594,7 +5615,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) } free_register(reg); - list_T *const args = tv_list_alloc(); + list_T *const args = tv_list_alloc(1); const char regname = (char)name; tv_list_append_string(args, ®name, 1); @@ -5610,13 +5631,14 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) list_T *res = result.vval.v_list; list_T *lines = NULL; - if (res->lv_len == 2 && res->lv_first->li_tv.v_type == VAR_LIST) { - lines = res->lv_first->li_tv.vval.v_list; - if (res->lv_last->li_tv.v_type != VAR_STRING) { + if (tv_list_len(res) == 2 + && TV_LIST_ITEM_TV(tv_list_first(res))->v_type == VAR_LIST) { + lines = TV_LIST_ITEM_TV(tv_list_first(res))->vval.v_list; + if (TV_LIST_ITEM_TV(tv_list_last(res))->v_type != VAR_STRING) { goto err; } - char_u *regtype = res->lv_last->li_tv.vval.v_string; - if (regtype == NULL || strlen((char*)regtype) > 1) { + char_u *regtype = TV_LIST_ITEM_TV(tv_list_last(res))->vval.v_string; + if (regtype == NULL || strlen((char *)regtype) > 1) { goto err; } switch (regtype[0]) { @@ -5641,20 +5663,21 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet) reg->y_type = kMTUnknown; } - reg->y_array = xcalloc((size_t)lines->lv_len, sizeof(uint8_t *)); - reg->y_size = (size_t)lines->lv_len; + reg->y_array = xcalloc((size_t)tv_list_len(lines), sizeof(char_u *)); + reg->y_size = (size_t)tv_list_len(lines); reg->additional_data = NULL; reg->timestamp = 0; // Timestamp is not saved for clipboard registers because clipboard registers // are not saved in the ShaDa file. int i = 0; - for (listitem_T *li = lines->lv_first; li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_STRING) { + TV_LIST_ITER_CONST(lines, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_STRING) { goto err; } - reg->y_array[i++] = (char_u *)xstrdupnul((char *)li->li_tv.vval.v_string); - } + reg->y_array[i++] = (char_u *)xstrdupnul( + (const char *)TV_LIST_ITEM_TV(li)->vval.v_string); + }); if (reg->y_size > 0 && strlen((char*)reg->y_array[reg->y_size-1]) == 0) { // a known-to-be charwise yank might have a final linebreak @@ -5711,15 +5734,13 @@ static void set_clipboard(int name, yankreg_T *reg) return; } - list_T *lines = tv_list_alloc(); + list_T *const lines = tv_list_alloc( + (ptrdiff_t)reg->y_size + (reg->y_type != kMTCharWise)); for (size_t i = 0; i < reg->y_size; i++) { tv_list_append_string(lines, (const char *)reg->y_array[i], -1); } - list_T *args = tv_list_alloc(); - tv_list_append_list(args, lines); - char regtype; switch (reg->y_type) { case kMTLineWise: { @@ -5740,10 +5761,11 @@ static void set_clipboard(int name, yankreg_T *reg) assert(false); } } - tv_list_append_string(args, ®type, 1); - const char regname = (char)name; - tv_list_append_string(args, ®name, 1); + list_T *args = tv_list_alloc(3); + tv_list_append_list(args, lines); + tv_list_append_string(args, ®type, 1); + tv_list_append_string(args, ((char[]) { (char)name }), 1); (void)eval_call_provider("clipboard", "set", args); } diff --git a/src/nvim/option.c b/src/nvim/option.c index 913d27d508..c43ba2fc4f 100644 --- a/src/nvim/option.c +++ b/src/nvim/option.c @@ -13,7 +13,8 @@ // add some code to didset_window_options(). // - For a buffer option, add some code to buf_copy_options(). // - For a buffer string option, add code to check_buf_options(). -// - If it's a numeric option, add any necessary bounds checks to do_set(). +// - If it's a numeric option, add any necessary bounds checks to +// set_num_option(). // - If it's a list of flags, add some code in do_set(), search for WW_ALL. // - When adding an option with expansion (P_EXPAND), but with a different // default for Vi and Vim (no P_VI_DEF), add some code at VIMEXP. @@ -74,7 +75,9 @@ #include "nvim/undo.h" #include "nvim/window.h" #include "nvim/os/os.h" +#include "nvim/api/private/helpers.h" #include "nvim/os/input.h" +#include "nvim/os/lang.h" /* * The options that are local to a window or buffer have "indir" set to one of @@ -247,6 +250,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 HIGHLIGHT_INIT \ "8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText," \ @@ -784,6 +788,8 @@ void set_init_1(void) didset_options2(); + lang_init(); + // enc_locale() will try to find the encoding of the current locale. // This will be used when 'default' is used as encoding specifier // in 'fileencodings' @@ -826,24 +832,26 @@ set_option_default ( if (flags & P_STRING) { /* Use set_string_option_direct() for local options to handle * freeing and allocating the value. */ - if (options[opt_idx].indir != PV_NONE) + if (options[opt_idx].indir != PV_NONE) { set_string_option_direct(NULL, opt_idx, - options[opt_idx].def_val[dvi], opt_flags, 0); - else { - if ((opt_flags & OPT_FREE) && (flags & P_ALLOCED)) + options[opt_idx].def_val[dvi], opt_flags, 0); + } else { + if ((opt_flags & OPT_FREE) && (flags & P_ALLOCED)) { free_string_option(*(char_u **)(varp)); + } *(char_u **)varp = options[opt_idx].def_val[dvi]; options[opt_idx].flags &= ~P_ALLOCED; } } else if (flags & P_NUM) { - if (options[opt_idx].indir == PV_SCROLL) + if (options[opt_idx].indir == PV_SCROLL) { win_comp_scroll(curwin); - else { - *(long *)varp = (long)options[opt_idx].def_val[dvi]; - /* May also set global value for local option. */ - if (both) + } else { + *(long *)varp = (long)(intptr_t)options[opt_idx].def_val[dvi]; + // May also set global value for local option. + if (both) { *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = *(long *)varp; + } } } else { /* P_BOOL */ *(int *)varp = (int)(intptr_t)options[opt_idx].def_val[dvi]; @@ -921,7 +929,7 @@ void set_number_default(char *name, long val) opt_idx = findoption(name); if (opt_idx >= 0) { - options[opt_idx].def_val[VI_DEFAULT] = (char_u *)val; + options[opt_idx].def_val[VI_DEFAULT] = (char_u *)(intptr_t)val; } } @@ -1052,7 +1060,7 @@ void set_init_3(void) xfree(p); } - if (bufempty()) { + if (BUFEMPTY()) { int idx_ffs = findoption_len(S_LEN("ffs")); // Apply the first entry of 'fileformats' to the initial buffer. @@ -1185,6 +1193,7 @@ do_set ( set_options_default(OPT_FREE | opt_flags); didset_options(); didset_options2(); + ui_refresh_options(); redraw_all_later(CLEAR); } else { showoptions(1, opt_flags); @@ -1434,20 +1443,19 @@ do_set ( * [-]0-9 set number * other error */ - ++arg; - if (nextchar == '&') - value = (long)options[opt_idx].def_val[ - ((flags & P_VI_DEF) || cp_val) - ? VI_DEFAULT : VIM_DEFAULT]; - else if (nextchar == '<') { - /* For 'undolevels' NO_LOCAL_UNDOLEVEL means to - * use the global value. */ - if ((long *)varp == &curbuf->b_p_ul - && opt_flags == OPT_LOCAL) + arg++; + if (nextchar == '&') { + value = (long)(intptr_t)options[opt_idx].def_val[ + ((flags & P_VI_DEF) || cp_val) ? VI_DEFAULT : VIM_DEFAULT]; + } else if (nextchar == '<') { + // For 'undolevels' NO_LOCAL_UNDOLEVEL means to + // use the global value. + if ((long *)varp == &curbuf->b_p_ul && opt_flags == OPT_LOCAL) { value = NO_LOCAL_UNDOLEVEL; - else + } else { value = *(long *)get_varp_scope( &(options[opt_idx]), OPT_GLOBAL); + } } else if (((long *)varp == &p_wc || (long *)varp == &p_wcm) && (*arg == '<' @@ -1460,8 +1468,7 @@ do_set ( goto skip; } } else if (*arg == '-' || ascii_isdigit(*arg)) { - // Allow negative (for 'undolevels'), octal and - // hex numbers. + // Allow negative, octal and hex numbers. vim_str2nr(arg, NULL, &i, STR2NR_ALL, &value, NULL, 0); if (arg[i] != NUL && !ascii_iswhite(arg[i])) { errmsg = e_invarg; @@ -1491,6 +1498,7 @@ do_set ( char_u *newval; char_u *origval = NULL; char *saved_origval = NULL; + char *saved_newval = NULL; unsigned newlen; int comma; int bs; @@ -1507,7 +1515,17 @@ do_set ( /* The old value is kept until we are sure that the * new value is valid. */ oldval = *(char_u **)varp; - if (nextchar == '&') { /* set to default val */ + + // When setting the local value of a global + // option, the old value may be the global value. + if (((int)options[opt_idx].indir & PV_BOTH) && (opt_flags + & OPT_LOCAL)) { + origval = *(char_u **)get_varp(&options[opt_idx]); + } else { + origval = oldval; + } + + if (nextchar == '&') { // set to default val newval = options[opt_idx].def_val[ ((flags & P_VI_DEF) || cp_val) ? VI_DEFAULT : VIM_DEFAULT]; @@ -1566,6 +1584,9 @@ do_set ( break; } xfree(oldval); + if (origval == oldval) { + origval = *(char_u **)varp; + } oldval = *(char_u **)varp; } /* @@ -1602,15 +1623,6 @@ do_set ( ++arg; } - /* When setting the local value of a global - * option, the old value may be the global value. */ - if (((int)options[opt_idx].indir & PV_BOTH) - && (opt_flags & OPT_LOCAL)) - origval = *(char_u **)get_varp( - &options[opt_idx]); - else - origval = oldval; - /* * Copy the new string into allocated memory. * Can't use set_string_option_direct(), because @@ -1780,39 +1792,37 @@ do_set ( new_value_alloced = TRUE; } - /* Set the new value. */ + // Set the new value. *(char_u **)(varp) = newval; - if (!starting && origval != NULL) { + if (!starting && origval != NULL && newval != NULL) { // origval may be freed by // did_set_string_option(), make a copy. - saved_origval = xstrdup((char *) origval); + saved_origval = xstrdup((char *)origval); + // newval (and varp) may become invalid if the + // buffer is closed by autocommands. + saved_newval = xstrdup((char *)newval); } - /* Handle side effects, and set the global value for - * ":set" on local options. */ + // Handle side effects, and set the global value for + // ":set" on local options. Note: when setting 'syntax' + // or 'filetype' autocommands may be triggered that can + // cause havoc. errmsg = did_set_string_option(opt_idx, (char_u **)varp, new_value_alloced, oldval, errbuf, opt_flags); + if (errmsg == NULL) { + trigger_optionsset_string(opt_idx, opt_flags, saved_origval, + saved_newval); + } + xfree(saved_origval); + xfree(saved_newval); + // If error detected, print the error message. if (errmsg != NULL) { - xfree(saved_origval); goto skip; } - if (saved_origval != NULL) { - char buf_type[7]; - vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", - (opt_flags & OPT_LOCAL) ? "local" : "global"); - set_vim_var_string(VV_OPTION_NEW, *(char **) varp, -1); - set_vim_var_string(VV_OPTION_OLD, saved_origval, -1); - set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); - apply_autocmds(EVENT_OPTIONSET, - (char_u *)options[opt_idx].fullname, - NULL, false, NULL); - reset_v_option_vars(); - xfree(saved_origval); - } } else { // key code option(FIXME(tarruda): Show a warning or something // similar) @@ -2207,6 +2217,7 @@ void check_buf_options(buf_T *buf) check_string_option(&buf->b_p_tsr); check_string_option(&buf->b_p_lw); check_string_option(&buf->b_p_bkc); + check_string_option(&buf->b_p_menc); } /* @@ -2248,7 +2259,7 @@ int was_set_insecurely(char_u *opt, int opt_flags) uint32_t *flagp = insecure_flag(idx, opt_flags); return (*flagp & P_INSECURE) != 0; } - EMSG2(_(e_intern2), "was_set_insecurely()"); + internal_error("was_set_insecurely()"); return -1; } @@ -2307,8 +2318,8 @@ set_string_option_direct ( if (idx == -1) { // Use name. idx = findoption((const char *)name); if (idx < 0) { // Not found (should not happen). - EMSG2(_(e_intern2), "set_string_option_direct()"); - EMSG2(_("For option %s"), name); + internal_error("set_string_option_direct()"); + IEMSG2(_("For option %s"), name); return; } } @@ -2394,6 +2405,7 @@ static char *set_string_option(const int opt_idx, const char *const value, *varp = s; char *const saved_oldval = (starting ? NULL : xstrdup(oldval)); + char *const saved_newval = (starting ? NULL : xstrdup(s)); char *const r = (char *)did_set_string_option( opt_idx, (char_u **)varp, (int)true, (char_u *)oldval, NULL, opt_flags); @@ -2402,19 +2414,12 @@ static char *set_string_option(const int opt_idx, const char *const value, } // call autocommand after handling side effects - if (saved_oldval != NULL) { - char buf_type[7]; - vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", - (opt_flags & OPT_LOCAL) ? "local" : "global"); - set_vim_var_string(VV_OPTION_NEW, (char *)(*varp), -1); - set_vim_var_string(VV_OPTION_OLD, saved_oldval, -1); - set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); - apply_autocmds(EVENT_OPTIONSET, - (char_u *)options[opt_idx].fullname, - NULL, false, NULL); - reset_v_option_vars(); - xfree(saved_oldval); + if (r == NULL) { + trigger_optionsset_string(opt_idx, opt_flags, + saved_oldval, saved_newval); } + xfree(saved_oldval); + xfree(saved_newval); return r; } @@ -2431,6 +2436,11 @@ static bool valid_filetype(char_u *val) return true; } +#ifdef _MSC_VER +// MSVC optimizations are disabled for this function because it +// incorrectly generates an empty string for SHM_ALL. +#pragma optimize("", off) +#endif /* * Handle string options that need some action to perform when changed. * Returns NULL for success, or an error message for an error. @@ -2450,6 +2460,7 @@ did_set_string_option ( int did_chartab = FALSE; char_u **gvarp; bool free_oldval = (options[opt_idx].flags & P_ALLOCED); + int ft_changed = false; /* Get the global option to compare with, otherwise we would have to check * two values for all local options. */ @@ -2621,8 +2632,8 @@ did_set_string_option ( else if (varp == &p_ei) { if (check_ei() == FAIL) errmsg = e_invarg; - /* 'encoding' and 'fileencoding' */ - } else if (varp == &p_enc || gvarp == &p_fenc) { + // 'encoding', 'fileencoding' and 'makeencoding' + } else if (varp == &p_enc || gvarp == &p_fenc || gvarp == &p_menc) { if (gvarp == &p_fenc) { if (!MODIFIABLE(curbuf) && opt_flags != OPT_GLOBAL) { errmsg = e_modifiable; @@ -3169,6 +3180,8 @@ did_set_string_option ( } else if (gvarp == &p_ft) { if (!valid_filetype(*varp)) { errmsg = e_invarg; + } else { + ft_changed = STRCMP(oldval, *varp) != 0; } } else if (gvarp == &p_syn) { if (!valid_filetype(*varp)) { @@ -3251,10 +3264,12 @@ did_set_string_option ( apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, curbuf->b_fname, TRUE, curbuf); } else if (varp == &(curbuf->b_p_ft)) { - /* 'filetype' is set, trigger the FileType autocommand */ - did_filetype = TRUE; - apply_autocmds(EVENT_FILETYPE, curbuf->b_p_ft, - curbuf->b_fname, TRUE, curbuf); + // 'filetype' is set, trigger the FileType autocommand + if (!(opt_flags & OPT_MODELINE) || ft_changed) { + did_filetype = true; + apply_autocmds(EVENT_FILETYPE, curbuf->b_p_ft, + curbuf->b_fname, true, curbuf); + } } if (varp == &(curwin->w_s->b_p_spl)) { char_u fname[200]; @@ -3295,6 +3310,9 @@ did_set_string_option ( return errmsg; } +#ifdef _MSC_VER +#pragma optimize("", on) +#endif /* * Simple int comparison function for use with qsort() @@ -3371,37 +3389,39 @@ skip: return NULL; /* no error */ } -/* - * Handle setting 'listchars' or 'fillchars'. - * Returns error message, NULL if it's OK. - */ + +/// Handle setting 'listchars' or 'fillchars'. +/// Assume monocell characters +/// +/// @param varp either &p_lcs ('listchars') or &p_fcs ('fillchar') +/// @return error message, NULL if it's OK. static char_u *set_chars_option(char_u **varp) { int round, i, len, entries; char_u *p, *s; int c1, c2 = 0; struct charstab { - int *cp; - char *name; + int *cp; ///< char value + char *name; ///< char id + int def; ///< default value }; - static struct charstab filltab[] = - { - {&fill_stl, "stl"}, - {&fill_stlnc, "stlnc"}, - {&fill_vert, "vert"}, - {&fill_fold, "fold"}, - {&fill_diff, "diff"}, + static struct charstab filltab[] = { + { &fill_stl, "stl" , ' ' }, + { &fill_stlnc, "stlnc", ' ' }, + { &fill_vert, "vert" , 9474 }, // │ + { &fill_fold, "fold" , 183 }, // · + { &fill_diff, "diff" , '-' }, + { &fill_msgsep, "msgsep", ' ' }, }; - static struct charstab lcstab[] = - { - {&lcs_eol, "eol"}, - {&lcs_ext, "extends"}, - {&lcs_nbsp, "nbsp"}, - {&lcs_prec, "precedes"}, - {&lcs_space, "space"}, - {&lcs_tab2, "tab"}, - {&lcs_trail, "trail"}, - {&lcs_conceal, "conceal"}, + static struct charstab lcstab[] = { + { &lcs_eol, "eol", NUL }, + { &lcs_ext, "extends", NUL }, + { &lcs_nbsp, "nbsp", NUL }, + { &lcs_prec, "precedes", NUL }, + { &lcs_space, "space", NUL }, + { &lcs_tab2, "tab", NUL }, + { &lcs_trail, "trail", NUL }, + { &lcs_conceal, "conceal", NUL }, }; struct charstab *tab; @@ -3411,20 +3431,29 @@ static char_u *set_chars_option(char_u **varp) } else { tab = filltab; entries = ARRAY_SIZE(filltab); + if (*p_ambw == 'd') { + // XXX: If ambiwidth=double then "|" and "·" take 2 columns, which is + // forbidden (TUI limitation?). Set old defaults. + filltab[2].def = '|'; + filltab[3].def = '-'; + } else { + filltab[2].def = 9474; // │ + filltab[3].def = 183; // · + } } - /* first round: check for valid value, second round: assign values */ - for (round = 0; round <= 1; ++round) { + // first round: check for valid value, second round: assign values + for (round = 0; round <= 1; round++) { if (round > 0) { - /* After checking that the value is valid: set defaults: space for - * 'fillchars', NUL for 'listchars' */ - for (i = 0; i < entries; ++i) - if (tab[i].cp != NULL) - *(tab[i].cp) = (varp == &p_lcs ? NUL : ' '); - if (varp == &p_lcs) + // After checking that the value is valid: set defaults + for (i = 0; i < entries; i++) { + if (tab[i].cp != NULL) { + *(tab[i].cp) = tab[i].def; + } + } + if (varp == &p_lcs) { lcs_tab1 = NUL; - else - fill_diff = '-'; + } } p = *varp; while (*p) { @@ -4021,6 +4050,10 @@ static char *set_bool_option(const int opt_idx, char_u *const varp, (char_u *) options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); + if (options[opt_idx].flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(options[opt_idx].fullname), + BOOLEAN_OBJ(value)); + } } comp_col(); /* in case 'ruler' or 'showcmd' changed */ @@ -4057,238 +4090,259 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, return (char *)e_secure; } - *pp = value; - /* Remember where the option was set. */ - set_option_scriptID_idx(opt_idx, opt_flags, current_SID); - - if (curbuf->b_p_sw < 0) { - errmsg = e_positive; - curbuf->b_p_sw = curbuf->b_p_ts; + // Many number options assume their value is in the signed int range. + if (value < INT_MIN || value > INT_MAX) { + return (char *)e_invarg; } - /* - * Number options that need some action when changed - */ - if (pp == &p_wh || pp == &p_hh) { - if (p_wh < 1) { + // Options that need some validation. + if (pp == &p_wh) { + if (value < 1) { errmsg = e_positive; - p_wh = 1; - } - if (p_wmh > p_wh) { + } else if (p_wmh > value) { errmsg = e_winheight; - p_wh = p_wmh; } - if (p_hh < 0) { + } else if (pp == &p_hh) { + if (value < 0) { errmsg = e_positive; - p_hh = 0; - } - - /* Change window height NOW */ - if (!ONE_WINDOW) { - if (pp == &p_wh && curwin->w_height < p_wh) - win_setheight((int)p_wh); - if (pp == &p_hh && curbuf->b_help && curwin->w_height < p_hh) - win_setheight((int)p_hh); } - } - /* 'winminheight' */ - else if (pp == &p_wmh) { - if (p_wmh < 0) { + } else if (pp == &p_wmh) { + if (value < 0) { errmsg = e_positive; - p_wmh = 0; - } - if (p_wmh > p_wh) { + } else if (value > p_wh) { errmsg = e_winheight; - p_wmh = p_wh; } - win_setminheight(); } else if (pp == &p_wiw) { - if (p_wiw < 1) { + if (value < 1) { errmsg = e_positive; - p_wiw = 1; + } else if (p_wmw > value) { + errmsg = e_winwidth; } - if (p_wmw > p_wiw) { + } else if (pp == &p_wmw) { + if (value < 0) { + errmsg = e_positive; + } else if (value > p_wiw) { errmsg = e_winwidth; - p_wiw = p_wmw; } - - /* Change window width NOW */ - if (!ONE_WINDOW && curwin->w_width < p_wiw) - win_setwidth((int)p_wiw); - } - /* 'winminwidth' */ - else if (pp == &p_wmw) { - if (p_wmw < 0) { + } else if (pp == &p_mco) { + if (value > MAX_MCO) { + errmsg = e_invarg; + } else if (value < 0) { errmsg = e_positive; - p_wmw = 0; } - if (p_wmw > p_wiw) { - errmsg = e_winwidth; - p_wmw = p_wiw; + } else if (pp == &p_titlelen) { + if (value < 0) { + errmsg = e_positive; } - win_setminheight(); - } else if (pp == &p_ls) { - /* (re)set last window status line */ - last_status(false); - } - /* (re)set tab page line */ - else if (pp == &p_stal) { - shell_new_rows(); /* recompute window positions and heights */ - } - /* 'foldlevel' */ - else if (pp == &curwin->w_p_fdl) { - if (curwin->w_p_fdl < 0) - curwin->w_p_fdl = 0; - newFoldLevel(); - } - /* 'foldminlines' */ - else if (pp == &curwin->w_p_fml) { - foldUpdateAll(curwin); - } - /* 'foldnestmax' */ - else if (pp == &curwin->w_p_fdn) { - if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) - foldUpdateAll(curwin); - } - /* 'foldcolumn' */ - else if (pp == &curwin->w_p_fdc) { - if (curwin->w_p_fdc < 0) { + } else if (pp == &p_uc) { + if (value < 0) { errmsg = e_positive; - curwin->w_p_fdc = 0; - } else if (curwin->w_p_fdc > 12) { - errmsg = e_invarg; - curwin->w_p_fdc = 12; } - // 'shiftwidth' or 'tabstop' - } else if (pp == &curbuf->b_p_sw || pp == (long *)&curbuf->b_p_ts) { - if (foldmethodIsIndent(curwin)) { - foldUpdateAll(curwin); + } else if (pp == &p_ch) { + if (value < 1) { + errmsg = e_positive; } - // When 'shiftwidth' changes, or it's zero and 'tabstop' changes: - // parse 'cinoptions'. - if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) { - parse_cino(curbuf); + } else if (pp == &p_tm) { + if (value < 0) { + errmsg = e_positive; } - } - /* 'maxcombine' */ - else if (pp == &p_mco) { - if (p_mco > MAX_MCO) - p_mco = MAX_MCO; - else if (p_mco < 0) - p_mco = 0; - screenclear(); /* will re-allocate the screen */ - } else if (pp == &curbuf->b_p_iminsert) { - if (curbuf->b_p_iminsert < 0 || curbuf->b_p_iminsert > B_IMODE_LAST) { + } else if (pp == &p_hi) { + if (value < 0) { + errmsg = e_positive; + } else if (value > 10000) { errmsg = e_invarg; - curbuf->b_p_iminsert = B_IMODE_NONE; } - p_iminsert = curbuf->b_p_iminsert; - showmode(); - /* Show/unshow value of 'keymap' in status lines. */ - status_redraw_curbuf(); - } else if (pp == &p_window) { - if (p_window < 1) - p_window = 1; - else if (p_window >= Rows) - p_window = Rows - 1; - } else if (pp == &curbuf->b_p_imsearch) { - if (curbuf->b_p_imsearch < -1 || curbuf->b_p_imsearch > B_IMODE_LAST) { + } else if (pp == &p_re) { + if (value < 0 || value > 2) { errmsg = e_invarg; - curbuf->b_p_imsearch = B_IMODE_NONE; } - p_imsearch = curbuf->b_p_imsearch; - } else if (pp == &p_channel || pp == &curbuf->b_p_channel) { - errmsg = e_invarg; - *pp = old_value; - } - /* if 'titlelen' has changed, redraw the title */ - else if (pp == &p_titlelen) { - if (p_titlelen < 0) { + } else if (pp == &p_report) { + if (value < 0) { errmsg = e_positive; - p_titlelen = 85; } - if (starting != NO_SCREEN && old_value != p_titlelen) - need_maketitle = TRUE; - } - /* if p_ch changed value, change the command line height */ - else if (pp == &p_ch) { - if (p_ch < 1) { + } else if (pp == &p_titlelen) { + if (value < 0) { errmsg = e_positive; - p_ch = 1; } - if (p_ch > Rows - min_rows() + 1) - p_ch = Rows - min_rows() + 1; - - /* Only compute the new window layout when startup has been - * completed. Otherwise the frame sizes may be wrong. */ - if (p_ch != old_value && full_screen - ) - command_height(); - } - /* when 'updatecount' changes from zero to non-zero, open swap files */ - else if (pp == &p_uc) { - if (p_uc < 0) { + } else if (pp == &p_so) { + if (value < 0 && full_screen) { + errmsg = e_scroll; + } + } else if (pp == &p_siso) { + if (value < 0 && full_screen) { errmsg = e_positive; - p_uc = 100; } - if (p_uc && !old_value) - ml_open_files(); - } else if (pp == &curwin->w_p_cole) { - if (curwin->w_p_cole < 0) { + } else if (pp == &p_cwh) { + if (value < 1) { + errmsg = e_positive; + } + } else if (pp == &p_ut) { + if (value < 0) { + errmsg = e_positive; + } + } else if (pp == &p_ss) { + if (value < 0) { + errmsg = e_positive; + } + } else if (pp == &curwin->w_p_fdl + || pp == (long *)GLOBAL_WO(&curwin->w_p_fdl)) { + if (value < 0) { + errmsg = e_positive; + } + } else if (pp == &curwin->w_p_fdc + || pp == (long *)GLOBAL_WO(&curwin->w_p_fdc)) { + if (value < 0) { errmsg = e_positive; - curwin->w_p_cole = 0; - } else if (curwin->w_p_cole > 3) { + } else if (value > 12) { errmsg = e_invarg; - curwin->w_p_cole = 3; - } - } - /* sync undo before 'undolevels' changes */ - else if (pp == &p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - p_ul = old_value; - u_sync(TRUE); - p_ul = value; - } else if (pp == &curbuf->b_p_ul) { - /* use the old value, otherwise u_sync() may not work properly */ - curbuf->b_p_ul = old_value; - u_sync(TRUE); - curbuf->b_p_ul = value; - } - /* 'numberwidth' must be positive */ - else if (pp == &curwin->w_p_nuw) { - if (curwin->w_p_nuw < 1) { + } + } else if (pp == &curwin->w_p_cole + || pp == (long *)GLOBAL_WO(&curwin->w_p_cole)) { + if (value < 0) { errmsg = e_positive; - curwin->w_p_nuw = 1; + } else if (value > 3) { + errmsg = e_invarg; } - if (curwin->w_p_nuw > 10) { + } else if (pp == &curwin->w_p_nuw + || pp == (long *)GLOBAL_WO(&curwin->w_p_nuw)) { + if (value < 1) { + errmsg = e_positive; + } else if (value > 10) { errmsg = e_invarg; - curwin->w_p_nuw = 10; } - curwin->w_nrwidth_line_count = 0; - } else if (pp == &curbuf->b_p_tw) { - if (curbuf->b_p_tw < 0) { + } else if (pp == &curbuf->b_p_iminsert || pp == &p_iminsert) { + if (value < 0 || value > B_IMODE_LAST) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_imsearch || pp == &p_imsearch) { + if (value < -1 || value > B_IMODE_LAST) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_channel || pp == &p_channel) { + errmsg = e_invarg; + } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { + if (value < -1 || value > SB_MAX + || (value != -1 && opt_flags == OPT_LOCAL && !curbuf->terminal)) { + errmsg = e_invarg; + } + } else if (pp == &curbuf->b_p_sw || pp == &p_sw) { + if (value < 0) { errmsg = e_positive; - curbuf->b_p_tw = 0; } + } else if (pp == &curbuf->b_p_ts || pp == &p_ts) { + if (value < 1) { + errmsg = e_positive; + } + } else if (pp == &curbuf->b_p_tw || pp == &p_tw) { + if (value < 0) { + errmsg = e_positive; + } + } + + // Don't change the value and return early if validation failed. + if (errmsg != NULL) { + return (char *)errmsg; + } + + *pp = value; + // Remember where the option was set. + set_option_scriptID_idx(opt_idx, opt_flags, current_SID); + + // For these options we want to fix some invalid values. + if (pp == &p_window) { + if (p_window < 1) { + p_window = Rows - 1; + } else if (p_window >= Rows) { + p_window = Rows - 1; + } + } else if (pp == &p_ch) { + if (p_ch > Rows - min_rows() + 1) { + p_ch = Rows - min_rows() + 1; + } + } + // Number options that need some action when changed + if (pp == &p_wh) { + if (!ONE_WINDOW && curwin->w_height < p_wh) { + win_setheight((int)p_wh); + } + } else if (pp == &p_hh) { + if (!ONE_WINDOW && curbuf->b_help && curwin->w_height < p_hh) { + win_setheight((int)p_hh); + } + } else if (pp == &p_wmh) { + win_setminheight(); + } else if (pp == &p_wiw) { + if (!ONE_WINDOW && curwin->w_width < p_wiw) { + win_setwidth((int)p_wiw); + } + } else if (pp == &p_ls) { + last_status(false); // (re)set last window status line. + } else if (pp == &p_stal) { + // (re)set tab page line + shell_new_rows(); // recompute window positions and heights + } else if (pp == &curwin->w_p_fdl) { + newFoldLevel(); + } else if (pp == &curwin->w_p_fml) { + foldUpdateAll(curwin); + } else if (pp == &curwin->w_p_fdn) { + if (foldmethodIsSyntax(curwin) || foldmethodIsIndent(curwin)) { + foldUpdateAll(curwin); + } + } else if (pp == &curbuf->b_p_sw || pp == &curbuf->b_p_ts) { + // 'shiftwidth' or 'tabstop' + if (foldmethodIsIndent(curwin)) { + foldUpdateAll(curwin); + } + // When 'shiftwidth' changes, or it's zero and 'tabstop' changes: + // parse 'cinoptions'. + if (pp == &curbuf->b_p_sw || curbuf->b_p_sw == 0) { + parse_cino(curbuf); + } + } else if (pp == &p_mco) { + screenclear(); // will re-allocate the screen + } else if (pp == &curbuf->b_p_iminsert) { + showmode(); + // Show/unshow value of 'keymap' in status lines. + status_redraw_curbuf(); + } else if (pp == &p_titlelen) { + // if 'titlelen' has changed, redraw the title + if (starting != NO_SCREEN && old_value != p_titlelen) { + need_maketitle = true; + } + } else if (pp == &p_ch) { + // if p_ch changed value, change the command line height + // Only compute the new window layout when startup has been + // completed. Otherwise the frame sizes may be wrong. + if (p_ch != old_value && full_screen) { + command_height(); + } + } else if (pp == &p_uc) { + // when 'updatecount' changes from zero to non-zero, open swap files + if (p_uc && !old_value) { + ml_open_files(); + } + } else if (pp == &p_ul || pp == &curbuf->b_p_ul) { + // sync undo before 'undolevels' changes + // use the old value, otherwise u_sync() may not work properly + *pp = old_value; + u_sync(true); + *pp = value; + } else if (pp == &curbuf->b_p_tw) { FOR_ALL_TAB_WINDOWS(tp, wp) { check_colorcolumn(wp); } } else if (pp == &curbuf->b_p_scbk || pp == &p_scbk) { - // 'scrollback' - if (*pp < -1 || *pp > SB_MAX - || (*pp != -1 && opt_flags == OPT_LOCAL && !curbuf->terminal)) { - errmsg = e_invarg; - *pp = old_value; - } else if (curbuf->terminal) { + if (curbuf->terminal) { // Force the scrollback to take effect. terminal_resize(curbuf->terminal, UINT16_MAX, UINT16_MAX); } + } else if (pp == &curwin->w_p_nuw) { + curwin->w_nrwidth_line_count = 0; } - /* - * Check the bounds for numeric options here - */ + + // Check the (new) bounds for Rows and Columns here. if (Rows < min_rows() && full_screen) { if (errbuf != NULL) { vim_snprintf((char *)errbuf, errbuflen, @@ -4308,19 +4362,17 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, limit_screen_size(); - /* - * If the screen (shell) height has been changed, assume it is the - * physical screenheight. - */ + // If the screen (shell) height has been changed, assume it is the + // physical screenheight. if (old_Rows != Rows || old_Columns != Columns) { - /* Changing the screen size is not allowed while updating the screen. */ + // 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); } else { - /* Postpone the resizing; check the size and cmdline position for - * messages. */ + // Postpone the resizing; check the size and cmdline position for + // messages. check_shellsize(); if (cmdline_row > Rows - p_ch && Rows > p_ch) { assert(p_ch >= 0 && Rows - p_ch <= INT_MAX); @@ -4332,14 +4384,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, } } - if (curbuf->b_p_ts <= 0) { - errmsg = e_positive; - curbuf->b_p_ts = 8; - } - if (p_tm < 0) { - errmsg = e_positive; - p_tm = 0; - } if ((curwin->w_p_scr <= 0 || (curwin->w_p_scr > curwin->w_height && curwin->w_height > 0)) @@ -4356,21 +4400,6 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, else /* curwin->w_p_scr > curwin->w_height */ curwin->w_p_scr = curwin->w_height; } - if (p_hi < 0) { - errmsg = e_positive; - p_hi = 0; - } else if (p_hi > 10000) { - errmsg = e_invarg; - p_hi = 10000; - } - if (p_re < 0 || p_re > 2) { - errmsg = e_invarg; - p_re = 0; - } - if (p_report < 0) { - errmsg = e_positive; - p_report = 1; - } if ((p_sj < -100 || p_sj >= Rows) && full_screen) { if (Rows != old_Rows) /* Rows changed, just adjust p_sj */ p_sj = Rows / 2; @@ -4379,30 +4408,11 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, p_sj = 1; } } - if (p_so < 0 && full_screen) { - errmsg = e_scroll; - p_so = 0; - } - if (p_siso < 0 && full_screen) { - errmsg = e_positive; - p_siso = 0; - } - if (p_cwh < 1) { - errmsg = e_positive; - p_cwh = 1; - } - if (p_ut < 0) { - errmsg = e_positive; - p_ut = 2000; - } - if (p_ss < 0) { - errmsg = e_positive; - p_ss = 0; - } - /* May set global value for local option. */ - if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) + // May set global value for local option. + if ((opt_flags & (OPT_LOCAL | OPT_GLOBAL)) == 0) { *(long *)get_varp_scope(&(options[opt_idx]), OPT_GLOBAL) = *pp; + } if (pp == &curbuf->b_p_scbk && !curbuf->terminal) { // Normal buffer: reset local 'scrollback' after updating the global value. @@ -4426,6 +4436,10 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, (char_u *) options[opt_idx].fullname, NULL, false, NULL); reset_v_option_vars(); + if (options[opt_idx].flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(options[opt_idx].fullname), + INTEGER_OBJ(value)); + } } comp_col(); /* in case 'columns' or 'ls' changed */ @@ -4437,6 +4451,27 @@ static char *set_num_option(int opt_idx, char_u *varp, long value, return (char *)errmsg; } +static void trigger_optionsset_string(int opt_idx, int opt_flags, + char *oldval, char *newval) +{ + if (oldval != NULL && newval != NULL) { + char buf_type[7]; + + vim_snprintf(buf_type, ARRAY_SIZE(buf_type), "%s", + (opt_flags & OPT_LOCAL) ? "local" : "global"); + set_vim_var_string(VV_OPTION_OLD, oldval, -1); + set_vim_var_string(VV_OPTION_NEW, newval, -1); + set_vim_var_string(VV_OPTION_TYPE, buf_type, -1); + apply_autocmds(EVENT_OPTIONSET, + (char_u *)options[opt_idx].fullname, NULL, false, NULL); + reset_v_option_vars(); + if (options[opt_idx].flags & P_UI_OPTION) { + ui_call_option_set(cstr_as_string(options[opt_idx].fullname), + STRING_OBJ(cstr_as_string(newval))); + } + } +} + /* * Called after an option changed: check if something needs to be redrawn. */ @@ -4906,15 +4941,14 @@ showoptions ( vimoption_T **items = xmalloc(sizeof(vimoption_T *) * PARAM_COUNT); - /* Highlight title */ - if (all == 2) - MSG_PUTS_TITLE(_("\n--- Terminal codes ---")); - else if (opt_flags & OPT_GLOBAL) + // Highlight title + if (opt_flags & OPT_GLOBAL) { MSG_PUTS_TITLE(_("\n--- Global option values ---")); - else if (opt_flags & OPT_LOCAL) + } else if (opt_flags & OPT_LOCAL) { MSG_PUTS_TITLE(_("\n--- Local option values ---")); - else + } else { MSG_PUTS_TITLE(_("\n--- Options ---")); + } /* * do the loop two times: @@ -4989,14 +5023,39 @@ static int optval_default(vimoption_T *p, char_u *varp) if (varp == NULL) return TRUE; /* hidden option is always at default */ dvi = ((p->flags & P_VI_DEF) || p_cp) ? VI_DEFAULT : VIM_DEFAULT; - if (p->flags & P_NUM) - return *(long *)varp == (long)p->def_val[dvi]; - if (p->flags & P_BOOL) + if (p->flags & P_NUM) { + return *(long *)varp == (long)(intptr_t)p->def_val[dvi]; + } + if (p->flags & P_BOOL) { return *(int *)varp == (int)(intptr_t)p->def_val[dvi]; - /* P_STRING */ + } + // P_STRING return STRCMP(*(char_u **)varp, p->def_val[dvi]) == 0; } +/// Send update to UIs with values of UI relevant options +void ui_refresh_options(void) +{ + for (int opt_idx = 0; options[opt_idx].fullname; opt_idx++) { + uint32_t flags = options[opt_idx].flags; + if (!(flags & P_UI_OPTION)) { + continue; + } + String name = cstr_as_string(options[opt_idx].fullname); + void *varp = options[opt_idx].var; + Object value = OBJECT_INIT; + if (flags & P_BOOL) { + value = BOOLEAN_OBJ(*(int *)varp); + } else if (flags & P_NUM) { + value = INTEGER_OBJ(*(long *)varp); + } else if (flags & P_STRING) { + // cstr_as_string handles NULL string + value = STRING_OBJ(cstr_as_string(*(char **)varp)); + } + ui_call_option_set(name, value); + } +} + /* * showoneopt: show the value of one option * must not be called with a hidden option! @@ -5356,6 +5415,9 @@ void unset_global_local_option(char *name, void *from) case PV_LW: clear_string_option(&buf->b_p_lw); break; + case PV_MENC: + clear_string_option(&buf->b_p_menc); + break; } } @@ -5389,6 +5451,7 @@ static char_u *get_varp_scope(vimoption_T *p, int opt_flags) case PV_UL: return (char_u *)&(curbuf->b_p_ul); case PV_LW: return (char_u *)&(curbuf->b_p_lw); case PV_BKC: return (char_u *)&(curbuf->b_p_bkc); + case PV_MENC: return (char_u *)&(curbuf->b_p_menc); } return NULL; /* "cannot happen" */ } @@ -5444,6 +5507,8 @@ static char_u *get_varp(vimoption_T *p) ? (char_u *)&(curbuf->b_p_ul) : p->var; case PV_LW: return *curbuf->b_p_lw != NUL ? (char_u *)&(curbuf->b_p_lw) : p->var; + case PV_MENC: return *curbuf->b_p_menc != NUL + ? (char_u *)&(curbuf->b_p_menc) : p->var; case PV_ARAB: return (char_u *)&(curwin->w_p_arab); case PV_LIST: return (char_u *)&(curwin->w_p_list); @@ -5540,7 +5605,7 @@ static char_u *get_varp(vimoption_T *p) case PV_KMAP: return (char_u *)&(curbuf->b_p_keymap); case PV_SCL: return (char_u *)&(curwin->w_p_scl); case PV_WINHL: return (char_u *)&(curwin->w_p_winhl); - default: EMSG(_("E356: get_varp ERROR")); + default: IEMSG(_("E356: get_varp ERROR")); } /* always return a valid pointer to avoid a crash! */ return (char_u *)&(curbuf->b_p_wm); @@ -5841,6 +5906,7 @@ void buf_copy_options(buf_T *buf, int flags) buf->b_p_qe = vim_strsave(p_qe); buf->b_p_udf = p_udf; buf->b_p_lw = empty_option; + buf->b_p_menc = empty_option; /* * Don't copy the options set by ex_help(), use the saved values, diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h index b16f222705..66a49fd6e0 100644 --- a/src/nvim/option_defs.h +++ b/src/nvim/option_defs.h @@ -394,11 +394,13 @@ EXTERN char_u *p_dir; /* 'directory' */ EXTERN char_u *p_dy; /* 'display' */ EXTERN unsigned dy_flags; #ifdef IN_OPTION_C -static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", NULL }; +static char *(p_dy_values[]) = { "lastline", "truncate", "uhex", "msgsep", + NULL }; #endif #define DY_LASTLINE 0x001 #define DY_TRUNCATE 0x002 #define DY_UHEX 0x004 +#define DY_MSGSEP 0x008 EXTERN int p_ed; // 'edcompatible' EXTERN int p_emoji; // 'emoji' EXTERN char_u *p_ead; // 'eadirection' @@ -447,13 +449,13 @@ EXTERN char_u *p_popt; // 'printoptions' EXTERN char_u *p_header; // 'printheader' EXTERN int p_prompt; // 'prompt' EXTERN char_u *p_guicursor; // 'guicursor' +EXTERN char_u *p_guifont; // 'guifont' +EXTERN char_u *p_guifontset; // 'guifontset' +EXTERN char_u *p_guifontwide; // 'guifontwide' EXTERN char_u *p_hf; // 'helpfile' EXTERN long p_hh; // 'helpheight' EXTERN char_u *p_hlg; // 'helplang' EXTERN int p_hid; // 'hidden' -// Use P_HID to check if a buffer is to be hidden when it is no longer -// visible in a window. -# define P_HID(buf) (buf_hide(buf)) EXTERN char_u *p_hl; // 'highlight' EXTERN int p_hls; // 'hlsearch' EXTERN long p_hi; // 'history' @@ -478,6 +480,7 @@ 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 char_u *p_lispwords; // 'lispwords' EXTERN long p_ls; // 'laststatus' EXTERN long p_stal; // 'showtabline' @@ -486,6 +489,7 @@ EXTERN char_u *p_lcs; // 'listchars' EXTERN int p_lz; // 'lazyredraw' EXTERN int p_lpl; // 'loadplugins' EXTERN int p_magic; // 'magic' +EXTERN char_u *p_menc; // 'makeencoding' EXTERN char_u *p_mef; // 'makeef' EXTERN char_u *p_mp; // 'makeprg' EXTERN char_u *p_cc; // 'colorcolumn' @@ -733,6 +737,7 @@ enum { , BV_KP , BV_LISP , BV_LW + , BV_MENC , BV_MA , BV_ML , BV_MOD diff --git a/src/nvim/options.lua b/src/nvim/options.lua index dd28a765fd..80484d0ad2 100644 --- a/src/nvim/options.lua +++ b/src/nvim/options.lua @@ -68,7 +68,8 @@ return { type='bool', scope={'global'}, vi_def=true, vim=true, - redraw={'everything'}, + redraw={'everything', 'ui_option'}, + varname='p_arshape', defaults={if_true={vi=true}} }, @@ -91,7 +92,7 @@ return { full_name='ambiwidth', abbreviation='ambw', type='string', scope={'global'}, vi_def=true, - redraw={'everything'}, + redraw={'everything', 'ui_option'}, varname='p_ambw', defaults={if_true={vi="single"}} }, @@ -532,7 +533,7 @@ return { vi_def=true, vim=true, varname='p_csverbose', - defaults={if_true={vi=0}} + defaults={if_true={vi=1}} }, { full_name='cursorbind', abbreviation='crb', @@ -641,7 +642,7 @@ return { vim=true, redraw={'all_windows'}, varname='p_dy', - defaults={if_true={vi="", vim="lastline"}} + defaults={if_true={vi="", vim="lastline,msgsep"}} }, { full_name='eadirection', abbreviation='ead', @@ -661,7 +662,7 @@ return { full_name='emoji', abbreviation='emo', type='bool', scope={'global'}, vi_def=true, - redraw={'everything'}, + redraw={'everything', 'ui_option'}, varname='p_emoji', defaults={if_true={vi=true}} }, @@ -811,7 +812,7 @@ return { vi_def=true, redraw={'all_windows'}, varname='p_fcs', - defaults={if_true={vi="vert:|,fold:-"}} + defaults={if_true={vi=''}} }, { full_name='fixendofline', abbreviation='fixeol', @@ -1021,23 +1022,26 @@ return { type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, - redraw={'everything'}, - enable_if=false, + varname='p_guifont', + redraw={'everything', 'ui_option'}, + defaults={if_true={vi=""}} }, { full_name='guifontset', abbreviation='gfs', type='string', list='onecomma', scope={'global'}, vi_def=true, - redraw={'everything'}, - enable_if=false, + varname='p_guifontset', + redraw={'everything', 'ui_option'}, + defaults={if_true={vi=""}} }, { full_name='guifontwide', abbreviation='gfw', type='string', list='onecomma', scope={'global'}, deny_duplicates=true, vi_def=true, - redraw={'everything'}, - enable_if=false, + redraw={'everything', 'ui_option'}, + varname='p_guifontwide', + defaults={if_true={vi=""}} }, { full_name='guioptions', abbreviation='go', @@ -1395,8 +1399,9 @@ return { full_name='linespace', abbreviation='lsp', type='number', scope={'global'}, vi_def=true, - redraw={'everything'}, - enable_if=false, + redraw={'everything', 'ui_option'}, + varname='p_linespace', + defaults={if_true={vi=0}} }, { full_name='lisp', @@ -1453,6 +1458,13 @@ return { defaults={if_true={vi=""}} }, { + full_name='makeencoding', abbreviation='menc', + type='string', scope={'global', 'buffer'}, + vi_def=true, + varname='p_menc', + defaults={if_true={vi=""}} + }, + { full_name='makeprg', abbreviation='mp', type='string', scope={'global', 'buffer'}, secure=true, @@ -2036,7 +2048,7 @@ return { varname='p_shcf', defaults={ condition='WIN32', - if_true={vi="/c"}, + if_true={vi="/s /c"}, if_false={vi="-c"} } }, @@ -2092,7 +2104,11 @@ return { secure=true, vi_def=true, varname='p_sxq', - defaults={if_true={vi=""}} + defaults={ + condition='WIN32', + if_true={vi="\""}, + if_false={vi=""}, + } }, { full_name='shellxescape', abbreviation='sxe', @@ -2164,7 +2180,7 @@ return { full_name='showtabline', abbreviation='stal', type='number', scope={'global'}, vi_def=true, - redraw={'all_windows'}, + redraw={'all_windows', 'ui_option'}, varname='p_stal', defaults={if_true={vi=1}} }, @@ -2173,7 +2189,7 @@ return { type='number', scope={'global'}, vi_def=true, varname='p_ss', - defaults={if_true={vi=0}} + defaults={if_true={vi=1}} }, { full_name='sidescrolloff', abbreviation='siso', @@ -2435,7 +2451,7 @@ return { full_name='termguicolors', abbreviation='tgc', type='bool', scope={'global'}, vi_def=false, - redraw={'everything'}, + redraw={'ui_option'}, varname='p_tgc', defaults={if_true={vi=false}} }, @@ -2505,11 +2521,10 @@ return { full_name='titleold', type='string', scope={'global'}, secure=true, - gettext=true, no_mkrc=true, vi_def=true, varname='p_titleold', - defaults={if_true={vi=N_("")}} + defaults={if_true={vi=""}} }, { full_name='titlestring', diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c index de0cd10d9c..3fcb9415c7 100644 --- a/src/nvim/os/env.c +++ b/src/nvim/os/env.c @@ -886,8 +886,8 @@ bool os_setenv_append_path(const char *fname) // No prescribed maximum on unix. # define MAX_ENVPATHLEN INT_MAX #endif - if (!path_is_absolute_path((char_u *)fname)) { - EMSG2(_(e_intern2), "os_setenv_append_path()"); + if (!path_is_absolute((char_u *)fname)) { + internal_error("os_setenv_append_path()"); return false; } const char *tail = (char *)path_tail_with_sep((char_u *)fname); diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c index b040904d0b..0ef9307a5a 100644 --- a/src/nvim/os/fileio.c +++ b/src/nvim/os/fileio.c @@ -4,7 +4,7 @@ /// @file fileio.c /// /// Buffered reading/writing to a file. Unlike fileio.c this is not dealing with -/// Neovim stuctures for buffer, with autocommands, etc: just fopen/fread/fwrite +/// Nvim stuctures for buffer, with autocommands, etc: just fopen/fread/fwrite /// replacement. #include <assert.h> @@ -39,11 +39,11 @@ /// @param[in] fname File name to open. /// @param[in] flags Flags, @see FileOpenFlags. Currently reading from and /// writing to the file at once is not supported, so either -/// FILE_WRITE_ONLY or FILE_READ_ONLY is required. +/// kFileWriteOnly or kFileReadOnly is required. /// @param[in] mode Permissions for the newly created file (ignored if flags -/// does not have FILE_CREATE\*). +/// does not have kFileCreate\*). /// -/// @return Error code (@see os_strerror()) or 0. +/// @return Error code, or 0 on success. @see os_strerror() int file_open(FileDescriptor *const ret_fp, const char *const fname, const int flags, const int mode) FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT @@ -124,12 +124,11 @@ int file_open_fd(FileDescriptor *const ret_fp, const int fd, /// Like file_open(), but allocate and return ret_fp /// -/// @param[out] error Error code, @see os_strerror(). Is set to zero on -/// success. +/// @param[out] error Error code, or 0 on success. @see os_strerror() /// @param[in] fname File name to open. /// @param[in] flags Flags, @see FileOpenFlags. /// @param[in] mode Permissions for the newly created file (ignored if flags -/// does not have FILE_CREATE\*). +/// does not have kFileCreate\*). /// /// @return [allocated] Opened file or NULL in case of error. FileDescriptor *file_open_new(int *const error, const char *const fname, @@ -146,8 +145,7 @@ FileDescriptor *file_open_new(int *const error, const char *const fname, /// Like file_open_fd(), but allocate and return ret_fp /// -/// @param[out] error Error code, @see os_strerror(). Is set to zero on -/// success. +/// @param[out] error Error code, or 0 on success. @see os_strerror() /// @param[in] fd File descriptor to wrap. /// @param[in] flags Flags, @see FileOpenFlags. /// @param[in] mode Permissions for the newly created file (ignored if flags diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c index 87bab93420..0414794d01 100644 --- a/src/nvim/os/fs.c +++ b/src/nvim/os/fs.c @@ -222,7 +222,7 @@ int os_exepath(char *buffer, size_t *size) bool os_can_exe(const char_u *name, char_u **abspath, bool use_path) FUNC_ATTR_NONNULL_ARG(1) { - bool no_path = !use_path || path_is_absolute_path(name); + bool no_path = !use_path || path_is_absolute(name); #ifndef WIN32 // If the filename is "qualified" (relative or absolute) do not check $PATH. no_path |= (name[0] == '.' @@ -244,7 +244,7 @@ bool os_can_exe(const char_u *name, char_u **abspath, bool use_path) #endif if (ok) { if (abspath != NULL) { - *abspath = save_absolute_path(name); + *abspath = save_abs_path(name); } return true; } @@ -357,7 +357,7 @@ static bool is_executable_in_path(const char_u *name, char_u **abspath) #endif if (ok) { if (abspath != NULL) { // Caller asked for a copy of the path. - *abspath = save_absolute_path((char_u *)buf); + *abspath = save_abs_path((char_u *)buf); } rv = true; @@ -485,7 +485,7 @@ ptrdiff_t os_read(const int fd, bool *const ret_eof, char *const ret_buf, while (read_bytes != size) { assert(size >= read_bytes); const ptrdiff_t cur_read_bytes = read(fd, ret_buf + read_bytes, - size - read_bytes); + IO_COUNT(size - read_bytes)); if (cur_read_bytes > 0) { read_bytes += (size_t)cur_read_bytes; } @@ -598,7 +598,7 @@ ptrdiff_t os_write(const int fd, const char *const buf, const size_t size, while (written_bytes != size) { assert(size >= written_bytes); const ptrdiff_t cur_written_bytes = write(fd, buf + written_bytes, - size - written_bytes); + IO_COUNT(size - written_bytes)); if (cur_written_bytes > 0) { written_bytes += (size_t)cur_written_bytes; } @@ -1026,7 +1026,7 @@ bool os_fileid_equal_fileinfo(const FileID *file_id, /// to and return that name in allocated memory. /// Otherwise NULL is returned. char *os_resolve_shortcut(const char *fname) - FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC { HRESULT hr; IPersistFile *ppf = NULL; diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c index 7d6f2abd7f..405500767d 100644 --- a/src/nvim/os/input.c +++ b/src/nvim/os/input.c @@ -188,7 +188,7 @@ size_t input_enqueue(String keys) uint8_t buf[6] = { 0 }; unsigned int new_size = trans_special((const uint8_t **)&ptr, (size_t)(end - ptr), buf, true, - true); + false); if (new_size) { new_size = handle_mouse_event(&ptr, buf, new_size); diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c new file mode 100644 index 0000000000..47c278ee97 --- /dev/null +++ b/src/nvim/os/lang.c @@ -0,0 +1,43 @@ +// 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 + +#ifdef __APPLE__ +# define Boolean CFBoolean // Avoid conflict with API's Boolean +# include <CoreFoundation/CFLocale.h> +# include <CoreFoundation/CFString.h> +# undef Boolean +#endif + +#ifdef HAVE_LOCALE_H +# include <locale.h> +#endif +#include "nvim/os/os.h" + +void lang_init(void) +{ +#ifdef __APPLE__ + if (os_getenv("LANG") == NULL) { + CFLocaleRef cf_locale = CFLocaleCopyCurrent(); + CFTypeRef cf_lang_region = CFLocaleGetValue(cf_locale, + kCFLocaleIdentifier); + CFRetain(cf_lang_region); + CFRelease(cf_locale); + + const char *lang_region = CFStringGetCStringPtr(cf_lang_region, + kCFStringEncodingUTF8); + if (lang_region) { + os_setenv("LANG", lang_region, true); + } else { + char buf[20] = { 0 }; + if (CFStringGetCString(cf_lang_region, buf, 20, + kCFStringEncodingUTF8)) { + os_setenv("LANG", lang_region, true); + } + } + CFRelease(cf_lang_region); +# ifdef HAVE_LOCALE_H + setlocale(LC_ALL, ""); +# endif + } +#endif +} diff --git a/src/nvim/os/lang.h b/src/nvim/os/lang.h new file mode 100644 index 0000000000..f60e064f57 --- /dev/null +++ b/src/nvim/os/lang.h @@ -0,0 +1,7 @@ +#ifndef NVIM_OS_LANG_H +#define NVIM_OS_LANG_H + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/lang.h.generated.h" +#endif +#endif // NVIM_OS_LANG_H diff --git a/src/nvim/os/os_defs.h b/src/nvim/os/os_defs.h index 87f8d214bd..f81785675e 100644 --- a/src/nvim/os/os_defs.h +++ b/src/nvim/os/os_defs.h @@ -4,7 +4,6 @@ #include <ctype.h> #include <stdio.h> #include <stdlib.h> -#include <sys/param.h> #include <sys/stat.h> #include <sys/types.h> @@ -14,13 +13,6 @@ # include "nvim/os/unix_defs.h" #endif -/// File descriptor number used for standard IO streams -enum { - OS_STDIN_FILENO = STDIN_FILENO, - OS_STDOUT_FILENO = STDOUT_FILENO, - OS_STDERR_FILENO = STDERR_FILENO, -}; - #define BASENAMELEN (NAME_MAX - 5) // Use the system path length if it makes sense. diff --git a/src/nvim/os/process.c b/src/nvim/os/process.c new file mode 100644 index 0000000000..a67e7487eb --- /dev/null +++ b/src/nvim/os/process.c @@ -0,0 +1,267 @@ +// 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 + +/// OS process functions +/// +/// psutil is a good reference for cross-platform syscall voodoo: +/// https://github.com/giampaolo/psutil/tree/master/psutil/arch + +#include <uv.h> // for HANDLE (win32) + +#ifdef WIN32 +# include <tlhelp32.h> // for CreateToolhelp32Snapshot +#endif + +#if defined(__FreeBSD__) // XXX: OpenBSD ? +# include <string.h> +# include <sys/types.h> +# include <sys/user.h> +#endif + +#if defined(__NetBSD__) || defined(__OpenBSD__) +# include <sys/param.h> +#endif + +#if defined(__APPLE__) || defined(BSD) +# include <sys/sysctl.h> +# include <pwd.h> +#endif + +#include "nvim/globals.h" +#include "nvim/log.h" +#include "nvim/os/process.h" +#include "nvim/os/os.h" +#include "nvim/os/os_defs.h" +#include "nvim/api/private/helpers.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/process.c.generated.h" +#endif + +#ifdef WIN32 +static bool os_proc_tree_kill_rec(HANDLE process, int sig) +{ + if (process == NULL) { + return false; + } + PROCESSENTRY32 pe; + DWORD pid = GetProcessId(process); + + if (pid != 0) { + HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (h != INVALID_HANDLE_VALUE) { + pe.dwSize = sizeof(PROCESSENTRY32); + if (!Process32First(h, &pe)) { + goto theend; + } + do { + if (pe.th32ParentProcessID == pid) { + HANDLE ph = OpenProcess(PROCESS_ALL_ACCESS, false, pe.th32ProcessID); + if (ph != NULL) { + os_proc_tree_kill_rec(ph, sig); + CloseHandle(ph); + } + } + } while (Process32Next(h, &pe)); + CloseHandle(h); + } + } + +theend: + return (bool)TerminateProcess(process, (unsigned int)sig); +} +/// Kills process `pid` and its descendants recursively. +bool os_proc_tree_kill(int pid, int sig) +{ + assert(sig >= 0); + assert(sig == SIGTERM || sig == SIGKILL); + if (pid > 0) { + ILOG("terminating process tree: %d", pid); + HANDLE h = OpenProcess(PROCESS_ALL_ACCESS, false, (DWORD)pid); + return os_proc_tree_kill_rec(h, sig); + } else { + ELOG("invalid pid: %d", pid); + } + return false; +} +#else +/// Kills process group where `pid` is the process group leader. +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); + } + return false; +} +#endif + +/// Gets the process ids of the immediate children of process `ppid`. +/// +/// @param ppid Process to inspect. +/// @param[out,allocated] proc_list Child process ids. +/// @param[out] proc_count Number of child processes. +/// @return 0 on success, 1 if process not found, 2 on other error. +int os_proc_children(int ppid, int **proc_list, size_t *proc_count) +{ + if (ppid < 0) { + return 2; + } + + int *temp = NULL; + *proc_list = NULL; + *proc_count = 0; + +#ifdef WIN32 + PROCESSENTRY32 pe; + + // Snapshot of all processes. + HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (h == INVALID_HANDLE_VALUE) { + return 2; + } + + pe.dwSize = sizeof(PROCESSENTRY32); + // Get root process. + if (!Process32First(h, &pe)) { + CloseHandle(h); + return 2; + } + // Collect processes whose parent matches `ppid`. + do { + if (pe.th32ParentProcessID == (DWORD)ppid) { + temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp)); + temp[*proc_count] = (int)pe.th32ProcessID; + (*proc_count)++; + } + } while (Process32Next(h, &pe)); + CloseHandle(h); + +#elif defined(__APPLE__) || defined(BSD) +# if defined(__APPLE__) +# define KP_PID(o) o.kp_proc.p_pid +# define KP_PPID(o) o.kp_eproc.e_ppid +# elif defined(__FreeBSD__) +# define KP_PID(o) o.ki_pid +# define KP_PPID(o) o.ki_ppid +# else +# define KP_PID(o) o.p_pid +# define KP_PPID(o) o.p_ppid +# endif +# ifdef __NetBSD__ + static int name[] = { + CTL_KERN, KERN_PROC2, KERN_PROC_ALL, 0, (int)(sizeof(struct kinfo_proc2)), 0 + }; +# else + static int name[] = { CTL_KERN, KERN_PROC, KERN_PROC_ALL, 0 }; +# endif + + // Get total process count. + size_t len = 0; + int rv = sysctl(name, ARRAY_SIZE(name) - 1, NULL, &len, NULL, 0); + if (rv) { + return 2; + } + + // Get ALL processes. +# ifdef __NetBSD__ + struct kinfo_proc2 *p_list = xmalloc(len); +# else + struct kinfo_proc *p_list = xmalloc(len); +# endif + rv = sysctl(name, ARRAY_SIZE(name) - 1, p_list, &len, NULL, 0); + if (rv) { + xfree(p_list); + return 2; + } + + // Collect processes whose parent matches `ppid`. + bool exists = false; + size_t p_count = len / sizeof(*p_list); + for (size_t i = 0; i < p_count; i++) { + exists = exists || KP_PID(p_list[i]) == ppid; + if (KP_PPID(p_list[i]) == ppid) { + temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp)); + temp[*proc_count] = KP_PID(p_list[i]); + (*proc_count)++; + } + } + xfree(p_list); + if (!exists) { + return 1; // Process not found. + } + +#elif defined(__linux__) + char proc_p[256] = { 0 }; + // Collect processes whose parent matches `ppid`. + // Rationale: children are defined in thread with same ID of process. + snprintf(proc_p, sizeof(proc_p), "/proc/%d/task/%d/children", ppid, ppid); + FILE *fp = fopen(proc_p, "r"); + if (fp == NULL) { + return 2; // Process not found, or /proc/…/children not supported. + } + int match_pid; + while (fscanf(fp, "%d", &match_pid) > 0) { + temp = xrealloc(temp, (*proc_count + 1) * sizeof(*temp)); + temp[*proc_count] = match_pid; + (*proc_count)++; + } + fclose(fp); +#endif + + *proc_list = temp; + return 0; +} + +#ifdef WIN32 +/// Gets various properties of the process identified by `pid`. +/// +/// @param pid Process to inspect. +/// @return Map of process properties, empty on error. +Dictionary os_proc_info(int pid) +{ + Dictionary pinfo = ARRAY_DICT_INIT; + PROCESSENTRY32 pe; + + // Snapshot of all processes. This is used instead of: + // OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, …) + // to avoid ERROR_PARTIAL_COPY. https://stackoverflow.com/a/29942376 + HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + if (h == INVALID_HANDLE_VALUE) { + return pinfo; // Return empty. + } + + pe.dwSize = sizeof(PROCESSENTRY32); + // Get root process. + if (!Process32First(h, &pe)) { + CloseHandle(h); + return pinfo; // Return empty. + } + // Find the process. + do { + if (pe.th32ProcessID == (DWORD)pid) { + break; + } + } while (Process32Next(h, &pe)); + CloseHandle(h); + + if (pe.th32ProcessID == (DWORD)pid) { + PUT(pinfo, "pid", INTEGER_OBJ(pid)); + PUT(pinfo, "ppid", INTEGER_OBJ((int)pe.th32ParentProcessID)); + PUT(pinfo, "name", STRING_OBJ(cstr_to_string(pe.szExeFile))); + } + + return pinfo; +} +#endif diff --git a/src/nvim/os/process.h b/src/nvim/os/process.h new file mode 100644 index 0000000000..1722d56bd3 --- /dev/null +++ b/src/nvim/os/process.h @@ -0,0 +1,11 @@ +#ifndef NVIM_OS_PROCESS_H +#define NVIM_OS_PROCESS_H + +#include <stddef.h> +#include "nvim/api/private/defs.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "os/process.h.generated.h" +#endif + +#endif // NVIM_OS_PROCESS_H diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_process_unix.c index 53301e4b53..dfe2cfbb8d 100644 --- a/src/nvim/os/pty_process_unix.c +++ b/src/nvim/os/pty_process_unix.c @@ -72,8 +72,7 @@ int pty_process_spawn(PtyProcess *ptyproc) ELOG("forkpty failed: %s", strerror(errno)); return status; } else if (pid == 0) { - init_child(ptyproc); - abort(); + init_child(ptyproc); // never returns } // make sure the master file descriptor is non blocking @@ -146,8 +145,12 @@ void pty_process_teardown(Loop *loop) uv_signal_stop(&loop->children_watcher); } -static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL +static void init_child(PtyProcess *ptyproc) + FUNC_ATTR_NONNULL_ALL { + // New session/process-group. #6530 + setsid(); + unsetenv("COLUMNS"); unsetenv("LINES"); unsetenv("TERMCAP"); @@ -163,14 +166,15 @@ static void init_child(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL Process *proc = (Process *)ptyproc; if (proc->cwd && os_chdir(proc->cwd) != 0) { - fprintf(stderr, "chdir failed: %s\n", strerror(errno)); + ELOG("chdir failed: %s", strerror(errno)); return; } char *prog = ptyproc->process.argv[0]; setenv("TERM", ptyproc->term_name ? ptyproc->term_name : "ansi", 1); execvp(prog, ptyproc->process.argv); - fprintf(stderr, "execvp failed: %s: %s\n", strerror(errno), prog); + ELOG("execvp failed: %s: %s", strerror(errno), prog); + _exit(122); // 122 is EXEC_FAILED in the Vim source. } static void init_termios(struct termios *termios) FUNC_ATTR_NONNULL_ALL diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_process_win.c index 3c4839a076..a6774a6275 100644 --- a/src/nvim/os/pty_process_win.c +++ b/src/nvim/os/pty_process_win.c @@ -1,3 +1,6 @@ +// 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 + #include <assert.h> #include <stdbool.h> #include <stdlib.h> @@ -131,7 +134,7 @@ int pty_process_spawn(PtyProcess *ptyproc) } goto cleanup; } - proc->pid = GetProcessId(process_handle); + proc->pid = (int)GetProcessId(process_handle); if (!RegisterWaitForSingleObject( &ptyproc->finish_wait, @@ -339,20 +342,20 @@ static void quote_cmd_arg(char *dest, size_t dest_remaining, const char *src) } // Expected input/output: - // input : hello"world - // output: "hello\"world" - // input : hello""world - // output: "hello\"\"world" - // input : hello\world - // output: hello\world - // input : hello\\world - // output: hello\\world - // input : hello\"world - // output: "hello\\\"world" - // input : hello\\"world - // output: "hello\\\\\"world" - // input : hello world\ - // output: "hello world\\" + // input : 'hello"world' + // output: '"hello\"world"' + // input : 'hello""world' + // output: '"hello\"\"world"' + // input : 'hello\world' + // output: 'hello\world' + // input : 'hello\\world' + // output: 'hello\\world' + // input : 'hello\"world' + // output: '"hello\\\"world"' + // input : 'hello\\"world' + // output: '"hello\\\\\"world"' + // input : 'hello world\' + // output: '"hello world\\"' assert(dest_remaining--); *(dest++) = NUL; diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c index e32c6e05d2..04f59d7522 100644 --- a/src/nvim/os/shell.c +++ b/src/nvim/os/shell.c @@ -123,6 +123,9 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) if (opts & kShellOptRead) { output_ptr = &output; forward_output = false; + } else if (opts & kShellOptDoOut) { + // Caller has already redirected output + forward_output = false; } } @@ -133,7 +136,7 @@ int os_call_shell(char_u *cmd, ShellOpts opts, char_u *extra_args) xfree(input.data); if (output) { - (void)write_output(output, nread, true, true); + (void)write_output(output, nread, true); xfree(output); } @@ -189,6 +192,7 @@ static int do_os_system(char **argv, { out_data_decide_throttle(0); // Initialize throttle decider. out_data_ring(NULL, 0); // Initialize output ring-buffer. + bool has_input = (input != NULL && input[0] != '\0'); // the output buffer DynamicBuffer buf = DYNAMIC_BUFFER_INIT; @@ -212,7 +216,7 @@ static int do_os_system(char **argv, MultiQueue *events = multiqueue_new_child(main_loop.events); proc->events = events; proc->argv = argv; - int status = process_spawn(proc, input != NULL, true, true); + int status = process_spawn(proc, has_input, true, true); if (status) { loop_poll_events(&main_loop, 0); // Failed, probably 'shell' is not executable. @@ -231,7 +235,7 @@ static int do_os_system(char **argv, // deal with stream events as fast a possible. It prevents closing the // streams while there's still data in the OS buffer (due to the process // exiting before all data is read). - if (input != NULL) { + if (has_input) { wstream_init(&proc->in, 0); } rstream_init(&proc->out, 0); @@ -240,8 +244,8 @@ static int do_os_system(char **argv, rstream_start(&proc->err, data_cb, &buf); // write the input, if any - if (input) { - WBuffer *input_buffer = wstream_new_buffer((char *) input, len, 1, NULL); + if (has_input) { + WBuffer *input_buffer = wstream_new_buffer((char *)input, len, 1, NULL); if (!wstream_write(&proc->in, input_buffer)) { // couldn't write, stop the process and tell the user about it @@ -256,11 +260,25 @@ static int do_os_system(char **argv, // busy state. ui_busy_start(); ui_flush(); + if (forward_output) { + msg_sb_eol(); + msg_start(); + msg_no_more = true; + lines_left = -1; + } int exitcode = process_wait(proc, -1, NULL); if (!got_int && out_data_decide_throttle(0)) { // Last chunk of output was skipped; display it now. out_data_ring(NULL, SIZE_MAX); } + if (forward_output) { + // caller should decide if wait_return is invoked + no_wait_return++; + msg_end(); + no_wait_return--; + msg_no_more = false; + } + ui_busy_stop(); // prepare the out parameters if requested @@ -370,10 +388,10 @@ static bool out_data_decide_throttle(size_t size) pulse_msg[1] = (tick == 0 || 1 == tick) ? ' ' : '.'; pulse_msg[2] = (tick == 0 || 1 == tick || 2 == tick) ? ' ' : '.'; if (visit == 1) { - screen_del_lines(0, 0, 1, (int)Rows, NULL); + msg_putchar('\n'); } - int lastrow = (int)Rows - 1; - screen_puts_len((char_u *)pulse_msg, ARRAY_SIZE(pulse_msg), lastrow, 0, 0); + msg_putchar('\r'); // put cursor at start of line + msg_puts(pulse_msg); ui_flush(); return true; } @@ -404,7 +422,7 @@ static void out_data_ring(char *output, size_t size) } if (output == NULL && size == SIZE_MAX) { // Print mode - out_data_append_to_screen(last_skipped, last_skipped_len, true); + out_data_append_to_screen(last_skipped, &last_skipped_len, true); return; } @@ -432,60 +450,40 @@ static void out_data_ring(char *output, size_t size) /// @param output Data to append to screen lines. /// @param remaining Size of data. /// @param new_line If true, next data output will be on a new line. -static void out_data_append_to_screen(char *output, size_t remaining, - bool new_line) +static void out_data_append_to_screen(char *output, size_t *count, + bool eof) { - static colnr_T last_col = 0; // Column of last row to append to. - - size_t off = 0; - int last_row = (int)Rows - 1; - - while (output != NULL && off < remaining) { - // Found end of line? - if (output[off] == NL) { - // Can we start a new line or do we need to continue the last one? - if (last_col == 0) { - screen_del_lines(0, 0, 1, (int)Rows, NULL); + char *p = output, *end = output + *count; + while (p < end) { + if (*p == '\n' || *p == '\r' || *p == TAB || *p == BELL) { + msg_putchar_attr((uint8_t)(*p), 0); + p++; + } else { + // Note: this is not 100% precise: + // 1. we don't check if received continuation bytes are already invalid + // and we thus do some buffering that could be avoided + // 2. we don't compose chars over buffer boundaries, even if we see an + // incomplete UTF-8 sequence that could be composing with the last + // complete sequence. + // This will be corrected when we switch to vterm based implementation + int i = *p ? mb_ptr2len_len((char_u *)p, (int)(end-p)) : 1; + if (!eof && i == 1 && utf8len_tab_zero[*(uint8_t *)p] > (end-p)) { + *count = (size_t)(p - output); + goto end; } - screen_puts_len((char_u *)output, (int)off, last_row, last_col, 0); - last_col = 0; - size_t skip = off + 1; - output += skip; - remaining -= skip; - off = 0; - continue; - } - - // TODO(bfredl): using msg_puts would be better until - // terminal emulation is implemented. - if (output[off] < 0x20) { - output[off] = ' '; - } - - off++; - } - - if (output != NULL && remaining) { - if (last_col == 0) { - screen_del_lines(0, 0, 1, (int)Rows, NULL); + (void)msg_outtrans_len_attr((char_u *)p, i, 0); + p += i; } - screen_puts_len((char_u *)output, (int)remaining, last_row, last_col, 0); - last_col += (colnr_T)remaining; - } - - if (new_line) { - last_col = 0; } +end: ui_flush(); } static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data, bool eof) { - // We always output the whole buffer, so the buffer can never - // wrap around. size_t cnt; char *ptr = rbuffer_read_ptr(buf, &cnt); @@ -494,12 +492,16 @@ static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data, // Save the skipped output. If it is the final chunk, we display it later. out_data_ring(ptr, cnt); } else { - out_data_append_to_screen(ptr, cnt, eof); + out_data_append_to_screen(ptr, &cnt, eof); } if (cnt) { rbuffer_consumed(buf, cnt); } + + // Move remaining data to start of buffer, so the buffer can never + // wrap around. + rbuffer_reset(buf); } /// Parses a command string into a sequence of words, taking quotes into @@ -588,14 +590,10 @@ static void read_input(DynamicBuffer *buf) if (len == l) { // Finished a line, add a NL, unless this line should not have one. - // FIXME need to make this more readable if (lnum != curbuf->b_op_end.lnum - || (!curbuf->b_p_bin - && curbuf->b_p_fixeol) + || (!curbuf->b_p_bin && curbuf->b_p_fixeol) || (lnum != curbuf->b_no_eol_lnum - && (lnum != - curbuf->b_ml.ml_line_count - || curbuf->b_p_eol))) { + && (lnum != curbuf->b_ml.ml_line_count || curbuf->b_p_eol))) { dynamic_buffer_ensure(buf, buf->len + 1); buf->data[buf->len++] = NL; } @@ -611,28 +609,20 @@ static void read_input(DynamicBuffer *buf) } } -static size_t write_output(char *output, size_t remaining, bool to_buffer, - bool eof) +static size_t write_output(char *output, size_t remaining, bool eof) { if (!output) { return 0; } - char replacement_NUL = to_buffer ? NL : 1; char *start = output; size_t off = 0; - int lastrow = (int)Rows - 1; while (off < remaining) { if (output[off] == NL) { // Insert the line - if (to_buffer) { - output[off] = NUL; - ml_append(curwin->w_cursor.lnum++, (char_u *)output, (int)off + 1, - false); - } else { - screen_del_lines(0, 0, 1, (int)Rows, NULL); - screen_puts_len((char_u *)output, (int)off, lastrow, 0, 0); - } + output[off] = NUL; + ml_append(curwin->w_cursor.lnum++, (char_u *)output, (int)off + 1, + false); size_t skip = off + 1; output += skip; remaining -= skip; @@ -642,24 +632,19 @@ static size_t write_output(char *output, size_t remaining, bool to_buffer, if (output[off] == NUL) { // Translate NUL to NL - output[off] = replacement_NUL; + output[off] = NL; } off++; } if (eof) { if (remaining) { - if (to_buffer) { - // append unfinished line - ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false); - // remember that the NL was missing - curbuf->b_no_eol_lnum = curwin->w_cursor.lnum; - } else { - screen_del_lines(0, 0, 1, (int)Rows, NULL); - screen_puts_len((char_u *)output, (int)remaining, lastrow, 0, 0); - } + // append unfinished line + ml_append(curwin->w_cursor.lnum++, (char_u *)output, 0, false); + // remember that the NL was missing + curbuf->b_no_eol_lnum = curwin->w_cursor.lnum; output += remaining; - } else if (to_buffer) { + } else { curbuf->b_no_eol_lnum = 0; } } diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c index fd6d3b32e4..732be072e1 100644 --- a/src/nvim/os/signal.c +++ b/src/nvim/os/signal.c @@ -10,6 +10,7 @@ #endif #include "nvim/ascii.h" +#include "nvim/log.h" #include "nvim/vim.h" #include "nvim/globals.h" #include "nvim/memline.h" @@ -162,7 +163,7 @@ static void on_signal(SignalWatcher *handle, int signum, void *data) } break; default: - fprintf(stderr, "Invalid signal %d", signum); + ELOG("invalid signal: %d", signum); break; } } diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c index c471352c02..290d421acc 100644 --- a/src/nvim/os/time.c +++ b/src/nvim/os/time.c @@ -38,33 +38,33 @@ uint64_t os_hrtime(void) return uv_hrtime(); } -/// Sleeps for a certain amount of milliseconds. +/// Sleeps for `ms` milliseconds. /// -/// @param milliseconds Number of milliseconds to sleep +/// @param ms Number of milliseconds to sleep /// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt. -void os_delay(uint64_t milliseconds, bool ignoreinput) +void os_delay(uint64_t ms, bool ignoreinput) { if (ignoreinput) { - if (milliseconds > INT_MAX) { - milliseconds = INT_MAX; + if (ms > INT_MAX) { + ms = INT_MAX; } - LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)milliseconds, got_int); + LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, (int)ms, got_int); } else { - os_microdelay(milliseconds * 1000u, ignoreinput); + os_microdelay(ms * 1000u, ignoreinput); } } -/// Sleeps for a certain amount of microseconds. +/// Sleeps for `us` microseconds. /// -/// @param ms Number of microseconds to sleep. +/// @param us Number of microseconds to sleep. /// @param ignoreinput If true, ignore all input (including SIGINT/CTRL-C). /// If false, waiting is aborted on any input. -void os_microdelay(uint64_t ms, bool ignoreinput) +void os_microdelay(uint64_t us, bool ignoreinput) { uint64_t elapsed = 0u; uint64_t base = uv_hrtime(); // Convert microseconds to nanoseconds, or UINT64_MAX on overflow. - const uint64_t ns = (ms < UINT64_MAX / 1000u) ? ms * 1000u : UINT64_MAX; + const uint64_t ns = (us < UINT64_MAX / 1000u) ? us * 1000u : UINT64_MAX; uv_mutex_lock(&delay_mutex); diff --git a/src/nvim/os/unix_defs.h b/src/nvim/os/unix_defs.h index 5c9daca476..60a2dfa882 100644 --- a/src/nvim/os/unix_defs.h +++ b/src/nvim/os/unix_defs.h @@ -1,9 +1,8 @@ #ifndef NVIM_OS_UNIX_DEFS_H #define NVIM_OS_UNIX_DEFS_H -// Windows doesn't have unistd.h, so we include it here to avoid numerous -// instances of `#ifdef WIN32'. #include <unistd.h> +#include <sys/param.h> // POSIX.1-2008 says that NAME_MAX should be in here #include <limits.h> diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h index 8fd2e51f8b..db93f016bf 100644 --- a/src/nvim/os/win_defs.h +++ b/src/nvim/os/win_defs.h @@ -32,11 +32,8 @@ // Windows defines a RGB macro that produces 0x00bbggrr color values for use // with GDI. Our macro is different, and we don't use GDI. -#if defined(RGB) -# undef RGB - // Duplicated from macros.h to avoid include-order sensitivity. -# define RGB(r, g, b) ((r << 16) | (g << 8) | b) -#endif +// Duplicated from macros.h to avoid include-order sensitivity. +#define RGB_(r, g, b) ((r << 16) | (g << 8) | b) #ifdef _MSC_VER # ifndef inline @@ -45,6 +42,9 @@ # ifndef restrict # define restrict __restrict # endif +# ifndef STDIN_FILENO +# define STDIN_FILENO _fileno(stdin) +# endif # ifndef STDOUT_FILENO # define STDOUT_FILENO _fileno(stdout) # endif @@ -60,6 +60,7 @@ #ifdef _MSC_VER typedef SSIZE_T ssize_t; +typedef int mode_t; #endif #ifndef SSIZE_MAX diff --git a/src/nvim/os_unix.c b/src/nvim/os_unix.c index d7ba675b68..a27fee4e90 100644 --- a/src/nvim/os_unix.c +++ b/src/nvim/os_unix.c @@ -133,7 +133,8 @@ void mch_free_acl(vim_acl_T aclent) } #endif -void mch_exit(int r) FUNC_ATTR_NORETURN +void mch_exit(int r) + FUNC_ATTR_NORETURN { exiting = true; diff --git a/src/nvim/path.c b/src/nvim/path.c index 51adcfb135..168d835a66 100644 --- a/src/nvim/path.c +++ b/src/nvim/path.c @@ -332,7 +332,7 @@ int path_fnamencmp(const char *const fname1, const char *const fname2, && (p_fic ? (c1 != c2 && CH_FOLD(c1) != CH_FOLD(c2)) : c1 != c2)) { break; } - len -= MB_PTR2LEN((const char_u *)p1); + len -= (size_t)MB_PTR2LEN((const char_u *)p1); p1 += MB_PTR2LEN((const char_u *)p1); p2 += MB_PTR2LEN((const char_u *)p2); } @@ -452,10 +452,10 @@ 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_absolute_path(const char_u *name) +char_u *save_abs_path(const char_u *name) FUNC_ATTR_MALLOC FUNC_ATTR_NONNULL_ALL { - if (!path_is_absolute_path(name)) { + if (!path_is_absolute(name)) { return (char_u *)FullName_save((char *)name, true); } return vim_strsave((char_u *) name); @@ -814,7 +814,7 @@ static void expand_path_option(char_u *curdir, garray_T *gap) STRCPY(buf, curdir); // relative to current directory } else if (path_with_url((char *)buf)) { continue; // URL can't be used here - } else if (!path_is_absolute_path(buf)) { + } else if (!path_is_absolute(buf)) { // Expand relative path to their full path equivalent size_t len = STRLEN(curdir); if (len + STRLEN(buf) + 3 > MAXPATHL) { @@ -949,19 +949,17 @@ static void uniquefy_paths(garray_T *gap, char_u *pattern) } } - if (path_is_absolute_path(path)) { - /* - * Last resort: shorten relative to curdir if possible. - * 'possible' means: - * 1. It is under the current directory. - * 2. The result is actually shorter than the original. - * - * Before curdir After - * /foo/bar/file.txt /foo/bar ./file.txt - * c:\foo\bar\file.txt c:\foo\bar .\file.txt - * /file.txt / /file.txt - * c:\file.txt c:\ .\file.txt - */ + if (path_is_absolute(path)) { + // Last resort: shorten relative to curdir if possible. + // 'possible' means: + // 1. It is under the current directory. + // 2. The result is actually shorter than the original. + // + // Before curdir After + // /foo/bar/file.txt /foo/bar ./file.txt + // c:\foo\bar\file.txt c:\foo\bar .\file.txt + // /file.txt / /file.txt + // c:\file.txt c:\ .\file.txt short_name = path_shorten_fname(path, curdir); if (short_name != NULL && short_name > path + 1 ) { @@ -1221,7 +1219,7 @@ int gen_expand_wildcards(int num_pat, char_u **pat, int *num_file, */ if (path_has_exp_wildcard(p)) { if ((flags & EW_PATH) - && !path_is_absolute_path(p) + && !path_is_absolute(p) && !(p[0] == '.' && (vim_ispathsep(p[1]) || (p[1] == '.' && vim_ispathsep(p[2])))) @@ -1667,7 +1665,7 @@ int path_with_url(const char *fname) */ bool vim_isAbsName(char_u *name) { - return path_with_url((char *)name) != 0 || path_is_absolute_path(name); + return path_with_url((char *)name) != 0 || path_is_absolute(name); } /// Save absolute file name to "buf[len]". @@ -1691,7 +1689,7 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force) if (strlen(fname) > (len - 1)) { xstrlcpy(buf, fname, len); // truncate #ifdef WIN32 - slash_adjust(buf); + slash_adjust((char_u *)buf); #endif return FAIL; } @@ -1701,12 +1699,12 @@ int vim_FullName(const char *fname, char *buf, size_t len, bool force) return OK; } - int rv = path_get_absolute_path((char_u *)fname, (char_u *)buf, len, force); + int rv = path_to_absolute((char_u *)fname, (char_u *)buf, len, force); if (rv == FAIL) { xstrlcpy(buf, fname, len); // something failed; use the filename } #ifdef WIN32 - slash_adjust(buf); + slash_adjust((char_u *)buf); #endif return rv; } @@ -1741,7 +1739,7 @@ char *fix_fname(const char *fname) path_fix_case((char_u *)fname); // set correct case for file name # endif - return fname; + return (char *)fname; #endif } @@ -1910,7 +1908,7 @@ int pathcmp(const char *p, const char *q, int maxlen) /// - Pointer into `full_path` if shortened. /// - `full_path` unchanged if no shorter name is possible. /// - NULL if `full_path` is NULL. -char_u *path_shorten_fname_if_possible(char_u *full_path) +char_u *path_try_shorten_fname(char_u *full_path) { char_u *dirname = xmalloc(MAXPATHL); char_u *p = full_path; @@ -2191,8 +2189,8 @@ int append_path(char *path, const char *to_append, size_t max_len) /// @param force also expand when "fname" is already absolute. /// /// @return FAIL for failure, OK for success. -static int path_get_absolute_path(const char_u *fname, char_u *buf, - size_t len, int force) +static int path_to_absolute(const char_u *fname, char_u *buf, size_t len, + int force) { char_u *p; *buf = NUL; @@ -2201,8 +2199,14 @@ static int path_get_absolute_path(const char_u *fname, char_u *buf, char *end_of_path = (char *) fname; // expand it if forced or not an absolute path - if (force || !path_is_absolute_path(fname)) { - if ((p = vim_strrchr(fname, PATHSEP)) != NULL) { + if (force || !path_is_absolute(fname)) { + p = vim_strrchr(fname, '/'); +#ifdef WIN32 + if (p == NULL) { + p = vim_strrchr(fname, '\\'); + } +#endif + if (p != NULL) { // relative to root if (p == fname) { // only one path component @@ -2228,10 +2232,10 @@ static int path_get_absolute_path(const char_u *fname, char_u *buf, return append_path((char *)buf, end_of_path, len); } -/// Check if the given file is absolute. +/// Check if file `fname` is a full (absolute) path. /// /// @return `TRUE` if "fname" is absolute. -int path_is_absolute_path(const char_u *fname) +int path_is_absolute(const char_u *fname) { #ifdef WIN32 // A name like "d:/foo" and "//server/share" is absolute @@ -2256,7 +2260,7 @@ void path_guess_exepath(const char *argv0, char *buf, size_t bufsize) { char *path = getenv("PATH"); - if (path == NULL || path_is_absolute_path((char_u *)argv0)) { + if (path == NULL || path_is_absolute((char_u *)argv0)) { xstrlcpy(buf, argv0, bufsize); } else if (argv0[0] == '.' || strchr(argv0, PATHSEP)) { // Relative to CWD. diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt index 121f22129a..94cc63baea 100644 --- a/src/nvim/po/CMakeLists.txt +++ b/src/nvim/po/CMakeLists.txt @@ -2,10 +2,8 @@ find_package(Gettext) find_program(XGETTEXT_PRG xgettext) find_program(ICONV_PRG iconv) -if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) - set(ENV{OLD_PO_FILE_INPUT} yes) - set(ENV{OLD_PO_FILE_OUTPUT} yes) - +option(LANGUAGES "Localizations to build") +if(NOT LANGUAGES) set(LANGUAGES af ca @@ -31,6 +29,12 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) vi zh_CN.UTF-8 zh_TW.UTF-8) +endif() + +if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) + set(ENV{OLD_PO_FILE_INPUT} yes) + set(ENV{OLD_PO_FILE_OUTPUT} yes) + set(NVIM_RELATIVE_SOURCES) foreach(SRC ${NVIM_SOURCES} ${NVIM_HEADERS}) @@ -135,22 +139,30 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG) endmacro() # Create some translations from others. - BuildPoIconv(ja utf-8 euc-jp) - BuildMo(ja.euc-jp) + if(";${LANGUAGES};" MATCHES ";ja;") + BuildPoIconv(ja utf-8 euc-jp) + BuildMo(ja.euc-jp) + endif() - BuildPoIconv(cs ISO-8859-2 cp1250) - BuildMo(cs.cp1250) + if(";${LANGUAGES};" MATCHES ";cs;") + BuildPoIconv(cs ISO-8859-2 cp1250) + BuildMo(cs.cp1250) + endif() - BuildPoIconv(sk ISO-8859-2 cp1250) - BuildMo(sk.cp1250) + if(";${LANGUAGES};" MATCHES ";sk;") + BuildPoIconv(sk ISO-8859-2 cp1250) + BuildMo(sk.cp1250) + endif() add_custom_target(update-po-nb COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/no.po ${CMAKE_CURRENT_SOURCE_DIR}/nb.po DEPENDS no.po) list(APPEND UPDATE_PO_TARGETS update-po-nb) - CheckPo(nb) - BuildMo(nb) + if(";${LANGUAGES};" MATCHES ";no;") + CheckPo(nb) + BuildMo(nb) + endif() foreach(LANGUAGE ${LANGUAGES}) set(poFile "${CMAKE_CURRENT_SOURCE_DIR}/${LANGUAGE}.po") diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po index 27bb2d3a80..f42ad04409 100644 --- a/src/nvim/po/af.po +++ b/src/nvim/po/af.po @@ -4654,7 +4654,7 @@ msgstr " GEFAAL" #~ msgstr "" #, fuzzy, c-format -#~ msgid "Did not rename %s because %s does not looks like a ShaDa file" +#~ msgid "Did not rename %s because %s does not look like a ShaDa file" #~ msgstr " [lyk nie soos 'n Vim ruiller nie]" #, c-format diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po index e491649e14..8fa0cc48d4 100644 --- a/src/nvim/po/fi.po +++ b/src/nvim/po/fi.po @@ -4719,7 +4719,7 @@ msgstr " EPÄONNISTUI" #~ msgstr "E886: Viminfo-tiedostoa ei voit uudelleennimetä nimelle %s" #, fuzzy, c-format -#~ msgid "Did not rename %s because %s does not looks like a ShaDa file" +#~ msgid "Did not rename %s because %s does not look like a ShaDa file" #~ msgstr " [ei näytä Vimin swap-tiedostolta]" #, c-format diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po index 68425073c7..1ac2d2247a 100644 --- a/src/nvim/po/uk.po +++ b/src/nvim/po/uk.po @@ -4473,7 +4473,7 @@ msgid "Can't rename ShaDa file from %s to %s!" msgstr "Не вдалося перейменувати файл ShaDa з %s у %s!" #, c-format -msgid "Did not rename %s because %s does not looks like a ShaDa file" +msgid "Did not rename %s because %s does not look like a ShaDa file" msgstr "Не перейменував %s, тому що %s не схожий на файл ShaDa" #, c-format diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c index 2462975c9b..348daf028a 100644 --- a/src/nvim/popupmnu.c +++ b/src/nvim/popupmnu.c @@ -567,6 +567,7 @@ static int pum_set_selected(int n, int repeat) && (repeat <= 1) && (vim_strchr(p_cot, 'p') != NULL)) { win_T *curwin_save = curwin; + tabpage_T *curtab_save = curtab; int res = OK; // Open a preview window. 3 lines by default. Prefer @@ -593,7 +594,7 @@ static int pum_set_selected(int n, int repeat) && (curbuf->b_p_bt[2] == 'f') && (curbuf->b_p_bh[0] == 'w')) { // Already a "wipeout" buffer, make it empty. - while (!bufempty()) { + while (!BUFEMPTY()) { ml_delete((linenr_T)1, FALSE); } } else { @@ -647,7 +648,12 @@ static int pum_set_selected(int n, int repeat) curwin->w_cursor.lnum = 1; curwin->w_cursor.col = 0; - if ((curwin != curwin_save) && win_valid(curwin_save)) { + if ((curwin != curwin_save && win_valid(curwin_save)) + || (curtab != curtab_save && valid_tabpage(curtab_save))) { + if (curtab != curtab_save && valid_tabpage(curtab_save)) { + goto_tabpage_tp(curtab_save, false, false); + } + // When the first completion is done and the preview // window is not resized, skip the preview window's // status line redrawing. diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c index 1fc585f0c9..46124e0672 100644 --- a/src/nvim/quickfix.c +++ b/src/nvim/quickfix.c @@ -76,17 +76,25 @@ struct qfline_S { */ #define LISTCOUNT 10 +/// Quickfix/Location list definition +/// +/// Usually the list contains one or more entries. But an empty list can be +/// created using setqflist()/setloclist() with a title and/or user context +/// information and entries can be added later using setqflist()/setloclist(). typedef struct qf_list_S { - qfline_T *qf_start; // pointer to the first error - qfline_T *qf_last; // pointer to the last error - qfline_T *qf_ptr; // pointer to the current error - int qf_count; // number of errors (0 means no error list) - int qf_index; // current index in the error list - int qf_nonevalid; // TRUE if not a single valid entry found - char_u *qf_title; // title derived from the command that created - // the error list + qfline_T *qf_start; ///< pointer to the first error + qfline_T *qf_last; ///< pointer to the last error + qfline_T *qf_ptr; ///< pointer to the current error + int qf_count; ///< number of errors (0 means empty list) + int qf_index; ///< current index in the error list + int qf_nonevalid; ///< TRUE if not a single valid entry found + char_u *qf_title; ///< title derived from the command that created + ///< the error list or set by setqflist + typval_T *qf_ctx; ///< context set by setqflist/setloclist } qf_list_T; +/// Quickfix/Location list stack definition +/// Contains a list of quickfix/location lists (qf_list_T) struct qf_info_S { /* * Count of references to this list. Used only for location lists. @@ -156,10 +164,12 @@ typedef struct { FILE *fd; typval_T *tv; char_u *p_str; + list_T *p_list; listitem_T *p_li; buf_T *buf; linenr_T buflnum; linenr_T lnumlast; + vimconv_T vc; } qfstate_T; typedef struct { @@ -193,19 +203,19 @@ typedef struct { static char_u *qf_last_bufname = NULL; static bufref_T qf_last_bufref = { NULL, 0, 0 }; -/* - * Read the errorfile "efile" into memory, line by line, building the error - * list. Set the error list's title to qf_title. - * Return -1 for error, number of errors for success. - */ -int -qf_init ( - win_T *wp, - char_u *efile, - char_u *errorformat, - int newlist, /* TRUE: start a new error list */ - char_u *qf_title -) +/// Read the errorfile "efile" into memory, line by line, building the error +/// list. Set the error list's title to qf_title. +/// +/// @params wp If non-NULL, make a location list +/// @params efile If non-NULL, errorfile to parse +/// @params errorformat 'errorformat' string used to parse the error lines +/// @params newlist If true, create a new error list +/// @params qf_title If non-NULL, title of the error list +/// @params enc If non-NULL, encoding used to parse errors +/// +/// @returns -1 for error, number of errors for success. +int qf_init(win_T *wp, char_u *efile, char_u *errorformat, int newlist, + char_u *qf_title, char_u *enc) { qf_info_T *qi = &ql_info; @@ -214,8 +224,8 @@ qf_init ( } return qf_init_ext(qi, efile, curbuf, NULL, errorformat, newlist, - (linenr_T)0, (linenr_T)0, - qf_title); + (linenr_T)0, (linenr_T)0, + qf_title, enc); } // Maximum number of bytes allowed per line while reading an errorfile. @@ -516,17 +526,17 @@ static int qf_get_next_list_line(qfstate_T *state) // Get the next line from the supplied list while (p_li != NULL - && (p_li->li_tv.v_type != VAR_STRING - || p_li->li_tv.vval.v_string == NULL)) { - p_li = p_li->li_next; // Skip non-string items + && (TV_LIST_ITEM_TV(p_li)->v_type != VAR_STRING + || TV_LIST_ITEM_TV(p_li)->vval.v_string == NULL)) { + p_li = TV_LIST_ITEM_NEXT(state->p_list, p_li); // Skip non-string items. } - if (p_li == NULL) { // End of the list + if (p_li == NULL) { // End of the list. state->p_li = NULL; return QF_END_OF_INPUT; } - len = STRLEN(p_li->li_tv.vval.v_string); + len = STRLEN(TV_LIST_ITEM_TV(p_li)->vval.v_string); if (len > IOSIZE - 2) { state->linebuf = qf_grow_linebuf(state, len); } else { @@ -534,9 +544,10 @@ static int qf_get_next_list_line(qfstate_T *state) state->linelen = len; } - STRLCPY(state->linebuf, p_li->li_tv.vval.v_string, state->linelen + 1); + STRLCPY(state->linebuf, TV_LIST_ITEM_TV(p_li)->vval.v_string, + state->linelen + 1); - state->p_li = p_li->li_next; // next item + state->p_li = TV_LIST_ITEM_NEXT(state->p_list, p_li); return QF_OK; } @@ -638,6 +649,22 @@ retry: } else { state->linebuf = IObuff; } + + // Convert a line if it contains a non-ASCII character + if (state->vc.vc_type != CONV_NONE && has_non_ascii(state->linebuf)) { + char_u *line = string_convert(&state->vc, state->linebuf, &state->linelen); + if (line != NULL) { + if (state->linelen < IOSIZE) { + STRLCPY(state->linebuf, line, state->linelen + 1); + xfree(line); + } else { + xfree(state->growbuf); + state->linebuf = state->growbuf = line; + state->growbufsiz = state->linelen < LINE_MAXLEN + ? state->linelen : LINE_MAXLEN; + } + } + } return QF_OK; } @@ -783,7 +810,7 @@ restofline: fields->type = *regmatch.startp[i]; } if (fmt_ptr->flags == '+' && !qi->qf_multiscan) { // %+ - if (linelen > fields->errmsglen) { + if (linelen >= fields->errmsglen) { // linelen + null terminator fields->errmsg = xrealloc(fields->errmsg, linelen + 1); fields->errmsglen = linelen + 1; @@ -794,7 +821,7 @@ restofline: continue; } len = (size_t)(regmatch.endp[i] - regmatch.startp[i]); - if (len > fields->errmsglen) { + if (len >= fields->errmsglen) { // len + null terminator fields->errmsg = xrealloc(fields->errmsg, len + 1); fields->errmsglen = len + 1; @@ -871,7 +898,7 @@ restofline: fields->namebuf[0] = NUL; // no match found, remove file name fields->lnum = 0; // don't jump to this line fields->valid = false; - if (linelen > fields->errmsglen) { + if (linelen >= fields->errmsglen) { // linelen + null terminator fields->errmsg = xrealloc(fields->errmsg, linelen + 1); fields->errmsglen = linelen + 1; @@ -976,15 +1003,15 @@ qf_init_ext( buf_T *buf, typval_T *tv, char_u *errorformat, - int newlist, /* TRUE: start a new error list */ - linenr_T lnumfirst, /* first line number to use */ - linenr_T lnumlast, /* last line number to use */ - char_u *qf_title + int newlist, // TRUE: start a new error list + linenr_T lnumfirst, // first line number to use + linenr_T lnumlast, // last line number to use + char_u *qf_title, + char_u *enc ) { - qfstate_T state = { NULL, 0, NULL, 0, NULL, NULL, NULL, NULL, - NULL, 0, 0 }; - qffields_T fields = { NULL, NULL, 0, 0L, 0, false, NULL, 0, 0, 0 }; + qfstate_T state; + qffields_T fields; qfline_T *old_last = NULL; bool adding = false; static efm_T *fmt_first = NULL; @@ -997,6 +1024,13 @@ qf_init_ext( xfree(qf_last_bufname); qf_last_bufname = NULL; + memset(&state, 0, sizeof(state)); + memset(&fields, 0, sizeof(fields)); + state.vc.vc_type = CONV_NONE; + if (enc != NULL && *enc != NUL) { + convert_setup(&state.vc, enc, p_enc); + } + fields.namebuf = xmalloc(CMDBUFFSIZE + 1); fields.errmsglen = CMDBUFFSIZE + 1; fields.errmsg = xmalloc(fields.errmsglen); @@ -1064,7 +1098,8 @@ qf_init_ext( if (tv->v_type == VAR_STRING) { state.p_str = tv->vval.v_string; } else if (tv->v_type == VAR_LIST) { - state.p_li = tv->vval.v_list->lv_first; + state.p_list = tv->vval.v_list; + state.p_li = tv_list_first(tv->vval.v_list); } state.tv = tv; } @@ -1093,6 +1128,7 @@ qf_init_ext( } if (qf_add_entry(qi, + qi->qf_curlist, qi->qf_directory, (*fields.namebuf || qi->qf_directory) ? fields.namebuf : ((qi->qf_currfile && fields.valid) @@ -1147,16 +1183,24 @@ qf_init_end: qf_update_buffer(qi, old_last); + if (state.vc.vc_type != CONV_NONE) { + convert_setup(&state.vc, NULL, NULL); + } + return retval; } -static void qf_store_title(qf_info_T *qi, char_u *title) +static void qf_store_title(qf_info_T *qi, int qf_idx, char_u *title) { + xfree(qi->qf_lists[qf_idx].qf_title); + qi->qf_lists[qf_idx].qf_title = NULL; + if (title != NULL) { - char_u *p = xmalloc(STRLEN(title) + 2); + size_t len = STRLEN(title) + 1; + char_u *p = xmallocz(len); - qi->qf_lists[qi->qf_curlist].qf_title = p; - sprintf((char *)p, ":%s", (char *)title); + qi->qf_lists[qf_idx].qf_title = p; + snprintf((char *)p, len + 1, ":%s", (char *)title); } } @@ -1185,7 +1229,7 @@ static void qf_new_list(qf_info_T *qi, char_u *qf_title) } else qi->qf_curlist = qi->qf_listcount++; memset(&qi->qf_lists[qi->qf_curlist], 0, (size_t)(sizeof(qf_list_T))); - qf_store_title(qi, qf_title); + qf_store_title(qi, qi->qf_curlist, qf_title); } /* @@ -1228,6 +1272,7 @@ void qf_free_all(win_T *wp) /// Add an entry to the end of the list of errors. /// /// @param qi quickfix list +/// @param qf_idx list index /// @param dir optional directory name /// @param fname file name or NULL /// @param bufnum buffer number or zero @@ -1241,9 +1286,10 @@ void qf_free_all(win_T *wp) /// @param valid valid entry /// /// @returns OK or FAIL. -static int qf_add_entry(qf_info_T *qi, char_u *dir, char_u *fname, int bufnum, - char_u *mesg, long lnum, int col, char_u vis_col, - char_u *pattern, int nr, char_u type, char_u valid) +static int qf_add_entry(qf_info_T *qi, int qf_idx, char_u *dir, char_u *fname, + int bufnum, char_u *mesg, long lnum, int col, + char_u vis_col, char_u *pattern, int nr, char_u type, + char_u valid) { qfline_T *qfp = xmalloc(sizeof(qfline_T)); qfline_T **lastp; // pointer to qf_last or NULL @@ -1274,12 +1320,12 @@ static int qf_add_entry(qf_info_T *qi, char_u *dir, char_u *fname, int bufnum, qfp->qf_type = (char_u)type; qfp->qf_valid = valid; - lastp = &qi->qf_lists[qi->qf_curlist].qf_last; - if (qi->qf_lists[qi->qf_curlist].qf_count == 0) { - /* first element in the list */ - qi->qf_lists[qi->qf_curlist].qf_start = qfp; - qi->qf_lists[qi->qf_curlist].qf_ptr = qfp; - qi->qf_lists[qi->qf_curlist].qf_index = 0; + lastp = &qi->qf_lists[qf_idx].qf_last; + if (qi->qf_lists[qf_idx].qf_count == 0) { + // first element in the list + qi->qf_lists[qf_idx].qf_start = qfp; + qi->qf_lists[qf_idx].qf_ptr = qfp; + qi->qf_lists[qf_idx].qf_index = 0; qfp->qf_prev = NULL; } else { assert(*lastp); @@ -1289,12 +1335,11 @@ static int qf_add_entry(qf_info_T *qi, char_u *dir, char_u *fname, int bufnum, qfp->qf_next = NULL; qfp->qf_cleared = false; *lastp = qfp; - qi->qf_lists[qi->qf_curlist].qf_count++; - if (qi->qf_lists[qi->qf_curlist].qf_index == 0 && qfp->qf_valid) { - /* first valid entry */ - qi->qf_lists[qi->qf_curlist].qf_index = - qi->qf_lists[qi->qf_curlist].qf_count; - qi->qf_lists[qi->qf_curlist].qf_ptr = qfp; + qi->qf_lists[qf_idx].qf_count++; + if (qi->qf_lists[qf_idx].qf_index == 0 && qfp->qf_valid) { + // first valid entry + qi->qf_lists[qf_idx].qf_index = qi->qf_lists[qf_idx].qf_count; + qi->qf_lists[qf_idx].qf_ptr = qfp; } return OK; @@ -1381,6 +1426,13 @@ void copy_loclist(win_T *from, win_T *to) else to_qfl->qf_title = NULL; + if (from_qfl->qf_ctx != NULL) { + to_qfl->qf_ctx = xcalloc(1, sizeof(typval_T)); + tv_copy(from_qfl->qf_ctx, to_qfl->qf_ctx); + } else { + to_qfl->qf_ctx = NULL; + } + if (from_qfl->qf_count) { qfline_T *from_qfp; qfline_T *prevp; @@ -1390,6 +1442,7 @@ void copy_loclist(win_T *from, win_T *to) i < from_qfl->qf_count && from_qfp != NULL; i++, from_qfp = from_qfp->qf_next) { if (qf_add_entry(to->w_llist, + to->w_llist->qf_curlist, NULL, NULL, 0, @@ -2353,8 +2406,9 @@ void qf_history(exarg_T *eap) } } -/// Free all the entries in the error list "idx". -static void qf_free(qf_info_T *qi, int idx) +/// Free all the entries in the error list "idx". Note that other information +/// associated with the list like context and title are not freed. +static void qf_free_items(qf_info_T *qi, int idx) { qfline_T *qfp; qfline_T *qfpnext; @@ -2378,16 +2432,34 @@ static void qf_free(qf_info_T *qi, int idx) qi->qf_lists[idx].qf_start = qfpnext; qi->qf_lists[idx].qf_count--; } - xfree(qi->qf_lists[idx].qf_title); + qi->qf_lists[idx].qf_start = NULL; qi->qf_lists[idx].qf_ptr = NULL; - qi->qf_lists[idx].qf_title = NULL; qi->qf_lists[idx].qf_index = 0; + qi->qf_lists[idx].qf_start = NULL; + qi->qf_lists[idx].qf_last = NULL; + qi->qf_lists[idx].qf_ptr = NULL; + qi->qf_lists[idx].qf_nonevalid = true; qf_clean_dir_stack(&qi->qf_dir_stack); qi->qf_directory = NULL; qf_clean_dir_stack(&qi->qf_file_stack); qi->qf_currfile = NULL; + qi->qf_multiline = false; + qi->qf_multiignore = false; + qi->qf_multiscan = false; +} + +/// Free error list "idx". Frees all the entries in the quickfix list, +/// associated context information and the title. +static void qf_free(qf_info_T *qi, int idx) +{ + qf_free_items(qi, idx); + + xfree(qi->qf_lists[idx].qf_title); + qi->qf_lists[idx].qf_title = NULL; + tv_free(qi->qf_lists[idx].qf_ctx); + qi->qf_lists[idx].qf_ctx = NULL; } /* @@ -2853,7 +2925,7 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) if (old_last == NULL) { if (buf != curbuf) { - EMSG2(_(e_intern2), "qf_fill_buffer()"); + internal_error("qf_fill_buffer()"); return; } @@ -2957,7 +3029,7 @@ static void qf_fill_buffer(qf_info_T *qi, buf_T *buf, qfline_T *old_last) /* * Return TRUE if "buf" is the quickfix buffer. */ -int bt_quickfix(buf_T *buf) +int bt_quickfix(const buf_T *const buf) { return buf != NULL && buf->b_p_bt[0] == 'q'; } @@ -3024,6 +3096,7 @@ void ex_make(exarg_T *eap) qf_info_T *qi = &ql_info; int res; char_u *au_name = NULL; + char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; /* Redirect ":grep" to ":vimgrep" if 'grepprg' is "internal". */ if (grep_internal(eap->cmdidx)) { @@ -3075,19 +3148,18 @@ void ex_make(exarg_T *eap) } msg_start(); MSG_PUTS(":!"); - msg_outtrans((char_u *) cmd); // show what we are doing + msg_outtrans((char_u *)cmd); // show what we are doing - // let the shell know if we are redirecting output or not - do_shell((char_u *) cmd, *p_sp != NUL ? kShellOptDoOut : 0); + do_shell((char_u *)cmd, 0); res = qf_init(wp, fname, (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake) ? p_gefm : p_efm, - (eap->cmdidx != CMD_grepadd - && eap->cmdidx != CMD_lgrepadd), - *eap->cmdlinep); - if (wp != NULL) + (eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd), + *eap->cmdlinep, enc); + if (wp != NULL) { qi = GET_LOC_LIST(wp); + } if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, curbuf->b_fname, TRUE, curbuf); @@ -3429,19 +3501,18 @@ void ex_cfile(exarg_T *eap) if (*eap->arg != NUL) set_string_option_direct((char_u *)"ef", -1, eap->arg, OPT_FREE, 0); - /* - * This function is used by the :cfile, :cgetfile and :caddfile - * commands. - * :cfile always creates a new quickfix list and jumps to the - * first error. - * :cgetfile creates a new quickfix list but doesn't jump to the - * first error. - * :caddfile adds to an existing quickfix list. If there is no - * quickfix list then a new list is created. - */ + char_u *enc = (*curbuf->b_p_menc != NUL) ? curbuf->b_p_menc : p_menc; + // This function is used by the :cfile, :cgetfile and :caddfile + // commands. + // :cfile always creates a new quickfix list and jumps to the + // first error. + // :cgetfile creates a new quickfix list but doesn't jump to the + // first error. + // :caddfile adds to an existing quickfix list. If there is no + // quickfix list then a new list is created. if (qf_init(wp, p_ef, p_efm, (eap->cmdidx != CMD_caddfile && eap->cmdidx != CMD_laddfile), - *eap->cmdlinep) > 0 + *eap->cmdlinep, enc) > 0 && (eap->cmdidx == CMD_cfile || eap->cmdidx == CMD_lfile)) { if (au_name != NULL) @@ -3578,8 +3649,8 @@ void ex_vimgrep(exarg_T *eap) cur_qf_start = qi->qf_lists[qi->qf_curlist].qf_start; seconds = (time_t)0; - for (fi = 0; fi < fcount && !got_int && tomatch > 0; ++fi) { - fname = path_shorten_fname_if_possible(fnames[fi]); + for (fi = 0; fi < fcount && !got_int && tomatch > 0; fi++) { + fname = path_try_shorten_fname(fnames[fi]); if (time(NULL) > seconds) { /* Display the file name every second or so, show the user we are * working on it. */ @@ -3656,6 +3727,7 @@ void ex_vimgrep(exarg_T *eap) // dummy buffer, unless duplicate_name is set, then the // buffer will be wiped out below. if (qf_add_entry(qi, + qi->qf_curlist, NULL, // dir fname, duplicate_name ? 0 : buf->b_fnum, @@ -3837,6 +3909,7 @@ load_dummy_buffer ( bufref_T newbuf_to_wipe; int failed = true; aco_save_T aco; + int readfile_result; // Allocate a buffer without putting it in the buffer list. newbuf = buflist_new(NULL, NULL, (linenr_T)1, BLN_DUMMY); @@ -3850,7 +3923,9 @@ load_dummy_buffer ( /* need to open the memfile before putting the buffer in a window */ if (ml_open(newbuf) == OK) { - /* set curwin/curbuf to buf and save a few things */ + // Make sure this buffer isn't wiped out by auto commands. + newbuf->b_locked++; + // set curwin/curbuf to buf and save a few things aucmd_prepbuf(&aco, newbuf); /* Need to set the filename for autocommands. */ @@ -3864,9 +3939,11 @@ load_dummy_buffer ( curbuf->b_flags &= ~BF_DUMMY; newbuf_to_wipe.br_buf = NULL; - if (readfile(fname, NULL, - (linenr_T)0, (linenr_T)0, (linenr_T)MAXLNUM, - NULL, READ_NEW | READ_DUMMY) == OK + readfile_result = readfile(fname, NULL, (linenr_T)0, (linenr_T)0, + (linenr_T)MAXLNUM, NULL, + READ_NEW | READ_DUMMY); + newbuf->b_locked--; + if (readfile_result == OK && !got_int && !(curbuf->b_flags & BF_NEW)) { failed = FALSE; @@ -4025,6 +4102,7 @@ enum { QF_GETLIST_ITEMS = 0x2, QF_GETLIST_NR = 0x4, QF_GETLIST_WINID = 0x8, + QF_GETLIST_CONTEXT = 0x10, QF_GETLIST_ALL = 0xFF }; @@ -4034,16 +4112,22 @@ enum { int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) { qf_info_T *qi = &ql_info; + dictitem_T *di; if (wp != NULL) { qi = GET_LOC_LIST(wp); if (qi == NULL) { + // If querying for the size of the location list, return 0 + if (((di = tv_dict_find(what, S_LEN("nr"))) != NULL) + && (di->di_tv.v_type == VAR_STRING) + && strequal((const char *)di->di_tv.vval.v_string, "$")) { + return tv_dict_add_nr(retdict, S_LEN("nr"), 0); + } return FAIL; } } int status = OK; - dictitem_T *di; int flags = QF_GETLIST_NONE; int qf_idx = qi->qf_curlist; // default is the current list @@ -4056,6 +4140,17 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { return FAIL; } + } else if (qi->qf_listcount == 0) { // stack is empty + return FAIL; + } + flags |= QF_GETLIST_NR; + } else if (di->di_tv.v_type == VAR_STRING + && strequal((const char *)di->di_tv.vval.v_string, "$")) { + // Get the last quickfix list number + if (qi->qf_listcount > 0) { + qf_idx = qi->qf_listcount - 1; + } else { + qf_idx = -1; // Quickfix stack is empty } flags |= QF_GETLIST_NR; } else { @@ -4063,16 +4158,26 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) } } - if (tv_dict_find(what, S_LEN("all")) != NULL) { - flags |= QF_GETLIST_ALL; - } + if (qf_idx != -1) { + if (tv_dict_find(what, S_LEN("all")) != NULL) { + flags |= QF_GETLIST_ALL; + } - if (tv_dict_find(what, S_LEN("title")) != NULL) { - flags |= QF_GETLIST_TITLE; - } + if (tv_dict_find(what, S_LEN("title")) != NULL) { + flags |= QF_GETLIST_TITLE; + } + + if (tv_dict_find(what, S_LEN("winid")) != NULL) { + flags |= QF_GETLIST_WINID; + } - if (tv_dict_find(what, S_LEN("winid")) != NULL) { - flags |= QF_GETLIST_WINID; + if (tv_dict_find(what, S_LEN("context")) != NULL) { + flags |= QF_GETLIST_CONTEXT; + } + + if (tv_dict_find(what, S_LEN("items")) != NULL) { + flags |= QF_GETLIST_ITEMS; + } } if (flags & QF_GETLIST_TITLE) { @@ -4091,39 +4196,60 @@ int get_errorlist_properties(win_T *wp, dict_T *what, dict_T *retdict) status = tv_dict_add_nr(retdict, S_LEN("winid"), win->handle); } } + if ((status == OK) && (flags & QF_GETLIST_ITEMS)) { + list_T *l = tv_list_alloc(kListLenMayKnow); + (void)get_errorlist(wp, qf_idx, l); + tv_dict_add_list(retdict, S_LEN("items"), l); + } + + if ((status == OK) && (flags & QF_GETLIST_CONTEXT)) { + if (qi->qf_lists[qf_idx].qf_ctx != NULL) { + di = tv_dict_item_alloc_len(S_LEN("context")); + if (di != NULL) { + tv_copy(qi->qf_lists[qf_idx].qf_ctx, &di->di_tv); + if (tv_dict_add(retdict, di) == FAIL) { + tv_dict_item_free(di); + } + } + } else { + status = tv_dict_add_str(retdict, S_LEN("context"), ""); + } + } return status; } /// Add list of entries to quickfix/location list. Each list entry is /// a dictionary with item information. -static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, - int action) +static int qf_add_entries(qf_info_T *qi, int qf_idx, list_T *list, + char_u *title, int action) { - listitem_T *li; dict_T *d; qfline_T *old_last = NULL; int retval = OK; bool did_bufnr_emsg = false; - if (action == ' ' || qi->qf_curlist == qi->qf_listcount) { + if (action == ' ' || qf_idx == qi->qf_listcount) { // make place for a new list qf_new_list(qi, title); - } else if (action == 'a' && qi->qf_lists[qi->qf_curlist].qf_count > 0) { + qf_idx = qi->qf_curlist; + } else if (action == 'a' && qi->qf_lists[qf_idx].qf_count > 0) { // Adding to existing list, use last entry. - old_last = qi->qf_lists[qi->qf_curlist].qf_last; + old_last = qi->qf_lists[qf_idx].qf_last; } else if (action == 'r') { - qf_free(qi, qi->qf_curlist); - qf_store_title(qi, title); + qf_free_items(qi, qf_idx); + qf_store_title(qi, qf_idx, title); } - for (li = list->lv_first; li != NULL; li = li->li_next) { - if (li->li_tv.v_type != VAR_DICT) - continue; /* Skip non-dict items */ + TV_LIST_ITER_CONST(list, li, { + if (TV_LIST_ITEM_TV(li)->v_type != VAR_DICT) { + continue; // Skip non-dict items. + } - d = li->li_tv.vval.v_dict; - if (d == NULL) + d = TV_LIST_ITEM_TV(li)->vval.v_dict; + if (d == NULL) { continue; + } char *const filename = tv_dict_get_string(d, "filename", true); int bufnum = (int)tv_dict_get_number(d, "bufnr"); @@ -4154,7 +4280,13 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, bufnum = 0; } + // If the 'valid' field is present it overrules the detected value. + if (tv_dict_find(d, "valid", -1) != NULL) { + valid = (int)tv_dict_get_number(d, "valid"); + } + int status = qf_add_entry(qi, + qf_idx, NULL, // dir (char_u *)filename, bufnum, @@ -4175,18 +4307,18 @@ static int qf_add_entries(qf_info_T *qi, list_T *list, char_u *title, retval = FAIL; break; } - } + }); - if (qi->qf_lists[qi->qf_curlist].qf_index == 0) { + if (qi->qf_lists[qf_idx].qf_index == 0) { // no valid entry - qi->qf_lists[qi->qf_curlist].qf_nonevalid = true; + qi->qf_lists[qf_idx].qf_nonevalid = true; } else { - qi->qf_lists[qi->qf_curlist].qf_nonevalid = false; + qi->qf_lists[qf_idx].qf_nonevalid = false; } if (action != 'a') { - qi->qf_lists[qi->qf_curlist].qf_ptr = qi->qf_lists[qi->qf_curlist].qf_start; - if (qi->qf_lists[qi->qf_curlist].qf_count > 0) { - qi->qf_lists[qi->qf_curlist].qf_index = 1; + qi->qf_lists[qf_idx].qf_ptr = qi->qf_lists[qf_idx].qf_start; + if (qi->qf_lists[qf_idx].qf_count > 0) { + qi->qf_lists[qf_idx].qf_index = 1; } } @@ -4209,14 +4341,28 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) if ((di = tv_dict_find(what, S_LEN("nr"))) != NULL) { // Use the specified quickfix/location list if (di->di_tv.v_type == VAR_NUMBER) { - qf_idx = (int)di->di_tv.vval.v_number - 1; - if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { + // for zero use the current list + if (di->di_tv.vval.v_number != 0) { + qf_idx = (int)di->di_tv.vval.v_number - 1; + } + + if ((action == ' ' || action == 'a') && qf_idx == qi->qf_listcount) { + // When creating a new list, accept qf_idx pointing to the next + // non-available list + newlist = true; + } else if (qf_idx < 0 || qf_idx >= qi->qf_listcount) { return FAIL; + } else { + newlist = false; // use the specified list } + } else if (di->di_tv.v_type == VAR_STRING + && strequal((const char *)di->di_tv.vval.v_string, "$") + && qi->qf_listcount > 0) { + qf_idx = qi->qf_listcount - 1; + newlist = false; } else { return FAIL; } - newlist = false; // use the specified list } if (newlist) { @@ -4235,10 +4381,85 @@ static int qf_set_properties(qf_info_T *qi, dict_T *what, int action) retval = OK; } } + if ((di = tv_dict_find(what, S_LEN("items"))) != NULL) { + if (di->di_tv.v_type == VAR_LIST) { + char_u *title_save = vim_strsave(qi->qf_lists[qf_idx].qf_title); + + retval = qf_add_entries(qi, qf_idx, di->di_tv.vval.v_list, + title_save, action == ' ' ? 'a' : action); + xfree(title_save); + } + } + + if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) { + tv_free(qi->qf_lists[qf_idx].qf_ctx); + + typval_T *ctx = xcalloc(1, sizeof(typval_T)); + tv_copy(&di->di_tv, ctx); + qi->qf_lists[qf_idx].qf_ctx = ctx; + } return retval; } +// Find the non-location list window with the specified location list. +static win_T * find_win_with_ll(qf_info_T *qi) +{ + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + if ((wp->w_llist == qi) && !bt_quickfix(wp->w_buffer)) { + return wp; + } + } + + return NULL; +} + +// Free the entire quickfix/location list stack. +// If the quickfix/location list window is open, then clear it. +static void qf_free_stack(win_T *wp, qf_info_T *qi) +{ + win_T *qfwin = qf_find_win(qi); + + if (qfwin != NULL) { + // If the quickfix/location list window is open, then clear it + if (qi->qf_curlist < qi->qf_listcount) { + qf_free(qi, qi->qf_curlist); + } + qf_update_buffer(qi, NULL); + } + + win_T *llwin = NULL; + win_T *orig_wp = wp; + if (wp != NULL && IS_LL_WINDOW(wp)) { + // If in the location list window, then use the non-location list + // window with this location list (if present) + llwin = find_win_with_ll(qi); + if (llwin != NULL) { + wp = llwin; + } + } + + qf_free_all(wp); + if (wp == NULL) { + // quickfix list + qi->qf_curlist = 0; + qi->qf_listcount = 0; + } else if (IS_LL_WINDOW(orig_wp)) { + // If the location list window is open, then create a new empty location + // list + qf_info_T *new_ll = ll_new_list(); + + // first free the list reference in the location list window + ll_free_all(&orig_wp->w_llist_ref); + + orig_wp->w_llist_ref = new_ll; + if (llwin != NULL) { + llwin->w_llist = new_ll; + new_ll->qf_refcount++; + } + } +} + // Populate the quickfix list with the items supplied in the list // of dictionaries. "title" will be copied to w:quickfix_title // "action" is 'a' for add, 'r' for replace. Otherwise create a new list. @@ -4252,15 +4473,54 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title, qi = ll_get_or_alloc_list(wp); } - if (what != NULL) { + if (action == 'f') { + // Free the entire quickfix or location list stack + qf_free_stack(wp, qi); + } else if (what != NULL) { retval = qf_set_properties(qi, what, action); } else { - retval = qf_add_entries(qi, list, title, action); + retval = qf_add_entries(qi, qi->qf_curlist, list, title, action); } return retval; } +static bool mark_quickfix_ctx(qf_info_T *qi, int copyID) +{ + bool abort = false; + + for (int i = 0; i < LISTCOUNT && !abort; i++) { + typval_T *ctx = qi->qf_lists[i].qf_ctx; + if (ctx != NULL && ctx->v_type != VAR_NUMBER + && ctx->v_type != VAR_STRING && ctx->v_type != VAR_FLOAT) { + abort = set_ref_in_item(ctx, copyID, NULL, NULL); + } + } + + return abort; +} + +/// Mark the context of the quickfix list and the location lists (if present) as +/// "in use". So that garabage collection doesn't free the context. +bool set_ref_in_quickfix(int copyID) +{ + bool abort = mark_quickfix_ctx(&ql_info, copyID); + if (abort) { + return abort; + } + + FOR_ALL_TAB_WINDOWS(tp, win) { + if (win->w_llist != NULL) { + abort = mark_quickfix_ctx(win->w_llist, copyID); + if (abort) { + return abort; + } + } + } + + return abort; +} + /* * ":[range]cbuffer [bufnr]" command. * ":[range]caddbuffer [bufnr]" command. @@ -4338,7 +4598,7 @@ void ex_cbuffer(exarg_T *eap) if (qf_init_ext(qi, NULL, buf, NULL, p_efm, (eap->cmdidx != CMD_caddbuffer && eap->cmdidx != CMD_laddbuffer), - eap->line1, eap->line2, qf_title) > 0) { + eap->line1, eap->line2, qf_title, NULL) > 0) { if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, curbuf->b_fname, true, curbuf); @@ -4399,11 +4659,11 @@ void ex_cexpr(exarg_T *eap) typval_T tv; if (eval0(eap->arg, &tv, NULL, true) != FAIL) { if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL) - || (tv.v_type == VAR_LIST && tv.vval.v_list != NULL)) { + || tv.v_type == VAR_LIST) { if (qf_init_ext(qi, NULL, NULL, &tv, p_efm, (eap->cmdidx != CMD_caddexpr && eap->cmdidx != CMD_laddexpr), - (linenr_T)0, (linenr_T)0, *eap->cmdlinep) > 0) { + (linenr_T)0, (linenr_T)0, *eap->cmdlinep, NULL) > 0) { if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, (char_u *)au_name, curbuf->b_fname, true, curbuf); @@ -4474,6 +4734,9 @@ void ex_helpgrep(exarg_T *eap) } } + // Autocommands may change the list. Save it for later comparison + qf_info_T *save_qi = qi; + regmatch.regprog = vim_regcomp(eap->arg, RE_MAGIC + RE_STRING); regmatch.rm_ic = FALSE; if (regmatch.regprog != NULL) { @@ -4534,6 +4797,7 @@ void ex_helpgrep(exarg_T *eap) line[--l] = NUL; if (qf_add_entry(qi, + qi->qf_curlist, NULL, // dir fnames[fi], 0, @@ -4586,10 +4850,11 @@ void ex_helpgrep(exarg_T *eap) if (au_name != NULL) { apply_autocmds(EVENT_QUICKFIXCMDPOST, au_name, - curbuf->b_fname, TRUE, curbuf); - if (!new_qi && qi != &ql_info && qf_find_buf(qi) == NULL) - /* autocommands made "qi" invalid */ + curbuf->b_fname, true, curbuf); + if (!new_qi && qi != save_qi && qf_find_buf(qi) == NULL) { + // autocommands made "qi" invalid return; + } } /* Jump to first match. */ diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c index ae611a0005..e4de43b49e 100644 --- a/src/nvim/regexp.c +++ b/src/nvim/regexp.c @@ -232,17 +232,17 @@ #define LAST_NL NUPPER + ADD_NL #define WITH_NL(op) ((op) >= FIRST_NL && (op) <= LAST_NL) -#define MOPEN 80 /* -89 Mark this point in input as start of - * \( subexpr. MOPEN + 0 marks start of - * match. */ -#define MCLOSE 90 /* -99 Analogous to MOPEN. MCLOSE + 0 marks - * end of match. */ -#define BACKREF 100 /* -109 node Match same string again \1-\9 */ - -# define ZOPEN 110 /* -119 Mark this point in input as start of - * \z( subexpr. */ -# define ZCLOSE 120 /* -129 Analogous to ZOPEN. */ -# define ZREF 130 /* -139 node Match external submatch \z1-\z9 */ +#define MOPEN 80 // -89 Mark this point in input as start of + // \( … \) subexpr. MOPEN + 0 marks start of + // match. +#define MCLOSE 90 // -99 Analogous to MOPEN. MCLOSE + 0 marks + // end of match. +#define BACKREF 100 // -109 node Match same string again \1-\9. + +# define ZOPEN 110 // -119 Mark this point in input as start of + // \z( … \) subexpr. +# define ZCLOSE 120 // -129 Analogous to ZOPEN. +# define ZREF 130 // -139 node Match external submatch \z1-\z9 #define BRACE_COMPLEX 140 /* -149 node Match nodes between m & n times */ @@ -458,18 +458,15 @@ static int toggle_Magic(int x) /* Used for an error (down from) vim_regcomp(): give the error message, set * rc_did_emsg and return NULL */ -#define EMSG_RET_NULL(m) return (EMSG(m), rc_did_emsg = TRUE, (void *)NULL) -#define EMSG_RET_FAIL(m) return (EMSG(m), rc_did_emsg = TRUE, FAIL) -#define EMSG2_RET_NULL(m, \ - c) return (EMSG2((m), \ - (c) ? "" : "\\"), rc_did_emsg = TRUE, \ - (void *)NULL) -#define EMSG2_RET_FAIL(m, \ - c) return (EMSG2((m), \ - (c) ? "" : "\\"), rc_did_emsg = TRUE, \ - FAIL) +#define EMSG_RET_NULL(m) return (EMSG(m), rc_did_emsg = true, (void *)NULL) +#define IEMSG_RET_NULL(m) return (IEMSG(m), rc_did_emsg = true, (void *)NULL) +#define EMSG_RET_FAIL(m) return (EMSG(m), rc_did_emsg = true, FAIL) +#define EMSG2_RET_NULL(m, c) \ + return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, (void *)NULL) +#define EMSG2_RET_FAIL(m, c) \ + return (EMSG2((m), (c) ? "" : "\\"), rc_did_emsg = true, FAIL) #define EMSG_ONE_RET_NULL EMSG2_RET_NULL(_( \ - "E369: invalid item in %s%%[]"), reg_magic == MAGIC_ALL) + "E369: invalid item in %s%%[]"), reg_magic == MAGIC_ALL) #define MAX_LIMIT (32767L << 16L) @@ -1891,8 +1888,8 @@ static char_u *regatom(int *flagp) case Magic(')'): if (one_exactly) EMSG_ONE_RET_NULL; - EMSG_RET_NULL(_(e_internal)); /* Supposed to be caught earlier. */ - /* NOTREACHED */ + IEMSG_RET_NULL(_(e_internal)); // Supposed to be caught earlier. + // NOTREACHED case Magic('='): case Magic('?'): @@ -4534,7 +4531,7 @@ regmatch ( brace_max[no] = OPERAND_MAX(scan); brace_count[no] = 0; } else { - EMSG(_(e_internal)); /* Shouldn't happen */ + internal_error("BRACE_LIMITS"); status = RA_FAIL; } } @@ -6477,41 +6474,35 @@ static regsubmatch_T rsm; // can only be used when can_f_submatch is true /// vim_regsub_both(). static int fill_submatch_list(int argc, typval_T *argv, int argcount) { - listitem_T *li; - int i; - char_u *s; - if (argcount == 0) { // called function doesn't take an argument return 0; } // Relies on sl_list to be the first item in staticList10_T. - init_static_list((staticList10_T *)(argv->vval.v_list)); + tv_list_init_static10((staticList10_T *)argv->vval.v_list); // There are always 10 list items in staticList10_T. - li = argv->vval.v_list->lv_first; - for (i = 0; i < 10; i++) { - s = rsm.sm_match->startp[i]; + listitem_T *li = tv_list_first(argv->vval.v_list); + for (int i = 0; i < 10; i++) { + char_u *s = rsm.sm_match->startp[i]; if (s == NULL || rsm.sm_match->endp[i] == NULL) { s = NULL; } else { s = vim_strnsave(s, (int)(rsm.sm_match->endp[i] - s)); } - li->li_tv.v_type = VAR_STRING; - li->li_tv.vval.v_string = s; - li = li->li_next; + TV_LIST_ITEM_TV(li)->v_type = VAR_STRING; + TV_LIST_ITEM_TV(li)->vval.v_string = s; + li = TV_LIST_ITEM_NEXT(argv->vval.v_list, li); } return 1; } static void clear_submatch_list(staticList10_T *sl) { - int i; - - for (i = 0; i < 10; i++) { - xfree(sl->sl_items[i].li_tv.vval.v_string); - } + TV_LIST_ITER(&sl->sl_list, li, { + xfree(TV_LIST_ITEM_TV(li)->vval.v_string); + }); } /// vim_regsub() - perform substitutions after a vim_regexec() or @@ -6645,13 +6636,12 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, typval_T argv[2]; int dummy; typval_T rettv; - staticList10_T matchList; + staticList10_T matchList = TV_LIST_STATIC10_INIT; rettv.v_type = VAR_STRING; rettv.vval.v_string = NULL; argv[0].v_type = VAR_LIST; argv[0].vval.v_list = &matchList.sl_list; - matchList.sl_list.lv_len = 0; if (expr->v_type == VAR_FUNC) { s = expr->vval.v_string; call_func(s, (int)STRLEN(s), &rettv, 1, argv, @@ -6665,7 +6655,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest, fill_submatch_list, 0L, 0L, &dummy, true, partial, NULL); } - if (matchList.sl_list.lv_len > 0) { + if (tv_list_len(&matchList.sl_list) > 0) { // fill_submatch_list() was called. clear_submatch_list(&matchList); } @@ -7054,7 +7044,7 @@ list_T *reg_submatch_list(int no) colnr_T scol = rsm.sm_mmatch->startpos[no].col; colnr_T ecol = rsm.sm_mmatch->endpos[no].col; - list = tv_list_alloc(); + list = tv_list_alloc(elnum - slnum + 1); s = (const char *)reg_getline_submatch(slnum) + scol; if (slnum == elnum) { @@ -7073,7 +7063,7 @@ list_T *reg_submatch_list(int no) if (s == NULL || rsm.sm_match->endp[no] == NULL) { return NULL; } - list = tv_list_alloc(); + list = tv_list_alloc(1); tv_list_append_string(list, s, (const char *)rsm.sm_match->endp[no] - s); } diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c index 93ba9ce097..c520ef5fb9 100644 --- a/src/nvim/regexp_nfa.c +++ b/src/nvim/regexp_nfa.c @@ -1263,7 +1263,7 @@ static int nfa_regatom(void) rc_did_emsg = TRUE; return FAIL; } - EMSGN("INTERNAL: Unknown character class char: %" PRId64, c); + IEMSGN("INTERNAL: Unknown character class char: %" PRId64, c); return FAIL; } /* When '.' is followed by a composing char ignore the dot, so that @@ -4413,8 +4413,8 @@ static int check_char_class(int class, int c) break; default: - /* should not be here :P */ - EMSGN(_(e_ill_char_class), class); + // should not be here :P + IEMSGN(_(e_ill_char_class), class); return FAIL; } return FAIL; @@ -5992,8 +5992,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, int c = t->state->c; #ifdef REGEXP_DEBUG - if (c < 0) - EMSGN("INTERNAL: Negative state char: %" PRId64, c); + if (c < 0) { + IEMSGN("INTERNAL: Negative state char: %" PRId64, c); + } #endif result = (c == curc); @@ -6462,12 +6463,13 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags) * (and count its size). */ postfix = re2post(); if (postfix == NULL) { - /* TODO: only give this error for debugging? */ - if (post_ptr >= post_end) - EMSGN("Internal error: estimated max number " - "of states insufficient: %" PRId64, - post_end - post_start); - goto fail; /* Cascaded (syntax?) error */ + // TODO(vim): only give this error for debugging? + if (post_ptr >= post_end) { + IEMSGN("Internal error: estimated max number " + "of states insufficient: %" PRId64, + post_end - post_start); + } + goto fail; // Cascaded (syntax?) error } /* diff --git a/src/nvim/screen.c b/src/nvim/screen.c index ed96e98d32..731bb2658a 100644 --- a/src/nvim/screen.c +++ b/src/nvim/screen.c @@ -139,11 +139,6 @@ * doesn't fit. */ #define W_ENDCOL(wp) (wp->w_wincol + wp->w_width) -/* - * The attributes that are actually active for writing to the screen. - */ -static int screen_attr = 0; - static match_T search_hl; /* used for 'hlsearch' highlight matching */ static foldinfo_T win_foldinfo; /* info for 'foldcolumn' */ @@ -189,8 +184,6 @@ void redraw_win_later(win_T *wp, int type) void redraw_later_clear(void) { redraw_all_later(CLEAR); - /* Use attributes that is very unlikely to appear in text. */ - screen_attr = HL_BOLD | HL_UNDERLINE | HL_INVERSE; } /* @@ -307,13 +300,25 @@ void update_screen(int type) * if the screen was scrolled up when displaying a message, scroll it down */ if (msg_scrolled) { - clear_cmdline = TRUE; - if (msg_scrolled > Rows - 5) /* clearing is faster */ + clear_cmdline = true; + if (dy_flags & DY_MSGSEP) { + FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { + int valid = Rows - msg_scrollsize(); + if (wp->w_winrow + wp->w_height > valid) { + wp->w_redr_type = NOT_VALID; + wp->w_lines_valid = 0; + } + if (wp->w_winrow + wp->w_height + wp->w_status_height > valid) { + wp->w_redr_status = true; + } + } + } else if (msg_scrolled > Rows - 5) { // clearing is faster type = CLEAR; - else if (type != CLEAR) { - check_for_delay(FALSE); - if (screen_ins_lines(0, 0, msg_scrolled, (int)Rows, NULL) == FAIL) + } else if (type != CLEAR) { + check_for_delay(false); + if (screen_ins_lines(0, 0, msg_scrolled, (int)Rows, NULL) == FAIL) { type = CLEAR; + } FOR_ALL_WINDOWS_IN_TAB(wp, curtab) { if (wp->w_winrow < msg_scrolled) { if (wp->w_winrow + wp->w_height > msg_scrolled @@ -1589,6 +1594,20 @@ static void win_update(win_T *wp) got_int = save_got_int; } +/// Returns width of the signcolumn that should be used for the whole window +/// +/// @param wp window we want signcolumn width from +/// @return max width of signcolumn (cell unit) +/// +/// @note Returns a constant for now but hopefully we can improve neovim so that +/// the returned value width adapts to the maximum number of marks to draw +/// for the window +/// TODO(teto) +int win_signcol_width(win_T *wp) +{ + // 2 is vim default value + return 2; +} /* * Clear the rest of the window and mark the unused lines with "c1". use "c2" @@ -1616,7 +1635,7 @@ static void win_draw_end(win_T *wp, int c1, int c2, int row, int endrow, hlf_T h } if (signcolumn_on(wp)) { - int nn = n + 2; + int nn = n + win_signcol_width(wp); /* draw the sign column left of the fold column */ if (nn > wp->w_width) { @@ -1657,7 +1676,7 @@ static void win_draw_end(win_T *wp, int c1, int c2, int row, int endrow, hlf_T h } if (signcolumn_on(wp)) { - int nn = n + 2; + int nn = n + win_signcol_width(wp); /* draw the sign column after the fold column */ if (nn > wp->w_width) { @@ -1769,12 +1788,13 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T * text */ RL_MEMSET(col, win_hl_attr(wp, HLF_FL), wp->w_width - col); - // If signs are being displayed, add two spaces. + // If signs are being displayed, add spaces. if (signcolumn_on(wp)) { len = wp->w_width - col; if (len > 0) { - if (len > 2) { - len = 2; + int len_max = win_signcol_width(wp); + if (len > len_max) { + len = len_max; } copy_text_attr(off + col, (char_u *)" ", len, win_hl_attr(wp, HLF_FL)); @@ -2024,7 +2044,7 @@ static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T } screen_line(row + wp->w_winrow, wp->w_wincol, wp->w_width, - wp->w_width, false, wp); + wp->w_width, false, wp, 0); /* * Update w_cline_height and w_cline_folded if the cursor line was @@ -2445,10 +2465,6 @@ win_line ( line_attr = win_hl_attr(wp, HLF_QFL); } - if (wp->w_hl_attr_normal != 0) { - line_attr = hl_combine_attr(wp->w_hl_attr_normal, line_attr); - } - if (line_attr != 0) { area_highlighting = true; } @@ -2742,18 +2758,26 @@ win_line ( * buffer or when using Netbeans. */ if (signcolumn_on(wp)) { int text_sign; - /* Draw two cells with the sign value or blank. */ + // Draw cells with the sign value or blank. c_extra = ' '; char_attr = win_hl_attr(wp, HLF_SC); n_extra = 2; + n_extra = win_signcol_width(wp); if (row == startrow + filler_lines && filler_todo <= 0) { text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT); if (text_sign != 0) { p_extra = sign_get_text(text_sign); + int symbol_blen = (int)STRLEN(p_extra); if (p_extra != NULL) { c_extra = NUL; - n_extra = (int)STRLEN(p_extra); + // symbol(s) bytes + (filling spaces) (one byte each) + n_extra = symbol_blen + + (win_signcol_width(wp) - mb_string2cells(p_extra)); + memset(extra, ' ', sizeof(extra)); + STRNCPY(extra, p_extra, STRLEN(p_extra)); + p_extra = extra; + p_extra[n_extra] = NUL; } char_attr = sign_get_attr(text_sign, FALSE); } @@ -2898,7 +2922,8 @@ win_line ( && lnum == wp->w_cursor.lnum && vcol >= (long)wp->w_virtcol && filler_todo <= 0 ) { - screen_line(screen_row, wp->w_wincol, col, -wp->w_width, wp->w_p_rl, wp); + screen_line(screen_row, wp->w_wincol, col, -wp->w_width, wp->w_p_rl, wp, + wp->w_hl_attr_normal); // Pretend we have finished updating the window. Except when // 'cursorcolumn' is set. if (wp->w_p_cuc) { @@ -3143,14 +3168,15 @@ win_line ( } --n_extra; } else { + int c0; + if (p_extra_free != NULL) { xfree(p_extra_free); p_extra_free = NULL; } - /* - * Get a character from the line itself. - */ - c = *ptr; + + // Get a character from the line itself. + c0 = c = *ptr; if (has_mbyte) { mb_c = c; if (enc_utf8) { @@ -3160,11 +3186,12 @@ win_line ( mb_utf8 = FALSE; if (mb_l > 1) { mb_c = utfc_ptr2char(ptr, u8cc); - /* Overlong encoded ASCII or ASCII with composing char - * is displayed normally, except a NUL. */ - if (mb_c < 0x80) - c = mb_c; - mb_utf8 = TRUE; + // Overlong encoded ASCII or ASCII with composing char + // is displayed normally, except a NUL. + if (mb_c < 0x80) { + c0 = c = mb_c; + } + mb_utf8 = true; /* At start of the line we can have a composing char. * Draw it as a space with a composing char. */ @@ -3358,10 +3385,11 @@ win_line ( /* Use nextline[] if possible, it has the start of the * next line concatenated. */ - if ((prev_ptr - line) - nextlinecol >= 0) - p = nextline + (prev_ptr - line) - nextlinecol; - else + if ((prev_ptr - line) - nextlinecol >= 0) { + p = nextline + ((prev_ptr - line) - nextlinecol); + } else { p = prev_ptr; + } cap_col -= (int)(prev_ptr - line); size_t tmplen = spell_check(wp, p, &spell_hlf, &cap_col, nochange); assert(tmplen <= INT_MAX); @@ -3428,10 +3456,8 @@ win_line ( char_attr = hl_combine_attr(char_attr, term_attrs[vcol]); } - /* - * Found last space before word: check for line break. - */ - if (wp->w_p_lbr && vim_isbreak(c) && !vim_isbreak(*ptr)) { + // Found last space before word: check for line break. + if (wp->w_p_lbr && c0 == c && vim_isbreak(c) && !vim_isbreak(*ptr)) { int mb_off = has_mbyte ? (*mb_head_off)(line, ptr - 1) : 0; char_u *p = ptr - (mb_off + 1); // TODO: is passing p for start of the line OK? @@ -3990,7 +4016,8 @@ win_line ( col++; } } - screen_line(screen_row, wp->w_wincol, col, wp->w_width, wp->w_p_rl, wp); + screen_line(screen_row, wp->w_wincol, col, wp->w_width, wp->w_p_rl, wp, + wp->w_hl_attr_normal); row++; /* @@ -4213,7 +4240,7 @@ win_line ( || (n_extra != 0 && (c_extra != NUL || *p_extra != NUL))) ) { screen_line(screen_row, wp->w_wincol, col - boguscols, - wp->w_width, wp->w_p_rl, wp); + wp->w_width, wp->w_p_rl, wp, wp->w_hl_attr_normal); boguscols = 0; ++row; ++screen_row; @@ -4367,7 +4394,8 @@ static int char_needs_redraw(int off_from, int off_to, int cols) && comp_char_differs(off_from, off_to)) || ((*mb_off2cells)(off_from, off_from + cols) > 1 && ScreenLines[off_from + 1] - != ScreenLines[off_to + 1]))))); + != ScreenLines[off_to + 1]))) + || p_wd < 0)); } /* @@ -4382,7 +4410,7 @@ static int char_needs_redraw(int off_from, int off_to, int cols) * When FALSE and "clear_width" > 0, clear columns "endcol" to "clear_width" */ static void screen_line(int row, int coloff, int endcol, - int clear_width, int rlflag, win_T *wp) + int clear_width, int rlflag, win_T *wp, int bg_attr) { unsigned off_from; unsigned off_to; @@ -4415,15 +4443,16 @@ static void screen_line(int row, int coloff, int endcol, /* Clear rest first, because it's left of the text. */ if (clear_width > 0) { while (col <= endcol && ScreenLines[off_to] == ' ' - && ScreenAttrs[off_to] == 0 + && ScreenAttrs[off_to] == bg_attr && (!enc_utf8 || ScreenLinesUC[off_to] == 0) ) { ++off_to; ++col; } - if (col <= endcol) - screen_fill(row, row + 1, col + coloff, - endcol + coloff + 1, ' ', ' ', 0); + if (col <= endcol) { + screen_fill(row, row + 1, col + coloff, endcol + coloff + 1, ' ', ' ', + bg_attr); + } } col = endcol + 1; off_to = LineOffset[row] + col + coloff; @@ -4431,6 +4460,13 @@ static void screen_line(int row, int coloff, int endcol, endcol = (clear_width > 0 ? clear_width : -clear_width); } + if (bg_attr) { + for (int c = col; c < endcol; c++) { + ScreenAttrs[off_from+c] = hl_combine_attr(bg_attr, + ScreenAttrs[off_from+c]); + } + } + redraw_next = char_needs_redraw(off_from, off_to, endcol - col); while (col < endcol) { @@ -4529,15 +4565,15 @@ static void screen_line(int row, int coloff, int endcol, /* blank out the rest of the line */ while (col < clear_width && ScreenLines[off_to] == ' ' - && ScreenAttrs[off_to] == 0 + && ScreenAttrs[off_to] == bg_attr && (!enc_utf8 || ScreenLinesUC[off_to] == 0) ) { ++off_to; ++col; } if (col < clear_width) { - screen_fill(row, row + 1, col + coloff, clear_width + coloff, - ' ', ' ', 0); + screen_fill(row, row + 1, col + coloff, clear_width + coloff, ' ', ' ', + bg_attr); off_to += clear_width - col; col = clear_width; } @@ -5847,30 +5883,16 @@ next_search_hl_pos( return 0; } -static void screen_start_highlight(int attr) -{ - screen_attr = attr; - ui_start_highlight(attr); -} - -static void screen_stop_highlight(void) -{ - ui_stop_highlight(); - screen_attr = 0; -} - /* * Put character ScreenLines["off"] on the screen at position "row" and "col", * using the attributes from ScreenAttrs["off"]. */ static void screen_char(unsigned off, int row, int col) { - int attr; - - /* Check for illegal values, just in case (could happen just after - * resizing). */ - if (row >= screen_Rows || col >= screen_Columns) + // Check for illegal values, just in case (could happen just after resizing). + if (row >= screen_Rows || col >= screen_Columns) { return; + } // Outputting the last character on the screen may scrollup the screen. // Don't to it! Mark the character invalid (update it when scrolled up) @@ -5882,17 +5904,8 @@ static void screen_char(unsigned off, int row, int col) return; } - /* - * Stop highlighting first, so it's easier to move the cursor. - */ - attr = ScreenAttrs[off]; - if (screen_attr != attr) - screen_stop_highlight(); - ui_cursor_goto(row, col); - - if (screen_attr != attr) - screen_start_highlight(attr); + ui_set_highlight(ScreenAttrs[off]); if (enc_utf8 && ScreenLinesUC[off] != 0) { char_u buf[MB_MAXBYTES + 1]; @@ -6001,7 +6014,7 @@ void screen_fill(int start_row, int end_row, int start_col, int end_col, int c1, ++off; if (off < end_off) { /* something to be cleared */ col = off - LineOffset[row]; - screen_stop_highlight(); + ui_clear_highlight(); ui_cursor_goto(row, col); // clear rest of this screen line ui_call_eol_clear(); col = end_col - col; @@ -6383,8 +6396,7 @@ static void screenclear2(void) return; } - screen_stop_highlight(); /* don't want highlighting here */ - + ui_clear_highlight(); // don't want highlighting here /* blank out ScreenLines */ for (i = 0; i < Rows; ++i) { @@ -6475,64 +6487,11 @@ void setcursor(void) /// Returns FAIL if the lines are not inserted, OK for success. int win_ins_lines(win_T *wp, int row, int line_count, int invalid, int mayclear) { - int did_delete; - int nextrow; - int lastrow; - int retval; - - if (invalid) - wp->w_lines_valid = 0; - - if (wp->w_height < 5) - return FAIL; - - if (line_count > wp->w_height - row) - line_count = wp->w_height - row; - - retval = win_do_lines(wp, row, line_count, mayclear, FALSE); - if (retval != MAYBE) - return retval; - - /* - * If there is a next window or a status line, we first try to delete the - * lines at the bottom to avoid messing what is after the window. - * If this fails and there are following windows, don't do anything to avoid - * messing up those windows, better just redraw. - */ - did_delete = FALSE; - if (wp->w_next != NULL || wp->w_status_height) { - if (screen_del_lines(0, wp->w_winrow + wp->w_height - line_count, - line_count, (int)Rows, NULL) == OK) - did_delete = TRUE; - else if (wp->w_next) - return FAIL; - } - /* - * if no lines deleted, blank the lines that will end up below the window - */ - if (!did_delete) { - wp->w_redr_status = TRUE; - redraw_cmdline = TRUE; - nextrow = wp->w_winrow + wp->w_height + wp->w_status_height; - lastrow = nextrow + line_count; - if (lastrow > Rows) - lastrow = Rows; - screen_fill(nextrow - line_count, lastrow - line_count, - wp->w_wincol, W_ENDCOL(wp), - ' ', ' ', 0); - } - - if (screen_ins_lines(0, wp->w_winrow + row, line_count, (int)Rows, NULL) - == FAIL) { - /* deletion will have messed up other windows */ - if (did_delete) { - wp->w_redr_status = TRUE; - win_rest_invalid(wp->w_next); - } + if (wp->w_height < 5) { return FAIL; } - return OK; + return win_do_lines(wp, row, line_count, invalid, mayclear, false); } /// Delete "line_count" window lines at "row" in window "wp". @@ -6542,47 +6501,18 @@ int win_ins_lines(win_T *wp, int row, int line_count, int invalid, int mayclear) /// Return OK for success, FAIL if the lines are not deleted. int win_del_lines(win_T *wp, int row, int line_count, int invalid, int mayclear) { - int retval; - - if (invalid) - wp->w_lines_valid = 0; - - if (line_count > wp->w_height - row) - line_count = wp->w_height - row; - - retval = win_do_lines(wp, row, line_count, mayclear, TRUE); - if (retval != MAYBE) - return retval; - - if (screen_del_lines(0, wp->w_winrow + row, line_count, - (int)Rows, NULL) == FAIL) { - return FAIL; - } - - /* - * If there are windows or status lines below, try to put them at the - * correct place. If we can't do that, they have to be redrawn. - */ - if (wp->w_next || wp->w_status_height || cmdline_row < Rows - 1) { - if (screen_ins_lines(0, wp->w_winrow + wp->w_height - line_count, - line_count, (int)Rows, NULL) == FAIL) { - wp->w_redr_status = TRUE; - win_rest_invalid(wp->w_next); - } - } - /* - * If this is the last window and there is no status line, redraw the - * command line later. - */ - else - redraw_cmdline = TRUE; - return OK; + return win_do_lines(wp, row, line_count, invalid, mayclear, true); } // Common code for win_ins_lines() and win_del_lines(). // Returns OK or FAIL when the work has been done. -static int win_do_lines(win_T *wp, int row, int line_count, int mayclear, int del) +static int win_do_lines(win_T *wp, int row, int line_count, + int invalid, int mayclear, int del) { + if (invalid) { + wp->w_lines_valid = 0; + } + if (!redrawing() || line_count <= 0) { return FAIL; } @@ -6836,16 +6766,20 @@ int showmode(void) if (p_ri) MSG_PUTS_ATTR(_(" REVERSE"), attr); MSG_PUTS_ATTR(_(" INSERT"), attr); - } else if (restart_edit == 'I') + } else if (restart_edit == 'I' || restart_edit == 'i' + || restart_edit == 'a') { MSG_PUTS_ATTR(_(" (insert)"), attr); - else if (restart_edit == 'R') + } else if (restart_edit == 'R') { MSG_PUTS_ATTR(_(" (replace)"), attr); - else if (restart_edit == 'V') + } else if (restart_edit == 'V') { MSG_PUTS_ATTR(_(" (vreplace)"), attr); - if (p_hkmap) + } + if (p_hkmap) { MSG_PUTS_ATTR(_(" Hebrew"), attr); - if (p_fkmap) + } + if (p_fkmap) { MSG_PUTS_ATTR(farsi_text_5, attr); + } if (State & LANGMAP) { if (curwin->w_p_arab) { MSG_PUTS_ATTR(_(" Arabic"), attr); @@ -7200,11 +7134,7 @@ static int fillchar_status(int *attr, win_T *wp) static int fillchar_vsep(win_T *wp, int *attr) { *attr = win_hl_attr(wp, HLF_C); - if (*attr == 0 && fill_vert == ' ') { - return '|'; - } else { - return fill_vert; - } + return fill_vert; } /* diff --git a/src/nvim/search.c b/src/nvim/search.c index 387614fd09..1943e2ca43 100644 --- a/src/nvim/search.c +++ b/src/nvim/search.c @@ -96,6 +96,9 @@ static int lastc_bytelen = 1; /* >1 for multi-byte char */ /* copy of spats[], for keeping the search patterns while executing autocmds */ static struct spat saved_spats[2]; +// copy of spats[RE_SEARCH], for keeping the search patterns while incremental +// searching +static struct spat saved_last_search_spat; static int saved_last_idx = 0; static int saved_no_hlsearch = 0; @@ -305,6 +308,36 @@ void free_search_patterns(void) #endif +/// Save and restore the search pattern for incremental highlight search +/// feature. +/// +/// It's similar but different from save_search_patterns() and +/// restore_search_patterns(), because the search pattern must be restored when +/// cancelling incremental searching even if it's called inside user functions. +void save_last_search_pattern(void) +{ + saved_last_search_spat = spats[RE_SEARCH]; + if (spats[RE_SEARCH].pat != NULL) { + saved_last_search_spat.pat = vim_strsave(spats[RE_SEARCH].pat); + } + saved_last_idx = last_idx; + saved_no_hlsearch = no_hlsearch; +} + +void restore_last_search_pattern(void) +{ + xfree(spats[RE_SEARCH].pat); + spats[RE_SEARCH] = saved_last_search_spat; + set_vv_searchforward(); + last_idx = saved_last_idx; + SET_NO_HLSEARCH(saved_no_hlsearch); +} + +char_u *last_search_pattern(void) +{ + return spats[RE_SEARCH].pat; +} + /* * Return TRUE when case should be ignored for search pattern "pat". * Uses the 'ignorecase' and 'smartcase' options. @@ -742,12 +775,17 @@ int searchit( } } if (ptr[matchcol] == NUL - || (nmatched = vim_regexec_multi(®match, - win, buf, lnum + matchpos.lnum, - matchcol, - tm - )) == 0) - break; + || (nmatched = vim_regexec_multi( + ®match, win, buf, lnum + matchpos.lnum, matchcol, + tm)) == 0) { + // If the search timed out, we did find a match + // but it might be the wrong one, so that's not + // OK. + if (tm != NULL && profile_passed_limit(*tm)) { + match_ok = false; + } + break; + } /* Need to get the line pointer again, a * multi-line search may have made it invalid. */ @@ -1342,13 +1380,15 @@ int searchc(cmdarg_T *cap, int t_cmd) lastc_bytelen += (*mb_char2bytes)(cap->ncharC2, lastc_bytes + lastc_bytelen); } } - } else { /* repeat previous search */ - if (*lastc == NUL) + } else { // repeat previous search + if (*lastc == NUL && lastc_bytelen == 1) { return FAIL; - if (dir) /* repeat in opposite direction */ + } + if (dir) { // repeat in opposite direction dir = -lastcdir; - else + } else { dir = lastcdir; + } t_cmd = last_t_cmd; c = *lastc; /* For multi-byte re-use last lastc_bytes[] and lastc_bytelen. */ @@ -1382,13 +1422,13 @@ int searchc(cmdarg_T *cap, int t_cmd) col -= (*mb_head_off)(p, p + col - 1) + 1; } if (lastc_bytelen == 1) { - if (p[col] == c && stop) + if (p[col] == c && stop) { break; - } else { - if (memcmp(p + col, lastc_bytes, lastc_bytelen) == 0 && stop) + } + } else if (STRNCMP(p + col, lastc_bytes, lastc_bytelen) == 0 && stop) { break; } - stop = TRUE; + stop = true; } } else { for (;; ) { @@ -2227,10 +2267,11 @@ int findsent(int dir, long count) break; found_dot = TRUE; } - if (decl(&pos) == -1) + if (decl(&pos) == -1) { break; - /* when going forward: Stop in front of empty line */ - if (lineempty(pos.lnum) && dir == FORWARD) { + } + // when going forward: Stop in front of empty line + if (LINEEMPTY(pos.lnum) && dir == FORWARD) { incl(&pos); goto found; } @@ -2534,10 +2575,12 @@ int bck_word(long count, int bigword, int stop) */ while (cls() == 0) { if (curwin->w_cursor.col == 0 - && lineempty(curwin->w_cursor.lnum)) + && LINEEMPTY(curwin->w_cursor.lnum)) { goto finished; - if (dec_cursor() == -1) /* hit start of file, stop here */ + } + if (dec_cursor() == -1) { // hit start of file, stop here return OK; + } } /* @@ -2601,10 +2644,12 @@ int end_word(long count, int bigword, int stop, int empty) */ while (cls() == 0) { if (empty && curwin->w_cursor.col == 0 - && lineempty(curwin->w_cursor.lnum)) + && LINEEMPTY(curwin->w_cursor.lnum)) { goto finished; - if (inc_cursor() == -1) /* hit end of file, stop here */ + } + if (inc_cursor() == -1) { // hit end of file, stop here return FAIL; + } } /* @@ -2657,10 +2702,12 @@ bckend_word ( * Move backward to end of the previous word */ while (cls() == 0) { - if (curwin->w_cursor.col == 0 && lineempty(curwin->w_cursor.lnum)) + if (curwin->w_cursor.col == 0 && LINEEMPTY(curwin->w_cursor.lnum)) { break; - if ((i = dec_cursor()) == -1 || (eol && i == 1)) + } + if ((i = dec_cursor()) == -1 || (eol && i == 1)) { return OK; + } } } return OK; @@ -3667,11 +3714,25 @@ current_quote ( int selected_quote = FALSE; /* Has quote inside selection */ int i; - /* Correct cursor when 'selection' is exclusive */ + // Correct cursor when 'selection' is "exclusive". if (VIsual_active) { + // this only works within one line + if (VIsual.lnum != curwin->w_cursor.lnum) { + return false; + } + vis_bef_curs = lt(VIsual, curwin->w_cursor); - if (*p_sel == 'e' && vis_bef_curs) + if (*p_sel == 'e') { + if (!vis_bef_curs) { + // VIsual needs to be start of Visual selection. + pos_T t = curwin->w_cursor; + + curwin->w_cursor = VIsual; + VIsual = t; + vis_bef_curs = true; + } dec_cursor(); + } vis_empty = equalpos(VIsual, curwin->w_cursor); } diff --git a/src/nvim/shada.c b/src/nvim/shada.c index 736d6bf162..15ac28e1bf 100644 --- a/src/nvim/shada.c +++ b/src/nvim/shada.c @@ -16,12 +16,14 @@ #include "nvim/os/os.h" #include "nvim/os/time.h" #include "nvim/vim.h" +#include "nvim/pos.h" #include "nvim/ascii.h" #include "nvim/shada.h" #include "nvim/message.h" #include "nvim/globals.h" #include "nvim/memory.h" #include "nvim/mark.h" +#include "nvim/macros.h" #include "nvim/ops.h" #include "nvim/garray.h" #include "nvim/option.h" @@ -76,8 +78,8 @@ KHASH_SET_INIT_STR(strset) (vim_rename((char_u *)a, (char_u *)b)) #define mb_strnicmp(a, b, c) \ (mb_strnicmp((char_u *)a, (char_u *)b, c)) -#define path_shorten_fname_if_possible(b) \ - ((char *)path_shorten_fname_if_possible((char_u *)b)) +#define path_try_shorten_fname(b) \ + ((char *)path_try_shorten_fname((char_u *)b)) #define buflist_new(ffname, sfname, ...) \ (buflist_new((char_u *)ffname, (char_u *)sfname, __VA_ARGS__)) #define os_isdir(f) (os_isdir((char_u *) f)) @@ -375,7 +377,8 @@ KHASH_MAP_INIT_STR(file_marks, FileMarks) /// Before actually writing most of the data is read to this structure. typedef struct { HistoryMergerState hms[HIST_COUNT]; ///< Structures for history merging. - PossiblyFreedShadaEntry global_marks[NGLOBALMARKS]; ///< All global marks. + PossiblyFreedShadaEntry global_marks[NMARKS]; ///< Named global marks. + PossiblyFreedShadaEntry numbered_marks[EXTRA_MARKS]; ///< Numbered marks. PossiblyFreedShadaEntry registers[NUM_SAVED_REGISTERS]; ///< All registers. PossiblyFreedShadaEntry jumps[JUMPLISTSIZE]; ///< All dumped jumps. size_t jumps_size; ///< Number of jumps occupied. @@ -1180,8 +1183,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) list_T *oldfiles_list = get_vim_var_list(VV_OLDFILES); const bool force = flags & kShaDaForceit; const bool get_old_files = (flags & (kShaDaGetOldfiles | kShaDaForceit) - && (force || oldfiles_list == NULL - || oldfiles_list->lv_len == 0)); + && (force || tv_list_len(oldfiles_list) == 0)); const bool want_marks = flags & kShaDaWantMarks; const unsigned srni_flags = (unsigned) ( (flags & kShaDaWantInfo @@ -1218,7 +1220,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) khash_t(fnamebufs) fname_bufs = KHASH_EMPTY_TABLE(fnamebufs); khash_t(strset) oldfiles_set = KHASH_EMPTY_TABLE(strset); if (get_old_files && (oldfiles_list == NULL || force)) { - oldfiles_list = tv_list_alloc(); + oldfiles_list = tv_list_alloc(kListLenUnknown); set_vim_var_list(VV_OLDFILES, oldfiles_list); } ShaDaReadResult srni_ret; @@ -1398,7 +1400,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags) } case kSDItemBufferList: { for (size_t i = 0; i < cur_entry.data.buffer_list.size; i++) { - char *const sfname = path_shorten_fname_if_possible( + char *const sfname = path_try_shorten_fname( cur_entry.data.buffer_list.buffers[i].fname); buf_T *const buf = buflist_new( cur_entry.data.buffer_list.buffers[i].fname, sfname, 0, @@ -1599,13 +1601,13 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, #define DUMP_ADDITIONAL_ELEMENTS(src, what) \ do { \ if ((src) != NULL) { \ - for (listitem_T *li = (src)->lv_first; li != NULL; li = li->li_next) { \ - if (encode_vim_to_msgpack(spacker, &li->li_tv, \ + TV_LIST_ITER((src), li, { \ + if (encode_vim_to_msgpack(spacker, TV_LIST_ITEM_TV(li), \ _("additional elements of ShaDa " what)) \ == FAIL) { \ goto shada_pack_entry_error; \ } \ - } \ + }); \ } \ } while (0) #define DUMP_ADDITIONAL_DATA(src, what) \ @@ -1647,25 +1649,21 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, case kSDItemHistoryEntry: { const bool is_hist_search = entry.data.history_item.histtype == HIST_SEARCH; - const size_t arr_size = 2 + (size_t) is_hist_search + (size_t) ( - entry.data.history_item.additional_elements == NULL - ? 0 - : entry.data.history_item.additional_elements->lv_len); + const size_t arr_size = 2 + (size_t)is_hist_search + (size_t)( + tv_list_len(entry.data.history_item.additional_elements)); msgpack_pack_array(spacker, arr_size); msgpack_pack_uint8(spacker, entry.data.history_item.histtype); PACK_BIN(cstr_as_string(entry.data.history_item.string)); if (is_hist_search) { - msgpack_pack_uint8(spacker, (uint8_t) entry.data.history_item.sep); + msgpack_pack_uint8(spacker, (uint8_t)entry.data.history_item.sep); } DUMP_ADDITIONAL_ELEMENTS(entry.data.history_item.additional_elements, "history entry item"); break; } case kSDItemVariable: { - const size_t arr_size = 2 + (size_t) ( - entry.data.global_var.additional_elements == NULL - ? 0 - : entry.data.global_var.additional_elements->lv_len); + const size_t arr_size = 2 + (size_t)( + tv_list_len(entry.data.global_var.additional_elements)); msgpack_pack_array(spacker, arr_size); const String varname = cstr_as_string(entry.data.global_var.name); PACK_BIN(varname); @@ -1684,10 +1682,8 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, break; } case kSDItemSubString: { - const size_t arr_size = 1 + (size_t) ( - entry.data.sub_string.additional_elements == NULL - ? 0 - : entry.data.sub_string.additional_elements->lv_len); + const size_t arr_size = 1 + (size_t)( + tv_list_len(entry.data.sub_string.additional_elements)); msgpack_pack_array(spacker, arr_size); PACK_BIN(cstr_as_string(entry.data.sub_string.sub)); DUMP_ADDITIONAL_ELEMENTS(entry.data.sub_string.additional_elements, @@ -2027,6 +2023,113 @@ shada_parse_msgpack_extra_bytes: return ret; } +/// Format shada entry for debugging purposes +/// +/// @param[in] entry ShaDa entry to format. +/// +/// @return string representing ShaDa entry in a static buffer. +static const char *shada_format_entry(const ShadaEntry entry) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_UNUSED FUNC_ATTR_NONNULL_RET +{ + static char ret[1024]; + ret[0] = 0; + vim_snprintf(S_LEN(ret), "[ ] ts=%" PRIu64 " "); + // ^ Space for `can_free_entry` + switch (entry.type) { + case kSDItemMissing: { + vim_snprintf_add(S_LEN(ret), "Missing"); + break; + } + case kSDItemHeader: { + vim_snprintf_add(S_LEN(ret), "Header { TODO }"); + break; + } + case kSDItemBufferList: { + vim_snprintf_add(S_LEN(ret), "BufferList { TODO }"); + break; + } + case kSDItemUnknown: { + vim_snprintf_add(S_LEN(ret), "Unknown { TODO }"); + break; + } + case kSDItemSearchPattern: { + vim_snprintf_add(S_LEN(ret), "SearchPattern { TODO }"); + break; + } + case kSDItemSubString: { + vim_snprintf_add(S_LEN(ret), "SubString { TODO }"); + break; + } + case kSDItemHistoryEntry: { + vim_snprintf_add(S_LEN(ret), "HistoryEntry { TODO }"); + break; + } + case kSDItemRegister: { + vim_snprintf_add(S_LEN(ret), "Register { TODO }"); + break; + } + case kSDItemVariable: { + vim_snprintf_add(S_LEN(ret), "Variable { TODO }"); + break; + } +#define FORMAT_MARK_ENTRY(entry_name, name_fmt, name_fmt_arg) \ + do { \ + typval_T ad_tv = { \ + .v_type = VAR_DICT, \ + .vval.v_dict = entry.data.filemark.additional_data \ + }; \ + size_t ad_len; \ + char *const ad = encode_tv2string(&ad_tv, &ad_len); \ + vim_snprintf_add( \ + S_LEN(ret), \ + entry_name " {" name_fmt " file=[%zu]\"%.512s\", " \ + "pos={l=%" PRIdLINENR ",c=%" PRIdCOLNR ",a=%" PRIdCOLNR "}, " \ + "ad={%p:[%zu]%.64s} }", \ + name_fmt_arg, \ + strlen(entry.data.filemark.fname), \ + entry.data.filemark.fname, \ + entry.data.filemark.mark.lnum, \ + entry.data.filemark.mark.col, \ + entry.data.filemark.mark.coladd, \ + entry.data.filemark.additional_data, \ + ad_len, \ + ad); \ + } while (0) + case kSDItemGlobalMark: { + FORMAT_MARK_ENTRY("GlobalMark", " name='%c',", entry.data.filemark.name); + break; + } + case kSDItemChange: { + FORMAT_MARK_ENTRY("Change", "%s", ""); + break; + } + case kSDItemLocalMark: { + FORMAT_MARK_ENTRY("LocalMark", " name='%c',", entry.data.filemark.name); + break; + } + case kSDItemJump: { + FORMAT_MARK_ENTRY("Jump", "%s", ""); + break; + } +#undef FORMAT_MARK_ENTRY + } + return ret; +} + +/// Format possibly freed shada entry for debugging purposes +/// +/// @param[in] entry ShaDa entry to format. +/// +/// @return string representing ShaDa entry in a static buffer. +static const char *shada_format_pfreed_entry( + const PossiblyFreedShadaEntry pfs_entry) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_UNUSED FUNC_ATTR_NONNULL_RET +{ + char *ret = (char *)shada_format_entry(pfs_entry.data); + ret[1] = (pfs_entry.can_free_entry ? 'T' : 'F'); + return ret; +} + /// Read and merge in ShaDa file, used when writing /// /// @param[in] sd_reader Structure containing file reader definition. @@ -2078,9 +2181,12 @@ static inline ShaDaWriteResult shada_read_when_writing( shada_free_shada_entry(&wms_entry->data); \ } \ } \ - wms_entry->can_free_entry = true; \ - wms_entry->data = (entry); \ + *wms_entry = pfs_entry; \ } while (0) + const PossiblyFreedShadaEntry pfs_entry = { + .can_free_entry = true, + .data = entry, + }; switch (entry.type) { case kSDItemMissing: { break; @@ -2132,13 +2238,49 @@ static inline ShaDaWriteResult shada_read_when_writing( break; } case kSDItemGlobalMark: { - const int idx = mark_global_index(entry.data.filemark.name); - if (idx < 0) { - ret = shada_pack_entry(packer, entry, 0); - shada_free_shada_entry(&entry); - break; + if (ascii_isdigit(entry.data.filemark.name)) { + bool processed_mark = false; + // Completely ignore numbered mark names, make a list sorted by + // timestamp. + for (size_t i = ARRAY_SIZE(wms->numbered_marks); i > 0; i--) { + ShadaEntry wms_entry = wms->numbered_marks[i - 1].data; + if (wms_entry.type != kSDItemGlobalMark) { + continue; + } + // Ignore duplicates. + if (wms_entry.timestamp == entry.timestamp + && (wms_entry.data.filemark.additional_data == NULL + && entry.data.filemark.additional_data == NULL) + && marks_equal(wms_entry.data.filemark.mark, + entry.data.filemark.mark) + && strcmp(wms_entry.data.filemark.fname, + entry.data.filemark.fname) == 0) { + shada_free_shada_entry(&entry); + processed_mark = true; + break; + } + if (wms_entry.timestamp >= entry.timestamp) { + processed_mark = true; + if (i < ARRAY_SIZE(wms->numbered_marks)) { + replace_numbered_mark(wms, i, pfs_entry); + } else { + shada_free_shada_entry(&entry); + } + break; + } + } + if (!processed_mark) { + replace_numbered_mark(wms, 0, pfs_entry); + } + } else { + const int idx = mark_global_index(entry.data.filemark.name); + if (idx < 0) { + ret = shada_pack_entry(packer, entry, 0); + shada_free_shada_entry(&entry); + break; + } + COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry); } - COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry); break; } case kSDItemChange: @@ -2182,8 +2324,7 @@ static inline ShaDaWriteResult shada_read_when_writing( shada_free_shada_entry(&wms_entry->data); } } - wms_entry->can_free_entry = true; - wms_entry->data = entry; + *wms_entry = pfs_entry; } } else { #define FREE_POSSIBLY_FREED_SHADA_ENTRY(entry) \ @@ -2223,6 +2364,20 @@ static inline ShaDaWriteResult shada_read_when_writing( return ret; } +/// Check whether buffer should be ignored +/// +/// @param[in] buf buf_T* to check. +/// @param[in] removable_bufs Cache of buffers ignored due to their location. +/// +/// @return true or false. +static inline bool ignore_buf(const buf_T *const buf, + khash_t(bufset) *const removable_bufs) + FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE +{ + return (buf->b_ffname == NULL || !buf->b_p_bl || bt_quickfix(buf) \ + || in_bufset(removable_bufs, buf)); +} + /// Get list of buffers to write to the shada file /// /// @param[in] removable_bufs Buffers which are ignored @@ -2234,11 +2389,9 @@ static inline ShadaEntry shada_get_buflist( { int max_bufs = get_shada_parameter('%'); size_t buf_count = 0; -#define IGNORE_BUF(buf)\ - (buf->b_ffname == NULL || !buf->b_p_bl || bt_quickfix(buf) \ - || in_bufset(removable_bufs, buf)) // NOLINT(whitespace/indent) FOR_ALL_BUFFERS(buf) { - if (!IGNORE_BUF(buf) && (max_bufs < 0 || buf_count < (size_t)max_bufs)) { + if (!ignore_buf(buf, removable_bufs) + && (max_bufs < 0 || buf_count < (size_t)max_bufs)) { buf_count++; } } @@ -2256,7 +2409,7 @@ static inline ShadaEntry shada_get_buflist( }; size_t i = 0; FOR_ALL_BUFFERS(buf) { - if (IGNORE_BUF(buf)) { + if (ignore_buf(buf, removable_bufs)) { continue; } if (i >= buf_count) { @@ -2270,7 +2423,6 @@ static inline ShadaEntry shada_get_buflist( i++; } -#undef IGNORE_BUF return buflist_entry; } @@ -2372,6 +2524,34 @@ static inline void shada_initialize_registers(WriteMergerState *const wms, } while (reg_iter != NULL); } +/// Replace numbered mark in WriteMergerState +/// +/// Frees the last mark, moves (including adjusting mark names) marks from idx +/// to the last-but-one one and saves the new mark at given index. +/// +/// @param[out] wms Merger state to adjust. +/// @param[in] idx Index at which new mark should be placed. +/// @param[in] entry New mark. +static inline void replace_numbered_mark(WriteMergerState *const wms, + const size_t idx, + const PossiblyFreedShadaEntry entry) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + if (ARRAY_LAST_ENTRY(wms->numbered_marks).can_free_entry) { + shada_free_shada_entry(&ARRAY_LAST_ENTRY(wms->numbered_marks).data); + } + for (size_t i = idx; i < ARRAY_SIZE(wms->numbered_marks) - 1; i++) { + if (wms->numbered_marks[i].data.type == kSDItemGlobalMark) { + wms->numbered_marks[i].data.data.filemark.name = (char)('0' + (int)i + 1); + } + } + memmove(wms->numbered_marks + idx + 1, wms->numbered_marks + idx, + sizeof(wms->numbered_marks[0]) + * (ARRAY_SIZE(wms->numbered_marks) - 1 - idx)); + wms->numbered_marks[idx] = entry; + wms->numbered_marks[idx].data.data.filemark.name = (char)('0' + (int)idx); +} + /// Write ShaDa file /// /// @param[in] sd_writer Structure containing file writer definition. @@ -2564,6 +2744,12 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, 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)); @@ -2598,6 +2784,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, // Initialize global marks if (dump_global_marks) { const void *global_mark_iter = NULL; + size_t digit_mark_idx = 0; do { char name = NUL; xfmark_T fm; @@ -2620,7 +2807,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } fname = (const char *) buf->b_ffname; } - wms->global_marks[mark_global_index(name)] = (PossiblyFreedShadaEntry) { + const PossiblyFreedShadaEntry pf_entry = { .can_free_entry = false, .data = { .type = kSDItemGlobalMark, @@ -2630,11 +2817,16 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, .mark = fm.fmark.mark, .name = name, .additional_data = fm.fmark.additional_data, - .fname = (char *) fname, + .fname = (char *)fname, } } }, }; + if (ascii_isdigit(name)) { + replace_numbered_mark(wms, digit_mark_idx++, pf_entry); + } else { + wms->global_marks[mark_global_index(name)] = pf_entry; + } } while (global_mark_iter != NULL); } @@ -2716,6 +2908,26 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } } + // Update numbered marks: '0' should be replaced with the current position, + // '9' should be removed and all other marks shifted. + if (!ignore_buf(curbuf, &removable_bufs) && curwin->w_cursor.lnum != 0) { + replace_numbered_mark(wms, 0, (PossiblyFreedShadaEntry) { + .can_free_entry = false, + .data = { + .type = kSDItemGlobalMark, + .timestamp = os_time(), + .data = { + .filemark = { + .mark = curwin->w_cursor, + .name = '0', + .additional_data = NULL, + .fname = (char *)curbuf->b_ffname, + } + } + }, + }); + } + // Write the rest #define PACK_WMS_ARRAY(wms_array) \ do { \ @@ -2730,6 +2942,7 @@ static ShaDaWriteResult shada_write(ShaDaWriteDef *const sd_writer, } \ } while (0) PACK_WMS_ARRAY(wms->global_marks); + PACK_WMS_ARRAY(wms->numbered_marks); PACK_WMS_ARRAY(wms->registers); for (size_t i = 0; i < wms->jumps_size; i++) { if (shada_pack_pfreed_entry(packer, wms->jumps[i], max_kbyte) @@ -2824,6 +3037,7 @@ shada_write_exit: return ret; } +#undef IGNORE_BUF #undef PACK_STATIC_STR /// Write ShaDa file to a given location @@ -2993,7 +3207,7 @@ shada_write_file_nomerge: {} } else { if (sw_ret == kSDWriteReadNotShada) { EMSG3(_(RNERR "Did not rename %s because %s " - "does not looks like a ShaDa file"), tempname, fname); + "does not look like a ShaDa file"), tempname, fname); } else { EMSG3(_(RNERR "Did not rename %s to %s because there were errors " "during writing it"), tempname, fname); diff --git a/src/nvim/spell.c b/src/nvim/spell.c index 42c9bcc0ee..34eb2fdf1b 100644 --- a/src/nvim/spell.c +++ b/src/nvim/spell.c @@ -1484,21 +1484,23 @@ spell_move_to ( return found_len; } - if (curline) + if (curline) { break; // only check cursor line + } + + // If we are back at the starting line and searched it again there + // is no match, give up. + if (lnum == wp->w_cursor.lnum && wrapped) { + break; + } // Advance to next line. if (dir == BACKWARD) { - // If we are back at the starting line and searched it again there - // is no match, give up. - if (lnum == wp->w_cursor.lnum && wrapped) - break; - - if (lnum > 1) - --lnum; - else if (!p_ws) + if (lnum > 1) { + lnum--; + } else if (!p_ws) { break; // at first line and 'nowrapscan' - else { + } else { // Wrap around to the end of the buffer. May search the // starting line again and accept the last match. lnum = wp->w_buffer->b_ml.ml_line_count; @@ -1523,8 +1525,9 @@ spell_move_to ( // If we are back at the starting line and there is no match then // give up. - if (lnum == wp->w_cursor.lnum && (!found_one || wrapped)) + if (lnum == wp->w_cursor.lnum && !found_one) { break; + } // Skip the characters at the start of the next line that were // included in a match crossing line boundaries. @@ -2565,7 +2568,7 @@ static bool spell_iswordp(char_u *p, win_T *wp) int c; if (has_mbyte) { - l = MB_BYTE2LEN(*p); + l = MB_PTR2LEN(p); s = p; if (l == 1) { // be quick for ASCII @@ -3136,8 +3139,13 @@ spell_find_suggest ( if (su->su_badlen >= MAXWLEN) su->su_badlen = MAXWLEN - 1; // just in case STRLCPY(su->su_badword, su->su_badptr, su->su_badlen + 1); - (void)spell_casefold(su->su_badptr, su->su_badlen, - su->su_fbadword, MAXWLEN); + (void)spell_casefold(su->su_badptr, su->su_badlen, su->su_fbadword, MAXWLEN); + + // TODO(vim): make this work if the case-folded text is longer than the + // original text. Currently an illegal byte causes wrong pointer + // computations. + su->su_fbadword[su->su_badlen] = NUL; + // get caps flags for bad word su->su_badflags = badword_captype(su->su_badptr, su->su_badptr + su->su_badlen); @@ -3213,26 +3221,25 @@ spell_find_suggest ( // Find suggestions by evaluating expression "expr". static void spell_suggest_expr(suginfo_T *su, char_u *expr) { - list_T *list; - listitem_T *li; int score; const char *p; // The work is split up in a few parts to avoid having to export // suginfo_T. // First evaluate the expression and get the resulting list. - list = eval_spell_expr(su->su_badword, expr); + list_T *const list = eval_spell_expr(su->su_badword, expr); if (list != NULL) { // Loop over the items in the list. - for (li = list->lv_first; li != NULL; li = li->li_next) - if (li->li_tv.v_type == VAR_LIST) { + TV_LIST_ITER(list, li, { + if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { // Get the word and the score from the items. - score = get_spellword(li->li_tv.vval.v_list, &p); + score = get_spellword(TV_LIST_ITEM_TV(li)->vval.v_list, &p); if (score >= 0 && score <= su->su_maxscore) { add_suggestion(su, &su->su_ga, (const char_u *)p, su->su_badlen, score, 0, true, su->su_sallang, false); } } + }); tv_list_unref(list); } @@ -4108,10 +4115,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so && goodword_ends) { int l; - if (has_mbyte) - l = MB_BYTE2LEN(fword[sp->ts_fidx]); - else - l = 1; + l = MB_PTR2LEN(fword + sp->ts_fidx); if (fword_ends) { // Copy the skipped character to preword. memmove(preword + sp->ts_prewordlen, @@ -4257,8 +4261,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Correct ts_fidx for the byte length of the // character (we didn't check that before). sp->ts_fidx = sp->ts_fcharstart - + MB_BYTE2LEN( - fword[sp->ts_fcharstart]); + + MB_PTR2LEN(fword + sp->ts_fcharstart); // For changing a composing character adjust // the score from SCORE_SUBST to @@ -4364,11 +4367,12 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // results. if (has_mbyte) { c = mb_ptr2char(fword + sp->ts_fidx); - stack[depth].ts_fidx += MB_BYTE2LEN(fword[sp->ts_fidx]); - if (enc_utf8 && utf_iscomposing(c)) + stack[depth].ts_fidx += MB_PTR2LEN(fword + sp->ts_fidx); + if (enc_utf8 && utf_iscomposing(c)) { stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP; - else if (c == mb_ptr2char(fword + stack[depth].ts_fidx)) + } else if (c == mb_ptr2char(fword + stack[depth].ts_fidx)) { stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP; + } } else { ++stack[depth].ts_fidx; if (fword[sp->ts_fidx] == fword[sp->ts_fidx + 1]) @@ -4550,9 +4554,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo the STATE_SWAP swap: "21" -> "12". p = fword + sp->ts_fidx; if (has_mbyte) { - n = MB_BYTE2LEN(*p); + n = MB_PTR2LEN(p); c = mb_ptr2char(p + n); - memmove(p + MB_BYTE2LEN(p[n]), p, n); + memmove(p + MB_PTR2LEN(p + n), p, n); mb_char2bytes(c, p); } else { c = *p; @@ -4625,11 +4629,11 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo STATE_SWAP3: "321" -> "123" p = fword + sp->ts_fidx; if (has_mbyte) { - n = MB_BYTE2LEN(*p); + n = MB_PTR2LEN(p); c2 = mb_ptr2char(p + n); - fl = MB_BYTE2LEN(p[n]); + fl = MB_PTR2LEN(p + n); c = mb_ptr2char(p + n + fl); - tl = MB_BYTE2LEN(p[n + fl]); + tl = MB_PTR2LEN(p + n + fl); memmove(p + fl + tl, p, n); mb_char2bytes(c, p); mb_char2bytes(c2, p + tl); @@ -4688,10 +4692,10 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so // Undo ROT3L: "231" -> "123" p = fword + sp->ts_fidx; if (has_mbyte) { - n = MB_BYTE2LEN(*p); - n += MB_BYTE2LEN(p[n]); + n = MB_PTR2LEN(p); + n += MB_PTR2LEN(p + n); c = mb_ptr2char(p + n); - tl = MB_BYTE2LEN(p[n]); + tl = MB_PTR2LEN(p + n); memmove(p + tl, p, n); mb_char2bytes(c, p); } else { @@ -4741,9 +4745,9 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so p = fword + sp->ts_fidx; if (has_mbyte) { c = mb_ptr2char(p); - tl = MB_BYTE2LEN(*p); - n = MB_BYTE2LEN(p[tl]); - n += MB_BYTE2LEN(p[tl + n]); + tl = MB_PTR2LEN(p); + n = MB_PTR2LEN(p + tl); + n += MB_PTR2LEN(p + tl + n); memmove(p, p + tl, n); mb_char2bytes(c, p + n); } else { @@ -5355,7 +5359,7 @@ add_sound_suggest ( // Find the word nr in the soundfold tree. sfwordnr = soundfold_find(slang, goodword); if (sfwordnr < 0) { - EMSG2(_(e_intern2), "add_sound_suggest()"); + internal_error("add_sound_suggest()"); return; } @@ -7143,7 +7147,7 @@ void ex_spelldump(exarg_T *eap) set_option_value("spl", dummy, (char *)spl, OPT_LOCAL); xfree(spl); - if (!bufempty()) { + if (!BUFEMPTY()) { return; } diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c index 1f7f616782..f5d5d408a1 100644 --- a/src/nvim/spellfile.c +++ b/src/nvim/spellfile.c @@ -228,7 +228,6 @@ #include <stdio.h> #include <stdint.h> #include <wctype.h> -#include <strings.h> #include "nvim/vim.h" #include "nvim/spell_defs.h" @@ -3656,7 +3655,7 @@ static int spell_read_wordfile(spellinfo_T *spin, char_u *fname) flags |= WF_REGION; l = *p - '0'; - if (l > spin->si_region_count) { + if (l == 0 || l > spin->si_region_count) { smsg(_("Invalid region nr in %s line %d: %s"), fname, lnum, p); break; diff --git a/src/nvim/strings.c b/src/nvim/strings.c index 687f734742..e3f6a8cbf6 100644 --- a/src/nvim/strings.c +++ b/src/nvim/strings.c @@ -50,6 +50,13 @@ #include "nvim/os/shell.h" #include "nvim/eval/encode.h" +#ifdef __MINGW32__ +# undef fpclassify +# define fpclassify __fpclassify +# undef isnan +# define isnan _isnan +#endif + /* * Copy "string" into newly allocated memory. */ diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c index bc7362af72..2613c09c19 100644 --- a/src/nvim/syntax.c +++ b/src/nvim/syntax.c @@ -52,6 +52,7 @@ static bool did_syntax_onoff = false; struct hl_group { char_u *sg_name; ///< highlight group name char_u *sg_name_u; ///< uppercase of sg_name + int sg_cleared; ///< "hi clear" was used int sg_attr; ///< Screen attr @see ATTR_ENTRY int sg_link; ///< link to this highlight group ID int sg_set; ///< combination of flags in \ref SG_SET @@ -63,7 +64,7 @@ struct hl_group { int sg_cterm_bold; ///< bold attr was set for light color // for RGB UIs int sg_gui; ///< "gui=" highlighting attributes - ///< (combination of \ref HL_ATTRIBUTES) + ///< (combination of \ref HlAttrFlags) RgbValue sg_rgb_fg; ///< RGB foreground color RgbValue sg_rgb_bg; ///< RGB background color RgbValue sg_rgb_sp; ///< RGB special color @@ -880,7 +881,8 @@ static void syn_start_line(void) } next_match_idx = -1; - ++current_line_id; + current_line_id++; + next_seqnr = 1; } /* @@ -1598,6 +1600,7 @@ get_syntax_attr ( current_id = 0; current_trans_id = 0; current_flags = 0; + current_seqnr = 0; return 0; } @@ -2041,6 +2044,7 @@ syn_current_attr ( current_id = 0; current_trans_id = 0; current_flags = 0; + current_seqnr = 0; if (cur_si != NULL) { for (int idx = current_state.ga_len - 1; idx >= 0; --idx) { sip = &CUR_STATE(idx); @@ -2129,9 +2133,11 @@ syn_current_attr ( /* nextgroup ends at end of line, unless "skipnl" or "skipempty" present */ if (current_next_list != NULL - && syn_getcurline()[current_col + 1] == NUL - && !(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))) + && (line = syn_getcurline())[current_col] != NUL + && line[current_col + 1] == NUL + && !(current_next_flags & (HL_SKIPNL | HL_SKIPEMPTY))) { current_next_list = NULL; + } if (!GA_EMPTY(&zero_width_next_ga)) ga_clear(&zero_width_next_ga); @@ -3019,12 +3025,19 @@ static void syn_cmd_conceal(exarg_T *eap, int syncing) return; next = skiptowhite(arg); - if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) - curwin->w_s->b_syn_conceal = TRUE; - else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3) - curwin->w_s->b_syn_conceal = FALSE; - else + if (*arg == NUL) { + if (curwin->w_s->b_syn_conceal) { + MSG(_("syn conceal on")); + } else { + MSG(_("syn conceal off")); + } + } else if (STRNICMP(arg, "on", 2) == 0 && next - arg == 2) { + curwin->w_s->b_syn_conceal = true; + } else if (STRNICMP(arg, "off", 3) == 0 && next - arg == 3) { + curwin->w_s->b_syn_conceal = false; + } else { EMSG2(_("E390: Illegal argument: %s"), arg); + } } /* @@ -3040,12 +3053,19 @@ static void syn_cmd_case(exarg_T *eap, int syncing) return; next = skiptowhite(arg); - if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) - curwin->w_s->b_syn_ic = FALSE; - else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6) - curwin->w_s->b_syn_ic = TRUE; - else + if (*arg == NUL) { + if (curwin->w_s->b_syn_ic) { + MSG(_("syntax case ignore")); + } else { + MSG(_("syntax case match")); + } + } else if (STRNICMP(arg, "match", 5) == 0 && next - arg == 5) { + curwin->w_s->b_syn_ic = false; + } else if (STRNICMP(arg, "ignore", 6) == 0 && next - arg == 6) { + curwin->w_s->b_syn_ic = true; + } else { EMSG2(_("E390: Illegal argument: %s"), arg); + } } /* @@ -3061,7 +3081,15 @@ static void syn_cmd_spell(exarg_T *eap, int syncing) return; next = skiptowhite(arg); - if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) { + if (*arg == NUL) { + if (curwin->w_s->b_syn_spell == SYNSPL_TOP) { + MSG(_("syntax spell toplevel")); + } else if (curwin->w_s->b_syn_spell == SYNSPL_NOTOP) { + MSG(_("syntax spell notoplevel")); + } else { + MSG(_("syntax spell default")); + } + } else if (STRNICMP(arg, "toplevel", 8) == 0 && next - arg == 8) { curwin->w_s->b_syn_spell = SYNSPL_TOP; } else if (STRNICMP(arg, "notoplevel", 10) == 0 && next - arg == 10) { curwin->w_s->b_syn_spell = SYNSPL_NOTOP; @@ -3121,10 +3149,11 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing) */ void syntax_clear(synblock_T *block) { - block->b_syn_error = FALSE; /* clear previous error */ - block->b_syn_ic = FALSE; /* Use case, by default */ - block->b_syn_spell = SYNSPL_DEFAULT; /* default spell checking */ - block->b_syn_containedin = FALSE; + block->b_syn_error = false; // clear previous error + block->b_syn_ic = false; // Use case, by default + block->b_syn_spell = SYNSPL_DEFAULT; // default spell checking + block->b_syn_containedin = false; + block->b_syn_conceal = false; /* free the keywords */ clear_keywtab(&block->b_keywtab); @@ -4001,10 +4030,11 @@ get_group_name ( * Return NULL for any error; */ static char_u * -get_syn_options ( - char_u *arg, /* next argument to be checked */ - syn_opt_arg_T *opt, /* various things */ - int *conceal_char +get_syn_options( + char_u *arg, // next argument to be checked + syn_opt_arg_T *opt, // various things + int *conceal_char, + int skip // TRUE if skipping over command ) { char_u *gname_start, *gname; @@ -4080,14 +4110,17 @@ get_syn_options ( EMSG(_("E395: contains argument not accepted here")); return NULL; } - if (get_id_list(&arg, 8, &opt->cont_list) == FAIL) + if (get_id_list(&arg, 8, &opt->cont_list, skip) == FAIL) { return NULL; + } } else if (flagtab[fidx].argtype == 2) { - if (get_id_list(&arg, 11, &opt->cont_in_list) == FAIL) + if (get_id_list(&arg, 11, &opt->cont_in_list, skip) == FAIL) { return NULL; + } } else if (flagtab[fidx].argtype == 3) { - if (get_id_list(&arg, 9, &opt->next_list) == FAIL) + if (get_id_list(&arg, 9, &opt->next_list, skip) == FAIL) { return NULL; + } } else if (flagtab[fidx].argtype == 11 && arg[5] == '=') { /* cchar=? */ if (has_mbyte) { @@ -4206,11 +4239,11 @@ static void syn_cmd_include(exarg_T *eap, int syncing) */ eap->argt |= (XFILE | NOSPC); separate_nextcmd(eap); - if (*eap->arg == '<' || *eap->arg == '$' || path_is_absolute_path(eap->arg)) { - /* For an absolute path, "$VIM/..." or "<sfile>.." we ":source" the - * file. Need to expand the file name first. In other cases - * ":runtime!" is used. */ - source = TRUE; + if (*eap->arg == '<' || *eap->arg == '$' || path_is_absolute(eap->arg)) { + // For an absolute path, "$VIM/..." or "<sfile>.." we ":source" the + // file. Need to expand the file name first. In other cases + // ":runtime!" is used. + source = true; if (expand_filename(eap, syn_cmdlinep, &errormsg) == FAIL) { if (errormsg != NULL) EMSG(errormsg); @@ -4257,7 +4290,11 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) rest = get_group_name(arg, &group_name_end); if (rest != NULL) { - syn_id = syn_check_group(arg, (int)(group_name_end - arg)); + if (eap->skip) { + syn_id = -1; + } else { + syn_id = syn_check_group(arg, (int)(group_name_end - arg)); + } if (syn_id != 0) { // Allocate a buffer, for removing backslashes in the keyword. keyword_copy = xmalloc(STRLEN(rest) + 1); @@ -4276,7 +4313,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing) cnt = 0; p = keyword_copy; for (; rest != NULL && !ends_excmd(*rest); rest = skipwhite(rest)) { - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest == NULL || ends_excmd(*rest)) { break; } @@ -4375,17 +4412,18 @@ syn_cmd_match ( syn_opt_arg.cont_list = NULL; syn_opt_arg.cont_in_list = NULL; syn_opt_arg.next_list = NULL; - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); /* get the pattern. */ init_syn_patterns(); memset(&item, 0, sizeof(item)); rest = get_syn_pattern(rest, &item); - if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) + if (vim_regcomp_had_eol() && !(syn_opt_arg.flags & HL_EXCLUDENL)) { syn_opt_arg.flags |= HL_HAS_EOL; + } - /* Get options after the pattern */ - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); + // Get options after the pattern + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); if (rest != NULL) { /* all arguments are valid */ /* @@ -4497,14 +4535,13 @@ syn_cmd_region ( syn_opt_arg.cont_in_list = NULL; syn_opt_arg.next_list = NULL; - /* - * get the options, patterns and matchgroup. - */ + // get the options, patterns and matchgroup. while (rest != NULL && !ends_excmd(*rest)) { - /* Check for option arguments */ - rest = get_syn_options(rest, &syn_opt_arg, &conceal_char); - if (rest == NULL || ends_excmd(*rest)) + // Check for option arguments + rest = get_syn_options(rest, &syn_opt_arg, &conceal_char, eap->skip); + if (rest == NULL || ends_excmd(*rest)) { break; + } /* must be a pattern or matchgroup then */ key_end = rest; @@ -4926,13 +4963,17 @@ static void syn_cmd_cluster(exarg_T *eap, int syncing) break; clstr_list = NULL; - if (get_id_list(&rest, opt_len, &clstr_list) == FAIL) { + if (get_id_list(&rest, opt_len, &clstr_list, eap->skip) == FAIL) { EMSG2(_(e_invarg2), rest); break; } - syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list, - &clstr_list, list_op); - got_clstr = TRUE; + if (scl_id >= 0) { + syn_combine_list(&SYN_CLSTR(curwin->w_s)[scl_id].scl_list, + &clstr_list, list_op); + } else { + xfree(clstr_list); + } + got_clstr = true; } if (got_clstr) { @@ -5180,9 +5221,10 @@ static void syn_cmd_sync(exarg_T *eap, int syncing) static int get_id_list ( char_u **arg, - int keylen, /* length of keyword */ - short **list /* where to store the resulting list, if not - NULL, the list is silently skipped! */ + int keylen, // length of keyword + int16_t **list, // where to store the resulting list, if not + // NULL, the list is silently skipped! + int skip ) { char_u *p = NULL; @@ -5238,8 +5280,9 @@ get_id_list ( break; } if (count != 0) { - EMSG2(_("E408: %s must be first in contains list"), name + 1); - failed = TRUE; + EMSG2(_("E408: %s must be first in contains list"), + name + 1); + failed = true; xfree(name); break; } @@ -5251,7 +5294,11 @@ get_id_list ( id = SYNID_CONTAINED; id += current_syn_inc_tag; } else if (name[1] == '@') { - id = syn_check_cluster(name + 2, (int)(end - p - 1)); + if (skip) { + id = -1; + } else { + id = syn_check_cluster(name + 2, (int)(end - p - 1)); + } } else { /* * Handle full group name. @@ -5930,9 +5977,9 @@ static void syntime_report(void) // // When making changes here, also change runtime/colors/default.vim! -static char *highlight_init_both[] = -{ - "Conceal ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey", +static const char *highlight_init_both[] = { + "Conceal " + "ctermbg=DarkGrey ctermfg=LightGrey guibg=DarkGrey guifg=LightGrey", "Cursor guibg=fg guifg=bg", "lCursor guibg=fg guifg=bg", "DiffText cterm=bold ctermbg=Red gui=bold guibg=Red", @@ -5952,11 +5999,11 @@ static char *highlight_init_both[] = "default link QuickFixLine Search", "default link Substitute Search", "default link Whitespace NonText", + "default link MsgSeparator StatusLine", NULL }; -static char *highlight_init_light[] = -{ +static const char *highlight_init_light[] = { "ColorColumn ctermbg=LightRed guibg=LightRed", "CursorColumn ctermbg=LightGrey guibg=Grey90", "CursorLine cterm=underline guibg=Grey90", @@ -5989,8 +6036,7 @@ static char *highlight_init_light[] = NULL }; -static char *highlight_init_dark[] = -{ +static const char *highlight_init_dark[] = { "ColorColumn ctermbg=DarkRed guibg=DarkRed", "CursorColumn ctermbg=DarkGrey guibg=Grey40", "CursorLine cterm=underline guibg=Grey40", @@ -6023,18 +6069,223 @@ static char *highlight_init_dark[] = NULL }; +const char *const highlight_init_cmdline[] = { + // XXX When modifying a list modify it in both valid and invalid halfs. + // TODO(ZyX-I): merge valid and invalid groups via a macros. + + // NvimInternalError should appear only when highlighter has a bug. + "NvimInternalError ctermfg=Red ctermbg=Red guifg=Red guibg=Red", + + // Highlight groups (links) used by parser: + + "default link NvimAssignment Operator", + "default link NvimPlainAssignment NvimAssignment", + "default link NvimAugmentedAssignment NvimAssignment", + "default link NvimAssignmentWithAddition NvimAugmentedAssignment", + "default link NvimAssignmentWithSubtraction NvimAugmentedAssignment", + "default link NvimAssignmentWithConcatenation NvimAugmentedAssignment", + + "default link NvimOperator Operator", + + "default link NvimUnaryOperator NvimOperator", + "default link NvimUnaryPlus NvimUnaryOperator", + "default link NvimUnaryMinus NvimUnaryOperator", + "default link NvimNot NvimUnaryOperator", + + "default link NvimBinaryOperator NvimOperator", + "default link NvimComparison NvimBinaryOperator", + "default link NvimComparisonModifier NvimComparison", + "default link NvimBinaryPlus NvimBinaryOperator", + "default link NvimBinaryMinus NvimBinaryOperator", + "default link NvimConcat NvimBinaryOperator", + "default link NvimConcatOrSubscript NvimConcat", + "default link NvimOr NvimBinaryOperator", + "default link NvimAnd NvimBinaryOperator", + "default link NvimMultiplication NvimBinaryOperator", + "default link NvimDivision NvimBinaryOperator", + "default link NvimMod NvimBinaryOperator", + + "default link NvimTernary NvimOperator", + "default link NvimTernaryColon NvimTernary", + + "default link NvimParenthesis Delimiter", + "default link NvimLambda NvimParenthesis", + "default link NvimNestingParenthesis NvimParenthesis", + "default link NvimCallingParenthesis NvimParenthesis", + + "default link NvimSubscript NvimParenthesis", + "default link NvimSubscriptBracket NvimSubscript", + "default link NvimSubscriptColon NvimSubscript", + "default link NvimCurly NvimSubscript", + + "default link NvimContainer NvimParenthesis", + "default link NvimDict NvimContainer", + "default link NvimList NvimContainer", + + "default link NvimIdentifier Identifier", + "default link NvimIdentifierScope NvimIdentifier", + "default link NvimIdentifierScopeDelimiter NvimIdentifier", + "default link NvimIdentifierName NvimIdentifier", + "default link NvimIdentifierKey NvimIdentifier", + + "default link NvimColon Delimiter", + "default link NvimComma Delimiter", + "default link NvimArrow Delimiter", + + "default link NvimRegister SpecialChar", + "default link NvimNumber Number", + "default link NvimFloat NvimNumber", + "default link NvimNumberPrefix Type", + + "default link NvimOptionSigil Type", + "default link NvimOptionName NvimIdentifier", + "default link NvimOptionScope NvimIdentifierScope", + "default link NvimOptionScopeDelimiter NvimIdentifierScopeDelimiter", + + "default link NvimEnvironmentSigil NvimOptionSigil", + "default link NvimEnvironmentName NvimIdentifier", + + "default link NvimString String", + "default link NvimStringBody NvimString", + "default link NvimStringQuote NvimString", + "default link NvimStringSpecial SpecialChar", + + "default link NvimSingleQuote NvimStringQuote", + "default link NvimSingleQuotedBody NvimStringBody", + "default link NvimSingleQuotedQuote NvimStringSpecial", + + "default link NvimDoubleQuote NvimStringQuote", + "default link NvimDoubleQuotedBody NvimStringBody", + "default link NvimDoubleQuotedEscape NvimStringSpecial", + + "default link NvimFigureBrace NvimInternalError", + "default link NvimSingleQuotedUnknownEscape NvimInternalError", + + "default link NvimSpacing Normal", + + // NvimInvalid groups: + + "default link NvimInvalidSingleQuotedUnknownEscape NvimInternalError", + + "default link NvimInvalid Error", + + "default link NvimInvalidAssignment NvimInvalid", + "default link NvimInvalidPlainAssignment NvimInvalidAssignment", + "default link NvimInvalidAugmentedAssignment NvimInvalidAssignment", + "default link NvimInvalidAssignmentWithAddition " + "NvimInvalidAugmentedAssignment", + "default link NvimInvalidAssignmentWithSubtraction " + "NvimInvalidAugmentedAssignment", + "default link NvimInvalidAssignmentWithConcatenation " + "NvimInvalidAugmentedAssignment", + + "default link NvimInvalidOperator NvimInvalid", + + "default link NvimInvalidUnaryOperator NvimInvalidOperator", + "default link NvimInvalidUnaryPlus NvimInvalidUnaryOperator", + "default link NvimInvalidUnaryMinus NvimInvalidUnaryOperator", + "default link NvimInvalidNot NvimInvalidUnaryOperator", + + "default link NvimInvalidBinaryOperator NvimInvalidOperator", + "default link NvimInvalidComparison NvimInvalidBinaryOperator", + "default link NvimInvalidComparisonModifier NvimInvalidComparison", + "default link NvimInvalidBinaryPlus NvimInvalidBinaryOperator", + "default link NvimInvalidBinaryMinus NvimInvalidBinaryOperator", + "default link NvimInvalidConcat NvimInvalidBinaryOperator", + "default link NvimInvalidConcatOrSubscript NvimInvalidConcat", + "default link NvimInvalidOr NvimInvalidBinaryOperator", + "default link NvimInvalidAnd NvimInvalidBinaryOperator", + "default link NvimInvalidMultiplication NvimInvalidBinaryOperator", + "default link NvimInvalidDivision NvimInvalidBinaryOperator", + "default link NvimInvalidMod NvimInvalidBinaryOperator", + + "default link NvimInvalidTernary NvimInvalidOperator", + "default link NvimInvalidTernaryColon NvimInvalidTernary", + + "default link NvimInvalidDelimiter NvimInvalid", + + "default link NvimInvalidParenthesis NvimInvalidDelimiter", + "default link NvimInvalidLambda NvimInvalidParenthesis", + "default link NvimInvalidNestingParenthesis NvimInvalidParenthesis", + "default link NvimInvalidCallingParenthesis NvimInvalidParenthesis", + + "default link NvimInvalidSubscript NvimInvalidParenthesis", + "default link NvimInvalidSubscriptBracket NvimInvalidSubscript", + "default link NvimInvalidSubscriptColon NvimInvalidSubscript", + "default link NvimInvalidCurly NvimInvalidSubscript", + + "default link NvimInvalidContainer NvimInvalidParenthesis", + "default link NvimInvalidDict NvimInvalidContainer", + "default link NvimInvalidList NvimInvalidContainer", + + "default link NvimInvalidValue NvimInvalid", + + "default link NvimInvalidIdentifier NvimInvalidValue", + "default link NvimInvalidIdentifierScope NvimInvalidIdentifier", + "default link NvimInvalidIdentifierScopeDelimiter NvimInvalidIdentifier", + "default link NvimInvalidIdentifierName NvimInvalidIdentifier", + "default link NvimInvalidIdentifierKey NvimInvalidIdentifier", + + "default link NvimInvalidColon NvimInvalidDelimiter", + "default link NvimInvalidComma NvimInvalidDelimiter", + "default link NvimInvalidArrow NvimInvalidDelimiter", + + "default link NvimInvalidRegister NvimInvalidValue", + "default link NvimInvalidNumber NvimInvalidValue", + "default link NvimInvalidFloat NvimInvalidNumber", + "default link NvimInvalidNumberPrefix NvimInvalidNumber", + + "default link NvimInvalidOptionSigil NvimInvalidIdentifier", + "default link NvimInvalidOptionName NvimInvalidIdentifier", + "default link NvimInvalidOptionScope NvimInvalidIdentifierScope", + "default link NvimInvalidOptionScopeDelimiter " + "NvimInvalidIdentifierScopeDelimiter", + + "default link NvimInvalidEnvironmentSigil NvimInvalidOptionSigil", + "default link NvimInvalidEnvironmentName NvimInvalidIdentifier", + + // Invalid string bodies and specials are still highlighted as valid ones to + // minimize the red area. + "default link NvimInvalidString NvimInvalidValue", + "default link NvimInvalidStringBody NvimStringBody", + "default link NvimInvalidStringQuote NvimInvalidString", + "default link NvimInvalidStringSpecial NvimStringSpecial", + + "default link NvimInvalidSingleQuote NvimInvalidStringQuote", + "default link NvimInvalidSingleQuotedBody NvimInvalidStringBody", + "default link NvimInvalidSingleQuotedQuote NvimInvalidStringSpecial", + + "default link NvimInvalidDoubleQuote NvimInvalidStringQuote", + "default link NvimInvalidDoubleQuotedBody NvimInvalidStringBody", + "default link NvimInvalidDoubleQuotedEscape NvimInvalidStringSpecial", + "default link NvimInvalidDoubleQuotedUnknownEscape NvimInvalidValue", + + "default link NvimInvalidFigureBrace NvimInvalidDelimiter", + + "default link NvimInvalidSpacing ErrorMsg", + + // Not actually invalid, but we highlight user that he is doing something + // wrong. + "default link NvimDoubleQuotedUnknownEscape NvimInvalidValue", + NULL, +}; + +/// Create default links for Nvim* highlight groups used for cmdline coloring +void syn_init_cmdline_highlight(bool reset, bool init) +{ + for (size_t i = 0 ; highlight_init_cmdline[i] != NULL ; i++) { + do_highlight(highlight_init_cmdline[i], reset, init); + } +} /// Load colors from a file if "g:colors_name" is set, otherwise load builtin /// colors /// /// @param both include groups where 'bg' doesn't matter /// @param reset clear groups first -void -init_highlight(int both, int reset) +void init_highlight(bool both, bool reset) { - int i; - char **pp; - static int had_both = FALSE; + static int had_both = false; // Try finding the color scheme file. Used when a color file was loaded // and 'background' or 't_Co' is changed. @@ -6054,10 +6305,10 @@ init_highlight(int both, int reset) * Didn't use a color file, use the compiled-in colors. */ if (both) { - had_both = TRUE; - pp = highlight_init_both; - for (i = 0; pp[i] != NULL; i++) { - do_highlight((char_u *)pp[i], reset, true); + had_both = true; + const char *const *const pp = highlight_init_both; + for (size_t i = 0; pp[i] != NULL; i++) { + do_highlight(pp[i], reset, true); } } else if (!had_both) { // Don't do anything before the call with both == TRUE from main(). @@ -6066,10 +6317,11 @@ init_highlight(int both, int reset) return; } - pp = (*p_bg == 'l') ? highlight_init_light : highlight_init_dark; - - for (i = 0; pp[i] != NULL; i++) { - do_highlight((char_u *)pp[i], reset, true); + const char *const *const pp = ((*p_bg == 'l') + ? highlight_init_light + : highlight_init_dark); + for (size_t i = 0; pp[i] != NULL; i++) { + do_highlight(pp[i], reset, true); } /* Reverse looks ugly, but grey may not work for 8 colors. Thus let it @@ -6079,15 +6331,14 @@ init_highlight(int both, int reset) * Clear the attributes, needed when changing the t_Co value. */ if (t_colors > 8) { do_highlight( - (char_u *)(*p_bg == 'l' - ? "Visual cterm=NONE ctermbg=LightGrey" - : "Visual cterm=NONE ctermbg=DarkGrey"), false, - true); + (*p_bg == 'l' + ? "Visual cterm=NONE ctermbg=LightGrey" + : "Visual cterm=NONE ctermbg=DarkGrey"), false, true); } else { - do_highlight((char_u *)"Visual cterm=reverse ctermbg=NONE", - FALSE, TRUE); - if (*p_bg == 'l') - do_highlight((char_u *)"Search ctermfg=black", FALSE, TRUE); + do_highlight("Visual cterm=reverse ctermbg=NONE", false, true); + if (*p_bg == 'l') { + do_highlight("Search ctermfg=black", false, true); + } } /* @@ -6104,6 +6355,7 @@ init_highlight(int both, int reset) recursive--; } } + syn_init_cmdline_highlight(false, false); } /* @@ -6138,17 +6390,22 @@ int load_colors(char_u *name) } -/// Handle the ":highlight .." command. -/// When using ":hi clear" this is called recursively for each group with -/// "forceit" and "init" both TRUE. -/// @param init TRUE when called for initializing -void -do_highlight(char_u *line, int forceit, int init) { - char_u *name_end; - char_u *linep; - char_u *key_start; - char_u *arg_start; - char_u *key = NULL, *arg = NULL; +/// Handle ":highlight" command +/// +/// When using ":highlight clear" this is called recursively for each group with +/// forceit and init being both true. +/// +/// @param[in] line Command arguments. +/// @param[in] forceit True when bang is given, allows to link group even if +/// it has its own settings. +/// @param[in] init True when initializing. +void do_highlight(const char *line, const bool forceit, const bool init) + FUNC_ATTR_NONNULL_ALL +{ + const char *name_end; + const char *linep; + const char *key_start; + const char *arg_start; long i; int off; int len; @@ -6162,140 +6419,129 @@ do_highlight(char_u *line, int forceit, int init) { int color; bool is_normal_group = false; // "Normal" group - /* - * If no argument, list current highlighting. - */ - if (ends_excmd(*line)) { + // If no argument, list current highlighting. + if (ends_excmd((uint8_t)(*line))) { for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) { - // todo(vim): only call when the group has attributes set + // TODO(brammool): only call when the group has attributes set highlight_list_one(i); } return; } - /* - * Isolate the name. - */ - name_end = skiptowhite(line); - linep = skipwhite(name_end); + // Isolate the name. + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); - /* - * Check for "default" argument. - */ - if (STRNCMP(line, "default", name_end - line) == 0) { - dodefault = TRUE; + // Check for "default" argument. + if (strncmp(line, "default", name_end - line) == 0) { + dodefault = true; line = linep; - name_end = skiptowhite(line); - linep = skipwhite(name_end); + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); } - /* - * Check for "clear" or "link" argument. - */ - if (STRNCMP(line, "clear", name_end - line) == 0) - doclear = TRUE; - if (STRNCMP(line, "link", name_end - line) == 0) - dolink = TRUE; + // Check for "clear" or "link" argument. + if (strncmp(line, "clear", name_end - line) == 0) { + doclear = true; + } else if (strncmp(line, "link", name_end - line) == 0) { + dolink = true; + } - /* - * ":highlight {group-name}": list highlighting for one group. - */ - if (!doclear && !dolink && ends_excmd(*linep)) { - id = syn_namen2id(line, (int)(name_end - line)); - if (id == 0) - EMSG2(_("E411: highlight group not found: %s"), line); - else + // ":highlight {group-name}": list highlighting for one group. + if (!doclear && !dolink && ends_excmd((uint8_t)(*linep))) { + id = syn_namen2id((const char_u *)line, (int)(name_end - line)); + if (id == 0) { + emsgf(_("E411: highlight group not found: %s"), line); + } else { highlight_list_one(id); + } return; } - /* - * Handle ":highlight link {from} {to}" command. - */ + // Handle ":highlight link {from} {to}" command. if (dolink) { - char_u *from_start = linep; - char_u *from_end; - char_u *to_start; - char_u *to_end; + const char *from_start = linep; + const char *from_end; + const char *to_start; + const char *to_end; int from_id; int to_id; - from_end = skiptowhite(from_start); - to_start = skipwhite(from_end); - to_end = skiptowhite(to_start); + from_end = (const char *)skiptowhite((const char_u *)from_start); + to_start = (const char *)skipwhite((const char_u *)from_end); + to_end = (const char *)skiptowhite((const char_u *)to_start); - if (ends_excmd(*from_start) || ends_excmd(*to_start)) { - EMSG2(_("E412: Not enough arguments: \":highlight link %s\""), - from_start); + if (ends_excmd((uint8_t)(*from_start)) + || ends_excmd((uint8_t)(*to_start))) { + emsgf(_("E412: Not enough arguments: \":highlight link %s\""), + from_start); return; } - if (!ends_excmd(*skipwhite(to_end))) { - EMSG2(_("E413: Too many arguments: \":highlight link %s\""), from_start); + if (!ends_excmd(*skipwhite((const char_u *)to_end))) { + emsgf(_("E413: Too many arguments: \":highlight link %s\""), from_start); return; } - from_id = syn_check_group(from_start, (int)(from_end - from_start)); - if (STRNCMP(to_start, "NONE", 4) == 0) + from_id = syn_check_group((const char_u *)from_start, + (int)(from_end - from_start)); + if (strncmp(to_start, "NONE", 4) == 0) { to_id = 0; - else - to_id = syn_check_group(to_start, (int)(to_end - to_start)); + } else { + to_id = syn_check_group((const char_u *)to_start, + (int)(to_end - to_start)); + } if (from_id > 0 && (!init || HL_TABLE()[from_id - 1].sg_set == 0)) { - /* - * Don't allow a link when there already is some highlighting - * for the group, unless '!' is used - */ + // Don't allow a link when there already is some highlighting + // for the group, unless '!' is used if (to_id > 0 && !forceit && !init && hl_has_settings(from_id - 1, dodefault)) { - if (sourcing_name == NULL && !dodefault) + if (sourcing_name == NULL && !dodefault) { EMSG(_("E414: group has settings, highlight link ignored")); + } } else { if (!init) HL_TABLE()[from_id - 1].sg_set |= SG_LINK; HL_TABLE()[from_id - 1].sg_link = to_id; HL_TABLE()[from_id - 1].sg_scriptID = current_SID; + HL_TABLE()[from_id - 1].sg_cleared = false; redraw_all_later(SOME_VALID); } } - /* Only call highlight_changed() once, after sourcing a syntax file */ - need_highlight_changed = TRUE; + // Only call highlight_changed() once, after sourcing a syntax file. + need_highlight_changed = true; return; } if (doclear) { - /* - * ":highlight clear [group]" command. - */ + // ":highlight clear [group]" command. line = linep; - if (ends_excmd(*line)) { + if (ends_excmd((uint8_t)(*line))) { do_unlet(S_LEN("colors_name"), true); restore_cterm_colors(); - /* - * Clear all default highlight groups and load the defaults. - */ - for (int idx = 0; idx < highlight_ga.ga_len; ++idx) { + // Clear all default highlight groups and load the defaults. + for (int idx = 0; idx < highlight_ga.ga_len; idx++) { highlight_clear(idx); } - init_highlight(TRUE, TRUE); + init_highlight(true, true); highlight_changed(); redraw_later_clear(); return; } - name_end = skiptowhite(line); - linep = skipwhite(name_end); + name_end = (const char *)skiptowhite((const char_u *)line); + linep = (const char *)skipwhite((const char_u *)name_end); } - /* - * Find the group name in the table. If it does not exist yet, add it. - */ - id = syn_check_group(line, (int)(name_end - line)); - if (id == 0) /* failed (out of memory) */ + // Find the group name in the table. If it does not exist yet, add it. + id = syn_check_group((const char_u *)line, (int)(name_end - line)); + if (id == 0) { // Failed (out of memory). return; - idx = id - 1; /* index is ID minus one */ + } + idx = id - 1; // Index is ID minus one. // Return if "default" was used and the group already has settings if (dodefault && hl_has_settings(idx, true)) { @@ -6304,19 +6550,21 @@ do_highlight(char_u *line, int forceit, int init) { is_normal_group = (STRCMP(HL_TABLE()[idx].sg_name_u, "NORMAL") == 0); - /* Clear the highlighting for ":hi clear {group}" and ":hi clear". */ + // Clear the highlighting for ":hi clear {group}" and ":hi clear". if (doclear || (forceit && init)) { highlight_clear(idx); if (!doclear) HL_TABLE()[idx].sg_set = 0; } + char *key = NULL; + char *arg = NULL; if (!doclear) { - while (!ends_excmd(*linep)) { + while (!ends_excmd((uint8_t)(*linep))) { key_start = linep; if (*linep == '=') { - EMSG2(_("E415: unexpected equal sign: %s"), key_start); - error = TRUE; + emsgf(_("E415: unexpected equal sign: %s"), key_start); + error = true; break; } @@ -6326,61 +6574,58 @@ do_highlight(char_u *line, int forceit, int init) { linep++; } xfree(key); - key = vim_strnsave_up(key_start, (int)(linep - key_start)); - linep = skipwhite(linep); + key = (char *)vim_strnsave_up((const char_u *)key_start, + (int)(linep - key_start)); + linep = (const char *)skipwhite((const char_u *)linep); - if (STRCMP(key, "NONE") == 0) { + if (strcmp(key, "NONE") == 0) { if (!init || HL_TABLE()[idx].sg_set == 0) { - if (!init) + if (!init) { HL_TABLE()[idx].sg_set |= SG_CTERM+SG_GUI; + } highlight_clear(idx); } continue; } - /* - * Check for the equal sign. - */ + // Check for the equal sign. if (*linep != '=') { - EMSG2(_("E416: missing equal sign: %s"), key_start); - error = TRUE; + emsgf(_("E416: missing equal sign: %s"), key_start); + error = true; break; } - ++linep; + linep++; - /* - * Isolate the argument. - */ - linep = skipwhite(linep); - if (*linep == '\'') { /* guifg='color name' */ + // Isolate the argument. + linep = (const char *)skipwhite((const char_u *)linep); + if (*linep == '\'') { // guifg='color name' arg_start = ++linep; - linep = vim_strchr(linep, '\''); + linep = strchr(linep, '\''); if (linep == NULL) { - EMSG2(_(e_invarg2), key_start); - error = TRUE; + emsgf(_(e_invarg2), key_start); + error = true; break; } } else { arg_start = linep; - linep = skiptowhite(linep); + linep = (const char *)skiptowhite((const char_u *)linep); } if (linep == arg_start) { - EMSG2(_("E417: missing argument: %s"), key_start); - error = TRUE; + emsgf(_("E417: missing argument: %s"), key_start); + error = true; break; } xfree(arg); - arg = vim_strnsave(arg_start, (int)(linep - arg_start)); + arg = xstrndup(arg_start, (size_t)(linep - arg_start)); - if (*linep == '\'') - ++linep; + if (*linep == '\'') { + linep++; + } - /* - * Store the argument. - */ - if ( STRCMP(key, "TERM") == 0 - || STRCMP(key, "CTERM") == 0 - || STRCMP(key, "GUI") == 0) { + // Store the argument. + if (strcmp(key, "TERM") == 0 + || strcmp(key, "CTERM") == 0 + || strcmp(key, "GUI") == 0) { attr = 0; off = 0; while (arg[off] != NUL) { @@ -6393,26 +6638,30 @@ do_highlight(char_u *line, int forceit, int init) { } } if (i < 0) { - EMSG2(_("E418: Illegal value: %s"), arg); - error = TRUE; + emsgf(_("E418: Illegal value: %s"), arg); + error = true; break; } - if (arg[off] == ',') /* another one follows */ - ++off; + if (arg[off] == ',') { // Another one follows. + off++; + } } - if (error) + if (error) { break; + } if (*key == 'C') { if (!init || !(HL_TABLE()[idx].sg_set & SG_CTERM)) { - if (!init) + if (!init) { HL_TABLE()[idx].sg_set |= SG_CTERM; + } HL_TABLE()[idx].sg_cterm = attr; HL_TABLE()[idx].sg_cterm_bold = FALSE; } } else if (*key == 'G') { if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) { - if (!init) + if (!init) { HL_TABLE()[idx].sg_set |= SG_GUI; + } HL_TABLE()[idx].sg_gui = attr; } } @@ -6431,14 +6680,14 @@ do_highlight(char_u *line, int forceit, int init) { HL_TABLE()[idx].sg_cterm_bold = FALSE; } - if (ascii_isdigit(*arg)) + if (ascii_isdigit(*arg)) { color = atoi((char *)arg); - else if (STRICMP(arg, "fg") == 0) { - if (cterm_normal_fg_color) + } else if (STRICMP(arg, "fg") == 0) { + if (cterm_normal_fg_color) { color = cterm_normal_fg_color - 1; - else { + } else { EMSG(_("E419: FG color unknown")); - error = TRUE; + error = true; break; } } else if (STRICMP(arg, "bg") == 0) { @@ -6446,70 +6695,84 @@ do_highlight(char_u *line, int forceit, int init) { color = cterm_normal_bg_color - 1; else { EMSG(_("E420: BG color unknown")); - error = TRUE; + error = true; break; } } else { - static char *(color_names[28]) = { + static const char *color_names[] = { "Black", "DarkBlue", "DarkGreen", "DarkCyan", "DarkRed", "DarkMagenta", "Brown", "DarkYellow", "Gray", "Grey", "LightGray", "LightGrey", "DarkGray", "DarkGrey", "Blue", "LightBlue", "Green", "LightGreen", "Cyan", "LightCyan", "Red", "LightRed", "Magenta", - "LightMagenta", "Yellow", "LightYellow", "White", "NONE" + "LightMagenta", "Yellow", "LightYellow", "White", + "NONE" + }; + static const int color_numbers_16[] = { + 0, 1, 2, 3, + 4, 5, 6, 6, + 7, 7, + 7, 7, 8, 8, + 9, 9, 10, 10, + 11, 11, 12, 12, 13, + 13, 14, 14, 15, + -1 + }; + // For xterm with 88 colors: + static int color_numbers_88[] = { + 0, 4, 2, 6, + 1, 5, 32, 72, + 84, 84, + 7, 7, 82, 82, + 12, 43, 10, 61, + 14, 63, 9, 74, 13, + 75, 11, 78, 15, + -1 + }; + // For xterm with 256 colors: + static int color_numbers_256[] = { + 0, 4, 2, 6, + 1, 5, 130, 130, + 248, 248, + 7, 7, 242, 242, + 12, 81, 10, 121, + 14, 159, 9, 224, 13, + 225, 11, 229, 15, + -1 }; - static int color_numbers_16[28] = {0, 1, 2, 3, - 4, 5, 6, 6, - 7, 7, - 7, 7, 8, 8, - 9, 9, 10, 10, - 11, 11, 12, 12, 13, - 13, 14, 14, 15, -1}; - /* for xterm with 88 colors... */ - static int color_numbers_88[28] = {0, 4, 2, 6, - 1, 5, 32, 72, - 84, 84, - 7, 7, 82, 82, - 12, 43, 10, 61, - 14, 63, 9, 74, 13, - 75, 11, 78, 15, -1}; - /* for xterm with 256 colors... */ - static int color_numbers_256[28] = {0, 4, 2, 6, - 1, 5, 130, 130, - 248, 248, - 7, 7, 242, 242, - 12, 81, 10, 121, - 14, 159, 9, 224, 13, - 225, 11, 229, 15, -1}; - /* for terminals with less than 16 colors... */ - static int color_numbers_8[28] = {0, 4, 2, 6, - 1, 5, 3, 3, - 7, 7, - 7, 7, 0+8, 0+8, - 4+8, 4+8, 2+8, 2+8, - 6+8, 6+8, 1+8, 1+8, 5+8, - 5+8, 3+8, 3+8, 7+8, -1}; - - /* reduce calls to STRICMP a bit, it can be slow */ + // For terminals with less than 16 colors: + static int color_numbers_8[28] = { + 0, 4, 2, 6, + 1, 5, 3, 3, + 7, 7, + 7, 7, 0+8, 0+8, + 4+8, 4+8, 2+8, 2+8, + 6+8, 6+8, 1+8, 1+8, 5+8, + 5+8, 3+8, 3+8, 7+8, + -1 + }; + + // Reduce calls to STRICMP a bit, it can be slow. off = TOUPPER_ASC(*arg); - for (i = ARRAY_SIZE(color_names); --i >= 0; ) + for (i = ARRAY_SIZE(color_names); --i >= 0; ) { if (off == color_names[i][0] - && STRICMP(arg + 1, color_names[i] + 1) == 0) + && STRICMP(arg + 1, color_names[i] + 1) == 0) { break; + } + } if (i < 0) { - EMSG2(_( - "E421: Color name or number not recognized: %s"), - key_start); - error = TRUE; + emsgf(_("E421: Color name or number not recognized: %s"), + key_start); + error = true; break; } - /* Use the _16 table to check if its a valid color name. */ + // Use the _16 table to check if its a valid color name. color = color_numbers_16[i]; if (color >= 0) { if (t_colors == 8) { - /* t_Co is 8: use the 8 colors table */ + // t_Co is 8: use the 8 colors table. color = color_numbers_8[i]; if (key[5] == 'F') { /* set/reset bold attribute to get light foreground @@ -6533,16 +6796,13 @@ do_highlight(char_u *line, int forceit, int init) { } } } - /* Add one to the argument, to avoid zero. Zero is used for - * "NONE", then "color" is -1. */ + // Add one to the argument, to avoid zero. Zero is used for + // "NONE", then "color" is -1. if (key[5] == 'F') { HL_TABLE()[idx].sg_cterm_fg = color + 1; if (is_normal_group) { cterm_normal_fg_color = color + 1; - cterm_normal_fg_bold = (HL_TABLE()[idx].sg_cterm & HL_BOLD); - { - must_redraw = CLEAR; - } + must_redraw = CLEAR; } } else { HL_TABLE()[idx].sg_cterm_bg = color + 1; @@ -6566,15 +6826,15 @@ do_highlight(char_u *line, int forceit, int init) { } } } - } else if (STRCMP(key, "GUIFG") == 0) { + } else if (strcmp(key, "GUIFG") == 0) { 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 = (uint8_t *)xstrdup((char *)arg); - HL_TABLE()[idx].sg_rgb_fg = name_to_color(arg); + 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; @@ -6591,8 +6851,8 @@ do_highlight(char_u *line, int forceit, int init) { xfree(HL_TABLE()[idx].sg_rgb_bg_name); if (STRCMP(arg, "NONE") != 0) { - HL_TABLE()[idx].sg_rgb_bg_name = (uint8_t *)xstrdup((char *)arg); - HL_TABLE()[idx].sg_rgb_bg = name_to_color(arg); + 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; @@ -6602,15 +6862,15 @@ do_highlight(char_u *line, int forceit, int init) { if (is_normal_group) { normal_bg = HL_TABLE()[idx].sg_rgb_bg; } - } else if (STRCMP(key, "GUISP") == 0) { + } else if (strcmp(key, "GUISP") == 0) { 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 = (uint8_t *)xstrdup((char *)arg); - HL_TABLE()[idx].sg_rgb_sp = name_to_color(arg); + 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; @@ -6620,31 +6880,26 @@ do_highlight(char_u *line, int forceit, int init) { if (is_normal_group) { normal_sp = HL_TABLE()[idx].sg_rgb_sp; } - } else if (STRCMP(key, "START") == 0 || STRCMP(key, "STOP") == 0) { + } else if (strcmp(key, "START") == 0 || strcmp(key, "STOP") == 0) { // Ignored for now } else { - EMSG2(_("E423: Illegal argument: %s"), key_start); - error = TRUE; + emsgf(_("E423: Illegal argument: %s"), key_start); + error = true; break; } + HL_TABLE()[idx].sg_cleared = false; - /* - * When highlighting has been given for a group, don't link it. - */ + // When highlighting has been given for a group, don't link it. if (!init || !(HL_TABLE()[idx].sg_set & SG_LINK)) { HL_TABLE()[idx].sg_link = 0; } - /* - * Continue with next argument. - */ - linep = skipwhite(linep); + // Continue with next argument. + linep = (const char *)skipwhite((const char_u *)linep); } } - /* - * If there is an error, and it's a new entry, remove it from the table. - */ + // If there is an error, and it's a new entry, remove it from the table. if (error && idx == highlight_ga.ga_len) { syn_unadd_group(); } else { @@ -6690,7 +6945,6 @@ void restore_cterm_colors(void) normal_bg = -1; normal_sp = -1; cterm_normal_fg_color = 0; - cterm_normal_fg_bold = 0; cterm_normal_bg_color = 0; } @@ -6714,6 +6968,8 @@ static int hl_has_settings(int idx, int check_link) */ static void highlight_clear(int idx) { + HL_TABLE()[idx].sg_cleared = true; + HL_TABLE()[idx].sg_attr = 0; HL_TABLE()[idx].sg_cterm = 0; HL_TABLE()[idx].sg_cterm_bold = FALSE; @@ -6742,9 +6998,9 @@ static void highlight_clear(int idx) /// GUI can redraw at any time for any buffer. static garray_T attr_table = GA_EMPTY_INIT_VALUE; -static inline attrentry_T * ATTR_ENTRY(int idx) +static inline HlAttrs * ATTR_ENTRY(int idx) { - return &((attrentry_T *)attr_table.ga_data)[idx]; + return &((HlAttrs *)attr_table.ga_data)[idx]; } @@ -6752,23 +7008,21 @@ static inline attrentry_T * ATTR_ENTRY(int idx) /// Add a new entry to the term_attr_table, attr_table or gui_attr_table /// if the combination is new. /// @return 0 for error. -int get_attr_entry(attrentry_T *aep) +int get_attr_entry(HlAttrs *aep) { garray_T *table = &attr_table; - attrentry_T *taep; - static int recursive = FALSE; + HlAttrs *taep; + static int recursive = false; /* * Init the table, in case it wasn't done yet. */ - table->ga_itemsize = sizeof(attrentry_T); + table->ga_itemsize = sizeof(HlAttrs); ga_set_growsize(table, 7); - /* - * Try to find an entry with the same specifications. - */ - for (int i = 0; i < table->ga_len; ++i) { - taep = &(((attrentry_T *)table->ga_data)[i]); + // Try to find an entry with the same specifications. + for (int i = 0; i < table->ga_len; i++) { + taep = &(((HlAttrs *)table->ga_data)[i]); if (aep->cterm_ae_attr == taep->cterm_ae_attr && aep->cterm_fg_color == taep->cterm_fg_color && aep->cterm_bg_color == taep->cterm_bg_color @@ -6805,7 +7059,7 @@ int get_attr_entry(attrentry_T *aep) // This is a new combination of colors and font, add an entry. - taep = GA_APPEND_VIA_PTR(attrentry_T, table); + taep = GA_APPEND_VIA_PTR(HlAttrs, table); memset(taep, 0, sizeof(*taep)); taep->cterm_ae_attr = aep->cterm_ae_attr; taep->cterm_fg_color = aep->cterm_fg_color; @@ -6833,9 +7087,9 @@ void clear_hl_tables(void) // Return the resulting attributes. int hl_combine_attr(int char_attr, int prim_attr) { - attrentry_T *char_aep = NULL; - attrentry_T *spell_aep; - attrentry_T new_en = ATTRENTRY_INIT; + HlAttrs *char_aep = NULL; + HlAttrs *spell_aep; + HlAttrs new_en = HLATTRS_INIT; if (char_attr == 0) { return prim_attr; @@ -6884,7 +7138,7 @@ int hl_combine_attr(int char_attr, int prim_attr) /// \note this function does not apply exclusively to cterm attr contrary /// to what its name implies /// \warn don't call it with attr 0 (i.e., the null attribute) -attrentry_T *syn_cterm_attr2entry(int attr) +HlAttrs *syn_cterm_attr2entry(int attr) { attr -= ATTR_OFF; if (attr >= attr_table.ga_len) { @@ -7133,7 +7387,7 @@ syn_list_header(int did_header, int outlen, int id) /// @param idx corrected highlight index static void set_hl_attr(int idx) { - attrentry_T at_en = ATTRENTRY_INIT; + HlAttrs at_en = HLATTRS_INIT; struct hl_group *sgp = HL_TABLE() + idx; @@ -7202,7 +7456,7 @@ char_u *syn_id2name(int id) /* * Like syn_name2id(), but take a pointer + length argument. */ -int syn_namen2id(char_u *linep, int len) +int syn_namen2id(const char_u *linep, int len) { char_u *name = vim_strnsave(linep, len); int id = syn_name2id(name); @@ -7218,7 +7472,7 @@ int syn_namen2id(char_u *linep, int len) /// @param len length of \p pp /// /// @return 0 for failure else the id of the group -int syn_check_group(char_u *pp, int len) +int syn_check_group(const char_u *pp, int len) { char_u *name = vim_strnsave(pp, len); int id = syn_name2id(name); @@ -7505,14 +7759,28 @@ static void highlight_list_two(int cnt, int attr) } -/* - * Function given to ExpandGeneric() to obtain the list of group names. - * Also used for synIDattr() function. - */ -const char *get_highlight_name(expand_T *const xp, const int idx) +/// Function given to ExpandGeneric() to obtain the list of group names. +const char *get_highlight_name(expand_T *const xp, int idx) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + return get_highlight_name_ext(xp, idx, true); +} + + +/// Obtain a highlight group name. +/// When "skip_cleared" is TRUE don't return a cleared entry. +const char *get_highlight_name_ext(expand_T *xp, int idx, int skip_cleared) FUNC_ATTR_WARN_UNUSED_RESULT { - // TODO(justinmk): 'xp' is unused + if (idx < 0) { + return NULL; + } + + // Items are never removed from the table, skip the ones that were cleared. + if (skip_cleared && idx < highlight_ga.ga_len && HL_TABLE()[idx].sg_cleared) { + return ""; + } + if (idx == highlight_ga.ga_len && include_none != 0) { return "none"; } else if (idx == highlight_ga.ga_len + include_none @@ -7524,7 +7792,7 @@ const char *get_highlight_name(expand_T *const xp, const int idx) } else if (idx == highlight_ga.ga_len + include_none + include_default + 1 && include_link != 0) { return "clear"; - } else if (idx < 0 || idx >= highlight_ga.ga_len) { + } else if (idx >= highlight_ga.ga_len) { return NULL; } return (const char *)HL_TABLE()[idx].sg_name; @@ -7532,685 +7800,685 @@ const char *get_highlight_name(expand_T *const xp, const int idx) color_name_table_T color_name_table[] = { // Colors from rgb.txt - { "AliceBlue", RGB(0xf0, 0xf8, 0xff) }, - { "AntiqueWhite", RGB(0xfa, 0xeb, 0xd7) }, - { "AntiqueWhite1", RGB(0xff, 0xef, 0xdb) }, - { "AntiqueWhite2", RGB(0xee, 0xdf, 0xcc) }, - { "AntiqueWhite3", RGB(0xcd, 0xc0, 0xb0) }, - { "AntiqueWhite4", RGB(0x8b, 0x83, 0x78) }, - { "Aqua", RGB(0x00, 0xff, 0xff) }, - { "Aquamarine", RGB(0x7f, 0xff, 0xd4) }, - { "Aquamarine1", RGB(0x7f, 0xff, 0xd4) }, - { "Aquamarine2", RGB(0x76, 0xee, 0xc6) }, - { "Aquamarine3", RGB(0x66, 0xcd, 0xaa) }, - { "Aquamarine4", RGB(0x45, 0x8b, 0x74) }, - { "Azure", RGB(0xf0, 0xff, 0xff) }, - { "Azure1", RGB(0xf0, 0xff, 0xff) }, - { "Azure2", RGB(0xe0, 0xee, 0xee) }, - { "Azure3", RGB(0xc1, 0xcd, 0xcd) }, - { "Azure4", RGB(0x83, 0x8b, 0x8b) }, - { "Beige", RGB(0xf5, 0xf5, 0xdc) }, - { "Bisque", RGB(0xff, 0xe4, 0xc4) }, - { "Bisque1", RGB(0xff, 0xe4, 0xc4) }, - { "Bisque2", RGB(0xee, 0xd5, 0xb7) }, - { "Bisque3", RGB(0xcd, 0xb7, 0x9e) }, - { "Bisque4", RGB(0x8b, 0x7d, 0x6b) }, - { "Black", RGB(0x00, 0x00, 0x00) }, - { "BlanchedAlmond", RGB(0xff, 0xeb, 0xcd) }, - { "Blue", RGB(0x00, 0x00, 0xff) }, - { "Blue1", RGB(0x0, 0x0, 0xff) }, - { "Blue2", RGB(0x0, 0x0, 0xee) }, - { "Blue3", RGB(0x0, 0x0, 0xcd) }, - { "Blue4", RGB(0x0, 0x0, 0x8b) }, - { "BlueViolet", RGB(0x8a, 0x2b, 0xe2) }, - { "Brown", RGB(0xa5, 0x2a, 0x2a) }, - { "Brown1", RGB(0xff, 0x40, 0x40) }, - { "Brown2", RGB(0xee, 0x3b, 0x3b) }, - { "Brown3", RGB(0xcd, 0x33, 0x33) }, - { "Brown4", RGB(0x8b, 0x23, 0x23) }, - { "BurlyWood", RGB(0xde, 0xb8, 0x87) }, - { "Burlywood1", RGB(0xff, 0xd3, 0x9b) }, - { "Burlywood2", RGB(0xee, 0xc5, 0x91) }, - { "Burlywood3", RGB(0xcd, 0xaa, 0x7d) }, - { "Burlywood4", RGB(0x8b, 0x73, 0x55) }, - { "CadetBlue", RGB(0x5f, 0x9e, 0xa0) }, - { "CadetBlue1", RGB(0x98, 0xf5, 0xff) }, - { "CadetBlue2", RGB(0x8e, 0xe5, 0xee) }, - { "CadetBlue3", RGB(0x7a, 0xc5, 0xcd) }, - { "CadetBlue4", RGB(0x53, 0x86, 0x8b) }, - { "ChartReuse", RGB(0x7f, 0xff, 0x00) }, - { "Chartreuse1", RGB(0x7f, 0xff, 0x0) }, - { "Chartreuse2", RGB(0x76, 0xee, 0x0) }, - { "Chartreuse3", RGB(0x66, 0xcd, 0x0) }, - { "Chartreuse4", RGB(0x45, 0x8b, 0x0) }, - { "Chocolate", RGB(0xd2, 0x69, 0x1e) }, - { "Chocolate1", RGB(0xff, 0x7f, 0x24) }, - { "Chocolate2", RGB(0xee, 0x76, 0x21) }, - { "Chocolate3", RGB(0xcd, 0x66, 0x1d) }, - { "Chocolate4", RGB(0x8b, 0x45, 0x13) }, - { "Coral", RGB(0xff, 0x7f, 0x50) }, - { "Coral1", RGB(0xff, 0x72, 0x56) }, - { "Coral2", RGB(0xee, 0x6a, 0x50) }, - { "Coral3", RGB(0xcd, 0x5b, 0x45) }, - { "Coral4", RGB(0x8b, 0x3e, 0x2f) }, - { "CornFlowerBlue", RGB(0x64, 0x95, 0xed) }, - { "Cornsilk", RGB(0xff, 0xf8, 0xdc) }, - { "Cornsilk1", RGB(0xff, 0xf8, 0xdc) }, - { "Cornsilk2", RGB(0xee, 0xe8, 0xcd) }, - { "Cornsilk3", RGB(0xcd, 0xc8, 0xb1) }, - { "Cornsilk4", RGB(0x8b, 0x88, 0x78) }, - { "Crimson", RGB(0xdc, 0x14, 0x3c) }, - { "Cyan", RGB(0x00, 0xff, 0xff) }, - { "Cyan1", RGB(0x0, 0xff, 0xff) }, - { "Cyan2", RGB(0x0, 0xee, 0xee) }, - { "Cyan3", RGB(0x0, 0xcd, 0xcd) }, - { "Cyan4", RGB(0x0, 0x8b, 0x8b) }, - { "DarkBlue", RGB(0x00, 0x00, 0x8b) }, - { "DarkCyan", RGB(0x00, 0x8b, 0x8b) }, - { "DarkGoldenRod", RGB(0xb8, 0x86, 0x0b) }, - { "DarkGoldenrod1", RGB(0xff, 0xb9, 0xf) }, - { "DarkGoldenrod2", RGB(0xee, 0xad, 0xe) }, - { "DarkGoldenrod3", RGB(0xcd, 0x95, 0xc) }, - { "DarkGoldenrod4", RGB(0x8b, 0x65, 0x8) }, - { "DarkGray", RGB(0xa9, 0xa9, 0xa9) }, - { "DarkGreen", RGB(0x00, 0x64, 0x00) }, - { "DarkGrey", RGB(0xa9, 0xa9, 0xa9) }, - { "DarkKhaki", RGB(0xbd, 0xb7, 0x6b) }, - { "DarkMagenta", RGB(0x8b, 0x00, 0x8b) }, - { "DarkOliveGreen", RGB(0x55, 0x6b, 0x2f) }, - { "DarkOliveGreen1", RGB(0xca, 0xff, 0x70) }, - { "DarkOliveGreen2", RGB(0xbc, 0xee, 0x68) }, - { "DarkOliveGreen3", RGB(0xa2, 0xcd, 0x5a) }, - { "DarkOliveGreen4", RGB(0x6e, 0x8b, 0x3d) }, - { "DarkOrange", RGB(0xff, 0x8c, 0x00) }, - { "DarkOrange1", RGB(0xff, 0x7f, 0x0) }, - { "DarkOrange2", RGB(0xee, 0x76, 0x0) }, - { "DarkOrange3", RGB(0xcd, 0x66, 0x0) }, - { "DarkOrange4", RGB(0x8b, 0x45, 0x0) }, - { "DarkOrchid", RGB(0x99, 0x32, 0xcc) }, - { "DarkOrchid1", RGB(0xbf, 0x3e, 0xff) }, - { "DarkOrchid2", RGB(0xb2, 0x3a, 0xee) }, - { "DarkOrchid3", RGB(0x9a, 0x32, 0xcd) }, - { "DarkOrchid4", RGB(0x68, 0x22, 0x8b) }, - { "DarkRed", RGB(0x8b, 0x00, 0x00) }, - { "DarkSalmon", RGB(0xe9, 0x96, 0x7a) }, - { "DarkSeaGreen", RGB(0x8f, 0xbc, 0x8f) }, - { "DarkSeaGreen1", RGB(0xc1, 0xff, 0xc1) }, - { "DarkSeaGreen2", RGB(0xb4, 0xee, 0xb4) }, - { "DarkSeaGreen3", RGB(0x9b, 0xcd, 0x9b) }, - { "DarkSeaGreen4", RGB(0x69, 0x8b, 0x69) }, - { "DarkSlateBlue", RGB(0x48, 0x3d, 0x8b) }, - { "DarkSlateGray", RGB(0x2f, 0x4f, 0x4f) }, - { "DarkSlateGray1", RGB(0x97, 0xff, 0xff) }, - { "DarkSlateGray2", RGB(0x8d, 0xee, 0xee) }, - { "DarkSlateGray3", RGB(0x79, 0xcd, 0xcd) }, - { "DarkSlateGray4", RGB(0x52, 0x8b, 0x8b) }, - { "DarkSlateGrey", RGB(0x2f, 0x4f, 0x4f) }, - { "DarkTurquoise", RGB(0x00, 0xce, 0xd1) }, - { "DarkViolet", RGB(0x94, 0x00, 0xd3) }, - { "DarkYellow", RGB(0xbb, 0xbb, 0x00) }, - { "DeepPink", RGB(0xff, 0x14, 0x93) }, - { "DeepPink1", RGB(0xff, 0x14, 0x93) }, - { "DeepPink2", RGB(0xee, 0x12, 0x89) }, - { "DeepPink3", RGB(0xcd, 0x10, 0x76) }, - { "DeepPink4", RGB(0x8b, 0xa, 0x50) }, - { "DeepSkyBlue", RGB(0x00, 0xbf, 0xff) }, - { "DeepSkyBlue1", RGB(0x0, 0xbf, 0xff) }, - { "DeepSkyBlue2", RGB(0x0, 0xb2, 0xee) }, - { "DeepSkyBlue3", RGB(0x0, 0x9a, 0xcd) }, - { "DeepSkyBlue4", RGB(0x0, 0x68, 0x8b) }, - { "DimGray", RGB(0x69, 0x69, 0x69) }, - { "DimGrey", RGB(0x69, 0x69, 0x69) }, - { "DodgerBlue", RGB(0x1e, 0x90, 0xff) }, - { "DodgerBlue1", RGB(0x1e, 0x90, 0xff) }, - { "DodgerBlue2", RGB(0x1c, 0x86, 0xee) }, - { "DodgerBlue3", RGB(0x18, 0x74, 0xcd) }, - { "DodgerBlue4", RGB(0x10, 0x4e, 0x8b) }, - { "Firebrick", RGB(0xb2, 0x22, 0x22) }, - { "Firebrick1", RGB(0xff, 0x30, 0x30) }, - { "Firebrick2", RGB(0xee, 0x2c, 0x2c) }, - { "Firebrick3", RGB(0xcd, 0x26, 0x26) }, - { "Firebrick4", RGB(0x8b, 0x1a, 0x1a) }, - { "FloralWhite", RGB(0xff, 0xfa, 0xf0) }, - { "ForestGreen", RGB(0x22, 0x8b, 0x22) }, - { "Fuchsia", RGB(0xff, 0x00, 0xff) }, - { "Gainsboro", RGB(0xdc, 0xdc, 0xdc) }, - { "GhostWhite", RGB(0xf8, 0xf8, 0xff) }, - { "Gold", RGB(0xff, 0xd7, 0x00) }, - { "Gold1", RGB(0xff, 0xd7, 0x0) }, - { "Gold2", RGB(0xee, 0xc9, 0x0) }, - { "Gold3", RGB(0xcd, 0xad, 0x0) }, - { "Gold4", RGB(0x8b, 0x75, 0x0) }, - { "GoldenRod", RGB(0xda, 0xa5, 0x20) }, - { "Goldenrod1", RGB(0xff, 0xc1, 0x25) }, - { "Goldenrod2", RGB(0xee, 0xb4, 0x22) }, - { "Goldenrod3", RGB(0xcd, 0x9b, 0x1d) }, - { "Goldenrod4", RGB(0x8b, 0x69, 0x14) }, - { "Gray", RGB(0x80, 0x80, 0x80) }, - { "Gray0", RGB(0x0, 0x0, 0x0) }, - { "Gray1", RGB(0x3, 0x3, 0x3) }, - { "Gray10", RGB(0x1a, 0x1a, 0x1a) }, - { "Gray100", RGB(0xff, 0xff, 0xff) }, - { "Gray11", RGB(0x1c, 0x1c, 0x1c) }, - { "Gray12", RGB(0x1f, 0x1f, 0x1f) }, - { "Gray13", RGB(0x21, 0x21, 0x21) }, - { "Gray14", RGB(0x24, 0x24, 0x24) }, - { "Gray15", RGB(0x26, 0x26, 0x26) }, - { "Gray16", RGB(0x29, 0x29, 0x29) }, - { "Gray17", RGB(0x2b, 0x2b, 0x2b) }, - { "Gray18", RGB(0x2e, 0x2e, 0x2e) }, - { "Gray19", RGB(0x30, 0x30, 0x30) }, - { "Gray2", RGB(0x5, 0x5, 0x5) }, - { "Gray20", RGB(0x33, 0x33, 0x33) }, - { "Gray21", RGB(0x36, 0x36, 0x36) }, - { "Gray22", RGB(0x38, 0x38, 0x38) }, - { "Gray23", RGB(0x3b, 0x3b, 0x3b) }, - { "Gray24", RGB(0x3d, 0x3d, 0x3d) }, - { "Gray25", RGB(0x40, 0x40, 0x40) }, - { "Gray26", RGB(0x42, 0x42, 0x42) }, - { "Gray27", RGB(0x45, 0x45, 0x45) }, - { "Gray28", RGB(0x47, 0x47, 0x47) }, - { "Gray29", RGB(0x4a, 0x4a, 0x4a) }, - { "Gray3", RGB(0x8, 0x8, 0x8) }, - { "Gray30", RGB(0x4d, 0x4d, 0x4d) }, - { "Gray31", RGB(0x4f, 0x4f, 0x4f) }, - { "Gray32", RGB(0x52, 0x52, 0x52) }, - { "Gray33", RGB(0x54, 0x54, 0x54) }, - { "Gray34", RGB(0x57, 0x57, 0x57) }, - { "Gray35", RGB(0x59, 0x59, 0x59) }, - { "Gray36", RGB(0x5c, 0x5c, 0x5c) }, - { "Gray37", RGB(0x5e, 0x5e, 0x5e) }, - { "Gray38", RGB(0x61, 0x61, 0x61) }, - { "Gray39", RGB(0x63, 0x63, 0x63) }, - { "Gray4", RGB(0xa, 0xa, 0xa) }, - { "Gray40", RGB(0x66, 0x66, 0x66) }, - { "Gray41", RGB(0x69, 0x69, 0x69) }, - { "Gray42", RGB(0x6b, 0x6b, 0x6b) }, - { "Gray43", RGB(0x6e, 0x6e, 0x6e) }, - { "Gray44", RGB(0x70, 0x70, 0x70) }, - { "Gray45", RGB(0x73, 0x73, 0x73) }, - { "Gray46", RGB(0x75, 0x75, 0x75) }, - { "Gray47", RGB(0x78, 0x78, 0x78) }, - { "Gray48", RGB(0x7a, 0x7a, 0x7a) }, - { "Gray49", RGB(0x7d, 0x7d, 0x7d) }, - { "Gray5", RGB(0xd, 0xd, 0xd) }, - { "Gray50", RGB(0x7f, 0x7f, 0x7f) }, - { "Gray51", RGB(0x82, 0x82, 0x82) }, - { "Gray52", RGB(0x85, 0x85, 0x85) }, - { "Gray53", RGB(0x87, 0x87, 0x87) }, - { "Gray54", RGB(0x8a, 0x8a, 0x8a) }, - { "Gray55", RGB(0x8c, 0x8c, 0x8c) }, - { "Gray56", RGB(0x8f, 0x8f, 0x8f) }, - { "Gray57", RGB(0x91, 0x91, 0x91) }, - { "Gray58", RGB(0x94, 0x94, 0x94) }, - { "Gray59", RGB(0x96, 0x96, 0x96) }, - { "Gray6", RGB(0xf, 0xf, 0xf) }, - { "Gray60", RGB(0x99, 0x99, 0x99) }, - { "Gray61", RGB(0x9c, 0x9c, 0x9c) }, - { "Gray62", RGB(0x9e, 0x9e, 0x9e) }, - { "Gray63", RGB(0xa1, 0xa1, 0xa1) }, - { "Gray64", RGB(0xa3, 0xa3, 0xa3) }, - { "Gray65", RGB(0xa6, 0xa6, 0xa6) }, - { "Gray66", RGB(0xa8, 0xa8, 0xa8) }, - { "Gray67", RGB(0xab, 0xab, 0xab) }, - { "Gray68", RGB(0xad, 0xad, 0xad) }, - { "Gray69", RGB(0xb0, 0xb0, 0xb0) }, - { "Gray7", RGB(0x12, 0x12, 0x12) }, - { "Gray70", RGB(0xb3, 0xb3, 0xb3) }, - { "Gray71", RGB(0xb5, 0xb5, 0xb5) }, - { "Gray72", RGB(0xb8, 0xb8, 0xb8) }, - { "Gray73", RGB(0xba, 0xba, 0xba) }, - { "Gray74", RGB(0xbd, 0xbd, 0xbd) }, - { "Gray75", RGB(0xbf, 0xbf, 0xbf) }, - { "Gray76", RGB(0xc2, 0xc2, 0xc2) }, - { "Gray77", RGB(0xc4, 0xc4, 0xc4) }, - { "Gray78", RGB(0xc7, 0xc7, 0xc7) }, - { "Gray79", RGB(0xc9, 0xc9, 0xc9) }, - { "Gray8", RGB(0x14, 0x14, 0x14) }, - { "Gray80", RGB(0xcc, 0xcc, 0xcc) }, - { "Gray81", RGB(0xcf, 0xcf, 0xcf) }, - { "Gray82", RGB(0xd1, 0xd1, 0xd1) }, - { "Gray83", RGB(0xd4, 0xd4, 0xd4) }, - { "Gray84", RGB(0xd6, 0xd6, 0xd6) }, - { "Gray85", RGB(0xd9, 0xd9, 0xd9) }, - { "Gray86", RGB(0xdb, 0xdb, 0xdb) }, - { "Gray87", RGB(0xde, 0xde, 0xde) }, - { "Gray88", RGB(0xe0, 0xe0, 0xe0) }, - { "Gray89", RGB(0xe3, 0xe3, 0xe3) }, - { "Gray9", RGB(0x17, 0x17, 0x17) }, - { "Gray90", RGB(0xe5, 0xe5, 0xe5) }, - { "Gray91", RGB(0xe8, 0xe8, 0xe8) }, - { "Gray92", RGB(0xeb, 0xeb, 0xeb) }, - { "Gray93", RGB(0xed, 0xed, 0xed) }, - { "Gray94", RGB(0xf0, 0xf0, 0xf0) }, - { "Gray95", RGB(0xf2, 0xf2, 0xf2) }, - { "Gray96", RGB(0xf5, 0xf5, 0xf5) }, - { "Gray97", RGB(0xf7, 0xf7, 0xf7) }, - { "Gray98", RGB(0xfa, 0xfa, 0xfa) }, - { "Gray99", RGB(0xfc, 0xfc, 0xfc) }, - { "Green", RGB(0x00, 0x80, 0x00) }, - { "Green1", RGB(0x0, 0xff, 0x0) }, - { "Green2", RGB(0x0, 0xee, 0x0) }, - { "Green3", RGB(0x0, 0xcd, 0x0) }, - { "Green4", RGB(0x0, 0x8b, 0x0) }, - { "GreenYellow", RGB(0xad, 0xff, 0x2f) }, - { "Grey", RGB(0x80, 0x80, 0x80) }, - { "Grey0", RGB(0x0, 0x0, 0x0) }, - { "Grey1", RGB(0x3, 0x3, 0x3) }, - { "Grey10", RGB(0x1a, 0x1a, 0x1a) }, - { "Grey100", RGB(0xff, 0xff, 0xff) }, - { "Grey11", RGB(0x1c, 0x1c, 0x1c) }, - { "Grey12", RGB(0x1f, 0x1f, 0x1f) }, - { "Grey13", RGB(0x21, 0x21, 0x21) }, - { "Grey14", RGB(0x24, 0x24, 0x24) }, - { "Grey15", RGB(0x26, 0x26, 0x26) }, - { "Grey16", RGB(0x29, 0x29, 0x29) }, - { "Grey17", RGB(0x2b, 0x2b, 0x2b) }, - { "Grey18", RGB(0x2e, 0x2e, 0x2e) }, - { "Grey19", RGB(0x30, 0x30, 0x30) }, - { "Grey2", RGB(0x5, 0x5, 0x5) }, - { "Grey20", RGB(0x33, 0x33, 0x33) }, - { "Grey21", RGB(0x36, 0x36, 0x36) }, - { "Grey22", RGB(0x38, 0x38, 0x38) }, - { "Grey23", RGB(0x3b, 0x3b, 0x3b) }, - { "Grey24", RGB(0x3d, 0x3d, 0x3d) }, - { "Grey25", RGB(0x40, 0x40, 0x40) }, - { "Grey26", RGB(0x42, 0x42, 0x42) }, - { "Grey27", RGB(0x45, 0x45, 0x45) }, - { "Grey28", RGB(0x47, 0x47, 0x47) }, - { "Grey29", RGB(0x4a, 0x4a, 0x4a) }, - { "Grey3", RGB(0x8, 0x8, 0x8) }, - { "Grey30", RGB(0x4d, 0x4d, 0x4d) }, - { "Grey31", RGB(0x4f, 0x4f, 0x4f) }, - { "Grey32", RGB(0x52, 0x52, 0x52) }, - { "Grey33", RGB(0x54, 0x54, 0x54) }, - { "Grey34", RGB(0x57, 0x57, 0x57) }, - { "Grey35", RGB(0x59, 0x59, 0x59) }, - { "Grey36", RGB(0x5c, 0x5c, 0x5c) }, - { "Grey37", RGB(0x5e, 0x5e, 0x5e) }, - { "Grey38", RGB(0x61, 0x61, 0x61) }, - { "Grey39", RGB(0x63, 0x63, 0x63) }, - { "Grey4", RGB(0xa, 0xa, 0xa) }, - { "Grey40", RGB(0x66, 0x66, 0x66) }, - { "Grey41", RGB(0x69, 0x69, 0x69) }, - { "Grey42", RGB(0x6b, 0x6b, 0x6b) }, - { "Grey43", RGB(0x6e, 0x6e, 0x6e) }, - { "Grey44", RGB(0x70, 0x70, 0x70) }, - { "Grey45", RGB(0x73, 0x73, 0x73) }, - { "Grey46", RGB(0x75, 0x75, 0x75) }, - { "Grey47", RGB(0x78, 0x78, 0x78) }, - { "Grey48", RGB(0x7a, 0x7a, 0x7a) }, - { "Grey49", RGB(0x7d, 0x7d, 0x7d) }, - { "Grey5", RGB(0xd, 0xd, 0xd) }, - { "Grey50", RGB(0x7f, 0x7f, 0x7f) }, - { "Grey51", RGB(0x82, 0x82, 0x82) }, - { "Grey52", RGB(0x85, 0x85, 0x85) }, - { "Grey53", RGB(0x87, 0x87, 0x87) }, - { "Grey54", RGB(0x8a, 0x8a, 0x8a) }, - { "Grey55", RGB(0x8c, 0x8c, 0x8c) }, - { "Grey56", RGB(0x8f, 0x8f, 0x8f) }, - { "Grey57", RGB(0x91, 0x91, 0x91) }, - { "Grey58", RGB(0x94, 0x94, 0x94) }, - { "Grey59", RGB(0x96, 0x96, 0x96) }, - { "Grey6", RGB(0xf, 0xf, 0xf) }, - { "Grey60", RGB(0x99, 0x99, 0x99) }, - { "Grey61", RGB(0x9c, 0x9c, 0x9c) }, - { "Grey62", RGB(0x9e, 0x9e, 0x9e) }, - { "Grey63", RGB(0xa1, 0xa1, 0xa1) }, - { "Grey64", RGB(0xa3, 0xa3, 0xa3) }, - { "Grey65", RGB(0xa6, 0xa6, 0xa6) }, - { "Grey66", RGB(0xa8, 0xa8, 0xa8) }, - { "Grey67", RGB(0xab, 0xab, 0xab) }, - { "Grey68", RGB(0xad, 0xad, 0xad) }, - { "Grey69", RGB(0xb0, 0xb0, 0xb0) }, - { "Grey7", RGB(0x12, 0x12, 0x12) }, - { "Grey70", RGB(0xb3, 0xb3, 0xb3) }, - { "Grey71", RGB(0xb5, 0xb5, 0xb5) }, - { "Grey72", RGB(0xb8, 0xb8, 0xb8) }, - { "Grey73", RGB(0xba, 0xba, 0xba) }, - { "Grey74", RGB(0xbd, 0xbd, 0xbd) }, - { "Grey75", RGB(0xbf, 0xbf, 0xbf) }, - { "Grey76", RGB(0xc2, 0xc2, 0xc2) }, - { "Grey77", RGB(0xc4, 0xc4, 0xc4) }, - { "Grey78", RGB(0xc7, 0xc7, 0xc7) }, - { "Grey79", RGB(0xc9, 0xc9, 0xc9) }, - { "Grey8", RGB(0x14, 0x14, 0x14) }, - { "Grey80", RGB(0xcc, 0xcc, 0xcc) }, - { "Grey81", RGB(0xcf, 0xcf, 0xcf) }, - { "Grey82", RGB(0xd1, 0xd1, 0xd1) }, - { "Grey83", RGB(0xd4, 0xd4, 0xd4) }, - { "Grey84", RGB(0xd6, 0xd6, 0xd6) }, - { "Grey85", RGB(0xd9, 0xd9, 0xd9) }, - { "Grey86", RGB(0xdb, 0xdb, 0xdb) }, - { "Grey87", RGB(0xde, 0xde, 0xde) }, - { "Grey88", RGB(0xe0, 0xe0, 0xe0) }, - { "Grey89", RGB(0xe3, 0xe3, 0xe3) }, - { "Grey9", RGB(0x17, 0x17, 0x17) }, - { "Grey90", RGB(0xe5, 0xe5, 0xe5) }, - { "Grey91", RGB(0xe8, 0xe8, 0xe8) }, - { "Grey92", RGB(0xeb, 0xeb, 0xeb) }, - { "Grey93", RGB(0xed, 0xed, 0xed) }, - { "Grey94", RGB(0xf0, 0xf0, 0xf0) }, - { "Grey95", RGB(0xf2, 0xf2, 0xf2) }, - { "Grey96", RGB(0xf5, 0xf5, 0xf5) }, - { "Grey97", RGB(0xf7, 0xf7, 0xf7) }, - { "Grey98", RGB(0xfa, 0xfa, 0xfa) }, - { "Grey99", RGB(0xfc, 0xfc, 0xfc) }, - { "Honeydew", RGB(0xf0, 0xff, 0xf0) }, - { "Honeydew1", RGB(0xf0, 0xff, 0xf0) }, - { "Honeydew2", RGB(0xe0, 0xee, 0xe0) }, - { "Honeydew3", RGB(0xc1, 0xcd, 0xc1) }, - { "Honeydew4", RGB(0x83, 0x8b, 0x83) }, - { "HotPink", RGB(0xff, 0x69, 0xb4) }, - { "HotPink1", RGB(0xff, 0x6e, 0xb4) }, - { "HotPink2", RGB(0xee, 0x6a, 0xa7) }, - { "HotPink3", RGB(0xcd, 0x60, 0x90) }, - { "HotPink4", RGB(0x8b, 0x3a, 0x62) }, - { "IndianRed", RGB(0xcd, 0x5c, 0x5c) }, - { "IndianRed1", RGB(0xff, 0x6a, 0x6a) }, - { "IndianRed2", RGB(0xee, 0x63, 0x63) }, - { "IndianRed3", RGB(0xcd, 0x55, 0x55) }, - { "IndianRed4", RGB(0x8b, 0x3a, 0x3a) }, - { "Indigo", RGB(0x4b, 0x00, 0x82) }, - { "Ivory", RGB(0xff, 0xff, 0xf0) }, - { "Ivory1", RGB(0xff, 0xff, 0xf0) }, - { "Ivory2", RGB(0xee, 0xee, 0xe0) }, - { "Ivory3", RGB(0xcd, 0xcd, 0xc1) }, - { "Ivory4", RGB(0x8b, 0x8b, 0x83) }, - { "Khaki", RGB(0xf0, 0xe6, 0x8c) }, - { "Khaki1", RGB(0xff, 0xf6, 0x8f) }, - { "Khaki2", RGB(0xee, 0xe6, 0x85) }, - { "Khaki3", RGB(0xcd, 0xc6, 0x73) }, - { "Khaki4", RGB(0x8b, 0x86, 0x4e) }, - { "Lavender", RGB(0xe6, 0xe6, 0xfa) }, - { "LavenderBlush", RGB(0xff, 0xf0, 0xf5) }, - { "LavenderBlush1", RGB(0xff, 0xf0, 0xf5) }, - { "LavenderBlush2", RGB(0xee, 0xe0, 0xe5) }, - { "LavenderBlush3", RGB(0xcd, 0xc1, 0xc5) }, - { "LavenderBlush4", RGB(0x8b, 0x83, 0x86) }, - { "LawnGreen", RGB(0x7c, 0xfc, 0x00) }, - { "LemonChiffon", RGB(0xff, 0xfa, 0xcd) }, - { "LemonChiffon1", RGB(0xff, 0xfa, 0xcd) }, - { "LemonChiffon2", RGB(0xee, 0xe9, 0xbf) }, - { "LemonChiffon3", RGB(0xcd, 0xc9, 0xa5) }, - { "LemonChiffon4", RGB(0x8b, 0x89, 0x70) }, - { "LightBlue", RGB(0xad, 0xd8, 0xe6) }, - { "LightBlue1", RGB(0xbf, 0xef, 0xff) }, - { "LightBlue2", RGB(0xb2, 0xdf, 0xee) }, - { "LightBlue3", RGB(0x9a, 0xc0, 0xcd) }, - { "LightBlue4", RGB(0x68, 0x83, 0x8b) }, - { "LightCoral", RGB(0xf0, 0x80, 0x80) }, - { "LightCyan", RGB(0xe0, 0xff, 0xff) }, - { "LightCyan1", RGB(0xe0, 0xff, 0xff) }, - { "LightCyan2", RGB(0xd1, 0xee, 0xee) }, - { "LightCyan3", RGB(0xb4, 0xcd, 0xcd) }, - { "LightCyan4", RGB(0x7a, 0x8b, 0x8b) }, - { "LightGoldenrod", RGB(0xee, 0xdd, 0x82) }, - { "LightGoldenrod1", RGB(0xff, 0xec, 0x8b) }, - { "LightGoldenrod2", RGB(0xee, 0xdc, 0x82) }, - { "LightGoldenrod3", RGB(0xcd, 0xbe, 0x70) }, - { "LightGoldenrod4", RGB(0x8b, 0x81, 0x4c) }, - { "LightGoldenRodYellow", RGB(0xfa, 0xfa, 0xd2) }, - { "LightGray", RGB(0xd3, 0xd3, 0xd3) }, - { "LightGreen", RGB(0x90, 0xee, 0x90) }, - { "LightGrey", RGB(0xd3, 0xd3, 0xd3) }, - { "LightMagenta", RGB(0xff, 0xbb, 0xff) }, - { "LightPink", RGB(0xff, 0xb6, 0xc1) }, - { "LightPink1", RGB(0xff, 0xae, 0xb9) }, - { "LightPink2", RGB(0xee, 0xa2, 0xad) }, - { "LightPink3", RGB(0xcd, 0x8c, 0x95) }, - { "LightPink4", RGB(0x8b, 0x5f, 0x65) }, - { "LightRed", RGB(0xff, 0xbb, 0xbb) }, - { "LightSalmon", RGB(0xff, 0xa0, 0x7a) }, - { "LightSalmon1", RGB(0xff, 0xa0, 0x7a) }, - { "LightSalmon2", RGB(0xee, 0x95, 0x72) }, - { "LightSalmon3", RGB(0xcd, 0x81, 0x62) }, - { "LightSalmon4", RGB(0x8b, 0x57, 0x42) }, - { "LightSeaGreen", RGB(0x20, 0xb2, 0xaa) }, - { "LightSkyBlue", RGB(0x87, 0xce, 0xfa) }, - { "LightSkyBlue1", RGB(0xb0, 0xe2, 0xff) }, - { "LightSkyBlue2", RGB(0xa4, 0xd3, 0xee) }, - { "LightSkyBlue3", RGB(0x8d, 0xb6, 0xcd) }, - { "LightSkyBlue4", RGB(0x60, 0x7b, 0x8b) }, - { "LightSlateBlue", RGB(0x84, 0x70, 0xff) }, - { "LightSlateGray", RGB(0x77, 0x88, 0x99) }, - { "LightSlateGrey", RGB(0x77, 0x88, 0x99) }, - { "LightSteelBlue", RGB(0xb0, 0xc4, 0xde) }, - { "LightSteelBlue1", RGB(0xca, 0xe1, 0xff) }, - { "LightSteelBlue2", RGB(0xbc, 0xd2, 0xee) }, - { "LightSteelBlue3", RGB(0xa2, 0xb5, 0xcd) }, - { "LightSteelBlue4", RGB(0x6e, 0x7b, 0x8b) }, - { "LightYellow", RGB(0xff, 0xff, 0xe0) }, - { "LightYellow1", RGB(0xff, 0xff, 0xe0) }, - { "LightYellow2", RGB(0xee, 0xee, 0xd1) }, - { "LightYellow3", RGB(0xcd, 0xcd, 0xb4) }, - { "LightYellow4", RGB(0x8b, 0x8b, 0x7a) }, - { "Lime", RGB(0x00, 0xff, 0x00) }, - { "LimeGreen", RGB(0x32, 0xcd, 0x32) }, - { "Linen", RGB(0xfa, 0xf0, 0xe6) }, - { "Magenta", RGB(0xff, 0x00, 0xff) }, - { "Magenta1", RGB(0xff, 0x0, 0xff) }, - { "Magenta2", RGB(0xee, 0x0, 0xee) }, - { "Magenta3", RGB(0xcd, 0x0, 0xcd) }, - { "Magenta4", RGB(0x8b, 0x0, 0x8b) }, - { "Maroon", RGB(0x80, 0x00, 0x00) }, - { "Maroon1", RGB(0xff, 0x34, 0xb3) }, - { "Maroon2", RGB(0xee, 0x30, 0xa7) }, - { "Maroon3", RGB(0xcd, 0x29, 0x90) }, - { "Maroon4", RGB(0x8b, 0x1c, 0x62) }, - { "MediumAquamarine", RGB(0x66, 0xcd, 0xaa) }, - { "MediumBlue", RGB(0x00, 0x00, 0xcd) }, - { "MediumOrchid", RGB(0xba, 0x55, 0xd3) }, - { "MediumOrchid1", RGB(0xe0, 0x66, 0xff) }, - { "MediumOrchid2", RGB(0xd1, 0x5f, 0xee) }, - { "MediumOrchid3", RGB(0xb4, 0x52, 0xcd) }, - { "MediumOrchid4", RGB(0x7a, 0x37, 0x8b) }, - { "MediumPurple", RGB(0x93, 0x70, 0xdb) }, - { "MediumPurple1", RGB(0xab, 0x82, 0xff) }, - { "MediumPurple2", RGB(0x9f, 0x79, 0xee) }, - { "MediumPurple3", RGB(0x89, 0x68, 0xcd) }, - { "MediumPurple4", RGB(0x5d, 0x47, 0x8b) }, - { "MediumSeaGreen", RGB(0x3c, 0xb3, 0x71) }, - { "MediumSlateBlue", RGB(0x7b, 0x68, 0xee) }, - { "MediumSpringGreen", RGB(0x00, 0xfa, 0x9a) }, - { "MediumTurquoise", RGB(0x48, 0xd1, 0xcc) }, - { "MediumVioletRed", RGB(0xc7, 0x15, 0x85) }, - { "MidnightBlue", RGB(0x19, 0x19, 0x70) }, - { "MintCream", RGB(0xf5, 0xff, 0xfa) }, - { "MistyRose", RGB(0xff, 0xe4, 0xe1) }, - { "MistyRose1", RGB(0xff, 0xe4, 0xe1) }, - { "MistyRose2", RGB(0xee, 0xd5, 0xd2) }, - { "MistyRose3", RGB(0xcd, 0xb7, 0xb5) }, - { "MistyRose4", RGB(0x8b, 0x7d, 0x7b) }, - { "Moccasin", RGB(0xff, 0xe4, 0xb5) }, - { "NavajoWhite", RGB(0xff, 0xde, 0xad) }, - { "NavajoWhite1", RGB(0xff, 0xde, 0xad) }, - { "NavajoWhite2", RGB(0xee, 0xcf, 0xa1) }, - { "NavajoWhite3", RGB(0xcd, 0xb3, 0x8b) }, - { "NavajoWhite4", RGB(0x8b, 0x79, 0x5e) }, - { "Navy", RGB(0x00, 0x00, 0x80) }, - { "NavyBlue", RGB(0x0, 0x0, 0x80) }, - { "OldLace", RGB(0xfd, 0xf5, 0xe6) }, - { "Olive", RGB(0x80, 0x80, 0x00) }, - { "OliveDrab", RGB(0x6b, 0x8e, 0x23) }, - { "OliveDrab1", RGB(0xc0, 0xff, 0x3e) }, - { "OliveDrab2", RGB(0xb3, 0xee, 0x3a) }, - { "OliveDrab3", RGB(0x9a, 0xcd, 0x32) }, - { "OliveDrab4", RGB(0x69, 0x8b, 0x22) }, - { "Orange", RGB(0xff, 0xa5, 0x00) }, - { "Orange1", RGB(0xff, 0xa5, 0x0) }, - { "Orange2", RGB(0xee, 0x9a, 0x0) }, - { "Orange3", RGB(0xcd, 0x85, 0x0) }, - { "Orange4", RGB(0x8b, 0x5a, 0x0) }, - { "OrangeRed", RGB(0xff, 0x45, 0x00) }, - { "OrangeRed1", RGB(0xff, 0x45, 0x0) }, - { "OrangeRed2", RGB(0xee, 0x40, 0x0) }, - { "OrangeRed3", RGB(0xcd, 0x37, 0x0) }, - { "OrangeRed4", RGB(0x8b, 0x25, 0x0) }, - { "Orchid", RGB(0xda, 0x70, 0xd6) }, - { "Orchid1", RGB(0xff, 0x83, 0xfa) }, - { "Orchid2", RGB(0xee, 0x7a, 0xe9) }, - { "Orchid3", RGB(0xcd, 0x69, 0xc9) }, - { "Orchid4", RGB(0x8b, 0x47, 0x89) }, - { "PaleGoldenRod", RGB(0xee, 0xe8, 0xaa) }, - { "PaleGreen", RGB(0x98, 0xfb, 0x98) }, - { "PaleGreen1", RGB(0x9a, 0xff, 0x9a) }, - { "PaleGreen2", RGB(0x90, 0xee, 0x90) }, - { "PaleGreen3", RGB(0x7c, 0xcd, 0x7c) }, - { "PaleGreen4", RGB(0x54, 0x8b, 0x54) }, - { "PaleTurquoise", RGB(0xaf, 0xee, 0xee) }, - { "PaleTurquoise1", RGB(0xbb, 0xff, 0xff) }, - { "PaleTurquoise2", RGB(0xae, 0xee, 0xee) }, - { "PaleTurquoise3", RGB(0x96, 0xcd, 0xcd) }, - { "PaleTurquoise4", RGB(0x66, 0x8b, 0x8b) }, - { "PaleVioletRed", RGB(0xdb, 0x70, 0x93) }, - { "PaleVioletRed1", RGB(0xff, 0x82, 0xab) }, - { "PaleVioletRed2", RGB(0xee, 0x79, 0x9f) }, - { "PaleVioletRed3", RGB(0xcd, 0x68, 0x89) }, - { "PaleVioletRed4", RGB(0x8b, 0x47, 0x5d) }, - { "PapayaWhip", RGB(0xff, 0xef, 0xd5) }, - { "PeachPuff", RGB(0xff, 0xda, 0xb9) }, - { "PeachPuff1", RGB(0xff, 0xda, 0xb9) }, - { "PeachPuff2", RGB(0xee, 0xcb, 0xad) }, - { "PeachPuff3", RGB(0xcd, 0xaf, 0x95) }, - { "PeachPuff4", RGB(0x8b, 0x77, 0x65) }, - { "Peru", RGB(0xcd, 0x85, 0x3f) }, - { "Pink", RGB(0xff, 0xc0, 0xcb) }, - { "Pink1", RGB(0xff, 0xb5, 0xc5) }, - { "Pink2", RGB(0xee, 0xa9, 0xb8) }, - { "Pink3", RGB(0xcd, 0x91, 0x9e) }, - { "Pink4", RGB(0x8b, 0x63, 0x6c) }, - { "Plum", RGB(0xdd, 0xa0, 0xdd) }, - { "Plum1", RGB(0xff, 0xbb, 0xff) }, - { "Plum2", RGB(0xee, 0xae, 0xee) }, - { "Plum3", RGB(0xcd, 0x96, 0xcd) }, - { "Plum4", RGB(0x8b, 0x66, 0x8b) }, - { "PowderBlue", RGB(0xb0, 0xe0, 0xe6) }, - { "Purple", RGB(0x80, 0x00, 0x80) }, - { "Purple1", RGB(0x9b, 0x30, 0xff) }, - { "Purple2", RGB(0x91, 0x2c, 0xee) }, - { "Purple3", RGB(0x7d, 0x26, 0xcd) }, - { "Purple4", RGB(0x55, 0x1a, 0x8b) }, - { "RebeccaPurple", RGB(0x66, 0x33, 0x99) }, - { "Red", RGB(0xff, 0x00, 0x00) }, - { "Red1", RGB(0xff, 0x0, 0x0) }, - { "Red2", RGB(0xee, 0x0, 0x0) }, - { "Red3", RGB(0xcd, 0x0, 0x0) }, - { "Red4", RGB(0x8b, 0x0, 0x0) }, - { "RosyBrown", RGB(0xbc, 0x8f, 0x8f) }, - { "RosyBrown1", RGB(0xff, 0xc1, 0xc1) }, - { "RosyBrown2", RGB(0xee, 0xb4, 0xb4) }, - { "RosyBrown3", RGB(0xcd, 0x9b, 0x9b) }, - { "RosyBrown4", RGB(0x8b, 0x69, 0x69) }, - { "RoyalBlue", RGB(0x41, 0x69, 0xe1) }, - { "RoyalBlue1", RGB(0x48, 0x76, 0xff) }, - { "RoyalBlue2", RGB(0x43, 0x6e, 0xee) }, - { "RoyalBlue3", RGB(0x3a, 0x5f, 0xcd) }, - { "RoyalBlue4", RGB(0x27, 0x40, 0x8b) }, - { "SaddleBrown", RGB(0x8b, 0x45, 0x13) }, - { "Salmon", RGB(0xfa, 0x80, 0x72) }, - { "Salmon1", RGB(0xff, 0x8c, 0x69) }, - { "Salmon2", RGB(0xee, 0x82, 0x62) }, - { "Salmon3", RGB(0xcd, 0x70, 0x54) }, - { "Salmon4", RGB(0x8b, 0x4c, 0x39) }, - { "SandyBrown", RGB(0xf4, 0xa4, 0x60) }, - { "SeaGreen", RGB(0x2e, 0x8b, 0x57) }, - { "SeaGreen1", RGB(0x54, 0xff, 0x9f) }, - { "SeaGreen2", RGB(0x4e, 0xee, 0x94) }, - { "SeaGreen3", RGB(0x43, 0xcd, 0x80) }, - { "SeaGreen4", RGB(0x2e, 0x8b, 0x57) }, - { "SeaShell", RGB(0xff, 0xf5, 0xee) }, - { "Seashell1", RGB(0xff, 0xf5, 0xee) }, - { "Seashell2", RGB(0xee, 0xe5, 0xde) }, - { "Seashell3", RGB(0xcd, 0xc5, 0xbf) }, - { "Seashell4", RGB(0x8b, 0x86, 0x82) }, - { "Sienna", RGB(0xa0, 0x52, 0x2d) }, - { "Sienna1", RGB(0xff, 0x82, 0x47) }, - { "Sienna2", RGB(0xee, 0x79, 0x42) }, - { "Sienna3", RGB(0xcd, 0x68, 0x39) }, - { "Sienna4", RGB(0x8b, 0x47, 0x26) }, - { "Silver", RGB(0xc0, 0xc0, 0xc0) }, - { "SkyBlue", RGB(0x87, 0xce, 0xeb) }, - { "SkyBlue1", RGB(0x87, 0xce, 0xff) }, - { "SkyBlue2", RGB(0x7e, 0xc0, 0xee) }, - { "SkyBlue3", RGB(0x6c, 0xa6, 0xcd) }, - { "SkyBlue4", RGB(0x4a, 0x70, 0x8b) }, - { "SlateBlue", RGB(0x6a, 0x5a, 0xcd) }, - { "SlateBlue1", RGB(0x83, 0x6f, 0xff) }, - { "SlateBlue2", RGB(0x7a, 0x67, 0xee) }, - { "SlateBlue3", RGB(0x69, 0x59, 0xcd) }, - { "SlateBlue4", RGB(0x47, 0x3c, 0x8b) }, - { "SlateGray", RGB(0x70, 0x80, 0x90) }, - { "SlateGray1", RGB(0xc6, 0xe2, 0xff) }, - { "SlateGray2", RGB(0xb9, 0xd3, 0xee) }, - { "SlateGray3", RGB(0x9f, 0xb6, 0xcd) }, - { "SlateGray4", RGB(0x6c, 0x7b, 0x8b) }, - { "SlateGrey", RGB(0x70, 0x80, 0x90) }, - { "Snow", RGB(0xff, 0xfa, 0xfa) }, - { "Snow1", RGB(0xff, 0xfa, 0xfa) }, - { "Snow2", RGB(0xee, 0xe9, 0xe9) }, - { "Snow3", RGB(0xcd, 0xc9, 0xc9) }, - { "Snow4", RGB(0x8b, 0x89, 0x89) }, - { "SpringGreen", RGB(0x00, 0xff, 0x7f) }, - { "SpringGreen1", RGB(0x0, 0xff, 0x7f) }, - { "SpringGreen2", RGB(0x0, 0xee, 0x76) }, - { "SpringGreen3", RGB(0x0, 0xcd, 0x66) }, - { "SpringGreen4", RGB(0x0, 0x8b, 0x45) }, - { "SteelBlue", RGB(0x46, 0x82, 0xb4) }, - { "SteelBlue1", RGB(0x63, 0xb8, 0xff) }, - { "SteelBlue2", RGB(0x5c, 0xac, 0xee) }, - { "SteelBlue3", RGB(0x4f, 0x94, 0xcd) }, - { "SteelBlue4", RGB(0x36, 0x64, 0x8b) }, - { "Tan", RGB(0xd2, 0xb4, 0x8c) }, - { "Tan1", RGB(0xff, 0xa5, 0x4f) }, - { "Tan2", RGB(0xee, 0x9a, 0x49) }, - { "Tan3", RGB(0xcd, 0x85, 0x3f) }, - { "Tan4", RGB(0x8b, 0x5a, 0x2b) }, - { "Teal", RGB(0x00, 0x80, 0x80) }, - { "Thistle", RGB(0xd8, 0xbf, 0xd8) }, - { "Thistle1", RGB(0xff, 0xe1, 0xff) }, - { "Thistle2", RGB(0xee, 0xd2, 0xee) }, - { "Thistle3", RGB(0xcd, 0xb5, 0xcd) }, - { "Thistle4", RGB(0x8b, 0x7b, 0x8b) }, - { "Tomato", RGB(0xff, 0x63, 0x47) }, - { "Tomato1", RGB(0xff, 0x63, 0x47) }, - { "Tomato2", RGB(0xee, 0x5c, 0x42) }, - { "Tomato3", RGB(0xcd, 0x4f, 0x39) }, - { "Tomato4", RGB(0x8b, 0x36, 0x26) }, - { "Turquoise", RGB(0x40, 0xe0, 0xd0) }, - { "Turquoise1", RGB(0x0, 0xf5, 0xff) }, - { "Turquoise2", RGB(0x0, 0xe5, 0xee) }, - { "Turquoise3", RGB(0x0, 0xc5, 0xcd) }, - { "Turquoise4", RGB(0x0, 0x86, 0x8b) }, - { "Violet", RGB(0xee, 0x82, 0xee) }, - { "VioletRed", RGB(0xd0, 0x20, 0x90) }, - { "VioletRed1", RGB(0xff, 0x3e, 0x96) }, - { "VioletRed2", RGB(0xee, 0x3a, 0x8c) }, - { "VioletRed3", RGB(0xcd, 0x32, 0x78) }, - { "VioletRed4", RGB(0x8b, 0x22, 0x52) }, - { "WebGray", RGB(0x80, 0x80, 0x80) }, - { "WebGreen", RGB(0x0, 0x80, 0x0) }, - { "WebGrey", RGB(0x80, 0x80, 0x80) }, - { "WebMaroon", RGB(0x80, 0x0, 0x0) }, - { "WebPurple", RGB(0x80, 0x0, 0x80) }, - { "Wheat", RGB(0xf5, 0xde, 0xb3) }, - { "Wheat1", RGB(0xff, 0xe7, 0xba) }, - { "Wheat2", RGB(0xee, 0xd8, 0xae) }, - { "Wheat3", RGB(0xcd, 0xba, 0x96) }, - { "Wheat4", RGB(0x8b, 0x7e, 0x66) }, - { "White", RGB(0xff, 0xff, 0xff) }, - { "WhiteSmoke", RGB(0xf5, 0xf5, 0xf5) }, - { "X11Gray", RGB(0xbe, 0xbe, 0xbe) }, - { "X11Green", RGB(0x0, 0xff, 0x0) }, - { "X11Grey", RGB(0xbe, 0xbe, 0xbe) }, - { "X11Maroon", RGB(0xb0, 0x30, 0x60) }, - { "X11Purple", RGB(0xa0, 0x20, 0xf0) }, - { "Yellow", RGB(0xff, 0xff, 0x00) }, - { "Yellow1", RGB(0xff, 0xff, 0x0) }, - { "Yellow2", RGB(0xee, 0xee, 0x0) }, - { "Yellow3", RGB(0xcd, 0xcd, 0x0) }, - { "Yellow4", RGB(0x8b, 0x8b, 0x0) }, - { "YellowGreen", RGB(0x9a, 0xcd, 0x32) }, + { "AliceBlue", RGB_(0xf0, 0xf8, 0xff) }, + { "AntiqueWhite", RGB_(0xfa, 0xeb, 0xd7) }, + { "AntiqueWhite1", RGB_(0xff, 0xef, 0xdb) }, + { "AntiqueWhite2", RGB_(0xee, 0xdf, 0xcc) }, + { "AntiqueWhite3", RGB_(0xcd, 0xc0, 0xb0) }, + { "AntiqueWhite4", RGB_(0x8b, 0x83, 0x78) }, + { "Aqua", RGB_(0x00, 0xff, 0xff) }, + { "Aquamarine", RGB_(0x7f, 0xff, 0xd4) }, + { "Aquamarine1", RGB_(0x7f, 0xff, 0xd4) }, + { "Aquamarine2", RGB_(0x76, 0xee, 0xc6) }, + { "Aquamarine3", RGB_(0x66, 0xcd, 0xaa) }, + { "Aquamarine4", RGB_(0x45, 0x8b, 0x74) }, + { "Azure", RGB_(0xf0, 0xff, 0xff) }, + { "Azure1", RGB_(0xf0, 0xff, 0xff) }, + { "Azure2", RGB_(0xe0, 0xee, 0xee) }, + { "Azure3", RGB_(0xc1, 0xcd, 0xcd) }, + { "Azure4", RGB_(0x83, 0x8b, 0x8b) }, + { "Beige", RGB_(0xf5, 0xf5, 0xdc) }, + { "Bisque", RGB_(0xff, 0xe4, 0xc4) }, + { "Bisque1", RGB_(0xff, 0xe4, 0xc4) }, + { "Bisque2", RGB_(0xee, 0xd5, 0xb7) }, + { "Bisque3", RGB_(0xcd, 0xb7, 0x9e) }, + { "Bisque4", RGB_(0x8b, 0x7d, 0x6b) }, + { "Black", RGB_(0x00, 0x00, 0x00) }, + { "BlanchedAlmond", RGB_(0xff, 0xeb, 0xcd) }, + { "Blue", RGB_(0x00, 0x00, 0xff) }, + { "Blue1", RGB_(0x0, 0x0, 0xff) }, + { "Blue2", RGB_(0x0, 0x0, 0xee) }, + { "Blue3", RGB_(0x0, 0x0, 0xcd) }, + { "Blue4", RGB_(0x0, 0x0, 0x8b) }, + { "BlueViolet", RGB_(0x8a, 0x2b, 0xe2) }, + { "Brown", RGB_(0xa5, 0x2a, 0x2a) }, + { "Brown1", RGB_(0xff, 0x40, 0x40) }, + { "Brown2", RGB_(0xee, 0x3b, 0x3b) }, + { "Brown3", RGB_(0xcd, 0x33, 0x33) }, + { "Brown4", RGB_(0x8b, 0x23, 0x23) }, + { "BurlyWood", RGB_(0xde, 0xb8, 0x87) }, + { "Burlywood1", RGB_(0xff, 0xd3, 0x9b) }, + { "Burlywood2", RGB_(0xee, 0xc5, 0x91) }, + { "Burlywood3", RGB_(0xcd, 0xaa, 0x7d) }, + { "Burlywood4", RGB_(0x8b, 0x73, 0x55) }, + { "CadetBlue", RGB_(0x5f, 0x9e, 0xa0) }, + { "CadetBlue1", RGB_(0x98, 0xf5, 0xff) }, + { "CadetBlue2", RGB_(0x8e, 0xe5, 0xee) }, + { "CadetBlue3", RGB_(0x7a, 0xc5, 0xcd) }, + { "CadetBlue4", RGB_(0x53, 0x86, 0x8b) }, + { "ChartReuse", RGB_(0x7f, 0xff, 0x00) }, + { "Chartreuse1", RGB_(0x7f, 0xff, 0x0) }, + { "Chartreuse2", RGB_(0x76, 0xee, 0x0) }, + { "Chartreuse3", RGB_(0x66, 0xcd, 0x0) }, + { "Chartreuse4", RGB_(0x45, 0x8b, 0x0) }, + { "Chocolate", RGB_(0xd2, 0x69, 0x1e) }, + { "Chocolate1", RGB_(0xff, 0x7f, 0x24) }, + { "Chocolate2", RGB_(0xee, 0x76, 0x21) }, + { "Chocolate3", RGB_(0xcd, 0x66, 0x1d) }, + { "Chocolate4", RGB_(0x8b, 0x45, 0x13) }, + { "Coral", RGB_(0xff, 0x7f, 0x50) }, + { "Coral1", RGB_(0xff, 0x72, 0x56) }, + { "Coral2", RGB_(0xee, 0x6a, 0x50) }, + { "Coral3", RGB_(0xcd, 0x5b, 0x45) }, + { "Coral4", RGB_(0x8b, 0x3e, 0x2f) }, + { "CornFlowerBlue", RGB_(0x64, 0x95, 0xed) }, + { "Cornsilk", RGB_(0xff, 0xf8, 0xdc) }, + { "Cornsilk1", RGB_(0xff, 0xf8, 0xdc) }, + { "Cornsilk2", RGB_(0xee, 0xe8, 0xcd) }, + { "Cornsilk3", RGB_(0xcd, 0xc8, 0xb1) }, + { "Cornsilk4", RGB_(0x8b, 0x88, 0x78) }, + { "Crimson", RGB_(0xdc, 0x14, 0x3c) }, + { "Cyan", RGB_(0x00, 0xff, 0xff) }, + { "Cyan1", RGB_(0x0, 0xff, 0xff) }, + { "Cyan2", RGB_(0x0, 0xee, 0xee) }, + { "Cyan3", RGB_(0x0, 0xcd, 0xcd) }, + { "Cyan4", RGB_(0x0, 0x8b, 0x8b) }, + { "DarkBlue", RGB_(0x00, 0x00, 0x8b) }, + { "DarkCyan", RGB_(0x00, 0x8b, 0x8b) }, + { "DarkGoldenRod", RGB_(0xb8, 0x86, 0x0b) }, + { "DarkGoldenrod1", RGB_(0xff, 0xb9, 0xf) }, + { "DarkGoldenrod2", RGB_(0xee, 0xad, 0xe) }, + { "DarkGoldenrod3", RGB_(0xcd, 0x95, 0xc) }, + { "DarkGoldenrod4", RGB_(0x8b, 0x65, 0x8) }, + { "DarkGray", RGB_(0xa9, 0xa9, 0xa9) }, + { "DarkGreen", RGB_(0x00, 0x64, 0x00) }, + { "DarkGrey", RGB_(0xa9, 0xa9, 0xa9) }, + { "DarkKhaki", RGB_(0xbd, 0xb7, 0x6b) }, + { "DarkMagenta", RGB_(0x8b, 0x00, 0x8b) }, + { "DarkOliveGreen", RGB_(0x55, 0x6b, 0x2f) }, + { "DarkOliveGreen1", RGB_(0xca, 0xff, 0x70) }, + { "DarkOliveGreen2", RGB_(0xbc, 0xee, 0x68) }, + { "DarkOliveGreen3", RGB_(0xa2, 0xcd, 0x5a) }, + { "DarkOliveGreen4", RGB_(0x6e, 0x8b, 0x3d) }, + { "DarkOrange", RGB_(0xff, 0x8c, 0x00) }, + { "DarkOrange1", RGB_(0xff, 0x7f, 0x0) }, + { "DarkOrange2", RGB_(0xee, 0x76, 0x0) }, + { "DarkOrange3", RGB_(0xcd, 0x66, 0x0) }, + { "DarkOrange4", RGB_(0x8b, 0x45, 0x0) }, + { "DarkOrchid", RGB_(0x99, 0x32, 0xcc) }, + { "DarkOrchid1", RGB_(0xbf, 0x3e, 0xff) }, + { "DarkOrchid2", RGB_(0xb2, 0x3a, 0xee) }, + { "DarkOrchid3", RGB_(0x9a, 0x32, 0xcd) }, + { "DarkOrchid4", RGB_(0x68, 0x22, 0x8b) }, + { "DarkRed", RGB_(0x8b, 0x00, 0x00) }, + { "DarkSalmon", RGB_(0xe9, 0x96, 0x7a) }, + { "DarkSeaGreen", RGB_(0x8f, 0xbc, 0x8f) }, + { "DarkSeaGreen1", RGB_(0xc1, 0xff, 0xc1) }, + { "DarkSeaGreen2", RGB_(0xb4, 0xee, 0xb4) }, + { "DarkSeaGreen3", RGB_(0x9b, 0xcd, 0x9b) }, + { "DarkSeaGreen4", RGB_(0x69, 0x8b, 0x69) }, + { "DarkSlateBlue", RGB_(0x48, 0x3d, 0x8b) }, + { "DarkSlateGray", RGB_(0x2f, 0x4f, 0x4f) }, + { "DarkSlateGray1", RGB_(0x97, 0xff, 0xff) }, + { "DarkSlateGray2", RGB_(0x8d, 0xee, 0xee) }, + { "DarkSlateGray3", RGB_(0x79, 0xcd, 0xcd) }, + { "DarkSlateGray4", RGB_(0x52, 0x8b, 0x8b) }, + { "DarkSlateGrey", RGB_(0x2f, 0x4f, 0x4f) }, + { "DarkTurquoise", RGB_(0x00, 0xce, 0xd1) }, + { "DarkViolet", RGB_(0x94, 0x00, 0xd3) }, + { "DarkYellow", RGB_(0xbb, 0xbb, 0x00) }, + { "DeepPink", RGB_(0xff, 0x14, 0x93) }, + { "DeepPink1", RGB_(0xff, 0x14, 0x93) }, + { "DeepPink2", RGB_(0xee, 0x12, 0x89) }, + { "DeepPink3", RGB_(0xcd, 0x10, 0x76) }, + { "DeepPink4", RGB_(0x8b, 0xa, 0x50) }, + { "DeepSkyBlue", RGB_(0x00, 0xbf, 0xff) }, + { "DeepSkyBlue1", RGB_(0x0, 0xbf, 0xff) }, + { "DeepSkyBlue2", RGB_(0x0, 0xb2, 0xee) }, + { "DeepSkyBlue3", RGB_(0x0, 0x9a, 0xcd) }, + { "DeepSkyBlue4", RGB_(0x0, 0x68, 0x8b) }, + { "DimGray", RGB_(0x69, 0x69, 0x69) }, + { "DimGrey", RGB_(0x69, 0x69, 0x69) }, + { "DodgerBlue", RGB_(0x1e, 0x90, 0xff) }, + { "DodgerBlue1", RGB_(0x1e, 0x90, 0xff) }, + { "DodgerBlue2", RGB_(0x1c, 0x86, 0xee) }, + { "DodgerBlue3", RGB_(0x18, 0x74, 0xcd) }, + { "DodgerBlue4", RGB_(0x10, 0x4e, 0x8b) }, + { "Firebrick", RGB_(0xb2, 0x22, 0x22) }, + { "Firebrick1", RGB_(0xff, 0x30, 0x30) }, + { "Firebrick2", RGB_(0xee, 0x2c, 0x2c) }, + { "Firebrick3", RGB_(0xcd, 0x26, 0x26) }, + { "Firebrick4", RGB_(0x8b, 0x1a, 0x1a) }, + { "FloralWhite", RGB_(0xff, 0xfa, 0xf0) }, + { "ForestGreen", RGB_(0x22, 0x8b, 0x22) }, + { "Fuchsia", RGB_(0xff, 0x00, 0xff) }, + { "Gainsboro", RGB_(0xdc, 0xdc, 0xdc) }, + { "GhostWhite", RGB_(0xf8, 0xf8, 0xff) }, + { "Gold", RGB_(0xff, 0xd7, 0x00) }, + { "Gold1", RGB_(0xff, 0xd7, 0x0) }, + { "Gold2", RGB_(0xee, 0xc9, 0x0) }, + { "Gold3", RGB_(0xcd, 0xad, 0x0) }, + { "Gold4", RGB_(0x8b, 0x75, 0x0) }, + { "GoldenRod", RGB_(0xda, 0xa5, 0x20) }, + { "Goldenrod1", RGB_(0xff, 0xc1, 0x25) }, + { "Goldenrod2", RGB_(0xee, 0xb4, 0x22) }, + { "Goldenrod3", RGB_(0xcd, 0x9b, 0x1d) }, + { "Goldenrod4", RGB_(0x8b, 0x69, 0x14) }, + { "Gray", RGB_(0x80, 0x80, 0x80) }, + { "Gray0", RGB_(0x0, 0x0, 0x0) }, + { "Gray1", RGB_(0x3, 0x3, 0x3) }, + { "Gray10", RGB_(0x1a, 0x1a, 0x1a) }, + { "Gray100", RGB_(0xff, 0xff, 0xff) }, + { "Gray11", RGB_(0x1c, 0x1c, 0x1c) }, + { "Gray12", RGB_(0x1f, 0x1f, 0x1f) }, + { "Gray13", RGB_(0x21, 0x21, 0x21) }, + { "Gray14", RGB_(0x24, 0x24, 0x24) }, + { "Gray15", RGB_(0x26, 0x26, 0x26) }, + { "Gray16", RGB_(0x29, 0x29, 0x29) }, + { "Gray17", RGB_(0x2b, 0x2b, 0x2b) }, + { "Gray18", RGB_(0x2e, 0x2e, 0x2e) }, + { "Gray19", RGB_(0x30, 0x30, 0x30) }, + { "Gray2", RGB_(0x5, 0x5, 0x5) }, + { "Gray20", RGB_(0x33, 0x33, 0x33) }, + { "Gray21", RGB_(0x36, 0x36, 0x36) }, + { "Gray22", RGB_(0x38, 0x38, 0x38) }, + { "Gray23", RGB_(0x3b, 0x3b, 0x3b) }, + { "Gray24", RGB_(0x3d, 0x3d, 0x3d) }, + { "Gray25", RGB_(0x40, 0x40, 0x40) }, + { "Gray26", RGB_(0x42, 0x42, 0x42) }, + { "Gray27", RGB_(0x45, 0x45, 0x45) }, + { "Gray28", RGB_(0x47, 0x47, 0x47) }, + { "Gray29", RGB_(0x4a, 0x4a, 0x4a) }, + { "Gray3", RGB_(0x8, 0x8, 0x8) }, + { "Gray30", RGB_(0x4d, 0x4d, 0x4d) }, + { "Gray31", RGB_(0x4f, 0x4f, 0x4f) }, + { "Gray32", RGB_(0x52, 0x52, 0x52) }, + { "Gray33", RGB_(0x54, 0x54, 0x54) }, + { "Gray34", RGB_(0x57, 0x57, 0x57) }, + { "Gray35", RGB_(0x59, 0x59, 0x59) }, + { "Gray36", RGB_(0x5c, 0x5c, 0x5c) }, + { "Gray37", RGB_(0x5e, 0x5e, 0x5e) }, + { "Gray38", RGB_(0x61, 0x61, 0x61) }, + { "Gray39", RGB_(0x63, 0x63, 0x63) }, + { "Gray4", RGB_(0xa, 0xa, 0xa) }, + { "Gray40", RGB_(0x66, 0x66, 0x66) }, + { "Gray41", RGB_(0x69, 0x69, 0x69) }, + { "Gray42", RGB_(0x6b, 0x6b, 0x6b) }, + { "Gray43", RGB_(0x6e, 0x6e, 0x6e) }, + { "Gray44", RGB_(0x70, 0x70, 0x70) }, + { "Gray45", RGB_(0x73, 0x73, 0x73) }, + { "Gray46", RGB_(0x75, 0x75, 0x75) }, + { "Gray47", RGB_(0x78, 0x78, 0x78) }, + { "Gray48", RGB_(0x7a, 0x7a, 0x7a) }, + { "Gray49", RGB_(0x7d, 0x7d, 0x7d) }, + { "Gray5", RGB_(0xd, 0xd, 0xd) }, + { "Gray50", RGB_(0x7f, 0x7f, 0x7f) }, + { "Gray51", RGB_(0x82, 0x82, 0x82) }, + { "Gray52", RGB_(0x85, 0x85, 0x85) }, + { "Gray53", RGB_(0x87, 0x87, 0x87) }, + { "Gray54", RGB_(0x8a, 0x8a, 0x8a) }, + { "Gray55", RGB_(0x8c, 0x8c, 0x8c) }, + { "Gray56", RGB_(0x8f, 0x8f, 0x8f) }, + { "Gray57", RGB_(0x91, 0x91, 0x91) }, + { "Gray58", RGB_(0x94, 0x94, 0x94) }, + { "Gray59", RGB_(0x96, 0x96, 0x96) }, + { "Gray6", RGB_(0xf, 0xf, 0xf) }, + { "Gray60", RGB_(0x99, 0x99, 0x99) }, + { "Gray61", RGB_(0x9c, 0x9c, 0x9c) }, + { "Gray62", RGB_(0x9e, 0x9e, 0x9e) }, + { "Gray63", RGB_(0xa1, 0xa1, 0xa1) }, + { "Gray64", RGB_(0xa3, 0xa3, 0xa3) }, + { "Gray65", RGB_(0xa6, 0xa6, 0xa6) }, + { "Gray66", RGB_(0xa8, 0xa8, 0xa8) }, + { "Gray67", RGB_(0xab, 0xab, 0xab) }, + { "Gray68", RGB_(0xad, 0xad, 0xad) }, + { "Gray69", RGB_(0xb0, 0xb0, 0xb0) }, + { "Gray7", RGB_(0x12, 0x12, 0x12) }, + { "Gray70", RGB_(0xb3, 0xb3, 0xb3) }, + { "Gray71", RGB_(0xb5, 0xb5, 0xb5) }, + { "Gray72", RGB_(0xb8, 0xb8, 0xb8) }, + { "Gray73", RGB_(0xba, 0xba, 0xba) }, + { "Gray74", RGB_(0xbd, 0xbd, 0xbd) }, + { "Gray75", RGB_(0xbf, 0xbf, 0xbf) }, + { "Gray76", RGB_(0xc2, 0xc2, 0xc2) }, + { "Gray77", RGB_(0xc4, 0xc4, 0xc4) }, + { "Gray78", RGB_(0xc7, 0xc7, 0xc7) }, + { "Gray79", RGB_(0xc9, 0xc9, 0xc9) }, + { "Gray8", RGB_(0x14, 0x14, 0x14) }, + { "Gray80", RGB_(0xcc, 0xcc, 0xcc) }, + { "Gray81", RGB_(0xcf, 0xcf, 0xcf) }, + { "Gray82", RGB_(0xd1, 0xd1, 0xd1) }, + { "Gray83", RGB_(0xd4, 0xd4, 0xd4) }, + { "Gray84", RGB_(0xd6, 0xd6, 0xd6) }, + { "Gray85", RGB_(0xd9, 0xd9, 0xd9) }, + { "Gray86", RGB_(0xdb, 0xdb, 0xdb) }, + { "Gray87", RGB_(0xde, 0xde, 0xde) }, + { "Gray88", RGB_(0xe0, 0xe0, 0xe0) }, + { "Gray89", RGB_(0xe3, 0xe3, 0xe3) }, + { "Gray9", RGB_(0x17, 0x17, 0x17) }, + { "Gray90", RGB_(0xe5, 0xe5, 0xe5) }, + { "Gray91", RGB_(0xe8, 0xe8, 0xe8) }, + { "Gray92", RGB_(0xeb, 0xeb, 0xeb) }, + { "Gray93", RGB_(0xed, 0xed, 0xed) }, + { "Gray94", RGB_(0xf0, 0xf0, 0xf0) }, + { "Gray95", RGB_(0xf2, 0xf2, 0xf2) }, + { "Gray96", RGB_(0xf5, 0xf5, 0xf5) }, + { "Gray97", RGB_(0xf7, 0xf7, 0xf7) }, + { "Gray98", RGB_(0xfa, 0xfa, 0xfa) }, + { "Gray99", RGB_(0xfc, 0xfc, 0xfc) }, + { "Green", RGB_(0x00, 0x80, 0x00) }, + { "Green1", RGB_(0x0, 0xff, 0x0) }, + { "Green2", RGB_(0x0, 0xee, 0x0) }, + { "Green3", RGB_(0x0, 0xcd, 0x0) }, + { "Green4", RGB_(0x0, 0x8b, 0x0) }, + { "GreenYellow", RGB_(0xad, 0xff, 0x2f) }, + { "Grey", RGB_(0x80, 0x80, 0x80) }, + { "Grey0", RGB_(0x0, 0x0, 0x0) }, + { "Grey1", RGB_(0x3, 0x3, 0x3) }, + { "Grey10", RGB_(0x1a, 0x1a, 0x1a) }, + { "Grey100", RGB_(0xff, 0xff, 0xff) }, + { "Grey11", RGB_(0x1c, 0x1c, 0x1c) }, + { "Grey12", RGB_(0x1f, 0x1f, 0x1f) }, + { "Grey13", RGB_(0x21, 0x21, 0x21) }, + { "Grey14", RGB_(0x24, 0x24, 0x24) }, + { "Grey15", RGB_(0x26, 0x26, 0x26) }, + { "Grey16", RGB_(0x29, 0x29, 0x29) }, + { "Grey17", RGB_(0x2b, 0x2b, 0x2b) }, + { "Grey18", RGB_(0x2e, 0x2e, 0x2e) }, + { "Grey19", RGB_(0x30, 0x30, 0x30) }, + { "Grey2", RGB_(0x5, 0x5, 0x5) }, + { "Grey20", RGB_(0x33, 0x33, 0x33) }, + { "Grey21", RGB_(0x36, 0x36, 0x36) }, + { "Grey22", RGB_(0x38, 0x38, 0x38) }, + { "Grey23", RGB_(0x3b, 0x3b, 0x3b) }, + { "Grey24", RGB_(0x3d, 0x3d, 0x3d) }, + { "Grey25", RGB_(0x40, 0x40, 0x40) }, + { "Grey26", RGB_(0x42, 0x42, 0x42) }, + { "Grey27", RGB_(0x45, 0x45, 0x45) }, + { "Grey28", RGB_(0x47, 0x47, 0x47) }, + { "Grey29", RGB_(0x4a, 0x4a, 0x4a) }, + { "Grey3", RGB_(0x8, 0x8, 0x8) }, + { "Grey30", RGB_(0x4d, 0x4d, 0x4d) }, + { "Grey31", RGB_(0x4f, 0x4f, 0x4f) }, + { "Grey32", RGB_(0x52, 0x52, 0x52) }, + { "Grey33", RGB_(0x54, 0x54, 0x54) }, + { "Grey34", RGB_(0x57, 0x57, 0x57) }, + { "Grey35", RGB_(0x59, 0x59, 0x59) }, + { "Grey36", RGB_(0x5c, 0x5c, 0x5c) }, + { "Grey37", RGB_(0x5e, 0x5e, 0x5e) }, + { "Grey38", RGB_(0x61, 0x61, 0x61) }, + { "Grey39", RGB_(0x63, 0x63, 0x63) }, + { "Grey4", RGB_(0xa, 0xa, 0xa) }, + { "Grey40", RGB_(0x66, 0x66, 0x66) }, + { "Grey41", RGB_(0x69, 0x69, 0x69) }, + { "Grey42", RGB_(0x6b, 0x6b, 0x6b) }, + { "Grey43", RGB_(0x6e, 0x6e, 0x6e) }, + { "Grey44", RGB_(0x70, 0x70, 0x70) }, + { "Grey45", RGB_(0x73, 0x73, 0x73) }, + { "Grey46", RGB_(0x75, 0x75, 0x75) }, + { "Grey47", RGB_(0x78, 0x78, 0x78) }, + { "Grey48", RGB_(0x7a, 0x7a, 0x7a) }, + { "Grey49", RGB_(0x7d, 0x7d, 0x7d) }, + { "Grey5", RGB_(0xd, 0xd, 0xd) }, + { "Grey50", RGB_(0x7f, 0x7f, 0x7f) }, + { "Grey51", RGB_(0x82, 0x82, 0x82) }, + { "Grey52", RGB_(0x85, 0x85, 0x85) }, + { "Grey53", RGB_(0x87, 0x87, 0x87) }, + { "Grey54", RGB_(0x8a, 0x8a, 0x8a) }, + { "Grey55", RGB_(0x8c, 0x8c, 0x8c) }, + { "Grey56", RGB_(0x8f, 0x8f, 0x8f) }, + { "Grey57", RGB_(0x91, 0x91, 0x91) }, + { "Grey58", RGB_(0x94, 0x94, 0x94) }, + { "Grey59", RGB_(0x96, 0x96, 0x96) }, + { "Grey6", RGB_(0xf, 0xf, 0xf) }, + { "Grey60", RGB_(0x99, 0x99, 0x99) }, + { "Grey61", RGB_(0x9c, 0x9c, 0x9c) }, + { "Grey62", RGB_(0x9e, 0x9e, 0x9e) }, + { "Grey63", RGB_(0xa1, 0xa1, 0xa1) }, + { "Grey64", RGB_(0xa3, 0xa3, 0xa3) }, + { "Grey65", RGB_(0xa6, 0xa6, 0xa6) }, + { "Grey66", RGB_(0xa8, 0xa8, 0xa8) }, + { "Grey67", RGB_(0xab, 0xab, 0xab) }, + { "Grey68", RGB_(0xad, 0xad, 0xad) }, + { "Grey69", RGB_(0xb0, 0xb0, 0xb0) }, + { "Grey7", RGB_(0x12, 0x12, 0x12) }, + { "Grey70", RGB_(0xb3, 0xb3, 0xb3) }, + { "Grey71", RGB_(0xb5, 0xb5, 0xb5) }, + { "Grey72", RGB_(0xb8, 0xb8, 0xb8) }, + { "Grey73", RGB_(0xba, 0xba, 0xba) }, + { "Grey74", RGB_(0xbd, 0xbd, 0xbd) }, + { "Grey75", RGB_(0xbf, 0xbf, 0xbf) }, + { "Grey76", RGB_(0xc2, 0xc2, 0xc2) }, + { "Grey77", RGB_(0xc4, 0xc4, 0xc4) }, + { "Grey78", RGB_(0xc7, 0xc7, 0xc7) }, + { "Grey79", RGB_(0xc9, 0xc9, 0xc9) }, + { "Grey8", RGB_(0x14, 0x14, 0x14) }, + { "Grey80", RGB_(0xcc, 0xcc, 0xcc) }, + { "Grey81", RGB_(0xcf, 0xcf, 0xcf) }, + { "Grey82", RGB_(0xd1, 0xd1, 0xd1) }, + { "Grey83", RGB_(0xd4, 0xd4, 0xd4) }, + { "Grey84", RGB_(0xd6, 0xd6, 0xd6) }, + { "Grey85", RGB_(0xd9, 0xd9, 0xd9) }, + { "Grey86", RGB_(0xdb, 0xdb, 0xdb) }, + { "Grey87", RGB_(0xde, 0xde, 0xde) }, + { "Grey88", RGB_(0xe0, 0xe0, 0xe0) }, + { "Grey89", RGB_(0xe3, 0xe3, 0xe3) }, + { "Grey9", RGB_(0x17, 0x17, 0x17) }, + { "Grey90", RGB_(0xe5, 0xe5, 0xe5) }, + { "Grey91", RGB_(0xe8, 0xe8, 0xe8) }, + { "Grey92", RGB_(0xeb, 0xeb, 0xeb) }, + { "Grey93", RGB_(0xed, 0xed, 0xed) }, + { "Grey94", RGB_(0xf0, 0xf0, 0xf0) }, + { "Grey95", RGB_(0xf2, 0xf2, 0xf2) }, + { "Grey96", RGB_(0xf5, 0xf5, 0xf5) }, + { "Grey97", RGB_(0xf7, 0xf7, 0xf7) }, + { "Grey98", RGB_(0xfa, 0xfa, 0xfa) }, + { "Grey99", RGB_(0xfc, 0xfc, 0xfc) }, + { "Honeydew", RGB_(0xf0, 0xff, 0xf0) }, + { "Honeydew1", RGB_(0xf0, 0xff, 0xf0) }, + { "Honeydew2", RGB_(0xe0, 0xee, 0xe0) }, + { "Honeydew3", RGB_(0xc1, 0xcd, 0xc1) }, + { "Honeydew4", RGB_(0x83, 0x8b, 0x83) }, + { "HotPink", RGB_(0xff, 0x69, 0xb4) }, + { "HotPink1", RGB_(0xff, 0x6e, 0xb4) }, + { "HotPink2", RGB_(0xee, 0x6a, 0xa7) }, + { "HotPink3", RGB_(0xcd, 0x60, 0x90) }, + { "HotPink4", RGB_(0x8b, 0x3a, 0x62) }, + { "IndianRed", RGB_(0xcd, 0x5c, 0x5c) }, + { "IndianRed1", RGB_(0xff, 0x6a, 0x6a) }, + { "IndianRed2", RGB_(0xee, 0x63, 0x63) }, + { "IndianRed3", RGB_(0xcd, 0x55, 0x55) }, + { "IndianRed4", RGB_(0x8b, 0x3a, 0x3a) }, + { "Indigo", RGB_(0x4b, 0x00, 0x82) }, + { "Ivory", RGB_(0xff, 0xff, 0xf0) }, + { "Ivory1", RGB_(0xff, 0xff, 0xf0) }, + { "Ivory2", RGB_(0xee, 0xee, 0xe0) }, + { "Ivory3", RGB_(0xcd, 0xcd, 0xc1) }, + { "Ivory4", RGB_(0x8b, 0x8b, 0x83) }, + { "Khaki", RGB_(0xf0, 0xe6, 0x8c) }, + { "Khaki1", RGB_(0xff, 0xf6, 0x8f) }, + { "Khaki2", RGB_(0xee, 0xe6, 0x85) }, + { "Khaki3", RGB_(0xcd, 0xc6, 0x73) }, + { "Khaki4", RGB_(0x8b, 0x86, 0x4e) }, + { "Lavender", RGB_(0xe6, 0xe6, 0xfa) }, + { "LavenderBlush", RGB_(0xff, 0xf0, 0xf5) }, + { "LavenderBlush1", RGB_(0xff, 0xf0, 0xf5) }, + { "LavenderBlush2", RGB_(0xee, 0xe0, 0xe5) }, + { "LavenderBlush3", RGB_(0xcd, 0xc1, 0xc5) }, + { "LavenderBlush4", RGB_(0x8b, 0x83, 0x86) }, + { "LawnGreen", RGB_(0x7c, 0xfc, 0x00) }, + { "LemonChiffon", RGB_(0xff, 0xfa, 0xcd) }, + { "LemonChiffon1", RGB_(0xff, 0xfa, 0xcd) }, + { "LemonChiffon2", RGB_(0xee, 0xe9, 0xbf) }, + { "LemonChiffon3", RGB_(0xcd, 0xc9, 0xa5) }, + { "LemonChiffon4", RGB_(0x8b, 0x89, 0x70) }, + { "LightBlue", RGB_(0xad, 0xd8, 0xe6) }, + { "LightBlue1", RGB_(0xbf, 0xef, 0xff) }, + { "LightBlue2", RGB_(0xb2, 0xdf, 0xee) }, + { "LightBlue3", RGB_(0x9a, 0xc0, 0xcd) }, + { "LightBlue4", RGB_(0x68, 0x83, 0x8b) }, + { "LightCoral", RGB_(0xf0, 0x80, 0x80) }, + { "LightCyan", RGB_(0xe0, 0xff, 0xff) }, + { "LightCyan1", RGB_(0xe0, 0xff, 0xff) }, + { "LightCyan2", RGB_(0xd1, 0xee, 0xee) }, + { "LightCyan3", RGB_(0xb4, 0xcd, 0xcd) }, + { "LightCyan4", RGB_(0x7a, 0x8b, 0x8b) }, + { "LightGoldenrod", RGB_(0xee, 0xdd, 0x82) }, + { "LightGoldenrod1", RGB_(0xff, 0xec, 0x8b) }, + { "LightGoldenrod2", RGB_(0xee, 0xdc, 0x82) }, + { "LightGoldenrod3", RGB_(0xcd, 0xbe, 0x70) }, + { "LightGoldenrod4", RGB_(0x8b, 0x81, 0x4c) }, + { "LightGoldenRodYellow", RGB_(0xfa, 0xfa, 0xd2) }, + { "LightGray", RGB_(0xd3, 0xd3, 0xd3) }, + { "LightGreen", RGB_(0x90, 0xee, 0x90) }, + { "LightGrey", RGB_(0xd3, 0xd3, 0xd3) }, + { "LightMagenta", RGB_(0xff, 0xbb, 0xff) }, + { "LightPink", RGB_(0xff, 0xb6, 0xc1) }, + { "LightPink1", RGB_(0xff, 0xae, 0xb9) }, + { "LightPink2", RGB_(0xee, 0xa2, 0xad) }, + { "LightPink3", RGB_(0xcd, 0x8c, 0x95) }, + { "LightPink4", RGB_(0x8b, 0x5f, 0x65) }, + { "LightRed", RGB_(0xff, 0xbb, 0xbb) }, + { "LightSalmon", RGB_(0xff, 0xa0, 0x7a) }, + { "LightSalmon1", RGB_(0xff, 0xa0, 0x7a) }, + { "LightSalmon2", RGB_(0xee, 0x95, 0x72) }, + { "LightSalmon3", RGB_(0xcd, 0x81, 0x62) }, + { "LightSalmon4", RGB_(0x8b, 0x57, 0x42) }, + { "LightSeaGreen", RGB_(0x20, 0xb2, 0xaa) }, + { "LightSkyBlue", RGB_(0x87, 0xce, 0xfa) }, + { "LightSkyBlue1", RGB_(0xb0, 0xe2, 0xff) }, + { "LightSkyBlue2", RGB_(0xa4, 0xd3, 0xee) }, + { "LightSkyBlue3", RGB_(0x8d, 0xb6, 0xcd) }, + { "LightSkyBlue4", RGB_(0x60, 0x7b, 0x8b) }, + { "LightSlateBlue", RGB_(0x84, 0x70, 0xff) }, + { "LightSlateGray", RGB_(0x77, 0x88, 0x99) }, + { "LightSlateGrey", RGB_(0x77, 0x88, 0x99) }, + { "LightSteelBlue", RGB_(0xb0, 0xc4, 0xde) }, + { "LightSteelBlue1", RGB_(0xca, 0xe1, 0xff) }, + { "LightSteelBlue2", RGB_(0xbc, 0xd2, 0xee) }, + { "LightSteelBlue3", RGB_(0xa2, 0xb5, 0xcd) }, + { "LightSteelBlue4", RGB_(0x6e, 0x7b, 0x8b) }, + { "LightYellow", RGB_(0xff, 0xff, 0xe0) }, + { "LightYellow1", RGB_(0xff, 0xff, 0xe0) }, + { "LightYellow2", RGB_(0xee, 0xee, 0xd1) }, + { "LightYellow3", RGB_(0xcd, 0xcd, 0xb4) }, + { "LightYellow4", RGB_(0x8b, 0x8b, 0x7a) }, + { "Lime", RGB_(0x00, 0xff, 0x00) }, + { "LimeGreen", RGB_(0x32, 0xcd, 0x32) }, + { "Linen", RGB_(0xfa, 0xf0, 0xe6) }, + { "Magenta", RGB_(0xff, 0x00, 0xff) }, + { "Magenta1", RGB_(0xff, 0x0, 0xff) }, + { "Magenta2", RGB_(0xee, 0x0, 0xee) }, + { "Magenta3", RGB_(0xcd, 0x0, 0xcd) }, + { "Magenta4", RGB_(0x8b, 0x0, 0x8b) }, + { "Maroon", RGB_(0x80, 0x00, 0x00) }, + { "Maroon1", RGB_(0xff, 0x34, 0xb3) }, + { "Maroon2", RGB_(0xee, 0x30, 0xa7) }, + { "Maroon3", RGB_(0xcd, 0x29, 0x90) }, + { "Maroon4", RGB_(0x8b, 0x1c, 0x62) }, + { "MediumAquamarine", RGB_(0x66, 0xcd, 0xaa) }, + { "MediumBlue", RGB_(0x00, 0x00, 0xcd) }, + { "MediumOrchid", RGB_(0xba, 0x55, 0xd3) }, + { "MediumOrchid1", RGB_(0xe0, 0x66, 0xff) }, + { "MediumOrchid2", RGB_(0xd1, 0x5f, 0xee) }, + { "MediumOrchid3", RGB_(0xb4, 0x52, 0xcd) }, + { "MediumOrchid4", RGB_(0x7a, 0x37, 0x8b) }, + { "MediumPurple", RGB_(0x93, 0x70, 0xdb) }, + { "MediumPurple1", RGB_(0xab, 0x82, 0xff) }, + { "MediumPurple2", RGB_(0x9f, 0x79, 0xee) }, + { "MediumPurple3", RGB_(0x89, 0x68, 0xcd) }, + { "MediumPurple4", RGB_(0x5d, 0x47, 0x8b) }, + { "MediumSeaGreen", RGB_(0x3c, 0xb3, 0x71) }, + { "MediumSlateBlue", RGB_(0x7b, 0x68, 0xee) }, + { "MediumSpringGreen", RGB_(0x00, 0xfa, 0x9a) }, + { "MediumTurquoise", RGB_(0x48, 0xd1, 0xcc) }, + { "MediumVioletRed", RGB_(0xc7, 0x15, 0x85) }, + { "MidnightBlue", RGB_(0x19, 0x19, 0x70) }, + { "MintCream", RGB_(0xf5, 0xff, 0xfa) }, + { "MistyRose", RGB_(0xff, 0xe4, 0xe1) }, + { "MistyRose1", RGB_(0xff, 0xe4, 0xe1) }, + { "MistyRose2", RGB_(0xee, 0xd5, 0xd2) }, + { "MistyRose3", RGB_(0xcd, 0xb7, 0xb5) }, + { "MistyRose4", RGB_(0x8b, 0x7d, 0x7b) }, + { "Moccasin", RGB_(0xff, 0xe4, 0xb5) }, + { "NavajoWhite", RGB_(0xff, 0xde, 0xad) }, + { "NavajoWhite1", RGB_(0xff, 0xde, 0xad) }, + { "NavajoWhite2", RGB_(0xee, 0xcf, 0xa1) }, + { "NavajoWhite3", RGB_(0xcd, 0xb3, 0x8b) }, + { "NavajoWhite4", RGB_(0x8b, 0x79, 0x5e) }, + { "Navy", RGB_(0x00, 0x00, 0x80) }, + { "NavyBlue", RGB_(0x0, 0x0, 0x80) }, + { "OldLace", RGB_(0xfd, 0xf5, 0xe6) }, + { "Olive", RGB_(0x80, 0x80, 0x00) }, + { "OliveDrab", RGB_(0x6b, 0x8e, 0x23) }, + { "OliveDrab1", RGB_(0xc0, 0xff, 0x3e) }, + { "OliveDrab2", RGB_(0xb3, 0xee, 0x3a) }, + { "OliveDrab3", RGB_(0x9a, 0xcd, 0x32) }, + { "OliveDrab4", RGB_(0x69, 0x8b, 0x22) }, + { "Orange", RGB_(0xff, 0xa5, 0x00) }, + { "Orange1", RGB_(0xff, 0xa5, 0x0) }, + { "Orange2", RGB_(0xee, 0x9a, 0x0) }, + { "Orange3", RGB_(0xcd, 0x85, 0x0) }, + { "Orange4", RGB_(0x8b, 0x5a, 0x0) }, + { "OrangeRed", RGB_(0xff, 0x45, 0x00) }, + { "OrangeRed1", RGB_(0xff, 0x45, 0x0) }, + { "OrangeRed2", RGB_(0xee, 0x40, 0x0) }, + { "OrangeRed3", RGB_(0xcd, 0x37, 0x0) }, + { "OrangeRed4", RGB_(0x8b, 0x25, 0x0) }, + { "Orchid", RGB_(0xda, 0x70, 0xd6) }, + { "Orchid1", RGB_(0xff, 0x83, 0xfa) }, + { "Orchid2", RGB_(0xee, 0x7a, 0xe9) }, + { "Orchid3", RGB_(0xcd, 0x69, 0xc9) }, + { "Orchid4", RGB_(0x8b, 0x47, 0x89) }, + { "PaleGoldenRod", RGB_(0xee, 0xe8, 0xaa) }, + { "PaleGreen", RGB_(0x98, 0xfb, 0x98) }, + { "PaleGreen1", RGB_(0x9a, 0xff, 0x9a) }, + { "PaleGreen2", RGB_(0x90, 0xee, 0x90) }, + { "PaleGreen3", RGB_(0x7c, 0xcd, 0x7c) }, + { "PaleGreen4", RGB_(0x54, 0x8b, 0x54) }, + { "PaleTurquoise", RGB_(0xaf, 0xee, 0xee) }, + { "PaleTurquoise1", RGB_(0xbb, 0xff, 0xff) }, + { "PaleTurquoise2", RGB_(0xae, 0xee, 0xee) }, + { "PaleTurquoise3", RGB_(0x96, 0xcd, 0xcd) }, + { "PaleTurquoise4", RGB_(0x66, 0x8b, 0x8b) }, + { "PaleVioletRed", RGB_(0xdb, 0x70, 0x93) }, + { "PaleVioletRed1", RGB_(0xff, 0x82, 0xab) }, + { "PaleVioletRed2", RGB_(0xee, 0x79, 0x9f) }, + { "PaleVioletRed3", RGB_(0xcd, 0x68, 0x89) }, + { "PaleVioletRed4", RGB_(0x8b, 0x47, 0x5d) }, + { "PapayaWhip", RGB_(0xff, 0xef, 0xd5) }, + { "PeachPuff", RGB_(0xff, 0xda, 0xb9) }, + { "PeachPuff1", RGB_(0xff, 0xda, 0xb9) }, + { "PeachPuff2", RGB_(0xee, 0xcb, 0xad) }, + { "PeachPuff3", RGB_(0xcd, 0xaf, 0x95) }, + { "PeachPuff4", RGB_(0x8b, 0x77, 0x65) }, + { "Peru", RGB_(0xcd, 0x85, 0x3f) }, + { "Pink", RGB_(0xff, 0xc0, 0xcb) }, + { "Pink1", RGB_(0xff, 0xb5, 0xc5) }, + { "Pink2", RGB_(0xee, 0xa9, 0xb8) }, + { "Pink3", RGB_(0xcd, 0x91, 0x9e) }, + { "Pink4", RGB_(0x8b, 0x63, 0x6c) }, + { "Plum", RGB_(0xdd, 0xa0, 0xdd) }, + { "Plum1", RGB_(0xff, 0xbb, 0xff) }, + { "Plum2", RGB_(0xee, 0xae, 0xee) }, + { "Plum3", RGB_(0xcd, 0x96, 0xcd) }, + { "Plum4", RGB_(0x8b, 0x66, 0x8b) }, + { "PowderBlue", RGB_(0xb0, 0xe0, 0xe6) }, + { "Purple", RGB_(0x80, 0x00, 0x80) }, + { "Purple1", RGB_(0x9b, 0x30, 0xff) }, + { "Purple2", RGB_(0x91, 0x2c, 0xee) }, + { "Purple3", RGB_(0x7d, 0x26, 0xcd) }, + { "Purple4", RGB_(0x55, 0x1a, 0x8b) }, + { "RebeccaPurple", RGB_(0x66, 0x33, 0x99) }, + { "Red", RGB_(0xff, 0x00, 0x00) }, + { "Red1", RGB_(0xff, 0x0, 0x0) }, + { "Red2", RGB_(0xee, 0x0, 0x0) }, + { "Red3", RGB_(0xcd, 0x0, 0x0) }, + { "Red4", RGB_(0x8b, 0x0, 0x0) }, + { "RosyBrown", RGB_(0xbc, 0x8f, 0x8f) }, + { "RosyBrown1", RGB_(0xff, 0xc1, 0xc1) }, + { "RosyBrown2", RGB_(0xee, 0xb4, 0xb4) }, + { "RosyBrown3", RGB_(0xcd, 0x9b, 0x9b) }, + { "RosyBrown4", RGB_(0x8b, 0x69, 0x69) }, + { "RoyalBlue", RGB_(0x41, 0x69, 0xe1) }, + { "RoyalBlue1", RGB_(0x48, 0x76, 0xff) }, + { "RoyalBlue2", RGB_(0x43, 0x6e, 0xee) }, + { "RoyalBlue3", RGB_(0x3a, 0x5f, 0xcd) }, + { "RoyalBlue4", RGB_(0x27, 0x40, 0x8b) }, + { "SaddleBrown", RGB_(0x8b, 0x45, 0x13) }, + { "Salmon", RGB_(0xfa, 0x80, 0x72) }, + { "Salmon1", RGB_(0xff, 0x8c, 0x69) }, + { "Salmon2", RGB_(0xee, 0x82, 0x62) }, + { "Salmon3", RGB_(0xcd, 0x70, 0x54) }, + { "Salmon4", RGB_(0x8b, 0x4c, 0x39) }, + { "SandyBrown", RGB_(0xf4, 0xa4, 0x60) }, + { "SeaGreen", RGB_(0x2e, 0x8b, 0x57) }, + { "SeaGreen1", RGB_(0x54, 0xff, 0x9f) }, + { "SeaGreen2", RGB_(0x4e, 0xee, 0x94) }, + { "SeaGreen3", RGB_(0x43, 0xcd, 0x80) }, + { "SeaGreen4", RGB_(0x2e, 0x8b, 0x57) }, + { "SeaShell", RGB_(0xff, 0xf5, 0xee) }, + { "Seashell1", RGB_(0xff, 0xf5, 0xee) }, + { "Seashell2", RGB_(0xee, 0xe5, 0xde) }, + { "Seashell3", RGB_(0xcd, 0xc5, 0xbf) }, + { "Seashell4", RGB_(0x8b, 0x86, 0x82) }, + { "Sienna", RGB_(0xa0, 0x52, 0x2d) }, + { "Sienna1", RGB_(0xff, 0x82, 0x47) }, + { "Sienna2", RGB_(0xee, 0x79, 0x42) }, + { "Sienna3", RGB_(0xcd, 0x68, 0x39) }, + { "Sienna4", RGB_(0x8b, 0x47, 0x26) }, + { "Silver", RGB_(0xc0, 0xc0, 0xc0) }, + { "SkyBlue", RGB_(0x87, 0xce, 0xeb) }, + { "SkyBlue1", RGB_(0x87, 0xce, 0xff) }, + { "SkyBlue2", RGB_(0x7e, 0xc0, 0xee) }, + { "SkyBlue3", RGB_(0x6c, 0xa6, 0xcd) }, + { "SkyBlue4", RGB_(0x4a, 0x70, 0x8b) }, + { "SlateBlue", RGB_(0x6a, 0x5a, 0xcd) }, + { "SlateBlue1", RGB_(0x83, 0x6f, 0xff) }, + { "SlateBlue2", RGB_(0x7a, 0x67, 0xee) }, + { "SlateBlue3", RGB_(0x69, 0x59, 0xcd) }, + { "SlateBlue4", RGB_(0x47, 0x3c, 0x8b) }, + { "SlateGray", RGB_(0x70, 0x80, 0x90) }, + { "SlateGray1", RGB_(0xc6, 0xe2, 0xff) }, + { "SlateGray2", RGB_(0xb9, 0xd3, 0xee) }, + { "SlateGray3", RGB_(0x9f, 0xb6, 0xcd) }, + { "SlateGray4", RGB_(0x6c, 0x7b, 0x8b) }, + { "SlateGrey", RGB_(0x70, 0x80, 0x90) }, + { "Snow", RGB_(0xff, 0xfa, 0xfa) }, + { "Snow1", RGB_(0xff, 0xfa, 0xfa) }, + { "Snow2", RGB_(0xee, 0xe9, 0xe9) }, + { "Snow3", RGB_(0xcd, 0xc9, 0xc9) }, + { "Snow4", RGB_(0x8b, 0x89, 0x89) }, + { "SpringGreen", RGB_(0x00, 0xff, 0x7f) }, + { "SpringGreen1", RGB_(0x0, 0xff, 0x7f) }, + { "SpringGreen2", RGB_(0x0, 0xee, 0x76) }, + { "SpringGreen3", RGB_(0x0, 0xcd, 0x66) }, + { "SpringGreen4", RGB_(0x0, 0x8b, 0x45) }, + { "SteelBlue", RGB_(0x46, 0x82, 0xb4) }, + { "SteelBlue1", RGB_(0x63, 0xb8, 0xff) }, + { "SteelBlue2", RGB_(0x5c, 0xac, 0xee) }, + { "SteelBlue3", RGB_(0x4f, 0x94, 0xcd) }, + { "SteelBlue4", RGB_(0x36, 0x64, 0x8b) }, + { "Tan", RGB_(0xd2, 0xb4, 0x8c) }, + { "Tan1", RGB_(0xff, 0xa5, 0x4f) }, + { "Tan2", RGB_(0xee, 0x9a, 0x49) }, + { "Tan3", RGB_(0xcd, 0x85, 0x3f) }, + { "Tan4", RGB_(0x8b, 0x5a, 0x2b) }, + { "Teal", RGB_(0x00, 0x80, 0x80) }, + { "Thistle", RGB_(0xd8, 0xbf, 0xd8) }, + { "Thistle1", RGB_(0xff, 0xe1, 0xff) }, + { "Thistle2", RGB_(0xee, 0xd2, 0xee) }, + { "Thistle3", RGB_(0xcd, 0xb5, 0xcd) }, + { "Thistle4", RGB_(0x8b, 0x7b, 0x8b) }, + { "Tomato", RGB_(0xff, 0x63, 0x47) }, + { "Tomato1", RGB_(0xff, 0x63, 0x47) }, + { "Tomato2", RGB_(0xee, 0x5c, 0x42) }, + { "Tomato3", RGB_(0xcd, 0x4f, 0x39) }, + { "Tomato4", RGB_(0x8b, 0x36, 0x26) }, + { "Turquoise", RGB_(0x40, 0xe0, 0xd0) }, + { "Turquoise1", RGB_(0x0, 0xf5, 0xff) }, + { "Turquoise2", RGB_(0x0, 0xe5, 0xee) }, + { "Turquoise3", RGB_(0x0, 0xc5, 0xcd) }, + { "Turquoise4", RGB_(0x0, 0x86, 0x8b) }, + { "Violet", RGB_(0xee, 0x82, 0xee) }, + { "VioletRed", RGB_(0xd0, 0x20, 0x90) }, + { "VioletRed1", RGB_(0xff, 0x3e, 0x96) }, + { "VioletRed2", RGB_(0xee, 0x3a, 0x8c) }, + { "VioletRed3", RGB_(0xcd, 0x32, 0x78) }, + { "VioletRed4", RGB_(0x8b, 0x22, 0x52) }, + { "WebGray", RGB_(0x80, 0x80, 0x80) }, + { "WebGreen", RGB_(0x0, 0x80, 0x0) }, + { "WebGrey", RGB_(0x80, 0x80, 0x80) }, + { "WebMaroon", RGB_(0x80, 0x0, 0x0) }, + { "WebPurple", RGB_(0x80, 0x0, 0x80) }, + { "Wheat", RGB_(0xf5, 0xde, 0xb3) }, + { "Wheat1", RGB_(0xff, 0xe7, 0xba) }, + { "Wheat2", RGB_(0xee, 0xd8, 0xae) }, + { "Wheat3", RGB_(0xcd, 0xba, 0x96) }, + { "Wheat4", RGB_(0x8b, 0x7e, 0x66) }, + { "White", RGB_(0xff, 0xff, 0xff) }, + { "WhiteSmoke", RGB_(0xf5, 0xf5, 0xf5) }, + { "X11Gray", RGB_(0xbe, 0xbe, 0xbe) }, + { "X11Green", RGB_(0x0, 0xff, 0x0) }, + { "X11Grey", RGB_(0xbe, 0xbe, 0xbe) }, + { "X11Maroon", RGB_(0xb0, 0x30, 0x60) }, + { "X11Purple", RGB_(0xa0, 0x20, 0xf0) }, + { "Yellow", RGB_(0xff, 0xff, 0x00) }, + { "Yellow1", RGB_(0xff, 0xff, 0x0) }, + { "Yellow2", RGB_(0xee, 0xee, 0x0) }, + { "Yellow3", RGB_(0xcd, 0xcd, 0x0) }, + { "Yellow4", RGB_(0x8b, 0x8b, 0x0) }, + { "YellowGreen", RGB_(0x9a, 0xcd, 0x32) }, { NULL, 0 }, }; @@ -8221,7 +8489,7 @@ color_name_table_T color_name_table[] = { /// /// @param[in] name string value to convert to RGB /// return the hex value or -1 if could not find a correct value -RgbValue name_to_color(const uint8_t *name) +RgbValue name_to_color(const char_u *name) { if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2]) @@ -8247,24 +8515,21 @@ RgbValue name_to_color(const uint8_t *name) /// Gets highlight description for id `attr_id` as a map. Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Error *err) { - HlAttrs attrs = HLATTRS_INIT; + HlAttrs *aep = NULL; Dictionary dic = ARRAY_DICT_INIT; if (attr_id == 0) { - goto end; + return dic; } - attrentry_T *aep = syn_cterm_attr2entry((int)attr_id); + aep = syn_cterm_attr2entry((int)attr_id); if (!aep) { api_set_error(err, kErrorTypeException, "Invalid attribute id: %" PRId64, attr_id); return dic; } - attrs = attrentry2hlattrs(aep, rgb); - -end: - return hlattrs2dict(attrs); + return hlattrs2dict(aep, rgb); } diff --git a/src/nvim/syntax.h b/src/nvim/syntax.h index bb733ead30..f8282955a6 100644 --- a/src/nvim/syntax.h +++ b/src/nvim/syntax.h @@ -6,19 +6,6 @@ #include "nvim/buffer_defs.h" #include "nvim/ex_cmds_defs.h" -/// Terminal highlighting attribute bits. -/// Attributes above HL_ALL are used for syntax highlighting. -/// \addtogroup HL_ATTRIBUTES -/// @{ -#define HL_NORMAL 0x00 -#define HL_INVERSE 0x01 -#define HL_BOLD 0x02 -#define HL_ITALIC 0x04 -#define HL_UNDERLINE 0x08 -#define HL_UNDERCURL 0x10 -#define HL_STANDOUT 0x20 -/// @} - #define HL_CONTAINED 0x01 /* not used on toplevel */ #define HL_TRANSP 0x02 /* has no highlighting */ #define HL_ONELINE 0x04 /* match within one line only */ @@ -45,6 +32,9 @@ typedef struct { } color_name_table_T; extern color_name_table_T color_name_table[]; +/// Array of highlight definitions, used for unit testing +extern const char *const highlight_init_cmdline[]; + #ifdef INCLUDE_GENERATED_DECLARATIONS # include "syntax.h.generated.h" #endif diff --git a/src/nvim/syntax_defs.h b/src/nvim/syntax_defs.h index 7260853703..63089a62af 100644 --- a/src/nvim/syntax_defs.h +++ b/src/nvim/syntax_defs.h @@ -68,21 +68,4 @@ struct syn_state { * may have made the state invalid */ }; -// Structure shared between syntax.c, screen.c -typedef struct attr_entry { - int16_t rgb_ae_attr, cterm_ae_attr; // HL_BOLD, etc. - RgbValue rgb_fg_color, rgb_bg_color, rgb_sp_color; - int cterm_fg_color, cterm_bg_color; -} attrentry_T; - -#define ATTRENTRY_INIT { \ - .rgb_ae_attr = 0, \ - .cterm_ae_attr = 0, \ - .rgb_fg_color = -1, \ - .rgb_bg_color = -1, \ - .rgb_sp_color = -1, \ - .cterm_fg_color = 0, \ - .cterm_bg_color = 0, \ -} - #endif // NVIM_SYNTAX_DEFS_H diff --git a/src/nvim/tag.c b/src/nvim/tag.c index be9d621c7d..f23465e501 100644 --- a/src/nvim/tag.c +++ b/src/nvim/tag.c @@ -667,7 +667,7 @@ do_tag ( fname = xmalloc(MAXPATHL + 1); cmd = xmalloc(CMDBUFFSIZE + 1); - list = tv_list_alloc(); + list = tv_list_alloc(num_matches); for (i = 0; i < num_matches; ++i) { int len, cmd_len; diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c index dfa758f41e..276b47536f 100644 --- a/src/nvim/terminal.c +++ b/src/nvim/terminal.c @@ -61,7 +61,6 @@ #include "nvim/edit.h" #include "nvim/mouse.h" #include "nvim/memline.h" -#include "nvim/mark.h" #include "nvim/map.h" #include "nvim/misc1.h" #include "nvim/move.h" @@ -181,13 +180,14 @@ void terminal_init(void) vterm_state_get_palette_color(state, color_index, &color); } map_put(int, int)(color_indexes, - RGB(color.red, color.green, color.blue), color_index + 1); + RGB_(color.red, color.green, color.blue), + color_index + 1); } VTermColor fg, bg; vterm_state_get_default_colors(state, &fg, &bg); - default_vt_fg = RGB(fg.red, fg.green, fg.blue); - default_vt_bg = RGB(bg.red, bg.green, bg.blue); + default_vt_fg = RGB_(fg.red, fg.green, fg.blue); + default_vt_bg = RGB_(bg.red, bg.green, bg.blue); default_vt_bg_rgb = bg; vterm_free(vt); } @@ -376,8 +376,6 @@ void terminal_enter(void) // Ensure the terminal is properly sized. terminal_resize(s->term, 0, 0); - checkpcmark(); - setpcmark(); int save_state = State; s->save_rd = RedrawingDisabled; State = TERM_FOCUS; @@ -463,6 +461,10 @@ static int terminal_execute(VimState *state, int key) } break; + case K_COMMAND: + do_cmdline(NULL, getcmdkeycmd, NULL, 0); + break; + case Ctrl_N: if (s->got_bsl) { return 0; @@ -568,8 +570,8 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, VTermScreenCell cell; fetch_cell(term, row, col, &cell); // Get the rgb value set by libvterm. - int vt_fg = RGB(cell.fg.red, cell.fg.green, cell.fg.blue); - int vt_bg = RGB(cell.bg.red, cell.bg.green, cell.bg.blue); + int vt_fg = RGB_(cell.fg.red, cell.fg.green, cell.fg.blue); + int vt_bg = RGB_(cell.bg.red, cell.bg.green, cell.bg.blue); vt_fg = vt_fg != default_vt_fg ? vt_fg : - 1; vt_bg = vt_bg != default_vt_bg ? vt_bg : - 1; // Since libvterm does not expose the color index used by the program, we @@ -588,7 +590,7 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int attr_id = 0; if (hl_attrs || vt_fg != -1 || vt_bg != -1) { - attr_id = get_attr_entry(&(attrentry_T) { + attr_id = get_attr_entry(&(HlAttrs) { .cterm_ae_attr = (int16_t)hl_attrs, .cterm_fg_color = vt_fg_idx, .cterm_bg_color = vt_bg_idx, diff --git a/src/nvim/testdir/Makefile b/src/nvim/testdir/Makefile index 111bd172ef..a31e1843fc 100644 --- a/src/nvim/testdir/Makefile +++ b/src/nvim/testdir/Makefile @@ -2,44 +2,61 @@ # Makefile to run all tests for Vim # -NVIM_PRG ?= ../../../build/bin/nvim -TMPDIR ?= Xtest-tmpdir -SCRIPTSOURCE := ../../../runtime +ifeq ($(OS),Windows_NT) + NVIM_PRG ?= ../../../build/bin/nvim.exe +else + NVIM_PRG ?= ../../../build/bin/nvim +endif +ROOT := ../../.. export SHELL := sh export NVIM_PRG := $(NVIM_PRG) -export TMPDIR +export TMPDIR := $(abspath ../../../Xtest-tmpdir) -SCRIPTS ?= \ - test13.out \ +SCRIPTS_DEFAULT = \ test14.out \ - test17.out \ test24.out \ - test32.out \ test37.out \ test40.out \ test42.out \ test48.out \ test49.out \ test52.out \ - test53.out \ test64.out \ - test73.out \ - test79.out \ + +ifneq ($(OS),Windows_NT) + SCRIPTS_DEFAULTS := $(SCRIPTS_DEFAULT) \ + test17.out \ + +endif + +SCRIPTS ?= $(SCRIPTS_DEFAULT) # Tests using runtest.vim. # Keep test_alot*.res as the last one, sort the others. NEW_TESTS ?= \ + test_arabic.res \ test_autocmd.res \ test_bufwintabinfo.res \ + test_changedtick.res \ test_charsearch.res \ + test_cindent.res \ + test_close_count.res \ test_cmdline.res \ test_command_count.res \ test_cscope.res \ + test_curswant.res \ test_digraph.res \ + test_edit.res \ + test_erasebackword.res \ + test_exists.res \ test_diffmode.res \ test_farsi.res \ + test_file_size.res \ test_filter_map.res \ + test_find_complete.res \ + test_fixeol.res \ + test_findfile.res \ test_fnameescape.res \ test_fold.res \ test_ga.res \ @@ -53,27 +70,38 @@ NEW_TESTS ?= \ test_hlsearch.res \ test_increment.res \ test_increment_dbcs.res \ + test_ins_complete.res \ test_lambda.res \ test_langmap.res \ + test_let.res \ + test_lineending.res \ + test_listdict.res \ + test_listchars.res \ + test_makeencoding.res \ test_marks.res \ test_match.res \ test_matchadd_conceal.res \ - test_matchadd_conceal_utf8.res \ test_mksession.res \ - test_mksession_utf8.res \ test_nested_function.res \ test_normal.res \ + test_number.res \ + test_options.res \ test_profile.res \ + test_put.res \ test_quickfix.res \ + test_recover.res \ test_retab.res \ + test_scrollbind.res \ test_search.res \ test_signs.res \ test_smartindent.res \ + test_spell.res \ test_stat.res \ test_startup.res \ - test_startup_utf8.res \ test_substitute.res \ test_syntax.res \ + test_system.res \ + test_tab.res \ test_tabpage.res \ test_textobjects.res \ test_timers.res \ @@ -81,8 +109,11 @@ NEW_TESTS ?= \ test_usercommands.res \ test_vimscript.res \ test_visual.res \ + test_winbuf_close.res \ test_window_id.res \ test_writefile.res \ + test_alot_latin.res \ + test_alot_utf8.res \ test_alot.res SCRIPTS_GUI := test16.out @@ -103,7 +134,7 @@ ifdef USE_VALGRIND $(VALGRIND_TOOL) \ --suppressions=../../.valgrind.supp \ --error-exitcode=123 \ - --log-file=valgrind.\%p.$* \ + --log-file=valgrind-\%p.$* \ $(VGDB) \ --trace-children=yes else @@ -121,7 +152,8 @@ nongui: nolog $(SCRIPTS) newtests report gui: nolog $(SCRIPTS) $(SCRIPTS_GUI) newtests report .gdbinit: - echo 'set $$_exitcode = -1\nrun\nif $$_exitcode != -1\n quit\nend' > .gdbinit + @echo "[OLDTEST-PREP] Setting up .gdbinit" + @echo 'set $$_exitcode = -1\nrun\nif $$_exitcode != -1\n quit\nend' > .gdbinit report: @echo @@ -140,7 +172,7 @@ $(SCRIPTS) $(SCRIPTS_GUI): $(NVIM_PRG) test1.out RM_ON_RUN := test.out X* viminfo RM_ON_START := test.ok -RUN_VIM := VIMRUNTIME=$(SCRIPTSOURCE); export VIMRUNTIME; $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE -i viminfo --headless --noplugin -s dotest.in +RUN_VIM := $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE -i viminfo --headless --noplugin -s dotest.in clean: -rm -rf *.out \ @@ -148,6 +180,7 @@ clean: *.res \ *.rej \ *.orig \ + *.tlog \ test.log \ messages \ $(RM_ON_RUN) \ @@ -160,59 +193,32 @@ clean: del test1.out: .gdbinit test1.in - -rm -rf $*.failed $(RM_ON_RUN) $(RM_ON_START) wrongtermsize - mkdir -p $(TMPDIR) - $(RUN_VIM) $*.in - @/bin/sh -c "if test -e wrongtermsize; then \ - echo; \ - echo test1 FAILED - terminal size must be 80x24 or larger; \ - echo; exit 1; \ - elif diff test.out $*.ok; then \ - mv -f test.out $*.out; \ - else \ - echo; \ - echo test1 FAILED - Something basic is wrong; \ - echo; \ - exit 1; \ - fi" - -rm -rf X* viminfo + @echo "[OLDTEST-PREP] Running test1" + @rm -rf $*.failed $(RM_ON_RUN) $(RM_ON_START) wrongtermsize + @mkdir -p $(TMPDIR) + @/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIM) $*.in + @rm -f wrongtermsize + @rm -rf X* viminfo %.out: %.in .gdbinit - -rm -rf $*.failed test.ok $(RM_ON_RUN) - mkdir -p $(TMPDIR) - cp $*.ok test.ok - # Sleep a moment to avoid that the xterm title is messed up. - # 200 msec is sufficient, but only modern sleep supports a fraction of - # a second, fall back to a second if it fails. - @-/bin/sh -c "sleep .2 > /dev/null 2>&1 || sleep 1" - $(RUN_VIM) $*.in - - # Check if the test.out file matches test.ok. - @/bin/sh -c "if test -f test.out; then \ - if diff -u test.out $*.ok; then \ - mv -f test.out $*.out; \ - else \ - echo $* FAILED >> test.log; \ - mv -f test.out $*.failed; \ - fi; \ - else \ - echo $* NO OUTPUT >>test.log; \ - fi" - @/bin/sh -c "if test -f valgrind; then \ - mv -f valgrind valgrind.$*; \ - fi" - -rm -rf X* test.ok viminfo + @echo "[OLDESTTEST] Running" $* + @rm -rf $*.failed test.ok $(RM_ON_RUN) + @mkdir -p $(TMPDIR) + @cp $*.ok test.ok + @/bin/sh runnvim.sh --oldesttest $(ROOT) $(NVIM_PRG) $* $(RUN_VIM) $*.in + @rm -rf X* test.ok viminfo test49.out: test49.vim nolog: - -rm -f test.log messages + @echo "[OLDTEST-PREP] Removing test.log and messages" + @rm -f test.log messages # New style of tests uses Vim script with assert calls. These are easier # to write and a lot easier to read and debug. # Limitation: Only works with the +eval feature. -RUN_VIMTEST = VIMRUNTIME=$(SCRIPTSOURCE); export VIMRUNTIME; $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE --headless --noplugin +RUN_VIMTEST = $(TOOL) $(NVIM_PRG) -u unix.vim -U NONE --headless --noplugin newtests: newtestssilent @/bin/sh -c "if test -f messages && grep -q 'FAILED' messages; then \ @@ -222,5 +228,6 @@ newtests: newtestssilent newtestssilent: $(NEW_TESTS) %.res: %.vim .gdbinit - mkdir -p $(TMPDIR) - $(RUN_VIMTEST) -u NONE -S runtest.vim $*.vim + @echo "[OLDTEST] Running" $* + @mkdir -p $(TMPDIR) + @/bin/sh runnvim.sh $(ROOT) $(NVIM_PRG) $* $(RUN_VIMTEST) -u NONE -S runtest.vim $*.vim diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh new file mode 100755 index 0000000000..43556f3ad3 --- /dev/null +++ b/src/nvim/testdir/runnvim.sh @@ -0,0 +1,82 @@ +#!/bin/sh + +main() {( + local separator="================================================================================" + local oldesttest= + if test "$1" = "--oldesttest" ; then + shift + oldesttest=1 + fi + local root="$1" ; shift + local nvim_prg="$1" ; shift + local test_name="$1" ; shift + + local tlog="$test_name.tlog" + + export NVIM_TEST_ARGC=$# + local arg + local i=0 + for arg ; do + eval "export NVIM_TEST_ARG$i=\"\$arg\"" + i=$(( i+1 )) + done + + export CI_DIR="$root/ci" + export BUILD_DIR="$(dirname "$nvim_prg")/.." + export FAILED=0 + + . "$CI_DIR/common/suite.sh" + . "$CI_DIR/common/test.sh" + + export VIMRUNTIME="$root/runtime" + if ! "$nvim_prg" \ + -u NONE -i NONE \ + --headless \ + --cmd 'set shortmess+=I noswapfile noundofile nomore' \ + -S runnvim.vim \ + "$tlog" > "out-$tlog" 2> "err-$tlog" + then + fail "$test_name" F "Nvim exited with non-zero code" + fi + echo "Stdout of :terminal runner" >> "$tlog" + echo "$separator" >> "$tlog" + cat "out-$tlog" >> "$tlog" + echo "$separator" >> "$tlog" + echo "Stderr of :terminal runner" >> "$tlog" + echo "$separator" >> "$tlog" + cat "err-$tlog" >> "$tlog" + echo "$separator" >> "$tlog" + if test "$oldesttest" = 1 ; then + if ! diff -q test.out "$test_name.ok" > /dev/null 2>&1 ; then + if test -f test.out ; then + fail "$test_name" F "Oldest test .out file differs from .ok file" + echo "Diff between test.out and $test_name.ok" >> "$tlog" + echo "$separator" >> "$tlog" + diff -a test.out "$test_name.ok" >> "$tlog" + echo "$separator" >> "$tlog" + else + echo "No output in test.out" >> "$tlog" + fi + fi + fi + if test "$FAILED" = 1 ; then + travis_fold start "$NVIM_TEST_CURRENT_SUITE/$test_name" + fi + valgrind_check . + if test -n "$LOG_DIR" ; then + asan_check "$LOG_DIR" + fi + check_core_dumps + if test "$FAILED" = 1 ; then + cat "$tlog" + fi + rm -f "$tlog" + if test "$FAILED" = 1 ; then + travis_fold end "$NVIM_TEST_CURRENT_SUITE/$test_name" + fi + if test "$FAILED" = 1 ; then + echo "Test $test_name failed, see output above and summary for more details" >> test.log + fi +)} + +main "$@" diff --git a/src/nvim/testdir/runnvim.vim b/src/nvim/testdir/runnvim.vim new file mode 100644 index 0000000000..396a3a6477 --- /dev/null +++ b/src/nvim/testdir/runnvim.vim @@ -0,0 +1,55 @@ +let s:logger = {'d_events': []} +function s:logger.on_stdout(id, data, event) + call add(self.d_events, [a:event, a:data]) +endfunction +let s:logger.on_stderr = s:logger.on_stdout +function s:logger.on_exit(id, data, event) + call add(self.d_events, [a:event, ['']]) +endfunction + +function Main() + let argc = +$NVIM_TEST_ARGC + let args = [] + for i in range(argc) + call add(args, eval("$NVIM_TEST_ARG" . i)) + endfor + set lines=25 + set columns=80 + enew + let job = termopen(args, s:logger) + let results = jobwait([job], 5 * 60 * 1000) + " TODO(ZyX-I): Get colors + let screen = getline(1, '$') + bwipeout! + let stringified_events = map(s:logger.d_events, + \'v:val[0] . ": " . ' . + \'join(map(v:val[1], '. + \ '''substitute(v:val, '. + \ '"\\v\\C(\\p@!.|\\<)", '. + \ '"\\=printf(\"<%x>\", '. + \ 'char2nr(submatch(0)))", '. + \ '"")''), '. + \ '''\n'')') + call setline(1, [ + \ 'Job exited with code ' . results[0], + \ printf('Screen (%u lines)', len(screen)), + \ repeat('=', 80), + \] + screen + [ + \ repeat('=', 80), + \ printf('Events (%u lines):', len(stringified_events)), + \ repeat('=', 80), + \] + stringified_events + [ + \ repeat('=', 80), + \]) + write + if results[0] != 0 + if results[0] != -1 + call jobstop(job) + endif + cquit + else + qall + endif +endfunction + +call Main() diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim index 4de1345679..7090be7726 100644 --- a/src/nvim/testdir/runtest.vim +++ b/src/nvim/testdir/runtest.vim @@ -4,7 +4,7 @@ " " To execute only specific test functions, add a second argument. It will be " matched against the names of the Test_ function. E.g.: -" ../vim -u NONE -S runtest.vim test_channel.vim open_delay +" ../vim -u NONE -S runtest.vim test_channel.vim open_delay " The output can be found in the "messages" file. " " The test script may contain anything, only functions that start with @@ -42,10 +42,11 @@ endif " Common with all tests on all systems. source setup.vim +" For consistency run all tests with 'nocompatible' set. " This also enables use of line continuation. -set viminfo+=nviminfo +set nocp viminfo+=nviminfo -" Use utf-8 or latin1 be default, instead of whatever the system default +" Use utf-8 or latin1 by default, instead of whatever the system default " happens to be. Individual tests can overrule this at the top of the file. if has('multi_byte') set encoding=utf-8 @@ -62,22 +63,42 @@ lang mess C " Always use forward slashes. set shellslash -" Make sure $HOME does not get read or written. -let $HOME = '/does/not/exist' - -" Prepare for calling garbagecollect_for_testing(). +" Prepare for calling test_garbagecollect_now(). let v:testing = 1 -" Align Nvim defaults to Vim. -set directory^=. -set backspace= -set nohidden smarttab noautoindent noautoread complete-=i noruler noshowcmd -set listchars=eol:$ -" Prevent Nvim log from writing to stderr. -let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' +" Support function: get the alloc ID by name. +function GetAllocId(name) + exe 'split ' . s:srcdir . '/alloc.h' + let top = search('typedef enum') + if top == 0 + call add(v:errors, 'typedef not found in alloc.h') + endif + let lnum = search('aid_' . a:name . ',') + if lnum == 0 + call add(v:errors, 'Alloc ID ' . a:name . ' not defined') + endif + close + return lnum - top - 1 +endfunc func RunTheTest(test) echo 'Executing ' . a:test + + " Avoid stopping at the "hit enter" prompt + set nomore + + " Avoid a three second wait when a message is about to be overwritten by the + " mode message. + set noshowmode + + " Some tests wipe out buffers. To be consistent, always wipe out all + " buffers. + %bwipe! + + " The test may change the current directory. Save and restore the + " directory after executing the test. + let save_cwd = getcwd() + if exists("*SetUp") try call SetUp() @@ -88,14 +109,21 @@ func RunTheTest(test) call add(s:messages, 'Executing ' . a:test) let s:done += 1 - try + + if a:test =~ 'Test_nocatch_' + " Function handles errors itself. This avoids skipping commands after the + " error. exe 'call ' . a:test - catch /^\cskipped/ - call add(s:messages, ' Skipped') - call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '', '')) - catch - call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint) - endtry + else + try + exe 'call ' . a:test + catch /^\cskipped/ + call add(s:messages, ' Skipped') + call add(s:skipped, 'SKIPPED ' . a:test . ': ' . substitute(v:exception, '^\S*\s\+', '', '')) + catch + call add(v:errors, 'Caught exception in ' . a:test . ': ' . v:exception . ' @ ' . v:throwpoint) + endtry + endif if exists("*TearDown") try @@ -105,7 +133,14 @@ func RunTheTest(test) endtry endif - " Close any extra windows and make the current one not modified. + " Clear any autocommands + au! + + " Close any extra tab pages and windows and make the current one not modified. + while tabpagenr('$') > 1 + quit! + endwhile + while 1 let wincount = winnr('$') if wincount == 1 @@ -118,7 +153,8 @@ func RunTheTest(test) break endif endwhile - set nomodified + + exe 'cd ' . save_cwd endfunc func AfterTheTest() @@ -197,8 +233,16 @@ endif " Names of flaky tests. let s:flaky = [ - \ 'Test_with_partial_callback()', + \ 'Test_exit_callback_interval()', \ 'Test_oneshot()', + \ 'Test_out_cb()', + \ 'Test_paused()', + \ 'Test_quoteplus()', + \ 'Test_reltime()', + \ 'Test_terminal_composing_unicode()', + \ 'Test_terminal_redir_file()', + \ 'Test_terminal_tmap()', + \ 'Test_with_partial_callback()', \ 'Test_lambda_with_timer()', \ ] @@ -215,12 +259,30 @@ endif " Execute the tests in alphabetical order. for s:test in sort(s:tests) + " Silence, please! + set belloff=all + call RunTheTest(s:test) if len(v:errors) > 0 && index(s:flaky, s:test) >= 0 + call add(s:messages, 'Found errors in ' . s:test . ':') + call extend(s:messages, v:errors) call add(s:messages, 'Flaky test failed, running it again') + let first_run = v:errors + + " Flakiness is often caused by the system being very busy. Sleep a couple + " of seconds to have a higher chance of succeeding the second time. + sleep 2 + let v:errors = [] call RunTheTest(s:test) + if len(v:errors) > 0 + let second_run = v:errors + let v:errors = ['First run:'] + call extend(v:errors, first_run) + call add(v:errors, 'Second run:') + call extend(v:errors, second_run) + endif endif call AfterTheTest() diff --git a/src/nvim/testdir/sautest/autoload/footest.vim b/src/nvim/testdir/sautest/autoload/footest.vim index f467bc376d..1e78963a10 100644 --- a/src/nvim/testdir/sautest/autoload/footest.vim +++ b/src/nvim/testdir/sautest/autoload/footest.vim @@ -1,4 +1,4 @@ -" Autoload script used by test55 and test60 +" Autoload script used by test_listdict.vim, test_exists.vim and test_let.vim let footest#x = 1 func footest#F() return 0 diff --git a/src/nvim/testdir/setup.vim b/src/nvim/testdir/setup.vim index 06f2199214..7d6dd0c7ce 100644 --- a/src/nvim/testdir/setup.vim +++ b/src/nvim/testdir/setup.vim @@ -1,8 +1,15 @@ " Common preparations for running tests. -set noruler -set noshowcmd -set belloff= +" Align Nvim defaults to Vim. +set sidescroll=0 +set directory^=. +set backspace= +set nohidden smarttab noautoindent noautoread complete-=i noruler noshowcmd +set listchars=eol:$ +set fillchars=vert:\|,fold:- +" Prevent Nvim log from writing to stderr. +let $NVIM_LOG_FILE = exists($NVIM_LOG_FILE) ? $NVIM_LOG_FILE : 'Xnvim.log' + " Make sure 'runtimepath' and 'packpath' does not include $HOME. set rtp=$VIM/vimfiles,$VIMRUNTIME,$VIM/vimfiles/after diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim index 72cfea96c6..4925b04a82 100644 --- a/src/nvim/testdir/shared.vim +++ b/src/nvim/testdir/shared.vim @@ -1,5 +1,25 @@ " Functions shared by several tests. +" {Nvim} +" Filepath captured from output may be truncated, like this: +" /home/va...estdir/Xtest-tmpdir/nvimxbXN4i/10 +" Get last 2 segments, then combine with $TMPDIR. +func! Fix_truncated_tmpfile(fname) + " sanity check + if $TMPDIR ==# '' + throw '$TMPDIR is empty' + endif + let tmpdir_tail = fnamemodify(substitute($TMPDIR, '[\/]\+$', '', 'g'), ':t') + if tmpdir_tail ==# '' + throw 'empty tmpdir_tail' + endif + if a:fname !~# tmpdir_tail + throw printf('$TMPDIR (%s) not in fname: %s', tmpdir_tail, a:fname) + endif + let last2segments = matchstr(a:fname, '[\/][^\/]\+[\/][^\/]\+$') + return $TMPDIR.last2segments +endfunc + " Get the name of the Python executable. " Also keeps it in s:python. func PythonProg() diff --git a/src/nvim/testdir/test32.in b/src/nvim/testdir/test32.in deleted file mode 100644 index 76bd9be889..0000000000 --- a/src/nvim/testdir/test32.in +++ /dev/null @@ -1,60 +0,0 @@ -Test for insert expansion - -:se cpt=.,w -* add-expands (word from next line) from other window -* add-expands (current buffer first) -* Local expansion, ends in an empty line (unless it becomes a global expansion) -* starts Local and switches to global add-expansion -:se cpt=.,w,i -* i-add-expands and switches to local -* add-expands lines (it would end in an empty line if it didn't ignored it self) -:se cpt=kXtestfile -* checks k-expansion, and file expansion (use Xtest11 instead of test11, -* because TEST11.OUT may match first on DOS) -:se cpt=w -* checks make_cyclic in other window -:se cpt=u nohid -* checks unloaded buffer expansion -* checks adding mode abortion -:se cpt=t,d -* tag expansion, define add-expansion interrupted -* t-expansion - -STARTTEST -:se backspace="" -:se cpt=.,w ff=unix | $-2,$w!Xtestfile | set ff& -:se cot= -nO#include "Xtestfile" -ru -O - - -:se cpt=.,w,i -kOM - -:se cpt=kXtestfile -:w Xtest11.one -:w Xtest11.two -OIXA -:" use CTRL-X CTRL-F to complete Xtest11.one, remove it and then use -:" CTRL-X CTRL-F again to verify this doesn't cause trouble. -OXddk -:se cpt=w -OST -:se cpt=u nohid -oOEN -unl -:se cpt=t,d def=^\\k* tags=Xtestfile notagbsearch -O -a -:wq! test.out -ENDTEST - -start of testfile -run1 -run2 -end of testfile - -test11 36Gepeto /Tag/ -asd test11file 36G -Makefile to run diff --git a/src/nvim/testdir/test32.ok b/src/nvim/testdir/test32.ok deleted file mode 100644 index afc4463fac..0000000000 --- a/src/nvim/testdir/test32.ok +++ /dev/null @@ -1,15 +0,0 @@ -#include "Xtestfile" -run1 run3 -run3 run3 - -Makefile to run3 -Makefile to run3 -Makefile to run3 -Xtest11.two -STARTTEST -ENDTEST -unless -test11file 36Gepeto /Tag/ asd -asd -run1 run2 - diff --git a/src/nvim/testdir/test53.in b/src/nvim/testdir/test53.in deleted file mode 100644 index 20b5d019af..0000000000 --- a/src/nvim/testdir/test53.in +++ /dev/null @@ -1,74 +0,0 @@ -Tests for string and html text objects. vim: set ft=vim : - -Note that the end-of-line moves the cursor to the next test line. - -Also test match() and matchstr() - -STARTTEST -/^start:/ -da" -0va'a'rx -02f`da` -0fXdi" -03f'vi'ry -:set quoteescape=+*- -di` -$F"va"oha"i"rz -:" -/^<begin -jfXdit -0fXdit -fXdat -0fXdat -dit -:" -:put =matchstr(\"abcd\", \".\", 0, 2) " b -:put =matchstr(\"abcd\", \"..\", 0, 2) " bc -:put =matchstr(\"abcd\", \".\", 2, 0) " c (zero and negative -> first match) -:put =matchstr(\"abcd\", \".\", 0, -1) " a -:put =match(\"abcd\", \".\", 0, 5) " -1 -:put =match(\"abcd\", \".\", 0, -1) " 0 -:put =match('abc', '.', 0, 1) " 0 -:put =match('abc', '.', 0, 2) " 1 -:put =match('abc', '.', 0, 3) " 2 -:put =match('abc', '.', 0, 4) " -1 -:put =match('abc', '.', 1, 1) " 1 -:put =match('abc', '.', 2, 1) " 2 -:put =match('abc', '.', 3, 1) " -1 -:put =match('abc', '$', 0, 1) " 3 -:put =match('abc', '$', 0, 2) " -1 -:put =match('abc', '$', 1, 1) " 3 -:put =match('abc', '$', 2, 1) " 3 -:put =match('abc', '$', 3, 1) " 3 -:put =match('abc', '$', 4, 1) " -1 -:put =match('abc', '\zs', 0, 1) " 0 -:put =match('abc', '\zs', 0, 2) " 1 -:put =match('abc', '\zs', 0, 3) " 2 -:put =match('abc', '\zs', 0, 4) " 3 -:put =match('abc', '\zs', 0, 5) " -1 -:put =match('abc', '\zs', 1, 1) " 1 -:put =match('abc', '\zs', 2, 1) " 2 -:put =match('abc', '\zs', 3, 1) " 3 -:put =match('abc', '\zs', 4, 1) " -1 -:/^start:/,/^end:/wq! test.out -ENDTEST - -start: "wo\"rd\\" foo -'foo' 'bar' 'piep' -bla bla `quote` blah -out " in "noXno" -"'" 'blah' rep 'buh' -bla `s*`d-`+++`l**` b`la -voo "nah" sdf " asdf" sdf " sdf" sd - -<begin> --<b>asdf<i>Xasdf</i>asdf</b>- --<b>asdX<i>a<i />sdf</i>asdf</b>- --<b>asdf<i>Xasdf</i>asdf</b>- --<b>asdX<i>as<b />df</i>asdf</b>- --<b> -innertext object -</b> -</begin> -SEARCH: -end: diff --git a/src/nvim/testdir/test53.ok b/src/nvim/testdir/test53.ok deleted file mode 100644 index d57d86bbb0..0000000000 --- a/src/nvim/testdir/test53.ok +++ /dev/null @@ -1,45 +0,0 @@ -start: foo -xxxxxxxxxxxx'piep' -bla bla blah -out " in "" -"'" 'blah'yyyyy'buh' -bla `` b`la -voo "zzzzzzzzzzzzzzzzzzzzzzzzzzzzsd - -<begin> --<b>asdf<i></i>asdf</b>- --<b></b>- --<b>asdfasdf</b>- --- --<b></b> -</begin> -b -bc -c -a --1 -0 -0 -1 -2 --1 -1 -2 --1 -3 --1 -3 -3 -3 --1 -0 -1 -2 -3 --1 -1 -2 -3 --1 -SEARCH: -end: diff --git a/src/nvim/testdir/test73.in b/src/nvim/testdir/test73.in deleted file mode 100644 index 9d50f7a789..0000000000 --- a/src/nvim/testdir/test73.in +++ /dev/null @@ -1,168 +0,0 @@ -Tests for find completion. - -STARTTEST -:set wildmode=full -:" Do all test in a separate window to avoid E211 when we recursively -:" delete the Xfind directory during cleanup -:" -:" This will cause a few errors, do it silently. -:set visualbell -:" -:" On windows a stale "Xfind" directory may exist, remove it so that -:" we start from a clean state. -:call delete("Xfind", "rf") -:new -:let cwd=getcwd() -:let test_out = cwd . '/test.out' -:call mkdir('Xfind') -:cd Xfind -:set path= -:find -:exec "w! " . test_out -:close -:new -:set path=. -:find -:exec "w >>" . test_out -:close -:new -:set path=.,, -:find -:exec "w >>" . test_out -:close -:new -:set path=./** -:find -:exec "w >>" . test_out -:close -:new -:" We shouldn't find any file at this point, test.out must be empty. -:call mkdir('in') -:cd in -:call mkdir('path') -:exec "cd " . cwd -:e Xfind/file.txt -SHoly Grail:w -:e Xfind/in/file.txt -SJimmy Hoffa:w -:e Xfind/in/stuff.txt -SAnother Holy Grail:w -:e Xfind/in/path/file.txt -SE.T.:w -:set path=Xfind/** -:find file -:exec "w >>" . test_out -:find file -:exec "w >>" . test_out -:find file -:exec "w >>" . test_out -:" Rerun the previous three find completions, using fullpath in 'path' -:exec "set path=" . cwd . "/Xfind/**" -:find file -:exec "w >>" . test_out -:find file -:exec "w >>" . test_out -:find file -:exec "w >>" . test_out -:" Same steps again, using relative and fullpath items that point to the same -:" recursive location. -:" This is to test that there are no duplicates in the completion list. -:exec "set path+=Xfind/**" -:find file -:exec "w >>" . test_out -:find file -:exec "w >>" . test_out -:find file -:exec "w >>" . test_out -:find file -:" Test find completion for directory of current buffer, which at this point -:" is Xfind/in/file.txt. -:set path=. -:find st -:exec "w >>" . test_out -:" Test find completion for empty path item ",," which is the current directory -:cd Xfind -:set path=,, -:find f -:exec "w >>" . test_out -:" Test shortening of -:" -:" foo/x/bar/voyager.txt -:" foo/y/bar/voyager.txt -:" -:" When current directory is above foo/ they should be shortened to (in order -:" of appearance): -:" -:" x/bar/voyager.txt -:" y/bar/voyager.txt -:call mkdir('foo') -:cd foo -:call mkdir('x') -:call mkdir('y') -:cd x -:call mkdir('bar') -:cd .. -:cd y -:call mkdir('bar') -:cd .. -:cd .. -:" We should now be in the Xfind directory -:e foo/x/bar/voyager.txt -SVoyager 1:w -:e foo/y/bar/voyager.txt -SVoyager 2:w -:exec "set path=" . cwd . "/Xfind/**" -:find voyager -:exec "w >>" . test_out -:find voyager -:exec "w >>" . test_out -:" -:" When current directory is .../foo/y/bar they should be shortened to (in -:" order of appearance): -:" -:" ./voyager.txt -:" x/bar/voyager.txt -:cd foo -:cd y -:cd bar -:find voyager -:exec "w >> " . test_out -:find voyager -:exec "w >> " . test_out -:" Check the opposite too: -:cd .. -:cd .. -:cd x -:cd bar -:find voyager -:exec "w >> " . test_out -:find voyager -:exec "w >> " . test_out -:" Check for correct handling of shorten_fname()'s behavior on windows -:exec "cd " . cwd . "/Xfind/in" -:find file -:exec "w >>" . test_out -:" Test for relative to current buffer 'path' item -:exec "cd " . cwd . "/Xfind/" -:set path=./path -:" Open the file where Jimmy Hoffa is found -:e in/file.txt -:" Find the file containing 'E.T.' in the Xfind/in/path directory -:find file -:exec "w >>" . test_out -:" -:" Test that completion works when path=.,, -:" -:set path=.,, -:" Open Jimmy Hoffa file -:e in/file.txt -:exec "w >>" . test_out -:" Search for the file containing Holy Grail in same directory as in/path.txt -:find stu -:exec "w >>" . test_out -:q -:exec "cd " . cwd -:call delete("Xfind", "rf") -:qa! -ENDTEST - diff --git a/src/nvim/testdir/test73.ok b/src/nvim/testdir/test73.ok deleted file mode 100644 index 90efab756f..0000000000 --- a/src/nvim/testdir/test73.ok +++ /dev/null @@ -1,21 +0,0 @@ -Holy Grail -Jimmy Hoffa -E.T. -Holy Grail -Jimmy Hoffa -E.T. -Holy Grail -Jimmy Hoffa -E.T. -Another Holy Grail -Holy Grail -Voyager 1 -Voyager 2 -Voyager 2 -Voyager 1 -Voyager 1 -Voyager 2 -Jimmy Hoffa -E.T. -Jimmy Hoffa -Another Holy Grail diff --git a/src/nvim/testdir/test79.in b/src/nvim/testdir/test79.in Binary files differdeleted file mode 100644 index afbf2083d2..0000000000 --- a/src/nvim/testdir/test79.in +++ /dev/null diff --git a/src/nvim/testdir/test79.ok b/src/nvim/testdir/test79.ok Binary files differdeleted file mode 100644 index d4e0ae8819..0000000000 --- a/src/nvim/testdir/test79.ok +++ /dev/null diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim index c1f6405579..b4baf7a8d7 100644 --- a/src/nvim/testdir/test_alot.vim +++ b/src/nvim/testdir/test_alot.vim @@ -2,29 +2,33 @@ " This makes testing go faster, since Vim doesn't need to restart. source test_assign.vim +source test_changedtick.vim source test_cursor_func.vim -source test_execute_func.vim source test_ex_undo.vim +source test_execute_func.vim source test_expr.vim -source test_expr_utf8.vim source test_feedkeys.vim source test_filter_cmd.vim source test_filter_map.vim +source test_findfile.vim source test_float_func.vim source test_functions.vim source test_ga.vim source test_goto.vim source test_jumps.vim source test_fileformat.vim +source test_filetype.vim source test_lambda.vim source test_menu.vim source test_mapping.vim source test_messages.vim -source test_options.vim source test_partial.vim source test_popup.vim +source test_put.vim +source test_recover.vim source test_regexp_utf8.vim source test_source_utf8.vim +source test_sha256.vim source test_statusline.vim source test_syn_attr.vim source test_tabline.vim diff --git a/src/nvim/testdir/test_alot_latin.vim b/src/nvim/testdir/test_alot_latin.vim new file mode 100644 index 0000000000..ebb3bde4ce --- /dev/null +++ b/src/nvim/testdir/test_alot_latin.vim @@ -0,0 +1,10 @@ +" A series of tests that can run in one Vim invocation. +" This makes testing go faster, since Vim doesn't need to restart. + +" These tests use latin1 'encoding'. Setting 'encoding' is in the individual +" files, so that they can be run by themselves. + +" Nvim does not allow setting 'encoding', so skip this test group. +finish + +source test_regexp_latin.vim diff --git a/src/nvim/testdir/test_alot_utf8.vim b/src/nvim/testdir/test_alot_utf8.vim new file mode 100644 index 0000000000..648d806a94 --- /dev/null +++ b/src/nvim/testdir/test_alot_utf8.vim @@ -0,0 +1,17 @@ +" A series of tests that can run in one Vim invocation. +" This makes testing go faster, since Vim doesn't need to restart. + +" These tests use utf8 'encoding'. Setting 'encoding' is already done in +" runtest.vim. Checking for the multi_byte feature is in the individual +" files, so that they can be run by themselves. + +source test_charsearch_utf8.vim +source test_expr_utf8.vim +source test_listlbr_utf8.vim +source test_matchadd_conceal_utf8.vim +source test_mksession_utf8.vim +source test_regexp_utf8.vim +source test_source_utf8.vim +source test_startup_utf8.vim +source test_utf8.vim +source test_utf8_comparisons.vim diff --git a/src/nvim/testdir/test_arabic.vim b/src/nvim/testdir/test_arabic.vim new file mode 100644 index 0000000000..9a16833a4c --- /dev/null +++ b/src/nvim/testdir/test_arabic.vim @@ -0,0 +1,472 @@ +" Simplistic testing of Arabic mode. + +if !has('arabic') || !has('multi_byte') + finish +endif + +source view_util.vim + +" Return list of Unicode characters at line lnum. +" Combining characters are treated as a single item. +func s:get_chars(lnum) + call cursor(a:lnum, 1) + let chars = [] + let numchars = strchars(getline('.'), 1) + for i in range(1, numchars) + exe 'norm ' i . '|' + let c=execute('ascii') + let c=substitute(c, '\n\?<.\{-}Hex\s*', 'U+', 'g') + let c=substitute(c, ',\s*Octal\s*\d*', '', 'g') + call add(chars, c) + endfor + return chars +endfunc + +func Test_arabic_toggle() + set arabic + call assert_equal(1, &rightleft) + call assert_equal(1, &arabicshape) + call assert_equal('arabic', &keymap) + call assert_equal(1, &delcombine) + + set iminsert=1 imsearch=1 + set arabic& + call assert_equal(0, &rightleft) + call assert_equal(1, &arabicshape) + call assert_equal('arabic', &keymap) + call assert_equal(1, &delcombine) + call assert_equal(0, &iminsert) + call assert_equal(-1, &imsearch) + + set arabicshape& keymap= delcombine& +endfunc + +func Test_arabic_input() + new + set arabic + " Typing sghl in Arabic insert mode should show the + " Arabic word 'Salaam' i.e. 'peace', spelled: + " SEEN, LAM, ALEF, MEEM. + " See: https://www.mediawiki.org/wiki/VisualEditor/Typing/Right-to-left + call feedkeys('isghl!', 'tx') + call assert_match("^ *!\uFEE1\uFEFC\uFEB3$", ScreenLines(1, &columns)[0]) + call assert_equal([ + \ 'U+0633', + \ 'U+0644 U+0627', + \ 'U+0645', + \ 'U+21'], s:get_chars(1)) + + " Without shaping, it should give individual Arabic letters. + set noarabicshape + call assert_match("^ *!\u0645\u0627\u0644\u0633$", ScreenLines(1, &columns)[0]) + call assert_equal([ + \ 'U+0633', + \ 'U+0644', + \ 'U+0627', + \ 'U+0645', + \ 'U+21'], s:get_chars(1)) + + set arabic& arabicshape& + bwipe! +endfunc + +func Test_arabic_toggle_keymap() + new + set arabic + call feedkeys("i12\<C-^>12\<C-^>12", 'tx') + call assert_match("^ *٢١21٢١$", ScreenLines(1, &columns)[0]) + call assert_equal('١٢12١٢', getline('.')) + set arabic& + bwipe! +endfunc + +func Test_delcombine() + new + set arabic + call feedkeys("isghl\<BS>\<BS>", 'tx') + call assert_match("^ *\uFEDE\uFEB3$", ScreenLines(1, &columns)[0]) + call assert_equal(['U+0633', 'U+0644'], s:get_chars(1)) + + " Now the same with 'nodelcombine' + set nodelcombine + %d + call feedkeys("isghl\<BS>\<BS>", 'tx') + call assert_match("^ *\uFEB1$", ScreenLines(1, &columns)[0]) + call assert_equal(['U+0633'], s:get_chars(1)) + set arabic& + bwipe! +endfunc + +" Values from src/arabic.h (not all used yet) +let s:a_COMMA = "\u060C" +let s:a_SEMICOLON = "\u061B" +let s:a_QUESTION = "\u061F" +let s:a_HAMZA = "\u0621" +let s:a_ALEF_MADDA = "\u0622" +let s:a_ALEF_HAMZA_ABOVE = "\u0623" +let s:a_WAW_HAMZA = "\u0624" +let s:a_ALEF_HAMZA_BELOW = "\u0625" +let s:a_YEH_HAMZA = "\u0626" +let s:a_ALEF = "\u0627" +let s:a_BEH = "\u0628" +let s:a_TEH_MARBUTA = "\u0629" +let s:a_TEH = "\u062a" +let s:a_THEH = "\u062b" +let s:a_JEEM = "\u062c" +let s:a_HAH = "\u062d" +let s:a_KHAH = "\u062e" +let s:a_DAL = "\u062f" +let s:a_THAL = "\u0630" +let s:a_REH = "\u0631" +let s:a_ZAIN = "\u0632" +let s:a_SEEN = "\u0633" +let s:a_SHEEN = "\u0634" +let s:a_SAD = "\u0635" +let s:a_DAD = "\u0636" +let s:a_TAH = "\u0637" +let s:a_ZAH = "\u0638" +let s:a_AIN = "\u0639" +let s:a_GHAIN = "\u063a" +let s:a_TATWEEL = "\u0640" +let s:a_FEH = "\u0641" +let s:a_QAF = "\u0642" +let s:a_KAF = "\u0643" +let s:a_LAM = "\u0644" +let s:a_MEEM = "\u0645" +let s:a_NOON = "\u0646" +let s:a_HEH = "\u0647" +let s:a_WAW = "\u0648" +let s:a_ALEF_MAKSURA = "\u0649" +let s:a_YEH = "\u064a" + +let s:a_FATHATAN = "\u064b" +let s:a_DAMMATAN = "\u064c" +let s:a_KASRATAN = "\u064d" +let s:a_FATHA = "\u064e" +let s:a_DAMMA = "\u064f" +let s:a_KASRA = "\u0650" +let s:a_SHADDA = "\u0651" +let s:a_SUKUN = "\u0652" + +let s:a_MADDA_ABOVE = "\u0653" +let s:a_HAMZA_ABOVE = "\u0654" +let s:a_HAMZA_BELOW = "\u0655" + +let s:a_ZERO = "\u0660" +let s:a_ONE = "\u0661" +let s:a_TWO = "\u0662" +let s:a_THREE = "\u0663" +let s:a_FOUR = "\u0664" +let s:a_FIVE = "\u0665" +let s:a_SIX = "\u0666" +let s:a_SEVEN = "\u0667" +let s:a_EIGHT = "\u0668" +let s:a_NINE = "\u0669" +let s:a_PERCENT = "\u066a" +let s:a_DECIMAL = "\u066b" +let s:a_THOUSANDS = "\u066c" +let s:a_STAR = "\u066d" +let s:a_MINI_ALEF = "\u0670" + +let s:a_s_FATHATAN = "\ufe70" +let s:a_m_TATWEEL_FATHATAN = "\ufe71" +let s:a_s_DAMMATAN = "\ufe72" + +let s:a_s_KASRATAN = "\ufe74" + +let s:a_s_FATHA = "\ufe76" +let s:a_m_FATHA = "\ufe77" +let s:a_s_DAMMA = "\ufe78" +let s:a_m_DAMMA = "\ufe79" +let s:a_s_KASRA = "\ufe7a" +let s:a_m_KASRA = "\ufe7b" +let s:a_s_SHADDA = "\ufe7c" +let s:a_m_SHADDA = "\ufe7d" +let s:a_s_SUKUN = "\ufe7e" +let s:a_m_SUKUN = "\ufe7f" + +let s:a_s_HAMZA = "\ufe80" +let s:a_s_ALEF_MADDA = "\ufe81" +let s:a_f_ALEF_MADDA = "\ufe82" +let s:a_s_ALEF_HAMZA_ABOVE = "\ufe83" +let s:a_f_ALEF_HAMZA_ABOVE = "\ufe84" +let s:a_s_WAW_HAMZA = "\ufe85" +let s:a_f_WAW_HAMZA = "\ufe86" +let s:a_s_ALEF_HAMZA_BELOW = "\ufe87" +let s:a_f_ALEF_HAMZA_BELOW = "\ufe88" +let s:a_s_YEH_HAMZA = "\ufe89" +let s:a_f_YEH_HAMZA = "\ufe8a" +let s:a_i_YEH_HAMZA = "\ufe8b" +let s:a_m_YEH_HAMZA = "\ufe8c" +let s:a_s_ALEF = "\ufe8d" +let s:a_f_ALEF = "\ufe8e" +let s:a_s_BEH = "\ufe8f" +let s:a_f_BEH = "\ufe90" +let s:a_i_BEH = "\ufe91" +let s:a_m_BEH = "\ufe92" +let s:a_s_TEH_MARBUTA = "\ufe93" +let s:a_f_TEH_MARBUTA = "\ufe94" +let s:a_s_TEH = "\ufe95" +let s:a_f_TEH = "\ufe96" +let s:a_i_TEH = "\ufe97" +let s:a_m_TEH = "\ufe98" +let s:a_s_THEH = "\ufe99" +let s:a_f_THEH = "\ufe9a" +let s:a_i_THEH = "\ufe9b" +let s:a_m_THEH = "\ufe9c" +let s:a_s_JEEM = "\ufe9d" +let s:a_f_JEEM = "\ufe9e" +let s:a_i_JEEM = "\ufe9f" +let s:a_m_JEEM = "\ufea0" +let s:a_s_HAH = "\ufea1" +let s:a_f_HAH = "\ufea2" +let s:a_i_HAH = "\ufea3" +let s:a_m_HAH = "\ufea4" +let s:a_s_KHAH = "\ufea5" +let s:a_f_KHAH = "\ufea6" +let s:a_i_KHAH = "\ufea7" +let s:a_m_KHAH = "\ufea8" +let s:a_s_DAL = "\ufea9" +let s:a_f_DAL = "\ufeaa" +let s:a_s_THAL = "\ufeab" +let s:a_f_THAL = "\ufeac" +let s:a_s_REH = "\ufead" +let s:a_f_REH = "\ufeae" +let s:a_s_ZAIN = "\ufeaf" +let s:a_f_ZAIN = "\ufeb0" +let s:a_s_SEEN = "\ufeb1" +let s:a_f_SEEN = "\ufeb2" +let s:a_i_SEEN = "\ufeb3" +let s:a_m_SEEN = "\ufeb4" +let s:a_s_SHEEN = "\ufeb5" +let s:a_f_SHEEN = "\ufeb6" +let s:a_i_SHEEN = "\ufeb7" +let s:a_m_SHEEN = "\ufeb8" +let s:a_s_SAD = "\ufeb9" +let s:a_f_SAD = "\ufeba" +let s:a_i_SAD = "\ufebb" +let s:a_m_SAD = "\ufebc" +let s:a_s_DAD = "\ufebd" +let s:a_f_DAD = "\ufebe" +let s:a_i_DAD = "\ufebf" +let s:a_m_DAD = "\ufec0" +let s:a_s_TAH = "\ufec1" +let s:a_f_TAH = "\ufec2" +let s:a_i_TAH = "\ufec3" +let s:a_m_TAH = "\ufec4" +let s:a_s_ZAH = "\ufec5" +let s:a_f_ZAH = "\ufec6" +let s:a_i_ZAH = "\ufec7" +let s:a_m_ZAH = "\ufec8" +let s:a_s_AIN = "\ufec9" +let s:a_f_AIN = "\ufeca" +let s:a_i_AIN = "\ufecb" +let s:a_m_AIN = "\ufecc" +let s:a_s_GHAIN = "\ufecd" +let s:a_f_GHAIN = "\ufece" +let s:a_i_GHAIN = "\ufecf" +let s:a_m_GHAIN = "\ufed0" +let s:a_s_FEH = "\ufed1" +let s:a_f_FEH = "\ufed2" +let s:a_i_FEH = "\ufed3" +let s:a_m_FEH = "\ufed4" +let s:a_s_QAF = "\ufed5" +let s:a_f_QAF = "\ufed6" +let s:a_i_QAF = "\ufed7" +let s:a_m_QAF = "\ufed8" +let s:a_s_KAF = "\ufed9" +let s:a_f_KAF = "\ufeda" +let s:a_i_KAF = "\ufedb" +let s:a_m_KAF = "\ufedc" +let s:a_s_LAM = "\ufedd" +let s:a_f_LAM = "\ufede" +let s:a_i_LAM = "\ufedf" +let s:a_m_LAM = "\ufee0" +let s:a_s_MEEM = "\ufee1" +let s:a_f_MEEM = "\ufee2" +let s:a_i_MEEM = "\ufee3" +let s:a_m_MEEM = "\ufee4" +let s:a_s_NOON = "\ufee5" +let s:a_f_NOON = "\ufee6" +let s:a_i_NOON = "\ufee7" +let s:a_m_NOON = "\ufee8" +let s:a_s_HEH = "\ufee9" +let s:a_f_HEH = "\ufeea" +let s:a_i_HEH = "\ufeeb" +let s:a_m_HEH = "\ufeec" +let s:a_s_WAW = "\ufeed" +let s:a_f_WAW = "\ufeee" +let s:a_s_ALEF_MAKSURA = "\ufeef" +let s:a_f_ALEF_MAKSURA = "\ufef0" +let s:a_s_YEH = "\ufef1" +let s:a_f_YEH = "\ufef2" +let s:a_i_YEH = "\ufef3" +let s:a_m_YEH = "\ufef4" +let s:a_s_LAM_ALEF_MADDA_ABOVE = "\ufef5" +let s:a_f_LAM_ALEF_MADDA_ABOVE = "\ufef6" +let s:a_s_LAM_ALEF_HAMZA_ABOVE = "\ufef7" +let s:a_f_LAM_ALEF_HAMZA_ABOVE = "\ufef8" +let s:a_s_LAM_ALEF_HAMZA_BELOW = "\ufef9" +let s:a_f_LAM_ALEF_HAMZA_BELOW = "\ufefa" +let s:a_s_LAM_ALEF = "\ufefb" +let s:a_f_LAM_ALEF = "\ufefc" + +let s:a_BYTE_ORDER_MARK = "\ufeff" + +func Test_shape_initial() + new + set arabicshape + + " Shaping arabic {testchar} non-arabic Tests chg_c_a2i(). + " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result + for pair in [[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_i_YEH_HAMZA], + \ [s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA], + \ [s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_s_ALEF_MADDA], + \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_ABOVE], + \ [s:a_WAW_HAMZA, s:a_s_GHAIN, s:a_s_WAW_HAMZA], + \ [s:a_ALEF_HAMZA_BELOW, s:a_s_GHAIN, s:a_s_ALEF_HAMZA_BELOW], + \ [s:a_ALEF, s:a_s_GHAIN, s:a_s_ALEF], + \ [s:a_TEH_MARBUTA, s:a_s_GHAIN, s:a_s_TEH_MARBUTA], + \ [s:a_DAL, s:a_s_GHAIN, s:a_s_DAL], + \ [s:a_THAL, s:a_s_GHAIN, s:a_s_THAL], + \ [s:a_REH, s:a_s_GHAIN, s:a_s_REH], + \ [s:a_ZAIN, s:a_s_GHAIN, s:a_s_ZAIN], + \ [s:a_TATWEEL, s:a_f_GHAIN, s:a_TATWEEL], + \ [s:a_WAW, s:a_s_GHAIN, s:a_s_WAW], + \ [s:a_ALEF_MAKSURA, s:a_s_GHAIN, s:a_s_ALEF_MAKSURA], + \ [s:a_BEH, s:a_f_GHAIN, s:a_i_BEH], + \ [s:a_TEH, s:a_f_GHAIN, s:a_i_TEH], + \ [s:a_THEH, s:a_f_GHAIN, s:a_i_THEH], + \ [s:a_JEEM, s:a_f_GHAIN, s:a_i_JEEM], + \ [s:a_HAH, s:a_f_GHAIN, s:a_i_HAH], + \ [s:a_KHAH, s:a_f_GHAIN, s:a_i_KHAH], + \ [s:a_SEEN, s:a_f_GHAIN, s:a_i_SEEN], + \ [s:a_SHEEN, s:a_f_GHAIN, s:a_i_SHEEN], + \ [s:a_SAD, s:a_f_GHAIN, s:a_i_SAD], + \ [s:a_DAD, s:a_f_GHAIN, s:a_i_DAD], + \ [s:a_TAH, s:a_f_GHAIN, s:a_i_TAH], + \ [s:a_ZAH, s:a_f_GHAIN, s:a_i_ZAH], + \ [s:a_AIN, s:a_f_GHAIN, s:a_i_AIN], + \ [s:a_GHAIN, s:a_f_GHAIN, s:a_i_GHAIN], + \ [s:a_FEH, s:a_f_GHAIN, s:a_i_FEH], + \ [s:a_QAF, s:a_f_GHAIN, s:a_i_QAF], + \ [s:a_KAF, s:a_f_GHAIN, s:a_i_KAF], + \ [s:a_LAM, s:a_f_GHAIN, s:a_i_LAM], + \ [s:a_MEEM, s:a_f_GHAIN, s:a_i_MEEM], + \ [s:a_NOON, s:a_f_GHAIN, s:a_i_NOON], + \ [s:a_HEH, s:a_f_GHAIN, s:a_i_HEH], + \ [s:a_YEH, s:a_f_GHAIN, s:a_i_YEH], + \ ] + call setline(1, s:a_GHAIN . pair[0] . ' ') + call assert_equal([pair[1] . pair[2] . ' '], ScreenLines(1, 3)) + endfor + + set arabicshape& + bwipe! +endfunc + +func Test_shape_isolated() + new + set arabicshape + + " Shaping non-arabic {testchar} non-arabic Tests chg_c_a2s(). + " pair[0] = testchar, pair[1] = current-result + for pair in [[s:a_HAMZA, s:a_s_HAMZA], + \ [s:a_ALEF_MADDA, s:a_s_ALEF_MADDA], + \ [s:a_ALEF_HAMZA_ABOVE, s:a_s_ALEF_HAMZA_ABOVE], + \ [s:a_WAW_HAMZA, s:a_s_WAW_HAMZA], + \ [s:a_ALEF_HAMZA_BELOW, s:a_s_ALEF_HAMZA_BELOW], + \ [s:a_YEH_HAMZA, s:a_s_YEH_HAMZA], + \ [s:a_ALEF, s:a_s_ALEF], + \ [s:a_TEH_MARBUTA, s:a_s_TEH_MARBUTA], + \ [s:a_DAL, s:a_s_DAL], + \ [s:a_THAL, s:a_s_THAL], + \ [s:a_REH, s:a_s_REH], + \ [s:a_ZAIN, s:a_s_ZAIN], + \ [s:a_TATWEEL, s:a_TATWEEL], + \ [s:a_WAW, s:a_s_WAW], + \ [s:a_ALEF_MAKSURA, s:a_s_ALEF_MAKSURA], + \ [s:a_BEH, s:a_s_BEH], + \ [s:a_TEH, s:a_s_TEH], + \ [s:a_THEH, s:a_s_THEH], + \ [s:a_JEEM, s:a_s_JEEM], + \ [s:a_HAH, s:a_s_HAH], + \ [s:a_KHAH, s:a_s_KHAH], + \ [s:a_SEEN, s:a_s_SEEN], + \ [s:a_SHEEN, s:a_s_SHEEN], + \ [s:a_SAD, s:a_s_SAD], + \ [s:a_DAD, s:a_s_DAD], + \ [s:a_TAH, s:a_s_TAH], + \ [s:a_ZAH, s:a_s_ZAH], + \ [s:a_AIN, s:a_s_AIN], + \ [s:a_GHAIN, s:a_s_GHAIN], + \ [s:a_FEH, s:a_s_FEH], + \ [s:a_QAF, s:a_s_QAF], + \ [s:a_KAF, s:a_s_KAF], + \ [s:a_LAM, s:a_s_LAM], + \ [s:a_MEEM, s:a_s_MEEM], + \ [s:a_NOON, s:a_s_NOON], + \ [s:a_HEH, s:a_s_HEH], + \ [s:a_YEH, s:a_s_YEH], + \ ] + call setline(1, ' ' . pair[0] . ' ') + call assert_equal([' ' . pair[1] . ' '], ScreenLines(1, 3)) + endfor + + set arabicshape& + bwipe! +endfunc + +func Test_shape_medial() + new + set arabicshape + + " Shaping arabic {testchar} arabic Tests chg_c_a2m(). + " pair[0] = testchar, pair[1] = next-result, pair[2] = current-result, + " pair[3] = previous-result + for pair in [[s:a_HAMZA, s:a_s_GHAIN, s:a_s_HAMZA, s:a_s_BEH], + \[s:a_ALEF_MADDA, s:a_s_GHAIN, s:a_f_ALEF_MADDA, s:a_i_BEH], + \[s:a_ALEF_HAMZA_ABOVE, s:a_s_GHAIN, s:a_f_ALEF_HAMZA_ABOVE, s:a_i_BEH], + \[s:a_WAW_HAMZA, s:a_s_GHAIN, s:a_f_WAW_HAMZA, s:a_i_BEH], + \[s:a_ALEF_HAMZA_BELOW, s:a_s_GHAIN, s:a_f_ALEF_HAMZA_BELOW, s:a_i_BEH], + \[s:a_YEH_HAMZA, s:a_f_GHAIN, s:a_m_YEH_HAMZA, s:a_i_BEH], + \[s:a_ALEF, s:a_s_GHAIN, s:a_f_ALEF, s:a_i_BEH], + \[s:a_BEH, s:a_f_GHAIN, s:a_m_BEH, s:a_i_BEH], + \[s:a_TEH_MARBUTA, s:a_s_GHAIN, s:a_f_TEH_MARBUTA, s:a_i_BEH], + \[s:a_TEH, s:a_f_GHAIN, s:a_m_TEH, s:a_i_BEH], + \[s:a_THEH, s:a_f_GHAIN, s:a_m_THEH, s:a_i_BEH], + \[s:a_JEEM, s:a_f_GHAIN, s:a_m_JEEM, s:a_i_BEH], + \[s:a_HAH, s:a_f_GHAIN, s:a_m_HAH, s:a_i_BEH], + \[s:a_KHAH, s:a_f_GHAIN, s:a_m_KHAH, s:a_i_BEH], + \[s:a_DAL, s:a_s_GHAIN, s:a_f_DAL, s:a_i_BEH], + \[s:a_THAL, s:a_s_GHAIN, s:a_f_THAL, s:a_i_BEH], + \[s:a_REH, s:a_s_GHAIN, s:a_f_REH, s:a_i_BEH], + \[s:a_ZAIN, s:a_s_GHAIN, s:a_f_ZAIN, s:a_i_BEH], + \[s:a_SEEN, s:a_f_GHAIN, s:a_m_SEEN, s:a_i_BEH], + \[s:a_SHEEN, s:a_f_GHAIN, s:a_m_SHEEN, s:a_i_BEH], + \[s:a_SAD, s:a_f_GHAIN, s:a_m_SAD, s:a_i_BEH], + \[s:a_DAD, s:a_f_GHAIN, s:a_m_DAD, s:a_i_BEH], + \[s:a_TAH, s:a_f_GHAIN, s:a_m_TAH, s:a_i_BEH], + \[s:a_ZAH, s:a_f_GHAIN, s:a_m_ZAH, s:a_i_BEH], + \[s:a_AIN, s:a_f_GHAIN, s:a_m_AIN, s:a_i_BEH], + \[s:a_GHAIN, s:a_f_GHAIN, s:a_m_GHAIN, s:a_i_BEH], + \[s:a_TATWEEL, s:a_f_GHAIN, s:a_TATWEEL, s:a_i_BEH], + \[s:a_FEH, s:a_f_GHAIN, s:a_m_FEH, s:a_i_BEH], + \[s:a_QAF, s:a_f_GHAIN, s:a_m_QAF, s:a_i_BEH], + \[s:a_KAF, s:a_f_GHAIN, s:a_m_KAF, s:a_i_BEH], + \[s:a_LAM, s:a_f_GHAIN, s:a_m_LAM, s:a_i_BEH], + \[s:a_MEEM, s:a_f_GHAIN, s:a_m_MEEM, s:a_i_BEH], + \[s:a_NOON, s:a_f_GHAIN, s:a_m_NOON, s:a_i_BEH], + \[s:a_HEH, s:a_f_GHAIN, s:a_m_HEH, s:a_i_BEH], + \[s:a_WAW, s:a_s_GHAIN, s:a_f_WAW, s:a_i_BEH], + \[s:a_ALEF_MAKSURA, s:a_s_GHAIN, s:a_f_ALEF_MAKSURA, s:a_i_BEH], + \[s:a_YEH, s:a_f_GHAIN, s:a_m_YEH, s:a_i_BEH], + \ ] + call setline(1, s:a_GHAIN . pair[0] . s:a_BEH) + call assert_equal([pair[1] . pair[2] . pair[3]], ScreenLines(1, 3)) + endfor + + set arabicshape& + bwipe! +endfunc + diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim new file mode 100644 index 0000000000..19d0cee47a --- /dev/null +++ b/src/nvim/testdir/test_arglist.vim @@ -0,0 +1,355 @@ +" Test argument list commands + +func Test_argidx() + args a b c + last + call assert_equal(2, argidx()) + %argdelete + call assert_equal(0, argidx()) + " doing it again doesn't result in an error + %argdelete + call assert_equal(0, argidx()) + call assert_fails('2argdelete', 'E16:') + + args a b c + call assert_equal(0, argidx()) + next + call assert_equal(1, argidx()) + next + call assert_equal(2, argidx()) + 1argdelete + call assert_equal(1, argidx()) + 1argdelete + call assert_equal(0, argidx()) + 1argdelete + call assert_equal(0, argidx()) +endfunc + +func Test_argadd() + %argdelete + argadd a b c + call assert_equal(0, argidx()) + + %argdelete + argadd a + call assert_equal(0, argidx()) + argadd b c d + call assert_equal(0, argidx()) + + call Init_abc() + argadd x + call Assert_argc(['a', 'b', 'x', 'c']) + call assert_equal(1, argidx()) + + call Init_abc() + 0argadd x + call Assert_argc(['x', 'a', 'b', 'c']) + call assert_equal(2, argidx()) + + call Init_abc() + 1argadd x + call Assert_argc(['a', 'x', 'b', 'c']) + call assert_equal(2, argidx()) + + call Init_abc() + $argadd x + call Assert_argc(['a', 'b', 'c', 'x']) + call assert_equal(1, argidx()) + + call Init_abc() + $argadd x + +2argadd y + call Assert_argc(['a', 'b', 'c', 'x', 'y']) + call assert_equal(1, argidx()) + + %argd + edit d + arga + call assert_equal(1, len(argv())) + call assert_equal('d', get(argv(), 0, '')) + + %argd + edit some\ file + arga + call assert_equal(1, len(argv())) + call assert_equal('some file', get(argv(), 0, '')) + + %argd + new + arga + call assert_equal(0, len(argv())) +endfunc + +func Init_abc() + args a b c + next +endfunc + +func Assert_argc(l) + call assert_equal(len(a:l), argc()) + let i = 0 + while i < len(a:l) && i < argc() + call assert_equal(a:l[i], argv(i)) + let i += 1 + endwhile +endfunc + +" Test for [count]argument and [count]argdelete commands +" Ported from the test_argument_count.in test script +func Test_argument() + " Clean the argument list + arga a | %argd + + let save_hidden = &hidden + set hidden + + let g:buffers = [] + augroup TEST + au BufEnter * call add(buffers, expand('%:t')) + augroup END + + argadd a b c d + $argu + $-argu + -argu + 1argu + +2argu + + augroup TEST + au! + augroup END + + call assert_equal(['d', 'c', 'b', 'a', 'c'], g:buffers) + + redir => result + ar + redir END + call assert_true(result =~# 'a b \[c] d') + + .argd + call assert_equal(['a', 'b', 'd'], argv()) + + -argd + call assert_equal(['a', 'd'], argv()) + + $argd + call assert_equal(['a'], argv()) + + 1arga c + 1arga b + $argu + $arga x + call assert_equal(['a', 'b', 'c', 'x'], argv()) + + 0arga y + call assert_equal(['y', 'a', 'b', 'c', 'x'], argv()) + + %argd + call assert_equal([], argv()) + + arga a b c d e f + 2,$-argd + call assert_equal(['a', 'f'], argv()) + + let &hidden = save_hidden + + " Setting argument list should fail when the current buffer has unsaved + " changes + %argd + enew! + set modified + call assert_fails('args x y z', 'E37:') + args! x y z + call assert_equal(['x', 'y', 'z'], argv()) + call assert_equal('x', expand('%:t')) + + last | enew | argu + call assert_equal('z', expand('%:t')) + + %argdelete + call assert_fails('argument', 'E163:') +endfunc + +" Test for 0argadd and 0argedit +" Ported from the test_argument_0count.in test script +func Test_zero_argadd() + " Clean the argument list + arga a | %argd + + arga a b c d + 2argu + 0arga added + call assert_equal(['added', 'a', 'b', 'c', 'd'], argv()) + + 2argu + arga third + call assert_equal(['added', 'a', 'third', 'b', 'c', 'd'], argv()) + + %argd + arga a b c d + 2argu + 0arge edited + call assert_equal(['edited', 'a', 'b', 'c', 'd'], argv()) + + 2argu + arga third + call assert_equal(['edited', 'a', 'third', 'b', 'c', 'd'], argv()) + + 2argu + argedit file\ with\ spaces another file + call assert_equal(['edited', 'a', 'file with spaces', 'another', 'file', 'third', 'b', 'c', 'd'], argv()) + call assert_equal('file with spaces', expand('%')) +endfunc + +func Reset_arglist() + args a | %argd +endfunc + +" Test for argc() +func Test_argc() + call Reset_arglist() + call assert_equal(0, argc()) + argadd a b + call assert_equal(2, argc()) +endfunc + +" Test for arglistid() +func Test_arglistid() + call Reset_arglist() + arga a b + call assert_equal(0, arglistid()) + split + arglocal + call assert_equal(1, arglistid()) + tabnew | tabfirst + call assert_equal(0, arglistid(2)) + call assert_equal(1, arglistid(1, 1)) + call assert_equal(0, arglistid(2, 1)) + call assert_equal(1, arglistid(1, 2)) + tabonly | only | enew! + argglobal + call assert_equal(0, arglistid()) +endfunc + +" Test for argv() +func Test_argv() + call Reset_arglist() + call assert_equal([], argv()) + call assert_equal("", argv(2)) + argadd a b c d + call assert_equal('c', argv(2)) +endfunc + +" Test for the :argedit command +func Test_argedit() + call Reset_arglist() + argedit a + call assert_equal(['a'], argv()) + call assert_equal('a', expand('%:t')) + argedit b + call assert_equal(['a', 'b'], argv()) + call assert_equal('b', expand('%:t')) + argedit a + call assert_equal(['a', 'b', 'a'], argv()) + call assert_equal('a', expand('%:t')) + " When file name case is ignored, an existing buffer with only case + " difference is re-used. + argedit C D + call assert_equal('C', expand('%:t')) + call assert_equal(['a', 'b', 'a', 'C', 'D'], argv()) + argedit c + if has('fname_case') + call assert_equal(['a', 'b', 'a', 'C', 'c', 'D'], argv()) + else + call assert_equal(['a', 'b', 'a', 'C', 'C', 'D'], argv()) + endif + 0argedit x + if has('fname_case') + call assert_equal(['x', 'a', 'b', 'a', 'C', 'c', 'D'], argv()) + else + call assert_equal(['x', 'a', 'b', 'a', 'C', 'C', 'D'], argv()) + endif + enew! | set modified + call assert_fails('argedit y', 'E37:') + argedit! y + if has('fname_case') + call assert_equal(['x', 'y', 'y', 'a', 'b', 'a', 'C', 'c', 'D'], argv()) + else + call assert_equal(['x', 'y', 'y', 'a', 'b', 'a', 'C', 'C', 'D'], argv()) + endif + %argd + bwipe! C + bwipe! D +endfunc + +" Test for the :argdelete command +func Test_argdelete() + call Reset_arglist() + args aa a aaa b bb + argdelete a* + call assert_equal(['b', 'bb'], argv()) + call assert_equal('aa', expand('%:t')) + last + argdelete % + call assert_equal(['b'], argv()) + call assert_fails('argdelete', 'E471:') + call assert_fails('1,100argdelete', 'E16:') + %argd +endfunc + +" Tests for the :next, :prev, :first, :last, :rewind commands +func Test_argpos() + call Reset_arglist() + args a b c d + last + call assert_equal(3, argidx()) + call assert_fails('next', 'E165:') + prev + call assert_equal(2, argidx()) + Next + call assert_equal(1, argidx()) + first + call assert_equal(0, argidx()) + call assert_fails('prev', 'E164:') + 3next + call assert_equal(3, argidx()) + rewind + call assert_equal(0, argidx()) + %argd +endfunc + +" Test for autocommand that redefines the argument list, when doing ":all". +func Test_arglist_autocmd() + autocmd BufReadPost Xxx2 next Xxx2 Xxx1 + call writefile(['test file Xxx1'], 'Xxx1') + call writefile(['test file Xxx2'], 'Xxx2') + call writefile(['test file Xxx3'], 'Xxx3') + + new + " redefine arglist; go to Xxx1 + next! Xxx1 Xxx2 Xxx3 + " open window for all args + all + call assert_equal('test file Xxx1', getline(1)) + wincmd w + wincmd w + call assert_equal('test file Xxx1', getline(1)) + " should now be in Xxx2 + rewind + call assert_equal('test file Xxx2', getline(1)) + + autocmd! BufReadPost Xxx2 + enew! | only + call delete('Xxx1') + call delete('Xxx2') + call delete('Xxx3') + argdelete Xxx* + bwipe! Xxx1 Xxx2 Xxx3 +endfunc + +func Test_arg_all_expand() + call writefile(['test file Xxx1'], 'Xx x') + next notexist Xx\ x runtest.vim + call assert_equal('notexist Xx\ x runtest.vim', expand('##')) + call delete('Xx x') +endfunc diff --git a/src/nvim/testdir/test_autochdir.vim b/src/nvim/testdir/test_autochdir.vim new file mode 100644 index 0000000000..05d69631c4 --- /dev/null +++ b/src/nvim/testdir/test_autochdir.vim @@ -0,0 +1,19 @@ +" Test 'autochdir' behavior + +if !exists("+autochdir") + finish +endif + +func Test_set_filename() + let cwd = getcwd() + call test_autochdir() + set acd + new + w samples/Xtest + call assert_equal("Xtest", expand('%')) + call assert_equal("samples", substitute(getcwd(), '.*/\(\k*\)', '\1', '')) + bwipe! + set noacd + exe 'cd ' . cwd + call delete('samples/Xtest') +endfunc diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim index 82c04abf5b..24651b75e1 100644 --- a/src/nvim/testdir/test_autocmd.vim +++ b/src/nvim/testdir/test_autocmd.vim @@ -1,14 +1,13 @@ " Tests for autocommands -set belloff=all -function! s:cleanup_buffers() abort +func! s:cleanup_buffers() abort for bnr in range(1, bufnr('$')) if bufloaded(bnr) && bufnr('%') != bnr execute 'bd! ' . bnr endif endfor -endfunction +endfunc func Test_vim_did_enter() call assert_false(v:vim_did_enter) @@ -49,7 +48,7 @@ if has('timers') endfunc endif -function Test_bufunload() +func Test_bufunload() augroup test_bufunload_group autocmd! autocmd BufUnload * call add(s:li, "bufunload") @@ -80,7 +79,7 @@ function Test_bufunload() endfunc " SEGV occurs in older versions. (At least 7.4.2005 or older) -function Test_autocmd_bufunload_with_tabnext() +func Test_autocmd_bufunload_with_tabnext() tabedit tabfirst @@ -98,7 +97,7 @@ function Test_autocmd_bufunload_with_tabnext() quit endfunc -function Test_autocmd_bufwinleave_with_tabfirst() +func Test_autocmd_bufwinleave_with_tabfirst() tabedit augroup sample autocmd! @@ -110,7 +109,7 @@ function Test_autocmd_bufwinleave_with_tabfirst() endfunc " SEGV occurs in older versions. (At least 7.4.2321 or older) -function Test_autocmd_bufunload_avoiding_SEGV_01() +func Test_autocmd_bufunload_avoiding_SEGV_01() split aa.txt let lastbuf = bufnr('$') @@ -128,7 +127,7 @@ function Test_autocmd_bufunload_avoiding_SEGV_01() endfunc " SEGV occurs in older versions. (At least 7.4.2321 or older) -function Test_autocmd_bufunload_avoiding_SEGV_02() +func Test_autocmd_bufunload_avoiding_SEGV_02() setlocal buftype=nowrite let lastbuf = bufnr('$') @@ -353,8 +352,10 @@ endfunc " Closing a window might cause an endless loop " E814 for older Vims -function Test_autocmd_bufwipe_in_SessLoadPost() +func Test_autocmd_bufwipe_in_SessLoadPost() + edit Xtest tabnew + file Xsomething set noswapfile mksession! @@ -362,7 +363,7 @@ function Test_autocmd_bufwipe_in_SessLoadPost() \ 'let v:swapchoice="e"', \ 'augroup test_autocmd_sessionload', \ 'autocmd!', - \ 'autocmd SessionLoadPost * 4bw!', + \ 'autocmd SessionLoadPost * exe bufnr("Xsomething") . "bw!"', \ 'augroup END', \ '', \ 'func WriteErrors()', @@ -382,7 +383,7 @@ function Test_autocmd_bufwipe_in_SessLoadPost() endfunc " SEGV occurs in older versions. -function Test_autocmd_bufwipe_in_SessLoadPost2() +func Test_autocmd_bufwipe_in_SessLoadPost2() tabnew set noswapfile mksession! @@ -421,6 +422,382 @@ function Test_autocmd_bufwipe_in_SessLoadPost2() endfor endfunc +func Test_empty_doau() + doau \| +endfunc + +func s:AutoCommandOptionSet(match) + let item = remove(g:options, 0) + let expected = printf("Option: <%s>, Oldval: <%s>, NewVal: <%s>, Scope: <%s>\n", item[0], item[1], item[2], item[3]) + let actual = printf("Option: <%s>, Oldval: <%s>, NewVal: <%s>, Scope: <%s>\n", a:match, v:option_old, v:option_new, v:option_type) + let g:opt = [expected, actual] + "call assert_equal(expected, actual) +endfunc + +func Test_OptionSet() + throw 'skipped: Nvim does not support test_override()' + if !has("eval") || !has("autocmd") || !exists("+autochdir") + return + endif + + call test_override('starting', 1) + set nocp + au OptionSet * :call s:AutoCommandOptionSet(expand("<amatch>")) + + " 1: Setting number option" + let g:options=[['number', 0, 1, 'global']] + set nu + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 2: Setting local number option" + let g:options=[['number', 1, 0, 'local']] + setlocal nonu + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 3: Setting global number option" + let g:options=[['number', 1, 0, 'global']] + setglobal nonu + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 4: Setting local autoindent option" + let g:options=[['autoindent', 0, 1, 'local']] + setlocal ai + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 5: Setting global autoindent option" + let g:options=[['autoindent', 0, 1, 'global']] + setglobal ai + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 6: Setting global autoindent option" + let g:options=[['autoindent', 1, 0, 'global']] + set ai! + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " Should not print anything, use :noa + " 7: don't trigger OptionSet" + let g:options=[['invalid', 1, 1, 'invalid']] + noa set nonu + call assert_equal([['invalid', 1, 1, 'invalid']], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 8: Setting several global list and number option" + let g:options=[['list', 0, 1, 'global'], ['number', 0, 1, 'global']] + set list nu + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 9: don't trigger OptionSet" + let g:options=[['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']] + noa set nolist nonu + call assert_equal([['invalid', 1, 1, 'invalid'], ['invalid', 1, 1, 'invalid']], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 10: Setting global acd" + let g:options=[['autochdir', 0, 1, 'local']] + setlocal acd + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 11: Setting global autoread (also sets local value)" + let g:options=[['autoread', 0, 1, 'global']] + set ar + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 12: Setting local autoread" + let g:options=[['autoread', 1, 1, 'local']] + setlocal ar + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 13: Setting global autoread" + let g:options=[['autoread', 1, 0, 'global']] + setglobal invar + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 14: Setting option backspace through :let" + let g:options=[['backspace', '', 'eol,indent,start', 'global']] + let &bs="eol,indent,start" + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 15: Setting option backspace through setbufvar()" + let g:options=[['backup', 0, 1, 'local']] + " try twice, first time, shouldn't trigger because option name is invalid, + " second time, it should trigger + call assert_fails("call setbufvar(1, '&l:bk', 1)", "E355") + " should trigger, use correct option name + call setbufvar(1, '&backup', 1) + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 16: Setting number option using setwinvar" + let g:options=[['number', 0, 1, 'local']] + call setwinvar(0, '&number', 1) + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 17: Setting key option, shouldn't trigger" + let g:options=[['key', 'invalid', 'invalid1', 'invalid']] + setlocal key=blah + setlocal key= + call assert_equal([['key', 'invalid', 'invalid1', 'invalid']], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 18: Setting string option" + let oldval = &tags + let g:options=[['tags', oldval, 'tagpath', 'global']] + set tags=tagpath + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " 1l: Resetting string option" + let g:options=[['tags', 'tagpath', oldval, 'global']] + set tags& + call assert_equal([], g:options) + call assert_equal(g:opt[0], g:opt[1]) + + " Cleanup + au! OptionSet + for opt in ['nu', 'ai', 'acd', 'ar', 'bs', 'backup', 'cul', 'cp'] + exe printf(":set %s&vi", opt) + endfor + call test_override('starting', 0) + delfunc! AutoCommandOptionSet +endfunc + +func Test_OptionSet_diffmode() + throw 'skipped: Nvim does not support test_override()' + call test_override('starting', 1) + " 18: Changing an option when enetering diff mode + new + au OptionSet diff :let &l:cul=v:option_new + + call setline(1, ['buffer 1', 'line2', 'line3', 'line4']) + call assert_equal(0, &l:cul) + diffthis + call assert_equal(1, &l:cul) + + vnew + call setline(1, ['buffer 2', 'line 2', 'line 3', 'line4']) + call assert_equal(0, &l:cul) + diffthis + call assert_equal(1, &l:cul) + + diffoff + call assert_equal(0, &l:cul) + call assert_equal(1, getwinvar(2, '&l:cul')) + bw! + + call assert_equal(1, &l:cul) + diffoff! + call assert_equal(0, &l:cul) + call assert_equal(0, getwinvar(1, '&l:cul')) + bw! + + " Cleanup + au! OptionSet + call test_override('starting', 0) +endfunc + +func Test_OptionSet_diffmode_close() + throw 'skipped: Nvim does not support test_override()' + call test_override('starting', 1) + " 19: Try to close the current window when entering diff mode + " should not segfault + new + au OptionSet diff close + + call setline(1, ['buffer 1', 'line2', 'line3', 'line4']) + call assert_fails(':diffthis', 'E788') + call assert_equal(1, &diff) + vnew + call setline(1, ['buffer 2', 'line 2', 'line 3', 'line4']) + call assert_fails(':diffthis', 'E788') + call assert_equal(1, &diff) + bw! + call assert_fails(':diffoff!', 'E788') + bw! + + " Cleanup + au! OptionSet + call test_override('starting', 0) + "delfunc! AutoCommandOptionSet +endfunc + +" Test for Bufleave autocommand that deletes the buffer we are about to edit. +func Test_BufleaveWithDelete() + new | edit Xfile1 + + augroup test_bufleavewithdelete + autocmd! + autocmd BufLeave Xfile1 bwipe Xfile2 + augroup END + + call assert_fails('edit Xfile2', 'E143:') + call assert_equal('Xfile1', bufname('%')) + + autocmd! test_bufleavewithdelete BufLeave Xfile1 + augroup! test_bufleavewithdelete + + new + bwipe! Xfile1 +endfunc + +" Test for autocommand that changes the buffer list, when doing ":ball". +func Test_Acmd_BufAll() + enew! + %bwipe! + call writefile(['Test file Xxx1'], 'Xxx1') + call writefile(['Test file Xxx2'], 'Xxx2') + call writefile(['Test file Xxx3'], 'Xxx3') + + " Add three files to the buffer list + split Xxx1 + close + split Xxx2 + close + split Xxx3 + close + + " Wipe the buffer when the buffer is opened + au BufReadPost Xxx2 bwipe + + call append(0, 'Test file Xxx4') + ball + + call assert_equal(2, winnr('$')) + call assert_equal('Xxx1', bufname(winbufnr(winnr('$')))) + wincmd t + + au! BufReadPost + %bwipe! + call delete('Xxx1') + call delete('Xxx2') + call delete('Xxx3') + enew! | only +endfunc + +" Test for autocommand that changes current buffer on BufEnter event. +" Check if modelines are interpreted for the correct buffer. +func Test_Acmd_BufEnter() + %bwipe! + call writefile(['start of test file Xxx1', + \ "\<Tab>this is a test", + \ 'end of test file Xxx1'], 'Xxx1') + call writefile(['start of test file Xxx2', + \ 'vim: set noai :', + \ "\<Tab>this is a test", + \ 'end of test file Xxx2'], 'Xxx2') + + au BufEnter Xxx2 brew + set ai modeline modelines=3 + edit Xxx1 + " edit Xxx2, autocmd will do :brew + edit Xxx2 + exe "normal G?this is a\<CR>" + " Append text with autoindent to this file + normal othis should be auto-indented + call assert_equal("\<Tab>this should be auto-indented", getline('.')) + call assert_equal(3, line('.')) + " Remove autocmd and edit Xxx2 again + au! BufEnter Xxx2 + buf! Xxx2 + exe "normal G?this is a\<CR>" + " append text without autoindent to Xxx + normal othis should be in column 1 + call assert_equal("this should be in column 1", getline('.')) + call assert_equal(4, line('.')) + + %bwipe! + call delete('Xxx1') + call delete('Xxx2') + set ai&vim modeline&vim modelines&vim +endfunc + +" Test for issue #57 +" do not move cursor on <c-o> when autoindent is set +func Test_ai_CTRL_O() + enew! + set ai + let save_fo = &fo + set fo+=r + exe "normal o# abcdef\<Esc>2hi\<CR>\<C-O>d0\<Esc>" + exe "normal o# abcdef\<Esc>2hi\<C-O>d0\<Esc>" + call assert_equal(['# abc', 'def', 'def'], getline(2, 4)) + + set ai&vim + let &fo = save_fo + enew! +endfunc + +" Test for autocommand that deletes the current buffer on BufLeave event. +" Also test deleting the last buffer, should give a new, empty buffer. +func Test_BufLeave_Wipe() + throw 'skipped: TODO: ' + %bwipe! + let content = ['start of test file Xxx', + \ 'this is a test', + \ 'end of test file Xxx'] + call writefile(content, 'Xxx1') + call writefile(content, 'Xxx2') + + au BufLeave Xxx2 bwipe + edit Xxx1 + split Xxx2 + " delete buffer Xxx2, we should be back to Xxx1 + bwipe + call assert_equal('Xxx1', bufname('%')) + call assert_equal(1, winnr('$')) + + " Create an alternate buffer + %write! test.out + call assert_equal('test.out', bufname('#')) + " delete alternate buffer + bwipe test.out + call assert_equal('Xxx1', bufname('%')) + call assert_equal('', bufname('#')) + + au BufLeave Xxx1 bwipe + " delete current buffer, get an empty one + bwipe! + call assert_equal(1, line('$')) + call assert_equal('', bufname('%')) + let g:bufinfo = getbufinfo() + call assert_equal(1, len(g:bufinfo)) + + call delete('Xxx1') + call delete('Xxx2') + call delete('test.out') + %bwipe + au! BufLeave + + " check that bufinfo doesn't contain a pointer to freed memory + call test_garbagecollect_now() +endfunc + +func Test_QuitPre() + edit Xfoo + let winid = win_getid(winnr()) + split Xbar + au! QuitPre * let g:afile = expand('<afile>') + " Close the other window, <afile> should be correct. + exe win_id2win(winid) . 'q' + call assert_equal('Xfoo', g:afile) + + unlet g:afile + bwipe Xfoo + bwipe Xbar +endfunc + func Test_Cmdline() au! CmdlineEnter : let g:entered = expand('<afile>') au! CmdlineLeave : let g:left = expand('<afile>') @@ -436,9 +813,355 @@ func Test_Cmdline() au! CmdlineLeave / let g:left = expand('<afile>') let g:entered = 0 let g:left = 0 - call feedkeys("/hello<CR>", 'xt') + new + call setline(1, 'hello') + call feedkeys("/hello\<CR>", 'xt') call assert_equal('/', g:entered) call assert_equal('/', g:left) + bwipe! au! CmdlineEnter au! CmdlineLeave endfunc + +" Test for BufWritePre autocommand that deletes or unloads the buffer. +func Test_BufWritePre() + %bwipe + au BufWritePre Xxx1 bunload + au BufWritePre Xxx2 bwipe + + call writefile(['start of Xxx1', 'test', 'end of Xxx1'], 'Xxx1') + call writefile(['start of Xxx2', 'test', 'end of Xxx2'], 'Xxx2') + + edit Xtest + e! Xxx2 + bdel Xtest + e Xxx1 + " write it, will unload it and give an error msg + call assert_fails('w', 'E203') + call assert_equal('Xxx2', bufname('%')) + edit Xtest + e! Xxx2 + bwipe Xtest + " write it, will delete the buffer and give an error msg + call assert_fails('w', 'E203') + call assert_equal('Xxx1', bufname('%')) + au! BufWritePre + call delete('Xxx1') + call delete('Xxx2') +endfunc + +" Test for BufUnload autocommand that unloads all the other buffers +func Test_bufunload_all() + call writefile(['Test file Xxx1'], 'Xxx1')" + call writefile(['Test file Xxx2'], 'Xxx2')" + + let content = [ + \ "func UnloadAllBufs()", + \ " let i = 1", + \ " while i <= bufnr('$')", + \ " if i != bufnr('%') && bufloaded(i)", + \ " exe i . 'bunload'", + \ " endif", + \ " let i += 1", + \ " endwhile", + \ "endfunc", + \ "au BufUnload * call UnloadAllBufs()", + \ "au VimLeave * call writefile(['Test Finished'], 'Xout')", + \ "edit Xxx1", + \ "split Xxx2", + \ "q"] + call writefile(content, 'Xtest') + + call delete('Xout') + call system(v:progpath. ' -u NORC -i NONE -N -S Xtest') + call assert_true(filereadable('Xout')) + + call delete('Xxx1') + call delete('Xxx2') + call delete('Xtest') + call delete('Xout') +endfunc + +" Some tests for buffer-local autocommands +func Test_buflocal_autocmd() + let g:bname = '' + edit xx + au BufLeave <buffer> let g:bname = expand("%") + " here, autocommand for xx should trigger. + " but autocommand shall not apply to buffer named <buffer>. + edit somefile + call assert_equal('xx', g:bname) + let g:bname = '' + " here, autocommand shall be auto-deleted + bwipe xx + " autocmd should not trigger + edit xx + call assert_equal('', g:bname) + " autocmd should not trigger + edit somefile + call assert_equal('', g:bname) + enew + unlet g:bname +endfunc + +" Test for "*Cmd" autocommands +func Test_Cmd_Autocmds() + call writefile(['start of Xxx', "\tabc2", 'end of Xxx'], 'Xxx') + + enew! + au BufReadCmd XtestA 0r Xxx|$del + edit XtestA " will read text of Xxd instead + call assert_equal('start of Xxx', getline(1)) + + au BufWriteCmd XtestA call append(line("$"), "write") + write " will append a line to the file + call assert_equal('write', getline('$')) + call assert_fails('read XtestA', 'E484') " should not read anything + call assert_equal('write', getline(4)) + + " now we have: + " 1 start of Xxx + " 2 abc2 + " 3 end of Xxx + " 4 write + + au FileReadCmd XtestB '[r Xxx + 2r XtestB " will read Xxx below line 2 instead + call assert_equal('start of Xxx', getline(3)) + + " now we have: + " 1 start of Xxx + " 2 abc2 + " 3 start of Xxx + " 4 abc2 + " 5 end of Xxx + " 6 end of Xxx + " 7 write + + au FileWriteCmd XtestC '[,']copy $ + normal 4GA1 + 4,5w XtestC " will copy lines 4 and 5 to the end + call assert_equal("\tabc21", getline(8)) + call assert_fails('r XtestC', 'E484') " should not read anything + call assert_equal("end of Xxx", getline(9)) + + " now we have: + " 1 start of Xxx + " 2 abc2 + " 3 start of Xxx + " 4 abc21 + " 5 end of Xxx + " 6 end of Xxx + " 7 write + " 8 abc21 + " 9 end of Xxx + + let g:lines = [] + au FileAppendCmd XtestD call extend(g:lines, getline(line("'["), line("']"))) + w >>XtestD " will add lines to 'lines' + call assert_equal(9, len(g:lines)) + call assert_fails('$r XtestD', 'E484') " should not read anything + call assert_equal(9, line('$')) + call assert_equal('end of Xxx', getline('$')) + + au BufReadCmd XtestE 0r Xxx|$del + sp XtestE " split window with test.out + call assert_equal('end of Xxx', getline(3)) + + let g:lines = [] + exe "normal 2Goasdf\<Esc>\<C-W>\<C-W>" + au BufWriteCmd XtestE call extend(g:lines, getline(0, '$')) + wall " will write other window to 'lines' + call assert_equal(4, len(g:lines), g:lines) + call assert_equal("\tasdf", g:lines[2]) + + au! BufReadCmd + au! BufWriteCmd + au! FileReadCmd + au! FileWriteCmd + au! FileAppendCmd + %bwipe! + call delete('Xxx') + enew! +endfunc + +func SetChangeMarks(start, end) + exe a:start. 'mark [' + exe a:end. 'mark ]' +endfunc + +" Verify the effects of autocmds on '[ and '] +func Test_change_mark_in_autocmds() + edit! Xtest + call feedkeys("ia\<CR>b\<CR>c\<CR>d\<C-g>u", 'xtn') + + call SetChangeMarks(2, 3) + write + call assert_equal([1, 4], [line("'["), line("']")]) + + call SetChangeMarks(2, 3) + au BufWritePre * call assert_equal([1, 4], [line("'["), line("']")]) + write + au! BufWritePre + + if executable('cat') + write XtestFilter + write >> XtestFilter + + call SetChangeMarks(2, 3) + " Marks are set to the entire range of the write + au FilterWritePre * call assert_equal([1, 4], [line("'["), line("']")]) + " '[ is adjusted to just before the line that will receive the filtered + " data + au FilterReadPre * call assert_equal([4, 4], [line("'["), line("']")]) + " The filtered data is read into the buffer, and the source lines are + " still present, so the range is after the source lines + au FilterReadPost * call assert_equal([5, 12], [line("'["), line("']")]) + %!cat XtestFilter + " After the filtered data is read, the original lines are deleted + call assert_equal([1, 8], [line("'["), line("']")]) + au! FilterWritePre,FilterReadPre,FilterReadPost + undo + + call SetChangeMarks(1, 4) + au FilterWritePre * call assert_equal([2, 3], [line("'["), line("']")]) + au FilterReadPre * call assert_equal([3, 3], [line("'["), line("']")]) + au FilterReadPost * call assert_equal([4, 11], [line("'["), line("']")]) + 2,3!cat XtestFilter + call assert_equal([2, 9], [line("'["), line("']")]) + au! FilterWritePre,FilterReadPre,FilterReadPost + undo + + call delete('XtestFilter') + endif + + call SetChangeMarks(1, 4) + au FileWritePre * call assert_equal([2, 3], [line("'["), line("']")]) + 2,3write Xtest2 + au! FileWritePre + + call SetChangeMarks(2, 3) + au FileAppendPre * call assert_equal([1, 4], [line("'["), line("']")]) + write >> Xtest2 + au! FileAppendPre + + call SetChangeMarks(1, 4) + au FileAppendPre * call assert_equal([2, 3], [line("'["), line("']")]) + 2,3write >> Xtest2 + au! FileAppendPre + + call SetChangeMarks(1, 1) + au FileReadPre * call assert_equal([3, 1], [line("'["), line("']")]) + au FileReadPost * call assert_equal([4, 11], [line("'["), line("']")]) + 3read Xtest2 + au! FileReadPre,FileReadPost + undo + + call SetChangeMarks(4, 4) + " When the line is 0, it's adjusted to 1 + au FileReadPre * call assert_equal([1, 4], [line("'["), line("']")]) + au FileReadPost * call assert_equal([1, 8], [line("'["), line("']")]) + 0read Xtest2 + au! FileReadPre,FileReadPost + undo + + call SetChangeMarks(4, 4) + " When the line is 0, it's adjusted to 1 + au FileReadPre * call assert_equal([1, 4], [line("'["), line("']")]) + au FileReadPost * call assert_equal([2, 9], [line("'["), line("']")]) + 1read Xtest2 + au! FileReadPre,FileReadPost + undo + + bwipe! + call delete('Xtest') + call delete('Xtest2') +endfunc + +func Test_Filter_noshelltemp() + if !executable('cat') + return + endif + + enew! + call setline(1, ['a', 'b', 'c', 'd']) + + let shelltemp = &shelltemp + set shelltemp + + let g:filter_au = 0 + au FilterWritePre * let g:filter_au += 1 + au FilterReadPre * let g:filter_au += 1 + au FilterReadPost * let g:filter_au += 1 + %!cat + call assert_equal(3, g:filter_au) + + if has('filterpipe') + set noshelltemp + + let g:filter_au = 0 + au FilterWritePre * let g:filter_au += 1 + au FilterReadPre * let g:filter_au += 1 + au FilterReadPost * let g:filter_au += 1 + %!cat + call assert_equal(0, g:filter_au) + endif + + au! FilterWritePre,FilterReadPre,FilterReadPost + let &shelltemp = shelltemp + bwipe! +endfunc + +func Test_TextYankPost() + enew! + call setline(1, ['foo']) + + let g:event = [] + au TextYankPost * let g:event = copy(v:event) + + call assert_equal({}, v:event) + call assert_fails('let v:event = {}', 'E46:') + call assert_fails('let v:event.mykey = 0', 'E742:') + + norm "ayiw + call assert_equal( + \{'regcontents': ['foo'], 'regname': 'a', 'operator': 'y', 'regtype': 'v'}, + \g:event) + norm y_ + call assert_equal( + \{'regcontents': ['foo'], 'regname': '', 'operator': 'y', 'regtype': 'V'}, + \g:event) + call feedkeys("\<C-V>y", 'x') + call assert_equal( + \{'regcontents': ['f'], 'regname': '', 'operator': 'y', 'regtype': "\x161"}, + \g:event) + norm "xciwbar + call assert_equal( + \{'regcontents': ['foo'], 'regname': 'x', 'operator': 'c', 'regtype': 'v'}, + \g:event) + norm "bdiw + call assert_equal( + \{'regcontents': ['bar'], 'regname': 'b', 'operator': 'd', 'regtype': 'v'}, + \g:event) + + call assert_equal({}, v:event) + + au! TextYankPost + unlet g:event + bwipe! +endfunc + +func Test_nocatch_wipe_all_buffers() + " Real nasty autocommand: wipe all buffers on any event. + au * * bwipe * + call assert_fails('next x', 'E93') + bwipe + au! +endfunc + +func Test_nocatch_wipe_dummy_buffer() + " Nasty autocommand: wipe buffer on any event. + au * x bwipe + call assert_fails('lv½ /x', 'E480') + au! +endfunc diff --git a/src/nvim/testdir/test_bufwintabinfo.vim b/src/nvim/testdir/test_bufwintabinfo.vim index 1c9350c416..a592cd7b11 100644 --- a/src/nvim/testdir/test_bufwintabinfo.vim +++ b/src/nvim/testdir/test_bufwintabinfo.vim @@ -1,7 +1,6 @@ " Tests for the getbufinfo(), getwininfo() and gettabinfo() functions function Test_getbufwintabinfo() - 1,$bwipeout edit Xtestfile1 edit Xtestfile2 let buflist = getbufinfo() diff --git a/src/nvim/testdir/test_changedtick.vim b/src/nvim/testdir/test_changedtick.vim new file mode 100644 index 0000000000..3a91bb54aa --- /dev/null +++ b/src/nvim/testdir/test_changedtick.vim @@ -0,0 +1,57 @@ +" Tests for b:changedtick + +func Test_changedtick_increments() + new + " New buffer has an empty line, tick starts at 2. + let expected = 2 + call assert_equal(expected, b:changedtick) + call assert_equal(expected, b:['changedtick']) + call setline(1, 'hello') + let expected += 1 + call assert_equal(expected, b:changedtick) + call assert_equal(expected, b:['changedtick']) + undo + " Somehow undo counts as two changes. + let expected += 2 + call assert_equal(expected, b:changedtick) + call assert_equal(expected, b:['changedtick']) + bwipe! +endfunc + +func Test_changedtick_dict_entry() + let d = b: + call assert_equal(b:changedtick, d['changedtick']) +endfunc + +func Test_changedtick_bdel() + new + let bnr = bufnr('%') + let v = b:changedtick + bdel + " Delete counts as a change too. + call assert_equal(v + 1, getbufvar(bnr, 'changedtick')) +endfunc + +func Test_changedtick_islocked() + call assert_equal(0, islocked('b:changedtick')) + let d = b: + call assert_equal(0, islocked('d.changedtick')) +endfunc + +func Test_changedtick_fixed() + call assert_fails('let b:changedtick = 4', 'E46:') + call assert_fails('let b:["changedtick"] = 4', 'E46:') + + call assert_fails('lockvar b:changedtick', 'E940:') + call assert_fails('lockvar b:["changedtick"]', 'E46:') + call assert_fails('unlockvar b:changedtick', 'E940:') + call assert_fails('unlockvar b:["changedtick"]', 'E46:') + call assert_fails('unlet b:changedtick', 'E795:') + call assert_fails('unlet b:["changedtick"]', 'E46:') + + let d = b: + call assert_fails('lockvar d["changedtick"]', 'E46:') + call assert_fails('unlockvar d["changedtick"]', 'E46:') + call assert_fails('unlet d["changedtick"]', 'E46:') + +endfunc diff --git a/src/nvim/testdir/test_charsearch_utf8.vim b/src/nvim/testdir/test_charsearch_utf8.vim new file mode 100644 index 0000000000..ade7dd408c --- /dev/null +++ b/src/nvim/testdir/test_charsearch_utf8.vim @@ -0,0 +1,22 @@ +" Tests for related f{char} and t{char} using utf-8. +if !has('multi_byte') + finish +endif + +" Test for t,f,F,T movement commands +function! Test_search_cmds() + new! + call setline(1, "・最初から最後まで最強のVimは最高") + 1 + normal! f最 + call assert_equal([0, 1, 4, 0], getpos('.')) + normal! ; + call assert_equal([0, 1, 16, 0], getpos('.')) + normal! 2; + call assert_equal([0, 1, 43, 0], getpos('.')) + normal! , + call assert_equal([0, 1, 28, 0], getpos('.')) + bw! +endfunction + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim new file mode 100644 index 0000000000..444c4c4109 --- /dev/null +++ b/src/nvim/testdir/test_cindent.vim @@ -0,0 +1,76 @@ +" Test for cinoptions and cindent +" +" TODO: rewrite test3.in into this new style test + +func Test_cino_hash() + " Test that curbuf->b_ind_hash_comment is correctly reset + new + setlocal cindent cinoptions=#1 + setlocal cinoptions= + call setline(1, ["#include <iostream>"]) + call cursor(1, 1) + norm! o#include + "call feedkeys("o#include\<esc>", 't') + call assert_equal(["#include <iostream>", "#include"], getline(1,2)) + bwipe! +endfunc + +func Test_cino_extern_c() + " Test for cino-E + + let without_ind = [ + \ '#ifdef __cplusplus', + \ 'extern "C" {', + \ '#endif', + \ 'int func_a(void);', + \ '#ifdef __cplusplus', + \ '}', + \ '#endif' + \ ] + + let with_ind = [ + \ '#ifdef __cplusplus', + \ 'extern "C" {', + \ '#endif', + \ "\tint func_a(void);", + \ '#ifdef __cplusplus', + \ '}', + \ '#endif' + \ ] + new + setlocal cindent cinoptions=E0 + call setline(1, without_ind) + call feedkeys("gg=G", 'tx') + call assert_equal(with_ind, getline(1, '$')) + + setlocal cinoptions=E-s + call setline(1, with_ind) + call feedkeys("gg=G", 'tx') + call assert_equal(without_ind, getline(1, '$')) + + setlocal cinoptions=Es + let tests = [ + \ ['recognized', ['extern "C" {'], "\t\t;"], + \ ['recognized', ['extern "C++" {'], "\t\t;"], + \ ['recognized', ['extern /* com */ "C"{'], "\t\t;"], + \ ['recognized', ['extern"C"{'], "\t\t;"], + \ ['recognized', ['extern "C"', '{'], "\t\t;"], + \ ['not recognized', ['extern {'], "\t;"], + \ ['not recognized', ['extern /*"C"*/{'], "\t;"], + \ ['not recognized', ['extern "C" //{'], ";"], + \ ['not recognized', ['extern "C" /*{*/'], ";"], + \ ] + + for pair in tests + let lines = pair[1] + call setline(1, lines) + call feedkeys(len(lines) . "Go;", 'tx') + call assert_equal(pair[2], getline(len(lines) + 1), 'Failed for "' . string(lines) . '"') + endfor + + + + bwipe! +endfunc + +" vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_close_count.vim b/src/nvim/testdir/test_close_count.vim new file mode 100644 index 0000000000..1f9adba32d --- /dev/null +++ b/src/nvim/testdir/test_close_count.vim @@ -0,0 +1,174 @@ + +" Tests for :[count]close! command +func Test_close_count() + enew! | only + + let wids = [win_getid()] + for i in range(5) + new + call add(wids, win_getid()) + endfor + + 4wincmd w + close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[5], wids[4], wids[3], wids[1], wids[0]], ids) + + 1close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[3], wids[1], wids[0]], ids) + + $close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[3], wids[1]], ids) + + 1wincmd w + 2close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[1]], ids) + + 1wincmd w + new + call add(wids, win_getid()) + new + call add(wids, win_getid()) + 2wincmd w + -1close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[6], wids[4], wids[1]], ids) + + 2wincmd w + +1close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[6], wids[4]], ids) + + only! +endfunc + +" Tests for :[count]hide command +func Test_hide_count() + enew! | only + + let wids = [win_getid()] + for i in range(5) + new + call add(wids, win_getid()) + endfor + + 4wincmd w + .hide + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[5], wids[4], wids[3], wids[1], wids[0]], ids) + + 1hide + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[3], wids[1], wids[0]], ids) + + $hide + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[3], wids[1]], ids) + + 1wincmd w + 2hide + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[1]], ids) + + 1wincmd w + new + call add(wids, win_getid()) + new + call add(wids, win_getid()) + 3wincmd w + -hide + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[7], wids[4], wids[1]], ids) + + 2wincmd w + +hide + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[7], wids[4]], ids) + + only! +endfunc + +" Tests for :[count]close! command with 'hidden' +func Test_hidden_close_count() + enew! | only + + let wids = [win_getid()] + for i in range(5) + new + call add(wids, win_getid()) + endfor + + set hidden + + $ hide + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[5], wids[4], wids[3], wids[2], wids[1]], ids) + + $-1 close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[5], wids[4], wids[3], wids[1]], ids) + + 1wincmd w + .+close! + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[5], wids[3], wids[1]], ids) + + set nohidden + only! +endfunc + +" Tests for 'CTRL-W c' command to close windows. +func Test_winclose_command() + enew! | only + + let wids = [win_getid()] + for i in range(5) + new + call add(wids, win_getid()) + endfor + + set hidden + + 4wincmd w + exe "normal \<C-W>c" + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[5], wids[4], wids[3], wids[1], wids[0]], ids) + + exe "normal 1\<C-W>c" + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[3], wids[1], wids[0]], ids) + + exe "normal 9\<C-W>c" + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[3], wids[1]], ids) + + 1wincmd w + exe "normal 2\<C-W>c" + let ids = [] + windo call add(ids, win_getid()) + call assert_equal([wids[4], wids[1]], ids) + + set nohidden + only! +endfunc diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim index ac44e09a5a..673246e1fb 100644 --- a/src/nvim/testdir/test_cmdline.vim +++ b/src/nvim/testdir/test_cmdline.vim @@ -1,5 +1,6 @@ " Tests for editing the command line. + func Test_complete_tab() call writefile(['testfile'], 'Xtestfile') call feedkeys(":e Xtestf\t\r", "tx") @@ -25,8 +26,62 @@ func Test_complete_wildmenu() set nowildmenu endfunc +func Test_map_completion() + if !has('cmdline_compl') + return + endif + call feedkeys(":map <unique> <si\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <unique> <silent>', getreg(':')) + call feedkeys(":map <script> <un\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <script> <unique>', getreg(':')) + call feedkeys(":map <expr> <sc\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <expr> <script>', getreg(':')) + call feedkeys(":map <buffer> <e\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <buffer> <expr>', getreg(':')) + call feedkeys(":map <nowait> <b\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <nowait> <buffer>', getreg(':')) + call feedkeys(":map <special> <no\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <special> <nowait>', getreg(':')) + call feedkeys(":map <silent> <sp\<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"map <silent> <special>', getreg(':')) +endfunc + +func Test_match_completion() + if !has('cmdline_compl') + return + endif + hi Aardig ctermfg=green + call feedkeys(":match \<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"match Aardig', getreg(':')) + call feedkeys(":match \<S-Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"match none', getreg(':')) +endfunc + +func Test_highlight_completion() + if !has('cmdline_compl') + return + endif + hi Aardig ctermfg=green + call feedkeys(":hi \<Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"hi Aardig', getreg(':')) + call feedkeys(":hi li\<S-Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"hi link', getreg(':')) + call feedkeys(":hi d\<S-Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"hi default', getreg(':')) + call feedkeys(":hi c\<S-Tab>\<Home>\"\<CR>", 'xt') + call assert_equal('"hi clear', getreg(':')) + + " A cleared group does not show up in completions. + hi Anders ctermfg=green + call assert_equal(['Aardig', 'Anders'], getcompletion('A', 'highlight')) + hi clear Aardig + call assert_equal(['Anders'], getcompletion('A', 'highlight')) + hi clear Anders + call assert_equal([], getcompletion('A', 'highlight')) +endfunc + func Test_expr_completion() - if !(has('cmdline_compl') && has('eval')) + if !has('cmdline_compl') return endif for cmd in [ @@ -265,17 +320,17 @@ func Test_paste_in_cmdline() endfunc func Test_remove_char_in_cmdline() - call feedkeys(":abc def\<S-Left>\<Del>\<C-B>\"\<CR>", 'tx') - call assert_equal('"abc ef', @:) + call feedkeys(":abc def\<S-Left>\<Del>\<C-B>\"\<CR>", 'tx') + call assert_equal('"abc ef', @:) - call feedkeys(":abc def\<S-Left>\<BS>\<C-B>\"\<CR>", 'tx') - call assert_equal('"abcdef', @:) + call feedkeys(":abc def\<S-Left>\<BS>\<C-B>\"\<CR>", 'tx') + call assert_equal('"abcdef', @:) - call feedkeys(":abc def ghi\<S-Left>\<C-W>\<C-B>\"\<CR>", 'tx') - call assert_equal('"abc ghi', @:) + call feedkeys(":abc def ghi\<S-Left>\<C-W>\<C-B>\"\<CR>", 'tx') + call assert_equal('"abc ghi', @:) - call feedkeys(":abc def\<S-Left>\<C-U>\<C-B>\"\<CR>", 'tx') - call assert_equal('"def', @:) + call feedkeys(":abc def\<S-Left>\<C-U>\<C-B>\"\<CR>", 'tx') + call assert_equal('"def', @:) endfunc func Test_illegal_address1() @@ -330,4 +385,36 @@ func Test_cmdline_search_range() bwipe! endfunc +" Tests for getcmdline(), getcmdpos() and getcmdtype() +func Check_cmdline(cmdtype) + call assert_equal('MyCmd a', getcmdline()) + call assert_equal(8, getcmdpos()) + call assert_equal(a:cmdtype, getcmdtype()) + return '' +endfunc + +func Test_getcmdtype() + call feedkeys(":MyCmd a\<C-R>=Check_cmdline(':')\<CR>\<Esc>", "xt") + + let cmdtype = '' + debuggreedy + call feedkeys(":debug echo 'test'\<CR>", "t") + call feedkeys("let cmdtype = \<C-R>=string(getcmdtype())\<CR>\<CR>", "t") + call feedkeys("cont\<CR>", "xt") + 0debuggreedy + call assert_equal('>', cmdtype) + + call feedkeys("/MyCmd a\<C-R>=Check_cmdline('/')\<CR>\<Esc>", "xt") + call feedkeys("?MyCmd a\<C-R>=Check_cmdline('?')\<CR>\<Esc>", "xt") + + call feedkeys(":call input('Answer?')\<CR>", "t") + call feedkeys("MyCmd a\<C-R>=Check_cmdline('@')\<CR>\<C-C>", "xt") + + call feedkeys(":insert\<CR>MyCmd a\<C-R>=Check_cmdline('-')\<CR>\<Esc>", "xt") + + cnoremap <expr> <F6> Check_cmdline('=') + call feedkeys("a\<C-R>=MyCmd a\<F6>\<Esc>\<Esc>", "xt") + cunmap <F6> +endfunc + set cpo& diff --git a/src/nvim/testdir/test_command_count.vim b/src/nvim/testdir/test_command_count.vim index e438a8b077..2d793ed88f 100644 --- a/src/nvim/testdir/test_command_count.vim +++ b/src/nvim/testdir/test_command_count.vim @@ -1,6 +1,7 @@ " Test for user command counts. func Test_command_count_0() + let bufnr = bufnr('%') set hidden set noswapfile @@ -15,17 +16,17 @@ func Test_command_count_0() command! -range=% -addr=buffers RangeBuffersAll :let lines = [<line1>, <line2>] .,$RangeLoadedBuffers - call assert_equal([1, 1], lines) + call assert_equal([bufnr, bufnr], lines) %RangeLoadedBuffers - call assert_equal([1, 1], lines) + call assert_equal([bufnr, bufnr], lines) RangeLoadedBuffersAll - call assert_equal([1, 1], lines) + call assert_equal([bufnr, bufnr], lines) .,$RangeBuffers - call assert_equal([1, lastbuf], lines) + call assert_equal([bufnr, lastbuf], lines) %RangeBuffers - call assert_equal([1, lastbuf], lines) + call assert_equal([bufnr, lastbuf], lines) RangeBuffersAll - call assert_equal([1, lastbuf], lines) + call assert_equal([bufnr, lastbuf], lines) delcommand RangeLoadedBuffers delcommand RangeLoadedBuffersAll @@ -138,6 +139,7 @@ func Test_command_count_2() endfunc func Test_command_count_3() + let bufnr = bufnr('%') se nohidden e aaa let buf_aaa = bufnr('%') @@ -145,7 +147,7 @@ func Test_command_count_3() let buf_bbb = bufnr('%') e ccc let buf_ccc = bufnr('%') - buf 1 + exe bufnr . 'buf' call assert_equal([1, 1, 1], [buflisted(buf_aaa), buflisted(buf_bbb), buflisted(buf_ccc)]) exe buf_bbb . "," . buf_ccc . "bdelete" call assert_equal([1, 0, 0], [buflisted(buf_aaa), buflisted(buf_bbb), buflisted(buf_ccc)]) @@ -155,7 +157,7 @@ endfunc func Test_command_count_4() %argd - let bufnr = bufnr('$') + 1 + let bufnr = bufnr('$') arga aa bb cc dd ee ff 3argu let args = [] @@ -171,6 +173,8 @@ func Test_command_count_4() only! exe bufnr . 'buf' + bnext + let bufnr = bufnr('%') let buffers = [] .,$-bufdo call add(buffers, bufnr('%')) call assert_equal([bufnr, bufnr + 1, bufnr + 2, bufnr + 3, bufnr + 4], buffers) diff --git a/src/nvim/testdir/test_curswant.vim b/src/nvim/testdir/test_curswant.vim new file mode 100644 index 0000000000..e54cd4b280 --- /dev/null +++ b/src/nvim/testdir/test_curswant.vim @@ -0,0 +1,23 @@ +" Tests for curswant not changing when setting an option + +func Test_curswant() + new + call append(0, ['1234567890', '12345']) + + normal! ggf8j + call assert_equal(7, winsaveview().curswant) + let &tabstop=&tabstop + call assert_equal(4, winsaveview().curswant) + + normal! ggf8j + call assert_equal(7, winsaveview().curswant) + let &timeoutlen=&timeoutlen + call assert_equal(7, winsaveview().curswant) + + normal! ggf8j + call assert_equal(7, winsaveview().curswant) + let &ttimeoutlen=&ttimeoutlen + call assert_equal(7, winsaveview().curswant) + + enew! +endfunc diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim index 8ee82bd538..d95b29759e 100644 --- a/src/nvim/testdir/test_diffmode.vim +++ b/src/nvim/testdir/test_diffmode.vim @@ -198,14 +198,69 @@ func Test_diffget_diffput() call assert_fails('diffget', 'E101:') windo diffoff - bwipe! - bwipe! - enew! + %bwipe! +endfunc + +func Test_dp_do_buffer() + e! one + let bn1=bufnr('%') + let l = range(60) + call setline(1, l) + diffthis + + new two + let l[10] = 'one' + let l[20] = 'two' + let l[30] = 'three' + let l[40] = 'four' + let l[50] = 'five' + call setline(1, l) + diffthis + + " dp and do with invalid buffer number. + 11 + call assert_fails('norm 99999dp', 'E102:') + call assert_fails('norm 99999do', 'E102:') + call assert_fails('diffput non_existing_buffer', 'E94:') + call assert_fails('diffget non_existing_buffer', 'E94:') + + " dp and do with valid buffer number. + call assert_equal('one', getline('.')) + exe 'norm ' . bn1 . 'do' + call assert_equal('10', getline('.')) + 21 + call assert_equal('two', getline('.')) + diffget one + call assert_equal('20', getline('.')) + + 31 + exe 'norm ' . bn1 . 'dp' + 41 + diffput one + wincmd w + 31 + call assert_equal('three', getline('.')) + 41 + call assert_equal('four', getline('.')) + + " dp and do with buffer number which is not in diff mode. + new not_in_diff_mode + let bn3=bufnr('%') + wincmd w + 51 + call assert_fails('exe "norm" . bn3 . "dp"', 'E103:') + call assert_fails('exe "norm" . bn3 . "do"', 'E103:') + call assert_fails('diffput not_in_diff_mode', 'E94:') + call assert_fails('diffget not_in_diff_mode', 'E94:') + + windo diffoff + %bwipe! endfunc func Test_diffoff() enew! call setline(1, ['Two', 'Three']) + redraw let normattr = screenattr(1, 1) diffthis botright vert new @@ -220,10 +275,107 @@ func Test_diffoff() bwipe! endfunc +func Test_diffopt_icase() + set diffopt=icase,foldcolumn:0 + + e one + call setline(1, ['One', 'Two', 'Three', 'Four']) + redraw + let normattr = screenattr(1, 1) + diffthis + + botright vert new two + call setline(1, ['one', 'TWO', 'Three ', 'Four']) + diffthis + + redraw + call assert_equal(normattr, screenattr(1, 1)) + call assert_equal(normattr, screenattr(2, 1)) + call assert_notequal(normattr, screenattr(3, 1)) + call assert_equal(normattr, screenattr(4, 1)) + + diffoff! + %bwipe! + set diffopt& +endfunc + +func Test_diffopt_iwhite() + set diffopt=iwhite,foldcolumn:0 + + e one + " Difference in trailing spaces should be ignored, + " but not other space differences. + call setline(1, ["One \t", 'Two', 'Three', 'Four']) + redraw + let normattr = screenattr(1, 1) + diffthis + + botright vert new two + call setline(1, ["One\t ", "Two\t ", 'Three', ' Four']) + diffthis + + redraw + call assert_equal(normattr, screenattr(1, 1)) + call assert_equal(normattr, screenattr(2, 1)) + call assert_equal(normattr, screenattr(3, 1)) + call assert_notequal(normattr, screenattr(4, 1)) + + diffoff! + %bwipe! + set diffopt& +endfunc + +func Test_diffopt_context() + enew! + call setline(1, ['1', '2', '3', '4', '5', '6', '7']) + diffthis + new + call setline(1, ['1', '2', '3', '4', '5x', '6', '7']) + diffthis + + set diffopt=context:2 + call assert_equal('+-- 2 lines: 1', foldtextresult(1)) + set diffopt=context:1 + call assert_equal('+-- 3 lines: 1', foldtextresult(1)) + + diffoff! + %bwipe! + set diffopt& +endfunc + +func Test_diffopt_horizontal() + set diffopt=horizontal + diffsplit + + call assert_equal(&columns, winwidth(1)) + call assert_equal(&columns, winwidth(2)) + call assert_equal(&lines, winheight(1) + winheight(2) + 3) + call assert_inrange(0, 1, winheight(1) - winheight(2)) + + set diffopt& + diffoff! + %bwipe +endfunc + +func Test_diffopt_vertical() + set diffopt=vertical + diffsplit + + call assert_equal(&lines - 2, winheight(1)) + call assert_equal(&lines - 2, winheight(2)) + call assert_equal(&columns, winwidth(1) + winwidth(2) + 1) + call assert_inrange(0, 1, winwidth(1) - winwidth(2)) + + set diffopt& + diffoff! + %bwipe +endfunc + func Test_diffoff_hidden() set diffopt=filler,foldcolumn:0 e! one call setline(1, ['Two', 'Three']) + redraw let normattr = screenattr(1, 1) diffthis botright vert new two @@ -272,3 +424,140 @@ func Test_setting_cursor() call delete('Xtest1') call delete('Xtest2') endfunc + +func Test_diff_move_to() + new + call setline(1, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + diffthis + vnew + call setline(1, [1, '2x', 3, 4, 4, 5, '6x', 7, '8x', 9, '10x']) + diffthis + norm ]c + call assert_equal(2, line('.')) + norm 3]c + call assert_equal(9, line('.')) + norm 10]c + call assert_equal(11, line('.')) + norm [c + call assert_equal(9, line('.')) + norm 2[c + call assert_equal(5, line('.')) + norm 10[c + call assert_equal(2, line('.')) + %bwipe! +endfunc + +func Test_diffexpr() + if !executable('diff') + return + endif + + func DiffExpr() + silent exe '!diff ' . v:fname_in . ' ' . v:fname_new . '>' . v:fname_out + endfunc + set diffexpr=DiffExpr() + set diffopt=foldcolumn:0 + + enew! + call setline(1, ['one', 'two', 'three']) + redraw + let normattr = screenattr(1, 1) + diffthis + + botright vert new + call setline(1, ['one', 'two', 'three.']) + diffthis + + redraw + call assert_equal(normattr, screenattr(1, 1)) + call assert_equal(normattr, screenattr(2, 1)) + call assert_notequal(normattr, screenattr(3, 1)) + + diffoff! + %bwipe! + set diffexpr& diffopt& +endfunc + +func Test_diffpatch() + " The patch program on MS-Windows may fail or hang. + if !executable('patch') || !has('unix') + return + endif + new + insert +*************** +*** 1,3 **** + 1 +! 2 + 3 +--- 1,4 ---- + 1 +! 2x + 3 ++ 4 +. + saveas Xpatch + bwipe! + new + call assert_fails('diffpatch Xpatch', 'E816:') + + for name in ['Xpatch', 'Xpatch$HOME', 'Xpa''tch'] + call setline(1, ['1', '2', '3']) + if name != 'Xpatch' + call rename('Xpatch', name) + endif + exe 'diffpatch ' . escape(name, '$') + call assert_equal(['1', '2x', '3', '4'], getline(1, '$')) + if name != 'Xpatch' + call rename(name, 'Xpatch') + endif + bwipe! + endfor + + call delete('Xpatch') + bwipe! +endfunc + +func Test_diff_too_many_buffers() + for i in range(1, 8) + exe "new Xtest" . i + diffthis + endfor + new Xtest9 + call assert_fails('diffthis', 'E96:') + %bwipe! +endfunc + +func Test_diff_nomodifiable() + new + call setline(1, [1, 2, 3, 4]) + setl nomodifiable + diffthis + vnew + call setline(1, ['1x', 2, 3, 3, 4]) + diffthis + call assert_fails('norm dp', 'E793:') + setl nomodifiable + call assert_fails('norm do', 'E21:') + %bwipe! +endfunc + +func Test_diff_lastline() + enew! + only! + call setline(1, ['This is a ', 'line with five ', 'rows']) + diffthis + botright vert new + call setline(1, ['This is', 'a line with ', 'four rows']) + diffthis + 1 + call feedkeys("Je a\<CR>", 'tx') + call feedkeys("Je a\<CR>", 'tx') + let w1lines = winline() + wincmd w + $ + let w2lines = winline() + call assert_equal(w2lines, w1lines) + bwipe! + bwipe! +endfunc diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim new file mode 100644 index 0000000000..8f815478c2 --- /dev/null +++ b/src/nvim/testdir/test_edit.vim @@ -0,0 +1,1325 @@ +" Test for edit functions +" +if exists("+t_kD") + let &t_kD="[3;*~" +endif + +" Needed for testing basic rightleft: Test_edit_rightleft +source view_util.vim + +" Needs to come first until the bug in getchar() is +" fixed: https://groups.google.com/d/msg/vim_dev/fXL9yme4H4c/bOR-U6_bAQAJ +func! Test_edit_00b() + new + call setline(1, ['abc ']) + inoreabbr <buffer> h here some more + call cursor(1, 4) + " <c-l> expands the abbreviation and ends insertmode + call feedkeys(":set im\<cr> h\<c-l>:set noim\<cr>", 'tix') + call assert_equal(['abc here some more '], getline(1,'$')) + iunabbr <buffer> h + bw! +endfunc + +func! Test_edit_01() + " set for Travis CI? + " set nocp noesckeys + new + " 1) empty buffer + call assert_equal([''], getline(1,'$')) + " 2) delete in an empty line + call feedkeys("i\<del>\<esc>", 'tnix') + call assert_equal([''], getline(1,'$')) + %d + " 3) delete one character + call setline(1, 'a') + call feedkeys("i\<del>\<esc>", 'tnix') + call assert_equal([''], getline(1,'$')) + %d + " 4) delete a multibyte character + if has("multi_byte") + call setline(1, "\u0401") + call feedkeys("i\<del>\<esc>", 'tnix') + call assert_equal([''], getline(1,'$')) + %d + endif + " 5.1) delete linebreak with 'bs' option containing eol + let _bs=&bs + set bs=eol + call setline(1, ["abc def", "ghi jkl"]) + call cursor(1, 1) + call feedkeys("A\<del>\<esc>", 'tnix') + call assert_equal(['abc defghi jkl'], getline(1, 2)) + %d + " 5.2) delete linebreak with backspace option w/out eol + set bs= + call setline(1, ["abc def", "ghi jkl"]) + call cursor(1, 1) + call feedkeys("A\<del>\<esc>", 'tnix') + call assert_equal(["abc def", "ghi jkl"], getline(1, 2)) + let &bs=_bs + bw! +endfunc + +func! Test_edit_02() + " Change cursor position in InsertCharPre command + new + call setline(1, 'abc') + call cursor(1, 1) + fu! DoIt(...) + call cursor(1, 4) + if len(a:000) + let v:char=a:1 + endif + endfu + au InsertCharPre <buffer> :call DoIt('y') + call feedkeys("ix\<esc>", 'tnix') + call assert_equal(['abcy'], getline(1, '$')) + " Setting <Enter> in InsertCharPre + au! InsertCharPre <buffer> :call DoIt("\n") + call setline(1, 'abc') + call cursor(1, 1) + call feedkeys("ix\<esc>", 'tnix') + call assert_equal(['abc', ''], getline(1, '$')) + %d + au! InsertCharPre + " Change cursor position in InsertEnter command + " 1) when setting v:char, keeps changed cursor position + au! InsertEnter <buffer> :call DoIt('y') + call setline(1, 'abc') + call cursor(1, 1) + call feedkeys("ix\<esc>", 'tnix') + call assert_equal(['abxc'], getline(1, '$')) + " 2) when not setting v:char, restores changed cursor position + au! InsertEnter <buffer> :call DoIt() + call setline(1, 'abc') + call cursor(1, 1) + call feedkeys("ix\<esc>", 'tnix') + call assert_equal(['xabc'], getline(1, '$')) + au! InsertEnter + delfu DoIt + bw! +endfunc + +func! Test_edit_03() + " Change cursor after <c-o> command to end of line + new + call setline(1, 'abc') + call cursor(1, 1) + call feedkeys("i\<c-o>$y\<esc>", 'tnix') + call assert_equal(['abcy'], getline(1, '$')) + %d + call setline(1, 'abc') + call cursor(1, 1) + call feedkeys("i\<c-o>80|y\<esc>", 'tnix') + call assert_equal(['abcy'], getline(1, '$')) + %d + call setline(1, 'abc') + call feedkeys("Ad\<c-o>:s/$/efg/\<cr>hij", 'tnix') + call assert_equal(['hijabcdefg'], getline(1, '$')) + bw! +endfunc + +func! Test_edit_04() + " test for :stopinsert + new + call setline(1, 'abc') + call cursor(1, 1) + call feedkeys("i\<c-o>:stopinsert\<cr>$", 'tnix') + call feedkeys("aX\<esc>", 'tnix') + call assert_equal(['abcX'], getline(1, '$')) + %d + bw! +endfunc + +func! Test_edit_05() + " test for folds being opened + new + call setline(1, ['abcX', 'abcX', 'zzzZ']) + call cursor(1, 1) + set foldmethod=manual foldopen+=insert + " create fold for those two lines + norm! Vjzf + call feedkeys("$ay\<esc>", 'tnix') + call assert_equal(['abcXy', 'abcX', 'zzzZ'], getline(1, '$')) + %d + call setline(1, ['abcX', 'abcX', 'zzzZ']) + call cursor(1, 1) + set foldmethod=manual foldopen-=insert + " create fold for those two lines + norm! Vjzf + call feedkeys("$ay\<esc>", 'tnix') + call assert_equal(['abcXy', 'abcX', 'zzzZ'], getline(1, '$')) + %d + bw! +endfunc + +func! Test_edit_06() + " Test in diff mode + if !has("diff") || !executable("diff") + return + endif + new + call setline(1, ['abc', 'xxx', 'yyy']) + vnew + call setline(1, ['abc', 'zzz', 'xxx', 'yyy']) + wincmd p + diffthis + wincmd p + diffthis + wincmd p + call cursor(2, 1) + norm! zt + call feedkeys("Ozzz\<esc>", 'tnix') + call assert_equal(['abc', 'zzz', 'xxx', 'yyy'], getline(1,'$')) + bw! + bw! +endfunc + +func! Test_edit_07() + " 1) Test with completion <c-l> when popupmenu is visible + new + call setline(1, 'J') + + func! ListMonths() + call complete(col('.')-1, ['January', 'February', 'March', + \ 'April', 'May', 'June', 'July', 'August', 'September', + \ 'October', 'November', 'December']) + return '' + endfunc + inoremap <buffer> <F5> <C-R>=ListMonths()<CR> + + call feedkeys("A\<f5>\<c-p>". repeat("\<down>", 6)."\<c-l>\<down>\<c-l>\<cr>", 'tx') + call assert_equal(['July'], getline(1,'$')) + " 1) Test completion when InsertCharPre kicks in + %d + call setline(1, 'J') + fu! DoIt() + if v:char=='u' + let v:char='an' + endif + endfu + au InsertCharPre <buffer> :call DoIt() + call feedkeys("A\<f5>\<c-p>u\<cr>\<c-l>\<cr>", 'tx') + call assert_equal(["Jan\<c-l>",''], getline(1,'$')) + %d + call setline(1, 'J') + call feedkeys("A\<f5>\<c-p>u\<down>\<c-l>\<cr>", 'tx') + call assert_equal(["January"], getline(1,'$')) + + delfu ListMonths + delfu DoIt + iunmap <buffer> <f5> + bw! +endfunc + +func! Test_edit_08() + throw 'skipped: moved to test/functional/legacy/edit_spec.lua' + " reset insertmode from i_ctrl-r_= + let g:bufnr = bufnr('%') + new + call setline(1, ['abc']) + call cursor(1, 4) + call feedkeys(":set im\<cr>ZZZ\<c-r>=setbufvar(g:bufnr,'&im', 0)\<cr>",'tnix') + call assert_equal(['abZZZc'], getline(1,'$')) + call assert_equal([0, 1, 1, 0], getpos('.')) + call assert_false(0, '&im') + bw! + unlet g:bufnr +endfunc + +func! Test_edit_09() + " test i_CTRL-\ combinations + new + call setline(1, ['abc', 'def', 'ghi']) + call cursor(1, 1) + " 1) CTRL-\ CTLR-N + call feedkeys(":set im\<cr>\<c-\>\<c-n>ccABC\<c-l>", 'txin') + call assert_equal(['ABC', 'def', 'ghi'], getline(1,'$')) + call setline(1, ['ABC', 'def', 'ghi']) + " 2) CTRL-\ CTLR-G + call feedkeys("j0\<c-\>\<c-g>ZZZ\<cr>\<c-l>", 'txin') + call assert_equal(['ABC', 'ZZZ', 'def', 'ghi'], getline(1,'$')) + call feedkeys("I\<c-\>\<c-g>YYY\<c-l>", 'txin') + call assert_equal(['ABC', 'ZZZ', 'YYYdef', 'ghi'], getline(1,'$')) + set noinsertmode + " 3) CTRL-\ CTRL-O + call setline(1, ['ABC', 'ZZZ', 'def', 'ghi']) + call cursor(1, 1) + call feedkeys("A\<c-o>ix", 'txin') + call assert_equal(['ABxC', 'ZZZ', 'def', 'ghi'], getline(1,'$')) + call feedkeys("A\<c-\>\<c-o>ix", 'txin') + call assert_equal(['ABxCx', 'ZZZ', 'def', 'ghi'], getline(1,'$')) + " 4) CTRL-\ a (should be inserted literally, not special after <c-\> + call setline(1, ['ABC', 'ZZZ', 'def', 'ghi']) + call cursor(1, 1) + call feedkeys("A\<c-\>a", 'txin') + call assert_equal(["ABC\<c-\>a", 'ZZZ', 'def', 'ghi'], getline(1, '$')) + bw! +endfunc + +func! Test_edit_10() + " Test for starting selectmode + new + set selectmode=key keymodel=startsel + call setline(1, ['abc', 'def', 'ghi']) + call cursor(1, 4) + call feedkeys("A\<s-home>start\<esc>", 'txin') + call assert_equal(['startdef', 'ghi'], getline(1, '$')) + set selectmode= keymodel= + bw! +endfunc + +func! Test_edit_11() + " Test that indenting kicks in + new + set cindent + call setline(1, ['{', '', '']) + call cursor(2, 1) + call feedkeys("i\<c-f>int c;\<esc>", 'tnix') + call cursor(3, 1) + call feedkeys("i/* comment */", 'tnix') + call assert_equal(['{', "\<tab>int c;", "/* comment */"], getline(1, '$')) + " added changed cindentkeys slightly + set cindent cinkeys+=*/ + call setline(1, ['{', '', '']) + call cursor(2, 1) + call feedkeys("i\<c-f>int c;\<esc>", 'tnix') + call cursor(3, 1) + call feedkeys("i/* comment */", 'tnix') + call assert_equal(['{', "\<tab>int c;", "\<tab>/* comment */"], getline(1, '$')) + set cindent cinkeys+==end + call feedkeys("oend\<cr>\<esc>", 'tnix') + call assert_equal(['{', "\<tab>int c;", "\<tab>/* comment */", "\tend", ''], getline(1, '$')) + set cinkeys-==end + %d + " Use indentexpr instead of cindenting + func! Do_Indent() + if v:lnum == 3 + return 3*shiftwidth() + else + return 2*shiftwidth() + endif + endfunc + setl indentexpr=Do_Indent() indentkeys+=*/ + call setline(1, ['{', '', '']) + call cursor(2, 1) + call feedkeys("i\<c-f>int c;\<esc>", 'tnix') + call cursor(3, 1) + call feedkeys("i/* comment */", 'tnix') + call assert_equal(['{', "\<tab>\<tab>int c;", "\<tab>\<tab>\<tab>/* comment */"], getline(1, '$')) + set cinkeys&vim indentkeys&vim + set nocindent indentexpr= + delfu Do_Indent + bw! +endfunc + +func! Test_edit_12() + " Test changing indent in replace mode + new + call setline(1, ["\tabc", "\tdef"]) + call cursor(2, 4) + call feedkeys("R^\<c-d>", 'tnix') + call assert_equal(["\tabc", "def"], getline(1, '$')) + call assert_equal([0, 2, 2, 0], getpos('.')) + %d + call setline(1, ["\tabc", "\t\tdef"]) + call cursor(2, 2) + call feedkeys("R^\<c-d>", 'tnix') + call assert_equal(["\tabc", "def"], getline(1, '$')) + call assert_equal([0, 2, 1, 0], getpos('.')) + %d + call setline(1, ["\tabc", "\t\tdef"]) + call cursor(2, 2) + call feedkeys("R\<c-t>", 'tnix') + call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$')) + call assert_equal([0, 2, 2, 0], getpos('.')) + bw! + 10vnew + call setline(1, ["\tabc", "\t\tdef"]) + call cursor(2, 2) + call feedkeys("R\<c-t>", 'tnix') + call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$')) + call assert_equal([0, 2, 2, 0], getpos('.')) + %d + set sw=4 + call setline(1, ["\tabc", "\t\tdef"]) + call cursor(2, 2) + call feedkeys("R\<c-t>\<c-t>", 'tnix') + call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$')) + call assert_equal([0, 2, 2, 0], getpos('.')) + %d + call setline(1, ["\tabc", "\t\tdef"]) + call cursor(2, 2) + call feedkeys("R\<c-t>\<c-t>", 'tnix') + call assert_equal(["\tabc", "\t\t\tdef"], getline(1, '$')) + call assert_equal([0, 2, 2, 0], getpos('.')) + set et + set sw& et& + %d + call setline(1, ["\t/*"]) + set formatoptions=croql + call cursor(1, 3) + call feedkeys("A\<cr>\<cr>/", 'tnix') + call assert_equal(["\t/*", " *", " */"], getline(1, '$')) + set formatoptions& + bw! +endfunc + +func! Test_edit_13() + " Test smartindenting + if exists("+smartindent") + new + set smartindent autoindent + call setline(1, ["\tabc"]) + call feedkeys("A {\<cr>more\<cr>}\<esc>", 'tnix') + call assert_equal(["\tabc {", "\t\tmore", "\t}"], getline(1, '$')) + set smartindent& autoindent& + bw! + endif +endfunc + +func! Test_edit_CR() + " Test for <CR> in insert mode + " basically only in quickfix mode ist tested, the rest + " has been taken care of by other tests + if !has("quickfix") + return + endif + botright new + call writefile(range(1, 10), 'Xqflist.txt') + call setqflist([{'filename': 'Xqflist.txt', 'lnum': 2}]) + copen + set modifiable + call feedkeys("A\<cr>", 'tnix') + call assert_equal('Xqflist.txt', bufname('')) + call assert_equal(2, line('.')) + cclose + botright new + call setloclist(0, [{'filename': 'Xqflist.txt', 'lnum': 10}]) + lopen + set modifiable + call feedkeys("A\<cr>", 'tnix') + call assert_equal('Xqflist.txt', bufname('')) + call assert_equal(10, line('.')) + call feedkeys("A\<Enter>", 'tnix') + call feedkeys("A\<kEnter>", 'tnix') + call feedkeys("A\n", 'tnix') + call feedkeys("A\r", 'tnix') + call assert_equal(map(range(1, 10), 'string(v:val)') + ['', '', '', ''], getline(1, '$')) + bw! + lclose + call delete('Xqflist.txt') +endfunc + +func! Test_edit_CTRL_() + " disabled for Windows builds, why? + if !has("multi_byte") || !has("rightleft") || has("win32") + return + endif + let _encoding=&encoding + set encoding=utf-8 + " Test for CTRL-_ + new + call setline(1, ['abc']) + call cursor(1, 1) + call feedkeys("i\<c-_>xyz\<esc>", 'tnix') + call assert_equal(["\<C-_>xyzabc"], getline(1, '$')) + call assert_false(&revins) + set ari + call setline(1, ['abc']) + call cursor(1, 1) + call feedkeys("i\<c-_>xyz\<esc>", 'tnix') + call assert_equal(["æèñabc"], getline(1, '$')) + call assert_true(&revins) + call setline(1, ['abc']) + call cursor(1, 1) + call feedkeys("i\<c-_>xyz\<esc>", 'tnix') + call assert_equal(["xyzabc"], getline(1, '$')) + call assert_false(&revins) + set noari + let &encoding=_encoding + bw! +endfunc + +" needs to come first, to have the @. register empty +func! Test_edit_00a_CTRL_A() + " Test pressing CTRL-A + new + call setline(1, repeat([''], 5)) + call cursor(1, 1) + try + call feedkeys("A\<NUL>", 'tnix') + catch /^Vim\%((\a\+)\)\=:E29/ + call assert_true(1, 'E29 error caught') + endtry + call cursor(1, 1) + call feedkeys("Afoobar \<esc>", 'tnix') + call cursor(2, 1) + call feedkeys("A\<c-a>more\<esc>", 'tnix') + call cursor(3, 1) + call feedkeys("A\<NUL>and more\<esc>", 'tnix') + call assert_equal(['foobar ', 'foobar more', 'foobar morend more', '', ''], getline(1, '$')) + bw! +endfunc + +func! Test_edit_CTRL_EY() + " Ctrl-E/ Ctrl-Y in insert mode completion to scroll + 10new + call setline(1, range(1, 100)) + call cursor(30, 1) + norm! z. + call feedkeys("A\<c-x>\<c-e>\<c-e>\<c-e>\<c-e>\<c-e>", 'tnix') + call assert_equal(30, winsaveview()['topline']) + call assert_equal([0, 30, 2, 0], getpos('.')) + call feedkeys("A\<c-x>\<c-e>\<c-e>\<c-e>\<c-e>\<c-e>", 'tnix') + call feedkeys("A\<c-x>".repeat("\<c-y>", 10), 'tnix') + call assert_equal(21, winsaveview()['topline']) + call assert_equal([0, 30, 2, 0], getpos('.')) + bw! +endfunc + +func! Test_edit_CTRL_G() + new + call setline(1, ['foobar', 'foobar', 'foobar']) + call cursor(2, 4) + call feedkeys("ioooooooo\<c-g>k\<c-r>.\<esc>", 'tnix') + call assert_equal(['foooooooooobar', 'foooooooooobar', 'foobar'], getline(1, '$')) + call assert_equal([0, 1, 11, 0], getpos('.')) + call feedkeys("i\<c-g>k\<esc>", 'tnix') + call assert_equal([0, 1, 10, 0], getpos('.')) + call cursor(2, 4) + call feedkeys("i\<c-g>jzzzz\<esc>", 'tnix') + call assert_equal(['foooooooooobar', 'foooooooooobar', 'foozzzzbar'], getline(1, '$')) + call assert_equal([0, 3, 7, 0], getpos('.')) + call feedkeys("i\<c-g>j\<esc>", 'tnix') + call assert_equal([0, 3, 6, 0], getpos('.')) + bw! +endfunc + +func! Test_edit_CTRL_I() + " Tab in completion mode + let path=expand("%:p:h") + new + call setline(1, [path."/", '']) + call feedkeys("Arunt\<c-x>\<c-f>\<tab>\<cr>\<esc>", 'tnix') + call assert_match('runtest\.vim', getline(1)) + %d + call writefile(['one', 'two', 'three'], 'Xinclude.txt') + let include='#include Xinclude.txt' + call setline(1, [include, '']) + call cursor(2, 1) + call feedkeys("A\<c-x>\<tab>\<cr>\<esc>", 'tnix') + call assert_equal([include, 'one', ''], getline(1, '$')) + call feedkeys("2ggC\<c-x>\<tab>\<down>\<cr>\<esc>", 'tnix') + call assert_equal([include, 'two', ''], getline(1, '$')) + call feedkeys("2ggC\<c-x>\<tab>\<down>\<down>\<cr>\<esc>", 'tnix') + call assert_equal([include, 'three', ''], getline(1, '$')) + call feedkeys("2ggC\<c-x>\<tab>\<down>\<down>\<down>\<cr>\<esc>", 'tnix') + call assert_equal([include, '', ''], getline(1, '$')) + call delete("Xinclude.txt") + bw! +endfunc + +func! Test_edit_CTRL_K() + " Test pressing CTRL-K (basically only dictionary completion and digraphs + " the rest is already covered + call writefile(['A', 'AA', 'AAA', 'AAAA'], 'Xdictionary.txt') + set dictionary=Xdictionary.txt + new + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<cr>\<esc>", 'tnix') + call assert_equal(['AA', ''], getline(1, '$')) + %d + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<down>\<cr>\<esc>", 'tnix') + call assert_equal(['AAA'], getline(1, '$')) + %d + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<cr>\<esc>", 'tnix') + call assert_equal(['AAAA'], getline(1, '$')) + %d + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<down>\<cr>\<esc>", 'tnix') + call assert_equal(['A'], getline(1, '$')) + %d + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<down>\<down>\<down>\<down>\<cr>\<esc>", 'tnix') + call assert_equal(['AA'], getline(1, '$')) + + " press an unexecpted key after dictionary completion + %d + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<c-]>\<cr>\<esc>", 'tnix') + call assert_equal(['AA', ''], getline(1, '$')) + %d + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<c-s>\<cr>\<esc>", 'tnix') + call assert_equal(["AA\<c-s>", ''], getline(1, '$')) + %d + call setline(1, 'A') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-k>\<c-f>\<cr>\<esc>", 'tnix') + call assert_equal(["AA\<c-f>", ''], getline(1, '$')) + + set dictionary= + %d + call setline(1, 'A') + call cursor(1, 1) + let v:testing = 1 + try + call feedkeys("A\<c-x>\<c-k>\<esc>", 'tnix') + catch + " error sleeps 2 seconds, when v:testing is not set + let v:testing = 0 + endtry + call delete('Xdictionary.txt') + + if has("multi_byte") && !has("nvim") + call test_override("char_avail", 1) + set showcmd + %d + call feedkeys("A\<c-k>a:\<esc>", 'tnix') + call assert_equal(['ä'], getline(1, '$')) + call test_override("char_avail", 0) + set noshowcmd + endif + bw! +endfunc + +func! Test_edit_CTRL_L() + " Test Ctrl-X Ctrl-L (line completion) + new + set complete=. + call setline(1, ['one', 'two', 'three', '', '', '', '']) + call cursor(4, 1) + call feedkeys("A\<c-x>\<c-l>\<esc>", 'tnix') + call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$')) + 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 assert_equal(['one', 'two', 'three', 'three', '', '', ''], 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') + call assert_equal(['one', 'two', 'three', 't', '', '', ''], getline(1, '$')) + call feedkeys("cct\<c-x>\<c-l>\<c-p>\<c-p>\<c-p>\<esc>", 'tnix') + call assert_equal(['one', 'two', 'three', 'three', '', '', ''], getline(1, '$')) + set complete= + call cursor(5, 1) + call feedkeys("A\<c-x>\<c-l>\<c-p>\<c-n>\<esc>", 'tnix') + call assert_equal(['one', 'two', 'three', 'three', "\<c-l>\<c-p>\<c-n>", '', ''], getline(1, '$')) + set complete& + %d + if has("conceal") && has("syntax") && !has("nvim") + call setline(1, ['foo', 'bar', 'foobar']) + call test_override("char_avail", 1) + set conceallevel=2 concealcursor=n + syn on + syn match ErrorMsg "^bar" + call matchadd("Conceal", 'oo', 10, -1, {'conceal': 'X'}) + func! DoIt() + let g:change=1 + endfunc + au! TextChangedI <buffer> :call DoIt() + + call cursor(2, 1) + call assert_false(exists("g:change")) + call feedkeys("A \<esc>", 'tnix') + call assert_equal(['foo', 'bar ', 'foobar'], getline(1, '$')) + call assert_equal(1, g:change) + + call test_override("char_avail", 0) + call clearmatches() + syn off + au! TextChangedI + delfu DoIt + unlet! g:change + endif + bw! +endfunc + +func! Test_edit_CTRL_N() + " Check keyword completion + new + set complete=. + call setline(1, ['INFER', 'loWER', '', '', ]) + call cursor(3, 1) + call feedkeys("Ai\<c-n>\<cr>\<esc>", "tnix") + call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['INFER', 'loWER', 'i', 'LO', '', ''], getline(1, '$')) + %d + call setline(1, ['INFER', 'loWER', '', '', ]) + call cursor(3, 1) + set ignorecase infercase + call feedkeys("Ii\<c-n>\<cr>\<esc>", "tnix") + call feedkeys("ILO\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['INFER', 'loWER', 'infer', 'LOWER', '', ''], getline(1, '$')) + + set noignorecase noinfercase complete& + bw! +endfunc + +func! Test_edit_CTRL_O() + " Check for CTRL-O in insert mode + new + inoreabbr <buffer> h here some more + call setline(1, ['abc', 'def']) + call cursor(1, 1) + " Ctrl-O after an abbreviation + exe "norm A h\<c-o>:set nu\<cr> text" + call assert_equal(['abc here some more text', 'def'], getline(1, '$')) + call assert_true(&nu) + set nonu + iunabbr <buffer> h + " Ctrl-O at end of line with 've'=onemore + call cursor(1, 1) + call feedkeys("A\<c-o>:let g:a=getpos('.')\<cr>\<esc>", 'tnix') + call assert_equal([0, 1, 23, 0], g:a) + call cursor(1, 1) + set ve=onemore + call feedkeys("A\<c-o>:let g:a=getpos('.')\<cr>\<esc>", 'tnix') + call assert_equal([0, 1, 24, 0], g:a) + set ve= + unlet! g:a + bw! +endfunc + +func! Test_edit_CTRL_R() + throw 'skipped: Nvim does not support test_override()' + " Insert Register + new + call test_override("ALL", 1) + set showcmd + call feedkeys("AFOOBAR eins zwei\<esc>", 'tnix') + call feedkeys("O\<c-r>.", 'tnix') + call feedkeys("O\<c-r>=10*500\<cr>\<esc>", 'tnix') + call feedkeys("O\<c-r>=getreg('=', 1)\<cr>\<esc>", 'tnix') + call assert_equal(["getreg('=', 1)", '5000', "FOOBAR eins zwei", "FOOBAR eins zwei"], getline(1, '$')) + call test_override("ALL", 0) + set noshowcmd + bw! +endfunc + +func! Test_edit_CTRL_S() + " Test pressing CTRL-S (basically only spellfile completion) + " the rest is already covered + new + if !has("spell") + call setline(1, 'vim') + call feedkeys("A\<c-x>ss\<cr>\<esc>", 'tnix') + call assert_equal(['vims', ''], getline(1, '$')) + bw! + return + endif + call setline(1, 'vim') + " spell option not yet set + try + call feedkeys("A\<c-x>\<c-s>\<cr>\<esc>", 'tnix') + catch /^Vim\%((\a\+)\)\=:E756/ + call assert_true(1, 'error caught') + endtry + call assert_equal(['vim', ''], getline(1, '$')) + %d + setl spell spelllang=en + call setline(1, 'vim') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-s>\<cr>\<esc>", 'tnix') + call assert_equal(['Vim', ''], getline(1, '$')) + %d + call setline(1, 'vim') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-s>\<down>\<cr>\<esc>", 'tnix') + call assert_equal(['Aim'], getline(1, '$')) + %d + call setline(1, 'vim') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-s>\<c-p>\<cr>\<esc>", 'tnix') + call assert_equal(['vim', ''], getline(1, '$')) + %d + " empty buffer + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-s>\<c-p>\<cr>\<esc>", 'tnix') + call assert_equal(['', ''], getline(1, '$')) + setl nospell + bw! +endfunc + +func! Test_edit_CTRL_T() + " Check for CTRL-T and CTRL-X CTRL-T in insert mode + " 1) increase indent + new + call setline(1, "abc") + call cursor(1, 1) + call feedkeys("A\<c-t>xyz", 'tnix') + call assert_equal(["\<tab>abcxyz"], getline(1, '$')) + " 2) also when paste option is set + set paste + call setline(1, "abc") + call cursor(1, 1) + call feedkeys("A\<c-t>xyz", 'tnix') + call assert_equal(["\<tab>abcxyz"], getline(1, '$')) + set nopaste + " CTRL-X CTRL-T (thesaurus complete) + call writefile(['angry furious mad enraged'], 'Xthesaurus') + set thesaurus=Xthesaurus + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<cr>\<esc>", 'tnix') + call assert_equal(['mad', ''], getline(1, '$')) + %d + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['angry', ''], getline(1, '$')) + %d + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['furious', ''], getline(1, '$')) + %d + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['enraged', ''], getline(1, '$')) + %d + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<c-n>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['mad', ''], getline(1, '$')) + %d + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<c-n>\<c-n>\<c-n>\<c-n>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['mad', ''], getline(1, '$')) + " Using <c-p> <c-n> when 'complete' is empty + set complete= + %d + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['angry', ''], getline(1, '$')) + %d + call setline(1, 'mad') + call cursor(1, 1) + call feedkeys("A\<c-x>\<c-t>\<c-p>\<cr>\<esc>", 'tnix') + call assert_equal(['mad', ''], getline(1, '$')) + set complete& + + set thesaurus= + %d + call setline(1, 'mad') + call cursor(1, 1) + let v:testing = 1 + try + call feedkeys("A\<c-x>\<c-t>\<esc>", 'tnix') + catch + " error sleeps 2 seconds, when v:testing is not set + let v:testing = 0 + endtry + call assert_equal(['mad'], getline(1, '$')) + call delete('Xthesaurus') + bw! +endfunc + +func! Test_edit_CTRL_U() + " Test 'completefunc' + new + " -1, -2 and -3 are special return values + let g:special=0 + fun! CompleteMonths(findstart, base) + if a:findstart + " locate the start of the word + return g:special + else + " find months matching with "a:base" + let res = [] + for m in split("Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec") + if m =~ '^\c'.a:base + call add(res, {'word': m, 'abbr': m.' Month', 'icase': 0}) + endif + endfor + return {'words': res, 'refresh': 'always'} + endif + endfun + set completefunc=CompleteMonths + call setline(1, ['', '']) + call cursor(1, 1) + call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix') + call assert_equal(['X', '', ''], getline(1, '$')) + %d + let g:special=-1 + call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix') + call assert_equal(['XJan', ''], getline(1, '$')) + %d + let g:special=-2 + call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix') + call assert_equal(['X', ''], getline(1, '$')) + %d + let g:special=-3 + call feedkeys("AX\<c-x>\<c-u>\<cr>\<esc>", 'tnix') + call assert_equal(['X', ''], getline(1, '$')) + %d + let g:special=0 + call feedkeys("AM\<c-x>\<c-u>\<cr>\<esc>", 'tnix') + call assert_equal(['Mar', ''], getline(1, '$')) + %d + call feedkeys("AM\<c-x>\<c-u>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['May', ''], getline(1, '$')) + %d + call feedkeys("AM\<c-x>\<c-u>\<c-n>\<c-n>\<cr>\<esc>", 'tnix') + call assert_equal(['M', ''], getline(1, '$')) + delfu CompleteMonths + %d + try + call feedkeys("A\<c-x>\<c-u>", 'tnix') + call assert_fails(1, 'unknown completion function') + catch /^Vim\%((\a\+)\)\=:E117/ + call assert_true(1, 'E117 error caught') + endtry + set completefunc= + bw! +endfunc + +func! Test_edit_CTRL_Z() + " Ctrl-Z when insertmode is not set inserts it literally + new + call setline(1, 'abc') + call feedkeys("A\<c-z>\<esc>", 'tnix') + call assert_equal(["abc\<c-z>"], getline(1,'$')) + bw! + " TODO: How to Test Ctrl-Z in insert mode, e.g. suspend? +endfunc + +func! Test_edit_DROP() + if !has("dnd") + return + endif + new + call setline(1, ['abc def ghi']) + call cursor(1, 1) + try + call feedkeys("i\<Drop>\<Esc>", 'tnix') + call assert_fails(1, 'Invalid register name') + catch /^Vim\%((\a\+)\)\=:E353/ + call assert_true(1, 'error caught') + endtry + bw! +endfunc + +func! Test_edit_CTRL_V() + throw 'skipped: Nvim does not support test_override()' + if has("ebcdic") + return + endif + new + call setline(1, ['abc']) + call cursor(2, 1) + " force some redraws + set showmode showcmd + "call test_override_char_avail(1) + call test_override('ALL', 1) + call feedkeys("A\<c-v>\<c-n>\<c-v>\<c-l>\<c-v>\<c-b>\<esc>", 'tnix') + call assert_equal(["abc\x0e\x0c\x02"], getline(1, '$')) + + if has("rightleft") && exists("+rl") + set rl + call setline(1, ['abc']) + call cursor(2, 1) + call feedkeys("A\<c-v>\<c-n>\<c-v>\<c-l>\<c-v>\<c-b>\<esc>", 'tnix') + call assert_equal(["abc\x0e\x0c\x02"], getline(1, '$')) + set norl + endif + + call test_override('ALL', 0) + set noshowmode showcmd + bw! +endfunc + +func! Test_edit_F1() + " Pressing <f1> + new + call feedkeys(":set im\<cr>\<f1>\<c-l>", 'tnix') + set noinsertmode + call assert_equal('help', &buftype) + bw + bw +endfunc + +func! Test_edit_F21() + " Pressing <f21> + " sends a netbeans command + if has("netbeans_intg") + new + " I have no idea what this is supposed to do :) + call feedkeys("A\<F21>\<F1>\<esc>", 'tnix') + bw + endif +endfunc + +func! Test_edit_HOME_END() + " Test Home/End Keys + new + set foldopen+=hor + call setline(1, ['abc', 'def']) + call cursor(1, 1) + call feedkeys("AX\<Home>Y\<esc>", 'tnix') + call cursor(2, 1) + call feedkeys("iZ\<End>Y\<esc>", 'tnix') + call assert_equal(['YabcX', 'ZdefY'], getline(1, '$')) + + set foldopen-=hor + bw! +endfunc + +func! Test_edit_INS() + " Test for Pressing <Insert> + new + call setline(1, ['abc', 'def']) + call cursor(1, 1) + call feedkeys("i\<Insert>ZYX>", 'tnix') + call assert_equal(['ZYX>', 'def'], getline(1, '$')) + call setline(1, ['abc', 'def']) + call cursor(1, 1) + call feedkeys("i\<Insert>Z\<Insert>YX>", 'tnix') + call assert_equal(['ZYX>bc', 'def'], getline(1, '$')) + bw! +endfunc + +func! Test_edit_LEFT_RIGHT() + " Left, Shift-Left, Right, Shift-Right + new + call setline(1, ['abc def ghi', 'ABC DEF GHI', 'ZZZ YYY XXX']) + let _ww=&ww + set ww= + call cursor(2, 1) + call feedkeys("i\<left>\<esc>", 'tnix') + call assert_equal([0, 2, 1, 0], getpos('.')) + " Is this a bug, <s-left> does not respect whichwrap option + call feedkeys("i\<s-left>\<esc>", 'tnix') + call assert_equal([0, 1, 8, 0], getpos('.')) + call feedkeys("i". repeat("\<s-left>", 3). "\<esc>", 'tnix') + call assert_equal([0, 1, 1, 0], getpos('.')) + call feedkeys("i\<right>\<esc>", 'tnix') + call assert_equal([0, 1, 1, 0], getpos('.')) + call feedkeys("i\<right>\<right>\<esc>", 'tnix') + call assert_equal([0, 1, 2, 0], getpos('.')) + call feedkeys("A\<right>\<esc>", 'tnix') + call assert_equal([0, 1, 11, 0], getpos('.')) + call feedkeys("A\<s-right>\<esc>", 'tnix') + call assert_equal([0, 2, 1, 0], getpos('.')) + call feedkeys("i\<s-right>\<esc>", 'tnix') + call assert_equal([0, 2, 4, 0], getpos('.')) + call cursor(3, 11) + call feedkeys("A\<right>\<esc>", 'tnix') + call feedkeys("A\<s-right>\<esc>", 'tnix') + call assert_equal([0, 3, 11, 0], getpos('.')) + call cursor(2, 11) + " <S-Right> does not respect 'whichwrap' option + call feedkeys("A\<s-right>\<esc>", 'tnix') + call assert_equal([0, 3, 1, 0], getpos('.')) + " Check motion when 'whichwrap' contains cursor keys for insert mode + set ww+=[,] + call cursor(2, 1) + call feedkeys("i\<left>\<esc>", 'tnix') + call assert_equal([0, 1, 11, 0], getpos('.')) + call cursor(2, 11) + call feedkeys("A\<right>\<esc>", 'tnix') + call assert_equal([0, 3, 1, 0], getpos('.')) + call cursor(2, 11) + call feedkeys("A\<s-right>\<esc>", 'tnix') + call assert_equal([0, 3, 1, 0], getpos('.')) + let &ww = _ww + bw! +endfunc + +func! Test_edit_MOUSE() + " This is a simple test, since we not really using the mouse here + if !has("mouse") + return + endif + 10new + call setline(1, range(1, 100)) + call cursor(1, 1) + set mouse=a + call feedkeys("A\<ScrollWheelDown>\<esc>", 'tnix') + call assert_equal([0, 4, 1, 0], getpos('.')) + " This should move by one pageDown, but only moves + " by one line when the test is run... + call feedkeys("A\<S-ScrollWheelDown>\<esc>", 'tnix') + call assert_equal([0, 5, 1, 0], getpos('.')) + set nostartofline + call feedkeys("A\<C-ScrollWheelDown>\<esc>", 'tnix') + call assert_equal([0, 6, 1, 0], getpos('.')) + call feedkeys("A\<LeftMouse>\<esc>", 'tnix') + call assert_equal([0, 6, 1, 0], getpos('.')) + call feedkeys("A\<RightMouse>\<esc>", 'tnix') + call assert_equal([0, 6, 1, 0], getpos('.')) + call cursor(1, 100) + norm! zt + " this should move by a screen up, but when the test + " is run, it moves up to the top of the buffer... + call feedkeys("A\<ScrollWheelUp>\<esc>", 'tnix') + call assert_equal([0, 1, 1, 0], getpos('.')) + call cursor(1, 30) + norm! zt + call feedkeys("A\<S-ScrollWheelUp>\<esc>", 'tnix') + call assert_equal([0, 1, 1, 0], getpos('.')) + call cursor(1, 30) + norm! zt + call feedkeys("A\<C-ScrollWheelUp>\<esc>", 'tnix') + call assert_equal([0, 1, 1, 0], getpos('.')) + %d + call setline(1, repeat(["12345678901234567890"], 100)) + call cursor(2, 1) + call feedkeys("A\<ScrollWheelRight>\<esc>", 'tnix') + call assert_equal([0, 2, 20, 0], getpos('.')) + call feedkeys("A\<ScrollWheelLeft>\<esc>", 'tnix') + call assert_equal([0, 2, 20, 0], getpos('.')) + call feedkeys("A\<S-ScrollWheelRight>\<esc>", 'tnix') + call assert_equal([0, 2, 20, 0], getpos('.')) + call feedkeys("A\<S-ScrollWheelLeft>\<esc>", 'tnix') + call assert_equal([0, 2, 20, 0], getpos('.')) + call feedkeys("A\<C-ScrollWheelRight>\<esc>", 'tnix') + call assert_equal([0, 2, 20, 0], getpos('.')) + call feedkeys("A\<C-ScrollWheelLeft>\<esc>", 'tnix') + call assert_equal([0, 2, 20, 0], getpos('.')) + set mouse& startofline + bw! +endfunc + +func! Test_edit_PAGEUP_PAGEDOWN() + 10new + call setline(1, repeat(['abc def ghi'], 30)) + call cursor(1, 1) + call feedkeys("i\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 9, 1, 0], getpos('.')) + call feedkeys("i\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 17, 1, 0], getpos('.')) + call feedkeys("i\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 25, 1, 0], getpos('.')) + call feedkeys("i\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 30, 1, 0], getpos('.')) + call feedkeys("i\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 30, 1, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 29, 1, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 21, 1, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 13, 1, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 5, 1, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 5, 11, 0], getpos('.')) + " <S-Up> is the same as <PageUp> + " <S-Down> is the same as <PageDown> + call cursor(1, 1) + call feedkeys("i\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 9, 1, 0], getpos('.')) + call feedkeys("i\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 17, 1, 0], getpos('.')) + call feedkeys("i\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 25, 1, 0], getpos('.')) + call feedkeys("i\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 30, 1, 0], getpos('.')) + call feedkeys("i\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 30, 1, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 29, 1, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 21, 1, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 13, 1, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 5, 1, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 5, 11, 0], getpos('.')) + set nostartofline + call cursor(30, 11) + norm! zt + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 29, 11, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 21, 11, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 13, 11, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 5, 11, 0], getpos('.')) + call feedkeys("A\<PageUp>\<esc>", 'tnix') + call assert_equal([0, 5, 11, 0], getpos('.')) + call cursor(1, 1) + call feedkeys("A\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 9, 11, 0], getpos('.')) + call feedkeys("A\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 17, 11, 0], getpos('.')) + call feedkeys("A\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 25, 11, 0], getpos('.')) + call feedkeys("A\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 30, 11, 0], getpos('.')) + call feedkeys("A\<PageDown>\<esc>", 'tnix') + call assert_equal([0, 30, 11, 0], getpos('.')) + " <S-Up> is the same as <PageUp> + " <S-Down> is the same as <PageDown> + call cursor(30, 11) + norm! zt + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 29, 11, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 21, 11, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 13, 11, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 5, 11, 0], getpos('.')) + call feedkeys("A\<S-Up>\<esc>", 'tnix') + call assert_equal([0, 5, 11, 0], getpos('.')) + call cursor(1, 1) + call feedkeys("A\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 9, 11, 0], getpos('.')) + call feedkeys("A\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 17, 11, 0], getpos('.')) + call feedkeys("A\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 25, 11, 0], getpos('.')) + call feedkeys("A\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 30, 11, 0], getpos('.')) + call feedkeys("A\<S-Down>\<esc>", 'tnix') + call assert_equal([0, 30, 11, 0], getpos('.')) + bw! +endfunc + +func! Test_edit_forbidden() + new + " 1) edit in the sandbox is not allowed + call setline(1, 'a') + com! Sandbox :sandbox call feedkeys("i\<del>\<esc>", 'tnix') + call assert_fails(':Sandbox', 'E48:') + com! Sandbox :sandbox exe "norm! i\<del>" + call assert_fails(':Sandbox', 'E48:') + delcom Sandbox + call assert_equal(['a'], getline(1,'$')) + " 2) edit with textlock set + fu! DoIt() + call feedkeys("i\<del>\<esc>", 'tnix') + endfu + au InsertCharPre <buffer> :call DoIt() + try + call feedkeys("ix\<esc>", 'tnix') + call assert_fails(1, 'textlock') + catch /^Vim\%((\a\+)\)\=:E523/ " catch E523: not allowed here + endtry + " TODO: Might be a bug: should x really be inserted here + call assert_equal(['xa'], getline(1, '$')) + delfu DoIt + try + call feedkeys("ix\<esc>", 'tnix') + call assert_fails(1, 'unknown function') + catch /^Vim\%((\a\+)\)\=:E117/ " catch E117: unknown function + endtry + au! InsertCharPre + " 3) edit when completion is shown + fun! Complete(findstart, base) + if a:findstart + return col('.') + else + call feedkeys("i\<del>\<esc>", 'tnix') + return [] + endif + endfun + set completefunc=Complete + try + call feedkeys("i\<c-x>\<c-u>\<esc>", 'tnix') + call assert_fails(1, 'change in complete function') + catch /^Vim\%((\a\+)\)\=:E523/ " catch E523 + endtry + delfu Complete + set completefunc= + if has("rightleft") && exists("+fkmap") + " 4) 'R' when 'fkmap' and 'revins' is set. + set revins fkmap + try + normal Ri + call assert_fails(1, "R with 'fkmap' and 'ri' set") + catch + finally + set norevins nofkmap + endtry + endif + bw! +endfunc + +func! Test_edit_rightleft() + " Cursor in rightleft mode moves differently + if !exists("+rightleft") + return + endif + call NewWindow(10, 20) + call setline(1, ['abc', 'def', 'ghi']) + call cursor(1, 2) + set rightleft + " Screen looks as expected + let lines = ScreenLines([1, 4], winwidth(0)) + let expect = [ + \" cba", + \" fed", + \" ihg", + \" ~"] + call assert_equal(join(expect, "\n"), join(lines, "\n")) + " 2) right moves to the left + call feedkeys("i\<right>\<esc>x", 'txin') + call assert_equal(['bc', 'def', 'ghi'], getline(1,'$')) + call cursor(1, 2) + call feedkeys("i\<s-right>\<esc>", 'txin') + call cursor(1, 2) + call feedkeys("i\<c-right>\<esc>", 'txin') + " Screen looks as expected + let lines = ScreenLines([1, 4], winwidth(0)) + let expect = [ + \" cb", + \" fed", + \" ihg", + \" ~"] + call assert_equal(join(expect, "\n"), join(lines, "\n")) + " 2) left moves to the right + call setline(1, ['abc', 'def', 'ghi']) + call cursor(1, 2) + call feedkeys("i\<left>\<esc>x", 'txin') + call assert_equal(['ac', 'def', 'ghi'], getline(1,'$')) + call cursor(1, 2) + call feedkeys("i\<s-left>\<esc>", 'txin') + call cursor(1, 2) + call feedkeys("i\<c-left>\<esc>", 'txin') + " Screen looks as expected + let lines = ScreenLines([1, 4], winwidth(0)) + let expect = [ + \" ca", + \" fed", + \" ihg", + \" ~"] + call assert_equal(join(expect, "\n"), join(lines, "\n")) + set norightleft + bw! +endfunc + +func Test_edit_quit() + edit foo.txt + split + new + call setline(1, 'hello') + 3wincmd w + redraw! + call assert_fails('1q', 'E37:') + bwipe! foo.txt + only +endfunc + diff --git a/src/nvim/testdir/test_erasebackword.vim b/src/nvim/testdir/test_erasebackword.vim new file mode 100644 index 0000000000..098d6edfcb --- /dev/null +++ b/src/nvim/testdir/test_erasebackword.vim @@ -0,0 +1,25 @@ + +func Test_erasebackword() + if !has('multi_byte') + return + endif + + set encoding=utf-8 + enew + + exe "normal o wwwこんにちわ世界ワールドvim \<C-W>" + call assert_equal(' wwwこんにちわ世界ワールド', getline('.')) + exe "normal o wwwこんにちわ世界ワールドvim \<C-W>\<C-W>" + call assert_equal(' wwwこんにちわ世界', getline('.')) + exe "normal o wwwこんにちわ世界ワールドvim \<C-W>\<C-W>\<C-W>" + call assert_equal(' wwwこんにちわ', getline('.')) + exe "normal o wwwこんにちわ世界ワールドvim \<C-W>\<C-W>\<C-W>\<C-W>" + call assert_equal(' www', getline('.')) + exe "normal o wwwこんにちわ世界ワールドvim \<C-W>\<C-W>\<C-W>\<C-W>\<C-W>" + call assert_equal(' ', getline('.')) + exe "normal o wwwこんにちわ世界ワールドvim \<C-W>\<C-W>\<C-W>\<C-W>\<C-W>\<C-W>" + call assert_equal('', getline('.')) + + enew! + set encoding& +endfunc diff --git a/src/nvim/testdir/test_exists.vim b/src/nvim/testdir/test_exists.vim new file mode 100644 index 0000000000..fd34be83b0 --- /dev/null +++ b/src/nvim/testdir/test_exists.vim @@ -0,0 +1,321 @@ +" Tests for the exists() function +func Test_exists() + augroup myagroup + autocmd! BufEnter *.my echo "myfile edited" + autocmd! FuncUndefined UndefFun exec "fu UndefFun()\nendfu" + augroup END + set rtp+=./sautest + + " valid autocmd group + call assert_equal(1, exists('#myagroup')) + " valid autocmd group with garbage + call assert_equal(0, exists('#myagroup+b')) + " Valid autocmd group and event + call assert_equal(1, exists('#myagroup#BufEnter')) + " Valid autocmd group, event and pattern + call assert_equal(1, exists('#myagroup#BufEnter#*.my')) + " Valid autocmd event + call assert_equal(1, exists('#BufEnter')) + " Valid autocmd event and pattern + call assert_equal(1, exists('#BufEnter#*.my')) + " Non-existing autocmd group or event + call assert_equal(0, exists('#xyzagroup')) + " Non-existing autocmd group and valid autocmd event + call assert_equal(0, exists('#xyzagroup#BufEnter')) + " Valid autocmd group and event with no matching pattern + call assert_equal(0, exists('#myagroup#CmdwinEnter')) + " Valid autocmd group and non-existing autocmd event + call assert_equal(0, exists('#myagroup#xyzacmd')) + " Valid autocmd group and event and non-matching pattern + call assert_equal(0, exists('#myagroup#BufEnter#xyzpat')) + " Valid autocmd event and non-matching pattern + call assert_equal(0, exists('#BufEnter#xyzpat')) + " Empty autocmd group, event and pattern + call assert_equal(0, exists('###')) + " Empty autocmd group and event or empty event and pattern + call assert_equal(0, exists('##')) + " Valid autocmd event + call assert_equal(1, exists('##FileReadCmd')) + " Non-existing autocmd event + call assert_equal(0, exists('##MySpecialCmd')) + + " Existing and working option (long form) + call assert_equal(1, exists('&textwidth')) + " Existing and working option (short form) + call assert_equal(1, exists('&tw')) + " Existing and working option with garbage + call assert_equal(0, exists('&tw-')) + " Global option + call assert_equal(1, exists('&g:errorformat')) + " Local option + call assert_equal(1, exists('&l:errorformat')) + " Negative form of existing and working option (long form) + call assert_equal(0, exists('&nojoinspaces')) + " Negative form of existing and working option (short form) + call assert_equal(0, exists('&nojs')) + " Non-existing option + call assert_equal(0, exists('&myxyzoption')) + + " Existing and working option (long form) + call assert_equal(1, exists('+incsearch')) + " Existing and working option with garbage + call assert_equal(0, exists('+incsearch!1')) + " Existing and working option (short form) + call assert_equal(1, exists('+is')) + " Existing option that is hidden. + call assert_equal(0, exists('+autoprint')) + + " Existing environment variable + let $EDITOR_NAME = 'Vim Editor' + call assert_equal(1, exists('$EDITOR_NAME')) + " Non-existing environment variable + call assert_equal(0, exists('$NON_ENV_VAR')) + + " Valid internal function + call assert_equal(1, exists('*bufnr')) + " Valid internal function with () + call assert_equal(1, exists('*bufnr()')) + " Non-existing internal function + call assert_equal(0, exists('*myxyzfunc')) + " Valid internal function with garbage + call assert_equal(0, exists('*bufnr&6')) + " Valid user defined function + call assert_equal(1, exists('*Test_exists')) + " Non-existing user defined function + call assert_equal(0, exists('*MyxyzFunc')) + " Function that may be created by FuncUndefined event + call assert_equal(0, exists('*UndefFun')) + " Function that may be created by script autoloading + call assert_equal(0, exists('*footest#F')) + + " Valid internal command (full match) + call assert_equal(2, exists(':edit')) + " Valid internal command (full match) with garbage + call assert_equal(0, exists(':edit/a')) + " Valid internal command (partial match) + call assert_equal(1, exists(':q')) + " Non-existing internal command + call assert_equal(0, exists(':invalidcmd')) + + " User defined command (full match) + command! MyCmd :echo 'My command' + call assert_equal(2, exists(':MyCmd')) + " User defined command (partial match) + command! MyOtherCmd :echo 'Another command' + call assert_equal(3, exists(':My')) + + " Command modifier + call assert_equal(2, exists(':rightbelow')) + + " Non-existing user defined command (full match) + delcommand MyCmd + call assert_equal(0, exists(':MyCmd')) + + " Non-existing user defined command (partial match) + delcommand MyOtherCmd + call assert_equal(0, exists(':My')) + + " Valid local variable + let local_var = 1 + call assert_equal(1, exists('local_var')) + " Valid local variable with garbage + call assert_equal(0, exists('local_var%n')) + " Non-existing local variable + unlet local_var + call assert_equal(0, exists('local_var')) + + " Non-existing autoload variable that may be autoloaded + call assert_equal(0, exists('footest#x')) + + " Valid local list + let local_list = ["blue", "orange"] + call assert_equal(1, exists('local_list')) + " Valid local list item + call assert_equal(1, exists('local_list[1]')) + " Valid local list item with garbage + call assert_equal(0, exists('local_list[1]+5')) + " Invalid local list item + call assert_equal(0, exists('local_list[2]')) + " Non-existing local list + unlet local_list + call assert_equal(0, exists('local_list')) + " Valid local dictionary + let local_dict = {"xcord":100, "ycord":2} + call assert_equal(1, exists('local_dict')) + " Non-existing local dictionary + unlet local_dict + call assert_equal(0, exists('local_dict')) + " Existing local curly-brace variable + let str = "local" + let curly_{str}_var = 1 + call assert_equal(1, exists('curly_{str}_var')) + " Non-existing local curly-brace variable + unlet curly_{str}_var + call assert_equal(0, exists('curly_{str}_var')) + + " Existing global variable + let g:global_var = 1 + call assert_equal(1, exists('g:global_var')) + " Existing global variable with garbage + call assert_equal(0, exists('g:global_var-n')) + " Non-existing global variable + unlet g:global_var + call assert_equal(0, exists('g:global_var')) + " Existing global list + let g:global_list = ["blue", "orange"] + call assert_equal(1, exists('g:global_list')) + " Non-existing global list + unlet g:global_list + call assert_equal(0, exists('g:global_list')) + " Existing global dictionary + let g:global_dict = {"xcord":100, "ycord":2} + call assert_equal(1, exists('g:global_dict')) + " Non-existing global dictionary + unlet g:global_dict + call assert_equal(0, exists('g:global_dict')) + " Existing global curly-brace variable + let str = "global" + let g:curly_{str}_var = 1 + call assert_equal(1, exists('g:curly_{str}_var')) + " Non-existing global curly-brace variable + unlet g:curly_{str}_var + call assert_equal(0, exists('g:curly_{str}_var')) + + " Existing window variable + let w:window_var = 1 + call assert_equal(1, exists('w:window_var')) + " Non-existing window variable + unlet w:window_var + call assert_equal(0, exists('w:window_var')) + " Existing window list + let w:window_list = ["blue", "orange"] + call assert_equal(1, exists('w:window_list')) + " Non-existing window list + unlet w:window_list + call assert_equal(0, exists('w:window_list')) + " Existing window dictionary + let w:window_dict = {"xcord":100, "ycord":2} + call assert_equal(1, exists('w:window_dict')) + " Non-existing window dictionary + unlet w:window_dict + call assert_equal(0, exists('w:window_dict')) + " Existing window curly-brace variable + let str = "window" + let w:curly_{str}_var = 1 + call assert_equal(1, exists('w:curly_{str}_var')) + " Non-existing window curly-brace variable + unlet w:curly_{str}_var + call assert_equal(0, exists('w:curly_{str}_var')) + + " Existing tab variable + let t:tab_var = 1 + call assert_equal(1, exists('t:tab_var')) + " Non-existing tab variable + unlet t:tab_var + call assert_equal(0, exists('t:tab_var')) + " Existing tab list + let t:tab_list = ["blue", "orange"] + call assert_equal(1, exists('t:tab_list')) + " Non-existing tab list + unlet t:tab_list + call assert_equal(0, exists('t:tab_list')) + " Existing tab dictionary + let t:tab_dict = {"xcord":100, "ycord":2} + call assert_equal(1, exists('t:tab_dict')) + " Non-existing tab dictionary + unlet t:tab_dict + call assert_equal(0, exists('t:tab_dict')) + " Existing tab curly-brace variable + let str = "tab" + let t:curly_{str}_var = 1 + call assert_equal(1, exists('t:curly_{str}_var')) + " Non-existing tab curly-brace variable + unlet t:curly_{str}_var + call assert_equal(0, exists('t:curly_{str}_var')) + + " Existing buffer variable + let b:buffer_var = 1 + call assert_equal(1, exists('b:buffer_var')) + " Non-existing buffer variable + unlet b:buffer_var + call assert_equal(0, exists('b:buffer_var')) + " Existing buffer list + let b:buffer_list = ["blue", "orange"] + call assert_equal(1, exists('b:buffer_list')) + " Non-existing buffer list + unlet b:buffer_list + call assert_equal(0, exists('b:buffer_list')) + " Existing buffer dictionary + let b:buffer_dict = {"xcord":100, "ycord":2} + call assert_equal(1, exists('b:buffer_dict')) + " Non-existing buffer dictionary + unlet b:buffer_dict + call assert_equal(0, exists('b:buffer_dict')) + " Existing buffer curly-brace variable + let str = "buffer" + let b:curly_{str}_var = 1 + call assert_equal(1, exists('b:curly_{str}_var')) + " Non-existing buffer curly-brace variable + unlet b:curly_{str}_var + call assert_equal(0, exists('b:curly_{str}_var')) + + " Existing Vim internal variable + call assert_equal(1, exists('v:version')) + " Non-existing Vim internal variable + call assert_equal(0, exists('v:non_exists_var')) + + " Existing script-local variable + let s:script_var = 1 + call assert_equal(1, exists('s:script_var')) + " Non-existing script-local variable + unlet s:script_var + call assert_equal(0, exists('s:script_var')) + " Existing script-local list + let s:script_list = ["blue", "orange"] + call assert_equal(1, exists('s:script_list')) + " Non-existing script-local list + unlet s:script_list + call assert_equal(0, exists('s:script_list')) + " Existing script-local dictionary + let s:script_dict = {"xcord":100, "ycord":2} + call assert_equal(1, exists('s:script_dict')) + " Non-existing script-local dictionary + unlet s:script_dict + call assert_equal(0, exists('s:script_dict')) + " Existing script curly-brace variable + let str = "script" + let s:curly_{str}_var = 1 + call assert_equal(1, exists('s:curly_{str}_var')) + " Non-existing script-local curly-brace variable + unlet s:curly_{str}_var + call assert_equal(0, exists('s:curly_{str}_var')) + + " Existing script-local function + function! s:my_script_func() + endfunction + + echo '*s:my_script_func: 1' + call assert_equal(1, exists('*s:my_script_func')) + + " Non-existing script-local function + delfunction s:my_script_func + + call assert_equal(0, exists('*s:my_script_func')) + unlet str + + call assert_equal(1, g:footest#x) + call assert_equal(0, footest#F()) + call assert_equal(0, UndefFun()) +endfunc + +" exists() test for Function arguments +func FuncArg_Tests(func_arg, ...) + call assert_equal(1, exists('a:func_arg')) + call assert_equal(0, exists('a:non_exists_arg')) + call assert_equal(1, exists('a:1')) + call assert_equal(0, exists('a:2')) +endfunc + +func Test_exists_funcarg() + call FuncArg_Tests("arg1", "arg2") +endfunc diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim index d32facaa98..ad967c528c 100644 --- a/src/nvim/testdir/test_expr.vim +++ b/src/nvim/testdir/test_expr.vim @@ -439,3 +439,8 @@ func Test_funcref() call assert_equal(2, OneByRef()) call assert_fails('echo funcref("{")', 'E475:') endfunc + +func Test_empty_concatenate() + call assert_equal('b', 'a'[4:0] . 'b') + call assert_equal('b', 'b' . 'a'[4:0]) +endfunc diff --git a/src/nvim/testdir/test_expr_utf8.vim b/src/nvim/testdir/test_expr_utf8.vim index 9ea6d8872b..1737a9f745 100644 --- a/src/nvim/testdir/test_expr_utf8.vim +++ b/src/nvim/testdir/test_expr_utf8.vim @@ -3,7 +3,7 @@ if !has('multi_byte') finish endif -func Test_strgetchar_utf8() +func Test_strgetchar() call assert_equal(char2nr('á'), strgetchar('áxb', 0)) call assert_equal(char2nr('x'), strgetchar('áxb', 1)) @@ -16,7 +16,7 @@ func Test_strgetchar_utf8() call assert_equal(char2nr('い'), strgetchar('あaい', 2)) endfunc -func Test_strcharpart_utf8() +func Test_strcharpart() call assert_equal('áxb', strcharpart('áxb', 0)) call assert_equal('á', strcharpart('áxb', 0, 1)) call assert_equal('x', strcharpart('áxb', 1, 1)) diff --git a/src/nvim/testdir/test_farsi.vim b/src/nvim/testdir/test_farsi.vim index e094191599..9ff2653af4 100644 --- a/src/nvim/testdir/test_farsi.vim +++ b/src/nvim/testdir/test_farsi.vim @@ -1,4 +1,5 @@ " Simplistic testing of Farsi mode. +" Note: must be edited with latin1 encoding. if !has('farsi') || has('nvim') " Not supported in Nvim. #6192 finish @@ -82,3 +83,51 @@ func Test_farsi_map() set noaltkeymap bwipe! endfunc + +func Test_input_farsi() + new + setlocal rightleft fkmap + " numbers switch input direction + call feedkeys("aabc0123456789.+-^%#=xyz\<Esc>", 'tx') + call assert_equal("\x8cν\x93", getline('.')) + + " all non-number special chars with spaces + call feedkeys("oB E F H I K L M O P Q R T U W Y ` ! @ # $ % ^ & * () - _ = + \\ | : \" . / < > ? \<Esc>", 'tx') + call assert_equal(" []蠨頽", getline('.')) + + " all non-number special chars without spaces + call feedkeys("oBEFHIKLMOPQRTUWY`!@#$%^&*()-_=+\\|:\"./<>?\<Esc>",'tx') + call assert_equal("[]訩齫꺻", getline('.')) + + " all letter chars with spaces + call feedkeys("oa A b c C d D e f g G h i j J k l m n N o p q r s S t u v V w x X y z Z ; \ , [ ] \<Esc>", 'tx') + call assert_equal("Ѡ̠ΠϠƠàܠŠޠݠĠˠˠʠɠӠ٠Рؠ֠͠͠ҠԠԠנՠڠߠǠȠ", getline('.')) + + " all letter chars without spaces + call feedkeys("oaAbcCdDefgGhijJklmnNopqrsStuvVwxXyzZ;\,[]\<Esc>", 'tx') + call assert_equal("\x8c\x9f\x86\x83\x9d\x85\x80\x9c\x9b\x84\x8a\x89\x8e\x96\x8b\x95\x90\x8d\x93\x97\x87\x88", getline('.')) + + bwipe! +endfunc + +func Test_command_line_farsi() + set allowrevins altkeymap + + " letter characters with spaces + call feedkeys(":\"\<C-_>a A b c C d D e f g G h i j J k l m n N o p q r s S t u v V w x X y z Z ; \\ , [ ]\<CR>", 'tx') + call assert_equal("\"\x88ǠߠڠՠՠנԠԠҠ֠͠͠ؠР٠ӠɠʠˠˠĠݠޠŠܠàƠϠΠ̠", getreg(':')) + + " letter characters without spaces + call feedkeys(":\"\<C-_>aAbcCdDefgGhijJklmnNopqrsStuvVwxXyzZ;\\,[]\<CR>", 'tx') + call assert_equal("\"\x88\x87\x93\x8d\x90\x95\x8b\x96\x8e\x89\x8a\x84\x9b\x9c\x80\x85\x9d\x83\x86\x9f\x8c", getreg(':')) + + " other characters with spaces + call feedkeys(":\"\<C-_>0 1 2 3 4 5 6 7 8 9 ` . ! \" $ % ^ & / () = \\ ? + - _ * : # ~ @ < > { } | B E F H I K L M O P Q R T U W Y\<CR>", 'tx') + call assert_equal("\"][ }{~頭렽", getreg(':')) + + " other characters without spaces + call feedkeys(":\"\<C-_>0123456789`.!\"$%^&/()=\\?+-_*:#~@<>{}|BEFHIKLMOPQRTUWY\<CR>", 'tx') + call assert_equal("\"][}{~魫뽩", getreg(':')) + + set noallowrevins noaltkeymap +endfunc diff --git a/src/nvim/testdir/test_file_size.vim b/src/nvim/testdir/test_file_size.vim new file mode 100644 index 0000000000..3e78a7b23c --- /dev/null +++ b/src/nvim/testdir/test_file_size.vim @@ -0,0 +1,58 @@ +" Inserts 2 million lines with consecutive integers starting from 1 +" (essentially, the output of GNU's seq 1 2000000), writes them to Xtest +" and writes its cksum to test.out. +" +" We need 2 million lines to trigger a call to mf_hash_grow(). If it would mess +" up the lines the checksum would differ. +" +" cksum is part of POSIX and so should be available on most Unixes. +" If it isn't available then the test will be skipped. +func Test_File_Size() + if !executable('cksum') + return + endif + + new + set fileformat=unix undolevels=-1 + for i in range(1, 2000000, 100) + call append(i, range(i, i + 99)) + endfor + + 1delete + w! Xtest + let res = systemlist('cksum Xtest')[0] + let res = substitute(res, "\r", "", "") + call assert_equal('3678979763 14888896 Xtest', res) + + enew! + call delete('Xtest') + set fileformat& undolevels& +endfunc + +" Test for writing and reading a file of over 100 Kbyte +func Test_File_Read_Write() + enew! + + " Create a file with the following contents + " 1 line: "This is the start" + " 3001 lines: "This is the leader" + " 1 line: "This is the middle" + " 3001 lines: "This is the trailer" + " 1 line: "This is the end" + call append(0, "This is the start") + call append(1, repeat(["This is the leader"], 3001)) + call append(3002, "This is the middle") + call append(3003, repeat(["This is the trailer"], 3001)) + call append(6004, "This is the end") + + write! Xtest + enew! + edit! Xtest + + call assert_equal("This is the start", getline(1)) + call assert_equal("This is the middle", getline(3003)) + call assert_equal("This is the end", getline(6005)) + + enew! + call delete("Xtest") +endfunc diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim new file mode 100644 index 0000000000..43dc383f3d --- /dev/null +++ b/src/nvim/testdir/test_filetype.vim @@ -0,0 +1,557 @@ +" Test :setfiletype + +func Test_detection() + filetype on + augroup filetypedetect + au BufNewFile,BufRead * call assert_equal(1, did_filetype()) + augroup END + new something.vim + call assert_equal('vim', &filetype) + + bwipe! + filetype off +endfunc + +func Test_conf_type() + filetype on + call writefile(['# some comment', 'must be conf'], 'Xfile') + augroup filetypedetect + au BufNewFile,BufRead * call assert_equal(0, did_filetype()) + augroup END + split Xfile + call assert_equal('conf', &filetype) + + bwipe! + call delete('Xfile') + filetype off +endfunc + +func Test_other_type() + filetype on + augroup filetypedetect + au BufNewFile,BufRead * call assert_equal(0, did_filetype()) + au BufNewFile,BufRead Xfile setf testfile + au BufNewFile,BufRead * call assert_equal(1, did_filetype()) + augroup END + call writefile(['# some comment', 'must be conf'], 'Xfile') + split Xfile + call assert_equal('testfile', &filetype) + + bwipe! + call delete('Xfile') + filetype off +endfunc + +" Filetypes detected just from matching the file name. +let s:filename_checks = { + \ 'a2ps': ['/etc/a2ps.cfg', '/etc/a2ps/file.cfg', 'a2psrc', '.a2psrc'], + \ 'a65': ['file.a65'], + \ 'abap': ['file.abap'], + \ 'abc': ['file.abc'], + \ 'abel': ['file.abl'], + \ 'acedb': ['file.wrm'], + \ 'ada': ['file.adb', 'file.ads', 'file.ada', 'file.gpr'], + \ 'ahdl': ['file.tdf'], + \ 'alsaconf': ['.asoundrc', '/usr/share/alsa/alsa.conf', '/etc/asound.conf'], + \ 'aml': ['file.aml'], + \ 'ampl': ['file.run'], + \ 'ant': ['build.xml'], + \ 'apache': ['.htaccess', '/etc/httpd/file.conf'], + \ 'applescript': ['file.scpt'], + \ 'aptconf': ['apt.conf', '/.aptitude/config'], + \ 'arch': ['.arch-inventory'], + \ 'arduino': ['file.ino', 'file.pde'], + \ 'art': ['file.art'], + \ 'asciidoc': ['file.asciidoc', 'file.adoc'], + \ 'asn': ['file.asn', 'file.asn1'], + \ 'atlas': ['file.atl', 'file.as'], + \ 'autohotkey': ['file.ahk'], + \ 'autoit': ['file.au3'], + \ 'automake': ['GNUmakefile.am'], + \ 'ave': ['file.ave'], + \ 'awk': ['file.awk'], + \ 'b': ['file.mch', 'file.ref', 'file.imp'], + \ 'bc': ['file.bc'], + \ 'bdf': ['file.bdf'], + \ 'bib': ['file.bib'], + \ 'bindzone': ['named.root'], + \ 'blank': ['file.bl'], + \ 'bst': ['file.bst'], + \ 'bzr': ['bzr_log.any'], + \ 'c': ['enlightenment/file.cfg', 'file.qc', 'file.c'], + \ 'cabal': ['file.cabal'], + \ 'calendar': ['calendar'], + \ 'catalog': ['catalog'], + \ 'cdl': ['file.cdl'], + \ 'cdrdaoconf': ['/etc/cdrdao.conf', '/etc/defaults/cdrdao', '/etc/default/cdrdao', '.cdrdao'], + \ 'cdrtoc': ['file.toc'], + \ 'cf': ['file.cfm', 'file.cfi', 'file.cfc'], + \ 'cfengine': ['cfengine.conf'], + \ 'cfg': ['file.cfg', 'file.hgrc', 'filehgrc'], + \ 'ch': ['file.chf'], + \ 'chaiscript': ['file.chai'], + \ 'chaskell': ['file.chs'], + \ 'chill': ['file..ch'], + \ 'chordpro': ['file.chopro', 'file.crd', 'file.cho', 'file.crdpro', 'file.chordpro'], + \ 'cl': ['file.eni'], + \ 'clean': ['file.dcl', 'file.icl'], + \ 'clojure': ['file.clj', 'file.cljs', 'file.cljx', 'file.cljc'], + \ 'cmake': ['CMakeLists.txt', 'file.cmake', 'file.cmake.in'], + \ 'cmusrc': ['any/.cmus/autosave', 'any/.cmus/rc', 'any/.cmus/command-history', 'any/.cmus/file.theme', 'any/cmus/rc', 'any/cmus/file.theme'], + \ 'cobol': ['file.cbl', 'file.cob', 'file.lib'], + \ 'coco': ['file.atg'], + \ 'conaryrecipe': ['file.recipe'], + \ 'conf': ['auto.master'], + \ 'config': ['configure.in', 'configure.ac'], + \ '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'], + \ 'cs': ['file.cs'], + \ 'csc': ['file.csc'], + \ 'csdl': ['file.csdl'], + \ 'csp': ['file.csp', 'file.fdr'], + \ 'css': ['file.css'], + \ 'cterm': ['file.con'], + \ 'cucumber': ['file.feature'], + \ 'cuda': ['file.cu'], + \ 'cupl': ['file.pld'], + \ 'cuplsim': ['file.si'], + \ 'cvs': ['cvs123'], + \ 'cvsrc': ['.cvsrc'], + \ 'cynpp': ['file.cyn'], + \ 'datascript': ['file.ds'], + \ 'dcd': ['file.dcd'], + \ 'debcontrol': ['/debian/control'], + \ 'debsources': ['/etc/apt/sources.list', '/etc/apt/sources.list.d/file.list'], + \ 'def': ['file.def'], + \ 'denyhosts': ['denyhosts.conf'], + \ 'desc': ['file.desc'], + \ 'desktop': ['file.desktop', '.directory'], + \ 'dictconf': ['dict.conf', '.dictrc'], + \ 'dictdconf': ['dictd.conf'], + \ 'diff': ['file.diff', 'file.rej'], + \ 'dircolors': ['.dir_colors', '.dircolors', '/etc/DIR_COLORS'], + \ 'dnsmasq': ['/etc/dnsmasq.conf'], + \ 'dockerfile': ['Dockerfile', 'file.Dockerfile'], + \ 'dosbatch': ['file.bat', 'file.sys'], + \ 'dosini': ['.editorconfig', '/etc/yum.conf', 'file.ini'], + \ 'dot': ['file.dot'], + \ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe'], + \ 'dsl': ['file.dsl'], + \ 'dtd': ['file.dtd'], + \ 'dts': ['file.dts', 'file.dtsi'], + \ 'dylan': ['file.dylan'], + \ 'dylanintr': ['file.intr'], + \ 'dylanlid': ['file.lid'], + \ 'ecd': ['file.ecd'], + \ 'edif': ['file.edf', 'file.edif', 'file.edo'], + \ 'elinks': ['/etc/elinks.conf', '/.elinks/elinks.conf'], + \ 'elmfilt': ['filter-rules'], + \ 'erlang': ['file.erl', 'file.hrl', 'file.yaws'], + \ 'eruby': ['file.erb', 'file.rhtml'], + \ 'esmtprc': ['anyesmtprc'], + \ 'esqlc': ['file.ec', 'file.EC'], + \ 'esterel': ['file.strl'], + \ 'eterm': ['anyEterm/file.cfg'], + \ 'exim': ['exim.conf'], + \ 'expect': ['file.exp'], + \ 'exports': ['exports'], + \ 'factor': ['file.factor'], + \ 'falcon': ['file.fal'], + \ 'fan': ['file.fan', 'file.fwt'], + \ 'fetchmail': ['.fetchmailrc'], + \ 'fgl': ['file.4gl', 'file.4gh', 'file.m4gl'], + \ 'focexec': ['file.fex', 'file.focexec'], + \ 'forth': ['file.fs', 'file.ft'], + \ 'fortran': ['file.f', 'file.for', 'file.fortran', 'file.fpp', 'file.ftn', 'file.f77', 'file.f90', 'file.f95', 'file.f03', 'file.f08'], + \ 'framescript': ['file.fsl'], + \ 'freebasic': ['file.fb', 'file.bi'], + \ 'fstab': ['fstab', 'mtab'], + \ 'gdb': ['.gdbinit'], + \ 'gdmo': ['file.mo', 'file.gdmo'], + \ 'gedcom': ['file.ged', 'lltxxxxx.txt'], + \ 'gitcommit': ['COMMIT_EDITMSG', 'MERGE_MSG', 'TAG_EDITMSG'], + \ 'gitconfig': ['file.git/config', '.gitconfig', '.gitmodules', 'file.git/modules//config', '/.config/git/config'], + \ 'gitolite': ['gitolite.conf'], + \ 'gitrebase': ['git-rebase-todo'], + \ 'gitsendemail': ['.gitsendemail.msg.xxxxxx'], + \ 'gkrellmrc': ['gkrellmrc', 'gkrellmrc_x'], + \ 'gnash': ['gnashrc', '.gnashrc', 'gnashpluginrc', '.gnashpluginrc'], + \ 'gnuplot': ['file.gpi'], + \ 'go': ['file.go'], + \ 'gp': ['file.gp', '.gprc'], + \ 'gpg': ['/.gnupg/options', '/.gnupg/gpg.conf', '/usr/any/gnupg/options.skel'], + \ 'grads': ['file.gs'], + \ 'gretl': ['file.gretl'], + \ 'groovy': ['file.gradle', 'file.groovy'], + \ 'group': ['any/etc/group', 'any/etc/group-', 'any/etc/group.edit', 'any/etc/gshadow', 'any/etc/gshadow-', 'any/etc/gshadow.edit', 'any/var/backups/group.bak', 'any/var/backups/gshadow.bak'], + \ 'grub': ['/boot/grub/menu.lst', '/boot/grub/grub.conf', '/etc/grub.conf'], + \ 'gsp': ['file.gsp'], + \ 'gtkrc': ['.gtkrc', 'gtkrc'], + \ 'haml': ['file.haml'], + \ 'hamster': ['file.hsc', 'file.hsm'], + \ 'haskell': ['file.hs', 'file.hs-boot'], + \ 'haste': ['file.ht'], + \ 'hastepreproc': ['file.htpp'], + \ 'hb': ['file.hb'], + \ 'hercules': ['file.vc', 'file.ev', 'file.sum', 'file.errsum'], + \ 'hex': ['file.hex', 'file.h32'], + \ 'hgcommit': ['hg-editor-file.txt'], + \ 'hog': ['file.hog', 'snort.conf', 'vision.conf'], + \ 'hostconf': ['/etc/host.conf'], + \ 'hostsaccess': ['/etc/hosts.allow', '/etc/hosts.deny'], + \ 'htmlcheetah': ['file.tmpl'], + \ 'htmlm4': ['file.html.m4'], + \ 'httest': ['file.htt', 'file.htb'], + \ 'ibasic': ['file.iba', 'file.ibi'], + \ 'icemenu': ['/.icewm/menu'], + \ 'icon': ['file.icn'], + \ 'indent': ['.indent.pro', 'indentrc'], + \ 'inform': ['file.inf', 'file.INF'], + \ 'initng': ['/etc/initng/any/file.i', 'file.ii'], + \ 'inittab': ['inittab'], + \ 'ipfilter': ['ipf.conf', 'ipf6.conf', 'ipf.rules'], + \ 'iss': ['file.iss'], + \ 'ist': ['file.ist', 'file.mst'], + \ 'j': ['file.ijs'], + \ 'jal': ['file.jal', 'file.JAL'], + \ 'jam': ['file.jpl', 'file.jpr'], + \ 'java': ['file.java', 'file.jav'], + \ 'javacc': ['file.jj', 'file.jjt'], + \ 'javascript': ['file.js', 'file.javascript', 'file.es', 'file.jsx', 'file.mjs'], + \ 'jess': ['file.clp'], + \ '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'], + \ 'jsp': ['file.jsp'], + \ 'kconfig': ['Kconfig', 'Kconfig.debug'], + \ 'kivy': ['file.kv'], + \ 'kix': ['file.kix'], + \ 'kscript': ['file.ks'], + \ 'kwt': ['file.k'], + \ 'lace': ['file.ace', 'file.ACE'], + \ 'latte': ['file.latte', 'file.lte'], + \ 'ld': ['file.ld'], + \ 'ldif': ['file.ldif'], + \ 'less': ['file.less'], + \ 'lex': ['file.lex', 'file.l', 'file.lxx', 'file.l++'], + \ 'lftp': ['lftp.conf', '.lftprc', 'anylftp/rc'], + \ 'lhaskell': ['file.lhs'], + \ 'libao': ['/etc/libao.conf', '/.libao'], + \ 'lifelines': ['file.ll'], + \ 'lilo': ['lilo.conf'], + \ 'limits': ['/etc/limits', '/etc/anylimits.conf', '/etc/anylimits.d/file.conf'], + \ 'liquid': ['file.liquid'], + \ 'lisp': ['sbclrc', '.sbclrc'], + \ 'lite': ['file.lite', 'file.lt'], + \ 'litestep': ['/LiteStep/any/file.rc'], + \ 'loginaccess': ['/etc/login.access'], + \ 'logindefs': ['/etc/login.defs'], + \ 'logtalk': ['file.lgt'], + \ 'lotos': ['file.lot', 'file.lotos'], + \ 'lout': ['file.lou', 'file.lout'], + \ 'lprolog': ['file.sig'], + \ 'lsl': ['file.lsl'], + \ 'lss': ['file.lss'], + \ 'lua': ['file.lua', 'file.rockspec', 'file.nse'], + \ 'lynx': ['lynx.cfg'], + \ 'm4': ['file.at'], + \ 'mail': ['snd.123', '.letter', '.letter.123', '.followup', '.article', '.article.123', 'pico.123', 'mutt-xx-xxx', 'muttng-xx-xxx', 'ae123.txt', 'file.eml'], + \ 'mailaliases': ['/etc/mail/aliases', '/etc/aliases'], + \ 'mailcap': ['.mailcap', 'mailcap'], + \ 'make': ['file.mk', 'file.mak', 'file.dsp'], + \ 'mallard': ['file.page'], + \ 'manconf': ['/etc/man.conf', 'man.config'], + \ 'map': ['file.map'], + \ 'maple': ['file.mv', 'file.mpl', 'file.mws'], + \ 'markdown': ['file.markdown', 'file.mdown', 'file.mkd', 'file.mkdn', 'file.mdwn', 'file.md'], + \ 'mason': ['file.mason', 'file.mhtml', 'file.comp'], + \ 'master': ['file.mas', 'file.master'], + \ 'mel': ['file.mel'], + \ 'messages': ['/log/auth', '/log/cron', '/log/daemon', '/log/debug', '/log/kern', '/log/lpr', '/log/mail', '/log/messages', '/log/news/news', '/log/syslog', '/log/user', + \ '/log/auth.log', '/log/cron.log', '/log/daemon.log', '/log/debug.log', '/log/kern.log', '/log/lpr.log', '/log/mail.log', '/log/messages.log', '/log/news/news.log', '/log/syslog.log', '/log/user.log', + \ '/log/auth.err', '/log/cron.err', '/log/daemon.err', '/log/debug.err', '/log/kern.err', '/log/lpr.err', '/log/mail.err', '/log/messages.err', '/log/news/news.err', '/log/syslog.err', '/log/user.err', + \ '/log/auth.info', '/log/cron.info', '/log/daemon.info', '/log/debug.info', '/log/kern.info', '/log/lpr.info', '/log/mail.info', '/log/messages.info', '/log/news/news.info', '/log/syslog.info', '/log/user.info', + \ '/log/auth.warn', '/log/cron.warn', '/log/daemon.warn', '/log/debug.warn', '/log/kern.warn', '/log/lpr.warn', '/log/mail.warn', '/log/messages.warn', '/log/news/news.warn', '/log/syslog.warn', '/log/user.warn', + \ '/log/auth.crit', '/log/cron.crit', '/log/daemon.crit', '/log/debug.crit', '/log/kern.crit', '/log/lpr.crit', '/log/mail.crit', '/log/messages.crit', '/log/news/news.crit', '/log/syslog.crit', '/log/user.crit', + \ '/log/auth.notice', '/log/cron.notice', '/log/daemon.notice', '/log/debug.notice', '/log/kern.notice', '/log/lpr.notice', '/log/mail.notice', '/log/messages.notice', '/log/news/news.notice', '/log/syslog.notice', '/log/user.notice'], + \ 'mf': ['file.mf'], + \ 'mgl': ['file.mgl'], + \ 'mgp': ['file.mgp'], + \ 'mib': ['file.mib', 'file.my'], + \ 'mix': ['file.mix', 'file.mixal'], + \ 'mma': ['file.nb'], + \ 'mmp': ['file.mmp'], + \ 'modconf': ['/etc/modules.conf', '/etc/modules', '/etc/conf.modules'], + \ 'modula2': ['file.m2', 'file.mi'], + \ 'monk': ['file.isc', 'file.monk', 'file.ssc', 'file.tsc'], + \ 'moo': ['file.moo'], + \ 'mp': ['file.mp'], + \ 'mplayerconf': ['mplayer.conf', '/.mplayer/config'], + \ 'mrxvtrc': ['mrxvtrc', '.mrxvtrc'], + \ 'msidl': ['file.odl', 'file.mof'], + \ 'msql': ['file.msql'], + \ 'mupad': ['file.mu'], + \ 'mush': ['file.mush'], + \ 'muttrc': ['Muttngrc', 'Muttrc'], + \ 'mysql': ['file.mysql'], + \ 'n1ql': ['file.n1ql', 'file.nql'], + \ 'named': ['namedfile.conf', 'rndcfile.conf'], + \ 'nanorc': ['/etc/nanorc', 'file.nanorc'], + \ 'ncf': ['file.ncf'], + \ 'netrc': ['.netrc'], + \ 'ninja': ['file.ninja'], + \ 'nqc': ['file.nqc'], + \ 'nroff': ['file.tr', 'file.nr', 'file.roff', 'file.tmac', 'file.mom'], + \ 'nsis': ['file.nsi', 'file.nsh'], + \ 'obj': ['file.obj'], + \ 'ocaml': ['file.ml', 'file.mli', 'file.mll', 'file.mly', '.ocamlinit'], + \ 'occam': ['file.occ'], + \ 'omnimark': ['file.xom', 'file.xin'], + \ 'openroad': ['file.or'], + \ 'ora': ['file.ora'], + \ 'pamconf': ['/etc/pam.conf'], + \ 'papp': ['file.papp', 'file.pxml', 'file.pxsl'], + \ 'pascal': ['file.pas', 'file.dpr'], + \ 'passwd': ['any/etc/passwd', 'any/etc/passwd-', 'any/etc/passwd.edit', 'any/etc/shadow', 'any/etc/shadow-', 'any/etc/shadow.edit', 'any/var/backups/passwd.bak', 'any/var/backups/shadow.bak'], + \ 'pccts': ['file.g'], + \ 'pdf': ['file.pdf'], + \ 'perl': ['file.plx', 'file.al', 'file.psgi', 'gitolite.rc', '.gitolite.rc', 'example.gitolite.rc'], + \ 'perl6': ['file.p6', 'file.pm6', 'file.pl6'], + \ 'pf': ['pf.conf'], + \ 'pfmain': ['main.cf'], + \ 'php': ['file.php', 'file.php9', 'file.phtml', 'file.ctp'], + \ 'pike': ['file.pike', 'file.lpc', 'file.ulpc', 'file.pmod'], + \ 'pilrc': ['file.rcp'], + \ 'pine': ['.pinerc', 'pinerc', '.pinercex', 'pinercex'], + \ 'pinfo': ['/etc/pinforc', '/.pinforc'], + \ 'pli': ['file.pli', 'file.pl1'], + \ 'plm': ['file.plm', 'file.p36', 'file.pac'], + \ 'plp': ['file.plp'], + \ 'plsql': ['file.pls', 'file.plsql'], + \ 'po': ['file.po', 'file.pot'], + \ 'pod': ['file.pod'], + \ 'pod6': ['file.pod6'], + \ 'postscr': ['file.ps', 'file.pfa', 'file.afm', 'file.eps', 'file.epsf', 'file.epsi', 'file.ai'], + \ 'pov': ['file.pov'], + \ 'povini': ['.povrayrc'], + \ 'ppd': ['file.ppd'], + \ 'ppwiz': ['file.it', 'file.ih'], + \ 'privoxy': ['file.action'], + \ 'proc': ['file.pc'], + \ 'procmail': ['.procmail', '.procmailrc'], + \ 'prolog': ['file.pdb'], + \ 'promela': ['file.pml'], + \ 'proto': ['file.proto'], + \ 'protocols': ['/etc/protocols'], + \ 'psf': ['file.psf'], + \ 'pyrex': ['file.pyx', 'file.pxd'], + \ 'python': ['file.py', 'file.pyw', '.pythonstartup', '.pythonrc', 'file.ptl'], + \ 'quake': ['anybaseq2/file.cfg', 'anyid1/file.cfg', 'quake3/file.cfg'], + \ 'radiance': ['file.rad', 'file.mat'], + \ 'ratpoison': ['.ratpoisonrc', 'ratpoisonrc'], + \ 'rc': ['file.rc', 'file.rch'], + \ 'rcs': ['file,v'], + \ 'readline': ['.inputrc', 'inputrc'], + \ 'remind': ['.reminders', 'file.remind', 'file.rem'], + \ 'resolv': ['resolv.conf'], + \ 'reva': ['file.frt'], + \ 'rexx': ['file.rex', 'file.orx', 'file.rxo', 'file.rxj', 'file.jrexx', 'file.rexxj', 'file.rexx', 'file.testGroup', 'file.testUnit'], + \ 'rib': ['file.rib'], + \ 'rnc': ['file.rnc'], + \ 'rng': ['file.rng'], + \ 'robots': ['robots.txt'], + \ 'rpcgen': ['file.x'], + \ 'rpl': ['file.rpl'], + \ 'rst': ['file.rst'], + \ 'rtf': ['file.rtf'], + \ 'ruby': ['.irbrc', 'irbrc', 'file.rb', 'file.rbw', 'file.gemspec', 'file.ru', 'Gemfile', 'file.builder', 'file.rxml', 'file.rjs', 'file.rant', 'file.rake'], + \ 'rust': ['file.rs'], + \ 'samba': ['smb.conf'], + \ 'sas': ['file.sas'], + \ 'sass': ['file.sass'], + \ 'sather': ['file.sa'], + \ 'sbt': ['file.sbt'], + \ 'scala': ['file.scala'], + \ 'scheme': ['file.scm', 'file.ss', 'file.rkt'], + \ 'scilab': ['file.sci', 'file.sce'], + \ 'screen': ['.screenrc', 'screenrc'], + \ 'scss': ['file.scss'], + \ 'sd': ['file.sd'], + \ 'sdc': ['file.sdc'], + \ 'sdl': ['file.sdl', 'file.pr'], + \ 'sed': ['file.sed'], + \ 'sensors': ['/etc/sensors.conf', '/etc/sensors3.conf'], + \ 'services': ['/etc/services'], + \ 'setserial': ['/etc/serial.conf'], + \ 'sh': ['/etc/udev/cdsymlinks.conf'], + \ 'sieve': ['file.siv'], + \ 'simula': ['file.sim'], + \ 'sinda': ['file.sin', 'file.s85'], + \ 'sisu': ['file.sst', 'file.ssm', 'file.ssi', 'file.-sst', 'file._sst', 'file.sst.meta', 'file.-sst.meta', 'file._sst.meta'], + \ 'skill': ['file.il', 'file.ils', 'file.cdf'], + \ 'slang': ['file.sl'], + \ 'slice': ['file.ice'], + \ 'slpconf': ['/etc/slp.conf'], + \ 'slpreg': ['/etc/slp.reg'], + \ 'slpspi': ['/etc/slp.spi'], + \ 'slrnrc': ['.slrnrc'], + \ 'slrnsc': ['file.score'], + \ 'sm': ['sendmail.cf'], + \ 'smarty': ['file.tpl'], + \ 'smcl': ['file.hlp', 'file.ihlp', 'file.smcl'], + \ 'smith': ['file.smt', 'file.smith'], + \ 'sml': ['file.sml'], + \ 'snobol4': ['file.sno', 'file.spt'], + \ 'spec': ['file.spec'], + \ 'spice': ['file.sp', 'file.spice'], + \ 'spup': ['file.speedup', 'file.spdata', 'file.spd'], + \ 'spyce': ['file.spy', 'file.spi'], + \ 'sql': ['file.tyb', 'file.typ', 'file.tyc', 'file.pkb', 'file.pks'], + \ 'sqlj': ['file.sqlj'], + \ 'sqr': ['file.sqr', 'file.sqi'], + \ 'squid': ['squid.conf'], + \ 'srec': ['file.s19', 'file.s28', 'file.s37', 'file.mot', 'file.srec'], + \ 'sshconfig': ['ssh_config', '/.ssh/config'], + \ 'sshdconfig': ['sshd_config'], + \ 'st': ['file.st'], + \ 'stata': ['file.ado', 'file.do', 'file.imata', 'file.mata'], + \ 'stp': ['file.stp'], + \ 'sudoers': ['any/etc/sudoers', 'sudoers.tmp'], + \ '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'], + \ 'systemverilog': ['file.sv', 'file.svh'], + \ 'tags': ['tags'], + \ 'tak': ['file.tak'], + \ 'taskdata': ['pending.data', 'completed.data', 'undo.data'], + \ 'taskedit': ['file.task'], + \ 'tcl': ['file.tcl', 'file.tk', 'file.itcl', 'file.itk', 'file.jacl'], + \ 'teraterm': ['file.ttl'], + \ 'terminfo': ['file.ti'], + \ 'tex': ['file.latex', 'file.sty', 'file.dtx', 'file.ltx', 'file.bbl'], + \ 'texinfo': ['file.texinfo', 'file.texi', 'file.txi'], + \ 'texmf': ['texmf.cnf'], + \ 'text': ['file.text', 'README'], + \ 'tf': ['file.tf', '.tfrc', 'tfrc'], + \ 'tidy': ['.tidyrc', 'tidyrc'], + \ 'tilde': ['file.t.html'], + \ 'tli': ['file.tli'], + \ 'tmux': ['tmuxfile.conf', '.tmuxfile.conf'], + \ 'tpp': ['file.tpp'], + \ 'treetop': ['file.treetop'], + \ 'trustees': ['trustees.conf'], + \ 'tsalt': ['file.slt'], + \ 'tsscl': ['file.tsscl'], + \ 'tssgm': ['file.tssgm'], + \ 'tssop': ['file.tssop'], + \ 'twig': ['file.twig'], + \ 'uc': ['file.uc'], + \ 'udevconf': ['/etc/udev/udev.conf'], + \ 'udevperm': ['/etc/udev/permissions.d/file.permissions'], + \ 'uil': ['file.uit', 'file.uil'], + \ 'updatedb': ['/etc/updatedb.conf'], + \ 'upstart': ['/usr/share/upstart/file.conf', '/usr/share/upstart/file.override', '/etc/init/file.conf', '/etc/init/file.override', '/.init/file.conf', '/.init/file.override', '/.config/upstart/file.conf', '/.config/upstart/file.override'], + \ 'upstreamdat': ['upstream.dat', 'UPSTREAM.DAT', 'upstream.file.dat', 'UPSTREAM.FILE.DAT', 'file.upstream.dat', 'FILE.UPSTREAM.DAT'], + \ 'upstreaminstalllog': ['upstreaminstall.log', 'UPSTREAMINSTALL.LOG', 'upstreaminstall.file.log', 'UPSTREAMINSTALL.FILE.LOG', 'file.upstreaminstall.log', 'FILE.UPSTREAMINSTALL.LOG'], + \ 'upstreamlog': ['fdrupstream.log', 'upstream.log', 'UPSTREAM.LOG', 'upstream.file.log', 'UPSTREAM.FILE.LOG', 'file.upstream.log', 'FILE.UPSTREAM.LOG', 'UPSTREAM-file.log', 'UPSTREAM-FILE.LOG'], + \ 'usserverlog': ['usserver.log', 'USSERVER.LOG', 'usserver.file.log', 'USSERVER.FILE.LOG', 'file.usserver.log', 'FILE.USSERVER.LOG'], + \ 'usw2kagtlog': ['usw2kagt.log', 'USW2KAGT.LOG', 'usw2kagt.file.log', 'USW2KAGT.FILE.LOG', 'file.usw2kagt.log', 'FILE.USW2KAGT.LOG'], + \ 'vb': ['file.sba', 'file.vb', 'file.vbs', 'file.dsm', 'file.ctl'], + \ 'vera': ['file.vr', 'file.vri', 'file.vrh'], + \ 'verilog': ['file.v'], + \ 'verilogams': ['file.va', 'file.vams'], + \ 'vgrindefs': ['vgrindefs'], + \ 'vhdl': ['file.hdl', 'file.vhd', 'file.vhdl', 'file.vbe', 'file.vst'], + \ 'vim': ['file.vim', 'file.vba', '.exrc', '_exrc'], + \ 'viminfo': ['.viminfo', '_viminfo'], + \ 'vmasm': ['file.mar'], + \ 'voscm': ['file.cm'], + \ 'vrml': ['file.wrl'], + \ 'vroom': ['file.vroom'], + \ 'webmacro': ['file.wm'], + \ 'wget': ['.wgetrc', 'wgetrc'], + \ 'winbatch': ['file.wbt'], + \ 'wml': ['file.wml'], + \ 'wsml': ['file.wsml'], + \ 'wvdial': ['wvdial.conf', '.wvdialrc'], + \ 'xdefaults': ['.Xdefaults', '.Xpdefaults', '.Xresources', 'xdm-config', 'file.ad'], + \ 'xhtml': ['file.xhtml', 'file.xht'], + \ 'xinetd': ['/etc/xinetd.conf'], + \ 'xmath': ['file.msc', 'file.msf'], + \ 'xml': ['/etc/blkid.tab', '/etc/blkid.tab.old', 'file.xmi', 'file.csproj', 'file.csproj.user', 'file.ts', 'file.ui', 'file.tpm', '/etc/xdg/menus/file.menu', 'fglrxrc', 'file.xlf', 'file.xliff', 'file.xul'], + \ 'xmodmap': ['anyXmodmap'], + \ 'xf86conf': ['xorg.conf', 'xorg.conf-4'], + \ 'xpm2': ['file.xpm2'], + \ 'xquery': ['file.xq', 'file.xql', 'file.xqm', 'file.xquery', 'file.xqy'], + \ 'xs': ['file.xs'], + \ 'xsd': ['file.xsd'], + \ 'xslt': ['file.xsl', 'file.xslt'], + \ 'yacc': ['file.yy', 'file.yxx', 'file.y++'], + \ 'yaml': ['file.yaml', 'file.yml'], + \ 'z8a': ['file.z8a'], + \ 'zimbu': ['file.zu'], + \ 'zimbutempl': ['file.zut'], + \ 'zsh': ['.zprofile', '/etc/zprofile', '.zfbfmarks', 'file.zsh'], + \ + \ 'aap': ['file.aap'], + \ 'help': [$VIMRUNTIME . '/doc/help.txt'], + \ 'xpm': ['file.xpm'], + \ } + +let s:filename_case_checks = { + \ 'modula2': ['file.DEF', 'file.MOD'], + \ } + +func CheckItems(checks) + for [ft, names] in items(a:checks) + for i in range(0, len(names) - 1) + new + try + exe 'edit ' . names[i] + catch + call assert_report('cannot edit "' . names[i] . '": ' . v:errmsg) + endtry + call assert_equal(ft, &filetype, 'with file name: ' . names[i]) + bwipe! + endfor + endfor +endfunc + +func Test_filetype_detection() + filetype on + call CheckItems(s:filename_checks) + if has('fname_case') + call CheckItems(s:filename_case_checks) + endif + filetype off +endfunc + +" Filetypes detected from the file contents by scripts.vim +let s:script_checks = { + \ 'virata': [['% Virata'], + \ ['', '% Virata'], + \ ['', '', '% Virata'], + \ ['', '', '', '% Virata'], + \ ['', '', '', '', '% Virata']], + \ 'strace': [['execve("/usr/bin/pstree", ["pstree"], 0x7ff0 /* 63 vars */) = 0'], + \ ['15:17:47 execve("/usr/bin/pstree", ["pstree"], ... "_=/usr/bin/strace"]) = 0'], + \ ['__libc_start_main and something']], + \ } + +func Test_script_detection() + filetype on + for [ft, files] in items(s:script_checks) + for file in files + call writefile(file, 'Xtest') + split Xtest + call assert_equal(ft, &filetype, 'for text: ' . string(file)) + bwipe! + endfor + endfor + call delete('Xtest') + filetype off +endfunc + diff --git a/src/nvim/testdir/test_find_complete.vim b/src/nvim/testdir/test_find_complete.vim new file mode 100644 index 0000000000..4732109ed0 --- /dev/null +++ b/src/nvim/testdir/test_find_complete.vim @@ -0,0 +1,157 @@ +" Tests for the 'find' command completion. + +" Do all the tests in a separate window to avoid E211 when we recursively +" delete the Xfind directory during cleanup +func Test_find_complete() + set belloff=all + + " On windows a stale "Xfind" directory may exist, remove it so that + " we start from a clean state. + call delete("Xfind", "rf") + let cwd = getcwd() + let test_out = cwd . '/test.out' + call mkdir('Xfind') + cd Xfind + + new + set path= + call assert_fails('call feedkeys(":find\t\n", "xt")', 'E345:') + close + + new + set path=. + call assert_fails('call feedkeys(":find\t\n", "xt")', 'E32:') + close + + new + set path=.,, + call assert_fails('call feedkeys(":find\t\n", "xt")', 'E32:') + close + + new + set path=./** + call assert_fails('call feedkeys(":find\t\n", "xt")', 'E32:') + close + + " We shouldn't find any file till this point + + call mkdir('in/path', 'p') + exe 'cd ' . cwd + call writefile(['Holy Grail'], 'Xfind/file.txt') + call writefile(['Jimmy Hoffa'], 'Xfind/in/file.txt') + call writefile(['Another Holy Grail'], 'Xfind/in/stuff.txt') + call writefile(['E.T.'], 'Xfind/in/path/file.txt') + + new + set path=Xfind/** + call feedkeys(":find file\t\n", "xt") + call assert_equal('Holy Grail', getline(1)) + call feedkeys(":find file\t\t\n", "xt") + call assert_equal('Jimmy Hoffa', getline(1)) + call feedkeys(":find file\t\t\t\n", "xt") + call assert_equal('E.T.', getline(1)) + + " Rerun the previous three find completions, using fullpath in 'path' + exec "set path=" . cwd . "/Xfind/**" + + call feedkeys(":find file\t\n", "xt") + call assert_equal('Holy Grail', getline(1)) + call feedkeys(":find file\t\t\n", "xt") + call assert_equal('Jimmy Hoffa', getline(1)) + call feedkeys(":find file\t\t\t\n", "xt") + call assert_equal('E.T.', getline(1)) + + " Same steps again, using relative and fullpath items that point to the same + " recursive location. + " This is to test that there are no duplicates in the completion list. + set path+=Xfind/** + call feedkeys(":find file\t\n", "xt") + call assert_equal('Holy Grail', getline(1)) + call feedkeys(":find file\t\t\n", "xt") + call assert_equal('Jimmy Hoffa', getline(1)) + call feedkeys(":find file\t\t\t\n", "xt") + call assert_equal('E.T.', getline(1)) + call feedkeys(":find file\t\t\n", "xt") + + " Test find completion for directory of current buffer, which at this point + " is Xfind/in/file.txt. + set path=. + call feedkeys(":find st\t\n", "xt") + call assert_equal('Another Holy Grail', getline(1)) + + " Test find completion for empty path item ",," which is the current + " directory + cd Xfind + set path=,, + call feedkeys(":find f\t\n", "xt") + call assert_equal('Holy Grail', getline(1)) + + " Test shortening of + " + " foo/x/bar/voyager.txt + " foo/y/bar/voyager.txt + " + " When current directory is above foo/ they should be shortened to (in order + " of appearance): + " + " x/bar/voyager.txt + " y/bar/voyager.txt + call mkdir('foo/x/bar', 'p') + call mkdir('foo/y/bar', 'p') + call writefile(['Voyager 1'], 'foo/x/bar/voyager.txt') + call writefile(['Voyager 2'], 'foo/y/bar/voyager.txt') + + exec "set path=" . cwd . "/Xfind/**" + call feedkeys(":find voyager\t\n", "xt") + call assert_equal('Voyager 1', getline(1)) + call feedkeys(":find voyager\t\t\n", "xt") + call assert_equal('Voyager 2', getline(1)) + + " + " When current directory is .../foo/y/bar they should be shortened to (in + " order of appearance): + " + " ./voyager.txt + " x/bar/voyager.txt + cd foo/y/bar + call feedkeys(":find voyager\t\n", "xt") + call assert_equal('Voyager 2', getline(1)) + call feedkeys(":find voyager\t\t\n", "xt") + call assert_equal('Voyager 1', getline(1)) + + " Check the opposite too: + cd ../../x/bar + call feedkeys(":find voyager\t\n", "xt") + call assert_equal('Voyager 1', getline(1)) + call feedkeys(":find voyager\t\t\n", "xt") + call assert_equal('Voyager 2', getline(1)) + + " Check for correct handling of shorten_fname()'s behavior on windows + exec "cd " . cwd . "/Xfind/in" + call feedkeys(":find file\t\n", "xt") + call assert_equal('Jimmy Hoffa', getline(1)) + + " Test for relative to current buffer 'path' item + exec "cd " . cwd . "/Xfind/" + set path=./path + " Open the file where Jimmy Hoffa is found + e in/file.txt + " Find the file containing 'E.T.' in the Xfind/in/path directory + call feedkeys(":find file\t\n", "xt") + call assert_equal('E.T.', getline(1)) + + " Test that completion works when path=.,, + set path=.,, + " Open Jimmy Hoffa file + e in/file.txt + call assert_equal('Jimmy Hoffa', getline(1)) + + " Search for the file containing Holy Grail in same directory as in/path.txt + call feedkeys(":find stu\t\n", "xt") + call assert_equal('Another Holy Grail', getline(1)) + + enew | only + exe 'cd ' . cwd + call delete('Xfind', 'rf') + set path& +endfunc diff --git a/src/nvim/testdir/test_findfile.vim b/src/nvim/testdir/test_findfile.vim new file mode 100644 index 0000000000..d9a89801ea --- /dev/null +++ b/src/nvim/testdir/test_findfile.vim @@ -0,0 +1,25 @@ +" Test for findfile() +" +func Test_findfile() + new + let cwd=getcwd() + cd .. + + " Tests may be run from a shadow directory, so an extra cd needs to be done to + " get above src/ + if fnamemodify(getcwd(), ':t') != 'src' + cd ../.. + else + cd .. + endif + set ssl + + call assert_equal('src/nvim/testdir/test_findfile.vim', findfile('test_findfile.vim','src/nvim/test*')) + exe "cd" cwd + cd .. + call assert_equal('testdir/test_findfile.vim', findfile('test_findfile.vim','test*')) + call assert_equal('testdir/test_findfile.vim', findfile('test_findfile.vim','testdir')) + + exe "cd" cwd + q! +endfunc diff --git a/src/nvim/testdir/test_fixeol.vim b/src/nvim/testdir/test_fixeol.vim new file mode 100644 index 0000000000..32cb059e26 --- /dev/null +++ b/src/nvim/testdir/test_fixeol.vim @@ -0,0 +1,48 @@ +" Tests for 'fixeol' and 'eol' +func Test_fixeol() + " first write two test files – with and without trailing EOL + " use Unix fileformat for consistency + set ff=unix + enew! + call setline('.', 'with eol') + w! XXEol + enew! + set noeol nofixeol + call setline('.', 'without eol') + w! XXNoEol + set eol fixeol + bwipe XXEol XXNoEol + + " try editing files with 'fixeol' disabled + e! XXEol + normal ostays eol + set nofixeol + w! XXTestEol + e! XXNoEol + normal ostays without + set nofixeol + w! XXTestNoEol + bwipe! XXEol XXNoEol XXTestEol XXTestNoEol + set fixeol + + " Append "END" to each file so that we can see what the last written char + " was. + normal ggdGaEND + w >>XXEol + w >>XXNoEol + w >>XXTestEol + w >>XXTestNoEol + + call assert_equal(['with eol', 'END'], readfile('XXEol')) + call assert_equal(['without eolEND'], readfile('XXNoEol')) + call assert_equal(['with eol', 'stays eol', 'END'], readfile('XXTestEol')) + call assert_equal(['without eol', 'stays withoutEND'], + \ readfile('XXTestNoEol')) + + call delete('XXEol') + call delete('XXNoEol') + call delete('XXTestEol') + call delete('XXTestNoEol') + set ff& fixeol& eol& + enew! +endfunc diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim index 46c54e8614..7c6d38d7ec 100644 --- a/src/nvim/testdir/test_fold.vim +++ b/src/nvim/testdir/test_fold.vim @@ -360,3 +360,24 @@ func! Test_move_folds_around_indent() call assert_equal([0, 1, 1, 1, 1, 0, 0, 0, 1, 1], map(range(1, line('$')), 'foldlevel(v:val)')) bw! endfunc + +" test for patch 7.3.637 +" Cannot catch the error caused by a foldopen when there is no fold. +func Test_foldopen_exception() + enew! + let a = 'No error caught' + try + foldopen + catch + let a = matchstr(v:exception,'^[^ ]*') + endtry + call assert_equal('Vim(foldopen):E490:', a) + + let a = 'No error caught' + try + foobar + catch + let a = matchstr(v:exception,'^[^ ]*') + endtry + call assert_match('E492:', a) +endfunc diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim index 0ce034b63e..398e9ab331 100644 --- a/src/nvim/testdir/test_functions.vim +++ b/src/nvim/testdir/test_functions.vim @@ -311,3 +311,45 @@ func! Test_mode() bwipe! iunmap <F2> endfunc + +func Test_getbufvar() + let bnr = bufnr('%') + let b:var_num = '1234' + let def_num = '5678' + call assert_equal('1234', getbufvar(bnr, 'var_num')) + call assert_equal('1234', getbufvar(bnr, 'var_num', def_num)) + + let bd = getbufvar(bnr, '') + call assert_equal('1234', bd['var_num']) + call assert_true(exists("bd['changedtick']")) + call assert_equal(2, len(bd)) + + let bd2 = getbufvar(bnr, '', def_num) + call assert_equal(bd, bd2) + + unlet b:var_num + call assert_equal(def_num, getbufvar(bnr, 'var_num', def_num)) + call assert_equal('', getbufvar(bnr, 'var_num')) + + let bd = getbufvar(bnr, '') + call assert_equal(1, len(bd)) + let bd = getbufvar(bnr, '',def_num) + call assert_equal(1, len(bd)) + + call assert_equal('', getbufvar(9999, '')) + call assert_equal(def_num, getbufvar(9999, '', def_num)) + unlet def_num + + call assert_equal(0, getbufvar(bnr, '&autoindent')) + call assert_equal(0, getbufvar(bnr, '&autoindent', 1)) + + " Open new window with forced option values + set fileformats=unix,dos + new ++ff=dos ++bin ++enc=iso-8859-2 + call assert_equal('dos', getbufvar(bufnr('%'), '&fileformat')) + call assert_equal(1, getbufvar(bufnr('%'), '&bin')) + call assert_equal('iso-8859-2', getbufvar(bufnr('%'), '&fenc')) + close + + set fileformats& +endfunc diff --git a/src/nvim/testdir/test_hardcopy.vim b/src/nvim/testdir/test_hardcopy.vim index ea9790d134..7aea704e86 100644 --- a/src/nvim/testdir/test_hardcopy.vim +++ b/src/nvim/testdir/test_hardcopy.vim @@ -50,6 +50,7 @@ endfunc " We don't check much of the contents. func Test_with_syntax() if has('postscript') + edit test_hardcopy.vim set printoptions=syntax:y syn on hardcopy > Xhardcopy diff --git a/src/nvim/testdir/test_help_tagjump.vim b/src/nvim/testdir/test_help_tagjump.vim index 65d99c644c..06c48d8e76 100644 --- a/src/nvim/testdir/test_help_tagjump.vim +++ b/src/nvim/testdir/test_help_tagjump.vim @@ -18,6 +18,52 @@ func Test_help_tagjump() call assert_true(getline('.') =~ '\*help.txt\*') helpclose + help | + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*bar\*') + helpclose + + help "* + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*quotestar\*') + helpclose + + help sp?it + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*:split\*') + helpclose + + help :? + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*:?\*') + helpclose + + help FileW*Post + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*FileWritePost\*') + helpclose + + help `ls` + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*:ls\*') + helpclose + + help ^X + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*CTRL-X\*') + helpclose + + help i_^_CTRL-D + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*i_^_CTRL-D\*') + helpclose + + exec "help \<C-V>" + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*CTRL-V\*') + helpclose + + exec "help! ('textwidth'" call assert_equal("help", &filetype) call assert_true(getline('.') =~ "\\*'textwidth'\\*") @@ -47,6 +93,16 @@ func Test_help_tagjump() call assert_equal("help", &filetype) call assert_true(getline('.') =~ '\*{address}\*') helpclose + + exusage + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*:index\*') + helpclose + + viusage + call assert_equal("help", &filetype) + call assert_true(getline('.') =~ '\*normal-index\*') + helpclose endfunc let s:langs = ['en', 'ab', 'ja'] diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim new file mode 100644 index 0000000000..c307e33cbf --- /dev/null +++ b/src/nvim/testdir/test_ins_complete.vim @@ -0,0 +1,219 @@ + +" Test for insert expansion +func Test_ins_complete() + edit test_ins_complete.vim + " The files in the current directory interferes with the files + " used by this test. So use a separate directory for the test. + call mkdir('Xdir') + cd Xdir + + set ff=unix + call writefile(["test11\t36Gepeto\t/Tag/", + \ "asd\ttest11file\t36G", + \ "Makefile\tto\trun"], 'Xtestfile') + call writefile(['', 'start of testfile', + \ 'ru', + \ 'run1', + \ 'run2', + \ 'STARTTEST', + \ 'ENDTEST', + \ 'end of testfile'], 'Xtestdata') + set ff& + + enew! + edit Xtestdata + new + call append(0, ['#include "Xtestfile"', '']) + call cursor(2, 1) + + set cot= + set cpt=.,w + " add-expands (word from next line) from other window + exe "normal iru\<C-N>\<C-N>\<C-X>\<C-N>\<Esc>\<C-A>" + call assert_equal('run1 run3', getline('.')) + " add-expands (current buffer first) + exe "normal o\<C-P>\<C-X>\<C-N>" + call assert_equal('run3 run3', getline('.')) + " Local expansion, ends in an empty line (unless it becomes a global + " expansion) + exe "normal o\<C-X>\<C-P>\<C-P>\<C-P>\<C-P>\<C-P>" + call assert_equal('', getline('.')) + " starts Local and switches to global add-expansion + exe "normal o\<C-X>\<C-P>\<C-P>\<C-X>\<C-X>\<C-N>\<C-X>\<C-N>\<C-N>" + call assert_equal('run1 run2', getline('.')) + + set cpt=.,w,i + " i-add-expands and switches to local + exe "normal OM\<C-N>\<C-X>\<C-N>\<C-X>\<C-N>\<C-X>\<C-X>\<C-X>\<C-P>" + call assert_equal("Makefile\tto\trun3", getline('.')) + " add-expands lines (it would end in an empty line if it didn't ignored + " itself) + exe "normal o\<C-X>\<C-L>\<C-X>\<C-L>\<C-P>\<C-P>" + call assert_equal("Makefile\tto\trun3", getline('.')) + call assert_equal("Makefile\tto\trun3", getline(line('.') - 1)) + + set cpt=kXtestfile + " checks k-expansion, and file expansion (use Xtest11 instead of test11, + " because TEST11.OUT may match first on DOS) + write Xtest11.one + write Xtest11.two + exe "normal o\<C-N>\<Esc>IX\<Esc>A\<C-X>\<C-F>\<C-N>" + call assert_equal('Xtest11.two', getline('.')) + + " use CTRL-X CTRL-F to complete Xtest11.one, remove it and then use CTRL-X + " CTRL-F again to verify this doesn't cause trouble. + exe "normal oXt\<C-X>\<C-F>\<BS>\<BS>\<BS>\<BS>\<BS>\<BS>\<BS>\<BS>\<C-X>\<C-F>" + call assert_equal('Xtest11.one', getline('.')) + normal ddk + + set cpt=w + " checks make_cyclic in other window + exe "normal oST\<C-N>\<C-P>\<C-P>\<C-P>\<C-P>" + call assert_equal('STARTTEST', getline('.')) + + set cpt=u nohid + " checks unloaded buffer expansion + only + exe "normal oEN\<C-N>" + call assert_equal('ENDTEST', getline('.')) + " checks adding mode abortion + exe "normal ounl\<C-N>\<C-X>\<C-X>\<C-P>" + call assert_equal('unless', getline('.')) + + set cpt=t,d def=^\\k* tags=Xtestfile notagbsearch + " tag expansion, define add-expansion interrupted + exe "normal o\<C-X>\<C-]>\<C-X>\<C-D>\<C-X>\<C-D>\<C-X>\<C-X>\<C-D>\<C-X>\<C-D>\<C-X>\<C-D>\<C-X>\<C-D>" + call assert_equal('test11file 36Gepeto /Tag/ asd', getline('.')) + " t-expansion + exe "normal oa\<C-N>\<Esc>" + call assert_equal('asd', getline('.')) + + %bw! + call delete('Xtestfile') + call delete('Xtest11.one') + call delete('Xtest11.two') + call delete('Xtestdata') + set cpt& cot& def& tags& tagbsearch& hidden& + cd .. + call delete('Xdir', 'rf') +endfunc + +function! s:CompleteDone_CompleteFuncDict( findstart, base ) + if a:findstart + return 0 + endif + + return { + \ 'words': [ + \ { + \ 'word': 'aword', + \ 'abbr': 'wrd', + \ 'menu': 'extra text', + \ 'info': 'words are cool', + \ 'kind': 'W', + \ 'user_data': 'test' + \ } + \ ] + \ } +endfunction + +function! s:CompleteDone_CheckCompletedItemDict() + call assert_equal( 'aword', v:completed_item[ 'word' ] ) + call assert_equal( 'wrd', v:completed_item[ 'abbr' ] ) + call assert_equal( 'extra text', v:completed_item[ 'menu' ] ) + call assert_equal( 'words are cool', v:completed_item[ 'info' ] ) + call assert_equal( 'W', v:completed_item[ 'kind' ] ) + call assert_equal( 'test', v:completed_item[ 'user_data' ] ) + + let s:called_completedone = 1 +endfunction + +function Test_CompleteDoneDict() + au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemDict() + + set completefunc=<SID>CompleteDone_CompleteFuncDict + execute "normal a\<C-X>\<C-U>\<C-Y>" + set completefunc& + + call assert_equal( 'test', v:completed_item[ 'user_data' ] ) + call assert_true( s:called_completedone ) + + let s:called_completedone = 0 + au! CompleteDone +endfunc + +function! s:CompleteDone_CompleteFuncDictNoUserData( findstart, base ) + if a:findstart + return 0 + endif + + return { + \ 'words': [ + \ { + \ 'word': 'aword', + \ 'abbr': 'wrd', + \ 'menu': 'extra text', + \ 'info': 'words are cool', + \ 'kind': 'W' + \ } + \ ] + \ } +endfunction + +function! s:CompleteDone_CheckCompletedItemDictNoUserData() + call assert_equal( 'aword', v:completed_item[ 'word' ] ) + call assert_equal( 'wrd', v:completed_item[ 'abbr' ] ) + call assert_equal( 'extra text', v:completed_item[ 'menu' ] ) + call assert_equal( 'words are cool', v:completed_item[ 'info' ] ) + call assert_equal( 'W', v:completed_item[ 'kind' ] ) + call assert_equal( '', v:completed_item[ 'user_data' ] ) + + let s:called_completedone = 1 +endfunction + +function Test_CompleteDoneDictNoUserData() + au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemDictNoUserData() + + set completefunc=<SID>CompleteDone_CompleteFuncDictNoUserData + execute "normal a\<C-X>\<C-U>\<C-Y>" + set completefunc& + + call assert_equal( '', v:completed_item[ 'user_data' ] ) + call assert_true( s:called_completedone ) + + let s:called_completedone = 0 + au! CompleteDone +endfunc + +function! s:CompleteDone_CompleteFuncList( findstart, base ) + if a:findstart + return 0 + endif + + return [ 'aword' ] +endfunction + +function! s:CompleteDone_CheckCompletedItemList() + call assert_equal( 'aword', v:completed_item[ 'word' ] ) + call assert_equal( '', v:completed_item[ 'abbr' ] ) + call assert_equal( '', v:completed_item[ 'menu' ] ) + call assert_equal( '', v:completed_item[ 'info' ] ) + call assert_equal( '', v:completed_item[ 'kind' ] ) + call assert_equal( '', v:completed_item[ 'user_data' ] ) + + let s:called_completedone = 1 +endfunction + +function Test_CompleteDoneList() + au CompleteDone * :call <SID>CompleteDone_CheckCompletedItemList() + + set completefunc=<SID>CompleteDone_CompleteFuncList + execute "normal a\<C-X>\<C-U>\<C-Y>" + set completefunc& + + call assert_equal( '', v:completed_item[ 'user_data' ] ) + call assert_true( s:called_completedone ) + + let s:called_completedone = 0 + au! CompleteDone +endfunc diff --git a/src/nvim/testdir/test_let.vim b/src/nvim/testdir/test_let.vim new file mode 100644 index 0000000000..24c6ef5e01 --- /dev/null +++ b/src/nvim/testdir/test_let.vim @@ -0,0 +1,27 @@ +" Tests for the :let command. + +func Test_let() + " Test to not autoload when assigning. It causes internal error. + set runtimepath+=./sautest + let Test104#numvar = function('tr') + call assert_equal("function('tr')", string(Test104#numvar)) + + let a = 1 + let b = 2 + + let out = execute('let a b') + let s = "\na #1\nb #2" + call assert_equal(s, out) + + let out = execute('let {0 == 1 ? "a" : "b"}') + let s = "\nb #2" + call assert_equal(s, out) + + let out = execute('let {0 == 1 ? "a" : "b"} a') + let s = "\nb #2\na #1" + call assert_equal(s, out) + + let out = execute('let a {0 == 1 ? "a" : "b"}') + let s = "\na #1\nb #2" + call assert_equal(s, out) +endfunc diff --git a/src/nvim/testdir/test_lineending.vim b/src/nvim/testdir/test_lineending.vim new file mode 100644 index 0000000000..5be3be8db3 --- /dev/null +++ b/src/nvim/testdir/test_lineending.vim @@ -0,0 +1,19 @@ +" Tests for saving/loading a file with some lines ending in +" CTRL-M, some not +func Test_lineending() + let l = ["this line ends in a\<CR>", + \ "this one doesn't", + \ "this one does\<CR>", + \ "and the last one doesn't"] + set fileformat=dos + enew! + call append(0, l) + $delete + write Xfile1 + bwipe Xfile1 + edit Xfile1 + let t = getline(1, '$') + call assert_equal(l, t) + new | only + call delete('Xfile1') +endfunc diff --git a/src/nvim/testdir/test_lispwords.vim b/src/nvim/testdir/test_lispwords.vim new file mode 100644 index 0000000000..4c05504cf1 --- /dev/null +++ b/src/nvim/testdir/test_lispwords.vim @@ -0,0 +1,82 @@ +" Tests for 'lispwords' settings being global-local + +set nocompatible viminfo+=nviminfo + +func Test_global_local_lispwords() + setglobal lispwords=foo,bar,baz + setlocal lispwords-=foo | setlocal lispwords+=quux + call assert_equal('foo,bar,baz', &g:lispwords) + call assert_equal('bar,baz,quux', &l:lispwords) + call assert_equal('bar,baz,quux', &lispwords) + + setlocal lispwords< + call assert_equal('foo,bar,baz', &g:lispwords) + call assert_equal('foo,bar,baz', &l:lispwords) + call assert_equal('foo,bar,baz', &lispwords) +endfunc + +func Test_lisp_indent() + enew! + + call append(0, [ + \ '(defun html-file (base)', + \ '(format nil "~(~A~).html" base))', + \ '', + \ '(defmacro page (name title &rest body)', + \ '(let ((ti (gensym)))', + \ '`(with-open-file (*standard-output*', + \ '(html-file ,name)', + \ ':direction :output', + \ ':if-exists :supersede)', + \ '(let ((,ti ,title))', + \ '(as title ,ti)', + \ '(with center ', + \ '(as h2 (string-upcase ,ti)))', + \ '(brs 3)', + \ ',@body))))', + \ '', + \ ';;; Utilities for generating links', + \ '', + \ '(defmacro with-link (dest &rest body)', + \ '`(progn', + \ '(format t "<a href=\"~A\">" (html-file ,dest))', + \ ',@body', + \ '(princ "</a>")))' + \ ]) + set lisp + set lispwords& + let save_copt = &cpoptions + set cpoptions+=p + normal 1G=G + + call assert_equal([ + \ '(defun html-file (base)', + \ ' (format nil "~(~A~).html" base))', + \ '', + \ '(defmacro page (name title &rest body)', + \ ' (let ((ti (gensym)))', + \ ' `(with-open-file (*standard-output*', + \ ' (html-file ,name)', + \ ' :direction :output', + \ ' :if-exists :supersede)', + \ ' (let ((,ti ,title))', + \ ' (as title ,ti)', + \ ' (with center ', + \ ' (as h2 (string-upcase ,ti)))', + \ ' (brs 3)', + \ ' ,@body))))', + \ '', + \ ';;; Utilities for generating links', + \ '', + \ '(defmacro with-link (dest &rest body)', + \ ' `(progn', + \ ' (format t "<a href=\"~A\">" (html-file ,dest))', + \ ' ,@body', + \ ' (princ "</a>")))', + \ '' + \ ], getline(1, "$")) + + enew! + let &cpoptions=save_copt + set nolisp +endfunc diff --git a/src/nvim/testdir/test_listchars.vim b/src/nvim/testdir/test_listchars.vim new file mode 100644 index 0000000000..57ea7ca5a9 --- /dev/null +++ b/src/nvim/testdir/test_listchars.vim @@ -0,0 +1,63 @@ +" Tests for 'listchars' display with 'list' and :list + +source view_util.vim + +func Test_listchars() + enew! + set ff=unix + set list + + set listchars+=tab:>-,space:.,trail:< + call append(0, [ + \ ' aa ', + \ ' bb ', + \ ' cccc ', + \ 'dd ee ', + \ ' ' + \ ]) + let expected = [ + \ '>-------aa>-----$', + \ '..bb>---<<$', + \ '...cccc><$', + \ 'dd........ee<<>-$', + \ '<$' + \ ] + redraw! + for i in range(1, 5) + call cursor(i, 1) + call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) + endfor + + set listchars-=trail:< + let expected = [ + \ '>-------aa>-----$', + \ '..bb>---..$', + \ '...cccc>.$', + \ 'dd........ee..>-$', + \ '.$' + \ ] + redraw! + for i in range(1, 5) + call cursor(i, 1) + call assert_equal([expected[i - 1]], ScreenLines(i, virtcol('$'))) + endfor + + set listchars+=trail:< + set nolist + normal ggdG + call append(0, [ + \ ' fff ', + \ ' gg ', + \ ' h ', + \ 'iii ', + \ ]) + let l = split(execute("%list"), "\n") + call assert_equal([ + \ '..fff>--<<$', + \ '>-------gg>-----$', + \ '.....h>-$', + \ 'iii<<<<><<$', '$'], l) + + enew! + set listchars& ff& +endfunc diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim new file mode 100644 index 0000000000..023332c90a --- /dev/null +++ b/src/nvim/testdir/test_listdict.vim @@ -0,0 +1,603 @@ +" Tests for the List and Dict types + +func TearDown() + " Run garbage collection after every test + call test_garbagecollect_now() +endfunc + +" Tests for List type + +" List creation +func Test_list_create() + " Creating List directly with different types + let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},] + call assert_equal("[1, 'as''d', [1, 2, function('strlen')], {'a': 1}]", string(l)) + call assert_equal({'a' : 1}, l[-1]) + call assert_equal(1, l[-4]) + let x = 10 + try + let x = l[-5] + catch + call assert_match('E684:', v:exception) + endtry + call assert_equal(10, x) +endfunc + +" List slices +func Test_list_slice() + let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},] + call assert_equal([1, 'as''d', [1, 2, function('strlen')], {'a': 1}], l[:]) + call assert_equal(['as''d', [1, 2, function('strlen')], {'a': 1}], l[1:]) + call assert_equal([1, 'as''d', [1, 2, function('strlen')]], l[:-2]) + call assert_equal([1, 'as''d', [1, 2, function('strlen')], {'a': 1}], l[0:8]) + call assert_equal([], l[8:-1]) +endfunc + +" List identity +func Test_list_identity() + let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},] + let ll = l + let lx = copy(l) + call assert_true(l == ll) + call assert_false(l isnot ll) + call assert_true(l is ll) + call assert_true(l == lx) + call assert_false(l is lx) + call assert_true(l isnot lx) +endfunc + +" removing items with :unlet +func Test_list_unlet() + let l = [1, 'as''d', [1, 2, function("strlen")], {'a': 1},] + unlet l[2] + call assert_equal([1, 'as''d', {'a': 1}], l) + let l = range(8) + unlet l[:3] + unlet l[1:] + call assert_equal([4], l) + + " removing items out of range: silently skip items that don't exist + let l = [0, 1, 2, 3] + call assert_fails('unlet l[2:1]', 'E684') + let l = [0, 1, 2, 3] + unlet l[2:2] + call assert_equal([0, 1, 3], l) + let l = [0, 1, 2, 3] + unlet l[2:3] + call assert_equal([0, 1], l) + let l = [0, 1, 2, 3] + unlet l[2:4] + call assert_equal([0, 1], l) + let l = [0, 1, 2, 3] + unlet l[2:5] + call assert_equal([0, 1], l) + let l = [0, 1, 2, 3] + call assert_fails('unlet l[-1:2]', 'E684') + let l = [0, 1, 2, 3] + unlet l[-2:2] + call assert_equal([0, 1, 3], l) + let l = [0, 1, 2, 3] + unlet l[-3:2] + call assert_equal([0, 3], l) + let l = [0, 1, 2, 3] + unlet l[-4:2] + call assert_equal([3], l) + let l = [0, 1, 2, 3] + unlet l[-5:2] + call assert_equal([3], l) + let l = [0, 1, 2, 3] + unlet l[-6:2] + call assert_equal([3], l) +endfunc + +" assignment to a list +func Test_list_assign() + let l = [0, 1, 2, 3] + let [va, vb] = l[2:3] + call assert_equal([2, 3], [va, vb]) + call assert_fails('let [va, vb] = l', 'E687') + call assert_fails('let [va, vb] = l[1:1]', 'E688') +endfunc + +" test for range assign +func Test_list_range_assign() + let l = [0] + let l[:] = [1, 2] + call assert_equal([1, 2], l) +endfunc + +" Tests for Dictionary type + +func Test_dict() + " Creating Dictionary directly with different types + let d = {001: 'asd', 'b': [1, 2, function('strlen')], -1: {'a': 1},} + call assert_equal("{'1': 'asd', 'b': [1, 2, function('strlen')], '-1': {'a': 1}}", string(d)) + call assert_equal('asd', d.1) + call assert_equal(['-1', '1', 'b'], sort(keys(d))) + call assert_equal(['asd', [1, 2, function('strlen')], {'a': 1}], values(d)) + let v = [] + for [key, val] in items(d) + call extend(v, [key, val]) + unlet key val + endfor + call assert_equal(['1','asd','b',[1, 2, function('strlen')],'-1',{'a': 1}], v) + + call extend(d, {3:33, 1:99}) + call extend(d, {'b':'bbb', 'c':'ccc'}, "keep") + call assert_fails("call extend(d, {3:333,4:444}, 'error')", 'E737') + call assert_equal({'c': 'ccc', '1': 99, 'b': [1, 2, function('strlen')], '3': 33, '-1': {'a': 1}}, d) + call filter(d, 'v:key =~ ''[ac391]''') + call assert_equal({'c': 'ccc', '1': 99, '3': 33, '-1': {'a': 1}}, d) +endfunc + +" Dictionary identity +func Test_dict_identity() + let d = {001: 'asd', 'b': [1, 2, function('strlen')], -1: {'a': 1},} + let dd = d + let dx = copy(d) + call assert_true(d == dd) + call assert_false(d isnot dd) + call assert_true(d is dd) + call assert_true(d == dx) + call assert_false(d is dx) + call assert_true(d isnot dx) +endfunc + +" removing items with :unlet +func Test_dict_unlet() + let d = {'b':'bbb', '1': 99, '3': 33, '-1': {'a': 1}} + unlet d.b + unlet d[-1] + call assert_equal({'1': 99, '3': 33}, d) +endfunc + +" manipulating a big Dictionary (hashtable.c has a border of 1000 entries) +func Test_dict_big() + let d = {} + for i in range(1500) + let d[i] = 3000 - i + endfor + call assert_equal([3000, 2900, 2001, 1600, 1501], [d[0], d[100], d[999], d[1400], d[1499]]) + let str = '' + try + let n = d[1500] + catch + let str=substitute(v:exception, '\v(.{14}).*( \d{4}).*', '\1\2', '') + endtry + call assert_equal('Vim(let):E716: 1500', str) + + " lookup each items + for i in range(1500) + call assert_equal(3000 - i, d[i]) + endfor + let i += 1 + + " delete even items + while i >= 2 + let i -= 2 + unlet d[i] + endwhile + call assert_equal('NONE', get(d, 1500 - 100, 'NONE')) + call assert_equal(2999, d[1]) + + " delete odd items, checking value, one intentionally wrong + let d[33] = 999 + let i = 1 + while i < 1500 + if i != 33 + call assert_equal(3000 - i, d[i]) + else + call assert_equal(999, d[i]) + endif + unlet d[i] + let i += 2 + endwhile + call assert_equal({}, d) + unlet d +endfunc + +" Dictionary function +func Test_dict_func() + let d = {} + func d.func(a) dict + return a:a . len(self.data) + endfunc + let d.data = [1,2,3] + call assert_equal('len: 3', d.func('len: ')) + let x = d.func('again: ') + call assert_equal('again: 3', x) + let Fn = d.func + call assert_equal('xxx3', Fn('xxx')) +endfunc + +" Function in script-local List or Dict +func Test_script_local_dict_func() + let g:dict = {} + function g:dict.func() dict + return 'g:dict.func' . self.foo[1] . self.foo[0]('asdf') + endfunc + let g:dict.foo = ['-', 2, 3] + call insert(g:dict.foo, function('strlen')) + call assert_equal('g:dict.func-4', g:dict.func()) + unlet g:dict +endfunc + +" Nasty: remove func from Dict that's being called (works) +func Test_dict_func_remove_in_use() + let d = {1:1} + func d.func(a) + return "a:" . a:a + endfunc + let expected = 'a:' . string(get(d, 'func')) + call assert_equal(expected, d.func(string(remove(d, 'func')))) +endfunc + +" Nasty: deepcopy() dict that refers to itself (fails when noref used) +func Test_dict_deepcopy() + let d = {1:1, 2:2} + let l = [4, d, 6] + let d[3] = l + let dc = deepcopy(d) + call assert_fails('call deepcopy(d, 1)', 'E698') + let l2 = [0, l, l, 3] + let l[1] = l2 + let l3 = deepcopy(l2) + call assert_true(l3[1] is l3[2]) +endfunc + +" Locked variables +func Test_list_locked_var() + let expected = [ + \ [['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp']], + \ [['1000-000', 'ppppppF'], + \ ['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp']], + \ [['1100-100', 'ppFppFF'], + \ ['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp']], + \ [['1110-110', 'pFFpFFF'], + \ ['0010-010', 'pFppFpp'], + \ ['0000-000', 'ppppppp']], + \ [['1111-111', 'FFFFFFF'], + \ ['0011-011', 'FFpFFpp'], + \ ['0000-000', 'ppppppp']] + \ ] + for depth in range(5) + for u in range(3) + unlet! l + let l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}] + exe "lockvar " . depth . " l" + if u == 1 + exe "unlockvar l" + elseif u == 2 + exe "unlockvar " . depth . " l" + endif + let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]") + call assert_equal(expected[depth][u][0], ps) + let ps = '' + try + let l[1][1][0] = 99 + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + let l[1][1] = [99] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + let l[1] = [99] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + let l[2]['6'][7] = 99 + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + let l[2][6] = {99: 99} + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + let l[2] = {99: 99} + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + let l = [99] + let ps .= 'p' + catch + let ps .= 'F' + endtry + call assert_equal(expected[depth][u][1], ps) + endfor + endfor +endfunc + +" Unletting locked variables +func Test_list_locked_var_unlet() + let expected = [ + \ [['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp']], + \ [['1000-000', 'ppFppFp'], + \ ['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp']], + \ [['1100-100', 'pFFpFFp'], + \ ['0000-000', 'ppppppp'], + \ ['0000-000', 'ppppppp']], + \ [['1110-110', 'FFFFFFp'], + \ ['0010-010', 'FppFppp'], + \ ['0000-000', 'ppppppp']], + \ [['1111-111', 'FFFFFFp'], + \ ['0011-011', 'FppFppp'], + \ ['0000-000', 'ppppppp']] + \ ] + + for depth in range(5) + for u in range(3) + unlet! l + let l = [0, [1, [2, 3]], {4: 5, 6: {7: 8}}] + exe "lockvar " . depth . " l" + if u == 1 + exe "unlockvar l" + elseif u == 2 + exe "unlockvar " . depth . " l" + endif + let ps = islocked("l").islocked("l[1]").islocked("l[1][1]").islocked("l[1][1][0]").'-'.islocked("l[2]").islocked("l[2]['6']").islocked("l[2]['6'][7]") + call assert_equal(expected[depth][u][0], ps) + let ps = '' + try + unlet l[2]['6'][7] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + unlet l[2][6] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + unlet l[2] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + unlet l[1][1][0] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + unlet l[1][1] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + unlet l[1] + let ps .= 'p' + catch + let ps .= 'F' + endtry + try + unlet l + let ps .= 'p' + catch + let ps .= 'F' + endtry + call assert_equal(expected[depth][u][1], ps) + endfor + endfor +endfunc + +" Locked variables and :unlet or list / dict functions + +" No :unlet after lock on dict: +func Test_dict_lock_unlet() + unlet! d + let d = {'a': 99, 'b': 100} + lockvar 1 d + call assert_fails('unlet d.a', 'E741') +endfunc + +" unlet after lock on dict item +func Test_dict_item_lock_unlet() + unlet! d + let d = {'a': 99, 'b': 100} + lockvar d.a + unlet d.a + call assert_equal({'b' : 100}, d) +endfunc + +" filter() after lock on dict item +func Test_dict_lock_filter() + unlet! d + let d = {'a': 99, 'b': 100} + lockvar d.a + call filter(d, 'v:key != "a"') + call assert_equal({'b' : 100}, d) +endfunc + +" map() after lock on dict +func Test_dict_lock_map() + unlet! d + let d = {'a': 99, 'b': 100} + lockvar 1 d + call map(d, 'v:val + 200') + call assert_equal({'a' : 299, 'b' : 300}, d) +endfunc + +" No extend() after lock on dict item +func Test_dict_lock_extend() + unlet! d + let d = {'a': 99, 'b': 100} + lockvar d.a + call assert_fails("call extend(d, {'a' : 123})", 'E741') + call assert_equal({'a': 99, 'b': 100}, d) +endfunc + +" No remove() of write-protected scope-level variable +func! Tfunc(this_is_a_long_parameter_name) + call assert_fails("call remove(a:, 'this_is_a_long_parameter_name')", 'E795') +endfun +func Test_dict_scope_var_remove() + call Tfunc('testval') +endfunc + +" No extend() of write-protected scope-level variable +func! Tfunc(this_is_a_long_parameter_name) + call assert_fails("call extend(a:, {'this_is_a_long_parameter_name': 1234})", 'E742') +endfunc +func Test_dict_scope_var_extend() + call Tfunc('testval') +endfunc + +" No :unlet of variable in locked scope +func Test_lock_var_unlet() + let b:testvar = 123 + lockvar 1 b: + call assert_fails('unlet b:testvar', 'E741:') + unlockvar 1 b: + unlet! b:testvar +endfunc + +" No :let += of locked list variable +func Test_let_lock_list() + let l = ['a', 'b', 3] + lockvar 1 l + call assert_fails("let l += ['x']", 'E741:') + call assert_equal(['a', 'b', 3], l) + + unlet l + let l = [1, 2, 3, 4] + lockvar! l + call assert_equal([1, 2, 3, 4], l) + unlockvar l[1] + call assert_fails('unlet l[0:1]', 'E741:') + call assert_equal([1, 2, 3, 4], l) + call assert_fails('unlet l[1:2]', 'E741:') + call assert_equal([1, 2, 3, 4], l) + unlockvar l[1] + call assert_fails('let l[0:1] = [0, 1]', 'E741:') + call assert_equal([1, 2, 3, 4], l) + call assert_fails('let l[1:2] = [0, 1]', 'E741:') + call assert_equal([1, 2, 3, 4], l) + unlet l +endfunc + +" lockvar/islocked() triggering script autoloading +func Test_lockvar_script_autoload() + let old_rtp = &rtp + set rtp+=./sautest + lockvar g:footest#x + unlockvar g:footest#x + call assert_equal(-1, islocked('g:footest#x')) + call assert_equal(0, exists('g:footest#x')) + call assert_equal(1, g:footest#x) + let &rtp = old_rtp +endfunc + +" a:000 function argument test +func s:arg_list_test(...) + call assert_fails('let a:000 = [1, 2]', 'E46:') + call assert_fails('let a:000[0] = 9', 'E742:') + call assert_fails('let a:000[2] = [9, 10]', 'E742:') + call assert_fails('let a:000[3] = {9 : 10}', 'E742:') + + " now the tests that should pass + let a:000[2][1] = 9 + call extend(a:000[2], [5, 6]) + let a:000[3][5] = 8 + let a:000[3]['a'] = 12 + call assert_equal([1, 2, [3, 9, 5, 6], {'a': 12, '5': 8}], a:000) +endfunc + +func Test_func_arg_list() + call s:arg_list_test(1, 2, [3, 4], {5: 6}) +endfunc + +" Tests for reverse(), sort(), uniq() +func Test_reverse_sort_uniq() + let l = ['-0', 'A11', 2, 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5] + call assert_equal(['-0', 'A11', 2, 'xaaa', 4, 'foo', 'foo6', 'foo', [0, 1, 2], 'x8', [0, 1, 2], 1.5], uniq(copy(l))) + call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(l)) + call assert_equal([1.5, [0, 1, 2], 'x8', [0, 1, 2], 'foo', 'foo6', 'foo', 4, 'xaaa', 2, 2, 'A11', '-0'], reverse(reverse(l))) + call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(l)) + call assert_equal([[0, 1, 2], [0, 1, 2], 4, 2, 2, 1.5, 'xaaa', 'x8', 'foo6', 'foo', 'foo', 'A11', '-0'], reverse(sort(l))) + call assert_equal(['-0', 'A11', 'foo', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 2, 4, [0, 1, 2], [0, 1, 2]], sort(reverse(sort(l)))) + call assert_equal(['-0', 'A11', 'foo', 'foo6', 'x8', 'xaaa', 1.5, 2, 4, [0, 1, 2]], uniq(sort(l))) + + let l=[7, 9, 'one', 18, 12, 22, 'two', 10.0e-16, -1, 'three', 0xff, 0.22, 'four'] + call assert_equal([-1, 'one', 'two', 'three', 'four', 1.0e-15, 0.22, 7, 9, 12, 18, 22, 255], sort(copy(l), 'n')) + + let l=[7, 9, 18, 12, 22, 10.0e-16, -1, 0xff, 0, -0, 0.22, 'bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', {}, []] + call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 1)) + call assert_equal(['bar', 'BAR', 'Bar', 'Foo', 'FOO', 'foo', 'FOOBAR', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l), 'i')) + call assert_equal(['BAR', 'Bar', 'FOO', 'FOOBAR', 'Foo', 'bar', 'foo', -1, 0, 0, 0.22, 1.0e-15, 12, 18, 22, 255, 7, 9, [], {}], sort(copy(l))) +endfunc + +" splitting a string to a List +func Test_str_split() + call assert_equal(['aa', 'bb'], split(' aa bb ')) + call assert_equal(['aa', 'bb'], split(' aa bb ', '\W\+', 0)) + call assert_equal(['', 'aa', 'bb', ''], split(' aa bb ', '\W\+', 1)) + call assert_equal(['', '', 'aa', '', 'bb', '', ''], split(' aa bb ', '\W', 1)) + call assert_equal(['aa', '', 'bb'], split(':aa::bb:', ':', 0)) + call assert_equal(['', 'aa', '', 'bb', ''], split(':aa::bb:', ':', 1)) + call assert_equal(['aa', '', 'bb', 'cc', ''], split('aa,,bb, cc,', ',\s*', 1)) + call assert_equal(['a', 'b', 'c'], split('abc', '\zs')) + call assert_equal(['', 'a', '', 'b', '', 'c', ''], split('abc', '\zs', 1)) +endfunc + +" compare recursively linked list and dict +func Test_listdict_compare() + let l = [1, 2, 3, 4] + let d = {'1': 1, '2': l, '3': 3} + let l[1] = d + call assert_true(l == l) + call assert_true(d == d) + call assert_false(l != deepcopy(l)) + call assert_false(d != deepcopy(d)) +endfunc + + " compare complex recursively linked list and dict +func Test_listdict_compare_complex() + let l = [] + call add(l, l) + let dict4 = {"l": l} + call add(dict4.l, dict4) + let lcopy = deepcopy(l) + let dict4copy = deepcopy(dict4) + call assert_true(l == lcopy) + call assert_true(dict4 == dict4copy) +endfunc + +func Test_listdict_extend() + " Pass the same List to extend() + let l = [1, 2, 3, 4, 5] + call extend(l, l) + call assert_equal([1, 2, 3, 4, 5, 1, 2, 3, 4, 5], l) + + " Pass the same Dict to extend() + let d = { 'a': {'b': 'B'}} + call extend(d, d) + call assert_equal({'a': {'b': 'B'}}, d) + + " Pass the same Dict to extend() with "error" + call assert_fails("call extend(d, d, 'error')", 'E737:') + call assert_equal({'a': {'b': 'B'}}, d) +endfunc diff --git a/src/nvim/testdir/test_listlbr.vim b/src/nvim/testdir/test_listlbr.vim index 7856ee82ab..d28dbc444c 100644 --- a/src/nvim/testdir/test_listlbr.vim +++ b/src/nvim/testdir/test_listlbr.vim @@ -1,5 +1,8 @@ " Test for linebreak and list option (non-utf8) +" Nvim does not allow setting 'encoding', so skip this test. +finish + set encoding=latin1 scriptencoding latin1 diff --git a/src/nvim/testdir/test_listlbr_utf8.vim b/src/nvim/testdir/test_listlbr_utf8.vim index 980d67d49d..56a4cc9b31 100644 --- a/src/nvim/testdir/test_listlbr_utf8.vim +++ b/src/nvim/testdir/test_listlbr_utf8.vim @@ -194,6 +194,33 @@ func Test_multibyte_sign_and_colorcolumn() call s:close_windows() endfunc +func Test_illegal_byte_and_breakat() + call s:test_windows("setl sbr= brk+=<") + vert resize 18 + call setline(1, repeat("\x80", 6)) + redraw! + let lines = s:screen_lines([1, 2], winwidth(0)) + let expect = [ +\ "<80><80><80><80><8", +\ "0><80> ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows('setl brk&vim') +endfunc + +func Test_multibyte_wrap_and_breakat() + call s:test_windows("setl sbr= brk+=>") + call setline(1, repeat('a', 17) . repeat('あ', 2)) + redraw! + let lines = s:screen_lines([1, 2], winwidth(0)) + let expect = [ +\ "aaaaaaaaaaaaaaaaaあ>", +\ "あ ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows('setl brk&vim') +endfunc + func Test_chinese_char_on_wrap_column() call s:test_windows("setl nolbr wrap sbr=") syntax off diff --git a/src/nvim/testdir/test_makeencoding.py b/src/nvim/testdir/test_makeencoding.py new file mode 100644 index 0000000000..041edadc0a --- /dev/null +++ b/src/nvim/testdir/test_makeencoding.py @@ -0,0 +1,67 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Test program for :make, :grep and :cgetfile. + +from __future__ import print_function, unicode_literals +import locale +import io +import sys + +def set_output_encoding(enc=None): + """Set the encoding of stdout and stderr + + arguments: + enc -- Encoding name. + If omitted, locale.getpreferredencoding() is used. + """ + if enc is None: + enc = locale.getpreferredencoding() + + def get_text_writer(fo, **kwargs): + kw = dict(kwargs) + kw.setdefault('errors', 'backslashreplace') # use \uXXXX style + kw.setdefault('closefd', False) + + if sys.version_info[0] < 3: + # Work around for Python 2.x + # New line conversion isn't needed here. Done in somewhere else. + 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 + else: + write(s) + try: + writer.flush() # needed on Windows + except IOError: + pass + writer.write = convwrite + else: + writer = io.open(fo.fileno(), mode='w', **kw) + return writer + + sys.stdout = get_text_writer(sys.stdout, encoding=enc) + sys.stderr = get_text_writer(sys.stderr, encoding=enc) + + +def main(): + enc = 'utf-8' + if len(sys.argv) > 1: + enc = sys.argv[1] + set_output_encoding(enc) + + message_tbl = { + 'utf-8': 'ÀÈÌÒÙ こんにちは 你好', + 'latin1': 'ÀÈÌÒÙ', + 'cp932': 'こんにちは', + 'cp936': '你好', + } + + print('Xfoobar.c(10) : %s (%s)' % (message_tbl[enc], enc)) + + +if __name__ == "__main__": + main() diff --git a/src/nvim/testdir/test_makeencoding.vim b/src/nvim/testdir/test_makeencoding.vim new file mode 100644 index 0000000000..a3d5538a47 --- /dev/null +++ b/src/nvim/testdir/test_makeencoding.vim @@ -0,0 +1,106 @@ +" Tests for 'makeencoding'. +if !has('multi_byte') + finish +endif + +source shared.vim + +let s:python = PythonProg() +if s:python == '' + " Can't run this test. + finish +endif + +let s:script = 'test_makeencoding.py' + +let s:message_tbl = { + \ 'utf-8': 'ÀÈÌÒÙ こんにちは 你好', + \ 'latin1': 'ÀÈÌÒÙ', + \ 'cp932': 'こんにちは', + \ 'cp936': '你好', + \} + + +" Tests for :cgetfile and :lgetfile. +func Test_getfile() + set errorfile=Xerror.txt + set errorformat=%f(%l)\ :\ %m + + " :cgetfile + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile + cgetfile + copen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + cclose + endfor + + " :lgetfile + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent !" . s:python . " " . s:script . " " . enc . " > " . &errorfile + lgetfile + lopen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + lclose + endfor + + call delete(&errorfile) +endfunc + + +" Tests for :grep and :lgrep. +func Test_grep() + let &grepprg = s:python + set grepformat=%f(%l)\ :\ %m + + " :grep + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent grep! " . s:script . " " . enc + copen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + cclose + endfor + + " :lgrep + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent lgrep! " . s:script . " " . enc + lopen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + lclose + endfor +endfunc + + +" Tests for :make and :lmake. +func Test_make() + let &makeprg = s:python + set errorformat=%f(%l)\ :\ %m + + " :make + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent make! " . s:script . " " . enc + copen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + cclose + endfor + + " :lmake + for enc in keys(s:message_tbl) + let &makeencoding = enc + exec "silent lmake! " . s:script . " " . enc + lopen + call assert_equal("Xfoobar.c|10| " . s:message_tbl[enc] . " (" . enc . ")", + \ getline('.')) + lclose + endfor +endfunc diff --git a/src/nvim/testdir/test_mksession_utf8.vim b/src/nvim/testdir/test_mksession_utf8.vim index c05a1d3b6d..8ffbba2a1c 100644 --- a/src/nvim/testdir/test_mksession_utf8.vim +++ b/src/nvim/testdir/test_mksession_utf8.vim @@ -99,6 +99,7 @@ func Test_mksession_utf8() call delete('test_mks.out') call delete(tmpfile) let &wrap = wrap_save + set sessionoptions& splitbelow& fileencoding& endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_normal.vim b/src/nvim/testdir/test_normal.vim index 4747d5704d..1d15c7f83d 100644 --- a/src/nvim/testdir/test_normal.vim +++ b/src/nvim/testdir/test_normal.vim @@ -1,5 +1,7 @@ " Test for various Normal mode commands +source shared.vim + func! Setup_NewWindow() 10new call setline(1, range(1,100)) @@ -1069,7 +1071,6 @@ func! Test_normal18_z_fold() endfunc func! Test_normal19_z_spell() - throw "skipped: Nvim 'spell' requires download" if !has("spell") || !has('syntax') return endif @@ -1120,6 +1121,7 @@ func! Test_normal19_z_spell() let a=execute('unsilent norm! V$zG') call assert_match("Word '2 goood' added to .*", a) let fname=matchstr(a, 'to\s\+\zs\f\+$') + let fname=Fix_truncated_tmpfile(fname) let cnt=readfile(fname) call assert_equal('2 goood', cnt[0]) @@ -2335,3 +2337,45 @@ func! Test_normal54_Ctrl_bsl() " clean up bw! endfunc + +" Test for the gr (virtual replace) command +" Test for the bug fixed by 7.4.387 +func Test_gr_command() + enew! + let save_cpo = &cpo + call append(0, ['First line', 'Second line', 'Third line']) + exe "normal i\<C-G>u" + call cursor(2, 1) + set cpo-=X + normal 4gro + call assert_equal('oooond line', getline(2)) + undo + set cpo+=X + normal 4gro + call assert_equal('ooooecond line', getline(2)) + let &cpo = save_cpo + enew! +endfunc + +" When splitting a window the changelist position is wrong. +" Test the changelist position after splitting a window. +" Test for the bug fixed by 7.4.386 +func Test_changelist() + let save_ul = &ul + enew! + call append('$', ['1', '2']) + exe "normal i\<C-G>u" + exe "normal Gkylpa\<C-G>u" + set ul=100 + exe "normal Gylpa\<C-G>u" + set ul=100 + normal gg + vsplit + normal g; + call assert_equal([3, 2], [line('.'), col('.')]) + normal g; + call assert_equal([2, 2], [line('.'), col('.')]) + call assert_fails('normal g;', 'E662:') + %bwipe! + let &ul = save_ul +endfunc diff --git a/src/nvim/testdir/test_number.vim b/src/nvim/testdir/test_number.vim new file mode 100644 index 0000000000..59debcea0d --- /dev/null +++ b/src/nvim/testdir/test_number.vim @@ -0,0 +1,254 @@ +" Test for 'number' and 'relativenumber' + +source view_util.vim + +func! s:screen_lines(start, end) abort + return ScreenLines([a:start, a:end], 8) +endfunc + +func! s:compare_lines(expect, actual) + call assert_equal(a:expect, a:actual) +endfunc + +func! s:test_windows(h, w) abort + call NewWindow(a:h, a:w) +endfunc + +func! s:close_windows() abort + call CloseWindow() +endfunc + +func! s:validate_cursor() abort + " update skipcol. + " wincol(): + " f_wincol + " -> validate_cursor + " -> curs_columns + call wincol() +endfunc + +func Test_set_options() + set nu rnu + call assert_equal(1, &nu) + call assert_equal(1, &rnu) + + call s:test_windows(10, 20) + call assert_equal(1, &nu) + call assert_equal(1, &rnu) + call s:close_windows() + + set nu& rnu& +endfunc + +func Test_set_global_and_local() + " setlocal must NOT reset the other global value + set nonu nornu + setglobal nu + setlocal rnu + call assert_equal(1, &g:nu) + + set nonu nornu + setglobal rnu + setlocal nu + call assert_equal(1, &g:rnu) + + " setglobal MUST reset the other global value + set nonu nornu + setglobal nu + setglobal rnu + call assert_equal(1, &g:nu) + + set nonu nornu + setglobal rnu + setglobal nu + call assert_equal(1, &g:rnu) + + " set MUST reset the other global value + set nonu nornu + set nu + set rnu + call assert_equal(1, &g:nu) + + set nonu nornu + set rnu + set nu + call assert_equal(1, &g:rnu) + + set nu& rnu& +endfunc + +func Test_number() + call s:test_windows(10, 20) + call setline(1, ["abcdefghij", "klmnopqrst", "uvwxyzABCD", "EFGHIJKLMN", "OPQRSTUVWX", "YZ"]) + setl number + let lines = s:screen_lines(1, 4) + let expect = [ +\ " 1 abcd", +\ " 2 klmn", +\ " 3 uvwx", +\ " 4 EFGH", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc + +func Test_relativenumber() + call s:test_windows(10, 20) + call setline(1, ["abcdefghij", "klmnopqrst", "uvwxyzABCD", "EFGHIJKLMN", "OPQRSTUVWX", "YZ"]) + 3 + setl relativenumber + let lines = s:screen_lines(1, 6) + let expect = [ +\ " 2 abcd", +\ " 1 klmn", +\ " 0 uvwx", +\ " 1 EFGH", +\ " 2 OPQR", +\ " 3 YZ ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc + +func Test_number_with_relativenumber() + call s:test_windows(10, 20) + call setline(1, ["abcdefghij", "klmnopqrst", "uvwxyzABCD", "EFGHIJKLMN", "OPQRSTUVWX", "YZ"]) + 4 + setl number relativenumber + let lines = s:screen_lines(1, 6) + let expect = [ +\ " 3 abcd", +\ " 2 klmn", +\ " 1 uvwx", +\ "4 EFGH", +\ " 1 OPQR", +\ " 2 YZ ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc + +func Test_number_with_linewrap1() + call s:test_windows(3, 20) + normal! 61ia + setl number wrap + call s:validate_cursor() + let lines = s:screen_lines(1, 3) + let expect = [ +\ "--1 aaaa", +\ " aaaa", +\ " aaaa", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc + +" Pending: https://groups.google.com/forum/#!topic/vim_dev/tzNKP7EDWYI +func XTest_number_with_linewrap2() + call s:test_windows(3, 20) + normal! 61ia + setl number wrap + call s:validate_cursor() + 0 + call s:validate_cursor() + let lines = s:screen_lines(1, 3) + let expect = [ +\ " 1 aaaa", +\ " aaaa", +\ " aaaa", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc + +" Pending: https://groups.google.com/forum/#!topic/vim_dev/tzNKP7EDWYI +func XTest_number_with_linewrap3() + call s:test_windows(4, 20) + normal! 81ia + setl number wrap + call s:validate_cursor() + setl nonumber + call s:validate_cursor() + let lines = s:screen_lines(1, 4) + let expect = [ +\ "aaaaaaaa", +\ "aaaaaaaa", +\ "aaaaaaaa", +\ "a ", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc + +func Test_numberwidth() + call s:test_windows(10, 20) + call setline(1, repeat(['aaaa'], 10)) + setl number numberwidth=6 + let lines = s:screen_lines(1, 3) + let expect = [ +\ " 1 aa", +\ " 2 aa", +\ " 3 aa", +\ ] + call s:compare_lines(expect, lines) + + set relativenumber + let lines = s:screen_lines(1, 3) + let expect = [ +\ "1 aa", +\ " 1 aa", +\ " 2 aa", +\ ] + call s:compare_lines(expect, lines) + + set nonumber + let lines = s:screen_lines(1, 3) + let expect = [ +\ " 0 aa", +\ " 1 aa", +\ " 2 aa", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc + +func Test_numberwidth_adjusted() + call s:test_windows(10, 20) + call setline(1, repeat(['aaaa'], 10000)) + setl number numberwidth=4 + let lines = s:screen_lines(1, 3) + let expect = [ +\ " 1 aa", +\ " 2 aa", +\ " 3 aa", +\ ] + call s:compare_lines(expect, lines) + + $ + let lines = s:screen_lines(8, 10) + let expect = [ +\ " 9998 aa", +\ " 9999 aa", +\ "10000 aa", +\ ] + call s:compare_lines(expect, lines) + + setl relativenumber + let lines = s:screen_lines(8, 10) + let expect = [ +\ " 2 aa", +\ " 1 aa", +\ "10000 aa", +\ ] + call s:compare_lines(expect, lines) + + setl nonumber + let lines = s:screen_lines(8, 10) + let expect = [ +\ " 2 aaaa", +\ " 1 aaaa", +\ " 0 aaaa", +\ ] + call s:compare_lines(expect, lines) + call s:close_windows() +endfunc diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim index 08ee00e352..a15d15213a 100644 --- a/src/nvim/testdir/test_options.vim +++ b/src/nvim/testdir/test_options.vim @@ -119,12 +119,109 @@ func Check_dir_option(name) call assert_fails("set " . a:name . "=/not.*there", "E474:") endfunc +func Test_cinkeys() + " This used to cause invalid memory access + set cindent cinkeys=0 + norm a + set cindent& cinkeys& +endfunc + func Test_dictionary() call Check_dir_option('dictionary') endfunc func Test_thesaurus() call Check_dir_option('thesaurus') +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', @:) + + " Expand boolan options. When doing :set no<Tab> + " vim displays the options names without "no" but completion uses "no...". + call feedkeys(":set nodi\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set nodiff digraph', @:) + + call feedkeys(":set invdi\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set invdiff digraph', @:) + + " Expand abbreviation of options. + call feedkeys(":set ts\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set tabstop thesaurus', @:) + + " Expand current value + call feedkeys(":set fileencodings=\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set fileencodings=ucs-bom,utf-8,default,latin1', @:) + + call feedkeys(":set fileencodings:\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set fileencodings:ucs-bom,utf-8,default,latin1', @:) + + " Expand directories. + call feedkeys(":set cdpath=./\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('./samples/ ', @:) + call assert_notmatch('./small.vim ', @:) + + " Expand files and directories. + call feedkeys(":set tags=./\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('./samples/ ./sautest/ ./setup.vim ./shared.vim', @:) + + call feedkeys(":set tags=./\\\\ dif\<C-A>\<C-B>\"\<CR>", 'tx') + call assert_equal('"set tags=./\\ diff diffexpr diffopt', @:) +endfunc + +func Test_set_errors() + call assert_fails('set scroll=-1', 'E49:') + 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 colorcolumn=-a') + call assert_fails('set colorcolumn=a') + call assert_fails('set colorcolumn=1,') + call assert_fails('set cmdheight=-1', 'E487:') + call assert_fails('set cmdwinheight=-1', 'E487:') + if has('conceal') + call assert_fails('set conceallevel=-1', 'E487:') + call assert_fails('set conceallevel=4', 'E474:') + endif + call assert_fails('set helpheight=-1', 'E487:') + call assert_fails('set history=-1', 'E487:') + call assert_fails('set report=-1', 'E487:') + call assert_fails('set shiftwidth=-1', 'E487:') + call assert_fails('set sidescroll=-1', 'E487:') + call assert_fails('set tabstop=-1', 'E487:') + call assert_fails('set textwidth=-1', 'E487:') + call assert_fails('set timeoutlen=-1', 'E487:') + call assert_fails('set updatecount=-1', 'E487:') + call assert_fails('set updatetime=-1', 'E487:') + call assert_fails('set winheight=-1', 'E487:') + call assert_fails('set tabstop!', 'E488:') + call assert_fails('set xxx', 'E518:') + call assert_fails('set beautify?', 'E518:') + call assert_fails('set undolevels=x', 'E521:') + call assert_fails('set tabstop=', 'E521:') + call assert_fails('set comments=-', 'E524:') + call assert_fails('set comments=a', 'E525:') + call assert_fails('set foldmarker=x', 'E536:') + call assert_fails('set commentstring=x', 'E537:') + call assert_fails('set complete=x', 'E539:') + call assert_fails('set statusline=%{', 'E540:') + call assert_fails('set statusline=' . repeat("%p", 81), 'E541:') + call assert_fails('set statusline=%(', 'E542:') + if has('cursorshape') + " This invalid value for 'guicursor' used to cause Vim to crash. + call assert_fails('set guicursor=i-ci,r-cr:h', 'E545:') + call assert_fails('set guicursor=i-ci', 'E545:') + call assert_fails('set guicursor=x', 'E545:') + call assert_fails('set guicursor=r-cr:horx', 'E548:') + call assert_fails('set guicursor=r-cr:hor0', 'E549:') + endif + call assert_fails('set backupext=~ patchmode=~', 'E589:') + call assert_fails('set winminheight=10 winheight=9', 'E591:') + call assert_fails('set winminwidth=10 winwidth=9', 'E592:') + call assert_fails("set showbreak=\x01", 'E595:') + call assert_fails('set t_foo=', 'E846:') endfunc func Test_complete() diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim index e1ba142d1c..f5bdd717dd 100644 --- a/src/nvim/testdir/test_popup.vim +++ b/src/nvim/testdir/test_popup.vim @@ -570,4 +570,42 @@ func Test_completion_clear_candidate_list() endfunc +func Test_popup_and_preview_autocommand() + " This used to crash Vim + if !has('python') + return + endif + let h = winheight(0) + if h < 15 + return + endif + new + augroup MyBufAdd + au! + au BufAdd * nested tab sball + augroup END + set omnifunc=pythoncomplete#Complete + call setline(1, 'import os') + " make the line long + call setline(2, ' os.') + $ + call feedkeys("A\<C-X>\<C-O>\<C-N>\<C-N>\<C-N>\<enter>\<esc>", 'tx') + call assert_equal("import os", getline(1)) + call assert_match(' os.\(EX_IOERR\|O_CREAT\)$', getline(2)) + call assert_equal(1, winnr('$')) + " previewwindow option is not set + call assert_equal(0, &previewwindow) + norm! gt + call assert_equal(0, &previewwindow) + norm! gT + call assert_equal(12, tabpagenr('$')) + tabonly + pclose + augroup MyBufAdd + au! + augroup END + augroup! MyBufAdd + bw! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim new file mode 100644 index 0000000000..38c812bc9c --- /dev/null +++ b/src/nvim/testdir/test_put.vim @@ -0,0 +1,36 @@ + +func Test_put_block() + if !has('multi_byte') + return + endif + new + call feedkeys("i\<C-V>u2500\<CR>x\<ESC>", 'x') + call feedkeys("\<C-V>y", 'x') + call feedkeys("gg0p", 'x') + call assert_equal("\u2500x", getline(1)) + bwipe! +endfunc + +func Test_put_char_block() + new + call setline(1, ['Line 1', 'Line 2']) + f Xfile_put + " visually select both lines and put the cursor at the top of the visual + " selection and then put the buffer name over it + exe "norm! G0\<c-v>ke\"%p" + call assert_equal(['Xfile_put 1', 'Xfile_put 2'], getline(1,2)) + bw! +endfunc + +func Test_put_char_block2() + new + let a = [ getreg('a'), getregtype('a') ] + call setreg('a', ' one ', 'v') + call setline(1, ['Line 1', '', 'Line 3', '']) + " visually select the first 3 lines and put register a over it + exe "norm! ggl\<c-v>2j2l\"ap" + call assert_equal(['L one 1', '', 'L one 3', ''], getline(1,4)) + " clean up + bw! + call setreg('a', a[0], a[1]) +endfunc diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim index aff5fc2eed..dd177fd633 100644 --- a/src/nvim/testdir/test_quickfix.vim +++ b/src/nvim/testdir/test_quickfix.vim @@ -24,19 +24,21 @@ func s:setup_commands(cchar) command! -nargs=* Xgetbuffer <mods>cgetbuffer <args> command! -nargs=* Xaddbuffer <mods>caddbuffer <args> command! -nargs=* Xrewind <mods>crewind <args> - command! -nargs=* -bang Xnext <mods>cnext<bang> <args> - command! -nargs=* -bang Xprev <mods>cprev<bang> <args> + command! -count -nargs=* -bang Xnext <mods><count>cnext<bang> <args> + command! -count -nargs=* -bang Xprev <mods><count>cprev<bang> <args> command! -nargs=* -bang Xfirst <mods>cfirst<bang> <args> command! -nargs=* -bang Xlast <mods>clast<bang> <args> command! -nargs=* -bang Xnfile <mods>cnfile<bang> <args> command! -nargs=* -bang Xpfile <mods>cpfile<bang> <args> command! -nargs=* Xexpr <mods>cexpr <args> - command! -nargs=* Xvimgrep <mods>vimgrep <args> + command! -range -nargs=* Xvimgrep <mods><count>vimgrep <args> + command! -nargs=* Xvimgrepadd <mods>vimgrepadd <args> command! -nargs=* Xgrep <mods> grep <args> command! -nargs=* Xgrepadd <mods> grepadd <args> command! -nargs=* Xhelpgrep helpgrep <args> let g:Xgetlist = function('getqflist') let g:Xsetlist = function('setqflist') + call setqflist([], 'f') else command! -nargs=* -bang Xlist <mods>llist<bang> <args> command! -nargs=* Xgetexpr <mods>lgetexpr <args> @@ -54,19 +56,21 @@ func s:setup_commands(cchar) command! -nargs=* Xgetbuffer <mods>lgetbuffer <args> command! -nargs=* Xaddbuffer <mods>laddbuffer <args> command! -nargs=* Xrewind <mods>lrewind <args> - command! -nargs=* -bang Xnext <mods>lnext<bang> <args> - command! -nargs=* -bang Xprev <mods>lprev<bang> <args> + command! -count -nargs=* -bang Xnext <mods><count>lnext<bang> <args> + command! -count -nargs=* -bang Xprev <mods><count>lprev<bang> <args> command! -nargs=* -bang Xfirst <mods>lfirst<bang> <args> command! -nargs=* -bang Xlast <mods>llast<bang> <args> command! -nargs=* -bang Xnfile <mods>lnfile<bang> <args> command! -nargs=* -bang Xpfile <mods>lpfile<bang> <args> command! -nargs=* Xexpr <mods>lexpr <args> - command! -nargs=* Xvimgrep <mods>lvimgrep <args> + command! -range -nargs=* Xvimgrep <mods><count>lvimgrep <args> + command! -nargs=* Xvimgrepadd <mods>lvimgrepadd <args> command! -nargs=* Xgrep <mods> lgrep <args> command! -nargs=* Xgrepadd <mods> lgrepadd <args> command! -nargs=* Xhelpgrep lhelpgrep <args> let g:Xgetlist = function('getloclist', [0]) let g:Xsetlist = function('setloclist', [0]) + call setloclist(0, [], 'f') endif endfunc @@ -74,6 +78,9 @@ endfunc func XlistTests(cchar) call s:setup_commands(a:cchar) + if a:cchar == 'l' + call assert_fails('llist', 'E776:') + endif " With an empty list, command should return error Xgetexpr [] silent! Xlist @@ -85,49 +92,52 @@ func XlistTests(cchar) \ 'non-error 3', 'Xtestfile3:3:1:Line3'] " List only valid entries - redir => result - Xlist - redir END - let l = split(result, "\n") + let l = split(execute('Xlist', ''), "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1', \ ' 4 Xtestfile2:2 col 2: Line2', \ ' 6 Xtestfile3:3 col 1: Line3'], l) " List all the entries - redir => result - Xlist! - redir END - let l = split(result, "\n") + let l = split(execute('Xlist!', ''), "\n") call assert_equal([' 1: non-error 1', ' 2 Xtestfile1:1 col 3: Line1', \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2', \ ' 5: non-error 3', ' 6 Xtestfile3:3 col 1: Line3'], l) " List a range of errors - redir => result - Xlist 3,6 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist 3,6', ''), "\n") call assert_equal([' 4 Xtestfile2:2 col 2: Line2', \ ' 6 Xtestfile3:3 col 1: Line3'], l) - redir => result - Xlist! 3,4 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist! 3,4', ''), "\n") call assert_equal([' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) - redir => result - Xlist -6,-4 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist -6,-4', ''), "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1'], l) - redir => result - Xlist! -5,-3 - redir END - let l = split(result, "\n") + let l = split(execute('Xlist! -5,-3', ''), "\n") call assert_equal([' 2 Xtestfile1:1 col 3: Line1', \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) + + " Test for '+' + let l = split(execute('Xlist! +2', ''), "\n") + call assert_equal([' 2 Xtestfile1:1 col 3: Line1', + \ ' 3: non-error 2', ' 4 Xtestfile2:2 col 2: Line2'], l) + + " Different types of errors + call g:Xsetlist([{'lnum':10,'col':5,'type':'W', 'text':'Warning','nr':11}, + \ {'lnum':20,'col':10,'type':'e','text':'Error','nr':22}, + \ {'lnum':30,'col':15,'type':'i','text':'Info','nr':33}, + \ {'lnum':40,'col':20,'type':'x', 'text':'Other','nr':44}, + \ {'lnum':50,'col':25,'type':"\<C-A>",'text':'one','nr':55}]) + let l = split(execute('Xlist', ""), "\n") + call assert_equal([' 1:10 col 5 warning 11: Warning', + \ ' 2:20 col 10 error 22: Error', + \ ' 3:30 col 15 info 33: Info', + \ ' 4:40 col 20 x 44: Other', + \ ' 5:50 col 25 55: one'], l) + + " Error cases + call assert_fails('Xlist abc', 'E488:') endfunc func Test_clist() @@ -141,6 +151,9 @@ endfunc func XageTests(cchar) call s:setup_commands(a:cchar) + let list = [{'bufnr': bufnr('%'), 'lnum': 1}] + call g:Xsetlist(list) + " Jumping to a non existent list should return error silent! Xolder 99 call assert_true(v:errmsg ==# 'E380: At bottom of quickfix stack') @@ -174,11 +187,7 @@ func XageTests(cchar) endfunc func Test_cage() - let list = [{'bufnr': 1, 'lnum': 1}] - call setqflist(list) call XageTests('c') - - call setloclist(0, list) call XageTests('l') endfunc @@ -187,6 +196,11 @@ endfunc func XwindowTests(cchar) call s:setup_commands(a:cchar) + " Opening the location list window without any errors should fail + if a:cchar == 'l' + call assert_fails('lopen', 'E776:') + endif + " Create a list with no valid entries Xgetexpr ['non-error 1', 'non-error 2', 'non-error 3'] @@ -227,6 +241,19 @@ func XwindowTests(cchar) " Calling cwindow should close the quickfix window with no valid errors Xwindow call assert_true(winnr('$') == 1) + + if a:cchar == 'c' + " Opening the quickfix window in multiple tab pages should reuse the + " quickfix buffer + Xgetexpr ['Xtestfile1:1:3:Line1', 'Xtestfile2:2:2:Line2', + \ 'Xtestfile3:3:1:Line3'] + Xopen + let qfbufnum = bufnr('%') + tabnew + Xopen + call assert_equal(qfbufnum, bufnr('%')) + new | only | tabonly + endif endfunc func Test_cwindow() @@ -316,6 +343,23 @@ func XbufferTests(cchar) \ l[3].lnum == 750 && l[3].col == 25 && l[3].text ==# 'Line 750') enew! + " Check for invalid buffer + call assert_fails('Xbuffer 199', 'E474:') + + " Check for unloaded buffer + edit Xtestfile1 + let bnr = bufnr('%') + enew! + call assert_fails('Xbuffer ' . bnr, 'E681:') + + " Check for invalid range + " Using Xbuffer will not run the range check in the cbuffer/lbuffer + " commands. So directly call the commands. + if (a:cchar == 'c') + call assert_fails('900,999cbuffer', 'E16:') + else + call assert_fails('900,999lbuffer', 'E16:') + endif endfunc func Test_cbuffer() @@ -338,13 +382,22 @@ endfunc func Xtest_browse(cchar) call s:setup_commands(a:cchar) + " Jumping to first or next location list entry without any error should + " result in failure + if a:cchar == 'l' + call assert_fails('lfirst', 'E776:') + call assert_fails('lnext', 'E776:') + endif + call s:create_test_file('Xqftestfile1') call s:create_test_file('Xqftestfile2') Xgetexpr ['Xqftestfile1:5:Line5', \ 'Xqftestfile1:6:Line6', \ 'Xqftestfile2:10:Line10', - \ 'Xqftestfile2:11:Line11'] + \ 'Xqftestfile2:11:Line11', + \ 'RegularLine1', + \ 'RegularLine2'] Xfirst call assert_fails('Xprev', 'E553') @@ -356,6 +409,7 @@ func Xtest_browse(cchar) call assert_equal('Xqftestfile1', bufname('%')) call assert_equal(6, line('.')) Xlast + Xprev call assert_equal('Xqftestfile2', bufname('%')) call assert_equal(11, line('.')) call assert_fails('Xnext', 'E553') @@ -364,6 +418,16 @@ func Xtest_browse(cchar) call assert_equal('Xqftestfile1', bufname('%')) call assert_equal(5, line('.')) + 10Xnext + call assert_equal('Xqftestfile2', bufname('%')) + call assert_equal(11, line('.')) + 10Xprev + call assert_equal('Xqftestfile1', bufname('%')) + call assert_equal(5, line('.')) + + Xexpr "" + call assert_fails('Xnext', 'E42:') + call delete('Xqftestfile1') call delete('Xqftestfile2') endfunc @@ -383,8 +447,32 @@ func s:test_xhelpgrep(cchar) let title_text = ':lhelpgrep quickfix' endif call assert_true(w:quickfix_title =~ title_text, w:quickfix_title) + + " Jumping to a help topic should open the help window + only + Xnext + call assert_true(&buftype == 'help') + call assert_true(winnr('$') == 2) + " Jumping to the next match should reuse the help window + Xnext + call assert_true(&buftype == 'help') + call assert_true(winnr() == 1) + call assert_true(winnr('$') == 2) + " Jumping to the next match from the quickfix window should reuse the help + " window + Xopen + Xnext + call assert_true(&buftype == 'help') + call assert_true(winnr() == 1) + call assert_true(winnr('$') == 2) + " This wipes out the buffer, make sure that doesn't cause trouble. Xclose + + new | only + + " Search for non existing help string + call assert_fails('Xhelpgrep a1b2c3', 'E480:') endfunc func Test_helpgrep() @@ -521,10 +609,7 @@ func Test_locationlist() lrewind enew lopen - lnext - lnext - lnext - lnext + 4lnext vert split wincmd L lopen @@ -578,7 +663,7 @@ func Test_locationlist() wincmd n | only augroup! testgroup - endfunc +endfunc func Test_locationlist_curwin_was_closed() augroup testgroup @@ -597,7 +682,7 @@ func Test_locationlist_curwin_was_closed() call assert_fails('lrewind', 'E924:') augroup! testgroup - endfunc +endfunc func Test_locationlist_cross_tab_jump() call writefile(['loclistfoo'], 'loclistfoo') @@ -734,7 +819,7 @@ func Test_efm1() call delete('Xerrorfile1') call delete('Xerrorfile2') call delete('Xtestfile') - endfunc +endfunc " Test for quickfix directory stack support func s:dir_stack_tests(cchar) @@ -893,30 +978,44 @@ func Test_efm2() call assert_equal(l[0].pattern, '^\VLine search text\$') call assert_equal(l[0].lnum, 0) + let l = split(execute('clist', ''), "\n") + call assert_equal([' 1 Xtestfile:^\VLine search text\$: '], l) + " Test for %P, %Q and %t format specifiers let lines=["[Xtestfile1]", \ "(1,17) error: ';' missing", \ "(21,2) warning: variable 'z' not defined", \ "(67,3) error: end of file found before string ended", + \ "--", \ "", \ "[Xtestfile2]", + \ "--", \ "", \ "[Xtestfile3]", \ "NEW compiler v1.1", \ "(2,2) warning: variable 'x' not defined", - \ "(67,3) warning: 's' already defined" + \ "(67,3) warning: 's' already defined", + \ "--" \] - set efm=%+P[%f],(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%-Q + set efm=%+P[%f]%r,(%l\\,%c)%*[\ ]%t%*[^:]:\ %m,%+Q--%r + " To exercise the push/pop file functionality in quickfix, the test files + " need to be created. + call writefile(['Line1'], 'Xtestfile1') + call writefile(['Line2'], 'Xtestfile2') + call writefile(['Line3'], 'Xtestfile3') cexpr "" for l in lines caddexpr l endfor let l = getqflist() - call assert_equal(9, len(l)) + call assert_equal(12, len(l)) call assert_equal(21, l[2].lnum) call assert_equal(2, l[2].col) call assert_equal('w', l[2].type) call assert_equal('e', l[3].type) + call delete('Xtestfile1') + call delete('Xtestfile2') + call delete('Xtestfile3') " Tests for %E, %C and %Z format specifiers let lines = ["Error 275", @@ -968,6 +1067,25 @@ func Test_efm2() call assert_equal(1, l[4].valid) call assert_equal('unittests/dbfacadeTest.py', bufname(l[4].bufnr)) + " The following sequence of commands used to crash Vim + set efm=%W%m + cgetexpr ['msg1'] + let l = getqflist() + call assert_equal(1, len(l), string(l)) + call assert_equal('msg1', l[0].text) + set efm=%C%m + lexpr 'msg2' + let l = getloclist(0) + call assert_equal(1, len(l), string(l)) + call assert_equal('msg2', l[0].text) + lopen + call setqflist([], 'r') + caddbuf + let l = getqflist() + call assert_equal(1, len(l), string(l)) + call assert_equal('|| msg2', l[0].text) + + new | only let &efm = save_efm endfunc @@ -1064,6 +1182,32 @@ func SetXlistTests(cchar, bnum) call g:Xsetlist([]) let l = g:Xgetlist() call assert_equal(0, len(l)) + + " Tests for setting the 'valid' flag + call g:Xsetlist([{'bufnr':a:bnum, 'lnum':4, 'valid':0}]) + Xwindow + call assert_equal(1, winnr('$')) + let l = g:Xgetlist() + call g:Xsetlist(l) + call assert_equal(0, g:Xgetlist()[0].valid) + call g:Xsetlist([{'text':'Text1', 'valid':1}]) + Xwindow + call assert_equal(2, winnr('$')) + Xclose + let save_efm = &efm + set efm=%m + Xgetexpr 'TestMessage' + let l = g:Xgetlist() + call g:Xsetlist(l) + call assert_equal(1, g:Xgetlist()[0].valid) + let &efm = save_efm + + " Error cases: + " Refer to a non-existing buffer and pass a non-dictionary type + call assert_fails("call g:Xsetlist([{'bufnr':998, 'lnum':4}," . + \ " {'bufnr':999, 'lnum':5}])", 'E92:') + call g:Xsetlist([[1, 2,3]]) + call assert_equal(0, len(g:Xgetlist())) endfunc func Test_setqflist() @@ -1082,7 +1226,8 @@ func Xlist_empty_middle(cchar) call s:setup_commands(a:cchar) " create three quickfix lists - Xvimgrep Test_ test_quickfix.vim + let @/ = 'Test_' + Xvimgrep // test_quickfix.vim let testlen = len(g:Xgetlist()) call assert_true(testlen > 0) Xvimgrep empty test_quickfix.vim @@ -1290,18 +1435,18 @@ func Test_switchbuf() let winid = win_getid() cfirst | cnext call assert_equal(winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(winid, win_getid()) enew set switchbuf=useopen cfirst | cnext call assert_equal(file1_winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(file2_winid, win_getid()) - cnext | cnext + 2cnext call assert_equal(file2_winid, win_getid()) enew | only @@ -1311,9 +1456,9 @@ func Test_switchbuf() tabfirst cfirst | cnext call assert_equal(2, tabpagenr()) - cnext | cnext + 2cnext call assert_equal(3, tabpagenr()) - cnext | cnext + 2cnext call assert_equal(3, tabpagenr()) tabfirst | tabonly | enew @@ -1351,11 +1496,25 @@ func Test_switchbuf() call assert_equal(2, winnr('$')) call assert_equal(1, bufwinnr('Xqftestfile3')) + " If only quickfix window is open in the current tabpage, jumping to an + " entry with 'switchubf' set to 'usetab' should search in other tabpages. enew | only + set switchbuf=usetab + tabedit Xqftestfile1 + tabedit Xqftestfile2 + tabedit Xqftestfile3 + tabfirst + copen | only + clast + call assert_equal(4, tabpagenr()) + tabfirst | tabonly | enew | only call delete('Xqftestfile1') call delete('Xqftestfile2') call delete('Xqftestfile3') + set switchbuf&vim + + enew | only endfunc func Xadjust_qflnum(cchar) @@ -1468,12 +1627,17 @@ endfunc func XbottomTests(cchar) call s:setup_commands(a:cchar) - call g:Xsetlist([{'filename': 'foo', 'lnum': 42}]) + " Calling lbottom without any errors should fail + if a:cchar == 'l' + call assert_fails('lbottom', 'E776:') + endif + + call g:Xsetlist([{'filename': 'foo', 'lnum': 42}]) Xopen let wid = win_getid() call assert_equal(1, line('.')) wincmd w - call g:Xsetlist([{'filename': 'var', 'lnum': 24}], 'a') + call g:Xsetlist([{'filename': 'var', 'lnum': 24}], 'a') Xbottom call win_gotoid(wid) call assert_equal(2, line('.')) @@ -1489,10 +1653,9 @@ endfunc func HistoryTest(cchar) call s:setup_commands(a:cchar) - call assert_fails(a:cchar . 'older 99', 'E380:') " clear all lists after the first one, then replace the first one. call g:Xsetlist([]) - Xolder + call assert_fails('Xolder 99', 'E380:') let entry = {'filename': 'foo', 'lnum': 42} call g:Xsetlist([entry], 'r') call g:Xsetlist([entry, entry]) @@ -1535,6 +1698,7 @@ func Xproperty_tests(cchar) call assert_fails('call g:Xsetlist([], "a", [])', 'E715:') " Set and get the title + call g:Xsetlist([]) Xopen wincmd p call g:Xsetlist([{'filename':'foo', 'lnum':27}]) @@ -1561,6 +1725,22 @@ func Xproperty_tests(cchar) call g:Xsetlist([], ' ', {'title' : 'N3'}) call assert_equal('N2', g:Xgetlist({'nr':2, 'title':1}).title) + " Changing the title of an earlier quickfix list + call g:Xsetlist([], ' ', {'title' : 'NewTitle', 'nr' : 2}) + call assert_equal('NewTitle', g:Xgetlist({'nr':2, 'title':1}).title) + + " Changing the title of an invalid quickfix list + call assert_equal(-1, g:Xsetlist([], ' ', + \ {'title' : 'SomeTitle', 'nr' : 99})) + call assert_equal(-1, g:Xsetlist([], ' ', + \ {'title' : 'SomeTitle', 'nr' : 'abc'})) + + if a:cchar == 'c' + copen + call assert_equal({'winid':win_getid()}, getqflist({'winid':1})) + cclose + endif + " Invalid arguments call assert_fails('call g:Xgetlist([])', 'E715') call assert_fails('call g:Xsetlist([], "a", [])', 'E715') @@ -1568,16 +1748,148 @@ func Xproperty_tests(cchar) call assert_equal(-1, s) call assert_equal({}, g:Xgetlist({'abc':1})) + call assert_equal({}, g:Xgetlist({'nr':99, 'title':1})) + call assert_equal({}, g:Xgetlist({'nr':[], 'title':1})) if a:cchar == 'l' - call assert_equal({}, getloclist(99, {'title': 1})) + call assert_equal({}, getloclist(99, {'title': 1})) endif - endfunc + + " Context related tests + call g:Xsetlist([], 'a', {'context':[1,2,3]}) + call test_garbagecollect_now() + let d = g:Xgetlist({'context':1}) + call assert_equal([1,2,3], d.context) + call g:Xsetlist([], 'a', {'context':{'color':'green'}}) + let d = g:Xgetlist({'context':1}) + call assert_equal({'color':'green'}, d.context) + call g:Xsetlist([], 'a', {'context':"Context info"}) + let d = g:Xgetlist({'context':1}) + call assert_equal("Context info", d.context) + call g:Xsetlist([], 'a', {'context':246}) + let d = g:Xgetlist({'context':1}) + call assert_equal(246, d.context) + if a:cchar == 'l' + " Test for copying context across two different location lists + new | only + let w1_id = win_getid() + let l = [1] + call setloclist(0, [], 'a', {'context':l}) + new + let w2_id = win_getid() + call add(l, 2) + call assert_equal([1, 2], getloclist(w1_id, {'context':1}).context) + call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context) + unlet! l + call assert_equal([1, 2], getloclist(w2_id, {'context':1}).context) + only + call setloclist(0, [], 'f') + call assert_equal({}, getloclist(0, {'context':1})) + endif + + " Test for changing the context of previous quickfix lists + call g:Xsetlist([], 'f') + Xexpr "One" + Xexpr "Two" + Xexpr "Three" + call g:Xsetlist([], ' ', {'context' : [1], 'nr' : 1}) + call g:Xsetlist([], ' ', {'context' : [2], 'nr' : 2}) + " Also, check for setting the context using quickfix list number zero. + call g:Xsetlist([], ' ', {'context' : [3], 'nr' : 0}) + call test_garbagecollect_now() + let l = g:Xgetlist({'nr' : 1, 'context' : 1}) + call assert_equal([1], l.context) + let l = g:Xgetlist({'nr' : 2, 'context' : 1}) + call assert_equal([2], l.context) + let l = g:Xgetlist({'nr' : 3, 'context' : 1}) + call assert_equal([3], l.context) + + " Test for changing the context through reference and for garbage + " collection of quickfix context + let l = ["red"] + call g:Xsetlist([], ' ', {'context' : l}) + call add(l, "blue") + let x = g:Xgetlist({'context' : 1}) + call add(x.context, "green") + call assert_equal(["red", "blue", "green"], l) + call assert_equal(["red", "blue", "green"], x.context) + unlet l + call test_garbagecollect_now() + let m = g:Xgetlist({'context' : 1}) + call assert_equal(["red", "blue", "green"], m.context) + + " Test for setting/getting items + Xexpr "" + let qfprev = g:Xgetlist({'nr':0}) + call g:Xsetlist([], ' ', {'title':'Green', + \ 'items' : [{'filename':'F1', 'lnum':10}]}) + let qfcur = g:Xgetlist({'nr':0}) + call assert_true(qfcur.nr == qfprev.nr + 1) + let l = g:Xgetlist({'items':1}) + call assert_equal('F1', bufname(l.items[0].bufnr)) + call assert_equal(10, l.items[0].lnum) + call g:Xsetlist([], 'a', {'items' : [{'filename':'F2', 'lnum':20}, + \ {'filename':'F2', 'lnum':30}]}) + let l = g:Xgetlist({'items':1}) + call assert_equal('F2', bufname(l.items[2].bufnr)) + call assert_equal(30, l.items[2].lnum) + call g:Xsetlist([], 'r', {'items' : [{'filename':'F3', 'lnum':40}]}) + let l = g:Xgetlist({'items':1}) + call assert_equal('F3', bufname(l.items[0].bufnr)) + call assert_equal(40, l.items[0].lnum) + call g:Xsetlist([], 'r', {'items' : []}) + let l = g:Xgetlist({'items':1}) + call assert_equal(0, len(l.items)) + + " Save and restore the quickfix stack + call g:Xsetlist([], 'f') + call assert_equal(0, g:Xgetlist({'nr':'$'}).nr) + Xexpr "File1:10:Line1" + Xexpr "File2:20:Line2" + Xexpr "File3:30:Line3" + let last_qf = g:Xgetlist({'nr':'$'}).nr + call assert_equal(3, last_qf) + let qstack = [] + for i in range(1, last_qf) + let qstack = add(qstack, g:Xgetlist({'nr':i, 'all':1})) + endfor + call g:Xsetlist([], 'f') + for i in range(len(qstack)) + call g:Xsetlist([], ' ', qstack[i]) + endfor + call assert_equal(3, g:Xgetlist({'nr':'$'}).nr) + call assert_equal(10, g:Xgetlist({'nr':1, 'items':1}).items[0].lnum) + call assert_equal(20, g:Xgetlist({'nr':2, 'items':1}).items[0].lnum) + call assert_equal(30, g:Xgetlist({'nr':3, 'items':1}).items[0].lnum) + call g:Xsetlist([], 'f') + + " Swap two quickfix lists + Xexpr "File1:10:Line10" + Xexpr "File2:20:Line20" + Xexpr "File3:30:Line30" + call g:Xsetlist([], 'r', {'nr':1,'title':'Colors','context':['Colors']}) + call g:Xsetlist([], 'r', {'nr':2,'title':'Fruits','context':['Fruits']}) + let l1=g:Xgetlist({'nr':1,'all':1}) + let l2=g:Xgetlist({'nr':2,'all':1}) + let l1.nr=2 + let l2.nr=1 + call g:Xsetlist([], 'r', l1) + call g:Xsetlist([], 'r', l2) + let newl1=g:Xgetlist({'nr':1,'all':1}) + let newl2=g:Xgetlist({'nr':2,'all':1}) + call assert_equal(':Fruits', newl1.title) + call assert_equal(['Fruits'], newl1.context) + call assert_equal('Line20', newl1.items[0].text) + call assert_equal(':Colors', newl2.title) + call assert_equal(['Colors'], newl2.context) + call assert_equal('Line10', newl2.items[0].text) + call g:Xsetlist([], 'f') +endfunc func Test_qf_property() call Xproperty_tests('c') call Xproperty_tests('l') - endfunc +endfunc " Tests for the QuickFixCmdPre/QuickFixCmdPost autocommands func QfAutoCmdHandler(loc, cmd) @@ -1673,3 +1985,227 @@ func Test_dirstack_cleanup() caddbuffer let &efm = save_efm endfunc + +" Tests for jumping to entries from the location list window and quickfix +" window +func Test_cwindow_jump() + set efm=%f%%%l%%%m + lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + lopen | only + lfirst + call assert_true(winnr('$') == 2) + call assert_true(winnr() == 1) + " Location list for the new window should be set + call assert_true(getloclist(0)[2].text == 'Line 30') + + " Open a scratch buffer + " Open a new window and create a location list + " Open the location list window and close the other window + " Jump to an entry. + " Should create a new window and jump to the entry. The scrtach buffer + " should not be used. + enew | only + set buftype=nofile + below new + lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + lopen + 2wincmd c + lnext + call assert_true(winnr('$') == 3) + call assert_true(winnr() == 2) + + " Open two windows with two different location lists + " Open the location list window and close the previous window + " Jump to an entry in the location list window + " Should open the file in the first window and not set the location list. + enew | only + lgetexpr ["F1%5%Line 5"] + below new + lgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + lopen + 2wincmd c + lnext + call assert_true(winnr() == 1) + call assert_true(getloclist(0)[0].text == 'Line 5') + + enew | only + cgetexpr ["F1%10%Line 10", "F2%20%Line 20", "F3%30%Line 30"] + copen + cnext + call assert_true(winnr('$') == 2) + call assert_true(winnr() == 1) + + enew | only + set efm&vim +endfunc + +func XvimgrepTests(cchar) + call s:setup_commands(a:cchar) + + call writefile(['Editor:VIM vim', + \ 'Editor:Emacs EmAcS', + \ 'Editor:Notepad NOTEPAD'], 'Xtestfile1') + call writefile(['Linux', 'MacOS', 'MS-Windows'], 'Xtestfile2') + + " Error cases + call assert_fails('Xvimgrep /abc *', 'E682:') + + let @/='' + call assert_fails('Xvimgrep // *', 'E35:') + + call assert_fails('Xvimgrep abc', 'E683:') + call assert_fails('Xvimgrep a1b2c3 Xtestfile1', 'E480:') + call assert_fails('Xvimgrep pat Xa1b2c3', 'E480:') + + Xexpr "" + Xvimgrepadd Notepad Xtestfile1 + Xvimgrepadd MacOS Xtestfile2 + let l = g:Xgetlist() + call assert_equal(2, len(l)) + call assert_equal('Editor:Notepad NOTEPAD', l[0].text) + + Xvimgrep #\cvim#g Xtestfile? + let l = g:Xgetlist() + call assert_equal(2, len(l)) + call assert_equal(8, l[0].col) + call assert_equal(12, l[1].col) + + 1Xvimgrep ?Editor? Xtestfile* + let l = g:Xgetlist() + call assert_equal(1, len(l)) + call assert_equal('Editor:VIM vim', l[0].text) + + edit +3 Xtestfile2 + Xvimgrep +\cemacs+j Xtestfile1 + let l = g:Xgetlist() + call assert_equal('Xtestfile2', bufname('')) + call assert_equal('Editor:Emacs EmAcS', l[0].text) + + call delete('Xtestfile1') + call delete('Xtestfile2') +endfunc + +" Tests for the :vimgrep command +func Test_vimgrep() + call XvimgrepTests('c') + call XvimgrepTests('l') +endfunc + +func XfreeTests(cchar) + call s:setup_commands(a:cchar) + + enew | only + + " Deleting the quickfix stack should work even When the current list is + " somewhere in the middle of the stack + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25'] + Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35'] + Xolder + call g:Xsetlist([], 'f') + call assert_equal(0, len(g:Xgetlist())) + + " After deleting the stack, adding a new list should create a stack with a + " single list. + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + call assert_equal(1, g:Xgetlist({'all':1}).nr) + + " Deleting the stack from a quickfix window should update/clear the + " quickfix/location list window. + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25'] + Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35'] + Xolder + Xwindow + call g:Xsetlist([], 'f') + call assert_equal(2, winnr('$')) + call assert_equal(1, line('$')) + Xclose + + " Deleting the stack from a non-quickfix window should update/clear the + " quickfix/location list window. + Xexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + Xexpr ['Xfile2:20:20:Line 20', 'Xfile2:25:25:Line 25'] + Xexpr ['Xfile3:30:30:Line 30', 'Xfile3:35:35:Line 35'] + Xolder + Xwindow + wincmd p + call g:Xsetlist([], 'f') + call assert_equal(0, len(g:Xgetlist())) + wincmd p + call assert_equal(2, winnr('$')) + call assert_equal(1, line('$')) + + " After deleting the location list stack, if the location list window is + " opened, then a new location list should be created. So opening the + " location list window again should not create a new window. + if a:cchar == 'l' + lexpr ['Xfile1:10:10:Line 10', 'Xfile1:15:15:Line 15'] + wincmd p + lopen + call assert_equal(2, winnr('$')) + endif + Xclose +endfunc + +" Tests for the quickifx free functionality +func Test_qf_free() + call XfreeTests('c') + call XfreeTests('l') +endfunc + +" Test for buffer overflow when parsing lines and adding new entries to +" the quickfix list. +func Test_bufoverflow() + set efm=%f:%l:%m + cgetexpr ['File1:100:' . repeat('x', 1025)] + + set efm=%+GCompiler:\ %.%#,%f:%l:%m + cgetexpr ['Compiler: ' . repeat('a', 1015), 'File1:10:Hello World'] + + set efm=%DEntering\ directory\ %f,%f:%l:%m + cgetexpr ['Entering directory ' . repeat('a', 1006), + \ 'File1:10:Hello World'] + set efm&vim +endfunc + +func Test_cclose_from_copen() + augroup QF_Test + au! + au FileType qf :cclose + augroup END + copen + augroup QF_Test + au! + augroup END + augroup! QF_Test +endfunc + +" Tests for getting the quickfix stack size +func XsizeTests(cchar) + call s:setup_commands(a:cchar) + + call g:Xsetlist([], 'f') + call assert_equal(0, g:Xgetlist({'nr':'$'}).nr) + call assert_equal(1, len(g:Xgetlist({'nr':'$', 'all':1}))) + call assert_equal(0, len(g:Xgetlist({'nr':0}))) + + Xexpr "File1:10:Line1" + Xexpr "File2:20:Line2" + Xexpr "File3:30:Line3" + Xolder | Xolder + call assert_equal(3, g:Xgetlist({'nr':'$'}).nr) + call g:Xsetlist([], 'f') + + Xexpr "File1:10:Line1" + Xexpr "File2:20:Line2" + Xexpr "File3:30:Line3" + Xolder | Xolder + call g:Xsetlist([], 'a', {'nr':'$', 'title':'Compiler'}) + call assert_equal('Compiler', g:Xgetlist({'nr':3, 'all':1}).title) +endfunc + +func Test_Qf_Size() + call XsizeTests('c') + call XsizeTests('l') +endfunc diff --git a/src/nvim/testdir/test_recover.vim b/src/nvim/testdir/test_recover.vim new file mode 100644 index 0000000000..46d884a97c --- /dev/null +++ b/src/nvim/testdir/test_recover.vim @@ -0,0 +1,63 @@ +" Test :recover + +func Test_recover_root_dir() + " This used to access invalid memory. + split Xtest + set dir=/ + call assert_fails('recover', 'E305:') + close! + + if has('win32') || filewritable('/') == 2 + " can write in / directory on MS-Windows + set dir=/notexist/ + endif + call assert_fails('split Xtest', 'E303:') + set dir& +endfunc + +" Inserts 10000 lines with text to fill the swap file with two levels of pointer +" blocks. Then recovers from the swap file and checks all text is restored. +" +" We need about 10000 lines of 100 characters to get two levels of pointer +" blocks. +func Test_swap_file() + set directory=. + set fileformat=unix undolevels=-1 + edit! Xtest + let text = "\tabcdefghijklmnoparstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnoparstuvwxyz0123456789" + let i = 1 + let linecount = 10000 + while i <= linecount + call append(i - 1, i . text) + let i += 1 + endwhile + $delete + preserve + " get the name of the swap file + let swname = split(execute("swapname"))[0] + let swname = substitute(swname, '[[:blank:][:cntrl:]]*\(.\{-}\)[[:blank:][:cntrl:]]*$', '\1', '') + " make a copy of the swap file in Xswap + set binary + exe 'sp ' . swname + w! Xswap + set nobinary + new + only! + bwipe! Xtest + call rename('Xswap', swname) + recover Xtest + call delete(swname) + let linedollar = line('$') + call assert_equal(linecount, linedollar) + if linedollar < linecount + let linecount = linedollar + endif + let i = 1 + while i <= linecount + call assert_equal(i . text, getline(i)) + let i += 1 + endwhile + + set undolevels& + enew! | only +endfunc diff --git a/src/nvim/testdir/test_regexp_latin.vim b/src/nvim/testdir/test_regexp_latin.vim new file mode 100644 index 0000000000..8528412806 --- /dev/null +++ b/src/nvim/testdir/test_regexp_latin.vim @@ -0,0 +1,32 @@ +" Tests for regexp in latin1 encoding +set encoding=latin1 +scriptencoding latin1 + +func s:equivalence_test() + let str = "A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z" + let groups = split(str) + for group1 in groups + for c in split(group1, '\zs') + " next statement confirms that equivalence class matches every + " character in group + call assert_match('^[[=' . c . '=]]*$', group1) + for group2 in groups + if group2 != group1 + " next statement converts that equivalence class doesn't match + " a character in any other group + call assert_equal(-1, match(group2, '[[=' . c . '=]]')) + endif + endfor + endfor + endfor +endfunc + +func Test_equivalence_re1() + set re=1 + call s:equivalence_test() +endfunc + +func Test_equivalence_re2() + set re=2 + call s:equivalence_test() +endfunc diff --git a/src/nvim/testdir/test_scrollbind.vim b/src/nvim/testdir/test_scrollbind.vim new file mode 100644 index 0000000000..baa24f1979 --- /dev/null +++ b/src/nvim/testdir/test_scrollbind.vim @@ -0,0 +1,32 @@ +" Test for 'scrollbind' causing an unexpected scroll of one of the windows. +func Test_scrollbind() + " We don't want the status line to cause problems: + set laststatus=0 + let totalLines = &lines * 20 + let middle = totalLines / 2 + new | only + for i in range(1, totalLines) + call setline(i, 'LINE ' . i) + endfor + exe string(middle) + normal zt + normal M + aboveleft vert new + for i in range(1, totalLines) + call setline(i, 'line ' . i) + endfor + exe string(middle) + normal zt + normal M + " Execute the following two commands at once to reproduce the problem. + setl scb | wincmd p + setl scb + wincmd w + let topLineLeft = line('w0') + wincmd p + let topLineRight = line('w0') + setl noscrollbind + wincmd p + setl noscrollbind + call assert_equal(0, topLineLeft - topLineRight) +endfunc diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim index a333e7f206..5da9397be5 100644 --- a/src/nvim/testdir/test_search.vim +++ b/src/nvim/testdir/test_search.vim @@ -2,13 +2,13 @@ func Test_search_cmdline() " See test/functional/legacy/search_spec.lua - throw 'skipped: Nvim does not support test_disable_char_avail()' + throw 'skipped: Nvim does not support test_override()' if !exists('+incsearch') return endif " need to disable char_avail, " so that expansion of commandline works - call test_disable_char_avail(1) + call test_override("char_avail", 1) new call setline(1, [' 1', ' 2 these', ' 3 the', ' 4 their', ' 5 there', ' 6 their', ' 7 the', ' 8 them', ' 9 these', ' 10 foobar']) " Test 1 @@ -195,19 +195,19 @@ func Test_search_cmdline() call assert_equal(' 3 the', getline('.')) " clean up - call test_disable_char_avail(0) + call test_override("char_avail", 0) bw! endfunc func Test_search_cmdline2() " See test/functional/legacy/search_spec.lua - throw 'skipped: Nvim does not support test_disable_char_avail()' + throw 'skipped: Nvim does not support test_override()' if !exists('+incsearch') return endif " need to disable char_avail, " so that expansion of commandline works - call test_disable_char_avail(1) + call test_override("char_avail", 1) new call setline(1, [' 1', ' 2 these', ' 3 the theother']) " Test 1 @@ -269,7 +269,7 @@ func Test_search_cmdline2() " clean up set noincsearch - call test_disable_char_avail(0) + call test_override("char_avail", 0) bw! endfunc @@ -298,3 +298,158 @@ func Test_searchpair() q! endfunc +func Test_searchc() + " These commands used to cause memory overflow in searchc(). + new + norm ixx + exe "norm 0t\u93cf" + bw! +endfunc + +func Test_search_cmdline3() + throw 'skipped: Nvim does not support test_override()' + if !exists('+incsearch') + return + endif + " need to disable char_avail, + " so that expansion of commandline works + call test_override("char_avail", 1) + new + call setline(1, [' 1', ' 2 the~e', ' 3 the theother']) + set incsearch + 1 + " first match + call feedkeys("/the\<c-l>\<cr>", 'tx') + call assert_equal(' 2 the~e', getline('.')) + " clean up + set noincsearch + call test_override("char_avail", 0) + bw! +endfunc + +func Test_search_cmdline4() + " See test/functional/legacy/search_spec.lua + throw 'skipped: Nvim does not support test_override()' + if !exists('+incsearch') + return + endif + " need to disable char_avail, + " so that expansion of commandline works + call test_override("char_avail", 1) + new + call setline(1, [' 1 the first', ' 2 the second', ' 3 the third']) + set incsearch + $ + call feedkeys("?the\<c-g>\<cr>", 'tx') + call assert_equal(' 3 the third', getline('.')) + $ + call feedkeys("?the\<c-g>\<c-g>\<cr>", 'tx') + call assert_equal(' 1 the first', getline('.')) + $ + call feedkeys("?the\<c-g>\<c-g>\<c-g>\<cr>", 'tx') + call assert_equal(' 2 the second', getline('.')) + $ + call feedkeys("?the\<c-t>\<cr>", 'tx') + call assert_equal(' 1 the first', getline('.')) + $ + call feedkeys("?the\<c-t>\<c-t>\<cr>", 'tx') + call assert_equal(' 3 the third', getline('.')) + $ + call feedkeys("?the\<c-t>\<c-t>\<c-t>\<cr>", 'tx') + call assert_equal(' 2 the second', getline('.')) + " clean up + set noincsearch + call test_override("char_avail", 0) + bw! +endfunc + +func Test_search_cmdline5() + if !exists('+incsearch') + return + endif + " Do not call test_override("char_avail", 1) so that <C-g> and <C-t> work + " regardless char_avail. + new + call setline(1, [' 1 the first', ' 2 the second', ' 3 the third']) + set incsearch + 1 + call feedkeys("/the\<c-g>\<c-g>\<cr>", 'tx') + call assert_equal(' 3 the third', getline('.')) + $ + call feedkeys("?the\<c-t>\<c-t>\<c-t>\<cr>", 'tx') + call assert_equal(' 2 the second', getline('.')) + " clean up + set noincsearch + bw! +endfunc + +" Tests for regexp with various magic settings +func Test_search_regexp() + enew! + + put ='1 a aa abb abbccc' + exe 'normal! /a*b\{2}c\+/e' . "\<CR>" + call assert_equal([0, 2, 17, 0], getpos('.')) + + put ='2 d dd dee deefff' + exe 'normal! /\Md\*e\{2}f\+/e' . "\<CR>" + call assert_equal([0, 3, 17, 0], getpos('.')) + + set nomagic + put ='3 g gg ghh ghhiii' + exe 'normal! /g\*h\{2}i\+/e' . "\<CR>" + call assert_equal([0, 4, 17, 0], getpos('.')) + + put ='4 j jj jkk jkklll' + exe 'normal! /\mj*k\{2}l\+/e' . "\<CR>" + call assert_equal([0, 5, 17, 0], getpos('.')) + + put ='5 m mm mnn mnnooo' + exe 'normal! /\vm*n{2}o+/e' . "\<CR>" + call assert_equal([0, 6, 17, 0], getpos('.')) + + put ='6 x ^aa$ x' + exe 'normal! /\V^aa$' . "\<CR>" + call assert_equal([0, 7, 5, 0], getpos('.')) + + set magic + put ='7 (a)(b) abbaa' + exe 'normal! /\v(a)(b)\2\1\1/e' . "\<CR>" + call assert_equal([0, 8, 14, 0], getpos('.')) + + put ='8 axx [ab]xx' + exe 'normal! /\V[ab]\(\[xy]\)\1' . "\<CR>" + call assert_equal([0, 9, 7, 0], getpos('.')) + + set undolevels=100 + put ='9 foobar' + put ='' + exe "normal! a\<C-G>u\<Esc>" + normal G + exe 'normal! dv?bar?' . "\<CR>" + call assert_equal('9 foo', getline('.')) + call assert_equal([0, 10, 5, 0], getpos('.')) + call assert_equal(10, line('$')) + normal u + call assert_equal('9 foobar', getline('.')) + call assert_equal([0, 10, 6, 0], getpos('.')) + call assert_equal(11, line('$')) + + set undolevels& + enew! +endfunc + +" Test for search('multi-byte char', 'bce') +func Test_search_multibyte() + if !has('multi_byte') + return + endif + let save_enc = &encoding + set encoding=utf8 + enew! + call append('$', 'A') + call cursor(2, 1) + call assert_equal(2, search('A', 'bce', line('.'))) + enew! + let &encoding = save_enc +endfunc diff --git a/src/nvim/testdir/test_sha256.vim b/src/nvim/testdir/test_sha256.vim new file mode 100644 index 0000000000..dd4707977e --- /dev/null +++ b/src/nvim/testdir/test_sha256.vim @@ -0,0 +1,22 @@ +" Tests for the sha256() function. + +if !has('cryptv') || !exists('*sha256') + finish +endif + +function Test_sha256() + " test for empty string: + call assert_equal(sha256(""), 'e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855') + + "'test for 1 char: + call assert_equal(sha256("a"), 'ca978112ca1bbdcafac231b39a23dc4da786eff8147c4e72b9807785afee48bb') + " + "test for 3 chars: + call assert_equal(sha256("abc"), 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad') + + " test for contains meta char: + call assert_equal(sha256("foo\nbar"), '807eff6267f3f926a21d234f7b0cf867a86f47e07a532f15e8cc39ed110ca776') + + " test for contains non-ascii char: + call assert_equal(sha256("\xde\xad\xbe\xef"), '5f78c33274e43fa9de5659265c1d917e25c03722dcb0b8d27db8d5feaa813953') +endfunction diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim index 75dbd74b34..a967435346 100644 --- a/src/nvim/testdir/test_signs.vim +++ b/src/nvim/testdir/test_signs.vim @@ -182,7 +182,7 @@ func Test_sign_invalid_commands() call assert_fails('sign define Sign1 xxx', 'E475:') call assert_fails('sign undefine', 'E156:') call assert_fails('sign list xxx', 'E155:') - call assert_fails('sign place 1 buffer=', 'E158:') + call assert_fails('sign place 1 buffer=999', 'E158:') call assert_fails('sign define Sign2 text=', 'E239:') endfunc diff --git a/src/nvim/testdir/test_source_utf8.vim b/src/nvim/testdir/test_source_utf8.vim index edb76fc43d..c29c2ec1f3 100644 --- a/src/nvim/testdir/test_source_utf8.vim +++ b/src/nvim/testdir/test_source_utf8.vim @@ -31,3 +31,33 @@ func Test_source_latin() bwipe! call delete('Xscript') endfunc + +" Test for sourcing a file with CTRL-V's at the end of the line +func Test_source_ctrl_v() + call writefile(['map __1 afirst', + \ 'map __2 asecond', + \ 'map __3 athird', + \ 'map __4 afourth', + \ 'map __5 afifth', + \ "map __1 asd\<C-V>", + \ "map __2 asd\<C-V>\<C-V>", + \ "map __3 asd\<C-V>\<C-V>", + \ "map __4 asd\<C-V>\<C-V>\<C-V>", + \ "map __5 asd\<C-V>\<C-V>\<C-V>", + \ ], 'Xtestfile') + source Xtestfile + enew! + exe "normal __1\<Esc>\<Esc>__2\<Esc>__3\<Esc>\<Esc>__4\<Esc>__5\<Esc>" + exe "%s/\<C-J>/0/g" + call assert_equal(['sd', + \ "map __2 asd\<Esc>secondsd\<Esc>sd0map __5 asd0fifth"], + \ getline(1, 2)) + + enew! + call delete('Xtestfile') + unmap __1 + unmap __2 + unmap __3 + unmap __4 + unmap __5 +endfunc diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim new file mode 100644 index 0000000000..21f2363731 --- /dev/null +++ b/src/nvim/testdir/test_spell.vim @@ -0,0 +1,821 @@ +" Test spell checking + +if !has('spell') + finish +endif + +func TearDown() + set nospell + call delete('Xtest.aff') + call delete('Xtest.dic') + call delete('Xtest.latin1.add') + call delete('Xtest.latin1.add.spl') + call delete('Xtest.latin1.spl') + call delete('Xtest.latin1.sug') +endfunc + +func Test_wrap_search() + new + call setline(1, ['The', '', 'A plong line with two zpelling mistakes', '', 'End']) + set spell wrapscan + normal ]s + call assert_equal('plong', expand('<cword>')) + normal ]s + call assert_equal('zpelling', expand('<cword>')) + normal ]s + call assert_equal('plong', expand('<cword>')) + bwipe! + set nospell +endfunc + +func Test_curswant() + new + call setline(1, ['Another plong line', 'abcdefghijklmnopq']) + set spell wrapscan + normal 0]s + call assert_equal('plong', expand('<cword>')) + normal j + call assert_equal(9, getcurpos()[2]) + normal 0[s + call assert_equal('plong', expand('<cword>')) + normal j + call assert_equal(9, getcurpos()[2]) + + normal 0]S + call assert_equal('plong', expand('<cword>')) + normal j + call assert_equal(9, getcurpos()[2]) + normal 0[S + call assert_equal('plong', expand('<cword>')) + normal j + call assert_equal(9, getcurpos()[2]) + + normal 1G0 + call assert_equal('plong', spellbadword()[0]) + normal j + call assert_equal(9, getcurpos()[2]) + + bwipe! + set nospell +endfunc + +func Test_z_equal_on_invalid_utf8_word() + split + set spell + call setline(1, "\xff") + norm z= + set nospell + bwipe! +endfunc + +func Test_spellreall() + new + set spell + call assert_fails('spellrepall', 'E752:') + call setline(1, ['A speling mistake. The same speling mistake.', + \ 'Another speling mistake.']) + call feedkeys(']s1z=', 'tx') + call assert_equal('A spelling mistake. The same speling mistake.', getline(1)) + call assert_equal('Another speling mistake.', getline(2)) + spellrepall + call assert_equal('A spelling mistake. The same spelling mistake.', getline(1)) + call assert_equal('Another spelling mistake.', getline(2)) + call assert_fails('spellrepall', 'E753:') + set spell& + bwipe! +endfunc + +func Test_zz_basic() + call LoadAffAndDic(g:test_data_aff1, g:test_data_dic1) + call RunGoodBad("wrong OK puts. Test the end", + \ "bad: inputs comment ok Ok. test d\xE9\xF4l end the", + \["Comment", "deol", "d\xE9\xF4r", "input", "OK", "output", "outputs", "outtest", "put", "puts", + \ "test", "testen", "testn", "the end", "uk", "wrong"], + \[ + \ ["bad", ["put", "uk", "OK"]], + \ ["inputs", ["input", "puts", "outputs"]], + \ ["comment", ["Comment", "outtest", "the end"]], + \ ["ok", ["OK", "uk", "put"]], + \ ["Ok", ["OK", "Uk", "Put"]], + \ ["test", ["Test", "testn", "testen"]], + \ ["d\xE9\xF4l", ["deol", "d\xE9\xF4r", "test"]], + \ ["end", ["put", "uk", "test"]], + \ ["the", ["put", "uk", "test"]], + \ ] + \ ) + + call assert_equal("gebletegek", soundfold('goobledygoook')) + call assert_equal("kepereneven", soundfold('koprnven')) + call assert_equal("everles gesvets etele", soundfold('oeverloos gezwets edale')) +endfunc + +" Postponed prefixes +func Test_zz_prefixes() + call LoadAffAndDic(g:test_data_aff2, g:test_data_dic1) + call RunGoodBad("puts", + \ "bad: inputs comment ok Ok end the. test d\xE9\xF4l", + \ ["Comment", "deol", "d\xE9\xF4r", "OK", "put", "input", "output", "puts", "outputs", "test", "outtest", "testen", "testn", "the end", "uk", "wrong"], + \ [ + \ ["bad", ["put", "uk", "OK"]], + \ ["inputs", ["input", "puts", "outputs"]], + \ ["comment", ["Comment"]], + \ ["ok", ["OK", "uk", "put"]], + \ ["Ok", ["OK", "Uk", "Put"]], + \ ["end", ["put", "uk", "deol"]], + \ ["the", ["put", "uk", "test"]], + \ ["test", ["Test", "testn", "testen"]], + \ ["d\xE9\xF4l", ["deol", "d\xE9\xF4r", "test"]], + \ ]) +endfunc + +"Compound words +func Test_zz_compound() + call LoadAffAndDic(g:test_data_aff3, g:test_data_dic3) + call RunGoodBad("foo m\xEF foobar foofoobar barfoo barbarfoo", + \ "bad: bar la foom\xEF barm\xEF m\xEFfoo m\xEFbar m\xEFm\xEF lala m\xEFla lam\xEF foola labar", + \ ["foo", "m\xEF"], + \ [ + \ ["bad", ["foo", "m\xEF"]], + \ ["bar", ["barfoo", "foobar", "foo"]], + \ ["la", ["m\xEF", "foo"]], + \ ["foom\xEF", ["foo m\xEF", "foo", "foofoo"]], + \ ["barm\xEF", ["barfoo", "m\xEF", "barbar"]], + \ ["m\xEFfoo", ["m\xEF foo", "foo", "foofoo"]], + \ ["m\xEFbar", ["foobar", "barbar", "m\xEF"]], + \ ["m\xEFm\xEF", ["m\xEF m\xEF", "m\xEF"]], + \ ["lala", []], + \ ["m\xEFla", ["m\xEF", "m\xEF m\xEF"]], + \ ["lam\xEF", ["m\xEF", "m\xEF m\xEF"]], + \ ["foola", ["foo", "foobar", "foofoo"]], + \ ["labar", ["barbar", "foobar"]], + \ ]) + + call LoadAffAndDic(g:test_data_aff4, g:test_data_dic4) + call RunGoodBad("word util bork prebork start end wordutil wordutils pro-ok bork borkbork borkborkbork borkborkborkbork borkborkborkborkbork tomato tomatotomato startend startword startwordword startwordend startwordwordend startwordwordwordend prebork preborkbork preborkborkbork nouword", + \ "bad: wordutilize pro borkborkborkborkborkbork tomatotomatotomato endstart endend startstart wordend wordstart preborkprebork preborkpreborkbork startwordwordwordwordend borkpreborkpreborkbork utilsbork startnouword", + \ ["bork", "prebork", "end", "pro-ok", "start", "tomato", "util", "utilize", "utils", "word", "nouword"], + \ [ + \ ["bad", ["end", "bork", "word"]], + \ ["wordutilize", ["word utilize", "wordutils", "wordutil"]], + \ ["pro", ["bork", "word", "end"]], + \ ["borkborkborkborkborkbork", ["bork borkborkborkborkbork", "borkbork borkborkborkbork", "borkborkbork borkborkbork"]], + \ ["tomatotomatotomato", ["tomato tomatotomato", "tomatotomato tomato", "tomato tomato tomato"]], + \ ["endstart", ["end start", "start"]], + \ ["endend", ["end end", "end"]], + \ ["startstart", ["start start"]], + \ ["wordend", ["word end", "word", "wordword"]], + \ ["wordstart", ["word start", "bork start"]], + \ ["preborkprebork", ["prebork prebork", "preborkbork", "preborkborkbork"]], + \ ["preborkpreborkbork", ["prebork preborkbork", "preborkborkbork", "preborkborkborkbork"]], + \ ["startwordwordwordwordend", ["startwordwordwordword end", "startwordwordwordword", "start wordwordwordword end"]], + \ ["borkpreborkpreborkbork", ["bork preborkpreborkbork", "bork prebork preborkbork", "bork preborkprebork bork"]], + \ ["utilsbork", ["utilbork", "utils bork", "util bork"]], + \ ["startnouword", ["start nouword", "startword", "startborkword"]], + \ ]) + +endfunc + +"Test affix flags with two characters +func Test_zz_affix() + call LoadAffAndDic(g:test_data_aff5, g:test_data_dic5) + call RunGoodBad("fooa1 fooa\xE9 bar prebar barbork prebarbork startprebar start end startend startmiddleend nouend", + \ "bad: foo fooa2 prabar probarbirk middle startmiddle middleend endstart startprobar startnouend", + \ ["bar", "barbork", "end", "fooa1", "fooa\xE9", "nouend", "prebar", "prebarbork", "start"], + \ [ + \ ["bad", ["bar", "end", "fooa1"]], + \ ["foo", ["fooa1", "fooa\xE9", "bar"]], + \ ["fooa2", ["fooa1", "fooa\xE9", "bar"]], + \ ["prabar", ["prebar", "bar", "bar bar"]], + \ ["probarbirk", ["prebarbork"]], + \ ["middle", []], + \ ["startmiddle", ["startmiddleend", "startmiddlebar"]], + \ ["middleend", []], + \ ["endstart", ["end start", "start"]], + \ ["startprobar", ["startprebar", "start prebar", "startbar"]], + \ ["startnouend", ["start nouend", "startend"]], + \ ]) + + call LoadAffAndDic(g:test_data_aff6, g:test_data_dic6) + call RunGoodBad("meea1 meea\xE9 bar prebar barbork prebarbork leadprebar lead end leadend leadmiddleend", + \ "bad: mee meea2 prabar probarbirk middle leadmiddle middleend endlead leadprobar", + \ ["bar", "barbork", "end", "lead", "meea1", "meea\xE9", "prebar", "prebarbork"], + \ [ + \ ["bad", ["bar", "end", "lead"]], + \ ["mee", ["meea1", "meea\xE9", "bar"]], + \ ["meea2", ["meea1", "meea\xE9", "lead"]], + \ ["prabar", ["prebar", "bar", "leadbar"]], + \ ["probarbirk", ["prebarbork"]], + \ ["middle", []], + \ ["leadmiddle", ["leadmiddleend", "leadmiddlebar"]], + \ ["middleend", []], + \ ["endlead", ["end lead", "lead", "end end"]], + \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]], + \ ]) + + call LoadAffAndDic(g:test_data_aff7, g:test_data_dic7) + call RunGoodBad("meea1 meea\xE9 bar prebar barmeat prebarmeat leadprebar lead tail leadtail leadmiddletail", + \ "bad: mee meea2 prabar probarmaat middle leadmiddle middletail taillead leadprobar", + \ ["bar", "barmeat", "lead", "meea1", "meea\xE9", "prebar", "prebarmeat", "tail"], + \ [ + \ ["bad", ["bar", "lead", "tail"]], + \ ["mee", ["meea1", "meea\xE9", "bar"]], + \ ["meea2", ["meea1", "meea\xE9", "lead"]], + \ ["prabar", ["prebar", "bar", "leadbar"]], + \ ["probarmaat", ["prebarmeat"]], + \ ["middle", []], + \ ["leadmiddle", ["leadmiddlebar"]], + \ ["middletail", []], + \ ["taillead", ["tail lead", "tail"]], + \ ["leadprobar", ["leadprebar", "lead prebar", "leadbar"]], + \ ]) +endfunc + +func Test_zz_NOSLITSUGS() + call LoadAffAndDic(g:test_data_aff8, g:test_data_dic8) + call RunGoodBad("foo bar faabar", "bad: foobar barfoo", + \ ["bar", "faabar", "foo"], + \ [ + \ ["bad", ["bar", "foo"]], + \ ["foobar", ["faabar", "foo bar", "bar"]], + \ ["barfoo", ["bar foo", "bar", "foo"]], + \ ]) +endfunc + +" Numbers +func Test_zz_Numbers() + call LoadAffAndDic(g:test_data_aff9, g:test_data_dic9) + call RunGoodBad("0b1011 0777 1234 0x01ff", "", + \ ["bar", "foo"], + \ [ + \ ]) +endfunc + +function FirstSpellWord() + call feedkeys("/^start:\n", 'tx') + normal ]smm + let [str, a] = spellbadword() + return str +endfunc + +function SecondSpellWord() + normal `m]s + let [str, a] = spellbadword() + return str +endfunc + +"Test with SAL instead of SOFO items; test automatic reloading +func Test_zz_sal_and_addition() + throw 'skipped: Nvim does not support enc=latin1' + set enc=latin1 + set spellfile= + call writefile(g:test_data_dic1, "Xtest.dic") + call writefile(g:test_data_aff_sal, "Xtest.aff") + mkspell! Xtest Xtest + set spl=Xtest.latin1.spl spell + call assert_equal('kbltykk', soundfold('goobledygoook')) + call assert_equal('kprnfn', soundfold('koprnven')) + call assert_equal('*fls kswts tl', soundfold('oeverloos gezwets edale')) + + "also use an addition file + call writefile(["/regions=usgbnz", "elequint/2", "elekwint/3"], "Xtest.latin1.add") + mkspell! Xtest.latin1.add.spl Xtest.latin1.add + + bwipe! + call setline(1, ["start: elequint test elekwint test elekwent asdf"]) + + set spellfile=Xtest.latin1.add + call assert_equal("elekwent", FirstSpellWord()) + + set spl=Xtest_us.latin1.spl + call assert_equal("elequint", FirstSpellWord()) + call assert_equal("elekwint", SecondSpellWord()) + + set spl=Xtest_gb.latin1.spl + call assert_equal("elekwint", FirstSpellWord()) + call assert_equal("elekwent", SecondSpellWord()) + + set spl=Xtest_nz.latin1.spl + call assert_equal("elequint", FirstSpellWord()) + call assert_equal("elekwent", SecondSpellWord()) + + set spl=Xtest_ca.latin1.spl + call assert_equal("elequint", FirstSpellWord()) + call assert_equal("elekwint", SecondSpellWord()) +endfunc + +func Test_region_error() + messages clear + call writefile(["/regions=usgbnz", "elequint/0"], "Xtest.latin1.add") + mkspell! Xtest.latin1.add.spl Xtest.latin1.add + call assert_match('Invalid region nr in Xtest.latin1.add line 2: 0', execute('messages')) + call delete('Xtest.latin1.add') + call delete('Xtest.latin1.add.spl') +endfunc + +" Check using z= in new buffer (crash fixed by patch 7.4a.028). +func Test_zeq_crash() + new + set maxmem=512 spell + call feedkeys('iasdz=:\"', 'tx') + + bwipe! +endfunc + +func LoadAffAndDic(aff_contents, dic_contents) + throw 'skipped: Nvim does not support enc=latin1' + set enc=latin1 + set spellfile= + call writefile(a:aff_contents, "Xtest.aff") + call writefile(a:dic_contents, "Xtest.dic") + " Generate a .spl file from a .dic and .aff file. + mkspell! Xtest Xtest + " use that spell file + set spl=Xtest.latin1.spl spell +endfunc + +func ListWords() + spelldump + %yank + quit + return split(@", "\n") +endfunc + +func TestGoodBadBase() + exe '1;/^good:' + normal 0f:]s + let prevbad = '' + let result = [] + while 1 + let [bad, a] = spellbadword() + if bad == '' || bad == prevbad || bad == 'badend' + break + endif + let prevbad = bad + let lst = spellsuggest(bad, 3) + normal mm + + call add(result, [bad, lst]) + normal `m]s + endwhile + return result +endfunc + +func RunGoodBad(good, bad, expected_words, expected_bad_words) + bwipe! + call setline(1, ["good: ", a:good, a:bad, " badend "]) + let words = ListWords() + call assert_equal(a:expected_words, words[1:-1]) + let bad_words = TestGoodBadBase() + call assert_equal(a:expected_bad_words, bad_words) + bwipe! +endfunc + +let g:test_data_aff1 = [ + \"SET ISO8859-1", + \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"SOFOFROM abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ\xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF\xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xBF", + \"SOFOTO ebctefghejklnnepkrstevvkesebctefghejklnnepkrstevvkeseeeeeeeceeeeeeeedneeeeeeeeeeepseeeeeeeeceeeeeeeedneeeeeeeeeeep?", + \"", + \"MIDWORD\t'-", + \"", + \"KEP =", + \"RAR ?", + \"BAD !", + \"", + \"PFX I N 1", + \"PFX I 0 in .", + \"", + \"PFX O Y 1", + \"PFX O 0 out .", + \"", + \"SFX S Y 2", + \"SFX S 0 s [^s]", + \"SFX S 0 es s", + \"", + \"SFX N N 3", + \"SFX N 0 en [^n]", + \"SFX N 0 nen n", + \"SFX N 0 n .", + \"", + \"REP 3", + \"REP g ch", + \"REP ch g", + \"REP svp s.v.p.", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \ ] +let g:test_data_dic1 = [ + \"123456", + \"test/NO", + \"# comment", + \"wrong", + \"Comment", + \"OK", + \"uk", + \"put/ISO", + \"the end", + \"deol", + \"d\xE9\xF4r", + \ ] +let g:test_data_aff2 = [ + \"SET ISO8859-1", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"PFXPOSTPONE", + \"", + \"MIDWORD\t'-", + \"", + \"KEP =", + \"RAR ?", + \"BAD !", + \"", + \"PFX I N 1", + \"PFX I 0 in .", + \"", + \"PFX O Y 1", + \"PFX O 0 out [a-z]", + \"", + \"SFX S Y 2", + \"SFX S 0 s [^s]", + \"SFX S 0 es s", + \"", + \"SFX N N 3", + \"SFX N 0 en [^n]", + \"SFX N 0 nen n", + \"SFX N 0 n .", + \"", + \"REP 3", + \"REP g ch", + \"REP ch g", + \"REP svp s.v.p.", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \ ] +let g:test_data_aff3 = [ + \"SET ISO8859-1", + \"", + \"COMPOUNDMIN 3", + \"COMPOUNDRULE m*", + \"NEEDCOMPOUND x", + \ ] +let g:test_data_dic3 = [ + \"1234", + \"foo/m", + \"bar/mx", + \"m\xEF/m", + \"la/mx", + \ ] +let g:test_data_aff4 = [ + \"SET ISO8859-1", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"COMPOUNDRULE m+", + \"COMPOUNDRULE sm*e", + \"COMPOUNDRULE sm+", + \"COMPOUNDMIN 3", + \"COMPOUNDWORDMAX 3", + \"COMPOUNDFORBIDFLAG t", + \"", + \"COMPOUNDSYLMAX 5", + \"SYLLABLE a\xE1e\xE9i\xEDo\xF3\xF6\xF5u\xFA\xFC\xFBy/aa/au/ea/ee/ei/ie/oa/oe/oo/ou/uu/ui", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \"", + \"NEEDAFFIX x", + \"", + \"PFXPOSTPONE", + \"", + \"MIDWORD '-", + \"", + \"SFX q N 1", + \"SFX q 0 -ok .", + \"", + \"SFX a Y 2", + \"SFX a 0 s .", + \"SFX a 0 ize/t .", + \"", + \"PFX p N 1", + \"PFX p 0 pre .", + \"", + \"PFX P N 1", + \"PFX P 0 nou .", + \ ] +let g:test_data_dic4 = [ + \"1234", + \"word/mP", + \"util/am", + \"pro/xq", + \"tomato/m", + \"bork/mp", + \"start/s", + \"end/e", + \ ] +let g:test_data_aff5 = [ + \"SET ISO8859-1", + \"", + \"FLAG long", + \"", + \"NEEDAFFIX !!", + \"", + \"COMPOUNDRULE ssmm*ee", + \"", + \"NEEDCOMPOUND xx", + \"COMPOUNDPERMITFLAG pp", + \"", + \"SFX 13 Y 1", + \"SFX 13 0 bork .", + \"", + \"SFX a1 Y 1", + \"SFX a1 0 a1 .", + \"", + \"SFX a\xE9 Y 1", + \"SFX a\xE9 0 a\xE9 .", + \"", + \"PFX zz Y 1", + \"PFX zz 0 pre/pp .", + \"", + \"PFX yy Y 1", + \"PFX yy 0 nou .", + \ ] +let g:test_data_dic5 = [ + \"1234", + \"foo/a1a\xE9!!", + \"bar/zz13ee", + \"start/ss", + \"end/eeyy", + \"middle/mmxx", + \ ] +let g:test_data_aff6 = [ + \"SET ISO8859-1", + \"", + \"FLAG caplong", + \"", + \"NEEDAFFIX A!", + \"", + \"COMPOUNDRULE sMm*Ee", + \"", + \"NEEDCOMPOUND Xx", + \"", + \"COMPOUNDPERMITFLAG p", + \"", + \"SFX N3 Y 1", + \"SFX N3 0 bork .", + \"", + \"SFX A1 Y 1", + \"SFX A1 0 a1 .", + \"", + \"SFX A\xE9 Y 1", + \"SFX A\xE9 0 a\xE9 .", + \"", + \"PFX Zz Y 1", + \"PFX Zz 0 pre/p .", + \ ] +let g:test_data_dic6 = [ + \"1234", + \"mee/A1A\xE9A!", + \"bar/ZzN3Ee", + \"lead/s", + \"end/Ee", + \"middle/MmXx", + \ ] +let g:test_data_aff7 = [ + \"SET ISO8859-1", + \"", + \"FLAG num", + \"", + \"NEEDAFFIX 9999", + \"", + \"COMPOUNDRULE 2,77*123", + \"", + \"NEEDCOMPOUND 1", + \"COMPOUNDPERMITFLAG 432", + \"", + \"SFX 61003 Y 1", + \"SFX 61003 0 meat .", + \"", + \"SFX 391 Y 1", + \"SFX 391 0 a1 .", + \"", + \"SFX 111 Y 1", + \"SFX 111 0 a\xE9 .", + \"", + \"PFX 17 Y 1", + \"PFX 17 0 pre/432 .", + \ ] +let g:test_data_dic7 = [ + \"1234", + \"mee/391,111,9999", + \"bar/17,61003,123", + \"lead/2", + \"tail/123", + \"middle/77,1", + \ ] +let g:test_data_aff8 = [ + \"SET ISO8859-1", + \"", + \"NOSPLITSUGS", + \ ] +let g:test_data_dic8 = [ + \"1234", + \"foo", + \"bar", + \"faabar", + \ ] +let g:test_data_aff9 = [ + \ ] +let g:test_data_dic9 = [ + \"1234", + \"foo", + \"bar", + \ ] +let g:test_data_aff_sal = [ + \"SET ISO8859-1", + \"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ", + \"", + \"FOL \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"LOW \xE0\xE1\xE2\xE3\xE4\xE5\xE6\xE7\xE8\xE9\xEA\xEB\xEC\xED\xEE\xEF\xF0\xF1\xF2\xF3\xF4\xF5\xF6\xF8\xF9\xFA\xFB\xFC\xFD\xFE\xDF\xFF", + \"UPP \xC0\xC1\xC2\xC3\xC4\xC5\xC6\xC7\xC8\xC9\xCA\xCB\xCC\xCD\xCE\xCF\xD0\xD1\xD2\xD3\xD4\xD5\xD6\xD8\xD9\xDA\xDB\xDC\xDD\xDE\xDF\xFF", + \"", + \"MIDWORD\t'-", + \"", + \"KEP =", + \"RAR ?", + \"BAD !", + \"", + \"PFX I N 1", + \"PFX I 0 in .", + \"", + \"PFX O Y 1", + \"PFX O 0 out .", + \"", + \"SFX S Y 2", + \"SFX S 0 s [^s]", + \"SFX S 0 es s", + \"", + \"SFX N N 3", + \"SFX N 0 en [^n]", + \"SFX N 0 nen n", + \"SFX N 0 n .", + \"", + \"REP 3", + \"REP g ch", + \"REP ch g", + \"REP svp s.v.p.", + \"", + \"MAP 9", + \"MAP a\xE0\xE1\xE2\xE3\xE4\xE5", + \"MAP e\xE8\xE9\xEA\xEB", + \"MAP i\xEC\xED\xEE\xEF", + \"MAP o\xF2\xF3\xF4\xF5\xF6", + \"MAP u\xF9\xFA\xFB\xFC", + \"MAP n\xF1", + \"MAP c\xE7", + \"MAP y\xFF\xFD", + \"MAP s\xDF", + \"", + \"SAL AH(AEIOUY)-^ *H", + \"SAL AR(AEIOUY)-^ *R", + \"SAL A(HR)^ *", + \"SAL A^ *", + \"SAL AH(AEIOUY)- H", + \"SAL AR(AEIOUY)- R", + \"SAL A(HR) _", + \"SAL \xC0^ *", + \"SAL \xC5^ *", + \"SAL BB- _", + \"SAL B B", + \"SAL CQ- _", + \"SAL CIA X", + \"SAL CH X", + \"SAL C(EIY)- S", + \"SAL CK K", + \"SAL COUGH^ KF", + \"SAL CC< C", + \"SAL C K", + \"SAL DG(EIY) K", + \"SAL DD- _", + \"SAL D T", + \"SAL \xC9< E", + \"SAL EH(AEIOUY)-^ *H", + \"SAL ER(AEIOUY)-^ *R", + \"SAL E(HR)^ *", + \"SAL ENOUGH^$ *NF", + \"SAL E^ *", + \"SAL EH(AEIOUY)- H", + \"SAL ER(AEIOUY)- R", + \"SAL E(HR) _", + \"SAL FF- _", + \"SAL F F", + \"SAL GN^ N", + \"SAL GN$ N", + \"SAL GNS$ NS", + \"SAL GNED$ N", + \"SAL GH(AEIOUY)- K", + \"SAL GH _", + \"SAL GG9 K", + \"SAL G K", + \"SAL H H", + \"SAL IH(AEIOUY)-^ *H", + \"SAL IR(AEIOUY)-^ *R", + \"SAL I(HR)^ *", + \"SAL I^ *", + \"SAL ING6 N", + \"SAL IH(AEIOUY)- H", + \"SAL IR(AEIOUY)- R", + \"SAL I(HR) _", + \"SAL J K", + \"SAL KN^ N", + \"SAL KK- _", + \"SAL K K", + \"SAL LAUGH^ LF", + \"SAL LL- _", + \"SAL L L", + \"SAL MB$ M", + \"SAL MM M", + \"SAL M M", + \"SAL NN- _", + \"SAL N N", + \"SAL OH(AEIOUY)-^ *H", + \"SAL OR(AEIOUY)-^ *R", + \"SAL O(HR)^ *", + \"SAL O^ *", + \"SAL OH(AEIOUY)- H", + \"SAL OR(AEIOUY)- R", + \"SAL O(HR) _", + \"SAL PH F", + \"SAL PN^ N", + \"SAL PP- _", + \"SAL P P", + \"SAL Q K", + \"SAL RH^ R", + \"SAL ROUGH^ RF", + \"SAL RR- _", + \"SAL R R", + \"SAL SCH(EOU)- SK", + \"SAL SC(IEY)- S", + \"SAL SH X", + \"SAL SI(AO)- X", + \"SAL SS- _", + \"SAL S S", + \"SAL TI(AO)- X", + \"SAL TH @", + \"SAL TCH-- _", + \"SAL TOUGH^ TF", + \"SAL TT- _", + \"SAL T T", + \"SAL UH(AEIOUY)-^ *H", + \"SAL UR(AEIOUY)-^ *R", + \"SAL U(HR)^ *", + \"SAL U^ *", + \"SAL UH(AEIOUY)- H", + \"SAL UR(AEIOUY)- R", + \"SAL U(HR) _", + \"SAL V^ W", + \"SAL V F", + \"SAL WR^ R", + \"SAL WH^ W", + \"SAL W(AEIOU)- W", + \"SAL X^ S", + \"SAL X KS", + \"SAL Y(AEIOU)- Y", + \"SAL ZZ- _", + \"SAL Z S", + \ ] diff --git a/src/nvim/testdir/test_stat.vim b/src/nvim/testdir/test_stat.vim index dee0d13e84..1239fe9427 100644 --- a/src/nvim/testdir/test_stat.vim +++ b/src/nvim/testdir/test_stat.vim @@ -1,20 +1,41 @@ " Tests for stat functions and checktime -func Test_existent_file() +func CheckFileTime(doSleep) let fname = 'Xtest.tmp' + let result = 0 let ts = localtime() + if a:doSleep + sleep 1 + endif let fl = ['Hello World!'] call writefile(fl, fname) let tf = getftime(fname) + if a:doSleep + sleep 1 + endif let te = localtime() - call assert_true(ts <= tf && tf <= te) - call assert_equal(strlen(fl[0] . "\n"), getfsize(fname)) - call assert_equal('file', getftype(fname)) - call assert_equal('rw-', getfperm(fname)[0:2]) + let time_correct = (ts <= tf && tf <= te) + if a:doSleep || time_correct + call assert_true(time_correct) + call assert_equal(strlen(fl[0] . "\n"), getfsize(fname)) + call assert_equal('file', getftype(fname)) + call assert_equal('rw-', getfperm(fname)[0:2]) + let result = 1 + endif call delete(fname) + return result +endfunc + +func Test_existent_file() + " On some systems the file timestamp is rounded to a multiple of 2 seconds. + " We need to sleep to handle that, but that makes the test slow. First try + " without the sleep, and if it fails try again with the sleep. + if CheckFileTime(0) == 0 + call CheckFileTime(1) + endif endfunc func Test_existent_directory() diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim index 82898df92d..cf85bd58ac 100644 --- a/src/nvim/testdir/test_statusline.vim +++ b/src/nvim/testdir/test_statusline.vim @@ -1,19 +1,39 @@ -function! StatuslineWithCaughtError() +" Test 'statusline' +" +" Not tested yet: +" %a +" %N +" %T +" %X +" %* + +source view_util.vim + +func s:get_statusline() + return ScreenLines(&lines - 1, &columns)[0] +endfunc + +func StatuslineWithCaughtError() let s:func_in_statusline_called = 1 try call eval('unknown expression') catch endtry return '' -endfunction +endfunc -function! StatuslineWithError() +func StatuslineWithError() let s:func_in_statusline_called = 1 call eval('unknown expression') return '' -endfunction +endfunc -function! Test_caught_error_in_statusline() +" Function used to display syntax group. +func SyntaxItem() + return synIDattr(synID(line("."),col("."),1),"name") +endfunc + +func Test_caught_error_in_statusline() let s:func_in_statusline_called = 0 set laststatus=2 let statusline = '%{StatuslineWithCaughtError()}' @@ -22,9 +42,9 @@ function! Test_caught_error_in_statusline() call assert_true(s:func_in_statusline_called) call assert_equal(statusline, &statusline) set statusline= -endfunction +endfunc -function! Test_statusline_will_be_disabled_with_error() +func Test_statusline_will_be_disabled_with_error() let s:func_in_statusline_called = 0 set laststatus=2 let statusline = '%{StatuslineWithError()}' @@ -36,4 +56,219 @@ function! Test_statusline_will_be_disabled_with_error() call assert_true(s:func_in_statusline_called) call assert_equal('', &statusline) set statusline= -endfunction +endfunc + +func Test_statusline() + new Xstatusline + only + set laststatus=2 + set splitbelow + call setline(1, range(1, 200)) + + " %b: Value of character under cursor. + " %B: As above, in hexadecimal. + call cursor(180, 2) + set statusline=%b,%B + call assert_match('^56,38\s*$', s:get_statusline()) + + " %o: Byte number in file of byte under cursor, first byte is 1. + " %O: As above, in hexadecimal. + set statusline=%o,%O + set fileformat=dos + call assert_match('^789,315\s*$', s:get_statusline()) + set fileformat=mac + call assert_match('^610,262\s*$', s:get_statusline()) + set fileformat=unix + call assert_match('^610,262\s*$', s:get_statusline()) + set fileformat& + + " %f: Path to the file in the buffer, as typed or relative to current dir. + set statusline=%f + call assert_match('^Xstatusline\s*$', s:get_statusline()) + + " %F: Full path to the file in the buffer. + set statusline=%F + call assert_match('/testdir/Xstatusline\s*$', s:get_statusline()) + + " %h: Help buffer flag, text is "[help]". + " %H: Help buffer flag, text is ",HLP". + set statusline=%h,%H + call assert_match('^,\s*$', s:get_statusline()) + help + call assert_match('^\[Help\],HLP\s*$', s:get_statusline()) + helpclose + + " %k: Value of "b:keymap_name" or 'keymap' + " when :lmap mappings are being used: <keymap>" + set statusline=%k + if has('keymap') + set keymap=esperanto + call assert_match('^<Eo>\s*$', s:get_statusline()) + set keymap& + else + call assert_match('^\s*$', s:get_statusline()) + endif + + " %l: Line number. + " %L: Number of line in buffer. + " %c: Column number. + set statusline=%l/%L,%c + call assert_match('^180/200,2\s*$', s:get_statusline()) + + " %m: Modified flag, text is "[+]", "[-]" if 'modifiable' is off. + " %M: Modified flag, text is ",+" or ",-". + set statusline=%m%M + call assert_match('^\[+\],+\s*$', s:get_statusline()) + set nomodifiable + call assert_match('^\[+-\],+-\s*$', s:get_statusline()) + write + call assert_match('^\[-\],-\s*$', s:get_statusline()) + set modifiable& + call assert_match('^\s*$', s:get_statusline()) + + " %n: Buffer number. + set statusline=%n + call assert_match('^'.bufnr('%').'\s*$', s:get_statusline()) + + " %p: Percentage through file in lines as in CTRL-G. + " %P: Percentage through file of displayed window. + set statusline=%p,%P + 0 + call assert_match('^0,Top\s*$', s:get_statusline()) + norm G + call assert_match('^100,Bot\s*$', s:get_statusline()) + 180 + " Don't check the exact percentage as it depends on the window size + call assert_match('^90,\(Top\|Bot\|\d\+%\)\s*$', s:get_statusline()) + + " %q: "[Quickfix List]", "[Location List]" or empty. + set statusline=%q + call assert_match('^\s*$', s:get_statusline()) + copen + call assert_match('^\[Quickfix List\]\s*$', s:get_statusline()) + cclose + lexpr getline(1, 2) + lopen + call assert_match('^\[Location List\]\s*$', s:get_statusline()) + lclose + + " %r: Readonly flag, text is "[RO]". + " %R: Readonly flag, text is ",RO". + set statusline=%r,%R + call assert_match('^,\s*$', s:get_statusline()) + help + call assert_match('^\[RO\],RO\s*$', s:get_statusline()) + helpclose + + " %t: File name (tail) of file in the buffer. + set statusline=%t + call assert_match('^Xstatusline\s*$', s:get_statusline()) + + " %v: Virtual column number. + " %V: Virtual column number as -{num}. Not displayed if equal to 'c'. + call cursor(180, 2) + set statusline=%v,%V + call assert_match('^2,\s*$', s:get_statusline()) + set virtualedit=all + norm 10| + call assert_match('^10,-10\s*$', s:get_statusline()) + set virtualedit& + + " %w: Preview window flag, text is "[Preview]". + " %W: Preview window flag, text is ",PRV". + set statusline=%w%W + call assert_match('^\s*$', s:get_statusline()) + pedit + wincmd j + call assert_match('^\[Preview\],PRV\s*$', s:get_statusline()) + pclose + + " %y: Type of file in the buffer, e.g., "[vim]". See 'filetype'. + " %Y: Type of file in the buffer, e.g., ",VIM". See 'filetype'. + set statusline=%y\ %Y + call assert_match('^\s*$', s:get_statusline()) + setfiletype vim + call assert_match('^\[vim\] VIM\s*$', s:get_statusline()) + + " %=: Separation point between left and right aligned items. + set statusline=foo%=bar + call assert_match('^foo\s\+bar\s*$', s:get_statusline()) + + " Test min/max width, leading zeroes, left/right justify. + set statusline=%04B + call cursor(180, 2) + call assert_match('^0038\s*$', s:get_statusline()) + set statusline=#%4B# + call assert_match('^# 38#\s*$', s:get_statusline()) + set statusline=#%-4B# + call assert_match('^#38 #\s*$', s:get_statusline()) + set statusline=%.6f + call assert_match('^<sline\s*$', s:get_statusline()) + + " %<: Where to truncate. + exe 'set statusline=a%<b' . repeat('c', 1000) . 'd' + call assert_match('^a<c*d$', s:get_statusline()) + exe 'set statusline=a' . repeat('b', 1000) . '%<c' + call assert_match('^ab*>$', s:get_statusline()) + + "%{: Evaluate expression between '%{' and '}' and substitute result. + syntax on + set statusline=%{SyntaxItem()} + call assert_match('^vimNumber\s*$', s:get_statusline()) + s/^/"/ + call assert_match('^vimLineComment\s*$', s:get_statusline()) + syntax off + + "%(: Start of item group. + set statusline=ab%(cd%q%)de + call assert_match('^abde\s*$', s:get_statusline()) + copen + call assert_match('^abcd\[Quickfix List\1]de\s*$', s:get_statusline()) + cclose + + " %#: Set highlight group. The name must follow and then a # again. + set statusline=ab%#Todo#cd%#Error#ef + call assert_match('^abcdef\s*$', s:get_statusline()) + let sa1=screenattr(&lines - 1, 1) + let sa2=screenattr(&lines - 1, 3) + let sa3=screenattr(&lines - 1, 5) + call assert_notequal(sa1, sa2) + call assert_notequal(sa1, sa3) + call assert_notequal(sa2, sa3) + call assert_equal(sa1, screenattr(&lines - 1, 2)) + call assert_equal(sa2, screenattr(&lines - 1, 4)) + call assert_equal(sa3, screenattr(&lines - 1, 6)) + call assert_equal(sa3, screenattr(&lines - 1, 7)) + + " %*: Set highlight group to User{N} + set statusline=a%1*b%0*c + call assert_match('^abc\s*$', s:get_statusline()) + let sa1=screenattr(&lines - 1, 1) + let sa2=screenattr(&lines - 1, 2) + let sa3=screenattr(&lines - 1, 3) + call assert_equal(sa1, sa3) + call assert_notequal(sa1, sa2) + + " %%: a percent sign. + set statusline=10%% + call assert_match('^10%\s*$', s:get_statusline()) + + " %!: evaluated expression is used as the option value + set statusline=%!2*3+1 + call assert_match('7\s*$', s:get_statusline()) + + " Check statusline in current and non-current window + " with the 'fillchars' option. + set fillchars=stl:^,stlnc:=,vert:\|,fold:-,diff:- + vsplit + set statusline=x%=y + call assert_match('^x^\+y^x=\+y$', s:get_statusline()) + set fillchars& + close + + %bw! + call delete('Xstatusline') + set statusline& + set laststatus& + set splitbelow& +endfunc diff --git a/src/nvim/testdir/test_substitute.vim b/src/nvim/testdir/test_substitute.vim index e2b6de03c3..75ce24de46 100644 --- a/src/nvim/testdir/test_substitute.vim +++ b/src/nvim/testdir/test_substitute.vim @@ -39,3 +39,285 @@ function! Test_multiline_subst() call assert_equal('xxxxx', getline(13)) enew! endfunction + +function! Test_substitute_variants() + " Validate that all the 2-/3-letter variants which embed the flags into the + " command name actually work. + enew! + let ln = 'Testing string' + let variants = [ + \ { 'cmd': ':s/Test/test/c', 'exp': 'testing string', 'prompt': 'y' }, + \ { 'cmd': ':s/foo/bar/ce', 'exp': ln }, + \ { 'cmd': ':s/t/r/cg', 'exp': 'Tesring srring', 'prompt': 'a' }, + \ { 'cmd': ':s/t/r/ci', 'exp': 'resting string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/cI', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/cn', 'exp': ln }, + \ { 'cmd': ':s/t/r/cp', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/cl', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/t/r/gc', 'exp': 'Tesring srring', 'prompt': 'a' }, + \ { 'cmd': ':s/foo/bar/ge', 'exp': ln }, + \ { 'cmd': ':s/t/r/g', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/gi', 'exp': 'resring srring' }, + \ { 'cmd': ':s/t/r/gI', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/gn', 'exp': ln }, + \ { 'cmd': ':s/t/r/gp', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/gl', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s//r/gr', 'exp': 'Testr strr' }, + \ { 'cmd': ':s/t/r/ic', 'exp': 'resting string', 'prompt': 'y' }, + \ { 'cmd': ':s/foo/bar/ie', 'exp': ln }, + \ { 'cmd': ':s/t/r/i', 'exp': 'resting string' }, + \ { 'cmd': ':s/t/r/iI', 'exp': 'Tesring string' }, + \ { 'cmd': ':s/t/r/in', 'exp': ln }, + \ { 'cmd': ':s/t/r/ip', 'exp': 'resting string' }, + \ { 'cmd': ':s//r/ir', 'exp': 'Testr string' }, + \ { 'cmd': ':s/t/r/Ic', 'exp': 'Tesring string', 'prompt': 'y' }, + \ { 'cmd': ':s/foo/bar/Ie', 'exp': ln }, + \ { 'cmd': ':s/t/r/Ig', 'exp': 'Tesring srring' }, + \ { 'cmd': ':s/t/r/Ii', 'exp': 'resting string' }, + \ { 'cmd': ':s/t/r/I', 'exp': 'Tesring string' }, + \ { 'cmd': ':s/t/r/Ip', 'exp': 'Tesring string' }, + \ { 'cmd': ':s/t/r/Il', 'exp': 'Tesring string' }, + \ { 'cmd': ':s//r/Ir', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rc', 'exp': 'Testr string', 'prompt': 'y' }, + \ { 'cmd': ':s//r/rg', 'exp': 'Testr strr' }, + \ { 'cmd': ':s//r/ri', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rI', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rn', 'exp': 'Testing string' }, + \ { 'cmd': ':s//r/rp', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/rl', 'exp': 'Testr string' }, + \ { 'cmd': ':s//r/r', 'exp': 'Testr string' }, + \] + + for var in variants + for run in [1, 2] + let cmd = var.cmd + if run == 2 && cmd =~ "/.*/.*/." + " Change :s/from/to/{flags} to :s{flags} + let cmd = substitute(cmd, '/.*/', '', '') + endif + call setline(1, [ln]) + let msg = printf('using "%s"', cmd) + let @/='ing' + let v:errmsg = '' + call feedkeys(cmd . "\<CR>" . get(var, 'prompt', ''), 'ntx') + " No error should exist (matters for testing e flag) + call assert_equal('', v:errmsg, msg) + call assert_equal(var.exp, getline('.'), msg) + endfor + endfor +endfunction + +func Test_substitute_repeat() + " This caused an invalid memory access. + split Xfile + s/^/x + call feedkeys("Qsc\<CR>y", 'tx') + bwipe! +endfunc + +" Tests for *sub-replace-special* and *sub-replace-expression* on :substitute. + +" Execute a list of :substitute command tests +func Run_SubCmd_Tests(tests) + enew! + for t in a:tests + let start = line('.') + 1 + let end = start + len(t[2]) - 1 + exe "normal o" . t[0] + call cursor(start, 1) + exe t[1] + call assert_equal(t[2], getline(start, end), t[1]) + endfor + enew! +endfunc + +func Test_sub_cmd_1() + set magic + set cpo& + + " List entry format: [input, cmd, output] + let tests = [['A', 's/A/&&/', ['AA']], + \ ['B', 's/B/\&/', ['&']], + \ ['C123456789', 's/C\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\0\9\8\7\6\5\4\3\2\1/', ['C123456789987654321']], + \ ['D', 's/D/d/', ['d']], + \ ['E', 's/E/~/', ['d']], + \ ['F', 's/F/\~/', ['~']], + \ ['G', 's/G/\ugg/', ['Gg']], + \ ['H', 's/H/\Uh\Eh/', ['Hh']], + \ ['I', 's/I/\lII/', ['iI']], + \ ['J', 's/J/\LJ\EJ/', ['jJ']], + \ ['K', 's/K/\Uk\ek/', ['Kk']], + \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']], + \ ['mMm', 's/M/\r/', ['m', 'm']], + \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']], + \ ['oOo', 's/O/\n/', ["o\no"]], + \ ['pPp', 's/P/\b/', ["p\<C-H>p"]], + \ ['qQq', 's/Q/\t/', ["q\tq"]], + \ ['rRr', 's/R/\\/', ['r\r']], + \ ['sSs', 's/S/\c/', ['scs']], + \ ['tTt', "s/T/\<C-V>\<C-J>/", ["t\<C-V>\<C-J>t"]], + \ ['U', 's/U/\L\uuUu\l\EU/', ['UuuU']], + \ ['V', 's/V/\U\lVvV\u\Ev/', ['vVVv']] + \ ] + call Run_SubCmd_Tests(tests) +endfunc + +func Test_sub_cmd_2() + set nomagic + set cpo& + + " List entry format: [input, cmd, output] + let tests = [['A', 's/A/&&/', ['&&']], + \ ['B', 's/B/\&/', ['B']], + \ ['C123456789', 's/\mC\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\0\9\8\7\6\5\4\3\2\1/', ['C123456789987654321']], + \ ['D', 's/D/d/', ['d']], + \ ['E', 's/E/~/', ['~']], + \ ['F', 's/F/\~/', ['~']], + \ ['G', 's/G/\ugg/', ['Gg']], + \ ['H', 's/H/\Uh\Eh/', ['Hh']], + \ ['I', 's/I/\lII/', ['iI']], + \ ['J', 's/J/\LJ\EJ/', ['jJ']], + \ ['K', 's/K/\Uk\ek/', ['Kk']], + \ ['lLl', "s/L/\<C-V>\<C-M>/", ["l\<C-V>", 'l']], + \ ['mMm', 's/M/\r/', ['m', 'm']], + \ ['nNn', "s/N/\\\<C-V>\<C-M>/", ["n\<C-V>", 'n']], + \ ['oOo', 's/O/\n/', ["o\no"]], + \ ['pPp', 's/P/\b/', ["p\<C-H>p"]], + \ ['qQq', 's/Q/\t/', ["q\tq"]], + \ ['rRr', 's/R/\\/', ['r\r']], + \ ['sSs', 's/S/\c/', ['scs']], + \ ['tTt', "s/T/\<C-V>\<C-J>/", ["t\<C-V>\<C-J>t"]], + \ ['U', 's/U/\L\uuUu\l\EU/', ['UuuU']], + \ ['V', 's/V/\U\lVvV\u\Ev/', ['vVVv']] + \ ] + call Run_SubCmd_Tests(tests) +endfunc + +func Test_sub_cmd_3() + set nomagic + set cpo& + + " List entry format: [input, cmd, output] + let tests = [['aAa', "s/A/\\='\\'/", ['a\a']], + \ ['bBb', "s/B/\\='\\\\'/", ['b\\b']], + \ ['cCc', "s/C/\\='\<C-V>\<C-M>'/", ["c\<C-V>", 'c']], + \ ['dDd', "s/D/\\='\\\<C-V>\<C-M>'/", ["d\\\<C-V>", 'd']], + \ ['eEe', "s/E/\\='\\\\\<C-V>\<C-M>'/", ["e\\\\\<C-V>", 'e']], + \ ['fFf', "s/F/\\='\r'/", ['f', 'f']], + \ ['gGg', "s/G/\\='\<C-V>\<C-J>'/", ["g\<C-V>", 'g']], + \ ['hHh', "s/H/\\='\\\<C-V>\<C-J>'/", ["h\\\<C-V>", 'h']], + \ ['iIi', "s/I/\\='\\\\\<C-V>\<C-J>'/", ["i\\\\\<C-V>", 'i']], + \ ['jJj', "s/J/\\='\n'/", ['j', 'j']], + \ ['kKk', 's/K/\="\r"/', ['k', 'k']], + \ ['lLl', 's/L/\="\n"/', ['l', 'l']] + \ ] + call Run_SubCmd_Tests(tests) +endfunc + +" Test for submatch() on :substitue. +func Test_sub_cmd_4() + set magic& + set cpo& + + " List entry format: [input, cmd, output] + let tests = [ ['aAa', "s/A/\\=substitute(submatch(0), '.', '\\', '')/", + \ ['a\a']], + \ ['bBb', "s/B/\\=substitute(submatch(0), '.', '\\', '')/", + \ ['b\b']], + \ ['cCc', "s/C/\\=substitute(submatch(0), '.', '\<C-V>\<C-M>', '')/", + \ ["c\<C-V>", 'c']], + \ ['dDd', "s/D/\\=substitute(submatch(0), '.', '\\\<C-V>\<C-M>', '')/", + \ ["d\<C-V>", 'd']], + \ ['eEe', "s/E/\\=substitute(submatch(0), '.', '\\\\\<C-V>\<C-M>', '')/", + \ ["e\\\<C-V>", 'e']], + \ ['fFf', "s/F/\\=substitute(submatch(0), '.', '\\r', '')/", + \ ['f', 'f']], + \ ['gGg', 's/G/\=substitute(submatch(0), ".", "\<C-V>\<C-J>", "")/', + \ ["g\<C-V>", 'g']], + \ ['hHh', 's/H/\=substitute(submatch(0), ".", "\\\<C-V>\<C-J>", "")/', + \ ["h\<C-V>", 'h']], + \ ['iIi', 's/I/\=substitute(submatch(0), ".", "\\\\\<C-V>\<C-J>", "")/', + \ ["i\\\<C-V>", 'i']], + \ ['jJj', "s/J/\\=substitute(submatch(0), '.', '\\n', '')/", + \ ['j', 'j']], + \ ['kKk', "s/K/\\=substitute(submatch(0), '.', '\\r', '')/", + \ ['k', 'k']], + \ ['lLl', "s/L/\\=substitute(submatch(0), '.', '\\n', '')/", + \ ['l', 'l']], + \ ] + call Run_SubCmd_Tests(tests) +endfunc + +func Test_sub_cmd_5() + set magic& + set cpo& + + " List entry format: [input, cmd, output] + let tests = [ ['A123456789', 's/A\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\=submatch(0) . submatch(9) . submatch(8) . submatch(7) . submatch(6) . submatch(5) . submatch(4) . submatch(3) . submatch(2) . submatch(1)/', ['A123456789987654321']], + \ ['B123456789', 's/B\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)\(.\)/\=string([submatch(0, 1), submatch(9, 1), submatch(8, 1), submatch(7, 1), submatch(6, 1), submatch(5, 1), submatch(4, 1), submatch(3, 1), submatch(2, 1), submatch(1, 1)])/', ["[['B123456789'], ['9'], ['8'], ['7'], ['6'], ['5'], ['4'], ['3'], ['2'], ['1']]"]], + \ ] + call Run_SubCmd_Tests(tests) +endfunc + +" Test for *:s%* on :substitute. +func Test_sub_cmd_6() + throw "skipped: Nvim removed POSIX-related 'cpoptions' flags" + set magic& + set cpo+=/ + + " List entry format: [input, cmd, output] + let tests = [ ['A', 's/A/a/', ['a']], + \ ['B', 's/B/%/', ['a']], + \ ] + call Run_SubCmd_Tests(tests) + + set cpo-=/ + let tests = [ ['C', 's/C/c/', ['c']], + \ ['D', 's/D/%/', ['%']], + \ ] + call Run_SubCmd_Tests(tests) + + set cpo& +endfunc + +" Test for :s replacing \n with line break. +func Test_sub_cmd_7() + set magic& + set cpo& + + " List entry format: [input, cmd, output] + let tests = [ ["A\<C-V>\<C-M>A", 's/A./\=submatch(0)/', ['A', 'A']], + \ ["B\<C-V>\<C-J>B", 's/B./\=submatch(0)/', ['B', 'B']], + \ ["C\<C-V>\<C-J>C", 's/C./\=strtrans(string(submatch(0, 1)))/', [strtrans("['C\<C-J>']C")]], + \ ["D\<C-V>\<C-J>\nD", 's/D.\nD/\=strtrans(string(submatch(0, 1)))/', [strtrans("['D\<C-J>', 'D']")]], + \ ["E\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>\n\<C-V>\<C-J>E", 's/E\_.\{-}E/\=strtrans(string(submatch(0, 1)))/', [strtrans("['E\<C-J>', '\<C-J>', '\<C-J>', '\<C-J>', '\<C-J>E']")]], + \ ] + call Run_SubCmd_Tests(tests) + + exe "normal oQ\nQ\<Esc>k" + call assert_fails('s/Q[^\n]Q/\=submatch(0)."foobar"/', 'E486') + enew! +endfunc + +func TitleString() + let check = 'foo' =~ 'bar' + return "" +endfunc + +func Test_sub_cmd_8() + set titlestring=%{TitleString()} + + enew! + call append(0, ['', 'test_one', 'test_two']) + call cursor(1,1) + /^test_one/s/.*/\="foo\nbar"/ + call assert_equal('foo', getline(2)) + call assert_equal('bar', getline(3)) + call feedkeys(':/^test_two/s/.*/\="foo\nbar"/c', "t") + call feedkeys("\<CR>y", "xt") + call assert_equal('foo', getline(4)) + call assert_equal('bar', getline(5)) + + enew! + set titlestring& +endfunc diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim index 6c084dd2a7..8465fe7d45 100644 --- a/src/nvim/testdir/test_syntax.vim +++ b/src/nvim/testdir/test_syntax.vim @@ -1,5 +1,7 @@ " Test for syntax and syntax iskeyword option +source view_util.vim + func GetSyntaxItem(pat) let c = '' let a = ['a', getreg('a'), getregtype('a')] @@ -152,9 +154,231 @@ func Test_syntax_completion() call feedkeys(":syn sync \<C-A>\<C-B>\"\<CR>", 'tx') call assert_equal('"syn sync ccomment clear fromstart linebreaks= linecont lines= match maxlines= minlines= region', @:) + " Check that clearing "Aap" avoids it showing up before Boolean. + hi Aap ctermfg=blue + call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx') + call assert_match('^"syn list Aap Boolean Character ', @:) + hi clear Aap + call feedkeys(":syn list \<C-A>\<C-B>\"\<CR>", 'tx') call assert_match('^"syn list Boolean Character ', @:) call feedkeys(":syn match \<C-A>\<C-B>\"\<CR>", 'tx') call assert_match('^"syn match Boolean Character ', @:) -endfunc
\ No newline at end of file +endfunc + +func Test_syntax_arg_skipped() + syn clear + syntax case ignore + if 0 + syntax case match + endif + call assert_match('case ignore', execute('syntax case')) + + syn keyword Foo foo + call assert_match('Foo', execute('syntax')) + syn clear + call assert_match('case match', execute('syntax case')) + call assert_notmatch('Foo', execute('syntax')) + + if has('conceal') + syn clear + syntax conceal on + if 0 + syntax conceal off + endif + call assert_match('conceal on', execute('syntax conceal')) + syn clear + call assert_match('conceal off', execute('syntax conceal')) + endif + + syntax conceal on + syntax conceal off + call assert_match('conceal off', execute('syntax conceal')) + + syntax region Bar start=/</ end=/>/ + if 0 + syntax region NotTest start=/</ end=/>/ contains=@Spell + endif + call assert_match('Bar', execute('syntax')) + call assert_notmatch('NotTest', execute('syntax')) + call assert_notmatch('Spell', execute('syntax')) + + hi Foo ctermfg=blue + let a = execute('hi Foo') + if 0 + syntax rest + endif + call assert_equal(a, execute('hi Foo')) + hi clear Bar + hi clear Foo + + set ft=tags + syn off + if 0 + syntax enable + endif + call assert_match('No Syntax items defined', execute('syntax')) + syntax enable + call assert_match('tagComment', execute('syntax')) + set ft= + + syn clear + if 0 + syntax include @Spell nothing + endif + call assert_notmatch('Spell', execute('syntax')) + + syn clear + syn iskeyword 48-57,$,_ + call assert_match('48-57,$,_', execute('syntax iskeyword')) + if 0 + syn clear + syn iskeyword clear + endif + call assert_match('48-57,$,_', execute('syntax iskeyword')) + syn iskeyword clear + call assert_match('not set', execute('syntax iskeyword')) + syn iskeyword 48-57,$,_ + syn clear + call assert_match('not set', execute('syntax iskeyword')) + + syn clear + syn keyword Foo foo + if 0 + syn keyword NotAdded bar + endif + call assert_match('Foo', execute('syntax')) + call assert_notmatch('NotAdded', execute('highlight')) + + syn clear + syn keyword Foo foo + call assert_match('Foo', execute('syntax')) + call assert_match('Foo', execute('syntax list')) + call assert_notmatch('Foo', execute('if 0 | syntax | endif')) + call assert_notmatch('Foo', execute('if 0 | syntax list | endif')) + + syn clear + syn match Fopi /asdf/ + if 0 + syn match Fopx /asdf/ + endif + call assert_match('Fopi', execute('syntax')) + call assert_notmatch('Fopx', execute('syntax')) + + syn clear + syn spell toplevel + call assert_match('spell toplevel', execute('syntax spell')) + if 0 + syn spell notoplevel + endif + call assert_match('spell toplevel', execute('syntax spell')) + syn spell notoplevel + call assert_match('spell notoplevel', execute('syntax spell')) + syn spell default + call assert_match('spell default', execute('syntax spell')) + + syn clear + if 0 + syntax cluster Spell + endif + call assert_notmatch('Spell', execute('syntax')) + + syn clear + syn keyword Foo foo + syn sync ccomment + syn sync maxlines=5 + if 0 + syn sync maxlines=11 + endif + call assert_match('on C-style comments', execute('syntax sync')) + call assert_match('maximal 5 lines', execute('syntax sync')) + syn sync clear + if 0 + syn sync ccomment + endif + call assert_notmatch('on C-style comments', execute('syntax sync')) + + syn clear +endfunc + +func Test_invalid_arg() + call assert_fails('syntax case asdf', 'E390:') + call assert_fails('syntax conceal asdf', 'E390:') + call assert_fails('syntax spell asdf', 'E390:') +endfunc + +func Test_syn_sync() + syntax region HereGroup start=/this/ end=/that/ + syntax sync match SyncHere grouphere HereGroup "pattern" + call assert_match('SyncHere', execute('syntax sync')) + syn sync clear + call assert_notmatch('SyncHere', execute('syntax sync')) + syn clear +endfunc + +func Test_syn_clear() + syntax keyword Foo foo + syntax keyword Bar tar + call assert_match('Foo', execute('syntax')) + call assert_match('Bar', execute('syntax')) + call assert_equal('Foo', synIDattr(hlID("Foo"), "name")) + syn clear Foo + call assert_notmatch('Foo', execute('syntax')) + call assert_match('Bar', execute('syntax')) + call assert_equal('Foo', synIDattr(hlID("Foo"), "name")) + syn clear Foo Bar + call assert_notmatch('Foo', execute('syntax')) + call assert_notmatch('Bar', execute('syntax')) + hi clear Foo + call assert_equal('Foo', synIDattr(hlID("Foo"), "name")) + hi clear Bar +endfunc + +func Test_invalid_name() + syn clear + syn keyword Nop yes + call assert_fails("syntax keyword Wr\x17ong bar", 'E669:') + syntax keyword @Wrong bar + call assert_match('W18:', execute('1messages')) + syn clear + hi clear Nop + hi clear @Wrong +endfunc + + +func Test_conceal() + if !has('conceal') + return + endif + + new + call setline(1, ['', '123456']) + syn match test23 "23" conceal cchar=X + syn match test45 "45" conceal + + set conceallevel=0 + call assert_equal('123456 ', ScreenLines(2, 7)[0]) + call assert_equal([[0, '', 0], [0, '', 0], [0, '', 0], [0, '', 0], [0, '', 0], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)')) + + set conceallevel=1 + call assert_equal('1X 6 ', ScreenLines(2, 7)[0]) + call assert_equal([[0, '', 0], [1, 'X', 1], [1, 'X', 1], [1, ' ', 2], [1, ' ', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)')) + + set conceallevel=1 + set listchars=conceal:Y + call assert_equal([[0, '', 0], [1, 'X', 1], [1, 'X', 1], [1, 'Y', 2], [1, 'Y', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)')) + call assert_equal('1XY6 ', ScreenLines(2, 7)[0]) + + set conceallevel=2 + call assert_match('1X6 ', ScreenLines(2, 7)[0]) + call assert_equal([[0, '', 0], [1, 'X', 1], [1, 'X', 1], [1, '', 2], [1, '', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)')) + + set conceallevel=3 + call assert_match('16 ', ScreenLines(2, 7)[0]) + call assert_equal([[0, '', 0], [1, '', 1], [1, '', 1], [1, '', 2], [1, '', 2], [0, '', 0]], map(range(1, 6), 'synconcealed(2, v:val)')) + + syn clear + set conceallevel& + bw! +endfunc diff --git a/src/nvim/testdir/test_system.vim b/src/nvim/testdir/test_system.vim new file mode 100644 index 0000000000..ce9d110d82 --- /dev/null +++ b/src/nvim/testdir/test_system.vim @@ -0,0 +1,92 @@ +" Tests for system() and systemlist() + +function! Test_System() + if !executable('echo') || !executable('cat') || !executable('wc') + return + endif + let out = system('echo 123') + " On Windows we may get a trailing space. + if out != "123 \n" + call assert_equal("123\n", out) + endif + + let out = systemlist('echo 123') + " On Windows we may get a trailing space and CR. + if out != ["123 \r"] + call assert_equal(['123'], out) + endif + + call assert_equal('123', system('cat', '123')) + call assert_equal(['123'], systemlist('cat', '123')) + call assert_equal(["as\<NL>df"], systemlist('cat', ["as\<NL>df"])) + + new Xdummy + call setline(1, ['asdf', "pw\<NL>er", 'xxxx']) + let out = system('wc -l', bufnr('%')) + " On OS/X we get leading spaces + let out = substitute(out, '^ *', '', '') + call assert_equal("3\n", out) + + let out = systemlist('wc -l', bufnr('%')) + " On Windows we may get a trailing CR. + if out != ["3\r"] + " On OS/X we get leading spaces + if type(out) == v:t_list + let out[0] = substitute(out[0], '^ *', '', '') + endif + call assert_equal(['3'], out) + endif + + let out = systemlist('cat', bufnr('%')) + " On Windows we may get a trailing CR. + if out != ["asdf\r", "pw\<NL>er\r", "xxxx\r"] + call assert_equal(['asdf', "pw\<NL>er", 'xxxx'], out) + endif + bwipe! + + call assert_fails('call system("wc -l", 99999)', 'E86:') +endfunction + +function! Test_system_exmode() + if has('unix') " echo $? only works on Unix + let cmd = ' -es --headless -u NONE -c "source Xscript" +q; echo "result=$?"' + " Need to put this in a script, "catch" isn't found after an unknown + " function. + call writefile(['try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript') + let a = system(v:progpath . cmd) + call assert_match('result=0', a) + call assert_equal(0, v:shell_error) + endif + + " Error before try does set error flag. + call writefile(['call nosuchfunction()', 'try', 'call doesnotexist()', 'catch', 'endtry'], 'Xscript') + if has('unix') " echo $? only works on Unix + let a = system(v:progpath . cmd) + call assert_notequal('0', a[0]) + endif + + let cmd = ' -es --headless -u NONE -c "source Xscript" +q' + let a = system(v:progpath . cmd) + call assert_notequal(0, v:shell_error) + call delete('Xscript') + + if has('unix') " echo $? only works on Unix + let cmd = ' -es --headless -u NONE -c "call doesnotexist()" +q; echo $?' + let a = system(v:progpath. cmd) + call assert_notequal(0, a[0]) + endif + + let cmd = ' -es --headless -u NONE -c "call doesnotexist()" +q' + let a = system(v:progpath. cmd) + call assert_notequal(0, v:shell_error) + + if has('unix') " echo $? only works on Unix + let cmd = ' -es --headless -u NONE -c "call doesnotexist()|let a=1" +q; echo $?' + let a = system(v:progpath. cmd) + call assert_notequal(0, a[0]) + endif + + let cmd = ' -es --headless -u NONE -c "call doesnotexist()|let a=1" +q' + let a = system(v:progpath. cmd) + call assert_notequal(0, v:shell_error) +endfunc diff --git a/src/nvim/testdir/test_tab.vim b/src/nvim/testdir/test_tab.vim new file mode 100644 index 0000000000..b847dbd962 --- /dev/null +++ b/src/nvim/testdir/test_tab.vim @@ -0,0 +1,45 @@ + +" Tests for "r<Tab>" with 'smarttab' and 'expandtab' set/not set. +" Also test that dv_ works correctly +func Test_smarttab() + enew! + set smarttab expandtab ts=8 sw=4 + " make sure that backspace works, no matter what termcap is used + exe "set t_kD=\<C-V>x7f t_kb=\<C-V>x08" + call append(0, ['start text', + \ "\t\tsome test text", + \ 'test text', + \ "\t\tother test text", + \ ' a cde', + \ ' f ghi', + \ 'test text', + \ ' Second line beginning with whitespace' + \ ]) + call cursor(1, 1) + exe "normal /some\<CR>" + exe "normal r\t" + call assert_equal("\t\t ome test text", getline('.')) + set noexpandtab + exe "normal /other\<CR>" + exe "normal r\t" + call assert_equal("\t\t ther test text", getline('.')) + + " Test replacing with Tabs and then backspacing to undo it + exe "normal j0wR\t\t\t\<BS>\<BS>\<BS>" + call assert_equal(" a cde", getline('.')) + " Test replacing with Tabs + exe "normal j0wR\t\t\t" + call assert_equal(" \t\thi", getline('.')) + + " Test that copyindent works with expandtab set + set expandtab smartindent copyindent ts=8 sw=8 sts=8 + exe "normal jo{\<CR>x" + call assert_equal('{', getline(line('.') - 1)) + call assert_equal(' x', getline('.')) + set nosol + exe "normal /Second line/\<CR>" + exe "normal fwdv_" + call assert_equal(' with whitespace', getline('.')) + enew! + set expandtab& smartindent& copyindent& ts& sw& sts& +endfunc diff --git a/src/nvim/testdir/test_tabpage.vim b/src/nvim/testdir/test_tabpage.vim index 33139fcda0..180563ebbd 100644 --- a/src/nvim/testdir/test_tabpage.vim +++ b/src/nvim/testdir/test_tabpage.vim @@ -473,5 +473,24 @@ func Test_tabnext_on_buf_unload2() endwhile endfunc +func Test_close_on_quitpre() + " This once caused a crash + edit Xtest + new + only + set bufhidden=delete + au QuitPre <buffer> close + tabnew tab1 + tabnew tab2 + 1tabn + q! + call assert_equal(1, tabpagenr()) + call assert_equal(2, tabpagenr('$')) + " clean up + while tabpagenr('$') > 1 + bwipe! + endwhile + buf Xtest +endfunc " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim index 0d697b3f3e..d5ce193bc6 100644 --- a/src/nvim/testdir/test_tagjump.vim +++ b/src/nvim/testdir/test_tagjump.vim @@ -95,4 +95,41 @@ function Test_keyword_jump() call delete('Xinclude') endfunction +" Test for jumping to a tag with 'hidden' set, with symbolic link in path of +" tag. This only works for Unix, because of the symbolic link. +func Test_tag_symbolic() + if !has('unix') + return + endif + set hidden + call delete("Xtest.dir", "rf") + call system("ln -s . Xtest.dir") + " Create a tags file with the current directory name inserted. + call writefile([ + \ "SECTION_OFF " . getcwd() . "/Xtest.dir/Xtest.c /^#define SECTION_OFF 3$/", + \ '', + \ ], 'Xtags') + call writefile(['#define SECTION_OFF 3', + \ '#define NUM_SECTIONS 3'], 'Xtest.c') + + " Try jumping to a tag, but with a path that contains a symbolic link. When + " wrong, this will give the ATTENTION message. The next space will then be + " eaten by hit-return, instead of moving the cursor to 'd'. + set tags=Xtags + enew! + call append(0, 'SECTION_OFF') + call cursor(1,1) + exe "normal \<C-]> " + call assert_equal('Xtest.c', expand('%:t')) + call assert_equal(2, col('.')) + + set hidden& + set tags& + enew! + call delete('Xtags') + call delete('Xtest.c') + call delete("Xtest.dir", "rf") + %bwipe! +endfunc + " vim: shiftwidth=2 sts=2 expandtab diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim new file mode 100644 index 0000000000..999566c6ac --- /dev/null +++ b/src/nvim/testdir/test_textformat.vim @@ -0,0 +1,168 @@ +" Tests for the various 'formatoptions' settings +func Test_text_format() + enew! + + setl noai tw=2 fo=t + call append('$', [ + \ '{', + \ ' ', + \ '', + \ '}']) + exe "normal /^{/+1\n0" + normal gRa b + let lnum = line('.') + call assert_equal([ + \ 'a', + \ 'b'], getline(lnum - 1, lnum)) + + normal ggdG + setl ai tw=2 fo=tw + call append('$', [ + \ '{', + \ 'a b ', + \ '', + \ 'a ', + \ '}']) + exe "normal /^{/+1\n0" + normal gqgqjjllab + let lnum = line('.') + call assert_equal([ + \ 'a ', + \ 'b ', + \ '', + \ 'a ', + \ 'b'], getline(lnum - 4, lnum)) + + normal ggdG + setl tw=3 fo=t + call append('$', [ + \ '{', + \ "a \<C-A>", + \ '}']) + exe "normal /^{/+1\n0" + exe "normal gqgqo\na \<C-V>\<C-A>" + let lnum = line('.') + call assert_equal([ + \ 'a', + \ "\<C-A>", + \ '', + \ 'a', + \ "\<C-A>"], getline(lnum - 4, lnum)) + + normal ggdG + setl tw=2 fo=tcq1 comments=:# + call append('$', [ + \ '{', + \ 'a b', + \ '#a b', + \ '}']) + exe "normal /^{/+1\n0" + exe "normal gqgqjgqgqo\na b\n#a b" + let lnum = line('.') + call assert_equal([ + \ 'a b', + \ '#a b', + \ '', + \ 'a b', + \ '#a b'], getline(lnum - 4, lnum)) + + normal ggdG + setl tw=5 fo=tcn comments=:# + call append('$', [ + \ '{', + \ ' 1 a', + \ '# 1 a', + \ '}']) + exe "normal /^{/+1\n0" + exe "normal A b\<Esc>jA b" + let lnum = line('.') + call assert_equal([ + \ ' 1 a', + \ ' b', + \ '# 1 a', + \ '# b'], getline(lnum - 3, lnum)) + + normal ggdG + setl tw=5 fo=t2a si + call append('$', [ + \ '{', + \ '', + \ ' x a', + \ ' b', + \ ' c', + \ '', + \ '}']) + exe "normal /^{/+3\n0" + exe "normal i \<Esc>A_" + let lnum = line('.') + call assert_equal([ + \ '', + \ ' x a', + \ ' b_', + \ ' c', + \ ''], getline(lnum - 2, lnum + 2)) + + normal ggdG + setl tw=5 fo=qn comments=:# + call append('$', [ + \ '{', + \ '# 1 a b', + \ '}']) + exe "normal /^{/+1\n5|" + normal gwap + call assert_equal(5, col('.')) + let lnum = line('.') + call assert_equal([ + \ '# 1 a', + \ '# b'], getline(lnum, lnum + 1)) + + normal ggdG + setl tw=5 fo=q2 comments=:# + call append('$', [ + \ '{', + \ '# x', + \ '# a b', + \ '}']) + exe "normal /^{/+1\n0" + normal gwap + let lnum = line('.') + call assert_equal([ + \ '# x a', + \ '# b'], getline(lnum, lnum + 1)) + + normal ggdG + setl tw& fo=a + call append('$', [ + \ '{', + \ ' 1aa', + \ ' 2bb', + \ '}']) + exe "normal /^{/+2\n0" + normal I^^ + call assert_equal('{ 1aa ^^2bb }', getline('.')) + + normal ggdG + setl tw=20 fo=an12wcq comments=s1:/*,mb:*,ex:*/ + call append('$', [ + \ '/* abc def ghi jkl ', + \ ' * mno pqr stu', + \ ' */']) + exe "normal /mno pqr/\n" + normal A vwx yz + let lnum = line('.') + call assert_equal([ + \ ' * mno pqr stu ', + \ ' * vwx yz', + \ ' */'], getline(lnum - 1, lnum + 1)) + + normal ggdG + setl tw=12 fo=tqnc comments=:# + call setline('.', '# 1 xxxxx') + normal A foobar + call assert_equal([ + \ '# 1 xxxxx', + \ '# foobar'], getline(1, 2)) + + setl ai& tw& fo& si& comments& + enew! +endfunc diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim index 630ae5d3a4..684f197f5f 100644 --- a/src/nvim/testdir/test_textobjects.vim +++ b/src/nvim/testdir/test_textobjects.vim @@ -4,7 +4,7 @@ if !has('textobjects') finish endif -function! CpoM(line, useM, expected) +func CpoM(line, useM, expected) new if a:useM @@ -28,16 +28,127 @@ function! CpoM(line, useM, expected) call assert_equal(getreg('"'), a:expected[2]) q! -endfunction +endfunc -function! Test_inner_block_without_cpo_M() +func Test_inner_block_without_cpo_M() call CpoM('(red \(blue) green)', 0, ['red \(blue', 'red \(blue', '']) -endfunction +endfunc -function! Test_inner_block_with_cpo_M_left_backslash() +func Test_inner_block_with_cpo_M_left_backslash() call CpoM('(red \(blue) green)', 1, ['red \(blue) green', 'blue', 'red \(blue) green']) -endfunction +endfunc -function! Test_inner_block_with_cpo_M_right_backslash() +func Test_inner_block_with_cpo_M_right_backslash() call CpoM('(red (blue\) green)', 1, ['red (blue\) green', 'blue\', 'red (blue\) green']) -endfunction +endfunc + +func Test_quote_selection_selection_exclusive() + new + call setline(1, "a 'bcde' f") + set selection=exclusive + exe "norm! fdvhi'y" + call assert_equal('bcde', @") + set selection&vim + bw! +endfunc + +" Tests for string and html text objects +func Test_string_html_objects() + enew! + + let t = '"wo\"rd\\" foo' + put =t + normal! da" + call assert_equal('foo', getline('.')) + + let t = "'foo' 'bar' 'piep'" + put =t + normal! 0va'a'rx + call assert_equal("xxxxxxxxxxxx'piep'", getline('.')) + + let t = "bla bla `quote` blah" + put =t + normal! 02f`da` + call assert_equal("bla bla blah", getline('.')) + + let t = 'out " in "noXno"' + put =t + normal! 0fXdi" + call assert_equal('out " in ""', getline('.')) + + let t = "\"'\" 'blah' rep 'buh'" + put =t + normal! 03f'vi'ry + call assert_equal("\"'\" 'blah'yyyyy'buh'", getline('.')) + + set quoteescape=+*- + let t = "bla `s*`d-`+++`l**` b`la" + put =t + normal! di` + call assert_equal("bla `` b`la", getline('.')) + + let t = 'voo "nah" sdf " asdf" sdf " sdf" sd' + put =t + normal! $F"va"oha"i"rz + call assert_equal('voo "zzzzzzzzzzzzzzzzzzzzzzzzzzzzsd', getline('.')) + + let t = "-<b>asdf<i>Xasdf</i>asdf</b>-" + put =t + normal! fXdit + call assert_equal('-<b>asdf<i></i>asdf</b>-', getline('.')) + + let t = "-<b>asdX<i>a<i />sdf</i>asdf</b>-" + put =t + normal! 0fXdit + call assert_equal('-<b></b>-', getline('.')) + + let t = "-<b>asdf<i>Xasdf</i>asdf</b>-" + put =t + normal! fXdat + call assert_equal('-<b>asdfasdf</b>-', getline('.')) + + let t = "-<b>asdX<i>as<b />df</i>asdf</b>-" + put =t + normal! 0fXdat + call assert_equal('--', getline('.')) + + let t = "-<b>\ninnertext object\n</b>" + put =t + normal! dit + call assert_equal('-<b></b>', getline('.')) + + set quoteescape& + enew! +endfunc + +" Tests for match() and matchstr() +func Test_match() + call assert_equal("b", matchstr("abcd", ".", 0, 2)) + call assert_equal("bc", matchstr("abcd", "..", 0, 2)) + call assert_equal("c", matchstr("abcd", ".", 2, 0)) + call assert_equal("a", matchstr("abcd", ".", 0, -1)) + call assert_equal(-1, match("abcd", ".", 0, 5)) + call assert_equal(0 , match("abcd", ".", 0, -1)) + call assert_equal(0 , match('abc', '.', 0, 1)) + call assert_equal(1 , match('abc', '.', 0, 2)) + call assert_equal(2 , match('abc', '.', 0, 3)) + call assert_equal(-1, match('abc', '.', 0, 4)) + call assert_equal(1 , match('abc', '.', 1, 1)) + call assert_equal(2 , match('abc', '.', 2, 1)) + call assert_equal(-1, match('abc', '.', 3, 1)) + call assert_equal(3 , match('abc', '$', 0, 1)) + call assert_equal(-1, match('abc', '$', 0, 2)) + call assert_equal(3 , match('abc', '$', 1, 1)) + call assert_equal(3 , match('abc', '$', 2, 1)) + call assert_equal(3 , match('abc', '$', 3, 1)) + call assert_equal(-1, match('abc', '$', 4, 1)) + call assert_equal(0 , match('abc', '\zs', 0, 1)) + call assert_equal(1 , match('abc', '\zs', 0, 2)) + call assert_equal(2 , match('abc', '\zs', 0, 3)) + call assert_equal(3 , match('abc', '\zs', 0, 4)) + call assert_equal(-1, match('abc', '\zs', 0, 5)) + call assert_equal(1 , match('abc', '\zs', 1, 1)) + call assert_equal(2 , match('abc', '\zs', 2, 1)) + call assert_equal(3 , match('abc', '\zs', 3, 1)) + call assert_equal(-1, match('abc', '\zs', 4, 1)) +endfunc diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim index d377062780..81ac2b6171 100644 --- a/src/nvim/testdir/test_timers.vim +++ b/src/nvim/testdir/test_timers.vim @@ -119,7 +119,7 @@ func Test_paused() let slept = WaitFor('g:val == 1') call assert_equal(1, g:val) if has('reltime') - call assert_inrange(0, 60, slept) + call assert_inrange(0, 100, slept) else call assert_inrange(0, 10, slept) endif diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim index 171618f6f7..2bc6073d52 100644 --- a/src/nvim/testdir/test_undo.vim +++ b/src/nvim/testdir/test_undo.vim @@ -265,3 +265,26 @@ func Test_undofile_earlier() call delete('Xfile') call delete('Xundofile') endfunc + +" Test for undo working properly when executing commands from a register. +" Also test this in an empty buffer. +func Test_cmd_in_reg_undo() + enew! + let @a="Ox\<Esc>jAy\<Esc>kdd" + edit +/^$ test_undo.vim + normal @au + call assert_equal(0, &modified) + return + new + normal @au + call assert_equal(0, &modified) + only! + let @a='' +endfunc + +func Test_redo_empty_line() + new + exe "norm\x16r\x160" + exe "norm." + bwipe! +endfunc diff --git a/src/nvim/testdir/test_unlet.vim b/src/nvim/testdir/test_unlet.vim index 96ba752d9f..3f06058d03 100644 --- a/src/nvim/testdir/test_unlet.vim +++ b/src/nvim/testdir/test_unlet.vim @@ -24,3 +24,7 @@ func Test_not_existing() call assert_true(v:exception =~ ':E108:') endtry endfunc + +func Test_unlet_fails() + call assert_fails('unlet v:["count"]', 'E46:') +endfunc diff --git a/src/nvim/testdir/test_utf8_comparisons.vim b/src/nvim/testdir/test_utf8_comparisons.vim new file mode 100644 index 0000000000..576e86142f --- /dev/null +++ b/src/nvim/testdir/test_utf8_comparisons.vim @@ -0,0 +1,95 @@ +" Tests for case-insensitive UTF-8 comparisons (utf_strnicmp() in mbyte.c) +" Also test "g~ap". + +if !has("multi_byte") + finish +endif + +function! Ch(a, op, b, expected) + call assert_equal(eval(printf('"%s" %s "%s"', a:a, a:op, a:b)), a:expected, + \ printf('"%s" %s "%s" should return %d', a:a, a:op, a:b, a:expected)) +endfunction + +function! Chk(a, b, result) + if a:result == 0 + call Ch(a:a, '==?', a:b, 1) + call Ch(a:a, '!=?', a:b, 0) + call Ch(a:a, '<=?', a:b, 1) + call Ch(a:a, '>=?', a:b, 1) + call Ch(a:a, '<?', a:b, 0) + call Ch(a:a, '>?', a:b, 0) + elseif a:result > 0 + call Ch(a:a, '==?', a:b, 0) + call Ch(a:a, '!=?', a:b, 1) + call Ch(a:a, '<=?', a:b, 0) + call Ch(a:a, '>=?', a:b, 1) + call Ch(a:a, '<?', a:b, 0) + call Ch(a:a, '>?', a:b, 1) + else + call Ch(a:a, '==?', a:b, 0) + call Ch(a:a, '!=?', a:b, 1) + call Ch(a:a, '<=?', a:b, 1) + call Ch(a:a, '>=?', a:b, 0) + call Ch(a:a, '<?', a:b, 1) + call Ch(a:a, '>?', a:b, 0) + endif +endfunction + +function! Check(a, b, result) + call Chk(a:a, a:b, a:result) + call Chk(a:b, a:a, -a:result) +endfunction + +function! LT(a, b) + call Check(a:a, a:b, -1) +endfunction + +function! GT(a, b) + call Check(a:a, a:b, 1) +endfunction + +function! EQ(a, b) + call Check(a:a, a:b, 0) +endfunction + +function Test_comparisons() + call EQ('', '') + call LT('', 'a') + call EQ('abc', 'abc') + call EQ('Abc', 'abC') + call LT('ab', 'abc') + call LT('AB', 'abc') + call LT('ab', 'aBc') + call EQ('\xd0\xb9\xd1\x86\xd1\x83\xd0\xba\xd0\xb5\xd0\xbd', '\xd0\xb9\xd0\xa6\xd0\xa3\xd0\xba\xd0\x95\xd0\xbd') + call LT('\xd0\xb9\xd1\x86\xd1\x83\xd0\xba\xd0\xb5\xd0\xbd', '\xd0\xaf\xd1\x86\xd1\x83\xd0\xba\xd0\xb5\xd0\xbd') + call EQ('\xe2\x84\xaa', 'k') + call LT('\xe2\x84\xaa', 'kkkkkk') + call EQ('\xe2\x84\xaa\xe2\x84\xaa\xe2\x84\xaa', 'kkk') + call LT('kk', '\xe2\x84\xaa\xe2\x84\xaa\xe2\x84\xaa') + call EQ('\xe2\x84\xaa\xe2\x84\xa6k\xe2\x84\xaak\xcf\x89', 'k\xcf\x89\xe2\x84\xaakk\xe2\x84\xa6') + call EQ('Abc\x80', 'AbC\x80') + call LT('Abc\x80', 'AbC\x81') + call LT('Abc', 'AbC\x80') + call LT('abc\x80DEF', 'abc\x80def') " case folding stops at the first bad character + call LT('\xc3XYZ', '\xc3xyz') + call EQ('\xef\xbc\xba', '\xef\xbd\x9a') " FF3A (upper), FF5A (lower) + call GT('\xef\xbc\xba', '\xef\xbc\xff') " first string is ok and equals \xef\xbd\x9a after folding, second string is illegal and was left unchanged, then the strings were bytewise compared + call LT('\xc3', '\xc3\x83') + call EQ('\xc3\xa3xYz', '\xc3\x83XyZ') + for n in range(0x60, 0xFF) + call LT(printf('xYz\x%.2X', n-1), printf('XyZ\x%.2X', n)) + endfor + for n in range(0x80, 0xBF) + call EQ(printf('xYz\xc2\x%.2XUvW', n), printf('XyZ\xc2\x%.2XuVw', n)) + endfor + for n in range(0xC0, 0xFF) + call LT(printf('xYz\xc2\x%.2XUvW', n), printf('XyZ\xc2\x%.2XuVw', n)) + endfor +endfunction + +" test that g~ap changes one paragraph only. +function Test_gap() + new + call feedkeys("iabcd\n\ndefggg0g~ap", "tx") + call assert_equal(["ABCD", "", "defg"], getline(1,3)) +endfunction diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim index 4e0f1bbd2f..c449fc91b0 100644 --- a/src/nvim/testdir/test_vimscript.vim +++ b/src/nvim/testdir/test_vimscript.vim @@ -1215,6 +1215,62 @@ func Test_bitwise_functions() call assert_fails("call invert({})", 'E728:') endfunc +" Test trailing text after :endfunction {{{1 +func Test_endfunction_trailing() + call assert_false(exists('*Xtest')) + + exe "func Xtest()\necho 'hello'\nendfunc\nlet done = 'yes'" + call assert_true(exists('*Xtest')) + call assert_equal('yes', done) + delfunc Xtest + unlet done + + exe "func Xtest()\necho 'hello'\nendfunc|let done = 'yes'" + call assert_true(exists('*Xtest')) + call assert_equal('yes', done) + delfunc Xtest + unlet done + + " trailing line break + exe "func Xtest()\necho 'hello'\nendfunc\n" + call assert_true(exists('*Xtest')) + delfunc Xtest + + set verbose=1 + exe "func Xtest()\necho 'hello'\nendfunc \" garbage" + call assert_true(exists('*Xtest')) + delfunc Xtest + + call assert_fails("func Xtest()\necho 'hello'\nendfunc garbage", 'E946') + call assert_true(exists('*Xtest')) + delfunc Xtest + set verbose=0 + + function Foo() + echo 'hello' + endfunction | echo 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' + delfunc Foo +endfunc + +func Test_delfunction_force() + delfunc! Xtest + delfunc! Xtest + func Xtest() + echo 'nothing' + endfunc + delfunc! Xtest + delfunc! Xtest +endfunc + +" Test using bang after user command {{{1 +func Test_user_command_with_bang() + command -bang Nieuw let nieuw = 1 + Ni! + call assert_equal(1, nieuw) + unlet nieuw + delcommand Nieuw +endfunc + "------------------------------------------------------------------------------- " Modelines {{{1 " vim: ts=8 sw=4 tw=80 fdm=marker diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim index 1694adbd32..0f2e7e493e 100644 --- a/src/nvim/testdir/test_visual.vim +++ b/src/nvim/testdir/test_visual.vim @@ -3,6 +3,7 @@ if !has('visual') finish endif + func Test_block_shift_multibyte() " Uses double-wide character. if !has('multi_byte') @@ -43,3 +44,111 @@ func Test_dotregister_paste() call assert_equal('hello world world', getline(1)) q! endfunc + +func Test_Visual_inner_quote() + new + normal oxX + normal vki' + bwipe! +endfunc + +" Test for visual block shift and tab characters. +func Test_block_shift_tab() + enew! + call append(0, repeat(['one two three'], 5)) + call cursor(1,1) + exe "normal i\<C-G>u" + exe "normal fe\<C-V>4jR\<Esc>ugvr1" + call assert_equal('on1 two three', getline(1)) + call assert_equal('on1 two three', getline(2)) + call assert_equal('on1 two three', getline(5)) + + enew! + call append(0, repeat(['abcdefghijklmnopqrstuvwxyz'], 5)) + call cursor(1,1) + exe "normal \<C-V>4jI \<Esc>j<<11|D" + exe "normal j7|a\<Tab>\<Tab>" + exe "normal j7|a\<Tab>\<Tab> " + exe "normal j7|a\<Tab> \<Tab>\<Esc>4k13|\<C-V>4j<" + call assert_equal(' abcdefghijklmnopqrstuvwxyz', getline(1)) + call assert_equal('abcdefghij', getline(2)) + call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(3)) + call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(4)) + call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(5)) + + %s/\s\+//g + call cursor(1,1) + exe "normal \<C-V>4jI \<Esc>j<<" + exe "normal j7|a\<Tab>\<Tab>" + exe "normal j7|a\<Tab>\<Tab>\<Tab>\<Tab>\<Tab>" + exe "normal j7|a\<Tab> \<Tab>\<Tab>\<Esc>4k13|\<C-V>4j3<" + call assert_equal(' abcdefghijklmnopqrstuvwxyz', getline(1)) + call assert_equal('abcdefghij', getline(2)) + call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(3)) + call assert_equal(" abc\<Tab>\<Tab>defghijklmnopqrstuvwxyz", getline(4)) + call assert_equal(" abc\<Tab> defghijklmnopqrstuvwxyz", getline(5)) + + enew! +endfunc + +" Tests Blockwise Visual when there are TABs before the text. +func Test_blockwise_visual() + enew! + call append(0, ['123456', + \ '234567', + \ '345678', + \ '', + \ 'test text test tex start here', + \ "\t\tsome text", + \ "\t\ttest text", + \ 'test text']) + call cursor(1,1) + exe "normal /start here$\<CR>" + exe 'normal "by$' . "\<C-V>jjlld" + exe "normal /456$\<CR>" + exe "normal \<C-V>jj" . '"bP' + call assert_equal(['123start here56', + \ '234start here67', + \ '345start here78', + \ '', + \ 'test text test tex rt here', + \ "\t\tsomext", + \ "\t\ttesext"], getline(1, 7)) + + enew! +endfunc + +" Test Virtual replace mode. +func Test_virtual_replace() + throw 'skipped: TODO: ' + exe "set t_kD=\<C-V>x7f t_kb=\<C-V>x08" + enew! + exe "normal a\nabcdefghi\njk\tlmn\n opq rst\n\<C-D>uvwxyz" + call cursor(1,1) + set ai bs=2 + exe "normal gR0\<C-D> 1\nA\nBCDEFGHIJ\n\tKL\nMNO\nPQR" + call assert_equal([' 1', + \ ' A', + \ ' BCDEFGHIJ', + \ ' KL', + \ ' MNO', + \ ' PQR', + \ ], getline(1, 6)) + normal G + mark a + inoremap <C-D> <Del> + exe "normal o0\<C-D>\nabcdefghi\njk\tlmn\n opq\trst\n\<C-D>uvwxyz\n" + exe "normal 'ajgR0\<C-D> 1\nA\nBCDEFGHIJ\n\tKL\nMNO\nPQR" . repeat("\<BS>", 29) + call assert_equal([' 1', + \ 'abcdefghi', + \ 'jk lmn', + \ ' opq rst', + \ 'uvwxyz'], getline(7, 11)) + normal G + exe "normal iab\tcdefghi\tjkl" + exe "normal 0gRAB......CDEFGHI.J\<Esc>o" + exe "normal iabcdefghijklmnopqrst\<Esc>0gRAB\tIJKLMNO\tQR" + call assert_equal(['AB......CDEFGHI.Jkl', + \ 'AB IJKLMNO QRst'], getline(12, 13)) + enew! +endfunc diff --git a/src/nvim/testdir/test_winbuf_close.vim b/src/nvim/testdir/test_winbuf_close.vim new file mode 100644 index 0000000000..ed64dd79b7 --- /dev/null +++ b/src/nvim/testdir/test_winbuf_close.vim @@ -0,0 +1,124 @@ +" Test for commands that close windows and/or buffers: +" :quit +" :close +" :hide +" :only +" :sall +" :all +" :ball +" :buf +" :edit +" +func Test_winbuf_close() + enew | only + + call writefile(['testtext 1'], 'Xtest1') + call writefile(['testtext 2'], 'Xtest2') + call writefile(['testtext 3'], 'Xtest3') + + next! Xtest1 Xtest2 + call setline(1, 'testtext 1 1') + + " test for working :n when hidden set + set hidden + next + call assert_equal('Xtest2', bufname('%')) + + " test for failing :rew when hidden not set + set nohidden + call setline(1, 'testtext 2 2') + call assert_fails('rewind', 'E37') + call assert_equal('Xtest2', bufname('%')) + call assert_equal('testtext 2 2', getline(1)) + + " test for working :rew when hidden set + set hidden + rewind + call assert_equal('Xtest1', bufname('%')) + call assert_equal('testtext 1 1', getline(1)) + + " test for :all keeping a buffer when it's modified + set nohidden + call setline(1, 'testtext 1 1 1') + split + next Xtest2 Xtest3 + all + 1wincmd w + call assert_equal('Xtest1', bufname('%')) + call assert_equal('testtext 1 1 1', getline(1)) + + " test abandoning changed buffer, should be unloaded even when 'hidden' set + set hidden + call setline(1, 'testtext 1 1 1 1') + quit! + call assert_equal('Xtest2', bufname('%')) + call assert_equal('testtext 2 2', getline(1)) + unhide + call assert_equal('Xtest2', bufname('%')) + call assert_equal('testtext 2 2', getline(1)) + + " test ":hide" hides anyway when 'hidden' not set + set nohidden + call setline(1, 'testtext 2 2 2') + hide + call assert_equal('Xtest3', bufname('%')) + call assert_equal('testtext 3', getline(1)) + + " test ":edit" failing in modified buffer when 'hidden' not set + call setline(1, 'testtext 3 3') + call assert_fails('edit Xtest1', 'E37') + call assert_equal('Xtest3', bufname('%')) + call assert_equal('testtext 3 3', getline(1)) + + " test ":edit" working in modified buffer when 'hidden' set + set hidden + edit Xtest1 + call assert_equal('Xtest1', bufname('%')) + call assert_equal('testtext 1', getline(1)) + + " test ":close" not hiding when 'hidden' not set in modified buffer + split Xtest3 + set nohidden + call setline(1, 'testtext 3 3 3') + call assert_fails('close', 'E37') + call assert_equal('Xtest3', bufname('%')) + call assert_equal('testtext 3 3 3', getline(1)) + + " test ":close!" does hide when 'hidden' not set in modified buffer; + call setline(1, 'testtext 3 3 3 3') + close! + call assert_equal('Xtest1', bufname('%')) + call assert_equal('testtext 1', getline(1)) + + set nohidden + + " test ":all!" hides changed buffer + split Xtest4 + call setline(1, 'testtext 4') + all! + 1wincmd w + call assert_equal('Xtest2', bufname('%')) + call assert_equal('testtext 2 2 2', getline(1)) + + " test ":q!" and hidden buffer. + bwipe! Xtest1 Xtest2 Xtest3 Xtest4 + split Xtest1 + wincmd w + bwipe! + set modified + bot split Xtest2 + set modified + bot split Xtest3 + set modified + wincmd t + hide + call assert_equal('Xtest2', bufname('%')) + quit! + call assert_equal('Xtest3', bufname('%')) + call assert_fails('silent! quit!', 'E162') + call assert_equal('Xtest1', bufname('%')) + + call delete('Xtest1') + call delete('Xtest2') + call delete('Xtest3') +endfunc diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim index 9d61921988..139d29a48b 100644 --- a/src/nvim/testdir/test_window_cmd.vim +++ b/src/nvim/testdir/test_window_cmd.vim @@ -306,20 +306,14 @@ func Test_window_width() set winfixwidth vsplit Xc let [ww1, ww2, ww3] = [winwidth(1), winwidth(2), winwidth(3)] - " FIXME: commented out: I would expect the width of 2nd window to - " remain 2 but it's actually 1?! - "call assert_equal(2, winwidth(2)) + call assert_equal(2, winwidth(2)) call assert_inrange(ww3, ww3 + 1, ww1) 3wincmd > - " FIXME: commented out: I would expect the width of 2nd window to - " remain 2 but it's actually 1?! - "call assert_equal(2, winwidth(2)) + call assert_equal(2, winwidth(2)) call assert_equal(ww1 + 3, winwidth(1)) call assert_equal(ww3 - 3, winwidth(3)) wincmd = - " FIXME: commented out: I would expect the width of 2nd window to - " remain 2 but it's actually 1?! - "call assert_equal(2, winwidth(2)) + call assert_equal(2, winwidth(2)) call assert_equal(ww1, winwidth(1)) call assert_equal(ww3, winwidth(3)) diff --git a/src/nvim/testdir/unix.vim b/src/nvim/testdir/unix.vim index a7daacf8cf..ce2beff7fe 100644 --- a/src/nvim/testdir/unix.vim +++ b/src/nvim/testdir/unix.vim @@ -2,6 +2,12 @@ " Always use "sh", don't use the value of "$SHELL". set shell=sh +if has('win32') + set shellcmdflag=-c shellxquote= shellxescape= shellquote= + let &shellredir = '>%s 2>&1' + set shellslash +endif + " Don't depend on system locale, always use utf-8 set encoding=utf-8 diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c index 96bb692db9..b04a6ce4f9 100644 --- a/src/nvim/tui/input.c +++ b/src/nvim/tui/input.c @@ -47,9 +47,11 @@ void term_input_init(TermInput *input, Loop *loop) termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS); // setup input handle #ifdef WIN32 - uv_tty_init(loop, &input->tty_in, 0, 1); + uv_tty_init(&loop->uv, &input->tty_in, 0, 1); uv_tty_set_mode(&input->tty_in, UV_TTY_MODE_RAW); - rstream_init_stream(&input->read_stream, &input->tty_in, 0xfff); + rstream_init_stream(&input->read_stream, + (uv_stream_t *)&input->tty_in, + 0xfff); #else rstream_init_fd(loop, &input->read_stream, input->in_fd, 0xfff); #endif diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c index fdc33f0a77..2f07e83158 100644 --- a/src/nvim/tui/terminfo.c +++ b/src/nvim/tui/terminfo.c @@ -9,7 +9,10 @@ #include <unibilium.h> #include "nvim/log.h" +#include "nvim/globals.h" #include "nvim/memory.h" +#include "nvim/message.h" +#include "nvim/option.h" #include "nvim/tui/terminfo.h" #ifdef INCLUDE_GENERATED_DECLARATIONS @@ -87,8 +90,8 @@ bool terminfo_is_term_family(const char *term, const char *family) size_t tlen = strlen(term); size_t flen = strlen(family); return tlen >= flen - && 0 == memcmp(term, family, flen) \ - // Per the commentary in terminfo, minus sign is the suffix separator. + && 0 == memcmp(term, family, flen) + // Per commentary in terminfo, minus is the only valid suffix separator. && ('\0' == term[flen] || '-' == term[flen]); } @@ -166,3 +169,87 @@ unibi_term *terminfo_from_builtin(const char *term, char **termname) unibi_set_bool(ut, unibi_back_color_erase, false); return ut; } + +/// Dumps termcap info to the messages area. +/// Serves a similar purpose as Vim `:set termcap` (removed in Nvim). +/// +/// @note adapted from unibilium unibi-dump.c +void terminfo_info_msg(const unibi_term *const ut) +{ + if (exiting) { + return; + } + msg_puts_title("\n\n--- Terminal info --- {{{\n"); + + char *term; + get_tty_option("term", &term); + msg_printf_attr(0, "&term: %s\n", term); + msg_printf_attr(0, "Description: %s\n", unibi_get_name(ut)); + const char **a = unibi_get_aliases(ut); + if (*a) { + msg_puts("Aliases: "); + do { + msg_printf_attr(0, "%s%s\n", *a, a[1] ? " | " : ""); + a++; + } while (*a); + } + + msg_puts("Boolean capabilities:\n"); + for (enum unibi_boolean i = unibi_boolean_begin_ + 1; + i < unibi_boolean_end_; i++) { + msg_printf_attr(0, " %-25s %-10s = %s\n", unibi_name_bool(i), + unibi_short_name_bool(i), + unibi_get_bool(ut, i) ? "true" : "false"); + } + + msg_puts("Numeric capabilities:\n"); + for (enum unibi_numeric i = unibi_numeric_begin_ + 1; + i < unibi_numeric_end_; i++) { + int n = unibi_get_num(ut, i); // -1 means "empty" + msg_printf_attr(0, " %-25s %-10s = %hd\n", unibi_name_num(i), + unibi_short_name_num(i), n); + } + + msg_puts("String capabilities:\n"); + for (enum unibi_string i = unibi_string_begin_ + 1; + i < unibi_string_end_; i++) { + const char *s = unibi_get_str(ut, i); + if (s) { + msg_printf_attr(0, " %-25s %-10s = ", unibi_name_str(i), + unibi_short_name_str(i)); + // Most of these strings will contain escape sequences. + msg_outtrans_special((char_u *)s, false); + msg_putchar('\n'); + } + } + + if (unibi_count_ext_bool(ut)) { + msg_puts("Extended boolean capabilities:\n"); + for (size_t i = 0; i < unibi_count_ext_bool(ut); i++) { + msg_printf_attr(0, " %-25s = %s\n", + unibi_get_ext_bool_name(ut, i), + unibi_get_ext_bool(ut, i) ? "true" : "false"); + } + } + + if (unibi_count_ext_num(ut)) { + msg_puts("Extended numeric capabilities:\n"); + for (size_t i = 0; i < unibi_count_ext_num(ut); i++) { + msg_printf_attr(0, " %-25s = %hd\n", + unibi_get_ext_num_name(ut, i), + unibi_get_ext_num(ut, i)); + } + } + + if (unibi_count_ext_str(ut)) { + msg_puts("Extended string capabilities:\n"); + for (size_t i = 0; i < unibi_count_ext_str(ut); i++) { + msg_printf_attr(0, " %-25s = ", unibi_get_ext_str_name(ut, i)); + msg_outtrans_special((char_u *)unibi_get_ext_str(ut, i), false); + msg_putchar('\n'); + } + } + + msg_puts("}}}\n"); + xfree(term); +} diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c index 2436295ad4..72a25b0b59 100644 --- a/src/nvim/tui/tui.c +++ b/src/nvim/tui/tui.c @@ -69,7 +69,6 @@ typedef struct { typedef struct { UIBridgeData *bridge; Loop *loop; - bool stop; unibi_var_t params[9]; char buf[OUTBUF_SIZE]; size_t bufpos; @@ -96,6 +95,7 @@ typedef struct { bool immediate_wrap_after_last_column; bool mouse_enabled; bool busy, is_invisible; + bool cork, overflow; cursorentry_T cursor_shapes[SHAPE_IDX_COUNT]; HlAttrs print_attrs; bool default_attr; @@ -123,9 +123,8 @@ static bool cursor_style_enabled = false; UI *tui_start(void) { - UI *ui = xcalloc(1, sizeof(UI)); + UI *ui = xcalloc(1, sizeof(UI)); // Freed by ui_bridge_stop(). ui->stop = tui_stop; - ui->rgb = p_tgc; ui->resize = tui_resize; ui->clear = tui_clear; ui->eol_clear = tui_eol_clear; @@ -143,14 +142,12 @@ UI *tui_start(void) ui->put = tui_put; ui->bell = tui_bell; ui->visual_bell = tui_visual_bell; - ui->update_fg = tui_update_fg; - ui->update_bg = tui_update_bg; - ui->update_sp = tui_update_sp; + ui->default_colors_set = tui_default_colors_set; ui->flush = tui_flush; ui->suspend = tui_suspend; ui->set_title = tui_set_title; ui->set_icon = tui_set_icon; - ui->event = tui_event; + ui->option_set= tui_option_set; memset(ui->ui_ext, 0, sizeof(ui->ui_ext)); @@ -167,24 +164,6 @@ static size_t unibi_pre_fmt_str(TUIData *data, unsigned int unibi_index, return unibi_run(str, data->params, buf, len); } -/// Emits some termcodes after Nvim startup, which were observed to slowdown -/// rendering during startup in tmux 2.3 (+focus-events). #7649 -static void terminfo_after_startup_event(void **argv) -{ - UI *ui = argv[0]; - bool defer = argv[1] != NULL; // clever(?) boolean without malloc() dance. - TUIData *data = ui->data; - if (defer) { // We're on the main-loop. Now forward to the TUI loop. - loop_schedule(data->loop, - event_create(terminfo_after_startup_event, 2, ui, NULL)); - return; - } - // Enable bracketed paste - unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); - // Enable focus reporting - unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); -} - static void termname_set_event(void **argv) { char *termname = argv[0]; @@ -200,6 +179,8 @@ static void terminfo_start(UI *ui) data->default_attr = false; data->is_invisible = true; data->busy = false; + data->cork = false; + data->overflow = false; data->showing_mode = SHAPE_IDX_N; data->unibi_ext.enable_mouse = -1; data->unibi_ext.disable_mouse = -1; @@ -236,7 +217,8 @@ static void terminfo_start(UI *ui) const char *vte_version_env = os_getenv("VTE_VERSION"); long vte_version = vte_version_env ? strtol(vte_version_env, NULL, 10) : 0; bool iterm_env = termprg && strstr(termprg, "iTerm.app"); - bool konsole = os_getenv("KONSOLE_PROFILE_NAME") + bool konsole = terminfo_is_term_family(term, "konsole") + || os_getenv("KONSOLE_PROFILE_NAME") || os_getenv("KONSOLE_DBUS_SESSION"); patch_terminfo_bugs(data, term, colorterm, vte_version, konsole, iterm_env); @@ -262,6 +244,9 @@ static void terminfo_start(UI *ui) unibi_out(ui, unibi_enter_ca_mode); unibi_out(ui, unibi_keypad_xmit); unibi_out(ui, unibi_clear_screen); + // Enable bracketed paste + unibi_out_ext(ui, data->unibi_ext.enable_bracketed_paste); + uv_loop_init(&data->write_loop); if (data->out_isatty) { uv_tty_init(&data->write_loop, &data->output_handle.tty, data->out_fd, 0); @@ -274,9 +259,6 @@ static void terminfo_start(UI *ui) uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0); uv_pipe_open(&data->output_handle.pipe, data->out_fd); } - - loop_schedule(&main_loop, - event_create(terminfo_after_startup_event, 2, ui, ui)); } static void terminfo_stop(UI *ui) @@ -294,7 +276,7 @@ static void terminfo_stop(UI *ui) unibi_out_ext(ui, data->unibi_ext.disable_bracketed_paste); // Disable focus reporting unibi_out_ext(ui, data->unibi_ext.disable_focus_reporting); - flush_buf(ui, true); + flush_buf(ui); uv_tty_reset_mode(); uv_close((uv_handle_t *)&data->output_handle, NULL); uv_run(&data->write_loop, UV_RUN_DEFAULT); @@ -315,6 +297,16 @@ static void tui_terminal_start(UI *ui) term_input_start(&data->input); } +static void tui_terminal_after_startup(UI *ui) + FUNC_ATTR_NONNULL_ALL +{ + TUIData *data = ui->data; + + // Emit this after Nvim startup, not during. This works around a tmux + // 2.3 bug(?) which caused slow drawing during startup. #7649 + unibi_out_ext(ui, data->unibi_ext.enable_focus_reporting); +} + static void tui_terminal_stop(UI *ui) { TUIData *data = ui->data; @@ -327,11 +319,17 @@ static void tui_terminal_stop(UI *ui) static void tui_stop(UI *ui) { tui_terminal_stop(ui); - TUIData *data = ui->data; - data->stop = true; + // Flag UI as "stopped". + ui->data = NULL; +} + +/// Returns true if UI `ui` is stopped. +static bool tui_is_stopped(UI *ui) +{ + return ui->data == NULL; } -// Main function of the TUI thread +/// Main function of the TUI thread. static void tui_main(UIBridgeData *bridge, UI *ui) { Loop tui_loop; @@ -352,13 +350,25 @@ static void tui_main(UIBridgeData *bridge, UI *ui) #endif term_input_init(&data->input, &tui_loop); tui_terminal_start(ui); - data->stop = false; - // allow the main thread to continue, we are ready to start handling UI - // callbacks + // Allow main thread to continue, we are ready to handle UI callbacks. CONTINUE(bridge); - while (!data->stop) { + loop_schedule_deferred(&main_loop, + event_create(show_termcap_event, 1, data->ut)); + + // "Active" loop: first ~100 ms of startup. + for (size_t ms = 0; ms < 100 && !tui_is_stopped(ui);) { + ms += (loop_poll_events(&tui_loop, 20) ? 20 : 1); + } + if (!tui_is_stopped(ui)) { + tui_terminal_after_startup(ui); + // Tickle `main_loop` with a dummy event, else the initial "focus-gained" + // terminal response may not get processed until user hits a key. + loop_schedule_deferred(&main_loop, event_create(tui_dummy_event, 0)); + } + // "Passive" (I/O-driven) loop: TUI thread "main loop". + while (!tui_is_stopped(ui)) { loop_poll_events(&tui_loop, -1); // tui_loop.events is never processed } @@ -370,9 +380,13 @@ static void tui_main(UIBridgeData *bridge, UI *ui) loop_close(&tui_loop, false); kv_destroy(data->invalid_regions); xfree(data); - xfree(ui); } +static void tui_dummy_event(void **argv) +{ +} + +/// Handoff point between the main (ui_bridge) thread and the TUI thread. static void tui_scheduler(Event event, void *d) { UI *ui = d; @@ -395,36 +409,59 @@ static void sigwinch_cb(SignalWatcher *watcher, int signum, void *data) ui_schedule_refresh(); } -static bool attrs_differ(HlAttrs a1, HlAttrs a2) +static bool attrs_differ(HlAttrs a1, HlAttrs a2, bool rgb) { - return a1.foreground != a2.foreground || a1.background != a2.background - || a1.bold != a2.bold || a1.italic != a2.italic - || a1.undercurl != a2.undercurl || a1.underline != a2.underline - || a1.reverse != a2.reverse; + if (rgb) { + // TODO(bfredl): when we start to support special color, + // rgb_sp_color must be added here + return a1.rgb_fg_color != a2.rgb_fg_color + || a1.rgb_bg_color != a2.rgb_bg_color + || a1.rgb_ae_attr != a2.rgb_ae_attr; + } else { + return a1.cterm_fg_color != a2.cterm_fg_color + || a1.cterm_bg_color != a2.cterm_bg_color + || a1.cterm_ae_attr != a2.cterm_ae_attr; + } } static void update_attrs(UI *ui, HlAttrs attrs) { TUIData *data = ui->data; - if (!attrs_differ(attrs, data->print_attrs)) { + if (!attrs_differ(attrs, data->print_attrs, ui->rgb)) { return; } data->print_attrs = attrs; UGrid *grid = &data->grid; - int fg = attrs.foreground != -1 ? attrs.foreground : grid->fg; - int bg = attrs.background != -1 ? attrs.background : grid->bg; + int fg = ui->rgb ? attrs.rgb_fg_color : (attrs.cterm_fg_color - 1); + if (fg == -1) { + fg = ui->rgb ? grid->clear_attrs.rgb_fg_color + : (grid->clear_attrs.cterm_fg_color - 1); + } + + int bg = ui->rgb ? attrs.rgb_bg_color : (attrs.cterm_bg_color - 1); + if (bg == -1) { + bg = ui->rgb ? grid->clear_attrs.rgb_bg_color + : (grid->clear_attrs.cterm_bg_color - 1); + } + + int attr = ui->rgb ? attrs.rgb_ae_attr : attrs.cterm_ae_attr; + bool bold = attr & HL_BOLD; + bool italic = attr & HL_ITALIC; + bool reverse = attr & HL_INVERSE; + bool standout = attr & HL_STANDOUT; + bool underline = attr & (HL_UNDERLINE), undercurl = attr & (HL_UNDERCURL); if (unibi_get_str(data->ut, unibi_set_attributes)) { - if (attrs.bold || attrs.reverse || attrs.underline || attrs.undercurl) { + if (bold || reverse || underline || undercurl) { UNIBI_SET_NUM_VAR(data->params[0], 0); // standout - UNIBI_SET_NUM_VAR(data->params[1], attrs.underline || attrs.undercurl); - UNIBI_SET_NUM_VAR(data->params[2], attrs.reverse); + UNIBI_SET_NUM_VAR(data->params[1], underline || undercurl); + UNIBI_SET_NUM_VAR(data->params[2], reverse); UNIBI_SET_NUM_VAR(data->params[3], 0); // blink UNIBI_SET_NUM_VAR(data->params[4], 0); // dim - UNIBI_SET_NUM_VAR(data->params[5], attrs.bold); + UNIBI_SET_NUM_VAR(data->params[5], bold); UNIBI_SET_NUM_VAR(data->params[6], 0); // blank UNIBI_SET_NUM_VAR(data->params[7], 0); // protect UNIBI_SET_NUM_VAR(data->params[8], 0); // alternate character set @@ -436,17 +473,20 @@ static void update_attrs(UI *ui, HlAttrs attrs) if (!data->default_attr) { unibi_out(ui, unibi_exit_attribute_mode); } - if (attrs.bold) { + if (bold) { unibi_out(ui, unibi_enter_bold_mode); } - if (attrs.underline || attrs.undercurl) { + if (underline || undercurl) { unibi_out(ui, unibi_enter_underline_mode); } - if (attrs.reverse) { + if (standout) { + unibi_out(ui, unibi_enter_standout_mode); + } + if (reverse) { unibi_out(ui, unibi_enter_reverse_mode); } } - if (attrs.italic) { + if (italic) { unibi_out(ui, unibi_enter_italics_mode); } if (ui->rgb) { @@ -476,8 +516,7 @@ static void update_attrs(UI *ui, HlAttrs attrs) } data->default_attr = fg == -1 && bg == -1 - && !attrs.bold && !attrs.italic && !attrs.underline && !attrs.undercurl - && !attrs.reverse; + && !bold && !italic && !underline && !undercurl && !reverse; } static void final_column_wrap(UI *ui) @@ -486,7 +525,7 @@ static void final_column_wrap(UI *ui) UGrid *grid = &data->grid; if (grid->col == ui->width) { grid->col = 0; - if (grid->row < ui->height) { + if (grid->row < MIN(ui->height, grid->height - 1)) { grid->row++; } } @@ -519,7 +558,7 @@ static bool cheap_to_print(UI *ui, int row, int col, int next) UCell *cell = grid->cells[row] + col; while (next) { next--; - if (attrs_differ(cell->attrs, data->print_attrs)) { + if (attrs_differ(cell->attrs, data->print_attrs, ui->rgb)) { if (data->default_attr) { return false; } @@ -644,13 +683,12 @@ static void clear_region(UI *ui, int top, int bot, int left, int right) int saved_col = grid->col; bool cleared = false; - if (grid->bg == -1 && right == ui->width -1) { + bool nobg = ui->rgb ? grid->clear_attrs.rgb_bg_color == -1 + : grid->clear_attrs.cterm_bg_color == 0; + if (nobg && right == ui->width -1) { // Background is set to the default color and the right edge matches the // screen end, try to use terminal codes for clearing the requested area. - HlAttrs clear_attrs = HLATTRS_INIT; - clear_attrs.foreground = grid->fg; - clear_attrs.background = grid->bg; - update_attrs(ui, clear_attrs); + update_attrs(ui, grid->clear_attrs); if (left == 0) { if (bot == ui->height - 1) { if (top == 0) { @@ -773,6 +811,7 @@ static void tui_clear(UI *ui) TUIData *data = ui->data; UGrid *grid = &data->grid; ugrid_clear(grid); + kv_size(data->invalid_regions) = 0; clear_region(ui, grid->top, grid->bot, grid->left, grid->right); } @@ -791,7 +830,7 @@ static void tui_cursor_goto(UI *ui, Integer row, Integer col) CursorShape tui_cursor_decode_shape(const char *shape_str) { - CursorShape shape = 0; + CursorShape shape; if (strequal(shape_str, "block")) { shape = SHAPE_BLOCK; } else if (strequal(shape_str, "vertical")) { @@ -799,7 +838,8 @@ CursorShape tui_cursor_decode_shape(const char *shape_str) } else if (strequal(shape_str, "horizontal")) { shape = SHAPE_HOR; } else { - EMSG2(_(e_invarg2), shape_str); + WLOG("Unknown shape value '%s'", shape_str); + shape = SHAPE_BLOCK; } return shape; } @@ -885,22 +925,22 @@ static void tui_set_mode(UI *ui, ModeShape mode) } TUIData *data = ui->data; cursorentry_T c = data->cursor_shapes[mode]; - int shape = c.shape; if (c.id != 0 && ui->rgb) { int attr = syn_id2attr(c.id); if (attr > 0) { - attrentry_T *aep = syn_cterm_attr2entry(attr); + HlAttrs *aep = syn_cterm_attr2entry(attr); UNIBI_SET_NUM_VAR(data->params[0], aep->rgb_bg_color); unibi_out_ext(ui, data->unibi_ext.set_cursor_color); } } - switch (shape) { + int shape; + switch (c.shape) { + default: abort(); break; case SHAPE_BLOCK: shape = 1; break; case SHAPE_HOR: shape = 3; break; case SHAPE_VER: shape = 5; break; - default: WLOG("Unknown shape value %d", shape); break; } UNIBI_SET_NUM_VAR(data->params[0], shape + (int)(c.blinkon == 0)); unibi_out_ext(ui, data->unibi_ext.set_cursor_style); @@ -945,10 +985,7 @@ static void tui_scroll(UI *ui, Integer count) cursor_goto(ui, grid->top, grid->left); // also set default color attributes or some terminals can become funny if (scroll_clears_to_current_colour) { - HlAttrs clear_attrs = HLATTRS_INIT; - clear_attrs.foreground = grid->fg; - clear_attrs.background = grid->bg; - update_attrs(ui, clear_attrs); + update_attrs(ui, grid->clear_attrs); } if (count > 0) { @@ -1013,19 +1050,16 @@ static void tui_visual_bell(UI *ui) unibi_out(ui, unibi_flash_screen); } -static void tui_update_fg(UI *ui, Integer fg) +static void tui_default_colors_set(UI *ui, Integer rgb_fg, Integer rgb_bg, + Integer rgb_sp, + Integer cterm_fg, Integer cterm_bg) { - ((TUIData *)ui->data)->grid.fg = (int)fg; -} - -static void tui_update_bg(UI *ui, Integer bg) -{ - ((TUIData *)ui->data)->grid.bg = (int)bg; -} - -static void tui_update_sp(UI *ui, Integer sp) -{ - // Do nothing; 'special' color is for GUI only + UGrid *grid = &((TUIData *)ui->data)->grid; + grid->clear_attrs.rgb_fg_color = (int)rgb_fg; + grid->clear_attrs.rgb_bg_color = (int)rgb_bg; + grid->clear_attrs.rgb_sp_color = (int)rgb_sp; + grid->clear_attrs.cterm_fg_color = (int)cterm_fg; + grid->clear_attrs.cterm_bg_color = (int)cterm_bg; } static void tui_flush(UI *ui) @@ -1050,6 +1084,7 @@ static void tui_flush(UI *ui) while (kv_size(data->invalid_regions)) { Rect r = kv_pop(data->invalid_regions); + assert(r.bot < grid->height && r.right < grid->width); UGRID_FOREACH_CELL(grid, r.top, r.bot, r.left, r.right, { cursor_goto(ui, row, col); print_cell(ui, cell); @@ -1058,7 +1093,25 @@ static void tui_flush(UI *ui) cursor_goto(ui, saved_row, saved_col); - flush_buf(ui, true); + flush_buf(ui); +} + +/// Dumps termcap info to the messages area, if 'verbose' >= 3. +static void show_termcap_event(void **argv) +{ + if (p_verbose < 3) { + return; + } + const unibi_term *const ut = argv[0]; + if (!ut) { + abort(); + } + verbose_enter(); + // XXX: (future) if unibi_term is modified (e.g. after a terminal + // query-response) this is a race condition. + terminfo_info_msg(ut); + verbose_leave(); + verbose_stop(); // flush now } #ifdef UNIX @@ -1076,6 +1129,7 @@ static void suspend_event(void **argv) loop_poll_events(data->loop, -1); } tui_terminal_start(ui); + tui_terminal_after_startup(ui); if (enable_mouse) { tui_mouse_on(ui); } @@ -1113,10 +1167,13 @@ static void tui_set_icon(UI *ui, String icon) { } -// NB: if we start to use this, the ui_bridge must be updated -// to make a copy for the tui thread -static void tui_event(UI *ui, char *name, Array args, bool *args_consumed) +static void tui_option_set(UI *ui, String name, Object value) { + TUIData *data = ui->data; + if (strequal(name.data, "termguicolors")) { + ui->rgb = value.data.boolean; + invalidate(ui, 0, data->grid.height-1, 0, data->grid.width-1); + } } static void invalidate(UI *ui, int top, int bot, int left, int right) @@ -1219,8 +1276,18 @@ static void unibi_goto(UI *ui, int row, int col) } \ if (str) { \ unibi_var_t vars[26 + 26]; \ + size_t orig_pos = data->bufpos; \ + \ memset(&vars, 0, sizeof(vars)); \ + data->cork = true; \ +retry: \ unibi_format(vars, vars + 26, str, data->params, out, ui, NULL, NULL); \ + if (data->overflow) { \ + data->bufpos = orig_pos; \ + flush_buf(ui); \ + goto retry; \ + } \ + data->cork = false; \ } \ } while (0) static void unibi_out(UI *ui, int unibi_index) @@ -1239,8 +1306,17 @@ static void out(void *ctx, const char *str, size_t len) TUIData *data = ui->data; size_t available = sizeof(data->buf) - data->bufpos; + if (data->cork && data->overflow) { + return; + } + if (len > available) { - flush_buf(ui, false); + if (data->cork) { + data->overflow = true; + return; + } else { + flush_buf(ui); + } } memcpy(data->buf + data->bufpos, str, len); @@ -1460,35 +1536,37 @@ static void patch_terminfo_bugs(TUIData *data, const char *term, } } - // Some terminals cannot be trusted to report DECSCUSR support. So we keep - // blacklist for when we should not trust the reported features. - if (!((vte_version != 0 && vte_version < 3900) || konsole)) { - // Dickey ncurses terminfo has included the Ss and Se capabilities, - // pioneered by tmux, since 2011-07-14. So adding them to terminal types, - // that do actually have such control sequences but lack the correct - // definitions in terminfo, is a fixup, not an augmentation. + // Blacklist of terminals that cannot be trusted to report DECSCUSR support. + if (!(st || (vte_version != 0 && vte_version < 3900) || konsole)) { data->unibi_ext.reset_cursor_style = unibi_find_ext_str(ut, "Se"); data->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss"); } + + // Dickey ncurses terminfo includes Ss/Se capabilities since 2011-07-14. So + // adding them to terminal types, that have such control sequences but lack + // the correct terminfo entries, is a fixup, not an augmentation. if (-1 == data->unibi_ext.set_cursor_style) { - // The DECSCUSR sequence to change the cursor shape is widely supported by - // several terminal types. https://github.com/gnachman/iTerm2/pull/92 + // DECSCUSR (cursor shape) sequence is widely supported by several terminal + // types. https://github.com/gnachman/iTerm2/pull/92 // xterm extension: vertical bar - if (!konsole && ((xterm && !vte_version) // anything claiming xterm compat - // per MinTTY 0.4.3-1 release notes from 2009 - || putty - // per https://bugzilla.gnome.org/show_bug.cgi?id=720821 - || (vte_version >= 3900) - || tmux // per tmux manual page - // https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html - || screen - || rxvt // per command.C - // per analysis of VT100Terminal.m - || iterm || iterm_pretending_xterm - || teraterm // per TeraTerm "Supported Control Functions" doco - // Some linux-type terminals (such as console-terminal-emulator - // from the nosh toolset) implement implement the xterm extension. - || (linuxvt && (xterm_version || (vte_version > 0) || colorterm)))) { + if (!konsole + && ((xterm && !vte_version) // anything claiming xterm compat + // per MinTTY 0.4.3-1 release notes from 2009 + || putty + // per https://bugzilla.gnome.org/show_bug.cgi?id=720821 + || (vte_version >= 3900) + || tmux // per tmux manual page + // https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html + || screen + || st // #7641 + || rxvt // per command.C + // per analysis of VT100Terminal.m + || iterm || iterm_pretending_xterm + || teraterm // per TeraTerm "Supported Control Functions" doco + // Some linux-type terminals implement the xterm extension. + // Example: console-terminal-emulator from the nosh toolset. + || (linuxvt + && (xterm_version || (vte_version > 0) || colorterm)))) { data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q"); if (-1 == data->unibi_ext.reset_cursor_style) { @@ -1577,11 +1655,13 @@ static void augment_terminfo(TUIData *data, const char *term, || konsole // per commentary in VT102Emulation.cpp || teraterm // per TeraTerm "Supported Control Functions" doco || rxvt) { // per command.C - data->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.resize_screen = (int)unibi_add_ext_str(ut, + "ext.resize_screen", "\x1b[8;%p1%d;%p2%dt"); } if (putty || xterm || rxvt) { - data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.reset_scroll_region = (int)unibi_add_ext_str(ut, + "ext.reset_scroll_region", "\x1b[r"); } @@ -1639,25 +1719,33 @@ static void augment_terminfo(TUIData *data, const char *term, /// Terminals usually ignore unrecognized private modes, and there is no /// known ambiguity with these. So we just set them unconditionally. - data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.enable_lr_margin = (int)unibi_add_ext_str(ut, + "ext.enable_lr_margin", "\x1b[?69h"); - data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.disable_lr_margin = (int)unibi_add_ext_str(ut, + "ext.disable_lr_margin", "\x1b[?69l"); - data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(ut, + "ext.enable_bpaste", "\x1b[?2004h"); - data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(ut, + "ext.disable_bpaste", "\x1b[?2004l"); - data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(ut, + "ext.enable_focus", rxvt ? "\x1b]777;focus;on\x7" : "\x1b[?1004h"); - data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(ut, + "ext.disable_focus", rxvt ? "\x1b]777;focus;off\x7" : "\x1b[?1004l"); - data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.enable_mouse = (int)unibi_add_ext_str(ut, + "ext.enable_mouse", "\x1b[?1002h\x1b[?1006h"); - data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, NULL, + data->unibi_ext.disable_mouse = (int)unibi_add_ext_str(ut, + "ext.disable_mouse", "\x1b[?1002l\x1b[?1006l"); } -static void flush_buf(UI *ui, bool toggle_cursor) +static void flush_buf(UI *ui) { uv_write_t req; uv_buf_t bufs[3]; @@ -1668,26 +1756,26 @@ static void flush_buf(UI *ui, bool toggle_cursor) return; } - if (toggle_cursor && !data->is_invisible) { + if (!data->is_invisible) { // cursor is visible. Write a "cursor invisible" command before writing the // buffer. bufp->base = data->invis; - bufp->len = data->invislen; + bufp->len = UV_BUF_LEN(data->invislen); bufp++; data->is_invisible = true; } if (data->bufpos > 0) { bufp->base = data->buf; - bufp->len = data->bufpos; + bufp->len = UV_BUF_LEN(data->bufpos); bufp++; } - if (toggle_cursor && !data->busy && data->is_invisible) { + if (!data->busy && data->is_invisible) { // not busy and the cursor is invisible. Write a "cursor normal" command // after writing the buffer. bufp->base = data->norm; - bufp->len = data->normlen; + bufp->len = UV_BUF_LEN(data->normlen); bufp++; data->is_invisible = data->busy; } @@ -1696,6 +1784,7 @@ static void flush_buf(UI *ui, bool toggle_cursor) bufs, (unsigned)(bufp - bufs), NULL); uv_run(&data->write_loop, UV_RUN_DEFAULT); data->bufpos = 0; + data->overflow = false; } #if TERMKEY_VERSION_MAJOR > 0 || TERMKEY_VERSION_MINOR > 18 @@ -1739,6 +1828,12 @@ static const char *tui_tk_ti_getstr(const char *name, const char *value, if (value != NULL && strequal(stty_erase, value)) { return stty_erase[0] == DEL ? CTRL_H_STR : DEL_STR; } + } else if (strequal(name, "key_mouse")) { + DLOG("libtermkey:kmous=%s", value); + // If key_mouse is found, libtermkey uses its terminfo driver (driver-ti.c) + // for mouse input, which by accident only supports X10 protocol. + // Force libtermkey to fallback to its CSI driver (driver-csi.c). #7948 + return NULL; } return value; diff --git a/src/nvim/ugrid.c b/src/nvim/ugrid.c index 2b5e96ee60..6d420ef2f8 100644 --- a/src/nvim/ugrid.c +++ b/src/nvim/ugrid.c @@ -17,7 +17,7 @@ void ugrid_init(UGrid *grid) { grid->attrs = HLATTRS_INIT; - grid->fg = grid->bg = -1; + grid->clear_attrs = HLATTRS_INIT; grid->cells = NULL; } @@ -107,6 +107,7 @@ UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size) UCell *cell = grid->cells[grid->row] + grid->col; cell->data[size] = 0; cell->attrs = grid->attrs; + assert(size <= CELLBYTES); if (text) { memcpy(cell->data, text, size); @@ -118,9 +119,7 @@ UCell *ugrid_put(UGrid *grid, uint8_t *text, size_t size) static void clear_region(UGrid *grid, int top, int bot, int left, int right) { - HlAttrs clear_attrs = HLATTRS_INIT; - clear_attrs.foreground = grid->fg; - clear_attrs.background = grid->bg; + HlAttrs clear_attrs = grid->clear_attrs; UGRID_FOREACH_CELL(grid, top, bot, left, right, { cell->data[0] = ' '; cell->data[1] = 0; @@ -135,6 +134,7 @@ static void destroy_cells(UGrid *grid) xfree(grid->cells[i]); } xfree(grid->cells); + grid->cells = NULL; } } diff --git a/src/nvim/ugrid.h b/src/nvim/ugrid.h index 1cf047502d..60c9068eb1 100644 --- a/src/nvim/ugrid.h +++ b/src/nvim/ugrid.h @@ -7,15 +7,17 @@ typedef struct ucell UCell; typedef struct ugrid UGrid; +#define CELLBYTES (4 * (MAX_MCO+1)) + struct ucell { - char data[6 * MAX_MCO + 1]; + char data[CELLBYTES + 1]; HlAttrs attrs; }; struct ugrid { int top, bot, left, right; int row, col; - int bg, fg; + HlAttrs clear_attrs; int width, height; HlAttrs attrs; UCell **cells; diff --git a/src/nvim/ui.c b/src/nvim/ui.c index 3b8b3ac9a7..42366fdb76 100644 --- a/src/nvim/ui.c +++ b/src/nvim/ui.c @@ -49,13 +49,13 @@ #define MAX_UI_COUNT 16 static UI *uis[MAX_UI_COUNT]; -static bool ui_ext[UI_WIDGETS] = { 0 }; +static bool ui_ext[kUIExtCount] = { 0 }; static size_t ui_count = 0; static int row = 0, col = 0; static struct { int top, bot, left, right; } sr; -static int current_attr_code = 0; +static int current_attr_code = -1; static bool pending_cursor_update = false; static int busy = 0; static int height, width; @@ -107,8 +107,9 @@ static char uilog_last_event[1024] = { 0 }; } \ } while (0) #endif -#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, MORE, ZERO, ignore) -#define SELECT_NTH(a1, a2, a3, a4, a5, a6, ...) a6 +#define CNT(...) SELECT_NTH(__VA_ARGS__, MORE, MORE, MORE, \ + MORE, MORE, ZERO, ignore) +#define SELECT_NTH(a1, a2, a3, a4, a5, a6, a7, ...) a7 #define UI_CALL_HELPER(c, ...) UI_CALL_HELPER2(c, __VA_ARGS__) // Resolves to UI_CALL_MORE or UI_CALL_ZERO. #define UI_CALL_HELPER2(c, ...) UI_CALL_##c(__VA_ARGS__) @@ -145,6 +146,9 @@ void ui_builtin_stop(void) bool ui_rgb_attached(void) { + if (!headless_mode && p_tgc) { + return true; + } for (size_t i = 0; i < ui_count; i++) { if (uis[i]->rgb) { return true; @@ -168,84 +172,61 @@ void ui_event(char *name, Array args) } -/// Converts an attrentry_T into an HlAttrs +/// Converts an HlAttrs into Dictionary /// /// @param[in] aep data to convert /// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*' -HlAttrs attrentry2hlattrs(const attrentry_T *aep, bool use_rgb) +Dictionary hlattrs2dict(const HlAttrs *aep, bool use_rgb) { assert(aep); - - HlAttrs attrs = HLATTRS_INIT; - int mask = 0; - - mask = use_rgb ? aep->rgb_ae_attr : aep->cterm_ae_attr; - - attrs.bold = mask & HL_BOLD; - attrs.underline = mask & HL_UNDERLINE; - attrs.undercurl = mask & HL_UNDERCURL; - attrs.italic = mask & HL_ITALIC; - attrs.reverse = mask & (HL_INVERSE | HL_STANDOUT); - - if (use_rgb) { - if (aep->rgb_fg_color != -1) { - attrs.foreground = aep->rgb_fg_color; - } - - if (aep->rgb_bg_color != -1) { - attrs.background = aep->rgb_bg_color; - } - - if (aep->rgb_sp_color != -1) { - attrs.special = aep->rgb_sp_color; - } - } else { - if (cterm_normal_fg_color != aep->cterm_fg_color) { - attrs.foreground = aep->cterm_fg_color - 1; - } - - if (cterm_normal_bg_color != aep->cterm_bg_color) { - attrs.background = aep->cterm_bg_color - 1; - } - } - - return attrs; -} - -Dictionary hlattrs2dict(HlAttrs attrs) -{ Dictionary hl = ARRAY_DICT_INIT; + int mask = use_rgb ? aep->rgb_ae_attr : aep->cterm_ae_attr; - if (attrs.bold) { + if (mask & HL_BOLD) { PUT(hl, "bold", BOOLEAN_OBJ(true)); } - if (attrs.underline) { + if (mask & HL_STANDOUT) { + PUT(hl, "standout", BOOLEAN_OBJ(true)); + } + + if (mask & HL_UNDERLINE) { PUT(hl, "underline", BOOLEAN_OBJ(true)); } - if (attrs.undercurl) { + if (mask & HL_UNDERCURL) { PUT(hl, "undercurl", BOOLEAN_OBJ(true)); } - if (attrs.italic) { + if (mask & HL_ITALIC) { PUT(hl, "italic", BOOLEAN_OBJ(true)); } - if (attrs.reverse) { + if (mask & HL_INVERSE) { PUT(hl, "reverse", BOOLEAN_OBJ(true)); } - if (attrs.foreground != -1) { - PUT(hl, "foreground", INTEGER_OBJ(attrs.foreground)); - } - if (attrs.background != -1) { - PUT(hl, "background", INTEGER_OBJ(attrs.background)); - } + if (use_rgb) { + if (aep->rgb_fg_color != -1) { + PUT(hl, "foreground", INTEGER_OBJ(aep->rgb_fg_color)); + } + + if (aep->rgb_bg_color != -1) { + PUT(hl, "background", INTEGER_OBJ(aep->rgb_bg_color)); + } - if (attrs.special != -1) { - PUT(hl, "special", INTEGER_OBJ(attrs.special)); + if (aep->rgb_sp_color != -1) { + PUT(hl, "special", INTEGER_OBJ(aep->rgb_sp_color)); + } + } else { + if (cterm_normal_fg_color != aep->cterm_fg_color) { + PUT(hl, "foreground", INTEGER_OBJ(aep->cterm_fg_color - 1)); + } + + if (cterm_normal_bg_color != aep->cterm_bg_color) { + PUT(hl, "background", INTEGER_OBJ(aep->cterm_bg_color - 1)); + } } return hl; @@ -263,8 +244,8 @@ void ui_refresh(void) } int width = INT_MAX, height = INT_MAX; - bool ext_widgets[UI_WIDGETS]; - for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) { + bool ext_widgets[kUIExtCount]; + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { ext_widgets[i] = true; } @@ -272,7 +253,7 @@ void ui_refresh(void) UI *ui = uis[i]; width = MIN(ui->width, width); height = MIN(ui->height, height); - for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) { + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { ext_widgets[i] &= ui->ui_ext[i]; } } @@ -284,12 +265,15 @@ void ui_refresh(void) screen_resize(width, height); p_lz = save_p_lz; - for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) { - ui_set_external(i, ext_widgets[i]); + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { + ui_ext[i] = ext_widgets[i]; + ui_call_option_set(cstr_as_string((char *)ui_ext_names[i]), + BOOLEAN_OBJ(ext_widgets[i])); } ui_mode_info_set(); old_mode_idx = -1; ui_cursor_shape(); + current_attr_code = -1; } static void ui_refresh_event(void **argv) @@ -307,6 +291,11 @@ void ui_resize(int new_width, int new_height) width = new_width; height = new_height; + // TODO(bfredl): update default colors when they changed, NOT on resize. + ui_call_default_colors_set(normal_fg, normal_bg, normal_sp, + cterm_normal_fg_color, cterm_normal_bg_color); + + // Deprecated: UI_CALL(update_fg, (ui->rgb ? normal_fg : cterm_normal_fg_color - 1)); UI_CALL(update_bg, (ui->rgb ? normal_bg : cterm_normal_bg_color - 1)); UI_CALL(update_sp, (ui->rgb ? normal_sp : -1)); @@ -339,6 +328,7 @@ void ui_attach_impl(UI *ui) } uis[ui_count++] = ui; + ui_refresh_options(); ui_refresh(); } @@ -399,26 +389,28 @@ void ui_reset_scroll_region(void) ui_call_set_scroll_region(sr.top, sr.bot, sr.left, sr.right); } -void ui_start_highlight(int attr_code) +void ui_set_highlight(int attr_code) { + if (current_attr_code == attr_code) { + return; + } current_attr_code = attr_code; - if (!ui_count) { - return; + HlAttrs attrs = HLATTRS_INIT; + + if (attr_code != 0) { + HlAttrs *aep = syn_cterm_attr2entry(attr_code); + if (aep) { + attrs = *aep; + } } - set_highlight_args(current_attr_code); + UI_CALL(highlight_set, attrs); } -void ui_stop_highlight(void) +void ui_clear_highlight(void) { - current_attr_code = HL_NORMAL; - - if (!ui_count) { - return; - } - - set_highlight_args(current_attr_code); + ui_set_highlight(0); } void ui_puts(uint8_t *str) @@ -446,6 +438,12 @@ void ui_puts(uint8_t *str) ui_linefeed(); } p += clen; + + if (p_wd) { // 'writedelay': flush & delay each time. + ui_flush(); + uint64_t wd = (uint64_t)labs(p_wd); + os_delay(wd, false); + } } } @@ -489,26 +487,6 @@ void ui_flush(void) ui_call_flush(); } -static void set_highlight_args(int attr_code) -{ - HlAttrs rgb_attrs = HLATTRS_INIT; - HlAttrs cterm_attrs = rgb_attrs; - - if (attr_code == HL_NORMAL) { - goto end; - } - attrentry_T *aep = syn_cterm_attr2entry(attr_code); - - if (!aep) { - goto end; - } - - rgb_attrs = attrentry2hlattrs(aep, true); - cterm_attrs = attrentry2hlattrs(aep, false); - -end: - UI_CALL(highlight_set, (ui->rgb ? rgb_attrs : cterm_attrs)); -} void ui_linefeed(void) { @@ -548,15 +526,23 @@ void ui_cursor_shape(void) } /// Returns true if `widget` is externalized. -bool ui_is_external(UIWidget widget) +bool ui_is_external(UIExtension widget) { return ui_ext[widget]; } -/// Sets `widget` as "external". -/// Such widgets are not drawn by Nvim; external UIs are expected to handle -/// higher-level UI events and present the data. -void ui_set_external(UIWidget widget, bool external) +Array ui_array(void) { - ui_ext[widget] = external; + Array all_uis = ARRAY_DICT_INIT; + for (size_t i = 0; i < ui_count; i++) { + Dictionary dic = ARRAY_DICT_INIT; + PUT(dic, "width", INTEGER_OBJ(uis[i]->width)); + PUT(dic, "height", INTEGER_OBJ(uis[i]->height)); + PUT(dic, "rgb", BOOLEAN_OBJ(uis[i]->rgb)); + for (UIExtension j = 0; j < kUIExtCount; j++) { + PUT(dic, ui_ext_names[j], BOOLEAN_OBJ(uis[i]->ui_ext[j])); + } + ADD(all_uis, DICTIONARY_OBJ(dic)); + } + return all_uis; } diff --git a/src/nvim/ui.h b/src/nvim/ui.h index f1ea0716e6..48896a6a3f 100644 --- a/src/nvim/ui.h +++ b/src/nvim/ui.h @@ -13,29 +13,27 @@ typedef enum { kUIPopupmenu, kUITabline, kUIWildmenu, -} UIWidget; -#define UI_WIDGETS (kUIWildmenu + 1) + kUIExtCount, +} UIExtension; -typedef struct { - bool bold, underline, undercurl, italic, reverse; - int foreground, background, special; -} HlAttrs; +EXTERN const char *ui_ext_names[] INIT(= { + "ext_cmdline", + "ext_popupmenu", + "ext_tabline", + "ext_wildmenu" +}); -#define HLATTRS_INIT \ - ((HlAttrs){ false, false, false, false, false, -1, -1, -1 }) typedef struct ui_t UI; struct ui_t { bool rgb; - bool ui_ext[UI_WIDGETS]; ///< Externalized widgets + bool ui_ext[kUIExtCount]; ///< Externalized widgets int width, height; void *data; - #ifdef INCLUDE_GENERATED_DECLARATIONS # include "ui_events.generated.h" #endif - void (*event)(UI *ui, char *name, Array args, bool *args_consumed); void (*stop)(UI *ui); }; diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c index 5585886612..56e0c0c454 100644 --- a/src/nvim/ui_bridge.c +++ b/src/nvim/ui_bridge.c @@ -59,16 +59,15 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) rv->bridge.put = ui_bridge_put; rv->bridge.bell = ui_bridge_bell; rv->bridge.visual_bell = ui_bridge_visual_bell; - rv->bridge.update_fg = ui_bridge_update_fg; - rv->bridge.update_bg = ui_bridge_update_bg; - rv->bridge.update_sp = ui_bridge_update_sp; + rv->bridge.default_colors_set = ui_bridge_default_colors_set; rv->bridge.flush = ui_bridge_flush; rv->bridge.suspend = ui_bridge_suspend; rv->bridge.set_title = ui_bridge_set_title; rv->bridge.set_icon = ui_bridge_set_icon; + rv->bridge.option_set = ui_bridge_option_set; rv->scheduler = scheduler; - for (UIWidget i = 0; (int)i < UI_WIDGETS; i++) { + for (UIExtension i = 0; (int)i < kUIExtCount; i++) { rv->bridge.ui_ext[i] = ui->ui_ext[i]; } @@ -82,6 +81,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler) abort(); } + // Suspend the main thread until CONTINUE is called by the UI thread. while (!rv->ready) { uv_cond_wait(&rv->cond, &rv->mutex); } @@ -106,6 +106,9 @@ static void ui_thread_run(void *data) static void ui_bridge_stop(UI *b) { + // Detach brigde first, so that "stop" is the last event the TUI loop + // receives from the main thread. #8041 + ui_detach_impl(b); UIBridgeData *bridge = (UIBridgeData *)b; bool stopped = bridge->stopped = false; UI_BRIDGE_CALL(b, stop, 1, b); @@ -116,12 +119,12 @@ static void ui_bridge_stop(UI *b) if (stopped) { break; } - loop_poll_events(&main_loop, 10); + loop_poll_events(&main_loop, 10); // Process one event. } uv_thread_join(&bridge->ui_thread); uv_mutex_destroy(&bridge->mutex); uv_cond_destroy(&bridge->cond); - ui_detach_impl(b); + xfree(bridge->ui); // Threads joined, now safe to free UI container. #7922 xfree(b); } static void ui_bridge_stop_event(void **argv) @@ -149,7 +152,7 @@ static void ui_bridge_suspend(UI *b) uv_mutex_lock(&data->mutex); UI_BRIDGE_CALL(b, suspend, 1, b); data->ready = false; - // suspend the main thread until CONTINUE is called by the UI thread + // Suspend the main thread until CONTINUE is called by the UI thread. while (!data->ready) { uv_cond_wait(&data->cond, &data->mutex); } diff --git a/src/nvim/undo.c b/src/nvim/undo.c index 3d7ebc8837..35857510fc 100644 --- a/src/nvim/undo.c +++ b/src/nvim/undo.c @@ -2096,8 +2096,8 @@ void undo_time(long step, int sec, int file, int absolute) uhp = uhp->uh_prev.ptr; if (uhp == NULL || uhp->uh_walk != mark) { - /* Need to redo more but can't find it... */ - EMSG2(_(e_intern2), "undo_time()"); + // Need to redo more but can't find it... + internal_error("undo_time()"); break; } } @@ -2163,8 +2163,8 @@ static void u_undoredo(int undo) if (top > curbuf->b_ml.ml_line_count || top >= bot || bot > curbuf->b_ml.ml_line_count + 1) { unblock_autocmds(); - EMSG(_("E438: u_undo: line numbers wrong")); - changed(); /* don't want UNCHANGED now */ + IEMSG(_("E438: u_undo: line numbers wrong")); + changed(); // don't want UNCHANGED now return; } @@ -2268,12 +2268,14 @@ static void u_undoredo(int undo) curhead->uh_entry = newlist; curhead->uh_flags = new_flags; - if ((old_flags & UH_EMPTYBUF) && bufempty()) + if ((old_flags & UH_EMPTYBUF) && BUFEMPTY()) { curbuf->b_ml.ml_flags |= ML_EMPTY; - if (old_flags & UH_CHANGED) + } + if (old_flags & UH_CHANGED) { changed(); - else + } else { unchanged(curbuf, FALSE); + } /* * restore marks from before undo/redo @@ -2655,7 +2657,7 @@ static void u_unch_branch(u_header_T *uhp) static u_entry_T *u_get_headentry(void) { if (curbuf->b_u_newhead == NULL || curbuf->b_u_newhead->uh_entry == NULL) { - EMSG(_("E439: undo list corrupt")); + IEMSG(_("E439: undo list corrupt")); return NULL; } return curbuf->b_u_newhead->uh_entry; @@ -2684,11 +2686,11 @@ static void u_getbot(void) extra = curbuf->b_ml.ml_line_count - uep->ue_lcount; uep->ue_bot = uep->ue_top + uep->ue_size + 1 + extra; if (uep->ue_bot < 1 || uep->ue_bot > curbuf->b_ml.ml_line_count) { - EMSG(_("E440: undo line missing")); - uep->ue_bot = uep->ue_top + 1; /* assume all lines deleted, will - * get all the old lines back - * without deleting the current - * ones */ + IEMSG(_("E440: undo line missing")); + uep->ue_bot = uep->ue_top + 1; // assume all lines deleted, will + // get all the old lines back + // without deleting the current + // ones } curbuf->b_u_newhead->uh_getbot_entry = NULL; @@ -2941,17 +2943,20 @@ bool curbufIsChanged(void) && (curbuf->b_changed || file_ff_differs(curbuf, true))); } -/* - * For undotree(): Append the list of undo blocks at "first_uhp" to "list". - * Recursive. - */ -void u_eval_tree(u_header_T *first_uhp, list_T *list) +/// Append the list of undo blocks to a newly allocated list +/// +/// For use in undotree(). Recursive. +/// +/// @param[in] first_uhp Undo blocks list to start with. +/// +/// @return [allocated] List with a representation of undo blocks. +list_T *u_eval_tree(const u_header_T *const first_uhp) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET { - u_header_T *uhp = first_uhp; - dict_T *dict; + list_T *const list = tv_list_alloc(kListLenMayKnow); - while (uhp != NULL) { - dict = tv_dict_alloc(); + for (const u_header_T *uhp = first_uhp; uhp != NULL; uhp = uhp->uh_prev.ptr) { + dict_T *const dict = tv_dict_alloc(); tv_dict_add_nr(dict, S_LEN("seq"), (varnumber_T)uhp->uh_seq); tv_dict_add_nr(dict, S_LEN("time"), (varnumber_T)uhp->uh_time); if (uhp == curbuf->b_u_newhead) { @@ -2965,14 +2970,12 @@ void u_eval_tree(u_header_T *first_uhp, list_T *list) } if (uhp->uh_alt_next.ptr != NULL) { - list_T *alt_list = tv_list_alloc(); - // Recursive call to add alternate undo tree. - u_eval_tree(uhp->uh_alt_next.ptr, alt_list); - tv_dict_add_list(dict, S_LEN("alt"), alt_list); + tv_dict_add_list(dict, S_LEN("alt"), u_eval_tree(uhp->uh_alt_next.ptr)); } tv_list_append_dict(list, dict); - uhp = uhp->uh_prev.ptr; } + + return list; } diff --git a/src/nvim/version.c b/src/nvim/version.c index 8d85855c6e..77ae849d2e 100644 --- a/src/nvim/version.c +++ b/src/nvim/version.c @@ -30,8 +30,8 @@ // for ":version", ":intro", and "nvim --version" #ifndef NVIM_VERSION_MEDIUM #define NVIM_VERSION_MEDIUM "v" STR(NVIM_VERSION_MAJOR)\ - "." STR(NVIM_VERSION_MINOR) "." STR(NVIM_VERSION_PATCH)\ - NVIM_VERSION_PRERELEASE +"." STR(NVIM_VERSION_MINOR) "." STR(NVIM_VERSION_PATCH)\ +NVIM_VERSION_PRERELEASE #endif #define NVIM_VERSION_LONG "NVIM " NVIM_VERSION_MEDIUM @@ -47,37 +47,241 @@ char *version_cflags = "Compilation: " NVIM_VERSION_CFLAGS; static char *features[] = { #ifdef HAVE_ACL - "+acl", +"+acl", #else - "-acl", +"-acl", #endif #if (defined(HAVE_ICONV_H) && defined(USE_ICONV)) || defined(DYNAMIC_ICONV) # ifdef DYNAMIC_ICONV - "+iconv/dyn", +"+iconv/dyn", # else - "+iconv", +"+iconv", # endif #else - "-iconv", +"-iconv", #endif #ifdef HAVE_JEMALLOC - "+jemalloc", +"+jemalloc", #else - "-jemalloc", +"-jemalloc", #endif #ifdef FEAT_TUI - "+tui", +"+tui", #else - "-tui", +"-tui", #endif - NULL +NULL }; // clang-format off static const int included_patches[] = { + 1561, + // 1560, + // 1559, + // 1558, + // 1557, + // 1556, + // 1555, + // 1554, + // 1553, + // 1552, + // 1551, + // 1550, + // 1549, + // 1548, + // 1547, + // 1546, + // 1545, + // 1544, + // 1543, + // 1542, + // 1541, + // 1540, + // 1539, + // 1538, + // 1537, + // 1536, + // 1535, + // 1534, + // 1533, + // 1532, + // 1531, + // 1530, + // 1529, + // 1528, + // 1527, + // 1526, + // 1525, + // 1524, + // 1523, + // 1522, + // 1521, + // 1520, + // 1519, + // 1518, + // 1517, + // 1516, + // 1515, + // 1514, + // 1513, + // 1512, + // 1511, + // 1510, + // 1509, + // 1508, + // 1507, + // 1506, + // 1505, + // 1504, + // 1503, + // 1502, + // 1501, + // 1500, + // 1499, + // 1498, + // 1497, + // 1496, + // 1495, + // 1494, + 1493, + // 1492, + // 1491, + // 1490, + // 1489, + // 1488, + // 1487, + // 1486, + // 1485, + // 1484, + 1483, + // 1482, + // 1481, + // 1480, + // 1479, + // 1478, + // 1477, + // 1476, + 1475, + // 1474, + // 1473, + // 1472, + // 1471, + // 1470, + // 1469, + // 1468, + // 1467, + // 1466, + // 1465, + // 1464, + // 1463, + // 1462, + // 1461, + // 1460, + // 1459, + // 1458, + // 1457, + // 1456, + // 1455, + // 1454, + // 1453, + // 1452, + // 1451, + // 1450, + // 1449, + // 1448, + // 1447, + // 1446, + // 1445, + // 1444, + // 1443, + 1442, + // 1441, + // 1440, + 1439, + // 1438, + // 1437, + // 1436, + 1435, + // 1434, + // 1433, + // 1432, + // 1431, + // 1430, + // 1429, + // 1428, + // 1427, + // 1426, + // 1425, + // 1424, + // 1423, + // 1422, + // 1421, + // 1420, + 1419, + // 1418, + // 1417, + // 1416, + // 1415, + // 1414, + // 1413, + // 1412, + // 1411, + // 1410, + // 1409, + // 1408, + // 1407, + // 1406, + // 1405, + // 1404, + 1403, + 1402, + // 1401, + // 1400, + // 1399, + // 1398, + // 1397, + 1396, + // 1395, + // 1394, + 1393, + // 1392, + // 1391, + // 1390, + // 1389, + // 1388, + // 1387, + // 1386, + // 1385, + // 1384, + // 1383, + // 1382, + // 1381, + // 1380, + // 1379, + // 1378, + // 1377, + // 1376, + // 1375, + // 1374, + // 1373, + // 1372, + // 1371, + 1370, + // 1369, + // 1368, + // 1367, + // 1366, + 1365, + // 1364, + // 1363, + // 1362, + // 1361, + // 1360, + // 1359, + // 1358, 1357, // 1356, // 1355, @@ -102,11 +306,11 @@ static const int included_patches[] = { // 1336, // 1335, // 1334, - // 1333, + 1333, // 1332, // 1331, // 1330, - // 1329, + 1329, // 1328, // 1327, // 1326, @@ -131,9 +335,9 @@ static const int included_patches[] = { // 1307, // 1306, // 1305, - // 1304, + 1304, // 1303, - // 1302, + 1302, // 1301, // 1300, // 1299, @@ -146,7 +350,7 @@ static const int included_patches[] = { // 1292, // 1291, // 1290, - // 1289, + 1289, // 1288, // 1287, // 1286, @@ -164,7 +368,7 @@ static const int included_patches[] = { // 1274, // 1273, // 1272, - // 1271, + 1271, // 1270, // 1269, // 1268, @@ -173,7 +377,7 @@ static const int included_patches[] = { // 1265, // 1264, // 1263, - // 1262, + 1262, // 1261, // 1260, // 1259, @@ -185,7 +389,7 @@ static const int included_patches[] = { // 1253, // 1252, // 1251, - // 1250, + 1250, // 1249, // 1248, // 1247, @@ -197,7 +401,7 @@ static const int included_patches[] = { // 1241, // 1240, // 1239, - // 1238, + 1238, // 1237, // 1236, // 1235, @@ -205,18 +409,18 @@ static const int included_patches[] = { // 1233, // 1232, // 1231, - // 1230, - // 1229, + 1230, + 1229, // 1228, // 1227, - // 1226, - // 1225, - // 1224, - // 1223, - // 1222, - // 1221, + 1226, + 1225, + 1224, + 1223, + 1222, + 1221, // 1220, - // 1219, + 1219, // 1218, // 1217, // 1216, @@ -225,11 +429,190 @@ static const int included_patches[] = { // 1213, // 1212, // 1211, - // 1210, + 1210, // 1209, // 1208, - // 1207, + 1207, 1206, + 1205, + 1204, + // 1203, + // 1202, + // 1201, + 1200, + // 1199, + // 1198, + // 1197, + // 1196, + // 1195, + // 1194, + // 1193, + // 1192, + // 1191, + // 1190, + 1189, + 1188, + // 1187, + 1186, + // 1185, + // 1184, + // 1183, + // 1182, + // 1181, + // 1180, + // 1179, + // 1178, + // 1177, + // 1176, + // 1175, + // 1174, + // 1173, + // 1172, + // 1171, + // 1170, + // 1169, + // 1168, + // 1167, + // 1166, + // 1165, + // 1164, + // 1163, + // 1162, + // 1161, + // 1160, + // 1159, + 1158, + // 1157, + // 1156, + // 1155, + // 1154, + // 1153, + // 1152, + // 1151, + // 1150, + // 1149, + // 1148, + // 1147, + // 1146, + // 1145, + // 1144, + // 1143, + // 1142, + // 1141, + // 1140, + // 1139, + // 1138, + // 1137, + // 1136, + // 1135, + // 1134, + // 1133, + // 1132, + // 1131, + // 1130, + // 1129, + // 1128, + // 1127, + // 1126, + // 1125, + // 1124, + // 1123, + // 1122, + // 1121, + // 1120, + // 1119, + // 1118, + // 1117, + // 1116, + // 1115, + // 1114, + // 1113, + // 1112, + // 1111, + // 1110, + // 1109, + 1108, + // 1107, + // 1106, + // 1105, + // 1104, + // 1103, + // 1102, + // 1101, + // 1100, + // 1099, + // 1098, + // 1097, + // 1096, + // 1095, + // 1094, + // 1093, + // 1092, + // 1091, + // 1090, + // 1089, + // 1088, + // 1087, + // 1086, + // 1085, + // 1084, + // 1083, + // 1082, + // 1081, + // 1080, + // 1079, + // 1078, + // 1077, + // 1076, + // 1075, + // 1074, + // 1073, + // 1072, + // 1071, + // 1070, + // 1069, + // 1068, + // 1067, + // 1066, + // 1065, + // 1064, + // 1063, + // 1062, + // 1061, + // 1060, + // 1059, + // 1058, + // 1057, + // 1056, + // 1055, + // 1054, + // 1053, + // 1052, + // 1051, + // 1050, + // 1049, + // 1048, + // 1047, + // 1046, + // 1045, + // 1044, + // 1043, + // 1042, + // 1041, + // 1040, + // 1039, + // 1038, + // 1037, + // 1036, + // 1035, + // 1034, + // 1033, + // 1032, + // 1031, + // 1030, + // 1029, + // 1028, + // 1027, // 1026, 1025, 1024, @@ -237,7 +620,7 @@ static const int included_patches[] = { // 1022, // 1021, // 1020, - // 1019, + 1019, // 1018, // 1017, // 1016, @@ -249,7 +632,7 @@ static const int included_patches[] = { // 1010, // 1009, // 1008, - // 1007, + 1007, // 1006, // 1005, // 1004, @@ -281,8 +664,8 @@ static const int included_patches[] = { // 978, // 977, // 976, - // 975, - // 974, + 975, + 974, // 973, // 972, // 971, @@ -294,14 +677,14 @@ static const int included_patches[] = { // 965, // 964, // 963, - // 962, + 962, // 961, // 960, // 959, // 958, // 957, // 956, - // 955, + 955, // 954, // 953, // 952, @@ -350,7 +733,7 @@ static const int included_patches[] = { // 909, // 908, // 907, - // 906, + 906, // 905, // 904, // 903, @@ -361,8 +744,8 @@ static const int included_patches[] = { // 898, // 897, // 896, - // 895, - // 894, + 895, + 894, // 893, // 892, // 891, @@ -394,11 +777,11 @@ static const int included_patches[] = { // 865, // 864, // 863, - // 862, - // 861, + 862, + 861, // 860, // 859, - // 858, + 858, // 857, // 856, // 855, @@ -409,7 +792,7 @@ static const int included_patches[] = { // 850, // 849, // 848, - // 847, + 847, // 846, // 845, // 844, @@ -464,7 +847,7 @@ static const int included_patches[] = { // 795, // 794, // 793, - // 792, + 792, // 791, // 790, // 789, @@ -520,7 +903,7 @@ static const int included_patches[] = { // 739, // 738, // 737, - // 736, + 736, // 735, // 734, // 733, @@ -533,9 +916,9 @@ static const int included_patches[] = { // 726, // 725, // 724, - // 723, + 723, // 722, - // 721, + 721, // 720, // 719, // 718, @@ -553,26 +936,26 @@ static const int included_patches[] = { // 706, // 705, // 704, - // 703, + 703, // 702, // 701, - // 700, - // 699, + 700, + 699, // 698, // 697, // 696, // 695, // 694, // 693, - // 692, + 692, // 691, // 690, - // 689, + 689, // 688, // 687, // 686, // 685, - // 684, + 684, // 683, // 682, // 681, @@ -580,33 +963,33 @@ static const int included_patches[] = { 679, 678, // 677, - // 676, + 676, // 675, - // 674, - // 673, - // 672, + 674, + 673, + 672, // 671, // 670, // 669, - // 668, - // 667, - // 666, - // 665, - // 664, + 668, + 667, + 666, + 665, + 664, // 663, - // 662, - // 661, - // 660, - // 659, - // 658, - // 657, - // 656, - // 655, - // 654, + 662, + 661, + 660, + 659, + 658, + 657, + 656, + 655, + 654, // 653, 652, // 651, - // 650, + 650, // 649, // 648, // 647, @@ -615,14 +998,14 @@ static const int included_patches[] = { // 644, // 643, // 642, - // 641, + 641, // 640, // 639, // 638, // 637, // 636, // 635, - // 634, + 634, // 633, // 632, // 631, @@ -634,7 +1017,7 @@ static const int included_patches[] = { // 625, // 624, // 623, - // 622, + 622, // 621, // 620, // 619, @@ -643,46 +1026,46 @@ static const int included_patches[] = { // 616, // 615, 614, - // 613, + 613, 612, // 611, // 610, // 609, - // 608, + 608, 607, - // 606, + 606, 605, // 604, // 603, // 602, - // 601, - // 600, - // 599, + 601, + 600, + 599, // 598, - // 597, + 597, // 596, - // 595, + 595, // 594, // 593, // 592, - // 591, - // 590, + 591, + 590, // 589, // 588, // 587, // 586, // 585, - // 584, + 584, // 583, - // 582, + 582, // 581, - // 580, - // 579, + 580, + 579, // 578, // 577, // 576, // 575, - // 574, + 574, // 573, // 572, 571, @@ -691,7 +1074,7 @@ static const int included_patches[] = { // 568, // 567, // 566, - // 565, + 565, // 564, // 563, // 562, @@ -720,7 +1103,7 @@ static const int included_patches[] = { // 539, // 538, // 537, - // 536, + 536, // 535, // 534, // 533, @@ -728,7 +1111,7 @@ static const int included_patches[] = { // 531, // 530, // 529, - // 528, + 528, // 527, // 526, // 525, @@ -739,7 +1122,7 @@ static const int included_patches[] = { // 520, // 519, 518, - // 517, + 517, // 516, // 515, // 514, @@ -766,27 +1149,27 @@ static const int included_patches[] = { // 493, // 492, // 491, - // 490, + 490, // 489, // 488, 487, 486, 485, - // 484, + 484, 483, 482, // 481, - // 480, - // 479, + 480, + 479, 478, 477, // 476, // 475, // 474, - // 473, - // 472, - // 471, - // 470, + 473, + 472, + 471, + 470, // 469, // 468, // 467, @@ -798,7 +1181,7 @@ static const int included_patches[] = { 461, // 460, 459, - // 458, + 458, 457, // 456, // 455, @@ -808,35 +1191,35 @@ static const int included_patches[] = { // 451, // 450, // 449, - // 448, + 448, // 447, - // 446, + 446, // 445, - // 444, - // 443, - // 442, + 444, + 443, + 442, // 441, - // 440, - // 439, + 440, + 439, // 438, 437, // 436, // 435, - // 434, - // 433, + 434, + 433, // 432, - // 431, + 431, // 430, // 429, // 428, - // 427, - // 426, + 427, + 426, // 425, - // 424, - // 423, + 424, + 423, // 422, - // 421, - // 420, + 421, + 420, // 419, // 418, // 417, @@ -850,39 +1233,39 @@ static const int included_patches[] = { // 409, 408, 407, - // 406, - // 405 NA - // 404, + 406, + 405, + 404, // 403, - // 402, + 402, // 401, - // 400 NA + 400, // 399, - // 398, + 398, // 397, // 396, // 395, 394, 393, // 392, - // 391, + 391, 390, - // 389, + 389, 388, // 387, // 386, - // 385, + 385, // 384, // 383, // 382, - // 381, - // 380, - // 379, + 381, + 380, + 379, 378, 377, 376, // 375, - // 374, + 374, // 373, // 372, // 371, @@ -892,20 +1275,20 @@ static const int included_patches[] = { // 367, // 366, // 365, - // 364, + 364, // 363, // 362, // 361, 360, - // 359, - // 358, + 359, + 358, // 357, // 356, // 355, // 354, 353, - // 352, - // 351, + 352, + 351, // 350, // 349, // 348, @@ -917,12 +1300,12 @@ static const int included_patches[] = { // 342, 341, // 340, - // 339, - // 338, - // 337, - // 336, - // 335, - // 334, + 339, + 338, + 337, + 336, + 335, + 334, 333, // 332, 331, @@ -933,73 +1316,73 @@ static const int included_patches[] = { 326, 325, 324, - // 323, + 323, 322, // 321, - // 320, + 320, 319, // 318, // 317, // 316, - // 315, - // 314, + 315, + 314, // 313, // 312, 311, - // 310, - // 309, + 310, + 309, 308, 307, 306, 305, // 304, // 303, - // 302 NA - // 301, + 302, + 301, 300, - // 299, + 299, 298, 297, // 296, 295, 294, - // 293, + 293, 292, 291, 290, 289, - // 288 NA + 288, 287, // 286, - // 285 NA - // 284 NA + 285, + 284, 283, 282, - // 281 NA + 281, 280, - // 279 NA - // 278 NA - // 277 NA - // 276 NA + 279, + 278, + 277, + 276, 275, 274, - // 273 NA - // 272 NA - // 271 NA - // 270 NA - // 269 NA - // 268 NA - // 267 NA + 273, + 272, + 271, + 270, + 269, + 268, + 267, 266, // 265, // 264, // 263, // 262, // 261, - // 260 NA + 260, 259, 258, - // 257 NA + 257, // 256, // 255, // 254, @@ -1007,118 +1390,118 @@ static const int included_patches[] = { // 252, // 251, 250, - // 249 NA - // 248 NA + 249, + 248, 247, - // 246 NA + 246, 245, - // 244, + 244, 243, 242, - // 241 NA - // 240 NA - // 239 NA + 241, + 240, + 239, // 238, 237, - // 236, + 236, 235, - // 234, + 234, // 233, - // 232 NA + 232, // 231, // 230, 229, // 228, - // 227, + 227, 226, - // 225, + 225, 224, 223, - // 222, - // 221 NA - // 220, + 222, + 221, + 220, 219, 218, - // 217 NA + 217, // 216, - // 215, - // 214, - // 213 NA - // 212, - // 211 NA + 215, + 214, + 213, + 212, + 211, // 210, 209, 208, // 207, 206, 205, - // 204, - // 203 NA - // 202, - // 201, - // 200, - // 199 NA - // 198, + 204, + 203, + 202, + 201, + 200, + 199, + 198, // 197, 196, 195, 194, - // 193 NA - // 192 NA - // 191 NA + 193, + 192, + 191, 190, 189, 188, - // 187 NA + 187, 186, - // 185, + 185, // 184, - // 183 NA + 183, 182, 181, - // 180 NA + 180, 179, 178, 177, 176, - // 175, + 175, 174, - // 173 NA + 173, 172, - // 171 NA - // 170 NA - // 169 NA + 171, + 170, + 169, 168, 167, - // 166 NA + 166, 165, 164, - // 163 NA - // 162 NA - // 161 NA - // 160, + 163, + 162, + 161, + 160, 159, 158, 157, 156, 155, - // 154, - // 153, - // 152 NA - // 151, + 154, + 153, + 152, + 151, 150, 149, 148, 147, 146, - // 145 NA - // 144 NA + 145, + 144, 143, 142, - // 141 NA + 141, 140, - // 139 NA - // 138 NA + 139, + 138, 137, 136, 135, @@ -1126,137 +1509,137 @@ static const int included_patches[] = { 133, 132, 131, - // 130 NA - // 129 NA + 130, + 129, 128, 127, 126, 125, 124, - // 123 NA - // 122 NA + 123, + 122, 121, - // 120 NA + 120, 119, 118, - // 117 NA + 117, 116, - // 115 NA - // 114 NA - // 113 NA + 115, + 114, + 113, 112, 111, 110, - // 109 NA - // 108 NA - // 107 NA + 109, + 108, + 107, 106, - // 105 NA + 105, 104, - // 103 NA + 103, 102, 101, 100, 99, - // 98 NA - // 97 NA + 98, + 97, 96, - // 95 NA - // 94 NA - // 93 NA + 95, + 94, + 93, 92, 91, 90, - // 89 NA + 89, 88, - // 87 NA + 87, 86, 85, 84, 83, - // 82 NA + 82, 81, - // 80 NA + 80, 79, 78, - // 77 NA - // 76 NA + 77, + 76, 75, - // 74, + 74, 73, - // 72 NA - // 71 NA - // 70 NA + 72, + 71, + 70, 69, 68, - // 67 NA + 67, 66, - // 65 NA + 65, 64, - // 63 NA + 63, 62, - // 61 NA + 61, 60, - // 59 NA + 59, 58, 57, 56, - // 55 NA - // 54 NA + 55, + 54, 53, 52, - // 51 NA - // 50 NA + 51, + 50, 49, - // 48 NA + 48, 47, 46, - // 45 NA + 45, 44, 43, 42, 41, 40, - // 39 NA + 39, 38, 37, - // 36 NA + 36, 35, 34, 33, 32, 31, - // 30 NA - // 29 NA - // 28 NA - // 27 NA + 30, + 29, + 28, + 27, 26, 25, - // 24 NA + 24, 23, - // 22 NA - // 21 NA + 22, + 21, 20, 19, - // 18 NA + 18, 17, - // 16 NA - // 15 NA - // 14 NA - // 13 NA + 16, + 15, + 14, + 13, 12, - // 11 NA - // 10 NA - // 9 NA + 11, + 10, + 9, 8, - // 7 NA + 7, 6, - // 5 NA + 5, 4, 3, 2, 1, - 0 + 0, }; // clang-format on @@ -1480,7 +1863,7 @@ static void version_msg(char *s) /// Show the intro message when not editing a file. void maybe_intro_message(void) { - if (bufempty() + if (BUFEMPTY() && (curbuf->b_fname == NULL) && (firstwin->w_next == NULL) && (vim_strchr(p_shm, SHM_INTRO) == NULL)) { diff --git a/src/nvim/vim.h b/src/nvim/vim.h index 7da3c8246f..0c13d331c8 100644 --- a/src/nvim/vim.h +++ b/src/nvim/vim.h @@ -26,13 +26,6 @@ /// length of a buffer to store a number in ASCII (64 bits binary + NUL) enum { NUMBUFLEN = 65 }; -// flags for vim_str2nr() -#define STR2NR_BIN 1 -#define STR2NR_OCT 2 -#define STR2NR_HEX 4 -#define STR2NR_ALL (STR2NR_BIN + STR2NR_OCT + STR2NR_HEX) -#define STR2NR_FORCE 8 // only when ONE of the above is used - #define MAX_TYPENR 65535 #define ROOT_UID 0 @@ -162,6 +155,7 @@ enum { EXPAND_USER_ADDR_TYPE, EXPAND_PACKADD, EXPAND_MESSAGES, + EXPAND_CHECKHEALTH, }; @@ -319,4 +313,8 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext() # define OPEN_CHR_FILES #endif +// Replacement for nchar used by nv_replace(). +#define REPLACE_CR_NCHAR -1 +#define REPLACE_NL_NCHAR -2 + #endif // NVIM_VIM_H diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c new file mode 100644 index 0000000000..ee59dc8c96 --- /dev/null +++ b/src/nvim/viml/parser/expressions.c @@ -0,0 +1,3103 @@ +// 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 + +/// VimL expression parser + +// Planned incompatibilities (to be included into vim_diff.txt when this parser +// will be an actual part of VimL evaluation process): +// +// 1. Expressions are first fully parsed and only then executed. This means +// that while ":echo [system('touch abc')" will create file "abc" in Vim and +// only then raise syntax error regarding missing comma in list in Neovim +// trying to execute that will immediately raise syntax error regarding +// missing list end without actually executing anything. +// 2. Expressions are first fully parsed, without considering any runtime +// information. This means things like that "d.a" does not change its +// meaning depending on type of "d" (or whether Vim is currently executing or +// skipping). For compatibility reasons the dot thus may either be “concat +// or subscript” operator or just “concat” operator. +// 3. Expressions parser is aware whether it is called for :echo or <C-r>=. +// This means that while "<C-r>=1 | 2<CR>" is equivalent to "<C-r>=1<CR>" +// because "| 2" part is left to be treated as a command separator and then +// ignored in Neovim it is an error. +// 4. Expressions parser has generally better error reporting. But for +// compatibility reasons most errors have error code E15 while error messages +// are significantly different from Vim’s E15. Also some error codes were +// retired because of being harder to emulate or because of them being +// a result of differences in parsing process: e.g. with ":echo {a, b}" Vim +// will attempt to parse expression as lambda, fail, check whether it is +// a curly-braces-name, fail again, and evaluate that as a dictionary, giving +// error regarding undefined variable "a" (or about missing colon). Neovim +// will not try to evaluate anything here: comma right after an argument name +// means that expression may not be anything, but lambda, so the resulting +// error message will never be about missing variable or colon: it will be +// about missing arrow (or a continuation of argument list). +// 5. Failing to parse expression always gives exactly one error message: no +// more stack of error messages like > +// +// :echo [1, +// E697: Missing end of List ']': +// E15: Invalid expression: [1, +// +// < , just exactly one E697 message. +// 6. Some expressions involving calling parenthesis which are treated +// separately by Vim even when not separated by spaces are treated as one +// expression by Neovim: e.g. ":echo (1)(1)" will yield runtime error after +// failing to call "1", while Vim will echo "1 1". Reasoning is the same: +// type of what is in the first expression is generally not known when +// parsing, so to have separate expressions like this separate them with +// spaces. +// 7. 'isident' no longer applies to environment variables, they always include +// ASCII alphanumeric characters and underscore and nothing except this. + +#include <stdbool.h> +#include <stddef.h> +#include <assert.h> +#include <string.h> + +#include "nvim/vim.h" +#include "nvim/memory.h" +#include "nvim/types.h" +#include "nvim/charset.h" +#include "nvim/ascii.h" +#include "nvim/assert.h" +#include "nvim/lib/kvec.h" +#include "nvim/eval/typval.h" + +#include "nvim/viml/parser/expressions.h" +#include "nvim/viml/parser/parser.h" + +#define vim_str2nr(s, ...) vim_str2nr((const char_u *)(s), __VA_ARGS__) + +typedef kvec_withinit_t(ExprASTNode **, 16) ExprASTStack; + +/// Which nodes may be wanted +typedef enum { + /// Operators: function call, subscripts, binary operators, … + /// + /// For unrestricted expressions. + kENodeOperator, + /// Values: literals, variables, nested expressions, unary operators. + /// + /// For unrestricted expressions as well, implies that top item in AST stack + /// points to NULL. + kENodeValue, +} ExprASTWantedNode; + +/// Parse type: what is being parsed currently +typedef enum { + /// Parsing regular VimL expression + kEPTExpr = 0, + /// Parsing lambda arguments + /// + /// Just like parsing function arguments, but it is valid to be ended with an + /// arrow only. + kEPTLambdaArguments, + /// Assignment: parsing for :let + kEPTAssignment, + /// Single assignment: used when lists are not allowed (i.e. when nesting) + kEPTSingleAssignment, +} ExprASTParseType; + +typedef kvec_withinit_t(ExprASTParseType, 4) ExprASTParseTypeStack; + +/// Operator priority level +typedef enum { + kEOpLvlInvalid = 0, + kEOpLvlComplexIdentifier, + kEOpLvlParens, + kEOpLvlAssignment, + kEOpLvlArrow, + kEOpLvlComma, + kEOpLvlColon, + kEOpLvlTernaryValue, + kEOpLvlTernary, + kEOpLvlOr, + kEOpLvlAnd, + kEOpLvlComparison, + kEOpLvlAddition, ///< Addition, subtraction and concatenation. + kEOpLvlMultiplication, ///< Multiplication, division and modulo. + kEOpLvlUnary, ///< Unary operations: not, minus, plus. + kEOpLvlSubscript, ///< Subscripts. + kEOpLvlValue, ///< Values: literals, variables, nested expressions, … +} ExprOpLvl; + +/// Operator associativity +typedef enum { + kEOpAssNo= 'n', ///< Not associative / not applicable. + kEOpAssLeft = 'l', ///< Left associativity. + kEOpAssRight = 'r', ///< Right associativity. +} ExprOpAssociativity; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/expressions.c.generated.h" +#endif + +/// Character used as a separator in autoload function/variable names. +#define AUTOLOAD_CHAR '#' + +/// Scale number by a given factor +/// +/// Used to apply exponent to a number. Idea taken from uClibc. +/// +/// @param[in] num Number to scale. Does not bother doing anything if it is +/// zero. +/// @param[in] base Base, should be 10 since non-decimal floating-point +/// numbers are not supported. +/// @param[in] exponent Exponent to scale by. +/// @param[in] exponent_negative True if exponent is negative. +static inline float_T scale_number(const float_T num, + const uint8_t base, + const uvarnumber_T exponent, + const bool exponent_negative) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_CONST +{ + if (num == 0 || exponent == 0) { + return num; + } + assert(base); + uvarnumber_T exp = exponent; + float_T p_base = (float_T)base; + float_T ret = num; + while (exp) { + if (exp & 1) { + if (exponent_negative) { + ret /= p_base; + } else { + ret *= p_base; + } + } + exp >>= 1; + p_base *= p_base; + } + return ret; +} + +/// Get next token for the VimL expression input +/// +/// @param pstate Parser state. +/// @param[in] flags Flags, @see LexExprFlags. +/// +/// @return Next token. +LexExprToken viml_pexpr_next_token(ParserState *const pstate, const int flags) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + LexExprToken ret = { + .type = kExprLexInvalid, + .start = pstate->pos, + }; + ParserLine pline; + if (!viml_parser_get_remaining_line(pstate, &pline)) { + ret.type = kExprLexEOC; + return ret; + } + if (pline.size <= 0) { + ret.len = 0; + ret.type = kExprLexEOC; + goto viml_pexpr_next_token_adv_return; + } + ret.len = 1; + const uint8_t schar = (uint8_t)pline.data[0]; +#define GET_CCS(ret, pline) \ + do { \ + if (ret.len < pline.size \ + && strchr("?#", pline.data[ret.len]) != NULL) { \ + ret.data.cmp.ccs = \ + (ExprCaseCompareStrategy)pline.data[ret.len]; \ + ret.len++; \ + } else { \ + ret.data.cmp.ccs = kCCStrategyUseOption; \ + } \ + } while (0) + switch (schar) { + // Paired brackets. +#define BRACKET(typ, opning, clsing) \ + case opning: \ + case clsing: { \ + ret.type = typ; \ + ret.data.brc.closing = (schar == clsing); \ + break; \ + } + BRACKET(kExprLexParenthesis, '(', ')') + BRACKET(kExprLexBracket, '[', ']') + BRACKET(kExprLexFigureBrace, '{', '}') +#undef BRACKET + + // Single character tokens without data. +#define CHAR(typ, ch) \ + case ch: { \ + ret.type = typ; \ + break; \ + } + CHAR(kExprLexQuestion, '?') + CHAR(kExprLexColon, ':') + CHAR(kExprLexComma, ',') +#undef CHAR + + // Multiplication/division/modulo. +#define MUL(mul_type, ch) \ + case ch: { \ + ret.type = kExprLexMultiplication; \ + ret.data.mul.type = mul_type; \ + break; \ + } + MUL(kExprLexMulMul, '*') + MUL(kExprLexMulDiv, '/') + MUL(kExprLexMulMod, '%') +#undef MUL + +#define CHARREG(typ, cond) \ + do { \ + ret.type = typ; \ + for (; (ret.len < pline.size \ + && cond(pline.data[ret.len])) \ + ; ret.len++) { \ + } \ + } while (0) + + // Whitespace. + case ' ': + case TAB: { + CHARREG(kExprLexSpacing, ascii_iswhite); + break; + } + + // Control character, except for NUL, NL and TAB. + case Ctrl_A: case Ctrl_B: case Ctrl_C: case Ctrl_D: case Ctrl_E: + case Ctrl_F: case Ctrl_G: case Ctrl_H: + + case Ctrl_K: case Ctrl_L: case Ctrl_M: case Ctrl_N: case Ctrl_O: + case Ctrl_P: case Ctrl_Q: case Ctrl_R: case Ctrl_S: case Ctrl_T: + case Ctrl_U: case Ctrl_V: case Ctrl_W: case Ctrl_X: case Ctrl_Y: + case Ctrl_Z: { +#define ISCTRL(schar) (schar < ' ') + CHARREG(kExprLexInvalid, ISCTRL); + ret.data.err.type = kExprLexSpacing; + ret.data.err.msg = + _("E15: Invalid control character present in input: %.*s"); + break; +#undef ISCTRL + } + + // Number. + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': case '8': case '9': { + ret.data.num.is_float = false; + ret.data.num.base = 10; + size_t frac_start = 0; + size_t exp_start = 0; + size_t frac_end = 0; + bool exp_negative = false; + CHARREG(kExprLexNumber, ascii_isdigit); + if (flags & kELFlagAllowFloat) { + const LexExprToken non_float_ret = ret; + if (pline.size > ret.len + 1 + && pline.data[ret.len] == '.' + && ascii_isdigit(pline.data[ret.len + 1])) { + ret.len++; + frac_start = ret.len; + frac_end = ret.len; + ret.data.num.is_float = true; + for (; ret.len < pline.size && ascii_isdigit(pline.data[ret.len]) + ; ret.len++) { + // A small optimization: trailing zeroes in fractional part do not + // add anything to significand, so it is useless to include them in + // frac_end. + if (pline.data[ret.len] != '0') { + frac_end = ret.len + 1; + } + } + if (pline.size > ret.len + 1 + && (pline.data[ret.len] == 'e' + || pline.data[ret.len] == 'E') + && ((pline.size > ret.len + 2 + && (pline.data[ret.len + 1] == '+' + || pline.data[ret.len + 1] == '-') + && ascii_isdigit(pline.data[ret.len + 2])) + || ascii_isdigit(pline.data[ret.len + 1]))) { + ret.len++; + if (pline.data[ret.len] == '+' + || (exp_negative = (pline.data[ret.len] == '-'))) { + ret.len++; + } + exp_start = ret.len; + CHARREG(kExprLexNumber, ascii_isdigit); + } + } + if (pline.size > ret.len + && (pline.data[ret.len] == '.' + || ASCII_ISALPHA(pline.data[ret.len]))) { + ret = non_float_ret; + } + } + // TODO(ZyX-I): detect overflows + if (ret.data.num.is_float) { + // Vim used to use string2float here which in turn uses strtod(). There + // are two problems with this approach: + // 1. strtod() is locale-dependent. Not sure how it is worked around so + // that I do not see relevant bugs, but it still does not look like + // a good idea. + // 2. strtod() does not accept length argument. + // + // The below variant of parsing floats was recognized as acceptable + // because it is basically how uClibc does the thing: it generates + // a number ignoring decimal point (but recording its position), then + // uses recorded position to scale number down when processing exponent. + float_T significand_part = 0; + uvarnumber_T exp_part = 0; + const size_t frac_size = (size_t)(frac_end - frac_start); + for (size_t i = 0; i < frac_end; i++) { + if (i == frac_start - 1) { + continue; + } + significand_part = significand_part * 10 + (pline.data[i] - '0'); + } + if (exp_start) { + vim_str2nr(pline.data + exp_start, NULL, NULL, 0, NULL, &exp_part, + (int)(ret.len - exp_start)); + } + if (exp_negative) { + exp_part += frac_size; + } else { + if (exp_part < frac_size) { + exp_negative = true; + exp_part = frac_size - exp_part; + } else { + exp_part -= frac_size; + } + } + ret.data.num.val.floating = scale_number(significand_part, 10, exp_part, + exp_negative); + } else { + int len; + int prep; + vim_str2nr(pline.data, &prep, &len, STR2NR_ALL, NULL, + &ret.data.num.val.integer, (int)pline.size); + ret.len = (size_t)len; + const uint8_t bases[] = { + [0] = 10, + ['0'] = 8, + ['x'] = 16, ['X'] = 16, + ['b'] = 2, ['B'] = 2, + }; + ret.data.num.base = bases[prep]; + } + break; + } + +#define ISWORD_OR_AUTOLOAD(x) \ + (ascii_isident(x) || (x) == AUTOLOAD_CHAR) + + // Environment variable. + case '$': { + CHARREG(kExprLexEnv, ascii_isident); + break; + } + + // Normal variable/function name. + case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': + case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': + case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': + case 'v': case 'w': case 'x': case 'y': case 'z': + case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': + case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': + case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': + case 'V': case 'W': case 'X': case 'Y': case 'Z': + case '_': { + ret.data.var.scope = 0; + ret.data.var.autoload = false; + CHARREG(kExprLexPlainIdentifier, ascii_isident); + // "is" and "isnot" operators. + if (!(flags & kELFlagIsNotCmp) + && ((ret.len == 2 && memcmp(pline.data, "is", 2) == 0) + || (ret.len == 5 && memcmp(pline.data, "isnot", 5) == 0))) { + ret.type = kExprLexComparison; + ret.data.cmp.type = kExprCmpIdentical; + ret.data.cmp.inv = (ret.len == 5); + GET_CCS(ret, pline); + // Scope: `s:`, etc. + } else if (ret.len == 1 + && pline.size > 1 + && memchr(EXPR_VAR_SCOPE_LIST, schar, + sizeof(EXPR_VAR_SCOPE_LIST)) != NULL + && pline.data[ret.len] == ':' + && !(flags & kELFlagForbidScope)) { + ret.len++; + ret.data.var.scope = (ExprVarScope)schar; + CHARREG(kExprLexPlainIdentifier, ISWORD_OR_AUTOLOAD); + ret.data.var.autoload = ( + memchr(pline.data + 2, AUTOLOAD_CHAR, ret.len - 2) + != NULL); + // Previous CHARREG stopped at autoload character in order to make it + // possible to detect `is#`. Continue now with autoload characters + // included. + // + // Warning: there is ambiguity for the lexer: `is#Foo(1)` is a call of + // function `is#Foo()`, `1is#Foo(1)` is a comparison `1 is# Foo(1)`. This + // needs to be resolved on the higher level where context is available. + } else if (pline.size > ret.len + && pline.data[ret.len] == AUTOLOAD_CHAR) { + ret.data.var.autoload = true; + CHARREG(kExprLexPlainIdentifier, ISWORD_OR_AUTOLOAD); + } + break; + } + +#undef ISWORD_OR_AUTOLOAD +#undef CHARREG + + // Option. + case '&': { +#define OPTNAMEMISS(ret) \ + do { \ + ret.type = kExprLexInvalid; \ + ret.data.err.type = kExprLexOption; \ + ret.data.err.msg = _("E112: Option name missing: %.*s"); \ + } while (0) + if (pline.size > 1 && pline.data[1] == '&') { + ret.type = kExprLexAnd; + ret.len++; + break; + } + if (pline.size == 1 || !ASCII_ISALPHA(pline.data[1])) { + OPTNAMEMISS(ret); + break; + } + ret.type = kExprLexOption; + if (pline.size > 2 + && pline.data[2] == ':' + && memchr(EXPR_OPT_SCOPE_LIST, pline.data[1], + sizeof(EXPR_OPT_SCOPE_LIST)) != NULL) { + ret.len += 2; + ret.data.opt.scope = (ExprOptScope)pline.data[1]; + ret.data.opt.name = pline.data + 3; + } else { + ret.data.opt.scope = kExprOptScopeUnspecified; + ret.data.opt.name = pline.data + 1; + } + const char *p = ret.data.opt.name; + const char *const e = pline.data + pline.size; + if (e - p >= 4 && p[0] == 't' && p[1] == '_') { + ret.data.opt.len = 4; + ret.len += 4; + } else { + for (; p < e && ASCII_ISALPHA(*p); p++) { + } + ret.data.opt.len = (size_t)(p - ret.data.opt.name); + if (ret.data.opt.len == 0) { + OPTNAMEMISS(ret); + } else { + ret.len += ret.data.opt.len; + } + } + break; +#undef OPTNAMEMISS + } + + // Register. + case '@': { + ret.type = kExprLexRegister; + if (pline.size > 1) { + ret.len++; + ret.data.reg.name = (uint8_t)pline.data[1]; + } else { + ret.data.reg.name = -1; + } + break; + } + + // Single quoted string. + case '\'': { + ret.type = kExprLexSingleQuotedString; + ret.data.str.closed = false; + for (; ret.len < pline.size && !ret.data.str.closed; ret.len++) { + if (pline.data[ret.len] == '\'') { + if (ret.len + 1 < pline.size && pline.data[ret.len + 1] == '\'') { + ret.len++; + } else { + ret.data.str.closed = true; + } + } + } + break; + } + + // Double quoted string. + case '"': { + ret.type = kExprLexDoubleQuotedString; + ret.data.str.closed = false; + for (; ret.len < pline.size && !ret.data.str.closed; ret.len++) { + if (pline.data[ret.len] == '\\') { + if (ret.len + 1 < pline.size) { + ret.len++; + } + } else if (pline.data[ret.len] == '"') { + ret.data.str.closed = true; + } + } + break; + } + + // Unary not, (un)equality and regex (not) match comparison operators. + case '!': + case '=': { + if (pline.size == 1) { + ret.type = (schar == '!' ? kExprLexNot : kExprLexAssignment); + ret.data.ass.type = kExprAsgnPlain; + break; + } + ret.type = kExprLexComparison; + ret.data.cmp.inv = (schar == '!'); + if (pline.data[1] == '=') { + ret.data.cmp.type = kExprCmpEqual; + ret.len++; + } else if (pline.data[1] == '~') { + ret.data.cmp.type = kExprCmpMatches; + ret.len++; + } else if (schar == '!') { + ret.type = kExprLexNot; + } else { + ret.type = kExprLexAssignment; + ret.data.ass.type = kExprAsgnPlain; + } + GET_CCS(ret, pline); + break; + } + + // Less/greater [or equal to] comparison operators. + case '>': + case '<': { + ret.type = kExprLexComparison; + const bool haseqsign = (pline.size > 1 && pline.data[1] == '='); + if (haseqsign) { + ret.len++; + } + GET_CCS(ret, pline); + ret.data.cmp.inv = (schar == '<'); + ret.data.cmp.type = ((ret.data.cmp.inv ^ haseqsign) + ? kExprCmpGreaterOrEqual + : kExprCmpGreater); + break; + } + + // Minus sign, arrow from lambdas or augmented assignment. + case '-': { + if (pline.size > 1 && pline.data[1] == '>') { + ret.len++; + ret.type = kExprLexArrow; + } else if (pline.size > 1 && pline.data[1] == '=') { + ret.len++; + ret.type = kExprLexAssignment; + ret.data.ass.type = kExprAsgnSubtract; + } else { + ret.type = kExprLexMinus; + } + break; + } + + // Sign or augmented assignment. +#define CHAR_OR_ASSIGN(ch, ch_type, ass_type) \ + case ch: { \ + if (pline.size > 1 && pline.data[1] == '=') { \ + ret.len++; \ + ret.type = kExprLexAssignment; \ + ret.data.ass.type = ass_type; \ + } else { \ + ret.type = ch_type; \ + } \ + break; \ + } + CHAR_OR_ASSIGN('+', kExprLexPlus, kExprAsgnAdd) + CHAR_OR_ASSIGN('.', kExprLexDot, kExprAsgnConcat) +#undef CHAR_OR_ASSIGN + + // Expression end because Ex command ended. + case NUL: + case NL: { + if (flags & kELFlagForbidEOC) { + ret.type = kExprLexInvalid; + ret.data.err.msg = _("E15: Unexpected EOC character: %.*s"); + ret.data.err.type = kExprLexSpacing; + } else { + ret.type = kExprLexEOC; + } + break; + } + + case '|': { + if (pline.size >= 2 && pline.data[ret.len] == '|') { + // "||" is or. + ret.len++; + ret.type = kExprLexOr; + } else if (flags & kELFlagForbidEOC) { + // Note: `<C-r>=1 | 2<CR>` actually yields 1 in Vim without any + // errors. This will be changed here. + ret.type = kExprLexInvalid; + ret.data.err.msg = _("E15: Unexpected EOC character: %.*s"); + ret.data.err.type = kExprLexOr; + } else { + ret.type = kExprLexEOC; + } + break; + } + + // Everything else is not valid. + default: { + ret.len = (size_t)utfc_ptr2len_len((const char_u *)pline.data, + (int)pline.size); + ret.type = kExprLexInvalid; + ret.data.err.type = kExprLexPlainIdentifier; + ret.data.err.msg = _("E15: Unidentified character: %.*s"); + break; + } + } +#undef GET_CCS +viml_pexpr_next_token_adv_return: + if (!(flags & kELFlagPeek)) { + viml_parser_advance(pstate, ret.len); + } + return ret; +} + +static const char *const eltkn_type_tab[] = { + [kExprLexInvalid] = "Invalid", + [kExprLexMissing] = "Missing", + [kExprLexSpacing] = "Spacing", + [kExprLexEOC] = "EOC", + + [kExprLexQuestion] = "Question", + [kExprLexColon] = "Colon", + [kExprLexOr] = "Or", + [kExprLexAnd] = "And", + [kExprLexComparison] = "Comparison", + [kExprLexPlus] = "Plus", + [kExprLexMinus] = "Minus", + [kExprLexDot] = "Dot", + [kExprLexMultiplication] = "Multiplication", + + [kExprLexNot] = "Not", + + [kExprLexNumber] = "Number", + [kExprLexSingleQuotedString] = "SingleQuotedString", + [kExprLexDoubleQuotedString] = "DoubleQuotedString", + [kExprLexOption] = "Option", + [kExprLexRegister] = "Register", + [kExprLexEnv] = "Env", + [kExprLexPlainIdentifier] = "PlainIdentifier", + + [kExprLexBracket] = "Bracket", + [kExprLexFigureBrace] = "FigureBrace", + [kExprLexParenthesis] = "Parenthesis", + [kExprLexComma] = "Comma", + [kExprLexArrow] = "Arrow", + [kExprLexAssignment] = "Assignment", +}; + +const char *const eltkn_cmp_type_tab[] = { + [kExprCmpEqual] = "Equal", + [kExprCmpMatches] = "Matches", + [kExprCmpGreater] = "Greater", + [kExprCmpGreaterOrEqual] = "GreaterOrEqual", + [kExprCmpIdentical] = "Identical", +}; + +const char *const expr_asgn_type_tab[] = { + [kExprAsgnPlain] = "Plain", + [kExprAsgnAdd] = "Add", + [kExprAsgnSubtract] = "Subtract", + [kExprAsgnConcat] = "Concat", +}; + +const char *const ccs_tab[] = { + [kCCStrategyUseOption] = "UseOption", + [kCCStrategyMatchCase] = "MatchCase", + [kCCStrategyIgnoreCase] = "IgnoreCase", +}; + +static const char *const eltkn_mul_type_tab[] = { + [kExprLexMulMul] = "Mul", + [kExprLexMulDiv] = "Div", + [kExprLexMulMod] = "Mod", +}; + +static const char *const eltkn_opt_scope_tab[] = { + [kExprOptScopeUnspecified] = "Unspecified", + [kExprOptScopeGlobal] = "Global", + [kExprOptScopeLocal] = "Local", +}; + +/// Represent token as a string +/// +/// Intended for testing and debugging purposes. +/// +/// @param[in] pstate Parser state, needed to get token string from it. May be +/// NULL, in which case in place of obtaining part of the +/// string represented by token only token length is +/// returned. +/// @param[in] token Token to represent. +/// @param[out] ret_size Return string size, for cases like NULs inside +/// a string. May be NULL. +/// +/// @return Token represented in a string form, in a static buffer (overwritten +/// on each call). +const char *viml_pexpr_repr_token(const ParserState *const pstate, + const LexExprToken token, + size_t *const ret_size) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + static char ret[1024]; + char *p = ret; + const char *const e = &ret[1024] - 1; +#define ADDSTR(...) \ + do { \ + p += snprintf(p, (size_t)(sizeof(ret) - (size_t)(p - ret)), __VA_ARGS__); \ + if (p >= e) { \ + goto viml_pexpr_repr_token_end; \ + } \ + } while (0) + ADDSTR("%zu:%zu:%s", token.start.line, token.start.col, + eltkn_type_tab[token.type]); + switch (token.type) { +#define TKNARGS(tkn_type, ...) \ + case tkn_type: { \ + ADDSTR(__VA_ARGS__); \ + break; \ + } + TKNARGS(kExprLexComparison, "(type=%s,ccs=%s,inv=%i)", + eltkn_cmp_type_tab[token.data.cmp.type], + ccs_tab[token.data.cmp.ccs], + (int)token.data.cmp.inv) + TKNARGS(kExprLexMultiplication, "(type=%s)", + eltkn_mul_type_tab[token.data.mul.type]) + TKNARGS(kExprLexAssignment, "(type=%s)", + expr_asgn_type_tab[token.data.ass.type]) + TKNARGS(kExprLexRegister, "(name=%s)", intchar2str(token.data.reg.name)) + case kExprLexDoubleQuotedString: + TKNARGS(kExprLexSingleQuotedString, "(closed=%i)", + (int)token.data.str.closed) + TKNARGS(kExprLexOption, "(scope=%s,name=%.*s)", + eltkn_opt_scope_tab[token.data.opt.scope], + (int)token.data.opt.len, token.data.opt.name) + TKNARGS(kExprLexPlainIdentifier, "(scope=%s,autoload=%i)", + intchar2str((int)token.data.var.scope), + (int)token.data.var.autoload) + TKNARGS(kExprLexNumber, "(is_float=%i,base=%i,val=%lg)", + (int)token.data.num.is_float, + (int)token.data.num.base, + (double)(token.data.num.is_float + ? (double)token.data.num.val.floating + : (double)token.data.num.val.integer)) + TKNARGS(kExprLexInvalid, "(msg=%s)", token.data.err.msg) + default: { + // No additional arguments. + break; + } +#undef TKNARGS + } + if (pstate == NULL) { + ADDSTR("::%zu", token.len); + } else { + *p++ = ':'; + memmove( + p, &pstate->reader.lines.items[token.start.line].data[token.start.col], + token.len); + p += token.len; + *p = NUL; + } +#undef ADDSTR +viml_pexpr_repr_token_end: + if (ret_size != NULL) { + *ret_size = (size_t)(p - ret); + } + return ret; +} + +const char *const east_node_type_tab[] = { + [kExprNodeMissing] = "Missing", + [kExprNodeOpMissing] = "OpMissing", + [kExprNodeTernary] = "Ternary", + [kExprNodeTernaryValue] = "TernaryValue", + [kExprNodeRegister] = "Register", + [kExprNodeSubscript] = "Subscript", + [kExprNodeListLiteral] = "ListLiteral", + [kExprNodeUnaryPlus] = "UnaryPlus", + [kExprNodeBinaryPlus] = "BinaryPlus", + [kExprNodeNested] = "Nested", + [kExprNodeCall] = "Call", + [kExprNodePlainIdentifier] = "PlainIdentifier", + [kExprNodePlainKey] = "PlainKey", + [kExprNodeComplexIdentifier] = "ComplexIdentifier", + [kExprNodeUnknownFigure] = "UnknownFigure", + [kExprNodeLambda] = "Lambda", + [kExprNodeDictLiteral] = "DictLiteral", + [kExprNodeCurlyBracesIdentifier] = "CurlyBracesIdentifier", + [kExprNodeComma] = "Comma", + [kExprNodeColon] = "Colon", + [kExprNodeArrow] = "Arrow", + [kExprNodeComparison] = "Comparison", + [kExprNodeConcat] = "Concat", + [kExprNodeConcatOrSubscript] = "ConcatOrSubscript", + [kExprNodeInteger] = "Integer", + [kExprNodeFloat] = "Float", + [kExprNodeSingleQuotedString] = "SingleQuotedString", + [kExprNodeDoubleQuotedString] = "DoubleQuotedString", + [kExprNodeOr] = "Or", + [kExprNodeAnd] = "And", + [kExprNodeUnaryMinus] = "UnaryMinus", + [kExprNodeBinaryMinus] = "BinaryMinus", + [kExprNodeNot] = "Not", + [kExprNodeMultiplication] = "Multiplication", + [kExprNodeDivision] = "Division", + [kExprNodeMod] = "Mod", + [kExprNodeOption] = "Option", + [kExprNodeEnvironment] = "Environment", + [kExprNodeAssignment] = "Assignment", +}; + +/// Represent `int` character as a string +/// +/// Converts +/// - ASCII digits into '{digit}' +/// - ASCII printable characters into a single-character strings +/// - everything else to numbers. +/// +/// @param[in] ch Character to convert. +/// +/// @return Converted string, stored in a static buffer (overriden after each +/// call). +static const char *intchar2str(const int ch) + FUNC_ATTR_WARN_UNUSED_RESULT +{ + static char buf[sizeof(int) * 3 + 1]; + if (' ' <= ch && ch < 0x7f) { + if (ascii_isdigit(ch)) { + buf[0] = '\''; + buf[1] = (char)ch; + buf[2] = '\''; + buf[3] = NUL; + } else { + buf[0] = (char)ch; + buf[1] = NUL; + } + } else { + snprintf(buf, sizeof(buf), "%i", ch); + } + return buf; +} + +#ifdef UNIT_TESTING +#include <stdio.h> + +REAL_FATTR_UNUSED +static inline void viml_pexpr_debug_print_ast_node( + const ExprASTNode *const *const eastnode_p, + const char *const prefix) +{ + if (*eastnode_p == NULL) { + fprintf(stderr, "%s %p : NULL\n", prefix, (void *)eastnode_p); + } else { + fprintf(stderr, "%s %p : %p : %s : %zu:%zu:%zu\n", + prefix, (void *)eastnode_p, (void *)(*eastnode_p), + east_node_type_tab[(*eastnode_p)->type], (*eastnode_p)->start.line, + (*eastnode_p)->start.col, (*eastnode_p)->len); + } +} + +REAL_FATTR_UNUSED +static inline void viml_pexpr_debug_print_ast_stack( + const ExprASTStack *const ast_stack, + const char *const msg) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + fprintf(stderr, "\n%sstack: %zu:\n", msg, kv_size(*ast_stack)); + for (size_t i = 0; i < kv_size(*ast_stack); i++) { + viml_pexpr_debug_print_ast_node( + (const ExprASTNode *const *)kv_A(*ast_stack, i), + "-"); + } +} + +REAL_FATTR_UNUSED +static inline void viml_pexpr_debug_print_token( + const ParserState *const pstate, const LexExprToken token) + FUNC_ATTR_ALWAYS_INLINE +{ + fprintf(stderr, "\ntkn: %s\n", viml_pexpr_repr_token(pstate, token, NULL)); +} +#define PSTACK(msg) \ + viml_pexpr_debug_print_ast_stack(&ast_stack, #msg) +#define PSTACK_P(msg) \ + viml_pexpr_debug_print_ast_stack(ast_stack, #msg) +#define PNODE_P(eastnode_p, msg) \ + viml_pexpr_debug_print_ast_node((const ExprASTNode *const *)eastnode_p, \ + (#msg)) +#define PTOKEN(tkn) \ + viml_pexpr_debug_print_token(pstate, tkn) +#endif + +const uint8_t node_maxchildren[] = { + [kExprNodeMissing] = 0, + [kExprNodeOpMissing] = 2, + [kExprNodeTernary] = 2, + [kExprNodeTernaryValue] = 2, + [kExprNodeRegister] = 0, + [kExprNodeSubscript] = 2, + [kExprNodeListLiteral] = 1, + [kExprNodeUnaryPlus] = 1, + [kExprNodeBinaryPlus] = 2, + [kExprNodeNested] = 1, + [kExprNodeCall] = 2, + [kExprNodePlainIdentifier] = 0, + [kExprNodePlainKey] = 0, + [kExprNodeComplexIdentifier] = 2, + [kExprNodeUnknownFigure] = 1, + [kExprNodeLambda] = 2, + [kExprNodeDictLiteral] = 1, + [kExprNodeCurlyBracesIdentifier] = 1, + [kExprNodeComma] = 2, + [kExprNodeColon] = 2, + [kExprNodeArrow] = 2, + [kExprNodeComparison] = 2, + [kExprNodeConcat] = 2, + [kExprNodeConcatOrSubscript] = 2, + [kExprNodeInteger] = 0, + [kExprNodeFloat] = 0, + [kExprNodeSingleQuotedString] = 0, + [kExprNodeDoubleQuotedString] = 0, + [kExprNodeOr] = 2, + [kExprNodeAnd] = 2, + [kExprNodeUnaryMinus] = 1, + [kExprNodeBinaryMinus] = 2, + [kExprNodeNot] = 1, + [kExprNodeMultiplication] = 2, + [kExprNodeDivision] = 2, + [kExprNodeMod] = 2, + [kExprNodeOption] = 0, + [kExprNodeEnvironment] = 0, + [kExprNodeAssignment] = 2, +}; + +/// Free memory occupied by AST +/// +/// @param ast AST stack to free. +void viml_pexpr_free_ast(ExprAST ast) +{ + ExprASTStack ast_stack; + kvi_init(ast_stack); + kvi_push(ast_stack, &ast.root); + while (kv_size(ast_stack)) { + ExprASTNode **const cur_node = kv_last(ast_stack); +#ifndef NDEBUG + // Explicitly check for AST recursiveness. + for (size_t i = 0 ; i < kv_size(ast_stack) - 1 ; i++) { + assert(*kv_A(ast_stack, i) != *cur_node); + } +#endif + if (*cur_node == NULL) { + assert(kv_size(ast_stack) == 1); + kv_drop(ast_stack, 1); + } else if ((*cur_node)->children != NULL) { +#ifndef NDEBUG + const uint8_t maxchildren = node_maxchildren[(*cur_node)->type]; + assert(maxchildren > 0); + assert(maxchildren <= 2); + assert(maxchildren == 1 + ? (*cur_node)->children->next == NULL + : ((*cur_node)->children->next == NULL + || (*cur_node)->children->next->next == NULL)); +#endif + kvi_push(ast_stack, &(*cur_node)->children); + } else if ((*cur_node)->next != NULL) { + kvi_push(ast_stack, &(*cur_node)->next); + } else if (*cur_node != NULL) { + kv_drop(ast_stack, 1); + switch ((*cur_node)->type) { + case kExprNodeDoubleQuotedString: + case kExprNodeSingleQuotedString: { + xfree((*cur_node)->data.str.value); + break; + } + case kExprNodeMissing: + case kExprNodeOpMissing: + case kExprNodeTernary: + case kExprNodeTernaryValue: + case kExprNodeRegister: + case kExprNodeSubscript: + case kExprNodeListLiteral: + case kExprNodeUnaryPlus: + case kExprNodeBinaryPlus: + case kExprNodeNested: + case kExprNodeCall: + case kExprNodePlainIdentifier: + case kExprNodePlainKey: + case kExprNodeComplexIdentifier: + case kExprNodeUnknownFigure: + case kExprNodeLambda: + case kExprNodeDictLiteral: + case kExprNodeCurlyBracesIdentifier: + case kExprNodeAssignment: + case kExprNodeComma: + case kExprNodeColon: + case kExprNodeArrow: + case kExprNodeComparison: + case kExprNodeConcat: + case kExprNodeConcatOrSubscript: + case kExprNodeInteger: + case kExprNodeFloat: + case kExprNodeOr: + case kExprNodeAnd: + case kExprNodeUnaryMinus: + case kExprNodeBinaryMinus: + case kExprNodeNot: + case kExprNodeMultiplication: + case kExprNodeDivision: + case kExprNodeMod: + case kExprNodeOption: + case kExprNodeEnvironment: { + break; + } + } + xfree(*cur_node); + *cur_node = NULL; + } + } + kvi_destroy(ast_stack); +} + +// Binary operator precedence and associativity: +// +// Operator | Precedence | Associativity +// ---------+------------+----------------- +// || | 2 | left +// && | 3 | left +// cmp* | 4 | not associative +// + - . | 5 | left +// * / % | 6 | left +// +// * comparison operators: +// +// == ==# ==? != !=# !=? +// =~ =~# =~? !~ !~# !~? +// > ># >? <= <=# <=? +// < <# <? >= >=# >=? +// is is# is? isnot isnot# isnot? + +/// Allocate a new node and set some of the values +/// +/// @param[in] type Node type to allocate. +/// @param[in] level Node level to allocate +static inline ExprASTNode *viml_pexpr_new_node(const ExprASTNodeType type) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_MALLOC +{ + ExprASTNode *ret = xmalloc(sizeof(*ret)); + ret->type = type; + ret->children = NULL; + ret->next = NULL; + return ret; +} + +static struct { + ExprOpLvl lvl; + ExprOpAssociativity ass; +} node_type_to_node_props[] = { + [kExprNodeMissing] = { kEOpLvlInvalid, kEOpAssNo, }, + [kExprNodeOpMissing] = { kEOpLvlMultiplication, kEOpAssNo }, + + [kExprNodeNested] = { kEOpLvlParens, kEOpAssNo }, + // Note: below nodes are kEOpLvlSubscript for “binary operator” itself, but + // kEOpLvlParens when it comes to inside the parenthesis. + [kExprNodeCall] = { kEOpLvlParens, kEOpAssNo }, + [kExprNodeSubscript] = { kEOpLvlParens, kEOpAssNo }, + + [kExprNodeUnknownFigure] = { kEOpLvlParens, kEOpAssLeft }, + [kExprNodeLambda] = { kEOpLvlParens, kEOpAssNo }, + [kExprNodeDictLiteral] = { kEOpLvlParens, kEOpAssNo }, + [kExprNodeListLiteral] = { kEOpLvlParens, kEOpAssNo }, + + [kExprNodeArrow] = { kEOpLvlArrow, kEOpAssNo }, + + // Right associativity for comma because this means easier access to arguments + // list, etc: for "[a, b, c, d]" you can access "a" in one step if it is + // represented as "list(comma(a, comma(b, comma(c, d))))" then if it is + // "list(comma(comma(comma(a, b), c), d))" in which case you will need to + // traverse all three comma() structures. And with comma operator (including + // actual comma operator from C which is not present in VimL) nobody cares + // about associativity, only about order of execution. + [kExprNodeComma] = { kEOpLvlComma, kEOpAssRight }, + + // Colons are not eligible for chaining, so nobody cares about associativity. + [kExprNodeColon] = { kEOpLvlColon, kEOpAssNo }, + + [kExprNodeTernary] = { kEOpLvlTernary, kEOpAssRight }, + + [kExprNodeOr] = { kEOpLvlOr, kEOpAssLeft }, + + [kExprNodeAnd] = { kEOpLvlAnd, kEOpAssLeft }, + + [kExprNodeTernaryValue] = { kEOpLvlTernaryValue, kEOpAssRight }, + + [kExprNodeComparison] = { kEOpLvlComparison, kEOpAssRight }, + + [kExprNodeBinaryPlus] = { kEOpLvlAddition, kEOpAssLeft }, + [kExprNodeBinaryMinus] = { kEOpLvlAddition, kEOpAssLeft }, + [kExprNodeConcat] = { kEOpLvlAddition, kEOpAssLeft }, + + [kExprNodeMultiplication] = { kEOpLvlMultiplication, kEOpAssLeft }, + [kExprNodeDivision] = { kEOpLvlMultiplication, kEOpAssLeft }, + [kExprNodeMod] = { kEOpLvlMultiplication, kEOpAssLeft }, + + [kExprNodeUnaryPlus] = { kEOpLvlUnary, kEOpAssNo }, + [kExprNodeUnaryMinus] = { kEOpLvlUnary, kEOpAssNo }, + [kExprNodeNot] = { kEOpLvlUnary, kEOpAssNo }, + + [kExprNodeConcatOrSubscript] = { kEOpLvlSubscript, kEOpAssLeft }, + + [kExprNodeCurlyBracesIdentifier] = { kEOpLvlComplexIdentifier, kEOpAssLeft }, + + [kExprNodeAssignment] = { kEOpLvlAssignment, kEOpAssLeft }, + + [kExprNodeComplexIdentifier] = { kEOpLvlValue, kEOpAssLeft }, + + [kExprNodePlainIdentifier] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodePlainKey] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeRegister] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeInteger] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeFloat] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeDoubleQuotedString] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeSingleQuotedString] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeOption] = { kEOpLvlValue, kEOpAssNo }, + [kExprNodeEnvironment] = { kEOpLvlValue, kEOpAssNo }, +}; + +/// Get AST node priority level +/// +/// Used primary to reduce line length, so keep the name short. +/// +/// @param[in] node Node to get priority for. +/// +/// @return Node priority level. +static inline ExprOpLvl node_lvl(const ExprASTNode node) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + return node_type_to_node_props[node.type].lvl; +} + +/// Get AST node associativity, to be used for operator nodes primary +/// +/// Used primary to reduce line length, so keep the name short. +/// +/// @param[in] node Node to get priority for. +/// +/// @return Node associativity. +static inline ExprOpAssociativity node_ass(const ExprASTNode node) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + return node_type_to_node_props[node.type].ass; +} + +/// Handle binary operator +/// +/// This function is responsible for handling priority levels as well. +/// +/// @param[in] pstate Parser state, used for error reporting. +/// @param ast_stack AST stack. May be popped of some values and will +/// definitely receive new ones. +/// @param bop_node New node to handle. +/// @param[out] want_node_p New value of want_node. +/// @param[out] ast_err Location where error is saved, if any. +/// +/// @return True if no errors occurred, false otherwise. +static bool viml_pexpr_handle_bop(const ParserState *const pstate, + ExprASTStack *const ast_stack, + ExprASTNode *const bop_node, + ExprASTWantedNode *const want_node_p, + ExprASTError *const ast_err) + FUNC_ATTR_NONNULL_ALL +{ + bool ret = true; + ExprASTNode **top_node_p = NULL; + ExprASTNode *top_node; + ExprOpLvl top_node_lvl; + ExprOpAssociativity top_node_ass; + assert(kv_size(*ast_stack)); + const ExprOpLvl bop_node_lvl = ((bop_node->type == kExprNodeCall + || bop_node->type == kExprNodeSubscript) + ? kEOpLvlSubscript + : node_lvl(*bop_node)); +#ifndef NDEBUG + const ExprOpAssociativity bop_node_ass = ( + (bop_node->type == kExprNodeCall + || bop_node->type == kExprNodeSubscript) + ? kEOpAssLeft + : node_ass(*bop_node)); +#endif + do { + ExprASTNode **new_top_node_p = kv_last(*ast_stack); + ExprASTNode *new_top_node = *new_top_node_p; + assert(new_top_node != NULL); + const ExprOpLvl new_top_node_lvl = node_lvl(*new_top_node); + const ExprOpAssociativity new_top_node_ass = node_ass(*new_top_node); + assert(bop_node_lvl != new_top_node_lvl + || bop_node_ass == new_top_node_ass); + if (top_node_p != NULL + && ((bop_node_lvl > new_top_node_lvl + || (bop_node_lvl == new_top_node_lvl + && new_top_node_ass == kEOpAssNo)))) { + break; + } + kv_drop(*ast_stack, 1); + top_node_p = new_top_node_p; + top_node = new_top_node; + top_node_lvl = new_top_node_lvl; + top_node_ass = new_top_node_ass; + if (bop_node_lvl == top_node_lvl && top_node_ass == kEOpAssRight) { + break; + } + } while (kv_size(*ast_stack)); + if (top_node_ass == kEOpAssLeft || top_node_lvl != bop_node_lvl) { + // outer(op(x,y)) -> outer(new_op(op(x,y),*)) + // + // Before: top_node_p = outer(*), points to op(x,y) + // Other stack elements unknown + // + // After: top_node_p = outer(*), points to new_op(op(x,y)) + // &bop_node->children->next = new_op(op(x,y),*), points to NULL + *top_node_p = bop_node; + bop_node->children = top_node; + assert(bop_node->children->next == NULL); + kvi_push(*ast_stack, top_node_p); + kvi_push(*ast_stack, &bop_node->children->next); + } else { + assert(top_node_lvl == bop_node_lvl && top_node_ass == kEOpAssRight); + assert(top_node->children != NULL && top_node->children->next != NULL); + // outer(op(x,y)) -> outer(op(x,new_op(y,*))) + // + // Before: top_node_p = outer(*), points to op(x,y) + // Other stack elements unknown + // + // After: top_node_p = outer(*), points to op(x,new_op(y)) + // &top_node->children->next = op(x,*), points to new_op(y) + // &bop_node->children->next = new_op(y,*), points to NULL + bop_node->children = top_node->children->next; + top_node->children->next = bop_node; + assert(bop_node->children->next == NULL); + kvi_push(*ast_stack, top_node_p); + kvi_push(*ast_stack, &top_node->children->next); + kvi_push(*ast_stack, &bop_node->children->next); + // TODO(ZyX-I): Make this not error, but treat like Python does + if (bop_node->type == kExprNodeComparison) { + east_set_error(pstate, ast_err, + _("E15: Operator is not associative: %.*s"), + bop_node->start); + ret = false; + } + } + *want_node_p = kENodeValue; + return ret; +} + +/// ParserPosition literal based on ParserPosition pos with columns shifted +/// +/// Function does not check whether resulting position is valid. +/// +/// @param[in] pos Position to shift. +/// @param[in] shift Number of bytes to shift. +/// +/// @return Shifted position. +static inline ParserPosition shifted_pos(const ParserPosition pos, + const size_t shift) + FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (ParserPosition) { .line = pos.line, .col = pos.col + shift }; +} + +/// ParserPosition literal based on ParserPosition pos with specified column +/// +/// Function does not check whether remaining position is valid. +/// +/// @param[in] pos Position to adjust. +/// @param[in] new_col New column. +/// +/// @return Shifted position. +static inline ParserPosition recol_pos(const ParserPosition pos, + const size_t new_col) + FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (ParserPosition) { .line = pos.line, .col = new_col }; +} + +/// Get highlight group name +#define HL(g) (is_invalid ? "NvimInvalid" #g : "Nvim" #g) + +/// Highlight current token with the given group +#define HL_CUR_TOKEN(g) \ + viml_parser_highlight(pstate, cur_token.start, cur_token.len, \ + HL(g)) + +/// Allocate new node, saving some values +#define NEW_NODE(type) \ + viml_pexpr_new_node(type) + +/// Set position of the given node to position from the given token +/// +/// @param cur_node Node to modify. +/// @param cur_token Token to set position from. +#define POS_FROM_TOKEN(cur_node, cur_token) \ + do { \ + (cur_node)->start = cur_token.start; \ + (cur_node)->len = cur_token.len; \ + } while (0) + +/// Allocate new node and set its position from the current token +/// +/// If previous token happened to contain spacing then it will be included. +/// +/// @param cur_node Variable to save allocated node to. +/// @param typ Node type. +#define NEW_NODE_WITH_CUR_POS(cur_node, typ) \ + do { \ + (cur_node) = NEW_NODE(typ); \ + POS_FROM_TOKEN((cur_node), cur_token); \ + if (prev_token.type == kExprLexSpacing) { \ + (cur_node)->start = prev_token.start; \ + (cur_node)->len += prev_token.len; \ + } \ + } while (0) + +/// Check whether it is possible to have next expression after current +/// +/// For :echo: `:echo @a @a` is a valid expression. `:echo (@a @a)` is not. +#define MAY_HAVE_NEXT_EXPR \ + (kv_size(ast_stack) == 1) + +/// Add operator node +/// +/// @param[in] cur_node Node to add. +#define ADD_OP_NODE(cur_node) \ + is_invalid |= !viml_pexpr_handle_bop(pstate, &ast_stack, cur_node, \ + &want_node, &ast.err) + +/// Record missing operator: for things like +/// +/// :echo @a @a +/// +/// (allowed) or +/// +/// :echo (@a @a) +/// +/// (parsed as OpMissing(@a, @a)). +#define OP_MISSING \ + do { \ + if (flags & kExprFlagsMulti && MAY_HAVE_NEXT_EXPR) { \ + /* Multiple expressions allowed, return without calling */ \ + /* viml_parser_advance(). */ \ + goto viml_pexpr_parse_end; \ + } else { \ + assert(*top_node_p != NULL); \ + ERROR_FROM_TOKEN_AND_MSG(cur_token, _("E15: Missing operator: %.*s")); \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOpMissing); \ + cur_node->len = 0; \ + ADD_OP_NODE(cur_node); \ + goto viml_pexpr_parse_process_token; \ + } \ + } while (0) + +/// Record missing value: for things like "* 5" +/// +/// @param[in] msg Error message. +#define ADD_VALUE_IF_MISSING(msg) \ + do { \ + if (want_node == kENodeValue) { \ + ERROR_FROM_TOKEN_AND_MSG(cur_token, (msg)); \ + NEW_NODE_WITH_CUR_POS((*top_node_p), kExprNodeMissing); \ + (*top_node_p)->len = 0; \ + want_node = kENodeOperator; \ + } \ + } while (0) + +/// Set AST error, unless AST already is not correct +/// +/// @param[out] ret_ast AST to set error in. +/// @param[in] pstate Parser state, used to get error message argument. +/// @param[in] msg Error message, assumed to be already translated and +/// containing a single %token "%.*s". +/// @param[in] start Position at which error occurred. +static inline void east_set_error(const ParserState *const pstate, + ExprASTError *const ret_ast_err, + const char *const msg, + const ParserPosition start) + FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE +{ + if (ret_ast_err->msg != NULL) { + return; + } + const ParserLine pline = pstate->reader.lines.items[start.line]; + ret_ast_err->msg = msg; + ret_ast_err->arg_len = (int)(pline.size - start.col); + ret_ast_err->arg = pline.data + start.col; +} + +/// Set error from the given token and given message +#define ERROR_FROM_TOKEN_AND_MSG(cur_token, msg) \ + do { \ + is_invalid = true; \ + east_set_error(pstate, &ast.err, msg, cur_token.start); \ + } while (0) + +/// Like #ERROR_FROM_TOKEN_AND_MSG, but gets position from a node +#define ERROR_FROM_NODE_AND_MSG(node, msg) \ + do { \ + is_invalid = true; \ + east_set_error(pstate, &ast.err, msg, node->start); \ + } while (0) + +/// Set error from the given kExprLexInvalid token +#define ERROR_FROM_TOKEN(cur_token) \ + ERROR_FROM_TOKEN_AND_MSG(cur_token, cur_token.data.err.msg) + +/// Select figure brace type, altering highlighting as well if needed +/// +/// @param[out] node Node to modify type. +/// @param[in] new_type New type, one of ExprASTNodeType values without +/// kExprNode prefix. +/// @param[in] hl Corresponding highlighting, passed as an argument to #HL. +#define SELECT_FIGURE_BRACE_TYPE(node, new_type, hl) \ + do { \ + ExprASTNode *const node_ = (node); \ + assert(node_->type == kExprNodeUnknownFigure \ + || node_->type == kExprNode##new_type); \ + node_->type = kExprNode##new_type; \ + if (pstate->colors) { \ + kv_A(*pstate->colors, node_->data.fig.opening_hl_idx).group = \ + HL(hl); \ + } \ + } while (0) + +/// Add identifier which should constitute complex identifier node +/// +/// This one is to be called only in case want_node is kENodeOperator. +/// +/// @param new_ident_node_code Code used to create a new identifier node and +/// update want_node and ast_stack, without +/// a trailing semicolon. +/// @param hl Highlighting name to use, passed as an argument to #HL. +#define ADD_IDENT(new_ident_node_code, hl) \ + do { \ + assert(want_node == kENodeOperator); \ + /* Operator: may only be curly braces name, but only under certain */ \ + /* conditions. */ \ +\ + /* First condition is that there is no space before a part of complex */ \ + /* identifier. */ \ + if (prev_token.type == kExprLexSpacing) { \ + OP_MISSING; \ + } \ + switch ((*top_node_p)->type) { \ + /* Second is that previous node is one of the identifiers: */ \ + /* complex, plain, curly braces. */ \ +\ + /* TODO(ZyX-I): Extend syntax to allow ${expr}. This is needed to */ \ + /* handle environment variables like those bash uses for */ \ + /* `export -f`: their names consist not only of alphanumeric */ \ + /* characetrs. */ \ + case kExprNodeComplexIdentifier: \ + case kExprNodePlainIdentifier: \ + case kExprNodeCurlyBracesIdentifier: { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComplexIdentifier); \ + cur_node->len = 0; \ + cur_node->children = *top_node_p; \ + *top_node_p = cur_node; \ + kvi_push(ast_stack, &cur_node->children->next); \ + ExprASTNode **const new_top_node_p = kv_last(ast_stack); \ + assert(*new_top_node_p == NULL); \ + new_ident_node_code; \ + *new_top_node_p = cur_node; \ + HL_CUR_TOKEN(hl); \ + break; \ + } \ + default: { \ + OP_MISSING; \ + break; \ + } \ + } \ + } while (0) + +/// Determine whether given parse type is an assignment +/// +/// @param[in] pt Checked parse type. +/// +/// @return true if parsing an assignment, false otherwise. +static inline bool pt_is_assignment(const ExprASTParseType pt) + FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT +{ + return (pt == kEPTAssignment || pt == kEPTSingleAssignment); +} + +/// Structure used to define “string shifts” necessary to map string +/// highlighting to actual strings. +typedef struct { + size_t start; ///< Where special character starts in original string. + size_t orig_len; ///< Length of orininal string (e.g. 4 for "\x80"). + size_t act_len; ///< Length of resulting character(s) (e.g. 1 for "\x80"). + bool escape_not_known; ///< True if escape sequence in original is not known. +} StringShift; + +/// Parse and highlight single- or double-quoted string +/// +/// Function is supposed to detect and highlight regular expressions (but does +/// not do now). +/// +/// @param[out] pstate Parser state which also contains a place where +/// highlighting is saved. +/// @param[out] node Node where string parsing results are saved. +/// @param[in] token Token to highlight. +/// @param[in] ast_stack Parser AST stack, used to detect whether current +/// string is a regex. +/// @param[in] is_invalid Whether currently processed token is not valid. +static void parse_quoted_string(ParserState *const pstate, + ExprASTNode *const node, + const LexExprToken token, + const ExprASTStack ast_stack, + const bool is_invalid) + FUNC_ATTR_NONNULL_ALL +{ + const ParserLine pline = pstate->reader.lines.items[token.start.line]; + const char *const s = pline.data + token.start.col; + const char *const e = s + token.len - token.data.str.closed; + const char *p = s + 1; + const bool is_double = (token.type == kExprLexDoubleQuotedString); + size_t size = token.len - token.data.str.closed - 1; + kvec_withinit_t(StringShift, 16) shifts; + kvi_init(shifts); + if (!is_double) { + viml_parser_highlight(pstate, token.start, 1, HL(SingleQuote)); + while (p < e) { + const char *const chunk_e = memchr(p, '\'', (size_t)(e - p)); + if (chunk_e == NULL) { + break; + } + size--; + p = chunk_e + 2; + if (pstate->colors) { + kvi_push(shifts, ((StringShift) { + .start = token.start.col + (size_t)(chunk_e - s), + .orig_len = 2, + .act_len = 1, + .escape_not_known = false, + })); + } + } + node->data.str.size = size; + if (size == 0) { + node->data.str.value = NULL; + } else { + char *v_p; + v_p = node->data.str.value = xmallocz(size); + p = s + 1; + while (p < e) { + const char *const chunk_e = memchr(p, '\'', (size_t)(e - p)); + if (chunk_e == NULL) { + memcpy(v_p, p, (size_t)(e - p)); + break; + } + memcpy(v_p, p, (size_t)(chunk_e - p)); + v_p += (size_t)(chunk_e - p) + 1; + v_p[-1] = '\''; + p = chunk_e + 2; + } + } + } else { + viml_parser_highlight(pstate, token.start, 1, HL(DoubleQuote)); + for (p = s + 1; p < e; p++) { + if (*p == '\\' && p + 1 < e) { + p++; + if (p + 1 == e) { + size--; + break; + } + switch (*p) { + // A "\<x>" form occupies at least 4 characters, and produces up to + // 6 characters: reserve space for 2 extra, but do not compute actual + // length just now, it would be costy. + case '<': { + size += 2; + break; + } + // Hexadecimal, always single byte, but at least three bytes each. + case 'x': case 'X': { + size--; + if (ascii_isxdigit(p[1])) { + size--; + if (p + 2 < e && ascii_isxdigit(p[2])) { + size--; + } + } + break; + } + // Unicode + // + // \uF takes 1 byte which is 2 bytes less then escape sequence. + // \uFF: 2 bytes, 2 bytes less. + // \uFFF: 3 bytes, 2 bytes less. + // \uFFFF: 3 bytes, 3 bytes less. + // \UFFFFF: 4 bytes, 3 bytes less. + // \UFFFFFF: 5 bytes, 3 bytes less. + // \UFFFFFFF: 6 bytes, 3 bytes less. + // \U7FFFFFFF: 6 bytes, 4 bytes less. + case 'u': case 'U': { + const char *const esc_start = p; + size_t n = (*p == 'u' ? 4 : 8); + int nr = 0; + p++; + while (p + 1 < e && n-- && ascii_isxdigit(p[1])) { + p++; + nr = (nr << 4) + hex2nr(*p); + } + // Escape length: (esc_start - 1) points to "\\", esc_start to "u" + // or "U", p to the byte after last byte. So escape sequence + // occupies p - (esc_start - 1), but it stands for a utf_char2len + // bytes. + size -= (size_t)((p - (esc_start - 1)) - utf_char2len(nr)); + p--; + break; + } + // Octal, always single byte, but at least two bytes each. + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': { + size--; + p++; + if (*p >= '0' && *p <= '7') { + size--; + p++; + if (p < e && *p >= '0' && *p <= '7') { + size--; + p++; + } + } + break; + } + default: { + size--; + break; + } + } + } + } + if (size == 0) { + node->data.str.value = NULL; + node->data.str.size = 0; + } else { + char *v_p; + v_p = node->data.str.value = xmalloc(size); + p = s + 1; + while (p < e) { + const char *const chunk_e = memchr(p, '\\', (size_t)(e - p)); + if (chunk_e == NULL) { + memcpy(v_p, p, (size_t)(e - p)); + v_p += e - p; + break; + } + memcpy(v_p, p, (size_t)(chunk_e - p)); + v_p += (size_t)(chunk_e - p); + p = chunk_e + 1; + if (p == e) { + *v_p++ = '\\'; + break; + } + bool is_unknown = false; + const char *const v_p_start = v_p; + switch (*p) { +#define SINGLE_CHAR_ESC(ch, real_ch) \ + case ch: { \ + *v_p++ = real_ch; \ + p++; \ + break; \ + } + SINGLE_CHAR_ESC('b', BS) + SINGLE_CHAR_ESC('e', ESC) + SINGLE_CHAR_ESC('f', FF) + SINGLE_CHAR_ESC('n', NL) + SINGLE_CHAR_ESC('r', CAR) + SINGLE_CHAR_ESC('t', TAB) + SINGLE_CHAR_ESC('"', '"') + SINGLE_CHAR_ESC('\\', '\\') +#undef SINGLE_CHAR_ESC + + // Hexadecimal or unicode. + case 'X': case 'x': case 'u': case 'U': { + if (p + 1 < e && ascii_isxdigit(p[1])) { + size_t n; + int nr; + bool is_hex = (*p == 'x' || *p == 'X'); + + if (is_hex) { + n = 2; + } else if (*p == 'u') { + n = 4; + } else { + n = 8; + } + nr = 0; + while (p + 1 < e && n-- && ascii_isxdigit(p[1])) { + p++; + nr = (nr << 4) + hex2nr(*p); + } + p++; + if (is_hex) { + *v_p++ = (char)nr; + } else { + v_p += utf_char2bytes(nr, (char_u *)v_p); + } + } else { + is_unknown = true; + *v_p++ = *p; + p++; + } + break; + } + // Octal: "\1", "\12", "\123". + case '0': case '1': case '2': case '3': case '4': case '5': case '6': + case '7': { + uint8_t ch = (uint8_t)(*p++ - '0'); + if (p < e && *p >= '0' && *p <= '7') { + ch = (uint8_t)((ch << 3) + *p++ - '0'); + if (p < e && *p >= '0' && *p <= '7') { + ch = (uint8_t)((ch << 3) + *p++ - '0'); + } + } + *v_p++ = (char)ch; + break; + } + // Special key, e.g.: "\<C-W>" + case '<': { + const size_t special_len = ( + trans_special((const char_u **)&p, (size_t)(e - p), + (char_u *)v_p, true, true)); + if (special_len != 0) { + v_p += special_len; + } else { + is_unknown = true; + mb_copy_char((const char_u **)&p, (char_u **)&v_p); + } + break; + } + default: { + is_unknown = true; + mb_copy_char((const char_u **)&p, (char_u **)&v_p); + break; + } + } + if (pstate->colors) { + kvi_push(shifts, ((StringShift) { + .start = token.start.col + (size_t)(chunk_e - s), + .orig_len = (size_t)(p - chunk_e), + .act_len = (size_t)(v_p - (char *)v_p_start), + .escape_not_known = is_unknown, + })); + } + } + node->data.str.size = (size_t)(v_p - node->data.str.value); + } + } + if (pstate->colors) { + // TODO(ZyX-I): use ast_stack to determine and highlight regular expressions + // TODO(ZyX-I): use ast_stack to determine and highlight printf format str + // TODO(ZyX-I): use ast_stack to determine and highlight expression strings + size_t next_col = token.start.col + 1; + const char *const body_str = (is_double + ? HL(DoubleQuotedBody) + : HL(SingleQuotedBody)); + const char *const esc_str = (is_double + ? HL(DoubleQuotedEscape) + : HL(SingleQuotedQuote)); + const char *const ukn_esc_str = (is_double + ? HL(DoubleQuotedUnknownEscape) + : HL(SingleQuotedUnknownEscape)); + for (size_t i = 0; i < kv_size(shifts); i++) { + const StringShift cur_shift = kv_A(shifts, i); + if (cur_shift.start > next_col) { + viml_parser_highlight(pstate, recol_pos(token.start, next_col), + cur_shift.start - next_col, + body_str); + } + viml_parser_highlight(pstate, recol_pos(token.start, cur_shift.start), + cur_shift.orig_len, + (cur_shift.escape_not_known + ? ukn_esc_str + : esc_str)); + next_col = cur_shift.start + cur_shift.orig_len; + } + if (next_col - token.start.col < token.len - token.data.str.closed) { + viml_parser_highlight(pstate, recol_pos(token.start, next_col), + (token.start.col + + token.len + - token.data.str.closed + - next_col), + body_str); + } + } + if (token.data.str.closed) { + if (is_double) { + viml_parser_highlight(pstate, shifted_pos(token.start, token.len - 1), + 1, HL(DoubleQuote)); + } else { + viml_parser_highlight(pstate, shifted_pos(token.start, token.len - 1), + 1, HL(SingleQuote)); + } + } + kvi_destroy(shifts); +} + +/// Additional flags to pass to lexer depending on want_node +static const int want_node_to_lexer_flags[] = { + [kENodeValue] = kELFlagIsNotCmp, + [kENodeOperator] = kELFlagForbidScope, +}; + +/// Number of characters to highlight as NumberPrefix depending on the base +static const uint8_t base_to_prefix_length[] = { + [2] = 2, + [8] = 1, + [10] = 0, + [16] = 2, +}; + +/// Parse one VimL expression +/// +/// @param pstate Parser state. +/// @param[in] flags Additional flags, see ExprParserFlags +/// +/// @return Parsed AST. +ExprAST viml_pexpr_parse(ParserState *const pstate, const int flags) + FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL +{ + ExprAST ast = { + .err = { + .msg = NULL, + .arg_len = 0, + .arg = NULL, + }, + .root = NULL, + }; + // Expression stack contains current branch in AST tree: that is + // - Stack item 0 contains root of the tree, i.e. &ast->root. + // - Stack item i points to the previous stack items’ last child. + // + // When parser expects “value” node that is something like identifier or "[" + // (list start) last stack item contains NULL. Otherwise last stack item is + // supposed to contain last “finished” value: e.g. "1" or "+(1, 1)" (node + // representing "1+1"). + ExprASTStack ast_stack; + kvi_init(ast_stack); + kvi_push(ast_stack, &ast.root); + ExprASTWantedNode want_node = kENodeValue; + ExprASTParseTypeStack pt_stack; + kvi_init(pt_stack); + kvi_push(pt_stack, kEPTExpr); + if (flags & kExprFlagsParseLet) { + kvi_push(pt_stack, kEPTAssignment); + } + LexExprToken prev_token = { .type = kExprLexMissing }; + bool highlighted_prev_spacing = false; + // Lambda node, valid when parsing lambda arguments only. + ExprASTNode *lambda_node = NULL; + size_t asgn_level = 0; + do { + const bool is_concat_or_subscript = ( + want_node == kENodeValue + && kv_size(ast_stack) > 1 + && (*kv_Z(ast_stack, 1))->type == kExprNodeConcatOrSubscript); + const int lexer_additional_flags = ( + kELFlagPeek + | ((flags & kExprFlagsDisallowEOC) ? kELFlagForbidEOC : 0) + | ((want_node == kENodeValue + && (kv_size(ast_stack) == 1 + || ((*kv_Z(ast_stack, 1))->type != kExprNodeConcat + && ((*kv_Z(ast_stack, 1))->type + != kExprNodeConcatOrSubscript)))) + ? kELFlagAllowFloat + : 0)); + LexExprToken cur_token = viml_pexpr_next_token( + pstate, want_node_to_lexer_flags[want_node] | lexer_additional_flags); + if (cur_token.type == kExprLexEOC) { + break; + } + LexExprTokenType tok_type = cur_token.type; + const bool token_invalid = (tok_type == kExprLexInvalid); + bool is_invalid = token_invalid; +viml_pexpr_parse_process_token: + // May use different flags this time. + cur_token = viml_pexpr_next_token( + pstate, want_node_to_lexer_flags[want_node] | lexer_additional_flags); + if (tok_type == kExprLexSpacing) { + if (is_invalid) { + HL_CUR_TOKEN(Spacing); + } else { + // Do not do anything: let regular spacing be highlighted as normal. + // This also allows later to highlight spacing as invalid. + } + goto viml_pexpr_parse_cycle_end; + } else if (is_invalid && prev_token.type == kExprLexSpacing + && !highlighted_prev_spacing) { + viml_parser_highlight(pstate, prev_token.start, prev_token.len, + HL(Spacing)); + is_invalid = false; + highlighted_prev_spacing = true; + } + const ParserLine pline = pstate->reader.lines.items[cur_token.start.line]; + ExprASTNode **const top_node_p = kv_last(ast_stack); + assert(kv_size(ast_stack) >= 1); + ExprASTNode *cur_node = NULL; +#ifndef NDEBUG + const bool want_value = (want_node == kENodeValue); + assert(want_value == (*top_node_p == NULL)); + assert(kv_A(ast_stack, 0) == &ast.root); + // Check that stack item i + 1 points to stack items’ i *last* child. + for (size_t i = 0; i + 1 < kv_size(ast_stack); i++) { + const bool item_null = (want_value && i + 2 == kv_size(ast_stack)); + assert((&(*kv_A(ast_stack, i))->children == kv_A(ast_stack, i + 1) + && (item_null + ? (*kv_A(ast_stack, i))->children == NULL + : (*kv_A(ast_stack, i))->children->next == NULL)) + || ((&(*kv_A(ast_stack, i))->children->next + == kv_A(ast_stack, i + 1)) + && (item_null + ? (*kv_A(ast_stack, i))->children->next == NULL + : (*kv_A(ast_stack, i))->children->next->next == NULL))); + } +#endif + // Note: in Vim whether expression "cond?d.a:2" is valid depends both on + // "cond" and whether "d" is a dictionary: expression is valid if condition + // is true and "d" is a dictionary (with "a" key or it will complain about + // missing one, but this is not relevant); if any of the requirements is + // broken then this thing is parsed as "d . a:2" yielding missing colon + // error. This parser does not allow such ambiguity, especially because it + // simply can’t: whether "d" is a dictionary is not known at the parsing + // time. + // + // Here example will always contain a concat with "a:2" sucking colon, + // making expression invalid both because there is no longer a spare colon + // for ternary and because concatenating dictionary with anything is not + // valid. There are more cases when this will make a difference though. + const bool node_is_key = ( + is_concat_or_subscript + && (cur_token.type == kExprLexPlainIdentifier + ? (!cur_token.data.var.autoload + && cur_token.data.var.scope == kExprVarScopeMissing) + : (cur_token.type == kExprLexNumber)) + && prev_token.type != kExprLexSpacing); + if (is_concat_or_subscript && !node_is_key) { + // Note: in Vim "d. a" (this is the reason behind `prev_token.type != + // kExprLexSpacing` part of the condition) as well as any other "d.{expr}" + // where "{expr}" does not look like a key is invalid whenever "d" happens + // to be a dictionary. Since parser has no idea whether preceding + // expression is actually a dictionary it can’t outright reject anything, + // so it turns kExprNodeConcatOrSubscript into kExprNodeConcat instead, + // which will yield different errors then Vim does in a number of + // circumstances, and in any case runtime and not parse time errors. + (*kv_Z(ast_stack, 1))->type = kExprNodeConcat; + } + // Pop some stack pt_stack items in case of misplaced nodes. + const bool is_single_assignment = kv_last(pt_stack) == kEPTSingleAssignment; + switch (kv_last(pt_stack)) { + case kEPTExpr: { + break; + } + case kEPTLambdaArguments: { + if ((want_node == kENodeOperator + && tok_type != kExprLexComma + && tok_type != kExprLexArrow) + || (want_node == kENodeValue + && !(cur_token.type == kExprLexPlainIdentifier + && cur_token.data.var.scope == kExprVarScopeMissing + && !cur_token.data.var.autoload) + && tok_type != kExprLexArrow)) { + lambda_node->data.fig.type_guesses.allow_lambda = false; + if (lambda_node->children != NULL + && lambda_node->children->type == kExprNodeComma) { + // If lambda has comma child this means that parser has already seen + // at least "{arg1,", so node cannot possibly be anything, but + // lambda. + + // Vim may give E121 or E720 in this case, but it does not look + // right to have either because both are results of reevaluation + // possibly-lambda node as a dictionary and here this is not going + // to happen. + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected lambda arguments list or arrow: %.*s")); + } else { + // Else it may appear that possibly-lambda node is actually + // a dictionary or curly-braces-name identifier. + lambda_node = NULL; + kv_drop(pt_stack, 1); + } + } + break; + } + case kEPTSingleAssignment: + case kEPTAssignment: { + if (want_node == kENodeValue + && tok_type != kExprLexBracket + && tok_type != kExprLexPlainIdentifier + && (tok_type != kExprLexFigureBrace || cur_token.data.brc.closing) + && !(node_is_key && tok_type == kExprLexNumber) + && tok_type != kExprLexEnv + && tok_type != kExprLexOption + && tok_type != kExprLexRegister) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected value part of assignment lvalue: %.*s")); + kv_drop(pt_stack, 1); + } else if (want_node == kENodeOperator + && tok_type != kExprLexBracket + && (tok_type != kExprLexFigureBrace + || cur_token.data.brc.closing) + && tok_type != kExprLexDot + && (tok_type != kExprLexComma || !is_single_assignment) + && tok_type != kExprLexAssignment + // Curly brace identifiers: will contain plain identifier or + // another curly brace in position where operator is wanted. + && !((tok_type == kExprLexPlainIdentifier + || (tok_type == kExprLexFigureBrace + && !cur_token.data.brc.closing)) + && prev_token.type != kExprLexSpacing)) { + if (flags & kExprFlagsMulti && MAY_HAVE_NEXT_EXPR) { + goto viml_pexpr_parse_end; + } + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected assignment operator or subscript: %.*s")); + kv_drop(pt_stack, 1); + } + assert(kv_size(pt_stack)); + break; + } + } + assert(kv_size(pt_stack)); + const ExprASTParseType cur_pt = kv_last(pt_stack); + assert(lambda_node == NULL || cur_pt == kEPTLambdaArguments); + switch (tok_type) { + case kExprLexMissing: + case kExprLexSpacing: + case kExprLexEOC: { + assert(false); + } + case kExprLexInvalid: { + ERROR_FROM_TOKEN(cur_token); + tok_type = cur_token.data.err.type; + goto viml_pexpr_parse_process_token; + } + case kExprLexRegister: { + if (want_node == kENodeOperator) { + // Register in operator position: e.g. @a @a + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeRegister); + cur_node->data.reg.name = cur_token.data.reg.name; + *top_node_p = cur_node; + want_node = kENodeOperator; + HL_CUR_TOKEN(Register); + break; + } +#define SIMPLE_UB_OP(op) \ + case kExprLex##op: { \ + if (want_node == kENodeValue) { \ + /* Value level: assume unary operator. */ \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnary##op); \ + *top_node_p = cur_node; \ + kvi_push(ast_stack, &cur_node->children); \ + HL_CUR_TOKEN(Unary##op); \ + } else { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeBinary##op); \ + ADD_OP_NODE(cur_node); \ + HL_CUR_TOKEN(Binary##op); \ + } \ + want_node = kENodeValue; \ + break; \ + } + SIMPLE_UB_OP(Plus) + SIMPLE_UB_OP(Minus) +#undef SIMPLE_UB_OP +#define SIMPLE_B_OP(op, msg) \ + case kExprLex##op: { \ + ADD_VALUE_IF_MISSING(_("E15: Unexpected " msg ": %.*s")); \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##op); \ + HL_CUR_TOKEN(op); \ + ADD_OP_NODE(cur_node); \ + break; \ + } + SIMPLE_B_OP(Or, "or operator") + SIMPLE_B_OP(And, "and operator") +#undef SIMPLE_B_OP + case kExprLexMultiplication: { + ADD_VALUE_IF_MISSING( + _("E15: Unexpected multiplication-like operator: %.*s")); + switch (cur_token.data.mul.type) { +#define MUL_OP(lex_op_tail, node_op_tail) \ + case kExprLexMul##lex_op_tail: { \ + NEW_NODE_WITH_CUR_POS(cur_node, kExprNode##node_op_tail); \ + HL_CUR_TOKEN(node_op_tail); \ + break; \ + } + MUL_OP(Mul, Multiplication) + MUL_OP(Div, Division) + MUL_OP(Mod, Mod) +#undef MUL_OP + } + ADD_OP_NODE(cur_node); + break; + } + case kExprLexOption: { + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeOption); + if (cur_token.type == kExprLexInvalid) { + assert(cur_token.len == 1 + || (cur_token.len == 3 + && pline.data[cur_token.start.col + 2] == ':')); + cur_node->data.opt.ident = ( + pline.data + cur_token.start.col + cur_token.len); + cur_node->data.opt.ident_len = 0; + cur_node->data.opt.scope = ( + cur_token.len == 3 + ? (ExprOptScope)pline.data[cur_token.start.col + 1] + : kExprOptScopeUnspecified); + } else { + cur_node->data.opt.ident = cur_token.data.opt.name; + cur_node->data.opt.ident_len = cur_token.data.opt.len; + cur_node->data.opt.scope = cur_token.data.opt.scope; + } + *top_node_p = cur_node; + want_node = kENodeOperator; + viml_parser_highlight(pstate, cur_token.start, 1, HL(OptionSigil)); + const size_t scope_shift = ( + cur_token.data.opt.scope == kExprOptScopeUnspecified ? 0 : 2); + if (scope_shift) { + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), 1, + HL(OptionScope)); + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 2), 1, + HL(OptionScopeDelimiter)); + } + viml_parser_highlight( + pstate, shifted_pos(cur_token.start, scope_shift + 1), + cur_token.len - (scope_shift + 1), HL(OptionName)); + break; + } + case kExprLexEnv: { + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeEnvironment); + cur_node->data.env.ident = pline.data + cur_token.start.col + 1; + cur_node->data.env.ident_len = cur_token.len - 1; + if (cur_node->data.env.ident_len == 0) { + ERROR_FROM_TOKEN_AND_MSG(cur_token, + _("E15: Environment variable name missing")); + } + *top_node_p = cur_node; + want_node = kENodeOperator; + viml_parser_highlight(pstate, cur_token.start, 1, HL(EnvironmentSigil)); + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), + cur_token.len - 1, HL(EnvironmentName)); + break; + } + case kExprLexNot: { + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeNot); + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + HL_CUR_TOKEN(Not); + break; + } + case kExprLexComparison: { + ADD_VALUE_IF_MISSING( + _("E15: Expected value, got comparison operator: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComparison); + if (cur_token.type == kExprLexInvalid) { + cur_node->data.cmp.ccs = kCCStrategyUseOption; + cur_node->data.cmp.type = kExprCmpEqual; + cur_node->data.cmp.inv = false; + } else { + cur_node->data.cmp.ccs = cur_token.data.cmp.ccs; + cur_node->data.cmp.type = cur_token.data.cmp.type; + cur_node->data.cmp.inv = cur_token.data.cmp.inv; + } + ADD_OP_NODE(cur_node); + if (cur_token.data.cmp.ccs != kCCStrategyUseOption) { + viml_parser_highlight(pstate, cur_token.start, cur_token.len - 1, + HL(Comparison)); + viml_parser_highlight( + pstate, shifted_pos(cur_token.start, cur_token.len - 1), 1, + HL(ComparisonModifier)); + } else { + HL_CUR_TOKEN(Comparison); + } + want_node = kENodeValue; + break; + } + case kExprLexComma: { + assert(!(want_node == kENodeValue && cur_pt == kEPTLambdaArguments)); + if (want_node == kENodeValue) { + // Value level: comma appearing here is not valid. + // Note: in Vim string(,x) will give E116, this is not the case here. + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Expected value, got comma: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); + cur_node->len = 0; + *top_node_p = cur_node; + want_node = kENodeOperator; + } + if (cur_pt == kEPTLambdaArguments) { + assert(lambda_node != NULL); + assert(lambda_node->data.fig.type_guesses.allow_lambda); + SELECT_FIGURE_BRACE_TYPE(lambda_node, Lambda, Lambda); + } + if (kv_size(ast_stack) < 2) { + goto viml_pexpr_parse_invalid_comma; + } + for (size_t i = 1; i < kv_size(ast_stack); i++) { + ExprASTNode *const *const eastnode_p = + (ExprASTNode *const *)kv_Z(ast_stack, i); + const ExprASTNodeType eastnode_type = (*eastnode_p)->type; + const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p); + if (eastnode_type == kExprNodeLambda) { + assert(cur_pt == kEPTLambdaArguments + && want_node == kENodeOperator); + break; + } else if (eastnode_type == kExprNodeDictLiteral + || eastnode_type == kExprNodeListLiteral + || eastnode_type == kExprNodeCall) { + break; + } else if (eastnode_type == kExprNodeComma + || eastnode_type == kExprNodeColon + || eastnode_lvl > kEOpLvlComma) { + // Do nothing + } else { +viml_pexpr_parse_invalid_comma: + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Comma outside of call, lambda or literal: %.*s")); + break; + } + if (i == kv_size(ast_stack) - 1) { + goto viml_pexpr_parse_invalid_comma; + } + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeComma); + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(Comma); + break; + } +#define EXP_VAL_COLON "E15: Expected value, got colon: %.*s" + case kExprLexColon: { + bool is_ternary = false; + if (kv_size(ast_stack) < 2) { + goto viml_pexpr_parse_invalid_colon; + } + bool can_be_ternary = true; + bool is_subscript = false; + for (size_t i = 1; i < kv_size(ast_stack); i++) { + ExprASTNode *const *const eastnode_p = + (ExprASTNode *const *)kv_Z(ast_stack, i); + const ExprASTNodeType eastnode_type = (*eastnode_p)->type; + const ExprOpLvl eastnode_lvl = node_lvl(**eastnode_p); + STATIC_ASSERT(kEOpLvlTernary > kEOpLvlComma, + "Unexpected operator priorities"); + if (can_be_ternary && eastnode_type == kExprNodeTernaryValue + && !(*eastnode_p)->data.ter.got_colon) { + kv_drop(ast_stack, i); + (*eastnode_p)->start = cur_token.start; + (*eastnode_p)->len = cur_token.len; + if (prev_token.type == kExprLexSpacing) { + (*eastnode_p)->start = prev_token.start; + (*eastnode_p)->len += prev_token.len; + } + is_ternary = true; + (*eastnode_p)->data.ter.got_colon = true; + ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); + assert((*eastnode_p)->children != NULL); + assert((*eastnode_p)->children->next == NULL); + kvi_push(ast_stack, &(*eastnode_p)->children->next); + break; + } else if (eastnode_type == kExprNodeUnknownFigure) { + SELECT_FIGURE_BRACE_TYPE(*eastnode_p, DictLiteral, Dict); + break; + } else if (eastnode_type == kExprNodeDictLiteral) { + break; + } else if (eastnode_type == kExprNodeSubscript) { + is_subscript = true; + can_be_ternary = false; + assert(!is_ternary); + break; + } else if (eastnode_type == kExprNodeColon) { + goto viml_pexpr_parse_invalid_colon; + } else if (eastnode_lvl >= kEOpLvlTernaryValue) { + // Do nothing + } else if (eastnode_lvl >= kEOpLvlComma) { + can_be_ternary = false; + } else { + goto viml_pexpr_parse_invalid_colon; + } + if (i == kv_size(ast_stack) - 1) { + goto viml_pexpr_parse_invalid_colon; + } + } + if (is_subscript) { + assert(kv_size(ast_stack) > 1); + // Colon immediately following subscript start: it is empty subscript + // part like a[:2]. + if (want_node == kENodeValue + && (*kv_Z(ast_stack, 1))->type == kExprNodeSubscript) { + NEW_NODE_WITH_CUR_POS(*top_node_p, kExprNodeMissing); + (*top_node_p)->len = 0; + want_node = kENodeOperator; + } else { + ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(SubscriptColon); + } else { + goto viml_pexpr_parse_valid_colon; +viml_pexpr_parse_invalid_colon: + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Colon outside of dictionary or ternary operator: %.*s")); +viml_pexpr_parse_valid_colon: + ADD_VALUE_IF_MISSING(_(EXP_VAL_COLON)); + if (is_ternary) { + HL_CUR_TOKEN(TernaryColon); + } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeColon); + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(Colon); + } + } + want_node = kENodeValue; + break; + } +#undef EXP_VAL_COLON + case kExprLexBracket: { + if (cur_token.data.brc.closing) { + ExprASTNode **new_top_node_p = NULL; + // Always drop the topmost value: + // + // 1. When want_node != kENodeValue topmost item on stack is + // a *finished* left operand, which may as well be "{@a}" which + // needs not be finished again. + // 2. Otherwise it is pointing to NULL what nobody wants. + kv_drop(ast_stack, 1); + if (!kv_size(ast_stack)) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeListLiteral); + cur_node->len = 0; + if (want_node != kENodeValue) { + cur_node->children = *top_node_p; + } + *top_node_p = cur_node; + goto viml_pexpr_parse_bracket_closing_error; + } + if (want_node == kENodeValue) { + // It is OK to want value if + // + // 1. It is empty list literal, in which case top node will be + // ListLiteral. + // 2. It is list literal with trailing comma, in which case top node + // will be that comma. + // 3. It is subscript with colon, but without one of the values: + // e.g. "a[:]", "a[1:]", top node will be colon in this case. + if ((*kv_last(ast_stack))->type != kExprNodeListLiteral + && (*kv_last(ast_stack))->type != kExprNodeComma + && (*kv_last(ast_stack))->type != kExprNodeColon) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected value, got closing bracket: %.*s")); + } + } else { + if (!kv_size(ast_stack)) { + new_top_node_p = top_node_p; + goto viml_pexpr_parse_bracket_closing_error; + } + } + do { + new_top_node_p = kv_pop(ast_stack); + } while (kv_size(ast_stack) + && (new_top_node_p == NULL + || ((*new_top_node_p)->type != kExprNodeListLiteral + && (*new_top_node_p)->type != kExprNodeSubscript))); + ExprASTNode *new_top_node = *new_top_node_p; + switch (new_top_node->type) { + case kExprNodeListLiteral: { + if (pt_is_assignment(cur_pt) && new_top_node->children == NULL) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E475: Unable to assign to empty list: %.*s")); + } + HL_CUR_TOKEN(List); + break; + } + case kExprNodeSubscript: { + HL_CUR_TOKEN(SubscriptBracket); + break; + } + default: { +viml_pexpr_parse_bracket_closing_error: + assert(!kv_size(ast_stack)); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Unexpected closing figure brace: %.*s")); + HL_CUR_TOKEN(List); + break; + } + } + kvi_push(ast_stack, new_top_node_p); + want_node = kENodeOperator; + if (kv_size(ast_stack) <= asgn_level) { + assert(kv_size(ast_stack) == asgn_level); + asgn_level = 0; + if (cur_pt == kEPTAssignment) { + assert(ast.err.msg); + } else if (cur_pt == kEPTExpr + && kv_size(pt_stack) > 1 + && pt_is_assignment(kv_Z(pt_stack, 1))) { + kv_drop(pt_stack, 1); + } + } + if (cur_pt == kEPTSingleAssignment && kv_size(ast_stack) == 1) { + kv_drop(pt_stack, 1); + } + } else { + if (want_node == kENodeValue) { + // Value means list literal or list assignment. + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeListLiteral); + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + want_node = kENodeValue; + if (cur_pt == kEPTAssignment) { + // Additional assignment parse type allows to easily forbid nested + // lists. + kvi_push(pt_stack, kEPTSingleAssignment); + } else if (cur_pt == kEPTSingleAssignment) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E475: Nested lists not allowed when assigning: %.*s")); + } + HL_CUR_TOKEN(List); + } else { + // Operator means subscript, also in assignment. But in assignment + // subscript may be pretty much any expression, so need to push + // kEPTExpr. + if (prev_token.type == kExprLexSpacing) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeSubscript); + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(SubscriptBracket); + if (pt_is_assignment(cur_pt)) { + assert(want_node == kENodeValue); // Subtract 1 for NULL at top. + asgn_level = kv_size(ast_stack) - 1; + kvi_push(pt_stack, kEPTExpr); + } + } + } + break; + } + case kExprLexFigureBrace: { + if (cur_token.data.brc.closing) { + ExprASTNode **new_top_node_p = NULL; + // Always drop the topmost value: + // + // 1. When want_node != kENodeValue topmost item on stack is + // a *finished* left operand, which may as well be "{@a}" which + // needs not be finished again. + // 2. Otherwise it is pointing to NULL what nobody wants. + kv_drop(ast_stack, 1); + if (!kv_size(ast_stack)) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnknownFigure); + cur_node->data.fig.type_guesses.allow_lambda = false; + cur_node->data.fig.type_guesses.allow_dict = false; + cur_node->data.fig.type_guesses.allow_ident = false; + cur_node->len = 0; + if (want_node != kENodeValue) { + cur_node->children = *top_node_p; + } + *top_node_p = cur_node; + new_top_node_p = top_node_p; + goto viml_pexpr_parse_figure_brace_closing_error; + } + if (want_node == kENodeValue) { + if ((*kv_last(ast_stack))->type != kExprNodeUnknownFigure + && (*kv_last(ast_stack))->type != kExprNodeComma) { + // kv_last being UnknownFigure may occur for empty dictionary + // literal, while Comma is expected in case of non-empty one. + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E15: Expected value, got closing figure brace: %.*s")); + } + } else { + if (!kv_size(ast_stack)) { + new_top_node_p = top_node_p; + goto viml_pexpr_parse_figure_brace_closing_error; + } + } + do { + new_top_node_p = kv_pop(ast_stack); + } while (kv_size(ast_stack) + && (new_top_node_p == NULL + || ((*new_top_node_p)->type != kExprNodeUnknownFigure + && (*new_top_node_p)->type != kExprNodeDictLiteral + && ((*new_top_node_p)->type + != kExprNodeCurlyBracesIdentifier) + && (*new_top_node_p)->type != kExprNodeLambda))); + ExprASTNode *new_top_node = *new_top_node_p; + switch (new_top_node->type) { + case kExprNodeUnknownFigure: { + if (new_top_node->children == NULL) { + // No children of curly braces node indicates empty dictionary. + assert(want_node == kENodeValue); + assert(new_top_node->data.fig.type_guesses.allow_dict); + SELECT_FIGURE_BRACE_TYPE(new_top_node, DictLiteral, Dict); + HL_CUR_TOKEN(Dict); + } else if (new_top_node->data.fig.type_guesses.allow_ident) { + SELECT_FIGURE_BRACE_TYPE(new_top_node, CurlyBracesIdentifier, + Curly); + HL_CUR_TOKEN(Curly); + } else { + // If by this time type of the node has not already been + // guessed, but it definitely is not a curly braces name then + // it is invalid for sure. + ERROR_FROM_NODE_AND_MSG( + new_top_node, + _("E15: Don't know what figure brace means: %.*s")); + if (pstate->colors) { + // Will reset to NvimInvalidFigureBrace. + kv_A(*pstate->colors, + new_top_node->data.fig.opening_hl_idx).group = ( + HL(FigureBrace)); + } + HL_CUR_TOKEN(FigureBrace); + } + break; + } + case kExprNodeDictLiteral: { + HL_CUR_TOKEN(Dict); + break; + } + case kExprNodeCurlyBracesIdentifier: { + HL_CUR_TOKEN(Curly); + break; + } + case kExprNodeLambda: { + HL_CUR_TOKEN(Lambda); + break; + } + default: { +viml_pexpr_parse_figure_brace_closing_error: + assert(!kv_size(ast_stack)); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Unexpected closing figure brace: %.*s")); + HL_CUR_TOKEN(FigureBrace); + break; + } + } + kvi_push(ast_stack, new_top_node_p); + want_node = kENodeOperator; + if (kv_size(ast_stack) <= asgn_level) { + assert(kv_size(ast_stack) == asgn_level); + if (cur_pt == kEPTExpr + && kv_size(pt_stack) > 1 + && pt_is_assignment(kv_Z(pt_stack, 1))) { + kv_drop(pt_stack, 1); + asgn_level = 0; + } + } + } else { + if (want_node == kENodeValue) { + HL_CUR_TOKEN(FigureBrace); + // Value: may be any of lambda, dictionary literal and curly braces + // name. + + // Though if we are in an assignment this may only be a curly braces + // name. + if (pt_is_assignment(cur_pt)) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCurlyBracesIdentifier); + cur_node->data.fig.type_guesses.allow_lambda = false; + cur_node->data.fig.type_guesses.allow_dict = false; + cur_node->data.fig.type_guesses.allow_ident = true; + kvi_push(pt_stack, kEPTExpr); + } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeUnknownFigure); + cur_node->data.fig.type_guesses.allow_lambda = true; + cur_node->data.fig.type_guesses.allow_dict = true; + cur_node->data.fig.type_guesses.allow_ident = true; + } + if (pstate->colors) { + cur_node->data.fig.opening_hl_idx = kv_size(*pstate->colors) - 1; + } + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + kvi_push(pt_stack, kEPTLambdaArguments); + lambda_node = cur_node; + } else { + ADD_IDENT( + do { + NEW_NODE_WITH_CUR_POS(cur_node, + kExprNodeCurlyBracesIdentifier); + cur_node->data.fig.opening_hl_idx = kv_size(*pstate->colors); + cur_node->data.fig.type_guesses.allow_lambda = false; + cur_node->data.fig.type_guesses.allow_dict = false; + cur_node->data.fig.type_guesses.allow_ident = true; + kvi_push(ast_stack, &cur_node->children); + if (pt_is_assignment(cur_pt)) { + kvi_push(pt_stack, kEPTExpr); + } + want_node = kENodeValue; + } while (0), + Curly); + } + if (pt_is_assignment(cur_pt) + && !pt_is_assignment(kv_last(pt_stack))) { + assert(want_node == kENodeValue); // Subtract 1 for NULL at top. + asgn_level = kv_size(ast_stack) - 1; + } + } + break; + } + case kExprLexArrow: { + if (cur_pt == kEPTLambdaArguments) { + kv_drop(pt_stack, 1); + assert(kv_size(pt_stack)); + if (want_node == kENodeValue) { + // Wanting value means trailing comma and NULL at the top of the + // stack. + kv_drop(ast_stack, 1); + } + assert(kv_size(ast_stack) >= 1); + while ((*kv_last(ast_stack))->type != kExprNodeLambda + && (*kv_last(ast_stack))->type != kExprNodeUnknownFigure) { + kv_drop(ast_stack, 1); + } + assert((*kv_last(ast_stack)) == lambda_node); + SELECT_FIGURE_BRACE_TYPE(lambda_node, Lambda, Lambda); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow); + if (lambda_node->children == NULL) { + assert(want_node == kENodeValue); + lambda_node->children = cur_node; + kvi_push(ast_stack, &lambda_node->children); + } else { + assert(lambda_node->children->next == NULL); + lambda_node->children->next = cur_node; + kvi_push(ast_stack, &lambda_node->children->next); + } + kvi_push(ast_stack, &cur_node->children); + lambda_node = NULL; + } else { + // Only first branch is valid. + ADD_VALUE_IF_MISSING(_("E15: Unexpected arrow: %.*s")); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Arrow outside of lambda: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeArrow); + ADD_OP_NODE(cur_node); + } + want_node = kENodeValue; + HL_CUR_TOKEN(Arrow); + break; + } + case kExprLexPlainIdentifier: { + const ExprVarScope scope = (cur_token.type == kExprLexInvalid + ? kExprVarScopeMissing + : cur_token.data.var.scope); + if (want_node == kENodeValue) { + want_node = kENodeOperator; + NEW_NODE_WITH_CUR_POS(cur_node, + (node_is_key + ? kExprNodePlainKey + : kExprNodePlainIdentifier)); + cur_node->data.var.scope = scope; + const size_t scope_shift = (scope == kExprVarScopeMissing ? 0 : 2); + cur_node->data.var.ident = (pline.data + cur_token.start.col + + scope_shift); + cur_node->data.var.ident_len = cur_token.len - scope_shift; + *top_node_p = cur_node; + if (scope_shift) { + assert(!node_is_key); + viml_parser_highlight(pstate, cur_token.start, 1, + HL(IdentifierScope)); + viml_parser_highlight(pstate, shifted_pos(cur_token.start, 1), 1, + HL(IdentifierScopeDelimiter)); + } + viml_parser_highlight(pstate, shifted_pos(cur_token.start, + scope_shift), + cur_token.len - scope_shift, + (node_is_key + ? HL(IdentifierKey) + : HL(IdentifierName))); + } else { + if (scope == kExprVarScopeMissing) { + ADD_IDENT( + do { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainIdentifier); + cur_node->data.var.scope = scope; + cur_node->data.var.ident = pline.data + cur_token.start.col; + cur_node->data.var.ident_len = cur_token.len; + want_node = kENodeOperator; + } while (0), + IdentifierName); + } else { + OP_MISSING; + } + } + break; + } + case kExprLexNumber: { + if (want_node != kENodeValue) { + OP_MISSING; + } + if (node_is_key) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodePlainKey); + cur_node->data.var.ident = pline.data + cur_token.start.col; + cur_node->data.var.ident_len = cur_token.len; + HL_CUR_TOKEN(IdentifierKey); + } else if (cur_token.data.num.is_float) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeFloat); + cur_node->data.flt.value = cur_token.data.num.val.floating; + HL_CUR_TOKEN(Float); + } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeInteger); + cur_node->data.num.value = cur_token.data.num.val.integer; + const uint8_t prefix_length = base_to_prefix_length[ + cur_token.data.num.base]; + viml_parser_highlight(pstate, cur_token.start, prefix_length, + HL(NumberPrefix)); + viml_parser_highlight( + pstate, shifted_pos(cur_token.start, prefix_length), + cur_token.len - prefix_length, HL(Number)); + } + want_node = kENodeOperator; + *top_node_p = cur_node; + break; + } + case kExprLexDot: { + ADD_VALUE_IF_MISSING(_("E15: Unexpected dot: %.*s")); + if (prev_token.type == kExprLexSpacing) { + if (cur_pt == kEPTAssignment) { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Cannot concatenate in assignments: %.*s")); + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeConcat); + HL_CUR_TOKEN(Concat); + } else { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeConcatOrSubscript); + HL_CUR_TOKEN(ConcatOrSubscript); + } + ADD_OP_NODE(cur_node); + break; + } + case kExprLexParenthesis: { + if (cur_token.data.brc.closing) { + if (want_node == kENodeValue) { + if (kv_size(ast_stack) > 1) { + const ExprASTNode *const prev_top_node = *kv_Z(ast_stack, 1); + if (prev_top_node->type == kExprNodeCall) { + // Function call without arguments, this is not an error. + // But further code does not expect NULL nodes. + kv_drop(ast_stack, 1); + goto viml_pexpr_parse_no_paren_closing_error; + } + } + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Expected value, got parenthesis: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeMissing); + cur_node->len = 0; + *top_node_p = cur_node; + } else { + // Always drop the topmost value: when want_node != kENodeValue + // topmost item on stack is a *finished* left operand, which may as + // well be "(@a)" which needs not be finished again. + kv_drop(ast_stack, 1); + } +viml_pexpr_parse_no_paren_closing_error: {} + ExprASTNode **new_top_node_p = NULL; + while (kv_size(ast_stack) + && (new_top_node_p == NULL + || ((*new_top_node_p)->type != kExprNodeNested + && (*new_top_node_p)->type != kExprNodeCall))) { + new_top_node_p = kv_pop(ast_stack); + } + if (new_top_node_p != NULL + && ((*new_top_node_p)->type == kExprNodeNested + || (*new_top_node_p)->type == kExprNodeCall)) { + if ((*new_top_node_p)->type == kExprNodeNested) { + HL_CUR_TOKEN(NestingParenthesis); + } else { + HL_CUR_TOKEN(CallingParenthesis); + } + } else { + // “Always drop the topmost value” branch has got rid of the single + // value stack had, so there is nothing known to enclose. Correct + // this. + if (new_top_node_p == NULL) { + new_top_node_p = top_node_p; + } + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Unexpected closing parenthesis: %.*s")); + HL_CUR_TOKEN(NestingParenthesis); + cur_node = NEW_NODE(kExprNodeNested); + cur_node->start = cur_token.start; + cur_node->len = 0; + // Unexpected closing parenthesis, assume that it was wanted to + // enclose everything in (). + cur_node->children = *new_top_node_p; + *new_top_node_p = cur_node; + assert(cur_node->next == NULL); + } + kvi_push(ast_stack, new_top_node_p); + want_node = kENodeOperator; + } else { + if (want_node == kENodeValue) { + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeNested); + *top_node_p = cur_node; + kvi_push(ast_stack, &cur_node->children); + HL_CUR_TOKEN(NestingParenthesis); + } else if (want_node == kENodeOperator) { + if (prev_token.type == kExprLexSpacing) { + // For some reason "function (args)" is a function call, but + // "(funcref) (args)" is not. AFAIR this somehow involves + // compatibility and Bram was commenting that this is + // intentionally inconsistent and he is not very happy with the + // situation himself. + if ((*top_node_p)->type != kExprNodePlainIdentifier + && (*top_node_p)->type != kExprNodeComplexIdentifier + && (*top_node_p)->type != kExprNodeCurlyBracesIdentifier) { + OP_MISSING; + } + } + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeCall); + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(CallingParenthesis); + } else { + // Currently it is impossible to reach this. + assert(false); + } + want_node = kENodeValue; + } + break; + } + case kExprLexQuestion: { + ADD_VALUE_IF_MISSING(_("E15: Expected value, got question mark: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeTernary); + ADD_OP_NODE(cur_node); + HL_CUR_TOKEN(Ternary); + ExprASTNode *ter_val_node; + NEW_NODE_WITH_CUR_POS(ter_val_node, kExprNodeTernaryValue); + ter_val_node->data.ter.got_colon = false; + assert(cur_node->children != NULL); + assert(cur_node->children->next == NULL); + assert(kv_last(ast_stack) == &cur_node->children->next); + *kv_last(ast_stack) = ter_val_node; + kvi_push(ast_stack, &ter_val_node->children); + break; + } + case kExprLexDoubleQuotedString: + case kExprLexSingleQuotedString: { + const bool is_double = (tok_type == kExprLexDoubleQuotedString); + if (!cur_token.data.str.closed) { + // It is weird, but Vim has two identical errors messages with + // different error numbers: "E114: Missing quote" and + // "E115: Missing quote". + ERROR_FROM_TOKEN_AND_MSG( + cur_token, (is_double + ? _("E114: Missing double quote: %.*s") + : _("E115: Missing single quote: %.*s"))); + } + if (want_node == kENodeOperator) { + OP_MISSING; + } + NEW_NODE_WITH_CUR_POS( + cur_node, (is_double + ? kExprNodeDoubleQuotedString + : kExprNodeSingleQuotedString)); + *top_node_p = cur_node; + parse_quoted_string(pstate, cur_node, cur_token, ast_stack, is_invalid); + want_node = kENodeOperator; + break; + } + case kExprLexAssignment: { + if (cur_pt == kEPTAssignment) { + kv_drop(pt_stack, 1); + } else if (cur_pt == kEPTSingleAssignment) { + kv_drop(pt_stack, 2); + ERROR_FROM_TOKEN_AND_MSG( + cur_token, + _("E475: Expected closing bracket to end list assignment " + "lvalue: %.*s")); + } else { + ERROR_FROM_TOKEN_AND_MSG( + cur_token, _("E15: Misplaced assignment: %.*s")); + } + assert(kv_size(pt_stack)); + assert(kv_last(pt_stack) == kEPTExpr); + ADD_VALUE_IF_MISSING(_("E15: Unexpected assignment: %.*s")); + NEW_NODE_WITH_CUR_POS(cur_node, kExprNodeAssignment); + cur_node->data.ass.type = cur_token.data.ass.type; + switch (cur_token.data.ass.type) { +#define HL_ASGN(asgn, hl) \ + case kExprAsgn##asgn: { HL_CUR_TOKEN(hl); break; } + HL_ASGN(Plain, PlainAssignment) + HL_ASGN(Add, AssignmentWithAddition) + HL_ASGN(Subtract, AssignmentWithSubtraction) + HL_ASGN(Concat, AssignmentWithConcatenation) +#undef HL_ASGN + } + ADD_OP_NODE(cur_node); + break; + } + } +viml_pexpr_parse_cycle_end: + prev_token = cur_token; + highlighted_prev_spacing = false; + viml_parser_advance(pstate, cur_token.len); + } while (true); +viml_pexpr_parse_end: + assert(kv_size(pt_stack)); + assert(kv_size(ast_stack)); + if (want_node == kENodeValue + // Blacklist some parse type entries as their presence means better error + // message in the other branch. + && kv_last(pt_stack) != kEPTLambdaArguments) { + east_set_error(pstate, &ast.err, _("E15: Expected value, got EOC: %.*s"), + pstate->pos); + } else if (kv_size(ast_stack) != 1) { + // Something may be wrong, check whether it really is. + + // Pointer to ast.root must never be dropped, so “!= 1” is expected to be + // the same as “> 1”. + assert(kv_size(ast_stack)); + // Topmost stack item must be a *finished* value, so it must not be + // analyzed. E.g. it may contain an already finished nested expression. + kv_drop(ast_stack, 1); + while (ast.err.msg == NULL && kv_size(ast_stack)) { + const ExprASTNode *const cur_node = (*kv_pop(ast_stack)); + // This should only happen when want_node == kENodeValue. + assert(cur_node != NULL); + // TODO(ZyX-I): Rehighlight as invalid? + switch (cur_node->type) { + case kExprNodeOpMissing: + case kExprNodeMissing: { + // Error should’ve been already reported. + break; + } + case kExprNodeCall: { + east_set_error( + pstate, &ast.err, + _("E116: Missing closing parenthesis for function call: %.*s"), + cur_node->start); + break; + } + case kExprNodeNested: { + east_set_error( + pstate, &ast.err, + _("E110: Missing closing parenthesis for nested expression" + ": %.*s"), + cur_node->start); + break; + } + case kExprNodeListLiteral: { + // For whatever reason "[1" yields "E696: Missing comma in list" error + // in Vim while "[1," yields E697. + east_set_error( + pstate, &ast.err, + _("E697: Missing end of List ']': %.*s"), + cur_node->start); + break; + } + case kExprNodeDictLiteral: { + // Same problem like with list literal with E722 (missing comma) vs + // E723, but additionally just "{" yields only E15. + east_set_error( + pstate, &ast.err, + _("E723: Missing end of Dictionary '}': %.*s"), + cur_node->start); + break; + } + case kExprNodeUnknownFigure: { + east_set_error( + pstate, &ast.err, + _("E15: Missing closing figure brace: %.*s"), + cur_node->start); + break; + } + case kExprNodeLambda: { + east_set_error( + pstate, &ast.err, + _("E15: Missing closing figure brace for lambda: %.*s"), + cur_node->start); + break; + } + case kExprNodeCurlyBracesIdentifier: { + // Until trailing "}" it is impossible to distinguish curly braces + // identifier and dictionary, so it must not appear in the stack like + // this. + assert(false); + } + case kExprNodeInteger: + case kExprNodeFloat: + case kExprNodeSingleQuotedString: + case kExprNodeDoubleQuotedString: + case kExprNodeOption: + case kExprNodeEnvironment: + case kExprNodeRegister: + case kExprNodePlainIdentifier: + case kExprNodePlainKey: { + // These are plain values and not containers, for them it should only + // be possible to show up in the topmost stack element, but it was + // unconditionally popped at the start. + assert(false); + } + case kExprNodeComma: + case kExprNodeColon: + case kExprNodeArrow: { + // It is actually only valid inside something else, but everything + // where one of the above is valid requires to be closed and thus is + // to be caught later. + break; + } + case kExprNodeSubscript: + case kExprNodeConcatOrSubscript: + case kExprNodeComplexIdentifier: + case kExprNodeAssignment: + case kExprNodeMod: + case kExprNodeDivision: + case kExprNodeMultiplication: + case kExprNodeNot: + case kExprNodeAnd: + case kExprNodeOr: + case kExprNodeConcat: + case kExprNodeComparison: + case kExprNodeUnaryMinus: + case kExprNodeUnaryPlus: + case kExprNodeBinaryMinus: + case kExprNodeTernary: + case kExprNodeBinaryPlus: { + // It is OK to see these in the stack. + break; + } + case kExprNodeTernaryValue: { + if (!cur_node->data.ter.got_colon) { + // Actually Vim throws E109 in more cases. + east_set_error( + pstate, &ast.err, _("E109: Missing ':' after '?': %.*s"), + cur_node->start); + } + break; + } + } + } + } + kvi_destroy(ast_stack); + return ast; +} // NOLINT(readability/fn_size) + +#undef NEW_NODE +#undef HL diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h new file mode 100644 index 0000000000..23e172da75 --- /dev/null +++ b/src/nvim/viml/parser/expressions.h @@ -0,0 +1,389 @@ +#ifndef NVIM_VIML_PARSER_EXPRESSIONS_H +#define NVIM_VIML_PARSER_EXPRESSIONS_H + +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +#include "nvim/types.h" +#include "nvim/viml/parser/parser.h" +#include "nvim/eval/typval.h" + +// Defines whether to ignore case: +// == kCCStrategyUseOption +// ==# kCCStrategyMatchCase +// ==? kCCStrategyIgnoreCase +typedef enum { + kCCStrategyUseOption = 0, // 0 for xcalloc + kCCStrategyMatchCase = '#', + kCCStrategyIgnoreCase = '?', +} ExprCaseCompareStrategy; + +/// Lexer token type +typedef enum { + kExprLexInvalid = 0, ///< Invalid token, indicaten an error. + kExprLexMissing, ///< Missing token, for use in parser. + kExprLexSpacing, ///< Spaces, tabs, newlines, etc. + kExprLexEOC, ///< End of command character: NL, |, just end of stream. + + kExprLexQuestion, ///< Question mark, for use in ternary. + kExprLexColon, ///< Colon, for use in ternary. + kExprLexOr, ///< Logical or operator. + kExprLexAnd, ///< Logical and operator. + kExprLexComparison, ///< One of the comparison operators. + kExprLexPlus, ///< Plus sign. + kExprLexMinus, ///< Minus sign. + kExprLexDot, ///< Dot: either concat or subscript, also part of the float. + kExprLexMultiplication, ///< Multiplication, division or modulo operator. + + kExprLexNot, ///< Not: !. + + kExprLexNumber, ///< Integer number literal, or part of a float. + kExprLexSingleQuotedString, ///< Single quoted string literal. + kExprLexDoubleQuotedString, ///< Double quoted string literal. + kExprLexOption, ///< &optionname option value. + kExprLexRegister, ///< @r register value. + kExprLexEnv, ///< Environment $variable value. + kExprLexPlainIdentifier, ///< Identifier without scope: `abc`, `foo#bar`. + + kExprLexBracket, ///< Bracket, either opening or closing. + kExprLexFigureBrace, ///< Figure brace, either opening or closing. + kExprLexParenthesis, ///< Parenthesis, either opening or closing. + kExprLexComma, ///< Comma. + kExprLexArrow, ///< Arrow, like from lambda expressions. + kExprLexAssignment, ///< Assignment: `=` or `{op}=`. + // XXX When modifying this enum you need to also modify eltkn_type_tab in + // expressions.c and tests and, possibly, viml_pexpr_repr_token. +} LexExprTokenType; + +typedef enum { + kExprCmpEqual, ///< Equality, unequality. + kExprCmpMatches, ///< Matches regex, not matches regex. + kExprCmpGreater, ///< `>` or `<=` + kExprCmpGreaterOrEqual, ///< `>=` or `<`. + kExprCmpIdentical, ///< `is` or `isnot` +} ExprComparisonType; + +/// All possible option scopes +typedef enum { + kExprOptScopeUnspecified = 0, + kExprOptScopeGlobal = 'g', + kExprOptScopeLocal = 'l', +} ExprOptScope; + +/// All possible assignment types: `=` and `{op}=`. +typedef enum { + kExprAsgnPlain = 0, ///< Plain assignment: `=`. + kExprAsgnAdd, ///< Assignment augmented with addition: `+=`. + kExprAsgnSubtract, ///< Assignment augmented with subtraction: `-=`. + kExprAsgnConcat, ///< Assignment augmented with concatenation: `.=`. +} ExprAssignmentType; + +#define EXPR_OPT_SCOPE_LIST \ + ((char[]){ kExprOptScopeGlobal, kExprOptScopeLocal }) + +/// All possible variable scopes +typedef enum { + kExprVarScopeMissing = 0, + kExprVarScopeScript = 's', + kExprVarScopeGlobal = 'g', + kExprVarScopeVim = 'v', + kExprVarScopeBuffer = 'b', + kExprVarScopeWindow = 'w', + kExprVarScopeTabpage = 't', + kExprVarScopeLocal = 'l', + kExprVarScopeArguments = 'a', +} ExprVarScope; + +#define EXPR_VAR_SCOPE_LIST \ + ((char[]) { \ + kExprVarScopeScript, kExprVarScopeGlobal, kExprVarScopeVim, \ + kExprVarScopeBuffer, kExprVarScopeWindow, kExprVarScopeTabpage, \ + kExprVarScopeLocal, kExprVarScopeBuffer, kExprVarScopeArguments, \ + }) + +/// Lexer token +typedef struct { + ParserPosition start; + size_t len; + LexExprTokenType type; + union { + struct { + ExprComparisonType type; ///< Comparison type. + ExprCaseCompareStrategy ccs; ///< Case comparison strategy. + bool inv; ///< True if comparison is to be inverted. + } cmp; ///< For kExprLexComparison. + + struct { + enum { + kExprLexMulMul, ///< Real multiplication. + kExprLexMulDiv, ///< Division. + kExprLexMulMod, ///< Modulo. + } type; ///< Multiplication type. + } mul; ///< For kExprLexMultiplication. + + struct { + bool closing; ///< True if bracket/etc is a closing one. + } brc; ///< For brackets/braces/parenthesis. + + struct { + int name; ///< Register name, may be -1 if name not present. + } reg; ///< For kExprLexRegister. + + struct { + bool closed; ///< True if quote was closed. + } str; ///< For kExprLexSingleQuotedString and kExprLexDoubleQuotedString. + + struct { + const char *name; ///< Option name start. + size_t len; ///< Option name length. + ExprOptScope scope; ///< Option scope: &l:, &g: or not specified. + } opt; ///< Option properties. + + struct { + ExprVarScope scope; ///< Scope character or 0 if not present. + bool autoload; ///< Has autoload characters. + } var; ///< For kExprLexPlainIdentifier + + struct { + LexExprTokenType type; ///< Suggested type for parsing incorrect code. + const char *msg; ///< Error message. + } err; ///< For kExprLexInvalid + + struct { + union { + float_T floating; + uvarnumber_T integer; + } val; ///< Number value. + uint8_t base; ///< Base: 2, 8, 10 or 16. + bool is_float; ///< True if number is a floating-point. + } num; ///< For kExprLexNumber + + struct { + ExprAssignmentType type; + } ass; ///< For kExprLexAssignment + } data; ///< Additional data, if needed. +} LexExprToken; + +typedef enum { + /// If set, “pointer” to the current byte in pstate will not be shifted + kELFlagPeek = (1 << 0), + /// Determines whether scope is allowed to come before the identifier + kELFlagForbidScope = (1 << 1), + /// Determines whether floating-point numbers are allowed + /// + /// I.e. whether dot is a decimal point separator or is not a part of + /// a number at all. + kELFlagAllowFloat = (1 << 2), + /// Determines whether `is` and `isnot` are seen as comparison operators + /// + /// If set they are supposed to be just regular identifiers. + kELFlagIsNotCmp = (1 << 3), + /// Determines whether EOC tokens are allowed + /// + /// If set then it will yield Invalid token with E15 in place of EOC one if + /// “EOC” is something like "|". It is fine with emitting EOC at the end of + /// string still, with or without this flag set. + kELFlagForbidEOC = (1 << 4), + // XXX Whenever you add a new flag, alter klee_assume() statement in + // viml_expressions_lexer.c. +} LexExprFlags; + +/// Expression AST node type +typedef enum { + kExprNodeMissing = 0, + kExprNodeOpMissing, + kExprNodeTernary, ///< Ternary operator. + kExprNodeTernaryValue, ///< Ternary operator, colon. + kExprNodeRegister, ///< Register. + kExprNodeSubscript, ///< Subscript. + kExprNodeListLiteral, ///< List literal. + kExprNodeUnaryPlus, + kExprNodeBinaryPlus, + kExprNodeNested, ///< Nested parenthesised expression. + kExprNodeCall, ///< Function call. + /// Plain identifier: simple variable/function name + /// + /// Looks like "string", "g:Foo", etc: consists from a single + /// kExprLexPlainIdentifier token. + kExprNodePlainIdentifier, + /// Plain dictionary key, for use with kExprNodeConcatOrSubscript + kExprNodePlainKey, + /// Complex identifier: variable/function name with curly braces + kExprNodeComplexIdentifier, + /// Figure brace expression which is not yet known + /// + /// May resolve to any of kExprNodeDictLiteral, kExprNodeLambda or + /// kExprNodeCurlyBracesIdentifier. + kExprNodeUnknownFigure, + kExprNodeLambda, ///< Lambda. + kExprNodeDictLiteral, ///< Dictionary literal. + kExprNodeCurlyBracesIdentifier, ///< Part of the curly braces name. + kExprNodeComma, ///< Comma “operator”. + kExprNodeColon, ///< Colon “operator”. + kExprNodeArrow, ///< Arrow “operator”. + kExprNodeComparison, ///< Various comparison operators. + /// Concat operator + /// + /// To be only used in cases when it is known for sure it is not a subscript. + kExprNodeConcat, + /// Concat or subscript operator + /// + /// For cases when it is not obvious whether expression is a concat or + /// a subscript. May only have either number or plain identifier as the second + /// child. To make it easier to avoid curly braces in place of + /// kExprNodePlainIdentifier node kExprNodePlainKey is used. + kExprNodeConcatOrSubscript, + kExprNodeInteger, ///< Integral number. + kExprNodeFloat, ///< Floating-point number. + kExprNodeSingleQuotedString, + kExprNodeDoubleQuotedString, + kExprNodeOr, + kExprNodeAnd, + kExprNodeUnaryMinus, + kExprNodeBinaryMinus, + kExprNodeNot, + kExprNodeMultiplication, + kExprNodeDivision, + kExprNodeMod, + kExprNodeOption, + kExprNodeEnvironment, + kExprNodeAssignment, + // XXX When modifying this list also modify east_node_type_tab both in parser + // and in tests, and you most likely will also have to alter list of + // highlight groups stored in highlight_init_cmdline variable. +} ExprASTNodeType; + +typedef struct expr_ast_node ExprASTNode; + +/// Structure representing one AST node +struct expr_ast_node { + ExprASTNodeType type; ///< Node type. + /// Node children: e.g. for 1 + 2 nodes 1 and 2 will be children of +. + ExprASTNode *children; + /// Next node: e.g. for 1 + 2 child nodes 1 and 2 are put into a single-linked + /// list: `(+)->children` references only node 1, node 2 is in + /// `(+)->children->next`. + ExprASTNode *next; + ParserPosition start; + size_t len; + union { + struct { + int name; ///< Register name, may be -1 if name not present. + } reg; ///< For kExprNodeRegister. + struct { + /// Which nodes UnknownFigure can’t possibly represent. + struct { + /// True if UnknownFigure may actually represent dictionary literal. + bool allow_dict; + /// True if UnknownFigure may actually represent lambda. + bool allow_lambda; + /// True if UnknownFigure may actually be part of curly braces name. + bool allow_ident; + } type_guesses; + /// Highlight chunk index, used for rehighlighting if needed + size_t opening_hl_idx; + } fig; ///< For kExprNodeUnknownFigure. + struct { + ExprVarScope scope; ///< Scope character or 0 if not present. + /// Actual identifier without scope. + /// + /// Points to inside parser reader state. + const char *ident; + size_t ident_len; ///< Actual identifier length. + } var; ///< For kExprNodePlainIdentifier and kExprNodePlainKey. + struct { + bool got_colon; ///< True if colon was seen. + } ter; ///< For kExprNodeTernaryValue. + struct { + ExprComparisonType type; ///< Comparison type. + ExprCaseCompareStrategy ccs; ///< Case comparison strategy. + bool inv; ///< True if comparison is to be inverted. + } cmp; ///< For kExprNodeComparison. + struct { + uvarnumber_T value; + } num; ///< For kExprNodeInteger. + struct { + float_T value; + } flt; ///< For kExprNodeFloat. + struct { + char *value; + size_t size; + } str; ///< For kExprNodeSingleQuotedString and + ///< kExprNodeDoubleQuotedString. + struct { + const char *ident; ///< Option name start. + size_t ident_len; ///< Option name length. + ExprOptScope scope; ///< Option scope: &l:, &g: or not specified. + } opt; ///< For kExprNodeOption. + struct { + const char *ident; ///< Environment variable name start. + size_t ident_len; ///< Environment variable name length. + } env; ///< For kExprNodeEnvironment. + struct { + ExprAssignmentType type; + } ass; ///< For kExprNodeAssignment + } data; +}; + +enum { + /// Allow multiple expressions in a row: e.g. for :echo + /// + /// Parser will still parse only one of them though. + kExprFlagsMulti = (1 << 0), + /// Allow NL, NUL and bar to be EOC + /// + /// When parsing expressions input by user bar is assumed to be a binary + /// operator and other two are spacings. + kExprFlagsDisallowEOC = (1 << 1), + /// Parse :let argument + /// + /// That mean that top level node must be an assignment and first nodes + /// belong to lvalues. + kExprFlagsParseLet = (1 << 2), + // XXX whenever you add a new flag, alter klee_assume() statement in + // viml_expressions_parser.c, nvim_parse_expression() flags parsing + // alongside with its documentation and flag sets in check_parsing() + // function in expressions parser functional and unit tests. +} ExprParserFlags; + +/// AST error definition +typedef struct { + /// Error message. Must contain a single printf format atom: %.*s. + const char *msg; + /// Error message argument: points to the location of the error. + const char *arg; + /// Message argument length: length till the end of string. + int arg_len; +} ExprASTError; + +/// Structure representing complety AST for one expression +typedef struct { + /// When AST is not correct this message will be printed. + /// + /// Uses `emsgf(msg, arg_len, arg);`, `msg` is assumed to contain only `%.*s`. + ExprASTError err; + /// Root node of the AST. + ExprASTNode *root; +} ExprAST; + +/// Array mapping ExprASTNodeType to maximum amount of children node may have +extern const uint8_t node_maxchildren[]; + +/// Array mapping ExprASTNodeType values to their stringified versions +extern const char *const east_node_type_tab[]; + +/// Array mapping ExprComparisonType values to their stringified versions +extern const char *const eltkn_cmp_type_tab[]; + +/// Array mapping ExprCaseCompareStrategy values to their stringified versions +extern const char *const ccs_tab[]; + +/// Array mapping ExprAssignmentType values to their stringified versions +extern const char *const expr_asgn_type_tab[]; + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/expressions.h.generated.h" +#endif + +#endif // NVIM_VIML_PARSER_EXPRESSIONS_H diff --git a/src/nvim/viml/parser/parser.c b/src/nvim/viml/parser/parser.c new file mode 100644 index 0000000000..8d26d08ea7 --- /dev/null +++ b/src/nvim/viml/parser/parser.c @@ -0,0 +1,16 @@ +// 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 + +#include "nvim/viml/parser/parser.h" + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/parser.c.generated.h" +#endif + + +void parser_simple_get_line(void *cookie, ParserLine *ret_pline) +{ + ParserLine **plines_p = (ParserLine **)cookie; + *ret_pline = **plines_p; + (*plines_p)++; +} diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h new file mode 100644 index 0000000000..7ac49709d8 --- /dev/null +++ b/src/nvim/viml/parser/parser.h @@ -0,0 +1,244 @@ +#ifndef NVIM_VIML_PARSER_PARSER_H +#define NVIM_VIML_PARSER_PARSER_H + +#include <stdbool.h> +#include <stddef.h> +#include <assert.h> + +#include "nvim/lib/kvec.h" +#include "nvim/func_attr.h" +#include "nvim/mbyte.h" +#include "nvim/memory.h" + +/// One parsed line +typedef struct { + const char *data; ///< Parsed line pointer + size_t size; ///< Parsed line size + bool allocated; ///< True if line may be freed. +} ParserLine; + +/// Line getter type for parser +/// +/// Line getter must return {NULL, 0} for EOF. +typedef void (*ParserLineGetter)(void *cookie, ParserLine *ret_pline); + +/// Parser position in the input +typedef struct { + size_t line; ///< Line index in ParserInputReader.lines. + size_t col; ///< Byte index in the line. +} ParserPosition; + +/// Parser state item. +typedef struct { + enum { + kPTopStateParsingCommand = 0, + kPTopStateParsingExpression, + } type; + union { + struct { + enum { + kExprUnknown = 0, + } type; + } expr; + } data; +} ParserStateItem; + +/// Structure defining input reader +typedef struct { + /// Function used to get next line. + ParserLineGetter get_line; + /// Data for get_line function. + void *cookie; + /// All lines obtained by get_line. + kvec_withinit_t(ParserLine, 4) lines; + /// Conversion, for :scriptencoding. + vimconv_T conv; +} ParserInputReader; + +/// Highlighted region definition +/// +/// Note: one chunk may highlight only one line. +typedef struct { + ParserPosition start; ///< Start of the highlight: line and column. + size_t end_col; ///< End column, points to the start of the next character. + const char *group; ///< Highlight group. +} ParserHighlightChunk; + +/// Highlighting defined by a parser +typedef kvec_withinit_t(ParserHighlightChunk, 16) ParserHighlight; + +/// Structure defining parser state +typedef struct { + /// Line reader. + ParserInputReader reader; + /// Position up to which input was parsed. + ParserPosition pos; + /// Parser state stack. + kvec_withinit_t(ParserStateItem, 16) stack; + /// Highlighting support. + ParserHighlight *colors; + /// True if line continuation can be used. + bool can_continuate; +} ParserState; + +static inline void viml_parser_init( + ParserState *const ret_pstate, + const ParserLineGetter get_line, void *const cookie, + ParserHighlight *const colors) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1, 2); + +/// Initialize a new parser state instance +/// +/// @param[out] ret_pstate Parser state to initialize. +/// @param[in] get_line Line getter function. +/// @param[in] cookie Argument for the get_line function. +/// @param[in] colors Where to save highlighting. May be NULL if it is not +/// needed. +static inline void viml_parser_init( + ParserState *const ret_pstate, + const ParserLineGetter get_line, void *const cookie, + ParserHighlight *const colors) +{ + *ret_pstate = (ParserState) { + .reader = { + .get_line = get_line, + .cookie = cookie, + .conv = MBYTE_NONE_CONV, + }, + .pos = { 0, 0 }, + .colors = colors, + .can_continuate = false, + }; + kvi_init(ret_pstate->reader.lines); + kvi_init(ret_pstate->stack); +} + +static inline void viml_parser_destroy(ParserState *const pstate) + REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE; + +/// Free all memory allocated by the parser on heap +/// +/// @param pstate Parser state to free. +static inline void viml_parser_destroy(ParserState *const pstate) +{ + for (size_t i = 0; i < kv_size(pstate->reader.lines); i++) { + ParserLine pline = kv_A(pstate->reader.lines, i); + if (pline.allocated) { + xfree((void *)pline.data); + } + } + kvi_destroy(pstate->reader.lines); + kvi_destroy(pstate->stack); +} + +static inline void viml_preader_get_line(ParserInputReader *const preader, + ParserLine *const ret_pline) + REAL_FATTR_NONNULL_ALL; + +/// Get one line from ParserInputReader +static inline void viml_preader_get_line(ParserInputReader *const preader, + ParserLine *const ret_pline) +{ + ParserLine pline; + preader->get_line(preader->cookie, &pline); + if (preader->conv.vc_type != CONV_NONE && pline.size) { + ParserLine cpline = { + .allocated = true, + .size = pline.size, + }; + cpline.data = (char *)string_convert(&preader->conv, + (char_u *)pline.data, + &cpline.size); + if (pline.allocated) { + xfree((void *)pline.data); + } + pline = cpline; + } + kvi_push(preader->lines, pline); + *ret_pline = pline; +} + +static inline bool viml_parser_get_remaining_line(ParserState *const pstate, + ParserLine *const ret_pline) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL; + +/// Get currently parsed line, shifted to pstate->pos.col +/// +/// @param pstate Parser state to operate on. +/// +/// @return True if there is a line, false in case of EOF. +static inline bool viml_parser_get_remaining_line(ParserState *const pstate, + ParserLine *const ret_pline) +{ + const size_t num_lines = kv_size(pstate->reader.lines); + if (pstate->pos.line == num_lines) { + viml_preader_get_line(&pstate->reader, ret_pline); + } else { + *ret_pline = kv_last(pstate->reader.lines); + } + assert(pstate->pos.line == kv_size(pstate->reader.lines) - 1); + if (ret_pline->data != NULL) { + ret_pline->data += pstate->pos.col; + ret_pline->size -= pstate->pos.col; + } + return ret_pline->data != NULL; +} + +static inline void viml_parser_advance(ParserState *const pstate, + const size_t len) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; + +/// Advance position by a given number of bytes +/// +/// At maximum advances to the next line. +/// +/// @param pstate Parser state to advance. +/// @param[in] len Number of bytes to advance. +static inline void viml_parser_advance(ParserState *const pstate, + const size_t len) +{ + assert(pstate->pos.line == kv_size(pstate->reader.lines) - 1); + const ParserLine pline = kv_last(pstate->reader.lines); + if (pstate->pos.col + len >= pline.size) { + pstate->pos.line++; + pstate->pos.col = 0; + } else { + pstate->pos.col += len; + } +} + +static inline void viml_parser_highlight(ParserState *const pstate, + const ParserPosition start, + const size_t end_col, + const char *const group) + REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL; + +/// Record highlighting of some region of text +/// +/// @param pstate Parser state to work with. +/// @param[in] start Start position of the highlight. +/// @param[in] len Highlighting chunk length. +/// @param[in] group Highlight group. +static inline void viml_parser_highlight(ParserState *const pstate, + const ParserPosition start, + const size_t len, + const char *const group) +{ + if (pstate->colors == NULL || len == 0) { + return; + } + assert(kv_size(*pstate->colors) == 0 + || kv_Z(*pstate->colors, 0).start.line < start.line + || kv_Z(*pstate->colors, 0).end_col <= start.col); + kvi_push(*pstate->colors, ((ParserHighlightChunk) { + .start = start, + .end_col = start.col + len, + .group = group, + })); +} + +#ifdef INCLUDE_GENERATED_DECLARATIONS +# include "viml/parser/parser.h.generated.h" +#endif + +#endif // NVIM_VIML_PARSER_PARSER_H diff --git a/src/nvim/window.c b/src/nvim/window.c index 4e4eb297aa..b4ef901d94 100644 --- a/src/nvim/window.c +++ b/src/nvim/window.c @@ -641,11 +641,12 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir) if (oldwin->w_width - new_size - 1 < p_wmw) do_equal = TRUE; - /* We don't like to take lines for the new window from a - * 'winfixwidth' window. Take them from a window to the left or right - * instead, if possible. */ - if (oldwin->w_p_wfw) - win_setwidth_win(oldwin->w_width + new_size, oldwin); + // We don't like to take lines for the new window from a + // 'winfixwidth' window. Take them from a window to the left or right + // instead, if possible. Add one for the separator. + if (oldwin->w_p_wfw) { + win_setwidth_win(oldwin->w_width + new_size + 1, oldwin); + } /* Only make all windows the same width if one of them (except oldwin) * is wider than one of the split windows. */ @@ -1724,7 +1725,6 @@ void close_windows(buf_T *buf, int keep_curwin) { tabpage_T *tp, *nexttp; int h = tabline_height(); - int count = tabpage_index(NULL); ++RedrawingDisabled; @@ -1762,10 +1762,6 @@ void close_windows(buf_T *buf, int keep_curwin) --RedrawingDisabled; - if (count != tabpage_index(NULL)) { - apply_autocmds(EVENT_TABCLOSED, NULL, NULL, false, curbuf); - } - redraw_tabline = true; if (h != tabline_height()) { shell_new_rows(); @@ -1848,7 +1844,6 @@ static bool close_last_window_tabpage(win_T *win, bool free_buf, // Since goto_tabpage_tp above did not trigger *Enter autocommands, do // that now. - apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, curbuf); apply_autocmds(EVENT_WINENTER, NULL, NULL, false, curbuf); apply_autocmds(EVENT_TABENTER, NULL, NULL, false, curbuf); if (old_curbuf != curbuf) { @@ -1991,6 +1986,14 @@ int win_close(win_T *win, int free_buf) * the screen space. */ wp = win_free_mem(win, &dir, NULL); + if (help_window) { + // Closing the help window moves the cursor back to the original window. + win_T *tmpwp = get_snapshot_focus(SNAP_HELP_IDX); + if (tmpwp != NULL) { + wp = tmpwp; + } + } + /* Make sure curwin isn't invalid. It can cause severe trouble when * printing an error message. For win_equal() curbuf needs to be valid * too. */ @@ -2100,19 +2103,29 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp) /* When closing the last window in a tab page remove the tab page. */ if (tp->tp_firstwin == tp->tp_lastwin) { - if (tp == first_tabpage) + char_u prev_idx[NUMBUFLEN]; + if (has_event(EVENT_TABCLOSED)) { + vim_snprintf((char *)prev_idx, NUMBUFLEN, "%i", tabpage_index(tp)); + } + + if (tp == first_tabpage) { first_tabpage = tp->tp_next; - else { + } else { for (ptp = first_tabpage; ptp != NULL && ptp->tp_next != tp; - ptp = ptp->tp_next) - ; + ptp = ptp->tp_next) { + // loop + } if (ptp == NULL) { - EMSG2(_(e_intern2), "win_close_othertab()"); + internal_error("win_close_othertab()"); return; } ptp->tp_next = tp->tp_next; } - free_tp = TRUE; + free_tp = true; + + if (has_event(EVENT_TABCLOSED)) { + apply_autocmds(EVENT_TABCLOSED, prev_idx, prev_idx, false, win->w_buffer); + } } /* Free the memory used for the window. */ @@ -2845,7 +2858,7 @@ close_others ( if (bufIsChanged(wp->w_buffer)) continue; } - win_close(wp, !P_HID(wp->w_buffer) && !bufIsChanged(wp->w_buffer)); + win_close(wp, !buf_hide(wp->w_buffer) && !bufIsChanged(wp->w_buffer)); } if (message && !ONE_WINDOW) @@ -5421,6 +5434,27 @@ static win_T *restore_snapshot_rec(frame_T *sn, frame_T *fr) return wp; } +/// Gets the focused window (the one holding the cursor) of the snapshot. +static win_T *get_snapshot_focus(int idx) +{ + if (curtab->tp_snapshot[idx] == NULL) { + return NULL; + } + + frame_T *sn = curtab->tp_snapshot[idx]; + // This should be equivalent to the recursive algorithm found in + // restore_snapshot as far as traveling nodes go. + while (sn->fr_child != NULL || sn->fr_next != NULL) { + while (sn->fr_child != NULL) { + sn = sn->fr_child; + } + if (sn->fr_next != NULL) { + sn = sn->fr_next; + } + } + + return sn->fr_win; +} /* * Set "win" to be the curwin and "tp" to be the current tab page. @@ -5577,49 +5611,48 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, } // Set up position matches - if (pos_list != NULL) - { - linenr_T toplnum = 0; - linenr_T botlnum = 0; - listitem_T *li; - int i; - - for (i = 0, li = pos_list->lv_first; li != NULL && i < MAXPOSMATCH; - i++, li = li->li_next) { - linenr_T lnum = 0; - colnr_T col = 0; - int len = 1; - list_T *subl; - listitem_T *subli; + if (pos_list != NULL) { + linenr_T toplnum = 0; + linenr_T botlnum = 0; + + int i = 0; + TV_LIST_ITER(pos_list, li, { + linenr_T lnum = 0; + colnr_T col = 0; + int len = 1; bool error = false; - if (li->li_tv.v_type == VAR_LIST) { - subl = li->li_tv.vval.v_list; - if (subl == NULL) { - goto fail; - } - subli = subl->lv_first; + if (TV_LIST_ITEM_TV(li)->v_type == VAR_LIST) { + const list_T *const subl = TV_LIST_ITEM_TV(li)->vval.v_list; + const listitem_T *subli = tv_list_first(subl); if (subli == NULL) { + emsgf(_("E5030: Empty list at position %d"), + (int)tv_list_idx_of_item(pos_list, li)); goto fail; } - lnum = tv_get_number_chk(&subli->li_tv, &error); + lnum = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); if (error) { goto fail; } - if (lnum == 0) { - --i; + if (lnum <= 0) { continue; } m->pos.pos[i].lnum = lnum; - subli = subli->li_next; + subli = TV_LIST_ITEM_NEXT(subl, subli); if (subli != NULL) { - col = tv_get_number_chk(&subli->li_tv, &error); + col = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); if (error) { goto fail; } - subli = subli->li_next; + if (col < 0) { + continue; + } + subli = TV_LIST_ITEM_NEXT(subl, subli); if (subli != NULL) { - len = tv_get_number_chk(&subli->li_tv, &error); + len = tv_get_number_chk(TV_LIST_ITEM_TV(subli), &error); + if (len < 0) { + continue; + } if (error) { goto fail; } @@ -5627,16 +5660,16 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, } m->pos.pos[i].col = col; m->pos.pos[i].len = len; - } else if (li->li_tv.v_type == VAR_NUMBER) { - if (li->li_tv.vval.v_number == 0) { - --i; + } else if (TV_LIST_ITEM_TV(li)->v_type == VAR_NUMBER) { + if (TV_LIST_ITEM_TV(li)->vval.v_number <= 0) { continue; } - m->pos.pos[i].lnum = li->li_tv.vval.v_number; + m->pos.pos[i].lnum = TV_LIST_ITEM_TV(li)->vval.v_number; m->pos.pos[i].col = 0; m->pos.pos[i].len = 0; } else { - EMSG(_("List or number required")); + emsgf(_("E5031: List or number required at position %d"), + (int)tv_list_idx_of_item(pos_list, li)); goto fail; } if (toplnum == 0 || lnum < toplnum) { @@ -5645,7 +5678,11 @@ int match_add(win_T *wp, const char *const grp, const char *const pat, if (botlnum == 0 || lnum >= botlnum) { botlnum = lnum + 1; } - } + i++; + if (i >= MAXPOSMATCH) { + break; + } + }); // Calculate top and bottom lines for redrawing area if (toplnum != 0){ @@ -5885,13 +5922,15 @@ void win_get_tabwin(handle_T id, int *tabnr, int *winnr) } } -void win_id2tabwin(typval_T *argvars, list_T *list) +void win_id2tabwin(typval_T *const argvars, typval_T *const rettv) { int winnr = 1; int tabnr = 1; handle_T id = (handle_T)tv_get_number(&argvars[0]); win_get_tabwin(id, &tabnr, &winnr); + + list_T *const list = tv_list_alloc_ret(rettv, 2); tv_list_append_number(list, tabnr); tv_list_append_number(list, winnr); } |