aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.asan-blacklist4
-rwxr-xr-xsrc/clint.py18
-rw-r--r--src/nvim/CMakeLists.txt18
-rw-r--r--src/nvim/README.md98
-rw-r--r--src/nvim/api/buffer.c12
-rw-r--r--src/nvim/api/private/defs.h2
-rw-r--r--src/nvim/api/private/helpers.c32
-rw-r--r--src/nvim/api/private/helpers.h1
-rw-r--r--src/nvim/api/ui.c53
-rw-r--r--src/nvim/api/ui_events.in.h11
-rw-r--r--src/nvim/api/vim.c606
-rw-r--r--src/nvim/arabic.c1070
-rw-r--r--src/nvim/ascii.h13
-rw-r--r--src/nvim/buffer.c93
-rw-r--r--src/nvim/buffer_defs.h2
-rw-r--r--src/nvim/channel.c138
-rw-r--r--src/nvim/charset.c207
-rw-r--r--src/nvim/charset.h16
-rw-r--r--src/nvim/cursor_shape.c9
-rw-r--r--src/nvim/diff.c82
-rw-r--r--src/nvim/digraph.c17
-rw-r--r--src/nvim/edit.c143
-rw-r--r--src/nvim/edit.h11
-rw-r--r--src/nvim/eval.c1969
-rw-r--r--src/nvim/eval.h1
-rw-r--r--src/nvim/eval.lua1
-rw-r--r--src/nvim/eval/decode.c98
-rw-r--r--src/nvim/eval/encode.c94
-rw-r--r--src/nvim/eval/encode.h13
-rw-r--r--src/nvim/eval/typval.c487
-rw-r--r--src/nvim/eval/typval.h380
-rw-r--r--src/nvim/eval/typval_encode.c.h139
-rw-r--r--src/nvim/event/libuv_process.c9
-rw-r--r--src/nvim/event/loop.c22
-rw-r--r--src/nvim/event/process.c21
-rw-r--r--src/nvim/event/rstream.c4
-rw-r--r--src/nvim/event/wstream.c2
-rw-r--r--src/nvim/ex_cmds.c64
-rw-r--r--src/nvim/ex_cmds.lua4
-rw-r--r--src/nvim/ex_cmds2.c73
-rw-r--r--src/nvim/ex_docmd.c259
-rw-r--r--src/nvim/ex_eval.c337
-rw-r--r--src/nvim/ex_getln.c281
-rw-r--r--src/nvim/farsi.c337
-rw-r--r--src/nvim/file_search.c4
-rw-r--r--src/nvim/fileio.c205
-rw-r--r--src/nvim/generators/gen_api_ui_events.lua15
-rwxr-xr-xsrc/nvim/generators/gen_declarations.lua64
-rw-r--r--src/nvim/generators/gen_options.lua1
-rw-r--r--src/nvim/getchar.c123
-rw-r--r--src/nvim/globals.h55
-rw-r--r--src/nvim/hashtab.c2
-rw-r--r--src/nvim/highlight_defs.h33
-rw-r--r--src/nvim/if_cscope.c51
-rw-r--r--src/nvim/indent_c.c65
-rw-r--r--src/nvim/keymap.c197
-rw-r--r--src/nvim/keymap.h9
-rw-r--r--src/nvim/lib/kbtree.h22
-rw-r--r--src/nvim/lib/kvec.h12
-rw-r--r--src/nvim/lib/ringbuf.h27
-rw-r--r--src/nvim/lua/converter.c51
-rw-r--r--src/nvim/lua/executor.c438
-rw-r--r--src/nvim/lua/vim.lua68
-rw-r--r--src/nvim/macros.h50
-rw-r--r--src/nvim/main.c137
-rw-r--r--src/nvim/map.h5
-rw-r--r--src/nvim/mark.c4
-rw-r--r--src/nvim/mbyte.c260
-rw-r--r--src/nvim/mbyte.h8
-rw-r--r--src/nvim/memfile.c5
-rw-r--r--src/nvim/memline.c108
-rw-r--r--src/nvim/memory.c2
-rw-r--r--src/nvim/menu.c2
-rw-r--r--src/nvim/message.c133
-rw-r--r--src/nvim/message.h9
-rw-r--r--src/nvim/misc1.c27
-rw-r--r--src/nvim/mouse.c117
-rw-r--r--src/nvim/move.c11
-rw-r--r--src/nvim/msgpack_rpc/server.c43
-rw-r--r--src/nvim/normal.c132
-rw-r--r--src/nvim/ops.c138
-rw-r--r--src/nvim/option.c772
-rw-r--r--src/nvim/option_defs.h13
-rw-r--r--src/nvim/options.lua57
-rw-r--r--src/nvim/os/env.c4
-rw-r--r--src/nvim/os/fileio.c16
-rw-r--r--src/nvim/os/fs.c12
-rw-r--r--src/nvim/os/input.c2
-rw-r--r--src/nvim/os/lang.c43
-rw-r--r--src/nvim/os/lang.h7
-rw-r--r--src/nvim/os/os_defs.h8
-rw-r--r--src/nvim/os/process.c267
-rw-r--r--src/nvim/os/process.h11
-rw-r--r--src/nvim/os/pty_process_unix.c14
-rw-r--r--src/nvim/os/pty_process_win.c33
-rw-r--r--src/nvim/os/shell.c147
-rw-r--r--src/nvim/os/signal.c3
-rw-r--r--src/nvim/os/time.c22
-rw-r--r--src/nvim/os/unix_defs.h3
-rw-r--r--src/nvim/os/win_defs.h11
-rw-r--r--src/nvim/os_unix.c3
-rw-r--r--src/nvim/path.c66
-rw-r--r--src/nvim/po/CMakeLists.txt36
-rw-r--r--src/nvim/po/af.po2
-rw-r--r--src/nvim/po/fi.po2
-rw-r--r--src/nvim/po/uk.po2
-rw-r--r--src/nvim/popupmnu.c10
-rw-r--r--src/nvim/quickfix.c527
-rw-r--r--src/nvim/regexp.c82
-rw-r--r--src/nvim/regexp_nfa.c24
-rw-r--r--src/nvim/screen.c296
-rw-r--r--src/nvim/search.c111
-rw-r--r--src/nvim/shada.c298
-rw-r--r--src/nvim/spell.c88
-rw-r--r--src/nvim/spellfile.c3
-rw-r--r--src/nvim/strings.c7
-rw-r--r--src/nvim/syntax.c2265
-rw-r--r--src/nvim/syntax.h16
-rw-r--r--src/nvim/syntax_defs.h17
-rw-r--r--src/nvim/tag.c2
-rw-r--r--src/nvim/terminal.c20
-rw-r--r--src/nvim/testdir/Makefile129
-rwxr-xr-xsrc/nvim/testdir/runnvim.sh82
-rw-r--r--src/nvim/testdir/runnvim.vim55
-rw-r--r--src/nvim/testdir/runtest.vim110
-rw-r--r--src/nvim/testdir/sautest/autoload/footest.vim2
-rw-r--r--src/nvim/testdir/setup.vim13
-rw-r--r--src/nvim/testdir/shared.vim20
-rw-r--r--src/nvim/testdir/test32.in60
-rw-r--r--src/nvim/testdir/test32.ok15
-rw-r--r--src/nvim/testdir/test53.in74
-rw-r--r--src/nvim/testdir/test53.ok45
-rw-r--r--src/nvim/testdir/test73.in168
-rw-r--r--src/nvim/testdir/test73.ok21
-rw-r--r--src/nvim/testdir/test79.inbin3335 -> 0 bytes
-rw-r--r--src/nvim/testdir/test79.okbin570 -> 0 bytes
-rw-r--r--src/nvim/testdir/test_alot.vim10
-rw-r--r--src/nvim/testdir/test_alot_latin.vim10
-rw-r--r--src/nvim/testdir/test_alot_utf8.vim17
-rw-r--r--src/nvim/testdir/test_arabic.vim472
-rw-r--r--src/nvim/testdir/test_arglist.vim355
-rw-r--r--src/nvim/testdir/test_autochdir.vim19
-rw-r--r--src/nvim/testdir/test_autocmd.vim747
-rw-r--r--src/nvim/testdir/test_bufwintabinfo.vim1
-rw-r--r--src/nvim/testdir/test_changedtick.vim57
-rw-r--r--src/nvim/testdir/test_charsearch_utf8.vim22
-rw-r--r--src/nvim/testdir/test_cindent.vim76
-rw-r--r--src/nvim/testdir/test_close_count.vim174
-rw-r--r--src/nvim/testdir/test_cmdline.vim105
-rw-r--r--src/nvim/testdir/test_command_count.vim20
-rw-r--r--src/nvim/testdir/test_curswant.vim23
-rw-r--r--src/nvim/testdir/test_diffmode.vim295
-rw-r--r--src/nvim/testdir/test_edit.vim1325
-rw-r--r--src/nvim/testdir/test_erasebackword.vim25
-rw-r--r--src/nvim/testdir/test_exists.vim321
-rw-r--r--src/nvim/testdir/test_expr.vim5
-rw-r--r--src/nvim/testdir/test_expr_utf8.vim4
-rw-r--r--src/nvim/testdir/test_farsi.vim49
-rw-r--r--src/nvim/testdir/test_file_size.vim58
-rw-r--r--src/nvim/testdir/test_filetype.vim557
-rw-r--r--src/nvim/testdir/test_find_complete.vim157
-rw-r--r--src/nvim/testdir/test_findfile.vim25
-rw-r--r--src/nvim/testdir/test_fixeol.vim48
-rw-r--r--src/nvim/testdir/test_fold.vim21
-rw-r--r--src/nvim/testdir/test_functions.vim42
-rw-r--r--src/nvim/testdir/test_hardcopy.vim1
-rw-r--r--src/nvim/testdir/test_help_tagjump.vim56
-rw-r--r--src/nvim/testdir/test_ins_complete.vim219
-rw-r--r--src/nvim/testdir/test_let.vim27
-rw-r--r--src/nvim/testdir/test_lineending.vim19
-rw-r--r--src/nvim/testdir/test_lispwords.vim82
-rw-r--r--src/nvim/testdir/test_listchars.vim63
-rw-r--r--src/nvim/testdir/test_listdict.vim603
-rw-r--r--src/nvim/testdir/test_listlbr.vim3
-rw-r--r--src/nvim/testdir/test_listlbr_utf8.vim27
-rw-r--r--src/nvim/testdir/test_makeencoding.py67
-rw-r--r--src/nvim/testdir/test_makeencoding.vim106
-rw-r--r--src/nvim/testdir/test_mksession_utf8.vim1
-rw-r--r--src/nvim/testdir/test_normal.vim46
-rw-r--r--src/nvim/testdir/test_number.vim254
-rw-r--r--src/nvim/testdir/test_options.vim97
-rw-r--r--src/nvim/testdir/test_popup.vim38
-rw-r--r--src/nvim/testdir/test_put.vim36
-rw-r--r--src/nvim/testdir/test_quickfix.vim654
-rw-r--r--src/nvim/testdir/test_recover.vim63
-rw-r--r--src/nvim/testdir/test_regexp_latin.vim32
-rw-r--r--src/nvim/testdir/test_scrollbind.vim32
-rw-r--r--src/nvim/testdir/test_search.vim167
-rw-r--r--src/nvim/testdir/test_sha256.vim22
-rw-r--r--src/nvim/testdir/test_signs.vim2
-rw-r--r--src/nvim/testdir/test_source_utf8.vim30
-rw-r--r--src/nvim/testdir/test_spell.vim821
-rw-r--r--src/nvim/testdir/test_stat.vim31
-rw-r--r--src/nvim/testdir/test_statusline.vim251
-rw-r--r--src/nvim/testdir/test_substitute.vim282
-rw-r--r--src/nvim/testdir/test_syntax.vim226
-rw-r--r--src/nvim/testdir/test_system.vim92
-rw-r--r--src/nvim/testdir/test_tab.vim45
-rw-r--r--src/nvim/testdir/test_tabpage.vim19
-rw-r--r--src/nvim/testdir/test_tagjump.vim37
-rw-r--r--src/nvim/testdir/test_textformat.vim168
-rw-r--r--src/nvim/testdir/test_textobjects.vim127
-rw-r--r--src/nvim/testdir/test_timers.vim2
-rw-r--r--src/nvim/testdir/test_undo.vim23
-rw-r--r--src/nvim/testdir/test_unlet.vim4
-rw-r--r--src/nvim/testdir/test_utf8_comparisons.vim95
-rw-r--r--src/nvim/testdir/test_vimscript.vim56
-rw-r--r--src/nvim/testdir/test_visual.vim109
-rw-r--r--src/nvim/testdir/test_winbuf_close.vim124
-rw-r--r--src/nvim/testdir/test_window_cmd.vim12
-rw-r--r--src/nvim/testdir/unix.vim6
-rw-r--r--src/nvim/tui/input.c6
-rw-r--r--src/nvim/tui/terminfo.c91
-rw-r--r--src/nvim/tui/tui.c355
-rw-r--r--src/nvim/ugrid.c8
-rw-r--r--src/nvim/ugrid.h6
-rw-r--r--src/nvim/ui.c188
-rw-r--r--src/nvim/ui.h20
-rw-r--r--src/nvim/ui_bridge.c17
-rw-r--r--src/nvim/undo.c57
-rw-r--r--src/nvim/version.c957
-rw-r--r--src/nvim/vim.h12
-rw-r--r--src/nvim/viml/parser/expressions.c3103
-rw-r--r--src/nvim/viml/parser/expressions.h389
-rw-r--r--src/nvim/viml/parser/parser.c16
-rw-r--r--src/nvim/viml/parser/parser.h244
-rw-r--r--src/nvim/window.c143
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), &ltv, 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(&params, argc, argv);
init_startuptime(&params);
@@ -259,11 +254,10 @@ int main(int argc, char **argv)
check_and_set_isatty(&params);
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(&params);
+ server_init(params.listen_addr);
if (GARGCOUNT > 0) {
fname = get_fname(&params, 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, &regname, 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, &regtype, 1);
- const char regname = (char)name;
- tv_list_append_string(args, &regname, 1);
+ list_T *args = tv_list_alloc(3);
+ tv_list_append_list(args, lines);
+ tv_list_append_string(args, &regtype, 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(&regmatch,
- win, buf, lnum + matchpos.lnum,
- matchcol,
- tm
- )) == 0)
- break;
+ || (nmatched = vim_regexec_multi(
+ &regmatch, 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
deleted file mode 100644
index afbf2083d2..0000000000
--- a/src/nvim/testdir/test79.in
+++ /dev/null
Binary files differ
diff --git a/src/nvim/testdir/test79.ok b/src/nvim/testdir/test79.ok
deleted file mode 100644
index d4e0ae8819..0000000000
--- a/src/nvim/testdir/test79.ok
+++ /dev/null
Binary files differ
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);
}