aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rwxr-xr-xsrc/clint.py2
-rw-r--r--src/nvim/CMakeLists.txt31
-rw-r--r--src/nvim/api/buffer.c594
-rw-r--r--src/nvim/api/private/helpers.c102
-rw-r--r--src/nvim/api/private/helpers.h4
-rw-r--r--src/nvim/api/ui.c2
-rw-r--r--src/nvim/api/ui_events.in.h10
-rw-r--r--src/nvim/api/vim.c225
-rw-r--r--src/nvim/api/window.c4
-rw-r--r--src/nvim/ascii.h2
-rw-r--r--src/nvim/auevents.lua7
-rw-r--r--src/nvim/buffer.c282
-rw-r--r--src/nvim/buffer_defs.h54
-rw-r--r--src/nvim/buffer_updates.c42
-rw-r--r--src/nvim/buffer_updates.h1
-rw-r--r--src/nvim/change.c40
-rw-r--r--src/nvim/charset.c46
-rw-r--r--src/nvim/cursor.c2
-rw-r--r--src/nvim/cursor_shape.c41
-rw-r--r--src/nvim/decoration.c330
-rw-r--r--src/nvim/decoration.h71
-rw-r--r--src/nvim/diff.c4
-rw-r--r--src/nvim/digraph.c1
-rw-r--r--src/nvim/edit.c142
-rw-r--r--src/nvim/eval.c189
-rw-r--r--src/nvim/eval.lua7
-rw-r--r--src/nvim/eval/decode.c2
-rw-r--r--src/nvim/eval/encode.c7
-rw-r--r--src/nvim/eval/funcs.c290
-rw-r--r--src/nvim/eval/typval.c3
-rw-r--r--src/nvim/eval/typval.h3
-rw-r--r--src/nvim/eval/userfunc.c214
-rw-r--r--src/nvim/ex_cmds.c120
-rw-r--r--src/nvim/ex_cmds.lua22
-rw-r--r--src/nvim/ex_cmds2.c70
-rw-r--r--src/nvim/ex_cmds_defs.h5
-rw-r--r--src/nvim/ex_docmd.c943
-rw-r--r--src/nvim/ex_eval.c55
-rw-r--r--src/nvim/ex_getln.c667
-rw-r--r--src/nvim/ex_session.c15
-rw-r--r--src/nvim/extmark.c635
-rw-r--r--src/nvim/extmark.h43
-rw-r--r--src/nvim/extmark_defs.h18
-rw-r--r--src/nvim/file_search.c5
-rw-r--r--src/nvim/fileio.c86
-rw-r--r--src/nvim/fold.c333
-rw-r--r--src/nvim/fold.h1
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua2
-rw-r--r--src/nvim/generators/gen_ex_cmds.lua28
-rw-r--r--src/nvim/generators/gen_options.lua9
-rw-r--r--src/nvim/getchar.c28
-rw-r--r--src/nvim/globals.h25
-rw-r--r--src/nvim/grid_defs.h2
-rw-r--r--src/nvim/hashtab.h1
-rw-r--r--src/nvim/highlight.c220
-rw-r--r--src/nvim/highlight_defs.h15
-rw-r--r--src/nvim/if_cscope.c2
-rw-r--r--src/nvim/indent.c24
-rw-r--r--src/nvim/indent_c.c16
-rw-r--r--src/nvim/keymap.c13
-rw-r--r--src/nvim/log.h15
-rw-r--r--src/nvim/lua/converter.c7
-rw-r--r--src/nvim/lua/executor.c164
-rw-r--r--src/nvim/lua/executor.h9
-rw-r--r--src/nvim/lua/treesitter.c356
-rw-r--r--src/nvim/lua/vim.lua156
-rw-r--r--src/nvim/macros.h25
-rw-r--r--src/nvim/main.c10
-rw-r--r--src/nvim/map.c21
-rw-r--r--src/nvim/map.h3
-rw-r--r--src/nvim/mark.c15
-rw-r--r--src/nvim/marktree.c68
-rw-r--r--src/nvim/marktree.h1
-rw-r--r--src/nvim/mbyte.c144
-rw-r--r--src/nvim/memline.c114
-rw-r--r--src/nvim/memline_defs.h2
-rw-r--r--src/nvim/menu.c2
-rw-r--r--src/nvim/message.c20
-rw-r--r--src/nvim/misc1.c7
-rw-r--r--src/nvim/mouse.c30
-rw-r--r--src/nvim/move.c83
-rw-r--r--src/nvim/msgpack_rpc/channel.c6
-rw-r--r--src/nvim/normal.c150
-rw-r--r--src/nvim/ops.c251
-rw-r--r--src/nvim/option.c128
-rw-r--r--src/nvim/option_defs.h13
-rw-r--r--src/nvim/options.lua391
-rw-r--r--src/nvim/os/dl.c6
-rw-r--r--src/nvim/os/env.c4
-rw-r--r--src/nvim/os/fs.c26
-rw-r--r--src/nvim/os/lang.c16
-rw-r--r--src/nvim/os/os_defs.h56
-rw-r--r--src/nvim/os/shell.c6
-rw-r--r--src/nvim/os/signal.c4
-rw-r--r--src/nvim/os/time.c9
-rw-r--r--src/nvim/os/win_defs.h22
-rw-r--r--src/nvim/po/check.vim1
-rw-r--r--src/nvim/po/uk.po1197
-rw-r--r--src/nvim/popupmnu.c87
-rw-r--r--src/nvim/quickfix.c550
-rw-r--r--src/nvim/regexp.c869
-rw-r--r--src/nvim/regexp_defs.h7
-rw-r--r--src/nvim/regexp_nfa.c1141
-rw-r--r--src/nvim/screen.c1105
-rw-r--r--src/nvim/search.c168
-rw-r--r--src/nvim/shada.c10
-rw-r--r--src/nvim/sign.c22
-rw-r--r--src/nvim/spell.c36
-rw-r--r--src/nvim/spell_defs.h1
-rw-r--r--src/nvim/spellfile.c54
-rw-r--r--src/nvim/syntax.c99
-rw-r--r--src/nvim/tag.c2
-rw-r--r--src/nvim/terminal.c4
-rw-r--r--src/nvim/testdir/check.vim99
-rwxr-xr-xsrc/nvim/testdir/runnvim.sh4
-rw-r--r--src/nvim/testdir/runtest.vim106
-rw-r--r--src/nvim/testdir/shared.vim31
-rw-r--r--src/nvim/testdir/summarize.vim1
-rw-r--r--src/nvim/testdir/test_alot.vim2
-rw-r--r--src/nvim/testdir/test_arglist.vim10
-rw-r--r--src/nvim/testdir/test_autocmd.vim46
-rw-r--r--src/nvim/testdir/test_backup.vim16
-rw-r--r--src/nvim/testdir/test_cindent.vim36
-rw-r--r--src/nvim/testdir/test_cjk_linebreak.vim97
-rw-r--r--src/nvim/testdir/test_cmdline.vim43
-rw-r--r--src/nvim/testdir/test_compiler.vim4
-rw-r--r--src/nvim/testdir/test_const.vim12
-rw-r--r--src/nvim/testdir/test_debugger.vim125
-rw-r--r--src/nvim/testdir/test_diffmode.vim209
-rw-r--r--src/nvim/testdir/test_display.vim155
-rw-r--r--src/nvim/testdir/test_edit.vim97
-rw-r--r--src/nvim/testdir/test_environ.vim25
-rw-r--r--src/nvim/testdir/test_eval_stuff.vim24
-rw-r--r--src/nvim/testdir/test_expand_func.vim47
-rw-r--r--src/nvim/testdir/test_expr.vim3
-rw-r--r--src/nvim/testdir/test_filetype.vim20
-rw-r--r--src/nvim/testdir/test_filter_map.vim1
-rw-r--r--src/nvim/testdir/test_fold.vim21
-rw-r--r--src/nvim/testdir/test_functions.vim51
-rw-r--r--src/nvim/testdir/test_gf.vim27
-rw-r--r--src/nvim/testdir/test_gn.vim27
-rw-r--r--src/nvim/testdir/test_highlight.vim14
-rw-r--r--src/nvim/testdir/test_ins_complete.vim112
-rw-r--r--src/nvim/testdir/test_interrupt.vim27
-rw-r--r--src/nvim/testdir/test_lambda.vim2
-rw-r--r--src/nvim/testdir/test_listdict.vim12
-rw-r--r--src/nvim/testdir/test_mapping.vim36
-rw-r--r--src/nvim/testdir/test_marks.vim48
-rw-r--r--src/nvim/testdir/test_matchadd_conceal.vim75
-rw-r--r--src/nvim/testdir/test_messages.vim24
-rw-r--r--src/nvim/testdir/test_mksession.vim136
-rw-r--r--src/nvim/testdir/test_options.vim11
-rw-r--r--src/nvim/testdir/test_perl.vim311
-rw-r--r--src/nvim/testdir/test_popup.vim47
-rw-r--r--src/nvim/testdir/test_put.vim26
-rw-r--r--src/nvim/testdir/test_quickfix.vim428
-rw-r--r--src/nvim/testdir/test_regexp_utf8.vim182
-rw-r--r--src/nvim/testdir/test_registers.vim69
-rw-r--r--src/nvim/testdir/test_restricted.vim103
-rw-r--r--src/nvim/testdir/test_ruby.vim385
-rw-r--r--src/nvim/testdir/test_search.vim490
-rw-r--r--src/nvim/testdir/test_search_stat.vim128
-rw-r--r--src/nvim/testdir/test_signs.vim114
-rw-r--r--src/nvim/testdir/test_spell.vim83
-rw-r--r--src/nvim/testdir/test_startup.vim26
-rw-r--r--src/nvim/testdir/test_statusline.vim34
-rw-r--r--src/nvim/testdir/test_swap.vim60
-rw-r--r--src/nvim/testdir/test_syntax.vim102
-rw-r--r--src/nvim/testdir/test_tabline.vim25
-rw-r--r--src/nvim/testdir/test_tagjump.vim34
-rw-r--r--src/nvim/testdir/test_textformat.vim20
-rw-r--r--src/nvim/testdir/test_textobjects.vim30
-rw-r--r--src/nvim/testdir/test_timers.vim42
-rw-r--r--src/nvim/testdir/test_undo.vim39
-rw-r--r--src/nvim/testdir/test_usercommands.vim229
-rw-r--r--src/nvim/testdir/test_version.vim12
-rw-r--r--src/nvim/testdir/test_vimscript.vim11
-rw-r--r--src/nvim/testdir/test_visual.vim11
-rw-r--r--src/nvim/testdir/test_window_cmd.vim46
-rw-r--r--src/nvim/testdir/test_windows_home.vim5
-rw-r--r--src/nvim/tui/tui.c210
-rw-r--r--src/nvim/types.h4
-rw-r--r--src/nvim/ui_bridge.c1
-rw-r--r--src/nvim/ui_compositor.c1
-rw-r--r--src/nvim/undo.c12
-rw-r--r--src/nvim/version.c46
-rw-r--r--src/nvim/vim.h4
-rw-r--r--src/nvim/viml/parser/expressions.c2
-rw-r--r--src/nvim/window.c114
-rw-r--r--src/tree_sitter/LICENSE21
-rw-r--r--src/tree_sitter/README.md16
-rw-r--r--src/tree_sitter/alloc.h95
-rw-r--r--src/tree_sitter/api.h876
-rw-r--r--src/tree_sitter/array.h158
-rw-r--r--src/tree_sitter/atomic.h42
-rw-r--r--src/tree_sitter/bits.h29
-rw-r--r--src/tree_sitter/clock.h141
-rw-r--r--src/tree_sitter/error_costs.h11
-rw-r--r--src/tree_sitter/get_changed_ranges.c482
-rw-r--r--src/tree_sitter/get_changed_ranges.h36
-rw-r--r--src/tree_sitter/language.c149
-rw-r--r--src/tree_sitter/language.h143
-rw-r--r--src/tree_sitter/length.h44
-rw-r--r--src/tree_sitter/lexer.c391
-rw-r--r--src/tree_sitter/lexer.h48
-rw-r--r--src/tree_sitter/lib.c17
-rw-r--r--src/tree_sitter/node.c677
-rw-r--r--src/tree_sitter/parser.c1879
-rw-r--r--src/tree_sitter/parser.h235
-rw-r--r--src/tree_sitter/point.h54
-rw-r--r--src/tree_sitter/query.c2035
-rw-r--r--src/tree_sitter/reduce_action.h34
-rw-r--r--src/tree_sitter/reusable_node.h88
-rw-r--r--src/tree_sitter/stack.c848
-rw-r--r--src/tree_sitter/stack.h135
-rw-r--r--src/tree_sitter/subtree.c982
-rw-r--r--src/tree_sitter/subtree.h285
-rw-r--r--src/tree_sitter/tree.c148
-rw-r--r--src/tree_sitter/tree.h34
-rw-r--r--src/tree_sitter/tree_cursor.c367
-rw-r--r--src/tree_sitter/tree_cursor.h21
-rw-r--r--src/tree_sitter/treesitter_commit_hash.txt1
-rw-r--r--src/tree_sitter/unicode.h50
-rw-r--r--src/tree_sitter/unicode/ICU_SHA1
-rw-r--r--src/tree_sitter/unicode/LICENSE414
-rw-r--r--src/tree_sitter/unicode/README.md29
-rw-r--r--src/tree_sitter/unicode/ptypes.h1
-rw-r--r--src/tree_sitter/unicode/umachine.h448
-rw-r--r--src/tree_sitter/unicode/urename.h1
-rw-r--r--src/tree_sitter/unicode/utf.h1
-rw-r--r--src/tree_sitter/unicode/utf16.h733
-rw-r--r--src/tree_sitter/unicode/utf8.h881
232 files changed, 13329 insertions, 18924 deletions
diff --git a/src/clint.py b/src/clint.py
index 8dc41fdb93..9b4128a0c9 100755
--- a/src/clint.py
+++ b/src/clint.py
@@ -350,7 +350,7 @@ def IsErrorInSuppressedErrorsList(category, linenum):
category: str, the category of the error.
linenum: int, the current line number.
Returns:
- bool, True iff the error should be suppressed due to presense in
+ bool, True iff the error should be suppressed due to presence in
suppressions file.
"""
return (category, linenum) in _error_suppressions_2
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index c7258dde12..8ec087c626 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -55,6 +55,7 @@ set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h)
set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h)
set(LUA_VIM_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/src/nvim/lua/vim.lua)
set(LUA_SHARED_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/shared.lua)
+set(LUA_INSPECT_MODULE_SOURCE ${PROJECT_SOURCE_DIR}/runtime/lua/vim/inspect.lua)
set(CHAR_BLOB_GENERATOR ${GENERATOR_DIR}/gen_char_blob.lua)
set(LINT_SUPPRESS_FILE ${PROJECT_BINARY_DIR}/errors.json)
set(LINT_SUPPRESS_URL_BASE "https://raw.githubusercontent.com/neovim/doc/gh-pages/reports/clint")
@@ -87,10 +88,6 @@ file(GLOB NVIM_HEADERS *.h)
file(GLOB XDIFF_SOURCES xdiff/*.c)
file(GLOB XDIFF_HEADERS xdiff/*.h)
-file(GLOB TREESITTER_SOURCES ../tree_sitter/*.c)
-file(GLOB TS_SOURCE_AMALGAM ../tree_sitter/lib.c)
-list(REMOVE_ITEM TREESITTER_SOURCES ${TS_SOURCE_AMALGAM})
-
foreach(subdir
os
api
@@ -187,9 +184,6 @@ if(NOT MSVC)
set_source_files_properties(
eval/funcs.c PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion")
endif()
-
- # tree-sitter: inlined external project, we don't maintain it. #10124
- set_source_files_properties(${TREESITTER_SOURCES} PROPERTIES COMPILE_FLAGS "${COMPILE_FLAGS} -Wno-conversion -Wno-pedantic -Wno-shadow -Wno-missing-prototypes -Wno-unused-variable")
endif()
if(NOT "${MIN_LOG_LEVEL}" MATCHES "^$")
@@ -330,10 +324,12 @@ add_custom_command(
COMMAND ${LUA_PRG} ${CHAR_BLOB_GENERATOR} ${VIM_MODULE_FILE}
${LUA_VIM_MODULE_SOURCE} vim_module
${LUA_SHARED_MODULE_SOURCE} shared_module
+ ${LUA_INSPECT_MODULE_SOURCE} inspect_module
DEPENDS
${CHAR_BLOB_GENERATOR}
${LUA_VIM_MODULE_SOURCE}
${LUA_SHARED_MODULE_SOURCE}
+ ${LUA_INSPECT_MODULE_SOURCE}
)
list(APPEND NVIM_GENERATED_SOURCES
@@ -449,6 +445,7 @@ list(APPEND NVIM_LINK_LIBRARIES
${LIBTERMKEY_LIBRARIES}
${UNIBILIUM_LIBRARIES}
${UTF8PROC_LIBRARIES}
+ ${TreeSitter_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT}
)
@@ -468,7 +465,7 @@ endif()
add_executable(nvim ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
${NVIM_GENERATED_SOURCES} ${NVIM_SOURCES} ${NVIM_HEADERS}
- ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES})
+ ${XDIFF_SOURCES} ${XDIFF_HEADERS})
target_link_libraries(nvim ${NVIM_EXEC_LINK_LIBRARIES})
install_helper(TARGETS nvim)
@@ -566,7 +563,7 @@ add_library(
EXCLUDE_FROM_ALL
${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES}
${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
- ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES}
+ ${XDIFF_SOURCES} ${XDIFF_HEADERS}
)
set_property(TARGET libnvim APPEND PROPERTY
INCLUDE_DIRECTORIES ${LUA_PREFERRED_INCLUDE_DIRS})
@@ -596,7 +593,7 @@ else()
EXCLUDE_FROM_ALL
${NVIM_SOURCES} ${NVIM_GENERATED_SOURCES}
${NVIM_HEADERS} ${NVIM_GENERATED_FOR_SOURCES} ${NVIM_GENERATED_FOR_HEADERS}
- ${XDIFF_SOURCES} ${XDIFF_HEADERS} ${TREESITTER_SOURCES}
+ ${XDIFF_SOURCES} ${XDIFF_HEADERS}
${UNIT_TEST_FIXTURES}
)
target_link_libraries(nvim-test ${NVIM_TEST_LINK_LIBRARIES})
@@ -620,9 +617,19 @@ if(CLANG_ASAN_UBSAN)
message(STATUS "Enabling Clang address sanitizer and undefined behavior sanitizer for nvim.")
check_c_compiler_flag(-fno-sanitize-recover=all SANITIZE_RECOVER_ALL)
if(SANITIZE_RECOVER_ALL)
- set(SANITIZE_RECOVER -fno-sanitize-recover=all) # Clang 3.6+
+ if(CI_BUILD)
+ # Try to recover from all sanitize issues so we get reports about all failures
+ set(SANITIZE_RECOVER -fsanitize-recover=all) # Clang 3.6+
+ else()
+ set(SANITIZE_RECOVER -fno-sanitize-recover=all) # Clang 3.6+
+ endif()
else()
- set(SANITIZE_RECOVER -fno-sanitize-recover) # Clang 3.5-
+ if(CI_BUILD)
+ # Try to recover from all sanitize issues so we get reports about all failures
+ set(SANITIZE_RECOVER -fsanitize-recover) # Clang 3.5-
+ else()
+ set(SANITIZE_RECOVER -fno-sanitize-recover) # Clang 3.5-
+ endif()
endif()
set_property(TARGET nvim APPEND PROPERTY COMPILE_DEFINITIONS EXITFREE)
set_property(TARGET nvim APPEND PROPERTY COMPILE_OPTIONS ${SANITIZE_RECOVER} -fno-omit-frame-pointer -fno-optimize-sibling-calls -fsanitize=address -fsanitize=undefined -fsanitize-blacklist=${PROJECT_SOURCE_DIR}/src/.asan-blacklist)
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 8e61976c4b..4fc0ee4fdf 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -7,6 +7,7 @@
#include <stdint.h>
#include <stdlib.h>
#include <limits.h>
+
#include <lauxlib.h>
#include "nvim/api/buffer.h"
@@ -27,6 +28,7 @@
#include "nvim/map.h"
#include "nvim/mark.h"
#include "nvim/extmark.h"
+#include "nvim/decoration.h"
#include "nvim/fileio.h"
#include "nvim/move.h"
#include "nvim/syntax.h"
@@ -175,8 +177,7 @@ Boolean nvim_buf_attach(uint64_t channel_id,
}
cb.on_lines = v->data.luaref;
v->data.luaref = LUA_NOREF;
- } else if (is_lua && strequal("_on_bytes", k.data)) {
- // NB: undocumented, untested and incomplete interface!
+ } else if (is_lua && strequal("on_bytes", k.data)) {
if (v->type != kObjectTypeLuaRef) {
api_set_error(err, kErrorTypeValidation, "callback is not a function");
goto error;
@@ -213,10 +214,10 @@ Boolean nvim_buf_attach(uint64_t channel_id,
error:
// TODO(bfredl): ASAN build should check that the ref table is empty?
- executor_free_luaref(cb.on_lines);
- executor_free_luaref(cb.on_bytes);
- executor_free_luaref(cb.on_changedtick);
- executor_free_luaref(cb.on_detach);
+ api_free_luaref(cb.on_lines);
+ api_free_luaref(cb.on_bytes);
+ api_free_luaref(cb.on_changedtick);
+ api_free_luaref(cb.on_detach);
return false;
}
@@ -245,78 +246,6 @@ Boolean nvim_buf_detach(uint64_t channel_id,
return true;
}
-static void buf_clear_luahl(buf_T *buf, bool force)
-{
- if (buf->b_luahl || force) {
- executor_free_luaref(buf->b_luahl_start);
- executor_free_luaref(buf->b_luahl_window);
- executor_free_luaref(buf->b_luahl_line);
- executor_free_luaref(buf->b_luahl_end);
- }
- buf->b_luahl_start = LUA_NOREF;
- buf->b_luahl_window = LUA_NOREF;
- buf->b_luahl_line = LUA_NOREF;
- buf->b_luahl_end = LUA_NOREF;
-}
-
-/// Unstabilized interface for defining syntax hl in lua.
-///
-/// This is not yet safe for general use, lua callbacks will need to
-/// be restricted, like textlock and probably other stuff.
-///
-/// The API on_line/nvim__put_attr is quite raw and not intended to be the
-/// final shape. Ideally this should operate on chunks larger than a single
-/// line to reduce interpreter overhead, and generate annotation objects
-/// (bufhl/virttext) on the fly but using the same representation.
-void nvim__buf_set_luahl(uint64_t channel_id, Buffer buffer,
- DictionaryOf(LuaRef) opts, Error *err)
- FUNC_API_LUA_ONLY
-{
- buf_T *buf = find_buffer_by_handle(buffer, err);
-
- if (!buf) {
- return;
- }
-
- redraw_buf_later(buf, NOT_VALID);
- buf_clear_luahl(buf, false);
-
- for (size_t i = 0; i < opts.size; i++) {
- String k = opts.items[i].key;
- Object *v = &opts.items[i].value;
- if (strequal("on_start", k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation, "callback is not a function");
- goto error;
- }
- buf->b_luahl_start = v->data.luaref;
- v->data.luaref = LUA_NOREF;
- } else if (strequal("on_window", k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation, "callback is not a function");
- goto error;
- }
- buf->b_luahl_window = v->data.luaref;
- v->data.luaref = LUA_NOREF;
- } else if (strequal("on_line", k.data)) {
- if (v->type != kObjectTypeLuaRef) {
- api_set_error(err, kErrorTypeValidation, "callback is not a function");
- goto error;
- }
- buf->b_luahl_line = v->data.luaref;
- v->data.luaref = LUA_NOREF;
- } else {
- api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
- goto error;
- }
- }
- buf->b_luahl = true;
- return;
-error:
- buf_clear_luahl(buf, true);
- buf->b_luahl = false;
-}
-
void nvim__buf_redraw_range(Buffer buffer, Integer first, Integer last,
Error *err)
FUNC_API_LUA_ONLY
@@ -1025,6 +954,53 @@ Boolean nvim_buf_is_loaded(Buffer buffer)
return buf && buf->b_ml.ml_mfp != NULL;
}
+/// Deletes the buffer. See |:bwipeout|
+///
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param opts Optional parameters. Keys:
+/// - force: Force deletion and ignore unsaved changes.
+/// - unload: Unloaded only, do not delete. See |:bunload|
+void nvim_buf_delete(Buffer buffer, Dictionary opts, Error *err)
+ FUNC_API_SINCE(7)
+{
+ buf_T *buf = find_buffer_by_handle(buffer, err);
+
+ if (ERROR_SET(err)) {
+ return;
+ }
+
+ bool force = false;
+ bool unload = false;
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object v = opts.items[i].value;
+ if (strequal("force", k.data)) {
+ force = api_object_to_bool(v, "force", false, err);
+ } else if (strequal("unload", k.data)) {
+ unload = api_object_to_bool(v, "unload", false, err);
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ return;
+ }
+ }
+
+ if (ERROR_SET(err)) {
+ return;
+ }
+
+ int result = do_buffer(
+ unload ? DOBUF_UNLOAD : DOBUF_WIPE,
+ DOBUF_FIRST,
+ FORWARD,
+ buf->handle,
+ force);
+
+ if (result == FAIL) {
+ api_set_error(err, kErrorTypeException, "Failed to unload buffer.");
+ return;
+ }
+}
+
/// Checks if a buffer is valid.
///
/// @note Even if a buffer is valid it may have been unloaded. See |api-buffer|
@@ -1108,15 +1084,67 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Error *err)
return rv;
}
+static Array extmark_to_array(ExtmarkInfo extmark, bool id, bool add_dict)
+{
+ Array rv = ARRAY_DICT_INIT;
+ if (id) {
+ ADD(rv, INTEGER_OBJ((Integer)extmark.mark_id));
+ }
+ ADD(rv, INTEGER_OBJ(extmark.row));
+ ADD(rv, INTEGER_OBJ(extmark.col));
+
+ if (add_dict) {
+ Dictionary dict = ARRAY_DICT_INIT;
+
+ if (extmark.end_row >= 0) {
+ PUT(dict, "end_row", INTEGER_OBJ(extmark.end_row));
+ PUT(dict, "end_col", INTEGER_OBJ(extmark.end_col));
+ }
+
+ if (extmark.decor) {
+ Decoration *decor = extmark.decor;
+ if (decor->hl_id) {
+ String name = cstr_to_string((const char *)syn_id2name(decor->hl_id));
+ PUT(dict, "hl_group", STRING_OBJ(name));
+ }
+ if (kv_size(decor->virt_text)) {
+ Array chunks = ARRAY_DICT_INIT;
+ for (size_t i = 0; i < decor->virt_text.size; i++) {
+ Array chunk = ARRAY_DICT_INIT;
+ VirtTextChunk *vtc = &decor->virt_text.items[i];
+ ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
+ if (vtc->hl_id > 0) {
+ ADD(chunk,
+ STRING_OBJ(cstr_to_string(
+ (const char *)syn_id2name(vtc->hl_id))));
+ }
+ ADD(chunks, ARRAY_OBJ(chunk));
+ }
+ PUT(dict, "virt_text", ARRAY_OBJ(chunks));
+ }
+ }
+
+ if (dict.size) {
+ ADD(rv, DICTIONARY_OBJ(dict));
+ }
+ }
+
+ return rv;
+}
+
/// Returns position for a given extmark id
///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param ns_id Namespace id from |nvim_create_namespace()|
/// @param id Extmark id
+/// @param opts Optional parameters. Keys:
+/// - limit: Maximum number of marks to return
+/// - details Whether to include the details dict
/// @param[out] err Error details, if any
/// @return (row, col) tuple or empty list () if extmark id was absent
ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
- Integer id, Error *err)
+ Integer id, Dictionary opts,
+ Error *err)
FUNC_API_SINCE(7)
{
Array rv = ARRAY_DICT_INIT;
@@ -1132,13 +1160,31 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
return rv;
}
+ bool details = false;
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ if (strequal("details", k.data)) {
+ if (v->type == kObjectTypeBoolean) {
+ details = v->data.boolean;
+ } else if (v->type == kObjectTypeInteger) {
+ details = v->data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "details is not an boolean");
+ return rv;
+ }
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ return rv;
+ }
+ }
+
+
ExtmarkInfo extmark = extmark_from_id(buf, (uint64_t)ns_id, (uint64_t)id);
if (extmark.row < 0) {
return rv;
}
- ADD(rv, INTEGER_OBJ((Integer)extmark.row));
- ADD(rv, INTEGER_OBJ((Integer)extmark.col));
- return rv;
+ return extmark_to_array(extmark, false, (bool)details);
}
/// Gets extmarks in "traversal order" from a |charwise| region defined by
@@ -1181,10 +1227,12 @@ ArrayOf(Integer) nvim_buf_get_extmark_by_id(Buffer buffer, Integer ns_id,
/// (whose position defines the bound)
/// @param opts Optional parameters. Keys:
/// - limit: Maximum number of marks to return
+/// - details Whether to include the details dict
/// @param[out] err Error details, if any
/// @return List of [extmark_id, row, col] tuples in "traversal order".
-Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
- Object end, Dictionary opts, Error *err)
+Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id,
+ Object start, Object end,
+ Dictionary opts, Error *err)
FUNC_API_SINCE(7)
{
Array rv = ARRAY_DICT_INIT;
@@ -1198,7 +1246,9 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
return rv;
}
+
Integer limit = -1;
+ bool details = false;
for (size_t i = 0; i < opts.size; i++) {
String k = opts.items[i].key;
@@ -1209,6 +1259,15 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
return rv;
}
limit = v->data.integer;
+ } else if (strequal("details", k.data)) {
+ if (v->type == kObjectTypeBoolean) {
+ details = v->data.boolean;
+ } else if (v->type == kObjectTypeInteger) {
+ details = v->data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation, "details is not an boolean");
+ return rv;
+ }
} else {
api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
return rv;
@@ -1241,16 +1300,11 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
}
- ExtmarkArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col, u_row,
- u_col, (int64_t)limit, reverse);
+ ExtmarkInfoArray marks = extmark_get(buf, (uint64_t)ns_id, l_row, l_col,
+ u_row, u_col, (int64_t)limit, reverse);
for (size_t i = 0; i < kv_size(marks); i++) {
- Array mark = ARRAY_DICT_INIT;
- ExtmarkInfo extmark = kv_A(marks, i);
- ADD(mark, INTEGER_OBJ((Integer)extmark.mark_id));
- ADD(mark, INTEGER_OBJ(extmark.row));
- ADD(mark, INTEGER_OBJ(extmark.col));
- ADD(rv, ARRAY_OBJ(mark));
+ ADD(rv, ARRAY_OBJ(extmark_to_array(kv_A(marks, i), true, (bool)details)));
}
kv_destroy(marks);
@@ -1260,27 +1314,40 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start,
/// Creates or updates an extmark.
///
/// To create a new extmark, pass id=0. The extmark id will be returned.
-// To move an existing mark, pass its id.
+/// To move an existing mark, pass its id.
///
/// It is also allowed to create a new mark by passing in a previously unused
/// id, but the caller must then keep track of existing and unused ids itself.
/// (Useful over RPC, to avoid waiting for the return value.)
///
+/// Using the optional arguments, it is possible to use this to highlight
+/// a range of text, and also to associate virtual text to the mark.
+///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param ns_id Namespace id from |nvim_create_namespace()|
-/// @param id Extmark id, or 0 to create new
/// @param line Line number where to place the mark
/// @param col Column where to place the mark
-/// @param opts Optional parameters. Currently not used.
+/// @param opts Optional parameters.
+/// - id : id of the extmark to edit.
+/// - end_line : ending line of the mark, 0-based inclusive.
+/// - end_col : ending col of the mark, 0-based inclusive.
+/// - hl_group : name of the highlight group used to highlight
+/// this mark.
+/// - virt_text : virtual text to link to this mark.
+/// - ephemeral : for use with |nvim_set_decoration_provider|
+/// callbacks. The mark will only be used for the current
+/// redraw cycle, and not be permantently stored in the
+/// buffer.
/// @param[out] err Error details, if any
/// @return Id of the created/updated extmark
-Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
+Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id,
Integer line, Integer col,
Dictionary opts, Error *err)
FUNC_API_SINCE(7)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
+ api_set_error(err, kErrorTypeValidation, "Invalid buffer id");
return 0;
}
@@ -1289,11 +1356,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
return 0;
}
- if (opts.size > 0) {
- api_set_error(err, kErrorTypeValidation, "opts dict isn't empty");
- return 0;
- }
-
size_t len = 0;
if (line < 0 || line > buf->b_ml.ml_line_count) {
api_set_error(err, kErrorTypeValidation, "line value outside range");
@@ -1309,18 +1371,138 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer id,
return 0;
}
- uint64_t id_num;
- if (id >= 0) {
- id_num = (uint64_t)id;
+ bool ephemeral = false;
+
+ uint64_t id = 0;
+ int line2 = -1, hl_id = 0;
+ colnr_T col2 = 0;
+ VirtText virt_text = KV_INITIAL_VALUE;
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ if (strequal("id", k.data)) {
+ if (v->type != kObjectTypeInteger || v->data.integer <= 0) {
+ api_set_error(err, kErrorTypeValidation,
+ "id is not a positive integer");
+ goto error;
+ }
+
+ id = (uint64_t)v->data.integer;
+ } else if (strequal("end_line", k.data)) {
+ if (v->type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation,
+ "end_line is not an integer");
+ goto error;
+ }
+ if (v->data.integer < 0 || v->data.integer > buf->b_ml.ml_line_count) {
+ api_set_error(err, kErrorTypeValidation,
+ "end_line value outside range");
+ goto error;
+ }
+
+ line2 = (int)v->data.integer;
+ } else if (strequal("end_col", k.data)) {
+ if (v->type != kObjectTypeInteger) {
+ api_set_error(err, kErrorTypeValidation,
+ "end_col is not an integer");
+ goto error;
+ }
+ if (v->data.integer < 0 || v->data.integer > MAXCOL) {
+ api_set_error(err, kErrorTypeValidation,
+ "end_col value outside range");
+ goto error;
+ }
+
+ col2 = (colnr_T)v->data.integer;
+ } else if (strequal("hl_group", k.data)) {
+ String hl_group;
+ switch (v->type) {
+ case kObjectTypeString:
+ hl_group = v->data.string;
+ hl_id = syn_check_group(
+ (char_u *)(hl_group.data),
+ (int)hl_group.size);
+ break;
+ case kObjectTypeInteger:
+ hl_id = (int)v->data.integer;
+ break;
+ default:
+ api_set_error(err, kErrorTypeValidation,
+ "hl_group is not valid.");
+ goto error;
+ }
+ } else if (strequal("virt_text", k.data)) {
+ if (v->type != kObjectTypeArray) {
+ api_set_error(err, kErrorTypeValidation,
+ "virt_text is not an Array");
+ goto error;
+ }
+ virt_text = parse_virt_text(v->data.array, err);
+ if (ERROR_SET(err)) {
+ goto error;
+ }
+ } else if (strequal("ephemeral", k.data)) {
+ ephemeral = api_object_to_bool(*v, "ephemeral", false, err);
+ if (ERROR_SET(err)) {
+ goto error;
+ }
+ } else {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ goto error;
+ }
+ }
+
+ if (col2 >= 0) {
+ if (line2 >= 0 && line2 < buf->b_ml.ml_line_count) {
+ len = STRLEN(ml_get_buf(buf, (linenr_T)line2 + 1, false));
+ } else if (line2 == buf->b_ml.ml_line_count) {
+ // We are trying to add an extmark past final newline
+ len = 0;
+ } else {
+ // reuse len from before
+ line2 = (int)line;
+ }
+ if (col2 > (Integer)len) {
+ api_set_error(err, kErrorTypeValidation, "end_col value outside range");
+ goto error;
+ }
+ } else if (line2 >= 0) {
+ col2 = 0;
+ }
+
+ // TODO(bfredl): synergize these two branches even more
+ if (ephemeral && decor_state.buf == buf) {
+ int attr_id = hl_id > 0 ? syn_id2attr(hl_id) : 0;
+ VirtText *vt_allocated = NULL;
+ if (kv_size(virt_text)) {
+ vt_allocated = xmalloc(sizeof *vt_allocated);
+ *vt_allocated = virt_text;
+ }
+ decor_add_ephemeral(attr_id, (int)line, (colnr_T)col,
+ (int)line2, (colnr_T)col2, vt_allocated);
} else {
- api_set_error(err, kErrorTypeValidation, "Invalid mark id");
- return 0;
+ if (ephemeral) {
+ api_set_error(err, kErrorTypeException, "not yet implemented");
+ goto error;
+ }
+ Decoration *decor = NULL;
+ if (kv_size(virt_text)) {
+ decor = xcalloc(1, sizeof(*decor));
+ decor->hl_id = hl_id;
+ decor->virt_text = virt_text;
+ } else if (hl_id) {
+ decor = decor_hl(hl_id);
+ }
+
+ id = extmark_set(buf, (uint64_t)ns_id, id, (int)line, (colnr_T)col,
+ line2, col2, decor, kExtmarkNoUndo);
}
- id_num = extmark_set(buf, (uint64_t)ns_id, id_num,
- (int)line, (colnr_T)col, kExtmarkUndo);
+ return (Integer)id;
- return (Integer)id_num;
+error:
+ clear_virttext(&virt_text);
+ return 0;
}
/// Removes an extmark.
@@ -1358,17 +1540,17 @@ Boolean nvim_buf_del_extmark(Buffer buffer,
/// like signs and marks do.
///
/// Namespaces are used for batch deletion/updating of a set of highlights. To
-/// create a namespace, use |nvim_create_namespace| which returns a namespace
+/// create a namespace, use |nvim_create_namespace()| which returns a namespace
/// id. Pass it in to this function as `ns_id` to add highlights to the
/// namespace. All highlights in the same namespace can then be cleared with
-/// single call to |nvim_buf_clear_namespace|. If the highlight never will be
+/// single call to |nvim_buf_clear_namespace()|. If the highlight never will be
/// deleted by an API call, pass `ns_id = -1`.
///
/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the
/// highlight, the allocated id is then returned. If `hl_group` is the empty
/// string no highlight is added, but a new `ns_id` is still returned. This is
/// supported for backwards compatibility, new code should use
-/// |nvim_create_namespace| to create a new empty namespace.
+/// |nvim_create_namespace()| to create a new empty namespace.
///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param ns_id namespace to use or -1 for ungrouped highlight
@@ -1412,9 +1594,9 @@ Integer nvim_buf_add_highlight(Buffer buffer,
return src_id;
}
- int hlg_id = 0;
+ int hl_id = 0;
if (hl_group.size > 0) {
- hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
+ hl_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
} else {
return src_id;
}
@@ -1425,10 +1607,10 @@ Integer nvim_buf_add_highlight(Buffer buffer,
end_line++;
}
- extmark_add_decoration(buf, ns_id, hlg_id,
- (int)line, (colnr_T)col_start,
- end_line, (colnr_T)col_end,
- VIRTTEXT_EMPTY);
+ extmark_set(buf, ns_id, 0,
+ (int)line, (colnr_T)col_start,
+ end_line, (colnr_T)col_end,
+ decor_hl(hl_id), kExtmarkNoUndo);
return src_id;
}
@@ -1470,7 +1652,7 @@ void nvim_buf_clear_namespace(Buffer buffer,
/// Clears highlights and virtual text from namespace and range of lines
///
-/// @deprecated use |nvim_buf_clear_namespace|.
+/// @deprecated use |nvim_buf_clear_namespace()|.
///
/// @param buffer Buffer handle, or 0 for current buffer
/// @param ns_id Namespace to clear, or -1 to clear all.
@@ -1488,43 +1670,6 @@ void nvim_buf_clear_highlight(Buffer buffer,
nvim_buf_clear_namespace(buffer, ns_id, line_start, line_end, err);
}
-static VirtText parse_virt_text(Array chunks, Error *err)
-{
- VirtText virt_text = KV_INITIAL_VALUE;
- for (size_t i = 0; i < chunks.size; i++) {
- if (chunks.items[i].type != kObjectTypeArray) {
- api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
- goto free_exit;
- }
- Array chunk = chunks.items[i].data.array;
- if (chunk.size == 0 || chunk.size > 2
- || chunk.items[0].type != kObjectTypeString
- || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) {
- api_set_error(err, kErrorTypeValidation,
- "Chunk is not an array with one or two strings");
- goto free_exit;
- }
-
- String str = chunk.items[0].data.string;
- char *text = transstr(str.size > 0 ? str.data : ""); // allocates
-
- int hl_id = 0;
- if (chunk.size == 2) {
- String hl = chunk.items[1].data.string;
- if (hl.size > 0) {
- hl_id = syn_check_group((char_u *)hl.data, (int)hl.size);
- }
- }
- kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
- }
-
- return virt_text;
-
-free_exit:
- clear_virttext(&virt_text);
- return virt_text;
-}
-
/// Set the virtual text (annotation) for a buffer line.
///
@@ -1534,11 +1679,11 @@ free_exit:
/// begin one cell (|lcs-eol| or space) after the ordinary text.
///
/// Namespaces are used to support batch deletion/updating of virtual text.
-/// To create a namespace, use |nvim_create_namespace|. Virtual text is
-/// cleared using |nvim_buf_clear_namespace|. The same `ns_id` can be used for
-/// both virtual text and highlights added by |nvim_buf_add_highlight|, both
-/// can then be cleared with a single call to |nvim_buf_clear_namespace|. If the
-/// virtual text never will be cleared by an API call, pass `ns_id = -1`.
+/// To create a namespace, use |nvim_create_namespace()|. Virtual text is
+/// cleared using |nvim_buf_clear_namespace()|. The same `ns_id` can be used for
+/// both virtual text and highlights added by |nvim_buf_add_highlight()|, both
+/// can then be cleared with a single call to |nvim_buf_clear_namespace()|. If
+/// the virtual text never will be cleared by an API call, pass `ns_id = -1`.
///
/// As a shorthand, `ns_id = 0` can be used to create a new namespace for the
/// virtual text, the allocated id is then returned.
@@ -1584,7 +1729,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer,
}
- VirtText *existing = extmark_find_virttext(buf, (int)line, ns_id);
+ VirtText *existing = decor_find_virttext(buf, (int)line, ns_id);
if (existing) {
clear_virttext(existing);
@@ -1592,113 +1737,49 @@ Integer nvim_buf_set_virtual_text(Buffer buffer,
return src_id;
}
- extmark_add_decoration(buf, ns_id, 0,
- (int)line, 0, -1, -1,
- virt_text);
+ Decoration *decor = xcalloc(1, sizeof(*decor));
+ decor->virt_text = virt_text;
+
+ extmark_set(buf, ns_id, 0, (int)line, 0, -1, -1, decor, kExtmarkNoUndo);
return src_id;
}
-/// Get the virtual text (annotation) for a buffer line.
-///
-/// The virtual text is returned as list of lists, whereas the inner lists have
-/// either one or two elements. The first element is the actual text, the
-/// optional second element is the highlight group.
+/// call a function with buffer as temporary current buffer
///
-/// The format is exactly the same as given to nvim_buf_set_virtual_text().
+/// This temporarily switches current buffer to "buffer".
+/// If the current window already shows "buffer", the window is not switched
+/// If a window inside the current tabpage (including a float) already shows the
+/// buffer One of these windows will be set as current window temporarily.
+/// Otherwise a temporary scratch window (calleed the "autocmd window" for
+/// historical reasons) will be used.
///
-/// If there is no virtual text associated with the given line, an empty list
-/// is returned.
+/// This is useful e.g. to call vimL functions that only work with the current
+/// buffer/window currently, like |termopen()|.
///
-/// @param buffer Buffer handle, or 0 for current buffer
-/// @param line Line to get the virtual text from (zero-indexed)
-/// @param[out] err Error details, if any
-/// @return List of virtual text chunks
-Array nvim_buf_get_virtual_text(Buffer buffer, Integer line, Error *err)
+/// @param buffer Buffer handle, or 0 for current buffer
+/// @param fun Function to call inside the buffer (currently lua callable
+/// only)
+/// @param[out] err Error details, if any
+/// @return Return value of function. NB: will deepcopy lua values
+/// currently, use upvalues to send lua references in and out.
+Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err)
FUNC_API_SINCE(7)
-{
- Array chunks = ARRAY_DICT_INIT;
-
- buf_T *buf = find_buffer_by_handle(buffer, err);
- if (!buf) {
- return chunks;
- }
-
- if (line < 0 || line >= MAXLNUM) {
- api_set_error(err, kErrorTypeValidation, "Line number outside range");
- return chunks;
- }
-
- VirtText *virt_text = extmark_find_virttext(buf, (int)line, 0);
-
- if (!virt_text) {
- return chunks;
- }
-
- for (size_t i = 0; i < virt_text->size; i++) {
- Array chunk = ARRAY_DICT_INIT;
- VirtTextChunk *vtc = &virt_text->items[i];
- ADD(chunk, STRING_OBJ(cstr_to_string(vtc->text)));
- if (vtc->hl_id > 0) {
- ADD(chunk, STRING_OBJ(cstr_to_string(
- (const char *)syn_id2name(vtc->hl_id))));
- }
- ADD(chunks, ARRAY_OBJ(chunk));
- }
-
- return chunks;
-}
-
-Integer nvim__buf_add_decoration(Buffer buffer, Integer ns_id, String hl_group,
- Integer start_row, Integer start_col,
- Integer end_row, Integer end_col,
- Array virt_text,
- Error *err)
+ FUNC_API_LUA_ONLY
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
- return 0;
- }
-
- if (!ns_initialized((uint64_t)ns_id)) {
- api_set_error(err, kErrorTypeValidation, "Invalid ns_id");
- return 0;
- }
-
-
- if (start_row < 0 || start_row >= MAXLNUM || end_row > MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "Line number outside range");
- return 0;
- }
-
- if (start_col < 0 || start_col > MAXCOL || end_col > MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "Column value outside range");
- return 0;
- }
- if (end_row < 0 || end_col < 0) {
- end_row = -1;
- end_col = -1;
- }
-
- if (start_row >= buf->b_ml.ml_line_count
- || end_row >= buf->b_ml.ml_line_count) {
- // safety check, we can't add marks outside the range
- return 0;
- }
-
- int hlg_id = 0;
- if (hl_group.size > 0) {
- hlg_id = syn_check_group((char_u *)hl_group.data, (int)hl_group.size);
+ return NIL;
}
+ try_start();
+ aco_save_T aco;
+ aucmd_prepbuf(&aco, (buf_T *)buf);
- VirtText vt = parse_virt_text(virt_text, err);
- if (ERROR_SET(err)) {
- return 0;
- }
+ Array args = ARRAY_DICT_INIT;
+ Object res = nlua_call_ref(fun, NULL, args, true, err);
- uint64_t mark_id = extmark_add_decoration(buf, (uint64_t)ns_id, hlg_id,
- (int)start_row, (colnr_T)start_col,
- (int)end_row, (colnr_T)end_col, vt);
- return (Integer)mark_id;
+ aucmd_restbuf(&aco);
+ try_end(err);
+ return res;
}
Dictionary nvim__buf_stats(Buffer buffer, Error *err)
@@ -1721,6 +1802,7 @@ Dictionary nvim__buf_stats(Buffer buffer, Error *err)
// NB: this should be zero at any time API functions are called,
// this exists to debug issues
PUT(rv, "dirty_bytes", INTEGER_OBJ((Integer)buf->deleted_bytes));
+ PUT(rv, "dirty_bytes2", INTEGER_OBJ((Integer)buf->deleted_bytes2));
u_header_T *uhp = NULL;
if (buf->b_u_curhead != NULL) {
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index f194b6b474..2c99d3426c 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -15,6 +15,8 @@
#include "nvim/lua/executor.h"
#include "nvim/ascii.h"
#include "nvim/assert.h"
+#include "nvim/charset.h"
+#include "nvim/syntax.h"
#include "nvim/vim.h"
#include "nvim/buffer.h"
#include "nvim/window.h"
@@ -25,6 +27,7 @@
#include "nvim/map_defs.h"
#include "nvim/map.h"
#include "nvim/extmark.h"
+#include "nvim/decoration.h"
#include "nvim/option.h"
#include "nvim/option_defs.h"
#include "nvim/version.h"
@@ -1198,7 +1201,7 @@ void api_free_object(Object value)
break;
case kObjectTypeLuaRef:
- executor_free_luaref(value.data.luaref);
+ api_free_luaref(value.data.luaref);
break;
default:
@@ -1579,3 +1582,100 @@ bool extmark_get_index_from_obj(buf_T *buf, Integer ns_id, Object obj, int
return false;
}
}
+
+VirtText parse_virt_text(Array chunks, Error *err)
+{
+ VirtText virt_text = KV_INITIAL_VALUE;
+ for (size_t i = 0; i < chunks.size; i++) {
+ if (chunks.items[i].type != kObjectTypeArray) {
+ api_set_error(err, kErrorTypeValidation, "Chunk is not an array");
+ goto free_exit;
+ }
+ Array chunk = chunks.items[i].data.array;
+ if (chunk.size == 0 || chunk.size > 2
+ || chunk.items[0].type != kObjectTypeString
+ || (chunk.size == 2 && chunk.items[1].type != kObjectTypeString)) {
+ api_set_error(err, kErrorTypeValidation,
+ "Chunk is not an array with one or two strings");
+ goto free_exit;
+ }
+
+ String str = chunk.items[0].data.string;
+ char *text = transstr(str.size > 0 ? str.data : ""); // allocates
+
+ int hl_id = 0;
+ if (chunk.size == 2) {
+ String hl = chunk.items[1].data.string;
+ if (hl.size > 0) {
+ hl_id = syn_check_group((char_u *)hl.data, (int)hl.size);
+ }
+ }
+ kv_push(virt_text, ((VirtTextChunk){ .text = text, .hl_id = hl_id }));
+ }
+
+ return virt_text;
+
+free_exit:
+ clear_virttext(&virt_text);
+ return virt_text;
+}
+
+/// Force obj to bool.
+/// If it fails, returns false and sets err
+/// @param obj The object to coerce to a boolean
+/// @param what The name of the object, used for error message
+/// @param nil_value What to return if the type is nil.
+/// @param err Set if there was an error in converting to a bool
+bool api_object_to_bool(Object obj, const char *what,
+ bool nil_value, Error *err)
+{
+ if (obj.type == kObjectTypeBoolean) {
+ return obj.data.boolean;
+ } else if (obj.type == kObjectTypeInteger) {
+ return obj.data.integer; // C semantics: non-zero int is true
+ } else if (obj.type == kObjectTypeNil) {
+ return nil_value; // caller decides what NIL (missing retval in lua) means
+ } else {
+ api_set_error(err, kErrorTypeValidation, "%s is not an boolean", what);
+ return false;
+ }
+}
+
+const char *describe_ns(NS ns_id)
+{
+ String name;
+ handle_T id;
+ map_foreach(namespace_ids, name, id, {
+ if ((NS)id == ns_id && name.size) {
+ return name.data;
+ }
+ })
+ return "(UNKNOWN PLUGIN)";
+}
+
+DecorProvider *get_provider(NS ns_id, bool force)
+{
+ ssize_t i;
+ for (i = 0; i < (ssize_t)kv_size(decor_providers); i++) {
+ DecorProvider *item = &kv_A(decor_providers, i);
+ if (item->ns_id == ns_id) {
+ return item;
+ } else if (item->ns_id > ns_id) {
+ break;
+ }
+ }
+
+ if (!force) {
+ return NULL;
+ }
+
+ for (ssize_t j = (ssize_t)kv_size(decor_providers)-1; j >= i; j++) {
+ // allocates if needed:
+ (void)kv_a(decor_providers, (size_t)j+1);
+ kv_A(decor_providers, (size_t)j+1) = kv_A(decor_providers, j);
+ }
+ DecorProvider *item = &kv_a(decor_providers, (size_t)i);
+ *item = DECORATION_PROVIDER_INIT(ns_id);
+
+ return item;
+}
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index df3a263dcf..271fd5b485 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -7,6 +7,7 @@
#include "nvim/vim.h"
#include "nvim/getchar.h"
#include "nvim/memory.h"
+#include "nvim/decoration.h"
#include "nvim/ex_eval.h"
#include "nvim/lib/kvec.h"
@@ -52,7 +53,8 @@
.type = kObjectTypeLuaRef, \
.data.luaref = r })
-#define NIL ((Object) {.type = kObjectTypeNil})
+#define NIL ((Object)OBJECT_INIT)
+#define NULL_STRING ((String)STRING_INIT)
#define PUT(dict, k, v) \
kv_push(dict, ((KeyValuePair) { .key = cstr_to_string(k), .value = v }))
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index 717713b948..51f1af4eb5 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -355,7 +355,7 @@ void nvim_ui_pum_set_height(uint64_t channel_id, Integer height, Error *err)
/// Note that this method is not to be confused with |nvim_ui_pum_set_height()|,
/// which sets the number of visible items in the popup menu, while this
/// function sets the bounding box of the popup menu, including visual
-/// decorations such as boarders and sliders. Floats need not use the same font
+/// elements such as borders and sliders. Floats need not use the same font
/// size, nor be anchored to exact grid corners, so one can set floating-point
/// numbers to the popup menu geometry.
///
diff --git a/src/nvim/api/ui_events.in.h b/src/nvim/api/ui_events.in.h
index ab31db39e9..e934d5dc92 100644
--- a/src/nvim/api/ui_events.in.h
+++ b/src/nvim/api/ui_events.in.h
@@ -1,7 +1,7 @@
#ifndef NVIM_API_UI_EVENTS_IN_H
#define NVIM_API_UI_EVENTS_IN_H
-// This file is not compiled, just parsed for definitons
+// This file is not compiled, just parsed for definitions
#ifdef INCLUDE_GENERATED_DECLARATIONS
# error "don't include this file, include nvim/ui.h"
#endif
@@ -36,13 +36,15 @@ void set_title(String title)
FUNC_API_SINCE(3);
void set_icon(String icon)
FUNC_API_SINCE(3);
+void screenshot(String path)
+ FUNC_API_SINCE(7) FUNC_API_REMOTE_IMPL;
void option_set(String name, Object value)
FUNC_API_SINCE(4) FUNC_API_BRIDGE_IMPL;
// Stop event is not exported as such, represented by EOF in the msgpack stream.
void stop(void)
FUNC_API_NOEXPORT;
-// First revison of the grid protocol, used by default
+// First revision of the grid protocol, used by default
void update_fg(Integer fg)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
void update_bg(Integer bg)
@@ -66,7 +68,7 @@ void set_scroll_region(Integer top, Integer bot, Integer left, Integer right)
void scroll(Integer count)
FUNC_API_SINCE(3) FUNC_API_REMOTE_ONLY;
-// Second revison of the grid protocol, used with ext_linegrid ui option
+// Second revision of the grid protocol, used with ext_linegrid ui option
void default_colors_set(Integer rgb_fg, Integer rgb_bg, Integer rgb_sp,
Integer cterm_fg, Integer cterm_bg)
FUNC_API_SINCE(4) FUNC_API_REMOTE_IMPL;
@@ -89,7 +91,7 @@ void grid_scroll(Integer grid, Integer top, Integer bot,
void grid_destroy(Integer grid)
FUNC_API_SINCE(6) FUNC_API_REMOTE_ONLY;
-// For perfomance and simplicity, we use the dense screen representation
+// For performance and simplicity, we use the dense screen representation
// in internal code, such as compositor and TUI. The remote_ui module will
// translate this in to the public grid_line format.
void raw_line(Integer grid, Integer row, Integer startcol,
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index d6f95c7a5f..cf822782d8 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -41,7 +41,7 @@
#include "nvim/ops.h"
#include "nvim/option.h"
#include "nvim/state.h"
-#include "nvim/extmark.h"
+#include "nvim/decoration.h"
#include "nvim/syntax.h"
#include "nvim/getchar.h"
#include "nvim/os/input.h"
@@ -199,14 +199,77 @@ Integer nvim_get_hl_id_by_name(String name)
return syn_check_group((const char_u *)name.data, (int)name.size);
}
+Dictionary nvim__get_hl_defs(Integer ns_id, Error *err)
+{
+ if (ns_id == 0) {
+ return get_global_hl_defs();
+ }
+ abort();
+}
+
+/// Set a highlight group.
+///
+/// @param ns_id number of namespace for this highlight
+/// @param name highlight group name, like ErrorMsg
+/// @param val highlight definiton map, like |nvim_get_hl_by_name|.
+/// @param[out] err Error details, if any
+///
+/// TODO: ns_id = 0, should modify :highlight namespace
+/// TODO val should take update vs reset flag
+void nvim_set_hl(Integer ns_id, String name, Dictionary val, Error *err)
+ FUNC_API_SINCE(7)
+{
+ int hl_id = syn_check_group( (char_u *)(name.data), (int)name.size);
+ int link_id = -1;
+
+ HlAttrs attrs = dict2hlattrs(val, true, &link_id, err);
+ if (!ERROR_SET(err)) {
+ ns_hl_def((NS)ns_id, hl_id, attrs, link_id);
+ }
+}
+
+/// Set active namespace for highlights.
+///
+/// NB: this function can be called from async contexts, but the
+/// semantics are not yet well-defined. To start with
+/// |nvim_set_decoration_provider| on_win and on_line callbacks
+/// are explicitly allowed to change the namespace during a redraw cycle.
+///
+/// @param ns_id the namespace to activate
+/// @param[out] err Error details, if any
+void nvim_set_hl_ns(Integer ns_id, Error *err)
+ FUNC_API_SINCE(7)
+ FUNC_API_FAST
+{
+ if (ns_id >= 0) {
+ ns_hl_active = (NS)ns_id;
+ }
+
+ // TODO(bfredl): this is a little bit hackish. Eventually we want a standard
+ // event path for redraws caused by "fast" events. This could tie in with
+ // better throttling of async events causing redraws, such as non-batched
+ // nvim_buf_set_extmark calls from async contexts.
+ if (!updating_screen && !ns_hl_changed) {
+ multiqueue_put(main_loop.events, on_redraw_event, 0);
+ }
+ ns_hl_changed = true;
+}
+
+static void on_redraw_event(void **argv)
+ FUNC_API_NOEXPORT
+{
+ redraw_all_later(NOT_VALID);
+}
+
+
/// Sends input-keys to Nvim, subject to various quirks controlled by `mode`
/// flags. This is a blocking call, unlike |nvim_input()|.
///
/// On execution error: does not fail, but updates v:errmsg.
-//
-// If you need to input sequences like <C-o> use nvim_replace_termcodes
-// to replace the termcodes and then pass the resulting string to
-// nvim_feedkeys. You'll also want to enable escape_csi.
+///
+/// If you need to input sequences like <C-o> use |nvim_replace_termcodes| to
+/// replace the termcodes and then pass the resulting string to nvim_feedkeys.
+/// You'll also want to enable escape_csi.
///
/// Example:
/// <pre>
@@ -475,7 +538,7 @@ Object nvim_execute_lua(String code, Array args, Error *err)
FUNC_API_DEPRECATED_SINCE(7)
FUNC_API_REMOTE_ONLY
{
- return executor_exec_lua_api(code, args, err);
+ return nlua_exec(code, args, err);
}
/// Execute Lua code. Parameters (if any) are available as `...` inside the
@@ -494,7 +557,7 @@ Object nvim_exec_lua(String code, Array args, Error *err)
FUNC_API_SINCE(7)
FUNC_API_REMOTE_ONLY
{
- return executor_exec_lua_api(code, args, err);
+ return nlua_exec(code, args, err);
}
/// Calls a VimL function.
@@ -678,7 +741,11 @@ Integer nvim_strwidth(String text, Error *err)
ArrayOf(String) nvim_list_runtime_paths(void)
FUNC_API_SINCE(1)
{
+ // TODO(bfredl): this should just work:
+ // return nvim_get_runtime_file(NULL_STRING, true);
+
Array rv = ARRAY_DICT_INIT;
+
char_u *rtp = p_rtp;
if (*rtp == NUL) {
@@ -725,22 +792,30 @@ ArrayOf(String) nvim_list_runtime_paths(void)
/// @param name pattern of files to search for
/// @param all whether to return all matches or only the first
/// @return list of absolute paths to the found files
-ArrayOf(String) nvim_get_runtime_file(String name, Boolean all)
+ArrayOf(String) nvim_get_runtime_file(String name, Boolean all, Error *err)
FUNC_API_SINCE(7)
+ FUNC_API_FAST
{
Array rv = ARRAY_DICT_INIT;
- if (!name.data) {
+
+ // TODO(bfredl):
+ if (name.size == 0) {
+ api_set_error(err, kErrorTypeValidation, "not yet implemented");
return rv;
}
+
int flags = DIP_START | (all ? DIP_ALL : 0);
- do_in_runtimepath((char_u *)name.data, flags, find_runtime_cb, &rv);
+ do_in_runtimepath((char_u *)name.data,
+ flags, find_runtime_cb, &rv);
return rv;
}
static void find_runtime_cb(char_u *fname, void *cookie)
{
Array *rv = (Array *)cookie;
- ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname)));
+ if (fname != NULL) {
+ ADD(*rv, STRING_OBJ(cstr_to_string((char *)fname)));
+ }
}
String nvim__get_lib_dir(void)
@@ -1477,7 +1552,7 @@ void nvim_unsubscribe(uint64_t channel_id, String event)
Integer nvim_get_color_by_name(String name)
FUNC_API_SINCE(1)
{
- return name_to_color((char_u *)name.data);
+ return name_to_color(name.data);
}
/// Returns a map of color names and RGB values.
@@ -2477,7 +2552,7 @@ Array nvim_get_proc_children(Integer pid, Error *err)
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_exec_lua(s, a, err);
+ Object o = nlua_exec(s, a, err);
api_free_string(s);
api_free_array(a);
if (o.type == kObjectTypeArray) {
@@ -2523,7 +2598,7 @@ Object nvim_get_proc(Integer pid, Error *err)
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_exec_lua(s, a, err);
+ Object o = nlua_exec(s, a, err);
api_free_string(s);
api_free_array(a);
if (o.type == kObjectTypeArray && o.data.array.size == 0) {
@@ -2604,26 +2679,112 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Error *err)
return ret;
}
-/// Set attrs in nvim__buf_set_lua_hl callbacks
-///
-/// TODO(bfredl): This is rather pedestrian. The final
-/// interface should probably be derived from a reformed
-/// bufhl/virttext interface with full support for multi-line
-/// ranges etc
-void nvim__put_attr(Integer id, Integer start_row, Integer start_col,
- Integer end_row, Integer end_col)
- FUNC_API_LUA_ONLY
+void nvim__screenshot(String path)
+ FUNC_API_FAST
{
- if (!lua_attr_active) {
- return;
- }
- if (id == 0 || syn_get_final_id((int)id) == 0) {
+ ui_call_screenshot(path);
+}
+
+static void clear_provider(DecorProvider *p)
+{
+ if (p == NULL) {
return;
}
- int attr = syn_id2attr((int)id);
- if (attr == 0) {
- return;
+ NLUA_CLEAR_REF(p->redraw_start);
+ NLUA_CLEAR_REF(p->redraw_buf);
+ NLUA_CLEAR_REF(p->redraw_win);
+ NLUA_CLEAR_REF(p->redraw_line);
+ NLUA_CLEAR_REF(p->redraw_end);
+ p->active = false;
+}
+
+/// Set or change decoration provider for a namespace
+///
+/// This is a very general purpose interface for having lua callbacks
+/// being triggered during the redraw code.
+///
+/// The expected usage is to set extmarks for the currently
+/// redrawn buffer. |nvim_buf_set_extmark| can be called to add marks
+/// on a per-window or per-lines basis. Use the `ephemeral` key to only
+/// use the mark for the current screen redraw (the callback will be called
+/// again for the next redraw ).
+///
+/// Note: this function should not be called often. Rather, the callbacks
+/// themselves can be used to throttle unneeded callbacks. the `on_start`
+/// callback can return `false` to disable the provider until the next redraw.
+/// Similarily, return `false` in `on_win` will skip the `on_lines` calls
+/// for that window (but any extmarks set in `on_win` will still be used).
+/// A plugin managing multiple sources of decoration should ideally only set
+/// one provider, and merge the sources internally. You can use multiple `ns_id`
+/// for the extmarks set/modified inside the callback anyway.
+///
+/// Note: doing anything other than setting extmarks is considered experimental.
+/// Doing things like changing options are not expliticly forbidden, but is
+/// likely to have unexpected consequences (such as 100% CPU consumption).
+/// doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is quite dubious
+/// for the moment.
+///
+/// @param ns_id Namespace id from |nvim_create_namespace()|
+/// @param opts Callbacks invoked during redraw:
+/// - on_start: called first on each screen redraw
+/// ["start", tick]
+/// - on_buf: called for each buffer being redrawn (before window
+/// callbacks)
+/// ["buf", bufnr, tick]
+/// - on_win: called when starting to redraw a specific window.
+/// ["win", winid, bufnr, topline, botline_guess]
+/// - on_line: called for each buffer line being redrawn. (The
+/// interation with fold lines is subject to change)
+/// ["win", winid, bufnr, row]
+/// - on_end: called at the end of a redraw cycle
+/// ["end", tick]
+void nvim_set_decoration_provider(Integer ns_id, DictionaryOf(LuaRef) opts,
+ Error *err)
+ FUNC_API_SINCE(7) FUNC_API_LUA_ONLY
+{
+ DecorProvider *p = get_provider((NS)ns_id, true);
+ clear_provider(p);
+
+ // regardless of what happens, it seems good idea to redraw
+ redraw_all_later(NOT_VALID); // TODO(bfredl): too soon?
+
+ struct {
+ const char *name;
+ LuaRef *dest;
+ } cbs[] = {
+ { "on_start", &p->redraw_start },
+ { "on_buf", &p->redraw_buf },
+ { "on_win", &p->redraw_win },
+ { "on_line", &p->redraw_line },
+ { "on_end", &p->redraw_end },
+ { "_on_hl_def", &p->hl_def },
+ { NULL, NULL },
+ };
+
+ for (size_t i = 0; i < opts.size; i++) {
+ String k = opts.items[i].key;
+ Object *v = &opts.items[i].value;
+ size_t j;
+ for (j = 0; cbs[j].name; j++) {
+ if (strequal(cbs[j].name, k.data)) {
+ if (v->type != kObjectTypeLuaRef) {
+ api_set_error(err, kErrorTypeValidation,
+ "%s is not a function", cbs[j].name);
+ goto error;
+ }
+ *(cbs[j].dest) = v->data.luaref;
+ v->data.luaref = LUA_NOREF;
+ break;
+ }
+ }
+ if (!cbs[j].name) {
+ api_set_error(err, kErrorTypeValidation, "unexpected key: %s", k.data);
+ goto error;
+ }
}
- decorations_add_luahl_attr(attr, (int)start_row, (colnr_T)start_col,
- (int)end_row, (colnr_T)end_col);
+
+ p->active = true;
+ return;
+error:
+ clear_provider(p);
}
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index ba43bc6cb2..f09a03f592 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -142,7 +142,7 @@ void nvim_win_set_cursor(Window window, ArrayOf(Integer, 2) pos, Error *err)
// make sure cursor is in visible range even if win != curwin
update_topline_win(win);
- redraw_win_later(win, VALID);
+ redraw_later(win, VALID);
}
/// Gets the window height
@@ -471,7 +471,7 @@ void nvim_win_set_config(Window window, Dictionary config, Error *err)
if (!win_new_float(win, fconfig, err)) {
return;
}
- redraw_later(NOT_VALID);
+ redraw_later(win, NOT_VALID);
} else {
win_config_float(win, fconfig);
win->w_pos_changed = true;
diff --git a/src/nvim/ascii.h b/src/nvim/ascii.h
index 31423e79af..2397af27cc 100644
--- a/src/nvim/ascii.h
+++ b/src/nvim/ascii.h
@@ -31,9 +31,7 @@
#define CSI 0x9b // Control Sequence Introducer
#define CSI_STR "\233"
#define DCS 0x90 // Device Control String
-#define DCS_STR "\033P"
#define STERM 0x9c // String Terminator
-#define STERM_STR "\033\\"
#define POUND 0xA3
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index 4391d997a7..6be51c504c 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -7,6 +7,7 @@ return {
'BufFilePre', -- before renaming a buffer
'BufHidden', -- just after buffer becomes hidden
'BufLeave', -- before leaving a buffer
+ 'BufModifiedSet', -- after the 'modified' state of a buffer changes
'BufNew', -- after creating any buffer
'BufNewFile', -- when creating a buffer for a new file
'BufReadCmd', -- read buffer using command
@@ -65,7 +66,8 @@ return {
'InsertChange', -- when changing Insert/Replace mode
'InsertCharPre', -- before inserting a char
'InsertEnter', -- when entering Insert mode
- 'InsertLeave', -- when leaving Insert mode
+ 'InsertLeave', -- just after leaving Insert mode
+ 'InsertLeavePre', -- just before leaving Insert mode
'MenuPopup', -- just before popup menu is displayed
'OptionSet', -- after setting any option
'QuickFixCmdPost', -- after :make, :grep etc.
@@ -112,6 +114,7 @@ return {
'WinEnter', -- after entering a window
'WinLeave', -- before leaving a window
'WinNew', -- when entering a new window
+ 'WinScrolled', -- after scrolling a window
},
aliases = {
BufCreate = 'BufAdd',
@@ -122,6 +125,7 @@ return {
-- List of nvim-specific events or aliases for the purpose of generating
-- syntax file
nvim_specific = {
+ BufModifiedSet=true,
DirChanged=true,
Signal=true,
TabClosed=true,
@@ -132,5 +136,6 @@ return {
UIEnter=true,
UILeave=true,
WinClosed=true,
+ WinScrolled=true,
},
}
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 86067aceac..0ebe33f2f8 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -220,13 +220,8 @@ int open_buffer(
int perm;
perm = os_getperm((const char *)curbuf->b_ffname);
- if (perm >= 0 && (0
-# ifdef S_ISFIFO
- || S_ISFIFO(perm)
-# endif
-# ifdef S_ISSOCK
+ if (perm >= 0 && (0 || S_ISFIFO(perm)
|| S_ISSOCK(perm)
-# endif
# ifdef OPEN_CHR_FILES
|| (S_ISCHR(perm)
&& is_dev_fd_file(curbuf->b_ffname))
@@ -837,7 +832,7 @@ static void clear_wininfo(buf_T *buf)
buf->b_wininfo = wip->wi_next;
if (wip->wi_optset) {
clear_winopt(&wip->wi_opt);
- deleteFoldRecurse(&wip->wi_folds);
+ deleteFoldRecurse(buf, &wip->wi_folds);
}
xfree(wip);
}
@@ -1623,7 +1618,7 @@ void enter_buffer(buf_T *buf)
}
curbuf->b_last_used = time(NULL);
- redraw_later(NOT_VALID);
+ redraw_later(curwin, NOT_VALID);
}
// Change to the directory of the current buffer.
@@ -1941,6 +1936,7 @@ void free_buf_options(buf_T *buf, int free_p_ff)
vim_regfree(buf->b_s.b_cap_prog);
buf->b_s.b_cap_prog = NULL;
clear_string_option(&buf->b_s.b_p_spl);
+ clear_string_option(&buf->b_s.b_p_spo);
clear_string_option(&buf->b_p_sua);
clear_string_option(&buf->b_p_ft);
clear_string_option(&buf->b_p_cink);
@@ -2502,7 +2498,7 @@ void buflist_setfpos(buf_T *const buf, win_T *const win,
}
if (copy_options && wip->wi_optset) {
clear_winopt(&wip->wi_opt);
- deleteFoldRecurse(&wip->wi_folds);
+ deleteFoldRecurse(buf, &wip->wi_folds);
}
}
if (lnum != 0) {
@@ -2650,7 +2646,7 @@ void buflist_list(exarg_T *eap)
int i;
garray_T buflist;
- buf_T **buflist_data = NULL, **p;
+ buf_T **buflist_data = NULL;
if (vim_strchr(eap->arg, 't')) {
ga_init(&buflist, sizeof(buf_T *), 50);
@@ -2662,13 +2658,14 @@ void buflist_list(exarg_T *eap)
qsort(buflist.ga_data, (size_t)buflist.ga_len,
sizeof(buf_T *), buf_time_compare);
- p = buflist_data = (buf_T **)buflist.ga_data;
- buf = *p;
+ buflist_data = (buf_T **)buflist.ga_data;
+ buf = *buflist_data;
}
+ buf_T **p = buflist_data;
for (;
buf != NULL && !got_int;
- buf = buflist_data
+ buf = buflist_data != NULL
? (++p < buflist_data + buflist.ga_len ? *p : NULL)
: buf->b_next) {
const bool is_terminal = buf->terminal;
@@ -3438,31 +3435,17 @@ int build_stl_str_hl(
int use_sandbox,
char_u fillchar,
int maxwidth,
- struct stl_hlrec *hltab,
- StlClickRecord *tabtab
+ stl_hlrec_t **hltab,
+ StlClickRecord **tabtab
)
{
- int groupitems[STL_MAX_ITEM];
- struct stl_item {
- // Where the item starts in the status line output buffer
- char_u *start;
- // Function to run for ClickFunc items.
- char *cmd;
- // The minimum width of the item
- int minwid;
- // The maximum width of the item
- int maxwid;
- enum {
- Normal,
- Empty,
- Group,
- Separate,
- Highlight,
- TabPage,
- ClickFunc,
- Trunc
- } type;
- } items[STL_MAX_ITEM];
+ static size_t stl_items_len = 20; // Initial value, grows as needed.
+ static stl_item_t *stl_items = NULL;
+ static int *stl_groupitems = NULL;
+ static stl_hlrec_t *stl_hltab = NULL;
+ static StlClickRecord *stl_tabtab = NULL;
+ static int *stl_separator_locations = NULL;
+
#define TMPLEN 70
char_u buf_tmp[TMPLEN];
char_u win_tmp[TMPLEN];
@@ -3470,6 +3453,14 @@ int build_stl_str_hl(
const int save_must_redraw = must_redraw;
const int save_redr_type = curwin->w_redr_type;
+ if (stl_items == NULL) {
+ stl_items = xmalloc(sizeof(stl_item_t) * stl_items_len);
+ stl_groupitems = xmalloc(sizeof(int) * stl_items_len);
+ stl_hltab = xmalloc(sizeof(stl_hlrec_t) * stl_items_len);
+ stl_tabtab = xmalloc(sizeof(stl_hlrec_t) * stl_items_len);
+ stl_separator_locations = xmalloc(sizeof(int) * stl_items_len);
+ }
+
// When the format starts with "%!" then evaluate it as an expression and
// use the result as the actual format string.
if (fmt[0] == '%' && fmt[1] == '!') {
@@ -3538,14 +3529,17 @@ int build_stl_str_hl(
// Proceed character by character through the statusline format string
// fmt_p is the current positon in the input buffer
for (char_u *fmt_p = usefmt; *fmt_p; ) {
- if (curitem == STL_MAX_ITEM) {
- // There are too many items. Add the error code to the statusline
- // to give the user a hint about what went wrong.
- if (out_p + 5 < out_end_p) {
- memmove(out_p, " E541", (size_t)5);
- out_p += 5;
- }
- break;
+ if (curitem == (int)stl_items_len) {
+ size_t new_len = stl_items_len * 3 / 2;
+
+ stl_items = xrealloc(stl_items, sizeof(stl_item_t) * new_len);
+ stl_groupitems = xrealloc(stl_groupitems, sizeof(int) * new_len);
+ stl_hltab = xrealloc(stl_hltab, sizeof(stl_hlrec_t) * new_len);
+ stl_tabtab = xrealloc(stl_tabtab, sizeof(StlClickRecord) * new_len);
+ stl_separator_locations =
+ xrealloc(stl_separator_locations, sizeof(int) * new_len);
+
+ stl_items_len = new_len;
}
if (*fmt_p != NUL && *fmt_p != '%') {
@@ -3589,16 +3583,16 @@ int build_stl_str_hl(
if (groupdepth > 0) {
continue;
}
- items[curitem].type = Separate;
- items[curitem++].start = out_p;
+ stl_items[curitem].type = Separate;
+ stl_items[curitem++].start = out_p;
continue;
}
// STL_TRUNCMARK: Where to begin truncating if the statusline is too long.
if (*fmt_p == STL_TRUNCMARK) {
fmt_p++;
- items[curitem].type = Trunc;
- items[curitem++].start = out_p;
+ stl_items[curitem].type = Trunc;
+ stl_items[curitem++].start = out_p;
continue;
}
@@ -3614,7 +3608,7 @@ int build_stl_str_hl(
// Determine how long the group is.
// Note: We set the current output position to null
// so `vim_strsize` will work.
- char_u *t = items[groupitems[groupdepth]].start;
+ char_u *t = stl_items[stl_groupitems[groupdepth]].start;
*out_p = NUL;
long group_len = vim_strsize(t);
@@ -3624,34 +3618,40 @@ int build_stl_str_hl(
// move the output pointer back to where the group started.
// Note: This erases any non-item characters that were in the group.
// Otherwise there would be no reason to do this step.
- if (curitem > groupitems[groupdepth] + 1
- && items[groupitems[groupdepth]].minwid == 0) {
+ if (curitem > stl_groupitems[groupdepth] + 1
+ && stl_items[stl_groupitems[groupdepth]].minwid == 0) {
// remove group if all items are empty and highlight group
// doesn't change
int group_start_userhl = 0;
int group_end_userhl = 0;
int n;
- for (n = groupitems[groupdepth] - 1; n >= 0; n--) {
- if (items[n].type == Highlight) {
- group_start_userhl = group_end_userhl = items[n].minwid;
+ for (n = stl_groupitems[groupdepth] - 1; n >= 0; n--) {
+ if (stl_items[n].type == Highlight) {
+ group_start_userhl = group_end_userhl = stl_items[n].minwid;
break;
}
}
- for (n = groupitems[groupdepth] + 1; n < curitem; n++) {
- if (items[n].type == Normal) {
+ for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) {
+ if (stl_items[n].type == Normal) {
break;
}
- if (items[n].type == Highlight) {
- group_end_userhl = items[n].minwid;
+ if (stl_items[n].type == Highlight) {
+ group_end_userhl = stl_items[n].minwid;
}
}
if (n == curitem && group_start_userhl == group_end_userhl) {
+ // empty group
out_p = t;
group_len = 0;
- // do not use the highlighting from the removed group
- for (n = groupitems[groupdepth] + 1; n < curitem; n++) {
- if (items[n].type == Highlight) {
- items[n].type = Empty;
+ for (n = stl_groupitems[groupdepth] + 1; n < curitem; n++) {
+ // do not use the highlighting from the removed group
+ if (stl_items[n].type == Highlight) {
+ stl_items[n].type = Empty;
+ }
+ // adjust the start position of TabPage to the next
+ // item position
+ if (stl_items[n].type == TabPage) {
+ stl_items[n].start = out_p;
}
}
}
@@ -3659,18 +3659,19 @@ int build_stl_str_hl(
// If the group is longer than it is allowed to be
// truncate by removing bytes from the start of the group text.
- if (group_len > items[groupitems[groupdepth]].maxwid) {
+ if (group_len > stl_items[stl_groupitems[groupdepth]].maxwid) {
// { Determine the number of bytes to remove
long n;
if (has_mbyte) {
// Find the first character that should be included.
n = 0;
- while (group_len >= items[groupitems[groupdepth]].maxwid) {
+ while (group_len >= stl_items[stl_groupitems[groupdepth]].maxwid) {
group_len -= ptr2cells(t + n);
n += (*mb_ptr2len)(t + n);
}
} else {
- n = (long)(out_p - t) - items[groupitems[groupdepth]].maxwid + 1;
+ n = (long)(out_p - t)
+ - stl_items[stl_groupitems[groupdepth]].maxwid + 1;
}
// }
@@ -3681,25 +3682,26 @@ int build_stl_str_hl(
memmove(t + 1, t + n, (size_t)(out_p - (t + n)));
out_p = out_p - n + 1;
// Fill up space left over by half a double-wide char.
- while (++group_len < items[groupitems[groupdepth]].minwid) {
+ while (++group_len < stl_items[stl_groupitems[groupdepth]].minwid) {
*out_p++ = fillchar;
}
// }
// correct the start of the items for the truncation
- for (int idx = groupitems[groupdepth] + 1; idx < curitem; idx++) {
+ for (int idx = stl_groupitems[groupdepth] + 1; idx < curitem; idx++) {
// Shift everything back by the number of removed bytes
- items[idx].start -= n;
+ stl_items[idx].start -= n;
// If the item was partially or completely truncated, set its
// start to the start of the group
- if (items[idx].start < t) {
- items[idx].start = t;
+ if (stl_items[idx].start < t) {
+ stl_items[idx].start = t;
}
}
// If the group is shorter than the minimum width, add padding characters.
- } else if (abs(items[groupitems[groupdepth]].minwid) > group_len) {
- long min_group_width = items[groupitems[groupdepth]].minwid;
+ } else if (
+ abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) {
+ long min_group_width = stl_items[stl_groupitems[groupdepth]].minwid;
// If the group is left-aligned, add characters to the right.
if (min_group_width < 0) {
min_group_width = 0 - min_group_width;
@@ -3718,8 +3720,8 @@ int build_stl_str_hl(
// }
// Adjust item start positions
- for (int n = groupitems[groupdepth] + 1; n < curitem; n++) {
- items[n].start += group_len;
+ for (int n = stl_groupitems[groupdepth] + 1; n < curitem; n++) {
+ stl_items[n].start += group_len;
}
// Prepend the fill characters
@@ -3755,9 +3757,9 @@ int build_stl_str_hl(
// User highlight groups override the min width field
// to denote the styling to use.
if (*fmt_p == STL_USER_HL) {
- items[curitem].type = Highlight;
- items[curitem].start = out_p;
- items[curitem].minwid = minwid > 9 ? 1 : minwid;
+ stl_items[curitem].type = Highlight;
+ stl_items[curitem].start = out_p;
+ stl_items[curitem].minwid = minwid > 9 ? 1 : minwid;
fmt_p++;
curitem++;
continue;
@@ -3791,8 +3793,8 @@ int build_stl_str_hl(
if (minwid == 0) {
// %X ends the close label, go back to the previous tab label nr.
for (long n = curitem - 1; n >= 0; n--) {
- if (items[n].type == TabPage && items[n].minwid >= 0) {
- minwid = items[n].minwid;
+ if (stl_items[n].type == TabPage && stl_items[n].minwid >= 0) {
+ minwid = stl_items[n].minwid;
break;
}
}
@@ -3801,9 +3803,9 @@ int build_stl_str_hl(
minwid = -minwid;
}
}
- items[curitem].type = TabPage;
- items[curitem].start = out_p;
- items[curitem].minwid = minwid;
+ stl_items[curitem].type = TabPage;
+ stl_items[curitem].start = out_p;
+ stl_items[curitem].minwid = minwid;
fmt_p++;
curitem++;
continue;
@@ -3818,10 +3820,10 @@ int build_stl_str_hl(
if (*fmt_p != STL_CLICK_FUNC) {
break;
}
- items[curitem].type = ClickFunc;
- items[curitem].start = out_p;
- items[curitem].cmd = xmemdupz(t, (size_t)(((char *)fmt_p - t)));
- items[curitem].minwid = minwid;
+ stl_items[curitem].type = ClickFunc;
+ stl_items[curitem].start = out_p;
+ stl_items[curitem].cmd = xmemdupz(t, (size_t)(((char *)fmt_p - t)));
+ stl_items[curitem].minwid = minwid;
fmt_p++;
curitem++;
continue;
@@ -3842,11 +3844,11 @@ int build_stl_str_hl(
// Denotes the start of a new group
if (*fmt_p == '(') {
- groupitems[groupdepth++] = curitem;
- items[curitem].type = Group;
- items[curitem].start = out_p;
- items[curitem].minwid = minwid;
- items[curitem].maxwid = maxwid;
+ stl_groupitems[groupdepth++] = curitem;
+ stl_items[curitem].type = Group;
+ stl_items[curitem].start = out_p;
+ stl_items[curitem].minwid = minwid;
+ stl_items[curitem].maxwid = maxwid;
fmt_p++;
curitem++;
continue;
@@ -4141,9 +4143,9 @@ int build_stl_str_hl(
// Create a highlight item based on the name
if (*fmt_p == '#') {
- items[curitem].type = Highlight;
- items[curitem].start = out_p;
- items[curitem].minwid = -syn_namen2id(t, (int)(fmt_p - t));
+ stl_items[curitem].type = Highlight;
+ stl_items[curitem].start = out_p;
+ stl_items[curitem].minwid = -syn_namen2id(t, (int)(fmt_p - t));
curitem++;
fmt_p++;
}
@@ -4154,8 +4156,8 @@ int build_stl_str_hl(
// If we made it this far, the item is normal and starts at
// our current position in the output buffer.
// Non-normal items would have `continued`.
- items[curitem].start = out_p;
- items[curitem].type = Normal;
+ stl_items[curitem].start = out_p;
+ stl_items[curitem].type = Normal;
// Copy the item string into the output buffer
if (str != NULL && *str) {
@@ -4313,7 +4315,7 @@ int build_stl_str_hl(
// Otherwise, there was nothing to print so mark the item as empty
} else {
- items[curitem].type = Empty;
+ stl_items[curitem].type = Empty;
}
// Only free the string buffer if we allocated it.
@@ -4354,13 +4356,13 @@ int build_stl_str_hl(
// Otherwise, look for the truncation item
} else {
// Default to truncating at the first item
- trunc_p = items[0].start;
+ trunc_p = stl_items[0].start;
item_idx = 0;
for (int i = 0; i < itemcnt; i++) {
- if (items[i].type == Trunc) {
- // Truncate at %< items.
- trunc_p = items[i].start;
+ if (stl_items[i].type == Trunc) {
+ // Truncate at %< stl_items.
+ trunc_p = stl_items[i].start;
item_idx = i;
break;
}
@@ -4395,7 +4397,7 @@ int build_stl_str_hl(
// Ignore any items in the statusline that occur after
// the truncation point
for (int i = 0; i < itemcnt; i++) {
- if (items[i].start > trunc_p) {
+ if (stl_items[i].start > trunc_p) {
itemcnt = i;
break;
}
@@ -4450,12 +4452,12 @@ int build_stl_str_hl(
for (int i = item_idx; i < itemcnt; i++) {
// Items starting at or after the end of the truncated section need
// to be moved backwards.
- if (items[i].start >= trunc_end_p) {
- items[i].start -= item_offset;
+ if (stl_items[i].start >= trunc_end_p) {
+ stl_items[i].start -= item_offset;
// Anything inside the truncated area is set to start
// at the `<` truncation character.
} else {
- items[i].start = trunc_p;
+ stl_items[i].start = trunc_p;
}
}
// }
@@ -4471,7 +4473,7 @@ int build_stl_str_hl(
// figuring out how many groups there are.
int num_separators = 0;
for (int i = 0; i < itemcnt; i++) {
- if (items[i].type == Separate) {
+ if (stl_items[i].type == Separate) {
num_separators++;
}
}
@@ -4480,11 +4482,10 @@ int build_stl_str_hl(
if (num_separators) {
// Create an array of the start location for each
// separator mark.
- int separator_locations[STL_MAX_ITEM];
int index = 0;
for (int i = 0; i < itemcnt; i++) {
- if (items[i].type == Separate) {
- separator_locations[index] = i;
+ if (stl_items[i].type == Separate) {
+ stl_separator_locations[index] = i;
index++;
}
}
@@ -4496,16 +4497,17 @@ int build_stl_str_hl(
for (int i = 0; i < num_separators; i++) {
int dislocation = (i == (num_separators - 1))
? final_spaces : standard_spaces;
- char_u *seploc = items[separator_locations[i]].start + dislocation;
- STRMOVE(seploc, items[separator_locations[i]].start);
- for (char_u *s = items[separator_locations[i]].start; s < seploc; s++) {
+ char_u *start = stl_items[stl_separator_locations[i]].start;
+ char_u *seploc = start + dislocation;
+ STRMOVE(seploc, start);
+ for (char_u *s = start; s < seploc; s++) {
*s = fillchar;
}
- for (int item_idx = separator_locations[i] + 1;
+ for (int item_idx = stl_separator_locations[i] + 1;
item_idx < itemcnt;
item_idx++) {
- items[item_idx].start += dislocation;
+ stl_items[item_idx].start += dislocation;
}
}
@@ -4515,11 +4517,12 @@ int build_stl_str_hl(
// Store the info about highlighting.
if (hltab != NULL) {
- struct stl_hlrec *sp = hltab;
+ *hltab = stl_hltab;
+ stl_hlrec_t *sp = stl_hltab;
for (long l = 0; l < itemcnt; l++) {
- if (items[l].type == Highlight) {
- sp->start = items[l].start;
- sp->userhl = items[l].minwid;
+ if (stl_items[l].type == Highlight) {
+ sp->start = stl_items[l].start;
+ sp->userhl = stl_items[l].minwid;
sp++;
}
}
@@ -4529,16 +4532,17 @@ int build_stl_str_hl(
// Store the info about tab pages labels.
if (tabtab != NULL) {
- StlClickRecord *cur_tab_rec = tabtab;
+ *tabtab = stl_tabtab;
+ StlClickRecord *cur_tab_rec = stl_tabtab;
for (long l = 0; l < itemcnt; l++) {
- if (items[l].type == TabPage) {
- cur_tab_rec->start = (char *)items[l].start;
- if (items[l].minwid == 0) {
+ if (stl_items[l].type == TabPage) {
+ cur_tab_rec->start = (char *)stl_items[l].start;
+ if (stl_items[l].minwid == 0) {
cur_tab_rec->def.type = kStlClickDisabled;
cur_tab_rec->def.tabnr = 0;
} else {
- int tabnr = items[l].minwid;
- if (items[l].minwid > 0) {
+ int tabnr = stl_items[l].minwid;
+ if (stl_items[l].minwid > 0) {
cur_tab_rec->def.type = kStlClickTabSwitch;
} else {
cur_tab_rec->def.type = kStlClickTabClose;
@@ -4548,11 +4552,11 @@ int build_stl_str_hl(
}
cur_tab_rec->def.func = NULL;
cur_tab_rec++;
- } else if (items[l].type == ClickFunc) {
- cur_tab_rec->start = (char *)items[l].start;
+ } else if (stl_items[l].type == ClickFunc) {
+ cur_tab_rec->start = (char *)stl_items[l].start;
cur_tab_rec->def.type = kStlClickFuncRun;
- cur_tab_rec->def.tabnr = items[l].minwid;
- cur_tab_rec->def.func = items[l].cmd;
+ cur_tab_rec->def.tabnr = stl_items[l].minwid;
+ cur_tab_rec->def.func = stl_items[l].cmd;
cur_tab_rec++;
}
}
@@ -5389,13 +5393,11 @@ bool buf_hide(const buf_T *const buf)
char_u *buf_spname(buf_T *buf)
{
if (bt_quickfix(buf)) {
- win_T *win;
- tabpage_T *tp;
+ win_T *win;
+ tabpage_T *tp;
- /*
- * For location list window, w_llist_ref points to the location list.
- * For quickfix window, w_llist_ref is NULL.
- */
+ // For location list window, w_llist_ref points to the location list.
+ // For quickfix window, w_llist_ref is NULL.
if (find_win_for_buf(buf, &win, &tp) && win->w_llist_ref != NULL) {
return (char_u *)_(msg_loclist);
} else {
@@ -5414,7 +5416,7 @@ char_u *buf_spname(buf_T *buf)
return (char_u *)_("[Scratch]");
}
if (buf->b_fname == NULL) {
- return (char_u *)_("[No Name]");
+ return buf_get_fname(buf);
}
return NULL;
}
@@ -5475,6 +5477,16 @@ int buf_signcols(buf_T *buf)
return buf->b_signcols;
}
+// Get "buf->b_fname", use "[No Name]" if it is NULL.
+char_u *buf_get_fname(const buf_T *buf)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
+{
+ if (buf->b_fname == NULL) {
+ return (char_u *)_("[No Name]");
+ }
+ return buf->b_fname;
+}
+
/*
* Set 'buflisted' for curbuf to "on" and trigger autocommands if it changed.
*/
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 550f8a5e40..93fe37b585 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -91,6 +91,7 @@ typedef struct {
#define BF_READERR 0x40 // got errors while reading the file
#define BF_DUMMY 0x80 // dummy buffer, only used internally
#define BF_PRESERVED 0x100 // ":preserve" was used
+#define BF_SYN_SET 0x200 // 'syntax' option was set
// Mask to check for flags that prevent normal writing
#define BF_WRITE_MASK (BF_NOTEDITED + BF_NEW + BF_READERR)
@@ -360,14 +361,36 @@ struct mapblock {
sctx_T m_script_ctx; // SCTX where map was defined
};
-/*
- * Used for highlighting in the status line.
- */
+/// Used for highlighting in the status line.
+typedef struct stl_hlrec stl_hlrec_t;
struct stl_hlrec {
char_u *start;
int userhl; // 0: no HL, 1-9: User HL, < 0 for syn ID
};
+/// Used for building the status line.
+typedef struct stl_item stl_item_t;
+struct stl_item {
+ // Where the item starts in the status line output buffer
+ char_u *start;
+ // Function to run for ClickFunc items.
+ char *cmd;
+ // The minimum width of the item
+ int minwid;
+ // The maximum width of the item
+ int maxwid;
+ enum {
+ Normal,
+ Empty,
+ Group,
+ Separate,
+ Highlight,
+ TabPage,
+ ClickFunc,
+ Trunc
+ } type;
+};
+
// values for b_syn_spell: what to do with toplevel text
#define SYNSPL_DEFAULT 0 // spell check if @Spell not defined
#define SYNSPL_TOP 1 // spell check toplevel text
@@ -451,6 +474,7 @@ typedef struct {
regprog_T *b_cap_prog; // program for 'spellcapcheck'
char_u *b_p_spf; // 'spellfile'
char_u *b_p_spl; // 'spelllang'
+ char_u *b_p_spo; // 'spelloptions'
int b_cjk; // all CJK letters as OK
char_u b_syn_chartab[32]; // syntax iskeyword option
char_u *b_syn_isk; // iskeyword option
@@ -517,6 +541,9 @@ struct file_buffer {
int b_changed; // 'modified': Set to true if something in the
// file has been changed and not written out.
+ bool b_changed_invalid; // Set if BufModified autocmd has not been
+ // triggered since the last time b_changed was
+ // modified.
/// Change-identifier incremented for each change, including undo.
///
@@ -543,6 +570,9 @@ struct file_buffer {
long b_mod_xlines; // number of extra buffer lines inserted;
// negative when lines were deleted
wininfo_T *b_wininfo; // list of last used info for each window
+ int b_mod_tick_syn; // last display tick syntax was updated
+ int b_mod_tick_decor; // last display tick decoration providers
+ // where invoked
long b_mtime; // last change time of original file
long b_mtime_read; // last change time when reading
@@ -656,6 +686,9 @@ struct file_buffer {
char_u *b_p_com; ///< 'comments'
char_u *b_p_cms; ///< 'commentstring'
char_u *b_p_cpt; ///< 'complete'
+#ifdef BACKSLASH_IN_FILENAME
+ char_u *b_p_csl; ///< 'completeslash'
+#endif
char_u *b_p_cfu; ///< 'completefunc'
char_u *b_p_ofu; ///< 'omnifunc'
char_u *b_p_tfu; ///< 'tagfunc'
@@ -765,6 +798,7 @@ struct file_buffer {
int b_ind_cpp_namespace;
int b_ind_if_for_while;
int b_ind_cpp_extern_c;
+ int b_ind_pragma;
linenr_T b_no_eol_lnum; /* non-zero lnum when last line of next binary
* write should not have an end-of-line */
@@ -835,18 +869,13 @@ struct file_buffer {
// tree-sitter) or the corresponding UTF-32/UTF-16 size (like LSP) of the
// deleted text.
size_t deleted_bytes;
+ size_t deleted_bytes2;
size_t deleted_codepoints;
size_t deleted_codeunits;
// The number for times the current line has been flushed in the memline.
int flush_count;
- bool b_luahl;
- LuaRef b_luahl_start;
- LuaRef b_luahl_window;
- LuaRef b_luahl_line;
- LuaRef b_luahl_end;
-
int b_diff_failed; // internal diff failed for this buffer
};
@@ -1204,6 +1233,13 @@ struct window_S {
colnr_T w_skipcol; // starting column when a single line
// doesn't fit in the window
+ // "w_last_topline" and "w_last_leftcol" are used to determine if
+ // a Scroll autocommand should be emitted.
+ linenr_T w_last_topline; ///< last known value for topline
+ colnr_T w_last_leftcol; ///< last known value for leftcol
+ int w_last_width; ///< last known value for width
+ int w_last_height; ///< last known value for height
+
//
// Layout of the window in the screen.
// May need to add "msg_scrolled" to "w_winrow" in rare situations.
diff --git a/src/nvim/buffer_updates.c b/src/nvim/buffer_updates.c
index e6393bf02c..fc671ad9e2 100644
--- a/src/nvim/buffer_updates.c
+++ b/src/nvim/buffer_updates.c
@@ -2,6 +2,7 @@
// it. PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com
#include "nvim/buffer_updates.h"
+#include "nvim/extmark.h"
#include "nvim/memline.h"
#include "nvim/api/private/helpers.h"
#include "nvim/msgpack_rpc/channel.h"
@@ -157,7 +158,7 @@ void buf_updates_unregister_all(buf_T *buf)
args.items[0] = BUFFER_OBJ(buf->handle);
textlock++;
- executor_exec_lua_cb(cb.on_detach, "detach", args, false, NULL);
+ nlua_call_ref(cb.on_detach, "detach", args, false, NULL);
textlock--;
}
free_update_callbacks(cb);
@@ -265,7 +266,7 @@ void buf_updates_send_changes(buf_T *buf,
args.items[7] = INTEGER_OBJ((Integer)deleted_codeunits);
}
textlock++;
- Object res = executor_exec_lua_cb(cb.on_lines, "lines", args, true, NULL);
+ Object res = nlua_call_ref(cb.on_lines, "lines", args, true, NULL);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
@@ -281,12 +282,14 @@ void buf_updates_send_changes(buf_T *buf,
kv_size(buf->update_callbacks) = j;
}
-void buf_updates_send_splice(buf_T *buf,
- linenr_T start_line, colnr_T start_col,
- linenr_T oldextent_line, colnr_T oldextent_col,
- linenr_T newextent_line, colnr_T newextent_col)
+void buf_updates_send_splice(
+ buf_T *buf,
+ int start_row, colnr_T start_col, bcount_t start_byte,
+ int old_row, colnr_T old_col, bcount_t old_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte)
{
- if (!buf_updates_active(buf)) {
+ if (!buf_updates_active(buf)
+ || (old_byte == 0 && new_byte == 0)) {
return;
}
@@ -296,7 +299,7 @@ void buf_updates_send_splice(buf_T *buf,
BufUpdateCallbacks cb = kv_A(buf->update_callbacks, i);
bool keep = true;
if (cb.on_bytes != LUA_NOREF) {
- FIXED_TEMP_ARRAY(args, 8);
+ FIXED_TEMP_ARRAY(args, 11);
// the first argument is always the buffer handle
args.items[0] = BUFFER_OBJ(buf->handle);
@@ -304,15 +307,18 @@ void buf_updates_send_splice(buf_T *buf,
// next argument is b:changedtick
args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf));
- args.items[2] = INTEGER_OBJ(start_line);
+ args.items[2] = INTEGER_OBJ(start_row);
args.items[3] = INTEGER_OBJ(start_col);
- args.items[4] = INTEGER_OBJ(oldextent_line);
- args.items[5] = INTEGER_OBJ(oldextent_col);
- args.items[6] = INTEGER_OBJ(newextent_line);
- args.items[7] = INTEGER_OBJ(newextent_col);
+ args.items[4] = INTEGER_OBJ(start_byte);
+ args.items[5] = INTEGER_OBJ(old_row);
+ args.items[6] = INTEGER_OBJ(old_col);
+ args.items[7] = INTEGER_OBJ(old_byte);
+ args.items[8] = INTEGER_OBJ(new_row);
+ args.items[9] = INTEGER_OBJ(new_col);
+ args.items[10] = INTEGER_OBJ(new_byte);
textlock++;
- Object res = executor_exec_lua_cb(cb.on_bytes, "bytes", args, true, NULL);
+ Object res = nlua_call_ref(cb.on_bytes, "bytes", args, true, NULL);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
@@ -347,8 +353,8 @@ void buf_updates_changedtick(buf_T *buf)
args.items[1] = INTEGER_OBJ(buf_get_changedtick(buf));
textlock++;
- Object res = executor_exec_lua_cb(cb.on_changedtick, "changedtick",
- args, true, NULL);
+ Object res = nlua_call_ref(cb.on_changedtick, "changedtick",
+ args, true, NULL);
textlock--;
if (res.type == kObjectTypeBoolean && res.data.boolean == true) {
@@ -382,6 +388,6 @@ void buf_updates_changedtick_single(buf_T *buf, uint64_t channel_id)
static void free_update_callbacks(BufUpdateCallbacks cb)
{
- executor_free_luaref(cb.on_lines);
- executor_free_luaref(cb.on_changedtick);
+ api_free_luaref(cb.on_lines);
+ api_free_luaref(cb.on_changedtick);
}
diff --git a/src/nvim/buffer_updates.h b/src/nvim/buffer_updates.h
index b2d0a62270..961fec879b 100644
--- a/src/nvim/buffer_updates.h
+++ b/src/nvim/buffer_updates.h
@@ -2,6 +2,7 @@
#define NVIM_BUFFER_UPDATES_H
#include "nvim/buffer_defs.h"
+#include "nvim/extmark.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer_updates.h.generated.h"
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 51afb40b40..271d350967 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -129,6 +129,7 @@ void changed(void)
void changed_internal(void)
{
curbuf->b_changed = true;
+ curbuf->b_changed_invalid = true;
ml_setflags(curbuf);
check_status(curbuf);
redraw_tabline = true;
@@ -142,7 +143,6 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume,
long xtra)
{
int i;
- int cols;
pos_T *p;
int add;
@@ -170,7 +170,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume,
if (p->lnum != lnum) {
add = true;
} else {
- cols = comp_textwidth(false);
+ int cols = comp_textwidth(false);
if (cols == 0) {
cols = 79;
}
@@ -295,7 +295,7 @@ static void changed_common(linenr_T lnum, colnr_T col, linenr_T lnume,
// change.
if (wp->w_p_rnu
|| (wp->w_p_cul && lnum <= wp->w_last_cursorline)) {
- redraw_win_later(wp, SOME_VALID);
+ redraw_later(wp, SOME_VALID);
}
}
}
@@ -349,7 +349,7 @@ void changed_bytes(linenr_T lnum, colnr_T col)
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_p_diff && wp != curwin) {
- redraw_win_later(wp, VALID);
+ redraw_later(wp, VALID);
wlnum = diff_lnum_win(lnum, wp);
if (wlnum > 0) {
changedOneline(wp->w_buffer, wlnum);
@@ -362,11 +362,10 @@ void changed_bytes(linenr_T lnum, colnr_T col)
/// insert/delete bytes at column
///
/// Like changed_bytes() but also adjust extmark for "new" bytes.
-/// When "new" is negative text was deleted.
-static void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new)
+void inserted_bytes(linenr_T lnum, colnr_T col, int old, int new)
{
if (curbuf_splice_pending == 0) {
- extmark_splice(curbuf, (int)lnum-1, col, 0, old, 0, new, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lnum-1, col, old, new, kExtmarkUndo);
}
changed_bytes(lnum, col);
@@ -477,7 +476,7 @@ changed_lines(
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_p_diff && wp != curwin) {
- redraw_win_later(wp, VALID);
+ redraw_later(wp, VALID);
wlnum = diff_lnum_win(lnum, wp);
if (wlnum > 0) {
changed_lines_buf(wp->w_buffer, wlnum,
@@ -504,6 +503,7 @@ void unchanged(buf_T *buf, int ff, bool always_inc_changedtick)
{
if (buf->b_changed || (ff && file_ff_differs(buf, false))) {
buf->b_changed = false;
+ buf->b_changed_invalid = true;
ml_setflags(buf);
if (ff) {
save_file_ff(buf);
@@ -1597,7 +1597,7 @@ int open_line(
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,
- kExtmarkUndo);
+ kExtmarkNOOP);
}
did_append = true;
} else {
@@ -1611,6 +1611,7 @@ int open_line(
}
ml_replace(curwin->w_cursor.lnum, p_extra, true);
changed_bytes(curwin->w_cursor.lnum, 0);
+ // TODO(vigoux): extmark_splice_cols here??
curwin->w_cursor.lnum--;
did_append = false;
}
@@ -1676,6 +1677,16 @@ int open_line(
truncate_spaces(saved_line);
}
ml_replace(curwin->w_cursor.lnum, saved_line, false);
+
+ int new_len = (int)STRLEN(saved_line);
+
+ // TODO(vigoux): maybe there is issues there with expandtabs ?
+ if (new_len < curwin->w_cursor.col) {
+ extmark_splice_cols(
+ curbuf, (int)curwin->w_cursor.lnum,
+ new_len, curwin->w_cursor.col - new_len, 0, kExtmarkUndo);
+ }
+
saved_line = NULL;
if (did_append) {
changed_lines(curwin->w_cursor.lnum, curwin->w_cursor.col,
@@ -1691,8 +1702,9 @@ int open_line(
// Always move extmarks - Here we move only the line where the
// cursor is, the previous mark_adjust takes care of the lines after
int cols_added = mincol-1+less_cols_off-less_cols;
- extmark_splice(curbuf, (int)lnum-1, mincol-1, 0, less_cols_off,
- 1, cols_added, kExtmarkUndo);
+ extmark_splice(curbuf, (int)lnum-1, mincol-1,
+ 0, less_cols_off, less_cols_off,
+ 1, cols_added, 1 + cols_added, kExtmarkUndo);
} else {
changed_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col);
}
@@ -1704,8 +1716,10 @@ int open_line(
}
if (did_append) {
changed_lines(curwin->w_cursor.lnum, 0, curwin->w_cursor.lnum, 1L, true);
- extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1,
- 0, 0, 0, 1, 0, kExtmarkUndo);
+ // bail out and just get the final lenght of the line we just manipulated
+ bcount_t extra = (bcount_t)STRLEN(ml_get(curwin->w_cursor.lnum));
+ extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, 0,
+ 0, 0, 0, 1, 0, 1+extra, kExtmarkUndo);
}
curbuf_splice_pending--;
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index f9d5adbc12..fb158f377a 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -509,7 +509,7 @@ char_u* str_foldcase(char_u *str, int orglen, char_u *buf, int buflen)
// Does NOT work for multi-byte characters, c must be <= 255.
// Also doesn't work for the first byte of a multi-byte, "c" must be a
// character!
-static char_u transchar_buf[11];
+static char_u transchar_charbuf[11];
/// Translate a character into a printable one, leaving printable ASCII intact
///
@@ -520,11 +520,17 @@ static char_u transchar_buf[11];
/// @return translated character into a static buffer.
char_u *transchar(int c)
{
+ return transchar_buf(curbuf, c);
+}
+
+char_u *transchar_buf(const buf_T *buf, int c)
+ FUNC_ATTR_NONNULL_ALL
+{
int i = 0;
if (IS_SPECIAL(c)) {
// special key code, display as ~@ char
- transchar_buf[0] = '~';
- transchar_buf[1] = '@';
+ transchar_charbuf[0] = '~';
+ transchar_charbuf[1] = '@';
i = 2;
c = K_SECOND(c);
}
@@ -532,14 +538,14 @@ char_u *transchar(int c)
if ((!chartab_initialized && (((c >= ' ') && (c <= '~'))))
|| ((c <= 0xFF) && vim_isprintc_strict(c))) {
// printable character
- transchar_buf[i] = (char_u)c;
- transchar_buf[i + 1] = NUL;
+ transchar_charbuf[i] = (char_u)c;
+ transchar_charbuf[i + 1] = NUL;
} else if (c <= 0xFF) {
- transchar_nonprint(transchar_buf + i, c);
+ transchar_nonprint(buf, transchar_charbuf + i, c);
} else {
- transchar_hex((char *)transchar_buf + i, c);
+ transchar_hex((char *)transchar_charbuf + i, c);
}
- return transchar_buf;
+ return transchar_charbuf;
}
/// Like transchar(), but called with a byte instead of a character
@@ -548,13 +554,13 @@ char_u *transchar(int c)
///
/// @param[in] c Byte to translate.
///
-/// @return pointer to translated character in transchar_buf.
+/// @return pointer to translated character in transchar_charbuf.
char_u *transchar_byte(const int c)
FUNC_ATTR_WARN_UNUSED_RESULT
{
if (c >= 0x80) {
- transchar_nonprint(transchar_buf, c);
- return transchar_buf;
+ transchar_nonprint(curbuf, transchar_charbuf, c);
+ return transchar_charbuf;
}
return transchar(c);
}
@@ -563,16 +569,18 @@ char_u *transchar_byte(const int c)
///
/// @warning Does not work for multi-byte characters, c must be <= 255.
///
-/// @param[out] buf Buffer to store result in, must be able to hold at least
-/// 5 bytes (conversion result + NUL).
+/// @param[in] buf Required to check the file format
+/// @param[out] charbuf Buffer to store result in, must be able to hold
+/// at least 5 bytes (conversion result + NUL).
/// @param[in] c Character to convert. NUL is assumed to be NL according to
/// `:h NL-used-for-NUL`.
-void transchar_nonprint(char_u *buf, int c)
+void transchar_nonprint(const buf_T *buf, char_u *charbuf, int c)
+ FUNC_ATTR_NONNULL_ALL
{
if (c == NL) {
// we use newline in place of a NUL
c = NUL;
- } else if ((c == CAR) && (get_fileformat(curbuf) == EOL_MAC)) {
+ } else if ((c == CAR) && (get_fileformat(buf) == EOL_MAC)) {
// we use CR in place of NL in this case
c = NL;
}
@@ -580,14 +588,14 @@ void transchar_nonprint(char_u *buf, int c)
if (dy_flags & DY_UHEX || c > 0x7f) {
// 'display' has "uhex"
- transchar_hex((char *)buf, c);
+ transchar_hex((char *)charbuf, c);
} else {
// 0x00 - 0x1f and 0x7f
- buf[0] = '^';
+ charbuf[0] = '^';
// DEL displayed as ^?
- buf[1] = (char_u)(c ^ 0x40);
+ charbuf[1] = (char_u)(c ^ 0x40);
- buf[2] = NUL;
+ charbuf[2] = NUL;
}
}
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c
index 036ae32589..d3ffab1759 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -482,7 +482,7 @@ bool leftcol_changed(void)
if (retval)
curwin->w_set_curswant = true;
- redraw_later(NOT_VALID);
+ redraw_later(curwin, NOT_VALID);
return retval;
}
diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c
index 3f06340611..0d21080aa5 100644
--- a/src/nvim/cursor_shape.c
+++ b/src/nvim/cursor_shape.c
@@ -13,6 +13,10 @@
#include "nvim/api/private/helpers.h"
#include "nvim/ui.h"
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "cursor_shape.c.generated.h"
+#endif
+
/// Handling of cursor and mouse pointer shapes in various modes.
cursorentry_T shape_table[SHAPE_IDX_COUNT] =
{
@@ -77,7 +81,9 @@ Array mode_style_array(void)
return all;
}
-/// Parse the 'guicursor' option
+/// Parses the 'guicursor' option.
+///
+/// Clears `shape_table` if 'guicursor' is empty.
///
/// @param what SHAPE_CURSOR or SHAPE_MOUSE ('mouseshape')
///
@@ -99,11 +105,17 @@ char_u *parse_shape_opt(int what)
// First round: check for errors; second round: do it for real.
for (round = 1; round <= 2; round++) {
+ if (round == 2 || *p_guicursor == NUL) {
+ // Set all entries to default (block, blinkon0, default color).
+ // This is the default for anything that is not set.
+ clear_shape_table();
+ if (*p_guicursor == NUL) {
+ ui_mode_info_set();
+ return NULL;
+ }
+ }
// Repeat for all comma separated parts.
modep = p_guicursor;
- if (*p_guicursor == NUL) {
- modep = (char_u *)"a:block-blinkon0";
- }
while (modep != NULL && *modep != NUL) {
colonp = vim_strchr(modep, ':');
commap = vim_strchr(modep, ',');
@@ -144,14 +156,6 @@ char_u *parse_shape_opt(int what)
if (all_idx >= 0) {
idx = all_idx--;
- } else if (round == 2) {
- {
- // Set the defaults, for the missing parts
- shape_table[idx].shape = SHAPE_BLOCK;
- shape_table[idx].blinkwait = 0L;
- shape_table[idx].blinkon = 0L;
- shape_table[idx].blinkoff = 0L;
- }
}
/* Parse the part after the colon */
@@ -330,3 +334,16 @@ int cursor_get_mode_idx(void)
return SHAPE_IDX_N;
}
}
+
+/// Clears all entries in shape_table to block, blinkon0, and default color.
+static void clear_shape_table(void)
+{
+ for (int idx = 0; idx < SHAPE_IDX_COUNT; idx++) {
+ shape_table[idx].shape = SHAPE_BLOCK;
+ shape_table[idx].blinkwait = 0L;
+ shape_table[idx].blinkon = 0L;
+ shape_table[idx].blinkoff = 0L;
+ shape_table[idx].id = 0;
+ shape_table[idx].id_lm = 0;
+ }
+}
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
new file mode 100644
index 0000000000..03ce2a37b5
--- /dev/null
+++ b/src/nvim/decoration.c
@@ -0,0 +1,330 @@
+// 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/vim.h"
+#include "nvim/extmark.h"
+#include "nvim/decoration.h"
+#include "nvim/screen.h"
+#include "nvim/syntax.h"
+#include "nvim/highlight.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "decoration.c.generated.h"
+#endif
+
+static PMap(uint64_t) *hl_decors;
+
+void decor_init(void)
+{
+ hl_decors = pmap_new(uint64_t)();
+}
+
+/// Add highlighting to a buffer, bounded by two cursor positions,
+/// with an offset.
+///
+/// TODO(bfredl): make decoration powerful enough so that this
+/// can be done with a single ephemeral decoration.
+///
+/// @param buf Buffer to add highlights to
+/// @param src_id src_id to use or 0 to use a new src_id group,
+/// or -1 for ungrouped highlight.
+/// @param hl_id Highlight group id
+/// @param pos_start Cursor position to start the hightlighting at
+/// @param pos_end Cursor position to end the highlighting at
+/// @param offset Move the whole highlighting this many columns to the right
+void bufhl_add_hl_pos_offset(buf_T *buf,
+ int src_id,
+ int hl_id,
+ lpos_T pos_start,
+ lpos_T pos_end,
+ colnr_T offset)
+{
+ colnr_T hl_start = 0;
+ colnr_T hl_end = 0;
+ Decoration *decor = decor_hl(hl_id);
+
+ // TODO(bfredl): if decoration had blocky mode, we could avoid this loop
+ for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) {
+ int end_off = 0;
+ if (pos_start.lnum < lnum && lnum < pos_end.lnum) {
+ // TODO(bfredl): This is quite ad-hoc, but the space between |num| and
+ // text being highlighted is the indication of \n being part of the
+ // substituted text. But it would be more consistent to highlight
+ // a space _after_ the previous line instead (like highlight EOL list
+ // char)
+ hl_start = MAX(offset-1, 0);
+ end_off = 1;
+ hl_end = 0;
+ } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) {
+ hl_start = pos_start.col + offset;
+ end_off = 1;
+ hl_end = 0;
+ } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) {
+ hl_start = MAX(offset-1, 0);
+ hl_end = pos_end.col + offset;
+ } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) {
+ hl_start = pos_start.col + offset;
+ hl_end = pos_end.col + offset;
+ }
+ (void)extmark_set(buf, (uint64_t)src_id, 0,
+ (int)lnum-1, hl_start, (int)lnum-1+end_off, hl_end,
+ decor, kExtmarkNoUndo);
+ }
+}
+
+Decoration *decor_hl(int hl_id)
+{
+ assert(hl_id > 0);
+ Decoration **dp = (Decoration **)pmap_ref(uint64_t)(hl_decors,
+ (uint64_t)hl_id, true);
+ if (*dp) {
+ return *dp;
+ }
+
+ Decoration *decor = xcalloc(1, sizeof(*decor));
+ decor->hl_id = hl_id;
+ decor->shared = true;
+ *dp = decor;
+ return decor;
+}
+
+void decor_redraw(buf_T *buf, int row1, int row2, Decoration *decor)
+{
+ if (decor->hl_id && row2 >= row1) {
+ redraw_buf_range_later(buf, row1+1, row2+1);
+ }
+
+ if (kv_size(decor->virt_text)) {
+ redraw_buf_line_later(buf, row1+1);
+ }
+}
+
+void decor_free(Decoration *decor)
+{
+ if (decor && !decor->shared) {
+ clear_virttext(&decor->virt_text);
+ xfree(decor);
+ }
+}
+
+void clear_virttext(VirtText *text)
+{
+ for (size_t i = 0; i < kv_size(*text); i++) {
+ xfree(kv_A(*text, i).text);
+ }
+ kv_destroy(*text);
+ *text = (VirtText)KV_INITIAL_VALUE;
+}
+
+VirtText *decor_find_virttext(buf_T *buf, int row, uint64_t ns_id)
+{
+ MarkTreeIter itr[1] = { 0 };
+ marktree_itr_get(buf->b_marktree, row, 0, itr);
+ while (true) {
+ mtmark_t mark = marktree_itr_current(itr);
+ if (mark.row < 0 || mark.row > row) {
+ break;
+ }
+ ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ mark.id, false);
+ if (item && (ns_id == 0 || ns_id == item->ns_id)
+ && item->decor && kv_size(item->decor->virt_text)) {
+ return &item->decor->virt_text;
+ }
+ marktree_itr_next(buf->b_marktree, itr);
+ }
+ return NULL;
+}
+
+bool decor_redraw_reset(buf_T *buf, DecorState *state)
+{
+ state->row = -1;
+ state->buf = buf;
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ HlRange item = kv_A(state->active, i);
+ if (item.virt_text_owned) {
+ clear_virttext(item.virt_text);
+ xfree(item.virt_text);
+ }
+ }
+ kv_size(state->active) = 0;
+ return buf->b_extmark_index;
+}
+
+
+bool decor_redraw_start(buf_T *buf, int top_row, DecorState *state)
+{
+ state->top_row = top_row;
+ marktree_itr_get(buf->b_marktree, top_row, 0, state->itr);
+ if (!state->itr->node) {
+ return false;
+ }
+ marktree_itr_rewind(buf->b_marktree, state->itr);
+ while (true) {
+ mtmark_t mark = marktree_itr_current(state->itr);
+ if (mark.row < 0) { // || mark.row > end_row
+ break;
+ }
+ if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG)) {
+ goto next_mark;
+ }
+ mtpos_t altpos = marktree_lookup(buf->b_marktree,
+ mark.id^MARKTREE_END_FLAG, NULL);
+
+ uint64_t start_id = mark.id & ~MARKTREE_END_FLAG;
+ ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ start_id, false);
+ if (!item || !item->decor) {
+ // TODO(bfredl): dedicated flag for being a decoration?
+ goto next_mark;
+ }
+ Decoration *decor = item->decor;
+
+ if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row
+ && !kv_size(decor->virt_text))
+ || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) {
+ goto next_mark;
+ }
+
+ int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0;
+ VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL;
+ HlRange range;
+ if (mark.id&MARKTREE_END_FLAG) {
+ range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col,
+ attr_id, vt, false };
+ } else {
+ range = (HlRange){ mark.row, mark.col, altpos.row,
+ altpos.col, attr_id, vt, false };
+ }
+ kv_push(state->active, range);
+
+next_mark:
+ if (marktree_itr_node_done(state->itr)) {
+ break;
+ }
+ marktree_itr_next(buf->b_marktree, state->itr);
+ }
+
+ return true; // TODO(bfredl): check if available in the region
+}
+
+bool decor_redraw_line(buf_T *buf, int row, DecorState *state)
+{
+ if (state->row == -1) {
+ decor_redraw_start(buf, row, state);
+ }
+ state->row = row;
+ state->col_until = -1;
+ return true; // TODO(bfredl): be more precise
+}
+
+int decor_redraw_col(buf_T *buf, int col, DecorState *state)
+{
+ if (col <= state->col_until) {
+ return state->current;
+ }
+ state->col_until = MAXCOL;
+ while (true) {
+ mtmark_t mark = marktree_itr_current(state->itr);
+ if (mark.row < 0 || mark.row > state->row) {
+ break;
+ } else if (mark.row == state->row && mark.col > col) {
+ state->col_until = mark.col-1;
+ break;
+ }
+
+ if ((mark.id&MARKTREE_END_FLAG)) {
+ // TODO(bfredl): check decoration flag
+ goto next_mark;
+ }
+ mtpos_t endpos = marktree_lookup(buf->b_marktree,
+ mark.id|MARKTREE_END_FLAG, NULL);
+
+ ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ mark.id, false);
+ if (!item || !item->decor) {
+ // TODO(bfredl): dedicated flag for being a decoration?
+ goto next_mark;
+ }
+ Decoration *decor = item->decor;
+
+ if (endpos.row < mark.row
+ || (endpos.row == mark.row && endpos.col <= mark.col)) {
+ if (!kv_size(decor->virt_text)) {
+ goto next_mark;
+ }
+ }
+
+ int attr_id = decor->hl_id > 0 ? syn_id2attr(decor->hl_id) : 0;
+ VirtText *vt = kv_size(decor->virt_text) ? &decor->virt_text : NULL;
+ kv_push(state->active, ((HlRange){ mark.row, mark.col,
+ endpos.row, endpos.col,
+ attr_id, vt, false }));
+
+next_mark:
+ marktree_itr_next(buf->b_marktree, state->itr);
+ }
+
+ int attr = 0;
+ size_t j = 0;
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ HlRange item = kv_A(state->active, i);
+ bool active = false, keep = true;
+ if (item.end_row < state->row
+ || (item.end_row == state->row && item.end_col <= col)) {
+ if (!(item.start_row >= state->row && item.virt_text)) {
+ keep = false;
+ }
+ } else {
+ if (item.start_row < state->row
+ || (item.start_row == state->row && item.start_col <= col)) {
+ active = true;
+ if (item.end_row == state->row) {
+ state->col_until = MIN(state->col_until, item.end_col-1);
+ }
+ } else {
+ if (item.start_row == state->row) {
+ state->col_until = MIN(state->col_until, item.start_col-1);
+ }
+ }
+ }
+ if (active && item.attr_id > 0) {
+ attr = hl_combine_attr(attr, item.attr_id);
+ }
+ if (keep) {
+ kv_A(state->active, j++) = kv_A(state->active, i);
+ } else if (item.virt_text_owned) {
+ clear_virttext(item.virt_text);
+ xfree(item.virt_text);
+ }
+ }
+ kv_size(state->active) = j;
+ state->current = attr;
+ return attr;
+}
+
+void decor_redraw_end(DecorState *state)
+{
+ state->buf = NULL;
+}
+
+VirtText *decor_redraw_virt_text(buf_T *buf, DecorState *state)
+{
+ decor_redraw_col(buf, MAXCOL, state);
+ for (size_t i = 0; i < kv_size(state->active); i++) {
+ HlRange item = kv_A(state->active, i);
+ if (item.start_row == state->row && item.virt_text) {
+ return item.virt_text;
+ }
+ }
+ return NULL;
+}
+
+void decor_add_ephemeral(int attr_id, int start_row, int start_col,
+ int end_row, int end_col, VirtText *virt_text)
+{
+ kv_push(decor_state.active,
+ ((HlRange){ start_row, start_col,
+ end_row, end_col,
+ attr_id, virt_text, virt_text != NULL }));
+}
diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h
new file mode 100644
index 0000000000..90fdc3dc43
--- /dev/null
+++ b/src/nvim/decoration.h
@@ -0,0 +1,71 @@
+#ifndef NVIM_DECORATION_H
+#define NVIM_DECORATION_H
+
+#include "nvim/pos.h"
+#include "nvim/buffer_defs.h"
+#include "nvim/extmark_defs.h"
+
+// actual Decoration data is in extmark_defs.h
+
+typedef struct {
+ char *text;
+ int hl_id;
+} VirtTextChunk;
+
+typedef kvec_t(VirtTextChunk) VirtText;
+#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE)
+
+struct Decoration
+{
+ int hl_id; // highlight group
+ VirtText virt_text;
+ // TODO(bfredl): style, signs, etc
+ bool shared; // shared decoration, don't free
+};
+
+typedef struct {
+ int start_row;
+ int start_col;
+ int end_row;
+ int end_col;
+ int attr_id;
+ VirtText *virt_text;
+ bool virt_text_owned;
+} HlRange;
+
+typedef struct {
+ MarkTreeIter itr[1];
+ kvec_t(HlRange) active;
+ buf_T *buf;
+ int top_row;
+ int row;
+ int col_until;
+ int current;
+ VirtText *virt_text;
+} DecorState;
+
+typedef struct {
+ NS ns_id;
+ bool active;
+ LuaRef redraw_start;
+ LuaRef redraw_buf;
+ LuaRef redraw_win;
+ LuaRef redraw_line;
+ LuaRef redraw_end;
+ LuaRef hl_def;
+ int hl_valid;
+} DecorProvider;
+
+EXTERN kvec_t(DecorProvider) decor_providers INIT(= KV_INITIAL_VALUE);
+EXTERN DecorState decor_state INIT(= { 0 });
+
+#define DECORATION_PROVIDER_INIT(ns_id) (DecorProvider) \
+ { ns_id, false, LUA_NOREF, LUA_NOREF, \
+ LUA_NOREF, LUA_NOREF, LUA_NOREF, \
+ LUA_NOREF, -1 }
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "decoration.h.generated.h"
+#endif
+
+#endif // NVIM_DECORATION_H
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index 3de5fc49bd..b9c293f6c8 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -643,7 +643,7 @@ void diff_redraw(bool dofold)
if (!wp->w_p_diff) {
continue;
}
- redraw_win_later(wp, SOME_VALID);
+ redraw_later(wp, SOME_VALID);
if (dofold && foldmethodIsDiff(wp)) {
foldUpdateAll(wp);
}
@@ -1415,7 +1415,7 @@ void diff_win_options(win_T *wp, int addbuf)
if (addbuf) {
diff_buf_add(wp->w_buffer);
}
- redraw_win_later(wp, NOT_VALID);
+ redraw_later(wp, NOT_VALID);
}
/// Set options not to show diffs. For the current window or all windows.
diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index 65d95ff158..dd32cef1e3 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -856,6 +856,7 @@ static digr_T digraphdefault[] =
{ '9', '"', 0x201f },
{ '/', '-', 0x2020 },
{ '/', '=', 0x2021 },
+ { 'o', 'o', 0x2022 },
{ '.', '.', 0x2025 },
{ ',', '.', 0x2026 },
{ '%', '0', 0x2030 },
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index ea38221dc7..9c8d64a6b2 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -1254,14 +1254,6 @@ check_pum:
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);
- u_sync(false);
- break;
- }
-
if (!p_paste) {
// Trigger InsertCharPre.
char_u *str = do_insert_char_pre(s->c);
@@ -1490,6 +1482,20 @@ static void ins_redraw(
}
}
+ // Trigger Scroll if viewport changed.
+ if (ready && has_event(EVENT_WINSCROLLED)
+ && win_did_scroll(curwin)) {
+ do_autocmd_winscrolled(curwin);
+ }
+
+ // Trigger BufModified if b_changed_invalid is set.
+ if (ready && has_event(EVENT_BUFMODIFIEDSET)
+ && curbuf->b_changed_invalid == true
+ && !pum_visible()) {
+ apply_autocmds(EVENT_BUFMODIFIEDSET, NULL, NULL, false, curbuf);
+ curbuf->b_changed_invalid = false;
+ }
+
if (curwin->w_p_cole > 0 && conceal_cursor_line(curwin)
&& conceal_cursor_moved) {
redrawWinline(curwin, curwin->w_cursor.lnum);
@@ -1919,10 +1925,10 @@ change_indent (
// TODO(bfredl): test for crazy edge cases, like we stand on a TAB or
// something? does this even do the right text change then?
int delta = orig_col - new_col;
- extmark_splice(curbuf, curwin->w_cursor.lnum-1, new_col,
- 0, delta < 0 ? -delta : 0,
- 0, delta > 0 ? delta : 0,
- kExtmarkUndo);
+ extmark_splice_cols(curbuf, curwin->w_cursor.lnum-1, new_col,
+ delta < 0 ? -delta : 0,
+ delta > 0 ? delta : 0,
+ kExtmarkUndo);
}
}
@@ -4179,6 +4185,21 @@ static int ins_compl_get_exp(pos_T *ini)
EW_FILE|EW_DIR|EW_ADDSLASH|EW_SILENT) == OK) {
// May change home directory back to "~".
tilde_replace(compl_pattern, num_matches, matches);
+#ifdef BACKSLASH_IN_FILENAME
+ if (curbuf->b_p_csl[0] != NUL) {
+ for (int i = 0; i < num_matches; i++) {
+ char_u *ptr = matches[i];
+ while (*ptr != NUL) {
+ if (curbuf->b_p_csl[0] == 's' && *ptr == '\\') {
+ *ptr = '/';
+ } else if (curbuf->b_p_csl[0] == 'b' && *ptr == '/') {
+ *ptr = '\\';
+ }
+ ptr += utfc_ptr2len(ptr);
+ }
+ }
+ }
+#endif
ins_compl_add_matches(num_matches, matches, p_fic || p_wic);
}
break;
@@ -5557,13 +5578,11 @@ void insertchar(
int second_indent // indent for second line if >= 0
)
{
- int textwidth;
char_u *p;
- int fo_ins_blank;
int force_format = flags & INSCHAR_FORMAT;
- textwidth = comp_textwidth(force_format);
- fo_ins_blank = has_format_option(FO_INS_BLANK);
+ const int textwidth = comp_textwidth(force_format);
+ const bool fo_ins_blank = has_format_option(FO_INS_BLANK);
/*
* Try to break the line in two or more pieces when:
@@ -5764,10 +5783,11 @@ internal_format (
int cc;
int save_char = NUL;
bool haveto_redraw = false;
- int fo_ins_blank = has_format_option(FO_INS_BLANK);
- int fo_multibyte = has_format_option(FO_MBYTE_BREAK);
- int fo_white_par = has_format_option(FO_WHITE_PAR);
- int first_line = TRUE;
+ const bool fo_ins_blank = has_format_option(FO_INS_BLANK);
+ const bool fo_multibyte = has_format_option(FO_MBYTE_BREAK);
+ const bool fo_rigor_tw = has_format_option(FO_RIGOROUS_TW);
+ const bool fo_white_par = has_format_option(FO_WHITE_PAR);
+ bool first_line = true;
colnr_T leader_len;
bool no_leader = false;
int do_comments = (flags & INSCHAR_DO_COM);
@@ -5846,6 +5866,7 @@ internal_format (
curwin->w_cursor.col = startcol;
foundcol = 0;
+ int skip_pos = 0;
/*
* Find position to break at.
@@ -5915,7 +5936,11 @@ internal_format (
foundcol = curwin->w_cursor.col;
if (curwin->w_cursor.col <= (colnr_T)wantcol)
break;
- } else if (cc >= 0x100 && fo_multibyte) {
+ } else if ((cc >= 0x100 || !utf_allow_break_before(cc))
+ && fo_multibyte) {
+ int ncc;
+ bool allow_break;
+
// Break after or before a multi-byte character.
if (curwin->w_cursor.col != startcol) {
// Don't break until after the comment leader
@@ -5924,8 +5949,11 @@ internal_format (
}
col = curwin->w_cursor.col;
inc_cursor();
- // Don't change end_foundcol if already set.
- if (foundcol != curwin->w_cursor.col) {
+ ncc = gchar_cursor();
+ allow_break = utf_allow_break(cc, ncc);
+
+ // If we have already checked this position, skip!
+ if (curwin->w_cursor.col != skip_pos && allow_break) {
foundcol = curwin->w_cursor.col;
end_foundcol = foundcol;
if (curwin->w_cursor.col <= (colnr_T)wantcol)
@@ -5937,6 +5965,7 @@ internal_format (
if (curwin->w_cursor.col == 0)
break;
+ ncc = cc;
col = curwin->w_cursor.col;
dec_cursor();
@@ -5945,17 +5974,56 @@ internal_format (
if (WHITECHAR(cc)) {
continue; // break with space
}
- // Don't break until after the comment leader
+ // Don't break until after the comment leader.
if (curwin->w_cursor.col < leader_len) {
break;
}
curwin->w_cursor.col = col;
+ skip_pos = curwin->w_cursor.col;
- foundcol = curwin->w_cursor.col;
- end_foundcol = foundcol;
- if (curwin->w_cursor.col <= (colnr_T)wantcol)
- break;
+ allow_break = utf_allow_break(cc, ncc);
+
+ // Must handle this to respect line break prohibition.
+ if (allow_break) {
+ foundcol = curwin->w_cursor.col;
+ end_foundcol = foundcol;
+ }
+ if (curwin->w_cursor.col <= (colnr_T)wantcol) {
+ const bool ncc_allow_break = utf_allow_break_before(ncc);
+
+ if (allow_break) {
+ break;
+ }
+ if (!ncc_allow_break && !fo_rigor_tw) {
+ // Enable at most 1 punct hang outside of textwidth.
+ if (curwin->w_cursor.col == startcol) {
+ // We are inserting a non-breakable char, postpone
+ // line break check to next insert.
+ end_foundcol = foundcol = 0;
+ break;
+ }
+
+ // Neither cc nor ncc is NUL if we are here, so
+ // it's safe to inc_cursor.
+ col = curwin->w_cursor.col;
+
+ inc_cursor();
+ cc = ncc;
+ ncc = gchar_cursor();
+ // handle insert
+ ncc = (ncc != NUL) ? ncc : c;
+
+ allow_break = utf_allow_break(cc, ncc);
+
+ if (allow_break) {
+ // Break only when we are not at end of line.
+ end_foundcol = foundcol = ncc == NUL? 0 : curwin->w_cursor.col;
+ break;
+ }
+ curwin->w_cursor.col = col;
+ }
+ }
}
if (curwin->w_cursor.col == 0)
break;
@@ -6057,7 +6125,7 @@ internal_format (
}
}
}
- first_line = FALSE;
+ first_line = false;
}
if (State & VREPLACE_FLAG) {
@@ -6244,12 +6312,10 @@ static void check_auto_format(
* Set default to window width (maximum 79) for "gq" operator.
*/
int comp_textwidth(
- int ff // force formatting (for "gq" command)
+ bool ff // force formatting (for "gq" command)
)
{
- int textwidth;
-
- textwidth = curbuf->b_p_tw;
+ int textwidth = curbuf->b_p_tw;
if (textwidth == 0 && curbuf->b_p_wm) {
// The width is the window width minus 'wrapmargin' minus all the
// things that add to the margin.
@@ -7699,6 +7765,10 @@ static bool ins_esc(long *count, int cmdchar, bool nomove)
undisplay_dollar();
}
+ if (cmdchar != 'r' && cmdchar != 'v') {
+ ins_apply_autocmds(EVENT_INSERTLEAVEPRE);
+ }
+
// When an autoindent was removed, curswant stays after the
// indent
if (restart_edit == NUL && (colnr_T)temp == curwin->w_cursor.col) {
@@ -8515,7 +8585,7 @@ static void ins_up(
if (old_topline != curwin->w_topline
|| old_topfill != curwin->w_topfill
)
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
start_arrow(&tpos);
can_cindent = true;
} else {
@@ -8563,7 +8633,7 @@ static void ins_down(
if (old_topline != curwin->w_topline
|| old_topfill != curwin->w_topfill
)
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
start_arrow(&tpos);
can_cindent = true;
} else {
@@ -8957,7 +9027,7 @@ static int ins_ctrl_ey(int tc)
scrolldown_clamp();
else
scrollup_clamp();
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
} else {
c = ins_copychar(curwin->w_cursor.lnum + (c == Ctrl_Y ? -1 : 1));
if (c != NUL) {
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 0cad5fd6c1..45d2bf7a91 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -736,7 +736,7 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv,
if (s == NULL || *s == NUL) {
return FAIL;
}
- if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL,
+ if (call_func(s, -1, rettv, argc, argv, NULL,
0L, 0L, &dummy, true, NULL, NULL) == FAIL) {
return FAIL;
}
@@ -746,7 +746,7 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv,
if (s == NULL || *s == NUL) {
return FAIL;
}
- if (call_func(s, (int)STRLEN(s), rettv, argc, argv, NULL,
+ if (call_func(s, -1, rettv, argc, argv, NULL,
0L, 0L, &dummy, true, partial, NULL) == FAIL) {
return FAIL;
}
@@ -760,7 +760,7 @@ int eval_expr_typval(const typval_T *expr, typval_T *argv,
if (eval1_emsg(&s, rettv, true) == FAIL) {
return FAIL;
}
- if (*s != NUL) { // check for trailing chars after expr
+ if (*skipwhite(s) != NUL) { // check for trailing chars after expr
tv_clear(rettv);
emsgf(_(e_invexpr2), s);
return FAIL;
@@ -1679,7 +1679,7 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
arg = (const char *)find_name_end((char_u *)arg, NULL, NULL,
FNE_INCL_BR | FNE_CHECK_START);
if (!ascii_iswhite(*arg) && !ends_excmd(*arg)) {
- emsg_severe = TRUE;
+ emsg_severe = true;
EMSG(_(e_trailing));
break;
}
@@ -1692,7 +1692,7 @@ static const char *list_arg_vars(exarg_T *eap, const char *arg, int *first)
/* This is mainly to keep test 49 working: when expanding
* curly braces fails overrule the exception error message. */
if (len < 0 && !aborting()) {
- emsg_severe = TRUE;
+ emsg_severe = true;
EMSG2(_(e_invarg2), arg);
break;
}
@@ -2007,7 +2007,7 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
* expression evaluation has been cancelled due to an
* aborting error, an interrupt, or an exception. */
if (!aborting() && !quiet) {
- emsg_severe = TRUE;
+ emsg_severe = true;
EMSG2(_(e_invarg2), name);
return NULL;
}
@@ -2086,6 +2086,7 @@ char_u *get_lval(char_u *const name, typval_T *const rettv,
tv_clear(&var1);
return NULL;
}
+ p = skipwhite(p);
}
// Optionally get the second index [ :expr].
@@ -2674,6 +2675,7 @@ void ex_lockvar(exarg_T *eap)
static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep)
{
char_u *arg = argstart;
+ char_u *name_end;
bool error = false;
lval_T lv;
@@ -2686,43 +2688,43 @@ static void ex_unletlock(exarg_T *eap, char_u *argstart, int deep)
return;
}
os_unsetenv(name);
- arg = skipwhite(arg);
- continue;
- }
-
- // Parse the name and find the end.
- char_u *const name_end = (char_u *)get_lval(arg, NULL, &lv, true,
- eap->skip || error,
- 0, FNE_CHECK_START);
- if (lv.ll_name == NULL) {
- error = true; // error, but continue parsing.
- }
- if (name_end == NULL || (!ascii_iswhite(*name_end)
- && !ends_excmd(*name_end))) {
- if (name_end != NULL) {
- emsg_severe = TRUE;
- EMSG(_(e_trailing));
+ name_end = arg;
+ } else {
+ // Parse the name and find the end.
+ name_end = get_lval(arg, NULL, &lv, true, eap->skip || error,
+ 0, FNE_CHECK_START);
+ if (lv.ll_name == NULL) {
+ error = true; // error, but continue parsing.
+ }
+ if (name_end == NULL
+ || (!ascii_iswhite(*name_end) && !ends_excmd(*name_end))) {
+ if (name_end != NULL) {
+ emsg_severe = true;
+ EMSG(_(e_trailing));
+ }
+ if (!(eap->skip || error)) {
+ clear_lval(&lv);
+ }
+ break;
}
- if (!(eap->skip || error))
- clear_lval(&lv);
- break;
- }
- if (!error && !eap->skip) {
- if (eap->cmdidx == CMD_unlet) {
- if (do_unlet_var(&lv, name_end, eap->forceit) == FAIL)
- error = TRUE;
- } else {
- if (do_lock_var(&lv, name_end, deep,
- eap->cmdidx == CMD_lockvar) == FAIL) {
- error = true;
+ if (!error && !eap->skip) {
+ if (eap->cmdidx == CMD_unlet) {
+ if (do_unlet_var(&lv, name_end, eap->forceit) == FAIL) {
+ error = true;
+ }
+ } else {
+ if (do_lock_var(&lv, name_end, deep,
+ eap->cmdidx == CMD_lockvar) == FAIL) {
+ error = true;
+ }
}
}
- }
-
- if (!eap->skip)
- clear_lval(&lv);
+ if (!eap->skip) {
+ clear_lval(&lv);
+ }
+ }
arg = skipwhite(name_end);
} while (!ends_excmd(*arg));
@@ -2992,7 +2994,6 @@ char_u *get_user_var_name(expand_T *xp, int idx)
static size_t tdone;
static size_t vidx;
static hashitem_T *hi;
- hashtab_T *ht;
if (idx == 0) {
gdone = bdone = wdone = vidx = 0;
@@ -3013,7 +3014,10 @@ char_u *get_user_var_name(expand_T *xp, int idx)
}
// b: variables
- ht = &curbuf->b_vars->dv_hashtab;
+ // In cmdwin, the alternative buffer should be used.
+ hashtab_T *ht = (cmdwin_type != 0 && get_cmdline_type() == NUL)
+ ? &prevwin->w_buffer->b_vars->dv_hashtab
+ : &curbuf->b_vars->dv_hashtab;
if (bdone < ht->ht_used) {
if (bdone++ == 0)
hi = ht->ht_array;
@@ -3025,7 +3029,10 @@ char_u *get_user_var_name(expand_T *xp, int idx)
}
// w: variables
- ht = &curwin->w_vars->dv_hashtab;
+ // In cmdwin, the alternative window should be used.
+ ht = (cmdwin_type != 0 && get_cmdline_type() == NUL)
+ ? &prevwin->w_vars->dv_hashtab
+ : &curwin->w_vars->dv_hashtab;
if (wdone < ht->ht_used) {
if (wdone++ == 0)
hi = ht->ht_array;
@@ -3800,8 +3807,9 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)
*/
for (;; ) {
op = **arg;
- if (op != '*' && op != '/' && op != '%')
+ if (op != '*' && op != '/' && op != '%') {
break;
+ }
if (evaluate) {
if (rettv->v_type == VAR_FLOAT) {
@@ -3903,6 +3911,7 @@ static int eval6(char_u **arg, typval_T *rettv, int evaluate, int want_string)
// (expression) nested expression
// [expr, expr] List
// {key: val, key: val} Dictionary
+// #{key: val, key: val} Dictionary with literal keys
//
// Also handle:
// ! in front logical NOT
@@ -4010,11 +4019,21 @@ static int eval7(
case '[': ret = get_list_tv(arg, rettv, evaluate);
break;
+ // Dictionary: #{key: val, key: val}
+ case '#':
+ if ((*arg)[1] == '{') {
+ (*arg)++;
+ ret = dict_get_tv(arg, rettv, evaluate, true);
+ } else {
+ ret = NOTDONE;
+ }
+ break;
+
// Lambda: {arg, arg -> expr}
- // Dictionary: {key: val, key: val}
+ // Dictionary: {'key': val, 'key': val}
case '{': ret = get_lambda_tv(arg, rettv, evaluate);
if (ret == NOTDONE) {
- ret = dict_get_tv(arg, rettv, evaluate);
+ ret = dict_get_tv(arg, rettv, evaluate, false);
}
break;
@@ -4516,7 +4535,6 @@ int get_option_tv(const char **const arg, typval_T *const rettv,
static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
{
char_u *p;
- char_u *name;
unsigned int extra = 0;
/*
@@ -4524,11 +4542,14 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
*/
for (p = *arg + 1; *p != NUL && *p != '"'; MB_PTR_ADV(p)) {
if (*p == '\\' && p[1] != NUL) {
- ++p;
- /* A "\<x>" form occupies at least 4 characters, and produces up
- * to 6 characters: reserve space for 2 extra */
- if (*p == '<')
- extra += 2;
+ p++;
+ // A "\<x>" form occupies at least 4 characters, and produces up
+ // to 21 characters (3 * 6 for the char and 3 for a modifier):
+ // reserve space for 18 extra.
+ // Each byte in the char could be encoded as K_SPECIAL K_EXTRA x.
+ if (*p == '<') {
+ extra += 18;
+ }
}
}
@@ -4547,7 +4568,8 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
* Copy the string into allocated memory, handling backslashed
* characters.
*/
- name = xmalloc(p - *arg + extra);
+ const int len = (int)(p - *arg + extra);
+ char_u *name = xmalloc(len);
rettv->v_type = VAR_STRING;
rettv->vval.v_string = name;
@@ -4614,6 +4636,9 @@ static int get_string_tv(char_u **arg, typval_T *rettv, int evaluate)
extra = trans_special((const char_u **)&p, STRLEN(p), name, true, true);
if (extra != 0) {
name += extra;
+ if (name >= rettv->vval.v_string + len) {
+ iemsg("get_string_tv() used more space than allocated");
+ }
break;
}
FALLTHROUGH;
@@ -5339,11 +5364,31 @@ static inline bool set_ref_dict(dict_T *dict, int copyID)
return false;
}
-/*
- * Allocate a variable for a Dictionary and fill it from "*arg".
- * Return OK or FAIL. Returns NOTDONE for {expr}.
- */
-static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)
+
+// Get the key for *{key: val} into "tv" and advance "arg".
+// Return FAIL when there is no valid key.
+static int get_literal_key(char_u **arg, typval_T *tv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ char_u *p;
+
+ if (!ASCII_ISALNUM(**arg) && **arg != '_' && **arg != '-') {
+ return FAIL;
+ }
+ for (p = *arg; ASCII_ISALNUM(*p) || *p == '_' || *p == '-'; p++) {
+ }
+ tv->v_type = VAR_STRING;
+ tv->vval.v_string = vim_strnsave(*arg, (int)(p - *arg));
+
+ *arg = skipwhite(p);
+ return OK;
+}
+
+// Allocate a variable for a Dictionary and fill it from "*arg".
+// "literal" is true for *{key: val}
+// Return OK or FAIL. Returns NOTDONE for {expr}.
+static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate,
+ bool literal)
{
dict_T *d = NULL;
typval_T tvkey;
@@ -5364,7 +5409,7 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)
if (eval1(&start, &tv, false) == FAIL) { // recursive!
return FAIL;
}
- if (*start == '}') {
+ if (*skipwhite(start) == '}') {
return NOTDONE;
}
}
@@ -5377,7 +5422,9 @@ static int dict_get_tv(char_u **arg, typval_T *rettv, int evaluate)
*arg = skipwhite(*arg + 1);
while (**arg != '}' && **arg != NUL) {
- if (eval1(arg, &tvkey, evaluate) == FAIL) { // recursive!
+ if ((literal
+ ? get_literal_key(arg, &tvkey)
+ : eval1(arg, &tvkey, evaluate)) == FAIL) { // recursive!
goto failret;
}
if (**arg != ':') {
@@ -6960,9 +7007,10 @@ void set_buffer_lines(buf_T *buf, linenr_T lnum_arg, bool append,
if (!append && lnum <= curbuf->b_ml.ml_line_count) {
// Existing line, replace it.
+ int old_len = (int)STRLEN(ml_get(lnum));
if (u_savesub(lnum) == OK
&& ml_replace(lnum, (char_u *)line, true) == OK) {
- changed_bytes(lnum, 0);
+ inserted_bytes(lnum, 0, old_len, STRLEN(line));
if (is_curbuf && lnum == curwin->w_cursor.lnum) {
check_cursor_col();
}
@@ -7073,7 +7121,7 @@ void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
do {
size_t dir_len;
const char *dir;
- iter = vim_env_iter(':', dirs, iter, &dir, &dir_len);
+ iter = vim_env_iter(ENV_SEPCHAR, dirs, iter, &dir, &dir_len);
if (dir != NULL && dir_len > 0) {
char *dir_with_nvim = xmemdupz(dir, dir_len);
dir_with_nvim = concat_fnames_realloc(dir_with_nvim, "nvim", true);
@@ -7104,7 +7152,7 @@ void get_system_output_as_rettv(typval_T *argvars, typval_T *rettv,
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL;
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -7261,7 +7309,7 @@ bool callback_call(Callback *const callback, const int argcount_in,
}
int dummy;
- return call_func(name, (int)STRLEN(name), rettv, argcount_in, argvars_in,
+ return call_func(name, -1, rettv, argcount_in, argvars_in,
NULL, curwin->w_cursor.lnum, curwin->w_cursor.lnum, &dummy,
true, partial, NULL);
}
@@ -8483,7 +8531,7 @@ handle_subscript(
} else {
s = (char_u *)"";
}
- ret = get_func_tv(s, lua ? slen : (int)STRLEN(s), rettv, (char_u **)arg,
+ ret = get_func_tv(s, lua ? slen : -1, rettv, (char_u **)arg,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&len, evaluate, pt, selfdict);
@@ -9044,7 +9092,7 @@ static void set_var_const(const char *name, const size_t name_len,
}
if (is_const) {
- v->di_tv.v_lock |= VAR_LOCKED;
+ tv_item_lock(&v->di_tv, 1, true);
}
}
@@ -10370,7 +10418,7 @@ Channel *find_job(uint64_t id, bool show_error)
void script_host_eval(char *name, typval_T *argvars, typval_T *rettv)
{
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -10381,10 +10429,13 @@ void script_host_eval(char *name, typval_T *argvars, typval_T *rettv)
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);
+ *rettv = eval_call_provider(name, "eval", args, false);
}
-typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
+/// @param discard Clears the value returned by the provider and returns
+/// an empty typval_T.
+typval_T eval_call_provider(char *provider, char *method, list_T *arguments,
+ bool discard)
{
if (!eval_has_provider(provider)) {
emsgf("E319: No \"%s\" provider found. Run \":checkhealth provider\"",
@@ -10443,6 +10494,10 @@ typval_T eval_call_provider(char *provider, char *method, list_T *arguments)
provider_call_nesting--;
assert(provider_call_nesting >= 0);
+ if (discard) {
+ tv_clear(&rettv);
+ }
+
return rettv;
}
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 023c60f118..6c316bb1fe 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -191,6 +191,7 @@ return {
inputsave={},
inputsecret={args={1, 2}},
insert={args={2, 3}},
+ interrupt={args=0},
invert={args=1},
isdirectory={args=1},
isinf={args=1},
@@ -255,6 +256,7 @@ return {
py3eval={args=1},
pyeval={args=1},
pyxeval={args=1},
+ perleval={args=1},
range={args={1, 3}},
readdir={args={1, 2}},
readfile={args={1, 3}},
@@ -273,6 +275,7 @@ return {
rpcrequest={args=varargs(2)},
rpcstart={args={1, 2}},
rpcstop={args=1},
+ rubyeval={args=1},
screenattr={args=2},
screenchar={args=2},
screencol={},
@@ -335,7 +338,7 @@ return {
stridx={args={2, 3}},
string={args=1},
strlen={args=1},
- strpart={args={2, 3}},
+ strpart={args={2, 4}},
strridx={args={2, 3}},
strtrans={args=1},
strwidth={args=1},
@@ -369,7 +372,7 @@ return {
tolower={args=1},
toupper={args=1},
tr={args=3},
- trim={args={1,2}},
+ trim={args={1,3}},
trunc={args=1, func="float_op_wrapper", data="&trunc"},
type={args=1},
undofile={args=1},
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c
index daba304f00..638fef331a 100644
--- a/src/nvim/eval/decode.c
+++ b/src/nvim/eval/decode.c
@@ -586,7 +586,7 @@ parse_json_number_check:
if (p == ints) {
emsgf(_("E474: Missing number after minus sign: %.*s"), LENP(s, e));
goto parse_json_number_fail;
- } else if (p == fracs || exps_s == fracs + 1) {
+ } else if (p == fracs || (fracs != NULL && exps_s == fracs + 1)) {
emsgf(_("E474: Missing number after decimal dot: %.*s"), LENP(s, e));
goto parse_json_number_fail;
} else if (p == exps) {
diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c
index 137f099df6..9a9f2e4287 100644
--- a/src/nvim/eval/encode.c
+++ b/src/nvim/eval/encode.c
@@ -159,8 +159,11 @@ static int conv_error(const char *const msg, const MPConvStack *const mpstack,
vim_snprintf((char *)IObuff, IOSIZE, idx_msg, idx);
ga_concat(&msg_ga, IObuff);
} else {
- typval_T key_tv = *TV_LIST_ITEM_TV(
- tv_list_first(TV_LIST_ITEM_TV(li)->vval.v_list));
+ assert(li != NULL);
+ listitem_T *const first_item =
+ tv_list_first(TV_LIST_ITEM_TV(li)->vval.v_list);
+ assert(first_item != NULL);
+ typval_T key_tv = *TV_LIST_ITEM_TV(first_item);
char *const key = encode_tv2echo(&key_tv, NULL);
vim_snprintf((char *) IObuff, IOSIZE, key_pair_msg, key, idx);
xfree(key);
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index e350d09935..679548ab91 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -32,6 +32,7 @@
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/lua/executor.h"
+#include "nvim/macros.h"
#include "nvim/mark.h"
#include "nvim/math.h"
#include "nvim/memline.h"
@@ -86,8 +87,10 @@ KHASH_MAP_INIT_STR(functions, VimLFuncDef)
#endif
PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES
+PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH
#include "funcs.generated.h"
PRAGMA_DIAG_POP
+PRAGMA_DIAG_POP
#endif
@@ -202,7 +205,7 @@ static void float_op_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void api_wrapper(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -859,7 +862,7 @@ static void f_chanclose(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = 0;
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -898,7 +901,7 @@ static void f_chansend(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = 0;
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -1477,7 +1480,7 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv, FunPtr fptr)
static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
rettv->vval.v_number = -1;
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -1512,7 +1515,7 @@ static void f_delete(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// dictwatcheradd(dict, key, funcref) function
static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -1550,7 +1553,7 @@ static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// dictwatcherdel(dict, key, funcref) function
static void f_dictwatcherdel(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -2068,6 +2071,12 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
expand_T xpc;
bool error = false;
char_u *result;
+#ifdef BACKSLASH_IN_FILENAME
+ char_u *p_csl_save = p_csl;
+
+ // avoid using 'completeslash' here
+ p_csl = empty_option;
+#endif
rettv->v_type = VAR_STRING;
if (argvars[1].v_type != VAR_UNKNOWN
@@ -2120,6 +2129,9 @@ static void f_expand(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_string = NULL;
}
}
+#ifdef BACKSLASH_IN_FILENAME
+ p_csl = p_csl_save;
+#endif
}
@@ -2407,9 +2419,9 @@ static void f_float2nr(typval_T *argvars, typval_T *rettv, FunPtr fptr)
float_T f;
if (tv_get_float_chk(argvars, &f)) {
- if (f <= -VARNUMBER_MAX + DBL_EPSILON) {
+ if (f <= (float_T)-VARNUMBER_MAX + DBL_EPSILON) {
rettv->vval.v_number = -VARNUMBER_MAX;
- } else if (f >= VARNUMBER_MAX - DBL_EPSILON) {
+ } else if (f >= (float_T)VARNUMBER_MAX - DBL_EPSILON) {
rettv->vval.v_number = VARNUMBER_MAX;
} else {
rettv->vval.v_number = (varnumber_T)f;
@@ -2581,8 +2593,6 @@ static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
char_u *text;
char_u buf[FOLD_TEXT_LEN];
- foldinfo_T foldinfo;
- int fold_count;
static bool entered = false;
rettv->v_type = VAR_STRING;
@@ -2596,9 +2606,10 @@ static void f_foldtextresult(typval_T *argvars, typval_T *rettv, FunPtr fptr)
if (lnum < 0) {
lnum = 0;
}
- fold_count = foldedCount(curwin, lnum, &foldinfo);
- if (fold_count > 0) {
- text = get_foldtext(curwin, lnum, lnum + fold_count - 1, &foldinfo, buf);
+
+ foldinfo_T info = fold_info(curwin, lnum);
+ if (info.fi_lines > 0) {
+ text = get_foldtext(curwin, lnum, lnum + info.fi_lines - 1, info, buf);
if (text == buf) {
text = vim_strsave(text);
}
@@ -3387,63 +3398,23 @@ static void f_getftype(typval_T *argvars, typval_T *rettv, FunPtr fptr)
FileInfo file_info;
if (os_fileinfo_link(fname, &file_info)) {
uint64_t mode = file_info.stat.st_mode;
-#ifdef S_ISREG
- if (S_ISREG(mode))
+ if (S_ISREG(mode)) {
t = "file";
- else if (S_ISDIR(mode))
+ } else if (S_ISDIR(mode)) {
t = "dir";
-# ifdef S_ISLNK
- else if (S_ISLNK(mode))
+ } else if (S_ISLNK(mode)) {
t = "link";
-# endif
-# ifdef S_ISBLK
- else if (S_ISBLK(mode))
+ } else if (S_ISBLK(mode)) {
t = "bdev";
-# endif
-# ifdef S_ISCHR
- else if (S_ISCHR(mode))
+ } else if (S_ISCHR(mode)) {
t = "cdev";
-# endif
-# ifdef S_ISFIFO
- else if (S_ISFIFO(mode))
+ } else if (S_ISFIFO(mode)) {
t = "fifo";
-# endif
-# ifdef S_ISSOCK
- else if (S_ISSOCK(mode))
+ } else if (S_ISSOCK(mode)) {
t = "socket";
-# endif
- else
- t = "other";
-#else
-# ifdef S_IFMT
- switch (mode & S_IFMT) {
- case S_IFREG: t = "file"; break;
- case S_IFDIR: t = "dir"; break;
-# ifdef S_IFLNK
- case S_IFLNK: t = "link"; break;
-# endif
-# ifdef S_IFBLK
- case S_IFBLK: t = "bdev"; break;
-# endif
-# ifdef S_IFCHR
- case S_IFCHR: t = "cdev"; break;
-# endif
-# ifdef S_IFIFO
- case S_IFIFO: t = "fifo"; break;
-# endif
-# ifdef S_IFSOCK
- case S_IFSOCK: t = "socket"; break;
-# endif
- default: t = "other";
- }
-# else
- if (os_isdir((const char_u *)fname)) {
- t = "dir";
} else {
- t = "file";
+ t = "other";
}
-# endif
-#endif
type = vim_strsave((char_u *)t);
}
rettv->vval.v_string = type;
@@ -4005,7 +3976,7 @@ static void f_glob(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "globpath()" function
static void f_globpath(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- int flags = 0; // Flags for globpath.
+ int flags = WILD_IGNORE_COMPLETESLASH; // Flags for globpath.
bool error = false;
// Return a string, or a list if the optional third argument is non-zero.
@@ -4717,6 +4688,14 @@ static void f_insert(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+// "interrupt()" function
+static void f_interrupt(typval_T *argvars FUNC_ATTR_UNUSED,
+ typval_T *rettv FUNC_ATTR_UNUSED,
+ FunPtr fptr FUNC_ATTR_UNUSED)
+{
+ got_int = true;
+}
+
/*
* "invert(expr)" function
*/
@@ -4819,7 +4798,7 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = 0;
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -4843,7 +4822,7 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = 0;
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -4876,7 +4855,7 @@ static void f_jobstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = 0;
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -5009,7 +4988,7 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = 0;
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -5042,7 +5021,7 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = 0;
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
if (argvars[0].v_type != VAR_LIST || (argvars[1].v_type != VAR_NUMBER
@@ -5260,7 +5239,7 @@ static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type)
rettv->vval.v_string = NULL;
}
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -5277,7 +5256,7 @@ static void libcall_common(typval_T *argvars, typval_T *rettv, int out_type)
// input variables
char *str_in = (in_type == VAR_STRING)
? (char *)argvars[2].vval.v_string : NULL;
- int64_t int_in = argvars[2].vval.v_number;
+ int int_in = argvars[2].vval.v_number;
// output variables
char **str_out = (out_type == VAR_STRING)
@@ -5471,7 +5450,7 @@ static void f_luaeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
return;
}
- executor_eval_lua(cstr_as_string((char *)str), &argvars[1], rettv);
+ nlua_typval_eval(cstr_as_string((char *)str), &argvars[1], rettv);
}
/*
@@ -5963,8 +5942,9 @@ static void f_mkdir(typval_T *argvars, typval_T *rettv, FunPtr fptr)
int prot = 0755; // -V536
rettv->vval.v_number = FAIL;
- if (check_restricted() || check_secure())
+ if (check_secure()) {
return;
+ }
char buf[NUMBUFLEN];
const char *const dir = tv_get_string_buf(&argvars[0], buf);
@@ -6363,6 +6343,20 @@ static void f_pyxeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
}
+///
+/// "perleval()" function
+///
+static void f_perleval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ script_host_eval("perl", argvars, rettv);
+}
+
+// "rubyeval()" function
+static void f_rubyeval(typval_T *argvars, typval_T *rettv, FunPtr fptr)
+{
+ script_host_eval("ruby", argvars, rettv);
+}
+
/*
* "range()" function
*/
@@ -6839,7 +6833,7 @@ static void f_remove(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
static void f_rename(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
rettv->vval.v_number = -1;
} else {
char buf[NUMBUFLEN];
@@ -6918,7 +6912,7 @@ static void f_resolve(typval_T *argvars, typval_T *rettv, FunPtr fptr)
}
ptrdiff_t len = (ptrdiff_t)strlen(p);
- if (len > 0 && after_pathsep(p, p + len)) {
+ if (len > 1 && after_pathsep(p, p + len)) {
has_trailing_pathsep = true;
p[len - 1] = NUL; // The trailing slash breaks readlink().
}
@@ -7237,7 +7231,7 @@ static void f_rpcnotify(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = 0;
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -7273,7 +7267,7 @@ static void f_rpcrequest(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->vval.v_number = 0;
const int l_provider_call_nesting = provider_call_nesting;
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -7370,7 +7364,7 @@ static void f_rpcstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = 0;
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -7436,7 +7430,7 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->v_type = VAR_NUMBER;
rettv->vval.v_number = 0;
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -7650,7 +7644,7 @@ static int searchpair_cmn(typval_T *argvars, pos_T *match_pos)
}
retval = do_searchpair(
- (char_u *)spat, (char_u *)mpat, (char_u *)epat, dir, skip,
+ spat, mpat, epat, dir, skip,
flags, match_pos, lnum_stop, time_limit);
theend:
@@ -7694,9 +7688,9 @@ static void f_searchpairpos(typval_T *argvars, typval_T *rettv, FunPtr fptr)
*/
long
do_searchpair(
- char_u *spat, // start pattern
- char_u *mpat, // middle pattern
- char_u *epat, // end pattern
+ const char *spat, // start pattern
+ const char *mpat, // middle pattern
+ const char *epat, // end pattern
int dir, // BACKWARD or FORWARD
const typval_T *skip, // skip expression
int flags, // SP_SETPCMARK and other SP_ values
@@ -7704,6 +7698,7 @@ do_searchpair(
linenr_T lnum_stop, // stop at this line if not zero
long time_limit // stop after this many msec
)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 3)
{
char_u *save_cpo;
char_u *pat, *pat2 = NULL, *pat3 = NULL;
@@ -7718,8 +7713,6 @@ do_searchpair(
bool use_skip = false;
int options = SEARCH_KEEP;
proftime_T tm;
- size_t pat2_len;
- size_t pat3_len;
// Make 'cpoptions' empty, the 'l' flag should not be used here.
save_cpo = p_cpo;
@@ -7730,9 +7723,9 @@ do_searchpair(
// Make two search patterns: start/end (pat2, for in nested pairs) and
// start/middle/end (pat3, for the top pair).
- pat2_len = STRLEN(spat) + STRLEN(epat) + 17;
+ const size_t pat2_len = strlen(spat) + strlen(epat) + 17;
pat2 = xmalloc(pat2_len);
- pat3_len = STRLEN(spat) + STRLEN(mpat) + STRLEN(epat) + 25;
+ const size_t pat3_len = strlen(spat) + strlen(mpat) + strlen(epat) + 25;
pat3 = xmalloc(pat3_len);
snprintf((char *)pat2, pat2_len, "\\m\\(%s\\m\\)\\|\\(%s\\m\\)", spat, epat);
if (*mpat == NUL) {
@@ -7899,7 +7892,7 @@ static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
rettv->v_type = VAR_STRING;
rettv->vval.v_string = NULL; // Address of the new server
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -7941,7 +7934,7 @@ static void f_serverstart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// "serverstop()" function
static void f_serverstop(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -8124,15 +8117,17 @@ static void f_setline(typval_T *argvars, typval_T *rettv, FunPtr fptr)
/// Create quickfix/location list from VimL values
///
/// Used by `setqflist()` and `setloclist()` functions. Accepts invalid
-/// list_arg, action_arg and what_arg arguments in which case errors out,
-/// including VAR_UNKNOWN parameters.
+/// args argument in which case errors out, including VAR_UNKNOWN parameters.
///
/// @param[in,out] wp Window to create location list for. May be NULL in
/// which case quickfix list will be created.
-/// @param[in] list_arg Quickfix list contents.
-/// @param[in] action_arg Action to perform: append to an existing list,
-/// replace its content or create a new one.
-/// @param[in] title_arg New list title. Defaults to caller function name.
+/// @param[in] args [list, action, what]
+/// @param[in] args[0] Quickfix list contents.
+/// @param[in] args[1] Optional. Action to perform:
+/// append to an existing list, replace its content,
+/// or create a new one.
+/// @param[in] args[2] Optional. Quickfix list properties or title.
+/// Defaults to caller function name.
/// @param[out] rettv Return value: 0 in case of success, -1 otherwise.
static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
FUNC_ATTR_NONNULL_ARG(2, 3)
@@ -8142,7 +8137,7 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
int action = ' ';
static int recursive = 0;
rettv->vval.v_number = -1;
- dict_T *d = NULL;
+ dict_T *what = NULL;
typval_T *list_arg = &args[0];
if (list_arg->v_type != VAR_LIST) {
@@ -8170,18 +8165,18 @@ static void set_qf_ll_list(win_T *wp, typval_T *args, typval_T *rettv)
return;
}
- typval_T *title_arg = &args[2];
- if (title_arg->v_type == VAR_UNKNOWN) {
+ typval_T *const what_arg = &args[2];
+ if (what_arg->v_type == VAR_UNKNOWN) {
// Option argument was not given.
goto skip_args;
- } else if (title_arg->v_type == VAR_STRING) {
- title = tv_get_string_chk(title_arg);
+ } else if (what_arg->v_type == VAR_STRING) {
+ title = tv_get_string_chk(what_arg);
if (!title) {
// Type error. Error already printed by tv_get_string_chk().
return;
}
- } else if (title_arg->v_type == VAR_DICT) {
- d = title_arg->vval.v_dict;
+ } else if (what_arg->v_type == VAR_DICT && what_arg->vval.v_dict != NULL) {
+ what = what_arg->vval.v_dict;
} else {
EMSG(_(e_dictreq));
return;
@@ -8194,7 +8189,7 @@ skip_args:
recursive++;
list_T *const l = list_arg->vval.v_list;
- if (set_errorlist(wp, l, action, (char_u *)title, d) == OK) {
+ if (set_errorlist(wp, l, action, (char_u *)title, what) == OK) {
rettv->vval.v_number = 0;
}
recursive--;
@@ -9156,7 +9151,7 @@ static int item_compare2(const void *s1, const void *s2, bool keep_zero)
rettv.v_type = VAR_UNKNOWN; // tv_clear() uses this
res = call_func((const char_u *)func_name,
- (int)STRLEN(func_name),
+ -1,
&rettv, 2, argv, NULL, 0L, 0L, &dummy, true,
partial, sortinfo->item_compare_selfdict);
tv_clear(&argv[0]);
@@ -9519,7 +9514,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr)
tv_list_alloc_ret(rettv, kListLenMayKnow);
if (typeerr) {
- return;
+ goto theend;
}
regmatch_T regmatch = {
@@ -9563,6 +9558,7 @@ static void f_split(typval_T *argvars, typval_T *rettv, FunPtr fptr)
vim_regfree(regmatch.regprog);
}
+theend:
p_cpo = save_cpo;
}
@@ -9937,6 +9933,16 @@ static void f_strpart(typval_T *argvars, typval_T *rettv, FunPtr fptr)
len = slen - n;
}
+ if (argvars[2].v_type != VAR_UNKNOWN && argvars[3].v_type != VAR_UNKNOWN) {
+ int off;
+
+ // length in characters
+ for (off = n; off < (int)slen && len > 0; len--) {
+ off += utfc_ptr2len((char_u *)p + off);
+ }
+ len = off - n;
+ }
+
rettv->v_type = VAR_STRING;
rettv->vval.v_string = (char_u *)xmemdupz(p + n, (size_t)len);
}
@@ -10461,7 +10467,7 @@ static void f_tempname(typval_T *argvars, typval_T *rettv, FunPtr fptr)
// "termopen(cmd[, cwd])" function
static void f_termopen(typval_T *argvars, typval_T *rettv, FunPtr fptr)
{
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
return;
}
@@ -10795,52 +10801,72 @@ static void f_trim(typval_T *argvars, typval_T *rettv, FunPtr fptr)
const char_u *prev;
const char_u *p;
int c1;
+ int dir = 0;
rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
if (head == NULL) {
- rettv->vval.v_string = NULL;
return;
}
if (argvars[1].v_type == VAR_STRING) {
mask = (const char_u *)tv_get_string_buf_chk(&argvars[1], buf2);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ bool error = false;
+ // leading or trailing characters to trim
+ dir = (int)tv_get_number_chk(&argvars[2], &error);
+ if (error) {
+ return;
+ }
+ if (dir < 0 || dir > 2) {
+ emsgf(_(e_invarg2), tv_get_string(&argvars[2]));
+ return;
+ }
+ }
}
- while (*head != NUL) {
- c1 = PTR2CHAR(head);
- if (mask == NULL) {
- if (c1 > ' ' && c1 != 0xa0) {
- break;
- }
- } else {
- for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
- if (c1 == PTR2CHAR(p)) {
+ if (dir == 0 || dir == 1) {
+ // Trim leading characters
+ while (*head != NUL) {
+ c1 = PTR2CHAR(head);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == PTR2CHAR(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
break;
}
}
- if (*p == NUL) {
- break;
- }
+ MB_PTR_ADV(head);
}
- MB_PTR_ADV(head);
}
- for (tail = head + STRLEN(head); tail > head; tail = prev) {
- prev = tail;
- MB_PTR_BACK(head, prev);
- c1 = PTR2CHAR(prev);
- if (mask == NULL) {
- if (c1 > ' ' && c1 != 0xa0) {
- break;
- }
- } else {
- for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
- if (c1 == PTR2CHAR(p)) {
+ tail = head + STRLEN(head);
+ if (dir == 0 || dir == 2) {
+ // Trim trailing characters
+ for (; tail > head; tail = prev) {
+ prev = tail;
+ MB_PTR_BACK(head, prev);
+ c1 = PTR2CHAR(prev);
+ if (mask == NULL) {
+ if (c1 > ' ' && c1 != 0xa0) {
+ break;
+ }
+ } else {
+ for (p = mask; *p != NUL; MB_PTR_ADV(p)) {
+ if (c1 == PTR2CHAR(p)) {
+ break;
+ }
+ }
+ if (*p == NUL) {
break;
}
- }
- if (*p == NUL) {
- break;
}
}
}
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index 8dde78de3d..ada6f78f10 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -1358,7 +1358,8 @@ void tv_dict_item_remove(dict_T *const dict, dictitem_T *const item)
//{{{2 Alloc/free
-/// Allocate an empty dictionary
+/// Allocate an empty dictionary.
+/// Caller should take care of the reference count.
///
/// @return [allocated] new dictionary.
dict_T *tv_dict_alloc(void)
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index 503a32a81e..1e3e9bd366 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -301,7 +301,8 @@ struct funccall_S {
int dbg_tick; ///< Debug_tick when breakpoint was set.
int level; ///< Top nesting level of executed function.
proftime_T prof_child; ///< Time spent in a child.
- funccall_T *caller; ///< Calling function or NULL.
+ funccall_T *caller; ///< Calling function or NULL; or next funccal in
+ ///< list pointed to by previous_funccal.
int fc_refcount; ///< Number of user functions that reference this funccall.
int fc_copyID; ///< CopyID used for garbage collection.
garray_T fc_funcs; ///< List of ufunc_T* which keep a reference to "func".
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 229f0e8dde..dc94bc698d 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -32,7 +32,11 @@
#define FC_DELETED 0x10 // :delfunction used while uf_refcount > 0
#define FC_REMOVED 0x20 // function redefined while uf_refcount > 0
#define FC_SANDBOX 0x40 // function defined in the sandbox
-#define FC_CFUNC 0x80 // C function extension
+#define FC_DEAD 0x80 // function kept only for reference to dfunc
+#define FC_EXPORT 0x100 // "export def Func()"
+#define FC_NOARGS 0x200 // no a: variables in lambda
+#define FC_VIM9 0x400 // defined in vim9 script file
+#define FC_CFUNC 0x800 // C function extension
#ifdef INCLUDE_GENERATED_DECLARATIONS
#include "eval/userfunc.c.generated.h"
@@ -246,6 +250,10 @@ int get_lambda_tv(char_u **arg, typval_T *rettv, bool evaluate)
((char_u **)(newlines.ga_data))[newlines.ga_len++] = p;
STRCPY(p, "return ");
STRLCPY(p + 7, s, e - s + 1);
+ if (strstr((char *)p + 7, "a:") == NULL) {
+ // No a: variables are used for sure.
+ flags |= FC_NOARGS;
+ }
fp->uf_refcount = 1;
STRCPY(fp->uf_name, name);
@@ -367,7 +375,7 @@ void emsg_funcname(char *ermsg, const char_u *name)
int
get_func_tv(
const char_u *name, // name of the function
- int len, // length of "name"
+ int len, // length of "name" or -1 to use strlen()
typval_T *rettv,
char_u **arg, // argument, pointing to the '('
linenr_T firstline, // first line of range
@@ -813,17 +821,12 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
current_funccal = fc;
fc->func = fp;
fc->rettv = rettv;
- rettv->vval.v_number = 0;
- fc->linenr = 0;
- fc->returned = FALSE;
fc->level = ex_nesting_level;
// Check if this function has a breakpoint.
fc->breakpoint = dbg_find_breakpoint(false, fp->uf_name, (linenr_T)0);
fc->dbg_tick = debug_tick;
// Set up fields for closure.
- fc->fc_refcount = 0;
- fc->fc_copyID = 0;
ga_init(&fc->fc_funcs, sizeof(ufunc_T *), 1);
func_ptr_ref(fp);
@@ -853,37 +856,42 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
++selfdict->dv_refcount;
}
- /*
- * Init a: variables.
- * Set a:0 to "argcount".
- * Set a:000 to a list with room for the "..." arguments.
- */
+ // Init a: variables, unless none found (in lambda).
+ // Set a:0 to "argcount".
+ // Set a:000 to a list with room for the "..." arguments.
init_var_dict(&fc->l_avars, &fc->l_avars_var, VAR_SCOPE);
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0",
- (varnumber_T)(argcount - fp->uf_args.ga_len));
+ if ((fp->uf_flags & FC_NOARGS) == 0) {
+ add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++], "0",
+ (varnumber_T)(argcount - fp->uf_args.ga_len));
+ }
fc->l_avars.dv_lock = VAR_FIXED;
- // Use "name" to avoid a warning from some compiler that checks the
- // destination size.
- v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
+ if ((fp->uf_flags & FC_NOARGS) == 0) {
+ // Use "name" to avoid a warning from some compiler that checks the
+ // destination size.
+ v = (dictitem_T *)&fc->fixvar[fixvar_idx++];
#ifndef __clang_analyzer__
- name = v->di_key;
- STRCPY(name, "000");
+ name = v->di_key;
+ STRCPY(name, "000");
#endif
- v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
- tv_dict_add(&fc->l_avars, v);
- v->di_tv.v_type = VAR_LIST;
- v->di_tv.v_lock = VAR_FIXED;
- v->di_tv.vval.v_list = &fc->l_varlist;
+ v->di_flags = DI_FLAGS_RO | DI_FLAGS_FIX;
+ tv_dict_add(&fc->l_avars, v);
+ v->di_tv.v_type = VAR_LIST;
+ v->di_tv.v_lock = VAR_FIXED;
+ v->di_tv.vval.v_list = &fc->l_varlist;
+ }
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.
// Set a:N to the "..." arguments.
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
- "firstline", (varnumber_T)firstline);
- add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
- "lastline", (varnumber_T)lastline);
+ // Skipped when no a: variables used (in lambda).
+ if ((fp->uf_flags & FC_NOARGS) == 0) {
+ add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
+ "firstline", (varnumber_T)firstline);
+ add_nr_var(&fc->l_avars, (dictitem_T *)&fc->fixvar[fixvar_idx++],
+ "lastline", (varnumber_T)lastline);
+ }
for (int i = 0; i < argcount; i++) {
bool addlocal = false;
@@ -895,6 +903,10 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
addlocal = true;
}
} else {
+ if ((fp->uf_flags & FC_NOARGS) != 0) {
+ // Bail out if no a: arguments used (in lambda).
+ break;
+ }
// "..." argument a:1, a:2, etc.
snprintf((char *)numbuf, sizeof(numbuf), "%d", ai + 1);
name = numbuf;
@@ -1034,9 +1046,19 @@ void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars,
save_did_emsg = did_emsg;
did_emsg = FALSE;
- // call do_cmdline() to execute the lines
- do_cmdline(NULL, get_func_line, (void *)fc,
- DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
+ if (islambda) {
+ char_u *p = *(char_u **)fp->uf_lines.ga_data + 7;
+
+ // A Lambda always has the command "return {expr}". It is much faster
+ // to evaluate {expr} directly.
+ ex_nesting_level++;
+ (void)eval1(&p, rettv, true);
+ ex_nesting_level--;
+ } else {
+ // call do_cmdline() to execute the lines
+ do_cmdline(NULL, get_func_line, (void *)fc,
+ DOCMD_NOWAIT|DOCMD_VERBOSE|DOCMD_REPEAT);
+ }
--RedrawingDisabled;
@@ -1291,7 +1313,7 @@ int func_call(char_u *name, typval_T *args, partial_T *partial,
tv_copy(TV_LIST_ITEM_TV(item), &argv[argc++]);
});
- r = call_func(name, (int)STRLEN(name), rettv, argc, argv, NULL,
+ r = call_func(name, -1, rettv, argc, argv, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&dummy, true, partial, selfdict);
@@ -1304,6 +1326,36 @@ func_call_skip_call:
return r;
}
+// Give an error message for the result of a function.
+// Nothing if "error" is FCERR_NONE.
+static void user_func_error(int error, const char_u *name)
+ FUNC_ATTR_NONNULL_ALL
+{
+ switch (error) {
+ case ERROR_UNKNOWN:
+ emsg_funcname(N_("E117: Unknown function: %s"), name);
+ break;
+ case ERROR_DELETED:
+ emsg_funcname(N_("E933: Function was deleted: %s"), name);
+ break;
+ case ERROR_TOOMANY:
+ emsg_funcname(_(e_toomanyarg), name);
+ break;
+ case ERROR_TOOFEW:
+ emsg_funcname(N_("E119: Not enough arguments for function: %s"),
+ name);
+ break;
+ case ERROR_SCRIPT:
+ emsg_funcname(N_("E120: Using <SID> not in a script context: %s"),
+ name);
+ break;
+ case ERROR_DICT:
+ emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"),
+ name);
+ break;
+ }
+}
+
/// Call a function with its resolved parameters
///
/// "argv_func", when not NULL, can be used to fill in arguments only when the
@@ -1316,7 +1368,7 @@ func_call_skip_call:
int
call_func(
const char_u *funcname, // name of the function
- int len, // length of "name"
+ int len, // length of "name" or -1 to use strlen()
typval_T *rettv, // [out] value goes here
int argcount_in, // number of "argvars"
typval_T *argvars_in, // vars for arguments, must have "argcount"
@@ -1333,11 +1385,11 @@ call_func(
{
int ret = FAIL;
int error = ERROR_NONE;
- ufunc_T *fp;
+ ufunc_T *fp = NULL;
char_u fname_buf[FLEN_FIXED + 1];
char_u *tofree = NULL;
- char_u *fname;
- char_u *name;
+ char_u *fname = NULL;
+ char_u *name = NULL;
int argcount = argcount_in;
typval_T *argvars = argvars_in;
dict_T *selfdict = selfdict_in;
@@ -1348,11 +1400,18 @@ call_func(
// even when call_func() returns FAIL.
rettv->v_type = VAR_UNKNOWN;
- // Make a copy of the name, if it comes from a funcref variable it could
- // be changed or deleted in the called function.
- name = vim_strnsave(funcname, len);
-
- fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+ if (len <= 0) {
+ len = (int)STRLEN(funcname);
+ }
+ if (partial != NULL) {
+ fp = partial->pt_func;
+ }
+ if (fp == NULL) {
+ // Make a copy of the name, if it comes from a funcref variable it could
+ // be changed or deleted in the called function.
+ name = vim_strnsave(funcname, len);
+ fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+ }
*doesrange = false;
@@ -1384,7 +1443,7 @@ call_func(
char_u *rfname = fname;
// Ignore "g:" before a function name.
- if (fname[0] == 'g' && fname[1] == ':') {
+ if (fp == NULL && fname[0] == 'g' && fname[1] == ':') {
rfname = fname + 2;
}
@@ -1395,14 +1454,11 @@ call_func(
if (is_luafunc(partial)) {
if (len > 0) {
error = ERROR_NONE;
- executor_call_lua((const char *)funcname, len,
- argvars, argcount, rettv);
+ nlua_typval_call((const char *)funcname, len, argvars, argcount, rettv);
}
- } else if (!builtin_function((const char *)rfname, -1)) {
+ } else if (fp != NULL || !builtin_function((const char *)rfname, -1)) {
// User defined function.
- if (partial != NULL && partial->pt_func != NULL) {
- fp = partial->pt_func;
- } else {
+ if (fp == NULL) {
fp = find_func(rfname);
}
@@ -1481,29 +1537,7 @@ theend:
// Report an error unless the argument evaluation or function call has been
// cancelled due to an aborting error, an interrupt, or an exception.
if (!aborting()) {
- switch (error) {
- case ERROR_UNKNOWN:
- emsg_funcname(N_("E117: Unknown function: %s"), name);
- break;
- case ERROR_DELETED:
- emsg_funcname(N_("E933: Function was deleted: %s"), name);
- break;
- case ERROR_TOOMANY:
- emsg_funcname(_(e_toomanyarg), name);
- break;
- case ERROR_TOOFEW:
- emsg_funcname(N_("E119: Not enough arguments for function: %s"),
- name);
- break;
- case ERROR_SCRIPT:
- emsg_funcname(N_("E120: Using <SID> not in a script context: %s"),
- name);
- break;
- case ERROR_DICT:
- emsg_funcname(N_("E725: Calling dict function without Dictionary: %s"),
- name);
- break;
- }
+ user_func_error(error, (name != NULL) ? name : funcname);
}
while (argv_clear > 0) {
@@ -2854,7 +2888,7 @@ void ex_call(exarg_T *eap)
curwin->w_cursor.coladd = 0;
}
arg = startarg;
- if (get_func_tv(name, (int)STRLEN(name), &rettv, &arg,
+ if (get_func_tv(name, -1, &rettv, &arg,
eap->line1, eap->line2, &doesrange,
true, partial, fudi.fd_dict) == FAIL) {
failed = true;
@@ -2886,8 +2920,10 @@ void ex_call(exarg_T *eap)
if (!failed || eap->cstack->cs_trylevel > 0) {
// Check for trailing illegal characters and a following command.
if (!ends_excmd(*arg)) {
- emsg_severe = TRUE;
- EMSG(_(e_trailing));
+ if (!failed) {
+ emsg_severe = true;
+ EMSG(_(e_trailing));
+ }
} else {
eap->nextcmd = check_nextcmd(arg);
}
@@ -3348,9 +3384,13 @@ bool set_ref_in_previous_funccal(int copyID)
{
bool abort = false;
- for (funccall_T *fc = previous_funccal; fc != NULL; fc = fc->caller) {
- abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL);
- abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL);
+ for (funccall_T *fc = previous_funccal; !abort && fc != NULL;
+ fc = fc->caller) {
+ fc->fc_copyID = copyID + 1;
+ abort = abort
+ || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID + 1, NULL)
+ || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID + 1, NULL)
+ || set_ref_in_list(&fc->l_varlist, copyID + 1, NULL);
}
return abort;
}
@@ -3361,9 +3401,11 @@ static bool set_ref_in_funccal(funccall_T *fc, int copyID)
if (fc->fc_copyID != copyID) {
fc->fc_copyID = copyID;
- abort = abort || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL);
- abort = abort || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL);
- abort = abort || set_ref_in_func(NULL, fc->func, copyID);
+ abort = abort
+ || set_ref_in_ht(&fc->l_vars.dv_hashtab, copyID, NULL)
+ || set_ref_in_ht(&fc->l_avars.dv_hashtab, copyID, NULL)
+ || set_ref_in_list(&fc->l_varlist, copyID, NULL)
+ || set_ref_in_func(NULL, fc->func, copyID);
}
return abort;
}
@@ -3373,12 +3415,13 @@ bool set_ref_in_call_stack(int copyID)
{
bool abort = false;
- for (funccall_T *fc = current_funccal; fc != NULL; fc = fc->caller) {
+ for (funccall_T *fc = current_funccal; !abort && fc != NULL;
+ fc = fc->caller) {
abort = abort || set_ref_in_funccal(fc, copyID);
}
// Also go through the funccal_stack.
- for (funccal_entry_T *entry = funccal_stack; entry != NULL;
+ for (funccal_entry_T *entry = funccal_stack; !abort && entry != NULL;
entry = entry->next) {
for (funccall_T *fc = entry->top_funccal; !abort && fc != NULL;
fc = fc->caller) {
@@ -3457,12 +3500,7 @@ bool set_ref_in_func(char_u *name, ufunc_T *fp_in, int copyID)
char_u *register_cfunc(cfunc_T cb, cfunc_free_T cb_free, void *state)
{
char_u *name = get_lambda_name();
- ufunc_T *fp = NULL;
-
- fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
- if (fp == NULL) {
- return NULL;
- }
+ ufunc_T *fp = xcalloc(1, offsetof(ufunc_T, uf_name) + STRLEN(name) + 1);
fp->uf_refcount = 1;
fp->uf_varargs = true;
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 519978f4fb..17afb33059 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -41,6 +41,7 @@
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/extmark.h"
+#include "nvim/decoration.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/message.h"
@@ -135,7 +136,7 @@ void do_ascii(const exarg_T *const eap)
char buf1[20];
if (vim_isprintc_strict(c) && (c < ' ' || c > '~')) {
char_u buf3[7];
- transchar_nonprint(buf3, c);
+ transchar_nonprint(curbuf, buf3, c);
vim_snprintf(buf1, sizeof(buf1), " <%s>", (char *)buf3);
} else {
buf1[0] = NUL;
@@ -326,14 +327,19 @@ static int linelen(int *has_tab)
int save;
int len;
- /* find the first non-blank character */
+ // Get the line. If it's empty bail out early (could be the empty string
+ // for an unloaded buffer).
line = get_cursor_line_ptr();
+ if (*line == NUL) {
+ return 0;
+ }
+ // find the first non-blank character
first = skipwhite(line);
- /* find the character after the last non-blank character */
+ // find the character after the last non-blank character
for (last = first + STRLEN(first);
- last > first && ascii_iswhite(last[-1]); --last)
- ;
+ last > first && ascii_iswhite(last[-1]); last--) {
+ }
save = *last;
*last = NUL;
// Get line length.
@@ -846,6 +852,11 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
return OK;
}
+ bcount_t start_byte = ml_find_line_or_offset(curbuf, line1, NULL, true);
+ bcount_t end_byte = ml_find_line_or_offset(curbuf, line2+1, NULL, true);
+ bcount_t extent_byte = end_byte-start_byte;
+ bcount_t dest_byte = ml_find_line_or_offset(curbuf, dest+1, NULL, true);
+
num_lines = line2 - line1 + 1;
/*
@@ -880,6 +891,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
last_line = curbuf->b_ml.ml_line_count;
mark_adjust_nofold(line1, line2, last_line - line2, 0L, kExtmarkNOOP);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, num_lines, false);
+ int line_off = 0;
+ bcount_t byte_off = 0;
if (dest >= line2) {
mark_adjust_nofold(line2 + 1, dest, -num_lines, 0L, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
@@ -889,6 +902,8 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
}
curbuf->b_op_start.lnum = dest - num_lines + 1;
curbuf->b_op_end.lnum = dest;
+ line_off = -num_lines;
+ byte_off = -extent_byte;
} else {
mark_adjust_nofold(dest + 1, line1 - 1, num_lines, 0L, kExtmarkNOOP);
FOR_ALL_TAB_WINDOWS(tab, win) {
@@ -904,11 +919,10 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
-(last_line - dest - extra), 0L, kExtmarkNOOP);
// extmarks are handled separately
- int size = line2-line1+1;
- int off = dest >= line2 ? -size : 0;
- extmark_move_region(curbuf, line1-1, 0,
- line2-line1+1, 0,
- dest+off, 0, kExtmarkUndo);
+ extmark_move_region(curbuf, line1-1, 0, start_byte,
+ line2-line1+1, 0, extent_byte,
+ dest+line_off, 0, dest_byte+byte_off,
+ kExtmarkUndo);
changed_lines(last_line - num_lines + 1, 0, last_line + 1, -extra, false);
@@ -1035,13 +1049,13 @@ void do_bang(int addr_count, exarg_T *eap, int forceit, int do_in, int do_out)
int len;
int scroll_save = msg_scroll;
- /*
- * Disallow shell commands in restricted mode (-Z)
- * Disallow shell commands from .exrc and .vimrc in current directory for
- * security reasons.
- */
- if (check_restricted() || check_secure())
+ //
+ // Disallow shell commands from .exrc and .vimrc in current directory for
+ // security reasons.
+ //
+ if (check_secure()) {
return;
+ }
if (addr_count == 0) { /* :! */
msg_scroll = FALSE; /* don't scroll here */
@@ -1369,10 +1383,9 @@ do_shell(
int flags // may be SHELL_DOOUT when output is redirected
)
{
- // Disallow shell commands in restricted mode (-Z)
// Disallow shell commands from .exrc and .vimrc in current directory for
// security reasons.
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
msg_end();
return;
}
@@ -2227,11 +2240,9 @@ int do_ecmd(
goto theend;
}
- /*
- * if the file was changed we may not be allowed to abandon it
- * - if we are going to re-edit the same file
- * - or if we are the only window on this file and if ECMD_HIDE is FALSE
- */
+ // If the file was changed we may not be allowed to abandon it:
+ // - if we are going to re-edit the same file
+ // - or if we are the only window on this file and if ECMD_HIDE is FALSE
if ( ((!other_file && !(flags & ECMD_OLDBUF))
|| (curbuf->b_nwindows == 1
&& !(flags & (ECMD_HIDE | ECMD_ADDBUF))))
@@ -2484,8 +2495,12 @@ int do_ecmd(
new_name = NULL;
}
set_bufref(&bufref, buf);
- if (p_ur < 0 || curbuf->b_ml.ml_line_count <= p_ur) {
- // Save all the text, so that the reload can be undone.
+
+ // If the buffer was used before, store the current contents so that
+ // the reload can be undone. Do not do this if the (empty) buffer is
+ // being re-used for another file.
+ if (!(curbuf->b_flags & BF_NEVERLOADED)
+ && (p_ur < 0 || curbuf->b_ml.ml_line_count <= p_ur)) {
// Sync first so that this is a separate undo-able action.
u_sync(false);
if (u_savecommon(0, curbuf->b_ml.ml_line_count + 1, 0, true)
@@ -3014,20 +3029,6 @@ void ex_z(exarg_T *eap)
ex_no_reprint = true;
}
-// Check if the restricted flag is set.
-// If so, give an error message and return true.
-// Otherwise, return false.
-bool check_restricted(void)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
-{
- if (restricted) {
- EMSG(_("E145: Shell commands and some functionality not allowed"
- " in restricted mode"));
- return true;
- }
- return false;
-}
-
/*
* Check if the secure flag is set (.exrc or .vimrc in current directory).
* If so, give an error message and return TRUE.
@@ -3435,13 +3436,13 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
sub_firstline = NULL;
- /*
- * ~ in the substitute pattern is replaced with the old pattern.
- * We do it here once to avoid it to be replaced over and over again.
- * But don't do it when it starts with "\=", then it's an expression.
- */
- if (!(sub[0] == '\\' && sub[1] == '='))
+ // ~ in the substitute pattern is replaced with the old pattern.
+ // We do it here once to avoid it to be replaced over and over again.
+ // But don't do it when it starts with "\=", then it's an expression.
+ assert(sub != NULL);
+ if (!(sub[0] == '\\' && sub[1] == '=')) {
sub = regtilde(sub, p_magic);
+ }
// Check for a match on each line.
// If preview: limit to max('cmdwinheight', viewport).
@@ -3706,8 +3707,8 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
update_topline();
validate_cursor();
update_screen(SOME_VALID);
- highlight_match = FALSE;
- redraw_later(SOME_VALID);
+ highlight_match = false;
+ redraw_later(curwin, SOME_VALID);
curwin->w_p_fen = save_p_fen;
if (msg_row == Rows - 1)
@@ -3908,6 +3909,18 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
ADJUST_SUB_FIRSTLNUM();
+ // TODO(bfredl): adjust also in preview, because decorations?
+ // this has some robustness issues, will look into later.
+ bool do_splice = !preview;
+ bcount_t replaced_bytes = 0;
+ lpos_T start = regmatch.startpos[0], end = regmatch.endpos[0];
+ if (do_splice) {
+ for (i = 0; i < nmatch-1; i++) {
+ replaced_bytes += STRLEN(ml_get(lnum_start+i)) + 1;
+ }
+ replaced_bytes += end.col - start.col;
+ }
+
// Now the trick is to replace CTRL-M chars with a real line
// break. This would make it impossible to insert a CTRL-M in
@@ -3951,17 +3964,14 @@ static buf_T *do_sub(exarg_T *eap, proftime_T timeout,
current_match.end.col = new_endcol;
current_match.end.lnum = lnum;
- // TODO(bfredl): adjust in preview, because decorations?
- // this has some robustness issues, will look into later.
- if (!preview) {
- lpos_T start = regmatch.startpos[0], end = regmatch.endpos[0];
+ if (do_splice) {
int matchcols = end.col - ((end.lnum == start.lnum)
? start.col : 0);
int subcols = new_endcol - ((lnum == lnum_start) ? start_col : 0);
extmark_splice(curbuf, lnum_start-1, start_col,
- end.lnum-start.lnum, matchcols,
- lnum-lnum_start, subcols, kExtmarkUndo);
- }
+ end.lnum-start.lnum, matchcols, replaced_bytes,
+ lnum-lnum_start, subcols, sublen-1, kExtmarkUndo);
+ }
}
@@ -5727,7 +5737,7 @@ static buf_T *show_sub(exarg_T *eap, pos_T old_cusr,
}
xfree(str);
- redraw_later(SOME_VALID);
+ redraw_later(curwin, SOME_VALID);
win_enter(save_curwin, false); // Return to original window
update_topline();
diff --git a/src/nvim/ex_cmds.lua b/src/nvim/ex_cmds.lua
index 252af409c0..d62b00fee1 100644
--- a/src/nvim/ex_cmds.lua
+++ b/src/nvim/ex_cmds.lua
@@ -337,7 +337,7 @@ return {
},
{
command='caddexpr',
- flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR),
+ flags=bit.bor(NEEDARG, WORD1, NOTRLCOM),
addr_type=ADDR_LINES,
func='ex_cexpr',
},
@@ -409,7 +409,7 @@ return {
},
{
command='cexpr',
- flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR, BANG),
+ flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, BANG),
addr_type=ADDR_LINES,
func='ex_cexpr',
},
@@ -447,7 +447,7 @@ return {
},
{
command='cgetexpr',
- flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR),
+ flags=bit.bor(NEEDARG, WORD1, NOTRLCOM),
addr_type=ADDR_LINES,
func='ex_cexpr',
},
@@ -1299,7 +1299,7 @@ return {
},
{
command='laddexpr',
- flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR),
+ flags=bit.bor(NEEDARG, WORD1, NOTRLCOM),
addr_type=ADDR_LINES,
func='ex_cexpr',
},
@@ -1389,7 +1389,7 @@ return {
},
{
command='lexpr',
- flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR, BANG),
+ flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, BANG),
addr_type=ADDR_LINES,
func='ex_cexpr',
},
@@ -1427,7 +1427,7 @@ return {
},
{
command='lgetexpr',
- flags=bit.bor(NEEDARG, WORD1, NOTRLCOM, TRLBAR),
+ flags=bit.bor(NEEDARG, WORD1, NOTRLCOM),
addr_type=ADDR_LINES,
func='ex_cexpr',
},
@@ -1927,13 +1927,19 @@ return {
command='perl',
flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, SBOXOK, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
- func='ex_script_ni',
+ func='ex_perl',
},
{
command='perldo',
flags=bit.bor(RANGE, EXTRA, DFLALL, NEEDARG, CMDWIN, RESTRICT),
addr_type=ADDR_LINES,
- func='ex_ni',
+ func='ex_perldo',
+ },
+ {
+ command='perlfile',
+ flags=bit.bor(RANGE, FILE1, NEEDARG, CMDWIN, RESTRICT),
+ addr_type=ADDR_LINES,
+ func='ex_perlfile',
},
{
command='pedit',
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index 7f4b01e306..713d18b44d 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -935,6 +935,21 @@ void ex_pydo3(exarg_T *eap)
script_host_do_range("python3", eap);
}
+void ex_perl(exarg_T *eap)
+{
+ script_host_execute("perl", eap);
+}
+
+void ex_perlfile(exarg_T *eap)
+{
+ script_host_execute_file("perl", eap);
+}
+
+void ex_perldo(exarg_T *eap)
+{
+ script_host_do_range("perl", eap);
+}
+
// Command line expansion for :profile.
static enum {
PEXP_SUBCMD, ///< expand :profile sub-commands
@@ -1984,9 +1999,16 @@ void ex_argadd(exarg_T *eap)
/// ":argdelete"
void ex_argdelete(exarg_T *eap)
{
- if (eap->addr_count > 0) {
- // ":1,4argdel": Delete all arguments in the range.
- if (eap->line2 > ARGCOUNT) {
+ if (eap->addr_count > 0 || *eap->arg == NUL) {
+ // ":argdel" works like ":.argdel"
+ if (eap->addr_count == 0) {
+ if (curwin->w_arg_idx >= ARGCOUNT) {
+ EMSG(_("E610: No argument to delete"));
+ return;
+ }
+ eap->line1 = eap->line2 = curwin->w_arg_idx + 1;
+ } else if (eap->line2 > ARGCOUNT) {
+ // ":1,4argdel": Delete all arguments in the range.
eap->line2 = ARGCOUNT;
}
linenr_T n = eap->line2 - eap->line1 + 1;
@@ -2016,8 +2038,6 @@ void ex_argdelete(exarg_T *eap)
curwin->w_arg_idx = ARGCOUNT - 1;
}
}
- } else if (*eap->arg == NUL) {
- EMSG(_(e_argreq));
} else {
do_arglist(eap->arg, AL_DEL, 0);
}
@@ -2038,6 +2058,10 @@ void ex_listdo(exarg_T *eap)
// Don't do syntax HL autocommands. Skipping the syntax file is a
// great speed improvement.
save_ei = au_event_disable(",Syntax");
+
+ FOR_ALL_BUFFERS(buf) {
+ buf->b_flags &= ~BF_SYN_SET;
+ }
}
if (eap->cmdidx == CMD_windo
@@ -2232,9 +2256,32 @@ void ex_listdo(exarg_T *eap)
}
if (save_ei != NULL) {
+ buf_T *bnext;
+ aco_save_T aco;
+
au_event_restore(save_ei);
- apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn,
- curbuf->b_fname, true, curbuf);
+
+ for (buf_T *buf = firstbuf; buf != NULL; buf = bnext) {
+ bnext = buf->b_next;
+ if (buf->b_nwindows > 0 && (buf->b_flags & BF_SYN_SET)) {
+ buf->b_flags &= ~BF_SYN_SET;
+
+ // buffer was opened while Syntax autocommands were disabled,
+ // need to trigger them now.
+ if (buf == curbuf) {
+ apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn,
+ curbuf->b_fname, true, curbuf);
+ } else {
+ aucmd_prepbuf(&aco, buf);
+ apply_autocmds(EVENT_SYNTAX, buf->b_p_syn,
+ buf->b_fname, true, buf);
+ aucmd_restbuf(&aco);
+ }
+
+ // start over, in case autocommands messed things up.
+ bnext = firstbuf;
+ }
+ }
}
}
@@ -2316,7 +2363,7 @@ void ex_compiler(exarg_T *eap)
do_unlet(S_LEN("b:current_compiler"), true);
snprintf((char *)buf, bufsize, "compiler/%s.vim", eap->arg);
- if (source_runtime(buf, DIP_ALL) == FAIL) {
+ if (source_in_path(p_rtp, buf, DIP_ALL) == FAIL) {
EMSG2(_("E666: compiler not supported: %s"), eap->arg);
}
xfree(buf);
@@ -2534,6 +2581,7 @@ int do_in_runtimepath(char_u *name, int flags, DoInRuntimepathCB callback,
/// return FAIL when no file could be sourced, OK otherwise.
int source_runtime(char_u *name, int flags)
{
+ flags |= (flags & DIP_NORTP) ? 0 : DIP_START;
return source_in_path(p_rtp, name, flags);
}
@@ -4152,7 +4200,7 @@ static void script_host_execute(char *name, exarg_T *eap)
tv_list_append_number(args, (int)eap->line1);
tv_list_append_number(args, (int)eap->line2);
- (void)eval_call_provider(name, "execute", args);
+ (void)eval_call_provider(name, "execute", args, true);
}
}
@@ -4167,7 +4215,7 @@ static void script_host_execute_file(char *name, exarg_T *eap)
// current range
tv_list_append_number(args, (int)eap->line1);
tv_list_append_number(args, (int)eap->line2);
- (void)eval_call_provider(name, "execute_file", args);
+ (void)eval_call_provider(name, "execute_file", args, true);
}
static void script_host_do_range(char *name, exarg_T *eap)
@@ -4176,7 +4224,7 @@ static void script_host_do_range(char *name, exarg_T *eap)
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);
- (void)eval_call_provider(name, "do_range", args);
+ (void)eval_call_provider(name, "do_range", args, true);
}
/// ":drop"
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index 1f0560ae48..21db3936b8 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -62,7 +62,6 @@
// curbuf_lock is set
#define MODIFY 0x200000 // forbidden in non-'modifiable' buffer
#define EXFLAGS 0x400000 // allow flags after count in argument
-#define RESTRICT 0x800000L // forbidden in restricted mode
#define FILES (XFILE | EXTRA) // multiple extra files allowed
#define WORD1 (EXTRA | NOSPC) // one extra word allowed
#define FILE1 (FILES | NOSPC) // 1 file allowed, defaults to current file
@@ -169,6 +168,10 @@ struct exarg {
LineGetter getline; ///< Function used to get the next line
void *cookie; ///< argument for getline()
cstack_T *cstack; ///< condition stack for ":if" etc.
+ long verbose_save; ///< saved value of p_verbose
+ int save_msg_silent; ///< saved value of msg_silent
+ int did_esilent; ///< how many times emsg_silent was incremented
+ bool did_sandbox; ///< when true did sandbox++
};
#define FORCE_BIN 1 // ":edit ++bin file"
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 5bf6aa73c6..a491a9d377 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -137,32 +137,6 @@ struct dbg_stuff {
except_T *current_exception;
};
-typedef struct {
- // parsed results
- exarg_T *eap;
- char_u *parsed_upto; // local we've parsed up to so far
- char_u *cmd; // start of command
- char_u *after_modifier;
-
- // errors
- char_u *errormsg;
-
- // globals that need to be updated
- cmdmod_T cmdmod;
- int sandbox;
- int msg_silent;
- int emsg_silent;
- bool ex_pressedreturn;
- long p_verbose;
-
- // other side-effects
- bool set_eventignore;
- long verbose_save;
- int save_msg_silent;
- int did_esilent;
- bool did_sandbox;
-} parse_state_T;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "ex_docmd.c.generated.h"
#endif
@@ -284,6 +258,27 @@ void do_exmode(int improved)
msg_scroll = save_msg_scroll;
}
+// Print the executed command for when 'verbose' is set.
+// When "lnum" is 0 only print the command.
+static void msg_verbose_cmd(linenr_T lnum, char_u *cmd)
+ FUNC_ATTR_NONNULL_ALL
+{
+ no_wait_return++;
+ verbose_enter_scroll();
+
+ if (lnum == 0) {
+ smsg(_("Executing: %s"), cmd);
+ } else {
+ smsg(_("line %" PRIdLINENR ": %s"), lnum, cmd);
+ }
+ if (msg_silent == 0) {
+ msg_puts("\n"); // don't overwrite this
+ }
+
+ verbose_leave_scroll();
+ no_wait_return--;
+}
+
/*
* Execute a simple command line. Used for translated commands like "*".
*/
@@ -593,17 +588,8 @@ int do_cmdline(char_u *cmdline, LineGetter fgetline,
}
}
- if (p_verbose >= 15 && sourcing_name != NULL) {
- ++no_wait_return;
- verbose_enter_scroll();
-
- smsg(_("line %" PRIdLINENR ": %s"), sourcing_lnum, cmdline_copy);
- if (msg_silent == 0) {
- msg_puts("\n"); // don't overwrite this either
- }
-
- verbose_leave_scroll();
- --no_wait_return;
+ if ((p_verbose >= 15 && sourcing_name != NULL) || p_verbose >= 16) {
+ msg_verbose_cmd(sourcing_lnum, cmdline_copy);
}
/*
@@ -1235,292 +1221,6 @@ static char_u *skip_colon_white(const char_u *p, bool skipleadingwhite)
return (char_u *)p;
}
-static void parse_state_to_global(const parse_state_T *parse_state)
-{
- cmdmod = parse_state->cmdmod;
- sandbox = parse_state->sandbox;
- msg_silent = parse_state->msg_silent;
- emsg_silent = parse_state->emsg_silent;
- ex_pressedreturn = parse_state->ex_pressedreturn;
- p_verbose = parse_state->p_verbose;
-
- if (parse_state->set_eventignore) {
- set_string_option_direct(
- (char_u *)"ei", -1, (char_u *)"all", OPT_FREE, SID_NONE);
- }
-}
-
-static void parse_state_from_global(parse_state_T *parse_state)
-{
- memset(parse_state, 0, sizeof(*parse_state));
- parse_state->cmdmod = cmdmod;
- parse_state->sandbox = sandbox;
- parse_state->msg_silent = msg_silent;
- parse_state->emsg_silent = emsg_silent;
- parse_state->ex_pressedreturn = ex_pressedreturn;
- parse_state->p_verbose = p_verbose;
-}
-
-//
-// Parse one Ex command.
-//
-// This has no side-effects, except for modifying parameters
-// passed in by pointer.
-//
-// The `out` should be zeroed, and its `ea` member initialised,
-// before calling this function.
-//
-static bool parse_one_cmd(
- char_u **cmdlinep,
- parse_state_T *const out,
- LineGetter fgetline,
- void *fgetline_cookie)
-{
- exarg_T ea = {
- .line1 = 1,
- .line2 = 1,
- };
- *out->eap = ea;
-
- // "#!anything" is handled like a comment.
- if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') {
- return false;
- }
-
- /*
- * Repeat until no more command modifiers are found.
- */
- ea.cmd = *cmdlinep;
- for (;; ) {
- /*
- * 1. Skip comment lines and leading white space and colons.
- */
- while (*ea.cmd == ' '
- || *ea.cmd == '\t'
- || *ea.cmd == ':') {
- ea.cmd++;
- }
-
- // in ex mode, an empty line works like :+
- if (*ea.cmd == NUL && exmode_active
- && (getline_equal(fgetline, fgetline_cookie, getexmodeline)
- || getline_equal(fgetline, fgetline_cookie, getexline))
- && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
- ea.cmd = (char_u *)"+";
- out->ex_pressedreturn = true;
- }
-
- // ignore comment and empty lines
- if (*ea.cmd == '"') {
- return false;
- }
- if (*ea.cmd == NUL) {
- out->ex_pressedreturn = true;
- return false;
- }
-
- /*
- * 2. Handle command modifiers.
- */
- char_u *p = skip_range(ea.cmd, NULL);
- switch (*p) {
- // When adding an entry, also modify cmd_exists().
- case 'a': if (!checkforcmd(&ea.cmd, "aboveleft", 3))
- break;
- out->cmdmod.split |= WSP_ABOVE;
- continue;
-
- case 'b': if (checkforcmd(&ea.cmd, "belowright", 3)) {
- out->cmdmod.split |= WSP_BELOW;
- continue;
- }
- if (checkforcmd(&ea.cmd, "browse", 3)) {
- out->cmdmod.browse = true;
- continue;
- }
- if (!checkforcmd(&ea.cmd, "botright", 2)) {
- break;
- }
- out->cmdmod.split |= WSP_BOT;
- continue;
-
- case 'c': if (!checkforcmd(&ea.cmd, "confirm", 4))
- break;
- out->cmdmod.confirm = true;
- continue;
-
- case 'k': if (checkforcmd(&ea.cmd, "keepmarks", 3)) {
- out->cmdmod.keepmarks = true;
- continue;
- }
- if (checkforcmd(&ea.cmd, "keepalt", 5)) {
- out->cmdmod.keepalt = true;
- continue;
- }
- if (checkforcmd(&ea.cmd, "keeppatterns", 5)) {
- out->cmdmod.keeppatterns = true;
- continue;
- }
- if (!checkforcmd(&ea.cmd, "keepjumps", 5)) {
- break;
- }
- out->cmdmod.keepjumps = true;
- continue;
-
- case 'f': { // only accept ":filter {pat} cmd"
- char_u *reg_pat;
-
- if (!checkforcmd(&p, "filter", 4) || *p == NUL || ends_excmd(*p)) {
- break;
- }
- if (*p == '!') {
- out->cmdmod.filter_force = true;
- p = skipwhite(p + 1);
- if (*p == NUL || ends_excmd(*p)) {
- break;
- }
- }
- p = skip_vimgrep_pat(p, &reg_pat, NULL);
- if (p == NULL || *p == NUL) {
- break;
- }
- out->cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC);
- if (out->cmdmod.filter_regmatch.regprog == NULL) {
- break;
- }
- ea.cmd = p;
- continue;
- }
-
- // ":hide" and ":hide | cmd" are not modifiers
- case 'h': if (p != ea.cmd || !checkforcmd(&p, "hide", 3)
- || *p == NUL || ends_excmd(*p))
- break;
- ea.cmd = p;
- out->cmdmod.hide = true;
- continue;
-
- case 'l': if (checkforcmd(&ea.cmd, "lockmarks", 3)) {
- out->cmdmod.lockmarks = true;
- continue;
- }
-
- if (!checkforcmd(&ea.cmd, "leftabove", 5)) {
- break;
- }
- out->cmdmod.split |= WSP_ABOVE;
- continue;
-
- case 'n':
- if (checkforcmd(&ea.cmd, "noautocmd", 3)) {
- if (out->cmdmod.save_ei == NULL) {
- // Set 'eventignore' to "all". Restore the
- // existing option value later.
- out->cmdmod.save_ei = vim_strsave(p_ei);
- out->set_eventignore = true;
- }
- continue;
- }
- if (!checkforcmd(&ea.cmd, "noswapfile", 3)) {
- break;
- }
- out->cmdmod.noswapfile = true;
- continue;
-
- case 'r': if (!checkforcmd(&ea.cmd, "rightbelow", 6))
- break;
- out->cmdmod.split |= WSP_BELOW;
- continue;
-
- case 's': if (checkforcmd(&ea.cmd, "sandbox", 3)) {
- if (!out->did_sandbox) {
- out->sandbox++;
- }
- out->did_sandbox = true;
- continue;
- }
- if (!checkforcmd(&ea.cmd, "silent", 3)) {
- break;
- }
- if (out->save_msg_silent == -1) {
- out->save_msg_silent = out->msg_silent;
- }
- out->msg_silent++;
- if (*ea.cmd == '!' && !ascii_iswhite(ea.cmd[-1])) {
- // ":silent!", but not "silent !cmd"
- ea.cmd = skipwhite(ea.cmd + 1);
- out->emsg_silent++;
- out->did_esilent++;
- }
- continue;
-
- case 't': if (checkforcmd(&p, "tab", 3)) {
- long tabnr = get_address(
- &ea, &ea.cmd, ADDR_TABS, ea.skip, false, 1);
-
- if (tabnr == MAXLNUM) {
- out->cmdmod.tab = tabpage_index(curtab) + 1;
- } else {
- if (tabnr < 0 || tabnr > LAST_TAB_NR) {
- out->errormsg = (char_u *)_(e_invrange);
- return false;
- }
- out->cmdmod.tab = tabnr + 1;
- }
- ea.cmd = p;
- continue;
- }
- if (!checkforcmd(&ea.cmd, "topleft", 2)) {
- break;
- }
- out->cmdmod.split |= WSP_TOP;
- continue;
-
- case 'u': if (!checkforcmd(&ea.cmd, "unsilent", 3))
- break;
- if (out->save_msg_silent == -1) {
- out->save_msg_silent = out->msg_silent;
- }
- out->msg_silent = 0;
- continue;
-
- case 'v': if (checkforcmd(&ea.cmd, "vertical", 4)) {
- out->cmdmod.split |= WSP_VERT;
- continue;
- }
- if (!checkforcmd(&p, "verbose", 4))
- break;
- if (out->verbose_save < 0) {
- out->verbose_save = out->p_verbose;
- }
- if (ascii_isdigit(*ea.cmd)) {
- out->p_verbose = atoi((char *)ea.cmd);
- } else {
- out->p_verbose = 1;
- }
- ea.cmd = p;
- continue;
- }
- break;
- }
- out->after_modifier = ea.cmd;
-
- // 3. Skip over the range to find the command. Let "p" point to after it.
- //
- // We need the command to know what kind of range it uses.
-
- out->cmd = ea.cmd;
- ea.cmd = skip_range(ea.cmd, NULL);
- if (*ea.cmd == '*') {
- ea.cmd = skipwhite(ea.cmd + 1);
- }
- out->parsed_upto = find_command(&ea, NULL);
-
- *out->eap = ea;
-
- return true;
-}
-
/*
* Execute one Ex command.
*
@@ -1549,12 +1249,16 @@ static char_u * do_one_cmd(char_u **cmdlinep,
linenr_T lnum;
long n;
char_u *errormsg = NULL; // error message
+ char_u *after_modifier = NULL;
exarg_T ea;
- int save_msg_scroll = msg_scroll;
- parse_state_T parsed;
+ const int save_msg_scroll = msg_scroll;
cmdmod_T save_cmdmod;
const int save_reg_executing = reg_executing;
+ char_u *cmd;
+ memset(&ea, 0, sizeof(ea));
+ ea.line1 = 1;
+ ea.line2 = 1;
ex_nesting_level++;
/* When the last file has not been edited :q has to be typed twice. */
@@ -1571,31 +1275,44 @@ static char_u * do_one_cmd(char_u **cmdlinep,
* recursive calls.
*/
save_cmdmod = cmdmod;
- memset(&cmdmod, 0, sizeof(cmdmod));
- parse_state_from_global(&parsed);
- parsed.eap = &ea;
- parsed.verbose_save = -1;
- parsed.save_msg_silent = -1;
- parsed.did_esilent = 0;
- parsed.did_sandbox = false;
- bool parse_success = parse_one_cmd(cmdlinep, &parsed, fgetline, cookie);
- parse_state_to_global(&parsed);
+ // "#!anything" is handled like a comment.
+ if ((*cmdlinep)[0] == '#' && (*cmdlinep)[1] == '!') {
+ goto doend;
+ }
+
+ // 1. Skip comment lines and leading white space and colons.
+ // 2. Handle command modifiers.
- // Update locals from parse_one_cmd()
- errormsg = parsed.errormsg;
- p = parsed.parsed_upto;
+ // The "ea" structure holds the arguments that can be used.
+ ea.cmd = *cmdlinep;
+ ea.cmdlinep = cmdlinep;
+ ea.getline = fgetline;
+ ea.cookie = cookie;
+ ea.cstack = cstack;
- if (!parse_success) {
+ if (parse_command_modifiers(&ea, &errormsg, false) == FAIL) {
goto doend;
}
+ after_modifier = ea.cmd;
+
ea.skip = (did_emsg
|| got_int
|| current_exception
|| (cstack->cs_idx >= 0
&& !(cstack->cs_flags[cstack->cs_idx] & CSF_ACTIVE)));
+ // 3. Skip over the range to find the command. Let "p" point to after it.
+ //
+ // We need the command to know what kind of range it uses.
+ cmd = ea.cmd;
+ ea.cmd = skip_range(ea.cmd, NULL);
+ if (*ea.cmd == '*') {
+ ea.cmd = skipwhite(ea.cmd + 1);
+ }
+ p = find_command(&ea, NULL);
+
// Count this line for profiling if skip is TRUE.
if (do_profiling == PROF_YES
&& (!ea.skip || cstack->cs_idx == 0
@@ -1665,8 +1382,8 @@ static char_u * do_one_cmd(char_u **cmdlinep,
}
}
- ea.cmd = parsed.cmd;
- if (parse_cmd_address(&ea, &errormsg) == FAIL) {
+ ea.cmd = cmd;
+ if (parse_cmd_address(&ea, &errormsg, false) == FAIL) {
goto doend;
}
@@ -1746,8 +1463,8 @@ static char_u * do_one_cmd(char_u **cmdlinep,
if (!(flags & DOCMD_VERBOSE)) {
// If the modifier was parsed OK the error must be in the following
// command
- if (parsed.after_modifier != NULL) {
- append_command(parsed.after_modifier);
+ if (after_modifier != NULL) {
+ append_command(after_modifier);
} else {
append_command(*cmdlinep);
}
@@ -1786,10 +1503,6 @@ static char_u * do_one_cmd(char_u **cmdlinep,
errormsg = (char_u *)_(e_sandbox);
goto doend;
}
- if (restricted != 0 && (ea.argt & RESTRICT)) {
- errormsg = (char_u *)_("E981: Command not allowed in restricted mode");
- goto doend;
- }
if (!MODIFIABLE(curbuf) && (ea.argt & MODIFY)
// allow :put in terminals
&& (!curbuf->terminal || ea.cmdidx != CMD_put)) {
@@ -2222,22 +1935,15 @@ static char_u * do_one_cmd(char_u **cmdlinep,
// The :try command saves the emsg_silent flag, reset it here when
// ":silent! try" was used, it should only apply to :try itself.
- if (ea.cmdidx == CMD_try && parsed.did_esilent > 0) {
- emsg_silent -= parsed.did_esilent;
+ if (ea.cmdidx == CMD_try && ea.did_esilent > 0) {
+ emsg_silent -= ea.did_esilent;
if (emsg_silent < 0) {
emsg_silent = 0;
}
- parsed.did_esilent = 0;
+ ea.did_esilent = 0;
}
// 7. Execute the command.
- //
- // The "ea" structure holds the arguments that can be used.
- ea.cmdlinep = cmdlinep;
- ea.getline = fgetline;
- ea.cookie = cookie;
- ea.cstack = cstack;
-
if (IS_USER_CMDIDX(ea.cmdidx)) {
/*
* Execute a user-defined command.
@@ -2293,9 +1999,285 @@ doend:
? cmdnames[(int)ea.cmdidx].cmd_name
: (char_u *)NULL);
- if (parsed.verbose_save >= 0) {
- p_verbose = parsed.verbose_save;
+ undo_cmdmod(&ea, save_msg_scroll);
+ cmdmod = save_cmdmod;
+ reg_executing = save_reg_executing;
+
+ if (ea.did_sandbox) {
+ sandbox--;
+ }
+
+ if (ea.nextcmd && *ea.nextcmd == NUL) /* not really a next command */
+ ea.nextcmd = NULL;
+
+ --ex_nesting_level;
+
+ return ea.nextcmd;
+}
+
+// Parse and skip over command modifiers:
+// - update eap->cmd
+// - store flags in "cmdmod".
+// - Set ex_pressedreturn for an empty command line.
+// - set msg_silent for ":silent"
+// - set 'eventignore' to "all" for ":noautocmd"
+// - set p_verbose for ":verbose"
+// - Increment "sandbox" for ":sandbox"
+// When "skip_only" is true the global variables are not changed, except for
+// "cmdmod".
+// Return FAIL when the command is not to be executed.
+// May set "errormsg" to an error message.
+int parse_command_modifiers(exarg_T *eap, char_u **errormsg, bool skip_only)
+{
+ char_u *p;
+
+ memset(&cmdmod, 0, sizeof(cmdmod));
+ eap->verbose_save = -1;
+ eap->save_msg_silent = -1;
+
+ // Repeat until no more command modifiers are found.
+ for (;; ) {
+ while (*eap->cmd == ' '
+ || *eap->cmd == '\t'
+ || *eap->cmd == ':') {
+ eap->cmd++;
+ }
+
+ // in ex mode, an empty line works like :+
+ if (*eap->cmd == NUL && exmode_active
+ && (getline_equal(eap->getline, eap->cookie, getexmodeline)
+ || getline_equal(eap->getline, eap->cookie, getexline))
+ && curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
+ eap->cmd = (char_u *)"+";
+ if (!skip_only) {
+ ex_pressedreturn = true;
+ }
+ }
+
+ // ignore comment and empty lines
+ if (*eap->cmd == '"') {
+ return FAIL;
+ }
+ if (*eap->cmd == NUL) {
+ if (!skip_only) {
+ ex_pressedreturn = true;
+ }
+ return FAIL;
+ }
+
+ p = skip_range(eap->cmd, NULL);
+ switch (*p) {
+ // When adding an entry, also modify cmd_exists().
+ case 'a': if (!checkforcmd(&eap->cmd, "aboveleft", 3))
+ break;
+ cmdmod.split |= WSP_ABOVE;
+ continue;
+
+ case 'b': if (checkforcmd(&eap->cmd, "belowright", 3)) {
+ cmdmod.split |= WSP_BELOW;
+ continue;
+ }
+ if (checkforcmd(&eap->cmd, "browse", 3)) {
+ cmdmod.browse = true;
+ continue;
+ }
+ if (!checkforcmd(&eap->cmd, "botright", 2)) {
+ break;
+ }
+ cmdmod.split |= WSP_BOT;
+ continue;
+
+ case 'c': if (!checkforcmd(&eap->cmd, "confirm", 4))
+ break;
+ cmdmod.confirm = true;
+ continue;
+
+ case 'k': if (checkforcmd(&eap->cmd, "keepmarks", 3)) {
+ cmdmod.keepmarks = true;
+ continue;
+ }
+ if (checkforcmd(&eap->cmd, "keepalt", 5)) {
+ cmdmod.keepalt = true;
+ continue;
+ }
+ if (checkforcmd(&eap->cmd, "keeppatterns", 5)) {
+ cmdmod.keeppatterns = true;
+ continue;
+ }
+ if (!checkforcmd(&eap->cmd, "keepjumps", 5)) {
+ break;
+ }
+ cmdmod.keepjumps = true;
+ continue;
+
+ case 'f': { // only accept ":filter {pat} cmd"
+ char_u *reg_pat;
+
+ if (!checkforcmd(&p, "filter", 4) || *p == NUL || ends_excmd(*p)) {
+ break;
+ }
+ if (*p == '!') {
+ cmdmod.filter_force = true;
+ p = skipwhite(p + 1);
+ if (*p == NUL || ends_excmd(*p)) {
+ break;
+ }
+ }
+ if (skip_only) {
+ p = skip_vimgrep_pat(p, NULL, NULL);
+ } else {
+ // NOTE: This puts a NUL after the pattern.
+ p = skip_vimgrep_pat(p, &reg_pat, NULL);
+ }
+ if (p == NULL || *p == NUL) {
+ break;
+ }
+ if (!skip_only) {
+ cmdmod.filter_regmatch.regprog = vim_regcomp(reg_pat, RE_MAGIC);
+ if (cmdmod.filter_regmatch.regprog == NULL) {
+ break;
+ }
+ }
+ eap->cmd = p;
+ continue;
+ }
+
+ // ":hide" and ":hide | cmd" are not modifiers
+ case 'h': if (p != eap->cmd || !checkforcmd(&p, "hide", 3)
+ || *p == NUL || ends_excmd(*p))
+ break;
+ eap->cmd = p;
+ cmdmod.hide = true;
+ continue;
+
+ case 'l': if (checkforcmd(&eap->cmd, "lockmarks", 3)) {
+ cmdmod.lockmarks = true;
+ continue;
+ }
+
+ if (!checkforcmd(&eap->cmd, "leftabove", 5)) {
+ break;
+ }
+ cmdmod.split |= WSP_ABOVE;
+ continue;
+
+ case 'n':
+ if (checkforcmd(&eap->cmd, "noautocmd", 3)) {
+ if (cmdmod.save_ei == NULL && !skip_only) {
+ // Set 'eventignore' to "all". Restore the
+ // existing option value later.
+ cmdmod.save_ei = vim_strsave(p_ei);
+ set_string_option_direct((char_u *)"ei", -1,
+ (char_u *)"all", OPT_FREE, SID_NONE);
+ }
+ continue;
+ }
+ if (!checkforcmd(&eap->cmd, "noswapfile", 3)) {
+ break;
+ }
+ cmdmod.noswapfile = true;
+ continue;
+
+ case 'r': if (!checkforcmd(&eap->cmd, "rightbelow", 6))
+ break;
+ cmdmod.split |= WSP_BELOW;
+ continue;
+
+ case 's': if (checkforcmd(&eap->cmd, "sandbox", 3)) {
+ if (!skip_only) {
+ if (!eap->did_sandbox) {
+ sandbox++;
+ }
+ eap->did_sandbox = true;
+ }
+ continue;
+ }
+ if (!checkforcmd(&eap->cmd, "silent", 3)) {
+ break;
+ }
+ if (!skip_only) {
+ if (eap->save_msg_silent == -1) {
+ eap->save_msg_silent = msg_silent;
+ }
+ msg_silent++;
+ }
+ if (*eap->cmd == '!' && !ascii_iswhite(eap->cmd[-1])) {
+ // ":silent!", but not "silent !cmd"
+ eap->cmd = skipwhite(eap->cmd + 1);
+ if (!skip_only) {
+ emsg_silent++;
+ eap->did_esilent++;
+ }
+ }
+ continue;
+
+ case 't': if (checkforcmd(&p, "tab", 3)) {
+ if (!skip_only) {
+ long tabnr = get_address(
+ eap, &eap->cmd, ADDR_TABS, eap->skip, skip_only, false, 1);
+
+ if (tabnr == MAXLNUM) {
+ cmdmod.tab = tabpage_index(curtab) + 1;
+ } else {
+ if (tabnr < 0 || tabnr > LAST_TAB_NR) {
+ *errormsg = (char_u *)_(e_invrange);
+ return false;
+ }
+ cmdmod.tab = tabnr + 1;
+ }
+ }
+ eap->cmd = p;
+ continue;
+ }
+ if (!checkforcmd(&eap->cmd, "topleft", 2)) {
+ break;
+ }
+ cmdmod.split |= WSP_TOP;
+ continue;
+
+ case 'u': if (!checkforcmd(&eap->cmd, "unsilent", 3))
+ break;
+ if (!skip_only) {
+ if (eap->save_msg_silent == -1) {
+ eap->save_msg_silent = msg_silent;
+ }
+ msg_silent = 0;
+ }
+ continue;
+
+ case 'v': if (checkforcmd(&eap->cmd, "vertical", 4)) {
+ cmdmod.split |= WSP_VERT;
+ continue;
+ }
+ if (!checkforcmd(&p, "verbose", 4))
+ break;
+ if (!skip_only) {
+ if (eap->verbose_save < 0) {
+ eap->verbose_save = p_verbose;
+ }
+ if (ascii_isdigit(*eap->cmd)) {
+ p_verbose = atoi((char *)eap->cmd);
+ } else {
+ p_verbose = 1;
+ }
+ }
+ eap->cmd = p;
+ continue;
+ }
+ break;
}
+
+ return OK;
+}
+
+// Undo and free contents of "cmdmod".
+static void undo_cmdmod(const exarg_T *eap, int save_msg_scroll)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (eap->verbose_save >= 0) {
+ p_verbose = eap->verbose_save;
+ }
+
if (cmdmod.save_ei != NULL) {
/* Restore 'eventignore' to the value before ":noautocmd". */
set_string_option_direct((char_u *)"ei", -1, cmdmod.save_ei,
@@ -2303,20 +2285,15 @@ doend:
free_string_option(cmdmod.save_ei);
}
- if (cmdmod.filter_regmatch.regprog != NULL) {
- vim_regfree(cmdmod.filter_regmatch.regprog);
- }
-
- cmdmod = save_cmdmod;
- reg_executing = save_reg_executing;
+ vim_regfree(cmdmod.filter_regmatch.regprog);
- if (parsed.save_msg_silent != -1) {
+ if (eap->save_msg_silent != -1) {
// messages could be enabled for a serious error, need to check if the
// counters don't become negative
- if (!did_emsg || msg_silent > parsed.save_msg_silent) {
- msg_silent = parsed.save_msg_silent;
+ if (!did_emsg || msg_silent > eap->save_msg_silent) {
+ msg_silent = eap->save_msg_silent;
}
- emsg_silent -= parsed.did_esilent;
+ emsg_silent -= eap->did_esilent;
if (emsg_silent < 0) {
emsg_silent = 0;
}
@@ -2324,27 +2301,19 @@ doend:
// message is actually displayed.
msg_scroll = save_msg_scroll;
- /* "silent reg" or "silent echo x" inside "redir" leaves msg_col
- * somewhere in the line. Put it back in the first column. */
- if (redirecting())
+ // "silent reg" or "silent echo x" inside "redir" leaves msg_col
+ // somewhere in the line. Put it back in the first column.
+ if (redirecting()) {
msg_col = 0;
+ }
}
-
- if (parsed.did_sandbox) {
- sandbox--;
- }
-
- if (ea.nextcmd && *ea.nextcmd == NUL) /* not really a next command */
- ea.nextcmd = NULL;
-
- --ex_nesting_level;
-
- return ea.nextcmd;
}
+
// Parse the address range, if any, in "eap".
+// May set the last search pattern, unless "silent" is true.
// Return FAIL and set "errormsg" or return OK.
-int parse_cmd_address(exarg_T *eap, char_u **errormsg)
+int parse_cmd_address(exarg_T *eap, char_u **errormsg, bool silent)
FUNC_ATTR_NONNULL_ALL
{
int address_count = 1;
@@ -2382,7 +2351,7 @@ int parse_cmd_address(exarg_T *eap, char_u **errormsg)
break;
}
eap->cmd = skipwhite(eap->cmd);
- lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip,
+ lnum = get_address(eap, &eap->cmd, eap->addr_type, eap->skip, silent,
eap->addr_count == 0, address_count++);
if (eap->cmd == NULL) { // error detected
return FAIL;
@@ -2427,6 +2396,7 @@ int parse_cmd_address(exarg_T *eap, char_u **errormsg)
}
break;
case ADDR_TABS_RELATIVE:
+ case ADDR_OTHER:
*errormsg = (char_u *)_(e_invrange);
return FAIL;
case ADDR_ARGUMENTS:
@@ -3725,18 +3695,18 @@ char_u *skip_range(
return (char_u *)cmd;
}
-/*
- * get a single EX address
- *
- * Set ptr to the next character after the part that was interpreted.
- * Set ptr to NULL when an error is encountered.
- *
- * Return MAXLNUM when no Ex address was found.
- */
+// Get a single EX address
+//
+// Set ptr to the next character after the part that was interpreted.
+// Set ptr to NULL when an error is encountered.
+// This may set the last used search pattern.
+//
+// Return MAXLNUM when no Ex address was found.
static linenr_T get_address(exarg_T *eap,
char_u **ptr,
int addr_type, // flag: one of ADDR_LINES, ...
int skip, // only skip the address, don't use it
+ bool silent, // no errors or side effects
int to_other_file, // flag: may jump to other file
int address_count) // 1 for first, >1 after comma
{
@@ -3868,13 +3838,15 @@ static linenr_T get_address(exarg_T *eap,
if (*cmd == c)
++cmd;
} else {
- pos = curwin->w_cursor; /* save curwin->w_cursor */
- /*
- * When '/' or '?' follows another address, start
- * from there.
- */
- if (lnum != MAXLNUM)
+ int flags;
+
+ pos = curwin->w_cursor; // save curwin->w_cursor
+
+ // When '/' or '?' follows another address, start from
+ // there.
+ if (lnum != MAXLNUM) {
curwin->w_cursor.lnum = lnum;
+ }
// Start a forward search at the end of the line (unless
// before the first line).
@@ -3888,7 +3860,8 @@ static linenr_T get_address(exarg_T *eap,
curwin->w_cursor.col = 0;
}
searchcmdlen = 0;
- if (!do_search(NULL, c, cmd, 1L, SEARCH_HIS | SEARCH_MSG, NULL)) {
+ flags = silent ? 0 : SEARCH_HIS | SEARCH_MSG;
+ if (!do_search(NULL, c, cmd, 1L, flags, NULL)) {
curwin->w_cursor = pos;
cmd = NULL;
goto error;
@@ -4469,6 +4442,9 @@ void separate_nextcmd(exarg_T *eap)
else if (p[0] == '`' && p[1] == '=' && (eap->argt & XFILE)) {
p += 2;
(void)skip_expr(&p);
+ if (*p == NUL) { // stop at NUL after CTRL-V
+ break;
+ }
}
/* Check for '"': start of comment or '|': next command */
/* :@" does not start a comment!
@@ -4949,7 +4925,7 @@ check_more(
int n = ARGCOUNT - curwin->w_arg_idx - 1;
if (!forceit && only_one_window()
- && ARGCOUNT > 1 && !arg_had_last && n >= 0 && quitmore == 0) {
+ && ARGCOUNT > 1 && !arg_had_last && n > 0 && quitmore == 0) {
if (message) {
if ((p_confirm || cmdmod.confirm) && curbuf->b_fname != NULL) {
char_u buff[DIALOG_MSG_SIZE];
@@ -5026,8 +5002,13 @@ static int uc_add_command(char_u *name, size_t name_len, char_u *rep,
}
if (cmp == 0) {
- if (!force) {
- EMSG(_("E174: Command already exists: add ! to replace it"));
+ // Command can be replaced with "command!" and when sourcing the
+ // same script again, but only once.
+ if (!force
+ && (cmd->uc_script_ctx.sc_sid != current_sctx.sc_sid
+ || cmd->uc_script_ctx.sc_seq == current_sctx.sc_seq)) {
+ EMSG2(_("E174: Command already exists: add ! to replace it: %s"),
+ name);
goto fail;
}
@@ -5076,16 +5057,18 @@ fail:
static struct {
int expand;
char *name;
+ char *shortname;
} addr_type_complete[] =
{
- { ADDR_ARGUMENTS, "arguments" },
- { ADDR_LINES, "lines" },
- { ADDR_LOADED_BUFFERS, "loaded_buffers" },
- { ADDR_TABS, "tabs" },
- { ADDR_BUFFERS, "buffers" },
- { ADDR_WINDOWS, "windows" },
- { ADDR_QUICKFIX, "quickfix" },
- { -1, NULL }
+ { ADDR_ARGUMENTS, "arguments", "arg" },
+ { ADDR_LINES, "lines", "line" },
+ { ADDR_LOADED_BUFFERS, "loaded_buffers", "load" },
+ { ADDR_TABS, "tabs", "tab" },
+ { ADDR_BUFFERS, "buffers", "buf" },
+ { ADDR_WINDOWS, "windows", "win" },
+ { ADDR_QUICKFIX, "quickfix", "qf" },
+ { ADDR_OTHER, "other", "?" },
+ { -1, NULL, NULL }
};
/*
@@ -5170,7 +5153,7 @@ static void uc_list(char_u *name, size_t name_len)
// Put out the title first time
if (!found) {
MSG_PUTS_TITLE(_("\n Name Args Address "
- "Complete Definition"));
+ "Complete Definition"));
}
found = true;
msg_putchar('\n');
@@ -5256,13 +5239,13 @@ static void uc_list(char_u *name, size_t name_len)
do {
IObuff[len++] = ' ';
- } while (len < 9 - over);
+ } while (len < 8 - over);
// Address Type
for (j = 0; addr_type_complete[j].expand != -1; j++) {
if (addr_type_complete[j].expand != ADDR_LINES
&& addr_type_complete[j].expand == cmd->uc_addr_type) {
- STRCPY(IObuff + len, addr_type_complete[j].name);
+ STRCPY(IObuff + len, addr_type_complete[j].shortname);
len += (int)STRLEN(IObuff + len);
break;
}
@@ -5281,13 +5264,13 @@ static void uc_list(char_u *name, size_t name_len)
do {
IObuff[len++] = ' ';
- } while (len < 24 - over);
+ } while (len < 25 - over);
IObuff[len] = '\0';
msg_outtrans(IObuff);
msg_outtrans_special(cmd->uc_rep, false,
- name_len == 0 ? Columns - 46 : 0);
+ name_len == 0 ? Columns - 47 : 0);
if (p_verbose > 0) {
last_set_msg(cmd->uc_script_ctx);
}
@@ -5416,7 +5399,7 @@ invalid_count:
if (parse_addr_type_arg(val, (int)vallen, argt, addr_type_arg) == FAIL) {
return FAIL;
}
- if (addr_type_arg != ADDR_LINES) {
+ if (*addr_type_arg != ADDR_LINES) {
*argt |= (ZEROR | NOTADR);
}
} else {
@@ -6637,25 +6620,22 @@ static void ex_hide(exarg_T *eap)
/// ":stop" and ":suspend": Suspend Vim.
static void ex_stop(exarg_T *eap)
{
- // Disallow suspending in restricted mode (-Z)
- if (!check_restricted()) {
- if (!eap->forceit) {
- autowrite_all();
- }
- apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL);
+ if (!eap->forceit) {
+ autowrite_all();
+ }
+ apply_autocmds(EVENT_VIMSUSPEND, NULL, NULL, false, NULL);
- // TODO(bfredl): the TUI should do this on suspend
- ui_cursor_goto(Rows - 1, 0);
- ui_call_grid_scroll(1, 0, Rows, 0, Columns, 1, 0);
- ui_flush();
- ui_call_suspend(); // call machine specific function
+ // TODO(bfredl): the TUI should do this on suspend
+ ui_cursor_goto(Rows - 1, 0);
+ ui_call_grid_scroll(1, 0, Rows, 0, Columns, 1, 0);
+ ui_flush();
+ ui_call_suspend(); // call machine specific function
- ui_flush();
- maketitle();
- resettitle(); // force updating the title
- ui_refresh(); // may have resized window
- apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL);
- }
+ ui_flush();
+ maketitle();
+ resettitle(); // force updating the title
+ ui_refresh(); // may have resized window
+ apply_autocmds(EVENT_VIMRESUME, NULL, NULL, false, NULL);
}
// ":exit", ":xit" and ":wq": Write file and quite the current window.
@@ -6945,8 +6925,9 @@ void ex_splitview(exarg_T *eap)
{
win_T *old_curwin = curwin;
char_u *fname = NULL;
-
-
+ const bool use_tab = eap->cmdidx == CMD_tabedit
+ || eap->cmdidx == CMD_tabfind
+ || eap->cmdidx == CMD_tabnew;
/* A ":split" in the quickfix window works like ":new". Don't want two
* quickfix windows. But it's OK when doing ":tab split". */
@@ -6968,9 +6949,7 @@ void ex_splitview(exarg_T *eap)
/*
* Either open new tab page or split the window.
*/
- if (eap->cmdidx == CMD_tabedit
- || eap->cmdidx == CMD_tabfind
- || eap->cmdidx == CMD_tabnew) {
+ if (use_tab) {
if (win_new_tabpage(cmdmod.tab != 0 ? cmdmod.tab : eap->addr_count == 0
? 0 : (int)eap->line2 + 1, eap->arg) != FAIL) {
do_exedit(eap, old_curwin);
@@ -7155,14 +7134,14 @@ static void ex_resize(exarg_T *eap)
n = atol((char *)eap->arg);
if (cmdmod.split & WSP_VERT) {
if (*eap->arg == '-' || *eap->arg == '+') {
- n += curwin->w_width;
+ n += wp->w_width;
} else if (n == 0 && eap->arg[0] == NUL) { // default is very wide
n = Columns;
}
win_setwidth_win(n, wp);
} else {
if (*eap->arg == '-' || *eap->arg == '+') {
- n += curwin->w_height;
+ n += wp->w_height;
} else if (n == 0 && eap->arg[0] == NUL) { // default is very high
n = Rows-1;
}
@@ -7396,7 +7375,7 @@ static void ex_syncbind(exarg_T *eap)
else
scrolldown(-y, TRUE);
curwin->w_scbind_pos = topline;
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
cursor_correct();
curwin->w_redr_status = TRUE;
}
@@ -7520,7 +7499,7 @@ void post_chdir(CdScope scope, bool trigger_dirchanged)
shorten_fnames(true);
if (trigger_dirchanged) {
- do_autocmd_dirchanged(cwd, scope);
+ do_autocmd_dirchanged(cwd, scope, false);
}
}
@@ -7788,7 +7767,7 @@ static void ex_put(exarg_T *eap)
*/
static void ex_copymove(exarg_T *eap)
{
- long n = get_address(eap, &eap->arg, eap->addr_type, false, false, 1);
+ long n = get_address(eap, &eap->arg, eap->addr_type, false, false, false, 1);
if (eap->arg == NULL) { // error detected
eap->nextcmd = NULL;
return;
@@ -8521,7 +8500,7 @@ static void ex_pedit(exarg_T *eap)
if (curwin != curwin_save && win_valid(curwin_save)) {
// Return cursor to where we were
validate_cursor();
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
win_enter(curwin_save, true);
}
g_do_tagpreview = 0;
@@ -8586,6 +8565,24 @@ static void ex_tag_cmd(exarg_T *eap, char_u *name)
eap->forceit, TRUE);
}
+enum {
+ SPEC_PERC = 0,
+ SPEC_HASH,
+ SPEC_CWORD,
+ SPEC_CCWORD,
+ SPEC_CEXPR,
+ SPEC_CFILE,
+ SPEC_SFILE,
+ SPEC_SLNUM,
+ SPEC_STACK,
+ SPEC_AFILE,
+ SPEC_ABUF,
+ SPEC_AMATCH,
+ SPEC_SFLNUM,
+ SPEC_SID,
+ // SPEC_CLIENT,
+};
+
/*
* Check "str" for starting with a special cmdline variable.
* If found return one of the SPEC_ values and set "*usedlen" to the length of
@@ -8596,30 +8593,21 @@ ssize_t find_cmdline_var(const char_u *src, size_t *usedlen)
{
size_t len;
static char *(spec_str[]) = {
- "%",
-#define SPEC_PERC 0
- "#",
-#define SPEC_HASH (SPEC_PERC + 1)
- "<cword>", // cursor word
-#define SPEC_CWORD (SPEC_HASH + 1)
- "<cWORD>", // cursor WORD
-#define SPEC_CCWORD (SPEC_CWORD + 1)
- "<cexpr>", // expr under cursor
-#define SPEC_CEXPR (SPEC_CCWORD + 1)
- "<cfile>", // cursor path name
-#define SPEC_CFILE (SPEC_CEXPR + 1)
- "<sfile>", // ":so" file name
-#define SPEC_SFILE (SPEC_CFILE + 1)
- "<slnum>", // ":so" file line number
-#define SPEC_SLNUM (SPEC_SFILE + 1)
- "<afile>", // autocommand file name
-#define SPEC_AFILE (SPEC_SLNUM + 1)
- "<abuf>", // autocommand buffer number
-#define SPEC_ABUF (SPEC_AFILE + 1)
- "<amatch>", // autocommand match name
-#define SPEC_AMATCH (SPEC_ABUF + 1)
- "<sflnum>", // script file line number
-#define SPEC_SFLNUM (SPEC_AMATCH + 1)
+ [SPEC_PERC] = "%",
+ [SPEC_HASH] = "#",
+ [SPEC_CWORD] = "<cword>", // cursor word
+ [SPEC_CCWORD] = "<cWORD>", // cursor WORD
+ [SPEC_CEXPR] = "<cexpr>", // expr under cursor
+ [SPEC_CFILE] = "<cfile>", // cursor path name
+ [SPEC_SFILE] = "<sfile>", // ":so" file name
+ [SPEC_SLNUM] = "<slnum>", // ":so" file line number
+ [SPEC_STACK] = "<stack>", // call stack
+ [SPEC_AFILE] = "<afile>", // autocommand file name
+ [SPEC_ABUF] = "<abuf>", // autocommand buffer number
+ [SPEC_AMATCH] = "<amatch>", // autocommand match name
+ [SPEC_SFLNUM] = "<sflnum>", // script file line number
+ [SPEC_SID] = "<SID>", // script ID: <SNR>123_
+ // [SPEC_CLIENT] = "<client>",
};
for (size_t i = 0; i < ARRAY_SIZE(spec_str); ++i) {
@@ -8866,6 +8854,16 @@ eval_vars (
result = (char_u *)strbuf;
break;
+ case SPEC_SID:
+ if (current_sctx.sc_sid <= 0) {
+ *errormsg = (char_u *)_(e_usingsid);
+ return NULL;
+ }
+ snprintf(strbuf, sizeof(strbuf), "<SNR>%" PRIdSCID "_",
+ current_sctx.sc_sid);
+ result = (char_u *)strbuf;
+ break;
+
default:
// should not happen
*errormsg = (char_u *)"";
@@ -9309,14 +9307,17 @@ static void ex_match(exarg_T *eap)
static void ex_fold(exarg_T *eap)
{
if (foldManualAllowed(true)) {
- foldCreate(curwin, eap->line1, eap->line2);
+ pos_T start = { eap->line1, 1, 0 };
+ pos_T end = { eap->line2, 1, 0 };
+ foldCreate(curwin, start, end);
}
}
static void ex_foldopen(exarg_T *eap)
{
- opFoldRange(eap->line1, eap->line2, eap->cmdidx == CMD_foldopen,
- eap->forceit, FALSE);
+ pos_T start = { eap->line1, 1, 0 };
+ pos_T end = { eap->line2, 1, 0 };
+ opFoldRange(start, end, eap->cmdidx == CMD_foldopen, eap->forceit, false);
}
static void ex_folddo(exarg_T *eap)
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index 81274fcf2a..0c7562980a 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -87,17 +87,16 @@
*/
static int cause_abort = FALSE;
-/*
- * Return TRUE when immediately aborting on error, or when an interrupt
- * occurred or an exception was thrown but not caught. Use for ":{range}call"
- * to check whether an aborted function that does not handle a range itself
- * should be called again for the next line in the range. Also used for
- * cancelling expression evaluation after a function call caused an immediate
- * abort. Note that the first emsg() call temporarily resets "force_abort"
- * until the throw point for error messages has been reached. That is, during
- * cancellation of an expression evaluation after an aborting function call or
- * due to a parsing error, aborting() always returns the same value.
- */
+// Return true when immediately aborting on error, or when an interrupt
+// occurred or an exception was thrown but not caught. Use for ":{range}call"
+// to check whether an aborted function that does not handle a range itself
+// should be called again for the next line in the range. Also used for
+// cancelling expression evaluation after a function call caused an immediate
+// abort. Note that the first emsg() call temporarily resets "force_abort"
+// until the throw point for error messages has been reached. That is, during
+// cancellation of an expression evaluation after an aborting function call or
+// due to a parsing error, aborting() always returns the same value.
+// "got_int" is also set by calling interrupt().
int aborting(void)
{
return (did_emsg && force_abort) || got_int || current_exception;
@@ -139,16 +138,15 @@ int aborted_in_try(void)
return force_abort;
}
-/*
- * cause_errthrow(): Cause a throw of an error exception if appropriate.
- * Return TRUE if the error message should not be displayed by emsg().
- * Sets "ignore", if the emsg() call should be ignored completely.
- *
- * When several messages appear in the same command, the first is usually the
- * most specific one and used as the exception value. The "severe" flag can be
- * set to TRUE, if a later but severer message should be used instead.
- */
-int cause_errthrow(char_u *mesg, int severe, int *ignore)
+// cause_errthrow(): Cause a throw of an error exception if appropriate.
+// Return true if the error message should not be displayed by emsg().
+// Sets "ignore", if the emsg() call should be ignored completely.
+//
+// When several messages appear in the same command, the first is usually the
+// most specific one and used as the exception value. The "severe" flag can be
+// set to true, if a later but severer message should be used instead.
+bool cause_errthrow(const char_u *mesg, bool severe, bool *ignore)
+ FUNC_ATTR_NONNULL_ALL
{
struct msglist *elem;
struct msglist **plist;
@@ -159,8 +157,9 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore)
* level. Also when no exception can be thrown. The message will be
* displayed by emsg().
*/
- if (suppress_errthrow)
- return FALSE;
+ if (suppress_errthrow) {
+ return false;
+ }
/*
* If emsg() has not been called previously, temporarily reset
@@ -196,8 +195,8 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore)
* not replaced by an interrupt message error exception.
*/
if (mesg == (char_u *)_(e_interr)) {
- *ignore = TRUE;
- return TRUE;
+ *ignore = true;
+ return true;
}
/*
@@ -232,8 +231,8 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore)
* catch clause; just finally clauses are executed before the script
* is terminated.
*/
- return FALSE;
- } else
+ return false;
+ } else // NOLINT(readability/braces)
#endif
{
/*
@@ -272,7 +271,7 @@ int cause_errthrow(char_u *mesg, int severe, int *ignore)
(*msg_list)->throw_msg = tmsg;
}
}
- return TRUE;
+ return true;
}
}
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index 93684e8606..53feffd2d7 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -137,6 +137,7 @@ struct cmdline_info {
/// Last value of prompt_id, incremented when doing new prompt
static unsigned last_prompt_id = 0;
+// Struct to store the viewstate during 'incsearch' highlighting.
typedef struct {
colnr_T vs_curswant;
colnr_T vs_leftcol;
@@ -146,6 +147,19 @@ typedef struct {
int vs_empty_rows;
} viewstate_T;
+// Struct to store the state of 'incsearch' highlighting.
+typedef struct {
+ pos_T search_start; // where 'incsearch' starts searching
+ pos_T save_cursor;
+ viewstate_T init_viewstate;
+ viewstate_T old_viewstate;
+ pos_T match_start;
+ pos_T match_end;
+ bool did_incsearch;
+ bool incsearch_postponed;
+ int magic_save;
+} incsearch_state_T;
+
typedef struct command_line_state {
VimState state;
int firstc;
@@ -159,14 +173,7 @@ typedef struct command_line_state {
int save_hiscnt; // history line before attempting
// to jump to next match
int histype; // history type to be used
- pos_T search_start; // where 'incsearch' starts searching
- pos_T save_cursor;
- viewstate_T init_viewstate;
- viewstate_T old_viewstate;
- pos_T match_start;
- pos_T match_end;
- int did_incsearch;
- int incsearch_postponed;
+ incsearch_state_T is_state;
int did_wild_list; // did wild_list() recently
int wim_index; // index in wim_flags[]
int res;
@@ -252,6 +259,395 @@ static void restore_viewstate(viewstate_T *vs)
curwin->w_empty_rows = vs->vs_empty_rows;
}
+static void init_incsearch_state(incsearch_state_T *s)
+{
+ s->match_start = curwin->w_cursor;
+ s->did_incsearch = false;
+ s->incsearch_postponed = false;
+ s->magic_save = p_magic;
+ clearpos(&s->match_end);
+ s->save_cursor = curwin->w_cursor; // may be restored later
+ s->search_start = curwin->w_cursor;
+ save_viewstate(&s->init_viewstate);
+ save_viewstate(&s->old_viewstate);
+}
+
+// Return true when 'incsearch' highlighting is to be done.
+// Sets search_first_line and search_last_line to the address range.
+static bool do_incsearch_highlighting(int firstc, incsearch_state_T *s,
+ int *skiplen, int *patlen)
+ FUNC_ATTR_NONNULL_ALL
+{
+ char_u *cmd;
+ cmdmod_T save_cmdmod = cmdmod;
+ char_u *p;
+ bool delim_optional = false;
+ int delim;
+ char_u *end;
+ char_u *dummy;
+ exarg_T ea;
+ pos_T save_cursor;
+ bool use_last_pat;
+ bool retval = false;
+
+ *skiplen = 0;
+ *patlen = ccline.cmdlen;
+
+ if (!p_is || cmd_silent) {
+ return false;
+ }
+
+ // by default search all lines
+ search_first_line = 0;
+ search_last_line = MAXLNUM;
+
+ if (firstc == '/' || firstc == '?') {
+ return true;
+ }
+ if (firstc != ':') {
+ return false;
+ }
+
+ emsg_off++;
+ memset(&ea, 0, sizeof(ea));
+ ea.line1 = 1;
+ ea.line2 = 1;
+ ea.cmd = ccline.cmdbuff;
+ ea.addr_type = ADDR_LINES;
+
+ parse_command_modifiers(&ea, &dummy, true);
+ cmdmod = save_cmdmod;
+
+ cmd = skip_range(ea.cmd, NULL);
+ if (vim_strchr((char_u *)"sgvl", *cmd) == NULL) {
+ goto theend;
+ }
+
+ // Skip over "substitute" to find the pattern separator.
+ for (p = cmd; ASCII_ISALPHA(*p); p++) {}
+ if (*skipwhite(p) == NUL) {
+ goto theend;
+ }
+
+ if (STRNCMP(cmd, "substitute", p - cmd) == 0
+ || STRNCMP(cmd, "smagic", p - cmd) == 0
+ || STRNCMP(cmd, "snomagic", MAX(p - cmd, 3)) == 0
+ || STRNCMP(cmd, "vglobal", p - cmd) == 0) {
+ if (*cmd == 's' && cmd[1] == 'm') {
+ p_magic = true;
+ } else if (*cmd == 's' && cmd[1] == 'n') {
+ p_magic = false;
+ }
+ } else if (STRNCMP(cmd, "sort", MAX(p - cmd, 3)) == 0) {
+ // skip over ! and flags
+ if (*p == '!') {
+ p = skipwhite(p + 1);
+ }
+ while (ASCII_ISALPHA(*(p = skipwhite(p)))) {
+ p++;
+ }
+ if (*p == NUL) {
+ goto theend;
+ }
+ } else if (STRNCMP(cmd, "vimgrep", MAX(p - cmd, 3)) == 0
+ || STRNCMP(cmd, "vimgrepadd", MAX(p - cmd, 8)) == 0
+ || STRNCMP(cmd, "lvimgrep", MAX(p - cmd, 2)) == 0
+ || STRNCMP(cmd, "lvimgrepadd", MAX(p - cmd, 9)) == 0
+ || STRNCMP(cmd, "global", p - cmd) == 0) {
+ // skip over "!/".
+ if (*p == '!') {
+ p++;
+ if (*skipwhite(p) == NUL) {
+ goto theend;
+ }
+ }
+ if (*cmd != 'g') {
+ delim_optional = true;
+ }
+ } else {
+ goto theend;
+ }
+
+ p = skipwhite(p);
+ delim = (delim_optional && vim_isIDc(*p)) ? ' ' : *p++;
+ end = skip_regexp(p, delim, p_magic, NULL);
+
+ use_last_pat = end == p && *end == delim;
+ if (end == p && !use_last_pat) {
+ goto theend;
+ }
+
+ // Don't do 'hlsearch' highlighting if the pattern matches everything.
+ if (!use_last_pat) {
+ char_u c = *end;
+ int empty;
+
+ *end = NUL;
+ empty = empty_pattern(p);
+ *end = c;
+ if (empty) {
+ goto theend;
+ }
+ }
+
+ // found a non-empty pattern or //
+ *skiplen = (int)(p - ccline.cmdbuff);
+ *patlen = (int)(end - p);
+
+ // parse the address range
+ save_cursor = curwin->w_cursor;
+ curwin->w_cursor = s->search_start;
+ parse_cmd_address(&ea, &dummy, true);
+ if (ea.addr_count > 0) {
+ // Allow for reverse match.
+ if (ea.line2 < ea.line1) {
+ search_first_line = ea.line2;
+ search_last_line = ea.line1;
+ } else {
+ search_first_line = ea.line1;
+ search_last_line = ea.line2;
+ }
+ } else if (cmd[0] == 's' && cmd[1] != 'o') {
+ // :s defaults to the current line
+ search_first_line = curwin->w_cursor.lnum;
+ search_last_line = curwin->w_cursor.lnum;
+ }
+
+ curwin->w_cursor = save_cursor;
+ retval = true;
+theend:
+ emsg_off--;
+ return retval;
+}
+
+// May do 'incsearch' highlighting if desired.
+static void may_do_incsearch_highlighting(int firstc, long count,
+ incsearch_state_T *s)
+{
+ pos_T end_pos;
+ proftime_T tm;
+ searchit_arg_T sia;
+ int skiplen, patlen;
+ char_u next_char;
+ char_u use_last_pat;
+
+ // Parsing range may already set the last search pattern.
+ // NOTE: must call restore_last_search_pattern() before returning!
+ save_last_search_pattern();
+
+ if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) {
+ restore_last_search_pattern();
+ finish_incsearch_highlighting(false, s, true);
+ return;
+ }
+
+ // if there is a character waiting, search and redraw later
+ if (char_avail()) {
+ restore_last_search_pattern();
+ s->incsearch_postponed = true;
+ return;
+ }
+ s->incsearch_postponed = false;
+
+ if (search_first_line == 0) {
+ // start at the original cursor position
+ curwin->w_cursor = s->search_start;
+ } else if (search_first_line > curbuf->b_ml.ml_line_count) {
+ // start after the last line
+ curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
+ curwin->w_cursor.col = MAXCOL;
+ } else {
+ // start at the first line in the range
+ curwin->w_cursor.lnum = search_first_line;
+ curwin->w_cursor.col = 0;
+ }
+ int found; // do_search() result
+
+ // Use the previous pattern for ":s//".
+ next_char = ccline.cmdbuff[skiplen + patlen];
+ use_last_pat = patlen == 0 && skiplen > 0
+ && ccline.cmdbuff[skiplen - 1] == next_char;
+
+ // If there is no pattern, don't do anything.
+ if (patlen == 0 && !use_last_pat) {
+ found = 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;
+ }
+ if (search_first_line != 0) {
+ search_flags += SEARCH_START;
+ }
+ ccline.cmdbuff[skiplen + patlen] = NUL;
+ memset(&sia, 0, sizeof(sia));
+ sia.sa_tm = &tm;
+ found = do_search(NULL, firstc == ':' ? '/' : firstc,
+ ccline.cmdbuff + skiplen, count,
+ search_flags, &sia);
+ ccline.cmdbuff[skiplen + patlen] = next_char;
+ emsg_off--;
+ if (curwin->w_cursor.lnum < search_first_line
+ || curwin->w_cursor.lnum > search_last_line) {
+ // match outside of address range
+ found = 0;
+ curwin->w_cursor = s->search_start;
+ }
+
+ // if interrupted while searching, behave like it failed
+ if (got_int) {
+ (void)vpeekc(); // remove <C-C> from input stream
+ got_int = false; // don't abandon the command line
+ found = 0;
+ } else if (char_avail()) {
+ // cancelled searching because a char was typed
+ s->incsearch_postponed = true;
+ }
+ ui_busy_stop();
+ }
+
+ if (found != 0) {
+ highlight_match = true; // highlight position
+ } else {
+ highlight_match = false; // remove highlight
+ }
+
+ // first restore the old curwin values, so the screen is
+ // positioned in the same way as the actual search command
+ restore_viewstate(&s->old_viewstate);
+ changed_cline_bef_curs();
+ update_topline();
+
+ if (found != 0) {
+ pos_T save_pos = curwin->w_cursor;
+
+ s->match_start = curwin->w_cursor;
+ set_search_match(&curwin->w_cursor);
+ validate_cursor();
+ end_pos = curwin->w_cursor;
+ s->match_end = end_pos;
+ curwin->w_cursor = save_pos;
+ } else {
+ end_pos = curwin->w_cursor; // shutup gcc 4
+ }
+ //
+ // Disable 'hlsearch' highlighting if the pattern matches
+ // everything. Avoids a flash when typing "foo\|".
+ if (!use_last_pat) {
+ next_char = ccline.cmdbuff[skiplen + patlen];
+ ccline.cmdbuff[skiplen + patlen] = NUL;
+ if (empty_pattern(ccline.cmdbuff) && !no_hlsearch) {
+ redraw_all_later(SOME_VALID);
+ set_no_hlsearch(true);
+ }
+ ccline.cmdbuff[skiplen + patlen] = next_char;
+ }
+
+ validate_cursor();
+ // May redraw the status line to show the cursor position.
+ if (p_ru && curwin->w_status_height > 0) {
+ curwin->w_redr_status = true;
+ }
+
+ update_screen(SOME_VALID);
+ highlight_match = false;
+ restore_last_search_pattern();
+
+ // Leave it at the end to make CTRL-R CTRL-W work. But not when beyond the
+ // end of the pattern, e.g. for ":s/pat/".
+ if (ccline.cmdbuff[skiplen + patlen] != NUL) {
+ curwin->w_cursor = s->search_start;
+ } else if (found != 0) {
+ curwin->w_cursor = end_pos;
+ }
+
+ msg_starthere();
+ redrawcmdline();
+ s->did_incsearch = true;
+}
+
+// When CTRL-L typed: add character from the match to the pattern.
+// May set "*c" to the added character.
+// Return OK when calling command_line_not_changed.
+static int may_add_char_to_search(int firstc, int *c, incsearch_state_T *s)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int skiplen, patlen;
+
+ // Parsing range may already set the last search pattern.
+ // NOTE: must call restore_last_search_pattern() before returning!
+ save_last_search_pattern();
+
+ // Add a character from under the cursor for 'incsearch'
+ if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) {
+ restore_last_search_pattern();
+ return FAIL;
+ }
+ restore_last_search_pattern();
+
+ if (s->did_incsearch) {
+ curwin->w_cursor = s->match_end;
+ *c = gchar_cursor();
+ if (*c != NUL) {
+ // If 'ignorecase' and 'smartcase' are set and the
+ // command line has no uppercase characters, convert
+ // the character to lowercase
+ if (p_ic && p_scs
+ && !pat_has_uppercase(ccline.cmdbuff + skiplen)) {
+ *c = mb_tolower(*c);
+ }
+ if (*c == firstc
+ || vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), *c)
+ != NULL) {
+ // put a backslash before special characters
+ stuffcharReadbuff(*c);
+ *c = '\\';
+ }
+ return FAIL;
+ }
+ }
+ return OK;
+}
+
+static void finish_incsearch_highlighting(int gotesc, incsearch_state_T *s,
+ bool call_update_screen)
+{
+ if (s->did_incsearch) {
+ s->did_incsearch = false;
+ if (gotesc) {
+ curwin->w_cursor = s->save_cursor;
+ } else {
+ if (!equalpos(s->save_cursor, s->search_start)) {
+ // put the '" mark at the original position
+ curwin->w_cursor = s->save_cursor;
+ setpcmark();
+ }
+ curwin->w_cursor = s->search_start; // -V519
+ }
+ restore_viewstate(&s->old_viewstate);
+ highlight_match = false;
+
+ // by default search all lines
+ search_first_line = 0;
+ search_last_line = MAXLNUM;
+
+ p_magic = s->magic_save;
+
+ validate_cursor(); // needed for TAB
+ redraw_all_later(SOME_VALID);
+ if (call_update_screen) {
+ update_screen(SOME_VALID);
+ }
+ }
+}
+
/// Internal entry point for cmdline mode.
///
/// caller must use save_cmdline and restore_cmdline. Best is to use
@@ -269,11 +665,10 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
.save_msg_scroll = msg_scroll,
.save_State = State,
.ignore_drag_release = true,
- .match_start = curwin->w_cursor,
};
CommandLineState *s = &state;
s->save_p_icm = vim_strsave(p_icm);
- save_viewstate(&state.init_viewstate);
+ init_incsearch_state(&s->is_state);
if (s->firstc == -1) {
s->firstc = NUL;
@@ -288,10 +683,6 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
ccline.prompt_id = last_prompt_id++;
ccline.level = cmdline_level;
ccline.overstrike = false; // always start in insert mode
- clearpos(&s->match_end);
- s->save_cursor = curwin->w_cursor; // may be restored later
- s->search_start = curwin->w_cursor;
- save_viewstate(&state.old_viewstate);
assert(indent >= 0);
@@ -455,22 +846,7 @@ static uint8_t *command_line_enter(int firstc, long count, int indent)
close_preview_windows();
}
- if (s->did_incsearch) {
- if (s->gotesc) {
- curwin->w_cursor = s->save_cursor;
- } else {
- if (!equalpos(s->save_cursor, s->search_start)) {
- // put the '" mark at the original position
- curwin->w_cursor = s->save_cursor;
- setpcmark();
- }
- curwin->w_cursor = s->search_start; // -V519
- }
- restore_viewstate(&s->old_viewstate);
- highlight_match = false;
- validate_cursor(); // needed for TAB
- redraw_all_later(SOME_VALID);
- }
+ finish_incsearch_highlighting(s->gotesc, &s->is_state, false);
if (ccline.cmdbuff != NULL) {
// Put line in history buffer (":" and "=" only when it was typed).
@@ -1075,24 +1451,46 @@ static int command_line_execute(VimState *state, int key)
return command_line_handle_key(s);
}
-static void command_line_next_incsearch(CommandLineState *s, bool next_match)
+// May adjust 'incsearch' highlighting for typing CTRL-G and CTRL-T, go to next
+// or previous match.
+// Returns FAIL when calling command_line_not_changed.
+static int may_do_command_line_next_incsearch(int firstc, long count,
+ incsearch_state_T *s,
+ bool next_match)
+ FUNC_ATTR_NONNULL_ALL
{
+ int skiplen, patlen;
+
+ // Parsing range may already set the last search pattern.
+ // NOTE: must call restore_last_search_pattern() before returning!
+ save_last_search_pattern();
+
+ if (!do_incsearch_highlighting(firstc, s, &skiplen, &patlen)) {
+ restore_last_search_pattern();
+ return OK;
+ }
+ if (patlen == 0 && ccline.cmdbuff[skiplen] == NUL) {
+ restore_last_search_pattern();
+ return FAIL;
+ }
+
ui_busy_start();
ui_flush();
pos_T t;
char_u *pat;
int search_flags = SEARCH_NOOF;
+ char_u save;
- if (s->firstc == ccline.cmdbuff[0]) {
+ if (firstc == ccline.cmdbuff[skiplen]) {
pat = last_search_pattern();
+ skiplen = 0;
+ patlen = (int)STRLEN(pat);
} else {
- pat = ccline.cmdbuff;
+ pat = ccline.cmdbuff + skiplen;
}
- save_last_search_pattern();
-
if (next_match) {
t = s->match_end;
if (lt(s->match_start, s->match_end)) {
@@ -1108,23 +1506,26 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match)
search_flags += SEARCH_KEEP;
}
emsg_off++;
+ save = pat[patlen];
+ pat[patlen] = NUL;
int found = searchit(curwin, curbuf, &t, NULL,
next_match ? FORWARD : BACKWARD,
- pat, s->count, search_flags,
+ pat, count, search_flags,
RE_SEARCH, NULL);
emsg_off--;
+ pat[patlen] = save;
ui_busy_stop();
if (found) {
s->search_start = s->match_start;
s->match_end = t;
s->match_start = t;
- if (!next_match && s->firstc == '/') {
+ if (!next_match && firstc != '?') {
// move just before the current match, so that
// when nv_search finishes the cursor will be
// put back on the match
s->search_start = t;
(void)decl(&s->search_start);
- } else if (next_match && s->firstc == '?') {
+ } else if (next_match && firstc == '?') {
// move just after the current match, so that
// when nv_search finishes the cursor will be
// put back on the match
@@ -1134,7 +1535,7 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match)
if (lt(t, s->search_start) && next_match) {
// wrap around
s->search_start = t;
- if (s->firstc == '?') {
+ if (firstc == '?') {
(void)incl(&s->search_start);
} else {
(void)decl(&s->search_start);
@@ -1149,12 +1550,14 @@ static void command_line_next_incsearch(CommandLineState *s, bool next_match)
highlight_match = true;
save_viewstate(&s->old_viewstate);
update_screen(NOT_VALID);
+ highlight_match = false;
redrawcmdline();
+ curwin->w_cursor = s->match_end;
} else {
vim_beep(BO_ERROR);
}
restore_last_search_pattern();
- return;
+ return FAIL;
}
static void command_line_next_histidx(CommandLineState *s, bool next_match)
@@ -1265,10 +1668,10 @@ static int command_line_handle_key(CommandLineState *s)
// Truncate at the end, required for multi-byte chars.
ccline.cmdbuff[ccline.cmdlen] = NUL;
if (ccline.cmdlen == 0) {
- s->search_start = s->save_cursor;
+ s->is_state.search_start = s->is_state.save_cursor;
// save view settings, so that the screen won't be restored at the
// wrong position
- s->old_viewstate = s->init_viewstate;
+ s->is_state.old_viewstate = s->is_state.init_viewstate;
}
redrawcmd();
} else if (ccline.cmdlen == 0 && s->c != Ctrl_W
@@ -1287,7 +1690,7 @@ static int command_line_handle_key(CommandLineState *s)
}
msg_putchar(' '); // delete ':'
}
- s->search_start = s->save_cursor;
+ s->is_state.search_start = s->is_state.save_cursor;
redraw_cmdline = true;
return 0; // back to cmd mode
}
@@ -1337,7 +1740,7 @@ static int command_line_handle_key(CommandLineState *s)
// Truncate at the end, required for multi-byte chars.
ccline.cmdbuff[ccline.cmdlen] = NUL;
if (ccline.cmdlen == 0) {
- s->search_start = s->save_cursor;
+ s->is_state.search_start = s->is_state.save_cursor;
}
redrawcmd();
return command_line_changed(s);
@@ -1565,31 +1968,7 @@ static int command_line_handle_key(CommandLineState *s)
return command_line_changed(s);
case Ctrl_L:
- if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) {
- // Add a character from under the cursor for 'incsearch'
- if (s->did_incsearch) {
- curwin->w_cursor = s->match_end;
- if (!equalpos(curwin->w_cursor, s->search_start)) {
- s->c = gchar_cursor();
- // If 'ignorecase' and 'smartcase' are set and the
- // command line has no uppercase characters, convert
- // the character to lowercase
- if (p_ic && p_scs
- && !pat_has_uppercase(ccline.cmdbuff)) {
- s->c = mb_tolower(s->c);
- }
- if (s->c != NUL) {
- if (s->c == s->firstc
- || vim_strchr((char_u *)(p_magic ? "\\~^$.*[" : "\\^$"), s->c)
- != NULL) {
- // put a backslash before special characters
- stuffcharReadbuff(s->c);
- s->c = '\\';
- }
- break;
- }
- }
- }
+ if (may_add_char_to_search(s->firstc, &s->c, &s->is_state) == OK) {
return command_line_not_changed(s);
}
@@ -1703,10 +2082,8 @@ 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 (ccline.cmdlen != 0) {
- command_line_next_incsearch(s, s->c == Ctrl_G);
- }
+ if (may_do_command_line_next_incsearch(s->firstc, s->count, &s->is_state,
+ s->c == Ctrl_G) == FAIL) {
return command_line_not_changed(s);
}
break;
@@ -1791,7 +2168,7 @@ static int command_line_not_changed(CommandLineState *s)
// command line did not change. Then we only search and redraw if something
// changed in the past.
// Enter command_line_changed() when the command line did change.
- if (!s->incsearch_postponed) {
+ if (!s->is_state.incsearch_postponed) {
return 1;
}
return command_line_changed(s);
@@ -1843,108 +2220,13 @@ static int command_line_changed(CommandLineState *s)
}
// 'incsearch' highlighting.
- if (p_is && !cmd_silent && (s->firstc == '/' || s->firstc == '?')) {
- pos_T end_pos;
- proftime_T tm;
- searchit_arg_T sia;
-
- // if there is a character waiting, search and redraw later
- if (char_avail()) {
- s->incsearch_postponed = true;
- return 1;
- }
- s->incsearch_postponed = false;
- curwin->w_cursor = s->search_start; // start at old position
- save_last_search_pattern();
- int i;
-
- // If there is no command line, don't do anything
- if (ccline.cmdlen == 0) {
- 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;
- }
- memset(&sia, 0, sizeof(sia));
- sia.sa_tm = &tm;
- i = do_search(NULL, s->firstc, ccline.cmdbuff, s->count,
- search_flags, &sia);
- emsg_off--;
- // if interrupted while searching, behave like it failed
- if (got_int) {
- (void)vpeekc(); // remove <C-C> from input stream
- got_int = false; // don't abandon the command line
- i = 0;
- } else if (char_avail()) {
- // cancelled searching because a char was typed
- s->incsearch_postponed = true;
- }
- ui_busy_stop();
- }
-
- if (i != 0) {
- highlight_match = true; // highlight position
- } else {
- highlight_match = false; // remove highlight
- }
-
- // first restore the old curwin values, so the screen is
- // positioned in the same way as the actual search command
- restore_viewstate(&s->old_viewstate);
- changed_cline_bef_curs();
- update_topline();
-
- if (i != 0) {
- pos_T save_pos = curwin->w_cursor;
-
- s->match_start = curwin->w_cursor;
- set_search_match(&curwin->w_cursor);
- validate_cursor();
- end_pos = curwin->w_cursor;
- s->match_end = end_pos;
- curwin->w_cursor = save_pos;
- } else {
- 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) {
- curwin->w_redr_status = true;
- }
-
- update_screen(SOME_VALID);
- restore_last_search_pattern();
-
- // Leave it at the end to make CTRL-R CTRL-W work.
- if (i != 0) {
- curwin->w_cursor = end_pos;
- }
-
- msg_starthere();
- redrawcmdline();
- s->did_incsearch = true;
- } else if (s->firstc == ':'
- && current_sctx.sc_sid == 0 // only if interactive
- && *p_icm != NUL // 'inccommand' is set
- && curbuf->b_p_ma // buffer is modifiable
- && cmdline_star == 0 // not typing a password
- && cmd_can_preview(ccline.cmdbuff)
- && !vpeekc_any()) {
+ if (s->firstc == ':'
+ && current_sctx.sc_sid == 0 // only if interactive
+ && *p_icm != NUL // 'inccommand' is set
+ && curbuf->b_p_ma // buffer is modifiable
+ && cmdline_star == 0 // not typing a password
+ && cmd_can_preview(ccline.cmdbuff)
+ && !vpeekc_any()) {
// Show 'inccommand' preview. It works like this:
// 1. Do the command.
// 2. Command implementation detects CMDPREVIEW state, then:
@@ -1958,8 +2240,8 @@ static int command_line_changed(CommandLineState *s)
emsg_silent--; // Unblock error reporting
// Restore the window "view".
- curwin->w_cursor = s->save_cursor;
- restore_viewstate(&s->old_viewstate);
+ curwin->w_cursor = s->is_state.save_cursor;
+ restore_viewstate(&s->is_state.old_viewstate);
update_topline();
redrawcmdline();
@@ -1968,6 +2250,8 @@ static int command_line_changed(CommandLineState *s)
State = (State & ~CMDPREVIEW);
close_preview_windows();
update_screen(SOME_VALID); // Clear 'inccommand' preview.
+ } else {
+ may_do_incsearch_highlighting(s->firstc, s->count, &s->is_state);
}
if (cmdmsg_rl || (p_arshape && !p_tbidi && enc_utf8)) {
@@ -4706,7 +4990,7 @@ ExpandFromContext (
char_u *pat,
int *num_file,
char_u ***file,
- int options /* EW_ flags */
+ int options // WILD_ flags
)
{
regmatch_T regmatch;
@@ -4768,6 +5052,21 @@ ExpandFromContext (
ret = expand_wildcards_eval(&pat, num_file, file, flags);
if (free_pat)
xfree(pat);
+#ifdef BACKSLASH_IN_FILENAME
+ if (p_csl[0] != NUL && (options & WILD_IGNORE_COMPLETESLASH) == 0) {
+ for (int i = 0; i < *num_file; i++) {
+ char_u *ptr = (*file)[i];
+ while (*ptr != NUL) {
+ if (p_csl[0] == 's' && *ptr == '\\') {
+ *ptr = '/';
+ } else if (p_csl[0] == 'b' && *ptr == '/') {
+ *ptr = '\\';
+ }
+ ptr += utfc_ptr2len(ptr);
+ }
+ }
+ }
+#endif
return ret;
}
@@ -4909,7 +5208,7 @@ ExpandFromContext (
* obtain strings, one by one. The strings are matched against a regexp
* program. Matching strings are copied into an array, which is returned.
*/
-void ExpandGeneric(
+static void ExpandGeneric(
expand_T *xp,
regmatch_T *regmatch,
int *num_file,
@@ -6184,17 +6483,20 @@ static int open_cmdwin(void)
ccline.redraw_state = kCmdRedrawNone;
ui_call_cmdline_hide(ccline.level);
}
- redraw_later(SOME_VALID);
+ redraw_later(curwin, SOME_VALID);
// Save the command line info, can be used recursively.
save_cmdline(&save_ccline);
- /* No Ex mode here! */
+ // No Ex mode here!
exmode_active = 0;
State = NORMAL;
setmouse();
+ // Reset here so it can be set by a CmdWinEnter autocommand.
+ cmdwin_result = 0;
+
// Trigger CmdwinEnter autocommands.
typestr[0] = (char_u)cmdwin_type;
typestr[1] = NUL;
@@ -6210,7 +6512,6 @@ static int open_cmdwin(void)
/*
* Call the main loop until <CR> or CTRL-C is typed.
*/
- cmdwin_result = 0;
normal_enter(true, false);
RedrawingDisabled = i;
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index 1b797c6168..42a9ef08f9 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -493,7 +493,8 @@ static int put_view(
/// Writes commands for restoring the current buffers, for :mksession.
///
-/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled.
+/// Legacy 'sessionoptions'/'viewoptions' flags SSOP_UNIX, SSOP_SLASH are
+/// always enabled.
///
/// @param dirnow Current directory name
/// @param fd File descriptor to write to
@@ -822,9 +823,9 @@ void ex_loadview(exarg_T *eap)
/// ":mkexrc", ":mkvimrc", ":mkview", ":mksession".
///
-/// Legacy 'sessionoptions' flags SSOP_UNIX, SSOP_SLASH are always enabled.
-/// - SSOP_UNIX: line-endings are always LF
-/// - SSOP_SLASH: filenames are always written with "/" slash
+/// Legacy 'sessionoptions'/'viewoptions' flags are always enabled:
+/// - SSOP_UNIX: line-endings are LF
+/// - SSOP_SLASH: filenames are written with "/" slash
void ex_mkrc(exarg_T *eap)
{
FILE *fd;
@@ -896,8 +897,8 @@ void ex_mkrc(exarg_T *eap)
if (!failed && view_session) {
if (put_line(fd,
- "let s:so_save = &so | let s:siso_save = &siso"
- " | set so=0 siso=0") == FAIL) {
+ "let s:so_save = &g:so | let s:siso_save = &g:siso"
+ " | setg so=0 siso=0 | setl so=-1 siso=-1") == FAIL) {
failed = true;
}
if (eap->cmdidx == CMD_mksession) {
@@ -948,7 +949,7 @@ void ex_mkrc(exarg_T *eap)
}
if (fprintf(fd,
"%s",
- "let &so = s:so_save | let &siso = s:siso_save\n"
+ "let &g:so = s:so_save | let &g:siso = s:siso_save\n"
"doautoall SessionLoadPost\n")
< 0) {
failed = true;
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index 1457a1172d..ba685b158e 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -1,38 +1,39 @@
// 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
-// Implements extended marks for plugins. Each mark exists in a btree of
-// lines containing btrees of columns.
+// Implements extended marks for plugins. Marks sit in a MarkTree
+// datastructure which provides both efficient mark insertations/lookups
+// and adjustment to text changes. See marktree.c for more details.
//
-// The btree provides efficient range lookups.
// A map of pointers to the marks is used for fast lookup by mark id.
//
-// Marks are moved by calls to extmark_splice. Additionally mark_adjust
-// might adjust extmarks to line inserts/deletes.
+// Marks are moved by calls to extmark_splice. Some standard interfaces
+// mark_adjust and inserted_bytes already adjust marks, check if these are
+// being used before adding extmark_splice calls!
//
// Undo/Redo of marks is implemented by storing the call arguments to
// extmark_splice. The list of arguments is applied in extmark_apply_undo.
-// The only case where we have to copy extmarks is for the area being effected
-// by a delete.
+// We have to copy extmark positions when the extmarks are within a
+// deleted/changed region.
//
// Marks live in namespaces that allow plugins/users to segregate marks
// from other users.
//
-// For possible ideas for efficency improvements see:
-// http://blog.atom.io/2015/06/16/optimizing-an-important-atom-primitive.html
-// TODO(bfredl): These ideas could be used for an enhanced btree, which
-// wouldn't need separate line and column layers.
-// Other implementations exist in gtk and tk toolkits.
-//
// Deleting marks only happens when explicitly calling extmark_del, deleteing
// over a range of marks will only move the marks. Deleting on a mark will
// leave it in same position unless it is on the EOL of a line.
+//
+// Extmarks are used to implement buffer decoration. Decoration is mostly
+// regarded as an application of extmarks, however for practical reasons code
+// that deletes an extmark with decoration will call back into the decoration
+// code for redrawing the line with the deleted decoration.
#include <assert.h>
#include "nvim/api/vim.h"
#include "nvim/vim.h"
#include "nvim/charset.h"
#include "nvim/extmark.h"
+#include "nvim/decoration.h"
#include "nvim/buffer_updates.h"
#include "nvim/memline.h"
#include "nvim/pos.h"
@@ -41,8 +42,6 @@
#include "nvim/lib/kbtree.h"
#include "nvim/undo.h"
#include "nvim/buffer.h"
-#include "nvim/syntax.h"
-#include "nvim/highlight.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "extmark.c.generated.h"
@@ -71,9 +70,11 @@ static ExtmarkNs *buf_ns_ref(buf_T *buf, uint64_t ns_id, bool put) {
/// must not be used during iteration!
/// @returns the mark id
uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id,
- int row, colnr_T col, ExtmarkOp op)
+ int row, colnr_T col, int end_row, colnr_T end_col,
+ Decoration *decor, ExtmarkOp op)
{
ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true);
+ assert(ns != NULL);
mtpos_t old_pos;
uint64_t mark = 0;
@@ -82,7 +83,7 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id,
} else {
uint64_t old_mark = map_get(uint64_t, uint64_t)(ns->map, id);
if (old_mark) {
- if (old_mark & MARKTREE_PAIRED_FLAG) {
+ if (old_mark & MARKTREE_PAIRED_FLAG || end_row > -1) {
extmark_del(buf, ns_id, id);
} else {
// TODO(bfredl): we need to do more if "revising" a decoration mark.
@@ -90,7 +91,12 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id,
old_pos = marktree_lookup(buf->b_marktree, old_mark, itr);
assert(itr->node);
if (old_pos.row == row && old_pos.col == col) {
- map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, old_mark);
+ ExtmarkItem it = map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ old_mark);
+ if (it.decor) {
+ decor_redraw(buf, row, row, it.decor);
+ decor_free(it.decor);
+ }
mark = marktree_revise(buf->b_marktree, itr);
goto revised;
}
@@ -101,11 +107,17 @@ uint64_t extmark_set(buf_T *buf, uint64_t ns_id, uint64_t id,
}
}
- mark = marktree_put(buf->b_marktree, row, col, true);
+ if (end_row > -1) {
+ mark = marktree_put_pair(buf->b_marktree,
+ row, col, true,
+ end_row, end_col, false);
+ } else {
+ mark = marktree_put(buf->b_marktree, row, col, true);
+ }
+
revised:
map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark,
- (ExtmarkItem){ ns_id, id, 0,
- KV_INITIAL_VALUE });
+ (ExtmarkItem){ ns_id, id, decor });
map_put(uint64_t, uint64_t)(ns->map, id, mark);
if (op != kExtmarkNoUndo) {
@@ -114,6 +126,10 @@ revised:
// adding new marks to old undo headers.
u_extmark_set(buf, mark, row, col);
}
+
+ if (decor) {
+ decor_redraw(buf, row, end_row > -1 ? end_row : row, decor);
+ }
return id;
}
@@ -152,27 +168,23 @@ bool extmark_del(buf_T *buf, uint64_t ns_id, uint64_t id)
assert(pos.row >= 0);
marktree_del_itr(buf->b_marktree, itr, false);
ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
+ mtpos_t pos2 = pos;
if (mark & MARKTREE_PAIRED_FLAG) {
- mtpos_t pos2 = marktree_lookup(buf->b_marktree,
- mark|MARKTREE_END_FLAG, itr);
+ pos2 = marktree_lookup(buf->b_marktree, mark|MARKTREE_END_FLAG, itr);
assert(pos2.row >= 0);
marktree_del_itr(buf->b_marktree, itr, false);
- if (item.hl_id && pos2.row >= pos.row) {
- redraw_buf_range_later(buf, pos.row+1, pos2.row+1);
- }
}
- if (kv_size(item.virt_text)) {
- redraw_buf_line_later(buf, pos.row+1);
+ if (item.decor) {
+ decor_redraw(buf, pos.row, pos2.row, item.decor);
+ decor_free(item.decor);
}
- clear_virttext(&item.virt_text);
map_del(uint64_t, uint64_t)(ns->map, id);
map_del(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark);
// TODO(bfredl): delete it from current undo header, opportunistically?
-
return true;
}
@@ -202,9 +214,11 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
}
// the value is either zero or the lnum (row+1) if highlight was present.
- static Map(uint64_t, uint64_t) *delete_set = NULL;
+ static Map(uint64_t, ssize_t) *delete_set = NULL;
+ typedef struct { Decoration *decor; int row1; } DecorItem;
+ static kvec_t(DecorItem) decors;
if (delete_set == NULL) {
- delete_set = map_new(uint64_t, uint64_t)();
+ delete_set = map_new(uint64_t, ssize_t)();
}
MarkTreeIter itr[1] = { 0 };
@@ -216,14 +230,16 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
|| (mark.row == u_row && mark.col > u_col)) {
break;
}
- uint64_t *del_status = map_ref(uint64_t, uint64_t)(delete_set, mark.id,
- false);
+ ssize_t *del_status = map_ref(uint64_t, ssize_t)(delete_set, mark.id,
+ false);
if (del_status) {
marktree_del_itr(buf->b_marktree, itr, false);
- map_del(uint64_t, uint64_t)(delete_set, mark.id);
- if (*del_status > 0) {
- redraw_buf_range_later(buf, (linenr_T)(*del_status), mark.row+1);
+ if (*del_status >= 0) { // we had a decor_id
+ DecorItem it = kv_A(decors, *del_status);
+ decor_redraw(buf, it.row1, mark.row, it.decor);
+ decor_free(it.decor);
}
+ map_del(uint64_t, ssize_t)(delete_set, mark.id);
continue;
}
@@ -233,15 +249,21 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
assert(item.ns_id > 0 && item.mark_id > 0);
if (item.mark_id > 0 && (item.ns_id == ns_id || all_ns)) {
- if (kv_size(item.virt_text)) {
- redraw_buf_line_later(buf, mark.row+1);
- }
- clear_virttext(&item.virt_text);
marks_cleared = true;
if (mark.id & MARKTREE_PAIRED_FLAG) {
uint64_t other = mark.id ^ MARKTREE_END_FLAG;
- uint64_t status = item.hl_id ? ((uint64_t)mark.row+1) : 0;
- map_put(uint64_t, uint64_t)(delete_set, other, status);
+ ssize_t decor_id = -1;
+ if (item.decor) {
+ // Save the decoration and the first pos. Clear the decoration
+ // later when we know the full range.
+ decor_id = (ssize_t)kv_size(decors);
+ kv_push(decors,
+ ((DecorItem) { .decor = item.decor, .row1 = mark.row }));
+ }
+ map_put(uint64_t, ssize_t)(delete_set, other, decor_id);
+ } else if (item.decor) {
+ decor_redraw(buf, mark.row, mark.row, item.decor);
+ decor_free(item.decor);
}
ExtmarkNs *my_ns = all_ns ? buf_ns_ref(buf, item.ns_id, false) : ns;
map_del(uint64_t, uint64_t)(my_ns->map, item.mark_id);
@@ -251,16 +273,20 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
marktree_itr_next(buf->b_marktree, itr);
}
}
- uint64_t id, status;
- map_foreach(delete_set, id, status, {
+ uint64_t id;
+ ssize_t decor_id;
+ map_foreach(delete_set, id, decor_id, {
mtpos_t pos = marktree_lookup(buf->b_marktree, id, itr);
assert(itr->node);
marktree_del_itr(buf->b_marktree, itr, false);
- if (status > 0) {
- redraw_buf_range_later(buf, (linenr_T)status, pos.row+1);
+ if (decor_id >= 0) {
+ DecorItem it = kv_A(decors, decor_id);
+ decor_redraw(buf, it.row1, pos.row, it.decor);
+ decor_free(it.decor);
}
});
- map_clear(uint64_t, uint64_t)(delete_set);
+ map_clear(uint64_t, ssize_t)(delete_set);
+ kv_size(decors) = 0;
return marks_cleared;
}
@@ -270,31 +296,44 @@ bool extmark_clear(buf_T *buf, uint64_t ns_id,
// will be searched to the start, or end
// dir can be set to control the order of the array
// amount = amount of marks to find or -1 for all
-ExtmarkArray extmark_get(buf_T *buf, uint64_t ns_id,
- int l_row, colnr_T l_col,
- int u_row, colnr_T u_col,
- int64_t amount, bool reverse)
+ExtmarkInfoArray extmark_get(buf_T *buf, uint64_t ns_id,
+ int l_row, colnr_T l_col,
+ int u_row, colnr_T u_col,
+ int64_t amount, bool reverse)
{
- ExtmarkArray array = KV_INITIAL_VALUE;
- MarkTreeIter itr[1] = { 0 };
+ ExtmarkInfoArray array = KV_INITIAL_VALUE;
+ MarkTreeIter itr[1];
// Find all the marks
marktree_itr_get_ext(buf->b_marktree, (mtpos_t){ l_row, l_col },
itr, reverse, false, NULL);
int order = reverse ? -1 : 1;
while ((int64_t)kv_size(array) < amount) {
mtmark_t mark = marktree_itr_current(itr);
+ mtpos_t endpos = { -1, -1 };
if (mark.row < 0
|| (mark.row - u_row) * order > 0
|| (mark.row == u_row && (mark.col - u_col) * order > 0)) {
break;
}
+ if (mark.id & MARKTREE_END_FLAG) {
+ goto next_mark;
+ } else if (mark.id & MARKTREE_PAIRED_FLAG) {
+ endpos = marktree_lookup(buf->b_marktree, mark.id | MARKTREE_END_FLAG,
+ NULL);
+ }
+
+
ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
mark.id);
if (item.ns_id == ns_id) {
kv_push(array, ((ExtmarkInfo) { .ns_id = item.ns_id,
.mark_id = item.mark_id,
- .row = mark.row, .col = mark.col }));
+ .row = mark.row, .col = mark.col,
+ .end_row = endpos.row,
+ .end_col = endpos.col,
+ .decor = item.decor }));
}
+next_mark:
if (reverse) {
marktree_itr_prev(buf->b_marktree, itr);
} else {
@@ -308,7 +347,7 @@ ExtmarkArray extmark_get(buf_T *buf, uint64_t ns_id,
ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id)
{
ExtmarkNs *ns = buf_ns_ref(buf, ns_id, false);
- ExtmarkInfo ret = { 0, 0, -1, -1 };
+ ExtmarkInfo ret = { 0, 0, -1, -1, -1, -1, NULL };
if (!ns) {
return ret;
}
@@ -319,12 +358,22 @@ ExtmarkInfo extmark_from_id(buf_T *buf, uint64_t ns_id, uint64_t id)
}
mtpos_t pos = marktree_lookup(buf->b_marktree, mark, NULL);
+ mtpos_t endpos = { -1, -1 };
+ if (mark & MARKTREE_PAIRED_FLAG) {
+ endpos = marktree_lookup(buf->b_marktree, mark | MARKTREE_END_FLAG, NULL);
+ }
assert(pos.row >= 0);
+ ExtmarkItem item = map_get(uint64_t, ExtmarkItem)(buf->b_extmark_index,
+ mark);
+
ret.ns_id = ns_id;
ret.mark_id = id;
ret.row = pos.row;
ret.col = pos.col;
+ ret.end_row = endpos.row;
+ ret.end_col = endpos.col;
+ ret.decor = item.decor;
return ret;
}
@@ -352,7 +401,7 @@ void extmark_free_all(buf_T *buf)
map_foreach(buf->b_extmark_index, id, item, {
(void)id;
- clear_virttext(&item.virt_text);
+ decor_free(item.decor);
});
map_free(uint64_t, ExtmarkItem)(buf->b_extmark_index);
buf->b_extmark_index = NULL;
@@ -428,18 +477,18 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
// Undo
ExtmarkSplice splice = undo_info.data.splice;
if (undo) {
- extmark_splice(curbuf,
- splice.start_row, splice.start_col,
- splice.newextent_row, splice.newextent_col,
- splice.oldextent_row, splice.oldextent_col,
- kExtmarkNoUndo);
+ extmark_splice_impl(curbuf,
+ splice.start_row, splice.start_col, splice.start_byte,
+ splice.new_row, splice.new_col, splice.new_byte,
+ splice.old_row, splice.old_col, splice.old_byte,
+ kExtmarkNoUndo);
} else {
- extmark_splice(curbuf,
- splice.start_row, splice.start_col,
- splice.oldextent_row, splice.oldextent_col,
- splice.newextent_row, splice.newextent_col,
- kExtmarkNoUndo);
+ extmark_splice_impl(curbuf,
+ splice.start_row, splice.start_col, splice.start_byte,
+ splice.old_row, splice.old_col, splice.old_byte,
+ splice.new_row, splice.new_col, splice.new_byte,
+ kExtmarkNoUndo);
}
// kExtmarkSavePos
} else if (undo_info.type == kExtmarkSavePos) {
@@ -458,15 +507,15 @@ void extmark_apply_undo(ExtmarkUndoObject undo_info, bool undo)
ExtmarkMove move = undo_info.data.move;
if (undo) {
extmark_move_region(curbuf,
- move.new_row, move.new_col,
- move.extent_row, move.extent_col,
- move.start_row, move.start_col,
+ move.new_row, move.new_col, move.new_byte,
+ move.extent_row, move.extent_col, move.extent_byte,
+ move.start_row, move.start_col, move.start_byte,
kExtmarkNoUndo);
} else {
extmark_move_region(curbuf,
- move.start_row, move.start_col,
- move.extent_row, move.extent_col,
- move.new_row, move.new_col,
+ move.start_row, move.start_col, move.start_byte,
+ move.extent_row, move.extent_col, move.extent_byte,
+ move.new_row, move.new_col, move.new_byte,
kExtmarkNoUndo);
}
}
@@ -481,51 +530,84 @@ void extmark_adjust(buf_T *buf,
long amount_after,
ExtmarkOp undo)
{
- if (!curbuf_splice_pending) {
- int old_extent, new_extent;
- if (amount == MAXLNUM) {
- old_extent = (int)(line2 - line1+1);
- new_extent = (int)(amount_after + old_extent);
- } else {
- // A region is either deleted (amount == MAXLNUM) or
- // added (line2 == MAXLNUM). The only other case is :move
- // which is handled by a separate entry point extmark_move_region.
- assert(line2 == MAXLNUM);
- old_extent = 0;
- new_extent = (int)amount;
- }
- extmark_splice(buf,
- (int)line1-1, 0,
- old_extent, 0,
- new_extent, 0, undo);
+ if (curbuf_splice_pending) {
+ return;
}
+ bcount_t start_byte = ml_find_line_or_offset(buf, line1, NULL, true);
+ bcount_t old_byte = 0, new_byte = 0;
+ int old_row, new_row;
+ if (amount == MAXLNUM) {
+ old_row = (int)(line2 - line1+1);
+ // TODO(bfredl): ej kasta?
+ old_byte = (bcount_t)buf->deleted_bytes2;
+
+ new_row = (int)(amount_after + old_row);
+ } else {
+ // A region is either deleted (amount == MAXLNUM) or
+ // added (line2 == MAXLNUM). The only other case is :move
+ // which is handled by a separate entry point extmark_move_region.
+ assert(line2 == MAXLNUM);
+ old_row = 0;
+ new_row = (int)amount;
+ }
+ if (new_row > 0) {
+ new_byte = ml_find_line_or_offset(buf, line1+new_row, NULL, true)
+ - start_byte;
+ }
+ extmark_splice_impl(buf,
+ (int)line1-1, 0, start_byte,
+ old_row, 0, old_byte,
+ new_row, 0, new_byte, undo);
}
void extmark_splice(buf_T *buf,
int start_row, colnr_T start_col,
- int oldextent_row, colnr_T oldextent_col,
- int newextent_row, colnr_T newextent_col,
+ int old_row, colnr_T old_col, bcount_t old_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte,
ExtmarkOp undo)
{
- buf_updates_send_splice(buf, start_row, start_col,
- oldextent_row, oldextent_col,
- newextent_row, newextent_col);
+ long offset = ml_find_line_or_offset(buf, start_row + 1, NULL, true);
+
+ // On empty buffers, when editing the first line, the line is buffered,
+ // causing offset to be < 0. While the buffer is not actually empty, the
+ // buffered line has not been flushed (and should not be) yet, so the call is
+ // valid but an edge case.
+ //
+ // TODO(vigoux): maybe the is a better way of testing that ?
+ if (offset < 0 && buf->b_ml.ml_chunksize == NULL) {
+ offset = 0;
+ }
+ extmark_splice_impl(buf, start_row, start_col, offset + start_col,
+ old_row, old_col, old_byte, new_row, new_col, new_byte,
+ undo);
+}
+
+void extmark_splice_impl(buf_T *buf,
+ int start_row, colnr_T start_col, bcount_t start_byte,
+ int old_row, colnr_T old_col, bcount_t old_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte,
+ ExtmarkOp undo)
+{
+ curbuf->deleted_bytes2 = 0;
+ buf_updates_send_splice(buf, start_row, start_col, start_byte,
+ old_row, old_col, old_byte,
+ new_row, new_col, new_byte);
- if (undo == kExtmarkUndo && (oldextent_row > 0 || oldextent_col > 0)) {
+ if (undo == kExtmarkUndo && (old_row > 0 || old_col > 0)) {
// Copy marks that would be effected by delete
// TODO(bfredl): Be "smart" about gravity here, left-gravity at the
// beginning and right-gravity at the end need not be preserved.
// Also be smart about marks that already have been saved (important for
// merge!)
- int end_row = start_row + oldextent_row;
- int end_col = (oldextent_row ? 0 : start_col) + oldextent_col;
+ int end_row = start_row + old_row;
+ int end_col = (old_row ? 0 : start_col) + old_col;
u_extmark_copy(buf, start_row, start_col, end_row, end_col);
}
marktree_splice(buf->b_marktree, start_row, start_col,
- oldextent_row, oldextent_col,
- newextent_row, newextent_col);
+ old_row, old_col,
+ new_row, new_col);
if (undo == kExtmarkUndo) {
u_header_T *uhp = u_force_get_undo_header(buf);
@@ -537,25 +619,29 @@ void extmark_splice(buf_T *buf,
// TODO(bfredl): this is quite rudimentary. We merge small (within line)
// inserts with each other and small deletes with each other. Add full
// merge algorithm later.
- if (oldextent_row == 0 && newextent_row == 0 && kv_size(uhp->uh_extmark)) {
+ if (old_row == 0 && new_row == 0 && kv_size(uhp->uh_extmark)) {
ExtmarkUndoObject *item = &kv_A(uhp->uh_extmark,
kv_size(uhp->uh_extmark)-1);
if (item->type == kExtmarkSplice) {
ExtmarkSplice *splice = &item->data.splice;
- if (splice->start_row == start_row && splice->oldextent_row == 0
- && splice->newextent_row == 0) {
- if (oldextent_col == 0 && start_col >= splice->start_col
- && start_col <= splice->start_col+splice->newextent_col) {
- splice->newextent_col += newextent_col;
+ if (splice->start_row == start_row && splice->old_row == 0
+ && splice->new_row == 0) {
+ if (old_col == 0 && start_col >= splice->start_col
+ && start_col <= splice->start_col+splice->new_col) {
+ splice->new_col += new_col;
+ splice->new_byte += new_byte;
merged = true;
- } else if (newextent_col == 0
- && start_col == splice->start_col+splice->newextent_col) {
- splice->oldextent_col += oldextent_col;
+ } else if (new_col == 0
+ && start_col == splice->start_col+splice->new_col) {
+ splice->old_col += old_col;
+ splice->old_byte += old_byte;
merged = true;
- } else if (newextent_col == 0
- && start_col + oldextent_col == splice->start_col) {
+ } else if (new_col == 0
+ && start_col + old_col == splice->start_col) {
splice->start_col = start_col;
- splice->oldextent_col += oldextent_col;
+ splice->start_byte = start_byte;
+ splice->old_col += old_col;
+ splice->old_byte += old_byte;
merged = true;
}
}
@@ -566,10 +652,13 @@ void extmark_splice(buf_T *buf,
ExtmarkSplice splice;
splice.start_row = start_row;
splice.start_col = start_col;
- splice.oldextent_row = oldextent_row;
- splice.oldextent_col = oldextent_col;
- splice.newextent_row = newextent_row;
- splice.newextent_col = newextent_col;
+ splice.start_byte = start_byte;
+ splice.old_row = old_row;
+ splice.old_col = old_col;
+ splice.old_byte = old_byte;
+ splice.new_row = new_row;
+ splice.new_col = new_col;
+ splice.new_byte = new_byte;
kv_push(uhp->uh_extmark,
((ExtmarkUndoObject){ .type = kExtmarkSplice,
@@ -584,30 +673,31 @@ void extmark_splice_cols(buf_T *buf,
ExtmarkOp undo)
{
extmark_splice(buf, start_row, start_col,
- 0, old_col,
- 0, new_col, undo);
+ 0, old_col, old_col,
+ 0, new_col, new_col, undo);
}
-void extmark_move_region(buf_T *buf,
- int start_row, colnr_T start_col,
- int extent_row, colnr_T extent_col,
- int new_row, colnr_T new_col,
- ExtmarkOp undo)
+void extmark_move_region(
+ buf_T *buf,
+ int start_row, colnr_T start_col, bcount_t start_byte,
+ int extent_row, colnr_T extent_col, bcount_t extent_byte,
+ int new_row, colnr_T new_col, bcount_t new_byte,
+ ExtmarkOp undo)
{
// TODO(bfredl): this is not synced to the buffer state inside the callback.
// But unless we make the undo implementation smarter, this is not ensured
// anyway.
- buf_updates_send_splice(buf, start_row, start_col,
- extent_row, extent_col,
- 0, 0);
+ buf_updates_send_splice(buf, start_row, start_col, start_byte,
+ extent_row, extent_col, extent_byte,
+ 0, 0, 0);
marktree_move_region(buf->b_marktree, start_row, start_col,
extent_row, extent_col,
new_row, new_col);
- buf_updates_send_splice(buf, new_row, new_col,
- 0, 0,
- extent_row, extent_col);
+ buf_updates_send_splice(buf, new_row, new_col, new_byte,
+ 0, 0, 0,
+ extent_row, extent_col, extent_byte);
if (undo == kExtmarkUndo) {
@@ -619,10 +709,13 @@ void extmark_move_region(buf_T *buf,
ExtmarkMove move;
move.start_row = start_row;
move.start_col = start_col;
+ move.start_byte = start_byte;
move.extent_row = extent_row;
move.extent_col = extent_col;
+ move.extent_byte = extent_byte;
move.new_row = new_row;
move.new_col = new_col;
+ move.new_byte = new_byte;
kv_push(uhp->uh_extmark,
((ExtmarkUndoObject){ .type = kExtmarkMove,
@@ -642,287 +735,3 @@ uint64_t src2ns(Integer *src_id)
}
}
-/// Adds a decoration to a buffer.
-///
-/// Unlike matchaddpos() highlights, these follow changes to the the buffer
-/// texts. Decorations are represented internally and in the API as extmarks.
-///
-/// @param buf The buffer to add decorations to
-/// @param ns_id A valid namespace id.
-/// @param hl_id Id of the highlight group to use (or zero)
-/// @param start_row The line to highlight
-/// @param start_col First column to highlight
-/// @param end_row The line to highlight
-/// @param end_col The last column to highlight
-/// @param virt_text Virtual text (currently placed at the EOL of start_row)
-/// @return The extmark id inside the namespace
-uint64_t extmark_add_decoration(buf_T *buf, uint64_t ns_id, int hl_id,
- int start_row, colnr_T start_col,
- int end_row, colnr_T end_col,
- VirtText virt_text)
-{
- ExtmarkNs *ns = buf_ns_ref(buf, ns_id, true);
- ExtmarkItem item;
- item.ns_id = ns_id;
- item.mark_id = ns->free_id++;
- item.hl_id = hl_id;
- item.virt_text = virt_text;
-
- uint64_t mark;
-
- if (end_row > -1) {
- mark = marktree_put_pair(buf->b_marktree,
- start_row, start_col, true,
- end_row, end_col, false);
- } else {
- mark = marktree_put(buf->b_marktree, start_row, start_col, true);
- }
-
- map_put(uint64_t, ExtmarkItem)(buf->b_extmark_index, mark, item);
- map_put(uint64_t, uint64_t)(ns->map, item.mark_id, mark);
-
- redraw_buf_range_later(buf, start_row+1,
- (end_row >= 0 ? end_row : start_row) + 1);
- return item.mark_id;
-}
-
-/// Add highlighting to a buffer, bounded by two cursor positions,
-/// with an offset.
-///
-/// @param buf Buffer to add highlights to
-/// @param src_id src_id to use or 0 to use a new src_id group,
-/// or -1 for ungrouped highlight.
-/// @param hl_id Highlight group id
-/// @param pos_start Cursor position to start the hightlighting at
-/// @param pos_end Cursor position to end the highlighting at
-/// @param offset Move the whole highlighting this many columns to the right
-void bufhl_add_hl_pos_offset(buf_T *buf,
- int src_id,
- int hl_id,
- lpos_T pos_start,
- lpos_T pos_end,
- colnr_T offset)
-{
- colnr_T hl_start = 0;
- colnr_T hl_end = 0;
-
- // TODO(bfredl): if decoration had blocky mode, we could avoid this loop
- for (linenr_T lnum = pos_start.lnum; lnum <= pos_end.lnum; lnum ++) {
- int end_off = 0;
- if (pos_start.lnum < lnum && lnum < pos_end.lnum) {
- // TODO(bfredl): This is quite ad-hoc, but the space between |num| and
- // text being highlighted is the indication of \n being part of the
- // substituted text. But it would be more consistent to highlight
- // a space _after_ the previous line instead (like highlight EOL list
- // char)
- hl_start = MAX(offset-1, 0);
- end_off = 1;
- hl_end = 0;
- } else if (lnum == pos_start.lnum && lnum < pos_end.lnum) {
- hl_start = pos_start.col + offset;
- end_off = 1;
- hl_end = 0;
- } else if (pos_start.lnum < lnum && lnum == pos_end.lnum) {
- hl_start = MAX(offset-1, 0);
- hl_end = pos_end.col + offset;
- } else if (pos_start.lnum == lnum && pos_end.lnum == lnum) {
- hl_start = pos_start.col + offset;
- hl_end = pos_end.col + offset;
- }
- (void)extmark_add_decoration(buf, (uint64_t)src_id, hl_id,
- (int)lnum-1, hl_start,
- (int)lnum-1+end_off, hl_end,
- VIRTTEXT_EMPTY);
- }
-}
-
-void clear_virttext(VirtText *text)
-{
- for (size_t i = 0; i < kv_size(*text); i++) {
- xfree(kv_A(*text, i).text);
- }
- kv_destroy(*text);
- *text = (VirtText)KV_INITIAL_VALUE;
-}
-
-VirtText *extmark_find_virttext(buf_T *buf, int row, uint64_t ns_id)
-{
- MarkTreeIter itr[1] = { 0 };
- marktree_itr_get(buf->b_marktree, row, 0, itr);
- while (true) {
- mtmark_t mark = marktree_itr_current(itr);
- if (mark.row < 0 || mark.row > row) {
- break;
- }
- ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- mark.id, false);
- if (item && (ns_id == 0 || ns_id == item->ns_id)
- && kv_size(item->virt_text)) {
- return &item->virt_text;
- }
- marktree_itr_next(buf->b_marktree, itr);
- }
- return NULL;
-}
-
-bool decorations_redraw_reset(buf_T *buf, DecorationRedrawState *state)
-{
- state->row = -1;
- kv_size(state->active) = 0;
- return buf->b_extmark_index || buf->b_luahl;
-}
-
-
-bool decorations_redraw_start(buf_T *buf, int top_row,
- DecorationRedrawState *state)
-{
- state->top_row = top_row;
- marktree_itr_get(buf->b_marktree, top_row, 0, state->itr);
- if (!state->itr->node) {
- return false;
- }
- marktree_itr_rewind(buf->b_marktree, state->itr);
- while (true) {
- mtmark_t mark = marktree_itr_current(state->itr);
- if (mark.row < 0) { // || mark.row > end_row
- break;
- }
- // TODO(bfredl): dedicated flag for being a decoration?
- if ((mark.row < top_row && mark.id&MARKTREE_END_FLAG)) {
- goto next_mark;
- }
- mtpos_t altpos = marktree_lookup(buf->b_marktree,
- mark.id^MARKTREE_END_FLAG, NULL);
-
- uint64_t start_id = mark.id & ~MARKTREE_END_FLAG;
- ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- start_id, false);
- if ((!(mark.id&MARKTREE_END_FLAG) && altpos.row < top_row
- && item && !kv_size(item->virt_text))
- || ((mark.id&MARKTREE_END_FLAG) && altpos.row >= top_row)) {
- goto next_mark;
- }
-
- if (item && (item->hl_id > 0 || kv_size(item->virt_text))) {
- int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0;
- VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL;
- HlRange range;
- if (mark.id&MARKTREE_END_FLAG) {
- range = (HlRange){ altpos.row, altpos.col, mark.row, mark.col,
- attr_id, vt };
- } else {
- range = (HlRange){ mark.row, mark.col, altpos.row,
- altpos.col, attr_id, vt };
- }
- kv_push(state->active, range);
- }
-next_mark:
- if (marktree_itr_node_done(state->itr)) {
- break;
- }
- marktree_itr_next(buf->b_marktree, state->itr);
- }
-
- return true; // TODO(bfredl): check if available in the region
-}
-
-bool decorations_redraw_line(buf_T *buf, int row, DecorationRedrawState *state)
-{
- if (state->row == -1) {
- decorations_redraw_start(buf, row, state);
- }
- state->row = row;
- state->col_until = -1;
- return true; // TODO(bfredl): be more precise
-}
-
-int decorations_redraw_col(buf_T *buf, int col, DecorationRedrawState *state)
-{
- if (col <= state->col_until) {
- return state->current;
- }
- state->col_until = MAXCOL;
- while (true) {
- mtmark_t mark = marktree_itr_current(state->itr);
- if (mark.row < 0 || mark.row > state->row) {
- break;
- } else if (mark.row == state->row && mark.col > col) {
- state->col_until = mark.col-1;
- break;
- }
-
- if ((mark.id&MARKTREE_END_FLAG)) {
- // TODO(bfredl): check decorations flag
- goto next_mark;
- }
- mtpos_t endpos = marktree_lookup(buf->b_marktree,
- mark.id|MARKTREE_END_FLAG, NULL);
-
- ExtmarkItem *item = map_ref(uint64_t, ExtmarkItem)(buf->b_extmark_index,
- mark.id, false);
-
- if (endpos.row < mark.row
- || (endpos.row == mark.row && endpos.col <= mark.col)) {
- if (item && !kv_size(item->virt_text)) {
- goto next_mark;
- }
- }
-
- if (item && (item->hl_id > 0 || kv_size(item->virt_text))) {
- int attr_id = item->hl_id > 0 ? syn_id2attr(item->hl_id) : 0;
- VirtText *vt = kv_size(item->virt_text) ? &item->virt_text : NULL;
- kv_push(state->active, ((HlRange){ mark.row, mark.col,
- endpos.row, endpos.col,
- attr_id, vt }));
- }
-
-next_mark:
- marktree_itr_next(buf->b_marktree, state->itr);
- }
-
- int attr = 0;
- size_t j = 0;
- for (size_t i = 0; i < kv_size(state->active); i++) {
- HlRange item = kv_A(state->active, i);
- bool active = false, keep = true;
- if (item.end_row < state->row
- || (item.end_row == state->row && item.end_col <= col)) {
- if (!(item.start_row >= state->row && item.virt_text)) {
- keep = false;
- }
- } else {
- if (item.start_row < state->row
- || (item.start_row == state->row && item.start_col <= col)) {
- active = true;
- if (item.end_row == state->row) {
- state->col_until = MIN(state->col_until, item.end_col-1);
- }
- } else {
- if (item.start_row == state->row) {
- state->col_until = MIN(state->col_until, item.start_col-1);
- }
- }
- }
- if (active && item.attr_id > 0) {
- attr = hl_combine_attr(attr, item.attr_id);
- }
- if (keep) {
- kv_A(state->active, j++) = kv_A(state->active, i);
- }
- }
- kv_size(state->active) = j;
- state->current = attr;
- return attr;
-}
-
-VirtText *decorations_redraw_virt_text(buf_T *buf, DecorationRedrawState *state)
-{
- decorations_redraw_col(buf, MAXCOL, state);
- for (size_t i = 0; i < kv_size(state->active); i++) {
- HlRange item = kv_A(state->active, i);
- if (item.start_row == state->row && item.virt_text) {
- return item.virt_text;
- }
- }
- return NULL;
-}
diff --git a/src/nvim/extmark.h b/src/nvim/extmark.h
index b5eb0db3b6..1bc42322a3 100644
--- a/src/nvim/extmark.h
+++ b/src/nvim/extmark.h
@@ -1,6 +1,7 @@
#ifndef NVIM_EXTMARK_H
#define NVIM_EXTMARK_H
+#include "nvim/pos.h"
#include "nvim/buffer_defs.h"
#include "nvim/extmark_defs.h"
#include "nvim/marktree.h"
@@ -13,19 +14,28 @@ typedef struct
uint64_t mark_id;
int row;
colnr_T col;
+ int end_row;
+ colnr_T end_col;
+ Decoration *decor;
} ExtmarkInfo;
-typedef kvec_t(ExtmarkInfo) ExtmarkArray;
+typedef kvec_t(ExtmarkInfo) ExtmarkInfoArray;
+
+// TODO(bfredl): good enough name for now.
+typedef ptrdiff_t bcount_t;
// delete the columns between mincol and endcol
typedef struct {
int start_row;
colnr_T start_col;
- int oldextent_row;
- colnr_T oldextent_col;
- int newextent_row;
- colnr_T newextent_col;
+ int old_row;
+ colnr_T old_col;
+ int new_row;
+ colnr_T new_col;
+ bcount_t start_byte;
+ bcount_t old_byte;
+ bcount_t new_byte;
} ExtmarkSplice;
// adjust marks after :move operation
@@ -36,6 +46,9 @@ typedef struct {
int extent_col;
int new_row;
int new_col;
+ bcount_t start_byte;
+ bcount_t extent_byte;
+ bcount_t new_byte;
} ExtmarkMove;
// extmark was updated
@@ -66,26 +79,6 @@ struct undo_object {
};
-typedef struct {
- int start_row;
- int start_col;
- int end_row;
- int end_col;
- int attr_id;
- VirtText *virt_text;
-} HlRange;
-
-typedef struct {
- MarkTreeIter itr[1];
- kvec_t(HlRange) active;
- int top_row;
- int row;
- int col_until;
- int current;
- VirtText *virt_text;
-} DecorationRedrawState;
-
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "extmark.h.generated.h"
#endif
diff --git a/src/nvim/extmark_defs.h b/src/nvim/extmark_defs.h
index c927048981..784280dace 100644
--- a/src/nvim/extmark_defs.h
+++ b/src/nvim/extmark_defs.h
@@ -1,25 +1,19 @@
#ifndef NVIM_EXTMARK_DEFS_H
#define NVIM_EXTMARK_DEFS_H
-#include "nvim/pos.h" // for colnr_T
+#include "nvim/types.h"
#include "nvim/lib/kvec.h"
-typedef struct {
- char *text;
- int hl_id;
-} VirtTextChunk;
-
-typedef kvec_t(VirtTextChunk) VirtText;
-#define VIRTTEXT_EMPTY ((VirtText)KV_INITIAL_VALUE)
+typedef struct Decoration Decoration;
typedef struct
{
uint64_t ns_id;
uint64_t mark_id;
- int hl_id; // highlight group
- // TODO(bfredl): virt_text is pretty larger than the rest,
- // pointer indirection?
- VirtText virt_text;
+ // TODO(bfredl): a lot of small allocations. Should probably use
+ // kvec_t(Decoration) as an arena. Alternatively, store ns_id/mark_id
+ // _inline_ in MarkTree and use the map only for decorations.
+ Decoration *decor;
} ExtmarkItem;
typedef struct undo_object ExtmarkUndoObject;
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index 47272df2f0..b1fa0b6779 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -1565,7 +1565,7 @@ theend:
return file_name;
}
-void do_autocmd_dirchanged(char *new_dir, CdScope scope)
+void do_autocmd_dirchanged(char *new_dir, CdScope scope, bool changed_window)
{
static bool recursive = false;
@@ -1601,6 +1601,7 @@ void do_autocmd_dirchanged(char *new_dir, CdScope scope)
tv_dict_add_str(dict, S_LEN("scope"), buf); // -V614
tv_dict_add_str(dict, S_LEN("cwd"), new_dir);
+ tv_dict_add_bool(dict, S_LEN("changed_window"), changed_window);
tv_dict_set_keys_readonly(dict);
apply_autocmds(EVENT_DIRCHANGED, (char_u *)buf, (char_u *)new_dir, false,
@@ -1633,7 +1634,7 @@ int vim_chdirfile(char_u *fname)
slash_adjust((char_u *)dir);
#endif
if (!strequal(dir, (char *)NameBuff)) {
- do_autocmd_dirchanged(dir, kCdScopeWindow);
+ do_autocmd_dirchanged(dir, kCdScopeWindow, false);
}
return OK;
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index 20f0cdccc3..e349f00fba 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -210,7 +210,8 @@ void filemess(buf_T *buf, char_u *name, char_u *s, int attr)
if (msg_silent != 0) {
return;
}
- add_quoted_fname((char *)IObuff, IOSIZE - 80, buf, (const char *)name);
+ add_quoted_fname((char *)IObuff, IOSIZE - 100, buf, (const char *)name);
+ // Avoid an over-long translation to cause trouble.
xstrlcat((char *)IObuff, (const char *)s, IOSIZE);
// For the first message may have to start a new line.
// For further ones overwrite the previous one, reset msg_scroll before
@@ -300,6 +301,7 @@ readfile(
int skip_read = false;
context_sha256_T sha_ctx;
int read_undo_file = false;
+ int split = 0; // number of split lines
linenr_T linecnt;
int error = FALSE; /* errors encountered */
int ff_error = EOL_UNKNOWN; /* file format with errors */
@@ -348,6 +350,7 @@ readfile(
char_u *old_b_fname;
int using_b_ffname;
int using_b_fname;
+ static char *msg_is_a_directory = N_("is a directory");
au_did_filetype = false; // reset before triggering any autocommands
@@ -442,37 +445,43 @@ readfile(
else
msg_scroll = TRUE; /* don't overwrite previous file message */
- /*
- * If the name is too long we might crash further on, quit here.
- */
+ // If the name is too long we might crash further on, quit here.
if (fname != NULL && *fname != NUL) {
- if (STRLEN(fname) >= MAXPATHL) {
+ size_t namelen = STRLEN(fname);
+
+ // If the name is too long we might crash further on, quit here.
+ if (namelen >= MAXPATHL) {
filemess(curbuf, fname, (char_u *)_("Illegal file name"), 0);
msg_end();
msg_scroll = msg_save;
return FAIL;
}
+
+ // If the name ends in a path separator, we can't open it. Check here,
+ // because reading the file may actually work, but then creating the
+ // swap file may destroy it! Reported on MS-DOS and Win 95.
+ if (after_pathsep((const char *)fname, (const char *)(fname + namelen))) {
+ filemess(curbuf, fname, (char_u *)_(msg_is_a_directory), 0);
+ msg_end();
+ msg_scroll = msg_save;
+ return FAIL;
+ }
}
if (!read_buffer && !read_stdin && !read_fifo) {
perm = os_getperm((const char *)fname);
-#ifdef UNIX
// On Unix it is possible to read a directory, so we have to
// check for it before os_open().
if (perm >= 0 && !S_ISREG(perm) // not a regular file ...
-# ifdef S_ISFIFO
&& !S_ISFIFO(perm) // ... or fifo
-# endif
-# ifdef S_ISSOCK
&& !S_ISSOCK(perm) // ... or socket
-# endif
# ifdef OPEN_CHR_FILES
&& !(S_ISCHR(perm) && is_dev_fd_file(fname))
// ... or a character special file named /dev/fd/<n>
# endif
) {
if (S_ISDIR(perm)) {
- filemess(curbuf, fname, (char_u *)_("is a directory"), 0);
+ filemess(curbuf, fname, (char_u *)_(msg_is_a_directory), 0);
} else {
filemess(curbuf, fname, (char_u *)_("is not a file"), 0);
}
@@ -480,7 +489,6 @@ readfile(
msg_scroll = msg_save;
return S_ISDIR(perm) ? NOTDONE : FAIL;
}
-#endif
}
/* Set default or forced 'fileformat' and 'binary'. */
@@ -539,13 +547,6 @@ readfile(
if (fd < 0) { // cannot open at all
msg_scroll = msg_save;
-#ifndef UNIX
- // On non-unix systems we can't open a directory, check here.
- if (os_isdir(fname)) {
- filemess(curbuf, sfname, (char_u *)_("is a directory"), 0);
- curbuf->b_p_ro = true; // must use "w!" now
- } else {
-#endif
if (!newfile) {
return FAIL;
}
@@ -603,9 +604,6 @@ readfile(
return FAIL;
}
-#ifndef UNIX
- }
-#endif
/*
* Only set the 'ro' flag for readonly files the first time they are
@@ -1013,8 +1011,21 @@ retry:
*/
{
if (!skip_read) {
- size = 0x10000L; /* use buffer >= 64K */
+ // Use buffer >= 64K. Add linerest to double the size if the
+ // line gets very long, to avoid a lot of copying. But don't
+ // read more than 1 Mbyte at a time, so we can be interrupted.
+ size = 0x10000L + linerest;
+ if (size > 0x100000L) {
+ size = 0x100000L;
+ }
+ }
+ // Protect against the argument of lalloc() going negative.
+ if (size < 0 || size + linerest + 1 < 0 || linerest >= MAXCOL) {
+ split++;
+ *ptr = NL; // split line by inserting a NL
+ size = 1;
+ } else if (!skip_read) {
for (; size >= 10; size /= 2) {
new_buffer = verbose_try_malloc((size_t)size + (size_t)linerest + 1);
if (new_buffer) {
@@ -1783,6 +1794,7 @@ failed:
linecnt--;
}
curbuf->deleted_bytes = 0;
+ curbuf->deleted_bytes2 = 0;
curbuf->deleted_codepoints = 0;
curbuf->deleted_codeunits = 0;
linecnt = curbuf->b_ml.ml_line_count - linecnt;
@@ -1824,25 +1836,14 @@ failed:
c = false;
#ifdef UNIX
-# ifdef S_ISFIFO
- if (S_ISFIFO(perm)) { /* fifo or socket */
- STRCAT(IObuff, _("[fifo/socket]"));
- c = TRUE;
- }
-# else
-# ifdef S_IFIFO
- if ((perm & S_IFMT) == S_IFIFO) { /* fifo */
+ if (S_ISFIFO(perm)) { // fifo
STRCAT(IObuff, _("[fifo]"));
c = TRUE;
}
-# endif
-# ifdef S_IFSOCK
- if ((perm & S_IFMT) == S_IFSOCK) { /* or socket */
+ if (S_ISSOCK(perm)) { // or socket
STRCAT(IObuff, _("[socket]"));
c = TRUE;
}
-# endif
-# endif
# ifdef OPEN_CHR_FILES
if (S_ISCHR(perm)) { /* or character special */
STRCAT(IObuff, _("[character special]"));
@@ -1862,6 +1863,10 @@ failed:
STRCAT(IObuff, _("[CR missing]"));
c = TRUE;
}
+ if (split) {
+ STRCAT(IObuff, _("[long lines split]"));
+ c = true;
+ }
if (notconverted) {
STRCAT(IObuff, _("[NOT converted]"));
c = TRUE;
@@ -3573,7 +3578,7 @@ restore_backup:
* the backup file our 'original' file.
*/
if (*p_pm && dobackup) {
- char *org = modname((char *)fname, (char *)p_pm, FALSE);
+ char *const org = modname((char *)fname, (char *)p_pm, false);
if (backup != NULL) {
/*
@@ -5535,7 +5540,6 @@ static void au_del_cmd(AutoCmd *ac)
static void au_cleanup(void)
{
AutoPat *ap, **prev_ap;
- AutoCmd *ac, **prev_ac;
event_T event;
if (autocmd_busy || !au_need_clean) {
@@ -5548,11 +5552,11 @@ static void au_cleanup(void)
// Loop over all autocommand patterns.
prev_ap = &(first_autopat[(int)event]);
for (ap = *prev_ap; ap != NULL; ap = *prev_ap) {
- // Loop over all commands for this pattern.
- prev_ac = &(ap->cmds);
bool has_cmd = false;
- for (ac = *prev_ac; ac != NULL; ac = *prev_ac) {
+ // Loop over all commands for this pattern.
+ AutoCmd **prev_ac = &(ap->cmds);
+ for (AutoCmd *ac = *prev_ac; ac != NULL; ac = *prev_ac) {
// Remove the command if the pattern is to be deleted or when
// the command has been marked for deletion.
if (ap->pat == NULL || ac->cmd == NULL) {
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 61a85171e8..24a73a5b9f 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -153,14 +153,22 @@ bool hasFolding(linenr_T lnum, linenr_T *firstp, linenr_T *lastp)
return hasFoldingWin(curwin, lnum, firstp, lastp, true, NULL);
}
-/* hasFoldingWin() {{{2 */
+// hasFoldingWin() {{{2
+/// Search folds starting at lnum
+/// @param lnum first line to search
+/// @param[out] first first line of fold containing lnum
+/// @param[out] lastp last line with a fold
+/// @param cache when true: use cached values of window
+/// @param[out] infop where to store fold info
+///
+/// @return true if range contains folds
bool hasFoldingWin(
win_T *const win,
const linenr_T lnum,
linenr_T *const firstp,
linenr_T *const lastp,
- const bool cache, // when true: use cached values of window
- foldinfo_T *const infop // where to store fold info
+ const bool cache,
+ foldinfo_T *const infop
)
{
bool had_folded = false;
@@ -280,26 +288,31 @@ int foldLevel(linenr_T lnum)
// Return false if line is not folded.
bool lineFolded(win_T *const win, const linenr_T lnum)
{
- return foldedCount(win, lnum, NULL) != 0;
+ return fold_info(win, lnum).fi_lines != 0;
}
-/* foldedCount() {{{2 */
-/*
- * Count the number of lines that are folded at line number "lnum".
- * Normally "lnum" is the first line of a possible fold, and the returned
- * number is the number of lines in the fold.
- * Doesn't use caching from the displayed window.
- * Returns number of folded lines from "lnum", or 0 if line is not folded.
- * When "infop" is not NULL, fills *infop with the fold level info.
- */
-long foldedCount(win_T *win, linenr_T lnum, foldinfo_T *infop)
+/// fold_info() {{{2
+///
+/// Count the number of lines that are folded at line number "lnum".
+/// Normally "lnum" is the first line of a possible fold, and the returned
+/// number is the number of lines in the fold.
+/// Doesn't use caching from the displayed window.
+///
+/// @return with the fold level info.
+/// fi_lines = number of folded lines from "lnum",
+/// or 0 if line is not folded.
+foldinfo_T fold_info(win_T *win, linenr_T lnum)
{
+ foldinfo_T info;
linenr_T last;
- if (hasFoldingWin(win, lnum, NULL, &last, false, infop)) {
- return (long)(last - lnum + 1);
+ if (hasFoldingWin(win, lnum, NULL, &last, false, &info)) {
+ info.fi_lines = (long)(last - lnum + 1);
+ } else {
+ info.fi_lines = 0;
}
- return 0;
+
+ return info;
}
/* foldmethodIsManual() {{{2 */
@@ -356,23 +369,21 @@ int foldmethodIsDiff(win_T *wp)
return wp->w_p_fdm[0] == 'd';
}
-/* closeFold() {{{2 */
-/*
- * Close fold for current window at line "lnum".
- * Repeat "count" times.
- */
-void closeFold(linenr_T lnum, long count)
+// closeFold() {{{2
+/// Close fold for current window at line "lnum".
+/// Repeat "count" times.
+void closeFold(pos_T pos, long count)
{
- setFoldRepeat(lnum, count, FALSE);
+ setFoldRepeat(pos, count, false);
}
/* closeFoldRecurse() {{{2 */
/*
* Close fold for current window at line "lnum" recursively.
*/
-void closeFoldRecurse(linenr_T lnum)
+void closeFoldRecurse(pos_T pos)
{
- (void)setManualFold(lnum, FALSE, TRUE, NULL);
+ (void)setManualFold(pos, false, true, NULL);
}
/* opFoldRange() {{{2 */
@@ -382,28 +393,32 @@ void closeFoldRecurse(linenr_T lnum)
*/
void
opFoldRange(
- linenr_T first,
- linenr_T last,
+ pos_T firstpos,
+ pos_T lastpos,
int opening, // TRUE to open, FALSE to close
int recurse, // TRUE to do it recursively
int had_visual // TRUE when Visual selection used
)
{
- int done = DONE_NOTHING; /* avoid error messages */
+ int done = DONE_NOTHING; // avoid error messages
+ linenr_T first = firstpos.lnum;
+ linenr_T last = lastpos.lnum;
linenr_T lnum;
linenr_T lnum_next;
for (lnum = first; lnum <= last; lnum = lnum_next + 1) {
+ pos_T temp = { lnum, 0, 0 };
lnum_next = lnum;
/* Opening one level only: next fold to open is after the one going to
* be opened. */
if (opening && !recurse)
(void)hasFolding(lnum, NULL, &lnum_next);
- (void)setManualFold(lnum, opening, recurse, &done);
- /* Closing one level only: next line to close a fold is after just
- * closed fold. */
- if (!opening && !recurse)
+ (void)setManualFold(temp, opening, recurse, &done);
+ // Closing one level only: next line to close a fold is after just
+ // closed fold.
+ if (!opening && !recurse) {
(void)hasFolding(lnum, NULL, &lnum_next);
+ }
}
if (done == DONE_NOTHING)
EMSG(_(e_nofold));
@@ -417,18 +432,18 @@ opFoldRange(
* Open fold for current window at line "lnum".
* Repeat "count" times.
*/
-void openFold(linenr_T lnum, long count)
+void openFold(pos_T pos, long count)
{
- setFoldRepeat(lnum, count, TRUE);
+ setFoldRepeat(pos, count, true);
}
/* openFoldRecurse() {{{2 */
/*
* Open fold for current window at line "lnum" recursively.
*/
-void openFoldRecurse(linenr_T lnum)
+void openFoldRecurse(pos_T pos)
{
- (void)setManualFold(lnum, TRUE, TRUE, NULL);
+ (void)setManualFold(pos, true, true, NULL);
}
/* foldOpenCursor() {{{2 */
@@ -443,9 +458,10 @@ void foldOpenCursor(void)
if (hasAnyFolding(curwin))
for (;; ) {
done = DONE_NOTHING;
- (void)setManualFold(curwin->w_cursor.lnum, TRUE, FALSE, &done);
- if (!(done & DONE_ACTION))
+ (void)setManualFold(curwin->w_cursor, true, false, &done);
+ if (!(done & DONE_ACTION)) {
break;
+ }
}
}
@@ -542,21 +558,21 @@ int foldManualAllowed(int create)
// foldCreate() {{{2
/// Create a fold from line "start" to line "end" (inclusive) in the current
/// window.
-void foldCreate(win_T *wp, linenr_T start, linenr_T end)
+void foldCreate(win_T *wp, pos_T start, pos_T end)
{
fold_T *fp;
garray_T *gap;
garray_T fold_ga;
- int i, j;
+ int i;
int cont;
int use_level = FALSE;
int closed = FALSE;
int level = 0;
- linenr_T start_rel = start;
- linenr_T end_rel = end;
+ pos_T start_rel = start;
+ pos_T end_rel = end;
- if (start > end) {
- /* reverse the range */
+ if (start.lnum > end.lnum) {
+ // reverse the range
end = start_rel;
start = end_rel;
start_rel = start;
@@ -573,60 +589,74 @@ void foldCreate(win_T *wp, linenr_T start, linenr_T end)
// Find the place to insert the new fold
gap = &wp->w_folds;
- for (;; ) {
- if (!foldFind(gap, start_rel, &fp))
- break;
- if (fp->fd_top + fp->fd_len > end_rel) {
- /* New fold is completely inside this fold: Go one level deeper. */
- gap = &fp->fd_nested;
- start_rel -= fp->fd_top;
- end_rel -= fp->fd_top;
- if (use_level || fp->fd_flags == FD_LEVEL) {
- use_level = true;
- if (level >= wp->w_p_fdl) {
+ if (gap->ga_len == 0) {
+ i = 0;
+ } else {
+ for (;;) {
+ if (!foldFind(gap, start_rel.lnum, &fp)) {
+ break;
+ }
+ if (fp->fd_top + fp->fd_len > end_rel.lnum) {
+ // New fold is completely inside this fold: Go one level deeper.
+ gap = &fp->fd_nested;
+ start_rel.lnum -= fp->fd_top;
+ end_rel.lnum -= fp->fd_top;
+ if (use_level || fp->fd_flags == FD_LEVEL) {
+ use_level = true;
+ if (level >= wp->w_p_fdl) {
+ closed = true;
+ }
+ } else if (fp->fd_flags == FD_CLOSED) {
closed = true;
}
- } else if (fp->fd_flags == FD_CLOSED) {
- closed = true;
+ level++;
+ } else {
+ // This fold and new fold overlap: Insert here and move some folds
+ // inside the new fold.
+ break;
}
- level++;
+ }
+ if (gap->ga_len == 0) {
+ i = 0;
} else {
- /* This fold and new fold overlap: Insert here and move some folds
- * inside the new fold. */
- break;
+ i = (int)(fp - (fold_T *)gap->ga_data);
}
}
- i = (int)(fp - (fold_T *)gap->ga_data);
ga_grow(gap, 1);
{
fp = (fold_T *)gap->ga_data + i;
ga_init(&fold_ga, (int)sizeof(fold_T), 10);
- /* Count number of folds that will be contained in the new fold. */
- for (cont = 0; i + cont < gap->ga_len; ++cont)
- if (fp[cont].fd_top > end_rel)
+ // Count number of folds that will be contained in the new fold.
+ for (cont = 0; i + cont < gap->ga_len; cont++) {
+ if (fp[cont].fd_top > end_rel.lnum) {
break;
+ }
+ }
if (cont > 0) {
ga_grow(&fold_ga, cont);
/* If the first fold starts before the new fold, let the new fold
* start there. Otherwise the existing fold would change. */
- if (start_rel > fp->fd_top)
- start_rel = fp->fd_top;
-
- /* When last contained fold isn't completely contained, adjust end
- * of new fold. */
- if (end_rel < fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1)
- end_rel = fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1;
- /* Move contained folds to inside new fold. */
+ if (start_rel.lnum > fp->fd_top) {
+ start_rel.lnum = fp->fd_top;
+ }
+
+ // When last contained fold isn't completely contained, adjust end
+ // of new fold.
+ if (end_rel.lnum < fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1) {
+ end_rel.lnum = fp[cont - 1].fd_top + fp[cont - 1].fd_len - 1;
+ }
+ // Move contained folds to inside new fold
memmove(fold_ga.ga_data, fp, sizeof(fold_T) * (size_t)cont);
fold_ga.ga_len += cont;
i += cont;
/* Adjust line numbers in contained folds to be relative to the
* new fold. */
- for (j = 0; j < cont; ++j)
- ((fold_T *)fold_ga.ga_data)[j].fd_top -= start_rel;
+ for (int j = 0; j < cont; j++) {
+ ((fold_T *)fold_ga.ga_data)[j].fd_top -= start_rel.lnum;
+ }
}
/* Move remaining entries to after the new fold. */
if (i < gap->ga_len)
@@ -636,8 +666,8 @@ void foldCreate(win_T *wp, linenr_T start, linenr_T end)
/* insert new fold */
fp->fd_nested = fold_ga;
- fp->fd_top = start_rel;
- fp->fd_len = end_rel - start_rel + 1;
+ fp->fd_top = start_rel.lnum;
+ fp->fd_len = end_rel.lnum - start_rel.lnum + 1;
/* We want the new fold to be closed. If it would remain open because
* of using 'foldlevel', need to adjust fd_flags of containing folds.
@@ -766,7 +796,7 @@ void deleteFold(
*/
void clearFolding(win_T *win)
{
- deleteFoldRecurse(&win->w_folds);
+ deleteFoldRecurse(win->w_buffer, &win->w_folds);
win->w_foldinvalid = false;
}
@@ -788,13 +818,15 @@ void foldUpdate(win_T *wp, linenr_T top, linenr_T bot)
return;
}
- // Mark all folds from top to bot as maybe-small.
- fold_T *fp;
- (void)foldFind(&wp->w_folds, top, &fp);
- while (fp < (fold_T *)wp->w_folds.ga_data + wp->w_folds.ga_len
- && fp->fd_top < bot) {
- fp->fd_small = kNone;
- fp++;
+ if (wp->w_folds.ga_len > 0) {
+ // Mark all folds from top to bot as maybe-small.
+ fold_T *fp;
+ (void)foldFind(&wp->w_folds, top, &fp);
+ while (fp < (fold_T *)wp->w_folds.ga_data + wp->w_folds.ga_len
+ && fp->fd_top < bot) {
+ fp->fd_small = kNone;
+ fp++;
+ }
}
if (foldmethodIsIndent(wp)
@@ -834,7 +866,7 @@ void foldUpdateAfterInsert(void)
void foldUpdateAll(win_T *win)
{
win->w_foldinvalid = true;
- redraw_win_later(win, NOT_VALID);
+ redraw_later(win, NOT_VALID);
}
// foldMoveTo() {{{2
@@ -860,6 +892,9 @@ int foldMoveTo(
// that moves the cursor is used.
linenr_T lnum_off = 0;
garray_T *gap = &curwin->w_folds;
+ if (gap->ga_len == 0) {
+ break;
+ }
bool use_level = false;
bool maybe_small = false;
linenr_T lnum_found = curwin->w_cursor.lnum;
@@ -1058,11 +1093,16 @@ void cloneFoldGrowArray(garray_T *from, garray_T *to)
* the first fold below it (careful: it can be beyond the end of the array!).
* Returns FALSE when there is no fold that contains "lnum".
*/
-static int foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp)
+static bool foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp)
{
linenr_T low, high;
fold_T *fp;
+ if (gap->ga_len == 0) {
+ *fpp = NULL;
+ return false;
+ }
+
/*
* Perform a binary search.
* "low" is lowest index of possible match.
@@ -1086,7 +1126,7 @@ static int foldFind(const garray_T *gap, linenr_T lnum, fold_T **fpp)
}
}
*fpp = fp + low;
- return FALSE;
+ return false;
}
/* foldLevelWin() {{{2 */
@@ -1131,14 +1171,14 @@ static void checkupdate(win_T *wp)
* Open or close fold for current window at line "lnum".
* Repeat "count" times.
*/
-static void setFoldRepeat(linenr_T lnum, long count, int do_open)
+static void setFoldRepeat(pos_T pos, long count, int do_open)
{
int done;
long n;
for (n = 0; n < count; ++n) {
done = DONE_NOTHING;
- (void)setManualFold(lnum, do_open, FALSE, &done);
+ (void)setManualFold(pos, do_open, false, &done);
if (!(done & DONE_ACTION)) {
/* Only give an error message when no fold could be opened. */
if (n == 0 && !(done & DONE_FOLD))
@@ -1155,12 +1195,13 @@ static void setFoldRepeat(linenr_T lnum, long count, int do_open)
*/
static linenr_T
setManualFold(
- linenr_T lnum,
+ pos_T pos,
int opening, // TRUE when opening, FALSE when closing
int recurse, // TRUE when closing/opening recursive
int *donep
)
{
+ linenr_T lnum = pos.lnum;
if (foldmethodIsDiff(curwin) && curwin->w_p_scb) {
linenr_T dlnum;
@@ -1220,9 +1261,10 @@ setManualFoldWin(
gap = &wp->w_folds;
for (;; ) {
if (!foldFind(gap, lnum, &fp)) {
- /* If there is a following fold, continue there next time. */
- if (fp < (fold_T *)gap->ga_data + gap->ga_len)
+ // If there is a following fold, continue there next time.
+ if (fp != NULL && fp < (fold_T *)gap->ga_data + gap->ga_len) {
next = fp->fd_top + off;
+ }
break;
}
@@ -1313,7 +1355,7 @@ static void deleteFoldEntry(win_T *const wp, garray_T *const gap, const int idx,
fold_T *fp = (fold_T *)gap->ga_data + idx;
if (recursive || GA_EMPTY(&fp->fd_nested)) {
// recursively delete the contained folds
- deleteFoldRecurse(&fp->fd_nested);
+ deleteFoldRecurse(wp->w_buffer, &fp->fd_nested);
gap->ga_len--;
if (idx < gap->ga_len) {
memmove(fp, fp + 1, sizeof(*fp) * (size_t)(gap->ga_len - idx));
@@ -1355,9 +1397,9 @@ static void deleteFoldEntry(win_T *const wp, garray_T *const gap, const int idx,
/*
* Delete nested folds in a fold.
*/
-void deleteFoldRecurse(garray_T *gap)
+void deleteFoldRecurse(buf_T *bp, garray_T *gap)
{
-# define DELETE_FOLD_NESTED(fd) deleteFoldRecurse(&((fd)->fd_nested))
+# define DELETE_FOLD_NESTED(fd) deleteFoldRecurse(bp, &((fd)->fd_nested))
GA_DEEP_CLEAR(gap, fold_T, DELETE_FOLD_NESTED);
}
@@ -1389,6 +1431,10 @@ static void foldMarkAdjustRecurse(
linenr_T last;
linenr_T top;
+ if (gap->ga_len == 0) {
+ return;
+ }
+
/* In Insert mode an inserted line at the top of a fold is considered part
* of the fold, otherwise it isn't. */
if ((State & INSERT) && amount == (linenr_T)1 && line2 == MAXLNUM)
@@ -1593,7 +1639,7 @@ static void setSmallMaybe(garray_T *gap)
* Create a fold from line "start" to line "end" (inclusive) in the current
* window by adding markers.
*/
-static void foldCreateMarkers(win_T *wp, linenr_T start, linenr_T end)
+static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end)
{
buf_T *buf = wp->w_buffer;
if (!MODIFIABLE(buf)) {
@@ -1608,13 +1654,13 @@ static void foldCreateMarkers(win_T *wp, linenr_T start, linenr_T end)
/* Update both changes here, to avoid all folds after the start are
* changed when the start marker is inserted and the end isn't. */
// TODO(teto): pass the buffer
- changed_lines(start, (colnr_T)0, end, 0L, false);
+ changed_lines(start.lnum, (colnr_T)0, end.lnum, 0L, false);
// Note: foldAddMarker() may not actually change start and/or end if
// u_save() is unable to save the buffer line, but we send the
// nvim_buf_lines_event anyway since it won't do any harm.
- int64_t num_changed = 1 + end - start;
- buf_updates_send_changes(buf, start, num_changed, num_changed, true);
+ int64_t num_changed = 1 + end.lnum - start.lnum;
+ buf_updates_send_changes(buf, start.lnum, num_changed, num_changed, true);
}
/* foldAddMarker() {{{2 */
@@ -1622,13 +1668,14 @@ static void foldCreateMarkers(win_T *wp, linenr_T start, linenr_T end)
* Add "marker[markerlen]" in 'commentstring' to line "lnum".
*/
static void foldAddMarker(
- buf_T *buf, linenr_T lnum, const char_u *marker, size_t markerlen)
+ buf_T *buf, pos_T pos, const char_u *marker, size_t markerlen)
{
char_u *cms = buf->b_p_cms;
char_u *line;
char_u *newline;
char_u *p = (char_u *)strstr((char *)buf->b_p_cms, "%s");
bool line_is_comment = false;
+ linenr_T lnum = pos.lnum;
// Allocate a new line: old-line + 'cms'-start + marker + 'cms'-end
line = ml_get_buf(buf, lnum, false);
@@ -1727,19 +1774,23 @@ static void foldDelMarker(
STRCPY(newline + (p - line), p + len);
ml_replace_buf(buf, lnum, newline, false);
extmark_splice_cols(buf, (int)lnum-1, (int)(p - line),
- (int)len,
- 0, kExtmarkUndo);
+ (int)len, 0, kExtmarkUndo);
}
break;
}
}
// get_foldtext() {{{2
-/// Return the text for a closed fold at line "lnum", with last line "lnume".
-/// When 'foldtext' isn't set puts the result in "buf[FOLD_TEXT_LEN]".
+/// Generates text to display
+///
+/// @param buf allocated memory of length FOLD_TEXT_LEN. Used when 'foldtext'
+/// isn't set puts the result in "buf[FOLD_TEXT_LEN]".
+/// @param at line "lnum", with last line "lnume".
+/// @return the text for a closed fold
+///
/// Otherwise the result is in allocated memory.
char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume,
- foldinfo_T *foldinfo, char_u *buf)
+ foldinfo_T foldinfo, char_u *buf)
FUNC_ATTR_NONNULL_ARG(1)
{
char_u *text = NULL;
@@ -1767,11 +1818,12 @@ char_u *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume,
set_vim_var_nr(VV_FOLDSTART, (varnumber_T) lnum);
set_vim_var_nr(VV_FOLDEND, (varnumber_T) lnume);
- /* Set "v:folddashes" to a string of "level" dashes. */
- /* Set "v:foldlevel" to "level". */
- level = foldinfo->fi_level;
- if (level > (int)sizeof(dashes) - 1)
+ // Set "v:folddashes" to a string of "level" dashes.
+ // Set "v:foldlevel" to "level".
+ level = foldinfo.fi_level;
+ if (level > (int)sizeof(dashes) - 1) {
level = (int)sizeof(dashes) - 1;
+ }
memset(dashes, '-', (size_t)level);
dashes[level] = NUL;
set_vim_var_string(VV_FOLDDASHES, dashes, -1);
@@ -2265,14 +2317,15 @@ static linenr_T foldUpdateIEMSRecurse(
/* Find an existing fold to re-use. Preferably one that
* includes startlnum, otherwise one that ends just before
* startlnum or starts after it. */
- if (foldFind(gap, startlnum, &fp)
- || (fp < ((fold_T *)gap->ga_data) + gap->ga_len
- && fp->fd_top <= firstlnum)
- || foldFind(gap, firstlnum - concat, &fp)
- || (fp < ((fold_T *)gap->ga_data) + gap->ga_len
- && ((lvl < level && fp->fd_top < flp->lnum)
- || (lvl >= level
- && fp->fd_top <= flp->lnum_save)))) {
+ if (gap->ga_len > 0
+ && (foldFind(gap, startlnum, &fp)
+ || (fp < ((fold_T *)gap->ga_data) + gap->ga_len
+ && fp->fd_top <= firstlnum)
+ || foldFind(gap, firstlnum - concat, &fp)
+ || (fp < ((fold_T *)gap->ga_data) + gap->ga_len
+ && ((lvl < level && fp->fd_top < flp->lnum)
+ || (lvl >= level
+ && fp->fd_top <= flp->lnum_save))))) {
if (fp->fd_top + fp->fd_len + concat > firstlnum) {
/* Use existing fold for the new fold. If it starts
* before where we started looking, extend it. If it
@@ -2363,7 +2416,11 @@ static linenr_T foldUpdateIEMSRecurse(
} else {
/* Insert new fold. Careful: ga_data may be NULL and it
* may change! */
- i = (int)(fp - (fold_T *)gap->ga_data);
+ if (gap->ga_len == 0) {
+ i = 0;
+ } else {
+ i = (int)(fp - (fold_T *)gap->ga_data);
+ }
foldInsert(gap, i);
fp = (fold_T *)gap->ga_data + i;
/* The new fold continues until bot, unless we find the
@@ -2559,9 +2616,10 @@ static void foldInsert(garray_T *gap, int i)
ga_grow(gap, 1);
fp = (fold_T *)gap->ga_data + i;
- if (i < gap->ga_len)
+ if (gap->ga_len > 0 && i < gap->ga_len) {
memmove(fp + 1, fp, sizeof(fold_T) * (size_t)(gap->ga_len - i));
- ++gap->ga_len;
+ }
+ gap->ga_len++;
ga_init(&fp->fd_nested, (int)sizeof(fold_T), 10);
}
@@ -2596,17 +2654,18 @@ static void foldSplit(buf_T *buf, garray_T *const gap,
* any between top and bot, they have been removed by the caller. */
garray_T *const gap1 = &fp->fd_nested;
garray_T *const gap2 = &fp[1].fd_nested;
- (void)(foldFind(gap1, bot + 1 - fp->fd_top, &fp2));
- const int len = (int)((fold_T *)gap1->ga_data + gap1->ga_len - fp2);
- if (len > 0) {
- ga_grow(gap2, len);
- for (int idx = 0; idx < len; idx++) {
- ((fold_T *)gap2->ga_data)[idx] = fp2[idx];
- ((fold_T *)gap2->ga_data)[idx].fd_top
- -= fp[1].fd_top - fp->fd_top;
- }
- gap2->ga_len = len;
- gap1->ga_len -= len;
+ if (foldFind(gap1, bot + 1 - fp->fd_top, &fp2)) {
+ const int len = (int)((fold_T *)gap1->ga_data + gap1->ga_len - fp2);
+ if (len > 0) {
+ ga_grow(gap2, len);
+ for (int idx = 0; idx < len; idx++) {
+ ((fold_T *)gap2->ga_data)[idx] = fp2[idx];
+ ((fold_T *)gap2->ga_data)[idx].fd_top
+ -= fp[1].fd_top - fp->fd_top;
+ }
+ gap2->ga_len = len;
+ gap1->ga_len -= len;
+ }
}
fp->fd_len = top - fp->fd_top;
fold_changed = true;
@@ -2641,7 +2700,7 @@ static void foldRemove(
return; // nothing to do
}
- for (;; ) {
+ while (gap->ga_len > 0) {
// Find fold that includes top or a following one.
if (foldFind(gap, top, &fp) && fp->fd_top < top) {
// 2: or 3: need to delete nested folds
@@ -2657,7 +2716,8 @@ static void foldRemove(
fold_changed = true;
continue;
}
- if (fp >= (fold_T *)(gap->ga_data) + gap->ga_len
+ if (gap->ga_data == NULL
+ || fp >= (fold_T *)(gap->ga_data) + gap->ga_len
|| fp->fd_top > bot) {
// 6: Found a fold below bot, can stop looking.
break;
@@ -2738,7 +2798,8 @@ static void truncate_fold(win_T *const wp, fold_T *fp, linenr_T end)
}
#define FOLD_END(fp) ((fp)->fd_top + (fp)->fd_len - 1)
-#define VALID_FOLD(fp, gap) ((fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len))
+#define VALID_FOLD(fp, gap) \
+ ((gap)->ga_len > 0 && (fp) < ((fold_T *)(gap)->ga_data + (gap)->ga_len))
#define FOLD_INDEX(fp, gap) ((size_t)(fp - ((fold_T *)(gap)->ga_data)))
void foldMoveRange(
win_T *const wp, garray_T *gap,
diff --git a/src/nvim/fold.h b/src/nvim/fold.h
index f35b328fb1..95c4b0c1dc 100644
--- a/src/nvim/fold.h
+++ b/src/nvim/fold.h
@@ -18,6 +18,7 @@ typedef struct foldinfo {
other fields are invalid */
int fi_low_level; /* lowest fold level that starts in the same
line */
+ long fi_lines;
} foldinfo_T;
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index 6e80ad0e5c..b31209e8ff 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -250,7 +250,7 @@ for i = 1, #functions do
end
output:write('\n } else {')
output:write('\n api_set_error(error, kErrorTypeException, \
- "Wrong type for argument '..j..', expecting '..param[1]..'");')
+ "Wrong type for argument '..j..' when calling '..fn.name..', expecting '..param[1]..'");')
output:write('\n goto cleanup;')
output:write('\n }\n')
else
diff --git a/src/nvim/generators/gen_ex_cmds.lua b/src/nvim/generators/gen_ex_cmds.lua
index 075d8ba9cc..849c82f50e 100644
--- a/src/nvim/generators/gen_ex_cmds.lua
+++ b/src/nvim/generators/gen_ex_cmds.lua
@@ -24,8 +24,6 @@ local defsfile = io.open(defsfname, 'w')
local defs = require('ex_cmds')
-local first = true
-
local byte_a = string.byte('a')
local byte_z = string.byte('z')
local a_to_z = byte_z - byte_a + 1
@@ -41,8 +39,7 @@ static const uint16_t cmdidxs1[%u] = {
-- fit in a byte.
local cmdidxs2_out = string.format([[
static const char_u cmdidxs2[%u][%u] = {
-/* 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 */
]], a_to_z, a_to_z)
enumfile:write([[
@@ -50,10 +47,8 @@ typedef enum CMD_index {
]])
defsfile:write(string.format([[
static const int command_count = %u;
-]], #defs))
-defsfile:write(string.format([[
static CommandDefinition cmdnames[%u] = {
-]], #defs))
+]], #defs, #defs))
local cmds, cmdidxs1, cmdidxs2 = {}, {}, {}
for _, cmd in ipairs(defs) do
local enumname = cmd.enum or ('CMD_' .. cmd.command)
@@ -61,11 +56,6 @@ for _, cmd in ipairs(defs) do
if byte_a <= byte_cmd and byte_cmd <= byte_z then
table.insert(cmds, cmd.command)
end
- if first then
- first = false
- else
- defsfile:write(',\n')
- end
enumfile:write(' ' .. enumname .. ',\n')
defsfile:write(string.format([[
[%s] = {
@@ -73,7 +63,8 @@ for _, cmd in ipairs(defs) do
.cmd_func = (ex_func_T)&%s,
.cmd_argt = %uL,
.cmd_addr_type = %i
- }]], enumname, cmd.command, cmd.func, cmd.flags, cmd.addr_type))
+ },
+]], enumname, cmd.command, cmd.func, cmd.flags, cmd.addr_type))
end
for i = #cmds, 1, -1 do
local cmd = cmds[i]
@@ -104,15 +95,14 @@ for i = byte_a, byte_z do
end
cmdidxs2_out = cmdidxs2_out .. ' },\n'
end
-defsfile:write([[
-
-};
-]])
enumfile:write([[
CMD_SIZE,
CMD_USER = -1,
CMD_USER_BUF = -2
} cmdidx_T;
]])
-defsfile:write(cmdidxs1_out .. '};\n')
-defsfile:write(cmdidxs2_out .. '};\n')
+defsfile:write(string.format([[
+};
+%s};
+%s};
+]], cmdidxs1_out, cmdidxs2_out))
diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua
index a8cf496cb9..d80a6219eb 100644
--- a/src/nvim/generators/gen_options.lua
+++ b/src/nvim/generators/gen_options.lua
@@ -141,9 +141,6 @@ local dump_option = function(i, o)
elseif #o.scope == 1 and o.scope[1] == 'window' then
w(' .var=VAR_WIN')
end
- if o.enable_if then
- w('#endif')
- end
if #o.scope == 1 and o.scope[1] == 'global' then
w(' .indir=PV_NONE')
else
@@ -163,6 +160,12 @@ local dump_option = function(i, o)
defines['PV_' .. varname:sub(3):upper()] = pv_name
w(' .indir=' .. pv_name)
end
+ if o.enable_if then
+ w('#else')
+ w(' .var=NULL')
+ w(' .indir=PV_NONE')
+ w('#endif')
+ end
if o.defaults then
if o.defaults.condition then
w(get_cond(o.defaults.condition))
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 5ab5a7db1b..456979be00 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -27,6 +27,7 @@
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/func_attr.h"
+#include "nvim/lua/executor.h"
#include "nvim/main.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
@@ -455,6 +456,9 @@ void flush_buffers(flush_buffers_T flush_typeahead)
typebuf.tb_silent = 0;
cmd_silent = false;
typebuf.tb_no_abbr_cnt = 0;
+ if (++typebuf.tb_change_cnt == 0) {
+ typebuf.tb_change_cnt = 1;
+ }
}
/*
@@ -1524,6 +1528,17 @@ int vgetc(void)
c = utf_ptr2char(buf);
}
+ // If mappings are enabled (i.e., not Ctrl-v) and the user directly typed
+ // something with a meta- or alt- modifier that was not mapped, interpret
+ // <M-x> as <Esc>x rather than as an unbound meta keypress. #8213
+ if (!no_mapping && KeyTyped
+ && (mod_mask == MOD_MASK_ALT || mod_mask == MOD_MASK_META)) {
+ mod_mask = 0;
+ stuffcharReadbuff(c);
+ u_sync(false);
+ c = ESC;
+ }
+
break;
}
}
@@ -1535,6 +1550,9 @@ int vgetc(void)
*/
may_garbage_collect = false;
+ // Exec lua callbacks for on_keystroke
+ nlua_execute_log_keystroke(c);
+
return c;
}
@@ -2037,14 +2055,19 @@ static int vgetorpeek(bool advance)
*/
if (mp->m_expr) {
int save_vgetc_busy = vgetc_busy;
+ const bool save_may_garbage_collect = may_garbage_collect;
vgetc_busy = 0;
+ may_garbage_collect = false;
+
save_m_keys = vim_strsave(mp->m_keys);
save_m_str = vim_strsave(mp->m_str);
s = eval_map_expr(save_m_str, NUL);
vgetc_busy = save_vgetc_busy;
- } else
+ may_garbage_collect = save_may_garbage_collect;
+ } else {
s = mp->m_str;
+ }
/*
* Insert the 'to' part in the typebuf.tb_buf.
@@ -4181,7 +4204,6 @@ int put_escstr(FILE *fd, char_u *strstart, int what)
{
char_u *str = strstart;
int c;
- int modifiers;
// :map xx <Nop>
if (*str == NUL && what == 1) {
@@ -4208,7 +4230,7 @@ int put_escstr(FILE *fd, char_u *strstart, int what)
* when they are read back.
*/
if (c == K_SPECIAL && what != 2) {
- modifiers = 0x0;
+ int modifiers = 0;
if (str[1] == KS_MODIFIER) {
modifiers = str[2];
str += 3;
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index d6d00d6e83..657afeaf4c 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -92,6 +92,10 @@ EXTERN struct nvim_stats_s {
EXTERN int Rows INIT(= DFLT_ROWS); // nr of rows in the screen
EXTERN int Columns INIT(= DFLT_COLS); // nr of columns in the screen
+EXTERN NS ns_hl_active INIT(= 0); // current ns that defines highlights
+EXTERN bool ns_hl_changed INIT(= false); // highlight need update
+
+
// We use 64-bit file functions here, if available. E.g. ftello() returns
// off_t instead of long, which helps if long is 32 bit and off_t is 64 bit.
// We assume that when fseeko() is available then ftello() is too.
@@ -125,8 +129,6 @@ typedef off_t off_T;
EXTERN int mod_mask INIT(= 0x0); // current key modifiers
-EXTERN bool lua_attr_active INIT(= false);
-
// Cmdline_row is the row where the command line starts, just below the
// last window.
// When the cmdline gets longer than the available space the screen gets
@@ -208,7 +210,7 @@ EXTERN int need_clr_eos INIT(= false); // need to clear text before
// displaying a message.
EXTERN int emsg_skip INIT(= 0); // don't display errors for
// expression that is skipped
-EXTERN int emsg_severe INIT(= false); // use message of next of several
+EXTERN bool emsg_severe INIT(= false); // use message of next of several
// emsg() calls for throw
EXTERN int did_endif INIT(= false); // just had ":endif"
EXTERN dict_T vimvardict; // Dictionary with v: variables
@@ -353,9 +355,11 @@ EXTERN int t_colors INIT(= 256); // int value of T_CCO
// position. Search_match_lines is the number of lines after the match (0 for
// a match within one line), search_match_endcol the column number of the
// character just after the match in the last line.
-EXTERN int highlight_match INIT(= false); // show search match pos
-EXTERN linenr_T search_match_lines; // lines of of matched string
-EXTERN colnr_T search_match_endcol; // col nr of match end
+EXTERN bool highlight_match INIT(= false); // show search match pos
+EXTERN linenr_T search_match_lines; // lines of of matched string
+EXTERN colnr_T search_match_endcol; // col nr of match end
+EXTERN linenr_T search_first_line INIT(= 0); // for :{FIRST},{last}s/pat
+EXTERN linenr_T search_last_line INIT(= MAXLNUM); // for :{first},{LAST}s/pat
EXTERN int no_smartcase INIT(= false); // don't use 'smartcase' once
@@ -488,9 +492,6 @@ EXTERN int stdout_isatty INIT(= true);
// volatile because it is used in a signal handler.
EXTERN volatile int full_screen INIT(= false);
-// When started in restricted mode (-Z).
-EXTERN int restricted INIT(= false);
-
/// Non-zero when only "safe" commands are allowed, e.g. when sourcing .exrc or
/// .vimrc in current directory.
EXTERN int secure INIT(= false);
@@ -941,8 +942,10 @@ EXTERN char_u e_readonly[] INIT(= N_(
EXTERN char_u e_readonlyvar[] INIT(= N_(
"E46: Cannot change read-only variable \"%.*s\""));
EXTERN char_u e_dictreq[] INIT(= N_("E715: Dictionary required"));
-EXTERN char_u e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s"));
-EXTERN char_u e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: %s"));
+EXTERN char_u e_toomanyarg[] INIT(= N_(
+ "E118: Too many arguments for function: %s"));
+EXTERN char_u e_dictkey[] INIT(= N_(
+ "E716: Key not present in Dictionary: \"%s\""));
EXTERN char_u e_listreq[] INIT(= N_("E714: List required"));
EXTERN char_u e_listdictarg[] INIT(= N_(
"E712: Argument of %s must be a List or Dictionary"));
diff --git a/src/nvim/grid_defs.h b/src/nvim/grid_defs.h
index c6687c8da9..e14aae73d8 100644
--- a/src/nvim/grid_defs.h
+++ b/src/nvim/grid_defs.h
@@ -11,7 +11,7 @@
// The characters and attributes drawn on grids.
typedef char_u schar_T[(MAX_MCO+1) * 4 + 1];
-typedef int16_t sattr_T;
+typedef int sattr_T;
/// ScreenGrid represents a resizable rectuangular grid displayed by UI clients.
///
diff --git a/src/nvim/hashtab.h b/src/nvim/hashtab.h
index 19633d455f..c82a6cc121 100644
--- a/src/nvim/hashtab.h
+++ b/src/nvim/hashtab.h
@@ -51,6 +51,7 @@ typedef struct hashitem_S {
/// Initial size for a hashtable.
/// Our items are relatively small and growing is expensive, thus start with 16.
/// Must be a power of 2.
+/// This allows for storing 10 items (2/3 of 16) before a resize is needed.
#define HT_INIT_SIZE 16
/// An array-based hashtable.
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index c0cae54572..898ff4ebfe 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -14,6 +14,7 @@
#include "nvim/ui.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/lua/executor.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "highlight.c.generated.h"
@@ -28,12 +29,16 @@ static Map(int, int) *combine_attr_entries;
static Map(int, int) *blend_attr_entries;
static Map(int, int) *blendthrough_attr_entries;
+/// highlight entries private to a namespace
+static Map(ColorKey, ColorItem) *ns_hl;
+
void highlight_init(void)
{
attr_entry_ids = map_new(HlEntry, int)();
combine_attr_entries = map_new(int, int)();
blend_attr_entries = map_new(int, int)();
blendthrough_attr_entries = map_new(int, int)();
+ ns_hl = map_new(ColorKey, ColorItem)();
// index 0 is no attribute, add dummy entry:
kv_push(attr_entries, ((HlEntry){ .attr = HLATTRS_INIT, .kind = kHlUnknown,
@@ -90,7 +95,12 @@ static int get_attr_entry(HlEntry entry)
}
}
- id = (int)kv_size(attr_entries);
+ size_t next_id = kv_size(attr_entries);
+ if (next_id > INT_MAX) {
+ ELOG("The index on attr_entries has overflowed");
+ return 0;
+ }
+ id = (int)next_id;
kv_push(attr_entries, entry);
map_put(HlEntry, int)(attr_entry_ids, entry, id);
@@ -124,21 +134,114 @@ void ui_send_all_hls(UI *ui)
}
/// Get attribute code for a syntax group.
-int hl_get_syn_attr(int idx, HlAttrs at_en)
+int hl_get_syn_attr(int ns_id, int idx, HlAttrs at_en)
{
// TODO(bfredl): should we do this unconditionally
if (at_en.cterm_fg_color != 0 || at_en.cterm_bg_color != 0
|| at_en.rgb_fg_color != -1 || at_en.rgb_bg_color != -1
|| at_en.rgb_sp_color != -1 || at_en.cterm_ae_attr != 0
- || at_en.rgb_ae_attr != 0) {
+ || at_en.rgb_ae_attr != 0 || ns_id != 0) {
return get_attr_entry((HlEntry){ .attr = at_en, .kind = kHlSyntax,
- .id1 = idx, .id2 = 0 });
+ .id1 = idx, .id2 = ns_id });
} else {
// If all the fields are cleared, clear the attr field back to default value
return 0;
}
}
+static ColorKey colored_key(NS ns_id, int syn_id)
+{
+ return (ColorKey){ .ns_id = (int)ns_id, .syn_id = syn_id };
+}
+
+void ns_hl_def(NS ns_id, int hl_id, HlAttrs attrs, int link_id)
+{
+ DecorProvider *p = get_provider(ns_id, true);
+ int attr_id = link_id > 0 ? -1 : hl_get_syn_attr(ns_id, hl_id, attrs);
+ ColorItem it = { .attr_id = attr_id,
+ .link_id = link_id,
+ .version = p->hl_valid };
+ map_put(ColorKey, ColorItem)(ns_hl, colored_key(ns_id, hl_id), it);
+}
+
+int ns_get_hl(NS ns_id, int hl_id, bool link)
+{
+ static int recursive = 0;
+
+ if (ns_id < 0) {
+ if (ns_hl_active <= 0) {
+ return -1;
+ }
+ ns_id = ns_hl_active;
+ }
+
+ DecorProvider *p = get_provider(ns_id, true);
+ ColorItem it = map_get(ColorKey, ColorItem)(ns_hl, colored_key(ns_id, hl_id));
+ // TODO(bfredl): map_ref true even this?
+ bool valid_cache = it.version >= p->hl_valid;
+
+ if (!valid_cache && p->hl_def != LUA_NOREF && !recursive) {
+ FIXED_TEMP_ARRAY(args, 3);
+ args.items[0] = INTEGER_OBJ((Integer)ns_id);
+ args.items[1] = STRING_OBJ(cstr_to_string((char *)syn_id2name(hl_id)));
+ args.items[2] = BOOLEAN_OBJ(link);
+ // TODO(bfredl): preload the "global" attr dict?
+
+ Error err = ERROR_INIT;
+ recursive++;
+ Object ret = nlua_call_ref(p->hl_def, "hl_def", args, true, &err);
+ recursive--;
+
+ // TODO(bfredl): or "inherit", combine with global value?
+ bool fallback = true;
+ int tmp = false;
+ HlAttrs attrs = HLATTRS_INIT;
+ if (ret.type == kObjectTypeDictionary) {
+ Dictionary dict = ret.data.dictionary;
+ fallback = false;
+ attrs = dict2hlattrs(dict, true, &it.link_id, &err);
+ for (size_t i = 0; i < dict.size; i++) {
+ char *key = dict.items[i].key.data;
+ Object val = dict.items[i].value;
+ bool truthy = api_object_to_bool(val, key, false, &err);
+
+ if (strequal(key, "fallback")) {
+ fallback = truthy;
+ } else if (strequal(key, "temp")) {
+ tmp = truthy;
+ }
+ }
+ if (it.link_id >= 0) {
+ fallback = true;
+ }
+ }
+
+ it.attr_id = fallback ? -1 : hl_get_syn_attr((int)ns_id, hl_id, attrs);
+ it.version = p->hl_valid-tmp;
+ map_put(ColorKey, ColorItem)(ns_hl, colored_key(ns_id, hl_id), it);
+ }
+
+ if (link) {
+ return it.attr_id >= 0 ? -1 : it.link_id;
+ } else {
+ return it.attr_id;
+ }
+}
+
+
+bool win_check_ns_hl(win_T *wp)
+{
+ if (ns_hl_changed) {
+ highlight_changed();
+ if (wp) {
+ update_window_hl(wp, true);
+ }
+ ns_hl_changed = false;
+ return true;
+ }
+ return false;
+}
+
/// Get attribute code for a builtin highlight group.
///
/// The final syntax group could be modified by hi-link or 'winhighlight'.
@@ -199,6 +302,17 @@ void update_window_hl(win_T *wp, bool invalid)
wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0;
}
+ // NOOOO! You cannot just pretend that "Normal" is just like any other
+ // syntax group! It needs at least 10 layers of special casing! Noooooo!
+ //
+ // haha, theme engine go brrr
+ int normality = syn_check_group((const char_u *)S_LEN("Normal"));
+ int ns_attr = ns_get_hl(-1, normality, false);
+ if (ns_attr > 0) {
+ // TODO(bfredl): hantera NormalNC and so on
+ wp->w_hl_attr_normal = ns_attr;
+ }
+
// if blend= attribute is not set, 'winblend' value overrides it.
if (wp->w_floating && wp->w_p_winbl > 0) {
HlEntry entry = kv_A(attr_entries, wp->w_hl_attr_normal);
@@ -270,6 +384,7 @@ void clear_hl_tables(bool reinit)
map_free(int, int)(combine_attr_entries);
map_free(int, int)(blend_attr_entries);
map_free(int, int)(blendthrough_attr_entries);
+ map_free(ColorKey, ColorItem)(ns_hl);
}
}
@@ -658,6 +773,103 @@ Dictionary hlattrs2dict(HlAttrs ae, bool use_rgb)
return hl;
}
+HlAttrs dict2hlattrs(Dictionary dict, bool use_rgb, int *link_id, Error *err)
+{
+ HlAttrs hlattrs = HLATTRS_INIT;
+
+ int32_t fg = -1, bg = -1, sp = -1;
+ int16_t mask = 0;
+ for (size_t i = 0; i < dict.size; i++) {
+ char *key = dict.items[i].key.data;
+ Object val = dict.items[i].value;
+
+ struct {
+ const char *name;
+ int16_t flag;
+ } flags[] = {
+ { "bold", HL_BOLD },
+ { "standout", HL_STANDOUT },
+ { "underline", HL_UNDERLINE },
+ { "undercurl", HL_UNDERCURL },
+ { "italic", HL_ITALIC },
+ { "reverse", HL_INVERSE },
+ { NULL, 0 },
+ };
+
+ int j;
+ for (j = 0; flags[j].name; j++) {
+ if (strequal(flags[j].name, key)) {
+ if (api_object_to_bool(val, key, false, err)) {
+ mask = mask | flags[j].flag;
+ }
+ break;
+ }
+ }
+
+ struct {
+ const char *name;
+ const char *shortname;
+ int *dest;
+ } colors[] = {
+ { "foreground", "fg", &fg },
+ { "background", "bg", &bg },
+ { "special", "sp", &sp },
+ { NULL, NULL, NULL },
+ };
+
+ int k;
+ for (k = 0; (!flags[j].name) && colors[k].name; k++) {
+ if (strequal(colors[k].name, key) || strequal(colors[k].shortname, key)) {
+ if (val.type == kObjectTypeInteger) {
+ *colors[k].dest = (int)val.data.integer;
+ } else if (val.type == kObjectTypeString) {
+ String str = val.data.string;
+ // TODO(bfredl): be more fancy with "bg", "fg" etc
+ if (str.size) {
+ *colors[k].dest = name_to_color(str.data);
+ }
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "'%s' must be string or integer", key);
+ }
+ break;
+ }
+ }
+
+
+ if (flags[j].name || colors[k].name) {
+ // handled above
+ } else if (link_id && strequal(key, "link")) {
+ if (val.type == kObjectTypeString) {
+ String str = val.data.string;
+ *link_id = syn_check_group((const char_u *)str.data, (int)str.size);
+ } else if (val.type == kObjectTypeInteger) {
+ // TODO(bfredl): validate range?
+ *link_id = (int)val.data.integer;
+ } else {
+ api_set_error(err, kErrorTypeValidation,
+ "'link' must be string or integer");
+ }
+ }
+
+ if (ERROR_SET(err)) {
+ return hlattrs; // error set, caller should not use retval
+ }
+ }
+
+ if (use_rgb) {
+ hlattrs.rgb_ae_attr = mask;
+ hlattrs.rgb_bg_color = bg;
+ hlattrs.rgb_fg_color = fg;
+ hlattrs.rgb_sp_color = sp;
+ } else {
+ hlattrs.cterm_ae_attr = mask;
+ hlattrs.cterm_bg_color = bg == -1 ? cterm_normal_bg_color : bg + 1;
+ hlattrs.cterm_fg_color = fg == -1 ? cterm_normal_fg_color : fg + 1;
+ }
+
+ return hlattrs;
+}
Array hl_inspect(int attr)
{
Array ret = ARRAY_DICT_INIT;
diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h
index 36f3181674..6a5c593ab1 100644
--- a/src/nvim/highlight_defs.h
+++ b/src/nvim/highlight_defs.h
@@ -4,6 +4,7 @@
#include <inttypes.h>
#include "nvim/macros.h"
+#include "nvim/types.h"
typedef int32_t RgbValue;
@@ -180,6 +181,20 @@ typedef struct {
HlKind kind;
int id1;
int id2;
+ int winid;
} HlEntry;
+typedef struct {
+ int ns_id;
+ int syn_id;
+} ColorKey;
+
+typedef struct {
+ int attr_id;
+ int link_id;
+ int version;
+} ColorItem;
+#define COLOR_ITEM_INITIALIZER { .attr_id = -1, .link_id = -1, .version = -1 }
+
+
#endif // NVIM_HIGHLIGHT_DEFS_H
diff --git a/src/nvim/if_cscope.c b/src/nvim/if_cscope.c
index 2af09f10cb..2dad8fb781 100644
--- a/src/nvim/if_cscope.c
+++ b/src/nvim/if_cscope.c
@@ -459,7 +459,7 @@ staterr:
int i;
// if filename is a directory, append the cscope database name to it
- if ((file_info.stat.st_mode & S_IFMT) == S_IFDIR) {
+ if (S_ISDIR(file_info.stat.st_mode)) {
fname2 = (char *)xmalloc(strlen(CSCOPE_DBFILE) + strlen(fname) + 2);
while (fname[strlen(fname)-1] == '/'
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index fb277b25fd..9e6693afdf 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -295,13 +295,18 @@ int set_indent(int size, int flags)
// Replace the line (unless undo fails).
if (!(flags & SIN_UNDO) || (u_savesub(curwin->w_cursor.lnum) == OK)) {
+ const colnr_T old_offset = (colnr_T)(p - oldline);
+ const colnr_T new_offset = (colnr_T)(s - newline);
+
+ // this may free "newline"
ml_replace(curwin->w_cursor.lnum, newline, false);
if (!(flags & SIN_NOMARK)) {
- extmark_splice(curbuf,
- (int)curwin->w_cursor.lnum-1, skipcols,
- 0, (int)(p-oldline) - skipcols,
- 0, (int)(s-newline) - skipcols,
- kExtmarkUndo);
+ extmark_splice_cols(curbuf,
+ (int)curwin->w_cursor.lnum-1,
+ skipcols,
+ old_offset - skipcols,
+ new_offset - skipcols,
+ kExtmarkUndo);
}
if (flags & SIN_CHANGED) {
@@ -310,15 +315,14 @@ int set_indent(int size, int flags)
// Correct saved cursor position if it is in this line.
if (saved_cursor.lnum == curwin->w_cursor.lnum) {
- if (saved_cursor.col >= (colnr_T)(p - oldline)) {
+ if (saved_cursor.col >= old_offset) {
// Cursor was after the indent, adjust for the number of
// bytes added/removed.
- saved_cursor.col += ind_len - (colnr_T)(p - oldline);
-
- } else if (saved_cursor.col >= (colnr_T)(s - newline)) {
+ saved_cursor.col += ind_len - old_offset;
+ } else if (saved_cursor.col >= new_offset) {
// Cursor was in the indent, and is now after it, put it back
// at the start of the indent (replacing spaces with TAB).
- saved_cursor.col = (colnr_T)(s - newline);
+ saved_cursor.col = new_offset;
}
}
retval = true;
diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c
index bb443161ef..9298e57411 100644
--- a/src/nvim/indent_c.c
+++ b/src/nvim/indent_c.c
@@ -1676,6 +1676,9 @@ void parse_cino(buf_T *buf)
// Handle C++ extern "C" or "C++"
buf->b_ind_cpp_extern_c = 0;
+ // Handle C #pragma directives
+ buf->b_ind_pragma = 0;
+
for (p = buf->b_p_cino; *p; ) {
l = p++;
if (*p == '-') {
@@ -1747,6 +1750,7 @@ void parse_cino(buf_T *buf)
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;
+ case 'P': buf->b_ind_pragma = n; break;
}
if (*p == ',')
++p;
@@ -1858,12 +1862,14 @@ int get_c_indent(void)
goto laterend;
}
- /*
- * #defines and so on always go at the left when included in 'cinkeys'.
- */
+ // #defines and so on go at the left when included in 'cinkeys',
+ // exluding pragmas when customized in 'cinoptions'
if (*theline == '#' && (*linecopy == '#' || in_cinkeys('#', ' ', true))) {
- amount = curbuf->b_ind_hash_comment;
- goto theend;
+ const char_u *const directive = skipwhite(theline + 1);
+ if (curbuf->b_ind_pragma == 0 || STRNCMP(directive, "pragma", 6) != 0) {
+ amount = curbuf->b_ind_hash_comment;
+ goto theend;
+ }
}
/*
diff --git a/src/nvim/keymap.c b/src/nvim/keymap.c
index a553110552..2b6f022d9d 100644
--- a/src/nvim/keymap.c
+++ b/src/nvim/keymap.c
@@ -530,13 +530,24 @@ unsigned int trans_special(const char_u **srcp, const size_t src_len,
{
int modifiers = 0;
int key;
- unsigned int dlen = 0;
key = find_special_key(srcp, src_len, &modifiers, keycode, false, in_string);
if (key == 0) {
return 0;
}
+ return special_to_buf(key, modifiers, keycode, dst);
+}
+
+/// Put the character sequence for "key" with "modifiers" into "dst" and return
+/// the resulting length.
+/// When "keycode" is TRUE prefer key code, e.g. K_DEL instead of DEL.
+/// The sequence is not NUL terminated.
+/// This is how characters in a string are encoded.
+unsigned int special_to_buf(int key, int modifiers, bool keycode, char_u *dst)
+{
+ unsigned int dlen = 0;
+
// Put the appropriate modifier in a string.
if (modifiers != 0) {
dst[dlen++] = K_SPECIAL;
diff --git a/src/nvim/log.h b/src/nvim/log.h
index 17ff095473..f2e74df031 100644
--- a/src/nvim/log.h
+++ b/src/nvim/log.h
@@ -5,6 +5,17 @@
#include <stdbool.h>
#include "auto/config.h"
+#include "nvim/macros.h"
+
+// USDT probes. Example invokation:
+// NVIM_PROBE(nvim_foo_bar, 1, string.data);
+#if defined(HAVE_SYS_SDT_H)
+#include <sys/sdt.h> // NOLINT
+#define NVIM_PROBE(name, n, ...) STAP_PROBE##n(neovim, name, __VA_ARGS__)
+#else
+#define NVIM_PROBE(name, n, ...)
+#endif
+
#define DEBUG_LOG_LEVEL 0
#define INFO_LOG_LEVEL 1
@@ -68,6 +79,10 @@
# define LOG_CALLSTACK_TO_FILE(fp) log_callstack_to_file(fp, __func__, __LINE__)
#endif
+#if NVIM_HAS_INCLUDE("sanitizer/asan_interface.h")
+# include "sanitizer/asan_interface.h"
+#endif
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "log.h.generated.h"
#endif
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 32e804d213..030df69caa 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -1249,6 +1249,13 @@ type_error:
return ret;
}
+LuaRef nlua_pop_LuaRef(lua_State *const lstate, Error *err)
+{
+ LuaRef rv = nlua_ref(lstate, -1);
+ lua_pop(lstate, 1);
+ return rv;
+}
+
#define GENERATE_INDEX_FUNCTION(type) \
type nlua_pop_##type(lua_State *lstate, Error *err) \
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT \
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index 0d5622f1e7..0a3c30134b 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -299,45 +299,66 @@ static int nlua_wait(lua_State *lstate)
return luaL_error(lstate, "timeout must be > 0");
}
- // Check if condition can be called.
- bool is_function = (lua_type(lstate, 2) == LUA_TFUNCTION);
+ int lua_top = lua_gettop(lstate);
- // Check if condition is callable table
- if (!is_function && luaL_getmetafield(lstate, 2, "__call") != 0) {
- is_function = (lua_type(lstate, -1) == LUA_TFUNCTION);
- lua_pop(lstate, 1);
- }
+ // Check if condition can be called.
+ bool is_function = false;
+ if (lua_top >= 2 && !lua_isnil(lstate, 2)) {
+ is_function = (lua_type(lstate, 2) == LUA_TFUNCTION);
+
+ // Check if condition is callable table
+ if (!is_function && luaL_getmetafield(lstate, 2, "__call") != 0) {
+ is_function = (lua_type(lstate, -1) == LUA_TFUNCTION);
+ lua_pop(lstate, 1);
+ }
- if (!is_function) {
- lua_pushliteral(lstate, "vim.wait: condition must be a function");
- return lua_error(lstate);
+ if (!is_function) {
+ lua_pushliteral(
+ lstate,
+ "vim.wait: if passed, condition must be a function");
+ return lua_error(lstate);
+ }
}
intptr_t interval = 200;
- if (lua_gettop(lstate) >= 3) {
+ if (lua_top >= 3 && !lua_isnil(lstate, 3)) {
interval = luaL_checkinteger(lstate, 3);
if (interval < 0) {
return luaL_error(lstate, "interval must be > 0");
}
}
+ bool fast_only = false;
+ if (lua_top >= 4) {
+ fast_only = lua_toboolean(lstate, 4);
+ }
+
+ MultiQueue *loop_events = fast_only || in_fast_callback > 0
+ ? main_loop.fast_events : main_loop.events;
+
TimeWatcher *tw = xmalloc(sizeof(TimeWatcher));
// Start dummy timer.
time_watcher_init(&main_loop, tw, NULL);
- tw->events = main_loop.events;
+ tw->events = loop_events;
tw->blockable = true;
- time_watcher_start(tw, dummy_timer_due_cb,
- (uint64_t)interval, (uint64_t)interval);
+ time_watcher_start(
+ tw,
+ dummy_timer_due_cb,
+ (uint64_t)interval,
+ (uint64_t)interval);
int pcall_status = 0;
bool callback_result = false;
LOOP_PROCESS_EVENTS_UNTIL(
&main_loop,
- main_loop.events,
+ loop_events,
(int)timeout,
- nlua_wait_condition(lstate, &pcall_status, &callback_result) || got_int);
+ is_function ? nlua_wait_condition(
+ lstate,
+ &pcall_status,
+ &callback_result) : false || got_int);
// Stop dummy timer
time_watcher_stop(tw);
@@ -467,7 +488,7 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
{
const char *code = (char *)&shared_module[0];
- if (luaL_loadbuffer(lstate, code, strlen(code), "@shared.lua")
+ if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/shared.lua")
|| lua_pcall(lstate, 0, 0, 0)) {
nlua_error(lstate, _("E5106: Error while creating shared module: %.*s"));
return 1;
@@ -475,6 +496,22 @@ static int nlua_state_init(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
}
{
+ lua_getglobal(lstate, "package"); // [package]
+ lua_getfield(lstate, -1, "loaded"); // [package, loaded]
+
+ const char *code = (char *)&inspect_module[0];
+ if (luaL_loadbuffer(lstate, code, strlen(code), "@vim/inspect.lua")
+ || lua_pcall(lstate, 0, 1, 0)) {
+ nlua_error(lstate, _("E5106: Error while creating inspect module: %.*s"));
+ return 1;
+ }
+ // [package, loaded, inspect]
+
+ lua_setfield(lstate, -2, "vim.inspect"); // [package, loaded]
+ lua_pop(lstate, 2); // []
+ }
+
+ {
const char *code = (char *)&vim_module[0];
if (luaL_loadbuffer(lstate, code, strlen(code), "@vim.lua")
|| lua_pcall(lstate, 0, 0, 0)) {
@@ -528,14 +565,6 @@ static lua_State *nlua_enter(void)
// stack: (empty)
lua_getglobal(lstate, "vim");
// stack: vim
- lua_getfield(lstate, -1, "_update_package_paths");
- // stack: vim, vim._update_package_paths
- if (lua_pcall(lstate, 0, 0, 0)) {
- // stack: vim, error
- nlua_error(lstate, _("E5117: Error while updating package paths: %.*s"));
- // stack: vim
- }
- // stack: vim
lua_pop(lstate, 1);
// stack: (empty)
last_p_rtp = (const void *)p_rtp;
@@ -837,7 +866,7 @@ void nlua_unref(lua_State *lstate, LuaRef ref)
}
}
-void executor_free_luaref(LuaRef ref)
+void api_free_luaref(LuaRef ref)
{
lua_State *const lstate = nlua_enter();
nlua_unref(lstate, ref);
@@ -862,6 +891,17 @@ LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref)
return new_ref;
}
+LuaRef api_new_luaref(LuaRef original_ref)
+{
+ if (original_ref == LUA_NOREF) {
+ return LUA_NOREF;
+ }
+
+ lua_State *const lstate = nlua_enter();
+ return nlua_newref(lstate, original_ref);
+}
+
+
/// Evaluate lua string
///
/// Used for luaeval().
@@ -871,8 +911,8 @@ LuaRef nlua_newref(lua_State *lstate, LuaRef original_ref)
/// @param[out] ret_tv Location where result will be saved.
///
/// @return Result of the execution.
-void executor_eval_lua(const String str, typval_T *const arg,
- typval_T *const ret_tv)
+void nlua_typval_eval(const String str, typval_T *const arg,
+ typval_T *const ret_tv)
FUNC_ATTR_NONNULL_ALL
{
#define EVALHEADER "local _A=select(1,...) return ("
@@ -894,8 +934,8 @@ void executor_eval_lua(const String str, typval_T *const arg,
}
}
-void executor_call_lua(const char *str, size_t len, typval_T *const args,
- int argcount, typval_T *ret_tv)
+void nlua_typval_call(const char *str, size_t len, typval_T *const args,
+ int argcount, typval_T *ret_tv)
FUNC_ATTR_NONNULL_ALL
{
#define CALLHEADER "return "
@@ -925,7 +965,7 @@ static void typval_exec_lua(const char *lcmd, size_t lcmd_len, const char *name,
typval_T *const args, int argcount, bool special,
typval_T *ret_tv)
{
- if (check_restricted() || check_secure()) {
+ if (check_secure()) {
if (ret_tv) {
ret_tv->v_type = VAR_NUMBER;
ret_tv->vval.v_number = 0;
@@ -998,14 +1038,14 @@ int typval_exec_lua_callable(
/// Execute Lua string
///
-/// Used for nvim_exec_lua().
+/// Used for nvim_exec_lua() and internally to execute a lua string.
///
/// @param[in] str String to execute.
/// @param[in] args array of ... args
/// @param[out] err Location where error will be saved.
///
/// @return Return value of the execution.
-Object executor_exec_lua_api(const String str, const Array args, Error *err)
+Object nlua_exec(const String str, const Array args, Error *err)
{
lua_State *const lstate = nlua_enter();
@@ -1032,17 +1072,30 @@ Object executor_exec_lua_api(const String str, const Array args, Error *err)
return nlua_pop_Object(lstate, false, err);
}
-Object executor_exec_lua_cb(LuaRef ref, const char *name, Array args,
- bool retval, Error *err)
+/// call a LuaRef as a function (or table with __call metamethod)
+///
+/// @param ref the reference to call (not consumed)
+/// @param name if non-NULL, sent to callback as first arg
+/// if NULL, only args are used
+/// @param retval if true, convert return value to Object
+/// if false, discard return value
+/// @param err Error details, if any (if NULL, errors are echoed)
+/// @return Return value of function, if retval was set. Otherwise NIL.
+Object nlua_call_ref(LuaRef ref, const char *name, Array args,
+ bool retval, Error *err)
{
lua_State *const lstate = nlua_enter();
nlua_pushref(lstate, ref);
- lua_pushstring(lstate, name);
+ int nargs = (int)args.size;
+ if (name != NULL) {
+ lua_pushstring(lstate, name);
+ nargs++;
+ }
for (size_t i = 0; i < args.size; i++) {
nlua_push_Object(lstate, args.items[i], false);
}
- if (lua_pcall(lstate, (int)args.size+1, retval ? 1 : 0, 0)) {
+ if (lua_pcall(lstate, nargs, retval ? 1 : 0, 0)) {
// if err is passed, the caller will deal with the error.
if (err) {
size_t len;
@@ -1457,3 +1510,40 @@ void nlua_free_typval_dict(dict_T *const d)
d->lua_table_ref = LUA_NOREF;
}
}
+
+void nlua_execute_log_keystroke(int c)
+{
+ char_u buf[NUMBUFLEN];
+ size_t buf_len = special_to_buf(c, mod_mask, false, buf);
+
+ lua_State *const lstate = nlua_enter();
+
+#ifndef NDEBUG
+ int top = lua_gettop(lstate);
+#endif
+
+ // [ vim ]
+ lua_getglobal(lstate, "vim");
+
+ // [ vim, vim._log_keystroke ]
+ lua_getfield(lstate, -1, "_log_keystroke");
+ luaL_checktype(lstate, -1, LUA_TFUNCTION);
+
+ // [ vim, vim._log_keystroke, buf ]
+ lua_pushlstring(lstate, (const char *)buf, buf_len);
+
+ if (lua_pcall(lstate, 1, 0, 0)) {
+ nlua_error(
+ lstate,
+ _("Error executing vim.log_keystroke lua callback: %.*s"));
+ }
+
+ // [ vim ]
+ lua_pop(lstate, 1);
+
+#ifndef NDEBUG
+ // [ ]
+ assert(top == lua_gettop(lstate));
+#endif
+}
+
diff --git a/src/nvim/lua/executor.h b/src/nvim/lua/executor.h
index 6599b44584..1d7a15d9aa 100644
--- a/src/nvim/lua/executor.h
+++ b/src/nvim/lua/executor.h
@@ -24,6 +24,15 @@ EXTERN LuaRef nlua_empty_dict_ref INIT(= LUA_NOREF);
memcpy(&err_->msg[0], s, sizeof(s)); \
} while (0)
+#define NLUA_CLEAR_REF(x) \
+ do { \
+ /* Take the address to avoid double evaluation. #1375 */ \
+ if ((x) != LUA_NOREF) { \
+ api_free_luaref(x); \
+ (x) = LUA_NOREF; \
+ } \
+ } while (0)
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/executor.h.generated.h"
#endif
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index 138031237e..0515bbfe53 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -23,11 +23,6 @@
#include "nvim/buffer.h"
typedef struct {
- TSParser *parser;
- TSTree *tree; // internal tree, used for editing/reparsing
-} TSLua_parser;
-
-typedef struct {
TSQueryCursor *cursor;
int predicated_match;
} TSLua_cursor;
@@ -39,10 +34,9 @@ typedef struct {
static struct luaL_Reg parser_meta[] = {
{ "__gc", parser_gc },
{ "__tostring", parser_tostring },
- { "parse_buf", parser_parse_buf },
- { "edit", parser_edit },
- { "tree", parser_tree },
+ { "parse", parser_parse },
{ "set_included_ranges", parser_set_ranges },
+ { "included_ranges", parser_get_ranges },
{ NULL, NULL }
};
@@ -50,6 +44,8 @@ static struct luaL_Reg tree_meta[] = {
{ "__gc", tree_gc },
{ "__tostring", tree_tostring },
{ "root", tree_root },
+ { "edit", tree_edit },
+ { "copy", tree_copy },
{ NULL, NULL }
};
@@ -57,11 +53,13 @@ static struct luaL_Reg node_meta[] = {
{ "__tostring", node_tostring },
{ "__eq", node_eq },
{ "__len", node_child_count },
+ { "id", node_id },
{ "range", node_range },
{ "start", node_start },
{ "end_", node_end },
{ "type", node_type },
{ "symbol", node_symbol },
+ { "field", node_field },
{ "named", node_named },
{ "missing", node_missing },
{ "has_error", node_has_error },
@@ -73,6 +71,7 @@ static struct luaL_Reg node_meta[] = {
{ "descendant_for_range", node_descendant_for_range },
{ "named_descendant_for_range", node_named_descendant_for_range },
{ "parent", node_parent },
+ { "iter_children", node_iter_children },
{ "_rawquery", node_rawquery },
{ NULL, NULL }
};
@@ -84,12 +83,17 @@ static struct luaL_Reg query_meta[] = {
{ NULL, NULL }
};
-// cursor is not exposed, but still needs garbage collection
+// cursors are not exposed, but still needs garbage collection
static struct luaL_Reg querycursor_meta[] = {
{ "__gc", querycursor_gc },
{ NULL, NULL }
};
+static struct luaL_Reg treecursor_meta[] = {
+ { "__gc", treecursor_gc },
+ { NULL, NULL }
+};
+
static PMap(cstr_t) *langs;
static void build_meta(lua_State *L, const char *tname, const luaL_Reg *meta)
@@ -116,6 +120,7 @@ void tslua_init(lua_State *L)
build_meta(L, "treesitter_node", node_meta);
build_meta(L, "treesitter_query", query_meta);
build_meta(L, "treesitter_querycursor", querycursor_meta);
+ build_meta(L, "treesitter_treecursor", treecursor_meta);
}
int tslua_has_language(lua_State *L)
@@ -166,6 +171,15 @@ int tslua_add_language(lua_State *L)
return luaL_error(L, "Failed to load parser: internal error");
}
+ uint32_t lang_version = ts_language_version(lang);
+ if (lang_version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION
+ || lang_version > TREE_SITTER_LANGUAGE_VERSION) {
+ return luaL_error(
+ L,
+ "ABI version mismatch : expected %d, found %d",
+ TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION, lang_version);
+ }
+
pmap_put(cstr_t)(langs, xstrdup(lang_name), lang);
lua_pushboolean(L, true);
@@ -228,34 +242,32 @@ int tslua_push_parser(lua_State *L)
return luaL_error(L, "no such language: %s", lang_name);
}
- TSParser *parser = ts_parser_new();
- ts_parser_set_language(parser, lang);
- TSLua_parser *p = lua_newuserdata(L, sizeof(TSLua_parser)); // [udata]
- p->parser = parser;
- p->tree = NULL;
+ TSParser **parser = lua_newuserdata(L, sizeof(TSParser *));
+ *parser = ts_parser_new();
+
+ if (!ts_parser_set_language(*parser, lang)) {
+ ts_parser_delete(*parser);
+ return luaL_error(L, "Failed to load language : %s", lang_name);
+ }
lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_parser"); // [udata, meta]
lua_setmetatable(L, -2); // [udata]
return 1;
}
-static TSLua_parser *parser_check(lua_State *L)
+static TSParser ** parser_check(lua_State *L, uint16_t index)
{
- return luaL_checkudata(L, 1, "treesitter_parser");
+ return luaL_checkudata(L, index, "treesitter_parser");
}
static int parser_gc(lua_State *L)
{
- TSLua_parser *p = parser_check(L);
+ TSParser **p = parser_check(L, 1);
if (!p) {
return 0;
}
- ts_parser_delete(p->parser);
- if (p->tree) {
- ts_tree_delete(p->tree);
- }
-
+ ts_parser_delete(*p);
return 0;
}
@@ -278,96 +290,133 @@ static const char *input_cb(void *payload, uint32_t byte_index,
}
char_u *line = ml_get_buf(bp, position.row+1, false);
size_t len = STRLEN(line);
-
if (position.column > len) {
*bytes_read = 0;
- } else {
- size_t tocopy = MIN(len-position.column, BUFSIZE);
-
- memcpy(buf, line+position.column, tocopy);
- // Translate embedded \n to NUL
- memchrsub(buf, '\n', '\0', tocopy);
- *bytes_read = (uint32_t)tocopy;
- if (tocopy < BUFSIZE) {
- // now add the final \n. If it didn't fit, input_cb will be called again
- // on the same line with advanced column.
- buf[tocopy] = '\n';
- (*bytes_read)++;
- }
+ return "";
+ }
+ size_t tocopy = MIN(len-position.column, BUFSIZE);
+
+ memcpy(buf, line+position.column, tocopy);
+ // Translate embedded \n to NUL
+ memchrsub(buf, '\n', '\0', tocopy);
+ *bytes_read = (uint32_t)tocopy;
+ if (tocopy < BUFSIZE) {
+ // now add the final \n. If it didn't fit, input_cb will be called again
+ // on the same line with advanced column.
+ buf[tocopy] = '\n';
+ (*bytes_read)++;
}
return buf;
#undef BUFSIZE
}
-static int parser_parse_buf(lua_State *L)
+static void push_ranges(lua_State *L,
+ const TSRange *ranges,
+ const unsigned int length)
{
- TSLua_parser *p = parser_check(L);
- if (!p) {
+ lua_createtable(L, length, 0);
+ for (size_t i = 0; i < length; i++) {
+ lua_createtable(L, 4, 0);
+ lua_pushinteger(L, ranges[i].start_point.row);
+ lua_rawseti(L, -2, 1);
+ lua_pushinteger(L, ranges[i].start_point.column);
+ lua_rawseti(L, -2, 2);
+ lua_pushinteger(L, ranges[i].end_point.row);
+ lua_rawseti(L, -2, 3);
+ lua_pushinteger(L, ranges[i].end_point.column);
+ lua_rawseti(L, -2, 4);
+
+ lua_rawseti(L, -2, i+1);
+ }
+}
+
+static int parser_parse(lua_State *L)
+{
+ TSParser **p = parser_check(L, 1);
+ if (!p || !(*p)) {
return 0;
}
- long bufnr = lua_tointeger(L, 2);
- buf_T *buf = handle_get_buffer(bufnr);
+ TSTree *old_tree = NULL;
+ if (!lua_isnil(L, 2)) {
+ TSTree **tmp = tree_check(L, 2);
+ old_tree = tmp ? *tmp : NULL;
+ }
- if (!buf) {
- return luaL_error(L, "invalid buffer handle: %d", bufnr);
+ TSTree *new_tree = NULL;
+ size_t len;
+ const char *str;
+ long bufnr;
+ buf_T *buf;
+ TSInput input;
+
+ // This switch is necessary because of the behavior of lua_isstring, that
+ // consider numbers as strings...
+ switch (lua_type(L, 3)) {
+ case LUA_TSTRING:
+ str = lua_tolstring(L, 3, &len);
+ new_tree = ts_parser_parse_string(*p, old_tree, str, len);
+ break;
+
+ case LUA_TNUMBER:
+ bufnr = lua_tointeger(L, 3);
+ buf = handle_get_buffer(bufnr);
+
+ if (!buf) {
+ return luaL_error(L, "invalid buffer handle: %d", bufnr);
+ }
+
+ input = (TSInput){ (void *)buf, input_cb, TSInputEncodingUTF8 };
+ new_tree = ts_parser_parse(*p, old_tree, input);
+
+ break;
+
+ default:
+ return luaL_error(L, "invalid argument to parser:parse()");
}
- TSInput input = { (void *)buf, input_cb, TSInputEncodingUTF8 };
- TSTree *new_tree = ts_parser_parse(p->parser, p->tree, input);
+ // Sometimes parsing fails (timeout, or wrong parser ABI)
+ // In those case, just return an error.
+ if (!new_tree) {
+ return luaL_error(L, "An error occured when parsing.");
+ }
+ // The new tree will be pushed to the stack, without copy, owwership is now to
+ // the lua GC.
+ // Old tree is still owned by the lua GC.
uint32_t n_ranges = 0;
- TSRange *changed = p->tree ? ts_tree_get_changed_ranges(p->tree, new_tree,
- &n_ranges) : NULL;
- if (p->tree) {
- ts_tree_delete(p->tree);
- }
- p->tree = new_tree;
+ TSRange *changed = old_tree ? ts_tree_get_changed_ranges(
+ old_tree, new_tree, &n_ranges) : NULL;
- tslua_push_tree(L, p->tree);
+ tslua_push_tree(L, new_tree, false); // [tree]
- lua_createtable(L, n_ranges, 0);
- for (size_t i = 0; i < n_ranges; i++) {
- lua_createtable(L, 4, 0);
- lua_pushinteger(L, changed[i].start_point.row);
- lua_rawseti(L, -2, 1);
- lua_pushinteger(L, changed[i].start_point.column);
- lua_rawseti(L, -2, 2);
- lua_pushinteger(L, changed[i].end_point.row);
- lua_rawseti(L, -2, 3);
- lua_pushinteger(L, changed[i].end_point.column);
- lua_rawseti(L, -2, 4);
+ push_ranges(L, changed, n_ranges); // [tree, ranges]
- lua_rawseti(L, -2, i+1);
- }
xfree(changed);
return 2;
}
-static int parser_tree(lua_State *L)
+static int tree_copy(lua_State *L)
{
- TSLua_parser *p = parser_check(L);
- if (!p) {
+ TSTree **tree = tree_check(L, 1);
+ if (!(*tree)) {
return 0;
}
- tslua_push_tree(L, p->tree);
+ tslua_push_tree(L, *tree, true); // [tree]
+
return 1;
}
-static int parser_edit(lua_State *L)
+static int tree_edit(lua_State *L)
{
if (lua_gettop(L) < 10) {
- lua_pushstring(L, "not enough args to parser:edit()");
+ lua_pushstring(L, "not enough args to tree:edit()");
return lua_error(L);
}
- TSLua_parser *p = parser_check(L);
- if (!p) {
- return 0;
- }
-
- if (!p->tree) {
+ TSTree **tree = tree_check(L, 1);
+ if (!(*tree)) {
return 0;
}
@@ -381,7 +430,7 @@ static int parser_edit(lua_State *L)
TSInputEdit edit = { start_byte, old_end_byte, new_end_byte,
start_point, old_end_point, new_end_point };
- ts_tree_edit(p->tree, &edit);
+ ts_tree_edit(*tree, &edit);
return 0;
}
@@ -394,8 +443,8 @@ static int parser_set_ranges(lua_State *L)
"not enough args to parser:set_included_ranges()");
}
- TSLua_parser *p = parser_check(L);
- if (!p || !p->tree) {
+ TSParser **p = parser_check(L, 1);
+ if (!p) {
return 0;
}
@@ -431,26 +480,46 @@ static int parser_set_ranges(lua_State *L)
}
// This memcpies ranges, thus we can free it afterwards
- ts_parser_set_included_ranges(p->parser, ranges, tbl_len);
+ ts_parser_set_included_ranges(*p, ranges, tbl_len);
xfree(ranges);
return 0;
}
+static int parser_get_ranges(lua_State *L)
+{
+ TSParser **p = parser_check(L, 1);
+ if (!p) {
+ return 0;
+ }
+
+ unsigned int len;
+ const TSRange *ranges = ts_parser_included_ranges(*p, &len);
+
+ push_ranges(L, ranges, len);
+ return 1;
+}
+
// Tree methods
/// push tree interface on lua stack.
///
/// This makes a copy of the tree, so ownership of the argument is unaffected.
-void tslua_push_tree(lua_State *L, TSTree *tree)
+void tslua_push_tree(lua_State *L, TSTree *tree, bool do_copy)
{
if (tree == NULL) {
lua_pushnil(L);
return;
}
TSTree **ud = lua_newuserdata(L, sizeof(TSTree *)); // [udata]
- *ud = ts_tree_copy(tree);
+
+ if (do_copy) {
+ *ud = ts_tree_copy(tree);
+ } else {
+ *ud = tree;
+ }
+
lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_tree"); // [udata, meta]
lua_setmetatable(L, -2); // [udata]
@@ -463,20 +532,20 @@ void tslua_push_tree(lua_State *L, TSTree *tree)
lua_setfenv(L, -2); // [udata]
}
-static TSTree *tree_check(lua_State *L)
+static TSTree **tree_check(lua_State *L, uint16_t index)
{
- TSTree **ud = luaL_checkudata(L, 1, "treesitter_tree");
- return *ud;
+ TSTree **ud = luaL_checkudata(L, index, "treesitter_tree");
+ return ud;
}
static int tree_gc(lua_State *L)
{
- TSTree *tree = tree_check(L);
+ TSTree **tree = tree_check(L, 1);
if (!tree) {
return 0;
}
- ts_tree_delete(tree);
+ ts_tree_delete(*tree);
return 0;
}
@@ -488,11 +557,11 @@ static int tree_tostring(lua_State *L)
static int tree_root(lua_State *L)
{
- TSTree *tree = tree_check(L);
+ TSTree **tree = tree_check(L, 1);
if (!tree) {
return 0;
}
- TSNode root = ts_tree_root_node(tree);
+ TSNode root = ts_tree_root_node(*tree);
push_node(L, root, 1);
return 1;
}
@@ -560,6 +629,17 @@ static int node_eq(lua_State *L)
return 1;
}
+static int node_id(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+
+ lua_pushlstring(L, (const char *)&node.id, sizeof node.id);
+ return 1;
+}
+
static int node_range(lua_State *L)
{
TSNode node;
@@ -646,6 +726,34 @@ static int node_symbol(lua_State *L)
return 1;
}
+static int node_field(lua_State *L)
+{
+ TSNode node;
+ if (!node_check(L, 1, &node)) {
+ return 0;
+ }
+
+ size_t name_len;
+ const char *field_name = luaL_checklstring(L, 2, &name_len);
+
+ TSTreeCursor cursor = ts_tree_cursor_new(node);
+
+ lua_newtable(L); // [table]
+ unsigned int curr_index = 0;
+
+ if (ts_tree_cursor_goto_first_child(&cursor)) {
+ do {
+ if (!STRCMP(field_name, ts_tree_cursor_current_field_name(&cursor))) {
+ push_node(L, ts_tree_cursor_current_node(&cursor), 1); // [table, node]
+ lua_rawseti(L, -2, ++curr_index);
+ }
+ } while (ts_tree_cursor_goto_next_sibling(&cursor));
+ }
+
+ ts_tree_cursor_delete(&cursor);
+ return 1;
+}
+
static int node_named(lua_State *L)
{
TSNode node;
@@ -746,6 +854,74 @@ static int node_named_descendant_for_range(lua_State *L)
return 1;
}
+static int node_next_child(lua_State *L)
+{
+ TSTreeCursor *ud = luaL_checkudata(
+ L, lua_upvalueindex(1), "treesitter_treecursor");
+ if (!ud) {
+ return 0;
+ }
+
+ TSNode source;
+ if (!node_check(L, lua_upvalueindex(2), &source)) {
+ return 0;
+ }
+
+ // First call should return first child
+ if (ts_node_eq(source, ts_tree_cursor_current_node(ud))) {
+ if (ts_tree_cursor_goto_first_child(ud)) {
+ goto push;
+ } else {
+ goto end;
+ }
+ }
+
+ if (ts_tree_cursor_goto_next_sibling(ud)) {
+push:
+ push_node(
+ L,
+ ts_tree_cursor_current_node(ud),
+ lua_upvalueindex(2)); // [node]
+
+ const char * field = ts_tree_cursor_current_field_name(ud);
+
+ if (field != NULL) {
+ lua_pushstring(L, ts_tree_cursor_current_field_name(ud));
+ } else {
+ lua_pushnil(L);
+ } // [node, field_name_or_nil]
+ return 2;
+ }
+
+end:
+ return 0;
+}
+
+static int node_iter_children(lua_State *L)
+{
+ TSNode source;
+ if (!node_check(L, 1, &source)) {
+ return 0;
+ }
+
+ TSTreeCursor *ud = lua_newuserdata(L, sizeof(TSTreeCursor)); // [udata]
+ *ud = ts_tree_cursor_new(source);
+
+ lua_getfield(L, LUA_REGISTRYINDEX, "treesitter_treecursor"); // [udata, mt]
+ lua_setmetatable(L, -2); // [udata]
+ lua_pushvalue(L, 1); // [udata, source_node]
+ lua_pushcclosure(L, node_next_child, 2);
+
+ return 1;
+}
+
+static int treecursor_gc(lua_State *L)
+{
+ TSTreeCursor *ud = luaL_checkudata(L, 1, "treesitter_treecursor");
+ ts_tree_cursor_delete(ud);
+ return 0;
+}
+
static int node_parent(lua_State *L)
{
TSNode node;
diff --git a/src/nvim/lua/vim.lua b/src/nvim/lua/vim.lua
index 820b237c4f..2c7ab46ffe 100644
--- a/src/nvim/lua/vim.lua
+++ b/src/nvim/lua/vim.lua
@@ -36,6 +36,9 @@
local vim = vim
assert(vim)
+vim.inspect = package.loaded['vim.inspect']
+assert(vim.inspect)
+
-- Internal-only until comments in #8107 are addressed.
-- Returns:
-- {errcode}, {output}
@@ -92,67 +95,44 @@ function vim._os_proc_children(ppid)
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
--- are no longer in &runtimepath.
-local last_nvim_paths = {}
-function vim._update_package_paths()
- local cur_nvim_paths = {}
- local rtps = vim.api.nvim_list_runtime_paths()
- local sep = package.config:sub(1, 1)
- for _, key in ipairs({'path', 'cpath'}) do
- local orig_str = package[key] .. ';'
- local pathtrails_ordered = {}
- local orig = {}
- -- Note: ignores trailing item without trailing `;`. Not using something
- -- simpler in order to preserve empty items (stand for default path).
- for s in orig_str:gmatch('[^;]*;') do
- s = s:sub(1, -2) -- Strip trailing semicolon
- orig[#orig + 1] = s
- end
- if key == 'path' then
- -- /?.lua and /?/init.lua
- pathtrails_ordered = {sep .. '?.lua', sep .. '?' .. sep .. 'init.lua'}
- else
- local pathtrails = {}
- for _, s in ipairs(orig) do
- -- Find out path patterns. pathtrail should contain something like
- -- /?.so, \?.dll. This allows not to bother determining what correct
- -- suffixes are.
- local pathtrail = s:match('[/\\][^/\\]*%?.*$')
- if pathtrail and not pathtrails[pathtrail] then
- pathtrails[pathtrail] = true
- pathtrails_ordered[#pathtrails_ordered + 1] = pathtrail
- end
- end
- end
- local new = {}
- for _, rtp in ipairs(rtps) do
- if not rtp:match(';') then
- for _, pathtrail in pairs(pathtrails_ordered) do
- local new_path = rtp .. sep .. 'lua' .. pathtrail
- -- Always keep paths from &runtimepath at the start:
- -- append them here disregarding orig possibly containing one of them.
- new[#new + 1] = new_path
- cur_nvim_paths[new_path] = true
- end
- end
+local pathtrails = {}
+vim._so_trails = {}
+for s in (package.cpath..';'):gmatch('[^;]*;') do
+ s = s:sub(1, -2) -- Strip trailing semicolon
+ -- Find out path patterns. pathtrail should contain something like
+ -- /?.so, \?.dll. This allows not to bother determining what correct
+ -- suffixes are.
+ local pathtrail = s:match('[/\\][^/\\]*%?.*$')
+ if pathtrail and not pathtrails[pathtrail] then
+ pathtrails[pathtrail] = true
+ table.insert(vim._so_trails, pathtrail)
+ end
+end
+
+function vim._load_package(name)
+ local basename = name:gsub('%.', '/')
+ local paths = {"lua/"..basename..".lua", "lua/"..basename.."/init.lua"}
+ for _,path in ipairs(paths) do
+ local found = vim.api.nvim_get_runtime_file(path, false)
+ if #found > 0 then
+ return loadfile(found[1])
end
- for _, orig_path in ipairs(orig) do
- -- Handle removing obsolete paths originating from &runtimepath: such
- -- paths either belong to cur_nvim_paths and were already added above or
- -- to last_nvim_paths and should not be added at all if corresponding
- -- entry was removed from &runtimepath list.
- if not (cur_nvim_paths[orig_path] or last_nvim_paths[orig_path]) then
- new[#new + 1] = orig_path
- end
+ end
+
+ for _,trail in ipairs(vim._so_trails) do
+ local path = "lua/"..trail:gsub('?',basename)
+ local found = vim.api.nvim_get_runtime_file(path, false)
+ if #found > 0 then
+ return package.loadlib(found[1])
end
- package[key] = table.concat(new, ';')
end
- last_nvim_paths = cur_nvim_paths
+ return nil
end
+table.insert(package.loaders, 1, vim._load_package)
+
+-- TODO(ZyX-I): Create compatibility layer.
+
--- Return a human-readable representation of the given object.
---
--@see https://github.com/kikito/inspect.lua
@@ -279,10 +259,7 @@ end
-- These are for loading runtime modules lazily since they aren't available in
-- the nvim binary as specified in executor.c
local function __index(t, key)
- if key == 'inspect' then
- t.inspect = require('vim.inspect')
- return t.inspect
- elseif key == 'treesitter' then
+ if key == 'treesitter' then
t.treesitter = require('vim.treesitter')
return t.treesitter
elseif require('vim.uri')[key] ~= nil then
@@ -295,6 +272,9 @@ local function __index(t, key)
elseif key == 'highlight' then
t.highlight = require('vim.highlight')
return t.highlight
+ elseif key == 'F' then
+ t.F = require('vim.F')
+ return t.F
end
end
@@ -489,4 +469,60 @@ function vim.defer_fn(fn, timeout)
return timer
end
+local on_keystroke_callbacks = {}
+
+--- Register a lua {fn} with an {id} to be run after every keystroke.
+---
+--@param fn function: Function to call. It should take one argument, which is a string.
+--- The string will contain the literal keys typed.
+--- See |i_CTRL-V|
+---
+--- If {fn} is nil, it removes the callback for the associated {ns_id}
+--@param ns_id number? Namespace ID. If not passed or 0, will generate and return a new
+--- namespace ID from |nvim_create_namesapce()|
+---
+--@return number Namespace ID associated with {fn}
+---
+--@note {fn} will be automatically removed if an error occurs while calling.
+--- This is to prevent the annoying situation of every keystroke erroring
+--- while trying to remove a broken callback.
+--@note {fn} will not be cleared from |nvim_buf_clear_namespace()|
+--@note {fn} will receive the keystrokes after mappings have been evaluated
+function vim.register_keystroke_callback(fn, ns_id)
+ vim.validate {
+ fn = { fn, 'c', true},
+ ns_id = { ns_id, 'n', true }
+ }
+
+ if ns_id == nil or ns_id == 0 then
+ ns_id = vim.api.nvim_create_namespace('')
+ end
+
+ on_keystroke_callbacks[ns_id] = fn
+ return ns_id
+end
+
+--- Function that executes the keystroke callbacks.
+--@private
+function vim._log_keystroke(char)
+ local failed_ns_ids = {}
+ local failed_messages = {}
+ for k, v in pairs(on_keystroke_callbacks) do
+ local ok, err_msg = pcall(v, char)
+ if not ok then
+ vim.register_keystroke_callback(nil, k)
+
+ table.insert(failed_ns_ids, k)
+ table.insert(failed_messages, err_msg)
+ end
+ end
+
+ if failed_ns_ids[1] then
+ error(string.format(
+ "Error executing 'on_keystroke' with ns_ids of '%s'\n With messages: %s",
+ table.concat(failed_ns_ids, ", "),
+ table.concat(failed_messages, "\n")))
+ end
+end
+
return module
diff --git a/src/nvim/macros.h b/src/nvim/macros.h
index 3df7fa768d..07dcb4a8e8 100644
--- a/src/nvim/macros.h
+++ b/src/nvim/macros.h
@@ -1,6 +1,8 @@
#ifndef NVIM_MACROS_H
#define NVIM_MACROS_H
+#include "auto/config.h"
+
// EXTERN is only defined in main.c. That's where global variables are
// actually defined and initialized.
#ifndef EXTERN
@@ -150,6 +152,12 @@
#define STR_(x) #x
#define STR(x) STR_(x)
+#ifndef __has_include
+# define NVIM_HAS_INCLUDE(x) 0
+#else
+# define NVIM_HAS_INCLUDE __has_include
+#endif
+
#ifndef __has_attribute
# define NVIM_HAS_ATTRIBUTE(x) 0
#elif defined(__clang__) && __clang__ == 1 \
@@ -203,16 +211,33 @@
# define PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Wmissing-prototypes\"")
+# ifdef HAVE_WIMPLICIT_FALLTHROUGH_FLAG
+# define PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH \
+ _Pragma("clang diagnostic push") \
+ _Pragma("clang diagnostic ignored \"-Wimplicit-fallthrough\"")
+# else
+# define PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH \
+ _Pragma("clang diagnostic push")
+# endif
# define PRAGMA_DIAG_POP \
_Pragma("clang diagnostic pop")
#elif defined(__GNUC__)
# define PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES \
_Pragma("GCC diagnostic push") \
_Pragma("GCC diagnostic ignored \"-Wmissing-prototypes\"")
+# ifdef HAVE_WIMPLICIT_FALLTHROUGH_FLAG
+# define PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH \
+ _Pragma("GCC diagnostic push") \
+ _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\"")
+# else
+# define PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH \
+ _Pragma("GCC diagnostic push")
+# endif
# define PRAGMA_DIAG_POP \
_Pragma("GCC diagnostic pop")
#else
# define PRAGMA_DIAG_PUSH_IGNORE_MISSING_PROTOTYPES
+# define PRAGMA_DIAG_PUSH_IGNORE_IMPLICIT_FALLTHROUGH
# define PRAGMA_DIAG_POP
#endif
diff --git a/src/nvim/main.c b/src/nvim/main.c
index f79fb57eae..6ff5216a84 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -7,6 +7,8 @@
#include <string.h>
#include <stdbool.h>
+#include <lua.h>
+#include <lauxlib.h>
#include <msgpack.h>
#include "nvim/ascii.h"
@@ -21,6 +23,7 @@
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
+#include "nvim/decoration.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/getchar.h"
@@ -160,6 +163,7 @@ void early_init(mparm_T *paramp)
env_init();
fs_init();
handle_init();
+ decor_init();
eval_init(); // init global variables
init_path(argv0 ? argv0 : "nvim");
init_normal_cmds(); // Init the table of Normal mode commands.
@@ -442,7 +446,7 @@ int main(int argc, char **argv)
if (exmode_active || use_remote_ui || use_builtin_ui) {
// Don't clear the screen when starting in Ex mode, or when a UI might have
// displayed messages.
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
} else {
screenclear(); // clear screen
TIME_MSG("clearing screen");
@@ -1008,10 +1012,6 @@ static void command_line_scan(mparm_T *parmp)
want_argument = true;
break;
}
- case 'Z': { // "-Z" restricted mode
- restricted = true;
- break;
- }
case 'c': { // "-c{command}" or "-c {command}" exec command
if (argv[0][argv_idx] != NUL) {
diff --git a/src/nvim/map.c b/src/nvim/map.c
index cba39f24b3..7d97b7f13d 100644
--- a/src/nvim/map.c
+++ b/src/nvim/map.c
@@ -12,6 +12,9 @@
#include <stdbool.h>
#include <string.h>
+#include <lua.h>
+#include <lauxlib.h>
+
#include "nvim/map.h"
#include "nvim/map_defs.h"
#include "nvim/vim.h"
@@ -173,6 +176,20 @@ static inline bool HlEntry_eq(HlEntry ae1, HlEntry ae2)
return memcmp(&ae1, &ae2, sizeof(ae1)) == 0;
}
+static inline khint_t ColorKey_hash(ColorKey ae)
+{
+ const uint8_t *data = (const uint8_t *)&ae;
+ khint_t h = 0;
+ for (size_t i = 0; i < sizeof(ae); i++) {
+ h = (h << 5) - h + data[i];
+ }
+ return h;
+}
+
+static inline bool ColorKey_eq(ColorKey ae1, ColorKey ae2)
+{
+ return memcmp(&ae1, &ae2, sizeof(ae1)) == 0;
+}
MAP_IMPL(int, int, DEFAULT_INITIALIZER)
@@ -183,8 +200,7 @@ MAP_IMPL(uint64_t, ssize_t, SSIZE_INITIALIZER)
MAP_IMPL(uint64_t, uint64_t, DEFAULT_INITIALIZER)
#define EXTMARK_NS_INITIALIZER { 0, 0 }
MAP_IMPL(uint64_t, ExtmarkNs, EXTMARK_NS_INITIALIZER)
-#define KVEC_INITIALIZER { .size = 0, .capacity = 0, .items = NULL }
-#define EXTMARK_ITEM_INITIALIZER { 0, 0, 0, KVEC_INITIALIZER }
+#define EXTMARK_ITEM_INITIALIZER { 0, 0, NULL }
MAP_IMPL(uint64_t, ExtmarkItem, EXTMARK_ITEM_INITIALIZER)
MAP_IMPL(handle_T, ptr_t, DEFAULT_INITIALIZER)
#define MSGPACK_HANDLER_INITIALIZER { .fn = NULL, .fast = false }
@@ -192,6 +208,7 @@ MAP_IMPL(String, MsgpackRpcRequestHandler, MSGPACK_HANDLER_INITIALIZER)
MAP_IMPL(HlEntry, int, DEFAULT_INITIALIZER)
MAP_IMPL(String, handle_T, 0)
+MAP_IMPL(ColorKey, ColorItem, COLOR_ITEM_INITIALIZER)
/// Deletes a key:value pair from a string:pointer map, and frees the
/// storage of both key and value.
diff --git a/src/nvim/map.h b/src/nvim/map.h
index 0ad7865bf0..7bd3d31330 100644
--- a/src/nvim/map.h
+++ b/src/nvim/map.h
@@ -55,6 +55,8 @@ MAP_DECLS(String, MsgpackRpcRequestHandler)
MAP_DECLS(HlEntry, int)
MAP_DECLS(String, handle_T)
+MAP_DECLS(ColorKey, ColorItem)
+
#define map_new(T, U) map_##T##_##U##_new
#define map_free(T, U) map_##T##_##U##_free
#define map_get(T, U) map_##T##_##U##_get
@@ -73,6 +75,7 @@ MAP_DECLS(String, handle_T)
#define pmap_has(T) map_has(T, ptr_t)
#define pmap_key(T) map_key(T, ptr_t)
#define pmap_put(T) map_put(T, ptr_t)
+#define pmap_ref(T) map_ref(T, ptr_t)
/// @see pmap_del2
#define pmap_del(T) map_del(T, ptr_t)
#define pmap_clear(T) map_clear(T, ptr_t)
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index fa7c7d61c9..1ecfae57ed 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -632,6 +632,7 @@ void ex_marks(exarg_T *eap)
char_u *arg = eap->arg;
int i;
char_u *name;
+ pos_T *posp, *startp, *endp;
if (arg != NULL && *arg == NUL)
arg = NULL;
@@ -657,8 +658,18 @@ void ex_marks(exarg_T *eap)
show_one_mark(']', arg, &curbuf->b_op_end, NULL, true);
show_one_mark('^', arg, &curbuf->b_last_insert.mark, NULL, true);
show_one_mark('.', arg, &curbuf->b_last_change.mark, NULL, true);
- show_one_mark('<', arg, &curbuf->b_visual.vi_start, NULL, true);
- show_one_mark('>', arg, &curbuf->b_visual.vi_end, NULL, true);
+
+ // Show the marks as where they will jump to.
+ startp = &curbuf->b_visual.vi_start;
+ endp = &curbuf->b_visual.vi_end;
+ if ((lt(*startp, *endp) || endp->lnum == 0) && startp->lnum != 0) {
+ posp = startp;
+ } else {
+ posp = endp;
+ }
+ show_one_mark('<', arg, posp, NULL, true);
+ show_one_mark('>', arg, posp == startp ? endp : startp, NULL, true);
+
show_one_mark(-1, arg, NULL, NULL, false);
}
diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c
index 6dd452b5af..e9ea2cbba9 100644
--- a/src/nvim/marktree.c
+++ b/src/nvim/marktree.c
@@ -326,38 +326,37 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
x->n--;
// 4.
- if (adjustment) {
- if (adjustment == 1) {
- abort();
- } else { // adjustment == -1
- int ilvl = itr->lvl-1;
- mtnode_t *lnode = x;
- do {
- mtnode_t *p = lnode->parent;
- if (ilvl < 0) {
- abort();
- }
- int i = itr->s[ilvl].i;
- assert(p->ptr[i] == lnode);
- if (i > 0) {
- unrelative(p->key[i-1].pos, &intkey.pos);
- }
- lnode = p;
- ilvl--;
- } while (lnode != cur);
-
- mtkey_t deleted = cur->key[curi];
- cur->key[curi] = intkey;
- refkey(b, cur, curi);
- relative(intkey.pos, &deleted.pos);
- mtnode_t *y = cur->ptr[curi+1];
- if (deleted.pos.row || deleted.pos.col) {
- while (y) {
- for (int k = 0; k < y->n; k++) {
- unrelative(deleted.pos, &y->key[k].pos);
- }
- y = y->level ? y->ptr[0] : NULL;
+ // if (adjustment == 1) {
+ // abort();
+ // }
+ if (adjustment == -1) {
+ int ilvl = itr->lvl-1;
+ const mtnode_t *lnode = x;
+ do {
+ const mtnode_t *const p = lnode->parent;
+ if (ilvl < 0) {
+ abort();
+ }
+ const int i = itr->s[ilvl].i;
+ assert(p->ptr[i] == lnode);
+ if (i > 0) {
+ unrelative(p->key[i-1].pos, &intkey.pos);
+ }
+ lnode = p;
+ ilvl--;
+ } while (lnode != cur);
+
+ mtkey_t deleted = cur->key[curi];
+ cur->key[curi] = intkey;
+ refkey(b, cur, curi);
+ relative(intkey.pos, &deleted.pos);
+ mtnode_t *y = cur->ptr[curi+1];
+ if (deleted.pos.row || deleted.pos.col) {
+ while (y) {
+ for (int k = 0; k < y->n; k++) {
+ unrelative(deleted.pos, &y->key[k].pos);
}
+ y = y->level ? y->ptr[0] : NULL;
}
}
}
@@ -435,9 +434,10 @@ void marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
// BONUS STEP: fix the iterator, so that it points to the key afterwards
// TODO(bfredl): with "rev" should point before
- if (adjustment == 1) {
- abort();
- } else if (adjustment == -1) {
+ // if (adjustment == 1) {
+ // abort();
+ // }
+ if (adjustment == -1) {
// tricky: we stand at the deleted space in the previous leaf node.
// But the inner key is now the previous key we stole, so we need
// to skip that one as well.
diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h
index 0c73e75b2e..8a1c564a6d 100644
--- a/src/nvim/marktree.h
+++ b/src/nvim/marktree.h
@@ -2,6 +2,7 @@
#define NVIM_MARKTREE_H
#include <stdint.h>
+#include "nvim/pos.h"
#include "nvim/map.h"
#include "nvim/garray.h"
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index e67be60aa6..ec4f4cbc21 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -512,7 +512,7 @@ int utf_ptr2cells(const char_u *p)
{
int c;
- /* Need to convert to a wide character. */
+ // Need to convert to a character number.
if (*p >= 0x80) {
c = utf_ptr2char(p);
/* An illegal byte is displayed as <xx>. */
@@ -582,7 +582,7 @@ size_t mb_string2cells_len(const char_u *str, size_t size)
return clen;
}
-/// Convert a UTF-8 byte sequence to a wide character
+/// Convert a UTF-8 byte sequence to a character number.
///
/// If the sequence is illegal or truncated by a NUL then the first byte is
/// returned.
@@ -1624,6 +1624,146 @@ int utf_head_off(const char_u *base, const char_u *p)
return (int)(p - q);
}
+// Whether space is NOT allowed before/after 'c'.
+bool utf_eat_space(int cc)
+ FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ return (cc >= 0x2000 && cc <= 0x206F) // General punctuations
+ || (cc >= 0x2e00 && cc <= 0x2e7f) // Supplemental punctuations
+ || (cc >= 0x3000 && cc <= 0x303f) // CJK symbols and punctuations
+ || (cc >= 0xff01 && cc <= 0xff0f) // Full width ASCII punctuations
+ || (cc >= 0xff1a && cc <= 0xff20) // ..
+ || (cc >= 0xff3b && cc <= 0xff40) // ..
+ || (cc >= 0xff5b && cc <= 0xff65); // ..
+}
+
+// Whether line break is allowed before "cc".
+bool utf_allow_break_before(int cc)
+ FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ static const int BOL_prohibition_punct[] = {
+ '!',
+ '%',
+ ')',
+ ',',
+ ':',
+ ';',
+ '>',
+ '?',
+ ']',
+ '}',
+ 0x2019, // ’ right single quotation mark
+ 0x201d, // ” right double quotation mark
+ 0x2020, // † dagger
+ 0x2021, // ‡ double dagger
+ 0x2026, // … horizontal ellipsis
+ 0x2030, // ‰ per mille sign
+ 0x2031, // ‱ per then thousand sign
+ 0x203c, // ‼ double exclamation mark
+ 0x2047, // ⁇ double question mark
+ 0x2048, // ⁈ question exclamation mark
+ 0x2049, // ⁉ exclamation question mark
+ 0x2103, // ℃ degree celsius
+ 0x2109, // ℉ degree fahrenheit
+ 0x3001, // 、 ideographic comma
+ 0x3002, // 。 ideographic full stop
+ 0x3009, // 〉 right angle bracket
+ 0x300b, // 》 right double angle bracket
+ 0x300d, // 」 right corner bracket
+ 0x300f, // 』 right white corner bracket
+ 0x3011, // 】 right black lenticular bracket
+ 0x3015, // 〕 right tortoise shell bracket
+ 0x3017, // 〗 right white lenticular bracket
+ 0x3019, // 〙 right white tortoise shell bracket
+ 0x301b, // 〛 right white square bracket
+ 0xff01, // ! fullwidth exclamation mark
+ 0xff09, // ) fullwidth right parenthesis
+ 0xff0c, // , fullwidth comma
+ 0xff0e, // . fullwidth full stop
+ 0xff1a, // : fullwidth colon
+ 0xff1b, // ; fullwidth semicolon
+ 0xff1f, // ? fullwidth question mark
+ 0xff3d, // ] fullwidth right square bracket
+ 0xff5d, // } fullwidth right curly bracket
+ };
+
+ int first = 0;
+ int last = ARRAY_SIZE(BOL_prohibition_punct) - 1;
+
+ while (first < last) {
+ const int mid = (first + last) / 2;
+
+ if (cc == BOL_prohibition_punct[mid]) {
+ return false;
+ } else if (cc > BOL_prohibition_punct[mid]) {
+ first = mid + 1;
+ } else {
+ last = mid - 1;
+ }
+ }
+
+ return cc != BOL_prohibition_punct[first];
+}
+
+// Whether line break is allowed after "cc".
+bool utf_allow_break_after(int cc)
+ FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ static const int EOL_prohibition_punct[] = {
+ '(',
+ '<',
+ '[',
+ '`',
+ '{',
+ // 0x2014, // — em dash
+ 0x2018, // ‘ left single quotation mark
+ 0x201c, // “ left double quotation mark
+ // 0x2053, // ~ swung dash
+ 0x3008, // 〈 left angle bracket
+ 0x300a, // 《 left double angle bracket
+ 0x300c, // 「 left corner bracket
+ 0x300e, // 『 left white corner bracket
+ 0x3010, // 【 left black lenticular bracket
+ 0x3014, // 〔 left tortoise shell bracket
+ 0x3016, // 〖 left white lenticular bracket
+ 0x3018, // 〘 left white tortoise shell bracket
+ 0x301a, // 〚 left white square bracket
+ 0xff08, // ( fullwidth left parenthesis
+ 0xff3b, // [ fullwidth left square bracket
+ 0xff5b, // { fullwidth left curly bracket
+ };
+
+ int first = 0;
+ int last = ARRAY_SIZE(EOL_prohibition_punct) - 1;
+
+ while (first < last) {
+ const int mid = (first + last)/2;
+
+ if (cc == EOL_prohibition_punct[mid]) {
+ return false;
+ } else if (cc > EOL_prohibition_punct[mid]) {
+ first = mid + 1;
+ } else {
+ last = mid - 1;
+ }
+ }
+
+ return cc != EOL_prohibition_punct[first];
+}
+
+// Whether line break is allowed between "cc" and "ncc".
+bool utf_allow_break(int cc, int ncc)
+ FUNC_ATTR_CONST FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ // don't break between two-letter punctuations
+ if (cc == ncc
+ && (cc == 0x2014 // em dash
+ || cc == 0x2026)) { // horizontal ellipsis
+ return false;
+ }
+ return utf_allow_break_after(cc) && utf_allow_break_before(ncc);
+}
+
/// Copy a character, advancing the pointers
///
/// @param[in,out] fp Source of the character to copy.
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index d5788d96b3..57ed0d6588 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -257,11 +257,12 @@ int ml_open(buf_T *buf)
/*
* init fields in memline struct
*/
- buf->b_ml.ml_stack_size = 0; /* no stack yet */
- buf->b_ml.ml_stack = NULL; /* no stack yet */
- buf->b_ml.ml_stack_top = 0; /* nothing in the stack */
- buf->b_ml.ml_locked = NULL; /* no cached block */
- buf->b_ml.ml_line_lnum = 0; /* no cached line */
+ buf->b_ml.ml_stack_size = 0; // no stack yet
+ buf->b_ml.ml_stack = NULL; // no stack yet
+ buf->b_ml.ml_stack_top = 0; // nothing in the stack
+ buf->b_ml.ml_locked = NULL; // no cached block
+ buf->b_ml.ml_line_lnum = 0; // no cached line
+ buf->b_ml.ml_line_offset = 0;
buf->b_ml.ml_chunksize = NULL;
if (cmdmod.noswapfile) {
@@ -831,11 +832,12 @@ void ml_recover(bool checkext)
/*
* init fields in memline struct
*/
- buf->b_ml.ml_stack_size = 0; /* no stack yet */
- buf->b_ml.ml_stack = NULL; /* no stack yet */
- buf->b_ml.ml_stack_top = 0; /* nothing in the stack */
- buf->b_ml.ml_line_lnum = 0; /* no cached line */
- buf->b_ml.ml_locked = NULL; /* no locked block */
+ buf->b_ml.ml_stack_size = 0; // no stack yet
+ buf->b_ml.ml_stack = NULL; // no stack yet
+ buf->b_ml.ml_stack_top = 0; // nothing in the stack
+ buf->b_ml.ml_line_lnum = 0; // no cached line
+ buf->b_ml.ml_line_offset = 0;
+ buf->b_ml.ml_locked = NULL; // no locked block
buf->b_ml.ml_flags = 0;
/*
@@ -1358,7 +1360,7 @@ recover_names (
* Try finding a swap file by simply adding ".swp" to the file name.
*/
if (*dirp == NUL && file_count + num_files == 0 && fname != NULL) {
- char_u *swapname = (char_u *)modname((char *)fname_res, ".swp", TRUE);
+ char_u *swapname = (char_u *)modname((char *)fname_res, ".swp", true);
if (swapname != NULL) {
if (os_path_exists(swapname)) {
files = xmalloc(sizeof(char_u *));
@@ -1636,10 +1638,11 @@ static int recov_file_names(char_u **names, char_u *path, int prepend_dot)
// May also add the file name with a dot prepended, for swap file in same
// dir as original file.
if (prepend_dot) {
- names[num_names] = (char_u *)modname((char *)path, ".sw?", TRUE);
- if (names[num_names] == NULL)
+ names[num_names] = (char_u *)modname((char *)path, ".sw?", true);
+ if (names[num_names] == NULL) {
return num_names;
- ++num_names;
+ }
+ num_names++;
}
// Form the normal swap file name pattern by appending ".sw?".
@@ -1816,6 +1819,7 @@ ml_get_buf (
linenr_T lnum,
bool will_change // line will be changed
)
+ FUNC_ATTR_NONNULL_ALL
{
bhdr_T *hp;
DATA_BL *dp;
@@ -2403,12 +2407,13 @@ void ml_add_deleted_len_buf(buf_T *buf, char_u *ptr, ssize_t len)
if (len == -1) {
len = STRLEN(ptr);
}
- buf->deleted_bytes += len+1;
- if (buf->update_need_codepoints) {
- mb_utflen(ptr, len, &buf->deleted_codepoints,
- &buf->deleted_codeunits);
- buf->deleted_codepoints++; // NL char
- buf->deleted_codeunits++;
+ curbuf->deleted_bytes += len+1;
+ curbuf->deleted_bytes2 += len+1;
+ if (curbuf->update_need_codepoints) {
+ mb_utflen(ptr, len, &curbuf->deleted_codepoints,
+ &curbuf->deleted_codeunits);
+ curbuf->deleted_codepoints++; // NL char
+ curbuf->deleted_codeunits++;
}
}
@@ -2418,17 +2423,17 @@ int ml_replace(linenr_T lnum, char_u *line, bool copy)
return ml_replace_buf(curbuf, lnum, line, copy);
}
-/*
- * Replace line lnum, with buffering, in current buffer.
- *
- * If "copy" is TRUE, make a copy of the line, otherwise the line has been
- * copied to allocated memory already.
- *
- * Check: The caller of this function should probably also call
- * changed_lines(), unless update_screen(NOT_VALID) is used.
- *
- * return FAIL for failure, OK otherwise
- */
+// Replace line "lnum", with buffering, in current buffer.
+//
+// If "copy" is true, make a copy of the line, otherwise the line has been
+// copied to allocated memory already.
+// If "copy" is false the "line" may be freed to add text properties!
+// Do not use it after calling ml_replace().
+//
+// Check: The caller of this function should probably also call
+// changed_lines(), unless update_screen(NOT_VALID) is used.
+//
+// return FAIL for failure, OK otherwise
int ml_replace_buf(buf_T *buf, linenr_T lnum, char_u *line, bool copy)
{
if (line == NULL) /* just checking... */
@@ -2831,6 +2836,7 @@ static void ml_flush_line(buf_T *buf)
}
buf->b_ml.ml_line_lnum = 0;
+ buf->b_ml.ml_line_offset = 0;
}
/*
@@ -3188,6 +3194,12 @@ char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name
char_u *fname_res = fname;
#ifdef HAVE_READLINK
char_u fname_buf[MAXPATHL];
+
+ // Expand symlink in the file name, so that we put the swap file with the
+ // actual file instead of with the symlink.
+ if (resolve_symlink(fname, fname_buf) == OK) {
+ fname_res = fname_buf;
+ }
#endif
int len = (int)STRLEN(dir_name);
@@ -3196,20 +3208,14 @@ char_u *makeswapname(char_u *fname, char_u *ffname, buf_T *buf, char_u *dir_name
&& 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);
+ s = (char_u *)make_percent_swname((char *)dir_name, (char *)fname_res);
+ if (s != NULL) {
+ r = (char_u *)modname((char *)s, ".swp", false);
xfree(s);
}
return r;
}
-#ifdef HAVE_READLINK
- /* Expand symlink in the file name, so that we put the swap file with the
- * actual file instead of with the symlink. */
- if (resolve_symlink(fname, fname_buf) == OK)
- fname_res = fname_buf;
-#endif
-
// Prepend a '.' to the swap file name for the current directory.
r = (char_u *)modname((char *)fname_res, ".swp",
dir_name[0] == '.' && dir_name[1] == NUL);
@@ -3980,10 +3986,10 @@ static void ml_updatechunk(buf_T *buf, linenr_T line, long len, int updtype)
/// Find offset for line or line with offset.
///
/// @param buf buffer to use
-/// @param lnum if > 0, find offset of lnum, store offset in offp
+/// @param lnum if > 0, find offset of lnum, return offset
/// if == 0, return line with offset *offp
-/// @param offp Location where offset of line is stored, or to read offset to
-/// use to find line. In the later case, store remaining offset.
+/// @param offp offset to use to find line, store remaining column offset
+/// Should be NULL when getting offset of line
/// @param no_ff ignore 'fileformat' option, always use one byte for NL.
///
/// @return -1 if information is not available
@@ -4003,8 +4009,22 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
int ffdos = !no_ff && (get_fileformat(buf) == EOL_DOS);
int extra = 0;
- // take care of cached line first
- ml_flush_line(buf);
+ // take care of cached line first. Only needed if the cached line is before
+ // the requested line. Additionally cache the value for the cached line.
+ // This is used by the extmark code which needs the byte offset of the edited
+ // line. So when doing multiple small edits on the same line the value is
+ // only calculated once.
+ //
+ // NB: caching doesn't work with 'fileformat'. This is not a problem for
+ // bytetracking, as bytetracking ignores 'fileformat' option. But calling
+ // line2byte() will invalidate the cache for the time being (this function
+ // was never cached to start with anyway).
+ bool can_cache = (lnum != 0 && !ffdos && buf->b_ml.ml_line_lnum == lnum);
+ if (lnum == 0 || buf->b_ml.ml_line_lnum < lnum || !no_ff) {
+ ml_flush_line(curbuf);
+ } else if (can_cache && buf->b_ml.ml_line_offset > 0) {
+ return buf->b_ml.ml_line_offset;
+ }
if (buf->b_ml.ml_usedchunks == -1
|| buf->b_ml.ml_chunksize == NULL
@@ -4100,6 +4120,10 @@ long ml_find_line_or_offset(buf_T *buf, linenr_T lnum, long *offp, bool no_ff)
}
}
+ if (can_cache && size > 0) {
+ buf->b_ml.ml_line_offset = size;
+ }
+
return size;
}
diff --git a/src/nvim/memline_defs.h b/src/nvim/memline_defs.h
index edd933b2cd..9a6f29a908 100644
--- a/src/nvim/memline_defs.h
+++ b/src/nvim/memline_defs.h
@@ -57,6 +57,8 @@ typedef struct memline {
linenr_T ml_line_lnum; // line number of cached line, 0 if not valid
char_u *ml_line_ptr; // pointer to cached line
+ size_t ml_line_offset; // cached byte offset of ml_line_lnum
+ int ml_line_offset_ff; // fileformat of cached line
bhdr_T *ml_locked; // block used by last ml_get
linenr_T ml_locked_low; // first line in ml_locked
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index b060464383..4ba2e36656 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -193,7 +193,7 @@ ex_menu(exarg_T *eap)
vimmenu_T **root_menu_ptr = get_root_menu(menu_path);
if (root_menu_ptr == &curwin->w_winbar) {
// Assume the window toolbar menu will change.
- redraw_later(NOT_VALID);
+ redraw_later(curwin, NOT_VALID);
}
if (enable != kNone) {
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 8999365d32..06ba607323 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -607,8 +607,7 @@ int emsg_not_now(void)
static bool emsg_multiline(const char *s, bool multiline)
{
int attr;
- int ignore = false;
- int severe;
+ bool ignore = false;
// Skip this if not giving error messages at the moment.
if (emsg_not_now()) {
@@ -617,9 +616,9 @@ static bool emsg_multiline(const char *s, bool multiline)
called_emsg = true;
- // If "emsg_severe" is TRUE: When an error exception is to be thrown,
+ // If "emsg_severe" is true: When an error exception is to be thrown,
// prefer this message over previous messages for the same command.
- severe = emsg_severe;
+ bool severe = emsg_severe;
emsg_severe = false;
if (!emsg_off || vim_strchr(p_debug, 't') != NULL) {
@@ -630,7 +629,7 @@ static bool emsg_multiline(const char *s, bool multiline)
* when the message should be ignored completely (used for the
* interrupt message).
*/
- if (cause_errthrow((char_u *)s, severe, &ignore) == true) {
+ if (cause_errthrow((char_u *)s, severe, &ignore)) {
if (!ignore) {
did_emsg++;
}
@@ -1217,7 +1216,7 @@ void wait_return(int redraw)
ui_refresh();
} else if (!skip_redraw) {
if (redraw == true || (msg_scrolled != 0 && redraw != -1)) {
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
}
if (ui_has(kUIMessages)) {
msg_ext_clear(true);
@@ -1622,7 +1621,7 @@ const char *str2special(const char **const sp, const bool replace_spaces,
// Check for an illegal byte.
if (MB_BYTE2LEN((uint8_t)(*str)) > len) {
- transchar_nonprint((char_u *)buf, c);
+ transchar_nonprint(curbuf, (char_u *)buf, c);
*sp = str + 1;
return buf;
}
@@ -1887,6 +1886,7 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr)
// wait-return prompt later. Needed when scrolling, resetting
// need_wait_return after some prompt, and then outputting something
// without scrolling
+ // Not needed when only using CR to move the cursor.
bool overflow = false;
if (ui_has(kUIMessages)) {
int count = msg_ext_visible + (msg_ext_overwrite ? 0 : 1);
@@ -1898,7 +1898,7 @@ void msg_puts_attr_len(const char *const str, const ptrdiff_t len, int attr)
overflow = msg_scrolled != 0;
}
- if (overflow && !msg_scrolled_ign) {
+ if (overflow && !msg_scrolled_ign && strcmp(str, "\r") != 0) {
need_wait_return = true;
}
msg_didany = true; // remember that something was outputted
@@ -2000,7 +2000,7 @@ static void msg_puts_display(const char_u *str, int maxlen, int attr,
|| (*s == TAB && msg_col <= 7)
|| (utf_ptr2cells(s) > 1
&& msg_col <= 2))
- : (msg_col + t_col >= Columns - 1
+ : ((*s != '\r' && msg_col + t_col >= Columns - 1)
|| (*s == TAB
&& msg_col + t_col >= ((Columns - 1) & ~7))
|| (utf_ptr2cells(s) > 1
@@ -2222,7 +2222,7 @@ void msg_scroll_up(bool may_throttle)
///
/// Probably message scrollback storage should reimplented as a file_buffer, and
/// message scrolling in TUI be reimplemented as a modal floating window. Then
-/// we get throttling "for free" using standard redraw_win_later code paths.
+/// we get throttling "for free" using standard redraw_later code paths.
void msg_scroll_flush(void)
{
if (msg_grid.throttled) {
diff --git a/src/nvim/misc1.c b/src/nvim/misc1.c
index 6dafbafb3e..fcffe64595 100644
--- a/src/nvim/misc1.c
+++ b/src/nvim/misc1.c
@@ -983,7 +983,7 @@ void preserve_exit(void)
FOR_ALL_BUFFERS(buf) {
if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) {
- mch_errmsg((uint8_t *)"Vim: preserving files...\n");
+ mch_errmsg("Vim: preserving files...\r\n");
ui_flush();
ml_sync_all(false, false, true); // preserve all swap files
break;
@@ -992,7 +992,7 @@ void preserve_exit(void)
ml_close_all(false); // close all memfiles, without deleting
- mch_errmsg("Vim: Finished.\n");
+ mch_errmsg("Vim: Finished.\r\n");
getout(1);
}
@@ -1091,8 +1091,9 @@ char_u *get_cmd_output(char_u *cmd, char_u *infile, ShellOpts flags,
{
char_u *buffer = NULL;
- if (check_restricted() || check_secure())
+ if (check_secure()) {
return NULL;
+ }
// get a name for the temp file
char_u *tempname = vim_tempname();
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index fcd9ee4f75..cff88de00b 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -75,6 +75,7 @@ int jump_to_mouse(int flags,
int grid = mouse_grid;
int mouse_char;
int fdc = 0;
+ ScreenGrid *gp = &default_grid;
mouse_past_bottom = false;
mouse_past_eol = false;
@@ -125,16 +126,6 @@ retnomove:
if (flags & MOUSE_SETPOS)
goto retnomove; // ugly goto...
- // Remember the character under the mouse, might be one of foldclose or
- // foldopen fillchars in the fold column.
- if (row >= 0 && row < Rows && col >= 0 && col <= Columns
- && default_grid.chars != NULL) {
- mouse_char = utf_ptr2char(default_grid.chars[default_grid.line_offset[row]
- + (unsigned)col]);
- } else {
- mouse_char = ' ';
- }
-
old_curwin = curwin;
old_cursor = curwin->w_cursor;
@@ -286,7 +277,7 @@ retnomove:
check_topfill(curwin, false);
curwin->w_valid &=
~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP);
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
row = 0;
} else if (row >= curwin->w_height_inner) {
count = 0;
@@ -316,7 +307,7 @@ retnomove:
}
}
check_topfill(curwin, false);
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
curwin->w_valid &=
~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_BOTLINE_AP);
row = curwin->w_height_inner - 1;
@@ -333,6 +324,19 @@ retnomove:
}
}
+ // Remember the character under the mouse, might be one of foldclose or
+ // foldopen fillchars in the fold column.
+ if (ui_has(kUIMultigrid)) {
+ gp = &curwin->w_grid;
+ }
+ if (row >= 0 && row < Rows && col >= 0 && col <= Columns
+ && gp->chars != NULL) {
+ mouse_char = utf_ptr2char(gp->chars[gp->line_offset[row]
+ + (unsigned)col]);
+ } else {
+ mouse_char = ' ';
+ }
+
// Check for position outside of the fold column.
if (curwin->w_p_rl ? col < curwin->w_width_inner - fdc :
col >= fdc + (cmdwin_type == 0 ? 0 : 1)) {
@@ -450,7 +454,7 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump)
// skip line number and fold column in front of the line
col -= win_col_off(win);
- if (col < 0) {
+ if (col <= 0) {
col = 0;
}
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 8a8a639a52..ccd19a81de 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -107,7 +107,7 @@ void redraw_for_cursorline(win_T *wp)
&& !pum_visible()) {
if (wp->w_p_rnu) {
// win_line() will redraw the number column only.
- redraw_win_later(wp, VALID);
+ redraw_later(wp, VALID);
}
if (win_cursorline_standout(wp)) {
if (wp->w_redr_type <= VALID && wp->w_last_cursorline != 0) {
@@ -117,7 +117,7 @@ void redraw_for_cursorline(win_T *wp)
redrawWinline(wp, wp->w_last_cursorline);
redrawWinline(wp, wp->w_cursor.lnum);
} else {
- redraw_win_later(wp, SOME_VALID);
+ redraw_later(wp, SOME_VALID);
}
}
}
@@ -172,7 +172,7 @@ void update_topline(void)
// 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);
+ redraw_later(curwin, NOT_VALID);
}
curwin->w_topline = 1;
curwin->w_botline = 2;
@@ -326,12 +326,14 @@ void update_topline(void)
dollar_vcol = -1;
if (curwin->w_skipcol != 0) {
curwin->w_skipcol = 0;
- redraw_later(NOT_VALID);
- } else
- redraw_later(VALID);
- /* May need to set w_skipcol when cursor in w_topline. */
- if (curwin->w_cursor.lnum == curwin->w_topline)
+ redraw_later(curwin, NOT_VALID);
+ } else {
+ redraw_later(curwin, VALID);
+ }
+ // May need to set w_skipcol when cursor in w_topline.
+ if (curwin->w_cursor.lnum == curwin->w_topline) {
validate_cursor();
+ }
}
*so_ptr = save_so;
@@ -439,7 +441,7 @@ void changed_window_setting_win(win_T *wp)
wp->w_lines_valid = 0;
changed_line_abv_curs_win(wp);
wp->w_valid &= ~(VALID_BOTLINE|VALID_BOTLINE_AP|VALID_TOPLINE);
- redraw_win_later(wp, NOT_VALID);
+ redraw_later(wp, NOT_VALID);
}
/*
@@ -455,8 +457,8 @@ void set_topline(win_T *wp, linenr_T lnum)
wp->w_topline_was_set = true;
wp->w_topfill = 0;
wp->w_valid &= ~(VALID_WROW|VALID_CROW|VALID_BOTLINE|VALID_TOPLINE);
- /* Don't set VALID_TOPLINE here, 'scrolloff' needs to be checked. */
- redraw_later(VALID);
+ // Don't set VALID_TOPLINE here, 'scrolloff' needs to be checked.
+ redraw_later(wp, VALID);
}
/*
@@ -634,14 +636,14 @@ void validate_virtcol_win(win_T *wp)
if (wp->w_p_cuc
&& !pum_visible()
)
- redraw_win_later(wp, SOME_VALID);
+ redraw_later(wp, SOME_VALID);
}
}
/*
* Validate curwin->w_cline_height only.
*/
-static void validate_cheight(void)
+void validate_cheight(void)
{
check_cursor_moved(curwin);
if (!(curwin->w_valid & VALID_CHEIGHT)) {
@@ -830,7 +832,7 @@ void curs_columns(
curwin->w_leftcol = new_leftcol;
win_check_anchored_floats(curwin);
// screen has to be redrawn with new curwin->w_leftcol
- redraw_later(NOT_VALID);
+ redraw_later(curwin, NOT_VALID);
}
}
curwin->w_wcol -= curwin->w_leftcol;
@@ -934,15 +936,19 @@ void curs_columns(
} else {
curwin->w_skipcol = 0;
}
- if (prev_skipcol != curwin->w_skipcol)
- redraw_later(NOT_VALID);
+ if (prev_skipcol != curwin->w_skipcol) {
+ redraw_later(curwin, NOT_VALID);
+ }
/* Redraw when w_virtcol changes and 'cursorcolumn' is set */
if (curwin->w_p_cuc && (curwin->w_valid & VALID_VIRTCOL) == 0
&& !pum_visible()) {
- redraw_later(SOME_VALID);
+ redraw_later(curwin, SOME_VALID);
}
+ // now w_leftcol is valid, avoid check_cursor_moved() thinking otherwise
+ curwin->w_valid_leftcol = curwin->w_leftcol;
+
curwin->w_valid |= VALID_WCOL|VALID_WROW|VALID_VIRTCOL;
}
@@ -1014,16 +1020,13 @@ void textpos2screenpos(win_T *wp, pos_T *pos, int *rowp, int *scolp,
*ecolp = ecol + coloff;
}
-/*
- * Scroll the current window down by "line_count" logical lines. "CTRL-Y"
- */
-void
-scrolldown (
- long line_count,
- int byfold /* true: count a closed fold as one line */
-)
+/// Scroll the current window down by "line_count" logical lines. "CTRL-Y"
+///
+/// @param line_count number of lines to scroll
+/// @param byfold if true, count a closed fold as one line
+bool scrolldown(long line_count, int byfold)
{
- int done = 0; /* total # of physical lines done */
+ int done = 0; // total # of physical lines done
/* Make sure w_topline is at the first of a sequence of folded lines. */
(void)hasFolding(curwin->w_topline, &curwin->w_topline, NULL);
@@ -1092,17 +1095,18 @@ scrolldown (
foldAdjustCursor();
coladvance(curwin->w_curswant);
}
+ return moved;
}
-/*
- * Scroll the current window up by "line_count" logical lines. "CTRL-E"
- */
-void
-scrollup (
- long line_count,
- int byfold /* true: count a closed fold as one line */
-)
+/// Scroll the current window up by "line_count" logical lines. "CTRL-E"
+///
+/// @param line_count number of lines to scroll
+/// @param byfold if true, count a closed fold as one line
+bool scrollup(long line_count, int byfold)
{
+ linenr_T topline = curwin->w_topline;
+ linenr_T botline = curwin->w_botline;
+
if ((byfold && hasAnyFolding(curwin))
|| curwin->w_p_diff) {
// count each sequence of folded lines as one logical line
@@ -1145,6 +1149,11 @@ scrollup (
~(VALID_WROW|VALID_WCOL|VALID_CHEIGHT|VALID_CROW|VALID_VIRTCOL);
coladvance(curwin->w_curswant);
}
+
+ bool moved = topline != curwin->w_topline
+ || botline != curwin->w_botline;
+
+ return moved;
}
/*
@@ -2010,7 +2019,7 @@ int onepage(Direction dir, long count)
}
}
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
return retval;
}
@@ -2196,7 +2205,7 @@ void halfpage(bool flag, linenr_T Prenum)
check_topfill(curwin, !flag);
cursor_correct();
beginline(BL_SOL | BL_FIX);
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
}
void do_check_cursorbind(void)
@@ -2244,7 +2253,7 @@ void do_check_cursorbind(void)
}
// Correct cursor for multi-byte character.
mb_adjust_cursor();
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
// Only scroll when 'scrollbind' hasn't done this.
if (!curwin->w_p_scb) {
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 92ca29209e..68ef4cd41e 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -262,11 +262,9 @@ static void parse_msgpack(Channel *channel)
call_set_error(channel, buf, ERROR_LOG_LEVEL);
}
msgpack_unpacked_destroy(&unpacked);
- // Bail out from this event loop iteration
- return;
+ } else {
+ handle_request(channel, &unpacked.data);
}
-
- handle_request(channel, &unpacked.data);
}
if (result == MSGPACK_UNPACK_NOMEM_ERROR) {
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 968cfde388..2805a7d74e 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -903,6 +903,10 @@ normal_end:
msg_nowait = false;
+ if (finish_op) {
+ set_reg_var(get_default_register_name());
+ }
+
// Reset finish_op, in case it was set
s->c = finish_op;
finish_op = false;
@@ -1193,6 +1197,15 @@ static void normal_check_interrupt(NormalState *s)
}
}
+static void normal_check_window_scrolled(NormalState *s)
+{
+ // Trigger Scroll if the viewport changed.
+ if (!finish_op && has_event(EVENT_WINSCROLLED)
+ && win_did_scroll(curwin)) {
+ do_autocmd_winscrolled(curwin);
+ }
+}
+
static void normal_check_cursor_moved(NormalState *s)
{
// Trigger CursorMoved if the cursor moved.
@@ -1216,6 +1229,16 @@ static void normal_check_text_changed(NormalState *s)
}
}
+static void normal_check_buffer_modified(NormalState *s)
+{
+ // Trigger BufModified if b_modified changed
+ if (!finish_op && has_event(EVENT_BUFMODIFIEDSET)
+ && curbuf->b_changed_invalid == true) {
+ apply_autocmds(EVENT_BUFMODIFIEDSET, NULL, NULL, false, curbuf);
+ curbuf->b_changed_invalid = false;
+ }
+}
+
static void normal_check_folds(NormalState *s)
{
// Include a closed fold completely in the Visual area.
@@ -1316,8 +1339,14 @@ static int normal_check(VimState *state)
if (skip_redraw || exmode_active) {
skip_redraw = false;
} else if (do_redraw || stuff_empty()) {
+ // Need to make sure w_topline and w_leftcol are correct before
+ // normal_check_window_scrolled() is called.
+ update_topline();
+
normal_check_cursor_moved(s);
normal_check_text_changed(s);
+ normal_check_window_scrolled(s);
+ normal_check_buffer_modified(s);
// Updating diffs from changed() does not always work properly,
// esp. updating folds. Do an update just before redrawing if
@@ -1977,20 +2006,20 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
case OP_FOLD:
VIsual_reselect = false; // don't reselect now
- foldCreate(curwin, oap->start.lnum, oap->end.lnum);
+ foldCreate(curwin, oap->start, oap->end);
break;
case OP_FOLDOPEN:
case OP_FOLDOPENREC:
case OP_FOLDCLOSE:
case OP_FOLDCLOSEREC:
- VIsual_reselect = false; /* don't reselect now */
- opFoldRange(oap->start.lnum, oap->end.lnum,
- oap->op_type == OP_FOLDOPEN
- || oap->op_type == OP_FOLDOPENREC,
- oap->op_type == OP_FOLDOPENREC
- || oap->op_type == OP_FOLDCLOSEREC,
- oap->is_VIsual);
+ VIsual_reselect = false; // don't reselect now
+ opFoldRange(oap->start, oap->end,
+ oap->op_type == OP_FOLDOPEN
+ || oap->op_type == OP_FOLDOPENREC,
+ oap->op_type == OP_FOLDOPENREC
+ || oap->op_type == OP_FOLDCLOSEREC,
+ oap->is_VIsual);
break;
case OP_FOLDDEL:
@@ -2483,7 +2512,7 @@ do_mouse (
typval_T rettv;
int doesrange;
(void)call_func((char_u *)tab_page_click_defs[mouse_col].func,
- (int)strlen(tab_page_click_defs[mouse_col].func),
+ -1,
&rettv, ARRAY_SIZE(argv), argv, NULL,
curwin->w_cursor.lnum, curwin->w_cursor.lnum,
&doesrange, true, NULL, NULL);
@@ -2590,14 +2619,16 @@ do_mouse (
&& !is_drag
&& (jump_flags & (MOUSE_FOLD_CLOSE | MOUSE_FOLD_OPEN))
&& which_button == MOUSE_LEFT) {
- /* open or close a fold at this line */
- if (jump_flags & MOUSE_FOLD_OPEN)
- openFold(curwin->w_cursor.lnum, 1L);
- else
- closeFold(curwin->w_cursor.lnum, 1L);
- /* don't move the cursor if still in the same window */
- if (curwin == old_curwin)
+ // open or close a fold at this line
+ if (jump_flags & MOUSE_FOLD_OPEN) {
+ openFold(curwin->w_cursor, 1L);
+ } else {
+ closeFold(curwin->w_cursor, 1L);
+ }
+ // don't move the cursor if still in the same window
+ if (curwin == old_curwin) {
curwin->w_cursor = save_cursor;
+ }
}
@@ -3599,7 +3630,7 @@ void check_scrollbind(linenr_T topline_diff, long leftcol_diff)
scrolldown(-y, false);
}
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
cursor_correct();
curwin->w_redr_status = true;
}
@@ -4105,10 +4136,10 @@ void scroll_redraw(int up, long count)
int prev_topfill = curwin->w_topfill;
linenr_T prev_lnum = curwin->w_cursor.lnum;
- if (up)
- scrollup(count, true);
- else
+ bool moved = up ?
+ scrollup(count, true) :
scrolldown(count, true);
+
if (get_scrolloff_value()) {
// Adjust the cursor position for 'scrolloff'. Mark w_topline as
// valid, otherwise the screen jumps back at the end of the file.
@@ -4138,10 +4169,13 @@ void scroll_redraw(int up, long count)
curwin->w_valid |= VALID_TOPLINE;
}
}
- if (curwin->w_cursor.lnum != prev_lnum)
+ if (curwin->w_cursor.lnum != prev_lnum) {
coladvance(curwin->w_curswant);
- curwin->w_viewport_invalid = true;
- redraw_later(VALID);
+ }
+ if (moved) {
+ curwin->w_viewport_invalid = true;
+ }
+ redraw_later(curwin, VALID);
}
/*
@@ -4239,7 +4273,7 @@ dozet:
FALLTHROUGH;
case 't': scroll_cursor_top(0, true);
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
set_fraction(curwin);
break;
@@ -4248,7 +4282,7 @@ dozet:
FALLTHROUGH;
case 'z': scroll_cursor_halfway(true);
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
set_fraction(curwin);
break;
@@ -4269,7 +4303,7 @@ dozet:
FALLTHROUGH;
case 'b': scroll_cursor_bot(0, true);
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
set_fraction(curwin);
break;
@@ -4316,7 +4350,7 @@ dozet:
col = 0;
if (curwin->w_leftcol != col) {
curwin->w_leftcol = col;
- redraw_later(NOT_VALID);
+ redraw_later(curwin, NOT_VALID);
}
}
break;
@@ -4335,7 +4369,7 @@ dozet:
}
if (curwin->w_leftcol != col) {
curwin->w_leftcol = col;
- redraw_later(NOT_VALID);
+ redraw_later(curwin, NOT_VALID);
}
}
break;
@@ -4393,51 +4427,55 @@ dozet:
case 'i': curwin->w_p_fen = !curwin->w_p_fen;
break;
- /* "za": open closed fold or close open fold at cursor */
- case 'a': if (hasFolding(curwin->w_cursor.lnum, NULL, NULL))
- openFold(curwin->w_cursor.lnum, cap->count1);
- else {
- closeFold(curwin->w_cursor.lnum, cap->count1);
+ // "za": open closed fold or close open fold at cursor
+ case 'a': if (hasFolding(curwin->w_cursor.lnum, NULL, NULL)) {
+ openFold(curwin->w_cursor, cap->count1);
+ } else {
+ closeFold(curwin->w_cursor, cap->count1);
curwin->w_p_fen = true;
}
break;
- /* "zA": open fold at cursor recursively */
- case 'A': if (hasFolding(curwin->w_cursor.lnum, NULL, NULL))
- openFoldRecurse(curwin->w_cursor.lnum);
- else {
- closeFoldRecurse(curwin->w_cursor.lnum);
+ // "zA": open fold at cursor recursively
+ case 'A': if (hasFolding(curwin->w_cursor.lnum, NULL, NULL)) {
+ openFoldRecurse(curwin->w_cursor);
+ } else {
+ closeFoldRecurse(curwin->w_cursor);
curwin->w_p_fen = true;
}
break;
- /* "zo": open fold at cursor or Visual area */
- case 'o': if (VIsual_active)
+ // "zo": open fold at cursor or Visual area
+ case 'o': if (VIsual_active) {
nv_operator(cap);
- else
- openFold(curwin->w_cursor.lnum, cap->count1);
+ } else {
+ openFold(curwin->w_cursor, cap->count1);
+ }
break;
- /* "zO": open fold recursively */
- case 'O': if (VIsual_active)
+ // "zO": open fold recursively
+ case 'O': if (VIsual_active) {
nv_operator(cap);
- else
- openFoldRecurse(curwin->w_cursor.lnum);
+ } else {
+ openFoldRecurse(curwin->w_cursor);
+ }
break;
- /* "zc": close fold at cursor or Visual area */
- case 'c': if (VIsual_active)
+ // "zc": close fold at cursor or Visual area
+ case 'c': if (VIsual_active) {
nv_operator(cap);
- else
- closeFold(curwin->w_cursor.lnum, cap->count1);
+ } else {
+ closeFold(curwin->w_cursor, cap->count1);
+ }
curwin->w_p_fen = true;
break;
- /* "zC": close fold recursively */
- case 'C': if (VIsual_active)
+ // "zC": close fold recursively
+ case 'C': if (VIsual_active) {
nv_operator(cap);
- else
- closeFoldRecurse(curwin->w_cursor.lnum);
+ } else {
+ closeFoldRecurse(curwin->w_cursor);
+ }
curwin->w_p_fen = true;
break;
@@ -4684,7 +4722,7 @@ static void nv_clear(cmdarg_T *cap)
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
wp->w_s->b_syn_slow = false;
}
- redraw_later(CLEAR);
+ redraw_later(curwin, CLEAR);
}
}
@@ -6835,7 +6873,7 @@ static void nv_g_cmd(cmdarg_T *cap)
} else {
if (cap->count1 > 1) {
// if it fails, let the cursor still move to the last char
- cursor_down(cap->count1 - 1, false);
+ (void)cursor_down(cap->count1 - 1, false);
}
i = curwin->w_leftcol + curwin->w_width_inner - col_off - 1;
coladvance((colnr_T)i);
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 595a699563..9e7d81fc82 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -119,7 +119,7 @@ static char opchars[][3] =
{ 'r', NUL, OPF_CHANGE }, // OP_REPLACE
{ 'I', NUL, OPF_CHANGE }, // OP_INSERT
{ 'A', NUL, OPF_CHANGE }, // OP_APPEND
- { 'z', 'f', OPF_LINES }, // OP_FOLD
+ { 'z', 'f', 0 }, // OP_FOLD
{ 'z', 'o', OPF_LINES }, // OP_FOLDOPEN
{ 'z', 'O', OPF_LINES }, // OP_FOLDOPENREC
{ 'z', 'c', OPF_LINES }, // OP_FOLDCLOSE
@@ -496,9 +496,9 @@ static void shift_block(oparg_T *oap, int amount)
// replace the line
ml_replace(curwin->w_cursor.lnum, newp, false);
changed_bytes(curwin->w_cursor.lnum, (colnr_T)bd.textcol);
- extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, startcol,
- 0, oldlen, 0, newlen,
- kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum-1, startcol,
+ oldlen, newlen,
+ kExtmarkUndo);
State = oldstate;
curwin->w_cursor.col = oldcol;
p_ri = old_p_ri;
@@ -595,9 +595,8 @@ static void block_insert(oparg_T *oap, char_u *s, int b_insert, struct block_def
STRMOVE(newp + offset, oldp);
ml_replace(lnum, newp, false);
- extmark_splice(curbuf, (int)lnum-1, startcol,
- 0, skipped,
- 0, offset-startcol, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lnum-1, startcol,
+ skipped, offset-startcol, kExtmarkUndo);
if (lnum == oap->end.lnum) {
/* Set "']" mark to the end of the block instead of the end of
@@ -1534,10 +1533,9 @@ int op_delete(oparg_T *oap)
// replace the line
ml_replace(lnum, newp, false);
- extmark_splice(curbuf, (int)lnum-1, bd.textcol,
- 0, bd.textlen,
- 0, bd.startspaces+bd.endspaces,
- kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lnum-1, bd.textcol,
+ bd.textlen, bd.startspaces+bd.endspaces,
+ kExtmarkUndo);
}
check_cursor_col();
@@ -1546,10 +1544,10 @@ int op_delete(oparg_T *oap)
oap->line_count = 0; // no lines deleted
} else if (oap->motion_type == kMTLineWise) {
if (oap->op_type == OP_CHANGE) {
- /* Delete the lines except the first one. Temporarily move the
- * cursor to the next line. Save the current line number, if the
- * last line is deleted it may be changed.
- */
+ // Delete the lines except the first one. Temporarily move the
+ // cursor to the next line. Save the current line number, if the
+ // last line is deleted it may be changed.
+
if (oap->line_count > 1) {
lnum = curwin->w_cursor.lnum;
++curwin->w_cursor.lnum;
@@ -1562,12 +1560,21 @@ int op_delete(oparg_T *oap)
beginline(BL_WHITE); // cursor on first non-white
did_ai = true; // delete the indent when ESC hit
ai_col = curwin->w_cursor.col;
- } else
- beginline(0); /* cursor in column 0 */
- truncate_line(FALSE); /* delete the rest of the line */
- /* leave cursor past last char in line */
- if (oap->line_count > 1)
- u_clearline(); /* "U" command not possible after "2cc" */
+ } else {
+ beginline(0); // cursor in column 0
+ }
+
+ int old_len = (int)STRLEN(ml_get(curwin->w_cursor.lnum));
+ truncate_line(false); // delete the rest of the line
+
+ extmark_splice_cols(curbuf,
+ (int)curwin->w_cursor.lnum-1, curwin->w_cursor.col,
+ old_len - curwin->w_cursor.col, 0, kExtmarkUndo);
+
+ // leave cursor past last char in line
+ if (oap->line_count > 1) {
+ u_clearline(); // "U" command not possible after "2cc"
+ }
} else {
del_lines(oap->line_count, TRUE);
beginline(BL_WHITE | BL_FIX);
@@ -1661,17 +1668,20 @@ int op_delete(oparg_T *oap)
curpos = curwin->w_cursor; // remember curwin->w_cursor
curwin->w_cursor.lnum++;
del_lines(oap->line_count - 2, false);
+ bcount_t deleted_bytes = (bcount_t)curbuf->deleted_bytes2 - startpos.col;
// delete from start of line until op_end
n = (oap->end.col + 1 - !oap->inclusive);
curwin->w_cursor.col = 0;
(void)del_bytes((colnr_T)n, !virtual_op,
oap->op_type == OP_DELETE && !oap->is_VIsual);
+ deleted_bytes += n;
curwin->w_cursor = curpos; // restore curwin->w_cursor
(void)do_join(2, false, false, false, false);
curbuf_splice_pending--;
extmark_splice(curbuf, (int)startpos.lnum-1, startpos.col,
- (int)oap->line_count-1, n, 0, 0, kExtmarkUndo);
+ (int)oap->line_count-1, n, deleted_bytes,
+ 0, 0, 0, kExtmarkUndo);
}
}
@@ -1711,7 +1721,7 @@ static inline void pbyte(pos_T lp, int c)
assert(c <= UCHAR_MAX);
*(ml_get_buf(curbuf, lp.lnum, true) + lp.col) = (char_u)c;
if (!curbuf_splice_pending) {
- extmark_splice(curbuf, (int)lp.lnum-1, lp.col, 0, 1, 0, 1, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lp.lnum-1, lp.col, 1, 1, kExtmarkUndo);
}
}
@@ -1856,6 +1866,7 @@ int op_replace(oparg_T *oap, int c)
}
// replace the line
ml_replace(curwin->w_cursor.lnum, newp, false);
+ curbuf_splice_pending++;
linenr_T baselnum = curwin->w_cursor.lnum;
if (after_p != NULL) {
ml_append(curwin->w_cursor.lnum++, after_p, (int)after_p_len, false);
@@ -1863,9 +1874,10 @@ int op_replace(oparg_T *oap, int c)
oap->end.lnum++;
xfree(after_p);
}
+ curbuf_splice_pending--;
extmark_splice(curbuf, (int)baselnum-1, bd.textcol,
- 0, bd.textlen,
- newrows, newcols, kExtmarkUndo);
+ 0, bd.textlen, bd.textlen,
+ newrows, newcols, newrows+newcols, kExtmarkUndo);
}
} else {
// Characterwise or linewise motion replace.
@@ -2399,9 +2411,8 @@ int op_change(oparg_T *oap)
oldp += bd.textcol;
STRMOVE(newp + offset, oldp);
ml_replace(linenr, newp, false);
- extmark_splice(curbuf, (int)linenr-1, bd.textcol,
- 0, 0,
- 0, vpos.coladd+(int)ins_len, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)linenr-1, bd.textcol,
+ 0, vpos.coladd+(int)ins_len, kExtmarkUndo);
}
}
check_cursor();
@@ -2641,7 +2652,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
xfree(reg->y_array);
}
if (curwin->w_p_rnu) {
- redraw_later(SOME_VALID); // cursor moved to start
+ redraw_later(curwin, SOME_VALID); // cursor moved to start
}
if (message) { // Display message about yank?
if (yank_type == kMTCharWise && yanklines == 1) {
@@ -3098,6 +3109,9 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
for (i = 0; i < y_size; i++) {
int spaces;
char shortline;
+ // can just be 0 or 1, needed for blockwise paste beyond the current
+ // buffer end
+ int lines_appended = 0;
bd.startspaces = 0;
bd.endspaces = 0;
@@ -3111,6 +3125,7 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
break;
}
nr_lines++;
+ lines_appended = 1;
}
/* get the old line and advance to the position to insert at */
oldp = get_cursor_line_ptr();
@@ -3182,17 +3197,16 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
assert(columns >= 0);
memmove(ptr, oldp + bd.textcol + delcount, (size_t)columns);
ml_replace(curwin->w_cursor.lnum, newp, false);
- extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, bd.textcol,
- 0, delcount,
- 0, (int)totlen,
- kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)curwin->w_cursor.lnum-1, bd.textcol,
+ delcount, (int)totlen + lines_appended, kExtmarkUndo);
++curwin->w_cursor.lnum;
if (i == 0)
curwin->w_cursor.col += bd.startspaces;
}
- changed_lines(lnum, 0, curwin->w_cursor.lnum, nr_lines, true);
+ changed_lines(lnum, 0, curbuf->b_op_start.lnum + (linenr_T)y_size
+ - (linenr_T)nr_lines , nr_lines, true);
/* Set '[ mark. */
curbuf->b_op_start = curwin->w_cursor;
@@ -3238,21 +3252,43 @@ 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 one line at a time
if (y_type == kMTCharWise && y_size == 1) {
linenr_T end_lnum = 0; // init for gcc
+ linenr_T start_lnum = lnum;
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;
}
+ if (end_lnum > start_lnum) {
+ // "col" is valid for the first line, in following lines
+ // the virtual column needs to be used. Matters for
+ // multi-byte characters.
+ pos_T pos = {
+ .lnum = lnum,
+ .col = col,
+ .coladd = 0,
+ };
+ getvcol(curwin, &pos, NULL, &vcol, NULL);
+ }
}
do {
totlen = (size_t)(count * yanklen);
if (totlen > 0) {
oldp = ml_get(lnum);
+ if (lnum > start_lnum) {
+ pos_T pos = {
+ .lnum = lnum,
+ };
+ if (getvpos(&pos, vcol) == OK) {
+ col = pos.col;
+ } else {
+ col = MAXCOL;
+ }
+ }
if (VIsual_active && col > (int)STRLEN(oldp)) {
lnum++;
continue;
@@ -3287,9 +3323,8 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
if (totlen && (restart_edit != 0 || (flags & PUT_CURSEND)))
++curwin->w_cursor.col;
changed_bytes(lnum, col);
- extmark_splice(curbuf, (int)lnum-1, col,
- 0, 0,
- 0, (int)totlen, kExtmarkUndo);
+ extmark_splice_cols(curbuf, (int)lnum-1, col,
+ 0, (int)totlen, kExtmarkUndo);
} else {
// Insert at least one line. When y_type is kMTCharWise, break the first
// line in two.
@@ -3353,13 +3388,23 @@ void do_put(int regname, yankreg_T *reg, int dir, long count, int flags)
}
}
+ bcount_t totsize = 0;
+ int lastsize = 0;
+ if (y_type == kMTCharWise
+ || (y_type == kMTLineWise && flags & PUT_LINE_SPLIT)) {
+ for (i = 0; i < y_size-1; i++) {
+ totsize += (bcount_t)STRLEN(y_array[i]) + 1;
+ }
+ lastsize = (int)STRLEN(y_array[y_size-1]);
+ totsize += lastsize;
+ }
if (y_type == kMTCharWise) {
- extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0,
- (int)y_size-1, (int)STRLEN(y_array[y_size-1]),
+ extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0, 0,
+ (int)y_size-1, lastsize, totsize,
kExtmarkUndo);
} else if (y_type == kMTLineWise && flags & PUT_LINE_SPLIT) {
- extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0,
- (int)y_size+1, 0, kExtmarkUndo);
+ extmark_splice(curbuf, (int)new_cursor.lnum-1, col, 0, 0, 0,
+ (int)y_size+1, 0, totsize+1, kExtmarkUndo);
}
}
@@ -3788,7 +3833,8 @@ int do_join(size_t count,
&& (!has_format_option(FO_MBYTE_JOIN)
|| (utf_ptr2char(curr) < 0x100 && endcurr1 < 0x100))
&& (!has_format_option(FO_MBYTE_JOIN2)
- || utf_ptr2char(curr) < 0x100 || endcurr1 < 0x100)
+ || (utf_ptr2char(curr) < 0x100 && !utf_eat_space(endcurr1))
+ || (endcurr1 < 0x100 && !utf_eat_space(utf_ptr2char(curr))))
) {
/* don't add a space if the line is ending in a space */
if (endcurr1 == ' ')
@@ -3803,9 +3849,10 @@ int do_join(size_t count,
}
if (t > 0 && curbuf_splice_pending == 0) {
+ colnr_T removed = (int)(curr- curr_start);
extmark_splice(curbuf, (int)curwin->w_cursor.lnum-1, sumsize,
- 1, (int)(curr- curr_start),
- 0, spaces[t],
+ 1, removed, removed + 1,
+ 0, spaces[t], spaces[t],
kExtmarkUndo);
}
currsize = (int)STRLEN(curr);
@@ -4112,49 +4159,41 @@ format_lines(
int avoid_fex /* don't use 'formatexpr' */
)
{
- int max_len;
- int is_not_par; /* current line not part of parag. */
- int next_is_not_par; /* next line not part of paragraph */
- int is_end_par; /* at end of paragraph */
- int prev_is_end_par = FALSE; /* prev. line not part of parag. */
- int next_is_start_par = FALSE;
- int leader_len = 0; /* leader len of current line */
- int next_leader_len; /* leader len of next line */
- char_u *leader_flags = NULL; /* flags for leader of current line */
- char_u *next_leader_flags; /* flags for leader of next line */
- int do_comments; /* format comments */
- int do_comments_list = 0; /* format comments with 'n' or '2' */
- int advance = TRUE;
- int second_indent = -1; /* indent for second line (comment
- * aware) */
- int do_second_indent;
- int do_number_indent;
- int do_trail_white;
- int first_par_line = TRUE;
+ bool is_not_par; // current line not part of parag.
+ bool next_is_not_par; // next line not part of paragraph
+ bool is_end_par; // at end of paragraph
+ bool prev_is_end_par = false; // prev. line not part of parag.
+ bool next_is_start_par = false;
+ int leader_len = 0; // leader len of current line
+ int next_leader_len; // leader len of next line
+ char_u *leader_flags = NULL; // flags for leader of current line
+ char_u *next_leader_flags; // flags for leader of next line
+ bool advance = true;
+ int second_indent = -1; // indent for second line (comment aware)
+ bool first_par_line = true;
int smd_save;
long count;
- int need_set_indent = TRUE; /* set indent of next paragraph */
- int force_format = FALSE;
- int old_State = State;
-
- /* length of a line to force formatting: 3 * 'tw' */
- max_len = comp_textwidth(TRUE) * 3;
-
- /* check for 'q', '2' and '1' in 'formatoptions' */
- do_comments = has_format_option(FO_Q_COMS);
- do_second_indent = has_format_option(FO_Q_SECOND);
- do_number_indent = has_format_option(FO_Q_NUMBER);
- do_trail_white = has_format_option(FO_WHITE_PAR);
-
- /*
- * Get info about the previous and current line.
- */
- if (curwin->w_cursor.lnum > 1)
- is_not_par = fmt_check_par(curwin->w_cursor.lnum - 1
- , &leader_len, &leader_flags, do_comments
- );
- else
- is_not_par = TRUE;
+ bool need_set_indent = true; // set indent of next paragraph
+ bool force_format = false;
+ const int old_State = State;
+
+ // length of a line to force formatting: 3 * 'tw'
+ const int max_len = comp_textwidth(true) * 3;
+
+ // check for 'q', '2' and '1' in 'formatoptions'
+ const bool do_comments = has_format_option(FO_Q_COMS); // format comments
+ int do_comments_list = 0; // format comments with 'n' or '2'
+ const bool do_second_indent = has_format_option(FO_Q_SECOND);
+ const bool do_number_indent = has_format_option(FO_Q_NUMBER);
+ const bool do_trail_white = has_format_option(FO_WHITE_PAR);
+
+ // Get info about the previous and current line.
+ if (curwin->w_cursor.lnum > 1) {
+ is_not_par = fmt_check_par(curwin->w_cursor.lnum - 1,
+ &leader_len, &leader_flags, do_comments);
+ } else {
+ is_not_par = true;
+ }
next_is_not_par = fmt_check_par(curwin->w_cursor.lnum
, &next_leader_len, &next_leader_flags, do_comments
);
@@ -4179,7 +4218,7 @@ format_lines(
* The last line to be formatted.
*/
if (count == 1 || curwin->w_cursor.lnum == curbuf->b_ml.ml_line_count) {
- next_is_not_par = TRUE;
+ next_is_not_par = true;
next_leader_len = 0;
next_leader_flags = NULL;
} else {
@@ -4190,7 +4229,7 @@ format_lines(
next_is_start_par =
(get_number_indent(curwin->w_cursor.lnum + 1) > 0);
}
- advance = TRUE;
+ advance = true;
is_end_par = (is_not_par || next_is_not_par || next_is_start_par);
if (!is_end_par && do_trail_white)
is_end_par = !ends_in_white(curwin->w_cursor.lnum);
@@ -4241,7 +4280,7 @@ format_lines(
leader_len, leader_flags,
next_leader_len, next_leader_flags)
)
- is_end_par = TRUE;
+ is_end_par = true;
/*
* If we have got to the end of a paragraph, or the line is
@@ -4278,9 +4317,9 @@ format_lines(
* end of the paragraph. */
if (line_count < 0)
break;
- first_par_line = TRUE;
+ first_par_line = true;
}
- force_format = FALSE;
+ force_format = false;
}
/*
@@ -4288,7 +4327,7 @@ format_lines(
* first delete the leader from the second line.
*/
if (!is_end_par) {
- advance = FALSE;
+ advance = false;
curwin->w_cursor.lnum++;
curwin->w_cursor.col = 0;
if (line_count < 0 && u_save_cursor() == FAIL)
@@ -4311,12 +4350,13 @@ format_lines(
beep_flush();
break;
}
- first_par_line = FALSE;
- /* If the line is getting long, format it next time */
- if (STRLEN(get_cursor_line_ptr()) > (size_t)max_len)
- force_format = TRUE;
- else
- force_format = FALSE;
+ first_par_line = false;
+ // If the line is getting long, format it next time
+ if (STRLEN(get_cursor_line_ptr()) > (size_t)max_len) {
+ force_format = true;
+ } else {
+ force_format = false;
+ }
}
}
line_breakcheck();
@@ -4377,11 +4417,10 @@ static int fmt_check_par(linenr_T lnum, int *leader_len, char_u **leader_flags,
int paragraph_start(linenr_T lnum)
{
char_u *p;
- int leader_len = 0; /* leader len of current line */
- char_u *leader_flags = NULL; /* flags for leader of current line */
- int next_leader_len = 0; /* leader len of next line */
- char_u *next_leader_flags = NULL; /* flags for leader of next line */
- int do_comments; /* format comments */
+ int leader_len = 0; // leader len of current line
+ char_u *leader_flags = NULL; // flags for leader of current line
+ int next_leader_len = 0; // leader len of next line
+ char_u *next_leader_flags = NULL; // flags for leader of next line
if (lnum <= 1)
return TRUE; /* start of the file */
@@ -4390,7 +4429,7 @@ int paragraph_start(linenr_T lnum)
if (*p == NUL)
return TRUE; /* after empty line */
- do_comments = has_format_option(FO_Q_COMS);
+ const bool do_comments = has_format_option(FO_Q_COMS); // format comments
if (fmt_check_par(lnum - 1, &leader_len, &leader_flags, do_comments)) {
return true; // after non-paragraph line
}
@@ -5897,7 +5936,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet)
const char regname = (char)name;
tv_list_append_string(args, &regname, 1);
- typval_T result = eval_call_provider("clipboard", "get", args);
+ typval_T result = eval_call_provider("clipboard", "get", args, false);
if (result.v_type != VAR_LIST) {
if (result.v_type == VAR_NUMBER && result.vval.v_number == 0) {
@@ -6045,7 +6084,7 @@ static void set_clipboard(int name, yankreg_T *reg)
tv_list_append_string(args, &regtype, 1); // -V614
tv_list_append_string(args, ((char[]) { (char)name }), 1);
- (void)eval_call_provider("clipboard", "set", args);
+ (void)eval_call_provider("clipboard", "set", args, true);
}
/// Avoid slow things (clipboard) during batch operations (while/for-loops).
diff --git a/src/nvim/option.c b/src/nvim/option.c
index d789ad3587..fcc051ef1a 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -80,6 +80,7 @@
#ifdef WIN32
# include "nvim/os/pty_conpty_win.h"
#endif
+#include "nvim/lua/executor.h"
#include "nvim/api/private/helpers.h"
#include "nvim/os/input.h"
#include "nvim/os/lang.h"
@@ -173,6 +174,7 @@ static char_u *p_syn;
static char_u *p_spc;
static char_u *p_spf;
static char_u *p_spl;
+static char_u *p_spo;
static long p_ts;
static long p_tw;
static int p_udf;
@@ -310,11 +312,14 @@ static char *(p_fdm_values[]) = { "manual", "expr", "marker", "indent",
static char *(p_fcl_values[]) = { "all", NULL };
static char *(p_cot_values[]) = { "menu", "menuone", "longest", "preview",
"noinsert", "noselect", NULL };
+#ifdef BACKSLASH_IN_FILENAME
+static char *(p_csl_values[]) = { "slash", "backslash", NULL };
+#endif
static char *(p_icm_values[]) = { "nosplit", "split", NULL };
static char *(p_scl_values[]) = { "yes", "no", "auto", "auto:1", "auto:2",
"auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9",
"yes:1", "yes:2", "yes:3", "yes:4", "yes:5", "yes:6", "yes:7", "yes:8",
- "yes:9", NULL };
+ "yes:9", "number", NULL };
static char *(p_fdc_values[]) = { "auto:1", "auto:2",
"auto:3", "auto:4", "auto:5", "auto:6", "auto:7", "auto:8", "auto:9",
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", NULL };
@@ -346,22 +351,23 @@ static char *strcpy_comma_escaped(char *dest, const char *src, const size_t len)
return &dest[len + shift];
}
-/// Compute length of a colon-separated value, doubled and with some suffixes
+/// Compute length of a ENV_SEPCHAR-separated value, doubled and with some
+/// suffixes
///
-/// @param[in] val Colon-separated array value.
+/// @param[in] val ENV_SEPCHAR-separated array value.
/// @param[in] common_suf_len Length of the common suffix which is appended to
/// each item in the array, twice.
/// @param[in] single_suf_len Length of the suffix which is appended to each
/// item in the array once.
///
-/// @return Length of the comma-separated string array that contains each item
-/// in the original array twice with suffixes with given length
+/// @return Length of the ENV_SEPCHAR-separated string array that contains each
+/// item in the original array twice with suffixes with given length
/// (common_suf is present after each new item, single_suf is present
/// after half of the new items) and with commas after each item, commas
/// inside the values are escaped.
-static inline size_t compute_double_colon_len(const char *const val,
- const size_t common_suf_len,
- const size_t single_suf_len)
+static inline size_t compute_double_env_sep_len(const char *const val,
+ const size_t common_suf_len,
+ const size_t single_suf_len)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
{
if (val == NULL || *val == NUL) {
@@ -372,7 +378,7 @@ static inline size_t compute_double_colon_len(const char *const val,
do {
size_t dir_len;
const char *dir;
- iter = vim_env_iter(':', val, iter, &dir, &dir_len);
+ iter = vim_env_iter(ENV_SEPCHAR, val, iter, &dir, &dir_len);
if (dir != NULL && dir_len > 0) {
ret += ((dir_len + memcnt(dir, ',', dir_len) + common_suf_len
+ !after_pathsep(dir, dir + dir_len)) * 2
@@ -384,13 +390,13 @@ static inline size_t compute_double_colon_len(const char *const val,
#define NVIM_SIZE (sizeof("nvim") - 1)
-/// Add directories to a comma-separated array from a colon-separated one
+/// Add directories to a ENV_SEPCHAR-separated array from a colon-separated one
///
/// Commas are escaped in process. To each item PATHSEP "nvim" is appended in
/// addition to suf1 and suf2.
///
/// @param[in,out] dest Destination comma-separated array.
-/// @param[in] val Source colon-separated array.
+/// @param[in] val Source ENV_SEPCHAR-separated array.
/// @param[in] suf1 If not NULL, suffix appended to destination. Prior to it
/// directory separator is appended. Suffix must not contain
/// commas.
@@ -403,10 +409,10 @@ static inline size_t compute_double_colon_len(const char *const val,
/// Otherwise in reverse.
///
/// @return (dest + appended_characters_length)
-static inline char *add_colon_dirs(char *dest, const char *const val,
- const char *const suf1, const size_t len1,
- const char *const suf2, const size_t len2,
- const bool forward)
+static inline char *add_env_sep_dirs(char *dest, const char *const val,
+ const char *const suf1, const size_t len1,
+ const char *const suf2, const size_t len2,
+ const bool forward)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1)
{
if (val == NULL || *val == NUL) {
@@ -416,8 +422,8 @@ static inline char *add_colon_dirs(char *dest, const char *const val,
do {
size_t dir_len;
const char *dir;
- iter = (forward ? vim_env_iter : vim_env_iter_rev)(':', val, iter, &dir,
- &dir_len);
+ iter = (forward ? vim_env_iter : vim_env_iter_rev)(ENV_SEPCHAR, val, iter,
+ &dir, &dir_len);
if (dir != NULL && dir_len > 0) {
dest = strcpy_comma_escaped(dest, dir, dir_len);
if (!after_pathsep(dest - 1, dest)) {
@@ -580,10 +586,11 @@ static void set_runtimepath_default(bool clean_arg)
rtp_size += libdir_len + memcnt(libdir, ',', libdir_len) + 1;
}
}
- rtp_size += compute_double_colon_len(data_dirs, NVIM_SIZE + 1 + SITE_SIZE + 1,
- AFTER_SIZE + 1);
- rtp_size += compute_double_colon_len(config_dirs, NVIM_SIZE + 1,
- AFTER_SIZE + 1);
+ rtp_size += compute_double_env_sep_len(data_dirs,
+ NVIM_SIZE + 1 + SITE_SIZE + 1,
+ AFTER_SIZE + 1);
+ rtp_size += compute_double_env_sep_len(config_dirs, NVIM_SIZE + 1,
+ AFTER_SIZE + 1);
if (rtp_size == 0) {
return;
}
@@ -591,20 +598,20 @@ static void set_runtimepath_default(bool clean_arg)
char *rtp_cur = rtp;
rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome,
NULL, 0, NULL, 0);
- rtp_cur = add_colon_dirs(rtp_cur, config_dirs, NULL, 0, NULL, 0, true);
+ rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, NULL, 0, NULL, 0, true);
rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome,
"site", SITE_SIZE, NULL, 0);
- rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, NULL, 0,
- true);
+ rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE, NULL, 0,
+ true);
rtp_cur = add_dir(rtp_cur, vimruntime, vimruntime_len, kXDGNone,
NULL, 0, NULL, 0);
rtp_cur = add_dir(rtp_cur, libdir, libdir_len, kXDGNone, NULL, 0, NULL, 0);
- rtp_cur = add_colon_dirs(rtp_cur, data_dirs, "site", SITE_SIZE,
- "after", AFTER_SIZE, false);
+ rtp_cur = add_env_sep_dirs(rtp_cur, data_dirs, "site", SITE_SIZE,
+ "after", AFTER_SIZE, false);
rtp_cur = add_dir(rtp_cur, data_home, data_len, kXDGDataHome,
"site", SITE_SIZE, "after", AFTER_SIZE);
- rtp_cur = add_colon_dirs(rtp_cur, config_dirs, "after", AFTER_SIZE, NULL, 0,
- false);
+ rtp_cur = add_env_sep_dirs(rtp_cur, config_dirs, "after", AFTER_SIZE, NULL, 0,
+ false);
rtp_cur = add_dir(rtp_cur, config_home, config_len, kXDGConfigHome,
"after", AFTER_SIZE, NULL, 0);
// Strip trailing comma.
@@ -1354,11 +1361,11 @@ int do_set(
// Disallow changing some options from modelines.
if (opt_flags & OPT_MODELINE) {
if (flags & (P_SECURE | P_NO_ML)) {
- errmsg = (char_u *)_("E520: Not allowed in a modeline");
+ errmsg = (char_u *)N_("E520: Not allowed in a modeline");
goto skip;
}
if ((flags & P_MLE) && !p_mle) {
- errmsg = (char_u *)_(
+ errmsg = (char_u *)N_(
"E992: Not allowed in a modeline when 'modelineexpr' is off");
goto skip;
}
@@ -1375,7 +1382,7 @@ int do_set(
// Disallow changing some options in the sandbox
if (sandbox != 0 && (flags & P_SECURE)) {
- errmsg = (char_u *)_(e_sandbox);
+ errmsg = e_sandbox;
goto skip;
}
@@ -1711,6 +1718,7 @@ int do_set(
#ifdef BACKSLASH_IN_FILENAME
&& !((flags & P_EXPAND)
&& vim_isfilec(arg[1])
+ && !ascii_iswhite(arg[1])
&& (arg[1] != '\\'
|| (s == newval
&& arg[2] != '\\')))
@@ -2281,6 +2289,7 @@ void check_buf_options(buf_T *buf)
check_string_option(&buf->b_s.b_p_spc);
check_string_option(&buf->b_s.b_p_spf);
check_string_option(&buf->b_s.b_p_spl);
+ check_string_option(&buf->b_s.b_p_spo);
check_string_option(&buf->b_p_sua);
check_string_option(&buf->b_p_cink);
check_string_option(&buf->b_p_cino);
@@ -2557,7 +2566,7 @@ static bool valid_spellfile(const char_u *val)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
for (const char_u *s = val; *s != NUL; s++) {
- if (!vim_isfilec(*s) && *s != ',') {
+ if (!vim_isfilec(*s) && *s != ',' && *s != ' ') {
return false;
}
}
@@ -3086,6 +3095,10 @@ ambw_end:
} else if (varp == &(curwin->w_s->b_p_spc)) {
// When 'spellcapcheck' is set compile the regexp program.
errmsg = compile_cap_prog(curwin->w_s);
+ } else if (varp == &(curwin->w_s->b_p_spo)) { // 'spelloptions'
+ if (**varp != NUL && STRCMP("camel", *varp) != 0) {
+ errmsg = e_invarg;
+ }
} else if (varp == &p_sps) { // 'spellsuggest'
if (spell_check_sps() != OK) {
errmsg = e_invarg;
@@ -3108,7 +3121,7 @@ ambw_end:
} else {
if (curwin->w_status_height) {
curwin->w_redr_status = true;
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
}
curbuf->b_help = (curbuf->b_p_bt[0] == 'h');
redraw_titles();
@@ -3178,11 +3191,25 @@ ambw_end:
} else {
completeopt_was_set();
}
+#ifdef BACKSLASH_IN_FILENAME
+ } else if (gvarp == &p_csl) { // 'completeslash'
+ if (check_opt_strings(p_csl, p_csl_values, false) != OK
+ || check_opt_strings(curbuf->b_p_csl, p_csl_values, false) != OK) {
+ errmsg = e_invarg;
+ }
+#endif
} else if (varp == &curwin->w_p_scl) {
// 'signcolumn'
if (check_opt_strings(*varp, p_scl_values, false) != OK) {
errmsg = e_invarg;
}
+ // When changing the 'signcolumn' to or from 'number', recompute the
+ // width of the number column if 'number' or 'relativenumber' is set.
+ if (((*oldval == 'n' && *(oldval + 1) == 'u')
+ || (*curwin->w_p_scl == 'n' && *(curwin->w_p_scl + 1) =='u'))
+ && (curwin->w_p_nu || curwin->w_p_rnu)) {
+ curwin->w_nrwidth_line_count = 0;
+ }
} else if (varp == &curwin->w_p_fdc || varp == &curwin->w_allbuf_opt.wo_fdc) {
// 'foldcolumn'
if (check_opt_strings(*varp, p_fdc_values, false) != OK) {
@@ -3417,6 +3444,7 @@ ambw_end:
// recursively, to avoid endless recurrence.
apply_autocmds(EVENT_SYNTAX, curbuf->b_p_syn, curbuf->b_fname,
value_changed || syn_recursive == 1, curbuf);
+ curbuf->b_flags |= BF_SYN_SET;
syn_recursive--;
} else if (varp == &(curbuf->b_p_ft)) {
// 'filetype' is set, trigger the FileType autocommand
@@ -3719,11 +3747,10 @@ static char_u *set_chars_option(win_T *wp, char_u **varp, bool set)
/// Return error message or NULL.
char_u *check_stl_option(char_u *s)
{
- int itemcnt = 0;
int groupdepth = 0;
static char_u errbuf[80];
- while (*s && itemcnt < STL_MAX_ITEM) {
+ while (*s) {
// Check for valid keys after % sequences
while (*s && *s != '%') {
s++;
@@ -3732,9 +3759,6 @@ char_u *check_stl_option(char_u *s)
break;
}
s++;
- if (*s != '%' && *s != ')') {
- itemcnt++;
- }
if (*s == '%' || *s == STL_TRUNCMARK || *s == STL_SEPARATE) {
s++;
continue;
@@ -3776,9 +3800,6 @@ char_u *check_stl_option(char_u *s)
}
}
}
- if (itemcnt >= STL_MAX_ITEM) {
- return (char_u *)N_("E541: too many items");
- }
if (groupdepth != 0) {
return (char_u *)N_("E542: unbalanced groups");
}
@@ -4670,7 +4691,7 @@ static void check_redraw(uint32_t flags)
redraw_curbuf_later(NOT_VALID);
}
if (flags & P_RWINONLY) {
- redraw_later(NOT_VALID);
+ redraw_later(curwin, NOT_VALID);
}
if (doclear) {
redraw_all_later(CLEAR);
@@ -5684,12 +5705,12 @@ void unset_global_local_option(char *name, void *from)
case PV_LCS:
clear_string_option(&((win_T *)from)->w_p_lcs);
set_chars_option((win_T *)from, &((win_T *)from)->w_p_lcs, true);
- redraw_win_later((win_T *)from, NOT_VALID);
+ redraw_later((win_T *)from, NOT_VALID);
break;
case PV_FCS:
clear_string_option(&((win_T *)from)->w_p_fcs);
set_chars_option((win_T *)from, &((win_T *)from)->w_p_fcs, true);
- redraw_win_later((win_T *)from, NOT_VALID);
+ redraw_later((win_T *)from, NOT_VALID);
break;
}
}
@@ -5844,6 +5865,9 @@ static char_u *get_varp(vimoption_T *p)
case PV_COM: return (char_u *)&(curbuf->b_p_com);
case PV_CMS: return (char_u *)&(curbuf->b_p_cms);
case PV_CPT: return (char_u *)&(curbuf->b_p_cpt);
+# ifdef BACKSLASH_IN_FILENAME
+ case PV_CSL: return (char_u *)&(curbuf->b_p_csl);
+# endif
case PV_CFU: return (char_u *)&(curbuf->b_p_cfu);
case PV_OFU: return (char_u *)&(curbuf->b_p_ofu);
case PV_EOL: return (char_u *)&(curbuf->b_p_eol);
@@ -5881,6 +5905,7 @@ static char_u *get_varp(vimoption_T *p)
case PV_SPC: return (char_u *)&(curwin->w_s->b_p_spc);
case PV_SPF: return (char_u *)&(curwin->w_s->b_p_spf);
case PV_SPL: return (char_u *)&(curwin->w_s->b_p_spl);
+ case PV_SPO: return (char_u *)&(curwin->w_s->b_p_spo);
case PV_SW: return (char_u *)&(curbuf->b_p_sw);
case PV_TFU: return (char_u *)&(curbuf->b_p_tfu);
case PV_TS: return (char_u *)&(curbuf->b_p_ts);
@@ -6130,6 +6155,9 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_inf = p_inf;
buf->b_p_swf = cmdmod.noswapfile ? false : p_swf;
buf->b_p_cpt = vim_strsave(p_cpt);
+# ifdef BACKSLASH_IN_FILENAME
+ buf->b_p_csl = vim_strsave(p_csl);
+# endif
buf->b_p_cfu = vim_strsave(p_cfu);
buf->b_p_ofu = vim_strsave(p_ofu);
buf->b_p_tfu = vim_strsave(p_tfu);
@@ -6160,6 +6188,7 @@ void buf_copy_options(buf_T *buf, int flags)
(void)compile_cap_prog(&buf->b_s);
buf->b_s.b_p_spf = vim_strsave(p_spf);
buf->b_s.b_p_spl = vim_strsave(p_spl);
+ buf->b_s.b_p_spo = vim_strsave(p_spo);
buf->b_p_inde = vim_strsave(p_inde);
buf->b_p_indk = vim_strsave(p_indk);
buf->b_p_fp = empty_option;
@@ -6779,7 +6808,8 @@ static void langmap_set(void)
/// Return true if format option 'x' is in effect.
/// Take care of no formatting when 'paste' is set.
-int has_format_option(int x)
+bool has_format_option(int x)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
if (p_paste) {
return false;
@@ -7246,7 +7276,8 @@ unsigned int get_bkc_value(buf_T *buf)
}
/// Return the current end-of-line type: EOL_DOS, EOL_UNIX or EOL_MAC.
-int get_fileformat(buf_T *buf)
+int get_fileformat(const buf_T *buf)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
int c = *buf->b_p_ff;
@@ -7397,7 +7428,10 @@ int win_signcol_count(win_T *wp)
int maximum = 1, needed_signcols;
const char *scl = (const char *)wp->w_p_scl;
- if (*scl == 'n') {
+ // Note: It checks "no" or "number" in 'signcolumn' option
+ if (*scl == 'n'
+ && (*(scl + 1) == 'o' || (*(scl + 1) == 'u'
+ && (wp->w_p_nu || wp->w_p_rnu)))) {
return 0;
}
needed_signcols = buf_signcols(wp->w_buffer);
diff --git a/src/nvim/option_defs.h b/src/nvim/option_defs.h
index ecaa941082..af0ea7f4a2 100644
--- a/src/nvim/option_defs.h
+++ b/src/nvim/option_defs.h
@@ -77,12 +77,13 @@
#define FO_ONE_LETTER '1'
#define FO_WHITE_PAR 'w' // trailing white space continues paragr.
#define FO_AUTO 'a' // automatic formatting
+#define FO_RIGOROUS_TW ']' // respect textwidth rigorously
#define FO_REMOVE_COMS 'j' // remove comment leaders when joining lines
#define FO_PERIOD_ABBR 'p' // don't break a single space after a period
#define DFLT_FO_VI "vt"
#define DFLT_FO_VIM "tcqj"
-#define FO_ALL "tcroq2vlb1mMBn,awjp" // for do_set()
+#define FO_ALL "tcroq2vlb1mMBn,aw]jp" // for do_set()
// characters for the p_cpo option:
#define CPO_ALTREAD 'a' // ":read" sets alternate file name
@@ -187,6 +188,7 @@ enum {
#define GO_ASELML 'A' // autoselect modeless selection
#define GO_BOT 'b' // use bottom scrollbar
#define GO_CONDIALOG 'c' // use console dialog
+#define GO_DARKTHEME 'd' // use dark theme variant
#define GO_TABLINE 'e' // may show tabline
#define GO_FORG 'f' // start GUI in foreground
#define GO_GREY 'g' // use grey menu items
@@ -204,7 +206,7 @@ enum {
#define GO_FOOTER 'F' // add footer
#define GO_VERTICAL 'v' // arrange dialog buttons vertically
#define GO_KEEPWINSIZE 'k' // keep GUI window size
-#define GO_ALL "aAbcefFghilmMprTvk" // all possible flags for 'go'
+#define GO_ALL "aAbcdefFghilmMprTvk" // all possible flags for 'go'
// flags for 'comments' option
#define COM_NEST 'n' // comments strings nest
@@ -372,6 +374,9 @@ EXTERN long p_columns; // 'columns'
EXTERN int p_confirm; // 'confirm'
EXTERN int p_cp; // 'compatible'
EXTERN char_u *p_cot; // 'completeopt'
+# ifdef BACKSLASH_IN_FILENAME
+EXTERN char_u *p_csl; // 'completeslash'
+# endif
EXTERN long p_pb; // 'pumblend'
EXTERN long p_ph; // 'pumheight'
EXTERN long p_pw; // 'pumwidth'
@@ -453,7 +458,6 @@ 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'
@@ -513,6 +517,7 @@ EXTERN long p_mle; // 'modelineexpr'
EXTERN long p_mls; // 'modelines'
EXTERN char_u *p_mouse; // 'mouse'
EXTERN char_u *p_mousem; // 'mousemodel'
+EXTERN long p_mousef; // 'mousefocus'
EXTERN long p_mouset; // 'mousetime'
EXTERN int p_more; // 'more'
EXTERN char_u *p_opfunc; // 'operatorfunc'
@@ -743,6 +748,7 @@ enum {
, BV_CPT
, BV_DICT
, BV_TSR
+ , BV_CSL
, BV_CFU
, BV_DEF
, BV_INC
@@ -787,6 +793,7 @@ enum {
, BV_SPC
, BV_SPF
, BV_SPL
+ , BV_SPO
, BV_STS
, BV_SUA
, BV_SW
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index e7c1a3fe88..64a09dc2b4 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -1,6 +1,7 @@
-- {
-- {
-- full_name='aleph', abbreviation='al',
+-- short_desc="ASCII code of the letter Aleph (Hebrew)",
-- varname='p_aleph', pv_name=nil,
-- type='number', list=nil, scope={'global'},
-- deny_duplicates=nil,
@@ -52,6 +53,7 @@ return {
options={
{
full_name='aleph', abbreviation='al',
+ short_desc=N_("ASCII code of the letter Aleph (Hebrew)"),
type='number', scope={'global'},
vi_def=true,
redraw={'curswant'},
@@ -60,6 +62,7 @@ return {
},
{
full_name='arabic', abbreviation='arab',
+ short_desc=N_("Arabic as a default second language"),
type='bool', scope={'window'},
vi_def=true,
vim=true,
@@ -68,6 +71,7 @@ return {
},
{
full_name='arabicshape', abbreviation='arshape',
+ short_desc=N_("do shaping for Arabic characters"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -78,6 +82,7 @@ return {
},
{
full_name='allowrevins', abbreviation='ari',
+ short_desc=N_("allow CTRL-_ in Insert and Command-line mode"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -86,6 +91,7 @@ return {
},
{
full_name='ambiwidth', abbreviation='ambw',
+ short_desc=N_("what to do with Unicode chars of ambiguous width"),
type='string', scope={'global'},
vi_def=true,
redraw={'all_windows', 'ui_option'},
@@ -94,6 +100,7 @@ return {
},
{
full_name='autochdir', abbreviation='acd',
+ short_desc=N_("change directory to the file in the current window"),
type='bool', scope={'global'},
vi_def=true,
varname='p_acd',
@@ -101,18 +108,21 @@ return {
},
{
full_name='autoindent', abbreviation='ai',
+ short_desc=N_("take indent for new line from previous line"),
type='bool', scope={'buffer'},
varname='p_ai',
defaults={if_true={vi=false, vim=true}}
},
{
full_name='autoread', abbreviation='ar',
+ short_desc=N_("autom. read file when changed outside of Vim"),
type='bool', scope={'global', 'buffer'},
varname='p_ar',
defaults={if_true={vi=false, vim=true}}
},
{
full_name='autowrite', abbreviation='aw',
+ short_desc=N_("automatically write file if changed"),
type='bool', scope={'global'},
vi_def=true,
varname='p_aw',
@@ -120,6 +130,7 @@ return {
},
{
full_name='autowriteall', abbreviation='awa',
+ short_desc=N_("as 'autowrite', but works with more commands"),
type='bool', scope={'global'},
vi_def=true,
varname='p_awa',
@@ -127,6 +138,7 @@ return {
},
{
full_name='background', abbreviation='bg',
+ short_desc=N_("\"dark\" or \"light\", used for highlight colors"),
type='string', scope={'global'},
vim=true,
redraw={'all_windows'},
@@ -135,6 +147,7 @@ return {
},
{
full_name='backspace', abbreviation='bs',
+ short_desc=N_("how backspace works at start of line"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vim=true,
@@ -143,6 +156,7 @@ return {
},
{
full_name='backup', abbreviation='bk',
+ short_desc=N_("keep backup file after overwriting a file"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -151,6 +165,7 @@ return {
},
{
full_name='backupcopy', abbreviation='bkc',
+ short_desc=N_("make backup as a copy, don't rename the file"),
type='string', list='onecomma', scope={'global', 'buffer'},
deny_duplicates=true,
vim=true,
@@ -163,6 +178,7 @@ return {
},
{
full_name='backupdir', abbreviation='bdir',
+ short_desc=N_("list of directories for the backup file"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
secure=true,
@@ -173,6 +189,7 @@ return {
},
{
full_name='backupext', abbreviation='bex',
+ short_desc=N_("extension used for the backup file"),
type='string', scope={'global'},
normal_fname_chars=true,
vi_def=true,
@@ -181,6 +198,7 @@ return {
},
{
full_name='backupskip', abbreviation='bsk',
+ short_desc=N_("no backup for files that match these patterns"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -189,6 +207,7 @@ return {
},
{
full_name='belloff', abbreviation='bo',
+ short_desc=N_("do not ring the bell for these reasons"),
type='string', list='comma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -197,6 +216,7 @@ return {
},
{
full_name='binary', abbreviation='bin',
+ short_desc=N_("read/write/edit file in binary mode"),
type='bool', scope={'buffer'},
vi_def=true,
redraw={'statuslines'},
@@ -205,6 +225,7 @@ return {
},
{
full_name='bomb',
+ short_desc=N_("a Byte Order Mark to the file"),
type='bool', scope={'buffer'},
no_mkrc=true,
vi_def=true,
@@ -214,6 +235,7 @@ return {
},
{
full_name='breakat', abbreviation='brk',
+ short_desc=N_("characters that may cause a line break"),
type='string', list='flags', scope={'global'},
vi_def=true,
redraw={'all_windows'},
@@ -222,6 +244,7 @@ return {
},
{
full_name='breakindent', abbreviation='bri',
+ short_desc=N_("wrapped line repeats indent"),
type='bool', scope={'window'},
vi_def=true,
vim=true,
@@ -230,6 +253,7 @@ return {
},
{
full_name='breakindentopt', abbreviation='briopt',
+ short_desc=N_("settings for 'breakindent'"),
type='string', list='onecomma', scope={'window'},
deny_duplicates=true,
vi_def=true,
@@ -239,12 +263,14 @@ return {
},
{
full_name='browsedir', abbreviation='bsdir',
+ short_desc=N_("which directory to start browsing in"),
type='string', scope={'global'},
vi_def=true,
enable_if=false,
},
{
full_name='bufhidden', abbreviation='bh',
+ short_desc=N_("what to do when buffer is no longer in window"),
type='string', scope={'buffer'},
noglob=true,
vi_def=true,
@@ -254,6 +280,7 @@ return {
},
{
full_name='buflisted', abbreviation='bl',
+ short_desc=N_("whether the buffer shows up in the buffer list"),
type='bool', scope={'buffer'},
noglob=true,
vi_def=true,
@@ -262,6 +289,7 @@ return {
},
{
full_name='buftype', abbreviation='bt',
+ short_desc=N_("special type of buffer"),
type='string', scope={'buffer'},
noglob=true,
vi_def=true,
@@ -271,6 +299,7 @@ return {
},
{
full_name='casemap', abbreviation='cmp',
+ short_desc=N_("specifies how case of letters is changed"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -279,6 +308,7 @@ return {
},
{
full_name='cdpath', abbreviation='cd',
+ short_desc=N_("list of directories searched with \":cd\""),
type='string', list='comma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -289,12 +319,14 @@ return {
},
{
full_name='cedit',
+ short_desc=N_("used to open the command-line window"),
type='string', scope={'global'},
varname='p_cedit',
defaults={if_true={vi="", vim=macros('CTRL_F_STR')}}
},
{
full_name='channel',
+ short_desc=N_("Channel connected to the buffer"),
type='number', scope={'buffer'},
no_mkrc=true,
nodefault=true,
@@ -303,6 +335,7 @@ return {
},
{
full_name='charconvert', abbreviation='ccv',
+ short_desc=N_("expression for character encoding conversion"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -311,6 +344,7 @@ return {
},
{
full_name='cindent', abbreviation='cin',
+ short_desc=N_("do C program indenting"),
type='bool', scope={'buffer'},
vi_def=true,
vim=true,
@@ -319,6 +353,7 @@ return {
},
{
full_name='cinkeys', abbreviation='cink',
+ short_desc=N_("keys that trigger indent when 'cindent' is set"),
type='string', list='onecomma', scope={'buffer'},
deny_duplicates=true,
vi_def=true,
@@ -328,6 +363,7 @@ return {
},
{
full_name='cinoptions', abbreviation='cino',
+ short_desc=N_("how to do indenting when 'cindent' is set"),
type='string', list='onecomma', scope={'buffer'},
deny_duplicates=true,
vi_def=true,
@@ -337,6 +373,7 @@ return {
},
{
full_name='cinwords', abbreviation='cinw',
+ short_desc=N_("words where 'si' and 'cin' add an indent"),
type='string', list='onecomma', scope={'buffer'},
deny_duplicates=true,
vi_def=true,
@@ -346,6 +383,7 @@ return {
},
{
full_name='clipboard', abbreviation='cb',
+ short_desc=N_("use the clipboard as the unnamed register"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -354,6 +392,7 @@ return {
},
{
full_name='cmdheight', abbreviation='ch',
+ short_desc=N_("number of lines to use for the command-line"),
type='number', scope={'global'},
vi_def=true,
redraw={'all_windows'},
@@ -362,6 +401,7 @@ return {
},
{
full_name='cmdwinheight', abbreviation='cwh',
+ short_desc=N_("height of the command-line window"),
type='number', scope={'global'},
vi_def=true,
varname='p_cwh',
@@ -369,6 +409,7 @@ return {
},
{
full_name='colorcolumn', abbreviation='cc',
+ short_desc=N_("columns to highlight"),
type='string', list='onecomma', scope={'window'},
deny_duplicates=true,
vi_def=true,
@@ -377,6 +418,7 @@ return {
},
{
full_name='columns', abbreviation='co',
+ short_desc=N_("number of columns in the display"),
type='number', scope={'global'},
no_mkrc=true,
vi_def=true,
@@ -386,6 +428,7 @@ return {
},
{
full_name='comments', abbreviation='com',
+ short_desc=N_("patterns that can start a comment line"),
type='string', list='onecomma', scope={'buffer'},
deny_duplicates=true,
vi_def=true,
@@ -396,6 +439,7 @@ return {
},
{
full_name='commentstring', abbreviation='cms',
+ short_desc=N_("template for comments; used for fold marker"),
type='string', scope={'buffer'},
vi_def=true,
alloced=true,
@@ -405,6 +449,7 @@ return {
},
{
full_name='compatible', abbreviation='cp',
+ short_desc=N_("No description"),
type='bool', scope={'global'},
redraw={'all_windows'},
varname='p_force_off',
@@ -414,6 +459,7 @@ return {
},
{
full_name='complete', abbreviation='cpt',
+ short_desc=N_("specify how Insert mode completion works"),
type='string', list='onecomma', scope={'buffer'},
deny_duplicates=true,
alloced=true,
@@ -422,6 +468,7 @@ return {
},
{
full_name='concealcursor', abbreviation='cocu',
+ short_desc=N_("whether concealable text is hidden in cursor line"),
type='string', scope={'window'},
vi_def=true,
alloced=true,
@@ -430,6 +477,7 @@ return {
},
{
full_name='conceallevel', abbreviation='cole',
+ short_desc=N_("whether concealable text is shown or hidden"),
type='number', scope={'window'},
vi_def=true,
redraw={'current_window'},
@@ -437,6 +485,7 @@ return {
},
{
full_name='completefunc', abbreviation='cfu',
+ short_desc=N_("function to be used for Insert mode completion"),
type='string', scope={'buffer'},
secure=true,
vi_def=true,
@@ -446,6 +495,7 @@ return {
},
{
full_name='completeopt', abbreviation='cot',
+ short_desc=N_("options for Insert mode completion"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -453,7 +503,17 @@ return {
defaults={if_true={vi="menu,preview"}}
},
{
+ full_name='completeslash', abbreviation='csl',
+ type='string', scope={'buffer'},
+ vi_def=true,
+ vim=true,
+ varname='p_csl',
+ enable_if='BACKSLASH_IN_FILENAME',
+ defaults={if_true={vi=""}}
+ },
+ {
full_name='confirm', abbreviation='cf',
+ short_desc=N_("ask what to do about unsaved/read-only files"),
type='bool', scope={'global'},
vi_def=true,
varname='p_confirm',
@@ -461,6 +521,7 @@ return {
},
{
full_name='copyindent', abbreviation='ci',
+ short_desc=N_("make 'autoindent' use existing indent structure"),
type='bool', scope={'buffer'},
vi_def=true,
vim=true,
@@ -469,6 +530,7 @@ return {
},
{
full_name='cpoptions', abbreviation='cpo',
+ short_desc=N_("flags for Vi-compatible behavior"),
type='string', list='flags', scope={'global'},
vim=true,
redraw={'all_windows'},
@@ -477,6 +539,7 @@ return {
},
{
full_name='cscopepathcomp', abbreviation='cspc',
+ short_desc=N_("how many components of the path to show"),
type='number', scope={'global'},
vi_def=true,
vim=true,
@@ -485,6 +548,7 @@ return {
},
{
full_name='cscopeprg', abbreviation='csprg',
+ short_desc=N_("command to execute cscope"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -494,6 +558,7 @@ return {
},
{
full_name='cscopequickfix', abbreviation='csqf',
+ short_desc=N_("use quickfix window for cscope results"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -502,6 +567,7 @@ return {
},
{
full_name='cscoperelative', abbreviation='csre',
+ short_desc=N_("Use cscope.out path basename as prefix"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -510,6 +576,7 @@ return {
},
{
full_name='cscopetag', abbreviation='cst',
+ short_desc=N_("use cscope for tag commands"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -518,6 +585,7 @@ return {
},
{
full_name='cscopetagorder', abbreviation='csto',
+ short_desc=N_("determines \":cstag\" search order"),
type='number', scope={'global'},
vi_def=true,
vim=true,
@@ -526,6 +594,7 @@ return {
},
{
full_name='cscopeverbose', abbreviation='csverb',
+ short_desc=N_("give messages when adding a cscope database"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -534,6 +603,7 @@ return {
},
{
full_name='cursorbind', abbreviation='crb',
+ short_desc=N_("move cursor in window as it moves in other windows"),
type='bool', scope={'window'},
vi_def=true,
pv_name='p_crbind',
@@ -541,6 +611,7 @@ return {
},
{
full_name='cursorcolumn', abbreviation='cuc',
+ short_desc=N_("highlight the screen column of the cursor"),
type='bool', scope={'window'},
vi_def=true,
redraw={'current_window_only'},
@@ -548,6 +619,7 @@ return {
},
{
full_name='cursorline', abbreviation='cul',
+ short_desc=N_("highlight the screen line of the cursor"),
type='bool', scope={'window'},
vi_def=true,
redraw={'current_window_only'},
@@ -555,6 +627,7 @@ return {
},
{
full_name='debug',
+ short_desc=N_("to \"msg\" to see all error messages"),
type='string', scope={'global'},
vi_def=true,
varname='p_debug',
@@ -562,6 +635,7 @@ return {
},
{
full_name='define', abbreviation='def',
+ short_desc=N_("pattern to be used to find a macro definition"),
type='string', scope={'global', 'buffer'},
vi_def=true,
alloced=true,
@@ -571,6 +645,7 @@ return {
},
{
full_name='delcombine', abbreviation='deco',
+ short_desc=N_("delete combining characters on their own"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -579,6 +654,7 @@ return {
},
{
full_name='dictionary', abbreviation='dict',
+ short_desc=N_("list of file names used for keyword completion"),
type='string', list='onecomma', scope={'global', 'buffer'},
deny_duplicates=true,
normal_dname_chars=true,
@@ -589,6 +665,7 @@ return {
},
{
full_name='diff',
+ short_desc=N_("diff mode for the current window"),
type='bool', scope={'window'},
noglob=true,
vi_def=true,
@@ -597,6 +674,7 @@ return {
},
{
full_name='diffexpr', abbreviation='dex',
+ short_desc=N_("expression used to obtain a diff file"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -606,6 +684,7 @@ return {
},
{
full_name='diffopt', abbreviation='dip',
+ short_desc=N_("options for using diff mode"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -616,6 +695,7 @@ return {
},
{
full_name='digraph', abbreviation='dg',
+ short_desc=N_("enable the entering of digraphs in Insert mode"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -624,6 +704,7 @@ return {
},
{
full_name='directory', abbreviation='dir',
+ short_desc=N_("list of directory names for the swap file"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
secure=true,
@@ -634,6 +715,7 @@ return {
},
{
full_name='display', abbreviation='dy',
+ short_desc=N_("list of flags for how to display text"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vim=true,
@@ -643,6 +725,7 @@ return {
},
{
full_name='eadirection', abbreviation='ead',
+ short_desc=N_("in which direction 'equalalways' works"),
type='string', scope={'global'},
vi_def=true,
varname='p_ead',
@@ -650,6 +733,7 @@ return {
},
{
full_name='edcompatible', abbreviation='ed',
+ short_desc=N_("No description"),
type='bool', scope={'global'},
vi_def=true,
varname='p_force_off',
@@ -657,6 +741,7 @@ return {
},
{
full_name='emoji', abbreviation='emo',
+ short_desc=N_("No description"),
type='bool', scope={'global'},
vi_def=true,
redraw={'all_windows', 'ui_option'},
@@ -665,6 +750,7 @@ return {
},
{
full_name='encoding', abbreviation='enc',
+ short_desc=N_("encoding used internally"),
type='string', scope={'global'},
deny_in_modelines=true,
vi_def=true,
@@ -673,6 +759,7 @@ return {
},
{
full_name='endofline', abbreviation='eol',
+ short_desc=N_("write <EOL> for last line in file"),
type='bool', scope={'buffer'},
no_mkrc=true,
vi_def=true,
@@ -682,6 +769,7 @@ return {
},
{
full_name='equalalways', abbreviation='ea',
+ short_desc=N_("windows are automatically made the same size"),
type='bool', scope={'global'},
vi_def=true,
redraw={'all_windows'},
@@ -690,6 +778,7 @@ return {
},
{
full_name='equalprg', abbreviation='ep',
+ short_desc=N_("external program to use for \"=\" command"),
type='string', scope={'global', 'buffer'},
secure=true,
vi_def=true,
@@ -699,6 +788,7 @@ return {
},
{
full_name='errorbells', abbreviation='eb',
+ short_desc=N_("ring the bell for error messages"),
type='bool', scope={'global'},
vi_def=true,
varname='p_eb',
@@ -706,6 +796,7 @@ return {
},
{
full_name='errorfile', abbreviation='ef',
+ short_desc=N_("name of the errorfile for the QuickFix mode"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -715,6 +806,7 @@ return {
},
{
full_name='errorformat', abbreviation='efm',
+ short_desc=N_("description of the lines in the error file"),
type='string', list='onecomma', scope={'global', 'buffer'},
deny_duplicates=true,
vi_def=true,
@@ -723,6 +815,7 @@ return {
},
{
full_name='eventignore', abbreviation='ei',
+ short_desc=N_("autocommand events that are ignored"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -731,6 +824,7 @@ return {
},
{
full_name='expandtab', abbreviation='et',
+ short_desc=N_("use spaces when <Tab> is inserted"),
type='bool', scope={'buffer'},
vi_def=true,
vim=true,
@@ -739,6 +833,7 @@ return {
},
{
full_name='exrc', abbreviation='ex',
+ short_desc=N_("read .nvimrc and .exrc in the current directory"),
type='bool', scope={'global'},
secure=true,
vi_def=true,
@@ -747,6 +842,7 @@ return {
},
{
full_name='fileencoding', abbreviation='fenc',
+ short_desc=N_("file encoding for multi-byte text"),
type='string', scope={'buffer'},
no_mkrc=true,
vi_def=true,
@@ -757,6 +853,7 @@ return {
},
{
full_name='fileencodings', abbreviation='fencs',
+ short_desc=N_("automatically detected character encodings"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -765,6 +862,7 @@ return {
},
{
full_name='fileformat', abbreviation='ff',
+ short_desc=N_("file format used for file I/O"),
type='string', scope={'buffer'},
no_mkrc=true,
vi_def=true,
@@ -775,6 +873,7 @@ return {
},
{
full_name='fileformats', abbreviation='ffs',
+ short_desc=N_("automatically detected values for 'fileformat'"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vim=true,
@@ -783,6 +882,7 @@ return {
},
{
full_name='fileignorecase', abbreviation='fic',
+ short_desc=N_("ignore case when using file names"),
type='bool', scope={'global'},
vi_def=true,
varname='p_fic',
@@ -794,6 +894,7 @@ return {
},
{
full_name='filetype', abbreviation='ft',
+ short_desc=N_("type of file, used for autocommands"),
type='string', scope={'buffer'},
noglob=true,
normal_fname_chars=true,
@@ -804,6 +905,7 @@ return {
},
{
full_name='fillchars', abbreviation='fcs',
+ short_desc=N_("characters to use for displaying special items"),
type='string', list='onecomma', scope={'global', 'window'},
deny_duplicates=true,
vi_def=true,
@@ -814,6 +916,7 @@ return {
},
{
full_name='fixendofline', abbreviation='fixeol',
+ short_desc=N_("make sure last line in file has <EOL>"),
type='bool', scope={'buffer'},
vi_def=true,
redraw={'statuslines'},
@@ -822,6 +925,7 @@ return {
},
{
full_name='foldclose', abbreviation='fcl',
+ short_desc=N_("close a fold when the cursor leaves it"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -831,6 +935,7 @@ return {
},
{
full_name='foldcolumn', abbreviation='fdc',
+ short_desc=N_("width of the column used to indicate folds"),
type='string', scope={'window'},
vi_def=true,
alloced=true,
@@ -839,6 +944,7 @@ return {
},
{
full_name='foldenable', abbreviation='fen',
+ short_desc=N_("set to display all folds open"),
type='bool', scope={'window'},
vi_def=true,
redraw={'current_window'},
@@ -846,6 +952,7 @@ return {
},
{
full_name='foldexpr', abbreviation='fde',
+ short_desc=N_("expression used when 'foldmethod' is \"expr\""),
type='string', scope={'window'},
vi_def=true,
vim=true,
@@ -856,6 +963,7 @@ return {
},
{
full_name='foldignore', abbreviation='fdi',
+ short_desc=N_("ignore lines when 'foldmethod' is \"indent\""),
type='string', scope={'window'},
vi_def=true,
vim=true,
@@ -865,6 +973,7 @@ return {
},
{
full_name='foldlevel', abbreviation='fdl',
+ short_desc=N_("close folds with a level higher than this"),
type='number', scope={'window'},
vi_def=true,
redraw={'current_window'},
@@ -872,6 +981,7 @@ return {
},
{
full_name='foldlevelstart', abbreviation='fdls',
+ short_desc=N_("'foldlevel' when starting to edit a file"),
type='number', scope={'global'},
vi_def=true,
redraw={'curswant'},
@@ -880,6 +990,7 @@ return {
},
{
full_name='foldmarker', abbreviation='fmr',
+ short_desc=N_("markers used when 'foldmethod' is \"marker\""),
type='string', list='onecomma', scope={'window'},
deny_duplicates=true,
vi_def=true,
@@ -890,6 +1001,7 @@ return {
},
{
full_name='foldmethod', abbreviation='fdm',
+ short_desc=N_("folding type"),
type='string', scope={'window'},
vi_def=true,
vim=true,
@@ -899,6 +1011,7 @@ return {
},
{
full_name='foldminlines', abbreviation='fml',
+ short_desc=N_("minimum number of lines for a fold to be closed"),
type='number', scope={'window'},
vi_def=true,
redraw={'current_window'},
@@ -906,6 +1019,7 @@ return {
},
{
full_name='foldnestmax', abbreviation='fdn',
+ short_desc=N_("maximum fold depth"),
type='number', scope={'window'},
vi_def=true,
redraw={'current_window'},
@@ -913,6 +1027,7 @@ return {
},
{
full_name='foldopen', abbreviation='fdo',
+ short_desc=N_("for which commands a fold will be opened"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -922,6 +1037,7 @@ return {
},
{
full_name='foldtext', abbreviation='fdt',
+ short_desc=N_("expression used to display for a closed fold"),
type='string', scope={'window'},
vi_def=true,
vim=true,
@@ -932,6 +1048,7 @@ return {
},
{
full_name='formatexpr', abbreviation='fex',
+ short_desc=N_("expression used with \"gq\" command"),
type='string', scope={'buffer'},
vi_def=true,
vim=true,
@@ -942,6 +1059,7 @@ return {
},
{
full_name='formatoptions', abbreviation='fo',
+ short_desc=N_("how automatic formatting is to be done"),
type='string', list='flags', scope={'buffer'},
vim=true,
alloced=true,
@@ -950,6 +1068,7 @@ return {
},
{
full_name='formatlistpat', abbreviation='flp',
+ short_desc=N_("pattern used to recognize a list header"),
type='string', scope={'buffer'},
vi_def=true,
alloced=true,
@@ -958,6 +1077,7 @@ return {
},
{
full_name='formatprg', abbreviation='fp',
+ short_desc=N_("name of external program used with \"gq\" command"),
type='string', scope={'global', 'buffer'},
secure=true,
vi_def=true,
@@ -967,6 +1087,7 @@ return {
},
{
full_name='fsync', abbreviation='fs',
+ short_desc=N_("whether to invoke fsync() after file write"),
type='bool', scope={'global'},
secure=true,
vi_def=true,
@@ -975,6 +1096,7 @@ return {
},
{
full_name='gdefault', abbreviation='gd',
+ short_desc=N_("the \":substitute\" flag 'g' is default on"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -983,6 +1105,7 @@ return {
},
{
full_name='grepformat', abbreviation='gfm',
+ short_desc=N_("format of 'grepprg' output"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -991,6 +1114,7 @@ return {
},
{
full_name='grepprg', abbreviation='gp',
+ short_desc=N_("program to use for \":grep\""),
type='string', scope={'global', 'buffer'},
secure=true,
vi_def=true,
@@ -1006,6 +1130,7 @@ return {
},
{
full_name='guicursor', abbreviation='gcr',
+ short_desc=N_("GUI: settings for cursor shape and blinking"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -1014,6 +1139,7 @@ return {
},
{
full_name='guifont', abbreviation='gfn',
+ short_desc=N_("GUI: Name(s) of font(s) to be used"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -1022,16 +1148,8 @@ return {
defaults={if_true={vi=""}}
},
{
- full_name='guifontset', abbreviation='gfs',
- type='string', list='onecomma', scope={'global'},
- deny_duplicates=true,
- vi_def=true,
- varname='p_guifontset',
- redraw={'ui_option'},
- defaults={if_true={vi=""}}
- },
- {
full_name='guifontwide', abbreviation='gfw',
+ short_desc=N_("list of font names for double-wide characters"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -1041,6 +1159,7 @@ return {
},
{
full_name='guioptions', abbreviation='go',
+ short_desc=N_("GUI: Which components and options are used"),
type='string', list='flags', scope={'global'},
vi_def=true,
redraw={'all_windows'},
@@ -1048,6 +1167,7 @@ return {
},
{
full_name='guitablabel', abbreviation='gtl',
+ short_desc=N_("GUI: custom label for a tab page"),
type='string', scope={'global'},
vi_def=true,
modelineexpr=true,
@@ -1056,6 +1176,7 @@ return {
},
{
full_name='guitabtooltip', abbreviation='gtt',
+ short_desc=N_("GUI: custom tooltip for a tab page"),
type='string', scope={'global'},
vi_def=true,
redraw={'current_window'},
@@ -1063,6 +1184,7 @@ return {
},
{
full_name='helpfile', abbreviation='hf',
+ short_desc=N_("full path name of the main help file"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -1072,6 +1194,7 @@ return {
},
{
full_name='helpheight', abbreviation='hh',
+ short_desc=N_("minimum height of a new help window"),
type='number', scope={'global'},
vi_def=true,
varname='p_hh',
@@ -1079,6 +1202,7 @@ return {
},
{
full_name='helplang', abbreviation='hlg',
+ short_desc=N_("preferred help languages"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -1087,6 +1211,7 @@ return {
},
{
full_name='hidden', abbreviation='hid',
+ short_desc=N_("don't unload buffer when it is |abandon|ed"),
type='bool', scope={'global'},
vi_def=true,
varname='p_hid',
@@ -1094,6 +1219,7 @@ return {
},
{
full_name='highlight', abbreviation='hl',
+ short_desc=N_("sets highlighting mode for various occasions"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -1102,6 +1228,7 @@ return {
},
{
full_name='history', abbreviation='hi',
+ short_desc=N_("number of command-lines that are remembered"),
type='number', scope={'global'},
vim=true,
varname='p_hi',
@@ -1109,6 +1236,7 @@ return {
},
{
full_name='hkmap', abbreviation='hk',
+ short_desc=N_("Hebrew keyboard mapping"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -1117,6 +1245,7 @@ return {
},
{
full_name='hkmapp', abbreviation='hkp',
+ short_desc=N_("phonetic Hebrew keyboard mapping"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -1125,6 +1254,7 @@ return {
},
{
full_name='hlsearch', abbreviation='hls',
+ short_desc=N_("highlight matches with last search pattern"),
type='bool', scope={'global'},
vim=true,
redraw={'all_windows'},
@@ -1133,6 +1263,7 @@ return {
},
{
full_name='icon',
+ short_desc=N_("Vim set the text of the window icon"),
type='bool', scope={'global'},
vi_def=true,
varname='p_icon',
@@ -1140,6 +1271,7 @@ return {
},
{
full_name='iconstring',
+ short_desc=N_("to use for the Vim icon text"),
type='string', scope={'global'},
vi_def=true,
modelineexpr=true,
@@ -1148,6 +1280,7 @@ return {
},
{
full_name='ignorecase', abbreviation='ic',
+ short_desc=N_("ignore case in search patterns"),
type='bool', scope={'global'},
vi_def=true,
varname='p_ic',
@@ -1155,6 +1288,7 @@ return {
},
{
full_name='imcmdline', abbreviation='imc',
+ short_desc=N_("use IM when starting to edit a command line"),
type='bool', scope={'global'},
vi_def=true,
enable_if=false,
@@ -1162,6 +1296,7 @@ return {
},
{
full_name='imdisable', abbreviation='imd',
+ short_desc=N_("do not use the IM in any mode"),
type='bool', scope={'global'},
vi_def=true,
enable_if=false,
@@ -1169,6 +1304,7 @@ return {
},
{
full_name='iminsert', abbreviation='imi',
+ short_desc=N_("use :lmap or IM in Insert mode"),
type='number', scope={'buffer'},
vi_def=true,
varname='p_iminsert', pv_name='p_imi',
@@ -1178,6 +1314,7 @@ return {
},
{
full_name='imsearch', abbreviation='ims',
+ short_desc=N_("use :lmap or IM when typing a search pattern"),
type='number', scope={'buffer'},
vi_def=true,
varname='p_imsearch', pv_name='p_ims',
@@ -1187,6 +1324,7 @@ return {
},
{
full_name='inccommand', abbreviation='icm',
+ short_desc=N_("Live preview of substitution"),
type='string', scope={'global'},
vi_def=true,
redraw={'all_windows'},
@@ -1195,6 +1333,7 @@ return {
},
{
full_name='include', abbreviation='inc',
+ short_desc=N_("pattern to be used to find an include file"),
type='string', scope={'global', 'buffer'},
vi_def=true,
alloced=true,
@@ -1203,6 +1342,7 @@ return {
},
{
full_name='includeexpr', abbreviation='inex',
+ short_desc=N_("expression used to process an include line"),
type='string', scope={'buffer'},
vi_def=true,
modelineexpr=true,
@@ -1212,6 +1352,7 @@ return {
},
{
full_name='incsearch', abbreviation='is',
+ short_desc=N_("highlight match while typing search pattern"),
type='bool', scope={'global'},
vim=true,
varname='p_is',
@@ -1219,6 +1360,7 @@ return {
},
{
full_name='indentexpr', abbreviation='inde',
+ short_desc=N_("expression used to obtain the indent of a line"),
type='string', scope={'buffer'},
vi_def=true,
vim=true,
@@ -1229,6 +1371,7 @@ return {
},
{
full_name='indentkeys', abbreviation='indk',
+ short_desc=N_("keys that trigger indenting with 'indentexpr'"),
type='string', list='onecomma', scope={'buffer'},
deny_duplicates=true,
vi_def=true,
@@ -1238,6 +1381,7 @@ return {
},
{
full_name='infercase', abbreviation='inf',
+ short_desc=N_("adjust case of match for keyword completion"),
type='bool', scope={'buffer'},
vi_def=true,
varname='p_inf',
@@ -1245,6 +1389,7 @@ return {
},
{
full_name='insertmode', abbreviation='im',
+ short_desc=N_("start the edit of a file in Insert mode"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -1253,6 +1398,7 @@ return {
},
{
full_name='isfname', abbreviation='isf',
+ short_desc=N_("characters included in file names and pathnames"),
type='string', list='comma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -1267,14 +1413,20 @@ return {
},
{
full_name='isident', abbreviation='isi',
+ short_desc=N_("characters included in identifiers"),
type='string', list='comma', scope={'global'},
deny_duplicates=true,
vi_def=true,
varname='p_isi',
- defaults={if_true={vi="@,48-57,_,192-255"}}
+ defaults={
+ condition='WIN32',
+ if_true={vi="@,48-57,_,128-167,224-235"},
+ if_false={vi="@,48-57,_,192-255"}
+ }
},
{
full_name='iskeyword', abbreviation='isk',
+ short_desc=N_("characters included in keywords"),
type='string', list='comma', scope={'buffer'},
deny_duplicates=true,
vim=true,
@@ -1284,6 +1436,7 @@ return {
},
{
full_name='isprint', abbreviation='isp',
+ short_desc=N_("printable characters"),
type='string', list='comma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -1294,6 +1447,7 @@ return {
},
{
full_name='joinspaces', abbreviation='js',
+ short_desc=N_("two spaces after a period with a join command"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -1302,6 +1456,7 @@ return {
},
{
full_name='jumpoptions', abbreviation='jop',
+ short_desc=N_("Controls the behavior of the jumplist"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
varname='p_jop',
@@ -1310,6 +1465,7 @@ return {
},
{
full_name='keymap', abbreviation='kmp',
+ short_desc=N_("name of a keyboard mapping"),
type='string', scope={'buffer'},
normal_fname_chars=true,
pri_mkrc=true,
@@ -1321,6 +1477,7 @@ return {
},
{
full_name='keymodel', abbreviation='km',
+ short_desc=N_("enable starting/stopping selection with keys"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -1329,6 +1486,7 @@ return {
},
{
full_name='keywordprg', abbreviation='kp',
+ short_desc=N_("program to use for the \"K\" command"),
type='string', scope={'global', 'buffer'},
secure=true,
vi_def=true,
@@ -1340,6 +1498,7 @@ return {
},
{
full_name='langmap', abbreviation='lmap',
+ short_desc=N_("alphabetic characters for other language mode"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
secure=true,
@@ -1349,6 +1508,7 @@ return {
},
{
full_name='langmenu', abbreviation='lm',
+ short_desc=N_("language to be used for the menus"),
type='string', scope={'global'},
normal_fname_chars=true,
vi_def=true,
@@ -1357,18 +1517,21 @@ return {
},
{
full_name='langnoremap', abbreviation='lnr',
+ short_desc=N_("do not apply 'langmap' to mapped characters"),
type='bool', scope={'global'},
varname='p_lnr',
defaults={if_true={vi=false, vim=true}}
},
{
full_name='langremap', abbreviation='lrm',
+ short_desc=N_('No description'),
type='bool', scope={'global'},
varname='p_lrm',
defaults={if_true={vi=true, vim=false}}
},
{
full_name='laststatus', abbreviation='ls',
+ short_desc=N_("tells when last window has status lines"),
type='number', scope={'global'},
vim=true,
redraw={'all_windows'},
@@ -1377,6 +1540,7 @@ return {
},
{
full_name='lazyredraw', abbreviation='lz',
+ short_desc=N_("don't redraw while executing macros"),
type='bool', scope={'global'},
vi_def=true,
varname='p_lz',
@@ -1384,6 +1548,7 @@ return {
},
{
full_name='linebreak', abbreviation='lbr',
+ short_desc=N_("wrap long lines at a blank"),
type='bool', scope={'window'},
vi_def=true,
redraw={'current_window'},
@@ -1391,6 +1556,7 @@ return {
},
{
full_name='lines',
+ short_desc=N_("of lines in the display"),
type='number', scope={'global'},
no_mkrc=true,
vi_def=true,
@@ -1400,6 +1566,7 @@ return {
},
{
full_name='linespace', abbreviation='lsp',
+ short_desc=N_("number of pixel lines to use between characters"),
type='number', scope={'global'},
vi_def=true,
redraw={'ui_option'},
@@ -1408,6 +1575,7 @@ return {
},
{
full_name='lisp',
+ short_desc=N_("indenting for Lisp"),
type='bool', scope={'buffer'},
vi_def=true,
varname='p_lisp',
@@ -1415,6 +1583,7 @@ return {
},
{
full_name='lispwords', abbreviation='lw',
+ short_desc=N_("words that change how lisp indenting works"),
type='string', list='onecomma', scope={'global', 'buffer'},
deny_duplicates=true,
vi_def=true,
@@ -1423,6 +1592,7 @@ return {
},
{
full_name='list',
+ short_desc=N_("<Tab> and <EOL>"),
type='bool', scope={'window'},
vi_def=true,
redraw={'current_window'},
@@ -1430,6 +1600,7 @@ return {
},
{
full_name='listchars', abbreviation='lcs',
+ short_desc=N_("characters for displaying in list mode"),
type='string', list='onecomma', scope={'global', 'window'},
deny_duplicates=true,
vim=true,
@@ -1440,6 +1611,7 @@ return {
},
{
full_name='loadplugins', abbreviation='lpl',
+ short_desc=N_("load plugin scripts when starting up"),
type='bool', scope={'global'},
vi_def=true,
varname='p_lpl',
@@ -1447,6 +1619,7 @@ return {
},
{
full_name='magic',
+ short_desc=N_("special characters in search patterns"),
type='bool', scope={'global'},
vi_def=true,
varname='p_magic',
@@ -1454,6 +1627,7 @@ return {
},
{
full_name='makeef', abbreviation='mef',
+ short_desc=N_("name of the errorfile for \":make\""),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -1463,6 +1637,7 @@ return {
},
{
full_name='makeencoding', abbreviation='menc',
+ short_desc=N_("Converts the output of external commands"),
type='string', scope={'global', 'buffer'},
vi_def=true,
varname='p_menc',
@@ -1470,6 +1645,7 @@ return {
},
{
full_name='makeprg', abbreviation='mp',
+ short_desc=N_("program to use for the \":make\" command"),
type='string', scope={'global', 'buffer'},
secure=true,
vi_def=true,
@@ -1479,6 +1655,7 @@ return {
},
{
full_name='matchpairs', abbreviation='mps',
+ short_desc=N_("pairs of characters that \"%\" can match"),
type='string', list='onecomma', scope={'buffer'},
deny_duplicates=true,
vi_def=true,
@@ -1488,6 +1665,7 @@ return {
},
{
full_name='matchtime', abbreviation='mat',
+ short_desc=N_("tenths of a second to show matching paren"),
type='number', scope={'global'},
vi_def=true,
varname='p_mat',
@@ -1495,6 +1673,7 @@ return {
},
{
full_name='maxcombine', abbreviation='mco',
+ short_desc=N_("maximum nr of combining characters displayed"),
type='number', scope={'global'},
vi_def=true,
varname='p_mco',
@@ -1502,6 +1681,7 @@ return {
},
{
full_name='maxfuncdepth', abbreviation='mfd',
+ short_desc=N_("maximum recursive depth for user functions"),
type='number', scope={'global'},
vi_def=true,
varname='p_mfd',
@@ -1509,6 +1689,7 @@ return {
},
{
full_name='maxmapdepth', abbreviation='mmd',
+ short_desc=N_("maximum recursive depth for mapping"),
type='number', scope={'global'},
vi_def=true,
varname='p_mmd',
@@ -1516,6 +1697,7 @@ return {
},
{
full_name='maxmempattern', abbreviation='mmp',
+ short_desc=N_("maximum memory (in Kbyte) used for pattern search"),
type='number', scope={'global'},
vi_def=true,
varname='p_mmp',
@@ -1523,6 +1705,7 @@ return {
},
{
full_name='menuitems', abbreviation='mis',
+ short_desc=N_("maximum number of items in a menu"),
type='number', scope={'global'},
vi_def=true,
varname='p_mis',
@@ -1530,6 +1713,7 @@ return {
},
{
full_name='mkspellmem', abbreviation='msm',
+ short_desc=N_("memory used before |:mkspell| compresses the tree"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -1539,6 +1723,7 @@ return {
},
{
full_name='modeline', abbreviation='ml',
+ short_desc=N_("recognize modelines at start or end of file"),
type='bool', scope={'buffer'},
vim=true,
varname='p_ml',
@@ -1546,6 +1731,7 @@ return {
},
{
full_name='modelineexpr', abbreviation='mle',
+ short_desc=N_("allow some options to be set in modeline"),
type='bool', scope={'global'},
vi_def=true,
secure=true,
@@ -1554,6 +1740,7 @@ return {
},
{
full_name='modelines', abbreviation='mls',
+ short_desc=N_("number of lines checked for modelines"),
type='number', scope={'global'},
vi_def=true,
varname='p_mls',
@@ -1561,6 +1748,7 @@ return {
},
{
full_name='modifiable', abbreviation='ma',
+ short_desc=N_("changes to the text are not possible"),
type='bool', scope={'buffer'},
noglob=true,
vi_def=true,
@@ -1569,6 +1757,7 @@ return {
},
{
full_name='modified', abbreviation='mod',
+ short_desc=N_("buffer has been modified"),
type='bool', scope={'buffer'},
no_mkrc=true,
vi_def=true,
@@ -1578,6 +1767,7 @@ return {
},
{
full_name='more',
+ short_desc=N_("listings when the whole screen is filled"),
type='bool', scope={'global'},
vim=true,
varname='p_more',
@@ -1585,19 +1775,23 @@ return {
},
{
full_name='mouse',
+ short_desc=N_("the use of mouse clicks"),
type='string', list='flags', scope={'global'},
varname='p_mouse',
defaults={if_true={vi="", vim=""}}
},
{
full_name='mousefocus', abbreviation='mousef',
+ short_desc=N_("keyboard focus follows the mouse"),
type='bool', scope={'global'},
vi_def=true,
- enable_if=false,
+ redraw={'ui_option'},
+ varname='p_mousef',
defaults={if_true={vi=false}}
},
{
full_name='mousehide', abbreviation='mh',
+ short_desc=N_("hide mouse pointer while typing"),
type='bool', scope={'global'},
vi_def=true,
enable_if=false,
@@ -1605,6 +1799,7 @@ return {
},
{
full_name='mousemodel', abbreviation='mousem',
+ short_desc=N_("changes meaning of mouse buttons"),
type='string', scope={'global'},
vi_def=true,
varname='p_mousem',
@@ -1612,6 +1807,7 @@ return {
},
{
full_name='mouseshape', abbreviation='mouses',
+ short_desc=N_("shape of the mouse pointer in different modes"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -1619,6 +1815,7 @@ return {
},
{
full_name='mousetime', abbreviation='mouset',
+ short_desc=N_("max time between mouse double-click"),
type='number', scope={'global'},
vi_def=true,
varname='p_mouset',
@@ -1626,6 +1823,7 @@ return {
},
{
full_name='nrformats', abbreviation='nf',
+ short_desc=N_("number formats recognized for CTRL-A command"),
type='string', list='onecomma', scope={'buffer'},
deny_duplicates=true,
alloced=true,
@@ -1634,6 +1832,7 @@ return {
},
{
full_name='number', abbreviation='nu',
+ short_desc=N_("print the line number in front of each line"),
type='bool', scope={'window'},
vi_def=true,
redraw={'current_window'},
@@ -1641,6 +1840,7 @@ return {
},
{
full_name='numberwidth', abbreviation='nuw',
+ short_desc=N_("number of columns used for the line number"),
type='number', scope={'window'},
vim=true,
redraw={'current_window'},
@@ -1648,6 +1848,7 @@ return {
},
{
full_name='omnifunc', abbreviation='ofu',
+ short_desc=N_("function for filetype-specific completion"),
type='string', scope={'buffer'},
secure=true,
vi_def=true,
@@ -1657,6 +1858,7 @@ return {
},
{
full_name='opendevice', abbreviation='odev',
+ short_desc=N_("allow reading/writing devices on MS-Windows"),
type='bool', scope={'global'},
vi_def=true,
enable_if=false,
@@ -1664,6 +1866,7 @@ return {
},
{
full_name='operatorfunc', abbreviation='opfunc',
+ short_desc=N_("function to be called for |g@| operator"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -1672,6 +1875,7 @@ return {
},
{
full_name='packpath', abbreviation='pp',
+ short_desc=N_("list of directories used for packages"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
secure=true,
@@ -1682,6 +1886,7 @@ return {
},
{
full_name='paragraphs', abbreviation='para',
+ short_desc=N_("nroff macros that separate paragraphs"),
type='string', scope={'global'},
vi_def=true,
varname='p_para',
@@ -1689,6 +1894,7 @@ return {
},
{
full_name='paste',
+ short_desc=N_("pasting text"),
type='bool', scope={'global'},
pri_mkrc=true,
vi_def=true,
@@ -1697,6 +1903,7 @@ return {
},
{
full_name='pastetoggle', abbreviation='pt',
+ short_desc=N_("key code that causes 'paste' to toggle"),
type='string', scope={'global'},
vi_def=true,
varname='p_pt',
@@ -1704,6 +1911,7 @@ return {
},
{
full_name='patchexpr', abbreviation='pex',
+ short_desc=N_("expression used to patch a file"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -1712,6 +1920,7 @@ return {
},
{
full_name='patchmode', abbreviation='pm',
+ short_desc=N_("keep the oldest version of a file"),
type='string', scope={'global'},
normal_fname_chars=true,
vi_def=true,
@@ -1720,6 +1929,7 @@ return {
},
{
full_name='path', abbreviation='pa',
+ short_desc=N_("list of directories searched with \"gf\" et.al."),
type='string', list='comma', scope={'global', 'buffer'},
deny_duplicates=true,
vi_def=true,
@@ -1729,6 +1939,7 @@ return {
},
{
full_name='preserveindent', abbreviation='pi',
+ short_desc=N_("preserve the indent structure when reindenting"),
type='bool', scope={'buffer'},
vi_def=true,
vim=true,
@@ -1737,6 +1948,7 @@ return {
},
{
full_name='previewheight', abbreviation='pvh',
+ short_desc=N_("height of the preview window"),
type='number', scope={'global'},
vi_def=true,
varname='p_pvh',
@@ -1744,6 +1956,7 @@ return {
},
{
full_name='previewwindow', abbreviation='pvw',
+ short_desc=N_("identifies the preview window"),
type='bool', scope={'window'},
noglob=true,
vi_def=true,
@@ -1752,6 +1965,7 @@ return {
},
{
full_name='printdevice', abbreviation='pdev',
+ short_desc=N_("name of the printer to be used for :hardcopy"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -1760,6 +1974,7 @@ return {
},
{
full_name='printencoding', abbreviation='penc',
+ short_desc=N_("encoding to be used for printing"),
type='string', scope={'global'},
vi_def=true,
varname='p_penc',
@@ -1767,6 +1982,7 @@ return {
},
{
full_name='printexpr', abbreviation='pexpr',
+ short_desc=N_("expression used to print PostScript for :hardcopy"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -1775,6 +1991,7 @@ return {
},
{
full_name='printfont', abbreviation='pfn',
+ short_desc=N_("name of the font to be used for :hardcopy"),
type='string', scope={'global'},
vi_def=true,
varname='p_pfn',
@@ -1782,6 +1999,7 @@ return {
},
{
full_name='printheader', abbreviation='pheader',
+ short_desc=N_("format of the header used for :hardcopy"),
type='string', scope={'global'},
vi_def=true,
varname='p_header',
@@ -1789,6 +2007,7 @@ return {
},
{
full_name='printmbcharset', abbreviation='pmbcs',
+ short_desc=N_("CJK character set to be used for :hardcopy"),
type='string', scope={'global'},
vi_def=true,
varname='p_pmcs',
@@ -1796,6 +2015,7 @@ return {
},
{
full_name='printmbfont', abbreviation='pmbfn',
+ short_desc=N_("font names to be used for CJK output of :hardcopy"),
type='string', scope={'global'},
vi_def=true,
varname='p_pmfn',
@@ -1803,6 +2023,7 @@ return {
},
{
full_name='printoptions', abbreviation='popt',
+ short_desc=N_("controls the format of :hardcopy output"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -1811,6 +2032,7 @@ return {
},
{
full_name='prompt',
+ short_desc=N_("enable prompt in Ex mode"),
type='bool', scope={'global'},
vi_def=true,
varname='p_prompt',
@@ -1818,6 +2040,7 @@ return {
},
{
full_name='pumblend', abbreviation='pb',
+ short_desc=N_("Controls transparency level of popup menu"),
type='number', scope={'global'},
vi_def=true,
redraw={'ui_option'},
@@ -1826,6 +2049,7 @@ return {
},
{
full_name='pumheight', abbreviation='ph',
+ short_desc=N_("maximum height of the popup menu"),
type='number', scope={'global'},
vi_def=true,
varname='p_ph',
@@ -1833,6 +2057,7 @@ return {
},
{
full_name='pumwidth', abbreviation='pw',
+ short_desc=N_("minimum width of the popup menu"),
type='number', scope={'global'},
vi_def=true,
varname='p_pw',
@@ -1840,6 +2065,7 @@ return {
},
{
full_name='pyxversion', abbreviation='pyx',
+ short_desc=N_("selects default python version to use"),
type='number', scope={'global'},
secure=true,
vi_def=true,
@@ -1848,6 +2074,7 @@ return {
},
{
full_name='quoteescape', abbreviation='qe',
+ short_desc=N_("escape characters used in a string"),
type='string', scope={'buffer'},
vi_def=true,
alloced=true,
@@ -1856,6 +2083,7 @@ return {
},
{
full_name='readonly', abbreviation='ro',
+ short_desc=N_("disallow writing the buffer"),
type='bool', scope={'buffer'},
noglob=true,
vi_def=true,
@@ -1865,6 +2093,7 @@ return {
},
{
full_name='redrawdebug', abbreviation='rdb',
+ short_desc=N_("Changes the way redrawing works (debug)"),
type='string', list='onecomma', scope={'global'},
vi_def=true,
varname='p_rdb',
@@ -1872,6 +2101,7 @@ return {
},
{
full_name='redrawtime', abbreviation='rdt',
+ short_desc=N_("timeout for 'hlsearch' and |:match| highlighting"),
type='number', scope={'global'},
vi_def=true,
varname='p_rdt',
@@ -1879,6 +2109,7 @@ return {
},
{
full_name='regexpengine', abbreviation='re',
+ short_desc=N_("default regexp engine to use"),
type='number', scope={'global'},
vi_def=true,
varname='p_re',
@@ -1886,6 +2117,7 @@ return {
},
{
full_name='relativenumber', abbreviation='rnu',
+ short_desc=N_("show relative line number in front of each line"),
type='bool', scope={'window'},
vi_def=true,
redraw={'current_window'},
@@ -1893,6 +2125,7 @@ return {
},
{
full_name='remap',
+ short_desc=N_("mappings to work recursively"),
type='bool', scope={'global'},
vi_def=true,
varname='p_remap',
@@ -1900,6 +2133,7 @@ return {
},
{
full_name='report',
+ short_desc=N_("for reporting nr. of lines changed"),
type='number', scope={'global'},
vi_def=true,
varname='p_report',
@@ -1907,6 +2141,7 @@ return {
},
{
full_name='revins', abbreviation='ri',
+ short_desc=N_("inserting characters will work backwards"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -1915,6 +2150,7 @@ return {
},
{
full_name='rightleft', abbreviation='rl',
+ short_desc=N_("window is right-to-left oriented"),
type='bool', scope={'window'},
vi_def=true,
redraw={'current_window'},
@@ -1922,6 +2158,7 @@ return {
},
{
full_name='rightleftcmd', abbreviation='rlc',
+ short_desc=N_("commands for which editing works right-to-left"),
type='string', scope={'window'},
vi_def=true,
alloced=true,
@@ -1930,6 +2167,7 @@ return {
},
{
full_name='ruler', abbreviation='ru',
+ short_desc=N_("show cursor line and column in the status line"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -1939,6 +2177,7 @@ return {
},
{
full_name='rulerformat', abbreviation='ruf',
+ short_desc=N_("custom format for the ruler"),
type='string', scope={'global'},
vi_def=true,
alloced=true,
@@ -1949,6 +2188,7 @@ return {
},
{
full_name='runtimepath', abbreviation='rtp',
+ short_desc=N_("list of directories used for runtime files"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
secure=true,
@@ -1959,6 +2199,7 @@ return {
},
{
full_name='scroll', abbreviation='scr',
+ short_desc=N_("lines to scroll with CTRL-U and CTRL-D"),
type='number', scope={'window'},
no_mkrc=true,
vi_def=true,
@@ -1967,6 +2208,7 @@ return {
},
{
full_name='scrollback', abbreviation='scbk',
+ short_desc=N_("lines to scroll with CTRL-U and CTRL-D"),
type='number', scope={'buffer'},
vi_def=true,
varname='p_scbk',
@@ -1975,6 +2217,7 @@ return {
},
{
full_name='scrollbind', abbreviation='scb',
+ short_desc=N_("scroll in window as other windows scroll"),
type='bool', scope={'window'},
vi_def=true,
pv_name='p_scbind',
@@ -1982,6 +2225,7 @@ return {
},
{
full_name='scrolljump', abbreviation='sj',
+ short_desc=N_("minimum number of lines to scroll"),
type='number', scope={'global'},
vi_def=true,
vim=true,
@@ -1990,6 +2234,7 @@ return {
},
{
full_name='scrolloff', abbreviation='so',
+ short_desc=N_("minimum nr. of lines above and below cursor"),
type='number', scope={'global', 'window'},
vi_def=true,
vim=true,
@@ -1999,6 +2244,7 @@ return {
},
{
full_name='scrollopt', abbreviation='sbo',
+ short_desc=N_("how 'scrollbind' should behave"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -2007,6 +2253,7 @@ return {
},
{
full_name='sections', abbreviation='sect',
+ short_desc=N_("nroff macros that separate sections"),
type='string', scope={'global'},
vi_def=true,
varname='p_sections',
@@ -2014,6 +2261,7 @@ return {
},
{
full_name='secure',
+ short_desc=N_("mode for reading .vimrc in current dir"),
type='bool', scope={'global'},
secure=true,
vi_def=true,
@@ -2022,6 +2270,7 @@ return {
},
{
full_name='selection', abbreviation='sel',
+ short_desc=N_("what type of selection to use"),
type='string', scope={'global'},
vi_def=true,
varname='p_sel',
@@ -2029,6 +2278,7 @@ return {
},
{
full_name='selectmode', abbreviation='slm',
+ short_desc=N_("when to use Select mode instead of Visual mode"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -2037,6 +2287,7 @@ return {
},
{
full_name='sessionoptions', abbreviation='ssop',
+ short_desc=N_("options for |:mksession|"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vim=true,
@@ -2048,6 +2299,7 @@ return {
},
{
full_name='shada', abbreviation='sd',
+ short_desc=N_("use .shada file upon startup and exiting"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
secure=true,
@@ -2056,6 +2308,7 @@ return {
},
{
full_name='shadafile', abbreviation='sdf',
+ short_desc=N_("overrides the filename used for shada"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -2065,6 +2318,7 @@ return {
},
{
full_name='shell', abbreviation='sh',
+ short_desc=N_("name of shell to use for external commands"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -2078,6 +2332,7 @@ return {
},
{
full_name='shellcmdflag', abbreviation='shcf',
+ short_desc=N_("flag to shell to execute one command"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -2090,6 +2345,7 @@ return {
},
{
full_name='shellpipe', abbreviation='sp',
+ short_desc=N_("string to put output of \":make\" in error file"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -2102,6 +2358,7 @@ return {
},
{
full_name='shellquote', abbreviation='shq',
+ short_desc=N_("quote character(s) for around shell command"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -2110,6 +2367,7 @@ return {
},
{
full_name='shellredir', abbreviation='srr',
+ short_desc=N_("string to put output of filter in a temp file"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -2122,6 +2380,7 @@ return {
},
{
full_name='shellslash', abbreviation='ssl',
+ short_desc=N_("use forward slash for shell file names"),
type='bool', scope={'global'},
vi_def=true,
varname='p_ssl',
@@ -2130,12 +2389,14 @@ return {
},
{
full_name='shelltemp', abbreviation='stmp',
+ short_desc=N_("whether to use a temp file for shell commands"),
type='bool', scope={'global'},
varname='p_stmp',
defaults={if_true={vi=false, vim=true}}
},
{
full_name='shellxquote', abbreviation='sxq',
+ short_desc=N_("like 'shellquote', but include redirection"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -2148,6 +2409,7 @@ return {
},
{
full_name='shellxescape', abbreviation='sxe',
+ short_desc=N_("characters to escape when 'shellxquote' is ("),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -2156,6 +2418,7 @@ return {
},
{
full_name='shiftround', abbreviation='sr',
+ short_desc=N_("round indent to multiple of shiftwidth"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -2164,6 +2427,7 @@ return {
},
{
full_name='shiftwidth', abbreviation='sw',
+ short_desc=N_("number of spaces to use for (auto)indent step"),
type='number', scope={'buffer'},
vi_def=true,
varname='p_sw',
@@ -2171,6 +2435,7 @@ return {
},
{
full_name='shortmess', abbreviation='shm',
+ short_desc=N_("list of flags, reduce length of messages"),
type='string', list='flags', scope={'global'},
vim=true,
varname='p_shm',
@@ -2178,6 +2443,7 @@ return {
},
{
full_name='showbreak', abbreviation='sbr',
+ short_desc=N_("string to use at the start of wrapped lines"),
type='string', scope={'global'},
vi_def=true,
redraw={'all_windows'},
@@ -2186,6 +2452,7 @@ return {
},
{
full_name='showcmd', abbreviation='sc',
+ short_desc=N_("show (partial) command in status line"),
type='bool', scope={'global'},
vim=true,
varname='p_sc',
@@ -2193,6 +2460,7 @@ return {
},
{
full_name='showfulltag', abbreviation='sft',
+ short_desc=N_("show full tag pattern when completing tag"),
type='bool', scope={'global'},
vi_def=true,
varname='p_sft',
@@ -2200,6 +2468,7 @@ return {
},
{
full_name='showmatch', abbreviation='sm',
+ short_desc=N_("briefly jump to matching bracket if insert one"),
type='bool', scope={'global'},
vi_def=true,
varname='p_sm',
@@ -2207,6 +2476,7 @@ return {
},
{
full_name='showmode', abbreviation='smd',
+ short_desc=N_("message on status line to show current mode"),
type='bool', scope={'global'},
vim=true,
varname='p_smd',
@@ -2214,6 +2484,7 @@ return {
},
{
full_name='showtabline', abbreviation='stal',
+ short_desc=N_("tells when the tab pages line is displayed"),
type='number', scope={'global'},
vi_def=true,
redraw={'all_windows', 'ui_option'},
@@ -2222,6 +2493,7 @@ return {
},
{
full_name='sidescroll', abbreviation='ss',
+ short_desc=N_("minimum number of columns to scroll horizontal"),
type='number', scope={'global'},
vi_def=true,
varname='p_ss',
@@ -2229,6 +2501,7 @@ return {
},
{
full_name='sidescrolloff', abbreviation='siso',
+ short_desc=N_("min. nr. of columns to left and right of cursor"),
type='number', scope={'global', 'window'},
vi_def=true,
vim=true,
@@ -2238,6 +2511,7 @@ return {
},
{
full_name='signcolumn', abbreviation='scl',
+ short_desc=N_("when to display the sign column"),
type='string', scope={'window'},
vi_def=true,
alloced=true,
@@ -2246,6 +2520,7 @@ return {
},
{
full_name='smartcase', abbreviation='scs',
+ short_desc=N_("no ignore case when pattern has uppercase"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -2254,6 +2529,7 @@ return {
},
{
full_name='smartindent', abbreviation='si',
+ short_desc=N_("smart autoindenting for C programs"),
type='bool', scope={'buffer'},
vi_def=true,
vim=true,
@@ -2262,6 +2538,7 @@ return {
},
{
full_name='smarttab', abbreviation='sta',
+ short_desc=N_("use 'shiftwidth' when inserting <Tab>"),
type='bool', scope={'global'},
vim=true,
varname='p_sta',
@@ -2269,6 +2546,7 @@ return {
},
{
full_name='softtabstop', abbreviation='sts',
+ short_desc=N_("number of spaces that <Tab> uses while editing"),
type='number', scope={'buffer'},
vi_def=true,
vim=true,
@@ -2277,6 +2555,7 @@ return {
},
{
full_name='spell',
+ short_desc=N_("spell checking"),
type='bool', scope={'window'},
vi_def=true,
redraw={'current_window'},
@@ -2284,6 +2563,7 @@ return {
},
{
full_name='spellcapcheck', abbreviation='spc',
+ short_desc=N_("pattern to locate end of a sentence"),
type='string', scope={'buffer'},
vi_def=true,
alloced=true,
@@ -2293,6 +2573,7 @@ return {
},
{
full_name='spellfile', abbreviation='spf',
+ short_desc=N_("files where |zg| and |zw| store words"),
type='string', list='onecomma', scope={'buffer'},
deny_duplicates=true,
secure=true,
@@ -2304,6 +2585,7 @@ return {
},
{
full_name='spelllang', abbreviation='spl',
+ short_desc=N_("language(s) to do spell checking for"),
type='string', list='onecomma', scope={'buffer'},
deny_duplicates=true,
vi_def=true,
@@ -2315,6 +2597,7 @@ return {
},
{
full_name='spellsuggest', abbreviation='sps',
+ short_desc=N_("method(s) used to suggest spelling corrections"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
secure=true,
@@ -2324,7 +2607,18 @@ return {
defaults={if_true={vi="best"}}
},
{
+ full_name='spelloptions', abbreviation='spo',
+ type='string', list='onecomma', scope={'buffer'},
+ deny_duplicates=true,
+ secure=true,
+ vi_def=true,
+ expand=true,
+ varname='p_spo',
+ defaults={if_true={vi="", vim=""}}
+ },
+ {
full_name='splitbelow', abbreviation='sb',
+ short_desc=N_("new window from split is below the current one"),
type='bool', scope={'global'},
vi_def=true,
varname='p_sb',
@@ -2332,6 +2626,7 @@ return {
},
{
full_name='splitright', abbreviation='spr',
+ short_desc=N_("new window is put right of the current one"),
type='bool', scope={'global'},
vi_def=true,
varname='p_spr',
@@ -2339,6 +2634,7 @@ return {
},
{
full_name='startofline', abbreviation='sol',
+ short_desc=N_("commands move cursor to first non-blank in line"),
type='bool', scope={'global'},
vi_def=true,
vim=false,
@@ -2347,6 +2643,7 @@ return {
},
{
full_name='statusline', abbreviation='stl',
+ short_desc=N_("custom format for the status line"),
type='string', scope={'global', 'window'},
vi_def=true,
alloced=true,
@@ -2357,6 +2654,7 @@ return {
},
{
full_name='suffixes', abbreviation='su',
+ short_desc=N_("suffixes that are ignored with multiple match"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -2365,6 +2663,7 @@ return {
},
{
full_name='suffixesadd', abbreviation='sua',
+ short_desc=N_("suffixes added when searching for a file"),
type='string', list='onecomma', scope={'buffer'},
deny_duplicates=true,
vi_def=true,
@@ -2374,6 +2673,7 @@ return {
},
{
full_name='swapfile', abbreviation='swf',
+ short_desc=N_("whether to use a swapfile for a buffer"),
type='bool', scope={'buffer'},
vi_def=true,
redraw={'statuslines'},
@@ -2382,6 +2682,7 @@ return {
},
{
full_name='switchbuf', abbreviation='swb',
+ short_desc=N_("sets behavior when switching to another buffer"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -2390,6 +2691,7 @@ return {
},
{
full_name='synmaxcol', abbreviation='smc',
+ short_desc=N_("maximum column to find syntax items"),
type='number', scope={'buffer'},
vi_def=true,
redraw={'current_buffer'},
@@ -2398,6 +2700,7 @@ return {
},
{
full_name='syntax', abbreviation='syn',
+ short_desc=N_("syntax to be loaded for current buffer"),
type='string', scope={'buffer'},
noglob=true,
normal_fname_chars=true,
@@ -2408,6 +2711,7 @@ return {
},
{
full_name='tagfunc', abbreviation='tfu',
+ short_desc=N_("function used to perform tag searches"),
type='string', scope={'buffer'},
vim=true,
vi_def=true,
@@ -2416,6 +2720,7 @@ return {
},
{
full_name='tabline', abbreviation='tal',
+ short_desc=N_("custom format for the console tab pages line"),
type='string', scope={'global'},
vi_def=true,
modelineexpr=true,
@@ -2425,6 +2730,7 @@ return {
},
{
full_name='tabpagemax', abbreviation='tpm',
+ short_desc=N_("maximum number of tab pages for |-p| and \"tab all\""),
type='number', scope={'global'},
vim=true,
varname='p_tpm',
@@ -2432,6 +2738,7 @@ return {
},
{
full_name='tabstop', abbreviation='ts',
+ short_desc=N_("number of spaces that <Tab> in file uses"),
type='number', scope={'buffer'},
vi_def=true,
redraw={'current_buffer'},
@@ -2440,6 +2747,7 @@ return {
},
{
full_name='tagbsearch', abbreviation='tbs',
+ short_desc=N_("use binary searching in tags files"),
type='bool', scope={'global'},
vi_def=true,
varname='p_tbs',
@@ -2447,6 +2755,7 @@ return {
},
{
full_name='tagcase', abbreviation='tc',
+ short_desc=N_("how to handle case when searching in tags files"),
type='string', scope={'global', 'buffer'},
vim=true,
varname='p_tc',
@@ -2454,6 +2763,7 @@ return {
},
{
full_name='taglength', abbreviation='tl',
+ short_desc=N_("number of significant characters for a tag"),
type='number', scope={'global'},
vi_def=true,
varname='p_tl',
@@ -2461,6 +2771,7 @@ return {
},
{
full_name='tagrelative', abbreviation='tr',
+ short_desc=N_("file names in tag file are relative"),
type='bool', scope={'global'},
vim=true,
varname='p_tr',
@@ -2468,6 +2779,7 @@ return {
},
{
full_name='tags', abbreviation='tag',
+ short_desc=N_("list of file names used by the tag command"),
type='string', list='onecomma', scope={'global', 'buffer'},
deny_duplicates=true,
vi_def=true,
@@ -2477,6 +2789,7 @@ return {
},
{
full_name='tagstack', abbreviation='tgst',
+ short_desc=N_("push tags onto the tag stack"),
type='bool', scope={'global'},
vi_def=true,
varname='p_tgst',
@@ -2484,6 +2797,7 @@ return {
},
{
full_name='termbidi', abbreviation='tbidi',
+ short_desc=N_("terminal takes care of bi-directionality"),
type='bool', scope={'global'},
vi_def=true,
varname='p_tbidi',
@@ -2491,12 +2805,14 @@ return {
},
{
full_name='termencoding', abbreviation='tenc',
+ short_desc=N_("Terminal encodig"),
type='string', scope={'global'},
vi_def=true,
defaults={if_true={vi=""}}
},
{
full_name='termguicolors', abbreviation='tgc',
+ short_desc=N_("Terminal true color support"),
type='bool', scope={'global'},
vi_def=false,
redraw={'ui_option'},
@@ -2505,6 +2821,7 @@ return {
},
{
full_name='terse',
+ short_desc=N_("hides notification of search wrap"),
type='bool', scope={'global'},
vi_def=true,
varname='p_terse',
@@ -2512,6 +2829,7 @@ return {
},
{
full_name='textwidth', abbreviation='tw',
+ short_desc=N_("maximum width of text that is being inserted"),
type='number', scope={'buffer'},
vi_def=true,
vim=true,
@@ -2521,6 +2839,7 @@ return {
},
{
full_name='thesaurus', abbreviation='tsr',
+ short_desc=N_("list of thesaurus files for keyword completion"),
type='string', list='onecomma', scope={'global', 'buffer'},
deny_duplicates=true,
normal_dname_chars=true,
@@ -2531,6 +2850,7 @@ return {
},
{
full_name='tildeop', abbreviation='top',
+ short_desc=N_("tilde command \"~\" behaves like an operator"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -2539,6 +2859,7 @@ return {
},
{
full_name='timeout', abbreviation='to',
+ short_desc=N_("time out on mappings and key codes"),
type='bool', scope={'global'},
vi_def=true,
varname='p_timeout',
@@ -2546,6 +2867,7 @@ return {
},
{
full_name='timeoutlen', abbreviation='tm',
+ short_desc=N_("time out time in milliseconds"),
type='number', scope={'global'},
vi_def=true,
varname='p_tm',
@@ -2553,6 +2875,7 @@ return {
},
{
full_name='title',
+ short_desc=N_("Vim set the title of the window"),
type='bool', scope={'global'},
vi_def=true,
varname='p_title',
@@ -2560,6 +2883,7 @@ return {
},
{
full_name='titlelen',
+ short_desc=N_("of 'columns' used for window title"),
type='number', scope={'global'},
vi_def=true,
varname='p_titlelen',
@@ -2567,6 +2891,7 @@ return {
},
{
full_name='titleold',
+ short_desc=N_("title, restored when exiting"),
type='string', scope={'global'},
secure=true,
no_mkrc=true,
@@ -2576,6 +2901,7 @@ return {
},
{
full_name='titlestring',
+ short_desc=N_("to use for the Vim window title"),
type='string', scope={'global'},
vi_def=true,
modelineexpr=true,
@@ -2584,6 +2910,7 @@ return {
},
{
full_name='ttimeout',
+ short_desc=N_("out on mappings"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -2593,6 +2920,7 @@ return {
},
{
full_name='ttimeoutlen', abbreviation='ttm',
+ short_desc=N_("time out time for key codes in milliseconds"),
type='number', scope={'global'},
vi_def=true,
redraw={'ui_option'},
@@ -2601,6 +2929,7 @@ return {
},
{
full_name='ttyfast', abbreviation='tf',
+ short_desc=N_("No description"),
type='bool', scope={'global'},
no_mkrc=true,
vi_def=true,
@@ -2609,6 +2938,7 @@ return {
},
{
full_name='undodir', abbreviation='udir',
+ short_desc=N_("where to store undo files"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
secure=true,
@@ -2619,6 +2949,7 @@ return {
},
{
full_name='undofile', abbreviation='udf',
+ short_desc=N_("save undo information in a file"),
type='bool', scope={'buffer'},
vi_def=true,
vim=true,
@@ -2627,6 +2958,7 @@ return {
},
{
full_name='undolevels', abbreviation='ul',
+ short_desc=N_("maximum number of changes that can be undone"),
type='number', scope={'global', 'buffer'},
vi_def=true,
varname='p_ul',
@@ -2634,6 +2966,7 @@ return {
},
{
full_name='undoreload', abbreviation='ur',
+ short_desc=N_("max nr of lines to save for undo on a buffer reload"),
type='number', scope={'global'},
vi_def=true,
varname='p_ur',
@@ -2641,6 +2974,7 @@ return {
},
{
full_name='updatecount', abbreviation='uc',
+ short_desc=N_("after this many characters flush swap file"),
type='number', scope={'global'},
vi_def=true,
varname='p_uc',
@@ -2648,6 +2982,7 @@ return {
},
{
full_name='updatetime', abbreviation='ut',
+ short_desc=N_("after this many milliseconds flush swap file"),
type='number', scope={'global'},
vi_def=true,
varname='p_ut',
@@ -2655,6 +2990,7 @@ return {
},
{
full_name='verbose', abbreviation='vbs',
+ short_desc=N_("give informative messages"),
type='number', scope={'global'},
vi_def=true,
varname='p_verbose',
@@ -2662,6 +2998,7 @@ return {
},
{
full_name='verbosefile', abbreviation='vfile',
+ short_desc=N_("file to write messages in"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -2671,6 +3008,7 @@ return {
},
{
full_name='viewdir', abbreviation='vdir',
+ short_desc=N_("directory where to store files with :mkview"),
type='string', scope={'global'},
secure=true,
vi_def=true,
@@ -2680,6 +3018,7 @@ return {
},
{
full_name='viewoptions', abbreviation='vop',
+ short_desc=N_("specifies what to save for :mkview"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -2689,15 +3028,18 @@ return {
{
-- Alias for "shada".
full_name='viminfo', abbreviation='vi',
+ short_desc=N_("Alias for shada"),
type='string', scope={'global'}, nodefault=true,
},
{
-- Alias for "shadafile".
full_name='viminfofile', abbreviation='vif',
+ short_desc=N_("Alias for shadafile instead"),
type='string', scope={'global'}, nodefault=true,
},
{
full_name='virtualedit', abbreviation='ve',
+ short_desc=N_("when to use virtual editing"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -2708,6 +3050,7 @@ return {
},
{
full_name='visualbell', abbreviation='vb',
+ short_desc=N_("use visual bell instead of beeping"),
type='bool', scope={'global'},
vi_def=true,
varname='p_vb',
@@ -2715,6 +3058,7 @@ return {
},
{
full_name='warn',
+ short_desc=N_("for shell command when buffer was changed"),
type='bool', scope={'global'},
vi_def=true,
varname='p_warn',
@@ -2722,6 +3066,7 @@ return {
},
{
full_name='whichwrap', abbreviation='ww',
+ short_desc=N_("allow specified keys to cross line boundaries"),
type='string', list='flagscomma', scope={'global'},
vim=true,
varname='p_ww',
@@ -2729,6 +3074,7 @@ return {
},
{
full_name='wildchar', abbreviation='wc',
+ short_desc=N_("command-line character for wildcard expansion"),
type='number', scope={'global'},
vim=true,
varname='p_wc',
@@ -2736,6 +3082,7 @@ return {
},
{
full_name='wildcharm', abbreviation='wcm',
+ short_desc=N_("like 'wildchar' but also works when mapped"),
type='number', scope={'global'},
vi_def=true,
varname='p_wcm',
@@ -2743,6 +3090,7 @@ return {
},
{
full_name='wildignore', abbreviation='wig',
+ short_desc=N_("files matching these patterns are not completed"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vi_def=true,
@@ -2751,6 +3099,7 @@ return {
},
{
full_name='wildignorecase', abbreviation='wic',
+ short_desc=N_("ignore case when completing file names"),
type='bool', scope={'global'},
vi_def=true,
varname='p_wic',
@@ -2758,6 +3107,7 @@ return {
},
{
full_name='wildmenu', abbreviation='wmnu',
+ short_desc=N_("use menu for command line completion"),
type='bool', scope={'global'},
vim=true,
varname='p_wmnu',
@@ -2765,6 +3115,7 @@ return {
},
{
full_name='wildmode', abbreviation='wim',
+ short_desc=N_("mode for 'wildchar' command-line expansion"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vim=true,
@@ -2773,6 +3124,7 @@ return {
},
{
full_name='wildoptions', abbreviation='wop',
+ short_desc=N_("specifies how command line completion is done"),
type='string', list='onecomma', scope={'global'},
deny_duplicates=true,
vim=true,
@@ -2781,6 +3133,7 @@ return {
},
{
full_name='winaltkeys', abbreviation='wak',
+ short_desc=N_("when the windows system handles ALT keys"),
type='string', scope={'global'},
vi_def=true,
varname='p_wak',
@@ -2788,6 +3141,7 @@ return {
},
{
full_name='winblend', abbreviation='winbl',
+ short_desc=N_("Controls transparency level for floating windows"),
type='number', scope={'window'},
vi_def=true,
redraw={'current_window'},
@@ -2795,6 +3149,7 @@ return {
},
{
full_name='winhighlight', abbreviation='winhl',
+ short_desc=N_("Setup window-local highlights");
type='string', scope={'window'},
vi_def=true,
alloced=true,
@@ -2803,6 +3158,7 @@ return {
},
{
full_name='window', abbreviation='wi',
+ short_desc=N_("nr of lines to scroll for CTRL-F and CTRL-B"),
type='number', scope={'global'},
vi_def=true,
varname='p_window',
@@ -2810,6 +3166,7 @@ return {
},
{
full_name='winheight', abbreviation='wh',
+ short_desc=N_("minimum number of lines for the current window"),
type='number', scope={'global'},
vi_def=true,
varname='p_wh',
@@ -2817,6 +3174,7 @@ return {
},
{
full_name='winfixheight', abbreviation='wfh',
+ short_desc=N_("keep window height when opening/closing windows"),
type='bool', scope={'window'},
vi_def=true,
redraw={'statuslines'},
@@ -2824,6 +3182,7 @@ return {
},
{
full_name='winfixwidth', abbreviation='wfw',
+ short_desc=N_("keep window width when opening/closing windows"),
type='bool', scope={'window'},
vi_def=true,
redraw={'statuslines'},
@@ -2831,6 +3190,7 @@ return {
},
{
full_name='winminheight', abbreviation='wmh',
+ short_desc=N_("minimum number of lines for any window"),
type='number', scope={'global'},
vi_def=true,
varname='p_wmh',
@@ -2838,6 +3198,7 @@ return {
},
{
full_name='winminwidth', abbreviation='wmw',
+ short_desc=N_("minimal number of columns for any window"),
type='number', scope={'global'},
vi_def=true,
varname='p_wmw',
@@ -2845,6 +3206,7 @@ return {
},
{
full_name='winwidth', abbreviation='wiw',
+ short_desc=N_("minimal number of columns for current window"),
type='number', scope={'global'},
vi_def=true,
varname='p_wiw',
@@ -2852,6 +3214,7 @@ return {
},
{
full_name='wrap',
+ short_desc=N_("lines wrap and continue on the next line"),
type='bool', scope={'window'},
vi_def=true,
redraw={'current_window'},
@@ -2859,6 +3222,7 @@ return {
},
{
full_name='wrapmargin', abbreviation='wm',
+ short_desc=N_("chars from the right where wrapping starts"),
type='number', scope={'buffer'},
vi_def=true,
varname='p_wm',
@@ -2866,6 +3230,7 @@ return {
},
{
full_name='wrapscan', abbreviation='ws',
+ short_desc=N_("searches wrap around the end of the file"),
type='bool', scope={'global'},
vi_def=true,
varname='p_ws',
@@ -2873,6 +3238,7 @@ return {
},
{
full_name='write',
+ short_desc=N_("to a file is allowed"),
type='bool', scope={'global'},
vi_def=true,
varname='p_write',
@@ -2880,6 +3246,7 @@ return {
},
{
full_name='writeany', abbreviation='wa',
+ short_desc=N_("write to file with no need for \"!\" override"),
type='bool', scope={'global'},
vi_def=true,
varname='p_wa',
@@ -2887,6 +3254,7 @@ return {
},
{
full_name='writebackup', abbreviation='wb',
+ short_desc=N_("make a backup before overwriting a file"),
type='bool', scope={'global'},
vi_def=true,
vim=true,
@@ -2895,6 +3263,7 @@ return {
},
{
full_name='writedelay', abbreviation='wd',
+ short_desc=N_("delay this many msec for each char (for debug)"),
type='number', scope={'global'},
vi_def=true,
varname='p_wd',
diff --git a/src/nvim/os/dl.c b/src/nvim/os/dl.c
index 2783411574..8483d316f3 100644
--- a/src/nvim/os/dl.c
+++ b/src/nvim/os/dl.c
@@ -20,8 +20,8 @@
typedef void (*gen_fn)(void);
typedef const char *(*str_str_fn)(const char *str);
typedef int (*str_int_fn)(const char *str);
-typedef const char *(*int_str_fn)(int64_t i);
-typedef int (*int_int_fn)(int64_t i);
+typedef const char *(*int_str_fn)(int i);
+typedef int (*int_int_fn)(int i);
/// os_libcall - call a function in a dynamic loadable library
///
@@ -41,7 +41,7 @@ typedef int (*int_int_fn)(int64_t i);
bool os_libcall(const char *libname,
const char *funcname,
const char *argv,
- int64_t argi,
+ int argi,
char **str_out,
int *int_out)
{
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index 082ad58223..879266e3d4 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -1176,7 +1176,9 @@ bool os_setenv_append_path(const char *fname)
temp[0] = NUL;
} else {
xstrlcpy(temp, path, newlen);
- xstrlcat(temp, ENV_SEPSTR, newlen);
+ if (ENV_SEPCHAR != path[pathlen - 1]) {
+ xstrlcat(temp, ENV_SEPSTR, newlen);
+ }
}
xstrlcat(temp, os_buf, newlen);
os_setenv("PATH", temp, 1);
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 873b611151..a3bef3389c 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -743,7 +743,9 @@ static int os_stat(const char *name, uv_stat_t *statbuf)
}
uv_fs_t request;
int result = uv_fs_stat(&fs_loop, &request, name, NULL);
- *statbuf = request.statbuf;
+ if (result == kLibuvSuccess) {
+ *statbuf = request.statbuf;
+ }
uv_fs_req_cleanup(&request);
return result;
}
@@ -1009,6 +1011,7 @@ int os_remove(const char *path)
bool os_fileinfo(const char *path, FileInfo *file_info)
FUNC_ATTR_NONNULL_ARG(2)
{
+ memset(file_info, 0, sizeof(*file_info));
return os_stat(path, &(file_info->stat)) == kLibuvSuccess;
}
@@ -1020,14 +1023,17 @@ bool os_fileinfo(const char *path, FileInfo *file_info)
bool os_fileinfo_link(const char *path, FileInfo *file_info)
FUNC_ATTR_NONNULL_ARG(2)
{
+ memset(file_info, 0, sizeof(*file_info));
if (path == NULL) {
return false;
}
uv_fs_t request;
- int result = uv_fs_lstat(&fs_loop, &request, path, NULL);
- file_info->stat = request.statbuf;
+ bool ok = uv_fs_lstat(&fs_loop, &request, path, NULL) == kLibuvSuccess;
+ if (ok) {
+ file_info->stat = request.statbuf;
+ }
uv_fs_req_cleanup(&request);
- return (result == kLibuvSuccess);
+ return ok;
}
/// Get the file information for a given file descriptor
@@ -1039,10 +1045,16 @@ bool os_fileinfo_fd(int file_descriptor, FileInfo *file_info)
FUNC_ATTR_NONNULL_ALL
{
uv_fs_t request;
- int result = uv_fs_fstat(&fs_loop, &request, file_descriptor, NULL);
- file_info->stat = request.statbuf;
+ memset(file_info, 0, sizeof(*file_info));
+ bool ok = uv_fs_fstat(&fs_loop,
+ &request,
+ file_descriptor,
+ NULL) == kLibuvSuccess;
+ if (ok) {
+ file_info->stat = request.statbuf;
+ }
uv_fs_req_cleanup(&request);
- return (result == kLibuvSuccess);
+ return ok;
}
/// Compare the inodes of two FileInfos
diff --git a/src/nvim/os/lang.c b/src/nvim/os/lang.c
index fe2d7986bf..603191a0ff 100644
--- a/src/nvim/os/lang.c
+++ b/src/nvim/os/lang.c
@@ -43,14 +43,20 @@ void lang_init(void)
}
}
+ char buf[50] = { 0 };
+ bool set_lang;
if (lang_region) {
- os_setenv("LANG", lang_region, true);
+ set_lang = true;
+ xstrlcpy(buf, lang_region, sizeof(buf));
} else {
- char buf[20] = { 0 };
- if (CFStringGetCString(cf_lang_region, buf, 20,
- kCFStringEncodingUTF8)) {
- os_setenv("LANG", buf, true);
+ set_lang = CFStringGetCString(cf_lang_region, buf, 40,
+ kCFStringEncodingUTF8);
+ }
+ if (set_lang) {
+ if (strcasestr(buf, "utf-8") == NULL) {
+ xstrlcat(buf, ".UTF-8", sizeof(buf));
}
+ os_setenv("LANG", buf, true);
}
CFRelease(cf_lang_region);
# ifdef HAVE_LOCALE_H
diff --git a/src/nvim/os/os_defs.h b/src/nvim/os/os_defs.h
index c8ac4218f6..1b7859b0d3 100644
--- a/src/nvim/os/os_defs.h
+++ b/src/nvim/os/os_defs.h
@@ -16,10 +16,11 @@
#define BASENAMELEN (NAME_MAX - 5)
// Use the system path length if it makes sense.
-#if defined(PATH_MAX) && (PATH_MAX > 1024)
+# define DEFAULT_MAXPATHL 4096
+#if defined(PATH_MAX) && (PATH_MAX > DEFAULT_MAXPATHL)
# define MAXPATHL PATH_MAX
#else
-# define MAXPATHL 1024
+# define MAXPATHL DEFAULT_MAXPATHL
#endif
// Command-processing buffer. Use large buffers for all platforms.
@@ -44,4 +45,55 @@
# define os_strtok strtok_r
#endif
+// stat macros
+#ifndef S_ISDIR
+# ifdef S_IFDIR
+# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
+# else
+# define S_ISDIR(m) 0
+# endif
+#endif
+#ifndef S_ISREG
+# ifdef S_IFREG
+# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
+# else
+# define S_ISREG(m) 0
+# endif
+#endif
+#ifndef S_ISBLK
+# ifdef S_IFBLK
+# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
+# else
+# define S_ISBLK(m) 0
+# endif
+#endif
+#ifndef S_ISSOCK
+# ifdef S_IFSOCK
+# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
+# else
+# define S_ISSOCK(m) 0
+# endif
+#endif
+#ifndef S_ISFIFO
+# ifdef S_IFIFO
+# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
+# else
+# define S_ISFIFO(m) 0
+# endif
+#endif
+#ifndef S_ISCHR
+# ifdef S_IFCHR
+# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
+# else
+# define S_ISCHR(m) 0
+# endif
+#endif
+#ifndef S_ISLNK
+# ifdef S_IFLNK
+# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
+# else
+# define S_ISLNK(m) 0
+# endif
+#endif
+
#endif // NVIM_OS_OS_DEFS_H
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index 6294d5e4e2..b5d890bf52 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -150,11 +150,11 @@ int os_expand_wildcards(int num_pat, char_u **pat, int *num_file,
return FAIL;
}
- // Don't allow the use of backticks in secure and restricted mode.
- if (secure || restricted) {
+ // Don't allow the use of backticks in secure.
+ if (secure) {
for (i = 0; i < num_pat; i++) {
if (vim_strchr(pat[i], '`') != NULL
- && (check_restricted() || check_secure())) {
+ && (check_secure())) {
return FAIL;
}
}
diff --git a/src/nvim/os/signal.c b/src/nvim/os/signal.c
index bfe230b521..bc774b8ebc 100644
--- a/src/nvim/os/signal.c
+++ b/src/nvim/os/signal.c
@@ -161,8 +161,8 @@ static void deadly_signal(int signum)
WLOG("got signal %d (%s)", signum, signal_name(signum));
- snprintf((char *)IObuff, sizeof(IObuff), "Vim: Caught deadly signal '%s'\n",
- signal_name(signum));
+ snprintf((char *)IObuff, sizeof(IObuff), "Vim: Caught deadly signal '%s'\r\n",
+ signal_name(signum));
// Preserve files and exit.
preserve_exit();
diff --git a/src/nvim/os/time.c b/src/nvim/os/time.c
index 346e40e02e..4b6533cd0c 100644
--- a/src/nvim/os/time.c
+++ b/src/nvim/os/time.c
@@ -56,6 +56,8 @@ uint64_t os_now(void)
/// Sleeps for `ms` milliseconds.
///
+/// @see uv_sleep() (libuv v1.34.0)
+///
/// @param ms Number of milliseconds to sleep
/// @param ignoreinput If true, only SIGINT (CTRL-C) can interrupt.
void os_delay(uint64_t ms, bool ignoreinput)
@@ -72,6 +74,8 @@ void os_delay(uint64_t ms, bool ignoreinput)
/// Sleeps for `us` microseconds.
///
+/// @see uv_sleep() (libuv v1.34.0)
+///
/// @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.
@@ -172,10 +176,11 @@ char *os_ctime_r(const time_t *restrict clock, char *restrict result,
struct tm *clock_local_ptr = os_localtime_r(clock, &clock_local);
// MSVC returns NULL for an invalid value of seconds.
if (clock_local_ptr == NULL) {
- snprintf(result, result_len, "%s\n", _("(Invalid)"));
+ xstrlcpy(result, _("(Invalid)"), result_len);
} else {
- strftime(result, result_len, "%a %b %d %H:%M:%S %Y\n", clock_local_ptr);
+ strftime(result, result_len, _("%a %b %d %H:%M:%S %Y"), clock_local_ptr);
}
+ xstrlcat(result, "\n", result_len);
return result;
}
diff --git a/src/nvim/os/win_defs.h b/src/nvim/os/win_defs.h
index 356094baa1..66d72de08d 100644
--- a/src/nvim/os/win_defs.h
+++ b/src/nvim/os/win_defs.h
@@ -74,28 +74,6 @@ typedef int mode_t;
# define O_NOFOLLOW 0
#endif
-#if !defined(S_ISDIR) && defined(S_IFDIR)
-# define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)
-#endif
-#if !defined(S_ISREG) && defined(S_IFREG)
-# define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)
-#endif
-#if !defined(S_ISLNK) && defined(S_IFLNK)
-# define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)
-#endif
-#if !defined(S_ISBLK) && defined(S_IFBLK)
-# define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)
-#endif
-#if !defined(S_ISSOCK) && defined(S_IFSOCK)
-# define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)
-#endif
-#if !defined(S_ISFIFO) && defined(S_IFIFO)
-# define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)
-#endif
-#if !defined(S_ISCHR) && defined(S_IFCHR)
-# define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)
-#endif
-
#ifndef STDIN_FILENO
# define STDIN_FILENO 0
#endif
diff --git a/src/nvim/po/check.vim b/src/nvim/po/check.vim
index 650c6155e2..d55d4cfa4d 100644
--- a/src/nvim/po/check.vim
+++ b/src/nvim/po/check.vim
@@ -25,6 +25,7 @@ func! GetMline()
" remove '%', not used for formatting.
let idline = substitute(idline, "'%'", '', 'g')
+ let idline = substitute(idline, "%%", '', 'g')
" remove '%' used for plural forms.
let idline = substitute(idline, '\\nPlural-Forms: .\+;\\n', '', '')
diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po
index 19ea8e897a..f2179f4f1b 100644
--- a/src/nvim/po/uk.po
+++ b/src/nvim/po/uk.po
@@ -14,8 +14,8 @@ msgid ""
msgstr ""
"Project-Id-Version: vim 7.4\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2018-12-18 22:42+0200\n"
-"PO-Revision-Date: 2018-12-18 22:42+0200\n"
+"POT-Creation-Date: 2020-08-23 18:45+0300\n"
+"PO-Revision-Date: 2020-08-23 20:19+0300\n"
"Last-Translator: Анатолій Сахнік <sakhnik@gmail.com>\n"
"Language-Team: Ukrainian\n"
"Language: uk\n"
@@ -95,6 +95,12 @@ msgid ""
"E89: No write since last change for buffer %<PRId64> (add ! to override)"
msgstr "E89: Буфер %<PRId64> має зміни (! щоб не зважати)"
+msgid "E37: No write since last change (add ! to override)"
+msgstr "E37: Зміни не було записано (! щоб не зважати)"
+
+msgid "E37: No write since last change"
+msgstr "E37: Не записано після останніх змін"
+
msgid "W14: Warning: List of file names overflow"
msgstr "W14: Обережно: Список назв файлів переповнено"
@@ -123,9 +129,6 @@ msgstr " [Змінено]"
msgid "[Not edited]"
msgstr "[Не редаговано]"
-msgid "[New file]"
-msgstr "[Новий файл]"
-
msgid "[Read errors]"
msgstr "[Помилки читання]"
@@ -168,23 +171,17 @@ msgstr "Знизу"
msgid "Top"
msgstr "Вгорі"
-msgid "[Scratch]"
-msgstr "[З нуля]"
+msgid "E382: Cannot write, 'buftype' option is set"
+msgstr "E382: Не можу записати, вказана опція 'buftype'"
-msgid ""
-"\n"
-"--- Signs ---"
-msgstr ""
-"\n"
-"--- Позначки ---"
+msgid "[Prompt]"
+msgstr "[Запит]"
-#, c-format
-msgid "Signs for %s:"
-msgstr "Позначки для %s:"
+msgid "[Scratch]"
+msgstr "[З нуля]"
-#, c-format
-msgid " line=%<PRId64> id=%d name=%s"
-msgstr " рядок=%<PRId64> id=%d назва=%s"
+msgid "W10: Warning: Changing a readonly file"
+msgstr "W10: Застереження: Змінюється файл призначений лише для читання"
msgid "can only be opened in headless mode"
msgstr "можна відкрити тільки без інтерфейсу користувача"
@@ -198,6 +195,9 @@ msgstr "Неможливо надіслати дані у закритий по
msgid "Can't send raw data to rpc channel"
msgstr "Неможливо надіслати дані у канал завдання"
+msgid "E474: Failed to convert list to msgpack string buffer"
+msgstr "E474: Не вдалося перетворити список у текстовий буфер msgpack"
+
msgid "E545: Missing colon"
msgstr "E545: Пропущено двокрапку"
@@ -233,9 +233,6 @@ msgstr "E816: Не вдалося прочитати результат patch"
msgid "E98: Cannot read diff output"
msgstr "E98: Не вдалося прочитати результат diff"
-msgid "E959: Invalid diff format."
-msgstr "E959: Неправильний формат порівняння."
-
msgid "E99: Current buffer is not in diff mode"
msgstr "E99: Цей буфер не в режимі порівняння"
@@ -264,6 +261,81 @@ msgstr "E787: Буфер несподівано змінився"
msgid "E104: Escape not allowed in digraph"
msgstr "E104: У диграфах не може міститися escape"
+msgid "Custom"
+msgstr "Власне"
+
+msgid "Latin supplement"
+msgstr "Латиниця доповнення"
+
+msgid "Greek and Coptic"
+msgstr "Грецька і коптська"
+
+msgid "Cyrillic"
+msgstr "Кирилиця"
+
+msgid "Hebrew"
+msgstr "Іврит"
+
+msgid "Arabic"
+msgstr "Арабська"
+
+msgid "Latin extended"
+msgstr "Латиниця розширена"
+
+msgid "Greek extended"
+msgstr "Грецька розширена"
+
+msgid "Punctuation"
+msgstr "Пунктуація"
+
+msgid "Super- and subscripts"
+msgstr "Над- і підписи"
+
+msgid "Currency"
+msgstr "Валюта"
+
+msgid "Other"
+msgstr "Інше"
+
+msgid "Roman numbers"
+msgstr "Римські цифри"
+
+msgid "Arrows"
+msgstr "Стрілки"
+
+msgid "Mathematical operators"
+msgstr "Математичні оператори"
+
+msgid "Technical"
+msgstr "Технічне"
+
+msgid "Box drawing"
+msgstr "Малювання прямокутників"
+
+msgid "Block elements"
+msgstr "Частини блоків"
+
+msgid "Geometric shapes"
+msgstr "Геометричні фігури"
+
+msgid "Symbols"
+msgstr "Символи"
+
+msgid "Dingbats"
+msgstr "Дурниці"
+
+msgid "CJK symbols and punctuation"
+msgstr "Символи і пунктуація CJK"
+
+msgid "Hiragana"
+msgstr "Хірагана"
+
+msgid "Katakana"
+msgstr "Катакана"
+
+msgid "Bopomofo"
+msgstr "Бопомофо"
+
msgid "E544: Keymap file not found"
msgstr "E544: Не знайдено файл розкладки"
@@ -379,55 +451,18 @@ msgstr "E18: Неочікувані символи у :let"
msgid "E111: Missing ']'"
msgstr "E111: Бракує ']'"
-#, c-format
-msgid "E686: Argument of %s must be a List"
-msgstr "E686: Аргумент у %s має бути списком"
-
-#, c-format
-msgid "E712: Argument of %s must be a List or Dictionary"
-msgstr "E712: Аргумент у %s має бути списком чи словником"
-
-msgid "E714: List required"
-msgstr "E714: Потрібен список"
-
-msgid "E715: Dictionary required"
-msgstr "E715: Потрібен словник"
-
-msgid "E928: String required"
-msgstr "E928: Потрібен String"
-
-#, c-format
-msgid "E118: Too many arguments for function: %s"
-msgstr "E118: Забагато аргументів для функції: %s"
-
-#, c-format
-msgid "E716: Key not present in Dictionary: %s"
-msgstr "E716: Немає такого ключа у словнику: %s"
-
-#, c-format
-msgid "E122: Function %s already exists, add ! to replace it"
-msgstr "E122: Функція %s уже існує, ! щоб замінити"
-
-msgid "E717: Dictionary entry already exists"
-msgstr "E717: Запис у словнику вже існує"
-
-msgid "E718: Funcref required"
-msgstr "E718: Треба посилання на функцію"
-
msgid "E719: Cannot use [:] with a Dictionary"
msgstr "E719: Не можна використати [:] зі словником"
#, c-format
-msgid "E130: Unknown function: %s"
-msgstr "E130: Невідома функція: %s"
-
-#, c-format
msgid "E461: Illegal variable name: %s"
msgstr "E461: Неприпустима назва змінної: %s"
-#, c-format
-msgid "E46: Cannot change read-only variable \"%.*s\""
-msgstr "E46: Змінна тільки для читання: «%.*s»"
+msgid "E995: Cannot modify existing variable"
+msgstr "E995: Неможливо змінити наявну змінну"
+
+msgid "E957: Invalid window number"
+msgstr "E957: Некоректний номер вікна"
#, c-format
msgid "E734: Wrong variable type for %s="
@@ -439,6 +474,19 @@ msgid ""
msgstr ""
"E5700: Вираз із 'spellsuggest' має повертати список із рівно двома елементами"
+msgid "E991: cannot use =<< here"
+msgstr "E991: Тут не можна використати =<<"
+
+msgid "E221: Marker cannot start with lower case letter"
+msgstr "E221: Позначка не може починатися із малої літери"
+
+msgid "E172: Missing marker"
+msgstr "E172: Бракує позначки"
+
+#, c-format
+msgid "E990: Missing end marker '%s'"
+msgstr "E990: Бракує позначки кінця «%s»"
+
msgid "E687: Less targets than List items"
msgstr "E687: Цілей менше, ніж елементів списку"
@@ -452,6 +500,15 @@ msgstr "Друга ; у списку змінних"
msgid "E738: Can't list variables for %s"
msgstr "E738: Не можна перерахувати змінні у %s"
+msgid "E996: Cannot lock an environment variable"
+msgstr "E996: Неможливо заблокувати змінну оточення"
+
+msgid "E996: Cannot lock an option"
+msgstr "E996: Неможливо заблокувати опцію"
+
+msgid "E996: Cannot lock a register"
+msgstr "E996: Неможливо заблокувати регістр"
+
#, c-format
msgid "E121: Undefined variable: %.*s"
msgstr "E121: Невизначена змінна: %.*s"
@@ -468,20 +525,22 @@ msgstr "E713: Неможливо вжити порожній ключ після
msgid "E709: [:] requires a List value"
msgstr "E709: [:] вимагає список"
+msgid "E996: Cannot lock a range"
+msgstr "E996: Неможливо заблокувати діапазон"
+
msgid "E710: List value has more items than target"
msgstr "E710: Список має більше елементів, ніж ціль"
msgid "E711: List value has not enough items"
msgstr "E711: Список має недостатньо елементів"
+msgid "E996: Cannot lock a list or dict"
+msgstr "E996: Неможливо заблокувати список чи словник"
+
msgid "E690: Missing \"in\" after :for"
msgstr "E690: Пропущено «in» після :for"
#, c-format
-msgid "E107: Missing parentheses: %s"
-msgstr "E107: Пропущено дужки: %s"
-
-#, c-format
msgid "E108: No such variable: \"%s\""
msgstr "E108: Змінної немає: «%s»"
@@ -563,68 +622,6 @@ msgstr "E722: Бракує коми у словнику: %s"
msgid "E723: Missing end of Dictionary '}': %s"
msgstr "E723: Немає кінцівки словника '}': %s"
-#, c-format
-msgid "E125: Illegal argument: %s"
-msgstr "E125: Недозволений аргумент: %s"
-
-#, c-format
-msgid "E853: Duplicate argument name: %s"
-msgstr "E853: Назва аргументу повторюється: %s"
-
-#, c-format
-msgid "E740: Too many arguments for function %s"
-msgstr "E740: Забагато аргументів для функції %s"
-
-#, c-format
-msgid "E116: Invalid arguments for function %s"
-msgstr "E116: Неправильні аргументи функції %s"
-
-#, c-format
-msgid "E117: Unknown function: %s"
-msgstr "E117: Невідома функція: %s"
-
-#, c-format
-msgid "E933: Function was deleted: %s"
-msgstr "E933: Функцію було видалено: %s"
-
-#, c-format
-msgid "E119: Not enough arguments for function: %s"
-msgstr "E119: Замало аргументів для функції %s"
-
-#, c-format
-msgid "E120: Using <SID> not in a script context: %s"
-msgstr "E120: <SID> використовується не у контексті скрипту: %s"
-
-#, c-format
-msgid "E725: Calling dict function without Dictionary: %s"
-msgstr "E725: Виклик dict-функції без словника: %s"
-
-#, c-format
-msgid "Error converting the call result: %s"
-msgstr "Не вдалося перетворити результат виклику: %s"
-
-msgid "add() argument"
-msgstr "аргумент add()"
-
-msgid "E699: Too many arguments"
-msgstr "E699: Забагато аргументів"
-
-#, c-format
-msgid "Invalid channel stream \"%s\""
-msgstr "Некоректний потік завдання «%s»"
-
-msgid "E785: complete() can only be used in Insert mode"
-msgstr "E785: complete() можна вживати тільки в режимі вставляння"
-
-msgid "&Ok"
-msgstr "&O:Гаразд"
-
-msgid "dictwatcheradd() argument"
-msgstr "аргумент dictwatcheradd()"
-
-msgid "extend() argument"
-msgstr "аргумент extend()"
-
msgid "map() argument"
msgstr "аргумент map()"
@@ -641,124 +638,13 @@ msgstr "E922: очікується словник"
msgid "E923: Second argument of function() must be a list or a dict"
msgstr "E923: Другий аргумент function() має бути списком чи словником"
-msgid "E5000: Cannot find tab number."
-msgstr "E5000: Не можна знайти номер вкладки."
-
-msgid "E5001: Higher scope cannot be -1 if lower scope is >= 0."
-msgstr "E5001: Вища область не може бути -1, якщо нижча область >= 0."
-
-msgid "E5002: Cannot find window number."
-msgstr "E5002: Неможливо знайти номер вікна."
-
msgid "E5050: {opts} must be the only argument"
msgstr "E5050: {opts} має бути єдиним аргументом"
-msgid "called inputrestore() more often than inputsave()"
-msgstr "Виклики до inputrestore() частіше, ніж до inputsave()"
-
-msgid "insert() argument"
-msgstr "аргумент insert()"
-
-msgid "E786: Range not allowed"
-msgstr "E786: Інтервал не дозволено"
-
-msgid "E474: Failed to convert list to string"
-msgstr "E474: Не вдалося перетворити список у текст"
-
-#, c-format
-msgid "E474: Failed to parse %.*s"
-msgstr "E474: Не вдалося розібрати %.*s"
-
-msgid "E701: Invalid type for len()"
-msgstr "E701: Некоректний тип для len()"
-
-#, c-format
-msgid "E798: ID is reserved for \":match\": %<PRId64>"
-msgstr "E798: ID зарезервовано для \":match\": %<PRId64>"
-
-#, c-format
-msgid "E798: ID is reserved for \"match\": %<PRId64>"
-msgstr "E798: ID зарезервовано для \"match\": %<PRId64>"
-
-#, c-format
-msgid "msgpackdump() argument, index %i"
-msgstr "аргумент msgpackdump(), індекс %i"
-
-msgid "E5070: Character number must not be less than zero"
-msgstr "E5070: Номер символа має бути невід’ємним"
-
-#, c-format
-msgid "E5071: Character number must not be greater than INT_MAX (%i)"
-msgstr "E5071: Номер символа не може бути більшим, ніж INT_MAX (%i)"
-
-msgid "E726: Stride is zero"
-msgstr "E726: Крок нульовий"
-
-msgid "E727: Start past end"
-msgstr "E727: Початок за кінцем"
-
-msgid "<empty>"
-msgstr "<нічого>"
-
-msgid "remove() argument"
-msgstr "аргумент remove()"
-
-msgid "E655: Too many symbolic links (cycle?)"
-msgstr "E655: Забагато символьних посилань (цикл?)"
-
-msgid "reverse() argument"
-msgstr "аргумент reverse()"
-
-#, c-format
-msgid "E5010: List item %d of the second argument is not a string"
-msgstr "E5010: Елемент списку %d другого аргументу не текст"
-
-#, c-format
-msgid "E927: Invalid action: '%s'"
-msgstr "E927: Неправильна дія: «%s»"
-
-#, c-format
-msgid "E474: List item %d is either not a dictionary or an empty one"
-msgstr "E474: Елемент списку %d або не словник або порожній"
-
-#, c-format
-msgid "E474: List item %d is missing one of the required keys"
-msgstr "E474: Елемент списку %d немає одного з обов’язкових ключів"
-
-#, c-format
-msgid "connection failed: %s"
-msgstr "з’єднання не вдалося: %s"
-
-msgid "sort() argument"
-msgstr "аргумент sort()"
-
-msgid "uniq() argument"
-msgstr "аргумент uniq()"
-
-msgid "E702: Sort compare function failed"
-msgstr "E702: Помилка у функції порівняння"
-
-msgid "E882: Uniq compare function failed"
-msgstr "E882: Помилка у функції порівняння uniq"
-
-#, c-format
-msgid "E6100: \"%s\" is not a valid stdpath"
-msgstr "E6100: \"%s\" — некоректний stdpath"
-
-msgid "(Invalid)"
-msgstr "(Неможливо)"
-
-#, c-format
-msgid "E935: invalid submatch number: %d"
-msgstr "E935: неправильний номер групи співпадіння: %d"
-
#, c-format
msgid "Executing command: \"%s\""
msgstr "Виконується команда: «%s»"
-msgid "Can only call this function in an unmodified buffer"
-msgstr "Цю функцію можна викликати тільки у незміненому буфері"
-
msgid "E921: Invalid callback argument"
msgstr "E921: Некоректний аргумент функції зворотнього виклику"
@@ -767,21 +653,6 @@ msgid "E80: Error while writing: %s"
msgstr "E80: Помилка під час запису: %s"
#, c-format
-msgid "E5060: Unknown flag: %s"
-msgstr "E5060: Невідомий прапорець: %s"
-
-msgid "E482: Can't open file with an empty name"
-msgstr "E482: Не вдалося відкрити файл з порожнім ім’ям"
-
-#, c-format
-msgid "E482: Can't open file %s for writing: %s"
-msgstr "E482: Не вдалося відкрити файл %s для запису: %s"
-
-#, c-format
-msgid "E80: Error when closing file %s: %s"
-msgstr "E80: Помилка при закритті файлу %s: %s"
-
-#, c-format
msgid "E963: setting %s to value with wrong type"
msgstr "E963: встановлення %s до значення з неправильним типом"
@@ -799,90 +670,11 @@ msgstr "E704: Назва змінної Funcref має починатися з
#, c-format
msgid "E705: Variable name conflicts with existing function: %s"
-msgstr "E705: Назва змінної співпадає з існуючою функцією: %s"
+msgstr "E705: Назва змінної співпадає з наявною функцією: %s"
msgid "E698: variable nested too deep for making a copy"
msgstr "E698: Змінна вкладена занадто глибоко щоб зробити її копію"
-#, c-format
-msgid "E123: Undefined function: %s"
-msgstr "E123: Невизначена функція: %s"
-
-#, c-format
-msgid "E124: Missing '(': %s"
-msgstr "E124: Бракує '(': %s"
-
-msgid "E862: Cannot use g: here"
-msgstr "E862: Тут не можна використати g:"
-
-#, c-format
-msgid "E932: Closure function should not be at top level: %s"
-msgstr "E932: Функція замикання не може бути на верхньому рівні: %s"
-
-msgid "E126: Missing :endfunction"
-msgstr "E126: Бракує :endfunction"
-
-#, c-format
-msgid "W22: Text found after :endfunction: %s"
-msgstr "W22: Трапився текст після :endfunction: %s"
-
-#, c-format
-msgid "E707: Function name conflicts with variable: %s"
-msgstr "E707: Назва функції співпадає зі змінною: %s"
-
-#, c-format
-msgid "E127: Cannot redefine function %s: It is in use"
-msgstr "E127: Не вдалося перевизначити функцію %s: вона використовується"
-
-#, c-format
-msgid "E746: Function name does not match script file name: %s"
-msgstr "E746: Назва функції не збігається з назвою файлу скрипту: %s"
-
-msgid "E129: Function name required"
-msgstr "E129: Не вказано назву функції"
-
-#, c-format
-msgid "E128: Function name must start with a capital or \"s:\": %s"
-msgstr "E128: Назва функції має починатися з великої літери або \"s:\": %s"
-
-#, c-format
-msgid "E884: Function name cannot contain a colon: %s"
-msgstr "E884: Назва функції не може містити двокрапку: %s"
-
-#, c-format
-msgid "E131: Cannot delete function %s: It is in use"
-msgstr "E131: Не вдалося знищити функцію %s: Вона використовується"
-
-#, c-format
-msgid "Cannot delete function %s: It is being used internally"
-msgstr "Не вдалося знищити функцію %s: Вона використовується"
-
-msgid "E132: Function call depth is higher than 'maxfuncdepth'"
-msgstr "E132: Глибина викликів функції перевищує 'maxfuncdepth'"
-
-#, c-format
-msgid "calling %s"
-msgstr "викликається %s"
-
-#, c-format
-msgid "%s aborted"
-msgstr "%s припинено"
-
-#, c-format
-msgid "%s returning #%<PRId64>"
-msgstr "%s повертає #%<PRId64>"
-
-#, c-format
-msgid "%s returning %s"
-msgstr "%s повертає %s"
-
-#, c-format
-msgid "continuing in %s"
-msgstr "продовження в %s"
-
-msgid "E133: :return not inside a function"
-msgstr "E133: :return поза межами функції"
-
msgid ""
"\n"
"\tLast set from "
@@ -1155,6 +947,179 @@ msgid "E684: list index out of range: %<PRId64>"
msgstr "E684: Індекс списку поза межами: %<PRId64>"
#, c-format
+msgid "E686: Argument of %s must be a List"
+msgstr "E686: Аргумент у %s має бути списком"
+
+msgid "E928: String required"
+msgstr "E928: Потрібен String"
+
+#, c-format
+msgid "Error converting the call result: %s"
+msgstr "Не вдалося перетворити результат виклику: %s"
+
+msgid "add() argument"
+msgstr "аргумент add()"
+
+#, c-format
+msgid "E158: Invalid buffer name: %s"
+msgstr "E158: Некоректна назва буфера: %s"
+
+#, c-format
+msgid "Invalid channel stream \"%s\""
+msgstr "Некоректний потік завдання «%s»"
+
+msgid "E785: complete() can only be used in Insert mode"
+msgstr "E785: complete() можна вживати тільки в режимі вставляння"
+
+msgid "&Ok"
+msgstr "&O:Гаразд"
+
+msgid "Context stack is empty"
+msgstr "Стек контексту порожній"
+
+msgid "dictwatcheradd() argument"
+msgstr "аргумент dictwatcheradd()"
+
+msgid "E900: maxdepth must be non-negative number"
+msgstr "E900: maxdepth має бути невід’ємним числом"
+
+msgid "flatten() argument"
+msgstr "аргумент flatten()"
+
+msgid "extend() argument"
+msgstr "аргумент extend()"
+
+msgid "E5000: Cannot find tab number."
+msgstr "E5000: Не можна знайти номер вкладки."
+
+msgid "E5001: Higher scope cannot be -1 if lower scope is >= 0."
+msgstr "E5001: Вища область не може бути -1, якщо нижча область >= 0."
+
+msgid "E5002: Cannot find window number."
+msgstr "E5002: Неможливо знайти номер вікна."
+
+msgid "called inputrestore() more often than inputsave()"
+msgstr "Виклики до inputrestore() частіше, ніж до inputsave()"
+
+msgid "insert() argument"
+msgstr "аргумент insert()"
+
+msgid "E786: Range not allowed"
+msgstr "E786: Інтервал не дозволено"
+
+msgid "E474: Failed to convert list to string"
+msgstr "E474: Не вдалося перетворити список у текст"
+
+#, c-format
+msgid "E474: Failed to parse %.*s"
+msgstr "E474: Не вдалося розібрати %.*s"
+
+msgid "E701: Invalid type for len()"
+msgstr "E701: Некоректний тип для len()"
+
+#, c-format
+msgid "E798: ID is reserved for \":match\": %<PRId64>"
+msgstr "E798: ID зарезервовано для \":match\": %<PRId64>"
+
+#, c-format
+msgid "E798: ID is reserved for \"match\": %<PRId64>"
+msgstr "E798: ID зарезервовано для \"match\": %<PRId64>"
+
+#, c-format
+msgid "msgpackdump() argument, index %i"
+msgstr "аргумент msgpackdump(), індекс %i"
+
+msgid "E5070: Character number must not be less than zero"
+msgstr "E5070: Номер символа має бути невід’ємним"
+
+#, c-format
+msgid "E5071: Character number must not be greater than INT_MAX (%i)"
+msgstr "E5071: Номер символа не може бути більшим, ніж INT_MAX (%i)"
+
+msgid "E726: Stride is zero"
+msgstr "E726: Крок нульовий"
+
+msgid "E727: Start past end"
+msgstr "E727: Початок за кінцем"
+
+msgid "<empty>"
+msgstr "<нічого>"
+
+msgid "remove() argument"
+msgstr "аргумент remove()"
+
+msgid "E655: Too many symbolic links (cycle?)"
+msgstr "E655: Забагато символьних посилань (цикл?)"
+
+msgid "reverse() argument"
+msgstr "аргумент reverse()"
+
+#, c-format
+msgid "E5010: List item %d of the second argument is not a string"
+msgstr "E5010: Елемент списку %d другого аргументу не текст"
+
+#, c-format
+msgid "E927: Invalid action: '%s'"
+msgstr "E927: Неправильна дія: «%s»"
+
+#, c-format
+msgid "E474: List item %d is either not a dictionary or an empty one"
+msgstr "E474: Елемент списку %d або не словник або порожній"
+
+#, c-format
+msgid "E474: List item %d is missing one of the required keys"
+msgstr "E474: Елемент списку %d немає одного з обов’язкових ключів"
+
+#, c-format
+msgid "E962: Invalid action: '%s'"
+msgstr "E962: Неправильна дія: «%s»"
+
+#, c-format
+msgid "connection failed: %s"
+msgstr "з’єднання не вдалося: %s"
+
+msgid "sort() argument"
+msgstr "аргумент sort()"
+
+msgid "uniq() argument"
+msgstr "аргумент uniq()"
+
+msgid "E702: Sort compare function failed"
+msgstr "E702: Помилка у функції порівняння"
+
+msgid "E882: Uniq compare function failed"
+msgstr "E882: Помилка у функції порівняння uniq"
+
+#, c-format
+msgid "E6100: \"%s\" is not a valid stdpath"
+msgstr "E6100: \"%s\" — некоректний stdpath"
+
+msgid "(Invalid)"
+msgstr "(Неможливо)"
+
+#, c-format
+msgid "E935: invalid submatch number: %d"
+msgstr "E935: неправильний номер групи співпадіння: %d"
+
+msgid "Can only call this function in an unmodified buffer"
+msgstr "Цю функцію можна викликати тільки у незміненому буфері"
+
+#, c-format
+msgid "E5060: Unknown flag: %s"
+msgstr "E5060: Невідомий прапорець: %s"
+
+msgid "E482: Can't open file with an empty name"
+msgstr "E482: Не вдалося відкрити файл з порожнім ім’ям"
+
+#, c-format
+msgid "E482: Can't open file %s for writing: %s"
+msgstr "E482: Не вдалося відкрити файл %s для запису: %s"
+
+#, c-format
+msgid "E80: Error when closing file %s: %s"
+msgstr "E80: Помилка при закритті файлу %s: %s"
+
+#, c-format
msgid "E5142: Failed to open file %s: %s"
msgstr "E5142: Не вдалося відкрити файл %s: %s"
@@ -1199,6 +1164,9 @@ msgstr "E745: Очікується Number чи String, трапився List"
msgid "E728: Expected a Number or a String, Dictionary found"
msgstr "E728: Очікується Number чи String, трапився Dictionary"
+msgid "E5299: Expected a Number or a String, Boolean found"
+msgstr "E5299: Очікується Number чи String, трапився Boolean"
+
msgid "E5300: Expected a Number or a String"
msgstr "E5300: Очікується Number чи String"
@@ -1235,12 +1203,151 @@ msgstr "E893: List вжито як Float"
msgid "E894: Using a Dictionary as a Float"
msgstr "E894: Dictionary вжито як Float"
+msgid "E362: Using a boolean value as a Float"
+msgstr "E362: Використано логічне значення як Float"
+
msgid "E907: Using a special value as a Float"
msgstr "E907: Використано спеціальне значення як Float"
msgid "E808: Number or Float required"
msgstr "E808: Треба вказати Number чи Float"
+#, c-format
+msgid "E122: Function %s already exists, add ! to replace it"
+msgstr "E122: Функція %s уже існує, ! щоб замінити"
+
+msgid "E717: Dictionary entry already exists"
+msgstr "E717: Запис у словнику вже існує"
+
+msgid "E718: Funcref required"
+msgstr "E718: Треба посилання на функцію"
+
+#, c-format
+msgid "E130: Unknown function: %s"
+msgstr "E130: Невідома функція: %s"
+
+#, c-format
+msgid "E125: Illegal argument: %s"
+msgstr "E125: Недозволений аргумент: %s"
+
+#, c-format
+msgid "E853: Duplicate argument name: %s"
+msgstr "E853: Назва аргументу повторюється: %s"
+
+#, c-format
+msgid "E740: Too many arguments for function %s"
+msgstr "E740: Забагато аргументів для функції %s"
+
+#, c-format
+msgid "E116: Invalid arguments for function %s"
+msgstr "E116: Неправильні аргументи функції %s"
+
+msgid "E132: Function call depth is higher than 'maxfuncdepth'"
+msgstr "E132: Глибина викликів функції перевищує 'maxfuncdepth'"
+
+#, c-format
+msgid "calling %s"
+msgstr "викликається %s"
+
+#, c-format
+msgid "%s aborted"
+msgstr "%s припинено"
+
+#, c-format
+msgid "%s returning #%<PRId64>"
+msgstr "%s повертає #%<PRId64>"
+
+#, c-format
+msgid "%s returning %s"
+msgstr "%s повертає %s"
+
+#, c-format
+msgid "continuing in %s"
+msgstr "продовження в %s"
+
+msgid "E699: Too many arguments"
+msgstr "E699: Забагато аргументів"
+
+#, c-format
+msgid "E117: Unknown function: %s"
+msgstr "E117: Невідома функція: %s"
+
+#, c-format
+msgid "E933: Function was deleted: %s"
+msgstr "E933: Функцію було видалено: %s"
+
+#, c-format
+msgid "E119: Not enough arguments for function: %s"
+msgstr "E119: Замало аргументів для функції %s"
+
+#, c-format
+msgid "E120: Using <SID> not in a script context: %s"
+msgstr "E120: <SID> використовується не у контексті скрипту: %s"
+
+#, c-format
+msgid "E725: Calling dict function without Dictionary: %s"
+msgstr "E725: Виклик dict-функції без словника: %s"
+
+msgid "E129: Function name required"
+msgstr "E129: Не вказано назву функції"
+
+#, c-format
+msgid "E128: Function name must start with a capital or \"s:\": %s"
+msgstr "E128: Назва функції має починатися з великої літери або \"s:\": %s"
+
+#, c-format
+msgid "E884: Function name cannot contain a colon: %s"
+msgstr "E884: Назва функції не може містити двокрапку: %s"
+
+#, c-format
+msgid "E123: Undefined function: %s"
+msgstr "E123: Невизначена функція: %s"
+
+#, c-format
+msgid "E124: Missing '(': %s"
+msgstr "E124: Бракує '(': %s"
+
+msgid "E862: Cannot use g: here"
+msgstr "E862: Тут не можна використати g:"
+
+#, c-format
+msgid "E932: Closure function should not be at top level: %s"
+msgstr "E932: Функція замикання не може бути на верхньому рівні: %s"
+
+msgid "E126: Missing :endfunction"
+msgstr "E126: Бракує :endfunction"
+
+#, c-format
+msgid "W22: Text found after :endfunction: %s"
+msgstr "W22: Трапився текст після :endfunction: %s"
+
+#, c-format
+msgid "E707: Function name conflicts with variable: %s"
+msgstr "E707: Назва функції співпадає зі змінною: %s"
+
+#, c-format
+msgid "E127: Cannot redefine function %s: It is in use"
+msgstr "E127: Не вдалося перевизначити функцію %s: вона використовується"
+
+#, c-format
+msgid "E746: Function name does not match script file name: %s"
+msgstr "E746: Назва функції не збігається з назвою файлу скрипту: %s"
+
+#, c-format
+msgid "E131: Cannot delete function %s: It is in use"
+msgstr "E131: Не вдалося знищити функцію %s: Вона використовується"
+
+#, c-format
+msgid "Cannot delete function %s: It is being used internally"
+msgstr "Не вдалося знищити функцію %s: Вона використовується"
+
+msgid "E133: :return not inside a function"
+msgstr "E133: :return поза межами функції"
+
+#, c-format
+msgid "E107: Missing parentheses: %s"
+msgstr "E107: Пропущено дужки: %s"
+
msgid "tcp address must be host:port"
msgstr "адреса tcp має бути вузол:порт"
@@ -1306,7 +1413,7 @@ msgstr "E140: Використайте ! для запису частини бу
#, c-format
msgid "Overwrite existing file \"%s\"?"
-msgstr "Переписати існуючий файл «%s»?"
+msgstr "Переписати наявний файл «%s»?"
#, c-format
msgid "Swap file \"%s\" exists, overwrite anyway?"
@@ -1352,8 +1459,9 @@ msgstr "E143: Автокоманди несподівано знищили но
msgid "E144: non-numeric argument to :z"
msgstr "E144: нечисловий аргумент для :z"
-msgid "E145: Shell commands not allowed in restricted mode"
-msgstr "E145: У обмеженому режимі не дозволені команди оболонки"
+msgid ""
+"E145: Shell commands and some functionality not allowed in restricted mode"
+msgstr "E145: У обмеженому режимі не дозволені команди оболонки і деяка функіональність"
msgid "E146: Regular expressions can't be delimited by letters"
msgstr "E146: Регулярні вирази не можна розділяти літерами"
@@ -1439,48 +1547,6 @@ msgstr "E154: Повторення мітки «%s» у файлі %s/%s"
msgid "E150: Not a directory: %s"
msgstr "E150: Не є каталогом: %s"
-#, c-format
-msgid "E160: Unknown sign command: %s"
-msgstr "E160: Невідома команда надпису: %s"
-
-msgid "E156: Missing sign name"
-msgstr "E156: Пропущено назву надпису"
-
-msgid "E612: Too many signs defined"
-msgstr "E612: Визначено забагато надписів"
-
-#, c-format
-msgid "E239: Invalid sign text: %s"
-msgstr "E239: Некоректний надпис: %s"
-
-#, c-format
-msgid "E155: Unknown sign: %s"
-msgstr "E155: Невідомий надпис: %s"
-
-msgid "E159: Missing sign number"
-msgstr "E159: Пропущено номер надпису"
-
-#, c-format
-msgid "E158: Invalid buffer name: %s"
-msgstr "E158: Некоректна назва буфера: %s"
-
-msgid "E934: Cannot jump to a buffer that does not have a name"
-msgstr "E934: Не можна перейти до буфера без назви"
-
-#, c-format
-msgid "E157: Invalid sign ID: %<PRId64>"
-msgstr "E157: Неправильний ID надпису: %<PRId64>"
-
-#, c-format
-msgid "E885: Not possible to change sign %s"
-msgstr "E885: Неможливо змінити знак %s"
-
-msgid " (not supported)"
-msgstr " (не підтримується)"
-
-msgid "[Deleted]"
-msgstr "[Знищено]"
-
msgid "No old files"
msgstr "Жодного старого файлу"
@@ -1545,6 +1611,9 @@ msgstr "E164: Це вже найперший файл"
msgid "E165: Cannot go beyond last file"
msgstr "E165: Це вже останній файл"
+msgid "E610: No argument to delete"
+msgstr "E610: Немає аргументів для знищення"
+
#, c-format
msgid "E666: compiler not supported: %s"
msgstr "E666: Компілятор не підтримується: %s"
@@ -1562,6 +1631,10 @@ msgid "not found in '%s': \"%s\""
msgstr "не знайдено в '%s': «%s»"
#, c-format
+msgid ":source error parsing command %s"
+msgstr ":source помилка розбору команди %s"
+
+#, c-format
msgid "Cannot source a directory: \"%s\""
msgstr "Не вдалося прочитати каталог: «%s»"
@@ -1607,6 +1680,9 @@ msgstr "Lua"
msgid "API client (channel id %<PRIu64>)"
msgstr "Клієнт API (канал «%<PRIu64>»)"
+msgid "anonymous :source"
+msgstr "анонімний :source"
+
msgid "W15: Warning: Wrong line separator, ^M may be missing"
msgstr "W15: Застереження: Неправильний роздільник рядків, можливо, бракує ^M"
@@ -1652,6 +1728,9 @@ msgstr "E464: Неоднозначний вжиток команди корис
msgid "E492: Not an editor command"
msgstr "E492: Це не команда редактора"
+msgid "E981: Command not allowed in restricted mode"
+msgstr "E981: Команду не дозволено у обмеженому режимі"
+
msgid "E493: Backwards range given"
msgstr "E493: Інтервал задано навиворіт"
@@ -1661,6 +1740,9 @@ msgstr "Інтервал задано навиворіт, щоб помінят
msgid "E494: Use w or w>>"
msgstr "E494: Спробуйте w або w>>"
+msgid "E943: Command table needs to be updated, run 'make'"
+msgstr "E943: Потрібно поновити таблицю команд, запустіть 'make'"
+
msgid "E319: The command is not available in this version"
msgstr "E319: Вибачте, цієї команди немає у цій версії"
@@ -1678,15 +1760,16 @@ msgstr "E173: Залишилося відредагувати ще один фа
msgid "E173: %<PRId64> more files to edit"
msgstr "E173: Залишилося %<PRId64> не редагованих файлів"
-msgid "E174: Command already exists: add ! to replace it"
-msgstr "E174: Команда вже існує, ! щоб замінити її"
+#, c-format
+msgid "E174: Command already exists: add ! to replace it: %s"
+msgstr "E174: Команда вже існує, ! щоб замінити її: %s"
msgid ""
"\n"
-" Name Args Address Complete Definition"
+" Name Args Address Complete Definition"
msgstr ""
"\n"
-" Назва Арг. Адреса Доповнення Визначення"
+" Назва Арг. Адреса Доповнення Визначення"
msgid "No user-defined commands found"
msgstr "Не знайдено команд користувача"
@@ -1805,6 +1888,9 @@ msgstr "E498: Немає назви файлу :source для заміни «<sf
msgid "E842: no line number to use for \"<slnum>\""
msgstr "E842: немає номера рядка, щоб використати з «<sfile>»"
+msgid "E961: no line number to use for \"<sflnum>\""
+msgstr "E961: немає номера рядка, щоб використати з «<sflnum>»"
+
#, c-format
msgid "E499: Empty file name for '%' or '#', only works with \":p:h\""
msgstr "E499: Назва файлу для '%' чи '#' порожня, працює лише з «:p:h»"
@@ -1962,9 +2048,6 @@ msgstr " тип файлу\n"
msgid "'history' option is zero"
msgstr "Опція 'history' порожня"
-msgid "E198: cmd_pchar beyond the command length"
-msgstr "E198: cmd_pchar поза межами команди"
-
msgid "E199: Active window or buffer deleted"
msgstr "E199: Активне вікно або буфер було знищено"
@@ -2007,9 +2090,6 @@ msgstr "каталог"
msgid "is not a file"
msgstr "не файл"
-msgid "[New File]"
-msgstr "[Новий файл]"
-
msgid "[New DIRECTORY]"
msgstr "[Новий каталог]"
@@ -2043,6 +2123,9 @@ msgstr "[спец. символьний]"
msgid "[CR missing]"
msgstr "[Бракує CR]"
+msgid "[long lines split]"
+msgstr "[довгі рядки розбито]"
+
msgid "[NOT converted]"
msgstr "[НЕ конвертовано]"
@@ -2069,6 +2152,12 @@ msgstr "Конвертація з 'charconvert' не вдалася"
msgid "can't read output of 'charconvert'"
msgstr "не вдалося прочитати вивід 'charconvert'"
+msgid "[New File]"
+msgstr "[Новий файл]"
+
+msgid "[New]"
+msgstr "[Новий]"
+
msgid "E676: No matching autocommands for acwrite buffer"
msgstr "E676: Немає відповідних автокоманд"
@@ -2087,15 +2176,6 @@ msgstr "лише для читання (! щоб не зважати)"
msgid "E506: Can't write to backup file (add ! to override)"
msgstr "E506: Не вдалося записати резервний файл (! щоб не зважати)"
-#, c-format
-msgid "E507: Close error for backup file (add ! to override): %s"
-msgstr "E507: Помилка закриття резервного файлу (! щоб не зважати): %s"
-
-msgid "E508: Can't read file for backup (add ! to override)"
-msgstr ""
-"E508: Не вдалося прочитати файл щоб створити резервну копію (! щоб не "
-"зважати)"
-
msgid "E509: Cannot create backup file (add ! to override)"
msgstr "E509: Не вдалося створити резервну копію (! щоб не зважати)"
@@ -2116,10 +2196,6 @@ msgid "E212: Can't open file for writing: %s"
msgstr "E212: Не вдалося відкрити файл для запису: %s"
#, c-format
-msgid "E667: Fsync failed: %s"
-msgstr "E667: Не вдалося виконати fsync: %s"
-
-#, c-format
msgid "E512: Close failed: %s"
msgstr "E512: Не вдалося закрити: %s"
@@ -2142,9 +2218,6 @@ msgstr " у рядку %<PRId64>;"
msgid "[Device]"
msgstr "[Пристрій]"
-msgid "[New]"
-msgstr "[Новий]"
-
msgid " [a]"
msgstr "[д]"
@@ -2461,6 +2534,18 @@ msgid "E475: Invalid argument: %s"
msgstr "E475: Некоректний аргумент: %s"
#, c-format
+msgid "E475: Invalid value for argument %s"
+msgstr "E475: Некоректне значення аргументу %s"
+
+#, c-format
+msgid "E475: Invalid value for argument %s: %s"
+msgstr "E475: Некоректне значення аргументу %s: %s"
+
+#, c-format
+msgid "E983: Duplicate argument: %s"
+msgstr "E983: Аргумент повторюється: %s"
+
+#, c-format
msgid "E15: Invalid expression: %s"
msgstr "E15: Неправильний вираз: %s"
@@ -2512,6 +2597,10 @@ msgid "E364: Library call failed for \"%s()\""
msgstr "E364: Бібліотечний виклик до «%s()» не вдався"
#, c-format
+msgid "E667: Fsync failed: %s"
+msgstr "E667: Не вдалося виконати fsync: %s"
+
+#, c-format
msgid "E739: Cannot create directory %s: %s"
msgstr "E739: Не вдалося створити каталог %s: %s"
@@ -2589,12 +2678,6 @@ msgstr "E484: Не вдалося відкрити файл %s: %s"
msgid "E485: Can't read file %s"
msgstr "E485: Не вдалося прочитати файл %s"
-msgid "E37: No write since last change (add ! to override)"
-msgstr "E37: Зміни не було записано (! щоб не зважати)"
-
-msgid "E37: No write since last change"
-msgstr "E37: Не записано після останніх змін"
-
msgid "E38: Null argument"
msgstr "E38: Відсутній аргумент"
@@ -2636,6 +2719,28 @@ msgstr "E44: Зіпсована програма регулярних вираз
msgid "E45: 'readonly' option is set (add ! to override)"
msgstr "E45: Встановлено опцію 'readonly' (! щоб не зважати)"
+#, c-format
+msgid "E46: Cannot change read-only variable \"%.*s\""
+msgstr "E46: Змінна тільки для читання: «%.*s»"
+
+msgid "E715: Dictionary required"
+msgstr "E715: Потрібен словник"
+
+#, c-format
+msgid "E118: Too many arguments for function: %s"
+msgstr "E118: Забагато аргументів для функції: %s"
+
+#, c-format
+msgid "E716: Key not present in Dictionary: %s"
+msgstr "E716: Немає такого ключа у словнику: %s"
+
+msgid "E714: List required"
+msgstr "E714: Потрібен список"
+
+#, c-format
+msgid "E712: Argument of %s must be a List or Dictionary"
+msgstr "E712: Аргумент у %s має бути списком чи словником"
+
msgid "E47: Error while reading errorfile"
msgstr "E47: Помилка читання файлу помилок"
@@ -2756,12 +2861,29 @@ msgstr "E5521: Заміна клавіш <Cmd> має закінчуватися
msgid "E5522: <Cmd> mapping must not include %s key"
msgstr "E5522: Заміна клавіш <Cmd> не може містити ключ %s"
+#, c-format
+msgid "E5555: API call: %s"
+msgstr "E5555: виклик API: %s"
+
+#, c-format
+msgid "E5560: %s must not be called in a lua loop callback"
+msgstr "E5560: %s має бути викликане у контексті циклу lua"
+
+msgid "E5601: Cannot close window, only floating window would remain"
+msgstr "E5601: Не вдалося закрити вікно, залишилося б тільки плавуче вікно"
+
+msgid "E5602: Cannot exchange or rotate float"
+msgstr "E5602: Не можна обміняти чи покрутити плавуче вікно"
+
msgid "search hit TOP, continuing at BOTTOM"
msgstr "Пошук дійшов до ПОЧАТКУ, продовжується з КІНЦЯ"
msgid "search hit BOTTOM, continuing at TOP"
msgstr "Пошук дійшов до КІНЦЯ, продовжується з ПОЧАТКУ"
+msgid " line "
+msgstr " рядок "
+
msgid "E550: Missing colon"
msgstr "E550: Пропущено двокрапку"
@@ -3032,6 +3154,14 @@ msgid "E5102: Lua failed to grow stack to %i"
msgstr "E5102: Lua не вдалося збільшити стек до %i"
#, c-format
+msgid "Error executing vim.schedule lua callback: %.*s"
+msgstr "Помилка виконання обробника lua vim.schedule: %.*s"
+
+#, c-format
+msgid "E5106: Error while creating shared module: %.*s"
+msgstr "E5106: Помилка створення розділюваного модуля: %.*s"
+
+#, c-format
msgid "E5106: Error while creating vim module: %.*s"
msgstr "E5106: Помилка створення модуля vim: %.*s"
@@ -3043,14 +3173,6 @@ msgid "E5117: Error while updating package paths: %.*s"
msgstr "E5117: Помилка оновлення шляхів пакунку: %.*s"
#, c-format
-msgid "E5104: Error while creating lua chunk: %.*s"
-msgstr "E5104: Помилка створення шматку lua: %.*s"
-
-#, c-format
-msgid "E5105: Error while calling lua chunk: %.*s"
-msgstr "E5105: Помилка виклику шматку lua: %.*s"
-
-#, c-format
msgid "E5114: Error while converting print argument #%i: %.*s"
msgstr "E5114: Не вдалося перетворити аргумент #%i друку: %.*s"
@@ -3063,27 +3185,31 @@ msgid "E5116: Error while calling debug string: %.*s"
msgstr "E5116: Помилка виклику налагодження: %.*s"
#, c-format
-msgid "E5107: Error while creating lua chunk for luaeval(): %.*s"
-msgstr "E5107: Помилка створення шматку lua для luaeval(): %.*s"
+msgid "E5107: Error loading lua %.*s"
+msgstr "E5107: Помилка завантаження lua %.*s"
#, c-format
-msgid "E5108: Error while calling lua chunk for luaeval(): %.*s"
-msgstr "E5108: Помилка виклику шматку lua для luaeval(): %.*s"
+msgid "E5108: Error executing lua %.*s"
+msgstr "E5108: Помилка виконання lua %.*s"
+
+#, c-format
+msgid "Error executing lua callback: %.*s"
+msgstr "Помилка виконання обробника lua: %.*s"
msgid "cannot save undo information"
msgstr "Не вдалося записати інформацію повернення"
#, c-format
-msgid "E5109: Error while creating lua chunk: %.*s"
-msgstr "E5109: Помилка створення шматку lua: %.*s"
+msgid "E5109: Error loading lua: %.*s"
+msgstr "E5109: Помилка завантаження lua: %.*s"
#, c-format
-msgid "E5110: Error while creating lua function: %.*s"
-msgstr "E5110: Помилка створення функції lua: %.*s"
+msgid "E5110: Error executing lua: %.*s"
+msgstr "E5110: Помилка виконання lua: %.*s"
#, c-format
-msgid "E5111: Error while calling lua function: %.*s"
-msgstr "E5111: Помилка виклику функції lua: %.*s"
+msgid "E5111: Error calling lua: %.*s"
+msgstr "E5111: Помилка виклику lua: %.*s"
#, c-format
msgid "E5112: Error while creating lua chunk: %.*s"
@@ -3093,6 +3219,10 @@ msgstr "E5112: Помилка створення шматку lua: %.*s"
msgid "E5113: Error while calling lua chunk: %.*s"
msgstr "E5113: Помилка виклику шматку lua: %.*s"
+#, c-format
+msgid "Error executing vim.log_keystroke lua callback: %.*s"
+msgstr "Помилка виконання обробника lua vim.log_keystroke: %.*s"
+
msgid "Argument missing after"
msgstr "Пропущено аргумент після"
@@ -3123,6 +3253,9 @@ msgstr "Не вдалося відкрити для читання: \"%s\": %s\n
msgid "Cannot open for script output: \""
msgstr "Не вдалося відкрити як вихідний файл: \""
+msgid "--embed conflicts with -es/-Es"
+msgstr "--embed конфліктує з -es/-Es"
+
msgid "pre-vimrc command line"
msgstr "команди перед vimrc"
@@ -3567,8 +3700,8 @@ msgid "E315: ml_get: invalid lnum: %<PRId64>"
msgstr "E315: ml_get: неправильний lnum: %<PRId64>"
#, c-format
-msgid "E316: ml_get: cannot find line %<PRId64>"
-msgstr "E316: ml_get: не знайшов рядок %<PRId64>"
+msgid "E316: ml_get: cannot find line %<PRId64> in buffer %d %s"
+msgstr "E316: ml_get: не знайшов рядок %<PRId64> у буфері %d %s"
msgid "E317: pointer block id wrong 3"
msgstr "E317: Вказівник блоку помилковий 3"
@@ -3666,6 +3799,9 @@ msgstr ""
"»,\n"
" щоб позбутися цього повідомлення.\n"
+msgid "Found a swap file that is not useful, deleting it"
+msgstr "Знайдено файл обміну, яким не можна скористатися, знищення"
+
msgid "Swap file \""
msgstr "Файл обміну «"
@@ -3750,6 +3886,10 @@ msgstr ""
"\n"
"--- Меню ---"
+#, c-format
+msgid "E335: Menu not defined for %s mode"
+msgstr "E335: Для режиму %s меню не визначено"
+
msgid "E333: Menu path must lead to a menu item"
msgstr "E333: Шлях повинен вести до елемента меню"
@@ -3758,10 +3898,6 @@ msgid "E334: Menu not found: %s"
msgstr "E334: Меню не знайдено: %s"
#, c-format
-msgid "E335: Menu not defined for %s mode"
-msgstr "E335: Для режиму %s меню не визначено"
-
-#, c-format
msgid "Error detected while processing %s:"
msgstr "Виявлено помилку під час виконання %s:"
@@ -3821,9 +3957,6 @@ msgstr ""
"&D:Жодного\n"
"&C:Скасувати"
-msgid "W10: Warning: Changing a readonly file"
-msgstr "W10: Застереження: Змінюється файл призначений лише для читання"
-
msgid "Type number and <Enter> or click with mouse (empty cancels): "
msgstr "Наберіть число й <Enter> чи клацніть мишкою (порожнє скасовує): "
@@ -3856,9 +3989,6 @@ msgstr "E349: Немає ідентифікатора над курсором"
msgid "E774: 'operatorfunc' is empty"
msgstr "E774: 'operatorfunc' порожня"
-msgid "Warning: terminal cannot highlight"
-msgstr "Застереження: Термінал не підтримує кольори"
-
msgid "E348: No string under cursor"
msgstr "E348: Немає рядка на курсорі"
@@ -3878,6 +4008,10 @@ msgid "Type :qa! and press <Enter> to abandon all changes and exit Nvim"
msgstr ""
"Введіть :qa! і натисність <Enter> щоб відкинути всі зміни і вийти Nvim"
+msgid "Type :qa and press <Enter> to exit Nvim"
+msgstr ""
+"Введіть :qa і натисність <Enter> щоб вийти з Nvim"
+
#, c-format
msgid "1 line %sed 1 time"
msgstr "Один рядок %s-но"
@@ -3998,6 +4132,9 @@ msgstr "E518: Невідома опція"
msgid "E520: Not allowed in a modeline"
msgstr "E520: Не дозволено у modeline"
+msgid "E992: Not allowed in a modeline when 'modelineexpr' is off"
+msgstr "E992: Не дозволено у modeline, коли вимкнено 'modelineexpr'"
+
msgid "E846: Key code not set"
msgstr "E846: Код ключа не встановлено"
@@ -4144,24 +4281,16 @@ msgstr ""
msgid "E5677: Error writing input to shell-command: %s"
msgstr "E5677: Не вдалося записати на вхід команди оболонки: %s"
-msgid ""
-"\n"
-"Could not get security context for "
-msgstr ""
-"\n"
-"Не вдалося отримати контекст безпеки для "
-
-msgid ""
-"\n"
-"Could not set security context for "
-msgstr ""
-"\n"
-"Не вдалося встановити контекст безпеки для "
-
#, c-format
msgid "E447: Can't find file \"%s\" in path"
msgstr "E447: Файл «%s» не знайдено у шляху пошуку"
+msgid "E553: No more items"
+msgstr "E553: Немає більше елементів"
+
+msgid "E926: Current location list was changed"
+msgstr "E926: Цей список місць було змінено"
+
#, c-format
msgid "E372: Too many %%%c in format string"
msgstr "E372: Забагато %%%c у рядку формату"
@@ -4191,18 +4320,12 @@ msgstr "E378: 'errorformat' не містить зразок"
msgid "E379: Missing or empty directory name"
msgstr "E379: Пропущена чи порожня назва каталогу"
-msgid "E553: No more items"
-msgstr "E553: Немає більше елементів"
-
msgid "E924: Current window was closed"
msgstr "E924: Активне вікно було закрито"
msgid "E925: Current quickfix was changed"
msgstr "E925: Цей quickfix було змінено"
-msgid "E926: Current location list was changed"
-msgstr "E926: Цей список місць було змінено"
-
#, c-format
msgid "(%d of %d)%s%s: "
msgstr "(%d з %d)%s%s: "
@@ -4223,9 +4346,6 @@ msgstr "E381: Вершина стеку виправлень"
msgid "No entries"
msgstr "Нічого"
-msgid "E382: Cannot write, 'buftype' option is set"
-msgstr "E382: Не можу записати, вказана опція 'buftype'"
-
msgid "E683: File name missing or invalid pattern"
msgstr "E683: Пропущено назву файлу чи некоректний шаблон"
@@ -4279,6 +4399,12 @@ msgstr "E69: Пропущено ] після %s%%["
msgid "E70: Empty %s%%[]"
msgstr "E70: %s%%[] порожній"
+msgid "E956: Cannot use pattern recursively"
+msgstr "E956: Не можна рекурсивно використати шаблон"
+
+msgid "E65: Illegal back reference"
+msgstr "E65: Некоректне зворотнє посилання"
+
msgid "E339: Pattern too long"
msgstr "E339: Зразок занадто довгий"
@@ -4315,9 +4441,6 @@ msgstr "E63: Некоректно вжито \\_"
msgid "E64: %s%c follows nothing"
msgstr "E64: Після %s%c нічого немає"
-msgid "E65: Illegal back reference"
-msgstr "E65: Некоректне зворотнє посилання"
-
msgid "E68: Invalid character after \\z"
msgstr "E68: Неправильний символ після \\z"
@@ -4658,6 +4781,63 @@ msgstr ""
"Помилка при читанні файлу ShaDa: список буферів у позиції %<PRIu64> містить "
"поле, яке не має назву файлу"
+msgid "[Deleted]"
+msgstr "[Знищено]"
+
+msgid ""
+"\n"
+"--- Signs ---"
+msgstr ""
+"\n"
+"--- Позначки ---"
+
+#, c-format
+msgid "Signs for %s:"
+msgstr "Позначки для %s:"
+
+#, c-format
+msgid " group=%s"
+msgstr " група=%s"
+
+#, c-format
+msgid " line=%ld id=%d%s name=%s priority=%d"
+msgstr " рядок=%ld id=%d%s назва=%s пріоритет=%d"
+
+msgid "E612: Too many signs defined"
+msgstr "E612: Визначено забагато надписів"
+
+#, c-format
+msgid "E239: Invalid sign text: %s"
+msgstr "E239: Некоректний надпис: %s"
+
+#, c-format
+msgid "E155: Unknown sign: %s"
+msgstr "E155: Невідомий надпис: %s"
+
+#, c-format
+msgid "E885: Not possible to change sign %s"
+msgstr "E885: Неможливо змінити знак %s"
+
+msgid "E159: Missing sign number"
+msgstr "E159: Пропущено номер надпису"
+
+#, c-format
+msgid "E157: Invalid sign ID: %<PRId64>"
+msgstr "E157: Неправильний ID надпису: %<PRId64>"
+
+msgid "E934: Cannot jump to a buffer that does not have a name"
+msgstr "E934: Не можна перейти до буфера без назви"
+
+#, c-format
+msgid "E160: Unknown sign command: %s"
+msgstr "E160: Невідома команда надпису: %s"
+
+msgid "E156: Missing sign name"
+msgstr "E156: Пропущено назву надпису"
+
+msgid " (not supported)"
+msgstr " (не підтримується)"
+
msgid "E759: Format error in spell file"
msgstr "E759: Помилка формату у файлі орфографії"
@@ -4709,12 +4889,6 @@ msgstr "Зайвий текст у %s у рядку %d: %s"
msgid "Affix name too long in %s line %d: %s"
msgstr "Назва афіксу завелика у %s у рядку %d: %s"
-msgid "E761: Format error in affix file FOL, LOW or UPP"
-msgstr "E761: Помилка формату у файлі афіксів FOL, LOW чи UPP"
-
-msgid "E762: Character in FOL, LOW or UPP is out of range"
-msgstr "E762: Символ у FOL, LOW чи UPP поза межами"
-
msgid "Compressing word tree..."
msgstr "Стискується дерево слів..."
@@ -4855,10 +5029,6 @@ msgstr "Повторення символу у MAP у %s у рядку %d"
msgid "Unrecognized or duplicate item in %s line %d: %s"
msgstr "Нерозпізнаний чи повторний елемент у %s у рядку %d: %s"
-#, c-format
-msgid "Missing FOL/LOW/UPP line in %s"
-msgstr "Пропущено рядок FOL/LOW/UPP у %s"
-
msgid "COMPOUNDSYLMAX used without SYLLABLE"
msgstr "Вжито COMPOUNDSYLMAX без SYLLABLE"
@@ -4960,8 +5130,8 @@ msgid "Ignored %d words with non-ASCII characters"
msgstr "Проігноровано %d слів із не-ASCII символами"
#, c-format
-msgid "Compressed %d of %d nodes; %d (%d%%) remaining"
-msgstr "Стиснено %d з %d вузлів; залишилося %d (%d%%)"
+msgid "Compressed %s of %ld nodes; %ld (%ld%%) remaining"
+msgstr "Стиснено %s з %ld вузлів; залишилося %ld (%ld%%)"
msgid "Reading back spell file..."
msgstr "Перечитується файл орфографії..."
@@ -5033,25 +5203,34 @@ msgstr "E807: Очікується аргумент Float для printf()"
msgid "E767: Too many arguments to printf()"
msgstr "E767: Забагато аргументів для printf()"
+#, c-format
+msgid "E390: Illegal argument: %s"
+msgstr "E390: Неправильний аргумент: %s"
+
msgid "No Syntax items defined for this buffer"
msgstr "Для буфера не визначено елементів синтаксису"
+msgid "'redrawtime' exceeded, syntax highlighting disabled"
+msgstr "'redrawtime' перевищено, підсвічування синтаксису вимкнено"
+
msgid "syntax conceal on"
msgstr "синтаксичне приховування увімк"
msgid "syntax conceal off"
msgstr "синтаксичне приховування вимк"
-#, c-format
-msgid "E390: Illegal argument: %s"
-msgstr "E390: Неправильний аргумент: %s"
-
msgid "syntax case ignore"
msgstr "синтаксис ігнорувати регістр"
msgid "syntax case match"
msgstr "синтаксис дотримуватися регістру"
+msgid "syntax foldlevel start"
+msgstr "рівень згортки синтаксису початок"
+
+msgid "syntax foldlevel minimum"
+msgstr "рівень згортки синтаксису мінімум"
+
msgid "syntax spell toplevel"
msgstr "синтаксис перевіряти всюди"
@@ -5064,6 +5243,9 @@ msgstr "синтаксис початково"
msgid "syntax iskeyword "
msgstr "синтаксис iskeyword "
+msgid "syntax iskeyword not set"
+msgstr "не встановлено синтаксис iskeyword"
+
#, c-format
msgid "E391: No such syntax cluster: %s"
msgstr "E391: Немає такого синтаксичного кластера: %s"
@@ -5124,7 +5306,7 @@ msgid "E844: invalid cchar value"
msgstr "E844: Некоректне значення cchar"
msgid "E393: group[t]here not accepted here"
-msgstr "E393: group[t]hete тут неприйнятний"
+msgstr "E393: group[t]here тут неприйнятний"
#, c-format
msgid "E394: Didn't find region item for %s"
@@ -5266,6 +5448,12 @@ msgstr "E555: Кінець стеку міток"
msgid "E556: at top of tag stack"
msgstr "E556: Вершина стеку міток"
+msgid "E986: cannot modify the tag stack within tagfunc"
+msgstr "E986: Не можна змінювати стек міток у tagfunc"
+
+msgid "E987: invalid return value from tagfunc"
+msgstr "E987: Некоректне повернене значення з tagfunc"
+
msgid "E425: Cannot go before first matching tag"
msgstr "E425: Це вже найперша відповідна мітка"
@@ -5273,12 +5461,6 @@ msgstr "E425: Це вже найперша відповідна мітка"
msgid "E426: tag not found: %s"
msgstr "E426: Мітку не знайдено: %s"
-msgid " # pri kind tag"
-msgstr " # прі тип мітка"
-
-msgid "file\n"
-msgstr "файл\n"
-
msgid "E427: There is only one matching tag"
msgstr "E427: Лише одна відповідна мітка"
@@ -5303,6 +5485,12 @@ msgstr " Використано мітку, не розрізняючи вел
msgid "E429: File \"%s\" does not exist"
msgstr "E429: Файл «%s» не існує"
+msgid " # pri kind tag"
+msgstr " # прі тип мітка"
+
+msgid "file\n"
+msgstr "файл\n"
+
msgid ""
"\n"
" # TO tag FROM line in file/text"
@@ -5314,9 +5502,6 @@ msgstr ""
msgid "Searching tags file %s"
msgstr "Шукається у файлі теґів %s"
-msgid "Ignoring long line in tags file"
-msgstr "Ігнорується довгий рядок у файлі з позначками"
-
#, c-format
msgid "E431: Format error in tags file \"%s\""
msgstr "E431: Помилка формату у файлі теґів «%s»"
@@ -5453,10 +5638,6 @@ msgstr "Немає нічого скасовувати"
msgid "number changes when saved"
msgstr "номер зміни час збережено"
-#, c-format
-msgid "%<PRId64> seconds ago"
-msgstr "%<PRId64> секунд тому"
-
msgid "E790: undojoin is not allowed after undo"
msgstr "E790: Не можна виконати undojoin після undo"
@@ -5468,22 +5649,22 @@ msgstr "E440: Відсутній рядок скасування"
msgid ""
"\n"
-"Compiled "
+"\n"
+"Features: "
msgstr ""
"\n"
-"Скомпілював "
-
-msgid "by "
-msgstr " "
+"\n"
+"Характеристики: "
msgid ""
"\n"
-"\n"
-"Features: "
+"Compiled "
msgstr ""
"\n"
-"\n"
-"Характеристики: "
+"Скомпілював "
+
+msgid "by "
+msgstr " "
msgid " system vimrc file: \""
msgstr " системний vimrc: \""
diff --git a/src/nvim/popupmnu.c b/src/nvim/popupmnu.c
index c712762bdf..ac0c1f6eb1 100644
--- a/src/nvim/popupmnu.c
+++ b/src/nvim/popupmnu.c
@@ -226,6 +226,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
pum_above = false;
// Leave two lines of context if possible
+ validate_cheight();
if (curwin->w_cline_row + curwin->w_cline_height - curwin->w_wrow >= 3) {
context_lines = 3;
} else {
@@ -300,49 +301,49 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed,
if (pum_width < p_pw) {
pum_width = (int)p_pw;
}
- }
- } else if (((cursor_col > p_pw || cursor_col > max_width) && !pum_rl)
- || (pum_rl && (cursor_col < Columns - p_pw
- || cursor_col < Columns - max_width))) {
- // align pum edge with "cursor_col"
- if (pum_rl && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) {
- pum_col = cursor_col + max_width + pum_scrollbar + 1;
- if (pum_col >= Columns) {
- pum_col = Columns - 1;
- }
- } else if (!pum_rl) {
- if (curwin->w_wincol > Columns - max_width - pum_scrollbar
- && max_width <= p_pw) {
- // use full width to end of the screen
- pum_col = cursor_col - max_width - pum_scrollbar;
- if (pum_col < 0) {
- pum_col = 0;
+ } else if (((cursor_col > p_pw || cursor_col > max_width) && !pum_rl)
+ || (pum_rl && (cursor_col < Columns - p_pw
+ || cursor_col < Columns - max_width))) {
+ // align pum edge with "cursor_col"
+ if (pum_rl && W_ENDCOL(curwin) < max_width + pum_scrollbar + 1) {
+ pum_col = cursor_col + max_width + pum_scrollbar + 1;
+ if (pum_col >= Columns) {
+ pum_col = Columns - 1;
+ }
+ } else if (!pum_rl) {
+ if (curwin->w_wincol > Columns - max_width - pum_scrollbar
+ && max_width <= p_pw) {
+ // use full width to end of the screen
+ pum_col = Columns - max_width - pum_scrollbar;
+ if (pum_col < 0) {
+ pum_col = 0;
+ }
}
}
- }
-
- if (pum_rl) {
- pum_width = pum_col - pum_scrollbar + 1;
- } else {
- pum_width = Columns - pum_col - pum_scrollbar;
- }
- if (pum_width < p_pw) {
- pum_width = (int)p_pw;
if (pum_rl) {
- if (pum_width > pum_col) {
- pum_width = pum_col;
- }
+ pum_width = pum_col - pum_scrollbar + 1;
} else {
- if (pum_width >= Columns - pum_col) {
- pum_width = Columns - pum_col - 1;
- }
+ pum_width = Columns - pum_col - pum_scrollbar;
}
- } else if (pum_width > max_width + pum_kind_width + pum_extra_width + 1
- && pum_width > p_pw) {
- pum_width = max_width + pum_kind_width + pum_extra_width + 1;
+
if (pum_width < p_pw) {
pum_width = (int)p_pw;
+ if (pum_rl) {
+ if (pum_width > pum_col) {
+ pum_width = pum_col;
+ }
+ } else {
+ if (pum_width >= Columns - pum_col) {
+ pum_width = Columns - pum_col - 1;
+ }
+ }
+ } else if (pum_width > max_width + pum_kind_width + pum_extra_width + 1
+ && pum_width > p_pw) {
+ pum_width = max_width + pum_kind_width + pum_extra_width + 1;
+ if (pum_width < p_pw) {
+ pum_width = (int)p_pw;
+ }
}
}
} else if (Columns < def_width) {
@@ -785,7 +786,7 @@ static int pum_set_selected(int n, int repeat)
// Return cursor to where we were
validate_cursor();
- redraw_later(SOME_VALID);
+ redraw_later(curwin, SOME_VALID);
// When the preview window was resized we need to
// update the view on the buffer. Only go back to
@@ -918,11 +919,11 @@ void pum_set_event_info(dict_T *dict)
r = (double)pum_row;
c = (double)pum_col;
}
- tv_dict_add_float(dict, S_LEN("height"), h);
- tv_dict_add_float(dict, S_LEN("width"), w);
- tv_dict_add_float(dict, S_LEN("row"), r);
- tv_dict_add_float(dict, S_LEN("col"), c);
- tv_dict_add_nr(dict, S_LEN("size"), pum_size);
- tv_dict_add_bool(dict, S_LEN("scrollbar"),
- pum_scrollbar ? kBoolVarTrue : kBoolVarFalse);
+ (void)tv_dict_add_float(dict, S_LEN("height"), h);
+ (void)tv_dict_add_float(dict, S_LEN("width"), w);
+ (void)tv_dict_add_float(dict, S_LEN("row"), r);
+ (void)tv_dict_add_float(dict, S_LEN("col"), c);
+ (void)tv_dict_add_nr(dict, S_LEN("size"), pum_size);
+ (void)tv_dict_add_bool(dict, S_LEN("scrollbar"),
+ pum_scrollbar ? kBoolVarTrue : kBoolVarFalse);
}
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index 484168e798..6ca97c3072 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -239,7 +239,10 @@ static char_u *e_no_more_items = (char_u *)N_("E553: No more items");
static char_u *qf_last_bufname = NULL;
static bufref_T qf_last_bufref = { NULL, 0, 0 };
-static char *e_loc_list_changed = N_("E926: Current location list was changed");
+static char *e_current_quickfix_list_was_changed =
+ N_("E925: Current quickfix list was changed");
+static char *e_current_location_list_was_changed =
+ N_("E926: Current location list was changed");
// Counter to prevent autocmds from freeing up location lists when they are
// still being used.
@@ -808,7 +811,7 @@ retry:
}
break;
}
- if (STRLEN(IObuff) < IOSIZE - 1 || IObuff[IOSIZE - 1] == '\n') {
+ if (STRLEN(IObuff) < IOSIZE - 1 || IObuff[IOSIZE - 2] == '\n') {
break;
}
}
@@ -901,6 +904,7 @@ static bool qf_list_has_valid_entries(qf_list_T *qfl)
/// Return a pointer to a list in the specified quickfix stack
static qf_list_T * qf_get_list(qf_info_T *qi, int idx)
+ FUNC_ATTR_NONNULL_ALL
{
return &qi->qf_lists[idx];
}
@@ -1088,6 +1092,7 @@ qf_init_ext(
)
FUNC_ATTR_NONNULL_ARG(1)
{
+ qf_list_T *qfl;
qfstate_T state = { 0 };
qffields_T fields = { 0 };
qfline_T *old_last = NULL;
@@ -1111,15 +1116,16 @@ qf_init_ext(
// make place for a new list
qf_new_list(qi, qf_title);
qf_idx = qi->qf_curlist;
+ qfl = qf_get_list(qi, qf_idx);
} else {
// Adding to existing list, use last entry.
adding = true;
- if (!qf_list_empty(qf_get_list(qi, qf_idx) )) {
- old_last = qi->qf_lists[qf_idx].qf_last;
+ qfl = qf_get_list(qi, qf_idx);
+ if (!qf_list_empty(qfl)) {
+ old_last = qfl->qf_last;
}
}
- qf_list_T *qfl = qf_get_list(qi, qf_idx);
// Use the local value of 'errorformat' if it's set.
if (errorformat == p_efm && tv == NULL && buf && *buf->b_p_efm != NUL) {
@@ -1230,6 +1236,7 @@ static char_u * qf_cmdtitle(char_u *cmd)
/// Return a pointer to the current list in the specified quickfix stack
static qf_list_T * qf_get_curlist(qf_info_T *qi)
+ FUNC_ATTR_NONNULL_ALL
{
return qf_get_list(qi, qi->qf_curlist);
}
@@ -1660,8 +1667,8 @@ static int qf_parse_multiline_pfx(int idx, qf_list_T *qfl, qffields_T *fields)
}
if (!qfprev->qf_col) {
qfprev->qf_col = fields->col;
+ qfprev->qf_viscol = fields->use_viscol;
}
- qfprev->qf_viscol = fields->use_viscol;
if (!qfprev->qf_fnum) {
qfprev->qf_fnum = qf_get_fnum(qfl, qfl->qf_directory,
*fields->namebuf || qfl->qf_directory
@@ -1770,7 +1777,7 @@ static void decr_quickfix_busy(void)
void check_quickfix_busy(void)
{
if (quickfix_busy != 0) {
- EMSGN("quickfix_busy not zero on exit: %ld", (long)quickfix_busy);
+ EMSGN("quickfix_busy not zero on exit: %" PRId64, (int64_t)quickfix_busy);
# ifdef ABORT_ON_INTERNAL_ERROR
abort();
# endif
@@ -2348,25 +2355,27 @@ static qfline_T *get_prev_valid_entry(qf_list_T *qfl, qfline_T *qf_ptr,
/// dir == FORWARD or FORWARD_FILE: next valid entry
/// dir == BACKWARD or BACKWARD_FILE: previous valid entry
static qfline_T *get_nth_valid_entry(qf_list_T *qfl, int errornr,
- qfline_T *qf_ptr, int *qf_index, int dir)
+ int dir, int *new_qfidx)
{
+ qfline_T *qf_ptr = qfl->qf_ptr;
+ int qf_idx = qfl->qf_index;
qfline_T *prev_qf_ptr;
int prev_index;
char_u *err = e_no_more_items;
while (errornr--) {
prev_qf_ptr = qf_ptr;
- prev_index = *qf_index;
+ prev_index = qf_idx;
if (dir == FORWARD || dir == FORWARD_FILE) {
- qf_ptr = get_next_valid_entry(qfl, qf_ptr, qf_index, dir);
+ qf_ptr = get_next_valid_entry(qfl, qf_ptr, &qf_idx, dir);
} else {
- qf_ptr = get_prev_valid_entry(qfl, qf_ptr, qf_index, dir);
+ qf_ptr = get_prev_valid_entry(qfl, qf_ptr, &qf_idx, dir);
}
if (qf_ptr == NULL) {
qf_ptr = prev_qf_ptr;
- *qf_index = prev_index;
+ qf_idx = prev_index;
if (err != NULL) {
EMSG(_(err));
return NULL;
@@ -2377,14 +2386,16 @@ static qfline_T *get_nth_valid_entry(qf_list_T *qfl, int errornr,
err = NULL;
}
+ *new_qfidx = qf_idx;
return qf_ptr;
}
-/// Get n'th (errornr) quickfix entry
-static qfline_T *get_nth_entry(qf_list_T *qfl, int errornr, qfline_T *qf_ptr,
- int *cur_qfidx)
+/// Get n'th (errornr) quickfix entry from the current entry in the quickfix
+/// list 'qfl'. Returns a pointer to the new entry and the index in 'new_qfidx'
+static qfline_T *get_nth_entry(qf_list_T *qfl, int errornr, int *new_qfidx)
{
- int qf_idx = *cur_qfidx;
+ qfline_T *qf_ptr = qfl->qf_ptr;
+ int qf_idx = qfl->qf_index;;
// New error number is less than the current error number
while (errornr < qf_idx && qf_idx > 1 && qf_ptr->qf_prev != NULL) {
@@ -2400,10 +2411,31 @@ static qfline_T *get_nth_entry(qf_list_T *qfl, int errornr, qfline_T *qf_ptr,
qf_ptr = qf_ptr->qf_next;
}
- *cur_qfidx = qf_idx;
+ *new_qfidx = qf_idx;
return qf_ptr;
}
+/// Get a entry specied by 'errornr' and 'dir' from the current
+/// quickfix/location list. 'errornr' specifies the index of the entry and 'dir'
+/// specifies the direction (FORWARD/BACKWARD/FORWARD_FILE/BACKWARD_FILE).
+/// Returns a pointer to the entry and the index of the new entry is stored in
+/// 'new_qfidx'.
+static qfline_T * qf_get_entry(qf_list_T *qfl, int errornr,
+ int dir, int *new_qfidx)
+{
+ qfline_T *qf_ptr = qfl->qf_ptr;
+ int qfidx = qfl->qf_index;
+
+ if (dir != 0) { // next/prev valid entry
+ qf_ptr = get_nth_valid_entry(qfl, errornr, dir, &qfidx);
+ } else if (errornr != 0) { // go to specified number
+ qf_ptr = get_nth_entry(qfl, errornr, &qfidx);
+ }
+
+ *new_qfidx = qfidx;
+ return qf_ptr;
+}
+
// Find a window displaying a Vim help file.
static win_T *qf_find_help_win(void)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
@@ -2423,12 +2455,13 @@ static void win_set_loclist(win_T *wp, qf_info_T *qi)
qi->qf_refcount++;
}
-/// Find a help window or open one.
-static int jump_to_help_window(qf_info_T *qi, int *opened_window)
+/// Find a help window or open one. If 'newwin' is true, then open a new help
+/// window.
+static int jump_to_help_window(qf_info_T *qi, bool newwin, int *opened_window)
{
win_T *wp = NULL;
- if (cmdmod.tab != 0) {
+ if (cmdmod.tab != 0 || newwin) {
wp = NULL;
} else {
wp = qf_find_help_win();
@@ -2446,8 +2479,10 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window)
flags |= WSP_TOP;
}
- if (IS_LL_STACK(qi)) {
- flags |= WSP_NEWLOC; // don't copy the location list
+ // If the user asks to open a new window, then copy the location list.
+ // Otherwise, don't copy the location list.
+ if (IS_LL_STACK(qi) && !newwin) {
+ flags |= WSP_NEWLOC;
}
if (win_split(0, flags) == FAIL) {
@@ -2460,8 +2495,10 @@ static int jump_to_help_window(qf_info_T *qi, int *opened_window)
win_setheight((int)p_hh);
}
- if (IS_LL_STACK(qi)) { // not a quickfix list
- // The new window should use the supplied location list
+ // When using location list, the new window should use the supplied
+ // location list. If the user asks to open a new window, then the new
+ // window will get a copy of the location list.
+ if (IS_LL_STACK(qi) && !newwin) {
win_set_loclist(curwin, qi);
}
}
@@ -2622,14 +2659,19 @@ static void qf_goto_win_with_qfl_file(int qf_fnum)
// Find a suitable window for opening a file (qf_fnum) from the
// quickfix/location list and jump to it. If the file is already opened in a
-// window, jump to it. Otherwise open a new window to display the file. This is
-// called from either a quickfix or a location list window.
-static int qf_jump_to_usable_window(int qf_fnum, int *opened_window)
+// window, jump to it. Otherwise open a new window to display the file. If
+// 'newwin' is true, then always open a new window. This is called from either
+// a quickfix or a location list window.
+static int qf_jump_to_usable_window(int qf_fnum, bool newwin,
+ int *opened_window)
{
win_T *usable_win_ptr = NULL;
bool usable_win = false;
- qf_info_T *ll_ref = curwin->w_llist_ref;
+ // If opening a new window, then don't use the location list referred by
+ // the current window. Otherwise two windows will refer to the same
+ // location list.
+ qf_info_T *ll_ref = newwin ? NULL : curwin->w_llist_ref;
if (ll_ref != NULL) {
// Find a non-quickfix window with this location list
usable_win_ptr = qf_find_win_with_loclist(ll_ref);
@@ -2654,7 +2696,7 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window)
// If there is only one window and it is the quickfix window, create a
// new one above the quickfix window.
- if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win) {
+ if ((ONE_WINDOW && bt_quickfix(curbuf)) || !usable_win || newwin) {
if (qf_open_new_file_win(ll_ref) != OK) {
return FAIL;
}
@@ -2672,52 +2714,52 @@ static int qf_jump_to_usable_window(int qf_fnum, int *opened_window)
/// Edit the selected file or help file.
static int qf_jump_edit_buffer(qf_info_T *qi, qfline_T *qf_ptr, int forceit,
- win_T *oldwin, int *opened_window, int *abort)
+ win_T *oldwin, int *opened_window)
{
qf_list_T *qfl = qf_get_curlist(qi);
+ long old_changetick = qfl->qf_changedtick;
+ int old_qf_curlist = qi->qf_curlist;
qfltype_T qfl_type = qfl->qfl_type;
int retval = OK;
+ unsigned save_qfid = qfl->qf_id;
if (qf_ptr->qf_type == 1) {
// Open help file (do_ecmd() will set b_help flag, readfile() will
// set b_p_ro flag).
if (!can_abandon(curbuf, forceit)) {
no_write_message();
- retval = FAIL;
+ return FAIL;
} else {
retval = do_ecmd(qf_ptr->qf_fnum, NULL, NULL, NULL, (linenr_T)1,
ECMD_HIDE + ECMD_SET_HELP,
oldwin == curwin ? curwin : NULL);
}
} else {
- unsigned save_qfid = qfl->qf_id;
-
retval = buflist_getfile(qf_ptr->qf_fnum, (linenr_T)1,
GETF_SETMARK | GETF_SWITCH, forceit);
+ }
+ // If a location list, check whether the associated window is still
+ // present.
+ if (qfl_type == QFLT_LOCATION && !win_valid_any_tab(oldwin)) {
+ EMSG(_("E924: Current window was closed"));
+ *opened_window = false;
+ return NOTDONE;
+ }
- if (qfl_type == QFLT_LOCATION) {
- // Location list. Check whether the associated window is still
- // present and the list is still valid.
- if (!win_valid_any_tab(oldwin)) {
- EMSG(_("E924: Current window was closed"));
- *abort = true;
- *opened_window = false;
- } else if (!qflist_valid(oldwin, save_qfid)) {
- EMSG(_(e_loc_list_changed));
- *abort = true;
- }
- } else if (!is_qf_entry_present(qfl, qf_ptr)) {
- if (qfl_type == QFLT_QUICKFIX) {
- EMSG(_("E925: Current quickfix was changed"));
- } else {
- EMSG(_(e_loc_list_changed));
- }
- *abort = true;
- }
+ if (qfl_type == QFLT_QUICKFIX && !qflist_valid(NULL, save_qfid)) {
+ EMSG(_(e_current_quickfix_list_was_changed));
+ return NOTDONE;
+ }
- if (*abort) {
- retval = FAIL;
+ if (old_qf_curlist != qi->qf_curlist
+ || old_changetick != qfl->qf_changedtick
+ || !is_qf_entry_present(qfl, qf_ptr)) {
+ if (qfl_type == QFLT_QUICKFIX) {
+ EMSG(_(e_current_quickfix_list_was_changed));
+ } else {
+ EMSG(_(e_current_location_list_was_changed));
}
+ return NOTDONE;
}
return retval;
@@ -2792,22 +2834,133 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr,
msg_scroll = (int)i;
}
-/// jump to a quickfix line
-/// if dir == FORWARD go "errornr" valid entries forward
-/// if dir == BACKWARD go "errornr" valid entries backward
-/// if dir == FORWARD_FILE go "errornr" valid entries files backward
-/// if dir == BACKWARD_FILE go "errornr" valid entries files backward
-/// else if "errornr" is zero, redisplay the same line
-/// else go to entry "errornr"
+/// Find a usable window for opening a file from the quickfix/location list. If
+/// a window is not found then open a new window. If 'newwin' is true, then open
+/// a new window.
+/// Returns OK if successfully jumped or opened a window. Returns FAIL if not
+/// able to jump/open a window. Returns NOTDONE if a file is not associated
+/// with the entry.
+static int qf_jump_open_window(qf_info_T *qi, qfline_T *qf_ptr, bool newwin,
+ int *opened_window)
+{
+ qf_list_T *qfl = qf_get_curlist(qi);
+ long old_changetick = qfl->qf_changedtick;
+ int old_qf_curlist = qi->qf_curlist;
+ qfltype_T qfl_type = qfl->qfl_type;
+
+ // For ":helpgrep" find a help window or open one.
+ if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)) {
+ if (jump_to_help_window(qi, newwin, opened_window) == FAIL) {
+ return FAIL;
+ }
+ }
+ if (old_qf_curlist != qi->qf_curlist
+ || old_changetick != qfl->qf_changedtick
+ || !is_qf_entry_present(qfl, qf_ptr)) {
+ if (qfl_type == QFLT_QUICKFIX) {
+ EMSG(_(e_current_quickfix_list_was_changed));
+ } else {
+ EMSG(_(e_current_location_list_was_changed));
+ }
+ return FAIL;
+ }
+
+ // If currently in the quickfix window, find another window to show the
+ // file in.
+ if (bt_quickfix(curbuf) && !*opened_window) {
+ // If there is no file specified, we don't know where to go.
+ // But do advance, otherwise ":cn" gets stuck.
+ if (qf_ptr->qf_fnum == 0) {
+ return NOTDONE;
+ }
+
+ if (qf_jump_to_usable_window(qf_ptr->qf_fnum, newwin, opened_window)
+ == FAIL) {
+ return FAIL;
+ }
+ }
+ if (old_qf_curlist != qi->qf_curlist
+ || old_changetick != qfl->qf_changedtick
+ || !is_qf_entry_present(qfl, qf_ptr)) {
+ if (qfl_type == QFLT_QUICKFIX) {
+ EMSG(_(e_current_quickfix_list_was_changed));
+ } else {
+ EMSG(_(e_current_location_list_was_changed));
+ }
+ return FAIL;
+ }
+
+ return OK;
+}
+
+/// Edit a selected file from the quickfix/location list and jump to a
+/// particular line/column, adjust the folds and display a message about the
+/// jump.
+/// Returns OK on success and FAIL on failing to open the file/buffer. Returns
+/// NOTDONE if the quickfix/location list is freed by an autocmd when opening
+/// the file.
+static int qf_jump_to_buffer(qf_info_T *qi, int qf_index, qfline_T *qf_ptr,
+ int forceit, win_T *oldwin, int *opened_window,
+ int openfold, int print_message)
+{
+ buf_T *old_curbuf;
+ linenr_T old_lnum;
+ int retval = OK;
+
+ // If there is a file name, read the wanted file if needed, and check
+ // autowrite etc.
+ old_curbuf = curbuf;
+ old_lnum = curwin->w_cursor.lnum;
+
+ if (qf_ptr->qf_fnum != 0) {
+ retval = qf_jump_edit_buffer(qi, qf_ptr, forceit, oldwin,
+ opened_window);
+ if (retval != OK) {
+ return retval;
+ }
+ }
+
+ // When not switched to another buffer, still need to set pc mark
+ if (curbuf == old_curbuf) {
+ setpcmark();
+ }
+
+ qf_jump_goto_line(qf_ptr->qf_lnum, qf_ptr->qf_col, qf_ptr->qf_viscol,
+ qf_ptr->qf_pattern);
+
+ if ((fdo_flags & FDO_QUICKFIX) && openfold) {
+ foldOpenCursor();
+ }
+ if (print_message) {
+ qf_jump_print_msg(qi, qf_index, qf_ptr, old_curbuf, old_lnum);
+ }
+
+ return retval;
+}
+
+/// Jump to a quickfix line and try to use an existing window.
void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit)
{
+ qf_jump_newwin(qi, dir, errornr, forceit, false);
+}
+
+// Jump to a quickfix line.
+// If dir == 0 go to entry "errornr".
+// If dir == FORWARD go "errornr" valid entries forward.
+// If dir == BACKWARD go "errornr" valid entries backward.
+// If dir == FORWARD_FILE go "errornr" valid entries files backward.
+// If dir == BACKWARD_FILE go "errornr" valid entries files backward
+// else if "errornr" is zero, redisplay the same line
+// If 'forceit' is true, then can discard changes to the current buffer.
+// If 'newwin' is true, then open the file in a new window.
+static void qf_jump_newwin(qf_info_T *qi, int dir, int errornr, int forceit,
+ bool newwin)
+{
qf_list_T *qfl;
qfline_T *qf_ptr;
qfline_T *old_qf_ptr;
int qf_index;
int old_qf_index;
- buf_T *old_curbuf;
- linenr_T old_lnum;
char_u *old_swb = p_swb;
unsigned old_swb_flags = swb_flags;
int opened_window = false;
@@ -2830,15 +2983,12 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit)
old_qf_ptr = qf_ptr;
qf_index = qfl->qf_index;
old_qf_index = qf_index;
- if (dir != 0) { // next/prev valid entry
- qf_ptr = get_nth_valid_entry(qfl, errornr, qf_ptr, &qf_index, dir);
- if (qf_ptr == NULL) {
- qf_ptr = old_qf_ptr;
- qf_index = old_qf_index;
- goto theend; // The horror... the horror...
- }
- } else if (errornr != 0) { // go to specified number
- qf_ptr = get_nth_entry(qfl, errornr, qf_ptr, &qf_index);
+
+ qf_ptr = qf_get_entry(qfl, errornr, dir, &qf_index);
+ if (qf_ptr == NULL) {
+ qf_ptr = old_qf_ptr;
+ qf_index = old_qf_index;
+ goto theend;
}
qfl->qf_index = qf_index;
@@ -2848,58 +2998,23 @@ void qf_jump(qf_info_T *qi, int dir, int errornr, int forceit)
print_message = false;
}
- // For ":helpgrep" find a help window or open one.
- if (qf_ptr->qf_type == 1 && (!bt_help(curwin->w_buffer) || cmdmod.tab != 0)) {
- if (jump_to_help_window(qi, &opened_window) == FAIL) {
- goto theend;
- }
+ retval = qf_jump_open_window(qi, qf_ptr, newwin, &opened_window);
+ if (retval == FAIL) {
+ goto failed;
}
-
- // If currently in the quickfix window, find another window to show the
- // file in.
- if (bt_quickfix(curbuf) && !opened_window) {
- // If there is no file specified, we don't know where to go.
- // But do advance, otherwise ":cn" gets stuck.
- if (qf_ptr->qf_fnum == 0) {
- goto theend;
- }
- if (qf_jump_to_usable_window(qf_ptr->qf_fnum, &opened_window) == FAIL) {
- goto failed;
- }
+ if (retval == NOTDONE) {
+ goto theend;
}
- // If there is a file name,
- // read the wanted file if needed, and check autowrite etc.
- old_curbuf = curbuf;
- old_lnum = curwin->w_cursor.lnum;
-
- if (qf_ptr->qf_fnum != 0) {
- int abort = false;
- retval = qf_jump_edit_buffer(qi, qf_ptr, forceit, oldwin, &opened_window,
- &abort);
- if (abort) {
- qi = NULL;
- qf_ptr = NULL;
- }
+ retval = qf_jump_to_buffer(qi, qf_index, qf_ptr, forceit, oldwin,
+ &opened_window, old_KeyTyped, print_message);
+ if (retval == NOTDONE) {
+ // Quickfix/location list is freed by an autocmd
+ qi = NULL;
+ qf_ptr = NULL;
}
- if (retval == OK) {
- // When not switched to another buffer, still need to set pc mark
- if (curbuf == old_curbuf) {
- setpcmark();
- }
-
- if (qf_ptr != NULL) {
- qf_jump_goto_line(qf_ptr->qf_lnum, qf_ptr->qf_col, qf_ptr->qf_viscol,
- qf_ptr->qf_pattern);
- }
-
- if ((fdo_flags & FDO_QUICKFIX) && old_KeyTyped)
- foldOpenCursor();
- if (print_message) {
- qf_jump_print_msg(qi, qf_index, qf_ptr, old_curbuf, old_lnum);
- }
- } else {
+ if (retval != OK) {
if (opened_window) {
win_close(curwin, true); // Close opened window
}
@@ -3185,7 +3300,7 @@ void qf_history(exarg_T *eap)
qf_info_T *qi = qf_cmd_get_stack(eap, false);
int i;
- if (qf_stack_empty(qi) || qf_list_empty(qf_get_curlist(qi))) {
+ if (qf_stack_empty(qi)) {
MSG(_("No entries"));
} else {
for (i = 0; i < qi->qf_listcount; i++) {
@@ -3353,14 +3468,9 @@ void qf_view_result(bool split)
}
if (split) {
- char cmd[32];
-
- snprintf(cmd, sizeof(cmd), "split +%" PRId64 "%s",
- (int64_t)curwin->w_cursor.lnum,
- IS_LL_WINDOW(curwin) ? "ll" : "cc");
- if (do_cmdline_cmd(cmd) == OK) {
- do_cmdline_cmd("clearjumps");
- }
+ // Open the selected entry in a new window
+ qf_jump_newwin(qi, 0, (int)curwin->w_cursor.lnum, false, true);
+ do_cmdline_cmd("clearjumps");
return;
}
@@ -3391,7 +3501,7 @@ void ex_cwindow(exarg_T *eap)
// it if we have errors; otherwise, leave it closed.
if (qf_stack_empty(qi)
|| qfl->qf_nonevalid
- || qf_list_empty(qf_get_curlist(qi))) {
+ || qf_list_empty(qfl)) {
if (win != NULL) {
ex_cclose(eap);
}
@@ -3444,10 +3554,23 @@ static int qf_goto_cwindow(const qf_info_T *qi, bool resize, int sz,
return OK;
}
+// Set options for the buffer in the quickfix or location list window.
+static void qf_set_cwindow_options(void)
+{
+ // switch off 'swapfile'
+ set_option_value("swf", 0L, NULL, OPT_LOCAL);
+ set_option_value("bt", 0L, "quickfix", OPT_LOCAL);
+ set_option_value("bh", 0L, "wipe", OPT_LOCAL);
+ RESET_BINDING(curwin);
+ curwin->w_p_diff = false;
+ set_option_value("fdm", 0L, "manual", OPT_LOCAL);
+}
+
// Open a new quickfix or location list window, load the quickfix buffer and
// set the appropriate options for the window.
// Returns FAIL if the window could not be opened.
-static int qf_open_new_cwindow(const qf_info_T *qi, int height)
+static int qf_open_new_cwindow(qf_info_T *qi, int height)
+ FUNC_ATTR_NONNULL_ALL
{
win_T *oldwin = curwin;
const tabpage_T *const prevtab = curtab;
@@ -3490,14 +3613,13 @@ static int qf_open_new_cwindow(const qf_info_T *qi, int height)
} else {
// Create a new quickfix buffer
(void)do_ecmd(0, NULL, NULL, NULL, ECMD_ONE, ECMD_HIDE, oldwin);
+ }
- // switch off 'swapfile'
- set_option_value("swf", 0L, NULL, OPT_LOCAL);
- set_option_value("bt", 0L, "quickfix", OPT_LOCAL);
- set_option_value("bh", 0L, "wipe", OPT_LOCAL);
- RESET_BINDING(curwin);
- curwin->w_p_diff = false;
- set_option_value("fdm", 0L, "manual", OPT_LOCAL);
+ // Set the options for the quickfix buffer/window (if not already done)
+ // Do this even if the quickfix buffer was already present, as an autocmd
+ // might have previously deleted (:bdelete) the quickfix buffer.
+ if (curbuf->b_p_bt[0] != 'q') {
+ qf_set_cwindow_options();
}
// Only set the height when still in the same tab page and there is no
@@ -3585,7 +3707,7 @@ static void qf_win_goto(win_T *win, linenr_T lnum)
curwin->w_cursor.coladd = 0;
curwin->w_curswant = 0;
update_topline(); // scroll to show the line
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
curwin->w_redr_status = true; // update ruler
curwin = old_curwin;
curbuf = curwin->w_buffer;
@@ -3686,8 +3808,8 @@ static win_T *qf_find_win(const qf_info_T *qi)
// Find a quickfix buffer.
// Searches in windows opened in all the tabs.
-static buf_T *qf_find_buf(const qf_info_T *qi)
- FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+static buf_T *qf_find_buf(qf_info_T *qi)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
FOR_ALL_TAB_WINDOWS(tp, win) {
if (is_qf_win(win, qi)) {
@@ -3750,56 +3872,63 @@ static void qf_update_buffer(qf_info_T *qi, qfline_T *old_last)
// Add an error line to the quickfix buffer.
static int qf_buf_add_line(buf_T *buf, linenr_T lnum, const qfline_T *qfp,
- char_u *dirname)
+ char_u *dirname, bool first_bufline)
FUNC_ATTR_NONNULL_ALL
{
int len;
buf_T *errbuf;
if (qfp->qf_module != NULL) {
- STRCPY(IObuff, qfp->qf_module);
+ STRLCPY(IObuff, qfp->qf_module, IOSIZE - 1);
len = (int)STRLEN(IObuff);
} else if (qfp->qf_fnum != 0
&& (errbuf = buflist_findnr(qfp->qf_fnum)) != NULL
&& errbuf->b_fname != NULL) {
if (qfp->qf_type == 1) { // :helpgrep
- STRLCPY(IObuff, path_tail(errbuf->b_fname), sizeof(IObuff));
+ STRLCPY(IObuff, path_tail(errbuf->b_fname), IOSIZE - 1);
} else {
- // shorten the file name if not done already
- if (errbuf->b_sfname == NULL
- || path_is_absolute(errbuf->b_sfname)) {
+ // Shorten the file name if not done already.
+ // For optimization, do this only for the first entry in a
+ // buffer.
+ if (first_bufline
+ && (errbuf->b_sfname == NULL
+ || path_is_absolute(errbuf->b_sfname))) {
if (*dirname == NUL) {
os_dirname(dirname, MAXPATHL);
}
shorten_buf_fname(errbuf, dirname, false);
}
- STRLCPY(IObuff, errbuf->b_fname, sizeof(IObuff));
+ STRLCPY(IObuff, errbuf->b_fname, IOSIZE - 1);
}
len = (int)STRLEN(IObuff);
} else {
len = 0;
}
- IObuff[len++] = '|';
-
+ if (len < IOSIZE - 1) {
+ IObuff[len++] = '|';
+ }
if (qfp->qf_lnum > 0) {
- snprintf((char *)IObuff + len, sizeof(IObuff), "%" PRId64,
+ snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%" PRId64,
(int64_t)qfp->qf_lnum);
len += (int)STRLEN(IObuff + len);
if (qfp->qf_col > 0) {
- snprintf((char *)IObuff + len, sizeof(IObuff), " col %d", qfp->qf_col);
+ snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), " col %d",
+ qfp->qf_col);
len += (int)STRLEN(IObuff + len);
}
- snprintf((char *)IObuff + len, sizeof(IObuff), "%s",
+ snprintf((char *)IObuff + len, (size_t)(IOSIZE - len), "%s",
(char *)qf_types(qfp->qf_type, qfp->qf_nr));
len += (int)STRLEN(IObuff + len);
} else if (qfp->qf_pattern != NULL) {
qf_fmt_text(qfp->qf_pattern, IObuff + len, IOSIZE - len);
len += (int)STRLEN(IObuff + len);
}
- IObuff[len++] = '|';
- IObuff[len++] = ' ';
+ if (len < IOSIZE - 2) {
+ IObuff[len++] = '|';
+ IObuff[len++] = ' ';
+ }
// Remove newlines and leading whitespace from the text.
// For an unrecognized line keep the indent, the compiler may
@@ -3841,6 +3970,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last)
// Check if there is anything to display
if (qfl != NULL) {
char_u dirname[MAXPATHL];
+ int prev_bufnr = -1;
*dirname = NUL;
@@ -3853,9 +3983,11 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last)
lnum = buf->b_ml.ml_line_count;
}
while (lnum < qfl->qf_count) {
- if (qf_buf_add_line(buf, lnum, qfp, dirname) == FAIL) {
+ if (qf_buf_add_line(buf, lnum, qfp, dirname,
+ prev_bufnr != qfp->qf_fnum) == FAIL) {
break;
}
+ prev_bufnr = qfp->qf_fnum;
lnum++;
qfp = qfp->qf_next;
if (qfp == NULL) {
@@ -4802,7 +4934,7 @@ static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid,
if (!qflist_valid(wp, qfid)) {
if (wp != NULL) {
// An autocmd has freed the location list
- EMSG(_(e_loc_list_changed));
+ EMSG(_(e_current_location_list_was_changed));
return false;
} else {
// Quickfix list is not found, create a new one.
@@ -4820,20 +4952,21 @@ static bool vgr_qflist_valid(win_T *wp, qf_info_T *qi, unsigned qfid,
/// Search for a pattern in all the lines in a buffer and add the matching lines
/// to a quickfix list.
-static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf,
- regmmatch_T *regmatch, long tomatch,
+static bool vgr_match_buflines(qf_list_T *qfl, char_u *fname, buf_T *buf,
+ regmmatch_T *regmatch, long *tomatch,
int duplicate_name, int flags)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 4, 5)
{
bool found_match = false;
- for (long lnum = 1; lnum <= buf->b_ml.ml_line_count && tomatch > 0; lnum++) {
+ for (long lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; lnum++) {
colnr_T col = 0;
while (vim_regexec_multi(regmatch, curwin, buf, lnum, col, NULL,
NULL) > 0) {
// Pass the buffer number so that it gets used even for a
// dummy buffer, unless duplicate_name is set, then the
// buffer will be wiped out below.
- if (qf_add_entry(qf_get_curlist(qi),
+ if (qf_add_entry(qfl,
NULL, // dir
fname,
NULL,
@@ -4852,7 +4985,7 @@ static bool vgr_match_buflines(qf_info_T *qi, char_u *fname, buf_T *buf,
break;
}
found_match = true;
- if (--tomatch == 0) {
+ if (--*tomatch == 0) {
break;
}
if ((flags & VGR_GLOBAL) == 0 || regmatch->endpos[0].lnum > 0) {
@@ -4894,6 +5027,20 @@ static void vgr_jump_to_match(qf_info_T *qi, int forceit, int *redraw_for_dummy,
}
}
+// Return true if "buf" had an existing swap file, the current swap file does
+// not end in ".swp".
+static bool existing_swapfile(const buf_T *buf)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (buf->b_ml.ml_mfp != NULL && buf->b_ml.ml_mfp->mf_fname != NULL) {
+ const char_u *const fname = buf->b_ml.ml_mfp->mf_fname;
+ const size_t len = STRLEN(fname);
+
+ return fname[len - 1] != 'p' || fname[len - 2] != 'w';
+ }
+ return false;
+}
+
// ":vimgrep {pattern} file(s)"
// ":vimgrepadd {pattern} file(s)"
// ":lvimgrep {pattern} file(s)"
@@ -5026,7 +5173,8 @@ void ex_vimgrep(exarg_T *eap)
} else {
// Try for a match in all lines of the buffer.
// For ":1vimgrep" look for first match only.
- found_match = vgr_match_buflines(qi, fname, buf, &regmatch, tomatch,
+ found_match = vgr_match_buflines(qf_get_curlist(qi),
+ fname, buf, &regmatch, &tomatch,
duplicate_name, flags);
if (using_dummy) {
@@ -5050,7 +5198,9 @@ void ex_vimgrep(exarg_T *eap)
if (!found_match) {
wipe_dummy_buffer(buf, dirname_start);
buf = NULL;
- } else if (buf != first_match_buf || (flags & VGR_NOJUMP)) {
+ } else if (buf != first_match_buf
+ || (flags & VGR_NOJUMP)
+ || existing_swapfile(buf)) {
unload_dummy_buffer(buf, dirname_start);
// Keeping the buffer, remove the dummy flag.
buf->b_flags &= ~BF_DUMMY;
@@ -6016,6 +6166,49 @@ static int qf_setprop_context(qf_list_T *qfl, dictitem_T *di)
return OK;
}
+// Set the current index in the specified quickfix list
+static int qf_setprop_curidx(qf_info_T *qi, qf_list_T *qfl,
+ const dictitem_T *di)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int newidx;
+
+ // If the specified index is '$', then use the last entry
+ if (di->di_tv.v_type == VAR_STRING
+ && di->di_tv.vval.v_string != NULL
+ && STRCMP(di->di_tv.vval.v_string, "$") == 0) {
+ newidx = qfl->qf_count;
+ } else {
+ // Otherwise use the specified index
+ bool denote = false;
+ newidx = (int)tv_get_number_chk(&di->di_tv, &denote);
+ if (denote) {
+ return FAIL;
+ }
+ }
+
+ if (newidx < 1) { // sanity check
+ return FAIL;
+ }
+ if (newidx > qfl->qf_count) {
+ newidx = qfl->qf_count;
+ }
+ const int old_qfidx = qfl->qf_index;
+ qfline_T *const qf_ptr = get_nth_entry(qfl, newidx, &newidx);
+ if (qf_ptr == NULL) {
+ return FAIL;
+ }
+ qfl->qf_ptr = qf_ptr;
+ qfl->qf_index = newidx;
+
+ // If the current list is modified and it is displayed in the quickfix
+ // window, then Update it.
+ if (qi->qf_lists[qi->qf_curlist].qf_id == qfl->qf_id) {
+ qf_win_pos_update(qi, old_qfidx);
+ }
+ return OK;
+}
+
/// Set quickfix/location list properties (title, items, context).
/// Also used to add items from parsing a list of lines.
/// Used by the setqflist() and setloclist() Vim script functions.
@@ -6051,6 +6244,9 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action,
if ((di = tv_dict_find(what, S_LEN("context"))) != NULL) {
retval = qf_setprop_context(qfl, di);
}
+ if ((di = tv_dict_find(what, S_LEN("idx"))) != NULL) {
+ retval = qf_setprop_curidx(qi, qfl, di);
+ }
if (retval == OK) {
qf_list_changed(qfl);
@@ -6061,7 +6257,8 @@ static int qf_set_properties(qf_info_T *qi, const dict_T *what, int action,
/// Find the non-location list window with the specified location list stack in
/// the current tabpage.
-static win_T * find_win_with_ll(qf_info_T *qi)
+static win_T *find_win_with_ll(const qf_info_T *qi)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if ((wp->w_llist == qi) && !bt_quickfix(wp->w_buffer)) {
@@ -6120,6 +6317,7 @@ static void qf_free_stack(win_T *wp, qf_info_T *qi)
// 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.
+// When "what" is not NULL then only set some properties.
int set_errorlist(win_T *wp, list_T *list, int action, char_u *title,
dict_T *what)
{
@@ -6136,6 +6334,12 @@ int set_errorlist(win_T *wp, list_T *list, int action, char_u *title,
return OK;
}
+ // A dict argument cannot be specified with a non-empty list argument
+ if (list != NULL && tv_list_len(list) != 0 && what != NULL) {
+ EMSG2(_(e_invarg2), _("cannot have both a list and a \"what\" argument"));
+ return FAIL;
+ }
+
incr_quickfix_busy();
if (what != NULL) {
@@ -6364,7 +6568,7 @@ void ex_cexpr(exarg_T *eap)
// Evaluate the expression. When the result is a string or a list we can
// use it to fill the errorlist.
typval_T tv;
- if (eval0(eap->arg, &tv, NULL, true) != FAIL) {
+ if (eval0(eap->arg, &tv, &eap->nextcmd, true) != FAIL) {
if ((tv.v_type == VAR_STRING && tv.vval.v_string != NULL)
|| tv.v_type == VAR_LIST) {
incr_quickfix_busy();
@@ -6435,7 +6639,7 @@ static qf_info_T *hgr_get_ll(bool *new_ll)
// Search for a pattern in a help file.
static void hgr_search_file(
- qf_info_T *qi,
+ qf_list_T *qfl,
char_u *fname,
regmatch_T *p_regmatch)
FUNC_ATTR_NONNULL_ARG(1, 3)
@@ -6457,7 +6661,7 @@ static void hgr_search_file(
line[--l] = NUL;
}
- if (qf_add_entry(qf_get_curlist(qi),
+ if (qf_add_entry(qfl,
NULL, // dir
fname,
NULL,
@@ -6490,7 +6694,7 @@ static void hgr_search_file(
// Search for a pattern in all the help files in the doc directory under
// the given directory.
static void hgr_search_files_in_dir(
- qf_info_T *qi,
+ qf_list_T *qfl,
char_u *dirname,
regmatch_T *p_regmatch,
const char_u *lang)
@@ -6515,7 +6719,7 @@ static void hgr_search_files_in_dir(
continue;
}
- hgr_search_file(qi, fnames[fi], p_regmatch);
+ hgr_search_file(qfl, fnames[fi], p_regmatch);
}
FreeWild(fcount, fnames);
}
@@ -6525,7 +6729,7 @@ static void hgr_search_files_in_dir(
// and add the matches to a quickfix list.
// 'lang' is the language specifier. If supplied, then only matches in the
// specified language are found.
-static void hgr_search_in_rtp(qf_info_T *qi, regmatch_T *p_regmatch,
+static void hgr_search_in_rtp(qf_list_T *qfl, regmatch_T *p_regmatch,
const char_u *lang)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
@@ -6534,7 +6738,7 @@ static void hgr_search_in_rtp(qf_info_T *qi, regmatch_T *p_regmatch,
while (*p != NUL && !got_int) {
copy_option_part(&p, NameBuff, MAXPATHL, ",");
- hgr_search_files_in_dir(qi, NameBuff, p_regmatch, lang);
+ hgr_search_files_in_dir(qfl, NameBuff, p_regmatch, lang);
}
}
@@ -6576,12 +6780,12 @@ void ex_helpgrep(exarg_T *eap)
if (regmatch.regprog != NULL) {
// Create a new quickfix list.
qf_new_list(qi, qf_cmdtitle(*eap->cmdlinep));
+ qf_list_T *const qfl = qf_get_curlist(qi);
- hgr_search_in_rtp(qi, &regmatch, lang);
+ hgr_search_in_rtp(qfl, &regmatch, lang);
vim_regfree(regmatch.regprog);
- qf_list_T *qfl = qf_get_curlist(qi);
qfl->qf_nonevalid = false;
qfl->qf_ptr = qfl->qf_start;
qfl->qf_index = 1;
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 34553fcec4..6316129c6a 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -40,11 +40,11 @@
* Named character class support added by Walter Briscoe (1998 Jul 01)
*/
-/* Uncomment the first if you do not want to see debugging logs or files
- * related to regular expressions, even when compiling with -DDEBUG.
- * Uncomment the second to get the regexp debugging. */
-/* #undef REGEXP_DEBUG */
-/* #define REGEXP_DEBUG */
+// By default: do not create debugging logs or files related to regular
+// expressions, even when compiling with -DDEBUG.
+// Uncomment the second line to get the regexp debugging.
+// #undef REGEXP_DEBUG
+// #define REGEXP_DEBUG
#include <assert.h>
#include <inttypes.h>
@@ -301,8 +301,8 @@ typedef struct {
*/
typedef struct {
union {
- char_u *ptr; /* reginput pointer, for single-line regexp */
- lpos_T pos; /* reginput pos, for multi-line regexp */
+ char_u *ptr; ///< rex.input pointer, for single-line regexp
+ lpos_T pos; ///< rex.input pos, for multi-line regexp
} rs_u;
int rs_len;
} regsave_T;
@@ -355,7 +355,7 @@ typedef struct regitem_S {
union {
save_se_T sesave;
regsave_T regsave;
- } rs_un; // room for saving reginput
+ } rs_un; ///< room for saving rex.input
} regitem_T;
@@ -490,6 +490,8 @@ static char_u e_z_not_allowed[] = N_("E66: \\z( not allowed here");
static char_u e_z1_not_allowed[] = N_("E67: \\z1 - \\z9 not allowed here");
static char_u e_missing_sb[] = N_("E69: Missing ] after %s%%[");
static char_u e_empty_sb[] = N_("E70: Empty %s%%[]");
+static char_u e_recursive[] = N_("E956: Cannot use pattern recursively");
+
#define NOT_MULTI 0
#define MULTI_ONE 1
#define MULTI_MULT 2
@@ -600,6 +602,12 @@ static int get_char_class(char_u **pp)
#define CLASS_BACKSPACE 14
"escape:]",
#define CLASS_ESCAPE 15
+ "ident:]",
+#define CLASS_IDENT 16
+ "keyword:]",
+#define CLASS_KEYWORD 17
+ "fname:]",
+#define CLASS_FNAME 18
};
#define CLASS_NONE 99
int i;
@@ -633,7 +641,7 @@ static short class_tab[256];
static void init_class_tab(void)
{
int i;
- static int done = FALSE;
+ static int done = false;
if (done)
return;
@@ -658,7 +666,7 @@ static void init_class_tab(void)
}
class_tab[' '] |= RI_WHITE;
class_tab['\t'] |= RI_WHITE;
- done = TRUE;
+ done = true;
}
# define ri_digit(c) (c < 0x100 && (class_tab[c] & RI_DIGIT))
@@ -678,26 +686,24 @@ static void init_class_tab(void)
#define RF_ICOMBINE 8 /* ignore combining characters */
#define RF_LOOKBH 16 /* uses "\@<=" or "\@<!" */
-/*
- * Global work variables for vim_regcomp().
- */
-
-static char_u *regparse; /* Input-scan pointer. */
-static int prevchr_len; /* byte length of previous char */
-static int num_complex_braces; /* Complex \{...} count */
-static int regnpar; /* () count. */
-static int regnzpar; /* \z() count. */
-static int re_has_z; /* \z item detected */
-static char_u *regcode; /* Code-emit pointer, or JUST_CALC_SIZE */
-static long regsize; /* Code size. */
-static int reg_toolong; /* TRUE when offset out of range */
-static char_u had_endbrace[NSUBEXP]; /* flags, TRUE if end of () found */
-static unsigned regflags; /* RF_ flags for prog */
-static long brace_min[10]; /* Minimums for complex brace repeats */
-static long brace_max[10]; /* Maximums for complex brace repeats */
-static int brace_count[10]; /* Current counts for complex brace repeats */
-static int had_eol; /* TRUE when EOL found by vim_regcomp() */
-static int one_exactly = FALSE; /* only do one char for EXACTLY */
+// Global work variables for vim_regcomp().
+
+static char_u *regparse; ///< Input-scan pointer.
+static int prevchr_len; ///< byte length of previous char
+static int num_complex_braces; ///< Complex \{...} count
+static int regnpar; ///< () count.
+static int regnzpar; ///< \z() count.
+static int re_has_z; ///< \z item detected
+static char_u *regcode; ///< Code-emit pointer, or JUST_CALC_SIZE
+static long regsize; ///< Code size.
+static int reg_toolong; ///< true when offset out of range
+static char_u had_endbrace[NSUBEXP]; ///< flags, true if end of () found
+static unsigned regflags; ///< RF_ flags for prog
+static long brace_min[10]; ///< Minimums for complex brace repeats
+static long brace_max[10]; ///< Maximums for complex brace repeats
+static int brace_count[10]; ///< Current counts for complex brace repeats
+static int had_eol; ///< true when EOL found by vim_regcomp()
+static int one_exactly = false; ///< only do one char for EXACTLY
static int reg_magic; /* magicness of the pattern: */
#define MAGIC_NONE 1 /* "\V" very unmagic */
@@ -754,10 +760,9 @@ static int nextchr; /* used for ungetchr() */
static regengine_T bt_regengine;
static regengine_T nfa_regengine;
-/*
- * Return TRUE if compiled regular expression "prog" can match a line break.
- */
-int re_multiline(regprog_T *prog)
+// Return true if compiled regular expression "prog" can match a line break.
+int re_multiline(const regprog_T *prog)
+ FUNC_ATTR_NONNULL_ALL
{
return prog->regflags & RF_HASNL;
}
@@ -1211,7 +1216,7 @@ char_u *skip_regexp(char_u *startp, int dirc, int magic, char_u **newp)
return p;
}
-/// Return TRUE if the back reference is legal. We must have seen the close
+/// Return true if the back reference is legal. We must have seen the close
/// brace.
/// TODO(vim): Should also check that we don't refer to something repeated
/// (+*=): what instance of the repetition should we match?
@@ -1234,7 +1239,7 @@ static int seen_endbrace(int refnum)
return false;
}
}
- return TRUE;
+ return true;
}
/*
@@ -1265,8 +1270,9 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags)
int len;
int flags;
- if (expr == NULL)
- EMSG_RET_NULL(_(e_null));
+ if (expr == NULL) {
+ IEMSG_RET_NULL(_(e_null));
+ }
init_class_tab();
@@ -1281,6 +1287,7 @@ static regprog_T *bt_regcomp(char_u *expr, int re_flags)
/* Allocate space. */
bt_regprog_T *r = xmalloc(sizeof(bt_regprog_T) + regsize);
+ r->re_in_use = false;
/*
* Second pass: emit code.
@@ -1394,9 +1401,9 @@ regcomp_start (
regnzpar = 1;
re_has_z = 0;
regsize = 0L;
- reg_toolong = FALSE;
+ reg_toolong = false;
regflags = 0;
- had_eol = FALSE;
+ had_eol = false;
}
/*
@@ -1408,7 +1415,7 @@ int vim_regcomp_had_eol(void)
return had_eol;
}
-// variables for parsing reginput
+// variables used for parsing
static int at_start; // True when on the first character
static int prev_at_start; // True when on the second character
@@ -1506,12 +1513,11 @@ reg (
EMSG_RET_NULL(_(e_trailing)); /* "Can't happen". */
/* NOTREACHED */
}
- /*
- * Here we set the flag allowing back references to this set of
- * parentheses.
- */
- if (paren == REG_PAREN)
- had_endbrace[parno] = TRUE; /* have seen the close paren */
+ // Here we set the flag allowing back references to this set of
+ // parentheses.
+ if (paren == REG_PAREN) {
+ had_endbrace[parno] = true; // have seen the close paren
+ }
return ret;
}
@@ -1565,7 +1571,7 @@ static char_u *regconcat(int *flagp)
char_u *chain = NULL;
char_u *latest;
int flags;
- int cont = TRUE;
+ int cont = true;
*flagp = WORST; /* Tentatively. */
@@ -1575,7 +1581,7 @@ static char_u *regconcat(int *flagp)
case Magic('|'):
case Magic('&'):
case Magic(')'):
- cont = FALSE;
+ cont = false;
break;
case Magic('Z'):
regflags |= RF_ICOMBINE;
@@ -1802,7 +1808,7 @@ static char_u *regatom(int *flagp)
case Magic('$'):
ret = regnode(EOL);
- had_eol = TRUE;
+ had_eol = true;
break;
case Magic('<'):
@@ -1821,7 +1827,7 @@ static char_u *regatom(int *flagp)
}
if (c == '$') { /* "\_$" is end-of-line */
ret = regnode(EOL);
- had_eol = TRUE;
+ had_eol = true;
break;
}
@@ -2069,11 +2075,12 @@ static char_u *regatom(int *flagp)
}
ungetchr();
- one_exactly = TRUE;
+ one_exactly = true;
lastnode = regatom(flagp);
- one_exactly = FALSE;
- if (lastnode == NULL)
+ one_exactly = false;
+ if (lastnode == NULL) {
return NULL;
+ }
}
if (ret == NULL)
EMSG2_RET_NULL(_(e_empty_sb),
@@ -2417,6 +2424,27 @@ collection:
case CLASS_ESCAPE:
regc(ESC);
break;
+ case CLASS_IDENT:
+ for (cu = 1; cu <= 255; cu++) {
+ if (vim_isIDc(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_KEYWORD:
+ for (cu = 1; cu <= 255; cu++) {
+ if (reg_iswordc(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
+ case CLASS_FNAME:
+ for (cu = 1; cu <= 255; cu++) {
+ if (vim_isfilec(cu)) {
+ regmbc(cu);
+ }
+ }
+ break;
}
} else {
// produce a multibyte character, including any
@@ -2514,15 +2542,13 @@ static bool re_mult_next(char *what)
return true;
}
-/*
- * Return TRUE if MULTIBYTECODE should be used instead of EXACTLY for
- * character "c".
- */
-static int use_multibytecode(int c)
+// Return true if MULTIBYTECODE should be used instead of EXACTLY for
+// character "c".
+static bool use_multibytecode(int c)
{
- return has_mbyte && (*mb_char2len)(c) > 1
+ return utf_char2len(c) > 1
&& (re_multi_type(peekchr()) != NOT_MULTI
- || (enc_utf8 && utf_iscomposing(c)));
+ || utf_iscomposing(c));
}
/*
@@ -2667,39 +2693,38 @@ static char_u *re_put_uint32(char_u *p, uint32_t val)
return p;
}
-/*
- * Set the next-pointer at the end of a node chain.
- */
+// Set the next-pointer at the end of a node chain.
static void regtail(char_u *p, char_u *val)
{
- char_u *scan;
- char_u *temp;
int offset;
- if (p == JUST_CALC_SIZE)
+ if (p == JUST_CALC_SIZE) {
return;
+ }
- /* Find last node. */
- scan = p;
+ // Find last node.
+ char_u *scan = p;
for (;; ) {
- temp = regnext(scan);
- if (temp == NULL)
+ char_u *temp = regnext(scan);
+ if (temp == NULL) {
break;
+ }
scan = temp;
}
- if (OP(scan) == BACK)
+ if (OP(scan) == BACK) {
offset = (int)(scan - val);
- else
+ } else {
offset = (int)(val - scan);
- /* When the offset uses more than 16 bits it can no longer fit in the two
- * bytes available. Use a global flag to avoid having to check return
- * values in too many places. */
- if (offset > 0xffff)
- reg_toolong = TRUE;
- else {
- *(scan + 1) = (char_u) (((unsigned)offset >> 8) & 0377);
- *(scan + 2) = (char_u) (offset & 0377);
+ }
+ // When the offset uses more than 16 bits it can no longer fit in the two
+ // bytes available. Use a global flag to avoid having to check return
+ // values in too many places.
+ if (offset > 0xffff) {
+ reg_toolong = true;
+ } else {
+ *(scan + 1) = (char_u)(((unsigned)offset >> 8) & 0377);
+ *(scan + 2) = (char_u)(offset & 0377);
}
}
@@ -2728,8 +2753,8 @@ static void initchr(char_u *str)
regparse = str;
prevchr_len = 0;
curchr = prevprevchr = prevchr = nextchr = -1;
- at_start = TRUE;
- prev_at_start = FALSE;
+ at_start = true;
+ prev_at_start = false;
}
/*
@@ -2771,7 +2796,7 @@ static void restore_parse_state(parse_state_T *ps)
*/
static int peekchr(void)
{
- static int after_slash = FALSE;
+ static int after_slash = false;
if (curchr != -1) {
return curchr;
@@ -2837,8 +2862,8 @@ static int peekchr(void)
|| (no_Magic(prevchr) == '('
&& prevprevchr == Magic('%')))) {
curchr = Magic('^');
- at_start = TRUE;
- prev_at_start = FALSE;
+ at_start = true;
+ prev_at_start = false;
}
break;
case '$':
@@ -2889,12 +2914,12 @@ static int peekchr(void)
*/
curchr = -1;
prev_at_start = at_start;
- at_start = FALSE; /* be able to say "/\*ptr" */
- ++regparse;
- ++after_slash;
+ at_start = false; // be able to say "/\*ptr"
+ regparse++;
+ after_slash++;
peekchr();
- --regparse;
- --after_slash;
+ regparse--;
+ after_slash--;
curchr = toggle_Magic(curchr);
} else if (vim_strchr(REGEXP_ABBR, c)) {
/*
@@ -2936,7 +2961,7 @@ static void skipchr(void)
}
regparse += prevchr_len;
prev_at_start = at_start;
- at_start = FALSE;
+ at_start = false;
prevprevchr = prevchr;
prevchr = curchr;
curchr = nextchr; /* use previously unget char, or -1 */
@@ -2980,7 +3005,7 @@ static void ungetchr(void)
curchr = prevchr;
prevchr = prevprevchr;
at_start = prev_at_start;
- prev_at_start = FALSE;
+ prev_at_start = false;
/* Backup regparse, so that it's at the same position as before the
* getchr(). */
@@ -3101,14 +3126,14 @@ static int coll_get_char(void)
*/
static int read_limits(long *minval, long *maxval)
{
- int reverse = FALSE;
+ int reverse = false;
char_u *first_char;
long tmp;
if (*regparse == '-') {
// Starts with '-', so reverse the range later.
regparse++;
- reverse = TRUE;
+ reverse = true;
}
first_char = regparse;
*minval = getdigits_long(&regparse, false, 0);
@@ -3153,17 +3178,6 @@ static int read_limits(long *minval, long *maxval)
* Global work variables for vim_regexec().
*/
-/* The current match-position is remembered with these variables: */
-static linenr_T reglnum; /* line number, relative to first line */
-static char_u *regline; /* start of current line */
-static char_u *reginput; /* current input, points into "regline" */
-
-static int need_clear_subexpr; /* subexpressions still need to be
- * cleared */
-static int need_clear_zsubexpr = FALSE; /* extmatch subexpressions
- * still need to be cleared */
-
-
/* Save the sub-expressions before attempting a match. */
#define save_se(savep, posp, pp) \
REG_MULTI ? save_se_multi((savep), (posp)) : save_se_one((savep), (pp))
@@ -3214,18 +3228,42 @@ typedef struct {
linenr_T reg_maxline;
bool reg_line_lbr; // "\n" in string is line break
+ // The current match-position is remembered with these variables:
+ linenr_T lnum; ///< line number, relative to first line
+ char_u *line; ///< start of current line
+ char_u *input; ///< current input, points into "regline"
+
+ int need_clear_subexpr; ///< subexpressions still need to be cleared
+ int need_clear_zsubexpr; ///< extmatch subexpressions still need to be
+ ///< cleared
+
+
// Internal copy of 'ignorecase'. It is set at each call to vim_regexec().
// Normally it gets the value of "rm_ic" or "rmm_ic", but when the pattern
// contains '\c' or '\C' the value is overruled.
bool reg_ic;
- // Similar to rex.reg_ic, but only for 'combining' characters. Set with \Z
+ // Similar to "reg_ic", but only for 'combining' characters. Set with \Z
// flag in the regexp. Defaults to false, always.
bool reg_icombine;
// Copy of "rmm_maxcol": maximum column to search for a match. Zero when
// there is no maximum.
colnr_T reg_maxcol;
+
+ // State for the NFA engine regexec.
+ int nfa_has_zend; ///< NFA regexp \ze operator encountered.
+ int nfa_has_backref; ///< NFA regexp \1 .. \9 encountered.
+ int nfa_nsubexpr; ///< Number of sub expressions actually being used
+ ///< during execution. 1 if only the whole match
+ ///< (subexpr 0) is used.
+ // listid is global, so that it increases on recursive calls to
+ // nfa_regmatch(), which means we don't have to clear the lastlist field of
+ // all the states.
+ int nfa_listid;
+ int nfa_alt_listid;
+
+ int nfa_has_zsubexpr; ///< NFA regexp has \z( ), set zsubexpr.
} regexec_T;
static regexec_T rex;
@@ -3266,6 +3304,13 @@ void free_regexp_stuff(void)
#endif
+// Return true if character 'c' is included in 'iskeyword' option for
+// "reg_buf" buffer.
+static bool reg_iswordc(int c)
+{
+ return vim_iswordc_buf(c, rex.reg_buf);
+}
+
/*
* Get pointer to the line "lnum", which is relative to "reg_firstlnum".
*/
@@ -3290,7 +3335,7 @@ static char_u *reg_endzp[NSUBEXP]; /* and end of \z(...\) matches */
static lpos_T reg_startzpos[NSUBEXP]; /* idem, beginning pos */
static lpos_T reg_endzpos[NSUBEXP]; /* idem, end pos */
-// TRUE if using multi-line regexp.
+// true if using multi-line regexp.
#define REG_MULTI (rex.reg_match == NULL)
/*
@@ -3439,7 +3484,7 @@ static long bt_regexec_both(char_u *line,
/* Be paranoid... */
if (prog == NULL || line == NULL) {
- EMSG(_(e_null));
+ IEMSG(_(e_null));
goto theend;
}
@@ -3491,13 +3536,13 @@ static long bt_regexec_both(char_u *line,
}
}
- regline = line;
- reglnum = 0;
- reg_toolong = FALSE;
+ rex.line = line;
+ rex.lnum = 0;
+ reg_toolong = false;
/* Simplest case: Anchored match need be tried only once. */
if (prog->reganch) {
- int c = utf_ptr2char(regline + col);
+ int c = utf_ptr2char(rex.line + col);
if (prog->regstart == NUL
|| prog->regstart == c
|| (rex.reg_ic
@@ -3514,12 +3559,12 @@ static long bt_regexec_both(char_u *line,
while (!got_int) {
if (prog->regstart != NUL) {
// Skip until the char we know it must start with.
- s = cstrchr(regline + col, prog->regstart);
+ s = cstrchr(rex.line + col, prog->regstart);
if (s == NULL) {
retval = 0;
break;
}
- col = (int)(s - regline);
+ col = (int)(s - rex.line);
}
// Check for maximum column to try.
@@ -3533,18 +3578,16 @@ static long bt_regexec_both(char_u *line,
break;
}
- /* if not currently on the first line, get it again */
- if (reglnum != 0) {
- reglnum = 0;
- regline = reg_getline((linenr_T)0);
+ // if not currently on the first line, get it again
+ if (rex.lnum != 0) {
+ rex.lnum = 0;
+ rex.line = reg_getline((linenr_T)0);
}
- if (regline[col] == NUL)
+ if (rex.line[col] == NUL) {
break;
- if (has_mbyte)
- col += (*mb_ptr2len)(regline + col);
- else
- ++col;
- /* Check for timeout once in a twenty times to avoid overhead. */
+ }
+ col += (*mb_ptr2len)(rex.line + col);
+ // Check for timeout once in a twenty times to avoid overhead.
if (tm != NULL && ++tm_count == 20) {
tm_count = 0;
if (profile_passed_limit(*tm)) {
@@ -3608,18 +3651,17 @@ void unref_extmatch(reg_extmatch_T *em)
}
}
-/// Try match of "prog" with at regline["col"].
+/// Try match of "prog" with at rex.line["col"].
/// @returns 0 for failure, or number of lines contained in the match.
static long regtry(bt_regprog_T *prog,
colnr_T col,
proftime_T *tm, // timeout limit or NULL
int *timed_out) // flag set on timeout or NULL
{
- reginput = regline + col;
- need_clear_subexpr = TRUE;
- /* Clear the external match subpointers if necessary. */
- if (prog->reghasz == REX_SET)
- need_clear_zsubexpr = TRUE;
+ rex.input = rex.line + col;
+ rex.need_clear_subexpr = true;
+ // Clear the external match subpointers if necessaey.
+ rex.need_clear_zsubexpr = (prog->reghasz == REX_SET);
if (regmatch(prog->program + 1, tm, timed_out) == 0) {
return 0;
@@ -3632,18 +3674,18 @@ static long regtry(bt_regprog_T *prog,
rex.reg_startpos[0].col = col;
}
if (rex.reg_endpos[0].lnum < 0) {
- rex.reg_endpos[0].lnum = reglnum;
- rex.reg_endpos[0].col = (int)(reginput - regline);
+ rex.reg_endpos[0].lnum = rex.lnum;
+ rex.reg_endpos[0].col = (int)(rex.input - rex.line);
} else {
// Use line number of "\ze".
- reglnum = rex.reg_endpos[0].lnum;
+ rex.lnum = rex.reg_endpos[0].lnum;
}
} else {
if (rex.reg_startp[0] == NULL) {
- rex.reg_startp[0] = regline + col;
+ rex.reg_startp[0] = rex.line + col;
}
if (rex.reg_endp[0] == NULL) {
- rex.reg_endp[0] = reginput;
+ rex.reg_endp[0] = rex.input;
}
}
/* Package any found \z(...\) matches for export. Default is none. */
@@ -3675,23 +3717,24 @@ static long regtry(bt_regprog_T *prog,
}
}
}
- return 1 + reglnum;
+ return 1 + rex.lnum;
}
// Get class of previous character.
static int reg_prev_class(void)
{
- if (reginput > regline) {
- return mb_get_class_tab(reginput - 1 - utf_head_off(regline, reginput - 1),
- rex.reg_buf->b_chartab);
+ if (rex.input > rex.line) {
+ return mb_get_class_tab(
+ rex.input - 1 - utf_head_off(rex.line, rex.input - 1),
+ rex.reg_buf->b_chartab);
}
return -1;
}
-// Return TRUE if the current reginput position matches the Visual area.
-static int reg_match_visual(void)
+// Return true if the current rex.input position matches the Visual area.
+static bool reg_match_visual(void)
{
pos_T top, bot;
linenr_T lnum;
@@ -3725,16 +3768,17 @@ static int reg_match_visual(void)
}
mode = curbuf->b_visual.vi_mode;
}
- lnum = reglnum + rex.reg_firstlnum;
+ lnum = rex.lnum + rex.reg_firstlnum;
if (lnum < top.lnum || lnum > bot.lnum) {
return false;
}
if (mode == 'v') {
- col = (colnr_T)(reginput - regline);
+ col = (colnr_T)(rex.input - rex.line);
if ((lnum == top.lnum && col < top.col)
- || (lnum == bot.lnum && col >= bot.col + (*p_sel != 'e')))
- return FALSE;
+ || (lnum == bot.lnum && col >= bot.col + (*p_sel != 'e'))) {
+ return false;
+ }
} else if (mode == Ctrl_V) {
getvvcol(wp, &top, &start, NULL, &end);
getvvcol(wp, &bot, &start2, NULL, &end2);
@@ -3744,17 +3788,18 @@ static int reg_match_visual(void)
end = end2;
if (top.col == MAXCOL || bot.col == MAXCOL)
end = MAXCOL;
- unsigned int cols_u = win_linetabsize(wp, regline,
- (colnr_T)(reginput - regline));
+ unsigned int cols_u = win_linetabsize(wp, rex.line,
+ (colnr_T)(rex.input - rex.line));
assert(cols_u <= MAXCOL);
colnr_T cols = (colnr_T)cols_u;
- if (cols < start || cols > end - (*p_sel == 'e'))
- return FALSE;
+ if (cols < start || cols > end - (*p_sel == 'e')) {
+ return false;
+ }
}
- return TRUE;
+ return true;
}
-#define ADVANCE_REGINPUT() MB_PTR_ADV(reginput)
+#define ADVANCE_REGINPUT() MB_PTR_ADV(rex.input)
/*
* The arguments from BRACE_LIMITS are stored here. They are actually local
@@ -3773,11 +3818,11 @@ static long bl_maxval;
/// (that don't need to know whether the rest of the match failed) by a nested
/// loop.
///
-/// Returns TRUE when there is a match. Leaves reginput and reglnum just after
-/// the last matched character.
-/// Returns FALSE when there is no match. Leaves reginput and reglnum in an
+/// Returns true when there is a match. Leaves rex.input and rex.lnum
+/// just after the last matched character.
+/// Returns false when there is no match. Leaves rex.input and rex.lnum in an
/// undefined state!
-static int regmatch(
+static bool regmatch(
char_u *scan, // Current node.
proftime_T *tm, // timeout limit or NULL
int *timed_out // flag set on timeout or NULL
@@ -3860,38 +3905,40 @@ static int regmatch(
op = OP(scan);
// Check for character class with NL added.
if (!rex.reg_line_lbr && WITH_NL(op) && REG_MULTI
- && *reginput == NUL && reglnum <= rex.reg_maxline) {
+ && *rex.input == NUL && rex.lnum <= rex.reg_maxline) {
reg_nextline();
- } else if (rex.reg_line_lbr && WITH_NL(op) && *reginput == '\n') {
+ } else if (rex.reg_line_lbr && WITH_NL(op) && *rex.input == '\n') {
ADVANCE_REGINPUT();
} else {
if (WITH_NL(op)) {
op -= ADD_NL;
}
- c = utf_ptr2char(reginput);
+ c = utf_ptr2char(rex.input);
switch (op) {
case BOL:
- if (reginput != regline)
+ if (rex.input != rex.line) {
status = RA_NOMATCH;
+ }
break;
case EOL:
- if (c != NUL)
+ if (c != NUL) {
status = RA_NOMATCH;
+ }
break;
case RE_BOF:
// We're not at the beginning of the file when below the first
// line where we started, not at the start of the line or we
// didn't start at the first line of the buffer.
- if (reglnum != 0 || reginput != regline
+ if (rex.lnum != 0 || rex.input != rex.line
|| (REG_MULTI && rex.reg_firstlnum > 1)) {
status = RA_NOMATCH;
}
break;
case RE_EOF:
- if (reglnum != rex.reg_maxline || c != NUL) {
+ if (rex.lnum != rex.reg_maxline || c != NUL) {
status = RA_NOMATCH;
}
break;
@@ -3900,8 +3947,9 @@ static int regmatch(
// Check if the buffer is in a window and compare the
// rex.reg_win->w_cursor position to the match position.
if (rex.reg_win == NULL
- || (reglnum + rex.reg_firstlnum != rex.reg_win->w_cursor.lnum)
- || ((colnr_T)(reginput - regline) != rex.reg_win->w_cursor.col)) {
+ || (rex.lnum + rex.reg_firstlnum != rex.reg_win->w_cursor.lnum)
+ || ((colnr_T)(rex.input - rex.line) !=
+ rex.reg_win->w_cursor.col)) {
status = RA_NOMATCH;
}
break;
@@ -3916,13 +3964,13 @@ static int regmatch(
pos = getmark_buf(rex.reg_buf, mark, false);
if (pos == NULL // mark doesn't exist
|| pos->lnum <= 0 // mark isn't set in reg_buf
- || (pos->lnum == reglnum + rex.reg_firstlnum
- ? (pos->col == (colnr_T)(reginput - regline)
+ || (pos->lnum == rex.lnum + rex.reg_firstlnum
+ ? (pos->col == (colnr_T)(rex.input - rex.line)
? (cmp == '<' || cmp == '>')
- : (pos->col < (colnr_T)(reginput - regline)
+ : (pos->col < (colnr_T)(rex.input - rex.line)
? cmp != '>'
: cmp != '<'))
- : (pos->lnum < reglnum + rex.reg_firstlnum
+ : (pos->lnum < rex.lnum + rex.reg_firstlnum
? cmp != '>'
: cmp != '<'))) {
status = RA_NOMATCH;
@@ -3936,79 +3984,70 @@ static int regmatch(
break;
case RE_LNUM:
- assert(reglnum + rex.reg_firstlnum >= 0
- && (uintmax_t)(reglnum + rex.reg_firstlnum) <= UINT32_MAX);
+ assert(rex.lnum + rex.reg_firstlnum >= 0
+ && (uintmax_t)(rex.lnum + rex.reg_firstlnum) <= UINT32_MAX);
if (!REG_MULTI
- || !re_num_cmp((uint32_t)(reglnum + rex.reg_firstlnum), scan)) {
+ || !re_num_cmp((uint32_t)(rex.lnum + rex.reg_firstlnum), scan)) {
status = RA_NOMATCH;
}
break;
case RE_COL:
- assert(reginput - regline + 1 >= 0
- && (uintmax_t)(reginput - regline + 1) <= UINT32_MAX);
- if (!re_num_cmp((uint32_t)(reginput - regline + 1), scan))
+ assert(rex.input - rex.line + 1 >= 0
+ && (uintmax_t)(rex.input - rex.line + 1) <= UINT32_MAX);
+ if (!re_num_cmp((uint32_t)(rex.input - rex.line + 1), scan)) {
status = RA_NOMATCH;
+ }
break;
case RE_VCOL:
if (!re_num_cmp(win_linetabsize(rex.reg_win == NULL
? curwin : rex.reg_win,
- regline,
- (colnr_T)(reginput - regline)) + 1,
+ rex.line,
+ (colnr_T)(rex.input - rex.line)) + 1,
scan)) {
status = RA_NOMATCH;
}
break;
- case BOW: /* \<word; reginput points to w */
- if (c == NUL) /* Can't match at end of line */
+ case BOW: // \<word; rex.input points to w
+ if (c == NUL) { // Can't match at end of line
status = RA_NOMATCH;
- else if (has_mbyte) {
- int this_class;
-
+ } else {
// Get class of current and previous char (if it exists).
- this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab);
+ const int this_class =
+ mb_get_class_tab(rex.input, rex.reg_buf->b_chartab);
if (this_class <= 1) {
status = RA_NOMATCH; // Not on a word at all.
} else if (reg_prev_class() == this_class) {
status = RA_NOMATCH; // Previous char is in same word.
}
- } else {
- if (!vim_iswordc_buf(c, rex.reg_buf)
- || (reginput > regline
- && vim_iswordc_buf(reginput[-1], rex.reg_buf))) {
- status = RA_NOMATCH;
- }
}
break;
- case EOW: /* word\>; reginput points after d */
- if (reginput == regline) /* Can't match at start of line */
+ case EOW: // word\>; rex.input points after d
+ if (rex.input == rex.line) { // Can't match at start of line
status = RA_NOMATCH;
- else if (has_mbyte) {
+ } else {
int this_class, prev_class;
// Get class of current and previous char (if it exists).
- this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab);
+ this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab);
prev_class = reg_prev_class();
if (this_class == prev_class
- || prev_class == 0 || prev_class == 1)
- status = RA_NOMATCH;
- } else {
- if (!vim_iswordc_buf(reginput[-1], rex.reg_buf)
- || (reginput[0] != NUL && vim_iswordc_buf(c, rex.reg_buf))) {
+ || prev_class == 0 || prev_class == 1) {
status = RA_NOMATCH;
}
}
- break; /* Matched with EOW */
+ break; // Matched with EOW
case ANY:
- /* ANY does not match new lines. */
- if (c == NUL)
+ // ANY does not match new lines.
+ if (c == NUL) {
status = RA_NOMATCH;
- else
+ } else {
ADVANCE_REGINPUT();
+ }
break;
case IDENT:
@@ -4019,14 +4058,15 @@ static int regmatch(
break;
case SIDENT:
- if (ascii_isdigit(*reginput) || !vim_isIDc(c))
+ if (ascii_isdigit(*rex.input) || !vim_isIDc(c)) {
status = RA_NOMATCH;
- else
+ } else {
ADVANCE_REGINPUT();
+ }
break;
case KWORD:
- if (!vim_iswordp_buf(reginput, rex.reg_buf)) {
+ if (!vim_iswordp_buf(rex.input, rex.reg_buf)) {
status = RA_NOMATCH;
} else {
ADVANCE_REGINPUT();
@@ -4034,8 +4074,8 @@ static int regmatch(
break;
case SKWORD:
- if (ascii_isdigit(*reginput)
- || !vim_iswordp_buf(reginput, rex.reg_buf)) {
+ if (ascii_isdigit(*rex.input)
+ || !vim_iswordp_buf(rex.input, rex.reg_buf)) {
status = RA_NOMATCH;
} else {
ADVANCE_REGINPUT();
@@ -4043,31 +4083,35 @@ static int regmatch(
break;
case FNAME:
- if (!vim_isfilec(c))
+ if (!vim_isfilec(c)) {
status = RA_NOMATCH;
- else
+ } else {
ADVANCE_REGINPUT();
+ }
break;
case SFNAME:
- if (ascii_isdigit(*reginput) || !vim_isfilec(c))
+ if (ascii_isdigit(*rex.input) || !vim_isfilec(c)) {
status = RA_NOMATCH;
- else
+ } else {
ADVANCE_REGINPUT();
+ }
break;
case PRINT:
- if (!vim_isprintc(PTR2CHAR(reginput)))
+ if (!vim_isprintc(PTR2CHAR(rex.input))) {
status = RA_NOMATCH;
- else
+ } else {
ADVANCE_REGINPUT();
+ }
break;
case SPRINT:
- if (ascii_isdigit(*reginput) || !vim_isprintc(PTR2CHAR(reginput)))
+ if (ascii_isdigit(*rex.input) || !vim_isprintc(PTR2CHAR(rex.input))) {
status = RA_NOMATCH;
- else
+ } else {
ADVANCE_REGINPUT();
+ }
break;
case WHITE:
@@ -4203,10 +4247,10 @@ static int regmatch(
opnd = OPERAND(scan);
// Inline the first byte, for speed.
- if (*opnd != *reginput
+ if (*opnd != *rex.input
&& (!rex.reg_ic
|| (!enc_utf8
- && mb_tolower(*opnd) != mb_tolower(*reginput)))) {
+ && mb_tolower(*opnd) != mb_tolower(*rex.input)))) {
status = RA_NOMATCH;
} else if (*opnd == NUL) {
// match empty string always works; happens when "~" is
@@ -4217,14 +4261,14 @@ static int regmatch(
} else {
// Need to match first byte again for multi-byte.
len = (int)STRLEN(opnd);
- if (cstrncmp(opnd, reginput, &len) != 0) {
+ if (cstrncmp(opnd, rex.input, &len) != 0) {
status = RA_NOMATCH;
}
}
// Check for following composing character, unless %C
// follows (skips over all composing chars).
if (status != RA_NOMATCH && enc_utf8
- && UTF_COMPOSINGLIKE(reginput, reginput + len)
+ && UTF_COMPOSINGLIKE(rex.input, rex.input + len)
&& !rex.reg_icombine
&& OP(next) != RE_COMPOSING) {
// raaron: This code makes a composing character get
@@ -4233,7 +4277,7 @@ static int regmatch(
status = RA_NOMATCH;
}
if (status != RA_NOMATCH) {
- reginput += len;
+ rex.input += len;
}
}
}
@@ -4250,54 +4294,52 @@ static int regmatch(
break;
case MULTIBYTECODE:
- if (has_mbyte) {
+ {
int i, len;
- char_u *opnd;
- int opndc = 0, inpc;
- opnd = OPERAND(scan);
+ const char_u *opnd = OPERAND(scan);
// Safety check (just in case 'encoding' was changed since
// compiling the program).
if ((len = (*mb_ptr2len)(opnd)) < 2) {
status = RA_NOMATCH;
break;
}
- if (enc_utf8) {
- opndc = utf_ptr2char(opnd);
- }
- if (enc_utf8 && utf_iscomposing(opndc)) {
- /* When only a composing char is given match at any
- * position where that composing char appears. */
+ const int opndc = utf_ptr2char(opnd);
+ if (utf_iscomposing(opndc)) {
+ // When only a composing char is given match at any
+ // position where that composing char appears.
status = RA_NOMATCH;
- for (i = 0; reginput[i] != NUL; i += utf_ptr2len(reginput + i)) {
- inpc = utf_ptr2char(reginput + i);
+ for (i = 0; rex.input[i] != NUL;
+ i += utf_ptr2len(rex.input + i)) {
+ const int inpc = utf_ptr2char(rex.input + i);
if (!utf_iscomposing(inpc)) {
if (i > 0) {
break;
}
} else if (opndc == inpc) {
// Include all following composing chars.
- len = i + utfc_ptr2len(reginput + i);
+ len = i + utfc_ptr2len(rex.input + i);
status = RA_MATCH;
break;
}
}
- } else
- for (i = 0; i < len; ++i)
- if (opnd[i] != reginput[i]) {
+ } else {
+ for (i = 0; i < len; i++) {
+ if (opnd[i] != rex.input[i]) {
status = RA_NOMATCH;
break;
}
- reginput += len;
- } else
- status = RA_NOMATCH;
+ }
+ }
+ rex.input += len;
+ }
break;
case RE_COMPOSING:
if (enc_utf8) {
// Skip composing characters.
- while (utf_iscomposing(utf_ptr2char(reginput))) {
- MB_CPTR_ADV(reginput);
+ while (utf_iscomposing(utf_ptr2char(rex.input))) {
+ MB_CPTR_ADV(rex.input);
}
}
break;
@@ -4460,7 +4502,7 @@ static int regmatch(
} else {
// Compare current input with back-ref in the same line.
len = (int)(rex.reg_endp[no] - rex.reg_startp[no]);
- if (cstrncmp(rex.reg_startp[no], reginput, &len) != 0) {
+ if (cstrncmp(rex.reg_startp[no], rex.input, &len) != 0) {
status = RA_NOMATCH;
}
}
@@ -4469,12 +4511,12 @@ static int regmatch(
// Backref was not set: Match an empty string.
len = 0;
} else {
- if (rex.reg_startpos[no].lnum == reglnum
- && rex.reg_endpos[no].lnum == reglnum) {
+ if (rex.reg_startpos[no].lnum == rex.lnum
+ && rex.reg_endpos[no].lnum == rex.lnum) {
// Compare back-ref within the current line.
len = rex.reg_endpos[no].col - rex.reg_startpos[no].col;
- if (cstrncmp(regline + rex.reg_startpos[no].col,
- reginput, &len) != 0) {
+ if (cstrncmp(rex.line + rex.reg_startpos[no].col,
+ rex.input, &len) != 0) {
status = RA_NOMATCH;
}
} else {
@@ -4491,8 +4533,8 @@ static int regmatch(
}
}
- /* Matched the backref, skip over it. */
- reginput += len;
+ // Matched the backref, skip over it.
+ rex.input += len;
}
break;
@@ -4506,20 +4548,18 @@ static int regmatch(
case ZREF + 8:
case ZREF + 9:
{
- int len;
-
cleanup_zsubexpr();
no = op - ZREF;
if (re_extmatch_in != NULL
&& re_extmatch_in->matches[no] != NULL) {
- len = (int)STRLEN(re_extmatch_in->matches[no]);
- if (cstrncmp(re_extmatch_in->matches[no],
- reginput, &len) != 0)
+ int len = (int)STRLEN(re_extmatch_in->matches[no]);
+ if (cstrncmp(re_extmatch_in->matches[no], rex.input, &len) != 0) {
status = RA_NOMATCH;
- else
- reginput += len;
+ } else {
+ rex.input += len;
+ }
} else {
- /* Backref was not set: Match an empty string. */
+ // Backref was not set: Match an empty string.
}
}
break;
@@ -4725,15 +4765,17 @@ static int regmatch(
case BHPOS:
if (REG_MULTI) {
- if (behind_pos.rs_u.pos.col != (colnr_T)(reginput - regline)
- || behind_pos.rs_u.pos.lnum != reglnum)
+ if (behind_pos.rs_u.pos.col != (colnr_T)(rex.input - rex.line)
+ || behind_pos.rs_u.pos.lnum != rex.lnum) {
status = RA_NOMATCH;
- } else if (behind_pos.rs_u.ptr != reginput)
+ }
+ } else if (behind_pos.rs_u.ptr != rex.input) {
status = RA_NOMATCH;
+ }
break;
case NEWL:
- if ((c != NUL || !REG_MULTI || reglnum > rex.reg_maxline
+ if ((c != NUL || !REG_MULTI || rex.lnum > rex.reg_maxline
|| rex.reg_line_lbr) && (c != '\n' || !rex.reg_line_lbr)) {
status = RA_NOMATCH;
} else if (rex.reg_line_lbr) {
@@ -4748,7 +4790,7 @@ static int regmatch(
break;
default:
- EMSG(_(e_re_corr));
+ IEMSG(_(e_re_corr));
#ifdef REGEXP_DEBUG
printf("Illegal op code %d\n", op);
#endif
@@ -4946,7 +4988,7 @@ static int regmatch(
if (limit > 0
&& ((rp->rs_un.regsave.rs_u.pos.lnum
< behind_pos.rs_u.pos.lnum
- ? (colnr_T)STRLEN(regline)
+ ? (colnr_T)STRLEN(rex.line)
: behind_pos.rs_u.pos.col)
- rp->rs_un.regsave.rs_u.pos.col >= limit))
no = FAIL;
@@ -4960,7 +5002,7 @@ static int regmatch(
else {
reg_restore(&rp->rs_un.regsave, &backpos);
rp->rs_un.regsave.rs_u.pos.col =
- (colnr_T)STRLEN(regline);
+ (colnr_T)STRLEN(rex.line);
}
} else {
const char_u *const line =
@@ -4972,10 +5014,10 @@ static int regmatch(
+ 1;
}
} else {
- if (rp->rs_un.regsave.rs_u.ptr == regline) {
+ if (rp->rs_un.regsave.rs_u.ptr == rex.line) {
no = FAIL;
} else {
- MB_PTR_BACK(regline, rp->rs_un.regsave.rs_u.ptr);
+ MB_PTR_BACK(rex.line, rp->rs_un.regsave.rs_u.ptr);
if (limit > 0
&& (long)(behind_pos.rs_u.ptr
- rp->rs_un.regsave.rs_u.ptr) > limit) {
@@ -5039,18 +5081,18 @@ static int regmatch(
* didn't match -- back up one char. */
if (--rst->count < rst->minval)
break;
- if (reginput == regline) {
+ if (rex.input == rex.line) {
// backup to last char of previous line
- reglnum--;
- regline = reg_getline(reglnum);
+ rex.lnum--;
+ rex.line = reg_getline(rex.lnum);
// Just in case regrepeat() didn't count right.
- if (regline == NULL) {
+ if (rex.line == NULL) {
break;
}
- reginput = regline + STRLEN(regline);
+ rex.input = rex.line + STRLEN(rex.line);
fast_breakcheck();
} else {
- MB_PTR_BACK(regline, reginput);
+ MB_PTR_BACK(rex.line, rex.input);
}
} else {
/* Range is backwards, use shortest match first.
@@ -5067,9 +5109,9 @@ static int regmatch(
} else
status = RA_NOMATCH;
- /* If it could match, try it. */
- if (rst->nextb == NUL || *reginput == rst->nextb
- || *reginput == rst->nextb_ic) {
+ // If it could match, try it.
+ if (rst->nextb == NUL || *rex.input == rst->nextb
+ || *rex.input == rst->nextb_ic) {
reg_save(&rp->rs_un.regsave, &backpos);
scan = regnext(rp->rs_scan);
status = RA_CONT;
@@ -5106,7 +5148,7 @@ static int regmatch(
* We get here only if there's trouble -- normally "case END" is
* the terminating point.
*/
- EMSG(_(e_re_corr));
+ IEMSG(_(e_re_corr));
#ifdef REGEXP_DEBUG
printf("Premature EOL\n");
#endif
@@ -5156,7 +5198,7 @@ static void regstack_pop(char_u **scan)
/*
* regrepeat - repeatedly match something simple, return how many.
- * Advances reginput (and reglnum) to just after the matched chars.
+ * Advances rex.input (and rex.lnum) to just after the matched chars.
*/
static int
regrepeat (
@@ -5165,12 +5207,11 @@ regrepeat (
)
{
long count = 0;
- char_u *scan;
char_u *opnd;
int mask;
int testval = 0;
- scan = reginput; /* Make local copy of reginput for speed. */
+ char_u *scan = rex.input; // Make local copy of rex.input for speed.
opnd = OPERAND(p);
switch (OP(p)) {
case ANY:
@@ -5182,15 +5223,16 @@ regrepeat (
count++;
MB_PTR_ADV(scan);
}
- if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
|| rex.reg_line_lbr || count == maxcount) {
break;
}
count++; // count the line-break
reg_nextline();
- scan = reginput;
- if (got_int)
+ scan = rex.input;
+ if (got_int) {
break;
+ }
}
break;
@@ -5204,14 +5246,15 @@ regrepeat (
if (vim_isIDc(PTR2CHAR(scan)) && (testval || !ascii_isdigit(*scan))) {
MB_PTR_ADV(scan);
} else if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
|| rex.reg_line_lbr) {
break;
}
reg_nextline();
- scan = reginput;
- if (got_int)
+ scan = rex.input;
+ if (got_int) {
break;
+ }
} else if (rex.reg_line_lbr && *scan == '\n' && WITH_NL(OP(p))) {
scan++;
} else {
@@ -5232,12 +5275,12 @@ regrepeat (
&& (testval || !ascii_isdigit(*scan))) {
MB_PTR_ADV(scan);
} else if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
|| rex.reg_line_lbr) {
break;
}
reg_nextline();
- scan = reginput;
+ scan = rex.input;
if (got_int) {
break;
}
@@ -5260,12 +5303,12 @@ regrepeat (
if (vim_isfilec(PTR2CHAR(scan)) && (testval || !ascii_isdigit(*scan))) {
MB_PTR_ADV(scan);
} else if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
|| rex.reg_line_lbr) {
break;
}
reg_nextline();
- scan = reginput;
+ scan = rex.input;
if (got_int) {
break;
}
@@ -5286,12 +5329,12 @@ regrepeat (
case SPRINT + ADD_NL:
while (count < maxcount) {
if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
|| rex.reg_line_lbr) {
break;
}
reg_nextline();
- scan = reginput;
+ scan = rex.input;
if (got_int) {
break;
}
@@ -5314,14 +5357,15 @@ do_class:
while (count < maxcount) {
int l;
if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
|| rex.reg_line_lbr) {
break;
}
reg_nextline();
- scan = reginput;
- if (got_int)
+ scan = rex.input;
+ if (got_int) {
break;
+ }
} else if (has_mbyte && (l = (*mb_ptr2len)(scan)) > 1) {
if (testval != 0)
break;
@@ -5467,12 +5511,12 @@ do_class:
while (count < maxcount) {
int len;
if (*scan == NUL) {
- if (!REG_MULTI || !WITH_NL(OP(p)) || reglnum > rex.reg_maxline
+ if (!REG_MULTI || !WITH_NL(OP(p)) || rex.lnum > rex.reg_maxline
|| rex.reg_line_lbr) {
break;
}
reg_nextline();
- scan = reginput;
+ scan = rex.input;
if (got_int) {
break;
}
@@ -5494,7 +5538,7 @@ do_class:
case NEWL:
while (count < maxcount
- && ((*scan == NUL && reglnum <= rex.reg_maxline && !rex.reg_line_lbr
+ && ((*scan == NUL && rex.lnum <= rex.reg_maxline && !rex.reg_line_lbr
&& REG_MULTI) || (*scan == '\n' && rex.reg_line_lbr))) {
count++;
if (rex.reg_line_lbr) {
@@ -5502,21 +5546,22 @@ do_class:
} else {
reg_nextline();
}
- scan = reginput;
- if (got_int)
+ scan = rex.input;
+ if (got_int) {
break;
+ }
}
break;
- default: /* Oh dear. Called inappropriately. */
- EMSG(_(e_re_corr));
+ default: // Oh dear. Called inappropriately.
+ IEMSG(_(e_re_corr));
#ifdef REGEXP_DEBUG
printf("Called regrepeat with op code %d\n", OP(p));
#endif
break;
}
- reginput = scan;
+ rex.input = scan;
return (int)count;
}
@@ -5546,7 +5591,7 @@ static char_u *regnext(char_u *p)
/*
* Check the regexp program for its magic number.
- * Return TRUE if it's wrong.
+ * Return true if it's wrong.
*/
static int prog_magic_wrong(void)
{
@@ -5560,9 +5605,9 @@ static int prog_magic_wrong(void)
if (UCHARAT(((bt_regprog_T *)prog)->program) != REGMAGIC) {
EMSG(_(e_re_corr));
- return TRUE;
+ return true;
}
- return FALSE;
+ return false;
}
/*
@@ -5572,7 +5617,7 @@ static int prog_magic_wrong(void)
*/
static void cleanup_subexpr(void)
{
- if (need_clear_subexpr) {
+ if (rex.need_clear_subexpr) {
if (REG_MULTI) {
// Use 0xff to set lnum to -1
memset(rex.reg_startpos, 0xff, sizeof(lpos_T) * NSUBEXP);
@@ -5581,13 +5626,13 @@ static void cleanup_subexpr(void)
memset(rex.reg_startp, 0, sizeof(char_u *) * NSUBEXP);
memset(rex.reg_endp, 0, sizeof(char_u *) * NSUBEXP);
}
- need_clear_subexpr = FALSE;
+ rex.need_clear_subexpr = false;
}
}
static void cleanup_zsubexpr(void)
{
- if (need_clear_zsubexpr) {
+ if (rex.need_clear_zsubexpr) {
if (REG_MULTI) {
/* Use 0xff to set lnum to -1 */
memset(reg_startzpos, 0xff, sizeof(lpos_T) * NSUBEXP);
@@ -5596,23 +5641,20 @@ static void cleanup_zsubexpr(void)
memset(reg_startzp, 0, sizeof(char_u *) * NSUBEXP);
memset(reg_endzp, 0, sizeof(char_u *) * NSUBEXP);
}
- need_clear_zsubexpr = FALSE;
+ rex.need_clear_zsubexpr = false;
}
}
-/*
- * Save the current subexpr to "bp", so that they can be restored
- * later by restore_subexpr().
- */
+// Save the current subexpr to "bp", so that they can be restored
+// later by restore_subexpr().
static void save_subexpr(regbehind_T *bp)
+ FUNC_ATTR_NONNULL_ALL
{
- int i;
-
- // When "need_clear_subexpr" is set we don't need to save the values, only
+ // When "rex.need_clear_subexpr" is set we don't need to save the values, only
// remember that this flag needs to be set again when restoring.
- bp->save_need_clear_subexpr = need_clear_subexpr;
- if (!need_clear_subexpr) {
- for (i = 0; i < NSUBEXP; ++i) {
+ bp->save_need_clear_subexpr = rex.need_clear_subexpr;
+ if (!rex.need_clear_subexpr) {
+ for (int i = 0; i < NSUBEXP; i++) {
if (REG_MULTI) {
bp->save_start[i].se_u.pos = rex.reg_startpos[i];
bp->save_end[i].se_u.pos = rex.reg_endpos[i];
@@ -5624,17 +5666,14 @@ static void save_subexpr(regbehind_T *bp)
}
}
-/*
- * Restore the subexpr from "bp".
- */
+// Restore the subexpr from "bp".
static void restore_subexpr(regbehind_T *bp)
+ FUNC_ATTR_NONNULL_ALL
{
- int i;
-
- /* Only need to restore saved values when they are not to be cleared. */
- need_clear_subexpr = bp->save_need_clear_subexpr;
- if (!need_clear_subexpr) {
- for (i = 0; i < NSUBEXP; ++i) {
+ // Only need to restore saved values when they are not to be cleared.
+ rex.need_clear_subexpr = bp->save_need_clear_subexpr;
+ if (!rex.need_clear_subexpr) {
+ for (int i = 0; i < NSUBEXP; i++) {
if (REG_MULTI) {
rex.reg_startpos[i] = bp->save_start[i].se_u.pos;
rex.reg_endpos[i] = bp->save_end[i].se_u.pos;
@@ -5646,56 +5685,54 @@ static void restore_subexpr(regbehind_T *bp)
}
}
-/*
- * Advance reglnum, regline and reginput to the next line.
- */
+// Advance rex.lnum, rex.line and rex.input to the next line.
static void reg_nextline(void)
{
- regline = reg_getline(++reglnum);
- reginput = regline;
+ rex.line = reg_getline(++rex.lnum);
+ rex.input = rex.line;
fast_breakcheck();
}
-/*
- * Save the input line and position in a regsave_T.
- */
+// Save the input line and position in a regsave_T.
static void reg_save(regsave_T *save, garray_T *gap)
+ FUNC_ATTR_NONNULL_ALL
{
if (REG_MULTI) {
- save->rs_u.pos.col = (colnr_T)(reginput - regline);
- save->rs_u.pos.lnum = reglnum;
- } else
- save->rs_u.ptr = reginput;
+ save->rs_u.pos.col = (colnr_T)(rex.input - rex.line);
+ save->rs_u.pos.lnum = rex.lnum;
+ } else {
+ save->rs_u.ptr = rex.input;
+ }
save->rs_len = gap->ga_len;
}
-/*
- * Restore the input line and position from a regsave_T.
- */
+// Restore the input line and position from a regsave_T.
static void reg_restore(regsave_T *save, garray_T *gap)
+ FUNC_ATTR_NONNULL_ALL
{
if (REG_MULTI) {
- if (reglnum != save->rs_u.pos.lnum) {
- /* only call reg_getline() when the line number changed to save
- * a bit of time */
- reglnum = save->rs_u.pos.lnum;
- regline = reg_getline(reglnum);
+ if (rex.lnum != save->rs_u.pos.lnum) {
+ // only call reg_getline() when the line number changed to save
+ // a bit of time
+ rex.lnum = save->rs_u.pos.lnum;
+ rex.line = reg_getline(rex.lnum);
}
- reginput = regline + save->rs_u.pos.col;
- } else
- reginput = save->rs_u.ptr;
+ rex.input = rex.line + save->rs_u.pos.col;
+ } else {
+ rex.input = save->rs_u.ptr;
+ }
gap->ga_len = save->rs_len;
}
-/*
- * Return TRUE if current position is equal to saved position.
- */
-static int reg_save_equal(regsave_T *save)
+// Return true if current position is equal to saved position.
+static bool reg_save_equal(const regsave_T *save)
+ FUNC_ATTR_NONNULL_ALL
{
- if (REG_MULTI)
- return reglnum == save->rs_u.pos.lnum
- && reginput == regline + save->rs_u.pos.col;
- return reginput == save->rs_u.ptr;
+ if (REG_MULTI) {
+ return rex.lnum == save->rs_u.pos.lnum
+ && rex.input == rex.line + save->rs_u.pos.col;
+ }
+ return rex.input == save->rs_u.ptr;
}
/*
@@ -5708,14 +5745,14 @@ static int reg_save_equal(regsave_T *save)
static void save_se_multi(save_se_T *savep, lpos_T *posp)
{
savep->se_u.pos = *posp;
- posp->lnum = reglnum;
- posp->col = (colnr_T)(reginput - regline);
+ posp->lnum = rex.lnum;
+ posp->col = (colnr_T)(rex.input - rex.line);
}
static void save_se_one(save_se_T *savep, char_u **pp)
{
savep->se_u.ptr = *pp;
- *pp = reginput;
+ *pp = rex.input;
}
/*
@@ -5750,17 +5787,17 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e
for (;; ) {
/* Since getting one line may invalidate the other, need to make copy.
* Slow! */
- if (regline != reg_tofree) {
- len = (int)STRLEN(regline);
+ if (rex.line != reg_tofree) {
+ len = (int)STRLEN(rex.line);
if (reg_tofree == NULL || len >= (int)reg_tofreelen) {
len += 50; /* get some extra */
xfree(reg_tofree);
reg_tofree = xmalloc(len);
reg_tofreelen = len;
}
- STRCPY(reg_tofree, regline);
- reginput = reg_tofree + (reginput - regline);
- regline = reg_tofree;
+ STRCPY(reg_tofree, rex.line);
+ rex.input = reg_tofree + (rex.input - rex.line);
+ rex.line = reg_tofree;
}
/* Get the line to compare with. */
@@ -5772,14 +5809,16 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e
else
len = (int)STRLEN(p + ccol);
- if (cstrncmp(p + ccol, reginput, &len) != 0)
- return RA_NOMATCH; /* doesn't match */
- if (bytelen != NULL)
+ if (cstrncmp(p + ccol, rex.input, &len) != 0) {
+ return RA_NOMATCH; // doesn't match
+ }
+ if (bytelen != NULL) {
*bytelen += len;
+ }
if (clnum == end_lnum) {
break; // match and at end!
}
- if (reglnum >= rex.reg_maxline) {
+ if (rex.lnum >= rex.reg_maxline) {
return RA_NOMATCH; // text too short
}
@@ -5793,8 +5832,8 @@ static int match_with_backref(linenr_T start_lnum, colnr_T start_col, linenr_T e
return RA_FAIL;
}
- /* found a match! Note that regline may now point to a copy of the line,
- * that should not matter. */
+ // found a match! Note that rex.line may now point to a copy of the line,
+ // that should not matter.
return RA_MATCH;
}
@@ -6477,7 +6516,7 @@ char_u *regtilde(char_u *source, int magic)
return newsub;
}
-static int can_f_submatch = FALSE; /* TRUE when submatch() can be used */
+static bool can_f_submatch = false; // true when submatch() can be used
// These pointers are used for reg_submatch(). Needed for when the
// substitution string is an expression that contains a call to substitute()
@@ -6534,11 +6573,11 @@ static void clear_submatch_list(staticList10_T *sl)
/// vim_regsub() - perform substitutions after a vim_regexec() or
/// vim_regexec_multi() match.
///
-/// If "copy" is TRUE really copy into "dest".
-/// If "copy" is FALSE nothing is copied, this is just to find out the length
+/// If "copy" is true really copy into "dest".
+/// If "copy" is false nothing is copied, this is just to find out the length
/// of the result.
///
-/// If "backslash" is TRUE, a backslash will be removed later, need to double
+/// If "backslash" is true, a backslash will be removed later, need to double
/// them to keep them, and insert a backslash before a CR to avoid it being
/// replaced with a line break later.
///
@@ -6630,8 +6669,8 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
if (expr != NULL || (source[0] == '\\' && source[1] == '=')) {
// To make sure that the length doesn't change between checking the
// length and copying the string, and to speed up things, the
- // resulting string is saved from the call with "copy" == FALSE to the
- // call with "copy" == TRUE.
+ // resulting string is saved from the call with "copy" == false to the
+ // call with "copy" == true.
if (copy) {
if (eval_result != NULL) {
STRCPY(dest, eval_result);
@@ -6639,7 +6678,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
XFREE_CLEAR(eval_result);
}
} else {
- int prev_can_f_submatch = can_f_submatch;
+ const bool prev_can_f_submatch = can_f_submatch;
regsubmatch_T rsm_save;
xfree(eval_result);
@@ -6669,14 +6708,14 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
argv[0].vval.v_list = &matchList.sl_list;
if (expr->v_type == VAR_FUNC) {
s = expr->vval.v_string;
- call_func(s, (int)STRLEN(s), &rettv, 1, argv,
+ call_func(s, -1, &rettv, 1, argv,
fill_submatch_list, 0L, 0L, &dummy,
true, NULL, NULL);
} else if (expr->v_type == VAR_PARTIAL) {
partial_T *partial = expr->vval.v_partial;
s = partial_name(partial);
- call_func(s, (int)STRLEN(s), &rettv, 1, argv,
+ call_func(s, -1, &rettv, 1, argv,
fill_submatch_list, 0L, 0L, &dummy,
true, partial, NULL);
}
@@ -6700,7 +6739,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
}
if (eval_result != NULL) {
- int had_backslash = FALSE;
+ int had_backslash = false;
for (s = eval_result; *s != NUL; MB_PTR_ADV(s)) {
// Change NL to CR, so that it becomes a line break,
@@ -6778,22 +6817,24 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
}
if (c == '\\' && *src != NUL) {
- /* Check for abbreviations -- webb */
+ // Check for abbreviations -- webb
switch (*src) {
case 'r': c = CAR; ++src; break;
case 'n': c = NL; ++src; break;
case 't': c = TAB; ++src; break;
- /* Oh no! \e already has meaning in subst pat :-( */
- /* case 'e': c = ESC; ++src; break; */
+ // Oh no! \e already has meaning in subst pat :-(
+ // case 'e': c = ESC; ++src; break;
case 'b': c = Ctrl_H; ++src; break;
- /* If "backslash" is TRUE the backslash will be removed
- * later. Used to insert a literal CR. */
- default: if (backslash) {
- if (copy)
+ // If "backslash" is true the backslash will be removed
+ // later. Used to insert a literal CR.
+ default:
+ if (backslash) {
+ if (copy) {
*dst = '\\';
- ++dst;
- }
+ }
+ dst++;
+ }
c = *src++;
}
} else {
@@ -6871,7 +6912,7 @@ static int vim_regsub_both(char_u *source, typval_T *expr, char_u *dest,
}
} else if (*s == NUL) { // we hit NUL.
if (copy) {
- EMSG(_(e_re_damg));
+ IEMSG(_(e_re_damg));
}
goto exit;
} else {
@@ -7163,8 +7204,10 @@ regprog_T *vim_regcomp(char_u *expr_arg, int re_flags)
regexp_engine = AUTOMATIC_ENGINE;
}
}
+#ifdef REGEXP_DEBUG
bt_regengine.expr = expr;
nfa_regengine.expr = expr;
+#endif
// reg_iswordc() uses rex.reg_buf
rex.reg_buf = curbuf;
@@ -7245,24 +7288,33 @@ static void report_re_switch(char_u *pat)
/// @param col the column to start looking for match
/// @param nl
///
-/// @return TRUE if there is a match, FALSE if not.
-static int vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col,
- bool nl)
+/// @return true if there is a match, false if not.
+static bool vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col,
+ bool nl)
{
regexec_T rex_save;
bool rex_in_use_save = rex_in_use;
+ // Cannot use the same prog recursively, it contains state.
+ if (rmp->regprog->re_in_use) {
+ EMSG(_(e_recursive));
+ return false;
+ }
+ rmp->regprog->re_in_use = true;
+
if (rex_in_use) {
// Being called recursively, save the state.
rex_save = rex;
}
rex_in_use = true;
+
rex.reg_startp = NULL;
rex.reg_endp = NULL;
rex.reg_startpos = NULL;
rex.reg_endpos = NULL;
int result = rmp->regprog->engine->regexec_nl(rmp, line, col, nl);
+ rmp->regprog->re_in_use = false;
// NFA engine aborted because it's very slow, use backtracking engine instead.
if (rmp->regprog->re_engine == AUTOMATIC_ENGINE
@@ -7276,7 +7328,9 @@ static int vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col,
report_re_switch(pat);
rmp->regprog = vim_regcomp(pat, re_flags);
if (rmp->regprog != NULL) {
+ rmp->regprog->re_in_use = true;
result = rmp->regprog->engine->regexec_nl(rmp, line, col, nl);
+ rmp->regprog->re_in_use = false;
}
xfree(pat);
@@ -7292,27 +7346,27 @@ static int vim_regexec_string(regmatch_T *rmp, char_u *line, colnr_T col,
}
// Note: "*prog" may be freed and changed.
-// Return TRUE if there is a match, FALSE if not.
-int vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line,
+// Return true if there is a match, false if not.
+bool vim_regexec_prog(regprog_T **prog, bool ignore_case, char_u *line,
colnr_T col)
{
regmatch_T regmatch = { .regprog = *prog, .rm_ic = ignore_case };
- int r = vim_regexec_string(&regmatch, line, col, false);
+ bool r = vim_regexec_string(&regmatch, line, col, false);
*prog = regmatch.regprog;
return r;
}
// Note: "rmp->regprog" may be freed and changed.
-// Return TRUE if there is a match, FALSE if not.
-int vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col)
+// Return true if there is a match, false if not.
+bool vim_regexec(regmatch_T *rmp, char_u *line, colnr_T col)
{
return vim_regexec_string(rmp, line, col, false);
}
// Like vim_regexec(), but consider a "\n" in "line" to be a line break.
// Note: "rmp->regprog" may be freed and changed.
-// Return TRUE if there is a match, FALSE if not.
-int vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col)
+// Return true if there is a match, false if not.
+bool vim_regexec_nl(regmatch_T *rmp, char_u *line, colnr_T col)
{
return vim_regexec_string(rmp, line, col, true);
}
@@ -7333,10 +7387,18 @@ long vim_regexec_multi(
proftime_T *tm, // timeout limit or NULL
int *timed_out // flag is set when timeout limit reached
)
+ FUNC_ATTR_NONNULL_ARG(1)
{
regexec_T rex_save;
bool rex_in_use_save = rex_in_use;
+ // Cannot use the same prog recursively, it contains state.
+ if (rmp->regprog->re_in_use) {
+ EMSG(_(e_recursive));
+ return false;
+ }
+ rmp->regprog->re_in_use = true;
+
if (rex_in_use) {
// Being called recursively, save the state.
rex_save = rex;
@@ -7345,6 +7407,7 @@ long vim_regexec_multi(
int result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col,
tm, timed_out);
+ rmp->regprog->re_in_use = false;
// NFA engine aborted because it's very slow, use backtracking engine instead.
if (rmp->regprog->re_engine == AUTOMATIC_ENGINE
@@ -7363,8 +7426,10 @@ long vim_regexec_multi(
reg_do_extmatch = 0;
if (rmp->regprog != NULL) {
+ rmp->regprog->re_in_use = true;
result = rmp->regprog->engine->regexec_multi(rmp, win, buf, lnum, col,
tm, timed_out);
+ rmp->regprog->re_in_use = false;
}
xfree(pat);
diff --git a/src/nvim/regexp_defs.h b/src/nvim/regexp_defs.h
index 116bfee91e..a729a91555 100644
--- a/src/nvim/regexp_defs.h
+++ b/src/nvim/regexp_defs.h
@@ -72,6 +72,7 @@ struct regprog {
unsigned regflags;
unsigned re_engine; ///< Automatic, backtracking or NFA engine.
unsigned re_flags; ///< Second argument for vim_regcomp().
+ bool re_in_use; ///< prog is being executed
};
/*
@@ -84,7 +85,8 @@ typedef struct {
regengine_T *engine;
unsigned regflags;
unsigned re_engine;
- unsigned re_flags; ///< Second argument for vim_regcomp().
+ unsigned re_flags;
+ bool re_in_use;
int regstart;
char_u reganch;
@@ -114,7 +116,8 @@ typedef struct {
regengine_T *engine;
unsigned regflags;
unsigned re_engine;
- unsigned re_flags; ///< Second argument for vim_regcomp().
+ unsigned re_flags;
+ bool re_in_use;
nfa_state_T *start; // points into state[]
diff --git a/src/nvim/regexp_nfa.c b/src/nvim/regexp_nfa.c
index 387732fdee..7cd1ae93d2 100644
--- a/src/nvim/regexp_nfa.c
+++ b/src/nvim/regexp_nfa.c
@@ -230,7 +230,10 @@ enum {
NFA_CLASS_TAB,
NFA_CLASS_RETURN,
NFA_CLASS_BACKSPACE,
- NFA_CLASS_ESCAPE
+ NFA_CLASS_ESCAPE,
+ NFA_CLASS_IDENT,
+ NFA_CLASS_KEYWORD,
+ NFA_CLASS_FNAME,
};
/* Keep in sync with classchars. */
@@ -267,9 +270,9 @@ struct Frag {
typedef struct Frag Frag_T;
typedef struct {
- int in_use; /* number of subexpr with useful info */
+ int in_use; ///< number of subexpr with useful info
- /* When REG_MULTI is TRUE list.multi is used, otherwise list.line. */
+ // When REG_MULTI is true list.multi is used, otherwise list.line.
union {
struct multipos {
linenr_T start_lnum;
@@ -310,48 +313,27 @@ typedef struct {
regsubs_T subs; /* submatch info, only party used */
} nfa_thread_T;
-/* nfa_list_T contains the alternative NFA execution states. */
+// nfa_list_T contains the alternative NFA execution states.
typedef struct {
- nfa_thread_T *t; /* allocated array of states */
- int n; /* nr of states currently in "t" */
- int len; /* max nr of states in "t" */
- int id; /* ID of the list */
- int has_pim; /* TRUE when any state has a PIM */
+ nfa_thread_T *t; ///< allocated array of states
+ int n; ///< nr of states currently in "t"
+ int len; ///< max nr of states in "t"
+ int id; ///< ID of the list
+ int has_pim; ///< true when any state has a PIM
} nfa_list_T;
-/// re_flags passed to nfa_regcomp().
-static int nfa_re_flags;
-
-/* NFA regexp \ze operator encountered. */
-static int nfa_has_zend;
-
-/* NFA regexp \1 .. \9 encountered. */
-static int nfa_has_backref;
-
-/* NFA regexp has \z( ), set zsubexpr. */
-static int nfa_has_zsubexpr;
-
-/* Number of sub expressions actually being used during execution. 1 if only
- * the whole match (subexpr 0) is used. */
-static int nfa_nsubexpr;
-
-static int *post_start; /* holds the postfix form of r.e. */
+// Variables only used in nfa_regcomp() and descendants.
+static int nfa_re_flags; ///< re_flags passed to nfa_regcomp().
+static int *post_start; ///< holds the postfix form of r.e.
static int *post_end;
static int *post_ptr;
-static int nstate; /* Number of states in the NFA. Also used when
- * executing. */
-static int istate; /* Index in the state vector, used in alloc_state() */
+static int nstate; ///< Number of states in the NFA. Also used when executing.
+static int istate; ///< Index in the state vector, used in alloc_state()
/* If not NULL match must end at this position */
static save_se_T *nfa_endp = NULL;
-/* listid is global, so that it increases on recursive calls to
- * nfa_regmatch(), which means we don't have to clear the lastlist field of
- * all the states. */
-static int nfa_listid;
-static int nfa_alt_listid;
-
/* 0 for first call to nfa_regmatch(), 1 for recursive call. */
static int nfa_ll_index = 0;
@@ -395,8 +377,8 @@ nfa_regcomp_start (
post_start = (int *)xmalloc(postfix_size);
post_ptr = post_start;
post_end = post_start + nstate_max;
- nfa_has_zend = FALSE;
- nfa_has_backref = FALSE;
+ rex.nfa_has_zend = false;
+ rex.nfa_has_backref = false;
/* shared with BT engine */
regcomp_start(expr, re_flags);
@@ -605,12 +587,10 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl)
# define CLASS_o9 0x02
# define CLASS_underscore 0x01
- int newl = FALSE;
char_u *p;
int config = 0;
- if (extra_newl == TRUE)
- newl = TRUE;
+ bool newl = extra_newl == true;
if (*end != ']')
return FAIL;
@@ -655,13 +635,13 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl)
}
p += 3;
} else if (p + 1 < end && *p == '\\' && *(p + 1) == 'n') {
- newl = TRUE;
+ newl = true;
p += 2;
} else if (*p == '_') {
config |= CLASS_underscore;
p++;
} else if (*p == '\n') {
- newl = TRUE;
+ newl = true;
p++;
} else
return FAIL;
@@ -670,8 +650,9 @@ static int nfa_recognize_char_class(char_u *start, char_u *end, int extra_newl)
if (p != end)
return FAIL;
- if (newl == TRUE)
+ if (newl == true) {
extra_newl = NFA_ADD_NL;
+ }
switch (config) {
case CLASS_o9:
@@ -1188,7 +1169,7 @@ static int nfa_regatom(void)
case Magic('$'):
EMIT(NFA_EOL);
- had_eol = TRUE;
+ had_eol = true;
break;
case Magic('<'):
@@ -1210,7 +1191,7 @@ static int nfa_regatom(void)
}
if (c == '$') { /* "\_$" is end-of-line */
EMIT(NFA_EOL);
- had_eol = TRUE;
+ had_eol = true;
break;
}
@@ -1257,7 +1238,7 @@ static int nfa_regatom(void)
if (p == NULL) {
if (extra == NFA_ADD_NL) {
EMSGN(_(e_ill_char_class), c);
- rc_did_emsg = TRUE;
+ rc_did_emsg = true;
return FAIL;
}
IEMSGN("INTERNAL: Unknown character class char: %" PRId64, c);
@@ -1346,7 +1327,7 @@ static int nfa_regatom(void)
return FAIL;
}
EMIT(NFA_BACKREF1 + refnum);
- nfa_has_backref = true;
+ rex.nfa_has_backref = true;
}
break;
@@ -1361,7 +1342,7 @@ static int nfa_regatom(void)
break;
case 'e':
EMIT(NFA_ZEND);
- nfa_has_zend = true;
+ rex.nfa_has_zend = true;
if (!re_mult_next("\\zs")) {
return false;
}
@@ -1380,8 +1361,8 @@ static int nfa_regatom(void)
EMSG_RET_FAIL(_(e_z1_not_allowed));
}
EMIT(NFA_ZREF1 + (no_Magic(c) - '1'));
- /* No need to set nfa_has_backref, the sub-matches don't
- * change when \z1 .. \z9 matches or not. */
+ // No need to set rex.nfa_has_backref, the sub-matches don't
+ // change when \z1 .. \z9 matches or not.
re_has_z = REX_USE;
break;
case '(':
@@ -1598,12 +1579,12 @@ collection:
EMIT(NFA_CONCAT);
MB_PTR_ADV(regparse);
}
- /* Emit the OR branches for each character in the [] */
- emit_range = FALSE;
+ // Emit the OR branches for each character in the []
+ emit_range = false;
while (regparse < endp) {
oldstartc = startc;
startc = -1;
- got_coll_char = FALSE;
+ got_coll_char = false;
if (*regparse == '[') {
/* Check for [: :], [= =], [. .] */
equiclass = collclass = 0;
@@ -1665,6 +1646,15 @@ collection:
case CLASS_ESCAPE:
EMIT(NFA_CLASS_ESCAPE);
break;
+ case CLASS_IDENT:
+ EMIT(NFA_CLASS_IDENT);
+ break;
+ case CLASS_KEYWORD:
+ EMIT(NFA_CLASS_KEYWORD);
+ break;
+ case CLASS_FNAME:
+ EMIT(NFA_CLASS_FNAME);
+ break;
}
EMIT(NFA_CONCAT);
continue;
@@ -1684,7 +1674,7 @@ collection:
/* Try a range like 'a-x' or '\t-z'. Also allows '-' as a
* start character. */
if (*regparse == '-' && oldstartc != -1) {
- emit_range = TRUE;
+ emit_range = true;
startc = oldstartc;
MB_PTR_ADV(regparse);
continue; // reading the end of the range
@@ -1764,7 +1754,7 @@ collection:
EMIT(NFA_CONCAT);
}
}
- emit_range = FALSE;
+ emit_range = false;
startc = -1;
} else {
/* This char (startc) is not part of a range. Just
@@ -1781,10 +1771,11 @@ collection:
if (!negated)
extra = NFA_ADD_NL;
} else {
- if (got_coll_char == TRUE && startc == 0)
+ if (got_coll_char == true && startc == 0) {
EMIT(0x0a);
- else
+ } else {
EMIT(startc);
+ }
EMIT(NFA_CONCAT);
}
}
@@ -1802,13 +1793,14 @@ collection:
regparse = endp;
MB_PTR_ADV(regparse);
- /* Mark end of the collection. */
- if (negated == TRUE)
+ // Mark end of the collection.
+ if (negated == true) {
EMIT(NFA_END_NEG_COLL);
- else
+ } else {
EMIT(NFA_END_COLL);
+ }
- /* \_[] also matches \n but it's not negated */
+ // \_[] also matches \n but it's not negated
if (extra == NFA_ADD_NL) {
EMIT(reg_string ? NL : NFA_NEWL);
EMIT(NFA_OR);
@@ -1877,7 +1869,7 @@ static int nfa_regpiece(void)
int op;
int ret;
long minval, maxval;
- int greedy = TRUE; /* Braces are prefixed with '-' ? */
+ bool greedy = true; // Braces are prefixed with '-' ?
parse_state_T old_state;
parse_state_T new_state;
int64_t c2;
@@ -1977,11 +1969,11 @@ static int nfa_regpiece(void)
* parenthesis have the same id
*/
- greedy = TRUE;
+ greedy = true;
c2 = peekchr();
if (c2 == '-' || c2 == Magic('-')) {
skipchr();
- greedy = FALSE;
+ greedy = false;
}
if (!read_limits(&minval, &maxval))
EMSG_RET_FAIL(_("E870: (NFA regexp) Error reading repetition limits"));
@@ -2019,7 +2011,7 @@ static int nfa_regpiece(void)
/* Save parse state after the repeated atom and the \{} */
save_parse_state(&new_state);
- quest = (greedy == TRUE ? NFA_QUEST : NFA_QUEST_NONGREEDY);
+ quest = (greedy == true ? NFA_QUEST : NFA_QUEST_NONGREEDY);
for (i = 0; i < maxval; i++) {
/* Goto beginning of the repeated atom */
restore_parse_state(&old_state);
@@ -2073,8 +2065,8 @@ static int nfa_regpiece(void)
*/
static int nfa_regconcat(void)
{
- int cont = TRUE;
- int first = TRUE;
+ bool cont = true;
+ bool first = true;
while (cont) {
switch (peekchr()) {
@@ -2082,7 +2074,7 @@ static int nfa_regconcat(void)
case Magic('|'):
case Magic('&'):
case Magic(')'):
- cont = FALSE;
+ cont = false;
break;
case Magic('Z'):
@@ -2119,12 +2111,14 @@ static int nfa_regconcat(void)
break;
default:
- if (nfa_regpiece() == FAIL)
+ if (nfa_regpiece() == FAIL) {
return FAIL;
- if (first == FALSE)
+ }
+ if (first == false) {
EMIT(NFA_CONCAT);
- else
- first = FALSE;
+ } else {
+ first = false;
+ }
break;
}
}
@@ -2230,15 +2224,14 @@ nfa_reg (
else
EMSG_RET_FAIL(_("E873: (NFA regexp) proper termination error"));
}
- /*
- * Here we set the flag allowing back references to this set of
- * parentheses.
- */
+ // Here we set the flag allowing back references to this set of
+ // parentheses.
if (paren == REG_PAREN) {
- had_endbrace[parno] = TRUE; /* have seen the close paren */
+ had_endbrace[parno] = true; // have seen the close paren
EMIT(NFA_MOPEN + parno);
- } else if (paren == REG_ZPAREN)
+ } else if (paren == REG_ZPAREN) {
EMIT(NFA_ZOPEN + parno);
+ }
return OK;
}
@@ -2248,10 +2241,10 @@ static char_u code[50];
static void nfa_set_code(int c)
{
- int addnl = FALSE;
+ int addnl = false;
if (c >= NFA_FIRST_NL && c <= NFA_LAST_NL) {
- addnl = TRUE;
+ addnl = true;
c -= NFA_ADD_NL;
}
@@ -2426,6 +2419,9 @@ static void nfa_set_code(int c)
case NFA_CLASS_RETURN: STRCPY(code, "NFA_CLASS_RETURN"); break;
case NFA_CLASS_BACKSPACE: STRCPY(code, "NFA_CLASS_BACKSPACE"); break;
case NFA_CLASS_ESCAPE: STRCPY(code, "NFA_CLASS_ESCAPE"); break;
+ case NFA_CLASS_IDENT: STRCPY(code, "NFA_CLASS_IDENT"); break;
+ case NFA_CLASS_KEYWORD: STRCPY(code, "NFA_CLASS_KEYWORD"); break;
+ case NFA_CLASS_FNAME: STRCPY(code, "NFA_CLASS_FNAME"); break;
case NFA_ANY: STRCPY(code, "NFA_ANY"); break;
case NFA_IDENT: STRCPY(code, "NFA_IDENT"); break;
@@ -2464,9 +2460,9 @@ static void nfa_set_code(int c)
code[5] = c;
}
- if (addnl == TRUE)
+ if (addnl == true) {
STRCAT(code, " + NEWLINE ");
-
+ }
}
static FILE *log_fd;
@@ -2848,11 +2844,8 @@ static int nfa_max_width(nfa_state_T *startstate, int depth)
case NFA_UPPER_IC:
case NFA_NUPPER_IC:
case NFA_ANY_COMPOSING:
- /* possibly non-ascii */
- if (has_mbyte)
- len += 3;
- else
- ++len;
+ // possibly non-ascii
+ len += 3;
break;
case NFA_START_INVISIBLE:
@@ -3019,12 +3012,12 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
for (p = postfix; p < end; ++p) {
switch (*p) {
case NFA_CONCAT:
- /* Concatenation.
- * Pay attention: this operator does not exist in the r.e. itself
- * (it is implicit, really). It is added when r.e. is translated
- * to postfix form in re2post(). */
- if (nfa_calc_size == TRUE) {
- /* nstate += 0; */
+ // Concatenation.
+ // Pay attention: this operator does not exist in the r.e. itself
+ // (it is implicit, really). It is added when r.e. is translated
+ // to postfix form in re2post().
+ if (nfa_calc_size == true) {
+ // nstate += 0;
break;
}
e2 = POP();
@@ -3034,8 +3027,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
break;
case NFA_OR:
- /* Alternation */
- if (nfa_calc_size == TRUE) {
+ // Alternation
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3048,8 +3041,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
break;
case NFA_STAR:
- /* Zero or more, prefer more */
- if (nfa_calc_size == TRUE) {
+ // Zero or more, prefer more
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3062,8 +3055,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
break;
case NFA_STAR_NONGREEDY:
- /* Zero or more, prefer zero */
- if (nfa_calc_size == TRUE) {
+ // Zero or more, prefer zero
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3076,8 +3069,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
break;
case NFA_QUEST:
- /* one or zero atoms=> greedy match */
- if (nfa_calc_size == TRUE) {
+ // one or zero atoms=> greedy match
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3089,8 +3082,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
break;
case NFA_QUEST_NONGREEDY:
- /* zero or one atoms => non-greedy match */
- if (nfa_calc_size == TRUE) {
+ // zero or one atoms => non-greedy match
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3106,7 +3099,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
/* On the stack is the sequence starting with NFA_START_COLL or
* NFA_START_NEG_COLL and all possible characters. Patch it to
* add the output to the start. */
- if (nfa_calc_size == TRUE) {
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3120,10 +3113,10 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
break;
case NFA_RANGE:
- /* Before this are two characters, the low and high end of a
- * range. Turn them into two states with MIN and MAX. */
- if (nfa_calc_size == TRUE) {
- /* nstate += 0; */
+ // Before this are two characters, the low and high end of a
+ // range. Turn them into two states with MIN and MAX.
+ if (nfa_calc_size == true) {
+ // nstate += 0;
break;
}
e2 = POP();
@@ -3137,8 +3130,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
break;
case NFA_EMPTY:
- /* 0-length, used in a repetition with max/min count of 0 */
- if (nfa_calc_size == TRUE) {
+ // 0-length, used in a repetition with max/min count of 0
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3152,20 +3145,19 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
{
int n;
- /* \%[abc] implemented as:
- * NFA_SPLIT
- * +-CHAR(a)
- * | +-NFA_SPLIT
- * | +-CHAR(b)
- * | | +-NFA_SPLIT
- * | | +-CHAR(c)
- * | | | +-next
- * | | +- next
- * | +- next
- * +- next
- */
- n = *++p; /* get number of characters */
- if (nfa_calc_size == TRUE) {
+ // \%[abc] implemented as:
+ // NFA_SPLIT
+ // +-CHAR(a)
+ // | +-NFA_SPLIT
+ // | +-CHAR(b)
+ // | | +-NFA_SPLIT
+ // | | +-CHAR(c)
+ // | | | +-next
+ // | | +- next
+ // | +- next
+ // +- next
+ n = *++p; // get number of characters
+ if (nfa_calc_size == true) {
nstate += n;
break;
}
@@ -3235,7 +3227,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
* Surrounds the preceding atom with START_INVISIBLE and
* END_INVISIBLE, similarly to MOPEN. */
- if (nfa_calc_size == TRUE) {
+ if (nfa_calc_size == true) {
nstate += pattern ? 4 : 2;
break;
}
@@ -3297,8 +3289,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
case NFA_ZOPEN7:
case NFA_ZOPEN8:
case NFA_ZOPEN9:
- case NFA_NOPEN: /* \%( \) "Invisible Submatch" */
- if (nfa_calc_size == TRUE) {
+ case NFA_NOPEN: // \%( \) "Invisible Submatch"
+ if (nfa_calc_size == true) {
nstate += 2;
break;
}
@@ -3376,7 +3368,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
case NFA_ZREF7:
case NFA_ZREF8:
case NFA_ZREF9:
- if (nfa_calc_size == TRUE) {
+ if (nfa_calc_size == true) {
nstate += 2;
break;
}
@@ -3405,7 +3397,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
{
int n = *++p; /* lnum, col or mark name */
- if (nfa_calc_size == TRUE) {
+ if (nfa_calc_size == true) {
nstate += 1;
break;
}
@@ -3420,8 +3412,8 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
case NFA_ZSTART:
case NFA_ZEND:
default:
- /* Operands */
- if (nfa_calc_size == TRUE) {
+ // Operands
+ if (nfa_calc_size == true) {
nstate++;
break;
}
@@ -3435,7 +3427,7 @@ static nfa_state_T *post2nfa(int *postfix, int *end, int nfa_calc_size)
} /* for(p = postfix; *p; ++p) */
- if (nfa_calc_size == TRUE) {
+ if (nfa_calc_size == true) {
nstate++;
goto theend; /* Return value when counting size is ignored anyway */
}
@@ -3489,11 +3481,11 @@ static void nfa_postprocess(nfa_regprog_T *prog)
|| c == NFA_START_INVISIBLE_BEFORE_NEG) {
int directly;
- /* Do it directly when what follows is possibly the end of the
- * match. */
- if (match_follows(prog->state[i].out1->out, 0))
- directly = TRUE;
- else {
+ // Do it directly when what follows is possibly the end of the
+ // match.
+ if (match_follows(prog->state[i].out1->out, 0)) {
+ directly = true;
+ } else {
int ch_invisible = failure_chance(prog->state[i].out, 0);
int ch_follows = failure_chance(prog->state[i].out1->out, 0);
@@ -3505,10 +3497,11 @@ static void nfa_postprocess(nfa_regprog_T *prog)
* unbounded, always prefer what follows then,
* unless what follows will always match.
* Otherwise strongly prefer what follows. */
- if (prog->state[i].val <= 0 && ch_follows > 0)
- directly = FALSE;
- else
+ if (prog->state[i].val <= 0 && ch_follows > 0) {
+ directly = false;
+ } else {
directly = ch_follows * 10 < ch_invisible;
+ }
} else {
/* normal invisible, first do the one with the
* highest failure chance */
@@ -3537,8 +3530,9 @@ static void nfa_postprocess(nfa_regprog_T *prog)
static void log_subsexpr(regsubs_T *subs)
{
log_subexpr(&subs->norm);
- if (nfa_has_zsubexpr)
+ if (rex.nfa_has_zsubexpr) {
log_subexpr(&subs->synt);
+ }
}
static void log_subexpr(regsub_T *sub)
@@ -3564,15 +3558,17 @@ static void log_subexpr(regsub_T *sub)
}
}
-static char *pim_info(nfa_pim_T *pim)
+static char *pim_info(const nfa_pim_T *pim)
{
static char buf[30];
- if (pim == NULL || pim->result == NFA_PIM_UNUSED)
+ if (pim == NULL || pim->result == NFA_PIM_UNUSED) {
buf[0] = NUL;
- else {
- sprintf(buf, " PIM col %d", REG_MULTI ? (int)pim->end.pos.col
- : (int)(pim->end.ptr - reginput));
+ } else {
+ snprintf(buf, sizeof(buf), " PIM col %d",
+ REG_MULTI
+ ? (int)pim->end.pos.col
+ : (int)(pim->end.ptr - rex.input));
}
return buf;
}
@@ -3591,19 +3587,21 @@ static void copy_pim(nfa_pim_T *to, nfa_pim_T *from)
to->result = from->result;
to->state = from->state;
copy_sub(&to->subs.norm, &from->subs.norm);
- if (nfa_has_zsubexpr)
+ if (rex.nfa_has_zsubexpr) {
copy_sub(&to->subs.synt, &from->subs.synt);
+ }
to->end = from->end;
}
static void clear_sub(regsub_T *sub)
{
- if (REG_MULTI)
- /* Use 0xff to set lnum to -1 */
+ if (REG_MULTI) {
+ // Use 0xff to set lnum to -1
memset(sub->list.multi, 0xff,
- sizeof(struct multipos) * nfa_nsubexpr);
- else
- memset(sub->list.line, 0, sizeof(struct linepos) * nfa_nsubexpr);
+ sizeof(struct multipos) * rex.nfa_nsubexpr);
+ } else {
+ memset(sub->list.line, 0, sizeof(struct linepos) * rex.nfa_nsubexpr);
+ }
sub->in_use = 0;
}
@@ -3651,7 +3649,7 @@ static void copy_sub_off(regsub_T *to, regsub_T *from)
*/
static void copy_ze_off(regsub_T *to, regsub_T *from)
{
- if (nfa_has_zend) {
+ if (rex.nfa_has_zend) {
if (REG_MULTI) {
if (from->list.multi[0].end_lnum >= 0){
to->list.multi[0].end_lnum = from->list.multi[0].end_lnum;
@@ -3664,9 +3662,9 @@ static void copy_ze_off(regsub_T *to, regsub_T *from)
}
}
-// Return TRUE if "sub1" and "sub2" have the same start positions.
+// Return true if "sub1" and "sub2" have the same start positions.
// When using back-references also check the end position.
-static int sub_equal(regsub_T *sub1, regsub_T *sub2)
+static bool sub_equal(regsub_T *sub1, regsub_T *sub2)
{
int i;
int todo;
@@ -3677,22 +3675,25 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2)
todo = sub1->in_use > sub2->in_use ? sub1->in_use : sub2->in_use;
if (REG_MULTI) {
- for (i = 0; i < todo; ++i) {
- if (i < sub1->in_use)
+ for (i = 0; i < todo; i++) {
+ if (i < sub1->in_use) {
s1 = sub1->list.multi[i].start_lnum;
- else
+ } else {
s1 = -1;
- if (i < sub2->in_use)
+ }
+ if (i < sub2->in_use) {
s2 = sub2->list.multi[i].start_lnum;
- else
+ } else {
s2 = -1;
- if (s1 != s2)
- return FALSE;
+ }
+ if (s1 != s2) {
+ return false;
+ }
if (s1 != -1 && sub1->list.multi[i].start_col
- != sub2->list.multi[i].start_col)
- return FALSE;
-
- if (nfa_has_backref) {
+ != sub2->list.multi[i].start_col) {
+ return false;
+ }
+ if (rex.nfa_has_backref) {
if (i < sub1->in_use) {
s1 = sub1->list.multi[i].end_lnum;
} else {
@@ -3704,28 +3705,30 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2)
s2 = -1;
}
if (s1 != s2) {
- return FALSE;
+ return false;
}
if (s1 != -1
&& sub1->list.multi[i].end_col != sub2->list.multi[i].end_col) {
- return FALSE;
+ return false;
}
}
}
} else {
- for (i = 0; i < todo; ++i) {
- if (i < sub1->in_use)
+ for (i = 0; i < todo; i++) {
+ if (i < sub1->in_use) {
sp1 = sub1->list.line[i].start;
- else
+ } else {
sp1 = NULL;
- if (i < sub2->in_use)
+ }
+ if (i < sub2->in_use) {
sp2 = sub2->list.line[i].start;
- else
+ } else {
sp2 = NULL;
- if (sp1 != sp2)
- return FALSE;
-
- if (nfa_has_backref) {
+ }
+ if (sp1 != sp2) {
+ return false;
+ }
+ if (rex.nfa_has_backref) {
if (i < sub1->in_use) {
sp1 = sub1->list.line[i].end;
} else {
@@ -3737,13 +3740,13 @@ static int sub_equal(regsub_T *sub1, regsub_T *sub2)
sp2 = NULL;
}
if (sp1 != sp2) {
- return FALSE;
+ return false;
}
}
}
}
- return TRUE;
+ return true;
}
#ifdef REGEXP_DEBUG
@@ -3754,83 +3757,81 @@ static void report_state(char *action,
nfa_pim_T *pim) {
int col;
- if (sub->in_use <= 0)
+ if (sub->in_use <= 0) {
col = -1;
- else if (REG_MULTI)
+ } else if (REG_MULTI) {
col = sub->list.multi[0].start_col;
- else
- col = (int)(sub->list.line[0].start - regline);
+ } else {
+ col = (int)(sub->list.line[0].start - rex.line);
+ }
nfa_set_code(state->c);
fprintf(log_fd, "> %s state %d to list %d. char %d: %s (start col %d)%s\n",
- action, abs(state->id), lid, state->c, code, col,
- pim_info(pim));
+ action, abs(state->id), lid, state->c, code, col,
+ pim_info(pim));
}
#endif
-/*
- * Return TRUE if the same state is already in list "l" with the same
- * positions as "subs".
- */
-static int
-has_state_with_pos (
- nfa_list_T *l, /* runtime state list */
- nfa_state_T *state, /* state to update */
- regsubs_T *subs, /* pointers to subexpressions */
- nfa_pim_T *pim /* postponed match or NULL */
+// Return true if the same state is already in list "l" with the same
+// positions as "subs".
+static bool has_state_with_pos(
+ nfa_list_T *l, // runtime state list
+ nfa_state_T *state, // state to update
+ regsubs_T *subs, // pointers to subexpressions
+ nfa_pim_T *pim // postponed match or NULL
)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 3)
{
- nfa_thread_T *thread;
- int i;
-
- for (i = 0; i < l->n; ++i) {
- thread = &l->t[i];
+ for (int i = 0; i < l->n; i++) {
+ nfa_thread_T *thread = &l->t[i];
if (thread->state->id == state->id
&& sub_equal(&thread->subs.norm, &subs->norm)
- && (!nfa_has_zsubexpr
+ && (!rex.nfa_has_zsubexpr
|| sub_equal(&thread->subs.synt, &subs->synt))
- && pim_equal(&thread->pim, pim))
- return TRUE;
+ && pim_equal(&thread->pim, pim)) {
+ return true;
+ }
}
- return FALSE;
+ return false;
}
-/*
- * Return TRUE if "one" and "two" are equal. That includes when both are not
- * set.
- */
-static int pim_equal(nfa_pim_T *one, nfa_pim_T *two)
+// Return true if "one" and "two" are equal. That includes when both are not
+// set.
+static bool pim_equal(const nfa_pim_T *one, const nfa_pim_T *two)
{
- int one_unused = (one == NULL || one->result == NFA_PIM_UNUSED);
- int two_unused = (two == NULL || two->result == NFA_PIM_UNUSED);
+ const bool one_unused = (one == NULL || one->result == NFA_PIM_UNUSED);
+ const bool two_unused = (two == NULL || two->result == NFA_PIM_UNUSED);
- if (one_unused)
- /* one is unused: equal when two is also unused */
+ if (one_unused) {
+ // one is unused: equal when two is also unused
return two_unused;
- if (two_unused)
- /* one is used and two is not: not equal */
- return FALSE;
- /* compare the state id */
- if (one->state->id != two->state->id)
- return FALSE;
- /* compare the position */
- if (REG_MULTI)
+ }
+ if (two_unused) {
+ // one is used and two is not: not equal
+ return false;
+ }
+ // compare the state id
+ if (one->state->id != two->state->id) {
+ return false;
+ }
+ // compare the position
+ if (REG_MULTI) {
return one->end.pos.lnum == two->end.pos.lnum
&& one->end.pos.col == two->end.pos.col;
+ }
return one->end.ptr == two->end.ptr;
}
-/*
- * Return TRUE if "state" leads to a NFA_MATCH without advancing the input.
- */
-static int match_follows(nfa_state_T *startstate, int depth)
+// Return true if "state" leads to a NFA_MATCH without advancing the input.
+static bool match_follows(const nfa_state_T *startstate, int depth)
+ FUNC_ATTR_NONNULL_ALL
{
- nfa_state_T *state = startstate;
-
- /* avoid too much recursion */
- if (depth > 10)
- return FALSE;
+ const nfa_state_T *state = startstate;
+ // avoid too much recursion
+ if (depth > 10) {
+ return false;
+ }
while (state != NULL) {
switch (state->c) {
case NFA_MATCH:
@@ -3838,7 +3839,7 @@ static int match_follows(nfa_state_T *startstate, int depth)
case NFA_END_INVISIBLE:
case NFA_END_INVISIBLE_NEG:
case NFA_END_PATTERN:
- return TRUE;
+ return true;
case NFA_SPLIT:
return match_follows(state->out, depth + 1)
@@ -3892,39 +3893,38 @@ static int match_follows(nfa_state_T *startstate, int depth)
case NFA_START_COLL:
case NFA_START_NEG_COLL:
case NFA_NEWL:
- /* state will advance input */
- return FALSE;
+ // state will advance input
+ return false;
default:
- if (state->c > 0)
- /* state will advance input */
- return FALSE;
-
- /* Others: zero-width or possibly zero-width, might still find
- * a match at the same position, keep looking. */
+ if (state->c > 0) {
+ // state will advance input
+ return false;
+ }
+ // Others: zero-width or possibly zero-width, might still find
+ // a match at the same position, keep looking.
break;
}
state = state->out;
}
- return FALSE;
+ return false;
}
-/*
- * Return TRUE if "state" is already in list "l".
- */
-static int
-state_in_list (
- nfa_list_T *l, /* runtime state list */
- nfa_state_T *state, /* state to update */
- regsubs_T *subs /* pointers to subexpressions */
+// Return true if "state" is already in list "l".
+static bool state_in_list(
+ nfa_list_T *l, // runtime state list
+ nfa_state_T *state, // state to update
+ regsubs_T *subs // pointers to subexpressions
)
+ FUNC_ATTR_NONNULL_ALL
{
if (state->lastlist[nfa_ll_index] == l->id) {
- if (!nfa_has_backref || has_state_with_pos(l, state, subs, NULL))
- return TRUE;
+ if (!rex.nfa_has_backref || has_state_with_pos(l, state, subs, NULL)) {
+ return true;
+ }
}
- return FALSE;
+ return false;
}
// Offset used for "off" by addstate_here().
@@ -3943,10 +3943,10 @@ static regsubs_T *addstate(
{
int subidx;
int off = off_arg;
- int add_here = FALSE;
+ int add_here = false;
int listindex = 0;
int k;
- int found = FALSE;
+ int found = false;
nfa_thread_T *thread;
struct multipos save_multipos;
int save_in_use;
@@ -3956,7 +3956,7 @@ static regsubs_T *addstate(
regsubs_T *subs = subs_arg;
static regsubs_T temp_subs;
#ifdef REGEXP_DEBUG
- int did_print = FALSE;
+ int did_print = false;
#endif
static int depth = 0;
@@ -4005,15 +4005,16 @@ static regsubs_T *addstate(
case NFA_BOL:
case NFA_BOF:
- /* "^" won't match past end-of-line, don't bother trying.
- * Except when at the end of the line, or when we are going to the
- * next line for a look-behind match. */
- if (reginput > regline
- && *reginput != NUL
+ // "^" won't match past end-of-line, don't bother trying.
+ // Except when at the end of the line, or when we are going to the
+ // next line for a look-behind match.
+ if (rex.input > rex.line
+ && *rex.input != NUL
&& (nfa_endp == NULL
|| !REG_MULTI
- || reglnum == nfa_endp->se_u.pos.lnum))
+ || rex.lnum == nfa_endp->se_u.pos.lnum)) {
goto skip_add;
+ }
FALLTHROUGH;
case NFA_MOPEN1:
@@ -4047,7 +4048,7 @@ static regsubs_T *addstate(
* unless it is an MOPEN that is used for a backreference or
* when there is a PIM. For NFA_MATCH check the position,
* lower position is preferred. */
- if (!nfa_has_backref && pim == NULL && !l->has_pim
+ if (!rex.nfa_has_backref && pim == NULL && !l->has_pim
&& state->c != NFA_MATCH) {
/* When called from addstate_here() do insert before
@@ -4055,7 +4056,7 @@ static regsubs_T *addstate(
if (add_here) {
for (k = 0; k < l->n && k < listindex; ++k) {
if (l->t[k].state->id == state->id) {
- found = TRUE;
+ found = true;
break;
}
}
@@ -4092,11 +4093,12 @@ skip_add:
return NULL;
}
if (subs != &temp_subs) {
- /* "subs" may point into the current array, need to make a
- * copy before it becomes invalid. */
+ // "subs" may point into the current array, need to make a
+ // copy before it becomes invalid.
copy_sub(&temp_subs.norm, &subs->norm);
- if (nfa_has_zsubexpr)
+ if (rex.nfa_has_zsubexpr) {
copy_sub(&temp_subs.synt, &subs->synt);
+ }
subs = &temp_subs;
}
@@ -4113,14 +4115,15 @@ skip_add:
thread->pim.result = NFA_PIM_UNUSED;
else {
copy_pim(&thread->pim, pim);
- l->has_pim = TRUE;
+ l->has_pim = true;
}
copy_sub(&thread->subs.norm, &subs->norm);
- if (nfa_has_zsubexpr)
+ if (rex.nfa_has_zsubexpr) {
copy_sub(&thread->subs.synt, &subs->synt);
+ }
#ifdef REGEXP_DEBUG
report_state("Adding", &thread->subs.norm, state, l->id, pim);
- did_print = TRUE;
+ did_print = true;
#endif
}
@@ -4195,13 +4198,12 @@ skip_add:
sub->in_use = subidx + 1;
}
if (off == -1) {
- sub->list.multi[subidx].start_lnum = reglnum + 1;
+ sub->list.multi[subidx].start_lnum = rex.lnum + 1;
sub->list.multi[subidx].start_col = 0;
} else {
-
- sub->list.multi[subidx].start_lnum = reglnum;
+ sub->list.multi[subidx].start_lnum = rex.lnum;
sub->list.multi[subidx].start_col =
- (colnr_T)(reginput - regline + off);
+ (colnr_T)(rex.input - rex.line + off);
}
sub->list.multi[subidx].end_lnum = -1;
} else {
@@ -4216,7 +4218,7 @@ skip_add:
}
sub->in_use = subidx + 1;
}
- sub->list.line[subidx].start = reginput + off;
+ sub->list.line[subidx].start = rex.input + off;
}
subs = addstate(l, state->out, subs, pim, off_arg);
@@ -4241,9 +4243,10 @@ skip_add:
break;
case NFA_MCLOSE:
- if (nfa_has_zend && (REG_MULTI
- ? subs->norm.list.multi[0].end_lnum >= 0
- : subs->norm.list.line[0].end != NULL)) {
+ if (rex.nfa_has_zend
+ && (REG_MULTI
+ ? subs->norm.list.multi[0].end_lnum >= 0
+ : subs->norm.list.line[0].end != NULL)) {
// Do not overwrite the position set by \ze.
subs = addstate(l, state->out, subs, pim, off_arg);
break;
@@ -4288,18 +4291,18 @@ skip_add:
if (REG_MULTI) {
save_multipos = sub->list.multi[subidx];
if (off == -1) {
- sub->list.multi[subidx].end_lnum = reglnum + 1;
+ sub->list.multi[subidx].end_lnum = rex.lnum + 1;
sub->list.multi[subidx].end_col = 0;
} else {
- sub->list.multi[subidx].end_lnum = reglnum;
+ sub->list.multi[subidx].end_lnum = rex.lnum;
sub->list.multi[subidx].end_col =
- (colnr_T)(reginput - regline + off);
+ (colnr_T)(rex.input - rex.line + off);
}
/* avoid compiler warnings */
save_ptr = NULL;
} else {
save_ptr = sub->list.line[subidx].end;
- sub->list.line[subidx].end = reginput + off;
+ sub->list.line[subidx].end = rex.input + off;
// avoid compiler warnings
memset(&save_multipos, 0, sizeof(save_multipos));
}
@@ -4486,6 +4489,21 @@ static int check_char_class(int class, int c)
return OK;
}
break;
+ case NFA_CLASS_IDENT:
+ if (vim_isIDc(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_KEYWORD:
+ if (reg_iswordc(c)) {
+ return OK;
+ }
+ break;
+ case NFA_CLASS_FNAME:
+ if (vim_isfilec(c)) {
+ return OK;
+ }
+ break;
default:
// should not be here :P
@@ -4497,7 +4515,7 @@ static int check_char_class(int class, int c)
/*
* Check for a match with subexpression "subidx".
- * Return TRUE if it matches.
+ * Return true if it matches.
*/
static int
match_backref (
@@ -4512,49 +4530,49 @@ match_backref (
retempty:
/* backref was not set, match an empty string */
*bytelen = 0;
- return TRUE;
+ return true;
}
if (REG_MULTI) {
if (sub->list.multi[subidx].start_lnum < 0
|| sub->list.multi[subidx].end_lnum < 0)
goto retempty;
- if (sub->list.multi[subidx].start_lnum == reglnum
- && sub->list.multi[subidx].end_lnum == reglnum) {
+ if (sub->list.multi[subidx].start_lnum == rex.lnum
+ && sub->list.multi[subidx].end_lnum == rex.lnum) {
len = sub->list.multi[subidx].end_col
- sub->list.multi[subidx].start_col;
- if (cstrncmp(regline + sub->list.multi[subidx].start_col,
- reginput, &len) == 0) {
+ if (cstrncmp(rex.line + sub->list.multi[subidx].start_col,
+ rex.input, &len) == 0) {
*bytelen = len;
- return TRUE;
+ return true;
}
} else {
- if (match_with_backref(
- sub->list.multi[subidx].start_lnum,
- sub->list.multi[subidx].start_col,
- sub->list.multi[subidx].end_lnum,
- sub->list.multi[subidx].end_col,
- bytelen) == RA_MATCH)
- return TRUE;
+ if (match_with_backref(sub->list.multi[subidx].start_lnum,
+ sub->list.multi[subidx].start_col,
+ sub->list.multi[subidx].end_lnum,
+ sub->list.multi[subidx].end_col,
+ bytelen) == RA_MATCH) {
+ return true;
+ }
}
} else {
if (sub->list.line[subidx].start == NULL
|| sub->list.line[subidx].end == NULL)
goto retempty;
len = (int)(sub->list.line[subidx].end - sub->list.line[subidx].start);
- if (cstrncmp(sub->list.line[subidx].start, reginput, &len) == 0) {
+ if (cstrncmp(sub->list.line[subidx].start, rex.input, &len) == 0) {
*bytelen = len;
- return TRUE;
+ return true;
}
}
- return FALSE;
+ return false;
}
/*
* Check for a match with \z subexpression "subidx".
- * Return TRUE if it matches.
+ * Return true if it matches.
*/
static int
match_zref (
@@ -4568,15 +4586,15 @@ match_zref (
if (re_extmatch_in == NULL || re_extmatch_in->matches[subidx] == NULL) {
/* backref was not set, match an empty string */
*bytelen = 0;
- return TRUE;
+ return true;
}
len = (int)STRLEN(re_extmatch_in->matches[subidx]);
- if (cstrncmp(re_extmatch_in->matches[subidx], reginput, &len) == 0) {
+ if (cstrncmp(re_extmatch_in->matches[subidx], rex.input, &len) == 0) {
*bytelen = len;
- return TRUE;
+ return true;
}
- return FALSE;
+ return false;
}
/*
@@ -4629,74 +4647,79 @@ static bool nfa_re_num_cmp(uintmax_t val, int op, uintmax_t pos)
static int recursive_regmatch(
nfa_state_T *state, nfa_pim_T *pim, nfa_regprog_T *prog,
regsubs_T *submatch, regsubs_T *m, int **listids, int *listids_len)
+ FUNC_ATTR_NONNULL_ARG(1, 3, 5, 6, 7)
{
- int save_reginput_col = (int)(reginput - regline);
- int save_reglnum = reglnum;
- int save_nfa_match = nfa_match;
- int save_nfa_listid = nfa_listid;
- save_se_T *save_nfa_endp = nfa_endp;
+ const int save_reginput_col = (int)(rex.input - rex.line);
+ const int save_reglnum = rex.lnum;
+ const int save_nfa_match = nfa_match;
+ const int save_nfa_listid = rex.nfa_listid;
+ save_se_T *const save_nfa_endp = nfa_endp;
save_se_T endpos;
save_se_T *endposp = NULL;
- int result;
- int need_restore = FALSE;
+ int need_restore = false;
if (pim != NULL) {
- /* start at the position where the postponed match was */
- if (REG_MULTI)
- reginput = regline + pim->end.pos.col;
- else
- reginput = pim->end.ptr;
+ // start at the position where the postponed match was
+ if (REG_MULTI) {
+ rex.input = rex.line + pim->end.pos.col;
+ } else {
+ rex.input = pim->end.ptr;
+ }
}
if (state->c == NFA_START_INVISIBLE_BEFORE
|| state->c == NFA_START_INVISIBLE_BEFORE_FIRST
|| state->c == NFA_START_INVISIBLE_BEFORE_NEG
|| state->c == NFA_START_INVISIBLE_BEFORE_NEG_FIRST) {
- /* The recursive match must end at the current position. When "pim" is
- * not NULL it specifies the current position. */
+ // The recursive match must end at the current position. When "pim" is
+ // not NULL it specifies the current position.
endposp = &endpos;
if (REG_MULTI) {
if (pim == NULL) {
- endpos.se_u.pos.col = (int)(reginput - regline);
- endpos.se_u.pos.lnum = reglnum;
- } else
+ endpos.se_u.pos.col = (int)(rex.input - rex.line);
+ endpos.se_u.pos.lnum = rex.lnum;
+ } else {
endpos.se_u.pos = pim->end.pos;
+ }
} else {
- if (pim == NULL)
- endpos.se_u.ptr = reginput;
- else
+ if (pim == NULL) {
+ endpos.se_u.ptr = rex.input;
+ } else {
endpos.se_u.ptr = pim->end.ptr;
+ }
}
- /* Go back the specified number of bytes, or as far as the
- * start of the previous line, to try matching "\@<=" or
- * not matching "\@<!". This is very inefficient, limit the number of
- * bytes if possible. */
+ // Go back the specified number of bytes, or as far as the
+ // start of the previous line, to try matching "\@<=" or
+ // not matching "\@<!". This is very inefficient, limit the number of
+ // bytes if possible.
if (state->val <= 0) {
if (REG_MULTI) {
- regline = reg_getline(--reglnum);
- if (regline == NULL)
- /* can't go before the first line */
- regline = reg_getline(++reglnum);
+ rex.line = reg_getline(--rex.lnum);
+ if (rex.line == NULL) {
+ // can't go before the first line
+ rex.line = reg_getline(++rex.lnum);
+ }
}
- reginput = regline;
+ rex.input = rex.line;
} else {
- if (REG_MULTI && (int)(reginput - regline) < state->val) {
- /* Not enough bytes in this line, go to end of
- * previous line. */
- regline = reg_getline(--reglnum);
- if (regline == NULL) {
- /* can't go before the first line */
- regline = reg_getline(++reglnum);
- reginput = regline;
- } else
- reginput = regline + STRLEN(regline);
+ if (REG_MULTI && (int)(rex.input - rex.line) < state->val) {
+ // Not enough bytes in this line, go to end of
+ // previous line.
+ rex.line = reg_getline(--rex.lnum);
+ if (rex.line == NULL) {
+ // can't go before the first line
+ rex.line = reg_getline(++rex.lnum);
+ rex.input = rex.line;
+ } else {
+ rex.input = rex.line + STRLEN(rex.line);
+ }
}
- if ((int)(reginput - regline) >= state->val) {
- reginput -= state->val;
- reginput -= utf_head_off(regline, reginput);
+ if ((int)(rex.input - rex.line) >= state->val) {
+ rex.input -= state->val;
+ rex.input -= utf_head_off(rex.line, rex.input);
} else {
- reginput = regline;
+ rex.input = rex.line;
}
}
}
@@ -4706,48 +4729,50 @@ static int recursive_regmatch(
fclose(log_fd);
log_fd = NULL;
#endif
- /* Have to clear the lastlist field of the NFA nodes, so that
- * nfa_regmatch() and addstate() can run properly after recursion. */
+ // Have to clear the lastlist field of the NFA nodes, so that
+ // nfa_regmatch() and addstate() can run properly after recursion.
if (nfa_ll_index == 1) {
- /* Already calling nfa_regmatch() recursively. Save the lastlist[1]
- * values and clear them. */
- if (*listids == NULL || *listids_len < nstate) {
+ // Already calling nfa_regmatch() recursively. Save the lastlist[1]
+ // values and clear them.
+ if (*listids == NULL || *listids_len < prog->nstate) {
xfree(*listids);
- *listids = xmalloc(sizeof(**listids) * nstate);
- *listids_len = nstate;
+ *listids = xmalloc(sizeof(**listids) * prog->nstate);
+ *listids_len = prog->nstate;
}
nfa_save_listids(prog, *listids);
- need_restore = TRUE;
- /* any value of nfa_listid will do */
+ need_restore = true;
+ // any value of rex.nfa_listid will do
} else {
- /* First recursive nfa_regmatch() call, switch to the second lastlist
- * entry. Make sure nfa_listid is different from a previous recursive
- * call, because some states may still have this ID. */
- ++nfa_ll_index;
- if (nfa_listid <= nfa_alt_listid)
- nfa_listid = nfa_alt_listid;
+ // First recursive nfa_regmatch() call, switch to the second lastlist
+ // entry. Make sure rex.nfa_listid is different from a previous
+ // recursive call, because some states may still have this ID.
+ nfa_ll_index++;
+ if (rex.nfa_listid <= rex.nfa_alt_listid) {
+ rex.nfa_listid = rex.nfa_alt_listid;
+ }
}
- /* Call nfa_regmatch() to check if the current concat matches at this
- * position. The concat ends with the node NFA_END_INVISIBLE */
+ // Call nfa_regmatch() to check if the current concat matches at this
+ // position. The concat ends with the node NFA_END_INVISIBLE
nfa_endp = endposp;
- result = nfa_regmatch(prog, state->out, submatch, m);
+ const int result = nfa_regmatch(prog, state->out, submatch, m);
- if (need_restore)
+ if (need_restore) {
nfa_restore_listids(prog, *listids);
- else {
- --nfa_ll_index;
- nfa_alt_listid = nfa_listid;
+ } else {
+ nfa_ll_index--;
+ rex.nfa_alt_listid = rex.nfa_listid;
}
- /* restore position in input text */
- reglnum = save_reglnum;
- if (REG_MULTI)
- regline = reg_getline(reglnum);
- reginput = regline + save_reginput_col;
+ // restore position in input text
+ rex.lnum = save_reglnum;
+ if (REG_MULTI) {
+ rex.line = reg_getline(rex.lnum);
+ }
+ rex.input = rex.line + save_reginput_col;
if (result != NFA_TOO_EXPENSIVE) {
nfa_match = save_nfa_match;
- nfa_listid = save_nfa_listid;
+ rex.nfa_listid = save_nfa_listid;
}
nfa_endp = save_nfa_endp;
@@ -4756,7 +4781,7 @@ static int recursive_regmatch(
if (log_fd != NULL) {
fprintf(log_fd, "****************************\n");
fprintf(log_fd, "FINISHED RUNNING nfa_regmatch() recursively\n");
- fprintf(log_fd, "MATCH = %s\n", !result ? "FALSE" : "OK");
+ fprintf(log_fd, "MATCH = %s\n", !result ? "false" : "OK");
fprintf(log_fd, "****************************\n");
} else {
EMSG(_(e_log_open_failed));
@@ -4930,11 +4955,11 @@ static int failure_chance(nfa_state_T *state, int depth)
*/
static int skip_to_start(int c, colnr_T *colp)
{
- const char_u *const s = cstrchr(regline + *colp, c);
+ const char_u *const s = cstrchr(rex.line + *colp, c);
if (s == NULL) {
return FAIL;
}
- *colp = (int)(s - regline);
+ *colp = (int)(s - rex.line);
return OK;
}
@@ -4948,12 +4973,12 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text)
#define PTR2LEN(x) utf_ptr2len(x)
colnr_T col = startcol;
- int regstart_len = PTR2LEN(regline + startcol);
+ int regstart_len = PTR2LEN(rex.line + startcol);
for (;;) {
bool match = true;
char_u *s1 = match_text;
- char_u *s2 = regline + col + regstart_len; // skip regstart
+ char_u *s2 = rex.line + col + regstart_len; // skip regstart
while (*s1) {
int c1_len = PTR2LEN(s1);
int c1 = PTR2CHAR(s1);
@@ -4973,12 +4998,12 @@ static long find_match_text(colnr_T startcol, int regstart, char_u *match_text)
&& !(enc_utf8 && utf_iscomposing(PTR2CHAR(s2)))) {
cleanup_subexpr();
if (REG_MULTI) {
- rex.reg_startpos[0].lnum = reglnum;
+ rex.reg_startpos[0].lnum = rex.lnum;
rex.reg_startpos[0].col = col;
- rex.reg_endpos[0].lnum = reglnum;
- rex.reg_endpos[0].col = s2 - regline;
+ rex.reg_endpos[0].lnum = rex.lnum;
+ rex.reg_endpos[0].col = s2 - rex.line;
} else {
- rex.reg_startp[0] = regline + col;
+ rex.reg_startp[0] = rex.line + col;
rex.reg_endp[0] = s2;
}
return 1L;
@@ -5008,17 +5033,18 @@ static int nfa_did_time_out(void)
/// Main matching routine.
///
-/// Run NFA to determine whether it matches reginput.
+/// Run NFA to determine whether it matches rex.input.
///
/// When "nfa_endp" is not NULL it is a required end-of-match position.
///
-/// Return TRUE if there is a match, FALSE if there is no match,
+/// Return true if there is a match, false if there is no match,
/// NFA_TOO_EXPENSIVE if we end up with too many states.
/// When there is a match "submatch" contains the positions.
///
/// Note: Caller must ensure that: start != NULL.
static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
regsubs_T *submatch, regsubs_T *m)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 4)
{
int result = false;
int flag = 0;
@@ -5063,11 +5089,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
nfa_match = false;
// Allocate memory for the lists of nodes.
- size_t size = (nstate + 1) * sizeof(nfa_thread_T);
+ size_t size = (prog->nstate + 1) * sizeof(nfa_thread_T);
list[0].t = xmalloc(size);
- list[0].len = nstate + 1;
+ list[0].len = prog->nstate + 1;
list[1].t = xmalloc(size);
- list[1].len = nstate + 1;
+ list[1].len = prog->nstate + 1;
#ifdef REGEXP_DEBUG
log_fd = fopen(NFA_REGEXP_RUN_LOG, "a");
@@ -5085,23 +5111,24 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
thislist = &list[0];
thislist->n = 0;
- thislist->has_pim = FALSE;
+ thislist->has_pim = false;
nextlist = &list[1];
nextlist->n = 0;
- nextlist->has_pim = FALSE;
+ nextlist->has_pim = false;
#ifdef REGEXP_DEBUG
fprintf(log_fd, "(---) STARTSTATE first\n");
#endif
- thislist->id = nfa_listid + 1;
+ thislist->id = rex.nfa_listid + 1;
- /* Inline optimized code for addstate(thislist, start, m, 0) if we know
- * it's the first MOPEN. */
+ // Inline optimized code for addstate(thislist, start, m, 0) if we know
+ // it's the first MOPEN.
if (toplevel) {
if (REG_MULTI) {
- m->norm.list.multi[0].start_lnum = reglnum;
- m->norm.list.multi[0].start_col = (colnr_T)(reginput - regline);
- } else
- m->norm.list.line[0].start = reginput;
+ m->norm.list.multi[0].start_lnum = rex.lnum;
+ m->norm.list.multi[0].start_col = (colnr_T)(rex.input - rex.line);
+ } else {
+ m->norm.list.line[0].start = rex.input;
+ }
m->norm.in_use = 1;
r = addstate(thislist, start->out, m, NULL, 0);
} else {
@@ -5122,8 +5149,8 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
* Run for each character.
*/
for (;; ) {
- int curc = utf_ptr2char(reginput);
- int clen = utfc_ptr2len(reginput);
+ int curc = utf_ptr2char(rex.input);
+ int clen = utfc_ptr2len(rex.input);
if (curc == NUL) {
clen = 0;
go_to_nextline = false;
@@ -5134,20 +5161,20 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
nextlist = &list[flag ^= 1];
nextlist->n = 0; // clear nextlist
nextlist->has_pim = false;
- nfa_listid++;
+ rex.nfa_listid++;
if (prog->re_engine == AUTOMATIC_ENGINE
- && (nfa_listid >= NFA_MAX_STATES)) {
+ && (rex.nfa_listid >= NFA_MAX_STATES)) {
// Too many states, retry with old engine.
nfa_match = NFA_TOO_EXPENSIVE;
goto theend;
}
- thislist->id = nfa_listid;
- nextlist->id = nfa_listid + 1;
+ thislist->id = rex.nfa_listid;
+ nextlist->id = rex.nfa_listid + 1;
#ifdef REGEXP_DEBUG
fprintf(log_fd, "------------------------------------------\n");
- fprintf(log_fd, ">>> Reginput is \"%s\"\n", reginput);
+ fprintf(log_fd, ">>> Reginput is \"%s\"\n", rex.input);
fprintf(log_fd,
">>> Advanced one character... Current char is %c (code %d) \n",
curc,
@@ -5200,7 +5227,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
} else if (REG_MULTI) {
col = t->subs.norm.list.multi[0].start_col;
} else {
- col = (int)(t->subs.norm.list.line[0].start - regline);
+ col = (int)(t->subs.norm.list.line[0].start - rex.line);
}
nfa_set_code(t->state->c);
fprintf(log_fd, "(%d) char %d %s (start col %d)%s... \n",
@@ -5226,64 +5253,66 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
}
nfa_match = true;
copy_sub(&submatch->norm, &t->subs.norm);
- if (nfa_has_zsubexpr)
+ if (rex.nfa_has_zsubexpr) {
copy_sub(&submatch->synt, &t->subs.synt);
+ }
#ifdef REGEXP_DEBUG
log_subsexpr(&t->subs);
#endif
- /* Found the left-most longest match, do not look at any other
- * states at this position. When the list of states is going
- * to be empty quit without advancing, so that "reginput" is
- * correct. */
- if (nextlist->n == 0)
+ // Found the left-most longest match, do not look at any other
+ // states at this position. When the list of states is going
+ // to be empty quit without advancing, so that "rex.input" is
+ // correct.
+ if (nextlist->n == 0) {
clen = 0;
+ }
goto nextchar;
}
case NFA_END_INVISIBLE:
case NFA_END_INVISIBLE_NEG:
case NFA_END_PATTERN:
- /*
- * This is only encountered after a NFA_START_INVISIBLE or
- * NFA_START_INVISIBLE_BEFORE node.
- * They surround a zero-width group, used with "\@=", "\&",
- * "\@!", "\@<=" and "\@<!".
- * If we got here, it means that the current "invisible" group
- * finished successfully, so return control to the parent
- * nfa_regmatch(). For a look-behind match only when it ends
- * in the position in "nfa_endp".
- * Submatches are stored in *m, and used in the parent call.
- */
+ // This is only encountered after a NFA_START_INVISIBLE or
+ // NFA_START_INVISIBLE_BEFORE node.
+ // They surround a zero-width group, used with "\@=", "\&",
+ // "\@!", "\@<=" and "\@<!".
+ // If we got here, it means that the current "invisible" group
+ // finished successfully, so return control to the parent
+ // nfa_regmatch(). For a look-behind match only when it ends
+ // in the position in "nfa_endp".
+ // Submatches are stored in *m, and used in the parent call.
#ifdef REGEXP_DEBUG
if (nfa_endp != NULL) {
- if (REG_MULTI)
- fprintf(
- log_fd,
- "Current lnum: %d, endp lnum: %d; current col: %d, endp col: %d\n",
- (int)reglnum,
- (int)nfa_endp->se_u.pos.lnum,
- (int)(reginput - regline),
- nfa_endp->se_u.pos.col);
- else
+ if (REG_MULTI) {
+ fprintf(log_fd,
+ "Current lnum: %d, endp lnum: %d;"
+ " current col: %d, endp col: %d\n",
+ (int)rex.lnum,
+ (int)nfa_endp->se_u.pos.lnum,
+ (int)(rex.input - rex.line),
+ nfa_endp->se_u.pos.col);
+ } else {
fprintf(log_fd, "Current col: %d, endp col: %d\n",
- (int)(reginput - regline),
- (int)(nfa_endp->se_u.ptr - reginput));
+ (int)(rex.input - rex.line),
+ (int)(nfa_endp->se_u.ptr - rex.input));
+ }
}
#endif
- /* If "nfa_endp" is set it's only a match if it ends at
- * "nfa_endp" */
- if (nfa_endp != NULL && (REG_MULTI
- ? (reglnum != nfa_endp->se_u.pos.lnum
- || (int)(reginput - regline)
- != nfa_endp->se_u.pos.col)
- : reginput != nfa_endp->se_u.ptr))
+ // If "nfa_endp" is set it's only a match if it ends at
+ // "nfa_endp"
+ if (nfa_endp != NULL
+ && (REG_MULTI
+ ? (rex.lnum != nfa_endp->se_u.pos.lnum
+ || (int)(rex.input - rex.line) != nfa_endp->se_u.pos.col)
+ : rex.input != nfa_endp->se_u.ptr)) {
break;
-
- /* do not set submatches for \@! */
+ }
+ // do not set submatches for \@!
if (t->state->c != NFA_END_INVISIBLE_NEG) {
copy_sub(&m->norm, &t->subs.norm);
- if (nfa_has_zsubexpr)
+ if (rex.nfa_has_zsubexpr) {
copy_sub(&m->synt, &t->subs.synt);
+ }
}
#ifdef REGEXP_DEBUG
fprintf(log_fd, "Match found:\n");
@@ -5322,9 +5351,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
// Copy submatch info for the recursive call, opposite
// of what happens on success below.
copy_sub_off(&m->norm, &t->subs.norm);
- if (nfa_has_zsubexpr)
+ if (rex.nfa_has_zsubexpr) {
copy_sub_off(&m->synt, &t->subs.synt);
-
+ }
// First try matching the invisible match, then what
// follows.
result = recursive_regmatch(t->state, NULL, prog, submatch, m,
@@ -5335,7 +5364,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
}
// for \@! and \@<! it is a match when the result is
- // FALSE
+ // false
if (result != (t->state->c == NFA_START_INVISIBLE_NEG
|| t->state->c == NFA_START_INVISIBLE_NEG_FIRST
|| t->state->c
@@ -5344,8 +5373,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
== NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
// Copy submatch info from the recursive call
copy_sub_off(&t->subs.norm, &m->norm);
- if (nfa_has_zsubexpr)
+ if (rex.nfa_has_zsubexpr) {
copy_sub_off(&t->subs.synt, &m->synt);
+ }
// If the pattern has \ze and it matched in the
// sub pattern, use it.
copy_ze_off(&t->subs.norm, &m->norm);
@@ -5369,11 +5399,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
pim.subs.norm.in_use = 0;
pim.subs.synt.in_use = 0;
if (REG_MULTI) {
- pim.end.pos.col = (int)(reginput - regline);
- pim.end.pos.lnum = reglnum;
- } else
- pim.end.ptr = reginput;
-
+ pim.end.pos.col = (int)(rex.input - rex.line);
+ pim.end.pos.lnum = rex.lnum;
+ } else {
+ pim.end.ptr = rex.input;
+ }
// t->state->out1 is the corresponding END_INVISIBLE
// node; Add its out to the current list (zero-width
// match).
@@ -5426,7 +5456,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
// Copy submatch info to the recursive call, opposite of what
// happens afterwards.
copy_sub_off(&m->norm, &t->subs.norm);
- if (nfa_has_zsubexpr) {
+ if (rex.nfa_has_zsubexpr) {
copy_sub_off(&m->synt, &t->subs.synt);
}
@@ -5446,7 +5476,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
#endif
// Copy submatch info from the recursive call
copy_sub_off(&t->subs.norm, &m->norm);
- if (nfa_has_zsubexpr) {
+ if (rex.nfa_has_zsubexpr) {
copy_sub_off(&t->subs.synt, &m->synt);
}
// Now we need to skip over the matched text and then
@@ -5454,9 +5484,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
if (REG_MULTI) {
// TODO(RE): multi-line match
bytelen = m->norm.list.multi[0].end_col
- - (int)(reginput - regline);
+ - (int)(rex.input - rex.line);
} else {
- bytelen = (int)(m->norm.list.line[0].end - reginput);
+ bytelen = (int)(m->norm.list.line[0].end - rex.input);
}
#ifdef REGEXP_DEBUG
@@ -5485,7 +5515,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
}
case NFA_BOL:
- if (reginput == regline) {
+ if (rex.input == rex.line) {
add_here = true;
add_state = t->state->out;
}
@@ -5503,20 +5533,16 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
if (curc == NUL) {
result = false;
- } else if (has_mbyte) {
+ } else {
int this_class;
// Get class of current and previous char (if it exists).
- this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab);
+ this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab);
if (this_class <= 1) {
result = false;
} else if (reg_prev_class() == this_class) {
result = false;
}
- } else if (!vim_iswordc_buf(curc, rex.reg_buf)
- || (reginput > regline
- && vim_iswordc_buf(reginput[-1], rex.reg_buf))) {
- result = false;
}
if (result) {
add_here = true;
@@ -5526,22 +5552,18 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
case NFA_EOW:
result = true;
- if (reginput == regline) {
+ if (rex.input == rex.line) {
result = false;
- } else if (has_mbyte) {
+ } else {
int this_class, prev_class;
// Get class of current and previous char (if it exists).
- this_class = mb_get_class_tab(reginput, rex.reg_buf->b_chartab);
+ this_class = mb_get_class_tab(rex.input, rex.reg_buf->b_chartab);
prev_class = reg_prev_class();
if (this_class == prev_class
|| prev_class == 0 || prev_class == 1) {
result = false;
}
- } else if (!vim_iswordc_buf(reginput[-1], rex.reg_buf)
- || (reginput[0] != NUL
- && vim_iswordc_buf(curc, rex.reg_buf))) {
- result = false;
}
if (result) {
add_here = true;
@@ -5550,7 +5572,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
break;
case NFA_BOF:
- if (reglnum == 0 && reginput == regline
+ if (rex.lnum == 0 && rex.input == rex.line
&& (!REG_MULTI || rex.reg_firstlnum == 1)) {
add_here = true;
add_state = t->state->out;
@@ -5558,7 +5580,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
break;
case NFA_EOF:
- if (reglnum == rex.reg_maxline && curc == NUL) {
+ if (rex.lnum == rex.reg_maxline && curc == NUL) {
add_here = true;
add_state = t->state->out;
}
@@ -5603,7 +5625,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
// We don't care about the order of composing characters.
// Get them into cchars[] first.
while (len < clen) {
- mc = utf_ptr2char(reginput + len);
+ mc = utf_ptr2char(rex.input + len);
cchars[ccount++] = mc;
len += mb_char2len(mc);
if (ccount == MAX_MCO)
@@ -5634,7 +5656,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
case NFA_NEWL:
if (curc == NUL && !rex.reg_line_lbr && REG_MULTI
- && reglnum <= rex.reg_maxline) {
+ && rex.lnum <= rex.reg_maxline) {
go_to_nextline = true;
// Pass -1 for the offset, which means taking the position
// at the start of the next line.
@@ -5688,7 +5710,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
for (; c1 <= c2; c1++) {
if (utf_fold(c1) == curc_low) {
result = result_if_matched;
- done = TRUE;
+ done = true;
break;
}
}
@@ -5746,13 +5768,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
break;
case NFA_KWORD: // \k
- result = vim_iswordp_buf(reginput, rex.reg_buf);
+ result = vim_iswordp_buf(rex.input, rex.reg_buf);
ADD_STATE_IF_MATCH(t->state);
break;
case NFA_SKWORD: // \K
result = !ascii_isdigit(curc)
- && vim_iswordp_buf(reginput, rex.reg_buf);
+ && vim_iswordp_buf(rex.input, rex.reg_buf);
ADD_STATE_IF_MATCH(t->state);
break;
@@ -5767,12 +5789,12 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
break;
case NFA_PRINT: // \p
- result = vim_isprintc(PTR2CHAR(reginput));
+ result = vim_isprintc(PTR2CHAR(rex.input));
ADD_STATE_IF_MATCH(t->state);
break;
case NFA_SPRINT: // \P
- result = !ascii_isdigit(curc) && vim_isprintc(PTR2CHAR(reginput));
+ result = !ascii_isdigit(curc) && vim_isprintc(PTR2CHAR(rex.input));
ADD_STATE_IF_MATCH(t->state);
break;
@@ -5959,14 +5981,14 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
case NFA_LNUM_LT:
assert(t->state->val >= 0
&& !((rex.reg_firstlnum > 0
- && reglnum > LONG_MAX - rex.reg_firstlnum)
+ && rex.lnum > LONG_MAX - rex.reg_firstlnum)
|| (rex.reg_firstlnum < 0
- && reglnum < LONG_MIN + rex.reg_firstlnum))
- && reglnum + rex.reg_firstlnum >= 0);
+ && rex.lnum < LONG_MIN + rex.reg_firstlnum))
+ && rex.lnum + rex.reg_firstlnum >= 0);
result = (REG_MULTI
&& nfa_re_num_cmp((uintmax_t)t->state->val,
t->state->c - NFA_LNUM,
- (uintmax_t)(reglnum + rex.reg_firstlnum)));
+ (uintmax_t)(rex.lnum + rex.reg_firstlnum)));
if (result) {
add_here = true;
add_state = t->state->out;
@@ -5977,11 +5999,11 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
case NFA_COL_GT:
case NFA_COL_LT:
assert(t->state->val >= 0
- && reginput >= regline
- && (uintmax_t)(reginput - regline) <= UINTMAX_MAX - 1);
+ && rex.input >= rex.line
+ && (uintmax_t)(rex.input - rex.line) <= UINTMAX_MAX - 1);
result = nfa_re_num_cmp((uintmax_t)t->state->val,
t->state->c - NFA_COL,
- (uintmax_t)(reginput - regline + 1));
+ (uintmax_t)(rex.input - rex.line + 1));
if (result) {
add_here = true;
add_state = t->state->out;
@@ -5993,7 +6015,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
case NFA_VCOL_LT:
{
int op = t->state->c - NFA_VCOL;
- colnr_T col = (colnr_T)(reginput - regline);
+ colnr_T col = (colnr_T)(rex.input - rex.line);
// Bail out quickly when there can't be a match, avoid the overhead of
// win_linetabsize() on long lines.
@@ -6014,7 +6036,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
result = col > t->state->val * ts;
}
if (!result) {
- uintmax_t lts = win_linetabsize(wp, regline, col);
+ uintmax_t lts = win_linetabsize(wp, rex.line, col);
assert(t->state->val >= 0);
result = nfa_re_num_cmp((uintmax_t)t->state->val, op, lts + 1);
}
@@ -6034,13 +6056,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
// Compare the mark position to the match position.
result = (pos != NULL // mark doesn't exist
&& pos->lnum > 0 // mark isn't set in reg_buf
- && (pos->lnum == reglnum + rex.reg_firstlnum
- ? (pos->col == (colnr_T)(reginput - regline)
+ && (pos->lnum == rex.lnum + rex.reg_firstlnum
+ ? (pos->col == (colnr_T)(rex.input - rex.line)
? t->state->c == NFA_MARK
- : (pos->col < (colnr_T)(reginput - regline)
+ : (pos->col < (colnr_T)(rex.input - rex.line)
? t->state->c == NFA_MARK_GT
: t->state->c == NFA_MARK_LT))
- : (pos->lnum < reglnum + rex.reg_firstlnum
+ : (pos->lnum < rex.lnum + rex.reg_firstlnum
? t->state->c == NFA_MARK_GT
: t->state->c == NFA_MARK_LT)));
if (result) {
@@ -6051,10 +6073,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
}
case NFA_CURSOR:
- result = (rex.reg_win != NULL
- && (reglnum + rex.reg_firstlnum == rex.reg_win->w_cursor.lnum)
- && ((colnr_T)(reginput - regline)
- == rex.reg_win->w_cursor.col));
+ result = rex.reg_win != NULL
+ && (rex.lnum + rex.reg_firstlnum == rex.reg_win->w_cursor.lnum)
+ && ((colnr_T)(rex.input - rex.line) == rex.reg_win->w_cursor.col);
if (result) {
add_here = true;
add_state = t->state->out;
@@ -6112,7 +6133,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
// If rex.reg_icombine is not set only skip over the character
// itself. When it is set skip over composing characters.
if (result && enc_utf8 && !rex.reg_icombine) {
- clen = utf_ptr2len(reginput);
+ clen = utf_ptr2len(rex.input);
}
ADD_STATE_IF_MATCH(t->state);
@@ -6143,7 +6164,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
&listids, &listids_len);
pim->result = result ? NFA_PIM_MATCH : NFA_PIM_NOMATCH;
// for \@! and \@<! it is a match when the result is
- // FALSE
+ // false
if (result != (pim->state->c == NFA_START_INVISIBLE_NEG
|| pim->state->c == NFA_START_INVISIBLE_NEG_FIRST
|| pim->state->c
@@ -6152,8 +6173,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
== NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
// Copy submatch info from the recursive call
copy_sub_off(&pim->subs.norm, &m->norm);
- if (nfa_has_zsubexpr)
+ if (rex.nfa_has_zsubexpr) {
copy_sub_off(&pim->subs.synt, &m->synt);
+ }
}
} else {
result = (pim->result == NFA_PIM_MATCH);
@@ -6163,12 +6185,12 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
log_fd,
"Using previous recursive nfa_regmatch() result, result == %d\n",
pim->result);
- fprintf(log_fd, "MATCH = %s\n", result ? "OK" : "FALSE");
+ fprintf(log_fd, "MATCH = %s\n", result ? "OK" : "false");
fprintf(log_fd, "\n");
#endif
}
- // for \@! and \@<! it is a match when result is FALSE
+ // for \@! and \@<! it is a match when result is false
if (result != (pim->state->c == NFA_START_INVISIBLE_NEG
|| pim->state->c == NFA_START_INVISIBLE_NEG_FIRST
|| pim->state->c
@@ -6177,8 +6199,9 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
== NFA_START_INVISIBLE_BEFORE_NEG_FIRST)) {
// Copy submatch info from the recursive call
copy_sub_off(&t->subs.norm, &pim->subs.norm);
- if (nfa_has_zsubexpr)
+ if (rex.nfa_has_zsubexpr) {
copy_sub_off(&t->subs.synt, &pim->subs.synt);
+ }
} else {
// look-behind match failed, don't add the state
continue;
@@ -6222,29 +6245,28 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
// Also don't start a match past the first line.
if (!nfa_match
&& ((toplevel
- && reglnum == 0
+ && rex.lnum == 0
&& clen != 0
&& (rex.reg_maxcol == 0
- || (colnr_T)(reginput - regline) < rex.reg_maxcol))
+ || (colnr_T)(rex.input - rex.line) < rex.reg_maxcol))
|| (nfa_endp != NULL
&& (REG_MULTI
- ? (reglnum < nfa_endp->se_u.pos.lnum
- || (reglnum == nfa_endp->se_u.pos.lnum
- && (int)(reginput - regline)
+ ? (rex.lnum < nfa_endp->se_u.pos.lnum
+ || (rex.lnum == nfa_endp->se_u.pos.lnum
+ && (int)(rex.input - rex.line)
< nfa_endp->se_u.pos.col))
- : reginput < nfa_endp->se_u.ptr)))) {
+ : rex.input < nfa_endp->se_u.ptr)))) {
#ifdef REGEXP_DEBUG
fprintf(log_fd, "(---) STARTSTATE\n");
#endif
// Inline optimized code for addstate() if we know the state is
// the first MOPEN.
if (toplevel) {
- int add = TRUE;
- int c;
+ int add = true;
if (prog->regstart != NUL && clen != 0) {
if (nextlist->n == 0) {
- colnr_T col = (colnr_T)(reginput - regline) + clen;
+ colnr_T col = (colnr_T)(rex.input - rex.line) + clen;
// Nextlist is empty, we can skip ahead to the
// character that must appear at the start.
@@ -6253,13 +6275,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
}
#ifdef REGEXP_DEBUG
fprintf(log_fd, " Skipping ahead %d bytes to regstart\n",
- col - ((colnr_T)(reginput - regline) + clen));
+ col - ((colnr_T)(rex.input - rex.line) + clen));
#endif
- reginput = regline + col - clen;
+ rex.input = rex.line + col - clen;
} else {
// Checking if the required start character matches is
// cheaper than adding a state that won't match.
- c = PTR2CHAR(reginput + clen);
+ const int c = PTR2CHAR(rex.input + clen);
if (c != prog->regstart
&& (!rex.reg_ic
|| utf_fold(c) != utf_fold(prog->regstart))) {
@@ -6267,17 +6289,18 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start,
fprintf(log_fd,
" Skipping start state, regstart does not match\n");
#endif
- add = FALSE;
+ add = false;
}
}
}
if (add) {
- if (REG_MULTI)
+ if (REG_MULTI) {
m->norm.list.multi[0].start_col =
- (colnr_T)(reginput - regline) + clen;
- else
- m->norm.list.line[0].start = reginput + clen;
+ (colnr_T)(rex.input - rex.line) + clen;
+ } else {
+ m->norm.list.line[0].start = rex.input + clen;
+ }
if (addstate(nextlist, start->out, m, NULL, clen) == NULL) {
nfa_match = NFA_TOO_EXPENSIVE;
goto theend;
@@ -6306,9 +6329,9 @@ nextchar:
// Advance to the next character, or advance to the next line, or
// finish.
if (clen != 0) {
- reginput += clen;
+ rex.input += clen;
} else if (go_to_nextline || (nfa_endp != NULL && REG_MULTI
- && reglnum < nfa_endp->se_u.pos.lnum)) {
+ && rex.lnum < nfa_endp->se_u.pos.lnum)) {
reg_nextline();
} else {
break;
@@ -6347,7 +6370,7 @@ theend:
return nfa_match;
}
-// Try match of "prog" with at regline["col"].
+// Try match of "prog" with at rex.line["col"].
// Returns <= 0 for failure, number of lines contained in the match otherwise.
static long nfa_regtry(nfa_regprog_T *prog,
colnr_T col,
@@ -6361,7 +6384,7 @@ static long nfa_regtry(nfa_regprog_T *prog,
FILE *f;
#endif
- reginput = regline + col;
+ rex.input = rex.line + col;
nfa_time_limit = tm;
nfa_timed_out = timed_out;
nfa_time_count = 0;
@@ -6374,7 +6397,7 @@ static long nfa_regtry(nfa_regprog_T *prog,
#ifdef REGEXP_DEBUG
fprintf(f, "\tRegexp is \"%s\"\n", nfa_regengine.expr);
#endif
- fprintf(f, "\tInput text is \"%s\" \n", reginput);
+ fprintf(f, "\tInput text is \"%s\" \n", rex.input);
fprintf(f, "\t=======================================================\n\n");
nfa_print_state(f, start);
fprintf(f, "\n\n");
@@ -6412,11 +6435,11 @@ static long nfa_regtry(nfa_regprog_T *prog,
}
if (rex.reg_endpos[0].lnum < 0) {
// pattern has a \ze but it didn't match, use current end
- rex.reg_endpos[0].lnum = reglnum;
- rex.reg_endpos[0].col = (int)(reginput - regline);
+ rex.reg_endpos[0].lnum = rex.lnum;
+ rex.reg_endpos[0].col = (int)(rex.input - rex.line);
} else {
// Use line number of "\ze".
- reglnum = rex.reg_endpos[0].lnum;
+ rex.lnum = rex.reg_endpos[0].lnum;
}
} else {
for (i = 0; i < subs.norm.in_use; i++) {
@@ -6425,10 +6448,10 @@ static long nfa_regtry(nfa_regprog_T *prog,
}
if (rex.reg_startp[0] == NULL) {
- rex.reg_startp[0] = regline + col;
+ rex.reg_startp[0] = rex.line + col;
}
if (rex.reg_endp[0] == NULL) {
- rex.reg_endp[0] = reginput;
+ rex.reg_endp[0] = rex.input;
}
}
@@ -6463,7 +6486,7 @@ static long nfa_regtry(nfa_regprog_T *prog,
}
}
- return 1 + reglnum;
+ return 1 + rex.lnum;
}
/// Match a regexp against a string ("line" points to the string) or multiple
@@ -6481,7 +6504,6 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol,
{
nfa_regprog_T *prog;
long retval = 0L;
- int i;
colnr_T col = startcol;
if (REG_MULTI) {
@@ -6497,7 +6519,7 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol,
/* Be paranoid... */
if (prog == NULL || line == NULL) {
- EMSG(_(e_null));
+ IEMSG(_(e_null));
goto theend;
}
@@ -6513,26 +6535,30 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol,
rex.reg_icombine = true;
}
- regline = line;
- reglnum = 0; /* relative to line */
+ rex.line = line;
+ rex.lnum = 0; // relative to line
- nfa_has_zend = prog->has_zend;
- nfa_has_backref = prog->has_backref;
- nfa_nsubexpr = prog->nsubexp;
- nfa_listid = 1;
- nfa_alt_listid = 2;
+ rex.nfa_has_zend = prog->has_zend;
+ rex.nfa_has_backref = prog->has_backref;
+ rex.nfa_nsubexpr = prog->nsubexp;
+ rex.nfa_listid = 1;
+ rex.nfa_alt_listid = 2;
+#ifdef REGEXP_DEBUG
nfa_regengine.expr = prog->pattern;
+#endif
if (prog->reganch && col > 0)
return 0L;
- need_clear_subexpr = TRUE;
- /* Clear the external match subpointers if necessary. */
+ rex.need_clear_subexpr = true;
+ // Clear the external match subpointers if necessary.
if (prog->reghasz == REX_SET) {
- nfa_has_zsubexpr = TRUE;
- need_clear_zsubexpr = TRUE;
- } else
- nfa_has_zsubexpr = FALSE;
+ rex.nfa_has_zsubexpr = true;
+ rex.need_clear_zsubexpr = true;
+ } else {
+ rex.nfa_has_zsubexpr = false;
+ rex.need_clear_zsubexpr = false;
+ }
if (prog->regstart != NUL) {
/* Skip ahead until a character we know the match must start with.
@@ -6552,8 +6578,10 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol,
goto theend;
}
- nstate = prog->nstate;
- for (i = 0; i < nstate; ++i) {
+ // Set the "nstate" used by nfa_regcomp() to zero to trigger an error when
+ // it's accidentally used during execution.
+ nstate = 0;
+ for (int i = 0; i < prog->nstate; i++) {
prog->state[i].id = i;
prog->state[i].lastlist[0] = 0;
prog->state[i].lastlist[1] = 0;
@@ -6561,7 +6589,9 @@ static long nfa_regexec_both(char_u *line, colnr_T startcol,
retval = nfa_regtry(prog, col, tm, timed_out);
+#ifdef REGEXP_DEBUG
nfa_regengine.expr = NULL;
+#endif
theend:
return retval;
@@ -6579,7 +6609,9 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags)
if (expr == NULL)
return NULL;
+#ifdef REGEXP_DEBUG
nfa_regengine.expr = expr;
+#endif
nfa_re_flags = re_flags;
init_class_tab();
@@ -6616,26 +6648,27 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags)
* PASS 1
* Count number of NFA states in "nstate". Do not build the NFA.
*/
- post2nfa(postfix, post_ptr, TRUE);
+ post2nfa(postfix, post_ptr, true);
/* allocate the regprog with space for the compiled regexp */
size_t prog_size = sizeof(nfa_regprog_T) + sizeof(nfa_state_T) * (nstate - 1);
prog = xmalloc(prog_size);
state_ptr = prog->state;
+ prog->re_in_use = false;
/*
* PASS 2
* Build the NFA
*/
- prog->start = post2nfa(postfix, post_ptr, FALSE);
- if (prog->start == NULL)
+ prog->start = post2nfa(postfix, post_ptr, false);
+ if (prog->start == NULL) {
goto fail;
-
+ }
prog->regflags = regflags;
prog->engine = &nfa_regengine;
prog->nstate = nstate;
- prog->has_zend = nfa_has_zend;
- prog->has_backref = nfa_has_backref;
+ prog->has_zend = rex.nfa_has_zend;
+ prog->has_backref = rex.nfa_has_backref;
prog->nsubexp = regnpar;
nfa_postprocess(prog);
@@ -6651,7 +6684,9 @@ static regprog_T *nfa_regcomp(char_u *expr, int re_flags)
/* Remember whether this pattern has any \z specials in it. */
prog->reghasz = re_has_z;
prog->pattern = vim_strsave(expr);
+#ifdef REGEXP_DEBUG
nfa_regengine.expr = NULL;
+#endif
out:
xfree(post_start);
@@ -6663,8 +6698,8 @@ fail:
XFREE_CLEAR(prog);
#ifdef REGEXP_DEBUG
nfa_postfix_dump(expr, FAIL);
-#endif
nfa_regengine.expr = NULL;
+#endif
goto out;
}
diff --git a/src/nvim/screen.c b/src/nvim/screen.c
index 7bed747e9a..8998f9037e 100644
--- a/src/nvim/screen.c
+++ b/src/nvim/screen.c
@@ -25,7 +25,7 @@
//
// Commands that scroll a window change w_topline and must call
// check_cursor() to move the cursor into the visible part of the window, and
-// call redraw_later(VALID) to have the window displayed by update_screen()
+// call redraw_later(wp, VALID) to have the window displayed by update_screen()
// later.
//
// Commands that change text in the buffer must call changed_bytes() or
@@ -37,7 +37,7 @@
//
// Commands that change how a window is displayed (e.g., setting 'list') or
// invalidate the contents of a window in another way (e.g., change fold
-// settings), must call redraw_later(NOT_VALID) to have the whole window
+// settings), must call redraw_later(wp, NOT_VALID) to have the whole window
// redisplayed by update_screen() later.
//
// Commands that change how a buffer is displayed (e.g., setting 'tabstop')
@@ -45,11 +45,11 @@
// buffer redisplayed by update_screen() later.
//
// Commands that change highlighting and possibly cause a scroll too must call
-// redraw_later(SOME_VALID) to update the whole window but still use scrolling
-// to avoid redrawing everything. But the length of displayed lines must not
-// change, use NOT_VALID then.
+// redraw_later(wp, SOME_VALID) to update the whole window but still use
+// scrolling to avoid redrawing everything. But the length of displayed lines
+// must not change, use NOT_VALID then.
//
-// Commands that move the window position must call redraw_later(NOT_VALID).
+// Commands that move the window position must call redraw_later(wp, NOT_VALID).
// TODO(neovim): should minimize redrawing by scrolling when possible.
//
// Commands that change everything (e.g., resizing the screen) must call
@@ -88,6 +88,7 @@
#include "nvim/main.h"
#include "nvim/mark.h"
#include "nvim/extmark.h"
+#include "nvim/decoration.h"
#include "nvim/mbyte.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
@@ -119,10 +120,12 @@
#include "nvim/api/private/helpers.h"
#include "nvim/api/vim.h"
#include "nvim/lua/executor.h"
+#include "nvim/lib/kvec.h"
#define MB_FILLER_CHAR '<' /* character used when a double-width character
* doesn't fit. */
+typedef kvec_withinit_t(DecorProvider *, 4) Providers;
// temporary buffer for rendering a single screenline, so it can be
// compared with previous contents to calculate smallest delta.
@@ -133,8 +136,6 @@ static sattr_T *linebuf_attr = NULL;
static match_T search_hl; /* used for 'hlsearch' highlight matching */
-static foldinfo_T win_foldinfo; /* info for 'foldcolumn' */
-
StlClickDefinition *tab_page_click_defs = NULL;
long tab_page_click_defs_size = 0;
@@ -158,22 +159,48 @@ static bool msg_grid_invalid = false;
static bool resizing = false;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "screen.c.generated.h"
#endif
#define SEARCH_HL_PRIORITY 0
-/*
- * Redraw the current window later, with update_screen(type).
- * Set must_redraw only if not already set to a higher value.
- * e.g. if must_redraw is CLEAR, type NOT_VALID will do nothing.
- */
-void redraw_later(int type)
+static char * provider_first_error = NULL;
+
+static bool provider_invoke(NS ns_id, const char *name, LuaRef ref,
+ Array args, bool default_true)
{
- redraw_win_later(curwin, type);
+ Error err = ERROR_INIT;
+
+ textlock++;
+ Object ret = nlua_call_ref(ref, name, args, true, &err);
+ textlock--;
+
+ if (!ERROR_SET(&err)
+ && api_object_to_bool(ret, "provider %s retval", default_true, &err)) {
+ return true;
+ }
+
+ if (ERROR_SET(&err)) {
+ const char *ns_name = describe_ns(ns_id);
+ ELOG("error in provider %s:%s: %s", ns_name, name, err.msg);
+ bool verbose_errs = true; // TODO(bfredl):
+ if (verbose_errs && provider_first_error == NULL) {
+ static char errbuf[IOSIZE];
+ snprintf(errbuf, sizeof errbuf, "%s: %s", ns_name, err.msg);
+ provider_first_error = xstrdup(errbuf);
+ }
+ }
+
+ api_free_object(ret);
+ return false;
}
-void redraw_win_later(win_T *wp, int type)
+/// Redraw a window later, with update_screen(type).
+///
+/// Set must_redraw only if not already set to a higher value.
+/// e.g. if must_redraw is CLEAR, type NOT_VALID will do nothing.
+void redraw_later(win_T *wp, int type)
FUNC_ATTR_NONNULL_ALL
{
if (!exiting && wp->w_redr_type < type) {
@@ -191,7 +218,7 @@ void redraw_win_later(win_T *wp, int type)
void redraw_all_later(int type)
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- redraw_win_later(wp, type);
+ redraw_later(wp, type);
}
// This may be needed when switching tabs.
if (must_redraw < type) {
@@ -202,7 +229,7 @@ void redraw_all_later(int type)
void screen_invalidate_highlights(void)
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- redraw_win_later(wp, NOT_VALID);
+ redraw_later(wp, NOT_VALID);
wp->w_grid.valid = false;
}
}
@@ -219,7 +246,7 @@ void redraw_buf_later(buf_T *buf, int type)
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_buffer == buf) {
- redraw_win_later(wp, type);
+ redraw_later(wp, type);
}
}
}
@@ -245,7 +272,7 @@ void redraw_buf_range_later(buf_T *buf, linenr_T firstline, linenr_T lastline)
if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lastline) {
wp->w_redraw_bot = lastline;
}
- redraw_win_later(wp, VALID);
+ redraw_later(wp, VALID);
}
}
}
@@ -273,7 +300,7 @@ redrawWinline(
if (wp->w_redraw_bot == 0 || wp->w_redraw_bot < lnum) {
wp->w_redraw_bot = lnum;
}
- redraw_win_later(wp, VALID);
+ redraw_later(wp, VALID);
}
}
@@ -327,7 +354,6 @@ int update_screen(int type)
/* Postpone the redrawing when it's not needed and when being called
* recursively. */
if (!redrawing() || updating_screen) {
- redraw_later(type); /* remember type for next time */
must_redraw = type;
if (type > INVERTED_ALL) {
curwin->w_lines_valid = 0; // don't use w_lines[].wl_size now
@@ -446,6 +472,35 @@ int update_screen(int type)
ui_comp_set_screen_valid(true);
+ Providers providers;
+ kvi_init(providers);
+ for (size_t i = 0; i < kv_size(decor_providers); i++) {
+ DecorProvider *p = &kv_A(decor_providers, i);
+ if (!p->active) {
+ continue;
+ }
+
+ bool active;
+ if (p->redraw_start != LUA_NOREF) {
+ FIXED_TEMP_ARRAY(args, 2);
+ args.items[0] = INTEGER_OBJ(display_tick);
+ args.items[1] = INTEGER_OBJ(type);
+ active = provider_invoke(p->ns_id, "start", p->redraw_start, args, true);
+ } else {
+ active = true;
+ }
+
+ if (active) {
+ kvi_push(providers, p);
+ }
+ }
+
+ // "start" callback could have changed highlights for global elements
+ if (win_check_ns_hl(NULL)) {
+ redraw_cmdline = true;
+ redraw_tabline = true;
+ }
+
if (clear_cmdline) /* going to clear cmdline (done below) */
check_for_delay(FALSE);
@@ -494,30 +549,24 @@ int update_screen(int type)
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
update_window_hl(wp, type >= NOT_VALID);
- if (wp->w_buffer->b_mod_set) {
- win_T *wwp;
-
- // Check if we already did this buffer.
- for (wwp = firstwin; wwp != wp; wwp = wwp->w_next) {
- if (wwp->w_buffer == wp->w_buffer) {
- break;
- }
- }
- if (wwp == wp && syntax_present(wp)) {
- syn_stack_apply_changes(wp->w_buffer);
- }
-
- buf_T *buf = wp->w_buffer;
- if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) {
- Error err = ERROR_INIT;
- FIXED_TEMP_ARRAY(args, 2);
- args.items[0] = BUFFER_OBJ(buf->handle);
- args.items[1] = INTEGER_OBJ(display_tick);
- executor_exec_lua_cb(buf->b_luahl_start, "start", args, false, &err);
- if (ERROR_SET(&err)) {
- ELOG("error in luahl start: %s", err.msg);
- api_clear_error(&err);
+ buf_T *buf = wp->w_buffer;
+ if (buf->b_mod_set) {
+ if (buf->b_mod_tick_syn < display_tick
+ && syntax_present(wp)) {
+ syn_stack_apply_changes(buf);
+ buf->b_mod_tick_syn = display_tick;
+ }
+
+ if (buf->b_mod_tick_decor < display_tick) {
+ for (size_t i = 0; i < kv_size(providers); i++) {
+ DecorProvider *p = kv_A(providers, i);
+ if (p && p->redraw_buf != LUA_NOREF) {
+ FIXED_TEMP_ARRAY(args, 1);
+ args.items[0] = BUFFER_OBJ(buf->handle);
+ provider_invoke(p->ns_id, "buf", p->redraw_buf, args, true);
+ }
}
+ buf->b_mod_tick_decor = display_tick;
}
}
}
@@ -541,7 +590,7 @@ int update_screen(int type)
did_one = TRUE;
start_search_hl();
}
- win_update(wp);
+ win_update(wp, &providers);
}
/* redraw status line after the window to minimize cursor movement */
@@ -578,6 +627,21 @@ int update_screen(int type)
maybe_intro_message();
did_intro = TRUE;
+ for (size_t i = 0; i < kv_size(providers); i++) {
+ DecorProvider *p = kv_A(providers, i);
+ if (!p->active) {
+ continue;
+ }
+
+ if (p->redraw_end != LUA_NOREF) {
+ FIXED_TEMP_ARRAY(args, 1);
+ args.items[0] = INTEGER_OBJ(display_tick);
+ provider_invoke(p->ns_id, "end", p->redraw_end, args, true);
+ }
+ }
+ kvi_destroy(providers);
+
+
// either cmdline is cleared, not drawn or mode is last drawn
cmdline_was_last_drawn = false;
return OK;
@@ -634,17 +698,6 @@ bool win_cursorline_standout(const win_T *wp)
|| (wp->w_p_cole > 0 && (VIsual_active || !conceal_cursor_line(wp)));
}
-static DecorationRedrawState decorations;
-bool decorations_active = false;
-
-void decorations_add_luahl_attr(int attr_id,
- int start_row, int start_col,
- int end_row, int end_col)
-{
- kv_push(decorations.active,
- ((HlRange){ start_row, start_col, end_row, end_col, attr_id, NULL }));
-}
-
/*
* Update a single window.
*
@@ -672,7 +725,7 @@ void decorations_add_luahl_attr(int attr_id,
* mid: from mid_start to mid_end (update inversion or changed text)
* bot: from bot_start to last row (when scrolled up)
*/
-static void win_update(win_T *wp)
+static void win_update(win_T *wp, Providers *providers)
{
buf_T *buf = wp->w_buffer;
int type;
@@ -697,9 +750,10 @@ static void win_update(win_T *wp)
int didline = FALSE; /* if TRUE, we finished the last line */
int i;
long j;
- static int recursive = FALSE; /* being called recursively */
- int old_botline = wp->w_botline;
- long fold_count;
+ static bool recursive = false; // being called recursively
+ const linenr_T old_botline = wp->w_botline;
+ const int old_wrow = wp->w_wrow;
+ const int old_wcol = wp->w_wcol;
// Remember what happened to the previous line.
#define DID_NONE 1 // didn't update a line
#define DID_LINE 2 // updated a normal line
@@ -710,6 +764,7 @@ static void win_update(win_T *wp)
linenr_T mod_bot = 0;
int save_got_int;
+
// If we can compute a change in the automatic sizing of the sign column
// under 'signcolumn=auto:X' and signs currently placed in the buffer, better
// figuring it out here so we can redraw the entire screen for it.
@@ -898,11 +953,12 @@ static void win_update(win_T *wp)
|| type == INVERTED || type == INVERTED_ALL)
&& !wp->w_botfill && !wp->w_old_botfill
) {
- if (mod_top != 0 && wp->w_topline == mod_top) {
- /*
- * w_topline is the first changed line, the scrolling will be done
- * further down.
- */
+ if (mod_top != 0
+ && wp->w_topline == mod_top
+ && (!wp->w_lines[0].wl_valid
+ || wp->w_topline <= wp->w_lines[0].wl_lnum)) {
+ // w_topline is the first changed line and window is not scrolled,
+ // the scrolling from changed lines will be done further down.
} else if (wp->w_lines[0].wl_valid
&& (wp->w_topline < wp->w_lines[0].wl_lnum
|| (wp->w_topline == wp->w_lines[0].wl_lnum
@@ -1226,7 +1282,6 @@ static void win_update(win_T *wp)
// Set the time limit to 'redrawtime'.
proftime_T syntax_tm = profile_setlimit(p_rdt);
syn_set_timeout(&syntax_tm);
- win_foldinfo.fi_level = 0;
/*
* Update all the window rows.
@@ -1236,28 +1291,32 @@ static void win_update(win_T *wp)
srow = 0;
lnum = wp->w_topline; // first line shown in window
- decorations_active = decorations_redraw_reset(buf, &decorations);
+ decor_redraw_reset(buf, &decor_state);
- if (buf->b_luahl && buf->b_luahl_window != LUA_NOREF) {
- Error err = ERROR_INIT;
- FIXED_TEMP_ARRAY(args, 4);
- linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE)
- ? wp->w_botline
- : (wp->w_topline + wp->w_height_inner));
- args.items[0] = WINDOW_OBJ(wp->handle);
- args.items[1] = BUFFER_OBJ(buf->handle);
- // TODO(bfredl): we are not using this, but should be first drawn line?
- args.items[2] = INTEGER_OBJ(wp->w_topline-1);
- args.items[3] = INTEGER_OBJ(knownmax);
- // TODO(bfredl): we could allow this callback to change mod_top, mod_bot.
- // For now the "start" callback is expected to use nvim__buf_redraw_range.
- executor_exec_lua_cb(buf->b_luahl_window, "window", args, false, &err);
- if (ERROR_SET(&err)) {
- ELOG("error in luahl window: %s", err.msg);
- api_clear_error(&err);
+ Providers line_providers;
+ kvi_init(line_providers);
+
+ linenr_T knownmax = ((wp->w_valid & VALID_BOTLINE)
+ ? wp->w_botline
+ : (wp->w_topline + wp->w_height_inner));
+
+ for (size_t k = 0; k < kv_size(*providers); k++) {
+ DecorProvider *p = kv_A(*providers, k);
+ if (p && p->redraw_win != LUA_NOREF) {
+ FIXED_TEMP_ARRAY(args, 4);
+ args.items[0] = WINDOW_OBJ(wp->handle);
+ args.items[1] = BUFFER_OBJ(buf->handle);
+ // TODO(bfredl): we are not using this, but should be first drawn line?
+ args.items[2] = INTEGER_OBJ(wp->w_topline-1);
+ args.items[3] = INTEGER_OBJ(knownmax);
+ if (provider_invoke(p->ns_id, "win", p->redraw_win, args, true)) {
+ kvi_push(line_providers, p);
+ }
}
}
+ win_check_ns_hl(wp);
+
for (;; ) {
/* stop updating when reached the end of the window (check for _past_
@@ -1448,24 +1507,19 @@ static void win_update(win_T *wp)
* Otherwise, display normally (can be several display lines when
* 'wrap' is on).
*/
- fold_count = foldedCount(wp, lnum, &win_foldinfo);
- if (fold_count != 0) {
- fold_line(wp, fold_count, &win_foldinfo, lnum, row);
- ++row;
- --fold_count;
- wp->w_lines[idx].wl_folded = TRUE;
- wp->w_lines[idx].wl_lastlnum = lnum + fold_count;
- did_update = DID_FOLD;
- } else if (idx < wp->w_lines_valid
- && wp->w_lines[idx].wl_valid
- && wp->w_lines[idx].wl_lnum == lnum
- && lnum > wp->w_topline
- && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE))
- && srow + wp->w_lines[idx].wl_size > wp->w_grid.Rows
- && diff_check_fill(wp, lnum) == 0
- ) {
- /* This line is not going to fit. Don't draw anything here,
- * will draw "@ " lines below. */
+ foldinfo_T foldinfo = fold_info(wp, lnum);
+
+ if (foldinfo.fi_lines == 0
+ && idx < wp->w_lines_valid
+ && wp->w_lines[idx].wl_valid
+ && wp->w_lines[idx].wl_lnum == lnum
+ && lnum > wp->w_topline
+ && !(dy_flags & (DY_LASTLINE | DY_TRUNCATE))
+ && srow + wp->w_lines[idx].wl_size > wp->w_grid.Rows
+ && diff_check_fill(wp, lnum) == 0
+ ) {
+ // This line is not going to fit. Don't draw anything here,
+ // will draw "@ " lines below.
row = wp->w_grid.Rows + 1;
} else {
prepare_search_hl(wp, lnum);
@@ -1474,14 +1528,21 @@ static void win_update(win_T *wp)
&& syntax_present(wp))
syntax_end_parsing(syntax_last_parsed + 1);
- /*
- * Display one line.
- */
- row = win_line(wp, lnum, srow, wp->w_grid.Rows, mod_top == 0, false);
+ // Display one line
+ row = win_line(wp, lnum, srow,
+ foldinfo.fi_lines ? srow : wp->w_grid.Rows,
+ mod_top == 0, false, foldinfo, &line_providers);
- wp->w_lines[idx].wl_folded = FALSE;
+ wp->w_lines[idx].wl_folded = foldinfo.fi_lines != 0;
wp->w_lines[idx].wl_lastlnum = lnum;
did_update = DID_LINE;
+
+ if (foldinfo.fi_lines > 0) {
+ did_update = DID_FOLD;
+ foldinfo.fi_lines--;
+ wp->w_lines[idx].wl_lastlnum = lnum + foldinfo.fi_lines;
+ }
+
syntax_last_parsed = lnum;
}
@@ -1496,20 +1557,18 @@ static void win_update(win_T *wp)
idx++;
break;
}
- if (dollar_vcol == -1)
+ if (dollar_vcol == -1) {
wp->w_lines[idx].wl_size = row - srow;
- ++idx;
- lnum += fold_count + 1;
+ }
+ idx++;
+ lnum += foldinfo.fi_lines + 1;
} else {
if (wp->w_p_rnu) {
// 'relativenumber' set: The text doesn't need to be drawn, but
// the number column nearly always does.
- fold_count = foldedCount(wp, lnum, &win_foldinfo);
- if (fold_count != 0) {
- fold_line(wp, fold_count, &win_foldinfo, lnum, row);
- } else {
- (void)win_line(wp, lnum, srow, wp->w_grid.Rows, true, true);
- }
+ foldinfo_T info = fold_info(wp, lnum);
+ (void)win_line(wp, lnum, srow, wp->w_grid.Rows, true, true,
+ info, &line_providers);
}
// This line does not need to be drawn, advance to the next one.
@@ -1605,6 +1664,8 @@ static void win_update(win_T *wp)
HLF_EOB);
}
+ kvi_destroy(line_providers);
+
if (wp->w_redr_type >= REDRAW_TOP) {
draw_vsep_win(wp, 0);
}
@@ -1631,21 +1692,55 @@ static void win_update(win_T *wp)
wp->w_valid |= VALID_BOTLINE;
wp->w_viewport_invalid = true;
if (wp == curwin && wp->w_botline != old_botline && !recursive) {
- recursive = TRUE;
+ const linenr_T old_topline = wp->w_topline;
+ const int new_wcol = wp->w_wcol;
+ recursive = true;
curwin->w_valid &= ~VALID_TOPLINE;
- update_topline(); /* may invalidate w_botline again */
- if (must_redraw != 0) {
- /* Don't update for changes in buffer again. */
+ update_topline(); // may invalidate w_botline again
+
+ if (old_wcol != new_wcol
+ && (wp->w_valid & (VALID_WCOL|VALID_WROW))
+ != (VALID_WCOL|VALID_WROW)) {
+ // A win_line() call applied a fix to screen cursor column to
+ // accomodate concealment of cursor line, but in this call to
+ // update_topline() the cursor's row or column got invalidated.
+ // If they are left invalid, setcursor() will recompute them
+ // but there won't be any further win_line() call to re-fix the
+ // column and the cursor will end up misplaced. So we call
+ // cursor validation now and reapply the fix again (or call
+ // win_line() to do it for us).
+ validate_cursor();
+ if (wp->w_wcol == old_wcol
+ && wp->w_wrow == old_wrow
+ && old_topline == wp->w_topline) {
+ wp->w_wcol = new_wcol;
+ } else {
+ redrawWinline(wp, wp->w_cursor.lnum);
+ }
+ }
+ // New redraw either due to updated topline or due to wcol fix.
+ if (wp->w_redr_type != 0) {
+ // Don't update for changes in buffer again.
i = curbuf->b_mod_set;
curbuf->b_mod_set = false;
- win_update(curwin);
- must_redraw = 0;
+ j = curbuf->b_mod_xlines;
+ curbuf->b_mod_xlines = 0;
+ win_update(curwin, providers);
curbuf->b_mod_set = i;
+ curbuf->b_mod_xlines = j;
+ }
+ // Other windows might have w_redr_type raised in update_topline().
+ must_redraw = 0;
+ FOR_ALL_WINDOWS_IN_TAB(wwp, curtab) {
+ if (wwp->w_redr_type > must_redraw) {
+ must_redraw = wwp->w_redr_type;
+ }
}
- recursive = FALSE;
+ recursive = false;
}
}
+
/* restore got_int, unless CTRL-C was hit while redrawing */
if (!got_int)
got_int = save_got_int;
@@ -1741,31 +1836,6 @@ static int advance_color_col(int vcol, int **color_cols)
return **color_cols >= 0;
}
-// Returns the next grid column.
-static int text_to_screenline(win_T *wp, char_u *text, int col, int off)
- FUNC_ATTR_NONNULL_ALL
-{
- int idx = wp->w_p_rl ? off : off + col;
- LineState s = LINE_STATE(text);
-
- while (*s.p != NUL) {
- // TODO(bfredl): cargo-culted from the old Vim code:
- // if(col + cells > wp->w_width - (wp->w_p_rl ? col : 0)) { break; }
- // This is obvious wrong. If Vim ever fixes this, solve for "cells" again
- // in the correct condition.
- const int maxcells = wp->w_grid.Columns - col - (wp->w_p_rl ? col : 0);
- const int cells = line_putchar(&s, &linebuf_char[idx], maxcells,
- wp->w_p_rl);
- if (cells == -1) {
- break;
- }
- col += cells;
- idx += cells;
- }
-
- return col;
-}
-
// Compute the width of the foldcolumn. Based on 'foldcolumn' and how much
// space is available for window "wp", minus "col".
static int compute_foldcolumn(win_T *wp, int col)
@@ -1830,271 +1900,6 @@ static int line_putchar(LineState *s, schar_T *dest, int maxcells, bool rl)
return cells;
}
-/*
- * Display one folded line.
- */
-static void fold_line(win_T *wp, long fold_count, foldinfo_T *foldinfo, linenr_T lnum, int row)
-{
- char_u buf[FOLD_TEXT_LEN];
- pos_T *top, *bot;
- linenr_T lnume = lnum + fold_count - 1;
- int len;
- char_u *text;
- int fdc;
- int col;
- int txtcol;
- int off;
-
- /* Build the fold line:
- * 1. Add the cmdwin_type for the command-line window
- * 2. Add the 'foldcolumn'
- * 3. Add the 'number' or 'relativenumber' column
- * 4. Compose the text
- * 5. Add the text
- * 6. set highlighting for the Visual area an other text
- */
- col = 0;
- off = 0;
-
- /*
- * 1. Add the cmdwin_type for the command-line window
- * Ignores 'rightleft', this window is never right-left.
- */
- if (cmdwin_type != 0 && wp == curwin) {
- schar_from_ascii(linebuf_char[off], cmdwin_type);
- linebuf_attr[off] = win_hl_attr(wp, HLF_AT);
- col++;
- }
-
-# define RL_MEMSET(p, v, l) \
- do { \
- if (wp->w_p_rl) { \
- for (int ri = 0; ri < l; ri++) { \
- linebuf_attr[off + (wp->w_grid.Columns - (p) - (l)) + ri] = v; \
- } \
- } else { \
- for (int ri = 0; ri < l; ri++) { \
- linebuf_attr[off + (p) + ri] = v; \
- } \
- } \
- } while (0)
-
- // 2. Add the 'foldcolumn'
- // Reduce the width when there is not enough space.
- fdc = compute_foldcolumn(wp, col);
- if (fdc > 0) {
- fill_foldcolumn(buf, wp, true, lnum);
- const char_u *it = &buf[0];
- for (int i = 0; i < fdc; i++) {
- int mb_c = mb_ptr2char_adv(&it);
- if (wp->w_p_rl) {
- schar_from_char(linebuf_char[off + wp->w_grid.Columns - i - 1 - col],
- mb_c);
- } else {
- schar_from_char(linebuf_char[off + col + i], mb_c);
- }
- }
- RL_MEMSET(col, win_hl_attr(wp, HLF_FC), fdc);
- col += fdc;
- }
-
- /* Set all attributes of the 'number' or 'relativenumber' column and the
- * text */
- RL_MEMSET(col, win_hl_attr(wp, HLF_FL), wp->w_grid.Columns - col);
-
- // If signs are being displayed, add spaces.
- if (win_signcol_count(wp) > 0) {
- len = wp->w_grid.Columns - col;
- if (len > 0) {
- int len_max = win_signcol_width(wp) * win_signcol_count(wp);
- if (len > len_max) {
- len = len_max;
- }
- char_u space_buf[18] = " ";
- assert((size_t)len_max <= sizeof(space_buf));
- copy_text_attr(off + col, space_buf, len,
- win_hl_attr(wp, HLF_FL));
- col += len;
- }
- }
-
- /*
- * 3. Add the 'number' or 'relativenumber' column
- */
- if (wp->w_p_nu || wp->w_p_rnu) {
- len = wp->w_grid.Columns - col;
- if (len > 0) {
- int w = number_width(wp);
- long num;
- char *fmt = "%*ld ";
-
- if (len > w + 1)
- len = w + 1;
-
- if (wp->w_p_nu && !wp->w_p_rnu)
- /* 'number' + 'norelativenumber' */
- num = (long)lnum;
- else {
- /* 'relativenumber', don't use negative numbers */
- num = labs((long)get_cursor_rel_lnum(wp, lnum));
- if (num == 0 && wp->w_p_nu && wp->w_p_rnu) {
- /* 'number' + 'relativenumber': cursor line shows absolute
- * line number */
- num = lnum;
- fmt = "%-*ld ";
- }
- }
-
- snprintf((char *)buf, FOLD_TEXT_LEN, fmt, w, num);
- if (wp->w_p_rl) {
- // the line number isn't reversed
- copy_text_attr(off + wp->w_grid.Columns - len - col, buf, len,
- win_hl_attr(wp, HLF_FL));
- } else {
- copy_text_attr(off + col, buf, len, win_hl_attr(wp, HLF_FL));
- }
- col += len;
- }
- }
-
- /*
- * 4. Compose the folded-line string with 'foldtext', if set.
- */
- text = get_foldtext(wp, lnum, lnume, foldinfo, buf);
-
- txtcol = col; /* remember where text starts */
-
- // 5. move the text to linebuf_char[off]. Fill up with "fold".
- // Right-left text is put in columns 0 - number-col, normal text is put
- // in columns number-col - window-width.
- col = text_to_screenline(wp, text, col, off);
-
- /* Fill the rest of the line with the fold filler */
- if (wp->w_p_rl)
- col -= txtcol;
-
- schar_T sc;
- schar_from_char(sc, wp->w_p_fcs_chars.fold);
- while (col < wp->w_grid.Columns
- - (wp->w_p_rl ? txtcol : 0)
- ) {
- schar_copy(linebuf_char[off+col++], sc);
- }
-
- if (text != buf)
- xfree(text);
-
- /*
- * 6. set highlighting for the Visual area an other text.
- * If all folded lines are in the Visual area, highlight the line.
- */
- if (VIsual_active && wp->w_buffer == curwin->w_buffer) {
- if (ltoreq(curwin->w_cursor, VIsual)) {
- /* Visual is after curwin->w_cursor */
- top = &curwin->w_cursor;
- bot = &VIsual;
- } else {
- /* Visual is before curwin->w_cursor */
- top = &VIsual;
- bot = &curwin->w_cursor;
- }
- if (lnum >= top->lnum
- && lnume <= bot->lnum
- && (VIsual_mode != 'v'
- || ((lnum > top->lnum
- || (lnum == top->lnum
- && top->col == 0))
- && (lnume < bot->lnum
- || (lnume == bot->lnum
- && (bot->col - (*p_sel == 'e'))
- >= (colnr_T)STRLEN(ml_get_buf(wp->w_buffer, lnume,
- FALSE))))))) {
- if (VIsual_mode == Ctrl_V) {
- // Visual block mode: highlight the chars part of the block
- if (wp->w_old_cursor_fcol + txtcol < (colnr_T)wp->w_grid.Columns) {
- if (wp->w_old_cursor_lcol != MAXCOL
- && wp->w_old_cursor_lcol + txtcol
- < (colnr_T)wp->w_grid.Columns) {
- len = wp->w_old_cursor_lcol;
- } else {
- len = wp->w_grid.Columns - txtcol;
- }
- RL_MEMSET(wp->w_old_cursor_fcol + txtcol, win_hl_attr(wp, HLF_V),
- len - (int)wp->w_old_cursor_fcol);
- }
- } else {
- // Set all attributes of the text
- RL_MEMSET(txtcol, win_hl_attr(wp, HLF_V), wp->w_grid.Columns - txtcol);
- }
- }
- }
-
- // Show colorcolumn in the fold line, but let cursorcolumn override it.
- if (wp->w_p_cc_cols) {
- int i = 0;
- int j = wp->w_p_cc_cols[i];
- int old_txtcol = txtcol;
-
- while (j > -1) {
- txtcol += j;
- if (wp->w_p_wrap) {
- txtcol -= wp->w_skipcol;
- } else {
- txtcol -= wp->w_leftcol;
- }
- if (txtcol >= 0 && txtcol < wp->w_grid.Columns) {
- linebuf_attr[off + txtcol] =
- hl_combine_attr(linebuf_attr[off + txtcol], win_hl_attr(wp, HLF_MC));
- }
- txtcol = old_txtcol;
- j = wp->w_p_cc_cols[++i];
- }
- }
-
- /* Show 'cursorcolumn' in the fold line. */
- if (wp->w_p_cuc) {
- txtcol += wp->w_virtcol;
- if (wp->w_p_wrap)
- txtcol -= wp->w_skipcol;
- else
- txtcol -= wp->w_leftcol;
- if (txtcol >= 0 && txtcol < wp->w_grid.Columns) {
- linebuf_attr[off + txtcol] = hl_combine_attr(
- linebuf_attr[off + txtcol], win_hl_attr(wp, HLF_CUC));
- }
- }
-
- grid_put_linebuf(&wp->w_grid, row, 0, wp->w_grid.Columns, wp->w_grid.Columns,
- false, wp, wp->w_hl_attr_normal, false);
-
- /*
- * Update w_cline_height and w_cline_folded if the cursor line was
- * updated (saves a call to plines() later).
- */
- if (wp == curwin
- && lnum <= curwin->w_cursor.lnum
- && lnume >= curwin->w_cursor.lnum) {
- curwin->w_cline_row = row;
- curwin->w_cline_height = 1;
- curwin->w_cline_folded = true;
- curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW);
- conceal_cursor_used = conceal_cursor_line(curwin);
- }
-}
-
-
-/// Copy "buf[len]" to linebuf_char["off"] and set attributes to "attr".
-///
-/// Only works for ASCII text!
-static void copy_text_attr(int off, char_u *buf, int len, int attr)
-{
- int i;
-
- for (i = 0; i < len; i++) {
- schar_from_ascii(linebuf_char[off + i], buf[i]);
- linebuf_attr[off + i] = attr;
- }
-}
/// Fills the foldcolumn at "p" for window "wp".
/// Only to be called when 'foldcolumn' > 0.
@@ -2109,7 +1914,7 @@ static size_t
fill_foldcolumn(
char_u *p,
win_T *wp,
- int closed,
+ foldinfo_T foldinfo,
linenr_T lnum
)
{
@@ -2120,10 +1925,11 @@ fill_foldcolumn(
size_t char_counter = 0;
int symbol = 0;
int len = 0;
+ bool closed = foldinfo.fi_lines > 0;
// Init to all spaces.
memset(p, ' ', MAX_MCO * fdc + 1);
- level = win_foldinfo.fi_level;
+ level = foldinfo.fi_level;
// If the column is too narrow, we start at the lowest level that
// fits and use numbers to indicated the depth.
@@ -2133,8 +1939,8 @@ fill_foldcolumn(
}
for (i = 0; i < MIN(fdc, level); i++) {
- if (win_foldinfo.fi_lnum == lnum
- && first_level + i >= win_foldinfo.fi_low_level) {
+ if (foldinfo.fi_lnum == lnum
+ && first_level + i >= foldinfo.fi_low_level) {
symbol = wp->w_p_fcs_chars.foldopen;
} else if (first_level == 1) {
symbol = wp->w_p_fcs_chars.foldsep;
@@ -2165,22 +1971,23 @@ fill_foldcolumn(
return MAX(char_counter + (fdc-i), (size_t)fdc);
}
-/*
- * Display line "lnum" of window 'wp' on the screen.
- * Start at row "startrow", stop when "endrow" is reached.
- * wp->w_virtcol needs to be valid.
- *
- * Return the number of last row the line occupies.
- */
-static int
-win_line (
- win_T *wp,
- linenr_T lnum,
- int startrow,
- int endrow,
- bool nochange, // not updating for changed text
- bool number_only // only update the number column
-)
+/// Display line "lnum" of window 'wp' on the screen.
+/// wp->w_virtcol needs to be valid.
+///
+/// @param lnum line to display
+/// @param startrow first row relative to window grid
+/// @param endrow last grid row to be redrawn
+/// @param nochange not updating for changed text
+/// @param number_only only update the number column
+/// @param foldinfo fold info for this line
+/// @param[in, out] providers decoration providers active this line
+/// items will be disables if they cause errors
+/// or explicitly return `false`.
+///
+/// @return the number of last row the line occupies.
+static int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow,
+ bool nochange, bool number_only, foldinfo_T foldinfo,
+ Providers *providers)
{
int c = 0; // init for GCC
long vcol = 0; // virtual column (for tabs)
@@ -2281,9 +2088,11 @@ win_line (
int prev_c1 = 0; // first composing char for prev_c
bool search_attr_from_match = false; // if search_attr is from :match
- bool has_decorations = false; // this buffer has decorations
+ bool has_decor = false; // this buffer has decoration
bool do_virttext = false; // draw virtual text for this line
+ char_u buf_fold[FOLD_TEXT_LEN + 1]; // Hold value returned by get_foldtext
+
/* draw_state: items that are drawn in sequence: */
#define WL_START 0 /* nothing done yet */
# define WL_CMDLINE WL_START + 1 /* cmdline window column */
@@ -2322,7 +2131,7 @@ win_line (
row = startrow;
- char *luatext = NULL;
+ char *err_text = NULL;
buf_T *buf = wp->w_buffer;
@@ -2347,37 +2156,37 @@ win_line (
}
}
- if (decorations_active) {
- if (buf->b_luahl && buf->b_luahl_line != LUA_NOREF) {
- Error err = ERROR_INIT;
+ has_decor = decor_redraw_line(wp->w_buffer, lnum-1,
+ &decor_state);
+
+ for (size_t k = 0; k < kv_size(*providers); k++) {
+ DecorProvider *p = kv_A(*providers, k);
+ if (p && p->redraw_line != LUA_NOREF) {
FIXED_TEMP_ARRAY(args, 3);
args.items[0] = WINDOW_OBJ(wp->handle);
args.items[1] = BUFFER_OBJ(buf->handle);
args.items[2] = INTEGER_OBJ(lnum-1);
- lua_attr_active = true;
- extra_check = true;
- Object o = executor_exec_lua_cb(buf->b_luahl_line, "line",
- args, true, &err);
- lua_attr_active = false;
- if (o.type == kObjectTypeString) {
- // TODO(bfredl): this is a bit of a hack. A final API should use an
- // "unified" interface where luahl can add both bufhl and virttext
- luatext = o.data.string.data;
- do_virttext = true;
- } else if (ERROR_SET(&err)) {
- ELOG("error in luahl line: %s", err.msg);
- luatext = err.msg;
- do_virttext = true;
+ if (provider_invoke(p->ns_id, "line", p->redraw_line, args, true)) {
+ has_decor = true;
+ } else {
+ // return 'false' or error: skip rest of this window
+ kv_A(*providers, k) = NULL;
}
- }
- has_decorations = decorations_redraw_line(wp->w_buffer, lnum-1,
- &decorations);
- if (has_decorations) {
- extra_check = true;
+ win_check_ns_hl(wp);
}
}
+ if (has_decor) {
+ extra_check = true;
+ }
+
+ if (provider_first_error) {
+ err_text = provider_first_error;
+ provider_first_error = NULL;
+ do_virttext = true;
+ }
+
// Check for columns to display for 'colorcolumn'.
color_cols = wp->w_buffer->terminal ? NULL : wp->w_p_cc_cols;
if (color_cols != NULL) {
@@ -2554,6 +2363,7 @@ win_line (
}
// If this line has a sign with line highlighting set line_attr.
+ // TODO(bfredl, vigoux): this should not take priority over decoration!
v = buf_getsigntype(wp->w_buffer, lnum, SIGN_LINEHL, 0, 1);
if (v != 0) {
line_attr = sign_get_attr((int)v, SIGN_LINEHL);
@@ -2816,6 +2626,7 @@ win_line (
for (;; ) {
int has_match_conc = 0; ///< match wants to conceal
bool did_decrement_ptr = false;
+
// Skip this quickly when working on the text.
if (draw_state != WL_LINE) {
if (draw_state == WL_CMDLINE - 1 && n_extra == 0) {
@@ -2838,7 +2649,7 @@ win_line (
// already be in use.
xfree(p_extra_free);
p_extra_free = xmalloc(MAX_MCO * fdc + 1);
- n_extra = fill_foldcolumn(p_extra_free, wp, false, lnum);
+ n_extra = fill_foldcolumn(p_extra_free, wp, foldinfo, lnum);
p_extra_free[n_extra] = NUL;
p_extra = p_extra_free;
c_extra = NUL;
@@ -2854,47 +2665,12 @@ win_line (
* buffer or when using Netbeans. */
int count = win_signcol_count(wp);
if (count > 0) {
- int text_sign;
- // Draw cells with the sign value or blank.
- c_extra = ' ';
- c_final = NUL;
- char_attr = win_hl_attr(wp, HLF_SC);
- n_extra = win_signcol_width(wp);
-
- if (row == startrow + filler_lines && filler_todo <= 0) {
- text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT,
- sign_idx, count);
- if (text_sign != 0) {
- p_extra = sign_get_text(text_sign);
- if (p_extra != NULL) {
- int symbol_blen = (int)STRLEN(p_extra);
-
- c_extra = NUL;
- c_final = NUL;
-
- // TODO(oni-link): Is sign text already extended to
- // full cell width?
- assert((size_t)win_signcol_width(wp)
- >= mb_string2cells(p_extra));
- // symbol(s) bytes + (filling spaces) (one byte each)
- n_extra = symbol_blen +
- (win_signcol_width(wp) - mb_string2cells(p_extra));
-
- assert(sizeof(extra) > (size_t)symbol_blen);
- memset(extra, ' ', sizeof(extra));
- memcpy(extra, p_extra, symbol_blen);
-
- p_extra = extra;
- p_extra[n_extra] = NUL;
- }
- char_attr = sign_get_attr(text_sign, SIGN_TEXT);
- }
- }
-
- sign_idx++;
- if (sign_idx < count) {
- draw_state = WL_SIGN - 1;
- }
+ get_sign_display_info(
+ false, wp, lnum, row,
+ startrow, filler_lines, filler_todo, count,
+ &c_extra, &c_final, extra, sizeof(extra),
+ &p_extra, &n_extra,
+ &char_attr, &draw_state, &sign_idx);
}
}
@@ -2903,65 +2679,78 @@ win_line (
/* Display the absolute or relative line number. After the
* first fill with blanks when the 'n' flag isn't in 'cpo' */
if ((wp->w_p_nu || wp->w_p_rnu)
- && (row == startrow
- + filler_lines
+ && (row == startrow + filler_lines
|| vim_strchr(p_cpo, CPO_NUMCOL) == NULL)) {
- /* Draw the line number (empty space after wrapping). */
- if (row == startrow
- + filler_lines
- ) {
- long num;
- char *fmt = "%*ld ";
-
- if (wp->w_p_nu && !wp->w_p_rnu)
- /* 'number' + 'norelativenumber' */
- num = (long)lnum;
- else {
- /* 'relativenumber', don't use negative numbers */
- num = labs((long)get_cursor_rel_lnum(wp, lnum));
- if (num == 0 && wp->w_p_nu && wp->w_p_rnu) {
- /* 'number' + 'relativenumber' */
- num = lnum;
- fmt = "%-*ld ";
+ // If 'signcolumn' is set to 'number' and a sign is present
+ // in 'lnum', then display the sign instead of the line
+ // number.
+ if (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u'
+ && buf_findsign_id(wp->w_buffer, lnum, (char_u *)"*") != 0) {
+ int count = win_signcol_count(wp);
+ get_sign_display_info(
+ true, wp, lnum, row,
+ startrow, filler_lines, filler_todo, count,
+ &c_extra, &c_final, extra, sizeof(extra),
+ &p_extra, &n_extra,
+ &char_attr, &draw_state, &sign_idx);
+ } else {
+ if (row == startrow + filler_lines) {
+ // Draw the line number (empty space after wrapping). */
+ long num;
+ char *fmt = "%*ld ";
+
+ if (wp->w_p_nu && !wp->w_p_rnu) {
+ // 'number' + 'norelativenumber'
+ num = (long)lnum;
+ } else {
+ // 'relativenumber', don't use negative numbers
+ num = labs((long)get_cursor_rel_lnum(wp, lnum));
+ if (num == 0 && wp->w_p_nu && wp->w_p_rnu) {
+ // 'number' + 'relativenumber'
+ num = lnum;
+ fmt = "%-*ld ";
+ }
}
- }
- sprintf((char *)extra, fmt,
- number_width(wp), num);
- if (wp->w_skipcol > 0)
- for (p_extra = extra; *p_extra == ' '; ++p_extra)
- *p_extra = '-';
- if (wp->w_p_rl) { // reverse line numbers
- // like rl_mirror(), but keep the space at the end
- char_u *p2 = skiptowhite(extra) - 1;
- for (char_u *p1 = extra; p1 < p2; p1++, p2--) {
- const int t = *p1;
- *p1 = *p2;
- *p2 = t;
+ snprintf((char *)extra, sizeof(extra),
+ fmt, number_width(wp), num);
+ if (wp->w_skipcol > 0) {
+ for (p_extra = extra; *p_extra == ' '; p_extra++) {
+ *p_extra = '-';
+ }
+ }
+ if (wp->w_p_rl) { // reverse line numbers
+ // like rl_mirror(), but keep the space at the end
+ char_u *p2 = skiptowhite(extra) - 1;
+ for (char_u *p1 = extra; p1 < p2; p1++, p2--) {
+ const int t = *p1;
+ *p1 = *p2;
+ *p2 = t;
+ }
}
+ p_extra = extra;
+ c_extra = NUL;
+ c_final = NUL;
+ } else {
+ c_extra = ' ';
+ c_final = NUL;
+ }
+ n_extra = number_width(wp) + 1;
+ char_attr = win_hl_attr(wp, HLF_N);
+
+ int num_sign = buf_getsigntype(
+ wp->w_buffer, lnum, SIGN_NUMHL, 0, 1);
+ if (num_sign != 0) {
+ // :sign defined with "numhl" highlight.
+ char_attr = sign_get_attr(num_sign, SIGN_NUMHL);
+ } else if ((wp->w_p_cul || wp->w_p_rnu)
+ && lnum == wp->w_cursor.lnum) {
+ // When 'cursorline' is set highlight the line number of
+ // the current line differently.
+ // TODO(vim): Can we use CursorLine instead of CursorLineNr
+ // when CursorLineNr isn't set?
+ char_attr = win_hl_attr(wp, HLF_CLN);
}
- p_extra = extra;
- c_extra = NUL;
- c_final = NUL;
- } else {
- c_extra = ' ';
- c_final = NUL;
- }
- n_extra = number_width(wp) + 1;
- char_attr = win_hl_attr(wp, HLF_N);
-
- int num_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_NUMHL,
- 0, 1);
- if (num_sign != 0) {
- // :sign defined with "numhl" highlight.
- char_attr = sign_get_attr(num_sign, SIGN_NUMHL);
- } else if ((wp->w_p_cul || wp->w_p_rnu)
- && lnum == wp->w_cursor.lnum) {
- // When 'cursorline' is set highlight the line number of
- // the current line differently.
- // TODO(vim): Can we use CursorLine instead of CursorLineNr
- // when CursorLineNr isn't set?
- char_attr = win_hl_attr(wp, HLF_CLN);
}
}
}
@@ -3068,10 +2857,12 @@ win_line (
}
// When still displaying '$' of change command, stop at cursor
- if ((dollar_vcol >= 0 && wp == curwin
- && lnum == wp->w_cursor.lnum && vcol >= (long)wp->w_virtcol
- && filler_todo <= 0)
- || (number_only && draw_state > WL_NR)) {
+ if (((dollar_vcol >= 0
+ && wp == curwin
+ && lnum == wp->w_cursor.lnum
+ && vcol >= (long)wp->w_virtcol)
+ || (number_only && draw_state > WL_NR))
+ && filler_todo <= 0) {
grid_put_linebuf(grid, row, 0, col, -grid->Columns, wp->w_p_rl, wp,
wp->w_hl_attr_normal, false);
// Pretend we have finished updating the window. Except when
@@ -3084,6 +2875,51 @@ win_line (
break;
}
+ if (draw_state == WL_LINE
+ && foldinfo.fi_level != 0
+ && foldinfo.fi_lines > 0
+ && vcol == 0
+ && n_extra == 0
+ && row == startrow) {
+ char_attr = win_hl_attr(wp, HLF_FL);
+
+ linenr_T lnume = lnum + foldinfo.fi_lines - 1;
+ memset(buf_fold, ' ', FOLD_TEXT_LEN);
+ p_extra = get_foldtext(wp, lnum, lnume, foldinfo, buf_fold);
+ n_extra = STRLEN(p_extra);
+
+ if (p_extra != buf_fold) {
+ xfree(p_extra_free);
+ p_extra_free = p_extra;
+ }
+ c_extra = NUL;
+ c_final = NUL;
+ p_extra[n_extra] = NUL;
+ }
+
+ if (draw_state == WL_LINE
+ && foldinfo.fi_level != 0
+ && foldinfo.fi_lines > 0
+ && col < grid->Columns
+ && n_extra == 0
+ && row == startrow) {
+ // fill rest of line with 'fold'
+ c_extra = wp->w_p_fcs_chars.fold;
+ c_final = NUL;
+
+ n_extra = wp->w_p_rl ? (col + 1) : (grid->Columns - col);
+ }
+
+ if (draw_state == WL_LINE
+ && foldinfo.fi_level != 0
+ && foldinfo.fi_lines > 0
+ && col >= grid->Columns
+ && n_extra != 0
+ && row == startrow) {
+ // Truncate the folding.
+ n_extra = 0;
+ }
+
if (draw_state == WL_LINE && (area_highlighting || has_spell)) {
// handle Visual or match highlighting in this line
if (vcol == fromcol
@@ -3312,6 +3148,10 @@ win_line (
p_extra++;
}
n_extra--;
+ } else if (foldinfo.fi_lines > 0) {
+ // skip writing the buffer line itself
+ c = NUL;
+ XFREE_CLEAR(p_extra_free);
} else {
int c0;
@@ -3476,6 +3316,7 @@ win_line (
* Only do this when there is no syntax highlighting, the
* @Spell cluster is not used or the current syntax item
* contains the @Spell cluster. */
+ v = (long)(ptr - line);
if (has_spell && v >= word_end && v > cur_checked_col) {
spell_attr = 0;
if (!attr_pri) {
@@ -3547,9 +3388,9 @@ win_line (
char_attr = hl_combine_attr(spell_attr, char_attr);
}
- if (has_decorations && v > 0) {
- int extmark_attr = decorations_redraw_col(wp->w_buffer, (colnr_T)v-1,
- &decorations);
+ if (has_decor && v > 0) {
+ int extmark_attr = decor_redraw_col(wp->w_buffer, (colnr_T)v-1,
+ &decor_state);
if (extmark_attr != 0) {
if (!attr_pri) {
char_attr = hl_combine_attr(char_attr, extmark_attr);
@@ -3782,7 +3623,7 @@ win_line (
mb_utf8 = false; // don't draw as UTF-8
}
} else if (c != NUL) {
- p_extra = transchar(c);
+ p_extra = transchar_buf(wp->w_buffer, c);
if (n_extra == 0) {
n_extra = byte2cells(c) - 1;
}
@@ -3879,7 +3720,7 @@ win_line (
// not showing the '>', put pointer back to avoid getting stuck
ptr++;
}
- }
+ } // end of printing from buffer content
/* In the cursor line and we may be concealing characters: correct
* the cursor column when we reach its position. */
@@ -3936,7 +3777,7 @@ win_line (
}
// At end of the text line or just after the last character.
- if (c == NUL) {
+ if (c == NUL && eol_hl_off == 0) {
long prevcol = (long)(ptr - line) - 1;
// we're not really at that column when skipping some text
@@ -4046,11 +3887,13 @@ win_line (
draw_color_col = advance_color_col(VCOL_HLC, &color_cols);
VirtText virt_text = KV_INITIAL_VALUE;
- if (luatext) {
- kv_push(virt_text, ((VirtTextChunk){ .text = luatext, .hl_id = 0 }));
+ if (err_text) {
+ int hl_err = syn_check_group((char_u *)S_LEN("ErrorMsg"));
+ kv_push(virt_text, ((VirtTextChunk){ .text = err_text,
+ .hl_id = hl_err }));
do_virttext = true;
- } else if (has_decorations) {
- VirtText *vp = decorations_redraw_virt_text(wp->w_buffer, &decorations);
+ } else if (has_decor) {
+ VirtText *vp = decor_redraw_virt_text(wp->w_buffer, &decor_state);
if (vp) {
virt_text = *vp;
do_virttext = true;
@@ -4189,11 +4032,10 @@ win_line (
if (wp == curwin && lnum == curwin->w_cursor.lnum) {
curwin->w_cline_row = startrow;
curwin->w_cline_height = row - startrow;
- curwin->w_cline_folded = false;
+ curwin->w_cline_folded = foldinfo.fi_lines > 0;
curwin->w_valid |= (VALID_CHEIGHT|VALID_CROW);
conceal_cursor_used = conceal_cursor_line(curwin);
}
-
break;
}
@@ -4382,6 +4224,7 @@ win_line (
* so far. If there is no more to display it is caught above.
*/
if ((wp->w_p_rl ? (col < 0) : (col >= grid->Columns))
+ && foldinfo.fi_lines == 0
&& (*ptr != NUL
|| filler_todo > 0
|| (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL
@@ -4467,7 +4310,7 @@ win_line (
}
xfree(p_extra_free);
- xfree(luatext);
+ xfree(err_text);
return row;
}
@@ -4494,6 +4337,88 @@ void screen_adjust_grid(ScreenGrid **grid, int *row_off, int *col_off)
}
}
+// Get information needed to display the sign in line 'lnum' in window 'wp'.
+// If 'nrcol' is TRUE, the sign is going to be displayed in the number column.
+// Otherwise the sign is going to be displayed in the sign column.
+static void get_sign_display_info(
+ bool nrcol,
+ win_T *wp,
+ linenr_T lnum,
+ int row,
+ int startrow,
+ int filler_lines,
+ int filler_todo,
+ int count,
+ int *c_extrap,
+ int *c_finalp,
+ char_u *extra,
+ size_t extra_size,
+ char_u **pp_extra,
+ int *n_extrap,
+ int *char_attrp,
+ int *draw_statep,
+ int *sign_idxp
+)
+{
+ int text_sign;
+
+ // Draw cells with the sign value or blank.
+ *c_extrap = ' ';
+ *c_finalp = NUL;
+ if (nrcol) {
+ *n_extrap = number_width(wp) + 1;
+ } else {
+ *char_attrp = win_hl_attr(wp, HLF_SC);
+ *n_extrap = win_signcol_width(wp);
+ }
+
+ if (row == startrow + filler_lines && filler_todo <= 0) {
+ text_sign = buf_getsigntype(wp->w_buffer, lnum, SIGN_TEXT,
+ *sign_idxp, count);
+ if (text_sign != 0) {
+ *pp_extra = sign_get_text(text_sign);
+ if (*pp_extra != NULL) {
+ *c_extrap = NUL;
+ *c_finalp = NUL;
+
+ if (nrcol) {
+ int n, width = number_width(wp) - 2;
+ for (n = 0; n < width; n++) {
+ extra[n] = ' ';
+ }
+ extra[n] = NUL;
+ STRCAT(extra, *pp_extra);
+ STRCAT(extra, " ");
+ *pp_extra = extra;
+ *n_extrap = (int)STRLEN(*pp_extra);
+ } else {
+ int symbol_blen = (int)STRLEN(*pp_extra);
+
+ // TODO(oni-link): Is sign text already extended to
+ // full cell width?
+ assert((size_t)win_signcol_width(wp) >= mb_string2cells(*pp_extra));
+ // symbol(s) bytes + (filling spaces) (one byte each)
+ *n_extrap = symbol_blen +
+ (win_signcol_width(wp) - mb_string2cells(*pp_extra));
+
+ assert(extra_size > (size_t)symbol_blen);
+ memset(extra, ' ', extra_size);
+ memcpy(extra, *pp_extra, symbol_blen);
+
+ *pp_extra = extra;
+ (*pp_extra)[*n_extrap] = NUL;
+ }
+ }
+ *char_attrp = sign_get_attr(text_sign, SIGN_TEXT);
+ }
+ }
+
+ (*sign_idxp)++;
+ if (*sign_idxp < count) {
+ *draw_statep = WL_SIGN - 1;
+ }
+}
+
/*
* Check whether the given character needs redrawing:
@@ -4713,8 +4638,8 @@ void status_redraw_all(void)
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_status_height) {
- wp->w_redr_status = TRUE;
- redraw_later(VALID);
+ wp->w_redr_status = true;
+ redraw_later(wp, VALID);
}
}
}
@@ -4731,7 +4656,7 @@ void status_redraw_buf(buf_T *buf)
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_status_height != 0 && wp->w_buffer == buf) {
wp->w_redr_status = true;
- redraw_later(VALID);
+ redraw_later(wp, VALID);
}
}
}
@@ -5255,8 +5180,8 @@ win_redr_custom (
char_u buf[MAXPATHL];
char_u *stl;
char_u *p;
- struct stl_hlrec hltab[STL_MAX_ITEM];
- StlClickRecord tabtab[STL_MAX_ITEM];
+ stl_hlrec_t *hltab;
+ StlClickRecord *tabtab;
int use_sandbox = false;
win_T *ewp;
int p_crb_save;
@@ -5334,9 +5259,9 @@ win_redr_custom (
/* Make a copy, because the statusline may include a function call that
* might change the option value and free the memory. */
stl = vim_strsave(stl);
- width = build_stl_str_hl(ewp, buf, sizeof(buf),
- stl, use_sandbox,
- fillchar, maxwidth, hltab, tabtab);
+ width =
+ build_stl_str_hl(ewp, buf, sizeof(buf), stl, use_sandbox,
+ fillchar, maxwidth, &hltab, &tabtab);
xfree(stl);
ewp->w_p_crb = p_crb_save;
@@ -5854,6 +5779,12 @@ next_search_hl (
long nmatched = 0;
int save_called_emsg = called_emsg;
+ // for :{range}s/pat only highlight inside the range
+ if (lnum < search_first_line || lnum > search_last_line) {
+ shl->lnum = 0;
+ return;
+ }
+
if (shl->lnum != 0) {
// Check for three situations:
// 1. If the "lnum" is below a previous match, start a new search.
@@ -6146,7 +6077,7 @@ void win_grid_alloc(win_T *wp)
|| grid->Rows != rows
|| grid->Columns != cols) {
if (want_allocation) {
- grid_alloc(grid, rows, cols, wp->w_grid.valid, wp->w_grid.valid);
+ grid_alloc(grid, rows, cols, wp->w_grid.valid, false);
grid->valid = true;
} else {
// Single grid mode, all rendering will be redirected to default_grid.
@@ -7344,9 +7275,17 @@ int number_width(win_T *wp)
++n;
} while (lnum > 0);
- /* 'numberwidth' gives the minimal width plus one */
- if (n < wp->w_p_nuw - 1)
+ // 'numberwidth' gives the minimal width plus one
+ if (n < wp->w_p_nuw - 1) {
n = wp->w_p_nuw - 1;
+ }
+
+ // If 'signcolumn' is set to 'number' and there is a sign to display, then
+ // the minimal width for the number column is 2.
+ if (n < 2 && (wp->w_buffer->b_signlist != NULL)
+ && (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')) {
+ n = 2;
+ }
wp->w_nrwidth_width = n;
return n;
@@ -7508,3 +7447,5 @@ win_T *get_win_by_grid_handle(handle_T handle)
}
return NULL;
}
+
+
diff --git a/src/nvim/search.c b/src/nvim/search.c
index b105d99d7c..b25333c9fa 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -96,11 +96,8 @@ 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 bool saved_no_hlsearch = false;
+static int saved_spats_last_idx = 0;
+static bool saved_spats_no_hlsearch = false;
static char_u *mr_pattern = NULL; // pattern used by search_regcomp()
static int mr_pattern_alloced = false; // mr_pattern was allocated
@@ -267,8 +264,8 @@ void save_search_patterns(void)
saved_spats[1] = spats[1];
if (spats[1].pat != NULL)
saved_spats[1].pat = vim_strsave(spats[1].pat);
- saved_last_idx = last_idx;
- saved_no_hlsearch = no_hlsearch;
+ saved_spats_last_idx = last_idx;
+ saved_spats_no_hlsearch = no_hlsearch;
}
}
@@ -280,8 +277,8 @@ void restore_search_patterns(void)
set_vv_searchforward();
free_spat(&spats[1]);
spats[1] = saved_spats[1];
- last_idx = saved_last_idx;
- set_no_hlsearch(saved_no_hlsearch);
+ last_idx = saved_spats_last_idx;
+ set_no_hlsearch(saved_spats_no_hlsearch);
}
}
@@ -308,6 +305,13 @@ void free_search_patterns(void)
#endif
+// copy of spats[RE_SEARCH], for keeping the search patterns while incremental
+// searching
+static struct spat saved_last_search_spat;
+static int did_save_last_search_spat = 0;
+static int saved_last_idx = 0;
+static bool saved_no_hlsearch = false;
+
/// Save and restore the search pattern for incremental highlight search
/// feature.
///
@@ -316,6 +320,11 @@ void free_search_patterns(void)
/// cancelling incremental searching even if it's called inside user functions.
void save_last_search_pattern(void)
{
+ if (++did_save_last_search_spat != 1) {
+ // nested call, nothing to do
+ return;
+ }
+
saved_last_search_spat = spats[RE_SEARCH];
if (spats[RE_SEARCH].pat != NULL) {
saved_last_search_spat.pat = vim_strsave(spats[RE_SEARCH].pat);
@@ -326,8 +335,19 @@ void save_last_search_pattern(void)
void restore_last_search_pattern(void)
{
+ if (--did_save_last_search_spat > 0) {
+ // nested call, nothing to do
+ return;
+ }
+ if (did_save_last_search_spat != 0) {
+ iemsg("restore_last_search_pattern() called more often than"
+ " save_last_search_pattern()");
+ return;
+ }
+
xfree(spats[RE_SEARCH].pat);
spats[RE_SEARCH] = saved_last_search_spat;
+ saved_last_search_spat.pat = NULL;
set_vv_searchforward();
last_idx = saved_last_idx;
set_no_hlsearch(saved_no_hlsearch);
@@ -474,7 +494,7 @@ void set_last_search_pat(const char_u *s, int idx, int magic, int setlast)
saved_spats[idx].pat = NULL;
else
saved_spats[idx].pat = vim_strsave(spats[idx].pat);
- saved_last_idx = last_idx;
+ saved_spats_last_idx = last_idx;
}
/* If 'hlsearch' set and search pat changed: need redraw. */
if (p_hls && idx == last_idx && !no_hlsearch)
@@ -631,6 +651,10 @@ int searchit(
colnr_T col = at_first_line && (options & SEARCH_COL) ? pos->col : 0;
nmatched = vim_regexec_multi(&regmatch, win, buf,
lnum, col, tm, timed_out);
+ // vim_regexec_multi() may clear "regprog"
+ if (regmatch.regprog == NULL) {
+ break;
+ }
// Abort searching on an error (e.g., out of stack).
if (called_emsg || (timed_out != NULL && *timed_out)) {
break;
@@ -702,6 +726,10 @@ int searchit(
match_ok = false;
break;
}
+ // vim_regexec_multi() may clear "regprog"
+ if (regmatch.regprog == NULL) {
+ break;
+ }
matchpos = regmatch.startpos[0];
endpos = regmatch.endpos[0];
submatch = first_submatch(&regmatch);
@@ -791,10 +819,13 @@ int searchit(
}
break;
}
-
- /* Need to get the line pointer again, a
- * multi-line search may have made it invalid. */
- ptr = ml_get_buf(buf, lnum + matchpos.lnum, FALSE);
+ // vim_regexec_multi() may clear "regprog"
+ if (regmatch.regprog == NULL) {
+ break;
+ }
+ // Need to get the line pointer again, a
+ // multi-line search may have made it invalid.
+ ptr = ml_get_buf(buf, lnum + matchpos.lnum, false);
}
/*
@@ -871,6 +902,11 @@ int searchit(
}
at_first_line = FALSE;
+ // vim_regexec_multi() may clear "regprog"
+ if (regmatch.regprog == NULL) {
+ break;
+ }
+
// Stop the search if wrapscan isn't set, "stop_lnum" is
// specified, after an interrupt, after a match and after looping
// twice.
@@ -1135,8 +1171,8 @@ int do_search(
pat = p; /* put pat after search command */
}
- if ((options & SEARCH_ECHO) && messaging()
- && !cmd_silent && msg_silent == 0) {
+ if ((options & SEARCH_ECHO) && messaging() && !msg_silent
+ && (!cmd_silent || !shortmess(SHM_SEARCHCOUNT))) {
char_u *trunc;
char_u off_buf[40];
size_t off_len = 0;
@@ -1145,7 +1181,8 @@ int do_search(
msg_start();
// Get the offset, so we know how long it is.
- if (spats[0].off.line || spats[0].off.end || spats[0].off.off) {
+ if (!cmd_silent
+ && (spats[0].off.line || spats[0].off.end || spats[0].off.off)) {
p = off_buf; // -V507
*p++ = dirc;
if (spats[0].off.end) {
@@ -1165,19 +1202,19 @@ int do_search(
}
if (*searchstr == NUL) {
- p = spats[last_idx].pat;
+ p = spats[0].pat;
} else {
p = searchstr;
}
- if (!shortmess(SHM_SEARCHCOUNT)) {
+ if (!shortmess(SHM_SEARCHCOUNT) || cmd_silent) {
// Reserve enough space for the search pattern + offset +
// search stat. Use all the space available, so that the
// search state is right aligned. If there is not enough space
// msg_strtrunc() will shorten in the middle.
if (ui_has(kUIMessages)) {
len = 0; // adjusted below
- } else if (msg_scrolled != 0) {
+ } else if (msg_scrolled != 0 && !cmd_silent) {
// Use all the columns.
len = (Rows - msg_row) * Columns - 1;
} else {
@@ -1194,11 +1231,13 @@ int do_search(
xfree(msgbuf);
msgbuf = xmalloc(len);
- {
- memset(msgbuf, ' ', len);
- msgbuf[0] = dirc;
- msgbuf[len - 1] = NUL;
+ memset(msgbuf, ' ', len);
+ msgbuf[len - 1] = NUL;
+ // do not fill the msgbuf buffer, if cmd_silent is set, leave it
+ // empty for the search_stat feature.
+ if (!cmd_silent) {
+ msgbuf[0] = dirc;
if (utf_iscomposing(utf_ptr2char(p))) {
// Use a space to draw the composing char on.
msgbuf[1] = ' ';
@@ -1342,12 +1381,15 @@ int do_search(
// Show [1/15] if 'S' is not in 'shortmess'.
if ((options & SEARCH_ECHO)
&& messaging()
- && !(cmd_silent + msg_silent)
+ && !msg_silent
&& c != FAIL
&& !shortmess(SHM_SEARCHCOUNT)
&& msgbuf != NULL) {
search_stat(dirc, &pos, show_top_bot_msg, msgbuf,
- (count != 1 || has_offset));
+ (count != 1
+ || has_offset
+ || (!(fdo_flags & FDO_SEARCH)
+ && hasFolding(curwin->w_cursor.lnum, NULL, NULL))));
}
// The search command can be followed by a ';' to do another search.
@@ -1612,8 +1654,9 @@ static bool find_rawstring_end(char_u *linep, pos_T *startpos, pos_T *endpos)
if (lnum == endpos->lnum && (colnr_T)(p - line) >= endpos->col) {
break;
}
- if (*p == ')' && p[delim_len + 1] == '"'
- && STRNCMP(delim_copy, p + 1, delim_len) == 0) {
+ if (*p == ')'
+ && STRNCMP(delim_copy, p + 1, delim_len) == 0
+ && p[delim_len + 1] == '"') {
found = true;
break;
}
@@ -3393,7 +3436,6 @@ current_tagblock(
pos_T start_pos;
pos_T end_pos;
pos_T old_start, old_end;
- char_u *spat, *epat;
char_u *p;
char_u *cp;
int len;
@@ -3447,9 +3489,9 @@ again:
*/
for (long n = 0; n < count; n++) {
if (do_searchpair(
- (char_u *)"<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)",
- (char_u *)"",
- (char_u *)"</[^>]*>", BACKWARD, NULL, 0,
+ "<[^ \t>/!]\\+\\%(\\_s\\_[^>]\\{-}[^/]>\\|$\\|\\_s\\=>\\)",
+ "",
+ "</[^>]*>", BACKWARD, NULL, 0,
NULL, (linenr_T)0, 0L) <= 0) {
curwin->w_cursor = old_pos;
goto theend;
@@ -3471,12 +3513,15 @@ again:
curwin->w_cursor = old_pos;
goto theend;
}
- spat = xmalloc(len + 31);
- epat = xmalloc(len + 9);
- sprintf((char *)spat, "<%.*s\\>\\%%(\\s\\_[^>]\\{-}[^/]>\\|>\\)\\c", len, p);
- sprintf((char *)epat, "</%.*s>\\c", len, p);
-
- const int r = do_searchpair(spat, (char_u *)"", epat, FORWARD, NULL,
+ const size_t spat_len = len + 39;
+ char *const spat = xmalloc(spat_len);
+ const size_t epat_len = len + 9;
+ char *const epat = xmalloc(epat_len);
+ snprintf(spat, spat_len,
+ "<%.*s\\>\\%%(\\_s\\_[^>]\\{-}\\_[^/]>\\|\\_s\\?>\\)\\c", len, p);
+ snprintf(epat, epat_len, "</%.*s>\\c", len, p);
+
+ const int r = do_searchpair(spat, "", epat, FORWARD, NULL,
0, NULL, (linenr_T)0, 0L);
xfree(spat);
@@ -4054,7 +4099,7 @@ abort_search:
int
current_search(
long count,
- int forward // true for forward, false for backward
+ bool forward // true for forward, false for backward
)
{
bool old_p_ws = p_ws;
@@ -4069,6 +4114,11 @@ current_search(
pos_T pos; // position after the pattern
int result; // result of various function calls
+ // When searching forward and the cursor is at the start of the Visual
+ // area, skip the first search backward, otherwise it doesn't move.
+ const bool skip_first_backward = forward && VIsual_active
+ && lt(curwin->w_cursor, VIsual);
+
orig_pos = pos = curwin->w_cursor;
if (VIsual_active) {
// Searching further will extend the match.
@@ -4086,13 +4136,20 @@ current_search(
return FAIL; // pattern not found
}
- /*
- * The trick is to first search backwards and then search forward again,
- * so that a match at the current cursor position will be correctly
- * captured.
- */
+ // The trick is to first search backwards and then search forward again,
+ // so that a match at the current cursor position will be correctly
+ // captured. When "forward" is false do it the other way around.
for (int i = 0; i < 2; i++) {
- int dir = forward ? i : !i;
+ int dir;
+ if (forward) {
+ if (i == 0 && skip_first_backward) {
+ continue;
+ }
+ dir = i;
+ } else {
+ dir = !i;
+ }
+
int flags = 0;
if (!dir && !zero_width) {
@@ -4139,11 +4196,17 @@ current_search(
VIsual = start_pos;
}
- // put cursor on last character of match
+ // put the cursor after the match
curwin->w_cursor = end_pos;
if (lt(VIsual, end_pos) && forward) {
- dec_cursor();
- } else if (VIsual_active && lt(curwin->w_cursor, VIsual)) {
+ if (skip_first_backward) {
+ // put the cursor on the start of the match
+ curwin->w_cursor = pos;
+ } else {
+ // put the cursor on last character of match
+ dec_cursor();
+ }
+ } else if (VIsual_active && lt(curwin->w_cursor, VIsual) && forward) {
curwin->w_cursor = pos; // put the cursor on the start of the match
}
VIsual_active = true;
@@ -4216,7 +4279,8 @@ is_zero_width(char_u *pattern, int move, pos_T *cur, Direction direction)
if (nmatched != 0) {
break;
}
- } while (direction == FORWARD
+ } while (regmatch.regprog != NULL
+ && direction == FORWARD
? regmatch.startpos[0].col < pos.col
: regmatch.startpos[0].col > pos.col);
@@ -4335,7 +4399,9 @@ static void search_stat(int dirc, pos_T *pos,
len = STRLEN(t);
if (show_top_bot_msg && len + 2 < SEARCH_STAT_BUF_LEN) {
- STRCPY(t + len, " W");
+ memmove(t + 2, t, len);
+ t[0] = 'W';
+ t[1] = ' ';
len += 2;
}
@@ -4857,7 +4923,7 @@ search_line:
&& curwin != curwin_save && win_valid(curwin_save)) {
/* Return cursor to where we were */
validate_cursor();
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
win_enter(curwin_save, true);
}
break;
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 95257fe945..2444910bb3 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -2207,8 +2207,12 @@ static inline ShaDaWriteResult shada_read_when_writing(
shada_free_shada_entry(&entry);
break;
}
- hms_insert(&wms->hms[entry.data.history_item.histtype], entry, true,
- true);
+ if (wms->hms[entry.data.history_item.histtype].hmll.size != 0) {
+ hms_insert(&wms->hms[entry.data.history_item.histtype], entry, true,
+ true);
+ } else {
+ shada_free_shada_entry(&entry);
+ }
break;
}
case kSDItemRegister: {
@@ -4144,7 +4148,7 @@ static inline size_t shada_init_jumps(
}
const char *const fname = (char *) (fm.fmark.fnum == 0
? (fm.fname == NULL ? NULL : fm.fname)
- : buf->b_ffname);
+ : buf ? buf->b_ffname : NULL);
if (fname == NULL) {
continue;
}
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index ab5d04d39b..ffe51287c5 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -881,6 +881,17 @@ int sign_undefine_by_name(const char_u *name)
return OK;
}
+static void may_force_numberwidth_recompute(buf_T *buf, int unplace)
+{
+ FOR_ALL_TAB_WINDOWS(tp, wp)
+ if (wp->w_buffer == buf
+ && (wp->w_p_nu || wp->w_p_rnu)
+ && (unplace || wp->w_nrwidth_width < 2)
+ && (*wp->w_p_scl == 'n' && *(wp->w_p_scl + 1) == 'u')) {
+ wp->w_nrwidth_line_count = 0;
+ }
+}
+
/// List the signs matching 'name'
static void sign_list_by_name(char_u *name)
{
@@ -935,6 +946,10 @@ int sign_place(
}
if (lnum > 0) {
redraw_buf_line_later(buf, lnum);
+
+ // When displaying signs in the 'number' column, if the width of the
+ // number column is less than 2, then force recomputing the width.
+ may_force_numberwidth_recompute(buf, false);
} else {
EMSG2(_("E885: Not possible to change sign %s"), sign_name);
return FAIL;
@@ -964,6 +979,13 @@ int sign_unplace(int sign_id, char_u *sign_group, buf_T *buf, linenr_T atlnum)
redraw_buf_line_later(buf, lnum);
}
+ // When all the signs in a buffer are removed, force recomputing the
+ // number column width (if enabled) in all the windows displaying the
+ // buffer if 'signcolumn' is set to 'number' in that window.
+ if (buf->b_signlist == NULL) {
+ may_force_numberwidth_recompute(buf, true);
+ }
+
return OK;
}
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index 95948dac78..636c71657d 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -362,6 +362,8 @@ size_t spell_check(
size_t wrongcaplen = 0;
int lpi;
bool count_word = docount;
+ bool use_camel_case = *wp->w_s->b_p_spo != NUL;
+ bool camel_case = false;
// A word never starts at a space or a control character. Return quickly
// then, skipping over the character.
@@ -394,9 +396,23 @@ size_t spell_check(
mi.mi_word = ptr;
mi.mi_fend = ptr;
if (spell_iswordp(mi.mi_fend, wp)) {
+ bool this_upper = false; // init for gcc
+
+ if (use_camel_case) {
+ c = PTR2CHAR(mi.mi_fend);
+ this_upper = SPELL_ISUPPER(c);
+ }
+
do {
MB_PTR_ADV(mi.mi_fend);
- } while (*mi.mi_fend != NUL && spell_iswordp(mi.mi_fend, wp));
+ if (use_camel_case) {
+ const bool prev_upper = this_upper;
+ c = PTR2CHAR(mi.mi_fend);
+ this_upper = SPELL_ISUPPER(c);
+ camel_case = !prev_upper && this_upper;
+ }
+ } while (*mi.mi_fend != NUL && spell_iswordp(mi.mi_fend, wp)
+ && !camel_case);
if (capcol != NULL && *capcol == 0 && wp->w_s->b_cap_prog != NULL) {
// Check word starting with capital letter.
@@ -428,6 +444,11 @@ size_t spell_check(
(void)spell_casefold(ptr, (int)(mi.mi_fend - ptr), mi.mi_fword, MAXWLEN + 1);
mi.mi_fwordlen = (int)STRLEN(mi.mi_fword);
+ if (camel_case) {
+ // introduce a fake word end space into the folded word.
+ mi.mi_fword[mi.mi_fwordlen - 1] = ' ';
+ }
+
// The word is bad unless we recognize it.
mi.mi_result = SP_BAD;
mi.mi_result2 = SP_BAD;
@@ -2237,7 +2258,7 @@ char_u *did_set_spelllang(win_T *wp)
theend:
xfree(spl_copy);
recursive = false;
- redraw_win_later(wp, NOT_VALID);
+ redraw_later(wp, NOT_VALID);
return ret_msg;
}
@@ -4405,8 +4426,6 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char_u *fword, bool so
}
break;
- FALLTHROUGH;
-
case STATE_INS:
// Insert one byte. Repeat this for each possible byte at this
// node.
@@ -5663,6 +5682,9 @@ check_suggestions (
int len;
hlf_T attr;
+ if (gap->ga_len == 0) {
+ return;
+ }
stp = &SUG(*gap, 0);
for (int i = gap->ga_len - 1; i >= 0; --i) {
// Need to append what follows to check for "the the".
@@ -5765,14 +5787,14 @@ cleanup_suggestions (
)
FUNC_ATTR_NONNULL_ALL
{
- suggest_T *stp = &SUG(*gap, 0);
-
if (gap->ga_len > 0) {
// Sort the list.
qsort(gap->ga_data, (size_t)gap->ga_len, sizeof(suggest_T), sug_compare);
// Truncate the list to the number of suggestions that will be displayed.
if (gap->ga_len > keep) {
+ suggest_T *const stp = &SUG(*gap, 0);
+
for (int i = keep; i < gap->ga_len; i++) {
xfree(stp[i].st_word);
}
@@ -6855,7 +6877,7 @@ void ex_spelldump(exarg_T *eap)
if (curbuf->b_ml.ml_line_count > 1) {
ml_delete(curbuf->b_ml.ml_line_count, false);
}
- redraw_later(NOT_VALID);
+ redraw_later(curwin, NOT_VALID);
}
// Go through all possible words and:
diff --git a/src/nvim/spell_defs.h b/src/nvim/spell_defs.h
index 034c580b3e..05667f060e 100644
--- a/src/nvim/spell_defs.h
+++ b/src/nvim/spell_defs.h
@@ -119,6 +119,7 @@ struct slang_S {
bool sl_add; // true if it's a .add file.
char_u *sl_fbyts; // case-folded word bytes
+ long sl_fbyts_len; // length of sl_fbyts
idx_T *sl_fidxs; // case-folded word indexes
char_u *sl_kbyts; // keep-case word bytes
idx_T *sl_kidxs; // keep-case word indexes
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index 6b9348e55d..b415a4635b 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -628,6 +628,7 @@ spell_load_file (
case SP_OTHERERROR: {
emsgf(_("E5042: Failed to read spell file %s: %s"),
fname, strerror(ferror(fd)));
+ goto endFAIL;
}
case 0: {
break;
@@ -763,20 +764,24 @@ truncerr:
}
// <LWORDTREE>
- res = spell_read_tree(fd, &lp->sl_fbyts, &lp->sl_fidxs, false, 0);
- if (res != 0)
+ res = spell_read_tree(fd, &lp->sl_fbyts, &lp->sl_fbyts_len,
+ &lp->sl_fidxs, false, 0);
+ if (res != 0) {
goto someerror;
+ }
// <KWORDTREE>
- res = spell_read_tree(fd, &lp->sl_kbyts, &lp->sl_kidxs, false, 0);
- if (res != 0)
+ res = spell_read_tree(fd, &lp->sl_kbyts, NULL, &lp->sl_kidxs, false, 0);
+ if (res != 0) {
goto someerror;
+ }
// <PREFIXTREE>
- res = spell_read_tree(fd, &lp->sl_pbyts, &lp->sl_pidxs, true,
- lp->sl_prefixcnt);
- if (res != 0)
+ res = spell_read_tree(fd, &lp->sl_pbyts, NULL, &lp->sl_pidxs, true,
+ lp->sl_prefixcnt);
+ if (res != 0) {
goto someerror;
+ }
// For a new file link it in the list of spell files.
if (old_lp == NULL && lang != NULL) {
@@ -919,8 +924,8 @@ void suggest_load_files(void)
// <SUGWORDTREE>: <wordtree>
// Read the trie with the soundfolded words.
- if (spell_read_tree(fd, &slang->sl_sbyts, &slang->sl_sidxs,
- false, 0) != 0) {
+ if (spell_read_tree(fd, &slang->sl_sbyts, NULL, &slang->sl_sidxs,
+ false, 0) != 0) {
someerror:
EMSG2(_("E782: error while reading .sug file: %s"),
slang->sl_fname);
@@ -983,15 +988,17 @@ nextone:
static char_u *read_cnt_string(FILE *fd, int cnt_bytes, int *cntp)
{
int cnt = 0;
- int i;
char_u *str;
// read the length bytes, MSB first
- for (i = 0; i < cnt_bytes; ++i)
- cnt = (cnt << 8) + getc(fd);
- if (cnt < 0) {
- *cntp = SP_TRUNCERROR;
- return NULL;
+ for (int i = 0; i < cnt_bytes; i++) {
+ const int c = getc(fd);
+
+ if (c == EOF) {
+ *cntp = SP_TRUNCERROR;
+ return NULL;
+ }
+ cnt = (cnt << 8) + (unsigned)c;
}
*cntp = cnt;
if (cnt == 0)
@@ -1627,10 +1634,12 @@ static int
spell_read_tree (
FILE *fd,
char_u **bytsp,
+ long *bytsp_len,
idx_T **idxsp,
bool prefixtree, // true for the prefix tree
int prefixcnt // when "prefixtree" is true: prefix count
)
+ FUNC_ATTR_NONNULL_ARG(1, 2, 4)
{
int idx;
char_u *bp;
@@ -1650,6 +1659,9 @@ spell_read_tree (
// Allocate the byte array.
bp = xmalloc(len);
*bytsp = bp;
+ if (bytsp_len != NULL) {
+ *bytsp_len = len;
+ }
// Allocate the index array.
ip = xcalloc(len, sizeof(*ip));
@@ -3037,9 +3049,9 @@ static int spell_read_dic(spellinfo_T *spin, char_u *fname, afffile_T *affile)
spin->si_msg_count = 999999;
// Read and ignore the first line: word count.
- (void)vim_fgets(line, MAXLINELEN, fd);
- if (!ascii_isdigit(*skipwhite(line)))
+ if (vim_fgets(line, MAXLINELEN, fd) || !ascii_isdigit(*skipwhite(line))) {
EMSG2(_("E760: No word count in %s"), fname);
+ }
// Read all the lines in the file one by one.
// The words are converted to 'encoding' here, before being added to
@@ -4847,10 +4859,10 @@ static int sug_filltree(spellinfo_T *spin, slang_T *slang)
spin->si_blocks_cnt = 0;
// Skip over any other NUL bytes (same word with different
- // flags).
- while (byts[n + 1] == 0) {
- ++n;
- ++curi[depth];
+ // flags). But don't go over the end.
+ while (n + 1 < slang->sl_fbyts_len && byts[n + 1] == 0) {
+ n++;
+ curi[depth]++;
}
} else {
// Normal char, go one level deeper.
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index 4aa7c21ce4..5ce126a593 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -73,9 +73,9 @@ struct hl_group {
RgbValue sg_rgb_fg; ///< RGB foreground color
RgbValue sg_rgb_bg; ///< RGB background color
RgbValue sg_rgb_sp; ///< RGB special color
- uint8_t *sg_rgb_fg_name; ///< RGB foreground color name
- uint8_t *sg_rgb_bg_name; ///< RGB background color name
- uint8_t *sg_rgb_sp_name; ///< RGB special color name
+ char *sg_rgb_fg_name; ///< RGB foreground color name
+ char *sg_rgb_bg_name; ///< RGB background color name
+ char *sg_rgb_sp_name; ///< RGB special color name
int sg_blend; ///< blend level (0-100 inclusive), -1 if unset
};
@@ -3147,7 +3147,7 @@ static void syn_cmd_spell(exarg_T *eap, int syncing)
}
// assume spell checking changed, force a redraw
- redraw_win_later(curwin, NOT_VALID);
+ redraw_later(curwin, NOT_VALID);
}
/// Handle ":syntax iskeyword" command.
@@ -3187,7 +3187,7 @@ static void syn_cmd_iskeyword(exarg_T *eap, int syncing)
curbuf->b_p_isk = save_isk;
}
}
- redraw_win_later(curwin, NOT_VALID);
+ redraw_later(curwin, NOT_VALID);
}
/*
@@ -4296,8 +4296,9 @@ static void syn_cmd_include(exarg_T *eap, int syncing)
current_syn_inc_tag = ++running_syn_inc_tag;
prev_toplvl_grp = curwin->w_s->b_syn_topgrp;
curwin->w_s->b_syn_topgrp = sgl_id;
- if (source ? do_source(eap->arg, false, DOSO_NONE) == FAIL
- : source_runtime(eap->arg, DIP_ALL) == FAIL) {
+ if (source
+ ? do_source(eap->arg, false, DOSO_NONE) == FAIL
+ : source_in_path(p_rtp, eap->arg, DIP_ALL) == FAIL) {
EMSG2(_(e_notopen), eap->arg);
}
curwin->w_s->b_syn_topgrp = prev_toplvl_grp;
@@ -5588,9 +5589,11 @@ void ex_ownsyntax(exarg_T *eap)
hash_init(&curwin->w_s->b_keywtab_ic);
// TODO: Keep the spell checking as it was. NOLINT(readability/todo)
curwin->w_p_spell = false; // No spell checking
+ // make sure option values are "empty_option" instead of NULL
clear_string_option(&curwin->w_s->b_p_spc);
clear_string_option(&curwin->w_s->b_p_spf);
clear_string_option(&curwin->w_s->b_p_spl);
+ clear_string_option(&curwin->w_s->b_p_spo);
clear_string_option(&curwin->w_s->b_syn_isk);
}
@@ -6897,7 +6900,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
}
}
} else if (strcmp(key, "GUIFG") == 0) {
- char_u **const namep = &HL_TABLE()[idx].sg_rgb_fg_name;
+ char **namep = &HL_TABLE()[idx].sg_rgb_fg_name;
if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
if (!init) {
@@ -6907,8 +6910,8 @@ void do_highlight(const char *line, const bool forceit, const bool init)
if (*namep == NULL || STRCMP(*namep, arg) != 0) {
xfree(*namep);
if (strcmp(arg, "NONE") != 0) {
- *namep = (char_u *)xstrdup(arg);
- HL_TABLE()[idx].sg_rgb_fg = name_to_color((char_u *)arg);
+ *namep = xstrdup(arg);
+ HL_TABLE()[idx].sg_rgb_fg = name_to_color(arg);
} else {
*namep = NULL;
HL_TABLE()[idx].sg_rgb_fg = -1;
@@ -6921,7 +6924,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
normal_fg = HL_TABLE()[idx].sg_rgb_fg;
}
} else if (STRCMP(key, "GUIBG") == 0) {
- char_u **const namep = &HL_TABLE()[idx].sg_rgb_bg_name;
+ char **const namep = &HL_TABLE()[idx].sg_rgb_bg_name;
if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
if (!init) {
@@ -6931,8 +6934,8 @@ void do_highlight(const char *line, const bool forceit, const bool init)
if (*namep == NULL || STRCMP(*namep, arg) != 0) {
xfree(*namep);
if (STRCMP(arg, "NONE") != 0) {
- *namep = (char_u *)xstrdup(arg);
- HL_TABLE()[idx].sg_rgb_bg = name_to_color((char_u *)arg);
+ *namep = xstrdup(arg);
+ HL_TABLE()[idx].sg_rgb_bg = name_to_color(arg);
} else {
*namep = NULL;
HL_TABLE()[idx].sg_rgb_bg = -1;
@@ -6945,7 +6948,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
normal_bg = HL_TABLE()[idx].sg_rgb_bg;
}
} else if (strcmp(key, "GUISP") == 0) {
- char_u **const namep = &HL_TABLE()[idx].sg_rgb_sp_name;
+ char **const namep = &HL_TABLE()[idx].sg_rgb_sp_name;
if (!init || !(HL_TABLE()[idx].sg_set & SG_GUI)) {
if (!init) {
@@ -6955,8 +6958,8 @@ void do_highlight(const char *line, const bool forceit, const bool init)
if (*namep == NULL || STRCMP(*namep, arg) != 0) {
xfree(*namep);
if (strcmp(arg, "NONE") != 0) {
- *namep = (char_u *)xstrdup(arg);
- HL_TABLE()[idx].sg_rgb_sp = name_to_color((char_u *)arg);
+ *namep = xstrdup(arg);
+ HL_TABLE()[idx].sg_rgb_sp = name_to_color(arg);
} else {
*namep = NULL;
HL_TABLE()[idx].sg_rgb_sp = -1;
@@ -7150,13 +7153,32 @@ static void highlight_list_one(const int id)
msg_outtrans(HL_TABLE()[HL_TABLE()[id - 1].sg_link - 1].sg_name);
}
- if (!didh)
- highlight_list_arg(id, didh, LIST_STRING, 0, (char_u *)"cleared", "");
+ if (!didh) {
+ highlight_list_arg(id, didh, LIST_STRING, 0, "cleared", "");
+ }
if (p_verbose > 0) {
last_set_msg(sgp->sg_script_ctx);
}
}
+Dictionary get_global_hl_defs(void)
+{
+ Dictionary rv = ARRAY_DICT_INIT;
+ for (int i = 1; i <= highlight_ga.ga_len && !got_int; i++) {
+ Dictionary attrs = ARRAY_DICT_INIT;
+ struct hl_group *h = &HL_TABLE()[i - 1];
+ if (h->sg_attr > 0) {
+ attrs = hlattrs2dict(syn_attr2entry(h->sg_attr), true);
+ } else if (h->sg_link > 0) {
+ const char *link = (const char *)HL_TABLE()[h->sg_link - 1].sg_name;
+ PUT(attrs, "link", STRING_OBJ(cstr_to_string(link)));
+ }
+ PUT(rv, (const char *)h->sg_name, DICTIONARY_OBJ(attrs));
+ }
+
+ return rv;
+}
+
/// Outputs a highlight when doing ":hi MyHighlight"
///
/// @param type one of \ref LIST_XXX
@@ -7164,15 +7186,15 @@ static void highlight_list_one(const int id)
/// @param sarg string used if \p type == LIST_STRING
static bool highlight_list_arg(
const int id, bool didh, const int type, int iarg,
- char_u *const sarg, const char *const name)
+ char *const sarg, const char *const name)
{
- char_u buf[100];
+ char buf[100];
if (got_int) {
return false;
}
if (type == LIST_STRING ? (sarg != NULL) : (iarg != 0)) {
- char_u *ts = buf;
+ char *ts = buf;
if (type == LIST_INT) {
snprintf((char *)buf, sizeof(buf), "%d", iarg - 1);
} else if (type == LIST_STRING) {
@@ -7189,15 +7211,15 @@ static bool highlight_list_arg(
}
}
- (void)syn_list_header(didh, (int)(vim_strsize(ts) + STRLEN(name) + 1), id,
- false);
+ (void)syn_list_header(didh, (int)(vim_strsize((char_u *)ts) + STRLEN(name)
+ + 1), id, false);
didh = true;
if (!got_int) {
if (*name != NUL) {
MSG_PUTS_ATTR(name, HL_ATTR(HLF_D));
MSG_PUTS_ATTR("=", HL_ATTR(HLF_D));
}
- msg_outtrans(ts);
+ msg_outtrans((char_u *)ts);
}
}
return didh;
@@ -7376,7 +7398,7 @@ static void set_hl_attr(int idx)
at_en.rgb_sp_color = sgp->sg_rgb_sp_name ? sgp->sg_rgb_sp : -1;
at_en.hl_blend = sgp->sg_blend;
- sgp->sg_attr = hl_get_syn_attr(idx+1, at_en);
+ sgp->sg_attr = hl_get_syn_attr(0, idx+1, at_en);
// a cursor style uses this syn_id, make sure its attribute is updated.
if (cursor_mode_uses_syn_id(idx+1)) {
@@ -7539,11 +7561,17 @@ int syn_id2attr(int hl_id)
struct hl_group *sgp;
hl_id = syn_get_final_id(hl_id);
- sgp = &HL_TABLE()[hl_id - 1]; /* index is ID minus one */
+ int attr = ns_get_hl(-1, hl_id, false);
+ if (attr >= 0) {
+ return attr;
+ }
+ sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one
return sgp->sg_attr;
}
+
+
/*
* Translate a group ID to the final group ID (following links).
*/
@@ -7560,9 +7588,22 @@ int syn_get_final_id(int hl_id)
* Look out for loops! Break after 100 links.
*/
for (count = 100; --count >= 0; ) {
- sgp = &HL_TABLE()[hl_id - 1]; /* index is ID minus one */
- if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len)
+ // ACHTUNG: when using "tmp" attribute (no link) the function might be
+ // called twice. it needs be smart enough to remember attr only to
+ // syn_id2attr time
+ int check = ns_get_hl(-1, hl_id, true);
+ if (check == 0) {
+ return 0; // how dare! it broke the link!
+ } else if (check > 0) {
+ hl_id = check;
+ continue;
+ }
+
+
+ sgp = &HL_TABLE()[hl_id - 1]; // index is ID minus one
+ if (sgp->sg_link == 0 || sgp->sg_link > highlight_ga.ga_len) {
break;
+ }
hl_id = sgp->sg_link;
}
@@ -8492,7 +8533,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 char_u *name)
+RgbValue name_to_color(const char *name)
{
if (name[0] == '#' && isxdigit(name[1]) && isxdigit(name[2])
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index 81d1ef4c9f..c6b1a0d04c 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -2903,7 +2903,7 @@ static int jumpto_tag(
&& curwin != curwin_save && win_valid(curwin_save)) {
/* Return cursor to where we were */
validate_cursor();
- redraw_later(VALID);
+ redraw_later(curwin, VALID);
win_enter(curwin_save, true);
}
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 52d3eef810..39e2ca6171 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -233,7 +233,7 @@ Terminal *terminal_open(TerminalOptions opts)
snprintf(var, sizeof(var), "terminal_color_%d", i);
char *name = get_config_string(var);
if (name) {
- color_val = name_to_color((uint8_t *)name);
+ color_val = name_to_color(name);
xfree(name);
if (color_val != -1) {
@@ -1060,7 +1060,7 @@ static bool send_mouse_event(Terminal *term, int c)
curwin->w_redr_status = true;
curwin = save_curwin;
curbuf = curwin->w_buffer;
- redraw_win_later(mouse_win, NOT_VALID);
+ redraw_later(mouse_win, NOT_VALID);
invalidate_terminal(term, -1, -1);
// Only need to exit focus if the scrolled window is the terminal window
return mouse_win == curwin;
diff --git a/src/nvim/testdir/check.vim b/src/nvim/testdir/check.vim
new file mode 100644
index 0000000000..7f6b7dcfec
--- /dev/null
+++ b/src/nvim/testdir/check.vim
@@ -0,0 +1,99 @@
+source shared.vim
+source term_util.vim
+
+" Command to check for the presence of a feature.
+command -nargs=1 CheckFeature call CheckFeature(<f-args>)
+func CheckFeature(name)
+ if !has(a:name)
+ throw 'Skipped: ' .. a:name .. ' feature missing'
+ endif
+endfunc
+
+" Command to check for the presence of a working option.
+command -nargs=1 CheckOption call CheckOption(<f-args>)
+func CheckOption(name)
+ if !exists('+' .. a:name)
+ throw 'Skipped: ' .. a:name .. ' option not supported'
+ endif
+endfunc
+
+" Command to check for the presence of a function.
+command -nargs=1 CheckFunction call CheckFunction(<f-args>)
+func CheckFunction(name)
+ if !exists('*' .. a:name)
+ throw 'Skipped: ' .. a:name .. ' function missing'
+ endif
+endfunc
+
+" Command to check for the presence of python. Argument should have been
+" obtained with PythonProg()
+func CheckPython(name)
+ if a:name == ''
+ throw 'Skipped: python command not available'
+ endif
+endfunc
+
+" Command to check for running on MS-Windows
+command CheckMSWindows call CheckMSWindows()
+func CheckMSWindows()
+ if !has('win32')
+ throw 'Skipped: only works on MS-Windows'
+ endif
+endfunc
+
+" Command to check for running on Unix
+command CheckUnix call CheckUnix()
+func CheckUnix()
+ if !has('unix')
+ throw 'Skipped: only works on Unix'
+ endif
+endfunc
+
+" Command to check that making screendumps is supported.
+" Caller must source screendump.vim
+command CheckScreendump call CheckScreendump()
+func CheckScreendump()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+ endif
+endfunc
+
+" Command to check that we can Run Vim in a terminal window
+command CheckRunVimInTerminal call CheckRunVimInTerminal()
+func CheckRunVimInTerminal()
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot run Vim in a terminal window'
+ endif
+endfunc
+
+" Command to check that we can run the GUI
+command CheckCanRunGui call CheckCanRunGui()
+func CheckCanRunGui()
+ if !has('gui') || ($DISPLAY == "" && !has('gui_running'))
+ throw 'Skipped: cannot start the GUI'
+ endif
+endfunc
+
+" Command to check that not currently using the GUI
+command CheckNotGui call CheckNotGui()
+func CheckNotGui()
+ if has('gui_running')
+ throw 'Skipped: only works in the terminal'
+ endif
+endfunc
+
+" Command to check that the current language is English
+command CheckEnglish call CheckEnglish()
+func CheckEnglish()
+ if v:lang != "C" && v:lang !~ '^[Ee]n'
+ throw 'Skipped: only works in English language environment'
+ endif
+endfunc
+
+" Command to check for NOT running on MS-Windows
+command CheckNotMSWindows call CheckNotMSWindows()
+func CheckNotMSWindows()
+ if has('win32')
+ throw 'Skipped: does not work on MS-Windows'
+ endif
+endfunc
diff --git a/src/nvim/testdir/runnvim.sh b/src/nvim/testdir/runnvim.sh
index 72f9254635..25cb8437b4 100755
--- a/src/nvim/testdir/runnvim.sh
+++ b/src/nvim/testdir/runnvim.sh
@@ -66,7 +66,7 @@ main() {(
fi
fi
if test "$FAILED" = 1 ; then
- travis_fold start "$NVIM_TEST_CURRENT_SUITE/$test_name"
+ ci_fold start "$NVIM_TEST_CURRENT_SUITE/$test_name"
fi
valgrind_check .
if test -n "$LOG_DIR" ; then
@@ -78,7 +78,7 @@ main() {(
fi
rm -f "$tlog"
if test "$FAILED" = 1 ; then
- travis_fold end "$NVIM_TEST_CURRENT_SUITE/$test_name"
+ ci_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
diff --git a/src/nvim/testdir/runtest.vim b/src/nvim/testdir/runtest.vim
index e249d499c4..b02514143c 100644
--- a/src/nvim/testdir/runtest.vim
+++ b/src/nvim/testdir/runtest.vim
@@ -7,6 +7,19 @@
" ../vim -u NONE -S runtest.vim test_channel.vim open_delay
" The output can be found in the "messages" file.
"
+" If the environment variable $TEST_FILTER is set then only test functions
+" matching this pattern are executed. E.g. for sh/bash:
+" export TEST_FILTER=Test_channel
+" For csh:
+" setenv TEST_FILTER Test_channel
+"
+" To ignore failure for tests that are known to fail in a certain environment,
+" set $TEST_MAY_FAIL to a comma separated list of function names. E.g. for
+" sh/bash:
+" export TEST_MAY_FAIL=Test_channel_one,Test_channel_other
+" The failure report will then not be included in the test.log file and
+" "make test" will not fail.
+"
" The test script may contain anything, only functions that start with
" "Test_" are special. These will be invoked and should contain assert
" functions. See test_assert.vim for an example.
@@ -65,10 +78,17 @@ set encoding=utf-8
let s:test_script_fname = expand('%')
au! SwapExists * call HandleSwapExists()
func HandleSwapExists()
- " Only ignore finding a swap file for the test script (the user might be
+ if exists('g:ignoreSwapExists')
+ return
+ endif
+ " Ignore finding a swap file for the test script (the user might be
" editing it and do ":make test_name") and the output file.
+ " Report finding another swap file and chose 'q' to avoid getting stuck.
if expand('<afile>') == 'messages' || expand('<afile>') =~ s:test_script_fname
let v:swapchoice = 'e'
+ else
+ call assert_report('Unexpected swap file: ' .. v:swapname)
+ let v:swapchoice = 'q'
endif
endfunc
@@ -84,6 +104,13 @@ let &runtimepath .= ','.expand($BUILD_DIR).'/runtime/'
" Always use forward slashes.
set shellslash
+let s:t_bold = &t_md
+let s:t_normal = &t_me
+if has('win32')
+ " avoid prompt that is long or contains a line break
+ let $PROMPT = '$P$G'
+endif
+
" Prepare for calling test_garbagecollect_now().
let v:testing = 1
@@ -131,13 +158,6 @@ func RunTheTest(test)
endtry
endif
- let message = 'Executed ' . a:test
- if has('reltime')
- let message ..= ' in ' .. reltimestr(reltime(func_start)) .. ' seconds'
- endif
- call add(s:messages, message)
- let s:done += 1
-
if a:test =~ 'Test_nocatch_'
" Function handles errors itself. This avoids skipping commands after the
" error.
@@ -191,13 +211,34 @@ func RunTheTest(test)
endwhile
exe 'cd ' . save_cwd
+
+ let message = 'Executed ' . a:test
+ if has('reltime')
+ let message ..= repeat(' ', 50 - len(message))
+ let time = reltime(func_start)
+ if has('float') && reltimefloat(time) > 0.1
+ let message = s:t_bold .. message
+ endif
+ let message ..= ' in ' .. reltimestr(time) .. ' seconds'
+ if has('float') && reltimefloat(time) > 0.1
+ let message ..= s:t_normal
+ endif
+ endif
+ call add(s:messages, message)
+ let s:done += 1
endfunc
-func AfterTheTest()
+func AfterTheTest(func_name)
if len(v:errors) > 0
- let s:fail += 1
- call add(s:errors, 'Found errors in ' . s:test . ':')
- call extend(s:errors, v:errors)
+ if match(s:may_fail_list, '^' .. a:func_name) >= 0
+ let s:fail_expected += 1
+ call add(s:errors_expected, 'Found errors in ' . s:test . ':')
+ call extend(s:errors_expected, v:errors)
+ else
+ let s:fail += 1
+ call add(s:errors, 'Found errors in ' . s:test . ':')
+ call extend(s:errors, v:errors)
+ endif
let v:errors = []
endif
endfunc
@@ -213,7 +254,7 @@ endfunc
" This function can be called by a test if it wants to abort testing.
func FinishTesting()
- call AfterTheTest()
+ call AfterTheTest('')
" Don't write viminfo on exit.
set viminfo=
@@ -221,7 +262,7 @@ func FinishTesting()
" Clean up files created by setup.vim
call delete('XfakeHOME', 'rf')
- if s:fail == 0
+ if s:fail == 0 && s:fail_expected == 0
" Success, create the .res file so that make knows it's done.
exe 'split ' . fnamemodify(g:testname, ':r') . '.res'
write
@@ -237,12 +278,21 @@ func FinishTesting()
endif
if s:done == 0
- let message = 'NO tests executed'
+ if s:filtered > 0
+ let message = "NO tests match $TEST_FILTER: '" .. $TEST_FILTER .. "'"
+ else
+ let message = 'NO tests executed'
+ endif
else
+ if s:filtered > 0
+ call add(s:messages, "Filtered " .. s:filtered .. " tests with $TEST_FILTER")
+ endif
let message = 'Executed ' . s:done . (s:done > 1 ? ' tests' : ' test')
endif
- if has('reltime')
+ if s:done > 0 && has('reltime')
+ let message = s:t_bold .. message .. repeat(' ', 40 - len(message))
let message ..= ' in ' .. reltimestr(reltime(s:start_time)) .. ' seconds'
+ let message ..= s:t_normal
endif
echo message
call add(s:messages, message)
@@ -252,6 +302,12 @@ func FinishTesting()
call add(s:messages, message)
call extend(s:messages, s:errors)
endif
+ if s:fail_expected > 0
+ let message = s:fail_expected . ' FAILED (matching $TEST_MAY_FAIL):'
+ echo message
+ call add(s:messages, message)
+ call extend(s:messages, s:errors_expected)
+ endif
" Add SKIPPED messages
call extend(s:messages, s:skipped)
@@ -271,11 +327,13 @@ endfunc
let g:testname = expand('%')
let s:done = 0
let s:fail = 0
+let s:fail_expected = 0
let s:errors = []
+let s:errors_expected = []
let s:messages = []
let s:skipped = []
if expand('%') =~ 'test_vimscript.vim'
- " this test has intentional s:errors, don't use try/catch.
+ " this test has intentional errors, don't use try/catch.
source %
else
try
@@ -306,11 +364,12 @@ let s:flaky_tests = [
\ 'Test_repeat_three()',
\ 'Test_state()',
\ 'Test_stop_all_in_callback()',
- \ 'Test_term_mouse_double_click_to_create_tab',
+ \ 'Test_term_mouse_double_click_to_create_tab()',
\ 'Test_term_mouse_multiple_clicks_to_visually_select()',
\ 'Test_terminal_composing_unicode()',
\ 'Test_terminal_redir_file()',
\ 'Test_terminal_tmap()',
+ \ 'Test_termwinscroll()',
\ 'Test_with_partial_callback()',
\ ]
@@ -330,8 +389,17 @@ endif
" If the environment variable $TEST_FILTER is set then filter the function
" names against it.
+let s:filtered = 0
if $TEST_FILTER != ''
+ let s:filtered = len(s:tests)
let s:tests = filter(s:tests, 'v:val =~ $TEST_FILTER')
+ let s:filtered -= len(s:tests)
+endif
+
+let s:may_fail_list = []
+if $TEST_MAY_FAIL != ''
+ " Split the list at commas and add () to make it match s:test.
+ let s:may_fail_list = split($TEST_MAY_FAIL, ',')->map({i, v -> v .. '()'})
endif
" Execute the tests in alphabetical order.
@@ -383,7 +451,7 @@ for s:test in sort(s:tests)
endwhile
endif
- call AfterTheTest()
+ call AfterTheTest(s:test)
endfor
call FinishTesting()
diff --git a/src/nvim/testdir/shared.vim b/src/nvim/testdir/shared.vim
index 41ff9b2bd6..0e20ac1593 100644
--- a/src/nvim/testdir/shared.vim
+++ b/src/nvim/testdir/shared.vim
@@ -56,6 +56,9 @@ endfunc
" Run "cmd". Returns the job if using a job.
func RunCommand(cmd)
+ " Running an external command can occasionally be slow or fail.
+ let g:test_is_flaky = 1
+
let job = 0
if has('job')
let job = job_start(a:cmd, {"stoponexit": "hup"})
@@ -240,7 +243,7 @@ func s:feedkeys(timer)
call feedkeys('x', 'nt')
endfunc
-" Get $VIMPROG to run Vim executable.
+" Get $VIMPROG to run the Vim executable.
" The Makefile writes it as the first line in the "vimcmd" file.
" Nvim: uses $NVIM_TEST_ARG0.
func GetVimProg()
@@ -271,7 +274,7 @@ func GetVimCommand(...)
let cmd = cmd . ' -u ' . name
endif
let cmd .= ' --headless -i NONE'
- let cmd = substitute(cmd, 'VIMRUNTIME=.*VIMRUNTIME;', '', '')
+ let cmd = substitute(cmd, 'VIMRUNTIME=\S\+', '', '')
" If using valgrind, make sure every run uses a different log file.
if cmd =~ 'valgrind.*--log-file='
@@ -288,12 +291,22 @@ func GetVimCommandClean()
let cmd = substitute(cmd, '-u NONE', '--clean', '')
let cmd = substitute(cmd, '--headless', '', '')
+ " Force using utf-8, Vim may pick up something else from the environment.
+ " let cmd ..= ' --cmd "set enc=utf8" '
+
" Optionally run Vim under valgrind
" let cmd = 'valgrind --tool=memcheck --leak-check=yes --num-callers=25 --log-file=valgrind ' . cmd
return cmd
endfunc
+" Get the command to run Vim, with --clean, and force to run in terminal so it
+" won't start a new GUI.
+func GetVimCommandCleanTerm()
+ " Add -v to have gvim run in the terminal (if possible)
+ return GetVimCommandClean() .. ' -v '
+endfunc
+
" Run Vim, using the "vimcmd" file and "-u NORC".
" "before" is a list of Vim commands to be executed before loading plugins.
" "after" is a list of Vim commands to be executed after loading plugins.
@@ -330,6 +343,16 @@ func RunVimPiped(before, after, arguments, pipecmd)
return 1
endfunc
-func CanRunGui()
- return has('gui') && ($DISPLAY != "" || has('gui_running'))
+" Get all messages but drop the maintainer entry.
+func GetMessages()
+ redir => result
+ redraw | messages
+ redir END
+ let msg_list = split(result, "\n")
+ " if msg_list->len() > 0 && msg_list[0] =~ 'Messages maintainer:'
+ " return msg_list[1:]
+ " endif
+ return msg_list
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/summarize.vim b/src/nvim/testdir/summarize.vim
index 7f8f758a71..da5856a2e7 100644
--- a/src/nvim/testdir/summarize.vim
+++ b/src/nvim/testdir/summarize.vim
@@ -1,3 +1,4 @@
+set cpo&vim
if 1
" This is executed only with the eval feature
set nocompatible
diff --git a/src/nvim/testdir/test_alot.vim b/src/nvim/testdir/test_alot.vim
index 5668f45dea..7647475427 100644
--- a/src/nvim/testdir/test_alot.vim
+++ b/src/nvim/testdir/test_alot.vim
@@ -13,7 +13,6 @@ source test_ex_undo.vim
source test_ex_z.vim
source test_execute_func.vim
source test_expand_func.vim
-source test_expr.vim
source test_feedkeys.vim
source test_filter_cmd.vim
source test_filter_map.vim
@@ -50,6 +49,7 @@ source test_tagjump.vim
source test_taglist.vim
source test_true_false.vim
source test_unlet.vim
+source test_version.vim
source test_virtualedit.vim
source test_window_cmd.vim
source test_wnext.vim
diff --git a/src/nvim/testdir/test_arglist.vim b/src/nvim/testdir/test_arglist.vim
index 6e7583ade3..92fedf9bfb 100644
--- a/src/nvim/testdir/test_arglist.vim
+++ b/src/nvim/testdir/test_arglist.vim
@@ -397,9 +397,15 @@ func Test_argdelete()
last
argdelete %
call assert_equal(['b'], argv())
- call assert_fails('argdelete', 'E471:')
+ call assert_fails('argdelete', 'E610:')
call assert_fails('1,100argdelete', 'E16:')
- %argd
+
+ call Reset_arglist()
+ args a b c d
+ next
+ argdel
+ call Assert_argc(['a', 'c', 'd'])
+ %argdel
endfunc
func Test_argdelete_completion()
diff --git a/src/nvim/testdir/test_autocmd.vim b/src/nvim/testdir/test_autocmd.vim
index d116246ef3..04a678eeb8 100644
--- a/src/nvim/testdir/test_autocmd.vim
+++ b/src/nvim/testdir/test_autocmd.vim
@@ -1,6 +1,8 @@
" Tests for autocommands
source shared.vim
+source check.vim
+source term_util.vim
func! s:cleanup_buffers() abort
for bnr in range(1, bufnr('$'))
@@ -1125,7 +1127,7 @@ func Test_change_mark_in_autocmds()
write
au! BufWritePre
- if executable('cat')
+ if has('unix')
write XtestFilter
write >> XtestFilter
@@ -1735,6 +1737,35 @@ func Test_throw_in_BufWritePre()
au! throwing
endfunc
+func Test_autocmd_CmdWinEnter()
+ CheckRunVimInTerminal
+ " There is not cmdwin switch, so
+ " test for cmdline_hist
+ " (both are available with small builds)
+ CheckFeature cmdline_hist
+ let lines =<< trim END
+ let b:dummy_var = 'This is a dummy'
+ autocmd CmdWinEnter * quit
+ let winnr = winnr('$')
+ END
+ let filename='XCmdWinEnter'
+ call writefile(lines, filename)
+ let buf = RunVimInTerminal('-S '.filename, #{rows: 6})
+
+ call term_sendkeys(buf, "q:")
+ call term_wait(buf)
+ call term_sendkeys(buf, ":echo b:dummy_var\<cr>")
+ call WaitForAssert({-> assert_match('^This is a dummy', term_getline(buf, 6))}, 1000)
+ call term_sendkeys(buf, ":echo &buftype\<cr>")
+ call WaitForAssert({-> assert_notmatch('^nofile', term_getline(buf, 6))}, 1000)
+ call term_sendkeys(buf, ":echo winnr\<cr>")
+ call WaitForAssert({-> assert_match('^1', term_getline(buf, 6))}, 1000)
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete(filename)
+endfunc
+
func Test_FileChangedShell_reload()
if !has('unix')
return
@@ -1866,4 +1897,17 @@ func Test_autocmd_FileReadCmd()
delfunc ReadFileCmd
endfunc
+" Tests for SigUSR1 autocmd event, which is only available on posix systems.
+func Test_autocmd_sigusr1()
+ CheckUnix
+
+ let g:sigusr1_passed = 0
+ au Signal SIGUSR1 let g:sigusr1_passed = 1
+ call system('/bin/kill -s usr1 ' . getpid())
+ call WaitForAssert({-> assert_true(g:sigusr1_passed)})
+
+ au! Signal
+ unlet g:sigusr1_passed
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_backup.vim b/src/nvim/testdir/test_backup.vim
index fa10430613..ce2bfe72bc 100644
--- a/src/nvim/testdir/test_backup.vim
+++ b/src/nvim/testdir/test_backup.vim
@@ -1,7 +1,7 @@
" Tests for the backup function
func Test_backup()
- set backup backupdir=.
+ set backup backupdir=. backupskip=
new
call setline(1, ['line1', 'line2'])
:f Xbackup.txt
@@ -12,13 +12,13 @@ func Test_backup()
let l = readfile('Xbackup.txt~')
call assert_equal(['line1', 'line2'], l)
bw!
- set backup&vim backupdir&vim
+ set backup&vim backupdir&vim backupskip&vim
call delete('Xbackup.txt')
call delete('Xbackup.txt~')
endfunc
func Test_backup2()
- set backup backupdir=.//
+ set backup backupdir=.// backupskip=
new
call setline(1, ['line1', 'line2', 'line3'])
:f Xbackup.txt
@@ -29,16 +29,16 @@ func Test_backup2()
sp *Xbackup.txt~
call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
let f=expand('%')
- call assert_match('src%nvim%testdir%Xbackup.txt\~', f)
+ call assert_match('%testdir%Xbackup.txt\~', f)
bw!
bw!
call delete('Xbackup.txt')
call delete(f)
- set backup&vim backupdir&vim
+ set backup&vim backupdir&vim backupskip&vim
endfunc
func Test_backup2_backupcopy()
- set backup backupdir=.// backupcopy=yes
+ set backup backupdir=.// backupcopy=yes backupskip=
new
call setline(1, ['line1', 'line2', 'line3'])
:f Xbackup.txt
@@ -49,10 +49,10 @@ func Test_backup2_backupcopy()
sp *Xbackup.txt~
call assert_equal(['line1', 'line2', 'line3'], getline(1,'$'))
let f=expand('%')
- call assert_match('src%nvim%testdir%Xbackup.txt\~', f)
+ call assert_match('%testdir%Xbackup.txt\~', f)
bw!
bw!
call delete('Xbackup.txt')
call delete(f)
- set backup&vim backupdir&vim backupcopy&vim
+ set backup&vim backupdir&vim backupcopy&vim backupskip&vim
endfunc
diff --git a/src/nvim/testdir/test_cindent.vim b/src/nvim/testdir/test_cindent.vim
index debc9da46d..b6c2d1467e 100644
--- a/src/nvim/testdir/test_cindent.vim
+++ b/src/nvim/testdir/test_cindent.vim
@@ -127,4 +127,40 @@ func Test_cindent_case()
bwipe!
endfunc
+func Test_cindent_pragma()
+ new
+ setl cindent ts=4 sw=4
+ setl cino=Ps
+
+ let code =<< trim [CODE]
+ {
+ #pragma omp parallel
+ {
+ #pragma omp task
+ foo();
+ # pragma omp taskwait
+ }
+ }
+ [CODE]
+
+ call append(0, code)
+ normal gg
+ normal =G
+
+ let expected =<< trim [CODE]
+ {
+ #pragma omp parallel
+ {
+ #pragma omp task
+ foo();
+ # pragma omp taskwait
+ }
+ }
+
+ [CODE]
+
+ call assert_equal(expected, getline(1, '$'))
+ enew! | close
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_cjk_linebreak.vim b/src/nvim/testdir/test_cjk_linebreak.vim
new file mode 100644
index 0000000000..dfaa8fa1af
--- /dev/null
+++ b/src/nvim/testdir/test_cjk_linebreak.vim
@@ -0,0 +1,97 @@
+scriptencoding utf-8
+
+func Run_cjk_linebreak_after(rigorous)
+ set textwidth=12
+ for punct in [
+ \ '!', '%', ')', ',', ':', ';', '>', '?', ']', '}', '’', '”', '†', '‡',
+ \ '…', '‰', '‱', '‼', '⁇', '⁈', '⁉', '℃', '℉', '、', '。', '〉', '》',
+ \ '」', '』', '】', '〕', '〗', '〙', '〛', '!', ')', ',', '.', ':',
+ \ ';', '?', ']', '}']
+ call setline('.', '这是一个测试' .. punct.'试试 CJK 行禁则补丁。')
+ normal gqq
+ if a:rigorous
+ call assert_equal('这是一个测', getline(1))
+ else
+ call assert_equal('这是一个测试' .. punct, getline(1))
+ endif
+ %d_
+ endfor
+endfunc
+
+func Test_cjk_linebreak_after()
+ set formatoptions=croqn2mB1j
+ call Run_cjk_linebreak_after(0)
+endfunc
+
+func Test_cjk_linebreak_after_rigorous()
+ set formatoptions=croqn2mB1j]
+ call Run_cjk_linebreak_after(1)
+endfunc
+
+func Run_cjk_linebreak_before()
+ set textwidth=12
+ for punct in [
+ \ '(', '<', '[', '`', '{', '‘', '“', '〈', '《', '「', '『', '【', '〔',
+ \ '〖', '〘', '〚', '(', '[', '{']
+ call setline('.', '这是个测试' .. punct.'试试 CJK 行禁则补丁。')
+ normal gqq
+ call assert_equal('这是个测试', getline(1))
+ %d_
+ endfor
+endfunc
+
+func Test_cjk_linebreak_before()
+ set formatoptions=croqn2mB1j
+ call Run_cjk_linebreak_before()
+endfunc
+
+func Test_cjk_linebreak_before_rigorous()
+ set formatoptions=croqn2mB1j]
+ call Run_cjk_linebreak_before()
+endfunc
+
+func Run_cjk_linebreak_nobetween(rigorous)
+ " …… must not start a line
+ call setline('.', '这是个测试……试试 CJK 行禁则补丁。')
+ set textwidth=12 ambiwidth=double
+ normal gqq
+ if a:rigorous
+ call assert_equal('这是个测', getline(1))
+ else
+ call assert_equal('这是个测试……', getline(1))
+ endif
+ %d_
+
+ call setline('.', '这是一个测试……试试 CJK 行禁则补丁。')
+ set textwidth=12 ambiwidth=double
+ normal gqq
+ call assert_equal('这是一个测', getline(1))
+ %d_
+
+ " but —— can
+ call setline('.', '这是个测试——试试 CJK 行禁则补丁。')
+ set textwidth=12 ambiwidth=double
+ normal gqq
+ call assert_equal('这是个测试', getline(1))
+endfunc
+
+func Test_cjk_linebreak_nobetween()
+ set formatoptions=croqn2mB1j
+ call Run_cjk_linebreak_nobetween(0)
+endfunc
+
+func Test_cjk_linebreak_nobetween_rigorous()
+ set formatoptions=croqn2mB1j]
+ call Run_cjk_linebreak_nobetween(1)
+endfunc
+
+func Test_cjk_linebreak_join_punct()
+ for punct in ['——', '〗', ',', '。', '……']
+ call setline(1, '文本文本' .. punct)
+ call setline(2, 'English')
+ set formatoptions=croqn2mB1j
+ normal ggJ
+ call assert_equal('文本文本' .. punct.'English', getline(1))
+ %d_
+ endfor
+endfunc
diff --git a/src/nvim/testdir/test_cmdline.vim b/src/nvim/testdir/test_cmdline.vim
index f8d84f1a49..e3c42a4fe3 100644
--- a/src/nvim/testdir/test_cmdline.vim
+++ b/src/nvim/testdir/test_cmdline.vim
@@ -1,5 +1,8 @@
" Tests for editing the command line.
+source check.vim
+source screendump.vim
+
func Test_complete_tab()
call writefile(['testfile'], 'Xtestfile')
call feedkeys(":e Xtestf\t\r", "tx")
@@ -718,6 +721,27 @@ func Test_verbosefile()
call delete('Xlog')
endfunc
+func Test_verbose_option()
+ " See test/functional/legacy/cmdline_spec.lua
+ CheckScreendump
+
+ let lines =<< trim [SCRIPT]
+ command DoSomething echo 'hello' |set ts=4 |let v = '123' |echo v
+ call feedkeys("\r", 't') " for the hit-enter prompt
+ set verbose=20
+ [SCRIPT]
+ call writefile(lines, 'XTest_verbose')
+
+ let buf = RunVimInTerminal('-S XTest_verbose', {'rows': 12})
+ call term_wait(buf, 100)
+ call term_sendkeys(buf, ":DoSomething\<CR>")
+ call VerifyScreenDump(buf, 'Test_verbose_option_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XTest_verbose')
+endfunc
+
func Test_setcmdpos()
func InsertTextAtPos(text, pos)
call assert_equal(0, setcmdpos(a:pos))
@@ -818,6 +842,25 @@ func Test_buffers_lastused()
bwipeout bufc
endfunc
+func Test_cmdlineclear_tabenter()
+ " See test/functional/legacy/cmdline_spec.lua
+ CheckScreendump
+
+ let lines =<< trim [SCRIPT]
+ call setline(1, range(30))
+ [SCRIPT]
+
+ call writefile(lines, 'XtestCmdlineClearTabenter')
+ let buf = RunVimInTerminal('-S XtestCmdlineClearTabenter', #{rows: 10})
+ call term_wait(buf, 50)
+ " in one tab make the command line higher with CTRL-W -
+ call term_sendkeys(buf, ":tabnew\<cr>\<C-w>-\<C-w>-gtgt")
+ call VerifyScreenDump(buf, 'Test_cmdlineclear_tabenter', {})
+
+ call StopVimInTerminal(buf)
+ call delete('XtestCmdlineClearTabenter')
+endfunc
+
" test that ";" works to find a match at the start of the first line
func Test_zero_line_search()
new
diff --git a/src/nvim/testdir/test_compiler.vim b/src/nvim/testdir/test_compiler.vim
index 6bb602717f..9101f8cfa0 100644
--- a/src/nvim/testdir/test_compiler.vim
+++ b/src/nvim/testdir/test_compiler.vim
@@ -42,12 +42,12 @@ func Test_compiler_without_arg()
let a = split(execute('compiler'))
call assert_match(runtime .. '/compiler/ant.vim$', a[0])
call assert_match(runtime .. '/compiler/bcc.vim$', a[1])
- call assert_match(runtime .. '/compiler/xmlwf.vim$', a[-1])
+ call assert_match(runtime .. '/compiler/xo.vim$', a[-1])
endfunc
func Test_compiler_completion()
call feedkeys(":compiler \<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_match('^"compiler ant bcc .* xmlwf$', @:)
+ call assert_match('^"compiler ant bcc .* xmlwf xo$', @:)
call feedkeys(":compiler p\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"compiler pbx perl php pylint pyunit', @:)
diff --git a/src/nvim/testdir/test_const.vim b/src/nvim/testdir/test_const.vim
index eaf200e9bb..fc7ea71f6e 100644
--- a/src/nvim/testdir/test_const.vim
+++ b/src/nvim/testdir/test_const.vim
@@ -36,6 +36,7 @@ func Test_define_var_with_lock()
call assert_fails('let s = "vim"', 'E741:')
call assert_fails('let F = funcref("s:noop")', 'E741:')
call assert_fails('let l = [1, 2, 3]', 'E741:')
+ call assert_fails('call filter(l, "v:val % 2 == 0")', 'E741:')
call assert_fails('let d = {"foo": 10}', 'E741:')
if has('channel')
call assert_fails('let j = test_null_job()', 'E741:')
@@ -247,11 +248,14 @@ func Test_lock_depth_is_1()
const l = [1, 2, 3]
const d = {'foo': 10}
- " Modify list
- call add(l, 4)
+ " Modify list - setting item is OK, adding/removing items not
let l[0] = 42
+ call assert_fails('call add(l, 4)', 'E741:')
+ call assert_fails('unlet l[1]', 'E741:')
- " Modify dict
- let d['bar'] = 'hello'
+ " Modify dict - changing item is OK, adding/removing items not
+ let d['foo'] = 'hello'
let d.foo = 44
+ call assert_fails("let d['bar'] = 'hello'", 'E741:')
+ call assert_fails("unlet d['foo']", 'E741:')
endfunc
diff --git a/src/nvim/testdir/test_debugger.vim b/src/nvim/testdir/test_debugger.vim
index 811717208e..59d51b855b 100644
--- a/src/nvim/testdir/test_debugger.vim
+++ b/src/nvim/testdir/test_debugger.vim
@@ -316,3 +316,128 @@ func Test_Debugger()
call delete('Xtest.vim')
endfunc
+
+" Test for setting a breakpoint on a :endif where the :if condition is false
+" and then quit the script. This should generate an interrupt.
+func Test_breakpt_endif_intr()
+ func F()
+ let g:Xpath ..= 'a'
+ if v:false
+ let g:Xpath ..= 'b'
+ endif
+ invalid_command
+ endfunc
+
+ let g:Xpath = ''
+ breakadd func 4 F
+ try
+ let caught_intr = 0
+ debuggreedy
+ call feedkeys(":call F()\<CR>quit\<CR>", "xt")
+ call F()
+ catch /^Vim:Interrupt$/
+ call assert_match('\.F, line 4', v:throwpoint)
+ let caught_intr = 1
+ endtry
+ 0debuggreedy
+ call assert_equal(1, caught_intr)
+ call assert_equal('a', g:Xpath)
+ breakdel *
+ delfunc F
+endfunc
+
+" Test for setting a breakpoint on a :else where the :if condition is false
+" and then quit the script. This should generate an interrupt.
+func Test_breakpt_else_intr()
+ func F()
+ let g:Xpath ..= 'a'
+ if v:false
+ let g:Xpath ..= 'b'
+ else
+ invalid_command
+ endif
+ invalid_command
+ endfunc
+
+ let g:Xpath = ''
+ breakadd func 4 F
+ try
+ let caught_intr = 0
+ debuggreedy
+ call feedkeys(":call F()\<CR>quit\<CR>", "xt")
+ call F()
+ catch /^Vim:Interrupt$/
+ call assert_match('\.F, line 4', v:throwpoint)
+ let caught_intr = 1
+ endtry
+ 0debuggreedy
+ call assert_equal(1, caught_intr)
+ call assert_equal('a', g:Xpath)
+ breakdel *
+ delfunc F
+endfunc
+
+" Test for setting a breakpoint on a :endwhile where the :while condition is
+" false and then quit the script. This should generate an interrupt.
+func Test_breakpt_endwhile_intr()
+ func F()
+ let g:Xpath ..= 'a'
+ while v:false
+ let g:Xpath ..= 'b'
+ endwhile
+ invalid_command
+ endfunc
+
+ let g:Xpath = ''
+ breakadd func 4 F
+ try
+ let caught_intr = 0
+ debuggreedy
+ call feedkeys(":call F()\<CR>quit\<CR>", "xt")
+ call F()
+ catch /^Vim:Interrupt$/
+ call assert_match('\.F, line 4', v:throwpoint)
+ let caught_intr = 1
+ endtry
+ 0debuggreedy
+ call assert_equal(1, caught_intr)
+ call assert_equal('a', g:Xpath)
+ breakdel *
+ delfunc F
+endfunc
+
+" Test for setting a breakpoint on an :endtry where an exception is pending to
+" be processed and then quit the script. This should generate an interrupt and
+" the thrown exception should be ignored.
+func Test_breakpt_endtry_intr()
+ func F()
+ try
+ let g:Xpath ..= 'a'
+ throw "abc"
+ endtry
+ invalid_command
+ endfunc
+
+ let g:Xpath = ''
+ breakadd func 4 F
+ try
+ let caught_intr = 0
+ let caught_abc = 0
+ debuggreedy
+ call feedkeys(":call F()\<CR>quit\<CR>", "xt")
+ call F()
+ catch /abc/
+ let caught_abc = 1
+ catch /^Vim:Interrupt$/
+ call assert_match('\.F, line 4', v:throwpoint)
+ let caught_intr = 1
+ endtry
+ 0debuggreedy
+ call assert_equal(1, caught_intr)
+ call assert_equal(0, caught_abc)
+ call assert_equal('a', g:Xpath)
+ breakdel *
+ delfunc F
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_diffmode.vim b/src/nvim/testdir/test_diffmode.vim
index 49bbe84869..f09a64c329 100644
--- a/src/nvim/testdir/test_diffmode.vim
+++ b/src/nvim/testdir/test_diffmode.vim
@@ -1,6 +1,7 @@
" Tests for diff mode
source shared.vim
source screendump.vim
+source check.vim
func Test_diff_fold_sync()
enew!
@@ -723,10 +724,140 @@ func Test_diff_lastline()
bwipe!
endfunc
+func Test_diff_screen()
+ CheckScreendump
+ CheckFeature menu
+
+ " clean up already existing swap files, just in case
+ call delete('.Xfile1.swp')
+ call delete('.Xfile2.swp')
+
+ " Test 1: Add a line in beginning of file 2
+ call WriteDiffFiles(0, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ let buf = RunVimInTerminal('-d Xfile1 Xfile2', {})
+ " Set autoread mode, so that Vim won't complain once we re-write the test
+ " files
+ call term_sendkeys(buf, ":set autoread\<CR>\<c-w>w:set autoread\<CR>\<c-w>w")
+
+ call VerifyBoth(buf, 'Test_diff_01', '')
+
+ " Test 2: Add a line in beginning of file 1
+ call WriteDiffFiles(buf, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_02', '')
+
+ " Test 3: Add a line at the end of file 2
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
+ call VerifyBoth(buf, 'Test_diff_03', '')
+
+ " Test 4: Add a line at the end of file 1
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_04', '')
+
+ " Test 5: Add a line in the middle of file 2, remove on at the end of file 1
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11], [1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_05', '')
+
+ " Test 6: Add a line in the middle of file 1, remove on at the end of file 2
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11])
+ call VerifyBoth(buf, 'Test_diff_06', '')
+
+ " Variants on test 6 with different context settings
+ call term_sendkeys(buf, ":set diffopt+=context:2\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_06.2', {})
+ call term_sendkeys(buf, ":set diffopt-=context:2\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=context:1\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_06.1', {})
+ call term_sendkeys(buf, ":set diffopt-=context:1\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=context:0\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_06.0', {})
+ call term_sendkeys(buf, ":set diffopt-=context:0\<cr>")
+
+ " Test 7 - 9: Test normal/patience/histogram diff algorithm
+ call WriteDiffFiles(buf, ['#include <stdio.h>', '', '// Frobs foo heartily', 'int frobnitz(int foo)', '{',
+ \ ' int i;', ' for(i = 0; i < 10; i++)', ' {', ' printf("Your answer is: ");',
+ \ ' printf("%d\n", foo);', ' }', '}', '', 'int fact(int n)', '{', ' if(n > 1)', ' {',
+ \ ' return fact(n-1) * n;', ' }', ' return 1;', '}', '', 'int main(int argc, char **argv)',
+ \ '{', ' frobnitz(fact(10));', '}'],
+ \ ['#include <stdio.h>', '', 'int fib(int n)', '{', ' if(n > 2)', ' {',
+ \ ' return fib(n-1) + fib(n-2);', ' }', ' return 1;', '}', '', '// Frobs foo heartily',
+ \ 'int frobnitz(int foo)', '{', ' int i;', ' for(i = 0; i < 10; i++)', ' {',
+ \ ' printf("%d\n", foo);', ' }', '}', '',
+ \ 'int main(int argc, char **argv)', '{', ' frobnitz(fib(10));', '}'])
+ call term_sendkeys(buf, ":diffupdate!\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_07', {})
+
+ call term_sendkeys(buf, ":set diffopt+=algorithm:patience\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_08', {})
+
+ call term_sendkeys(buf, ":set diffopt+=algorithm:histogram\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_09', {})
+
+ " Test 10-11: normal/indent-heuristic
+ call term_sendkeys(buf, ":set diffopt&vim\<cr>")
+ call WriteDiffFiles(buf, ['', ' def finalize(values)', '', ' values.each do |v|', ' v.finalize', ' end'],
+ \ ['', ' def finalize(values)', '', ' values.each do |v|', ' v.prepare', ' end', '',
+ \ ' values.each do |v|', ' v.finalize', ' end'])
+ call term_sendkeys(buf, ":diffupdate!\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_10', {})
+
+ " Leave trailing : at commandline!
+ call term_sendkeys(buf, ":set diffopt+=indent-heuristic\<cr>:\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_11', {}, 'one')
+ " shouldn't matter, if indent-algorithm comes before or after the algorithm
+ call term_sendkeys(buf, ":set diffopt&\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=indent-heuristic,algorithm:patience\<cr>:\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_11', {}, 'two')
+ call term_sendkeys(buf, ":set diffopt&\<cr>")
+ call term_sendkeys(buf, ":set diffopt+=algorithm:patience,indent-heuristic\<cr>:\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_11', {}, 'three')
+
+ " Test 12: diff the same file
+ call WriteDiffFiles(buf, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
+ call VerifyBoth(buf, 'Test_diff_12', '')
+
+ " Test 13: diff an empty file
+ call WriteDiffFiles(buf, [], [])
+ call VerifyBoth(buf, 'Test_diff_13', '')
+
+ " Test 14: test diffopt+=icase
+ call WriteDiffFiles(buf, ['a', 'b', 'cd'], ['A', 'b', 'cDe'])
+ call VerifyBoth(buf, 'Test_diff_14', " diffopt+=filler diffopt+=icase")
+
+ " Test 15-16: test diffopt+=iwhite
+ call WriteDiffFiles(buf, ['int main()', '{', ' printf("Hello, World!");', ' return 0;', '}'],
+ \ ['int main()', '{', ' if (0)', ' {', ' printf("Hello, World!");', ' return 0;', ' }', '}'])
+ call term_sendkeys(buf, ":diffupdate!\<cr>")
+ call term_sendkeys(buf, ":set diffopt&vim diffopt+=filler diffopt+=iwhite\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_15', {})
+ call term_sendkeys(buf, ":set diffopt+=internal\<cr>")
+ call VerifyScreenDump(buf, 'Test_diff_16', {})
+
+ " Test 17: test diffopt+=iblank
+ call WriteDiffFiles(buf, ['a', ' ', 'cd', 'ef', 'xxx'], ['a', 'cd', '', 'ef', 'yyy'])
+ call VerifyInternal(buf, 'Test_diff_17', " diffopt+=iblank")
+
+ " Test 18: test diffopt+=iblank,iwhite / iwhiteall / iwhiteeol
+ call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhite")
+ call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhiteall")
+ call VerifyInternal(buf, 'Test_diff_18', " diffopt+=iblank,iwhiteeol")
+
+ " Test 19: test diffopt+=iwhiteeol
+ call WriteDiffFiles(buf, ['a ', 'x', 'cd', 'ef', 'xx xx', 'foo', 'bar'], ['a', 'x', 'c d', ' ef', 'xx xx', 'foo', '', 'bar'])
+ call VerifyInternal(buf, 'Test_diff_19', " diffopt+=iwhiteeol")
+
+ " Test 19: test diffopt+=iwhiteall
+ call VerifyInternal(buf, 'Test_diff_20', " diffopt+=iwhiteall")
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xfile1')
+ call delete('Xfile2')
+endfunc
+
func Test_diff_with_cursorline()
- if !CanRunVimInTerminal()
- throw 'Skipped: cannot run Vim in a terminal window'
- endif
+ CheckScreendump
call writefile([
\ 'hi CursorLine ctermbg=red ctermfg=white',
@@ -750,13 +881,45 @@ func Test_diff_with_cursorline()
call delete('Xtest_diff_cursorline')
endfunc
+func Test_diff_with_syntax()
+ CheckScreendump
+
+ let lines =<< trim END
+ void doNothing() {
+ int x = 0;
+ char *s = "hello";
+ return 5;
+ }
+ END
+ call writefile(lines, 'Xprogram1.c')
+ let lines =<< trim END
+ void doSomething() {
+ int x = 0;
+ char *s = "there";
+ return 5;
+ }
+ END
+ call writefile(lines, 'Xprogram2.c')
+
+ let lines =<< trim END
+ edit Xprogram1.c
+ diffsplit Xprogram2.c
+ END
+ call writefile(lines, 'Xtest_diff_syntax')
+ let buf = RunVimInTerminal('-S Xtest_diff_syntax', {})
+
+ call VerifyScreenDump(buf, 'Test_diff_syntax_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_diff_syntax')
+ call delete('Xprogram1.c')
+ call delete('Xprogram2.c')
+endfunc
+
func Test_diff_of_diff()
- if !CanRunVimInTerminal()
- throw 'Skipped: cannot run Vim in a terminal window'
- endif
- if !has("rightleft")
- throw 'Skipped: rightleft not supported'
- endif
+ CheckScreendump
+ CheckFeature rightleft
call writefile([
\ 'call setline(1, ["aa","bb","cc","@@ -3,2 +5,7 @@","dd","ee","ff"])',
@@ -801,6 +964,34 @@ func Test_diff_closeoff()
enew!
endfunc
+func Test_diff_rnu()
+ CheckScreendump
+
+ let content =<< trim END
+ call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b'])
+ vnew
+ call setline(1, ['a', 'a', 'a', 'x', 'x', 'x', 'b', 'b', 'b', 'b', 'b'])
+ call setline(1, ['a', 'a', 'a', 'y', 'b', 'b', 'b', 'b', 'b'])
+ vnew
+ call setline(1, ['a', 'a', 'a', 'x', 'x', 'x', 'b', 'b', 'b', 'b', 'b'])
+ windo diffthis
+ setlocal number rnu foldcolumn=0
+ END
+ call writefile(content, 'Xtest_diff_rnu')
+ let buf = RunVimInTerminal('-S Xtest_diff_rnu', {})
+
+ call VerifyScreenDump(buf, 'Test_diff_rnu_01', {})
+
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_diff_rnu_02', {})
+ call term_sendkeys(buf, "j")
+ call VerifyScreenDump(buf, 'Test_diff_rnu_03', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtest_diff_rnu')
+endfunc
+
func Test_diff_and_scroll()
" this was causing an ml_get error
set ls=2
diff --git a/src/nvim/testdir/test_display.vim b/src/nvim/testdir/test_display.vim
index 1c2f5a05ff..c702b44b88 100644
--- a/src/nvim/testdir/test_display.vim
+++ b/src/nvim/testdir/test_display.vim
@@ -6,11 +6,12 @@
" endif
source view_util.vim
+source check.vim
+source screendump.vim
+
+func Test_display_foldcolumn()
+ CheckFeature folding
-func! Test_display_foldcolumn()
- if !has("folding")
- return
- endif
new
vnew
vert resize 25
@@ -26,10 +27,10 @@ func! Test_display_foldcolumn()
call cursor(2, 1)
norm! zt
- let lines=ScreenLines([1,2], winwidth(0))
+ let lines = ScreenLines([1,2], winwidth(0))
call assert_equal(expect, lines)
set fdc=2
- let lines=ScreenLines([1,2], winwidth(0))
+ let lines = ScreenLines([1,2], winwidth(0))
let expect = [
\ " e more noise blah blah<",
\ " 82> more stuff here "
@@ -41,9 +42,8 @@ func! Test_display_foldcolumn()
endfunc
func! Test_display_foldtext_mbyte()
- if !has("folding")
- return
- endif
+ CheckFeature folding
+
call NewWindow(10, 40)
call append(0, range(1,20))
exe "set foldmethod=manual foldtext=foldtext() fillchars=fold:\u2500,vert:\u2502 fdc=2"
@@ -70,6 +70,42 @@ func! Test_display_foldtext_mbyte()
bw!
endfunc
+" check that win_ins_lines() and win_del_lines() work when t_cs is empty.
+func Test_scroll_without_region()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, range(1, 20))
+ set t_cs=
+ set laststatus=2
+ END
+ call writefile(lines, 'Xtestscroll')
+ let buf = RunVimInTerminal('-S Xtestscroll', #{rows: 10})
+
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_1', {})
+
+ call term_sendkeys(buf, ":3delete\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_2', {})
+
+ call term_sendkeys(buf, ":4put\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_3', {})
+
+ call term_sendkeys(buf, ":undo\<cr>")
+ call term_sendkeys(buf, ":undo\<cr>")
+ call term_sendkeys(buf, ":set laststatus=0\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_4', {})
+
+ call term_sendkeys(buf, ":3delete\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_5', {})
+
+ call term_sendkeys(buf, ":4put\<cr>")
+ call VerifyScreenDump(buf, 'Test_scroll_no_region_6', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xtestscroll')
+endfunc
+
func Test_display_listchars_precedes()
set fillchars+=vert:\|
call NewWindow(10, 10)
@@ -125,3 +161,104 @@ func Test_display_listchars_precedes()
set list& listchars& wrap&
bw!
endfunc
+
+" Check that win_lines() works correctly with the number_only parameter=TRUE
+" should break early to optimize cost of drawing, but needs to make sure
+" that the number column is correctly highlighted.
+func Test_scroll_CursorLineNr_update()
+ CheckScreendump
+
+ let lines =<< trim END
+ hi CursorLineNr ctermfg=73 ctermbg=236
+ set nu rnu cursorline cursorlineopt=number
+ exe ":norm! o\<esc>110ia\<esc>"
+ END
+ let filename = 'Xdrawscreen'
+ call writefile(lines, filename)
+ let buf = RunVimInTerminal('-S '.filename, #{rows: 5, cols: 50})
+ call term_sendkeys(buf, "k")
+ call term_wait(buf)
+ call VerifyScreenDump(buf, 'Test_winline_rnu', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete(filename)
+endfunc
+
+" check a long file name does not result in the hit-enter prompt
+func Test_edit_long_file_name()
+ CheckScreendump
+
+ let longName = 'x'->repeat(min([&columns, 255]))
+ call writefile([], longName)
+ let buf = RunVimInTerminal('-N -u NONE ' .. longName, #{rows: 8})
+
+ call VerifyScreenDump(buf, 'Test_long_file_name_1', {})
+
+ call term_sendkeys(buf, ":q\<cr>")
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete(longName)
+endfunc
+
+func Test_unprintable_fileformats()
+ CheckScreendump
+
+ call writefile(["unix\r", "two"], 'Xunix.txt')
+ call writefile(["mac\r", "two"], 'Xmac.txt')
+ let lines =<< trim END
+ edit Xunix.txt
+ split Xmac.txt
+ edit ++ff=mac
+ END
+ let filename = 'Xunprintable'
+ call writefile(lines, filename)
+ let buf = RunVimInTerminal('-S '.filename, #{rows: 9, cols: 50})
+ call VerifyScreenDump(buf, 'Test_display_unprintable_01', {})
+ call term_sendkeys(buf, "\<C-W>\<C-W>\<C-L>")
+ call VerifyScreenDump(buf, 'Test_display_unprintable_02', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('Xunix.txt')
+ call delete('Xmac.txt')
+ call delete(filename)
+endfunc
+
+" Test for scrolling that modifies buffer during visual block
+func Test_visual_block_scroll()
+ " See test/functional/legacy/visual_mode_spec.lua
+ CheckScreendump
+
+ let lines =<< trim END
+ source $VIMRUNTIME/plugin/matchparen.vim
+ set scrolloff=1
+ call setline(1, ['a', 'b', 'c', 'd', 'e', '', '{', '}', '{', 'f', 'g', '}'])
+ call cursor(5, 1)
+ END
+
+ let filename = 'Xvisualblockmodifiedscroll'
+ call writefile(lines, filename)
+
+ let buf = RunVimInTerminal('-S '.filename, #{rows: 7})
+ call term_sendkeys(buf, "V\<C-D>\<C-D>")
+
+ call VerifyScreenDump(buf, 'Test_display_visual_block_scroll', {})
+
+ call StopVimInTerminal(buf)
+ call delete(filename)
+endfunc
+
+func Test_display_scroll_at_topline()
+ " See test/functional/legacy/display_spec.lua
+ CheckScreendump
+
+ let buf = RunVimInTerminal('', #{cols: 20})
+ call term_sendkeys(buf, ":call setline(1, repeat('a', 21))\<CR>")
+ call term_wait(buf)
+ call term_sendkeys(buf, "O\<Esc>")
+ call VerifyScreenDump(buf, 'Test_display_scroll_at_topline', #{rows: 4})
+
+ call StopVimInTerminal(buf)
+endfunc
diff --git a/src/nvim/testdir/test_edit.vim b/src/nvim/testdir/test_edit.vim
index 12d5d9790e..abad6983dc 100644
--- a/src/nvim/testdir/test_edit.vim
+++ b/src/nvim/testdir/test_edit.vim
@@ -1,9 +1,11 @@
" Test for edit functions
-"
+
if exists("+t_kD")
let &t_kD="[3;*~"
endif
+source check.vim
+
" Needed for testing basic rightleft: Test_edit_rightleft
source view_util.vim
@@ -733,17 +735,16 @@ func! Test_edit_CTRL_O()
endfunc
func! Test_edit_CTRL_R()
- throw 'skipped: Nvim does not support test_override()'
" Insert Register
new
- call test_override("ALL", 1)
+ " 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)
+ " call test_override("ALL", 0)
set noshowcmd
bw!
endfunc
@@ -955,7 +956,6 @@ func! Test_edit_DROP()
endfunc
func! Test_edit_CTRL_V()
- throw 'skipped: Nvim does not support test_override()'
if has("ebcdic")
return
endif
@@ -965,7 +965,7 @@ func! Test_edit_CTRL_V()
" force some redraws
set showmode showcmd
"call test_override_char_avail(1)
- call test_override('ALL', 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, '$'))
@@ -978,7 +978,7 @@ func! Test_edit_CTRL_V()
set norl
endif
- call test_override('ALL', 0)
+ " call test_override('ALL', 0)
set noshowmode showcmd
bw!
endfunc
@@ -1441,31 +1441,40 @@ endfunc
func Test_edit_InsertLeave()
new
+ au InsertLeavePre * let g:did_au_pre = 1
au InsertLeave * let g:did_au = 1
+ let g:did_au_pre = 0
let g:did_au = 0
call feedkeys("afoo\<Esc>", 'tx')
+ call assert_equal(1, g:did_au_pre)
call assert_equal(1, g:did_au)
call assert_equal('foo', getline(1))
+ let g:did_au_pre = 0
let g:did_au = 0
call feedkeys("Sbar\<C-C>", 'tx')
+ call assert_equal(1, g:did_au_pre)
call assert_equal(0, g:did_au)
call assert_equal('bar', getline(1))
inoremap x xx<Esc>
+ let g:did_au_pre = 0
let g:did_au = 0
call feedkeys("Saax", 'tx')
+ call assert_equal(1, g:did_au_pre)
call assert_equal(1, g:did_au)
call assert_equal('aaxx', getline(1))
inoremap x xx<C-C>
+ let g:did_au_pre = 0
let g:did_au = 0
call feedkeys("Sbbx", 'tx')
+ call assert_equal(1, g:did_au_pre)
call assert_equal(0, g:did_au)
call assert_equal('bbxx', getline(1))
bwipe!
- au! InsertLeave
+ au! InsertLeave InsertLeavePre
iunmap x
endfunc
@@ -1514,3 +1523,75 @@ func Test_edit_startinsert()
set backspace&
bwipe!
endfunc
+
+func Test_edit_noesckeys()
+ CheckNotGui
+ new
+
+ " <Left> moves cursor when 'esckeys' is set
+ exe "set t_kl=\<Esc>OD"
+ " set esckeys
+ call feedkeys("axyz\<Esc>ODX", "xt")
+ " call assert_equal("xyXz", getline(1))
+
+ " <Left> exits Insert mode when 'esckeys' is off
+ " set noesckeys
+ call setline(1, '')
+ call feedkeys("axyz\<Esc>ODX", "xt")
+ call assert_equal(["DX", "xyz"], getline(1, 2))
+
+ bwipe!
+ " set esckeys
+endfunc
+
+" Test for editing a directory
+func Test_edit_is_a_directory()
+ CheckEnglish
+ let dirname = getcwd() . "/Xdir"
+ call mkdir(dirname, 'p')
+
+ new
+ redir => msg
+ exe 'edit' dirname
+ redir END
+ call assert_match("is a directory$", split(msg, "\n")[0])
+ bwipe!
+
+ let dirname .= '/'
+
+ new
+ redir => msg
+ exe 'edit' dirname
+ redir END
+ call assert_match("is a directory$", split(msg, "\n")[0])
+ bwipe!
+
+ call delete(dirname, 'rf')
+endfunc
+
+func Test_edit_browse()
+ " in the GUI this opens a file picker, we only test the terminal behavior
+ CheckNotGui
+
+ " ":browse xxx" checks for the FileExplorer augroup and assumes editing "."
+ " works then.
+ augroup FileExplorer
+ au!
+ augroup END
+
+ " When the USE_FNAME_CASE is defined this used to cause a crash.
+ browse enew
+ bwipe!
+
+ browse split
+ bwipe!
+endfunc
+
+func Test_read_invalid()
+ " set encoding=latin1
+ " This was not properly checking for going past the end.
+ call assert_fails('r`=', 'E484')
+ set encoding=utf-8
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_environ.vim b/src/nvim/testdir/test_environ.vim
index 21bb09a690..a25d83753c 100644
--- a/src/nvim/testdir/test_environ.vim
+++ b/src/nvim/testdir/test_environ.vim
@@ -1,5 +1,9 @@
+" Test for environment variables.
+
scriptencoding utf-8
+source check.vim
+
func Test_environ()
unlet! $TESTENV
call assert_equal(0, has_key(environ(), 'TESTENV'))
@@ -42,3 +46,24 @@ func Test_external_env()
endif
call assert_equal('', result)
endfunc
+
+func Test_mac_locale()
+ CheckFeature osxdarwin
+
+ " If $LANG is not set then the system locale will be used.
+ " Run Vim after unsetting all the locale environmental vars, and capture the
+ " output of :lang.
+ let lang_results = system("unset LANG; unset LC_MESSAGES; " ..
+ \ shellescape(v:progpath) ..
+ \ " --clean -esX -c 'redir @a' -c 'lang' -c 'put a' -c 'print' -c 'qa!' ")
+
+ " Check that:
+ " 1. The locale is the form of <locale>.UTF-8.
+ " 2. Check that fourth item (LC_NUMERIC) is properly set to "C".
+ " Example match: "en_US.UTF-8/en_US.UTF-8/en_US.UTF-8/C/en_US.UTF-8/en_US.UTF-8"
+ call assert_match('"\([a-zA-Z_]\+\.UTF-8/\)\{3}C\(/[a-zA-Z_]\+\.UTF-8\)\{2}"',
+ \ lang_results,
+ \ "Default locale should have UTF-8 encoding set, and LC_NUMERIC set to 'C'")
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_eval_stuff.vim b/src/nvim/testdir/test_eval_stuff.vim
index 4b54a0d39f..061364fb73 100644
--- a/src/nvim/testdir/test_eval_stuff.vim
+++ b/src/nvim/testdir/test_eval_stuff.vim
@@ -108,3 +108,27 @@ func Test_skip_after_throw()
catch /something/
endtry
endfunc
+
+func Test_curly_assignment()
+ let s:svar = 'svar'
+ let g:gvar = 'gvar'
+ let lname = 'gvar'
+ let gname = 'gvar'
+ let {'s:'.lname} = {'g:'.gname}
+ call assert_equal('gvar', s:gvar)
+ let s:gvar = ''
+ let { 's:'.lname } = { 'g:'.gname }
+ call assert_equal('gvar', s:gvar)
+ let s:gvar = ''
+ let { 's:' . lname } = { 'g:' . gname }
+ call assert_equal('gvar', s:gvar)
+ let s:gvar = ''
+ let { 's:' .. lname } = { 'g:' .. gname }
+ call assert_equal('gvar', s:gvar)
+
+ unlet s:svar
+ unlet s:gvar
+ unlet g:gvar
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_expand_func.vim b/src/nvim/testdir/test_expand_func.vim
index fb29c3eb7a..9588d3b89d 100644
--- a/src/nvim/testdir/test_expand_func.vim
+++ b/src/nvim/testdir/test_expand_func.vim
@@ -1,5 +1,7 @@
" Tests for expand()
+source shared.vim
+
let s:sfile = expand('<sfile>')
let s:slnum = str2nr(expand('<slnum>'))
let s:sflnum = str2nr(expand('<sflnum>'))
@@ -16,6 +18,25 @@ func s:expand_sflnum()
return str2nr(expand('<sflnum>'))
endfunc
+" This test depends on the location in the test file, put it first.
+func Test_expand_sflnum()
+ call assert_equal(7, s:sflnum)
+ call assert_equal(24, str2nr(expand('<sflnum>')))
+
+ " Line-continuation
+ call assert_equal(
+ \ 27,
+ \ str2nr(expand('<sflnum>')))
+
+ " Call in script-local function
+ call assert_equal(18, s:expand_sflnum())
+
+ " Call in command
+ command Flnum echo expand('<sflnum>')
+ call assert_equal(36, str2nr(trim(execute('Flnum'))))
+ delcommand Flnum
+endfunc
+
func Test_expand_sfile()
call assert_match('test_expand_func\.vim$', s:sfile)
call assert_match('^function .*\.\.Test_expand_sfile$', expand('<sfile>'))
@@ -30,7 +51,7 @@ func Test_expand_sfile()
endfunc
func Test_expand_slnum()
- call assert_equal(4, s:slnum)
+ call assert_equal(6, s:slnum)
call assert_equal(2, str2nr(expand('<slnum>')))
" Line-continuation
@@ -47,20 +68,14 @@ func Test_expand_slnum()
delcommand Slnum
endfunc
-func Test_expand_sflnum()
- call assert_equal(5, s:sflnum)
- call assert_equal(52, str2nr(expand('<sflnum>')))
-
- " Line-continuation
- call assert_equal(
- \ 55,
- \ str2nr(expand('<sflnum>')))
-
- " Call in script-local function
- call assert_equal(16, s:expand_sflnum())
+func s:sid_test()
+ return 'works'
+endfunc
- " Call in command
- command Flnum echo expand('<sflnum>')
- call assert_equal(64, str2nr(trim(execute('Flnum'))))
- delcommand Flnum
+func Test_expand_SID()
+ let sid = expand('<SID>')
+ execute 'let g:sid_result = ' .. sid .. 'sid_test()'
+ call assert_equal('works', g:sid_result)
endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_expr.vim b/src/nvim/testdir/test_expr.vim
index 264d8b000f..b8d6f5aa7d 100644
--- a/src/nvim/testdir/test_expr.vim
+++ b/src/nvim/testdir/test_expr.vim
@@ -49,6 +49,9 @@ func Test_dict()
let d['a'] = 'aaa'
call assert_equal('none', d[''])
call assert_equal('aaa', d['a'])
+
+ let d[ 'b' ] = 'bbb'
+ call assert_equal('bbb', d[ 'b' ])
endfunc
func Test_strgetchar()
diff --git a/src/nvim/testdir/test_filetype.vim b/src/nvim/testdir/test_filetype.vim
index d440bdcb1e..9f7e153955 100644
--- a/src/nvim/testdir/test_filetype.vim
+++ b/src/nvim/testdir/test_filetype.vim
@@ -54,6 +54,7 @@ let s:filename_checks = {
\ 'acedb': ['file.wrm'],
\ 'ada': ['file.adb', 'file.ads', 'file.ada', 'file.gpr'],
\ 'ahdl': ['file.tdf'],
+ \ 'aidl': ['file.aidl'],
\ 'alsaconf': ['.asoundrc', '/usr/share/alsa/alsa.conf', '/etc/asound.conf'],
\ 'aml': ['file.aml'],
\ 'ampl': ['file.run'],
@@ -72,8 +73,9 @@ let s:filename_checks = {
\ 'autoit': ['file.au3'],
\ 'automake': ['GNUmakefile.am'],
\ 'ave': ['file.ave'],
- \ 'awk': ['file.awk'],
+ \ 'awk': ['file.awk', 'file.gawk'],
\ 'b': ['file.mch', 'file.ref', 'file.imp'],
+ \ 'bzl': ['file.bazel', 'file.bzl', 'WORKSPACE'],
\ 'bc': ['file.bc'],
\ 'bdf': ['file.bdf'],
\ 'bib': ['file.bib'],
@@ -107,7 +109,7 @@ let s:filename_checks = {
\ 'conaryrecipe': ['file.recipe'],
\ 'conf': ['auto.master'],
\ 'config': ['configure.in', 'configure.ac', 'Pipfile'],
- \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi'],
+ \ 'context': ['tex/context/any/file.tex', 'file.mkii', 'file.mkiv', 'file.mkvi', 'file.mkxl', 'file.mklx'],
\ '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'],
@@ -126,6 +128,7 @@ let s:filename_checks = {
\ 'dart': ['file.dart', 'file.drt'],
\ 'datascript': ['file.ds'],
\ 'dcd': ['file.dcd'],
+ \ 'debchangelog': ['changelog.Debian', 'changelog.dch', 'NEWS.Debian', 'NEWS.dch', '/debian/changelog'],
\ 'debcontrol': ['/debian/control'],
\ 'debsources': ['/etc/apt/sources.list', '/etc/apt/sources.list.d/file.list'],
\ 'def': ['file.def'],
@@ -139,7 +142,7 @@ let s:filename_checks = {
\ 'dnsmasq': ['/etc/dnsmasq.conf'],
\ 'dockerfile': ['Dockerfile', 'file.Dockerfile'],
\ 'dosbatch': ['file.bat', 'file.sys'],
- \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini'],
+ \ 'dosini': ['.editorconfig', '/etc/pacman.conf', '/etc/yum.conf', 'file.ini', 'npmrc', '.npmrc', 'php.ini', 'php.ini-5'],
\ 'dot': ['file.dot', 'file.gv'],
\ 'dracula': ['file.drac', 'file.drc', 'filelvs', 'filelpe'],
\ 'dsl': ['file.dsl'],
@@ -324,7 +327,7 @@ let s:filename_checks = {
\ 'pamconf': ['/etc/pam.conf'],
\ 'pamenv': ['/etc/security/pam_env.conf', '/home/user/.pam_environment'],
\ 'papp': ['file.papp', 'file.pxml', 'file.pxsl'],
- \ 'pascal': ['file.pas', 'file.dpr'],
+ \ 'pascal': ['file.pas', 'file.pp', 'file.dpr', 'file.lpr'],
\ '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'],
@@ -453,7 +456,7 @@ let s:filename_checks = {
\ 'texmf': ['texmf.cnf'],
\ 'text': ['file.text', 'README'],
\ 'tf': ['file.tf', '.tfrc', 'tfrc'],
- \ 'tidy': ['.tidyrc', 'tidyrc'],
+ \ 'tidy': ['.tidyrc', 'tidyrc', 'tidy.conf'],
\ 'tilde': ['file.t.html'],
\ 'tli': ['file.tli'],
\ 'tmux': ['tmuxfile.conf', '.tmuxfile.conf'],
@@ -470,6 +473,7 @@ let s:filename_checks = {
\ 'uc': ['file.uc'],
\ 'udevconf': ['/etc/udev/udev.conf'],
\ 'udevperm': ['/etc/udev/permissions.d/file.permissions'],
+ \ 'udevrules': ['/etc/udev/rules.d/file.rules', '/usr/lib/udev/rules.d/file.rules', '/lib/udev/rules.d/file.rules'],
\ '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'],
@@ -524,9 +528,11 @@ let s:filename_checks = {
let s:filename_case_checks = {
\ 'modula2': ['file.DEF', 'file.MOD'],
+ \ 'bzl': ['file.BUILD', 'BUILD'],
\ }
func CheckItems(checks)
+ set noswapfile
for [ft, names] in items(a:checks)
for i in range(0, len(names) - 1)
new
@@ -543,6 +549,7 @@ func CheckItems(checks)
bwipe!
endfor
endfor
+ set swapfile&
endfunc
func Test_filetype_detection()
@@ -595,7 +602,8 @@ let s:script_checks = {
\ 'bc': [['#!/path/bc']],
\ 'sed': [['#!/path/sed']],
\ 'ocaml': [['#!/path/ocaml']],
- \ 'awk': [['#!/path/awk']],
+ \ 'awk': [['#!/path/awk'],
+ \ ['#!/path/gawk']],
\ 'wml': [['#!/path/wml']],
\ 'scheme': [['#!/path/scheme']],
\ 'cfengine': [['#!/path/cfengine']],
diff --git a/src/nvim/testdir/test_filter_map.vim b/src/nvim/testdir/test_filter_map.vim
index 1dd3a5b29f..a15567bcf2 100644
--- a/src/nvim/testdir/test_filter_map.vim
+++ b/src/nvim/testdir/test_filter_map.vim
@@ -11,6 +11,7 @@ func Test_filter_map_list_expr_string()
call assert_equal([2, 4, 6, 8], map([1, 2, 3, 4], 'v:val * 2'))
call assert_equal([0, 2, 4, 6], map([1, 2, 3, 4], 'v:key * 2'))
call assert_equal([9, 9, 9, 9], map([1, 2, 3, 4], 9))
+ call assert_equal([7, 7, 7], map([1, 2, 3], ' 7 '))
endfunc
" dict with expression string
diff --git a/src/nvim/testdir/test_fold.vim b/src/nvim/testdir/test_fold.vim
index 692f6e4780..3c90c45952 100644
--- a/src/nvim/testdir/test_fold.vim
+++ b/src/nvim/testdir/test_fold.vim
@@ -795,3 +795,24 @@ func Test_fold_delete_first_line()
bwipe!
set foldmethod&
endfunc
+
+" this was crashing
+func Test_move_no_folds()
+ new
+ fold
+ setlocal fdm=expr
+ normal zj
+ bwipe!
+endfunc
+
+" this was crashing
+func Test_fold_create_delete_create()
+ new
+ fold
+ fold
+ normal zd
+ fold
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_functions.vim b/src/nvim/testdir/test_functions.vim
index 51689db9c4..917a5e8eca 100644
--- a/src/nvim/testdir/test_functions.vim
+++ b/src/nvim/testdir/test_functions.vim
@@ -214,7 +214,7 @@ func Test_strftime()
endif
endfunc
-func Test_resolve()
+func Test_resolve_unix()
if !has('unix')
return
endif
@@ -258,6 +258,8 @@ func Test_resolve()
call assert_equal('Xlink2', resolve('Xlink1'))
call assert_equal('./Xlink2', resolve('./Xlink1'))
call delete('Xlink1')
+
+ call assert_equal('/', resolve('/'))
endfunc
func Test_simplify()
@@ -334,6 +336,10 @@ func Test_strpart()
call assert_equal('lép', strpart('éléphant', 2, 4))
call assert_equal('léphant', strpart('éléphant', 2))
+
+ call assert_equal('é', strpart('éléphant', 0, 1, 1))
+ call assert_equal('ép', strpart('éléphant', 3, 2, v:true))
+ call assert_equal('ó', strpart('cómposed', 1, 1, 1))
endfunc
func Test_tolower()
@@ -1079,6 +1085,12 @@ func Test_trim()
call assert_equal("", trim("", ""))
call assert_equal("a", trim("a", ""))
call assert_equal("", trim("", "a"))
+ call assert_equal("vim", trim(" vim ", " ", 0))
+ call assert_equal("vim ", trim(" vim ", " ", 1))
+ call assert_equal(" vim", trim(" vim ", " ", 2))
+ call assert_fails('call trim(" vim ", " ", [])', 'E745:')
+ call assert_fails('call trim(" vim ", " ", -1)', 'E475:')
+ call assert_fails('call trim(" vim ", " ", 3)', 'E475:')
let chars = join(map(range(1, 0x20) + [0xa0], {n -> nr2char(n)}), '')
call assert_equal("x", trim(chars . "x" . chars))
@@ -1217,6 +1229,24 @@ func Test_reg_executing_and_recording()
unlet s:reg_stat
endfunc
+func Test_getchar()
+ call feedkeys('a', '')
+ call assert_equal(char2nr('a'), getchar())
+
+ " call test_setmouse(1, 3)
+ " let v:mouse_win = 9
+ " let v:mouse_winid = 9
+ " let v:mouse_lnum = 9
+ " let v:mouse_col = 9
+ " call feedkeys("\<S-LeftMouse>", '')
+ call nvim_input_mouse('left', 'press', 'S', 0, 0, 2)
+ call assert_equal("\<S-LeftMouse>", getchar())
+ call assert_equal(1, v:mouse_win)
+ call assert_equal(win_getid(1), v:mouse_winid)
+ call assert_equal(1, v:mouse_lnum)
+ call assert_equal(3, v:mouse_col)
+endfunc
+
func Test_libcall_libcallnr()
if !has('libcall')
return
@@ -1337,3 +1367,22 @@ func Test_readdir()
call delete('Xdir', 'rf')
endfunc
+
+" Test for the eval() function
+func Test_eval()
+ call assert_fails("call eval('5 a')", 'E488:')
+endfunc
+
+" Test for the nr2char() function
+func Test_nr2char()
+ " set encoding=latin1
+ call assert_equal('@', nr2char(64))
+ set encoding=utf8
+ call assert_equal('a', nr2char(97, 1))
+ call assert_equal('a', nr2char(97, 0))
+
+ call assert_equal("\x80\xfc\b\xf4\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x100000) .. '>"'))
+ call assert_equal("\x80\xfc\b\xfd\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX\x80\xfeX", eval('"\<M-' .. nr2char(0x40000000) .. '>"'))
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_gf.vim b/src/nvim/testdir/test_gf.vim
index 4a4ffcefa1..ee548037ba 100644
--- a/src/nvim/testdir/test_gf.vim
+++ b/src/nvim/testdir/test_gf.vim
@@ -1,3 +1,4 @@
+" Test for the gf and gF (goto file) commands
" This is a test if a URL is recognized by "gf", with the cursor before and
" after the "://". Also test ":\\".
@@ -109,7 +110,7 @@ func Test_gf()
endfunc
func Test_gf_visual()
- call writefile([], "Xtest_gf_visual")
+ call writefile(['one', 'two', 'three', 'four'], "Xtest_gf_visual")
new
call setline(1, 'XXXtest_gf_visualXXX')
set hidden
@@ -118,6 +119,30 @@ func Test_gf_visual()
norm! ttvtXgf
call assert_equal('Xtest_gf_visual', bufname('%'))
+ " if multiple lines are selected, then gf should fail
+ call setline(1, ["one", "two"])
+ normal VGgf
+ call assert_equal('Xtest_gf_visual', @%)
+
+ " following line number is used for gF
+ bwipe!
+ new
+ call setline(1, 'XXXtest_gf_visual:3XXX')
+ norm! 0ttvt:gF
+ call assert_equal('Xtest_gf_visual', bufname('%'))
+ call assert_equal(3, getcurpos()[1])
+
+ " line number in visual area is used for file name
+ if has('unix')
+ bwipe!
+ call writefile([], "Xtest_gf_visual:3")
+ new
+ call setline(1, 'XXXtest_gf_visual:3XXX')
+ norm! 0ttvtXgF
+ call assert_equal('Xtest_gf_visual:3', bufname('%'))
+ call delete('Xtest_gf_visual:3')
+ endif
+
bwipe!
call delete('Xtest_gf_visual')
set hidden&
diff --git a/src/nvim/testdir/test_gn.vim b/src/nvim/testdir/test_gn.vim
index d41675be0c..9acec51913 100644
--- a/src/nvim/testdir/test_gn.vim
+++ b/src/nvim/testdir/test_gn.vim
@@ -158,7 +158,32 @@ func Test_gn_command()
set wrapscan&vim
set belloff&vim
-endfu
+endfunc
+
+func Test_gN_repeat()
+ new
+ call setline(1, 'this list is a list with a list of a list.')
+ /list
+ normal $gNgNgNx
+ call assert_equal('list with a list of a list', @")
+ bwipe!
+endfunc
+
+func Test_gN_then_gn()
+ new
+
+ call setline(1, 'this list is a list with a list of a last.')
+ /l.st
+ normal $gNgNgnx
+ call assert_equal('last', @")
+
+ call setline(1, 'this list is a list with a lust of a last.')
+ /l.st
+ normal $gNgNgNgnx
+ call assert_equal('lust of a last', @")
+
+ bwipe!
+endfunc
func Test_gn_multi_line()
new
diff --git a/src/nvim/testdir/test_highlight.vim b/src/nvim/testdir/test_highlight.vim
index 6aa187b17e..00e42733a7 100644
--- a/src/nvim/testdir/test_highlight.vim
+++ b/src/nvim/testdir/test_highlight.vim
@@ -596,9 +596,17 @@ endfunc
" This test must come before the Test_cursorline test, as it appears this
" defines the Normal highlighting group anyway.
func Test_1_highlight_Normalgroup_exists()
- " MS-Windows GUI sets the font
- if !has('win32') || !has('gui_running')
- let hlNormal = HighlightArgs('Normal')
+ let hlNormal = HighlightArgs('Normal')
+ if !has('gui_running')
call assert_match('hi Normal\s*clear', hlNormal)
+ elseif has('gui_gtk2') || has('gui_gnome') || has('gui_gtk3')
+ " expect is DEFAULT_FONT of gui_gtk_x11.c
+ call assert_match('hi Normal\s*font=Monospace 10', hlNormal)
+ elseif has('gui_motif') || has('gui_athena')
+ " expect is DEFAULT_FONT of gui_x11.c
+ call assert_match('hi Normal\s*font=7x13', hlNormal)
+ elseif has('win32')
+ " expect any font
+ call assert_match('hi Normal\s*font=.*', hlNormal)
endif
endfunc
diff --git a/src/nvim/testdir/test_ins_complete.vim b/src/nvim/testdir/test_ins_complete.vim
index 1c275d5bd1..57a0a7aaf4 100644
--- a/src/nvim/testdir/test_ins_complete.vim
+++ b/src/nvim/testdir/test_ins_complete.vim
@@ -1,3 +1,5 @@
+source screendump.vim
+source check.vim
" Test for insert expansion
func Test_ins_complete()
@@ -325,7 +327,10 @@ func Test_compl_in_cmdwin()
set wildmenu wildchar=<Tab>
com! -nargs=1 -complete=command GetInput let input = <q-args>
com! -buffer TestCommand echo 'TestCommand'
+ let w:test_winvar = 'winvar'
+ let b:test_bufvar = 'bufvar'
+ " User-defined commands
let input = ''
call feedkeys("q:iGetInput T\<C-x>\<C-v>\<CR>", 'tx!')
call assert_equal('TestCommand', input)
@@ -334,7 +339,114 @@ func Test_compl_in_cmdwin()
call feedkeys("q::GetInput T\<Tab>\<CR>:q\<CR>", 'tx!')
call assert_equal('T', input)
+ com! -nargs=1 -complete=var GetInput let input = <q-args>
+ " Window-local variables
+ let input = ''
+ call feedkeys("q:iGetInput w:test_\<C-x>\<C-v>\<CR>", 'tx!')
+ call assert_equal('w:test_winvar', input)
+
+ let input = ''
+ call feedkeys("q::GetInput w:test_\<Tab>\<CR>:q\<CR>", 'tx!')
+ call assert_equal('w:test_', input)
+
+ " Buffer-local variables
+ let input = ''
+ call feedkeys("q:iGetInput b:test_\<C-x>\<C-v>\<CR>", 'tx!')
+ call assert_equal('b:test_bufvar', input)
+
+ let input = ''
+ call feedkeys("q::GetInput b:test_\<Tab>\<CR>:q\<CR>", 'tx!')
+ call assert_equal('b:test_', input)
+
delcom TestCommand
delcom GetInput
+ unlet w:test_winvar
+ unlet b:test_bufvar
set wildmenu& wildchar&
endfunc
+
+" Test for insert path completion with completeslash option
+func Test_ins_completeslash()
+ CheckMSWindows
+
+ call mkdir('Xdir')
+
+ let orig_shellslash = &shellslash
+ set cpt&
+
+ new
+
+ set noshellslash
+
+ set completeslash=
+ exe "normal oXd\<C-X>\<C-F>"
+ call assert_equal('Xdir\', getline('.'))
+
+ set completeslash=backslash
+ exe "normal oXd\<C-X>\<C-F>"
+ call assert_equal('Xdir\', getline('.'))
+
+ set completeslash=slash
+ exe "normal oXd\<C-X>\<C-F>"
+ call assert_equal('Xdir/', getline('.'))
+
+ set shellslash
+
+ set completeslash=
+ exe "normal oXd\<C-X>\<C-F>"
+ call assert_equal('Xdir/', getline('.'))
+
+ set completeslash=backslash
+ exe "normal oXd\<C-X>\<C-F>"
+ call assert_equal('Xdir\', getline('.'))
+
+ set completeslash=slash
+ exe "normal oXd\<C-X>\<C-F>"
+ call assert_equal('Xdir/', getline('.'))
+ %bw!
+ call delete('Xdir', 'rf')
+
+ set noshellslash
+ set completeslash=slash
+ call assert_true(stridx(globpath(&rtp, 'syntax/*.vim', 1, 1)[0], '\') != -1)
+
+ let &shellslash = orig_shellslash
+ set completeslash=
+endfunc
+
+func Test_issue_7021()
+ CheckMSWindows
+
+ let orig_shellslash = &shellslash
+ set noshellslash
+
+ set completeslash=slash
+ call assert_false(expand('~') =~ '/')
+
+ let &shellslash = orig_shellslash
+ set completeslash=
+endfunc
+
+func Test_pum_with_folds_two_tabs()
+ CheckScreendump
+
+ let lines =<< trim END
+ set fdm=marker
+ call setline(1, ['" x {{{1', '" a some text'])
+ call setline(3, range(&lines)->map({_, val -> '" a' .. val}))
+ norm! zm
+ tab sp
+ call feedkeys('2Gzv', 'xt')
+ call feedkeys("0fa", 'xt')
+ END
+
+ call writefile(lines, 'Xpumscript')
+ let buf = RunVimInTerminal('-S Xpumscript', #{rows: 10})
+ call term_wait(buf, 100)
+ call term_sendkeys(buf, "a\<C-N>")
+ call VerifyScreenDump(buf, 'Test_pum_with_folds_two_tabs', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+ call delete('Xpumscript')
+endfunc
diff --git a/src/nvim/testdir/test_interrupt.vim b/src/nvim/testdir/test_interrupt.vim
new file mode 100644
index 0000000000..111752d16a
--- /dev/null
+++ b/src/nvim/testdir/test_interrupt.vim
@@ -0,0 +1,27 @@
+" Test behavior of interrupt()
+
+let s:bufwritepre_called = 0
+let s:bufwritepost_called = 0
+
+func s:bufwritepre()
+ let s:bufwritepre_called = 1
+ call interrupt()
+endfunction
+
+func s:bufwritepost()
+ let s:bufwritepost_called = 1
+endfunction
+
+func Test_interrupt()
+ new Xfile
+ let n = 0
+ try
+ au BufWritePre Xfile call s:bufwritepre()
+ au BufWritePost Xfile call s:bufwritepost()
+ w!
+ catch /^Vim:Interrupt$/
+ endtry
+ call assert_equal(1, s:bufwritepre_called)
+ call assert_equal(0, s:bufwritepost_called)
+ call assert_equal(0, filereadable('Xfile'))
+endfunc
diff --git a/src/nvim/testdir/test_lambda.vim b/src/nvim/testdir/test_lambda.vim
index bfbb3e5c5b..f026c8a55f 100644
--- a/src/nvim/testdir/test_lambda.vim
+++ b/src/nvim/testdir/test_lambda.vim
@@ -181,7 +181,7 @@ function! Test_lambda_scope()
let l:D = s:NewCounter2()
call assert_equal(1, l:C())
- call assert_fails(':call l:D()', 'E15:') " E121: then E15:
+ call assert_fails(':call l:D()', 'E121:')
call assert_equal(2, l:C())
endfunction
diff --git a/src/nvim/testdir/test_listdict.vim b/src/nvim/testdir/test_listdict.vim
index bea62cb0ad..8e2a987e74 100644
--- a/src/nvim/testdir/test_listdict.vim
+++ b/src/nvim/testdir/test_listdict.vim
@@ -199,9 +199,9 @@ func Test_dict_big()
try
let n = d[1500]
catch
- let str=substitute(v:exception, '\v(.{14}).*( \d{4}).*', '\1\2', '')
+ let str = substitute(v:exception, '\v(.{14}).*( "\d{4}").*', '\1\2', '')
endtry
- call assert_equal('Vim(let):E716: 1500', str)
+ call assert_equal('Vim(let):E716: "1500"', str)
" lookup each items
for i in range(1500)
@@ -280,6 +280,14 @@ func Test_dict_func_remove_in_use()
call assert_equal(expected, d.func(string(remove(d, 'func'))))
endfunc
+func Test_dict_literal_keys()
+ call assert_equal({'one': 1, 'two2': 2, '3three': 3, '44': 4}, #{one: 1, two2: 2, 3three: 3, 44: 4},)
+
+ " why *{} cannot be used
+ let blue = 'blue'
+ call assert_equal('6', trim(execute('echo 2 *{blue: 3}.blue')))
+endfunc
+
" Nasty: deepcopy() dict that refers to itself (fails when noref used)
func Test_dict_deepcopy()
let d = {1:1, 2:2}
diff --git a/src/nvim/testdir/test_mapping.vim b/src/nvim/testdir/test_mapping.vim
index 82562339f6..152afb4b9d 100644
--- a/src/nvim/testdir/test_mapping.vim
+++ b/src/nvim/testdir/test_mapping.vim
@@ -391,6 +391,42 @@ func Test_motionforce_omap()
delfunc GetCommand
endfunc
+func Test_error_in_map_expr()
+ if !has('terminal') || (has('win32') && has('gui_running'))
+ throw 'Skipped: cannot run Vim in a terminal window'
+ endif
+
+ let lines =<< trim [CODE]
+ func Func()
+ " fail to create list
+ let x = [
+ endfunc
+ nmap <expr> ! Func()
+ set updatetime=50
+ [CODE]
+ call writefile(lines, 'Xtest.vim')
+
+ let buf = term_start(GetVimCommandCleanTerm() .. ' -S Xtest.vim', {'term_rows': 8})
+ let job = term_getjob(buf)
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 8))})
+
+ " GC must not run during map-expr processing, which can make Vim crash.
+ call term_sendkeys(buf, '!')
+ call term_wait(buf, 100)
+ call term_sendkeys(buf, "\<CR>")
+ call term_wait(buf, 100)
+ call assert_equal('run', job_status(job))
+
+ call term_sendkeys(buf, ":qall!\<CR>")
+ call WaitFor({-> job_status(job) ==# 'dead'})
+ if has('unix')
+ call assert_equal('', job_info(job).termsig)
+ endif
+
+ call delete('Xtest.vim')
+ exe buf .. 'bwipe!'
+endfunc
+
" Test for mapping errors
func Test_map_error()
call assert_fails('unmap', 'E474:')
diff --git a/src/nvim/testdir/test_marks.vim b/src/nvim/testdir/test_marks.vim
index 06b9dc9dab..66df57ea39 100644
--- a/src/nvim/testdir/test_marks.vim
+++ b/src/nvim/testdir/test_marks.vim
@@ -94,33 +94,43 @@ func Test_marks_cmd()
new Xtwo
call setline(1, ['ccc', 'ddd'])
norm! $mcGmD
+ exe "norm! GVgg\<Esc>G"
w!
b Xone
let a = split(execute('marks'), "\n")
call assert_equal(9, len(a))
- call assert_equal('mark line col file/text', a[0])
- call assert_equal(" ' 2 0 bbb", a[1])
- call assert_equal(' a 1 0 aaa', a[2])
- call assert_equal(' B 2 2 bbb', a[3])
- call assert_equal(' D 2 0 Xtwo', a[4])
- call assert_equal(' " 1 0 aaa', a[5])
- call assert_equal(' [ 1 0 aaa', a[6])
- call assert_equal(' ] 2 0 bbb', a[7])
- call assert_equal(' . 2 0 bbb', a[8])
+ call assert_equal(['mark line col file/text',
+ \ " ' 2 0 bbb",
+ \ ' a 1 0 aaa',
+ \ ' B 2 2 bbb',
+ \ ' D 2 0 Xtwo',
+ \ ' " 1 0 aaa',
+ \ ' [ 1 0 aaa',
+ \ ' ] 2 0 bbb',
+ \ ' . 2 0 bbb'], a)
b Xtwo
let a = split(execute('marks'), "\n")
- call assert_equal(9, len(a))
- call assert_equal('mark line col file/text', a[0])
- call assert_equal(" ' 1 0 ccc", a[1])
- call assert_equal(' c 1 2 ccc', a[2])
- call assert_equal(' B 2 2 Xone', a[3])
- call assert_equal(' D 2 0 ddd', a[4])
- call assert_equal(' " 2 0 ddd', a[5])
- call assert_equal(' [ 1 0 ccc', a[6])
- call assert_equal(' ] 2 0 ddd', a[7])
- call assert_equal(' . 2 0 ddd', a[8])
+ call assert_equal(11, len(a))
+ call assert_equal(['mark line col file/text',
+ \ " ' 1 0 ccc",
+ \ ' c 1 2 ccc',
+ \ ' B 2 2 Xone',
+ \ ' D 2 0 ddd',
+ \ ' " 2 0 ddd',
+ \ ' [ 1 0 ccc',
+ \ ' ] 2 0 ddd',
+ \ ' . 2 0 ddd',
+ \ ' < 1 0 ccc',
+ \ ' > 2 0 ddd'], a)
+ norm! Gdd
+ w!
+ let a = split(execute('marks <>'), "\n")
+ call assert_equal(3, len(a))
+ call assert_equal(['mark line col file/text',
+ \ ' < 1 0 ccc',
+ \ ' > 2 0 -invalid-'], a)
b Xone
delmarks aB
diff --git a/src/nvim/testdir/test_matchadd_conceal.vim b/src/nvim/testdir/test_matchadd_conceal.vim
index b918525dbc..393e183ddb 100644
--- a/src/nvim/testdir/test_matchadd_conceal.vim
+++ b/src/nvim/testdir/test_matchadd_conceal.vim
@@ -1,9 +1,11 @@
" Test for matchadd() and conceal feature
-if !has('conceal')
- finish
-endif
+
+source check.vim
+CheckFeature conceal
source shared.vim
+source term_util.vim
+source view_util.vim
function! Test_simple_matchadd()
new
@@ -273,3 +275,70 @@ function! Test_matchadd_and_syn_conceal()
call assert_notequal(screenattr(1, 11) , screenattr(1, 12))
call assert_equal(screenattr(1, 11) , screenattr(1, 32))
endfunction
+
+func Test_cursor_column_in_concealed_line_after_window_scroll()
+ CheckRunVimInTerminal
+
+ " Test for issue #5012 fix.
+ " For a concealed line with cursor, there should be no window's cursor
+ " position invalidation during win_update() after scrolling attempt that is
+ " not successful and no real topline change happens. The invalidation would
+ " cause a window's cursor position recalc outside of win_line() where it's
+ " not possible to take conceal into account.
+ let lines =<< trim END
+ 3split
+ let m = matchadd('Conceal', '=')
+ setl conceallevel=2 concealcursor=nc
+ normal gg
+ "==expr==
+ END
+ call writefile(lines, 'Xcolesearch')
+ let buf = RunVimInTerminal('Xcolesearch', {})
+ call term_wait(buf, 100)
+
+ " Jump to something that is beyond the bottom of the window,
+ " so there's a scroll down.
+ call term_sendkeys(buf, ":so %\<CR>")
+ call term_wait(buf, 100)
+ call term_sendkeys(buf, "/expr\<CR>")
+ call term_wait(buf, 100)
+
+ " Are the concealed parts of the current line really hidden?
+ let cursor_row = term_scrape(buf, '.')->map({_, e -> e.chars})->join('')
+ call assert_equal('"expr', cursor_row)
+
+ " BugFix check: Is the window's cursor column properly updated for hidden
+ " parts of the current line?
+ call assert_equal(2, term_getcursor(buf)[1])
+
+ call StopVimInTerminal(buf)
+ call delete('Xcolesearch')
+endfunc
+
+func Test_cursor_column_in_concealed_line_after_leftcol_change()
+ CheckRunVimInTerminal
+
+ " Test for issue #5214 fix.
+ let lines =<< trim END
+ 0put = 'ab' .. repeat('-', &columns) .. 'c'
+ call matchadd('Conceal', '-')
+ set nowrap ss=0 cole=3 cocu=n
+ END
+ call writefile(lines, 'Xcurs-columns')
+ let buf = RunVimInTerminal('-S Xcurs-columns', {})
+
+ " Go to the end of the line (3 columns beyond the end of the screen).
+ " Horizontal scroll would center the cursor in the screen line, but conceal
+ " makes it go to screen column 1.
+ call term_sendkeys(buf, "$")
+ call term_wait(buf)
+
+ " Are the concealed parts of the current line really hidden?
+ call WaitForAssert({-> assert_equal('c', term_getline(buf, '.'))})
+
+ " BugFix check: Is the window's cursor column properly updated for conceal?
+ call assert_equal(1, term_getcursor(buf)[1])
+
+ call StopVimInTerminal(buf)
+ call delete('Xcurs-columns')
+endfunc
diff --git a/src/nvim/testdir/test_messages.vim b/src/nvim/testdir/test_messages.vim
index 7fbf04311d..30239a90c2 100644
--- a/src/nvim/testdir/test_messages.vim
+++ b/src/nvim/testdir/test_messages.vim
@@ -1,20 +1,16 @@
" Tests for :messages, :echomsg, :echoerr
-function Test_messages()
+source shared.vim
+
+func Test_messages()
let oldmore = &more
try
set nomore
- " Avoid the "message maintainer" line.
- let $LANG = ''
- let $LC_ALL = ''
- let $LC_MESSAGES = ''
- let $LC_COLLATE = ''
let arr = map(range(10), '"hello" . v:val')
for s in arr
echomsg s | redraw
endfor
- let result = ''
" get last two messages
redir => result
@@ -25,22 +21,17 @@ function Test_messages()
" clear messages without last one
1messages clear
- redir => result
- redraw | messages
- redir END
- let msg_list = split(result, "\n")
+ let msg_list = GetMessages()
call assert_equal(['hello9'], msg_list)
" clear all messages
messages clear
- redir => result
- redraw | messages
- redir END
- call assert_equal('', result)
+ let msg_list = GetMessages()
+ call assert_equal([], msg_list)
finally
let &more = oldmore
endtry
-endfunction
+endfunc
" Patch 7.4.1696 defined the "clearmode()" command for clearing the mode
" indicator (e.g., "-- INSERT --") when ":stopinsert" is invoked. Message
@@ -74,6 +65,7 @@ func Test_echomsg()
call assert_equal("\n12345", execute(':echomsg 12345'))
call assert_equal("\n[]", execute(':echomsg []'))
call assert_equal("\n[1, 2, 3]", execute(':echomsg [1, 2, 3]'))
+ call assert_equal("\n[1, 2, []]", execute(':echomsg [1, 2, v:_null_list]'))
call assert_equal("\n{}", execute(':echomsg {}'))
call assert_equal("\n{'a': 1, 'b': 2}", execute(':echomsg {"a": 1, "b": 2}'))
if has('float')
diff --git a/src/nvim/testdir/test_mksession.vim b/src/nvim/testdir/test_mksession.vim
index 9c9e04be07..215065f941 100644
--- a/src/nvim/testdir/test_mksession.vim
+++ b/src/nvim/testdir/test_mksession.vim
@@ -9,6 +9,29 @@ endif
source shared.vim
source term_util.vim
+" Test for storing global and local argument list in a session file
+" This one must be done first.
+func Test__mksession_arglocal()
+ enew | only
+ n a b c
+ new
+ arglocal
+ mksession! Xtest_mks.out
+
+ %bwipe!
+ %argdelete
+ argglobal
+ source Xtest_mks.out
+ call assert_equal(2, winnr('$'))
+ call assert_equal(2, arglistid(1))
+ call assert_equal(0, arglistid(2))
+
+ %bwipe!
+ %argdelete
+ argglobal
+ call delete('Xtest_mks.out')
+endfunc
+
func Test_mksession()
tabnew
let wrap_save = &wrap
@@ -155,7 +178,7 @@ endfunc
" Verify that arglist is stored correctly to the session file.
func Test_mksession_arglist()
- argdel *
+ %argdel
next file1 file2 file3 file4
mksession! Xtest_mks.out
source Xtest_mks.out
@@ -307,6 +330,104 @@ func Test_mksession_quote_in_filename()
call delete('Xtest_mks_quoted.out')
endfunc
+" Test for storing global variables in a session file
+func Test_mksession_globals()
+ set sessionoptions+=globals
+
+ " create different global variables
+ let g:Global_string = "Sun is shining"
+ let g:Global_count = 100
+ let g:Global_pi = 3.14
+
+ mksession! Xtest_mks.out
+
+ unlet g:Global_string
+ unlet g:Global_count
+ unlet g:Global_pi
+
+ source Xtest_mks.out
+ call assert_equal("Sun is shining", g:Global_string)
+ call assert_equal(100, g:Global_count)
+ call assert_equal(3.14, g:Global_pi)
+
+ unlet g:Global_string
+ unlet g:Global_count
+ unlet g:Global_pi
+ call delete('Xtest_mks.out')
+ set sessionoptions&
+endfunc
+
+" Test for changing backslash to forward slash in filenames
+func Test_mksession_slash()
+ if exists('+shellslash')
+ throw 'Skipped: cannot use backslash in file name'
+ endif
+ enew
+ %bwipe!
+ e a\\b\\c
+ mksession! Xtest_mks1.out
+ set sessionoptions+=slash
+ mksession! Xtest_mks2.out
+
+ %bwipe!
+ source Xtest_mks1.out
+ call assert_equal('a/b/c', bufname(''))
+ %bwipe!
+ source Xtest_mks2.out
+ call assert_equal('a/b/c', bufname(''))
+
+ %bwipe!
+ call delete('Xtest_mks1.out')
+ call delete('Xtest_mks2.out')
+ set sessionoptions&
+endfunc
+
+" Test for changing directory to the session file directory
+func Test_mksession_sesdir()
+ call mkdir('Xproj')
+ mksession! Xproj/Xtest_mks1.out
+ set sessionoptions-=curdir
+ set sessionoptions+=sesdir
+ mksession! Xproj/Xtest_mks2.out
+
+ source Xproj/Xtest_mks1.out
+ call assert_equal('testdir', fnamemodify(getcwd(), ':t'))
+ source Xproj/Xtest_mks2.out
+ call assert_equal('Xproj', fnamemodify(getcwd(), ':t'))
+ cd ..
+
+ set sessionoptions&
+ call delete('Xproj', 'rf')
+endfunc
+
+" Test for storing the 'lines' and 'columns' settings
+func Test_mksession_resize()
+ mksession! Xtest_mks1.out
+ set sessionoptions+=resize
+ mksession! Xtest_mks2.out
+
+ let lines = readfile('Xtest_mks1.out')
+ let found_resize = v:false
+ for line in lines
+ if line =~ '^set lines='
+ let found_resize = v:true
+ endif
+ endfor
+ call assert_equal(v:false, found_resize)
+ let lines = readfile('Xtest_mks2.out')
+ let found_resize = v:false
+ for line in lines
+ if line =~ '^set lines='
+ let found_resize = v:true
+ endif
+ endfor
+ call assert_equal(v:true, found_resize)
+
+ call delete('Xtest_mks1.out')
+ call delete('Xtest_mks2.out')
+ set sessionoptions&
+endfunc
+
func s:ClearMappings()
mapclear
omapclear
@@ -349,4 +470,17 @@ func Test_mkvimrc()
call delete('Xtestvimrc')
endfunc
+func Test_scrolloff()
+ set sessionoptions+=localoptions
+ setlocal so=1 siso=1
+ mksession! Xtest_mks.out
+ setlocal so=-1 siso=-1
+ source Xtest_mks.out
+ call assert_equal(1, &l:so)
+ call assert_equal(1, &l:siso)
+ call delete('Xtest_mks.out')
+ setlocal so& siso&
+ set sessionoptions&
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_options.vim b/src/nvim/testdir/test_options.vim
index 04a5c62f66..10e16f4198 100644
--- a/src/nvim/testdir/test_options.vim
+++ b/src/nvim/testdir/test_options.vim
@@ -267,7 +267,6 @@ func Test_set_errors()
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.
@@ -576,3 +575,13 @@ func Test_opt_boolean()
set number&
endfunc
+" Test for setting option value containing spaces with isfname+=32
+func Test_isfname_with_options()
+ set isfname+=32
+ setlocal keywordprg=:term\ help.exe
+ call assert_equal(':term help.exe', &keywordprg)
+ set isfname&
+ setlocal keywordprg&
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_perl.vim b/src/nvim/testdir/test_perl.vim
new file mode 100644
index 0000000000..872194a804
--- /dev/null
+++ b/src/nvim/testdir/test_perl.vim
@@ -0,0 +1,311 @@
+" Tests for Perl interface
+
+if !has('perl') || has('win32')
+ finish
+endif
+
+" FIXME: RunTest don't see any error when Perl abort...
+perl $SIG{__WARN__} = sub { die "Unexpected warnings from perl: @_" };
+
+func Test_change_buffer()
+ call setline(line('$'), ['1 line 1'])
+ perl VIM::DoCommand("normal /^1\n")
+ perl $curline = VIM::Eval("line('.')")
+ perl $curbuf->Set($curline, "1 changed line 1")
+ call assert_equal('1 changed line 1', getline('$'))
+endfunc
+
+func Test_evaluate_list()
+ call setline(line('$'), ['2 line 2'])
+ perl VIM::DoCommand("normal /^2\n")
+ perl $curline = VIM::Eval("line('.')")
+ let l = ["abc", "def"]
+ perl << EOF
+ $l = VIM::Eval("l");
+ $curbuf->Append($curline, $l);
+EOF
+ normal j
+ .perldo s|\n|/|g
+ " call assert_equal('abc/def/', getline('$'))
+ call assert_equal('def', getline('$'))
+endfunc
+
+funct Test_VIM_Blob()
+ call assert_equal('0z', perleval('VIM::Blob("")'))
+ call assert_equal('0z31326162', perleval('VIM::Blob("12ab")'))
+ call assert_equal('0z00010203', perleval('VIM::Blob("\x00\x01\x02\x03")'))
+ call assert_equal('0z8081FEFF', perleval('VIM::Blob("\x80\x81\xfe\xff")'))
+endfunc
+
+func Test_buffer_Delete()
+ new
+ call setline(1, ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'])
+ perl $curbuf->Delete(7)
+ perl $curbuf->Delete(2, 5)
+ perl $curbuf->Delete(10)
+ call assert_equal(['a', 'f', 'h'], getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_buffer_Append()
+ new
+ perl $curbuf->Append(1, '1')
+ perl $curbuf->Append(2, '2', '3', '4')
+ perl @l = ('5' ..'7')
+ perl $curbuf->Append(0, @l)
+ call assert_equal(['5', '6', '7', '', '1', '2', '3', '4'], getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_buffer_Set()
+ new
+ call setline(1, ['1', '2', '3', '4', '5'])
+ perl $curbuf->Set(2, 'a', 'b', 'c')
+ perl $curbuf->Set(4, 'A', 'B', 'C')
+ call assert_equal(['1', 'a', 'b', 'A', 'B'], getline(1, '$'))
+ bwipe!
+endfunc
+
+func Test_buffer_Get()
+ new
+ call setline(1, ['1', '2', '3', '4'])
+ call assert_equal('2:3', perleval('join(":", $curbuf->Get(2, 3))'))
+ bwipe!
+endfunc
+
+func Test_buffer_Count()
+ new
+ call setline(1, ['a', 'b', 'c'])
+ call assert_equal(3, perleval('$curbuf->Count()'))
+ bwipe!
+endfunc
+
+func Test_buffer_Name()
+ new
+ call assert_equal('', perleval('$curbuf->Name()'))
+ bwipe!
+ new Xfoo
+ call assert_equal('Xfoo', perleval('$curbuf->Name()'))
+ bwipe!
+endfunc
+
+func Test_buffer_Number()
+ call assert_equal(bufnr('%'), perleval('$curbuf->Number()'))
+endfunc
+
+func Test_window_Cursor()
+ new
+ call setline(1, ['line1', 'line2'])
+ perl $curwin->Cursor(2, 3)
+ call assert_equal('2:3', perleval('join(":", $curwin->Cursor())'))
+ " Col is numbered from 0 in Perl, and from 1 in Vim script.
+ call assert_equal([0, 2, 4, 0], getpos('.'))
+ bwipe!
+endfunc
+
+func Test_window_SetHeight()
+ new
+ perl $curwin->SetHeight(2)
+ call assert_equal(2, winheight(0))
+ bwipe!
+endfunc
+
+func Test_VIM_Windows()
+ new
+ " VIM::Windows() without argument in scalar and list context.
+ perl $winnr = VIM::Windows()
+ perl @winlist = VIM::Windows()
+ perl $curbuf->Append(0, $winnr, scalar(@winlist))
+ call assert_equal(['2', '2', ''], getline(1, '$'))
+
+ " VIM::Windows() with window number argument.
+ perl (VIM::Windows(VIM::Eval('winnr()')))[0]->Buffer()->Set(1, 'bar')
+ call assert_equal('bar', getline(1))
+ bwipe!
+endfunc
+
+func Test_VIM_Buffers()
+ new Xbar
+ " VIM::Buffers() without argument in scalar and list context.
+ perl $nbuf = VIM::Buffers()
+ perl @buflist = VIM::Buffers()
+
+ " VIM::Buffers() with argument.
+ perl $curbuf = (VIM::Buffers('Xbar'))[0]
+ perl $curbuf->Append(0, $nbuf, scalar(@buflist))
+ call assert_equal(['2', '2', ''], getline(1, '$'))
+ bwipe!
+endfunc
+
+func <SID>catch_peval(expr)
+ try
+ call perleval(a:expr)
+ catch
+ return v:exception
+ endtry
+ call assert_report('no exception for `perleval("'.a:expr.'")`')
+ return ''
+endfunc
+
+func Test_perleval()
+ call assert_false(perleval('undef'))
+
+ " scalar
+ call assert_equal(0, perleval('0'))
+ call assert_equal(2, perleval('2'))
+ call assert_equal(-2, perleval('-2'))
+ if has('float')
+ call assert_equal(2.5, perleval('2.5'))
+ else
+ call assert_equal(2, perleval('2.5'))
+ end
+
+ " sandbox call assert_equal(2, perleval('2'))
+
+ call assert_equal('abc', perleval('"abc"'))
+ " call assert_equal("abc\ndef", perleval('"abc\0def"'))
+
+ " ref
+ call assert_equal([], perleval('[]'))
+ call assert_equal(['word', 42, [42],{}], perleval('["word", 42, [42], {}]'))
+
+ call assert_equal({}, perleval('{}'))
+ call assert_equal({'foo': 'bar'}, perleval('{foo => "bar"}'))
+
+ perl our %h; our @a;
+ let a = perleval('[\(%h, %h, @a, @a)]')
+ " call assert_true((a[0] is a[1]))
+ call assert_equal(a[0], a[1])
+ " call assert_true((a[2] is a[3]))
+ call assert_equal(a[2], a[3])
+ perl undef %h; undef @a;
+
+ " call assert_true(<SID>catch_peval('{"" , 0}') =~ 'Malformed key Dictionary')
+ " call assert_true(<SID>catch_peval('{"\0" , 0}') =~ 'Malformed key Dictionary')
+ " call assert_true(<SID>catch_peval('{"foo\0bar" , 0}') =~ 'Malformed key Dictionary')
+
+ call assert_equal('*VIM', perleval('"*VIM"'))
+ " call assert_true(perleval('\\0') =~ 'SCALAR(0x\x\+)')
+endfunc
+
+func Test_perldo()
+ sp __TEST__
+ exe 'read ' g:testname
+ perldo s/perl/vieux_chameau/g
+ 1
+ call assert_false(search('\Cperl'))
+ bw!
+
+ " Check deleting lines does not trigger ml_get error.
+ new
+ call setline(1, ['one', 'two', 'three'])
+ perldo VIM::DoCommand("%d_")
+ bwipe!
+
+ " Check switching to another buffer does not trigger ml_get error.
+ new
+ let wincount = winnr('$')
+ call setline(1, ['one', 'two', 'three'])
+ perldo VIM::DoCommand("new")
+ call assert_equal(wincount + 1, winnr('$'))
+ bwipe!
+ bwipe!
+endfunc
+
+func Test_VIM_package()
+ perl VIM::DoCommand('let l:var = "foo"')
+ call assert_equal(l:var, 'foo')
+
+ set noet
+ perl VIM::SetOption('et')
+ call assert_true(&et)
+endfunc
+
+func Test_stdio()
+ throw 'skipped: TODO: '
+ redir =>l:out
+ perl <<EOF
+ VIM::Msg("&VIM::Msg");
+ print "STDOUT";
+ print STDERR "STDERR";
+EOF
+ redir END
+ call assert_equal(['&VIM::Msg', 'STDOUT', 'STDERR'], split(l:out, "\n"))
+endfunc
+
+" Run first to get a clean namespace
+func Test_000_SvREFCNT()
+ throw 'skipped: TODO: '
+ for i in range(8)
+ exec 'new X'.i
+ endfor
+ new t
+ perl <<--perl
+#line 5 "Test_000_SvREFCNT()"
+ my ($b, $w);
+
+ my $num = 0;
+ for ( 0 .. 100 ) {
+ if ( ++$num >= 8 ) { $num = 0 }
+ VIM::DoCommand("buffer X$num");
+ $b = $curbuf;
+ }
+
+ VIM::DoCommand("buffer t");
+
+ $b = $curbuf for 0 .. 100;
+ $w = $curwin for 0 .. 100;
+ () = VIM::Buffers for 0 .. 100;
+ () = VIM::Windows for 0 .. 100;
+
+ VIM::DoCommand('bw! t');
+ if (exists &Internals::SvREFCNT) {
+ my $cb = Internals::SvREFCNT($$b);
+ my $cw = Internals::SvREFCNT($$w);
+ VIM::Eval("assert_equal(2, $cb, 'T1')");
+ VIM::Eval("assert_equal(2, $cw, 'T2')");
+ my $strongref;
+ foreach ( VIM::Buffers, VIM::Windows ) {
+ VIM::DoCommand("%bw!");
+ my $c = Internals::SvREFCNT($_);
+ VIM::Eval("assert_equal(2, $c, 'T3')");
+ $c = Internals::SvREFCNT($$_);
+ next if $c == 2 && !$strongref++;
+ VIM::Eval("assert_equal(1, $c, 'T4')");
+ }
+ $cb = Internals::SvREFCNT($$curbuf);
+ $cw = Internals::SvREFCNT($$curwin);
+ VIM::Eval("assert_equal(3, $cb, 'T5')");
+ VIM::Eval("assert_equal(3, $cw, 'T6')");
+ }
+ VIM::Eval("assert_false($$b)");
+ VIM::Eval("assert_false($$w)");
+--perl
+ %bw!
+endfunc
+
+func Test_set_cursor()
+ " Check that setting the cursor position works.
+ new
+ call setline(1, ['first line', 'second line'])
+ normal gg
+ perldo $curwin->Cursor(1, 5)
+ call assert_equal([1, 6], [line('.'), col('.')])
+
+ " Check that movement after setting cursor position keeps current column.
+ normal j
+ call assert_equal([2, 6], [line('.'), col('.')])
+endfunc
+
+" Test for various heredoc syntax
+func Test_perl_heredoc()
+ perl << END
+VIM::DoCommand('let s = "A"')
+END
+ perl <<
+VIM::DoCommand('let s ..= "B"')
+.
+ call assert_equal('AB', s)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_popup.vim b/src/nvim/testdir/test_popup.vim
index bb0ed6e00c..fb464d95ea 100644
--- a/src/nvim/testdir/test_popup.vim
+++ b/src/nvim/testdir/test_popup.vim
@@ -2,6 +2,7 @@
source shared.vim
source screendump.vim
+source check.vim
let g:months = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December']
let g:setting = ''
@@ -755,6 +756,52 @@ func Test_popup_and_previewwindow_dump()
call delete('Xscript')
endfunc
+func Test_balloon_split()
+ CheckFunction balloon_split
+
+ call assert_equal([
+ \ 'tempname: 0x555555e380a0 "/home/mool/.viminfz.tmp"',
+ \ ], balloon_split(
+ \ 'tempname: 0x555555e380a0 "/home/mool/.viminfz.tmp"'))
+ call assert_equal([
+ \ 'one two three four one two three four one two thre',
+ \ 'e four',
+ \ ], balloon_split(
+ \ 'one two three four one two three four one two three four'))
+
+ eval 'struct = {one = 1, two = 2, three = 3}'
+ \ ->balloon_split()
+ \ ->assert_equal([
+ \ 'struct = {',
+ \ ' one = 1,',
+ \ ' two = 2,',
+ \ ' three = 3}',
+ \ ])
+
+ call assert_equal([
+ \ 'struct = {',
+ \ ' one = 1,',
+ \ ' nested = {',
+ \ ' n1 = "yes",',
+ \ ' n2 = "no"}',
+ \ ' two = 2}',
+ \ ], balloon_split(
+ \ 'struct = {one = 1, nested = {n1 = "yes", n2 = "no"} two = 2}'))
+ call assert_equal([
+ \ 'struct = 0x234 {',
+ \ ' long = 2343 "\\"some long string that will be wr',
+ \ 'apped in two\\"",',
+ \ ' next = 123}',
+ \ ], balloon_split(
+ \ 'struct = 0x234 {long = 2343 "\\"some long string that will be wrapped in two\\"", next = 123}'))
+ call assert_equal([
+ \ 'Some comment',
+ \ '',
+ \ 'typedef this that;',
+ \ ], balloon_split(
+ \ "Some comment\n\ntypedef this that;"))
+endfunc
+
func Test_popup_position()
if !CanRunVimInTerminal()
return
diff --git a/src/nvim/testdir/test_put.vim b/src/nvim/testdir/test_put.vim
index 884ada7e88..15745d5619 100644
--- a/src/nvim/testdir/test_put.vim
+++ b/src/nvim/testdir/test_put.vim
@@ -22,12 +22,21 @@ 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))
+ call assert_equal(['L one 1', '', 'L one 3', ''], getline(1, 4))
+ " clean up
+ bw!
+endfunc
+
+func Test_put_lines()
+ new
+ let a = [ getreg('a'), getregtype('a') ]
+ call setline(1, ['Line 1', 'Line2', 'Line 3', ''])
+ exe 'norm! gg"add"AddG""p'
+ call assert_equal(['Line 3', '', 'Line 1', 'Line2'], getline(1, '$'))
" clean up
bw!
call setreg('a', a[0], a[1])
@@ -42,21 +51,10 @@ func Test_put_expr()
exec "4norm! \"=\<cr>P"
norm! j0.
norm! j0.
- call assert_equal(['A1','A2','A3','4A','5A','6A'], getline(1,'$'))
+ call assert_equal(['A1','A2','A3','4A','5A','6A'], getline(1, '$'))
bw!
endfunc
-func Test_put_lines()
- new
- let a = [ getreg('a'), getregtype('a') ]
- call setline(1, ['Line 1', 'Line2', 'Line 3', ''])
- exe 'norm! gg"add"AddG""p'
- call assert_equal(['Line 3', '', 'Line 1', 'Line2'], getline(1,'$'))
- " clean up
- bw!
- call setreg('a', a[0], a[1])
-endfunc
-
func Test_put_fails_when_nomodifiable()
new
setlocal nomodifiable
diff --git a/src/nvim/testdir/test_quickfix.vim b/src/nvim/testdir/test_quickfix.vim
index 35555ca9d3..049163890e 100644
--- a/src/nvim/testdir/test_quickfix.vim
+++ b/src/nvim/testdir/test_quickfix.vim
@@ -1,8 +1,7 @@
" Test for the quickfix commands.
-if !has('quickfix')
- finish
-endif
+source check.vim
+CheckFeature quickfix
set encoding=utf-8
@@ -95,7 +94,7 @@ func XlistTests(cchar)
" Populate the list and then try
Xgetexpr ['non-error 1', 'Xtestfile1:1:3:Line1',
\ 'non-error 2', 'Xtestfile2:2:2:Line2',
- \ 'non-error 3', 'Xtestfile3:3:1:Line3']
+ \ 'non-error| 3', 'Xtestfile3:3:1:Line3']
" List only valid entries
let l = split(execute('Xlist', ''), "\n")
@@ -107,7 +106,7 @@ func XlistTests(cchar)
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)
+ \ ' 5: non-error| 3', ' 6 Xtestfile3:3 col 1: Line3'], l)
" List a range of errors
let l = split(execute('Xlist 3,6', ''), "\n")
@@ -507,7 +506,7 @@ func Xtest_browse(cchar)
Xexpr ""
call assert_equal(0, g:Xgetlist({'idx' : 0}).idx)
call assert_equal(0, g:Xgetlist({'size' : 0}).size)
- Xaddexpr ['foo', 'bar', 'baz', 'quux', 'shmoo']
+ Xaddexpr ['foo', 'bar', 'baz', 'quux', 'sh|moo']
call assert_equal(5, g:Xgetlist({'size' : 0}).size)
Xlast
call assert_equal(5, g:Xgetlist({'idx' : 0}).idx)
@@ -554,6 +553,33 @@ func s:test_xhelpgrep(cchar)
" This wipes out the buffer, make sure that doesn't cause trouble.
Xclose
+ " When the current window is vertically split, jumping to a help match
+ " should open the help window at the top.
+ only | enew
+ let w1 = win_getid()
+ vert new
+ let w2 = win_getid()
+ Xnext
+ let w3 = win_getid()
+ call assert_true(&buftype == 'help')
+ call assert_true(winnr() == 1)
+ " See jump_to_help_window() for details
+ let w2_width = winwidth(w2)
+ if w2_width != &columns && w2_width < 80
+ call assert_equal(['col', [['leaf', w3],
+ \ ['row', [['leaf', w2], ['leaf', w1]]]]], winlayout())
+ else
+ call assert_equal(['row', [['col', [['leaf', w3], ['leaf', w2]]],
+ \ ['leaf', w1]]] , winlayout())
+ endif
+
+ new | only
+ set buftype=help
+ set modified
+ call assert_fails('Xnext', 'E37:')
+ set nomodified
+ new | only
+
if a:cchar == 'l'
" When a help window is present, running :lhelpgrep should reuse the
" help window and not the current window
@@ -1257,6 +1283,30 @@ func Test_quickfix_was_changed_by_autocmd()
call XquickfixChangedByAutocmd('l')
endfunc
+func Test_setloclist_in_autocommand()
+ call writefile(['test1', 'test2'], 'Xfile')
+ edit Xfile
+ let s:bufnr = bufnr()
+ call setloclist(1,
+ \ [{'bufnr' : s:bufnr, 'lnum' : 1, 'text' : 'test1'},
+ \ {'bufnr' : s:bufnr, 'lnum' : 2, 'text' : 'test2'}])
+
+ augroup Test_LocList
+ au!
+ autocmd BufEnter * call setloclist(1,
+ \ [{'bufnr' : s:bufnr, 'lnum' : 1, 'text' : 'test1'},
+ \ {'bufnr' : s:bufnr, 'lnum' : 2, 'text' : 'test2'}], 'r')
+ augroup END
+
+ lopen
+ call assert_fails('exe "normal j\<CR>"', 'E926:')
+
+ augroup Test_LocList
+ au!
+ augroup END
+ call delete('Xfile')
+endfunc
+
func Test_caddbuffer_to_empty()
helpgr quickfix
call setqflist([], 'r')
@@ -1570,6 +1620,24 @@ func Test_long_lines()
call s:long_lines_tests('l')
endfunc
+func Test_cgetfile_on_long_lines()
+ " Problematic values if the line is longer than 4096 bytes. Then 1024 bytes
+ " are read at a time.
+ for len in [4078, 4079, 4080, 5102, 5103, 5104, 6126, 6127, 6128, 7150, 7151, 7152]
+ let lines = [
+ \ '/tmp/file1:1:1:aaa',
+ \ '/tmp/file2:1:1:%s',
+ \ '/tmp/file3:1:1:bbb',
+ \ '/tmp/file4:1:1:ccc',
+ \ ]
+ let lines[1] = substitute(lines[1], '%s', repeat('x', len), '')
+ call writefile(lines, 'Xcqetfile.txt')
+ cgetfile Xcqetfile.txt
+ call assert_equal(4, getqflist(#{size: v:true}).size, 'with length ' .. len)
+ endfor
+ call delete('Xcqetfile.txt')
+endfunc
+
func s:create_test_file(filename)
let l = []
for i in range(1, 20)
@@ -1847,6 +1915,13 @@ func HistoryTest(cchar)
call g:Xsetlist([], 'f')
let l = split(execute(a:cchar . 'hist'), "\n")
call assert_equal('No entries', l[0])
+
+ " An empty list should still show the stack history
+ call g:Xsetlist([])
+ let res = split(execute(a:cchar . 'hist'), "\n")
+ call assert_equal('> error list 1 of 1; 0 ' . common, res[0])
+
+ call g:Xsetlist([], 'f')
endfunc
func Test_history()
@@ -2097,6 +2172,9 @@ func Xproperty_tests(cchar)
call assert_equal(['Colors'], newl2.context)
call assert_equal('Line10', newl2.items[0].text)
call g:Xsetlist([], 'f')
+
+ " Cannot specify both a non-empty list argument and a dict argument
+ call assert_fails("call g:Xsetlist([{}], ' ', {})", 'E475:')
endfunc
func Test_qf_property()
@@ -2104,6 +2182,56 @@ func Test_qf_property()
call Xproperty_tests('l')
endfunc
+" Test for setting the current index in the location/quickfix list
+func Xtest_setqfidx(cchar)
+ call s:setup_commands(a:cchar)
+
+ Xgetexpr "F1:10:1:Line1\nF2:20:2:Line2\nF3:30:3:Line3"
+ Xgetexpr "F4:10:1:Line1\nF5:20:2:Line2\nF6:30:3:Line3"
+ Xgetexpr "F7:10:1:Line1\nF8:20:2:Line2\nF9:30:3:Line3"
+
+ call g:Xsetlist([], 'a', {'nr' : 3, 'idx' : 2})
+ call g:Xsetlist([], 'a', {'nr' : 2, 'idx' : 2})
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 3})
+ Xolder 2
+ Xopen
+ call assert_equal(3, line('.'))
+ Xnewer
+ call assert_equal(2, line('.'))
+ Xnewer
+ call assert_equal(2, line('.'))
+ " Update the current index with the quickfix window open
+ wincmd w
+ call g:Xsetlist([], 'a', {'nr' : 3, 'idx' : 3})
+ Xopen
+ call assert_equal(3, line('.'))
+ Xclose
+
+ " Set the current index to the last entry
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : '$'})
+ call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
+ " A large value should set the index to the last index
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 1})
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 999})
+ call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
+ " Invalid index values
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : -1})
+ call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 0})
+ call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
+ call g:Xsetlist([], 'a', {'nr' : 1, 'idx' : 'xx'})
+ call assert_equal(3, g:Xgetlist({'nr' : 1, 'idx' : 0}).idx)
+ call assert_fails("call g:Xsetlist([], 'a', {'nr':1, 'idx':[]})", 'E745:')
+
+ call g:Xsetlist([], 'f')
+ new | only
+endfunc
+
+func Test_setqfidx()
+ call Xtest_setqfidx('c')
+ call Xtest_setqfidx('l')
+endfunc
+
" Tests for the QuickFixCmdPre/QuickFixCmdPost autocommands
func QfAutoCmdHandler(loc, cmd)
call add(g:acmds, a:loc . a:cmd)
@@ -2423,6 +2551,57 @@ func Test_vimgrep()
call XvimgrepTests('l')
endfunc
+" Test for incsearch highlighting of the :vimgrep pattern
+" This test used to cause "E315: ml_get: invalid lnum" errors.
+func Test_vimgrep_incsearch()
+ throw 'skipped: Nvim does not support test_override()'
+ enew
+ set incsearch
+ call test_override("char_avail", 1)
+
+ call feedkeys(":2vimgrep assert test_quickfix.vim test_cdo.vim\<CR>", "ntx")
+ let l = getqflist()
+ call assert_equal(2, len(l))
+
+ call test_override("ALL", 0)
+ set noincsearch
+endfunc
+
+" Test vimgrep without swap file
+func Test_vimgrep_without_swap_file()
+ let lines =<< trim [SCRIPT]
+ vimgrep grep test_c*
+ call writefile(['done'], 'Xresult')
+ qall!
+ [SCRIPT]
+ call writefile(lines, 'Xscript')
+ if RunVim([], [], '--clean -n -S Xscript Xscript')
+ call assert_equal(['done'], readfile('Xresult'))
+ endif
+ call delete('Xscript')
+ call delete('Xresult')
+endfunc
+
+func Test_vimgrep_existing_swapfile()
+ call writefile(['match apple with apple'], 'Xapple')
+ call writefile(['swapfile'], '.Xapple.swp')
+ let g:foundSwap = 0
+ let g:ignoreSwapExists = 1
+ augroup grep
+ au SwapExists * let foundSwap = 1 | let v:swapchoice = 'e'
+ augroup END
+ vimgrep apple Xapple
+ call assert_equal(1, g:foundSwap)
+ call assert_match('.Xapple.swo', swapname(''))
+
+ call delete('Xapple')
+ call delete('Xapple.swp')
+ augroup grep
+ au! SwapExists
+ augroup END
+ unlet g:ignoreSwapExists
+endfunc
+
func XfreeTests(cchar)
call s:setup_commands(a:cchar)
@@ -3315,6 +3494,17 @@ func Test_lvimgrep_crash()
augroup QF_Test
au!
augroup END
+
+ new | only
+ augroup QF_Test
+ au!
+ au BufEnter * call setloclist(0, [], 'r')
+ augroup END
+ call assert_fails('lvimgrep Test_lvimgrep_crash *', 'E926:')
+ augroup QF_Test
+ au!
+ augroup END
+
enew | only
endfunc
@@ -3405,6 +3595,41 @@ func Test_lhelpgrep_autocmd()
call assert_equal('help', &filetype)
call assert_equal(1, getloclist(0, {'nr' : '$'}).nr)
au! QuickFixCmdPost
+
+ new | only
+ augroup QF_Test
+ au!
+ au BufEnter * call setqflist([], 'f')
+ augroup END
+ call assert_fails('helpgrep quickfix', 'E925:')
+ " run the test with a help window already open
+ help
+ wincmd w
+ call assert_fails('helpgrep quickfix', 'E925:')
+ augroup QF_Test
+ au! BufEnter
+ augroup END
+
+ new | only
+ augroup QF_Test
+ au!
+ au BufEnter * call setqflist([], 'r')
+ augroup END
+ call assert_fails('helpgrep quickfix', 'E925:')
+ augroup QF_Test
+ au! BufEnter
+ augroup END
+
+ new | only
+ augroup QF_Test
+ au!
+ au BufEnter * call setloclist(0, [], 'r')
+ augroup END
+ call assert_fails('lhelpgrep quickfix', 'E926:')
+ augroup QF_Test
+ au! BufEnter
+ augroup END
+
new | only
endfunc
@@ -3430,6 +3655,18 @@ func Test_shorten_fname()
" Displaying the quickfix list should simplify the file path
silent! clist
call assert_equal('test_quickfix.vim', bufname('test_quickfix.vim'))
+ " Add a few entries for the same file with different paths and check whether
+ " the buffer name is shortened
+ %bwipe
+ call setqflist([], 'f')
+ call setqflist([{'filename' : 'test_quickfix.vim', 'lnum' : 10},
+ \ {'filename' : '../testdir/test_quickfix.vim', 'lnum' : 20},
+ \ {'filename' : fname, 'lnum' : 30}], ' ')
+ copen
+ call assert_equal(['test_quickfix.vim|10| ',
+ \ 'test_quickfix.vim|20| ',
+ \ 'test_quickfix.vim|30| '], getline(1, '$'))
+ cclose
endfunc
" Quickfix title tests
@@ -3730,6 +3967,52 @@ func Test_curswant()
cclose | helpclose
endfunc
+" Test for opening a file from the quickfix window using CTRL-W <Enter>
+" doesn't leave an empty buffer around.
+func Test_splitview()
+ call s:create_test_file('Xtestfile1')
+ call s:create_test_file('Xtestfile2')
+ new | only
+ let last_bufnr = bufnr('Test_sv_1', 1)
+ let l = ['Xtestfile1:2:Line2', 'Xtestfile2:4:Line4']
+ cgetexpr l
+ copen
+ let numbufs = len(getbufinfo())
+ exe "normal \<C-W>\<CR>"
+ copen
+ exe "normal j\<C-W>\<CR>"
+ " Make sure new empty buffers are not created
+ call assert_equal(numbufs, len(getbufinfo()))
+ " Creating a new buffer should use the next available buffer number
+ call assert_equal(last_bufnr + 4, bufnr("Test_sv_2", 1))
+ bwipe Test_sv_1
+ bwipe Test_sv_2
+ new | only
+
+ " When split opening files from location list window, make sure that two
+ " windows doesn't refer to the same location list
+ lgetexpr l
+ let locid = getloclist(0, {'id' : 0}).id
+ lopen
+ exe "normal \<C-W>\<CR>"
+ call assert_notequal(locid, getloclist(0, {'id' : 0}).id)
+ call assert_equal(0, getloclist(0, {'winid' : 0}).winid)
+ new | only
+
+ " When split opening files from a helpgrep location list window, a new help
+ " window should be opend with a copy of the location list.
+ lhelpgrep window
+ let locid = getloclist(0, {'id' : 0}).id
+ lwindow
+ exe "normal j\<C-W>\<CR>"
+ call assert_notequal(locid, getloclist(0, {'id' : 0}).id)
+ call assert_equal(0, getloclist(0, {'winid' : 0}).winid)
+ new | only
+
+ call delete('Xtestfile1')
+ call delete('Xtestfile2')
+endfunc
+
" Test for parsing entries using visual screen column
func Test_viscol()
enew
@@ -3781,11 +4064,102 @@ func Test_viscol()
cnext
call assert_equal([16, 25], [col('.'), virtcol('.')])
+ " Use screen column number with a multi-line error message
+ enew
+ call writefile(["à test"], 'Xfile1')
+ set efm=%E===\ %f\ ===,%C%l:%v,%Z%m
+ cexpr ["=== Xfile1 ===", "1:3", "errormsg"]
+ call assert_equal('Xfile1', @%)
+ call assert_equal([0, 1, 4, 0], getpos('.'))
+
+ " Repeat previous test with byte offset %c: ensure that fix to issue #7145
+ " does not break this
+ set efm=%E===\ %f\ ===,%C%l:%c,%Z%m
+ cexpr ["=== Xfile1 ===", "1:3", "errormsg"]
+ call assert_equal('Xfile1', @%)
+ call assert_equal([0, 1, 3, 0], getpos('.'))
+
enew | only
set efm&
call delete('Xfile1')
endfunc
+" Test for the quickfix window buffer
+func Xqfbuf_test(cchar)
+ call s:setup_commands(a:cchar)
+
+ " Quickfix buffer should be reused across closing and opening a quickfix
+ " window
+ Xexpr "F1:10:Line10"
+ Xopen
+ let qfbnum = bufnr('')
+ Xclose
+ " Even after the quickfix window is closed, the buffer should be loaded
+ call assert_true(bufloaded(qfbnum))
+ Xopen
+ " Buffer should be reused when opening the window again
+ call assert_equal(qfbnum, bufnr(''))
+ Xclose
+
+ if a:cchar == 'l'
+ %bwipe
+ " For a location list, when both the file window and the location list
+ " window for the list are closed, then the buffer should be freed.
+ new | only
+ lexpr "F1:10:Line10"
+ let wid = win_getid()
+ lopen
+ let qfbnum = bufnr('')
+ call assert_match(qfbnum . ' %a- "\[Location List]"', execute('ls'))
+ close
+ " When the location list window is closed, the buffer name should not
+ " change to 'Quickfix List'
+ call assert_match(qfbnum . ' h- "\[Location List]"', execute('ls'))
+ call assert_true(bufloaded(qfbnum))
+
+ " After deleting a location list buffer using ":bdelete", opening the
+ " location list window should mark the buffer as a location list buffer.
+ exe "bdelete " . qfbnum
+ lopen
+ call assert_equal("quickfix", &buftype)
+ call assert_equal(1, getwininfo(win_getid(winnr()))[0].loclist)
+ call assert_equal(wid, getloclist(0, {'filewinid' : 0}).filewinid)
+ call assert_false(&swapfile)
+ lclose
+
+ " When the location list is cleared for the window, the buffer should be
+ " removed
+ call setloclist(0, [], 'f')
+ call assert_false(bufexists(qfbnum))
+
+ " When the location list is freed with the location list window open, the
+ " location list buffer should not be lost. It should be reused when the
+ " location list is again populated.
+ lexpr "F1:10:Line10"
+ lopen
+ let wid = win_getid()
+ let qfbnum = bufnr('')
+ wincmd p
+ call setloclist(0, [], 'f')
+ lexpr "F1:10:Line10"
+ lopen
+ call assert_equal(wid, win_getid())
+ call assert_equal(qfbnum, bufnr(''))
+ lclose
+
+ " When the window with the location list is closed, the buffer should be
+ " removed
+ new | only
+ call assert_false(bufexists(qfbnum))
+ endif
+endfunc
+
+func Test_qfbuf()
+ throw 'skipped: enable after porting patch 8.1.0877'
+ call Xqfbuf_test('c')
+ call Xqfbuf_test('l')
+endfunc
+
" Test to make sure that an empty quickfix buffer is not reused for loading
" a normal buffer.
func Test_empty_qfbuf()
@@ -3994,4 +4368,46 @@ func Test_qfcmd_abort()
augroup END
endfunc
+" Test for using a file in one of the parent directories.
+func Test_search_in_dirstack()
+ call mkdir('Xtestdir/a/b/c', 'p')
+ let save_cwd = getcwd()
+ call writefile(["X1_L1", "X1_L2"], 'Xtestdir/Xfile1')
+ call writefile(["X2_L1", "X2_L2"], 'Xtestdir/a/Xfile2')
+ call writefile(["X3_L1", "X3_L2"], 'Xtestdir/a/b/Xfile3')
+ call writefile(["X4_L1", "X4_L2"], 'Xtestdir/a/b/c/Xfile4')
+
+ let lines = "Entering dir Xtestdir\n" .
+ \ "Entering dir a\n" .
+ \ "Entering dir b\n" .
+ \ "Xfile2:2:X2_L2\n" .
+ \ "Leaving dir a\n" .
+ \ "Xfile1:2:X1_L2\n" .
+ \ "Xfile3:1:X3_L1\n" .
+ \ "Entering dir c\n" .
+ \ "Xfile4:2:X4_L2\n" .
+ \ "Leaving dir c\n"
+ set efm=%DEntering\ dir\ %f,%XLeaving\ dir\ %f,%f:%l:%m
+ cexpr lines .. "Leaving dir Xtestdir|\n" | let next = 1
+ call assert_equal(11, getqflist({'size' : 0}).size)
+ call assert_equal(4, getqflist({'idx' : 0}).idx)
+ call assert_equal('X2_L2', getline('.'))
+ call assert_equal(1, next)
+ cnext
+ call assert_equal(6, getqflist({'idx' : 0}).idx)
+ call assert_equal('X1_L2', getline('.'))
+ cnext
+ call assert_equal(7, getqflist({'idx' : 0}).idx)
+ call assert_equal(1, line('$'))
+ call assert_equal('', getline(1))
+ cnext
+ call assert_equal(9, getqflist({'idx' : 0}).idx)
+ call assert_equal(1, line('$'))
+ call assert_equal('', getline(1))
+
+ set efm&
+ exe 'cd ' . save_cwd
+ call delete('Xtestdir', 'rf')
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_regexp_utf8.vim b/src/nvim/testdir/test_regexp_utf8.vim
index f48458566b..4466ad436a 100644
--- a/src/nvim/testdir/test_regexp_utf8.vim
+++ b/src/nvim/testdir/test_regexp_utf8.vim
@@ -32,6 +32,9 @@ func Test_equivalence_re2()
endfunc
func s:classes_test()
+ if has('win32')
+ set iskeyword=@,48-57,_,192-255
+ endif
set isprint=@,161-255
call assert_equal('Motörhead', matchstr('Motörhead', '[[:print:]]\+'))
@@ -51,6 +54,12 @@ func s:classes_test()
let tabchar = ''
let upperchars = ''
let xdigitchars = ''
+ let identchars = ''
+ let identchars1 = ''
+ let kwordchars = ''
+ let kwordchars1 = ''
+ let fnamechars = ''
+ let fnamechars1 = ''
let i = 1
while i <= 255
let c = nr2char(i)
@@ -102,6 +111,24 @@ func s:classes_test()
if c =~ '[[:xdigit:]]'
let xdigitchars .= c
endif
+ if c =~ '[[:ident:]]'
+ let identchars .= c
+ endif
+ if c =~ '\i'
+ let identchars1 .= c
+ endif
+ if c =~ '[[:keyword:]]'
+ let kwordchars .= c
+ endif
+ if c =~ '\k'
+ let kwordchars1 .= c
+ endif
+ if c =~ '[[:fname:]]'
+ let fnamechars .= c
+ endif
+ if c =~ '\f'
+ let fnamechars1 .= c
+ endif
let i += 1
endwhile
@@ -121,6 +148,37 @@ func s:classes_test()
call assert_equal("\t\n\x0b\f\r ", spacechars)
call assert_equal("\t", tabchar)
call assert_equal('0123456789ABCDEFabcdef', xdigitchars)
+
+ if has('win32')
+ let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz€‚ƒ„…†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ ¡¢£¤¥¦§µÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõöøùúûüýþÿ'
+ let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ elseif has('ebcdic')
+ let identchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz€ŒŽœž¬®µº¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ let kwordchars_ok = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz€ŒŽœž¬®µº¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖØÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ else
+ let identchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ let kwordchars_ok = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyzµÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ endif
+
+ if has('win32')
+ let fnamechars_ok = '!#$%+,-./0123456789:=@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]_abcdefghijklmnopqrstuvwxyz{}~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ elseif has('amiga')
+ let fnamechars_ok = '$+,-./0123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ elseif has('vms')
+ let fnamechars_ok = '#$%+,-./0123456789:;<>ABCDEFGHIJKLMNOPQRSTUVWXYZ[]_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ elseif has('ebcdic')
+ let fnamechars_ok = '#$%+,-./=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ else
+ let fnamechars_ok = '#$%+,-./0123456789=ABCDEFGHIJKLMNOPQRSTUVWXYZ_abcdefghijklmnopqrstuvwxyz~ ¡¢£¤¥¦§¨©ª«¬­®¯°±²³´µ¶·¸¹º»¼½¾¿ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖרÙÚÛÜÝÞßàáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ'
+ endif
+
+ call assert_equal(identchars_ok, identchars)
+ call assert_equal(kwordchars_ok, kwordchars)
+ call assert_equal(fnamechars_ok, fnamechars)
+
+ call assert_equal(identchars1, identchars)
+ call assert_equal(kwordchars1, kwordchars)
+ call assert_equal(fnamechars1, fnamechars)
endfunc
func Test_classes_re1()
@@ -351,4 +409,128 @@ func Test_regexp_ignore_case()
set regexpengine&
endfunc
+" Tests for regexp with multi-byte encoding and various magic settings
+func Run_regexp_multibyte_magic()
+ let text =<< trim END
+ 1 a aa abb abbccc
+ 2 d dd dee deefff
+ 3 g gg ghh ghhiii
+ 4 j jj jkk jkklll
+ 5 m mm mnn mnnooo
+ 6 x ^aa$ x
+ 7 (a)(b) abbaa
+ 8 axx [ab]xx
+ 9 หม่x อมx
+ a อมx หม่x
+ b ちカヨは
+ c x ¬€x
+ d 天使x
+ e y
+ f z
+ g a啷bb
+ j 0123❤x
+ k combinations
+ l äö üᾱ̆́
+ END
+
+ new
+ call setline(1, text)
+ exe 'normal /a*b\{2}c\+/e' .. "\<CR>x"
+ call assert_equal('1 a aa abb abbcc', getline('.'))
+ exe 'normal /\Md\*e\{2}f\+/e' .. "\<CR>x"
+ call assert_equal('2 d dd dee deeff', getline('.'))
+ set nomagic
+ exe 'normal /g\*h\{2}i\+/e' .. "\<CR>x"
+ call assert_equal('3 g gg ghh ghhii', getline('.'))
+ exe 'normal /\mj*k\{2}l\+/e' .. "\<CR>x"
+ call assert_equal('4 j jj jkk jkkll', getline('.'))
+ exe 'normal /\vm*n{2}o+/e' .. "\<CR>x"
+ call assert_equal('5 m mm mnn mnnoo', getline('.'))
+ exe 'normal /\V^aa$/' .. "\<CR>x"
+ call assert_equal('6 x aa$ x', getline('.'))
+ set magic
+ exe 'normal /\v(a)(b)\2\1\1/e' .. "\<CR>x"
+ call assert_equal('7 (a)(b) abba', getline('.'))
+ exe 'normal /\V[ab]\(\[xy]\)\1' .. "\<CR>x"
+ call assert_equal('8 axx ab]xx', getline('.'))
+
+ " search for multi-byte without composing char
+ exe 'normal /ม' .. "\<CR>x"
+ call assert_equal('9 หม่x อx', getline('.'))
+
+ " search for multi-byte with composing char
+ exe 'normal /ม่' .. "\<CR>x"
+ call assert_equal('a อมx หx', getline('.'))
+
+ " find word by change of word class
+ exe 'normal /ち\<カヨ\>は' .. "\<CR>x"
+ call assert_equal('b カヨは', getline('.'))
+
+ " Test \%u, [\u] and friends
+ " c
+ exe 'normal /\%u20ac' .. "\<CR>x"
+ call assert_equal('c x ¬x', getline('.'))
+ " d
+ exe 'normal /[\u4f7f\u5929]\+' .. "\<CR>x"
+ call assert_equal('d 使x', getline('.'))
+ " e
+ exe 'normal /\%U12345678' .. "\<CR>x"
+ call assert_equal('e y', getline('.'))
+ " f
+ exe 'normal /[\U1234abcd\u1234\uabcd]' .. "\<CR>x"
+ call assert_equal('f z', getline('.'))
+ " g
+ exe 'normal /\%d21879b' .. "\<CR>x"
+ call assert_equal('g abb', getline('.'))
+
+ " j Test backwards search from a multi-byte char
+ exe "normal /x\<CR>x?.\<CR>x"
+ call assert_equal('j 012❤', getline('.'))
+ " k
+ let @w=':%s#comb[i]nations#œ̄ṣ́m̥̄ᾱ̆́#g'
+ @w
+ call assert_equal('k œ̄ṣ́m̥̄ᾱ̆́', getline(18))
+
+ close!
+endfunc
+
+func Test_regexp_multibyte_magic()
+ set regexpengine=1
+ call Run_regexp_multibyte_magic()
+ set regexpengine=2
+ call Run_regexp_multibyte_magic()
+ set regexpengine&
+endfunc
+
+" Test for 7.3.192
+" command ":s/ \?/ /g" splits multi-byte characters into bytes
+func Test_split_multibyte_to_bytes()
+ new
+ call setline(1, 'l äö üᾱ̆́')
+ s/ \?/ /g
+ call assert_equal(' l ä ö ü ᾱ̆́', getline(1))
+ close!
+endfunc
+
+" Test for matchstr() with multibyte characters
+func Test_matchstr_multibyte()
+ new
+ call assert_equal('ב', matchstr("אבגד", ".", 0, 2))
+ call assert_equal('בג', matchstr("אבגד", "..", 0, 2))
+ call assert_equal('א', matchstr("אבגד", ".", 0, 0))
+ call assert_equal('ג', matchstr("אבגד", ".", 4, -1))
+ close!
+endfunc
+
+" Test for 7.4.636
+" A search with end offset gets stuck at end of file.
+func Test_search_with_end_offset()
+ new
+ call setline(1, ['', 'dog(a', 'cat('])
+ exe "normal /(/e+" .. "\<CR>"
+ normal "ayn
+ call assert_equal("a\ncat(", @a)
+ close!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_registers.vim b/src/nvim/testdir/test_registers.vim
index d20f8d1eef..19a7c6c9d0 100644
--- a/src/nvim/testdir/test_registers.vim
+++ b/src/nvim/testdir/test_registers.vim
@@ -167,6 +167,75 @@ func Test_set_register()
enew!
endfunc
+func Test_v_register()
+ enew
+ call setline(1, 'nothing')
+
+ func s:Put()
+ let s:register = v:register
+ exec 'normal! "' .. v:register .. 'P'
+ endfunc
+ nnoremap <buffer> <plug>(test) :<c-u>call s:Put()<cr>
+ nmap <buffer> S <plug>(test)
+
+ let @z = "testz\n"
+ let @" = "test@\n"
+
+ let s:register = ''
+ call feedkeys('"_ddS', 'mx')
+ call assert_equal('test@', getline('.')) " fails before 8.2.0929
+ call assert_equal('"', s:register) " fails before 8.2.0929
+
+ let s:register = ''
+ call feedkeys('"zS', 'mx')
+ call assert_equal('z', s:register)
+
+ let s:register = ''
+ call feedkeys('"zSS', 'mx')
+ call assert_equal('"', s:register)
+
+ let s:register = ''
+ call feedkeys('"_S', 'mx')
+ call assert_equal('_', s:register)
+
+ let s:register = ''
+ normal "_ddS
+ call assert_equal('"', s:register) " fails before 8.2.0929
+ call assert_equal('test@', getline('.')) " fails before 8.2.0929
+
+ let s:register = ''
+ execute 'normal "z:call' "s:Put()\n"
+ call assert_equal('z', s:register)
+ call assert_equal('testz', getline('.'))
+
+ " Test operator and omap
+ let @b = 'testb'
+ func s:OpFunc(...)
+ let s:register2 = v:register
+ endfunc
+ set opfunc=s:OpFunc
+
+ normal "bg@l
+ normal S
+ call assert_equal('"', s:register) " fails before 8.2.0929
+ call assert_equal('b', s:register2)
+
+ func s:Motion()
+ let s:register1 = v:register
+ normal! l
+ endfunc
+ onoremap <buffer> Q :<c-u>call s:Motion()<cr>
+
+ normal "bg@Q
+ normal S
+ call assert_equal('"', s:register)
+ call assert_equal('b', s:register1)
+ call assert_equal('"', s:register2)
+
+ set opfunc&
+ bwipe!
+endfunc
+
func Test_ve_blockpaste()
new
set ve=all
diff --git a/src/nvim/testdir/test_restricted.vim b/src/nvim/testdir/test_restricted.vim
deleted file mode 100644
index a29f7c33d3..0000000000
--- a/src/nvim/testdir/test_restricted.vim
+++ /dev/null
@@ -1,103 +0,0 @@
-" Test for "rvim" or "vim -Z"
-
-source shared.vim
-
-"if has('win32') && has('gui')
-" " Win32 GUI shows a dialog instead of displaying the error in the last line.
-" finish
-"endif
-
-func Test_restricted()
- call Run_restricted_test('!ls', 'E145:')
-endfunc
-
-func Run_restricted_test(ex_cmd, error)
- let cmd = GetVimCommand('Xrestricted')
- if cmd == ''
- return
- endif
-
- " Use a VimEnter autocommand to avoid that the error message is displayed in
- " a dialog with an OK button.
- call writefile([
- \ "func Init()",
- \ " silent! " . a:ex_cmd,
- \ " call writefile([v:errmsg], 'Xrestrout')",
- \ " qa!",
- \ "endfunc",
- \ "au VimEnter * call Init()",
- \ ], 'Xrestricted')
- call system(cmd . ' -Z')
- call assert_match(a:error, join(readfile('Xrestrout')))
-
- call delete('Xrestricted')
- call delete('Xrestrout')
-endfunc
-
-func Test_restricted_lua()
- if !has('lua')
- throw 'Skipped: Lua is not supported'
- endif
- call Run_restricted_test('lua print("Hello, Vim!")', 'E981:')
- call Run_restricted_test('luado return "hello"', 'E981:')
- call Run_restricted_test('luafile somefile', 'E981:')
- call Run_restricted_test('call luaeval("expression")', 'E145:')
-endfunc
-
-func Test_restricted_mzscheme()
- if !has('mzscheme')
- throw 'Skipped: MzScheme is not supported'
- endif
- call Run_restricted_test('mzscheme statement', 'E981:')
- call Run_restricted_test('mzfile somefile', 'E981:')
- call Run_restricted_test('call mzeval("expression")', 'E145:')
-endfunc
-
-func Test_restricted_perl()
- if !has('perl')
- throw 'Skipped: Perl is not supported'
- endif
- " TODO: how to make Safe mode fail?
- " call Run_restricted_test('perl system("ls")', 'E981:')
- " call Run_restricted_test('perldo system("hello")', 'E981:')
- " call Run_restricted_test('perlfile somefile', 'E981:')
- " call Run_restricted_test('call perleval("system(\"ls\")")', 'E145:')
-endfunc
-
-func Test_restricted_python()
- if !has('python')
- throw 'Skipped: Python is not supported'
- endif
- call Run_restricted_test('python print "hello"', 'E981:')
- call Run_restricted_test('pydo return "hello"', 'E981:')
- call Run_restricted_test('pyfile somefile', 'E981:')
- call Run_restricted_test('call pyeval("expression")', 'E145:')
-endfunc
-
-func Test_restricted_python3()
- if !has('python3')
- throw 'Skipped: Python3 is not supported'
- endif
- call Run_restricted_test('py3 print "hello"', 'E981:')
- call Run_restricted_test('py3do return "hello"', 'E981:')
- call Run_restricted_test('py3file somefile', 'E981:')
- call Run_restricted_test('call py3eval("expression")', 'E145:')
-endfunc
-
-func Test_restricted_ruby()
- if !has('ruby')
- throw 'Skipped: Ruby is not supported'
- endif
- call Run_restricted_test('ruby print "Hello"', 'E981:')
- call Run_restricted_test('rubydo print "Hello"', 'E981:')
- call Run_restricted_test('rubyfile somefile', 'E981:')
-endfunc
-
-func Test_restricted_tcl()
- if !has('tcl')
- throw 'Skipped: Tcl is not supported'
- endif
- call Run_restricted_test('tcl puts "Hello"', 'E981:')
- call Run_restricted_test('tcldo puts "Hello"', 'E981:')
- call Run_restricted_test('tclfile somefile', 'E981:')
-endfunc
diff --git a/src/nvim/testdir/test_ruby.vim b/src/nvim/testdir/test_ruby.vim
index 64199570a9..9c74c35049 100644
--- a/src/nvim/testdir/test_ruby.vim
+++ b/src/nvim/testdir/test_ruby.vim
@@ -1,8 +1,7 @@
" Tests for ruby interface
-if !has('ruby')
- finish
-end
+source check.vim
+CheckFeature ruby
func Test_ruby_change_buffer()
call setline(line('$'), ['1 line 1'])
@@ -11,37 +10,6 @@ func Test_ruby_change_buffer()
call assert_equal('1 changed line 1', getline('$'))
endfunc
-func Test_ruby_evaluate_list()
- throw 'skipped: TODO: '
- call setline(line('$'), ['2 line 2'])
- ruby Vim.command("normal /^2\n")
- let l = ["abc", "def"]
- ruby << EOF
- curline = $curbuf.line_number
- l = Vim.evaluate("l");
- $curbuf.append(curline, l.join("\n"))
-EOF
- normal j
- .rubydo $_ = $_.gsub(/\n/, '/')
- call assert_equal('abc/def', getline('$'))
-endfunc
-
-func Test_ruby_evaluate_dict()
- let d = {'a': 'foo', 'b': 123}
- redir => l:out
- ruby d = Vim.evaluate("d"); print d
- redir END
- call assert_equal(['{"a"=>"foo", "b"=>123}'], split(l:out, "\n"))
-endfunc
-
-func Test_ruby_evaluate_special_var()
- let l = [v:true, v:false, v:null]
- redir => l:out
- ruby d = Vim.evaluate("l"); print d
- redir END
- call assert_equal(['[true, false, nil]'], split(l:out, "\n"))
-endfunc
-
func Test_rubydo()
throw 'skipped: TODO: '
" Check deleting lines does not trigger ml_get error.
@@ -56,8 +24,7 @@ func Test_rubydo()
call setline(1, ['one', 'two', 'three'])
rubydo Vim.command("new")
call assert_equal(wincount + 1, winnr('$'))
- bwipe!
- bwipe!
+ %bwipe!
endfunc
func Test_rubyfile()
@@ -68,15 +35,359 @@ func Test_rubyfile()
call delete(tempfile)
endfunc
-func Test_set_cursor()
+func Test_ruby_set_cursor()
" Check that setting the cursor position works.
new
call setline(1, ['first line', 'second line'])
normal gg
rubydo $curwin.cursor = [1, 5]
call assert_equal([1, 6], [line('.'), col('.')])
+ call assert_equal([1, 5], rubyeval('$curwin.cursor'))
" Check that movement after setting cursor position keeps current column.
normal j
call assert_equal([2, 6], [line('.'), col('.')])
+ call assert_equal([2, 5], rubyeval('$curwin.cursor'))
+
+ " call assert_fails('ruby $curwin.cursor = [1]',
+ " \ 'ArgumentError: array length must be 2')
+ bwipe!
+endfunc
+
+" Test buffer.count and buffer.length (number of lines in buffer)
+func Test_ruby_buffer_count()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ call assert_equal(3, rubyeval('$curbuf.count'))
+ call assert_equal(3, rubyeval('$curbuf.length'))
+ bwipe!
+endfunc
+
+" Test buffer.name (buffer name)
+func Test_ruby_buffer_name()
+ new Xfoo
+ call assert_equal(expand('%:p'), rubyeval('$curbuf.name'))
+ bwipe
+ call assert_equal('', rubyeval('$curbuf.name'))
+endfunc
+
+" Test buffer.number (number of the buffer).
+func Test_ruby_buffer_number()
+ new
+ call assert_equal(bufnr('%'), rubyeval('$curbuf.number'))
+ new
+ call assert_equal(bufnr('%'), rubyeval('$curbuf.number'))
+
+ %bwipe
+endfunc
+
+" Test buffer.delete({n}) (delete line {n})
+func Test_ruby_buffer_delete()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ ruby $curbuf.delete(2)
+ call assert_equal(['one', 'three'], getline(1, '$'))
+
+ " call assert_fails('ruby $curbuf.delete(0)', 'IndexError: line number 0 out of range')
+ " call assert_fails('ruby $curbuf.delete(3)', 'IndexError: line number 3 out of range')
+ call assert_fails('ruby $curbuf.delete(3)', 'RuntimeError: Index out of bounds')
+
+ bwipe!
+endfunc
+
+" Test buffer.append({str}, str) (append line {str} after line {n})
+func Test_ruby_buffer_append()
+ new
+ ruby $curbuf.append(0, 'one')
+ ruby $curbuf.append(1, 'three')
+ ruby $curbuf.append(1, 'two')
+ ruby $curbuf.append(4, 'four')
+
+ call assert_equal(['one', 'two', 'three', '', 'four'], getline(1, '$'))
+
+ " call assert_fails('ruby $curbuf.append(-1, "x")',
+ " \ 'IndexError: line number -1 out of range')
+ call assert_fails('ruby $curbuf.append(-1, "x")',
+ \ 'ArgumentError: Index out of bounds')
+ call assert_fails('ruby $curbuf.append(6, "x")',
+ \ 'RuntimeError: Index out of bounds')
+
+ bwipe!
+endfunc
+
+" Test buffer.line (get or set the current line)
+func Test_ruby_buffer_line()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ 2
+ call assert_equal('two', rubyeval('$curbuf.line'))
+
+ ruby $curbuf.line = 'TWO'
+ call assert_equal(['one', 'TWO', 'three'], getline(1, '$'))
+
+ bwipe!
+endfunc
+
+" Test buffer.line_number (get current line number)
+func Test_ruby_buffer_line_number()
+ new
+ call setline(1, ['one', 'two', 'three'])
+ 2
+ call assert_equal(2, rubyeval('$curbuf.line_number'))
+
+ bwipe!
+endfunc
+
+func Test_ruby_buffer_get()
+ new
+ call setline(1, ['one', 'two'])
+ call assert_equal('one', rubyeval('$curbuf[1]'))
+ call assert_equal('two', rubyeval('$curbuf[2]'))
+
+ " call assert_fails('ruby $curbuf[0]',
+ " \ 'IndexError: line number 0 out of range')
+ call assert_fails('ruby $curbuf[3]',
+ \ 'RuntimeError: Index out of bounds')
+
+ bwipe!
+endfunc
+
+func Test_ruby_buffer_set()
+ new
+ call setline(1, ['one', 'two'])
+ ruby $curbuf[2] = 'TWO'
+ ruby $curbuf[1] = 'ONE'
+
+ " call assert_fails('ruby $curbuf[0] = "ZERO"',
+ " \ 'IndexError: line number 0 out of range')
+ " call assert_fails('ruby $curbuf[3] = "THREE"',
+ " \ 'IndexError: line number 3 out of range')
+ call assert_fails('ruby $curbuf[3] = "THREE"',
+ \ 'RuntimeError: Index out of bounds')
+ bwipe!
+endfunc
+
+" Test window.width (get or set window height).
+func Test_ruby_window_height()
+ new
+
+ " Test setting window height
+ ruby $curwin.height = 2
+ call assert_equal(2, winheight(0))
+
+ " Test getting window height
+ call assert_equal(2, rubyeval('$curwin.height'))
+
+ bwipe
+endfunc
+
+" Test window.width (get or set window width).
+func Test_ruby_window_width()
+ vnew
+
+ " Test setting window width
+ ruby $curwin.width = 2
+ call assert_equal(2, winwidth(0))
+
+ " Test getting window width
+ call assert_equal(2, rubyeval('$curwin.width'))
+
+ bwipe
+endfunc
+
+" Test window.buffer (get buffer object of a window object).
+func Test_ruby_window_buffer()
+ new Xfoo1
+ new Xfoo2
+ ruby $b2 = $curwin.buffer
+ ruby $w2 = $curwin
+ wincmd j
+ ruby $b1 = $curwin.buffer
+ ruby $w1 = $curwin
+
+ " call assert_equal(rubyeval('$b1'), rubyeval('$w1.buffer'))
+ " call assert_equal(rubyeval('$b2'), rubyeval('$w2.buffer'))
+ call assert_equal(bufnr('Xfoo1'), rubyeval('$w1.buffer.number'))
+ call assert_equal(bufnr('Xfoo2'), rubyeval('$w2.buffer.number'))
+
+ ruby $b1, $w1, $b2, $w2 = nil
+ %bwipe
+endfunc
+
+" Test Vim::Window.current (get current window object)
+func Test_ruby_Vim_window_current()
+ let cw = rubyeval('$curwin.to_s')
+ " call assert_equal(cw, rubyeval('Vim::Window.current'))
+ call assert_match('^#<Neovim::Window:0x\x\+>$', cw)
+endfunc
+
+" Test Vim::Window.count (number of windows)
+func Test_ruby_Vim_window_count()
+ new Xfoo1
+ new Xfoo2
+ split
+ call assert_equal(4, rubyeval('Vim::Window.count'))
+ %bwipe
+ call assert_equal(1, rubyeval('Vim::Window.count'))
+endfunc
+
+" Test Vim::Window[n] (get window object of window n)
+func Test_ruby_Vim_window_get()
+ new Xfoo1
+ new Xfoo2
+ call assert_match('Xfoo2$', rubyeval('Vim::Window[0].buffer.name'))
+ wincmd j
+ call assert_match('Xfoo1$', rubyeval('Vim::Window[1].buffer.name'))
+ wincmd j
+ call assert_equal('', rubyeval('Vim::Window[2].buffer.name'))
+ %bwipe
+endfunc
+
+" Test Vim::Buffer.current (return the buffer object of current buffer)
+func Test_ruby_Vim_buffer_current()
+ let cb = rubyeval('$curbuf.to_s')
+ " call assert_equal(cb, rubyeval('Vim::Buffer.current'))
+ call assert_match('^#<Neovim::Buffer:0x\x\+>$', cb)
+endfunc
+
+" Test Vim::Buffer:.count (return the number of buffers)
+func Test_ruby_Vim_buffer_count()
+ new Xfoo1
+ new Xfoo2
+ call assert_equal(3, rubyeval('Vim::Buffer.count'))
+ %bwipe
+ call assert_equal(1, rubyeval('Vim::Buffer.count'))
+endfunc
+
+" Test Vim::buffer[n] (return the buffer object of buffer number n)
+func Test_ruby_Vim_buffer_get()
+ new Xfoo1
+ new Xfoo2
+
+ " Index of Vim::Buffer[n] goes from 0 to the number of buffers.
+ call assert_equal('', rubyeval('Vim::Buffer[0].name'))
+ call assert_match('Xfoo1$', rubyeval('Vim::Buffer[1].name'))
+ call assert_match('Xfoo2$', rubyeval('Vim::Buffer[2].name'))
+ call assert_fails('ruby print Vim::Buffer[3].name',
+ \ "NoMethodError: undefined method `name' for nil:NilClass")
+ %bwipe
+endfunc
+
+" Test Vim::command({cmd}) (execute a Ex command))
+" Test Vim::command({cmd})
+func Test_ruby_Vim_command()
+ new
+ call setline(1, ['one', 'two', 'three', 'four'])
+ ruby Vim::command('2,3d')
+ call assert_equal(['one', 'four'], getline(1, '$'))
+ bwipe!
+endfunc
+
+" Test Vim::set_option (set a vim option)
+func Test_ruby_Vim_set_option()
+ call assert_equal(0, &number)
+ ruby Vim::set_option('number')
+ call assert_equal(1, &number)
+ ruby Vim::set_option('nonumber')
+ call assert_equal(0, &number)
+endfunc
+
+func Test_ruby_Vim_evaluate()
+ call assert_equal(123, rubyeval('Vim::evaluate("123")'))
+ " Vim::evaluate("123").class gives Integer or Fixnum depending
+ " on versions of Ruby.
+ call assert_match('^Integer\|Fixnum$', rubyeval('Vim::evaluate("123").class'))
+
+ if has('float')
+ call assert_equal(1.23, rubyeval('Vim::evaluate("1.23")'))
+ call assert_equal('Float', rubyeval('Vim::evaluate("1.23").class'))
+ endif
+
+ call assert_equal('foo', rubyeval('Vim::evaluate("\"foo\"")'))
+ call assert_equal('String', rubyeval('Vim::evaluate("\"foo\"").class'))
+
+ call assert_equal([1, 2], rubyeval('Vim::evaluate("[1, 2]")'))
+ call assert_equal('Array', rubyeval('Vim::evaluate("[1, 2]").class'))
+
+ call assert_equal({'1': 2}, rubyeval('Vim::evaluate("{1:2}")'))
+ call assert_equal('Hash', rubyeval('Vim::evaluate("{1:2}").class'))
+
+ call assert_equal(v:null, rubyeval('Vim::evaluate("v:null")'))
+ call assert_equal('NilClass', rubyeval('Vim::evaluate("v:null").class'))
+
+ " call assert_equal(v:null, rubyeval('Vim::evaluate("v:none")'))
+ " call assert_equal('NilClass', rubyeval('Vim::evaluate("v:none").class'))
+
+ call assert_equal(v:true, rubyeval('Vim::evaluate("v:true")'))
+ call assert_equal('TrueClass', rubyeval('Vim::evaluate("v:true").class'))
+ call assert_equal(v:false, rubyeval('Vim::evaluate("v:false")'))
+ call assert_equal('FalseClass',rubyeval('Vim::evaluate("v:false").class'))
+endfunc
+
+func Test_ruby_Vim_evaluate_list()
+ call setline(line('$'), ['2 line 2'])
+ ruby Vim.command("normal /^2\n")
+ let l = ["abc", "def"]
+ ruby << EOF
+ curline = $curbuf.line_number
+ l = Vim.evaluate("l");
+ $curbuf.append(curline, l.join("|"))
+EOF
+ normal j
+ .rubydo $_ = $_.gsub(/\|/, '/')
+ call assert_equal('abc/def', getline('$'))
+endfunc
+
+func Test_ruby_Vim_evaluate_dict()
+ let d = {'a': 'foo', 'b': 123}
+ redir => l:out
+ ruby d = Vim.evaluate("d"); print d
+ redir END
+ call assert_equal(['{"a"=>"foo", "b"=>123}'], split(l:out, "\n"))
+endfunc
+
+" Test Vim::message({msg}) (display message {msg})
+func Test_ruby_Vim_message()
+ throw 'skipped: TODO: '
+ ruby Vim::message('A message')
+ let messages = split(execute('message'), "\n")
+ call assert_equal('A message', messages[-1])
+endfunc
+
+func Test_ruby_print()
+ func RubyPrint(expr)
+ return trim(execute('ruby print ' . a:expr))
+ endfunc
+
+ call assert_equal('123', RubyPrint('123'))
+ call assert_equal('1.23', RubyPrint('1.23'))
+ call assert_equal('Hello World!', RubyPrint('"Hello World!"'))
+ call assert_equal('[1, 2]', RubyPrint('[1, 2]'))
+ call assert_equal('{"k1"=>"v1", "k2"=>"v2"}', RubyPrint('({"k1" => "v1", "k2" => "v2"})'))
+ call assert_equal('true', RubyPrint('true'))
+ call assert_equal('false', RubyPrint('false'))
+ call assert_equal('', RubyPrint('nil'))
+ call assert_match('Vim', RubyPrint('Vim'))
+ call assert_match('Module', RubyPrint('Vim.class'))
+
+ delfunc RubyPrint
+endfunc
+
+func Test_ruby_p()
+ ruby p 'Just a test'
+ let messages = GetMessages()
+ call assert_equal('"Just a test"', messages[-1])
+
+ " Check return values of p method
+
+ call assert_equal(123, rubyeval('p(123)'))
+ call assert_equal([1, 2, 3], rubyeval('p(1, 2, 3)'))
+
+ " Avoid the "message maintainer" line.
+ let $LANG = ''
+ messages clear
+ call assert_equal(v:true, rubyeval('p() == nil'))
+
+ let messages = GetMessages()
+ call assert_equal(0, len(messages))
endfunc
diff --git a/src/nvim/testdir/test_search.vim b/src/nvim/testdir/test_search.vim
index 8036dea29f..5db23c22a8 100644
--- a/src/nvim/testdir/test_search.vim
+++ b/src/nvim/testdir/test_search.vim
@@ -244,6 +244,10 @@ func Test_search_cmdline2()
" go to previous match (on line 2)
call feedkeys("/the\<C-G>\<C-G>\<C-G>\<C-T>\<C-T>\<C-T>\<cr>", 'tx')
call assert_equal(' 2 these', getline('.'))
+ 1
+ " go to previous match (on line 2)
+ call feedkeys("/the\<C-G>\<C-R>\<C-W>\<cr>", 'tx')
+ call assert_equal('theother', @/)
" Test 2: keep the view,
" after deleting a character from the search cmd
@@ -255,7 +259,7 @@ func Test_search_cmdline2()
call assert_equal({'lnum': 10, 'leftcol': 0, 'col': 4, 'topfill': 0, 'topline': 6, 'coladd': 0, 'skipcol': 0, 'curswant': 4}, winsaveview())
" remove all history entries
- for i in range(10)
+ for i in range(11)
call histdel('/')
endfor
@@ -346,25 +350,104 @@ func Test_searchc()
bw!
endfunc
-func Test_search_cmdline3()
+func Cmdline3_prep()
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
+endfunc
+
+func Incsearch_cleanup()
+ throw 'skipped: Nvim does not support test_override()'
+ set noincsearch
+ call test_override("char_avail", 0)
+ bw!
+endfunc
+
+func Test_search_cmdline3()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ call Cmdline3_prep()
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!
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_search_cmdline3s()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ call Cmdline3_prep()
+ 1
+ call feedkeys(":%s/the\<c-l>/xxx\<cr>", 'tx')
+ call assert_equal(' 2 xxxe', getline('.'))
+ undo
+ call feedkeys(":%subs/the\<c-l>/xxx\<cr>", 'tx')
+ call assert_equal(' 2 xxxe', getline('.'))
+ undo
+ call feedkeys(":%substitute/the\<c-l>/xxx\<cr>", 'tx')
+ call assert_equal(' 2 xxxe', getline('.'))
+ undo
+ call feedkeys(":%smagic/the.e/xxx\<cr>", 'tx')
+ call assert_equal(' 2 xxx', getline('.'))
+ undo
+ call assert_fails(":%snomagic/the.e/xxx\<cr>", 'E486')
+ "
+ call feedkeys(":%snomagic/the\\.e/xxx\<cr>", 'tx')
+ call assert_equal(' 2 xxx', getline('.'))
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_search_cmdline3g()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ call Cmdline3_prep()
+ 1
+ call feedkeys(":g/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline(2))
+ undo
+ call feedkeys(":global/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(' 3 the theother', getline(2))
+ undo
+ call feedkeys(":g!/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(1, line('$'))
+ call assert_equal(' 2 the~e', getline(1))
+ undo
+ call feedkeys(":global!/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(1, line('$'))
+ call assert_equal(' 2 the~e', getline(1))
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_search_cmdline3v()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ call Cmdline3_prep()
+ 1
+ call feedkeys(":v/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(1, line('$'))
+ call assert_equal(' 2 the~e', getline(1))
+ undo
+ call feedkeys(":vglobal/the\<c-l>/d\<cr>", 'tx')
+ call assert_equal(1, line('$'))
+ call assert_equal(' 2 the~e', getline(1))
+
+ call Incsearch_cleanup()
endfunc
func Test_search_cmdline4()
@@ -410,19 +493,58 @@ func Test_search_cmdline5()
" 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'])
+ 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('.'))
+ call assert_equal(' 1 the first', getline('.'))
" clean up
set noincsearch
bw!
endfunc
+func Test_search_cmdline7()
+ throw 'skipped: Nvim does not support test_override()'
+ " Test that an pressing <c-g> in an empty command line
+ " does not move the cursor
+ if !exists('+incsearch')
+ return
+ endif
+ " need to disable char_avail,
+ " so that expansion of commandline works
+ call test_override("char_avail", 1)
+ new
+ let @/ = 'b'
+ call setline(1, [' bbvimb', ''])
+ set incsearch
+ " first match
+ norm! gg0
+ " moves to next match of previous search pattern, just like /<cr>
+ call feedkeys("/\<c-g>\<cr>", 'tx')
+ call assert_equal([0,1,2,0], getpos('.'))
+ " moves to next match of previous search pattern, just like /<cr>
+ call feedkeys("/\<cr>", 'tx')
+ call assert_equal([0,1,3,0], getpos('.'))
+ " moves to next match of previous search pattern, just like /<cr>
+ call feedkeys("/\<c-t>\<cr>", 'tx')
+ call assert_equal([0,1,7,0], getpos('.'))
+
+ " using an offset uses the last search pattern
+ call cursor(1, 1)
+ call setline(1, ['1 bbvimb', ' 2 bbvimb'])
+ let @/ = 'b'
+ call feedkeys("//e\<c-g>\<cr>", 'tx')
+ call assert_equal('1 bbvimb', getline('.'))
+ call assert_equal(4, col('.'))
+
+ set noincsearch
+ call test_override("char_avail", 0)
+ bw!
+endfunc
+
" Tests for regexp with various magic settings
func Test_search_regexp()
enew!
@@ -504,6 +626,7 @@ func Test_incsearch_substitute_dump()
\ 'for n in range(1, 10)',
\ ' call setline(n, "foo " . n)',
\ 'endfor',
+ \ 'call setline(11, "bar 11")',
\ '3',
\ ], 'Xis_subst_script')
let buf = RunVimInTerminal('-S Xis_subst_script', {'rows': 9, 'cols': 70})
@@ -512,6 +635,7 @@ func Test_incsearch_substitute_dump()
sleep 100m
" Need to send one key at a time to force a redraw.
+ " Select three lines at the cursor with typed pattern.
call term_sendkeys(buf, ':.,.+2s/')
sleep 100m
call term_sendkeys(buf, 'f')
@@ -520,12 +644,219 @@ func Test_incsearch_substitute_dump()
sleep 100m
call term_sendkeys(buf, 'o')
call VerifyScreenDump(buf, 'Test_incsearch_substitute_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Select three lines at the cursor using previous pattern.
+ call term_sendkeys(buf, "/foo\<CR>")
+ sleep 100m
+ call term_sendkeys(buf, ':.,.+2s//')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_02', {})
+
+ " Deleting last slash should remove the match.
+ call term_sendkeys(buf, "\<BS>")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_03', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Reverse range is accepted
+ call term_sendkeys(buf, ':5,2s/foo')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_04', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " White space after the command is skipped
+ call term_sendkeys(buf, ':2,3sub /fo')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_05', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Command modifiers are skipped
+ call term_sendkeys(buf, ':above below browse botr confirm keepmar keepalt keeppat keepjum filter xxx hide lockm leftabove noau noswap rightbel sandbox silent silent! $tab top unsil vert verbose 4,5s/fo.')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_06', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ " Cursorline highlighting at match
+ call term_sendkeys(buf, ":set cursorline\<CR>")
+ call term_sendkeys(buf, 'G9G')
+ call term_sendkeys(buf, ':9,11s/bar')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_07', {})
+ call term_sendkeys(buf, "\<Esc>")
+ " Cursorline highlighting at cursor when no match
+ call term_sendkeys(buf, ':9,10s/bar')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_08', {})
call term_sendkeys(buf, "\<Esc>")
+
+ " Only \v handled as empty pattern, does not move cursor
+ call term_sendkeys(buf, '3G4G')
+ call term_sendkeys(buf, ":nohlsearch\<CR>")
+ call term_sendkeys(buf, ':6,7s/\v')
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_09', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ":set nocursorline\<CR>")
+
+ " All matches are highlighted for 'hlsearch' after the incsearch canceled
+ call term_sendkeys(buf, "1G*")
+ call term_sendkeys(buf, ":2,5s/foo")
+ sleep 100m
+ call term_sendkeys(buf, "\<Esc>")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_10', {})
+
+ call term_sendkeys(buf, ":split\<CR>")
+ call term_sendkeys(buf, ":let @/ = 'xyz'\<CR>")
+ call term_sendkeys(buf, ":%s/.")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_11', {})
+ call term_sendkeys(buf, "\<BS>")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_12', {})
+ call term_sendkeys(buf, "\<Esc>")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_13', {})
+ call term_sendkeys(buf, ":%bwipe!\<CR>")
+ call term_sendkeys(buf, ":only!\<CR>")
+
+ " get :'<,'>s command in history
+ call term_sendkeys(buf, ":set cmdheight=2\<CR>")
+ call term_sendkeys(buf, "aasdfasdf\<Esc>")
+ call term_sendkeys(buf, "V:s/a/b/g\<CR>")
+ " Using '<,'> does not give E20
+ call term_sendkeys(buf, ":new\<CR>")
+ call term_sendkeys(buf, "aasdfasdf\<Esc>")
+ call term_sendkeys(buf, ":\<Up>\<Up>")
+ call VerifyScreenDump(buf, 'Test_incsearch_substitute_14', {})
+ call term_sendkeys(buf, "<Esc>")
+
call StopVimInTerminal(buf)
call delete('Xis_subst_script')
endfunc
+" Similar to Test_incsearch_substitute_dump() for :sort
+func Test_incsearch_sort_dump()
+ if !exists('+incsearch')
+ return
+ endif
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+ endif
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
+ \ ], 'Xis_sort_script')
+ let buf = RunVimInTerminal('-S Xis_sort_script', {'rows': 9, 'cols': 70})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 100m
+
+ call term_sendkeys(buf, ':sort ni u /on')
+ call VerifyScreenDump(buf, 'Test_incsearch_sort_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ':sort! /on')
+ call VerifyScreenDump(buf, 'Test_incsearch_sort_02', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+ call delete('Xis_sort_script')
+endfunc
+
+" Similar to Test_incsearch_substitute_dump() for :vimgrep famiry
+func Test_incsearch_vimgrep_dump()
+ if !exists('+incsearch')
+ return
+ endif
+ if !CanRunVimInTerminal()
+ throw 'Skipped: cannot make screendumps'
+ endif
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'call setline(1, ["another one 2", "that one 3", "the one 1"])',
+ \ ], 'Xis_vimgrep_script')
+ let buf = RunVimInTerminal('-S Xis_vimgrep_script', {'rows': 9, 'cols': 70})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 100m
+
+ " Need to send one key at a time to force a redraw.
+ call term_sendkeys(buf, ':vimgrep on')
+ call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ':vimg /on/ *.txt')
+ call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_02', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ':vimgrepadd "\<on')
+ call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_03', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ':lv "tha')
+ call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_04', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call term_sendkeys(buf, ':lvimgrepa "the" **/*.txt')
+ call VerifyScreenDump(buf, 'Test_incsearch_vimgrep_05', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+ call delete('Xis_vimgrep_script')
+endfunc
+
+func Test_keep_last_search_pattern()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ new
+ call setline(1, ['foo', 'foo', 'foo'])
+ set incsearch
+ call test_override("char_avail", 1)
+ let @/ = 'bar'
+ call feedkeys(":/foo/s//\<Esc>", 'ntx')
+ call assert_equal('bar', @/)
+
+ " no error message if pattern not found
+ call feedkeys(":/xyz/s//\<Esc>", 'ntx')
+ call assert_equal('bar', @/)
+
+ bwipe!
+ call test_override("ALL", 0)
+ set noincsearch
+endfunc
+
+func Test_word_under_cursor_after_match()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ new
+ call setline(1, 'foo bar')
+ set incsearch
+ call test_override("char_avail", 1)
+ try
+ call feedkeys("/foo\<C-R>\<C-W>\<CR>", 'ntx')
+ catch /E486:/
+ endtry
+ call assert_equal('foobar', @/)
+
+ bwipe!
+ call test_override("ALL", 0)
+ set noincsearch
+endfunc
+
+func Test_subst_word_under_cursor()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ new
+ call setline(1, ['int SomeLongName;', 'for (xxx = 1; xxx < len; ++xxx)'])
+ set incsearch
+ call test_override("char_avail", 1)
+ call feedkeys("/LongName\<CR>", 'ntx')
+ call feedkeys(":%s/xxx/\<C-R>\<C-W>/g\<CR>", 'ntx')
+ call assert_equal('for (SomeLongName = 1; SomeLongName < len; ++SomeLongName)', getline(2))
+
+ bwipe!
+ call test_override("ALL", 0)
+ set noincsearch
+endfunc
+
func Test_incsearch_with_change()
if !has('timers') || !exists('+incsearch') || !CanRunVimInTerminal()
throw 'Skipped: cannot make screendumps and/or timers feature and/or incsearch option missing'
@@ -550,6 +881,21 @@ func Test_incsearch_with_change()
call delete('Xis_change_script')
endfunc
+func Test_incsearch_cmdline_modifier()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ call test_override("char_avail", 1)
+ new
+ call setline(1, ['foo'])
+ set incsearch
+ " Test that error E14 does not occur in parsing command modifier.
+ call feedkeys("V:tab", 'tx')
+
+ call Incsearch_cleanup()
+endfunc
+
func Test_incsearch_scrolling()
if !CanRunVimInTerminal()
return
@@ -580,6 +926,76 @@ func Test_incsearch_scrolling()
call delete('Xscript')
endfunc
+func Test_incsearch_search_dump()
+ if !exists('+incsearch')
+ return
+ endif
+ if !CanRunVimInTerminal()
+ return
+ endif
+ call writefile([
+ \ 'set incsearch hlsearch scrolloff=0',
+ \ 'for n in range(1, 8)',
+ \ ' call setline(n, "foo " . n)',
+ \ 'endfor',
+ \ '3',
+ \ ], 'Xis_search_script')
+ let buf = RunVimInTerminal('-S Xis_search_script', {'rows': 9, 'cols': 70})
+ " Give Vim a chance to redraw to get rid of the spaces in line 2 caused by
+ " the 'ambiwidth' check.
+ sleep 100m
+
+ " Need to send one key at a time to force a redraw.
+ call term_sendkeys(buf, '/fo')
+ call VerifyScreenDump(buf, 'Test_incsearch_search_01', {})
+ call term_sendkeys(buf, "\<Esc>")
+ sleep 100m
+
+ call term_sendkeys(buf, '/\v')
+ call VerifyScreenDump(buf, 'Test_incsearch_search_02', {})
+ call term_sendkeys(buf, "\<Esc>")
+
+ call StopVimInTerminal(buf)
+ call delete('Xis_search_script')
+endfunc
+
+func Test_incsearch_substitute()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ call test_override("char_avail", 1)
+ new
+ set incsearch
+ for n in range(1, 10)
+ call setline(n, 'foo ' . n)
+ endfor
+ 4
+ call feedkeys(":.,.+2s/foo\<BS>o\<BS>o/xxx\<cr>", 'tx')
+ call assert_equal('foo 3', getline(3))
+ call assert_equal('xxx 4', getline(4))
+ call assert_equal('xxx 5', getline(5))
+ call assert_equal('xxx 6', getline(6))
+ call assert_equal('foo 7', getline(7))
+
+ call Incsearch_cleanup()
+endfunc
+
+func Test_incsearch_substitute_long_line()
+ throw 'skipped: Nvim does not support test_override()'
+ new
+ call test_override("char_avail", 1)
+ set incsearch
+
+ call repeat('x', 100000)->setline(1)
+ call feedkeys(':s/\%c', 'xt')
+ redraw
+ call feedkeys("\<Esc>", 'xt')
+
+ call Incsearch_cleanup()
+ bwipe!
+endfunc
+
func Test_search_undefined_behaviour()
if !has("terminal")
return
@@ -616,6 +1032,40 @@ func Test_search_sentence()
/
endfunc
+" Test that there is no crash when there is a last search pattern but no last
+" substitute pattern.
+func Test_no_last_substitute_pat()
+ " Use viminfo to set the last search pattern to a string and make the last
+ " substitute pattern the most recent used and make it empty (NULL).
+ call writefile(['~MSle0/bar', '~MSle0~&'], 'Xviminfo')
+ rviminfo! Xviminfo
+ call assert_fails('normal n', 'E35:')
+
+ call delete('Xviminfo')
+endfunc
+
+func Test_search_Ctrl_L_combining()
+ " Make sure, that Ctrl-L works correctly with combining characters.
+ " It uses an artificial example of an 'a' with 4 combining chars:
+ " 'a' U+0061 Dec:97 LATIN SMALL LETTER A &#x61; /\%u61\Z "\u0061"
+ " ' ̀' U+0300 Dec:768 COMBINING GRAVE ACCENT &#x300; /\%u300\Z "\u0300"
+ " ' ́' U+0301 Dec:769 COMBINING ACUTE ACCENT &#x301; /\%u301\Z "\u0301"
+ " ' ̇' U+0307 Dec:775 COMBINING DOT ABOVE &#x307; /\%u307\Z "\u0307"
+ " ' ̣' U+0323 Dec:803 COMBINING DOT BELOW &#x323; /\%u323 "\u0323"
+ " Those should also appear on the commandline
+ if !has('multi_byte') || !exists('+incsearch')
+ return
+ endif
+ call Cmdline3_prep()
+ 1
+ let bufcontent = ['', 'Miạ̀́̇m']
+ call append('$', bufcontent)
+ call feedkeys("/Mi\<c-l>\<c-l>\<cr>", 'tx')
+ call assert_equal(5, line('.'))
+ call assert_equal(bufcontent[1], @/)
+ call Incsearch_cleanup()
+endfunc
+
func Test_large_hex_chars1()
" This used to cause a crash, the character becomes an NFA state.
try
@@ -653,6 +1103,24 @@ func Test_one_error_msg()
call assert_fails('call search(" \\((\\v[[=P=]]){185}+ ")', 'E871:')
endfunc
+func Test_incsearch_add_char_under_cursor()
+ throw 'skipped: Nvim does not support test_override()'
+ if !exists('+incsearch')
+ return
+ endif
+ set incsearch
+ new
+ call setline(1, ['find match', 'anything'])
+ 1
+ call test_override('char_avail', 1)
+ call feedkeys("fc/m\<C-L>\<C-L>\<C-L>\<C-L>\<C-L>\<CR>", 'tx')
+ call assert_equal('match', @/)
+ call test_override('char_avail', 0)
+
+ set incsearch&
+ bwipe!
+endfunc
+
" Test for the search() function with match at the cursor position
func Test_search_match_at_curpos()
new
diff --git a/src/nvim/testdir/test_search_stat.vim b/src/nvim/testdir/test_search_stat.vim
index cf36f3214a..11c6489ca2 100644
--- a/src/nvim/testdir/test_search_stat.vim
+++ b/src/nvim/testdir/test_search_stat.vim
@@ -1,11 +1,9 @@
" Tests for search_stats, when "S" is not in 'shortmess'
-"
-" This test is fragile, it might not work interactively, but it works when run
-" as test!
-source shared.vim
+source screendump.vim
+source check.vim
-func! Test_search_stat()
+func Test_search_stat()
new
set shortmess-=S
" Append 50 lines with text to search for, "foobar" appears 20 times
@@ -45,7 +43,7 @@ func! Test_search_stat()
call assert_match(pat .. stat, g:a)
call cursor(line('$'), 1)
let g:a = execute(':unsilent :norm! n')
- let stat = '\[1/>99\] W'
+ let stat = 'W \[1/>99\]'
call assert_match(pat .. stat, g:a)
" Many matches
@@ -55,7 +53,7 @@ func! Test_search_stat()
call assert_match(pat .. stat, g:a)
call cursor(1, 1)
let g:a = execute(':unsilent :norm! N')
- let stat = '\[>99/>99\] W'
+ let stat = 'W \[>99/>99\]'
call assert_match(pat .. stat, g:a)
" right-left
@@ -87,7 +85,7 @@ func! Test_search_stat()
call cursor('$',1)
let pat = 'raboof/\s\+'
let g:a = execute(':unsilent :norm! n')
- let stat = '\[20/1\]'
+ let stat = 'W \[20/1\]'
call assert_match(pat .. stat, g:a)
call assert_match('search hit BOTTOM, continuing at TOP', g:a)
set norl
@@ -98,10 +96,10 @@ func! Test_search_stat()
let @/ = 'foobar'
let pat = '?foobar\s\+'
let g:a = execute(':unsilent :norm! N')
- let stat = '\[20/20\]'
+ let stat = 'W \[20/20\]'
call assert_match(pat .. stat, g:a)
call assert_match('search hit TOP, continuing at BOTTOM', g:a)
- call assert_match('\[20/20\] W', Screenline(&lines))
+ call assert_match('W \[20/20\]', Screenline(&lines))
" normal, no match
call cursor(1,1)
@@ -160,7 +158,115 @@ func! Test_search_stat()
let stat = '\[1/2\]'
call assert_notmatch(pat .. stat, g:a)
- " close the window
+ " normal, n comes from a silent mapping
+ " First test a normal mapping, then a silent mapping
+ call cursor(1,1)
+ nnoremap n n
+ let @/ = 'find this'
+ let pat = '/find this\s\+'
+ let g:a = execute(':unsilent :norm n')
+ let g:b = split(g:a, "\n")[-1]
+ let stat = '\[1/2\]'
+ call assert_match(pat .. stat, g:b)
+ nnoremap <silent> n n
+ call cursor(1,1)
+ let g:a = execute(':unsilent :norm n')
+ let g:b = split(g:a, "\n")[-1]
+ let stat = '\[1/2\]'
+ call assert_notmatch(pat .. stat, g:b)
+ call assert_match(stat, g:b)
+ " Test that the message is not truncated
+ " it would insert '...' into the output.
+ call assert_match('^\s\+' .. stat, g:b)
+ unmap n
+
+ " Clean up
set shortmess+=S
+ " close the window
bwipe!
endfunc
+
+func Test_search_stat_foldopen()
+ CheckScreendump
+
+ let lines =<< trim END
+ set shortmess-=S
+ setl foldenable foldmethod=indent foldopen-=search
+ call append(0, ['if', "\tfoo", "\tfoo", 'endif'])
+ let @/ = 'foo'
+ call cursor(1,1)
+ norm n
+ END
+ call writefile(lines, 'Xsearchstat1')
+
+ let buf = RunVimInTerminal('-S Xsearchstat1', #{rows: 10})
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstat_3', {})
+
+ call term_sendkeys(buf, "n")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstat_3', {})
+
+ call term_sendkeys(buf, "n")
+ call TermWait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstat_3', {})
+
+ call StopVimInTerminal(buf)
+ call delete('Xsearchstat1')
+endfunc
+
+func! Test_search_stat_screendump()
+ CheckScreendump
+
+ let lines =<< trim END
+ set shortmess-=S
+ " Append 50 lines with text to search for, "foobar" appears 20 times
+ call append(0, repeat(['foobar', 'foo', 'fooooobar', 'foba', 'foobar'], 20))
+ call setline(2, 'find this')
+ call setline(70, 'find this')
+ nnoremap n n
+ let @/ = 'find this'
+ call cursor(1,1)
+ norm n
+ END
+ call writefile(lines, 'Xsearchstat')
+ let buf = RunVimInTerminal('-S Xsearchstat', #{rows: 10})
+ call term_wait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstat_1', {})
+
+ call term_sendkeys(buf, ":nnoremap <silent> n n\<cr>")
+ call term_sendkeys(buf, "gg0n")
+ call term_wait(buf)
+ call VerifyScreenDump(buf, 'Test_searchstat_2', {})
+
+ call StopVimInTerminal(buf)
+ call delete('Xsearchstat')
+endfunc
+
+func Test_searchcount_in_statusline()
+ CheckScreendump
+
+ let lines =<< trim END
+ set shortmess-=S
+ call append(0, 'this is something')
+ function TestSearchCount() abort
+ let search_count = searchcount()
+ if !empty(search_count)
+ return '[' . search_count.current . '/' . search_count.total . ']'
+ else
+ return ''
+ endif
+ endfunction
+ set hlsearch
+ set laststatus=2 statusline+=%{TestSearchCount()}
+ END
+ call writefile(lines, 'Xsearchstatusline')
+ let buf = RunVimInTerminal('-S Xsearchstatusline', #{rows: 10})
+ call TermWait(buf)
+ call term_sendkeys(buf, "/something")
+ call VerifyScreenDump(buf, 'Test_searchstat_4', {})
+
+ call term_sendkeys(buf, "\<Esc>")
+ call StopVimInTerminal(buf)
+ call delete('Xsearchstatusline')
+endfunc
diff --git a/src/nvim/testdir/test_signs.vim b/src/nvim/testdir/test_signs.vim
index 8b1927e4f0..2b7672f1af 100644
--- a/src/nvim/testdir/test_signs.vim
+++ b/src/nvim/testdir/test_signs.vim
@@ -1742,3 +1742,117 @@ func Test_sign_cursor_position()
call StopVimInTerminal(buf)
call delete('XtestSigncolumn')
endfunc
+
+" Return the 'len' characters in screen starting from (row,col)
+func s:ScreenLine(row, col, len)
+ let s = ''
+ for i in range(a:len)
+ let s .= nr2char(screenchar(a:row, a:col + i))
+ endfor
+ return s
+endfunc
+
+" Test for 'signcolumn' set to 'number'.
+func Test_sign_numcol()
+ new
+ call append(0, "01234")
+ " With 'signcolumn' set to 'number', make sure sign is displayed in the
+ " number column and line number is not displayed.
+ set numberwidth=2
+ set number
+ set signcolumn=number
+ sign define sign1 text==>
+ sign place 10 line=1 name=sign1
+ sign define sign2 text=V
+ redraw!
+ call assert_equal("=> 01234", s:ScreenLine(1, 1, 8))
+
+ " With 'signcolumn' set to 'number', when there is no sign, make sure line
+ " number is displayed in the number column
+ sign unplace 10
+ redraw!
+ call assert_equal("1 01234", s:ScreenLine(1, 1, 7))
+
+ " Disable number column. Check whether sign is displayed in the sign column
+ set numberwidth=4
+ set nonumber
+ sign place 10 line=1 name=sign1
+ redraw!
+ call assert_equal("=>01234", s:ScreenLine(1, 1, 7))
+
+ " Enable number column. Check whether sign is displayed in the number column
+ set number
+ redraw!
+ call assert_equal(" => 01234", s:ScreenLine(1, 1, 9))
+
+ " Disable sign column. Make sure line number is displayed
+ set signcolumn=no
+ redraw!
+ call assert_equal(" 1 01234", s:ScreenLine(1, 1, 9))
+
+ " Enable auto sign column. Make sure both sign and line number are displayed
+ set signcolumn=auto
+ redraw!
+ call assert_equal("=> 1 01234", s:ScreenLine(1, 1, 11))
+
+ " Test displaying signs in the number column with width 1
+ call sign_unplace('*')
+ call append(1, "abcde")
+ call append(2, "01234")
+ " Enable number column with width 1
+ set number numberwidth=1 signcolumn=auto
+ redraw!
+ call assert_equal("3 01234", s:ScreenLine(3, 1, 7))
+ " Place a sign and make sure number column width remains the same
+ sign place 20 line=2 name=sign1
+ redraw!
+ call assert_equal("=>2 abcde", s:ScreenLine(2, 1, 9))
+ call assert_equal(" 3 01234", s:ScreenLine(3, 1, 9))
+ " Set 'signcolumn' to 'number', make sure the number column width increases
+ set signcolumn=number
+ redraw!
+ call assert_equal("=> abcde", s:ScreenLine(2, 1, 8))
+ call assert_equal(" 3 01234", s:ScreenLine(3, 1, 8))
+ " Set 'signcolumn' to 'auto', make sure the number column width is 1.
+ set signcolumn=auto
+ redraw!
+ call assert_equal("=>2 abcde", s:ScreenLine(2, 1, 9))
+ call assert_equal(" 3 01234", s:ScreenLine(3, 1, 9))
+ " Set 'signcolumn' to 'number', make sure the number column width is 2.
+ set signcolumn=number
+ redraw!
+ call assert_equal("=> abcde", s:ScreenLine(2, 1, 8))
+ call assert_equal(" 3 01234", s:ScreenLine(3, 1, 8))
+ " Disable 'number' column
+ set nonumber
+ redraw!
+ call assert_equal("=>abcde", s:ScreenLine(2, 1, 7))
+ call assert_equal(" 01234", s:ScreenLine(3, 1, 7))
+ " Enable 'number' column
+ set number
+ redraw!
+ call assert_equal("=> abcde", s:ScreenLine(2, 1, 8))
+ call assert_equal(" 3 01234", s:ScreenLine(3, 1, 8))
+ " Remove the sign and make sure the width of the number column is 1.
+ call sign_unplace('', {'id' : 20})
+ redraw!
+ call assert_equal("3 01234", s:ScreenLine(3, 1, 7))
+ " When the first sign is placed with 'signcolumn' set to number, verify that
+ " the number column width increases
+ sign place 30 line=1 name=sign1
+ redraw!
+ call assert_equal("=> 01234", s:ScreenLine(1, 1, 8))
+ call assert_equal(" 2 abcde", s:ScreenLine(2, 1, 8))
+ " Add sign with multi-byte text
+ set numberwidth=4
+ sign place 40 line=2 name=sign2
+ redraw!
+ call assert_equal(" => 01234", s:ScreenLine(1, 1, 9))
+ call assert_equal(" V abcde", s:ScreenLine(2, 1, 9))
+
+ sign unplace * group=*
+ sign undefine sign1
+ set signcolumn&
+ set number&
+ enew! | close
+endfunc
diff --git a/src/nvim/testdir/test_spell.vim b/src/nvim/testdir/test_spell.vim
index e5eaa01e92..ab8a998bb8 100644
--- a/src/nvim/testdir/test_spell.vim
+++ b/src/nvim/testdir/test_spell.vim
@@ -1,10 +1,13 @@
" Test spell checking
" Note: this file uses latin1 encoding, but is used with utf-8 encoding.
+source check.vim
if !has('spell')
finish
endif
+source screendump.vim
+
func TearDown()
set nospell
call delete('Xtest.aff')
@@ -76,6 +79,11 @@ func Test_spellbadword()
call assert_equal(['bycycle', 'bad'], spellbadword('My bycycle.'))
call assert_equal(['another', 'caps'], spellbadword('A sentence. another sentence'))
+ call assert_equal(['TheCamelWord', 'bad'], spellbadword('TheCamelWord asdf'))
+ set spelloptions=camel
+ call assert_equal(['asdf', 'bad'], spellbadword('TheCamelWord asdf'))
+ set spelloptions=
+
set spelllang=en
call assert_equal(['', ''], spellbadword('centre'))
call assert_equal(['', ''], spellbadword('center'))
@@ -110,6 +118,43 @@ foobar/?
set spell&
endfunc
+func Test_spelllang_inv_region()
+ set spell spelllang=en_xx
+ let messages = GetMessages()
+ call assert_equal('Warning: region xx not supported', messages[-1])
+ set spell& spelllang&
+endfunc
+
+func Test_compl_with_CTRL_X_CTRL_K_using_spell()
+ " When spell checking is enabled and 'dictionary' is empty,
+ " CTRL-X CTRL-K in insert mode completes using the spelling dictionary.
+ new
+ set spell spelllang=en dictionary=
+
+ set ignorecase
+ call feedkeys("Senglis\<c-x>\<c-k>\<esc>", 'tnx')
+ call assert_equal(['English'], getline(1, '$'))
+ call feedkeys("SEnglis\<c-x>\<c-k>\<esc>", 'tnx')
+ call assert_equal(['English'], getline(1, '$'))
+
+ set noignorecase
+ call feedkeys("Senglis\<c-x>\<c-k>\<esc>", 'tnx')
+ call assert_equal(['englis'], getline(1, '$'))
+ call feedkeys("SEnglis\<c-x>\<c-k>\<esc>", 'tnx')
+ call assert_equal(['English'], getline(1, '$'))
+
+ set spelllang=en_us
+ call feedkeys("Stheat\<c-x>\<c-k>\<esc>", 'tnx')
+ call assert_equal(['theater'], getline(1, '$'))
+ set spelllang=en_gb
+ call feedkeys("Stheat\<c-x>\<c-k>\<esc>", 'tnx')
+ " FIXME: commented out, expected theatre bug got theater. See issue #7025.
+ " call assert_equal(['theatre'], getline(1, '$'))
+
+ bwipe!
+ set spell& spelllang& dictionary& ignorecase&
+endfunc
+
func Test_spellreall()
new
set spell
@@ -477,6 +522,44 @@ func RunGoodBad(good, bad, expected_words, expected_bad_words)
bwipe!
endfunc
+func Test_spell_screendump()
+ CheckScreendump
+
+ let lines =<< trim END
+ call setline(1, [
+ \ "This is some text without any spell errors. Everything",
+ \ "should just be black, nothing wrong here.",
+ \ "",
+ \ "This line has a sepll error. and missing caps.",
+ \ "And and this is the the duplication.",
+ \ "with missing caps here.",
+ \ ])
+ set spell spelllang=en_nz
+ END
+ call writefile(lines, 'XtestSpell')
+ let buf = RunVimInTerminal('-S XtestSpell', {'rows': 8})
+ call VerifyScreenDump(buf, 'Test_spell_1', {})
+
+ let lines =<< trim END
+ call setline(1, [
+ \ "This is some text without any spell errors. Everything",
+ \ "should just be black, nothing wrong here.",
+ \ "",
+ \ "This line has a sepll error. and missing caps.",
+ \ "And and this is the the duplication.",
+ \ "with missing caps here.",
+ \ ])
+ set spell spelllang=en_nz
+ END
+ call writefile(lines, 'XtestSpell')
+ let buf = RunVimInTerminal('-S XtestSpell', {'rows': 8})
+ call VerifyScreenDump(buf, 'Test_spell_1', {})
+
+ " clean up
+ call StopVimInTerminal(buf)
+ call delete('XtestSpell')
+endfunc
+
let g:test_data_aff1 = [
\"SET ISO8859-1",
\"TRY esianrtolcdugmphbyfvkwjkqxz-\xEB\xE9\xE8\xEA\xEF\xEE\xE4\xE0\xE2\xF6\xFC\xFB'ESIANRTOLCDUGMPHBYFVKWJKQXZ",
diff --git a/src/nvim/testdir/test_startup.vim b/src/nvim/testdir/test_startup.vim
index 9abaca5957..12bec745a8 100644
--- a/src/nvim/testdir/test_startup.vim
+++ b/src/nvim/testdir/test_startup.vim
@@ -369,12 +369,11 @@ func Test_invalid_args()
endfor
if has('clientserver')
- " FIXME: need to add --servername to this list
- " but it causes vim-8.1.1282 to crash!
for opt in ['--remote', '--remote-send', '--remote-silent', '--remote-expr',
\ '--remote-tab', '--remote-tab-wait',
\ '--remote-tab-wait-silent', '--remote-tab-silent',
\ '--remote-wait', '--remote-wait-silent',
+ \ '--servername',
\ ]
let out = split(system(GetVimCommand() .. ' ' .. opt), "\n")
call assert_equal(1, v:shell_error)
@@ -384,14 +383,21 @@ func Test_invalid_args()
endfor
endif
- " FIXME: commented out as this causes vim-8.1.1282 to crash!
- "if has('clipboard')
- " let out = split(system(GetVimCommand() .. ' --display'), "\n")
- " call assert_equal(1, v:shell_error)
- " call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
- " call assert_equal('Argument missing after: "--display"', out[1])
- " call assert_equal('More info with: "vim -h"', out[2])
- "endif
+ if has('gui_gtk')
+ let out = split(system(GetVimCommand() .. ' --display'), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Argument missing after: "--display"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+ endif
+
+ if has('xterm_clipboard')
+ let out = split(system(GetVimCommand() .. ' -display'), "\n")
+ call assert_equal(1, v:shell_error)
+ call assert_match('^VIM - Vi IMproved .* (.*)$', out[0])
+ call assert_equal('Argument missing after: "-display"', out[1])
+ call assert_equal('More info with: "vim -h"', out[2])
+ endif
let out = split(system(GetVimCommand() .. ' -ix'), "\n")
call assert_equal(1, v:shell_error)
diff --git a/src/nvim/testdir/test_statusline.vim b/src/nvim/testdir/test_statusline.vim
index 8c81ec3431..4e38f7ebd8 100644
--- a/src/nvim/testdir/test_statusline.vim
+++ b/src/nvim/testdir/test_statusline.vim
@@ -354,6 +354,21 @@ func Test_statusline()
delfunc GetNested
delfunc GetStatusLine
+ " Test statusline works with 80+ items
+ function! StatusLabel()
+ redrawstatus
+ return '[label]'
+ endfunc
+ let statusline = '%{StatusLabel()}'
+ for i in range(150)
+ let statusline .= '%#TabLine' . (i % 2 == 0 ? 'Fill' : 'Sel') . '#' . string(i)[0]
+ endfor
+ let &statusline = statusline
+ redrawstatus
+ set statusline&
+ delfunc StatusLabel
+
+
" Check statusline in current and non-current window
" with the 'fillchars' option.
set fillchars=stl:^,stlnc:=,vert:\|,fold:-,diff:-
@@ -412,3 +427,22 @@ func Test_statusline_removed_group()
call StopVimInTerminal(buf)
call delete('XTest_statusline')
endfunc
+
+func Test_statusline_after_split_vsplit()
+ only
+
+ " Make the status line of each window show the window number.
+ set ls=2 stl=%{winnr()}
+
+ split | redraw
+ vsplit | redraw
+
+ " The status line of the third window should read '3' here.
+ call assert_equal('3', nr2char(screenchar(&lines - 1, 1)))
+
+ only
+ set ls& stl&
+endfunc
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_swap.vim b/src/nvim/testdir/test_swap.vim
index e072e9ed7f..f27920d20f 100644
--- a/src/nvim/testdir/test_swap.vim
+++ b/src/nvim/testdir/test_swap.vim
@@ -1,5 +1,7 @@
" Tests for the swap feature
+source check.vim
+
func s:swapname()
return trim(execute('swapname'))
endfunc
@@ -305,3 +307,61 @@ func Test_swap_recover_ext()
augroup END
augroup! test_swap_recover_ext
endfunc
+
+" Test for selecting 'q' in the attention prompt
+func Test_swap_prompt_splitwin()
+ CheckRunVimInTerminal
+
+ call writefile(['foo bar'], 'Xfile1')
+ edit Xfile1
+ preserve " should help to make sure the swap file exists
+
+ let buf = RunVimInTerminal('', {'rows': 20})
+ call term_sendkeys(buf, ":set nomore\n")
+ call term_sendkeys(buf, ":set noruler\n")
+ call term_sendkeys(buf, ":split Xfile1\n")
+ call term_wait(buf)
+ call WaitForAssert({-> assert_match('^\[O\]pen Read-Only, (E)dit anyway, (R)ecover, (Q)uit, (A)bort: $', term_getline(buf, 20))})
+ call term_sendkeys(buf, "q")
+ call term_wait(buf)
+ call term_sendkeys(buf, ":\<CR>")
+ call WaitForAssert({-> assert_match('^:$', term_getline(buf, 20))})
+ call term_sendkeys(buf, ":echomsg winnr('$')\<CR>")
+ call term_wait(buf)
+ call WaitForAssert({-> assert_match('^1$', term_getline(buf, 20))})
+ call StopVimInTerminal(buf)
+ %bwipe!
+ call delete('Xfile1')
+endfunc
+
+func Test_swap_symlink()
+ if !has("unix")
+ return
+ endif
+
+ call writefile(['text'], 'Xtestfile')
+ silent !ln -s -f Xtestfile Xtestlink
+
+ set dir=.
+
+ " Test that swap file uses the name of the file when editing through a
+ " symbolic link (so that editing the file twice is detected)
+ edit Xtestlink
+ call assert_match('Xtestfile\.swp$', s:swapname())
+ bwipe!
+
+ call mkdir('Xswapdir')
+ exe 'set dir=' . getcwd() . '/Xswapdir//'
+
+ " Check that this also works when 'directory' ends with '//'
+ edit Xtestlink
+ call assert_match('Xtestfile\.swp$', s:swapname())
+ bwipe!
+
+ set dir&
+ call delete('Xtestfile')
+ call delete('Xtestlink')
+ call delete('Xswapdir', 'rf')
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_syntax.vim b/src/nvim/testdir/test_syntax.vim
index 85ee42420e..2617aa3945 100644
--- a/src/nvim/testdir/test_syntax.vim
+++ b/src/nvim/testdir/test_syntax.vim
@@ -369,7 +369,11 @@ func Test_ownsyntax()
call setline(1, '#define FOO')
syntax on
set filetype=c
+
ownsyntax perl
+ " this should not crash
+ set
+
call assert_equal('perlComment', synIDattr(synID(line('.'), col('.'), 1), 'name'))
call assert_equal('c', b:current_syntax)
call assert_equal('perl', w:current_syntax)
@@ -471,6 +475,40 @@ func Test_bg_detection()
hi Normal ctermbg=NONE
endfunc
+func Test_syntax_hangs()
+ if !has('reltime') || !has('float') || !has('syntax')
+ return
+ endif
+
+ " This pattern takes a long time to match, it should timeout.
+ new
+ call setline(1, ['aaa', repeat('abc ', 1000), 'ccc'])
+ let start = reltime()
+ set nolazyredraw redrawtime=101
+ syn match Error /\%#=1a*.*X\@<=b*/
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_true(elapsed > 0.1)
+ call assert_true(elapsed < 1.0)
+
+ " second time syntax HL is disabled
+ let start = reltime()
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_true(elapsed < 0.1)
+
+ " after CTRL-L the timeout flag is reset
+ let start = reltime()
+ exe "normal \<C-L>"
+ redraw
+ let elapsed = reltimefloat(reltime(start))
+ call assert_true(elapsed > 0.1)
+ call assert_true(elapsed < 1.0)
+
+ set redrawtime&
+ bwipe!
+endfunc
+
func Test_synstack_synIDtrans()
new
setfiletype c
@@ -546,38 +584,42 @@ func Test_syn_wrong_z_one()
bwipe!
endfunc
-func Test_syntax_hangs()
- if !has('reltime') || !has('float') || !has('syntax')
- return
- endif
-
- " This pattern takes a long time to match, it should timeout.
- new
- call setline(1, ['aaa', repeat('abc ', 1000), 'ccc'])
- let start = reltime()
- set nolazyredraw redrawtime=101
- syn match Error /\%#=1a*.*X\@<=b*/
- redraw
- let elapsed = reltimefloat(reltime(start))
- call assert_true(elapsed > 0.1)
- call assert_true(elapsed < 1.0)
-
- " second time syntax HL is disabled
- let start = reltime()
- redraw
- let elapsed = reltimefloat(reltime(start))
- call assert_true(elapsed < 0.1)
+func Test_syntax_after_bufdo()
+ call writefile(['/* aaa comment */'], 'Xaaa.c')
+ call writefile(['/* bbb comment */'], 'Xbbb.c')
+ call writefile(['/* ccc comment */'], 'Xccc.c')
+ call writefile(['/* ddd comment */'], 'Xddd.c')
+
+ let bnr = bufnr('%')
+ new Xaaa.c
+ badd Xbbb.c
+ badd Xccc.c
+ badd Xddd.c
+ exe "bwipe " . bnr
+ let l = []
+ bufdo call add(l, bufnr('%'))
+ call assert_equal(4, len(l))
- " after CTRL-L the timeout flag is reset
- let start = reltime()
- exe "normal \<C-L>"
- redraw
- let elapsed = reltimefloat(reltime(start))
- call assert_true(elapsed > 0.1)
- call assert_true(elapsed < 1.0)
+ syntax on
- set redrawtime&
- bwipe!
+ " This used to only enable syntax HL in the last buffer.
+ bufdo tab split
+ tabrewind
+ for tab in range(1, 4)
+ norm fm
+ call assert_equal(['cComment'], map(synstack(line("."), col(".")), 'synIDattr(v:val, "name")'))
+ tabnext
+ endfor
+
+ bwipe! Xaaa.c
+ bwipe! Xbbb.c
+ bwipe! Xccc.c
+ bwipe! Xddd.c
+ syntax off
+ call delete('Xaaa.c')
+ call delete('Xbbb.c')
+ call delete('Xccc.c')
+ call delete('Xddd.c')
endfunc
func Test_syntax_foldlevel()
diff --git a/src/nvim/testdir/test_tabline.vim b/src/nvim/testdir/test_tabline.vim
index f24552088b..117d962d08 100644
--- a/src/nvim/testdir/test_tabline.vim
+++ b/src/nvim/testdir/test_tabline.vim
@@ -64,3 +64,28 @@ func Test_redrawtabline()
let &showtabline = showtabline_save
au! Bufadd
endfunc
+
+function EmptyTabname()
+ return ""
+endfunction
+
+function MakeTabLine() abort
+ let titles = map(range(1, tabpagenr('$')), '"%( %" . v:val . "T%{EmptyTabname()}%T %)"')
+ let sep = 'あ'
+ let tabpages = join(titles, sep)
+ return tabpages .. sep .. '%=%999X X'
+endfunction
+
+func Test_tabline_empty_group()
+ " this was reading invalid memory
+ set tabline=%!MakeTabLine()
+ tabnew
+ redraw!
+
+ tabclose
+ set tabline=
+endfunc
+
+
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_tagjump.vim b/src/nvim/testdir/test_tagjump.vim
index 6abe5b7c89..7057cdefb2 100644
--- a/src/nvim/testdir/test_tagjump.vim
+++ b/src/nvim/testdir/test_tagjump.vim
@@ -1,5 +1,8 @@
" Tests for tagjump (tags and special searches)
+source check.vim
+source screendump.vim
+
" SEGV occurs in older versions. (At least 7.4.1748 or older)
func Test_ptag_with_notagstack()
set notagstack
@@ -551,6 +554,37 @@ func Test_tag_line_toolong()
let &verbose = old_vbs
endfunc
+" Check that using :tselect does not run into the hit-enter prompt.
+" Requires a terminal to trigger that prompt.
+func Test_tselect()
+ CheckScreendump
+
+ call writefile([
+ \ 'main Xtest.h /^void test();$/;" f',
+ \ 'main Xtest.c /^int main()$/;" f',
+ \ 'main Xtest.x /^void test()$/;" f',
+ \ ], 'Xtags')
+ cal writefile([
+ \ 'int main()',
+ \ 'void test()',
+ \ ], 'Xtest.c')
+
+ let lines =<< trim [SCRIPT]
+ set tags=Xtags
+ [SCRIPT]
+ call writefile(lines, 'XTest_tselect')
+ let buf = RunVimInTerminal('-S XTest_tselect', {'rows': 10, 'cols': 50})
+
+ call term_wait(buf, 100)
+ call term_sendkeys(buf, ":tselect main\<CR>2\<CR>")
+ call VerifyScreenDump(buf, 'Test_tselect_1', {})
+
+ call StopVimInTerminal(buf)
+ call delete('Xtags')
+ call delete('Xtest.c')
+ call delete('XTest_tselect')
+endfunc
+
func Test_tagline()
call writefile([
\ 'provision Xtest.py /^ def provision(self, **kwargs):$/;" m line:1 language:Python class:Foo',
diff --git a/src/nvim/testdir/test_textformat.vim b/src/nvim/testdir/test_textformat.vim
index 75673adf0a..2223be952c 100644
--- a/src/nvim/testdir/test_textformat.vim
+++ b/src/nvim/testdir/test_textformat.vim
@@ -1,4 +1,7 @@
" Tests for the various 'formatoptions' settings
+
+source check.vim
+
func Test_text_format()
enew!
@@ -490,6 +493,23 @@ func Test_format_list_auto()
set fo& ai& bs&
endfunc
+func Test_crash_github_issue_5095()
+ CheckFeature autocmd
+
+ " This used to segfault, see https://github.com/vim/vim/issues/5095
+ augroup testing
+ au BufNew x center
+ augroup END
+
+ next! x
+
+ bw
+ augroup testing
+ au!
+ augroup END
+ augroup! testing
+endfunc
+
" Test for formatting multi-byte text with 'fo=t'
func Test_tw_2_fo_t()
new
diff --git a/src/nvim/testdir/test_textobjects.vim b/src/nvim/testdir/test_textobjects.vim
index 7863317eb0..f70cc1f70a 100644
--- a/src/nvim/testdir/test_textobjects.vim
+++ b/src/nvim/testdir/test_textobjects.vim
@@ -152,6 +152,36 @@ func Test_string_html_objects()
normal! dit
call assert_equal('-<b></b>', getline('.'))
+ " copy the tag block from leading indentation before the start tag
+ let t = " <b>\ntext\n</b>"
+ $put =t
+ normal! 2kvaty
+ call assert_equal("<b>\ntext\n</b>", @")
+
+ " copy the tag block from the end tag
+ let t = "<title>\nwelcome\n</title>"
+ $put =t
+ normal! $vaty
+ call assert_equal("<title>\nwelcome\n</title>", @")
+
+ " copy the outer tag block from a tag without an end tag
+ let t = "<html>\n<title>welcome\n</html>"
+ $put =t
+ normal! k$vaty
+ call assert_equal("<html>\n<title>welcome\n</html>", @")
+
+ " nested tag that has < in a different line from >
+ let t = "<div><div\n></div></div>"
+ $put =t
+ normal! k0vaty
+ call assert_equal("<div><div\n></div></div>", @")
+
+ " nested tag with attribute that has < in a different line from >
+ let t = "<div><div\nattr=\"attr\"\n></div></div>"
+ $put =t
+ normal! 2k0vaty
+ call assert_equal("<div><div\nattr=\"attr\"\n></div></div>", @")
+
set quoteescape&
enew!
endfunc
diff --git a/src/nvim/testdir/test_timers.vim b/src/nvim/testdir/test_timers.vim
index cffd80ff4f..13971a918d 100644
--- a/src/nvim/testdir/test_timers.vim
+++ b/src/nvim/testdir/test_timers.vim
@@ -233,16 +233,17 @@ func Test_timer_catch_error()
endfunc
func FeedAndPeek(timer)
- call test_feedinput('a')
+ " call test_feedinput('a')
+ call nvim_input('a')
call getchar(1)
endfunc
func Interrupt(timer)
- call test_feedinput("\<C-C>")
+ " call test_feedinput("\<C-C>")
+ call nvim_input("\<C-C>")
endfunc
func Test_peek_and_get_char()
- throw 'skipped: Nvim does not support test_feedinput()'
if !has('unix') && !has('gui_running')
return
endif
@@ -339,6 +340,41 @@ func Test_nocatch_garbage_collect()
delfunc FeedChar
endfunc
+func Test_error_in_timer_callback()
+ if !has('terminal') || (has('win32') && has('gui_running'))
+ throw 'Skipped: cannot run Vim in a terminal window'
+ endif
+
+ let lines =<< trim [CODE]
+ func Func(timer)
+ " fail to create list
+ let x = [
+ endfunc
+ set updatetime=50
+ call timer_start(1, 'Func')
+ [CODE]
+ call writefile(lines, 'Xtest.vim')
+
+ let buf = term_start(GetVimCommandCleanTerm() .. ' -S Xtest.vim', {'term_rows': 8})
+ let job = term_getjob(buf)
+ call WaitForAssert({-> assert_notequal('', term_getline(buf, 8))})
+
+ " GC must not run during timer callback, which can make Vim crash.
+ call term_wait(buf, 100)
+ call term_sendkeys(buf, "\<CR>")
+ call term_wait(buf, 100)
+ call assert_equal('run', job_status(job))
+
+ call term_sendkeys(buf, ":qall!\<CR>")
+ call WaitFor({-> job_status(job) ==# 'dead'})
+ if has('unix')
+ call assert_equal('', job_info(job).termsig)
+ endif
+
+ call delete('Xtest.vim')
+ exe buf .. 'bwipe!'
+endfunc
+
func Test_timer_invalid_callback()
call assert_fails('call timer_start(0, "0")', 'E921')
endfunc
diff --git a/src/nvim/testdir/test_undo.vim b/src/nvim/testdir/test_undo.vim
index adcdcb1cd9..3b66071d6d 100644
--- a/src/nvim/testdir/test_undo.vim
+++ b/src/nvim/testdir/test_undo.vim
@@ -364,6 +364,25 @@ func Test_wundo_errors()
bwipe!
endfunc
+" Check that reading a truncted undo file doesn't hang.
+func Test_undofile_truncated()
+ throw 'skipped: TODO: '
+ new
+ call setline(1, 'hello')
+ set ul=100
+ wundo Xundofile
+ let contents = readfile('Xundofile', 'B')
+
+ " try several sizes
+ for size in range(20, 500, 33)
+ call writefile(contents[0:size], 'Xundofile')
+ call assert_fails('rundo Xundofile', 'E825:')
+ endfor
+
+ bwipe!
+ call delete('Xundofile')
+endfunc
+
func Test_rundo_errors()
call assert_fails('rundo XfileDoesNotExist', 'E822:')
@@ -373,6 +392,26 @@ func Test_rundo_errors()
call delete('Xundofile')
endfunc
+func Test_undofile_next()
+ set undofile
+ new Xfoo.txt
+ execute "norm ix\<c-g>uy\<c-g>uz\<Esc>"
+ write
+ bwipe
+
+ next Xfoo.txt
+ call assert_equal('xyz', getline(1))
+ silent undo
+ call assert_equal('xy', getline(1))
+ silent undo
+ call assert_equal('x', getline(1))
+ bwipe!
+
+ call delete('Xfoo.txt')
+ call delete('.Xfoo.txt.un~')
+ set undofile&
+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()
diff --git a/src/nvim/testdir/test_usercommands.vim b/src/nvim/testdir/test_usercommands.vim
index 2c7cb7bab7..0a89066a2b 100644
--- a/src/nvim/testdir/test_usercommands.vim
+++ b/src/nvim/testdir/test_usercommands.vim
@@ -184,6 +184,34 @@ func Test_Ambiguous()
call assert_fails("\x4ei\041", 'E492: Not an editor command: Ni!')
endfunc
+func Test_redefine_on_reload()
+ call writefile(['command ExistingCommand echo "yes"'], 'Xcommandexists')
+ call assert_equal(0, exists(':ExistingCommand'))
+ source Xcommandexists
+ call assert_equal(2, exists(':ExistingCommand'))
+ " Redefining a command when reloading a script is OK.
+ source Xcommandexists
+ call assert_equal(2, exists(':ExistingCommand'))
+
+ " But redefining in another script is not OK.
+ call writefile(['command ExistingCommand echo "yes"'], 'Xcommandexists2')
+ call assert_fails('source Xcommandexists2', 'E174:')
+ call delete('Xcommandexists2')
+
+ " And defining twice in one script is not OK.
+ delcommand ExistingCommand
+ call assert_equal(0, exists(':ExistingCommand'))
+ call writefile([
+ \ 'command ExistingCommand echo "yes"',
+ \ 'command ExistingCommand echo "no"',
+ \ ], 'Xcommandexists')
+ call assert_fails('source Xcommandexists', 'E174:')
+ call assert_equal(2, exists(':ExistingCommand'))
+
+ call delete('Xcommandexists')
+ delcommand ExistingCommand
+endfunc
+
func Test_CmdUndefined()
call assert_fails('Doit', 'E492:')
au CmdUndefined Doit :command Doit let g:didit = 'yes'
@@ -248,7 +276,7 @@ func Test_CmdCompletion()
call assert_equal('"com -nargs=* + 0 1 ?', @:)
call feedkeys(":com -addr=\<C-A>\<C-B>\"\<CR>", 'tx')
- call assert_equal('"com -addr=arguments buffers lines loaded_buffers quickfix tabs windows', @:)
+ call assert_equal('"com -addr=arguments buffers lines loaded_buffers other quickfix tabs windows', @:)
call feedkeys(":com -complete=co\<C-A>\<C-B>\"\<CR>", 'tx')
call assert_equal('"com -complete=color command compiler', @:)
@@ -312,3 +340,202 @@ func Test_use_execute_in_completion()
call assert_equal('"DoExec hi', @:)
delcommand DoExec
endfunc
+
+func Test_addr_all()
+ throw 'skipped: requires patch v8.1.0341 to pass'
+ command! -addr=lines DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ %DoSomething
+ call assert_equal(1, g:a1)
+ call assert_equal(line('$'), g:a2)
+
+ command! -addr=arguments DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ args one two three
+ %DoSomething
+ call assert_equal(1, g:a1)
+ call assert_equal(3, g:a2)
+
+ command! -addr=buffers DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ %DoSomething
+ for low in range(1, bufnr('$'))
+ if buflisted(low)
+ break
+ endif
+ endfor
+ call assert_equal(low, g:a1)
+ call assert_equal(bufnr('$'), g:a2)
+
+ command! -addr=loaded_buffers DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ %DoSomething
+ for low in range(1, bufnr('$'))
+ if bufloaded(low)
+ break
+ endif
+ endfor
+ call assert_equal(low, g:a1)
+ for up in range(bufnr('$'), 1, -1)
+ if bufloaded(up)
+ break
+ endif
+ endfor
+ call assert_equal(up, g:a2)
+
+ command! -addr=windows DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ new
+ %DoSomething
+ call assert_equal(1, g:a1)
+ call assert_equal(winnr('$'), g:a2)
+ bwipe
+
+ command! -addr=tabs DoSomething let g:a1 = <line1> | let g:a2 = <line2>
+ tabnew
+ %DoSomething
+ call assert_equal(1, g:a1)
+ call assert_equal(len(gettabinfo()), g:a2)
+ bwipe
+
+ command! -addr=other DoSomething echo 'nothing'
+ DoSomething
+ call assert_fails('%DoSomething')
+
+ delcommand DoSomething
+endfunc
+
+func Test_command_list()
+ command! DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 :",
+ \ execute('command DoCmd'))
+
+ " Test with various -range= and -count= argument values.
+ command! -range DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . :",
+ \ execute('command DoCmd'))
+ command! -range=% DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 % :",
+ \ execute('command! DoCmd'))
+ command! -range=2 DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 2 :",
+ \ execute('command DoCmd'))
+ command! -count=2 DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 2c :",
+ \ execute('command DoCmd'))
+
+ " Test with various -addr= argument values.
+ command! -addr=lines DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . :",
+ \ execute('command DoCmd'))
+ command! -addr=arguments DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . arg :",
+ \ execute('command DoCmd'))
+ command! -addr=buffers DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . buf :",
+ \ execute('command DoCmd'))
+ command! -addr=loaded_buffers DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . load :",
+ \ execute('command DoCmd'))
+ command! -addr=windows DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . win :",
+ \ execute('command DoCmd'))
+ command! -addr=tabs DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . tab :",
+ \ execute('command DoCmd'))
+ command! -addr=other DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 . ? :",
+ \ execute('command DoCmd'))
+
+ " Test with various -complete= argument values (non-exhaustive list)
+ command! -complete=arglist DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 arglist :",
+ \ execute('command DoCmd'))
+ command! -complete=augroup DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 augroup :",
+ \ execute('command DoCmd'))
+ command! -complete=custom,CustomComplete DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 custom :",
+ \ execute('command DoCmd'))
+ command! -complete=customlist,CustomComplete DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 customlist :",
+ \ execute('command DoCmd'))
+
+ " Test with various -narg= argument values.
+ command! -nargs=0 DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 :",
+ \ execute('command DoCmd'))
+ command! -nargs=1 DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 1 :",
+ \ execute('command DoCmd'))
+ command! -nargs=* DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd * :",
+ \ execute('command DoCmd'))
+ command! -nargs=? DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd ? :",
+ \ execute('command DoCmd'))
+ command! -nargs=+ DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd + :",
+ \ execute('command DoCmd'))
+
+ " Test with other arguments.
+ command! -bang DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n! DoCmd 0 :",
+ \ execute('command DoCmd'))
+ command! -bar DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n| DoCmd 0 :",
+ \ execute('command DoCmd'))
+ command! -register DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n\" DoCmd 0 :",
+ \ execute('command DoCmd'))
+ command! -buffer DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\nb DoCmd 0 :"
+ \ .. "\n\" DoCmd 0 :",
+ \ execute('command DoCmd'))
+ comclear
+
+ " Test with many args.
+ command! -bang -bar -register -buffer -nargs=+ -complete=environment -addr=windows -count=3 DoCmd :
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n!\"b|DoCmd + 3c win environment :",
+ \ execute('command DoCmd'))
+ comclear
+
+ " Test with special characters in command definition.
+ command! DoCmd :<cr><tab><c-d>
+ call assert_equal("\n Name Args Address Complete Definition"
+ \ .. "\n DoCmd 0 :<CR><Tab><C-D>",
+ \ execute('command DoCmd'))
+
+ " Test output in verbose mode.
+ command! DoCmd :
+ call assert_match("^\n"
+ \ .. " Name Args Address Complete Definition\n"
+ \ .. " DoCmd 0 :\n"
+ \ .. "\tLast set from .*/test_usercommands.vim line \\d\\+$",
+ \ execute('verbose command DoCmd'))
+
+ comclear
+ call assert_equal("\nNo user-defined commands found", execute(':command Xxx'))
+ call assert_equal("\nNo user-defined commands found", execute('command'))
+endfunc
diff --git a/src/nvim/testdir/test_version.vim b/src/nvim/testdir/test_version.vim
new file mode 100644
index 0000000000..46cf34979f
--- /dev/null
+++ b/src/nvim/testdir/test_version.vim
@@ -0,0 +1,12 @@
+" Test :version Ex command
+
+func Test_version()
+ " version should always return the same string.
+ let v1 = execute('version')
+ let v2 = execute('version')
+ call assert_equal(v1, v2)
+
+ call assert_match("^\n\nNVIM v[0-9]\\+\\.[0-9]\\+\\.[0-9]\\+.*", v1)
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_vimscript.vim b/src/nvim/testdir/test_vimscript.vim
index d2f13ff072..cb81997d39 100644
--- a/src/nvim/testdir/test_vimscript.vim
+++ b/src/nvim/testdir/test_vimscript.vim
@@ -1409,6 +1409,17 @@ func Test_compound_assignment_operators()
let @/ = ''
endfunc
+func Test_funccall_garbage_collect()
+ func Func(x, ...)
+ call add(a:x, a:000)
+ endfunc
+ call Func([], [])
+ " Must not crash cause by invalid freeing
+ call test_garbagecollect_now()
+ call assert_true(v:true)
+ delfunc Func
+endfunc
+
func Test_function_defined_line()
if has('gui_running')
" Can't catch the output of gvim.
diff --git a/src/nvim/testdir/test_visual.vim b/src/nvim/testdir/test_visual.vim
index 7fc8cdd7f4..734f264672 100644
--- a/src/nvim/testdir/test_visual.vim
+++ b/src/nvim/testdir/test_visual.vim
@@ -432,3 +432,14 @@ func Test_Visual_Block()
close!
endfunc
+
+func Test_visual_put_in_block()
+ new
+ call setline(1, ['xxxx', 'y∞yy', 'zzzz'])
+ normal 1G2yl
+ exe "normal 1G2l\<C-V>jjlp"
+ call assert_equal(['xxxx', 'y∞xx', 'zzxx'], getline(1, 3))
+ bwipe!
+endfunc
+
+" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_window_cmd.vim b/src/nvim/testdir/test_window_cmd.vim
index aaa291f87d..500e3ff088 100644
--- a/src/nvim/testdir/test_window_cmd.vim
+++ b/src/nvim/testdir/test_window_cmd.vim
@@ -841,4 +841,50 @@ func Test_winnr()
only | tabonly
endfunc
+func Test_window_resize()
+ " Vertical :resize (absolute, relative, min and max size).
+ vsplit
+ vert resize 8
+ call assert_equal(8, winwidth(0))
+ vert resize +2
+ call assert_equal(10, winwidth(0))
+ vert resize -2
+ call assert_equal(8, winwidth(0))
+ vert resize
+ call assert_equal(&columns - 2, winwidth(0))
+ vert resize 0
+ call assert_equal(1, winwidth(0))
+ vert resize 99999
+ call assert_equal(&columns - 2, winwidth(0))
+
+ %bwipe!
+
+ " Horizontal :resize (with absolute, relative size, min and max size).
+ split
+ resize 8
+ call assert_equal(8, winheight(0))
+ resize +2
+ call assert_equal(10, winheight(0))
+ resize -2
+ call assert_equal(8, winheight(0))
+ resize
+ call assert_equal(&lines - 4, winheight(0))
+ resize 0
+ call assert_equal(1, winheight(0))
+ resize 99999
+ call assert_equal(&lines - 4, winheight(0))
+
+ " :resize with explicit window number.
+ let other_winnr = winnr('j')
+ exe other_winnr .. 'resize 10'
+ call assert_equal(10, winheight(other_winnr))
+ call assert_equal(&lines - 10 - 3, winheight(0))
+ exe other_winnr .. 'resize +1'
+ exe other_winnr .. 'resize +1'
+ call assert_equal(12, winheight(other_winnr))
+ call assert_equal(&lines - 10 - 3 -2, winheight(0))
+
+ %bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
diff --git a/src/nvim/testdir/test_windows_home.vim b/src/nvim/testdir/test_windows_home.vim
index 2e311b9aa5..3c2db01444 100644
--- a/src/nvim/testdir/test_windows_home.vim
+++ b/src/nvim/testdir/test_windows_home.vim
@@ -1,8 +1,7 @@
" Test for $HOME on Windows.
-if !has('win32')
- finish
-endif
+source check.vim
+CheckMSWindows
let s:env = {}
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index bfd9435c49..62d7dc8b18 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -52,15 +52,10 @@
#define OUTBUF_SIZE 0xffff
#define TOO_MANY_EVENTS 1000000
-#define STARTS_WITH(str, prefix) \
- (strlen(str) >= (sizeof(prefix) - 1) && 0 == memcmp((str), (prefix), \
- sizeof(prefix) - 1))
-#define SCREEN_WRAP(is_screen, seq) ((is_screen) \
- ? DCS_STR seq STERM_STR : seq)
-#define SCREEN_TMUX_WRAP(is_screen, is_tmux, seq) \
- ((is_screen) \
- ? DCS_STR seq STERM_STR : (is_tmux) \
- ? DCS_STR "tmux;\x1b" seq STERM_STR : seq)
+#define STARTS_WITH(str, prefix) (strlen(str) >= (sizeof(prefix) - 1) \
+ && 0 == memcmp((str), (prefix), sizeof(prefix) - 1))
+#define TMUX_WRAP(is_tmux, seq) ((is_tmux) \
+ ? "\x1bPtmux;\x1b" seq "\x1b\\" : seq)
#define LINUXSET0C "\x1b[?0c"
#define LINUXSET1C "\x1b[?1c"
@@ -113,6 +108,7 @@ typedef struct {
bool cork, overflow;
bool cursor_color_changed;
bool is_starting;
+ FILE *screenshot;
cursorentry_T cursor_shapes[SHAPE_IDX_COUNT];
HlAttrs clear_attrs;
kvec_t(HlAttrs) attrs;
@@ -172,6 +168,7 @@ UI *tui_start(void)
ui->suspend = tui_suspend;
ui->set_title = tui_set_title;
ui->set_icon = tui_set_icon;
+ ui->screenshot = tui_screenshot;
ui->option_set= tui_option_set;
ui->raw_line = tui_raw_line;
@@ -319,7 +316,13 @@ static void terminfo_start(UI *ui)
#ifdef WIN32
uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_RAW);
#else
- uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO);
+ int retry_count = 10;
+ // A signal may cause uv_tty_set_mode() to fail (e.g., SIGCONT). Retry a
+ // few times. #12322
+ while (uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO) == UV_EINTR
+ && retry_count > 0) {
+ retry_count--;
+ }
#endif
} else {
uv_pipe_init(&data->write_loop, &data->output_handle.pipe, 0);
@@ -417,6 +420,7 @@ static void tui_main(UIBridgeData *bridge, UI *ui)
data->bridge = bridge;
data->loop = &tui_loop;
data->is_starting = true;
+ data->screenshot = NULL;
kv_init(data->invalid_regions);
signal_watcher_init(data->loop, &data->winch_handle, ui);
signal_watcher_init(data->loop, &data->cont_handle, data);
@@ -1085,6 +1089,7 @@ static void tui_set_mode(UI *ui, ModeShape mode)
}
} else if (c.id == 0) {
// No cursor color for this mode; reset to default.
+ data->want_invisible = false;
unibi_out_ext(ui, data->unibi_ext.reset_cursor_color);
}
@@ -1103,6 +1108,15 @@ static void tui_set_mode(UI *ui, ModeShape mode)
static void tui_mode_change(UI *ui, String mode, Integer mode_idx)
{
TUIData *data = ui->data;
+#ifdef UNIX
+ // If stdin is not a TTY, the LHS of pipe may change the state of the TTY
+ // after calling uv_tty_set_mode. So, set the mode of the TTY again here.
+ // #13073
+ if (data->is_starting && data->input.in_fd == STDERR_FILENO) {
+ uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_NORMAL);
+ uv_tty_set_mode(&data->output_handle.tty, UV_TTY_MODE_IO);
+ }
+#endif
tui_set_mode(ui, (ModeShape)mode_idx);
data->is_starting = false; // mode entered, no longer starting
data->showing_mode = (ModeShape)mode_idx;
@@ -1322,6 +1336,31 @@ static void tui_set_icon(UI *ui, String icon)
{
}
+static void tui_screenshot(UI *ui, String path)
+{
+ TUIData *data = ui->data;
+ UGrid *grid = &data->grid;
+ flush_buf(ui);
+ grid->row = 0;
+ grid->col = 0;
+
+ FILE *f = fopen(path.data, "w");
+ data->screenshot = f;
+ fprintf(f, "%d,%d\n", grid->height, grid->width);
+ unibi_out(ui, unibi_clear_screen);
+ for (int i = 0; i < grid->height; i++) {
+ cursor_goto(ui, i, 0);
+ for (int j = 0; j < grid->width; j++) {
+ print_cell(ui, &grid->cells[i][j]);
+ }
+ }
+ flush_buf(ui);
+ data->screenshot = NULL;
+
+ fclose(f);
+}
+
+
static void tui_option_set(UI *ui, String name, Object value)
{
TUIData *data = ui->data;
@@ -1591,10 +1630,6 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
bool mate_pretending_xterm = xterm && colorterm
&& strstr(colorterm, "mate-terminal");
bool true_xterm = xterm && !!xterm_version && !bsdvt;
- bool true_screen = screen && !os_getenv("TMUX");
- bool screen_host_linuxvt =
- terminfo_is_term_family(true_screen && term[6] == '.'
- ? term + 7 : NULL, "linux");
bool cygwin = terminfo_is_term_family(term, "cygwin");
char *fix_normal = (char *)unibi_get_str(ut, unibi_cursor_normal);
@@ -1737,10 +1772,8 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
#define XTERM_SETAB_16 \
"\x1b[%?%p1%{8}%<%t4%p1%d%e%p1%{16}%<%t10%p1%{8}%-%d%e39%;m"
- data->unibi_ext.get_bg =
- (int)unibi_add_ext_str(ut, "ext.get_bg",
- SCREEN_TMUX_WRAP(true_screen,
- tmux, "\x1b]11;?\x07"));
+ data->unibi_ext.get_bg = (int)unibi_add_ext_str(ut, "ext.get_bg",
+ "\x1b]11;?\x07");
// Terminals with 256-colour SGR support despite what terminfo says.
if (unibi_get_num(ut, unibi_max_colors) < 256) {
@@ -1775,32 +1808,6 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
data->unibi_ext.set_cursor_style = unibi_find_ext_str(ut, "Ss");
}
- // GNU Screen does not have Ss/Se. When terminfo has Ss/Se, it is wrapped with
- // DCS because it is inherited from the host terminal.
- if (true_screen) {
- size_t len;
- size_t dcs_st_len = strlen(DCS_STR) + strlen(STERM_STR);
- if (-1 != data->unibi_ext.set_cursor_style) {
- const char *orig_ss =
- unibi_get_ext_str(data->ut, (size_t)data->unibi_ext.reset_cursor_style);
- len = STRLEN(orig_ss) + dcs_st_len + 1;
- char *ss = xmalloc(len);
- snprintf(ss, len, "%s%s%s", DCS_STR, orig_ss, STERM_STR);
- unibi_set_ext_str(data->ut, (size_t)data->unibi_ext.set_cursor_style, ss);
- xfree(ss);
- }
- if (-1 != data->unibi_ext.reset_cursor_style) {
- const char *orig_se =
- unibi_get_ext_str(data->ut, (size_t)data->unibi_ext.reset_cursor_style);
- len = strlen(orig_se) + dcs_st_len + 1;
- char *se = xmalloc(len);
- snprintf(se, len, "%s%s%s", DCS_STR, orig_se, STERM_STR);
- unibi_set_ext_str(data->ut,
- (size_t)data->unibi_ext.reset_cursor_style, se);
- xfree(se);
- }
- }
-
// 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.
@@ -1816,12 +1823,7 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
|| (konsolev >= 180770) // #9364
|| tmux // per tmux manual page
// https://lists.gnu.org/archive/html/screen-devel/2013-03/msg00000.html
- || (true_screen
- && (!screen_host_linuxvt
- || (screen_host_linuxvt
- && (xterm_version || (vte_version > 0) || colorterm))))
- // Since GNU Screen does not support DECSCUSR, DECSCUSR is wrapped
- // in DCS and output to the host terminal.
+ || screen
|| st // #7641
|| rxvt // per command.C
// per analysis of VT100Terminal.m
@@ -1834,72 +1836,58 @@ static void patch_terminfo_bugs(TUIData *data, const char *term,
|| (linuxvt
&& (xterm_version || (vte_version > 0) || colorterm)))) {
data->unibi_ext.set_cursor_style =
- (int)unibi_add_ext_str(ut, "Ss",
- SCREEN_WRAP(true_screen, "\x1b[%p1%d q"));
+ (int)unibi_add_ext_str(ut, "Ss", "\x1b[%p1%d q");
if (-1 == data->unibi_ext.reset_cursor_style) {
data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se",
"");
}
unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style,
- SCREEN_WRAP(true_screen, "\x1b[ q"));
- } else if (linuxvt || screen_host_linuxvt) {
+ "\x1b[ q");
+ } else if (linuxvt) {
// Linux uses an idiosyncratic escape code to set the cursor shape and
// does not support DECSCUSR.
// See http://linuxgazette.net/137/anonymous.html for more info
- //
- // Since gnu Screen does not have Ss/Se, if the host terminal is a linux
- // console that does not support xterm extensions, it will wraps the
- // linux-specific sequence in DCS and outputs it.
- data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(
- ut, "Ss",
- SCREEN_WRAP(true_screen,
- "\x1b[?"
- "%?"
- // The parameter passed to Ss is the DECSCUSR parameter,
- // so the
- // terminal capability has to translate into the Linux
- // idiosyncratic parameter.
- //
- // linuxvt only supports block and underline. It is also
- // only possible to have a steady block (no steady
- // underline)
- "%p1%{2}%<" "%t%{8}" // blink block
- "%e%p1%{2}%=" "%t%{112}" // steady block
- "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half
- // block)
- "%e%p1%{4}%=" "%t%{4}" // steady underline
- "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline)
- "%e%p1%{6}%=" "%t%{2}" // steady bar
- "%e%{0}" // anything else
- "%;" "%dc"));
+ data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss",
+ "\x1b[?"
+ "%?"
+ // The parameter passed to Ss is the DECSCUSR parameter, so the
+ // terminal capability has to translate into the Linux idiosyncratic
+ // parameter.
+ //
+ // linuxvt only supports block and underline. It is also only
+ // possible to have a steady block (no steady underline)
+ "%p1%{2}%<" "%t%{8}" // blink block
+ "%e%p1%{2}%=" "%t%{112}" // steady block
+ "%e%p1%{3}%=" "%t%{4}" // blink underline (set to half block)
+ "%e%p1%{4}%=" "%t%{4}" // steady underline
+ "%e%p1%{5}%=" "%t%{2}" // blink bar (set to underline)
+ "%e%p1%{6}%=" "%t%{2}" // steady bar
+ "%e%{0}" // anything else
+ "%;" "%dc");
if (-1 == data->unibi_ext.reset_cursor_style) {
data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se",
"");
}
unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style,
- SCREEN_WRAP(true_screen, "\x1b[?c"));
+ "\x1b[?c");
} else if (konsolev > 0 && konsolev < 180770) {
// Konsole before version 18.07.70: set up a nonce profile. This has
// side-effects on temporary font resizing. #6798
- data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(
- ut, "Ss",
- SCREEN_TMUX_WRAP(true_screen, tmux,
- "\x1b]50;CursorShape=%?"
- "%p1%{3}%<" "%t%{0}" // block
- "%e%p1%{5}%<" "%t%{2}" // underline
- "%e%{1}" // everything else is bar
- "%;%d;BlinkingCursorEnabled=%?"
- "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude
- // zero as special,
- "%e%p1%{1}%&" // in all other c2ses we can treat bit
- // #0 as a flag.
- "%;%d\x07"));
+ data->unibi_ext.set_cursor_style = (int)unibi_add_ext_str(ut, "Ss",
+ TMUX_WRAP(tmux, "\x1b]50;CursorShape=%?"
+ "%p1%{3}%<" "%t%{0}" // block
+ "%e%p1%{5}%<" "%t%{2}" // underline
+ "%e%{1}" // everything else is bar
+ "%;%d;BlinkingCursorEnabled=%?"
+ "%p1%{1}%<" "%t%{1}" // Fortunately if we exclude zero as special,
+ "%e%p1%{1}%&" // in all other cases we can treat bit #0 as a flag.
+ "%;%d\x07"));
if (-1 == data->unibi_ext.reset_cursor_style) {
data->unibi_ext.reset_cursor_style = (int)unibi_add_ext_str(ut, "Se",
"");
}
unibi_set_ext_str(ut, (size_t)data->unibi_ext.reset_cursor_style,
- SCREEN_TMUX_WRAP(true_screen, tmux, "\x1b]50;\x07"));
+ "\x1b]50;\x07");
}
}
}
@@ -1931,10 +1919,6 @@ static void augment_terminfo(TUIData *data, const char *term,
const char *xterm_version = os_getenv("XTERM_VERSION");
bool true_xterm = xterm && !!xterm_version && !bsdvt;
- bool true_screen = screen && !os_getenv("TMUX");
- bool screen_host_rxvt =
- terminfo_is_term_family(true_screen
- && term[6] == '.' ? term + 7 : NULL, "rxvt");
// Only define this capability for terminal types that we know understand it.
if (dtterm // originated this extension
@@ -2001,7 +1985,7 @@ static void augment_terminfo(TUIData *data, const char *term,
// all panes, which is not particularly desirable. A better approach
// would use a tmux control sequence and an extra if(screen) test.
data->unibi_ext.set_cursor_color = (int)unibi_add_ext_str(
- ut, NULL, SCREEN_TMUX_WRAP(true_screen, tmux, "\033]Pl%p1%06x\033\\"));
+ ut, NULL, TMUX_WRAP(tmux, "\033]Pl%p1%06x\033\\"));
} else if ((xterm || rxvt || tmux || alacritty)
&& (vte_version == 0 || vte_version >= 3900)) {
// Supported in urxvt, newer VTE.
@@ -2021,27 +2005,21 @@ 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.
- /// If the DECSET is not supported by GNU Screen, it is wrapped with DCS and
- /// sent to the host terminal.
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, "ext.disable_lr_margin", "\x1b[?69l");
data->unibi_ext.enable_bracketed_paste = (int)unibi_add_ext_str(
- ut, "ext.enable_bpaste", SCREEN_WRAP(true_screen, "\x1b[?2004h"));
+ ut, "ext.enable_bpaste", "\x1b[?2004h");
data->unibi_ext.disable_bracketed_paste = (int)unibi_add_ext_str(
- ut, "ext.disable_bpaste", SCREEN_WRAP(true_screen, "\x1b[?2004l"));
+ ut, "ext.disable_bpaste", "\x1b[?2004l");
// For urxvt send BOTH xterm and old urxvt sequences. #8695
data->unibi_ext.enable_focus_reporting = (int)unibi_add_ext_str(
ut, "ext.enable_focus",
- (rxvt || screen_host_rxvt)
- ? SCREEN_WRAP(true_screen, "\x1b[?1004h\x1b]777;focus;on\x7")
- : SCREEN_WRAP(true_screen, "\x1b[?1004h"));
+ rxvt ? "\x1b[?1004h\x1b]777;focus;on\x7" : "\x1b[?1004h");
data->unibi_ext.disable_focus_reporting = (int)unibi_add_ext_str(
ut, "ext.disable_focus",
- (rxvt || screen_host_rxvt)
- ? SCREEN_WRAP(true_screen, "\x1b[?1004l\x1b]777;focus;off\x7")
- : SCREEN_WRAP(true_screen, "\x1b[?1004l"));
+ rxvt ? "\x1b[?1004l\x1b]777;focus;off\x7" : "\x1b[?1004l");
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(
@@ -2120,9 +2098,15 @@ static void flush_buf(UI *ui)
}
}
- uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle),
- bufs, (unsigned)(bufp - bufs), NULL);
- uv_run(&data->write_loop, UV_RUN_DEFAULT);
+ if (data->screenshot) {
+ for (size_t i = 0; i < (size_t)(bufp - bufs); i++) {
+ fwrite(bufs[i].base, bufs[i].len, 1, data->screenshot);
+ }
+ } else {
+ uv_write(&req, STRUCT_CAST(uv_stream_t, &data->output_handle),
+ bufs, (unsigned)(bufp - bufs), NULL);
+ uv_run(&data->write_loop, UV_RUN_DEFAULT);
+ }
data->bufpos = 0;
data->overflow = false;
}
diff --git a/src/nvim/types.h b/src/nvim/types.h
index 87560a43da..17f7e16740 100644
--- a/src/nvim/types.h
+++ b/src/nvim/types.h
@@ -16,11 +16,13 @@ typedef uint32_t u8char_T;
// Opaque handle used by API clients to refer to various objects in vim
typedef int handle_T;
-// Opaque handle to a lua value. Must be free with `executor_free_luaref` when
+// Opaque handle to a lua value. Must be free with `api_free_luaref` when
// not needed anymore! LUA_NOREF represents missing reference, i e to indicate
// absent callback etc.
typedef int LuaRef;
+typedef handle_T NS;
+
typedef struct expand expand_T;
typedef enum {
diff --git a/src/nvim/ui_bridge.c b/src/nvim/ui_bridge.c
index 9a1988739c..25f45b8fe6 100644
--- a/src/nvim/ui_bridge.c
+++ b/src/nvim/ui_bridge.c
@@ -61,6 +61,7 @@ UI *ui_bridge_attach(UI *ui, ui_main_fn ui_main, event_scheduler scheduler)
rv->bridge.suspend = ui_bridge_suspend;
rv->bridge.set_title = ui_bridge_set_title;
rv->bridge.set_icon = ui_bridge_set_icon;
+ rv->bridge.screenshot = ui_bridge_screenshot;
rv->bridge.option_set = ui_bridge_option_set;
rv->bridge.raw_line = ui_bridge_raw_line;
rv->bridge.inspect = ui_bridge_inspect;
diff --git a/src/nvim/ui_compositor.c b/src/nvim/ui_compositor.c
index bc7fee7e96..9d3ec21949 100644
--- a/src/nvim/ui_compositor.c
+++ b/src/nvim/ui_compositor.c
@@ -26,6 +26,7 @@
#include "nvim/screen.h"
#include "nvim/syntax.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/lua/executor.h"
#include "nvim/os/os.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index 97018f6c02..a8b8f7aa50 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -878,7 +878,12 @@ static u_header_T *unserialize_uhp(bufinfo_T *bi,
for (;; ) {
int len = undo_read_byte(bi);
- if (len == 0 || len == EOF) {
+ if (len == EOF) {
+ corruption_error("truncated", file_name);
+ u_free_uhp(uhp);
+ return NULL;
+ }
+ if (len == 0) {
break;
}
int what = undo_read_byte(bi);
@@ -2450,7 +2455,7 @@ static void u_undo_end(
{
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_buffer == curbuf && wp->w_p_cole > 0) {
- redraw_win_later(wp, NOT_VALID);
+ redraw_later(wp, NOT_VALID);
}
}
}
@@ -3029,8 +3034,6 @@ u_header_T *u_force_get_undo_header(buf_T *buf)
curbuf = buf;
// Args are tricky: this means replace empty range by empty range..
u_savecommon(0, 1, 1, true);
- curbuf = save_curbuf;
-
uhp = buf->b_u_curhead;
if (!uhp) {
uhp = buf->b_u_newhead;
@@ -3038,6 +3041,7 @@ u_header_T *u_force_get_undo_header(buf_T *buf)
abort();
}
}
+ curbuf = save_curbuf;
}
return uhp;
}
diff --git a/src/nvim/version.c b/src/nvim/version.c
index 190f13e74b..7296c74109 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -144,7 +144,7 @@ static const int included_patches[] = {
1777,
1776,
1775,
- // 1774,
+ 1774,
1773,
1772,
1771,
@@ -171,9 +171,9 @@ static const int included_patches[] = {
1750,
1749,
1748,
- // 1747,
+ 1747,
1746,
- // 1745,
+ 1745,
// 1744,
// 1743,
1742,
@@ -206,7 +206,7 @@ static const int included_patches[] = {
1715,
1714,
1713,
- // 1712,
+ 1712,
1711,
1710,
1709,
@@ -327,9 +327,9 @@ static const int included_patches[] = {
1594,
1593,
// 1592,
- // 1591,
+ 1591,
1590,
- // 1589,
+ 1589,
// 1588,
1587,
1586,
@@ -374,7 +374,7 @@ static const int included_patches[] = {
1547,
1546,
1545,
- // 1544,
+ 1544,
1543,
1542,
1541,
@@ -387,7 +387,7 @@ static const int included_patches[] = {
1534,
1533,
1532,
- // 1531,
+ 1531,
1530,
1529,
1528,
@@ -464,7 +464,7 @@ static const int included_patches[] = {
1457,
1456,
// 1455,
- // 1454,
+ 1454,
1453,
1452,
1451,
@@ -1970,11 +1970,21 @@ bool has_nvim_version(const char *const version_str)
///
/// @return true if patch `n` has been included.
bool has_vim_patch(int n)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
- for (int i = 0; included_patches[i] != 0; i++) {
- if (included_patches[i] == n) {
+ // Perform a binary search.
+ int l = 0;
+ int h = (int)(ARRAY_SIZE(included_patches)) - 1;
+ while (l < h) {
+ const int m = (l + h) / 2;
+ if (included_patches[m] == n) {
return true;
}
+ if (included_patches[m] < n) {
+ h = m;
+ } else {
+ l = m + 1;
+ }
}
return false;
}
@@ -2119,13 +2129,13 @@ void list_in_columns(char_u **items, int size, int current)
void list_lua_version(void)
{
- typval_T luaver_tv;
- typval_T arg = { .v_type = VAR_UNKNOWN }; // No args.
- char *luaver_expr = "((jit and jit.version) and jit.version or _VERSION)";
- executor_eval_lua(cstr_as_string(luaver_expr), &arg, &luaver_tv);
- assert(luaver_tv.v_type == VAR_STRING);
- MSG(luaver_tv.vval.v_string);
- xfree(luaver_tv.vval.v_string);
+ char *code = "return ((jit and jit.version) and jit.version or _VERSION)";
+ Error err = ERROR_INIT;
+ Object ret = nlua_exec(cstr_as_string(code), (Array)ARRAY_DICT_INIT, &err);
+ assert(!ERROR_SET(&err));
+ assert(ret.type == kObjectTypeString);
+ MSG(ret.data.string.data);
+ api_free_object(ret);
}
void list_version(void)
diff --git a/src/nvim/vim.h b/src/nvim/vim.h
index 832703e83d..900f2acd81 100644
--- a/src/nvim/vim.h
+++ b/src/nvim/vim.h
@@ -259,7 +259,6 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext()
#define PERROR(msg) (void) emsgf("%s: %s", msg, strerror(errno))
#define SHOWCMD_COLS 10 // columns needed by shown command
-#define STL_MAX_ITEM 80 // max nr of %<flag> in statusline
#include "nvim/path.h"
@@ -316,7 +315,8 @@ enum { FOLD_TEXT_LEN = 51 }; //!< buffer size for get_foldtext()
#define LOWEST_WIN_ID 1000
// BSD is supposed to cover FreeBSD and similar systems.
-#if (defined(BSD) || defined(__FreeBSD_kernel__)) && defined(S_ISCHR)
+#if (defined(BSD) || defined(__FreeBSD_kernel__)) \
+ && (defined(S_ISCHR) || defined(S_IFCHR))
# define OPEN_CHR_FILES
#endif
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index b77b80a5f3..44b6ab5f5a 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -1431,7 +1431,7 @@ static inline void east_set_error(const ParserState *const pstate,
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;
+ ret_ast_err->arg = pline.data ? pline.data + start.col : NULL;
}
/// Set error from the given token and given message
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 0fff93d984..3429e3df70 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -606,7 +606,7 @@ win_T *win_new_float(win_T *wp, FloatConfig fconfig, Error *err)
win_config_float(wp, fconfig);
wp->w_pos_changed = true;
- redraw_win_later(wp, VALID);
+ redraw_later(wp, VALID);
return wp;
}
@@ -679,7 +679,7 @@ void win_config_float(win_T *wp, FloatConfig fconfig)
wp->w_pos_changed = true;
if (change_external) {
wp->w_hl_needs_update = true;
- redraw_win_later(wp, NOT_VALID);
+ redraw_later(wp, NOT_VALID);
}
}
@@ -764,7 +764,7 @@ static void ui_ext_win_position(win_T *wp)
wp->w_grid.focusable = wp->w_float_config.focusable;
if (!valid) {
wp->w_grid.valid = false;
- redraw_win_later(wp, NOT_VALID);
+ redraw_later(wp, NOT_VALID);
}
}
} else {
@@ -1490,13 +1490,11 @@ int win_split_ins(int size, int flags, win_T *new_wp, int dir)
if (flags & (WSP_TOP | WSP_BOT))
(void)win_comp_pos();
- /*
- * Both windows need redrawing
- */
- redraw_win_later(wp, NOT_VALID);
- wp->w_redr_status = TRUE;
- redraw_win_later(oldwin, NOT_VALID);
- oldwin->w_redr_status = TRUE;
+ // Both windows need redrawing. Update all status lines, in case they
+ // show something related to the window count or position.
+ redraw_later(wp, NOT_VALID);
+ redraw_later(oldwin, NOT_VALID);
+ status_redraw_all();
if (need_status) {
msg_row = Rows - 1;
@@ -1825,8 +1823,8 @@ static void win_exchange(long Prenum)
(void)win_comp_pos(); /* recompute window positions */
win_enter(wp, true);
- redraw_later(NOT_VALID);
- redraw_win_later(wp, NOT_VALID);
+ redraw_later(curwin, NOT_VALID);
+ redraw_later(wp, NOT_VALID);
}
// rotate windows: if upwards true the second window becomes the first one
@@ -1998,8 +1996,8 @@ void win_move_after(win_T *win1, win_T *win2)
win_append(win2, win1);
frame_append(win2->w_frame, win1->w_frame);
- (void)win_comp_pos(); /* recompute w_winrow for all windows */
- redraw_later(NOT_VALID);
+ (void)win_comp_pos(); // recompute w_winrow for all windows
+ redraw_later(curwin, NOT_VALID);
}
win_enter(win1, false);
@@ -2580,9 +2578,10 @@ int win_close(win_T *win, bool free_buf)
return OK;
}
- /* Free independent synblock before the buffer is freed. */
- if (win->w_buffer != NULL)
+ // Free independent synblock before the buffer is freed.
+ if (win->w_buffer != NULL) {
reset_synblock(win);
+ }
/*
* Close the link to the buffer.
@@ -3636,7 +3635,7 @@ void curwin_init(void)
void win_init_empty(win_T *wp)
{
- redraw_win_later(wp, NOT_VALID);
+ redraw_later(wp, NOT_VALID);
wp->w_lines_valid = 0;
wp->w_cursor.lnum = 1;
wp->w_curswant = wp->w_cursor.col = 0;
@@ -4050,7 +4049,7 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au
prevwin = next_prevwin;
last_status(false); // status line may appear or disappear
- (void)win_comp_pos(); // recompute w_winrow for all windows
+ const int row = win_comp_pos(); // recompute w_winrow for all windows
diff_need_scrollbind = true;
/* The tabpage line may have appeared or disappeared, may need to resize
@@ -4061,11 +4060,20 @@ static void enter_tabpage(tabpage_T *tp, buf_T *old_curbuf, int trigger_enter_au
clear_cmdline = true;
}
p_ch = curtab->tp_ch_used;
- if (curtab->tp_old_Rows != Rows || (old_off != firstwin->w_winrow
- ))
+
+ // When cmdheight is changed in a tab page with '<C-w>-', cmdline_row is
+ // changed but p_ch and tp_ch_used are not changed. Thus we also need to
+ // check cmdline_row.
+ if ((row < cmdline_row) && (cmdline_row <= Rows - p_ch)) {
+ clear_cmdline = true;
+ }
+
+ if (curtab->tp_old_Rows != Rows || (old_off != firstwin->w_winrow)) {
shell_new_rows();
- if (curtab->tp_old_Columns != Columns && starting == 0)
- shell_new_columns(); /* update window widths */
+ }
+ if (curtab->tp_old_Columns != Columns && starting == 0) {
+ shell_new_columns(); // update window widths
+ }
lastused_tabpage = old_curtab;
@@ -4559,7 +4567,7 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid,
if (os_chdir(new_dir) == 0) {
if (!p_acd && !strequal(new_dir, cwd)) {
do_autocmd_dirchanged(new_dir, curwin->w_localdir
- ? kCdScopeWindow : kCdScopeTab);
+ ? kCdScopeWindow : kCdScopeTab, true);
}
shorten_fnames(true);
}
@@ -4568,7 +4576,7 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid,
// directory: Change to the global directory.
if (os_chdir((char *)globaldir) == 0) {
if (!p_acd && !strequal((char *)globaldir, cwd)) {
- do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal);
+ do_autocmd_dirchanged((char *)globaldir, kCdScopeGlobal, true);
}
}
XFREE_CLEAR(globaldir);
@@ -4588,10 +4596,11 @@ static void win_enter_ext(win_T *wp, bool undo_sync, int curwin_invalid,
}
maketitle();
- curwin->w_redr_status = TRUE;
- redraw_tabline = TRUE;
- if (restart_edit)
- redraw_later(VALID); /* causes status line redraw */
+ curwin->w_redr_status = true;
+ redraw_tabline = true;
+ if (restart_edit) {
+ redraw_later(curwin, VALID); // causes status line redraw
+ }
if (HL_ATTR(HLF_INACTIVE)
|| (prevwin && prevwin->w_hl_ids[HLF_INACTIVE])
@@ -4966,6 +4975,27 @@ void shell_new_columns(void)
win_reconfig_floats(); // The size of floats might change
}
+/// Check if "wp" has scrolled since last time it was checked
+/// @param wp the window to check
+bool win_did_scroll(win_T *wp)
+{
+ return (curwin->w_last_topline != curwin->w_topline
+ || curwin->w_last_leftcol != curwin->w_leftcol
+ || curwin->w_last_width != curwin->w_width
+ || curwin->w_last_height != curwin->w_height);
+}
+
+/// Trigger WinScrolled autocmd
+void do_autocmd_winscrolled(win_T *wp)
+{
+ apply_autocmds(EVENT_WINSCROLLED, NULL, NULL, false, curbuf);
+
+ wp->w_last_topline = wp->w_topline;
+ wp->w_last_leftcol = wp->w_leftcol;
+ wp->w_last_width = wp->w_width;
+ wp->w_last_height = wp->w_height;
+}
+
/*
* Save the size of all windows in "gap".
*/
@@ -5057,7 +5087,7 @@ static void frame_comp_pos(frame_T *topfrp, int *row, int *col)
/* position changed, redraw */
wp->w_winrow = *row;
wp->w_wincol = *col;
- redraw_win_later(wp, NOT_VALID);
+ redraw_later(wp, NOT_VALID);
wp->w_redr_status = true;
wp->w_pos_changed = true;
}
@@ -5110,7 +5140,7 @@ void win_setheight_win(int height, win_T *win)
if (win->w_floating) {
win->w_float_config.height = height;
win_config_float(win, win->w_float_config);
- redraw_win_later(win, NOT_VALID);
+ redraw_later(win, NOT_VALID);
} else {
frame_setheight(win->w_frame, height + win->w_status_height);
@@ -5313,7 +5343,7 @@ void win_setwidth_win(int width, win_T *wp)
if (wp->w_floating) {
wp->w_float_config.width = width;
win_config_float(wp, wp->w_float_config);
- redraw_win_later(wp, NOT_VALID);
+ redraw_later(wp, NOT_VALID);
} else {
frame_setwidth(wp->w_frame, width + wp->w_vsep_width);
@@ -5846,8 +5876,8 @@ void scroll_to_fraction(win_T *wp, int prev_height)
}
win_comp_scroll(wp);
- redraw_win_later(wp, SOME_VALID);
- wp->w_redr_status = TRUE;
+ redraw_later(wp, SOME_VALID);
+ wp->w_redr_status = true;
invalidate_botline_win(wp);
}
@@ -5886,7 +5916,7 @@ void win_set_inner_size(win_T *wp)
if (!exiting) {
scroll_to_fraction(wp, prev_height);
}
- redraw_win_later(wp, NOT_VALID); // SOME_VALID??
+ redraw_later(wp, NOT_VALID); // SOME_VALID??
}
if (width != wp->w_width_inner) {
@@ -5898,7 +5928,7 @@ void win_set_inner_size(win_T *wp)
update_topline();
curs_columns(true); // validate w_wrow
}
- redraw_win_later(wp, NOT_VALID);
+ redraw_later(wp, NOT_VALID);
}
if (wp->w_buffer->terminal) {
@@ -6019,6 +6049,12 @@ char_u *grab_file_name(long count, linenr_T *file_lnum)
char_u *ptr;
if (get_visual_text(NULL, &ptr, &len) == FAIL)
return NULL;
+ // Only recognize ":123" here
+ if (file_lnum != NULL && ptr[len] == ':' && isdigit(ptr[len + 1])) {
+ char_u *p = ptr + len + 1;
+
+ *file_lnum = getdigits_long(&p, false, 0);
+ }
return find_file_name_in_path(ptr, len, options, count, curbuf->b_ffname);
}
return file_name_at_cursor(options | FNAME_HYP, count, file_lnum);
@@ -6736,7 +6772,7 @@ int match_add(win_T *wp, const char *const grp, const char *const pat,
prev->next = m;
m->next = cur;
- redraw_win_later(wp, rtype);
+ redraw_later(wp, rtype);
return id;
fail:
@@ -6794,7 +6830,7 @@ int match_delete(win_T *wp, int id, int perr)
rtype = VALID;
}
xfree(cur);
- redraw_win_later(wp, rtype);
+ redraw_later(wp, rtype);
return 0;
}
@@ -6812,7 +6848,7 @@ void clear_matches(win_T *wp)
xfree(wp->w_match_head);
wp->w_match_head = m;
}
- redraw_win_later(wp, SOME_VALID);
+ redraw_later(wp, SOME_VALID);
}
/*
@@ -6986,7 +7022,7 @@ void win_findbuf(typval_T *argvars, list_T *list)
int bufnr = tv_get_number(&argvars[0]);
FOR_ALL_TAB_WINDOWS(tp, wp) {
- if (wp->w_buffer->b_fnum == bufnr) {
+ if (!wp->w_closing && wp->w_buffer->b_fnum == bufnr) {
tv_list_append_number(list, wp->handle);
}
}
diff --git a/src/tree_sitter/LICENSE b/src/tree_sitter/LICENSE
deleted file mode 100644
index 971b81f9a8..0000000000
--- a/src/tree_sitter/LICENSE
+++ /dev/null
@@ -1,21 +0,0 @@
-The MIT License (MIT)
-
-Copyright (c) 2018 Max Brunsfeld
-
-Permission is hereby granted, free of charge, to any person obtaining a copy
-of this software and associated documentation files (the "Software"), to deal
-in the Software without restriction, including without limitation the rights
-to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the Software is
-furnished to do so, subject to the following conditions:
-
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
diff --git a/src/tree_sitter/README.md b/src/tree_sitter/README.md
deleted file mode 100644
index 20cb35e7c3..0000000000
--- a/src/tree_sitter/README.md
+++ /dev/null
@@ -1,16 +0,0 @@
-Tree-sitter vendor runtime
-==========================
-
-This is the vendor runtime code for treesitter.
-
-The original code can be found [here](https://github.com/tree-sitter/tree-sitter).
-
-As this code is not ours, if you find any bugs, feel free to open an issue, so that we can
-investigate and determine if this should go upstream.
-
-# Updating
-
-To update the treesitter runtime, use the `update-ts-runtime.sh` script in the `scripts` directory:
-```sh
-./scripts/update-ts-runtime.sh <commit you want to update to>
-```
diff --git a/src/tree_sitter/alloc.h b/src/tree_sitter/alloc.h
deleted file mode 100644
index d3c6b5eca8..0000000000
--- a/src/tree_sitter/alloc.h
+++ /dev/null
@@ -1,95 +0,0 @@
-#ifndef TREE_SITTER_ALLOC_H_
-#define TREE_SITTER_ALLOC_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <stdlib.h>
-#include <stdbool.h>
-#include <stdio.h>
-
-#include "nvim/memory.h"
-
-#if 1
-
-static inline bool ts_toggle_allocation_recording(bool value) {
- return false;
-}
-
-#define ts_malloc xmalloc
-#define ts_calloc xcalloc
-#define ts_realloc xrealloc
-#define ts_free xfree
-
-#elif defined(TREE_SITTER_TEST)
-
-void *ts_record_malloc(size_t);
-void *ts_record_calloc(size_t, size_t);
-void *ts_record_realloc(void *, size_t);
-void ts_record_free(void *);
-bool ts_toggle_allocation_recording(bool);
-
-static inline void *ts_malloc(size_t size) {
- return ts_record_malloc(size);
-}
-
-static inline void *ts_calloc(size_t count, size_t size) {
- return ts_record_calloc(count, size);
-}
-
-static inline void *ts_realloc(void *buffer, size_t size) {
- return ts_record_realloc(buffer, size);
-}
-
-static inline void ts_free(void *buffer) {
- ts_record_free(buffer);
-}
-
-#else
-
-#include <stdlib.h>
-
-static inline bool ts_toggle_allocation_recording(bool value) {
- (void)value;
- return false;
-}
-
-static inline void *ts_malloc(size_t size) {
- void *result = malloc(size);
- if (size > 0 && !result) {
- fprintf(stderr, "tree-sitter failed to allocate %lu bytes", size);
- exit(1);
- }
- return result;
-}
-
-static inline void *ts_calloc(size_t count, size_t size) {
- void *result = calloc(count, size);
- if (count > 0 && !result) {
- fprintf(stderr, "tree-sitter failed to allocate %lu bytes", count * size);
- exit(1);
- }
- return result;
-}
-
-static inline void *ts_realloc(void *buffer, size_t size) {
- void *result = realloc(buffer, size);
- if (size > 0 && !result) {
- fprintf(stderr, "tree-sitter failed to reallocate %lu bytes", size);
- exit(1);
- }
- return result;
-}
-
-static inline void ts_free(void *buffer) {
- free(buffer);
-}
-
-#endif
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // TREE_SITTER_ALLOC_H_
diff --git a/src/tree_sitter/api.h b/src/tree_sitter/api.h
deleted file mode 100644
index 9d832e6ec4..0000000000
--- a/src/tree_sitter/api.h
+++ /dev/null
@@ -1,876 +0,0 @@
-#ifndef TREE_SITTER_API_H_
-#define TREE_SITTER_API_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <stdio.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <stdbool.h>
-
-/****************************/
-/* Section - ABI Versioning */
-/****************************/
-
-/**
- * The latest ABI version that is supported by the current version of the
- * library. When Languages are generated by the Tree-sitter CLI, they are
- * assigned an ABI version number that corresponds to the current CLI version.
- * The Tree-sitter library is generally backwards-compatible with languages
- * generated using older CLI versions, but is not forwards-compatible.
- */
-#define TREE_SITTER_LANGUAGE_VERSION 11
-
-/**
- * The earliest ABI version that is supported by the current version of the
- * library.
- */
-#define TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION 9
-
-/*******************/
-/* Section - Types */
-/*******************/
-
-typedef uint16_t TSSymbol;
-typedef uint16_t TSFieldId;
-typedef struct TSLanguage TSLanguage;
-typedef struct TSParser TSParser;
-typedef struct TSTree TSTree;
-typedef struct TSQuery TSQuery;
-typedef struct TSQueryCursor TSQueryCursor;
-
-typedef enum {
- TSInputEncodingUTF8,
- TSInputEncodingUTF16,
-} TSInputEncoding;
-
-typedef enum {
- TSSymbolTypeRegular,
- TSSymbolTypeAnonymous,
- TSSymbolTypeAuxiliary,
-} TSSymbolType;
-
-typedef struct {
- uint32_t row;
- uint32_t column;
-} TSPoint;
-
-typedef struct {
- TSPoint start_point;
- TSPoint end_point;
- uint32_t start_byte;
- uint32_t end_byte;
-} TSRange;
-
-typedef struct {
- void *payload;
- const char *(*read)(void *payload, uint32_t byte_index, TSPoint position, uint32_t *bytes_read);
- TSInputEncoding encoding;
-} TSInput;
-
-typedef enum {
- TSLogTypeParse,
- TSLogTypeLex,
-} TSLogType;
-
-typedef struct {
- void *payload;
- void (*log)(void *payload, TSLogType, const char *);
-} TSLogger;
-
-typedef struct {
- uint32_t start_byte;
- uint32_t old_end_byte;
- uint32_t new_end_byte;
- TSPoint start_point;
- TSPoint old_end_point;
- TSPoint new_end_point;
-} TSInputEdit;
-
-typedef struct {
- uint32_t context[4];
- const void *id;
- const TSTree *tree;
-} TSNode;
-
-typedef struct {
- const void *tree;
- const void *id;
- uint32_t context[2];
-} TSTreeCursor;
-
-typedef struct {
- TSNode node;
- uint32_t index;
-} TSQueryCapture;
-
-typedef struct {
- uint32_t id;
- uint16_t pattern_index;
- uint16_t capture_count;
- const TSQueryCapture *captures;
-} TSQueryMatch;
-
-typedef enum {
- TSQueryPredicateStepTypeDone,
- TSQueryPredicateStepTypeCapture,
- TSQueryPredicateStepTypeString,
-} TSQueryPredicateStepType;
-
-typedef struct {
- TSQueryPredicateStepType type;
- uint32_t value_id;
-} TSQueryPredicateStep;
-
-typedef enum {
- TSQueryErrorNone = 0,
- TSQueryErrorSyntax,
- TSQueryErrorNodeType,
- TSQueryErrorField,
- TSQueryErrorCapture,
-} TSQueryError;
-
-/********************/
-/* Section - Parser */
-/********************/
-
-/**
- * Create a new parser.
- */
-TSParser *ts_parser_new(void);
-
-/**
- * Delete the parser, freeing all of the memory that it used.
- */
-void ts_parser_delete(TSParser *parser);
-
-/**
- * Set the language that the parser should use for parsing.
- *
- * Returns a boolean indicating whether or not the language was successfully
- * assigned. True means assignment succeeded. False means there was a version
- * mismatch: the language was generated with an incompatible version of the
- * Tree-sitter CLI. Check the language's version using `ts_language_version`
- * and compare it to this library's `TREE_SITTER_LANGUAGE_VERSION` and
- * `TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION` constants.
- */
-bool ts_parser_set_language(TSParser *self, const TSLanguage *language);
-
-/**
- * Get the parser's current language.
- */
-const TSLanguage *ts_parser_language(const TSParser *self);
-
-/**
- * Set the ranges of text that the parser should include when parsing.
- *
- * By default, the parser will always include entire documents. This function
- * allows you to parse only a *portion* of a document but still return a syntax
- * tree whose ranges match up with the document as a whole. You can also pass
- * multiple disjoint ranges.
- *
- * The second and third parameters specify the location and length of an array
- * of ranges. The parser does *not* take ownership of these ranges; it copies
- * the data, so it doesn't matter how these ranges are allocated.
- *
- * If `length` is zero, then the entire document will be parsed. Otherwise,
- * the given ranges must be ordered from earliest to latest in the document,
- * and they must not overlap. That is, the following must hold for all
- * `i` < `length - 1`:
- *
- * ranges[i].end_byte <= ranges[i + 1].start_byte
- *
- * If this requirement is not satisfied, the operation will fail, the ranges
- * will not be assigned, and this function will return `false`. On success,
- * this function returns `true`
- */
-bool ts_parser_set_included_ranges(
- TSParser *self,
- const TSRange *ranges,
- uint32_t length
-);
-
-/**
- * Get the ranges of text that the parser will include when parsing.
- *
- * The returned pointer is owned by the parser. The caller should not free it
- * or write to it. The length of the array will be written to the given
- * `length` pointer.
- */
-const TSRange *ts_parser_included_ranges(
- const TSParser *self,
- uint32_t *length
-);
-
-/**
- * Use the parser to parse some source code and create a syntax tree.
- *
- * If you are parsing this document for the first time, pass `NULL` for the
- * `old_tree` parameter. Otherwise, if you have already parsed an earlier
- * version of this document and the document has since been edited, pass the
- * previous syntax tree so that the unchanged parts of it can be reused.
- * This will save time and memory. For this to work correctly, you must have
- * already edited the old syntax tree using the `ts_tree_edit` function in a
- * way that exactly matches the source code changes.
- *
- * The `TSInput` parameter lets you specify how to read the text. It has the
- * following three fields:
- * 1. `read`: A function to retrieve a chunk of text at a given byte offset
- * and (row, column) position. The function should return a pointer to the
- * text and write its length to the the `bytes_read` pointer. The parser
- * does not take ownership of this buffer; it just borrows it until it has
- * finished reading it. The function should write a zero value to the
- * `bytes_read` pointer to indicate the end of the document.
- * 2. `payload`: An arbitrary pointer that will be passed to each invocation
- * of the `read` function.
- * 3. `encoding`: An indication of how the text is encoded. Either
- * `TSInputEncodingUTF8` or `TSInputEncodingUTF16`.
- *
- * This function returns a syntax tree on success, and `NULL` on failure. There
- * are three possible reasons for failure:
- * 1. The parser does not have a language assigned. Check for this using the
- `ts_parser_language` function.
- * 2. Parsing was cancelled due to a timeout that was set by an earlier call to
- * the `ts_parser_set_timeout_micros` function. You can resume parsing from
- * where the parser left out by calling `ts_parser_parse` again with the
- * same arguments. Or you can start parsing from scratch by first calling
- * `ts_parser_reset`.
- * 3. Parsing was cancelled using a cancellation flag that was set by an
- * earlier call to `ts_parser_set_cancellation_flag`. You can resume parsing
- * from where the parser left out by calling `ts_parser_parse` again with
- * the same arguments.
- */
-TSTree *ts_parser_parse(
- TSParser *self,
- const TSTree *old_tree,
- TSInput input
-);
-
-/**
- * Use the parser to parse some source code stored in one contiguous buffer.
- * The first two parameters are the same as in the `ts_parser_parse` function
- * above. The second two parameters indicate the location of the buffer and its
- * length in bytes.
- */
-TSTree *ts_parser_parse_string(
- TSParser *self,
- const TSTree *old_tree,
- const char *string,
- uint32_t length
-);
-
-/**
- * Use the parser to parse some source code stored in one contiguous buffer with
- * a given encoding. The first four parameters work the same as in the
- * `ts_parser_parse_string` method above. The final parameter indicates whether
- * the text is encoded as UTF8 or UTF16.
- */
-TSTree *ts_parser_parse_string_encoding(
- TSParser *self,
- const TSTree *old_tree,
- const char *string,
- uint32_t length,
- TSInputEncoding encoding
-);
-
-/**
- * Instruct the parser to start the next parse from the beginning.
- *
- * If the parser previously failed because of a timeout or a cancellation, then
- * by default, it will resume where it left off on the next call to
- * `ts_parser_parse` or other parsing functions. If you don't want to resume,
- * and instead intend to use this parser to parse some other document, you must
- * call `ts_parser_reset` first.
- */
-void ts_parser_reset(TSParser *self);
-
-/**
- * Set the maximum duration in microseconds that parsing should be allowed to
- * take before halting.
- *
- * If parsing takes longer than this, it will halt early, returning NULL.
- * See `ts_parser_parse` for more information.
- */
-void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout);
-
-/**
- * Get the duration in microseconds that parsing is allowed to take.
- */
-uint64_t ts_parser_timeout_micros(const TSParser *self);
-
-/**
- * Set the parser's current cancellation flag pointer.
- *
- * If a non-null pointer is assigned, then the parser will periodically read
- * from this pointer during parsing. If it reads a non-zero value, it will
- * halt early, returning NULL. See `ts_parser_parse` for more information.
- */
-void ts_parser_set_cancellation_flag(TSParser *self, const size_t *flag);
-
-/**
- * Get the parser's current cancellation flag pointer.
- */
-const size_t *ts_parser_cancellation_flag(const TSParser *self);
-
-/**
- * Set the logger that a parser should use during parsing.
- *
- * The parser does not take ownership over the logger payload. If a logger was
- * previously assigned, the caller is responsible for releasing any memory
- * owned by the previous logger.
- */
-void ts_parser_set_logger(TSParser *self, TSLogger logger);
-
-/**
- * Get the parser's current logger.
- */
-TSLogger ts_parser_logger(const TSParser *self);
-
-/**
- * Set the file descriptor to which the parser should write debugging graphs
- * during parsing. The graphs are formatted in the DOT language. You may want
- * to pipe these graphs directly to a `dot(1)` process in order to generate
- * SVG output. You can turn off this logging by passing a negative number.
- */
-void ts_parser_print_dot_graphs(TSParser *self, int file);
-
-/******************/
-/* Section - Tree */
-/******************/
-
-/**
- * Create a shallow copy of the syntax tree. This is very fast.
- *
- * You need to copy a syntax tree in order to use it on more than one thread at
- * a time, as syntax trees are not thread safe.
- */
-TSTree *ts_tree_copy(const TSTree *self);
-
-/**
- * Delete the syntax tree, freeing all of the memory that it used.
- */
-void ts_tree_delete(TSTree *self);
-
-/**
- * Get the root node of the syntax tree.
- */
-TSNode ts_tree_root_node(const TSTree *self);
-
-/**
- * Get the language that was used to parse the syntax tree.
- */
-const TSLanguage *ts_tree_language(const TSTree *);
-
-/**
- * Edit the syntax tree to keep it in sync with source code that has been
- * edited.
- *
- * You must describe the edit both in terms of byte offsets and in terms of
- * (row, column) coordinates.
- */
-void ts_tree_edit(TSTree *self, const TSInputEdit *edit);
-
-/**
- * Compare an old edited syntax tree to a new syntax tree representing the same
- * document, returning an array of ranges whose syntactic structure has changed.
- *
- * For this to work correctly, the old syntax tree must have been edited such
- * that its ranges match up to the new tree. Generally, you'll want to call
- * this function right after calling one of the `ts_parser_parse` functions.
- * You need to pass the old tree that was passed to parse, as well as the new
- * tree that was returned from that function.
- *
- * The returned array is allocated using `malloc` and the caller is responsible
- * for freeing it using `free`. The length of the array will be written to the
- * given `length` pointer.
- */
-TSRange *ts_tree_get_changed_ranges(
- const TSTree *old_tree,
- const TSTree *new_tree,
- uint32_t *length
-);
-
-/**
- * Write a DOT graph describing the syntax tree to the given file.
- */
-void ts_tree_print_dot_graph(const TSTree *, FILE *);
-
-/******************/
-/* Section - Node */
-/******************/
-
-/**
- * Get the node's type as a null-terminated string.
- */
-const char *ts_node_type(TSNode);
-
-/**
- * Get the node's type as a numerical id.
- */
-TSSymbol ts_node_symbol(TSNode);
-
-/**
- * Get the node's start byte.
- */
-uint32_t ts_node_start_byte(TSNode);
-
-/**
- * Get the node's start position in terms of rows and columns.
- */
-TSPoint ts_node_start_point(TSNode);
-
-/**
- * Get the node's end byte.
- */
-uint32_t ts_node_end_byte(TSNode);
-
-/**
- * Get the node's end position in terms of rows and columns.
- */
-TSPoint ts_node_end_point(TSNode);
-
-/**
- * Get an S-expression representing the node as a string.
- *
- * This string is allocated with `malloc` and the caller is responsible for
- * freeing it using `free`.
- */
-char *ts_node_string(TSNode);
-
-/**
- * Check if the node is null. Functions like `ts_node_child` and
- * `ts_node_next_sibling` will return a null node to indicate that no such node
- * was found.
- */
-bool ts_node_is_null(TSNode);
-
-/**
- * Check if the node is *named*. Named nodes correspond to named rules in the
- * grammar, whereas *anonymous* nodes correspond to string literals in the
- * grammar.
- */
-bool ts_node_is_named(TSNode);
-
-/**
- * Check if the node is *missing*. Missing nodes are inserted by the parser in
- * order to recover from certain kinds of syntax errors.
- */
-bool ts_node_is_missing(TSNode);
-
-/**
- * Check if the node is *extra*. Extra nodes represent things like comments,
- * which are not required the grammar, but can appear anywhere.
- */
-bool ts_node_is_extra(TSNode);
-
-/**
- * Check if a syntax node has been edited.
- */
-bool ts_node_has_changes(TSNode);
-
-/**
- * Check if the node is a syntax error or contains any syntax errors.
- */
-bool ts_node_has_error(TSNode);
-
-/**
- * Get the node's immediate parent.
- */
-TSNode ts_node_parent(TSNode);
-
-/**
- * Get the node's child at the given index, where zero represents the first
- * child.
- */
-TSNode ts_node_child(TSNode, uint32_t);
-
-/**
- * Get the node's number of children.
- */
-uint32_t ts_node_child_count(TSNode);
-
-/**
- * Get the node's *named* child at the given index.
- *
- * See also `ts_node_is_named`.
- */
-TSNode ts_node_named_child(TSNode, uint32_t);
-
-/**
- * Get the node's number of *named* children.
- *
- * See also `ts_node_is_named`.
- */
-uint32_t ts_node_named_child_count(TSNode);
-
-/**
- * Get the node's child with the given field name.
- */
-TSNode ts_node_child_by_field_name(
- TSNode self,
- const char *field_name,
- uint32_t field_name_length
-);
-
-/**
- * Get the node's child with the given numerical field id.
- *
- * You can convert a field name to an id using the
- * `ts_language_field_id_for_name` function.
- */
-TSNode ts_node_child_by_field_id(TSNode, TSFieldId);
-
-/**
- * Get the node's next / previous sibling.
- */
-TSNode ts_node_next_sibling(TSNode);
-TSNode ts_node_prev_sibling(TSNode);
-
-/**
- * Get the node's next / previous *named* sibling.
- */
-TSNode ts_node_next_named_sibling(TSNode);
-TSNode ts_node_prev_named_sibling(TSNode);
-
-/**
- * Get the node's first child that extends beyond the given byte offset.
- */
-TSNode ts_node_first_child_for_byte(TSNode, uint32_t);
-
-/**
- * Get the node's first named child that extends beyond the given byte offset.
- */
-TSNode ts_node_first_named_child_for_byte(TSNode, uint32_t);
-
-/**
- * Get the smallest node within this node that spans the given range of bytes
- * or (row, column) positions.
- */
-TSNode ts_node_descendant_for_byte_range(TSNode, uint32_t, uint32_t);
-TSNode ts_node_descendant_for_point_range(TSNode, TSPoint, TSPoint);
-
-/**
- * Get the smallest named node within this node that spans the given range of
- * bytes or (row, column) positions.
- */
-TSNode ts_node_named_descendant_for_byte_range(TSNode, uint32_t, uint32_t);
-TSNode ts_node_named_descendant_for_point_range(TSNode, TSPoint, TSPoint);
-
-/**
- * Edit the node to keep it in-sync with source code that has been edited.
- *
- * This function is only rarely needed. When you edit a syntax tree with the
- * `ts_tree_edit` function, all of the nodes that you retrieve from the tree
- * afterward will already reflect the edit. You only need to use `ts_node_edit`
- * when you have a `TSNode` instance that you want to keep and continue to use
- * after an edit.
- */
-void ts_node_edit(TSNode *, const TSInputEdit *);
-
-/**
- * Check if two nodes are identical.
- */
-bool ts_node_eq(TSNode, TSNode);
-
-/************************/
-/* Section - TreeCursor */
-/************************/
-
-/**
- * Create a new tree cursor starting from the given node.
- *
- * A tree cursor allows you to walk a syntax tree more efficiently than is
- * possible using the `TSNode` functions. It is a mutable object that is always
- * on a certain syntax node, and can be moved imperatively to different nodes.
- */
-TSTreeCursor ts_tree_cursor_new(TSNode);
-
-/**
- * Delete a tree cursor, freeing all of the memory that it used.
- */
-void ts_tree_cursor_delete(TSTreeCursor *);
-
-/**
- * Re-initialize a tree cursor to start at a different node.
- */
-void ts_tree_cursor_reset(TSTreeCursor *, TSNode);
-
-/**
- * Get the tree cursor's current node.
- */
-TSNode ts_tree_cursor_current_node(const TSTreeCursor *);
-
-/**
- * Get the field name of the tree cursor's current node.
- *
- * This returns `NULL` if the current node doesn't have a field.
- * See also `ts_node_child_by_field_name`.
- */
-const char *ts_tree_cursor_current_field_name(const TSTreeCursor *);
-
-/**
- * Get the field name of the tree cursor's current node.
- *
- * This returns zero if the current node doesn't have a field.
- * See also `ts_node_child_by_field_id`, `ts_language_field_id_for_name`.
- */
-TSFieldId ts_tree_cursor_current_field_id(const TSTreeCursor *);
-
-/**
- * Move the cursor to the parent of its current node.
- *
- * This returns `true` if the cursor successfully moved, and returns `false`
- * if there was no parent node (the cursor was already on the root node).
- */
-bool ts_tree_cursor_goto_parent(TSTreeCursor *);
-
-/**
- * Move the cursor to the next sibling of its current node.
- *
- * This returns `true` if the cursor successfully moved, and returns `false`
- * if there was no next sibling node.
- */
-bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *);
-
-/**
- * Move the cursor to the first child of its current node.
- *
- * This returns `true` if the cursor successfully moved, and returns `false`
- * if there were no children.
- */
-bool ts_tree_cursor_goto_first_child(TSTreeCursor *);
-
-/**
- * Move the cursor to the first child of its current node that extends beyond
- * the given byte offset.
- *
- * This returns the index of the child node if one was found, and returns -1
- * if no such child was found.
- */
-int64_t ts_tree_cursor_goto_first_child_for_byte(TSTreeCursor *, uint32_t);
-
-TSTreeCursor ts_tree_cursor_copy(const TSTreeCursor *);
-
-/*******************/
-/* Section - Query */
-/*******************/
-
-/**
- * Create a new query from a string containing one or more S-expression
- * patterns. The query is associated with a particular language, and can
- * only be run on syntax nodes parsed with that language.
- *
- * If all of the given patterns are valid, this returns a `TSQuery`.
- * If a pattern is invalid, this returns `NULL`, and provides two pieces
- * of information about the problem:
- * 1. The byte offset of the error is written to the `error_offset` parameter.
- * 2. The type of error is written to the `error_type` parameter.
- */
-TSQuery *ts_query_new(
- const TSLanguage *language,
- const char *source,
- uint32_t source_len,
- uint32_t *error_offset,
- TSQueryError *error_type
-);
-
-/**
- * Delete a query, freeing all of the memory that it used.
- */
-void ts_query_delete(TSQuery *);
-
-/**
- * Get the number of patterns, captures, or string literals in the query.
- */
-uint32_t ts_query_pattern_count(const TSQuery *);
-uint32_t ts_query_capture_count(const TSQuery *);
-uint32_t ts_query_string_count(const TSQuery *);
-
-/**
- * Get the byte offset where the given pattern starts in the query's source.
- *
- * This can be useful when combining queries by concatenating their source
- * code strings.
- */
-uint32_t ts_query_start_byte_for_pattern(const TSQuery *, uint32_t);
-
-/**
- * Get all of the predicates for the given pattern in the query.
- *
- * The predicates are represented as a single array of steps. There are three
- * types of steps in this array, which correspond to the three legal values for
- * the `type` field:
- * - `TSQueryPredicateStepTypeCapture` - Steps with this type represent names
- * of captures. Their `value_id` can be used with the
- * `ts_query_capture_name_for_id` function to obtain the name of the capture.
- * - `TSQueryPredicateStepTypeString` - Steps with this type represent literal
- * strings. Their `value_id` can be used with the
- * `ts_query_string_value_for_id` function to obtain their string value.
- * - `TSQueryPredicateStepTypeDone` - Steps with this type are *sentinels*
- * that represent the end of an individual predicate. If a pattern has two
- * predicates, then there will be two steps with this `type` in the array.
- */
-const TSQueryPredicateStep *ts_query_predicates_for_pattern(
- const TSQuery *self,
- uint32_t pattern_index,
- uint32_t *length
-);
-
-/**
- * Get the name and length of one of the query's captures, or one of the
- * query's string literals. Each capture and string is associated with a
- * numeric id based on the order that it appeared in the query's source.
- */
-const char *ts_query_capture_name_for_id(
- const TSQuery *,
- uint32_t id,
- uint32_t *length
-);
-const char *ts_query_string_value_for_id(
- const TSQuery *,
- uint32_t id,
- uint32_t *length
-);
-
-/**
- * Disable a certain capture within a query.
- *
- * This prevents the capture from being returned in matches, and also avoids
- * any resource usage associated with recording the capture. Currently, there
- * is no way to undo this.
- */
-void ts_query_disable_capture(TSQuery *, const char *, uint32_t);
-
-/**
- * Disable a certain pattern within a query.
- *
- * This prevents the pattern from matching and removes most of the overhead
- * associated with the pattern. Currently, there is no way to undo this.
- */
-void ts_query_disable_pattern(TSQuery *, uint32_t);
-
-/**
- * Create a new cursor for executing a given query.
- *
- * The cursor stores the state that is needed to iteratively search
- * for matches. To use the query cursor, first call `ts_query_cursor_exec`
- * to start running a given query on a given syntax node. Then, there are
- * two options for consuming the results of the query:
- * 1. Repeatedly call `ts_query_cursor_next_match` to iterate over all of the
- * the *matches* in the order that they were found. Each match contains the
- * index of the pattern that matched, and an array of captures. Because
- * multiple patterns can match the same set of nodes, one match may contain
- * captures that appear *before* some of the captures from a previous match.
- * 2. Repeatedly call `ts_query_cursor_next_capture` to iterate over all of the
- * individual *captures* in the order that they appear. This is useful if
- * don't care about which pattern matched, and just want a single ordered
- * sequence of captures.
- *
- * If you don't care about consuming all of the results, you can stop calling
- * `ts_query_cursor_next_match` or `ts_query_cursor_next_capture` at any point.
- * You can then start executing another query on another node by calling
- * `ts_query_cursor_exec` again.
- */
-TSQueryCursor *ts_query_cursor_new(void);
-
-/**
- * Delete a query cursor, freeing all of the memory that it used.
- */
-void ts_query_cursor_delete(TSQueryCursor *);
-
-/**
- * Start running a given query on a given node.
- */
-void ts_query_cursor_exec(TSQueryCursor *, const TSQuery *, TSNode);
-
-/**
- * Set the range of bytes or (row, column) positions in which the query
- * will be executed.
- */
-void ts_query_cursor_set_byte_range(TSQueryCursor *, uint32_t, uint32_t);
-void ts_query_cursor_set_point_range(TSQueryCursor *, TSPoint, TSPoint);
-
-/**
- * Advance to the next match of the currently running query.
- *
- * If there is a match, write it to `*match` and return `true`.
- * Otherwise, return `false`.
- */
-bool ts_query_cursor_next_match(TSQueryCursor *, TSQueryMatch *match);
-void ts_query_cursor_remove_match(TSQueryCursor *, uint32_t id);
-
-/**
- * Advance to the next capture of the currently running query.
- *
- * If there is a capture, write its match to `*match` and its index within
- * the matche's capture list to `*capture_index`. Otherwise, return `false`.
- */
-bool ts_query_cursor_next_capture(
- TSQueryCursor *,
- TSQueryMatch *match,
- uint32_t *capture_index
-);
-
-/**********************/
-/* Section - Language */
-/**********************/
-
-/**
- * Get the number of distinct node types in the language.
- */
-uint32_t ts_language_symbol_count(const TSLanguage *);
-
-/**
- * Get a node type string for the given numerical id.
- */
-const char *ts_language_symbol_name(const TSLanguage *, TSSymbol);
-
-/**
- * Get the numerical id for the given node type string.
- */
-TSSymbol ts_language_symbol_for_name(
- const TSLanguage *self,
- const char *string,
- uint32_t length,
- bool is_named
-);
-
-/**
- * Get the number of distinct field names in the language.
- */
-uint32_t ts_language_field_count(const TSLanguage *);
-
-/**
- * Get the field name string for the given numerical id.
- */
-const char *ts_language_field_name_for_id(const TSLanguage *, TSFieldId);
-
-/**
- * Get the numerical id for the given field name string.
- */
-TSFieldId ts_language_field_id_for_name(const TSLanguage *, const char *, uint32_t);
-
-/**
- * Check whether the given node type id belongs to named nodes, anonymous nodes,
- * or a hidden nodes.
- *
- * See also `ts_node_is_named`. Hidden nodes are never returned from the API.
- */
-TSSymbolType ts_language_symbol_type(const TSLanguage *, TSSymbol);
-
-/**
- * Get the ABI version number for this language. This version number is used
- * to ensure that languages were generated by a compatible version of
- * Tree-sitter.
- *
- * See also `ts_parser_set_language`.
- */
-uint32_t ts_language_version(const TSLanguage *);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // TREE_SITTER_API_H_
diff --git a/src/tree_sitter/array.h b/src/tree_sitter/array.h
deleted file mode 100644
index 26cb8448f1..0000000000
--- a/src/tree_sitter/array.h
+++ /dev/null
@@ -1,158 +0,0 @@
-#ifndef TREE_SITTER_ARRAY_H_
-#define TREE_SITTER_ARRAY_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <string.h>
-#include <stdlib.h>
-#include <stdint.h>
-#include <assert.h>
-#include <stdbool.h>
-#include "./alloc.h"
-
-#define Array(T) \
- struct { \
- T *contents; \
- uint32_t size; \
- uint32_t capacity; \
- }
-
-#define array_init(self) \
- ((self)->size = 0, (self)->capacity = 0, (self)->contents = NULL)
-
-#define array_new() \
- { NULL, 0, 0 }
-
-#define array_get(self, index) \
- (assert((uint32_t)index < (self)->size), &(self)->contents[index])
-
-#define array_front(self) array_get(self, 0)
-
-#define array_back(self) array_get(self, (self)->size - 1)
-
-#define array_clear(self) ((self)->size = 0)
-
-#define array_reserve(self, new_capacity) \
- array__reserve((VoidArray *)(self), array__elem_size(self), new_capacity)
-
-#define array_erase(self, index) \
- array__erase((VoidArray *)(self), array__elem_size(self), index)
-
-#define array_delete(self) array__delete((VoidArray *)self)
-
-#define array_push(self, element) \
- (array__grow((VoidArray *)(self), 1, array__elem_size(self)), \
- (self)->contents[(self)->size++] = (element))
-
-#define array_grow_by(self, count) \
- (array__grow((VoidArray *)(self), count, array__elem_size(self)), \
- memset((self)->contents + (self)->size, 0, (count) * array__elem_size(self)), \
- (self)->size += (count))
-
-#define array_push_all(self, other) \
- array_splice((self), (self)->size, 0, (other)->size, (other)->contents)
-
-#define array_splice(self, index, old_count, new_count, new_contents) \
- array__splice((VoidArray *)(self), array__elem_size(self), index, old_count, \
- new_count, new_contents)
-
-#define array_insert(self, index, element) \
- array__splice((VoidArray *)(self), array__elem_size(self), index, 0, 1, &element)
-
-#define array_pop(self) ((self)->contents[--(self)->size])
-
-#define array_assign(self, other) \
- array__assign((VoidArray *)(self), (const VoidArray *)(other), array__elem_size(self))
-
-// Private
-
-typedef Array(void) VoidArray;
-
-#define array__elem_size(self) sizeof(*(self)->contents)
-
-static inline void array__delete(VoidArray *self) {
- ts_free(self->contents);
- self->contents = NULL;
- self->size = 0;
- self->capacity = 0;
-}
-
-static inline void array__erase(VoidArray *self, size_t element_size,
- uint32_t index) {
- assert(index < self->size);
- char *contents = (char *)self->contents;
- memmove(contents + index * element_size, contents + (index + 1) * element_size,
- (self->size - index - 1) * element_size);
- self->size--;
-}
-
-static inline void array__reserve(VoidArray *self, size_t element_size, uint32_t new_capacity) {
- if (new_capacity > self->capacity) {
- if (self->contents) {
- self->contents = ts_realloc(self->contents, new_capacity * element_size);
- } else {
- self->contents = ts_calloc(new_capacity, element_size);
- }
- self->capacity = new_capacity;
- }
-}
-
-static inline void array__assign(VoidArray *self, const VoidArray *other, size_t element_size) {
- array__reserve(self, element_size, other->size);
- self->size = other->size;
- memcpy(self->contents, other->contents, self->size * element_size);
-}
-
-static inline void array__grow(VoidArray *self, size_t count, size_t element_size) {
- size_t new_size = self->size + count;
- if (new_size > self->capacity) {
- size_t new_capacity = self->capacity * 2;
- if (new_capacity < 8) new_capacity = 8;
- if (new_capacity < new_size) new_capacity = new_size;
- array__reserve(self, element_size, new_capacity);
- }
-}
-
-static inline void array__splice(VoidArray *self, size_t element_size,
- uint32_t index, uint32_t old_count,
- uint32_t new_count, const void *elements) {
- uint32_t new_size = self->size + new_count - old_count;
- uint32_t old_end = index + old_count;
- uint32_t new_end = index + new_count;
- assert(old_end <= self->size);
-
- array__reserve(self, element_size, new_size);
-
- char *contents = (char *)self->contents;
- if (self->size > old_end) {
- memmove(
- contents + new_end * element_size,
- contents + old_end * element_size,
- (self->size - old_end) * element_size
- );
- }
- if (new_count > 0) {
- if (elements) {
- memcpy(
- (contents + index * element_size),
- elements,
- new_count * element_size
- );
- } else {
- memset(
- (contents + index * element_size),
- 0,
- new_count * element_size
- );
- }
- }
- self->size += new_count - old_count;
-}
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // TREE_SITTER_ARRAY_H_
diff --git a/src/tree_sitter/atomic.h b/src/tree_sitter/atomic.h
deleted file mode 100644
index 7bd0e850a9..0000000000
--- a/src/tree_sitter/atomic.h
+++ /dev/null
@@ -1,42 +0,0 @@
-#ifndef TREE_SITTER_ATOMIC_H_
-#define TREE_SITTER_ATOMIC_H_
-
-#include <stdint.h>
-
-#ifdef _WIN32
-
-#include <windows.h>
-
-static inline size_t atomic_load(const volatile size_t *p) {
- return *p;
-}
-
-static inline uint32_t atomic_inc(volatile uint32_t *p) {
- return InterlockedIncrement((long volatile *)p);
-}
-
-static inline uint32_t atomic_dec(volatile uint32_t *p) {
- return InterlockedDecrement((long volatile *)p);
-}
-
-#else
-
-static inline size_t atomic_load(const volatile size_t *p) {
-#ifdef __ATOMIC_RELAXED
- return __atomic_load_n(p, __ATOMIC_RELAXED);
-#else
- return __sync_fetch_and_add((volatile size_t *)p, 0);
-#endif
-}
-
-static inline uint32_t atomic_inc(volatile uint32_t *p) {
- return __sync_add_and_fetch(p, 1u);
-}
-
-static inline uint32_t atomic_dec(volatile uint32_t *p) {
- return __sync_sub_and_fetch(p, 1u);
-}
-
-#endif
-
-#endif // TREE_SITTER_ATOMIC_H_
diff --git a/src/tree_sitter/bits.h b/src/tree_sitter/bits.h
deleted file mode 100644
index ce7a715567..0000000000
--- a/src/tree_sitter/bits.h
+++ /dev/null
@@ -1,29 +0,0 @@
-#ifndef TREE_SITTER_BITS_H_
-#define TREE_SITTER_BITS_H_
-
-#include <stdint.h>
-
-static inline uint32_t bitmask_for_index(uint16_t id) {
- return (1u << (31 - id));
-}
-
-#if defined _WIN32 && !defined __GNUC__
-
-#include <intrin.h>
-
-static inline uint32_t count_leading_zeros(uint32_t x) {
- if (x == 0) return 32;
- uint32_t result;
- _BitScanReverse(&result, x);
- return 31 - result;
-}
-
-#else
-
-static inline uint32_t count_leading_zeros(uint32_t x) {
- if (x == 0) return 32;
- return __builtin_clz(x);
-}
-
-#endif
-#endif // TREE_SITTER_BITS_H_
diff --git a/src/tree_sitter/clock.h b/src/tree_sitter/clock.h
deleted file mode 100644
index 94545f3566..0000000000
--- a/src/tree_sitter/clock.h
+++ /dev/null
@@ -1,141 +0,0 @@
-#ifndef TREE_SITTER_CLOCK_H_
-#define TREE_SITTER_CLOCK_H_
-
-#include <stdint.h>
-
-typedef uint64_t TSDuration;
-
-#ifdef _WIN32
-
-// Windows:
-// * Represent a time as a performance counter value.
-// * Represent a duration as a number of performance counter ticks.
-
-#include <windows.h>
-typedef uint64_t TSClock;
-
-static inline TSDuration duration_from_micros(uint64_t micros) {
- LARGE_INTEGER frequency;
- QueryPerformanceFrequency(&frequency);
- return micros * (uint64_t)frequency.QuadPart / 1000000;
-}
-
-static inline uint64_t duration_to_micros(TSDuration self) {
- LARGE_INTEGER frequency;
- QueryPerformanceFrequency(&frequency);
- return self * 1000000 / (uint64_t)frequency.QuadPart;
-}
-
-static inline TSClock clock_null(void) {
- return 0;
-}
-
-static inline TSClock clock_now(void) {
- LARGE_INTEGER result;
- QueryPerformanceCounter(&result);
- return (uint64_t)result.QuadPart;
-}
-
-static inline TSClock clock_after(TSClock base, TSDuration duration) {
- return base + duration;
-}
-
-static inline bool clock_is_null(TSClock self) {
- return !self;
-}
-
-static inline bool clock_is_gt(TSClock self, TSClock other) {
- return self > other;
-}
-
-#elif defined(CLOCK_MONOTONIC) && !defined(__APPLE__)
-
-// POSIX with monotonic clock support (Linux)
-// * Represent a time as a monotonic (seconds, nanoseconds) pair.
-// * Represent a duration as a number of microseconds.
-//
-// On these platforms, parse timeouts will correspond accurately to
-// real time, regardless of what other processes are running.
-
-#include <time.h>
-typedef struct timespec TSClock;
-
-static inline TSDuration duration_from_micros(uint64_t micros) {
- return micros;
-}
-
-static inline uint64_t duration_to_micros(TSDuration self) {
- return self;
-}
-
-static inline TSClock clock_now(void) {
- TSClock result;
- clock_gettime(CLOCK_MONOTONIC, &result);
- return result;
-}
-
-static inline TSClock clock_null(void) {
- return (TSClock) {0, 0};
-}
-
-static inline TSClock clock_after(TSClock base, TSDuration duration) {
- TSClock result = base;
- result.tv_sec += duration / 1000000;
- result.tv_nsec += (duration % 1000000) * 1000;
- return result;
-}
-
-static inline bool clock_is_null(TSClock self) {
- return !self.tv_sec;
-}
-
-static inline bool clock_is_gt(TSClock self, TSClock other) {
- if (self.tv_sec > other.tv_sec) return true;
- if (self.tv_sec < other.tv_sec) return false;
- return self.tv_nsec > other.tv_nsec;
-}
-
-#else
-
-// macOS or POSIX without monotonic clock support
-// * Represent a time as a process clock value.
-// * Represent a duration as a number of process clock ticks.
-//
-// On these platforms, parse timeouts may be affected by other processes,
-// which is not ideal, but is better than using a non-monotonic time API
-// like `gettimeofday`.
-
-#include <time.h>
-typedef uint64_t TSClock;
-
-static inline TSDuration duration_from_micros(uint64_t micros) {
- return micros * (uint64_t)CLOCKS_PER_SEC / 1000000;
-}
-
-static inline uint64_t duration_to_micros(TSDuration self) {
- return self * 1000000 / (uint64_t)CLOCKS_PER_SEC;
-}
-
-static inline TSClock clock_null(void) {
- return 0;
-}
-
-static inline TSClock clock_now(void) {
- return (uint64_t)clock();
-}
-
-static inline TSClock clock_after(TSClock base, TSDuration duration) {
- return base + duration;
-}
-
-static inline bool clock_is_null(TSClock self) {
- return !self;
-}
-
-static inline bool clock_is_gt(TSClock self, TSClock other) {
- return self > other;
-}
-
-#endif
-
-#endif // TREE_SITTER_CLOCK_H_
diff --git a/src/tree_sitter/error_costs.h b/src/tree_sitter/error_costs.h
deleted file mode 100644
index 32d3666a66..0000000000
--- a/src/tree_sitter/error_costs.h
+++ /dev/null
@@ -1,11 +0,0 @@
-#ifndef TREE_SITTER_ERROR_COSTS_H_
-#define TREE_SITTER_ERROR_COSTS_H_
-
-#define ERROR_STATE 0
-#define ERROR_COST_PER_RECOVERY 500
-#define ERROR_COST_PER_MISSING_TREE 110
-#define ERROR_COST_PER_SKIPPED_TREE 100
-#define ERROR_COST_PER_SKIPPED_LINE 30
-#define ERROR_COST_PER_SKIPPED_CHAR 1
-
-#endif
diff --git a/src/tree_sitter/get_changed_ranges.c b/src/tree_sitter/get_changed_ranges.c
deleted file mode 100644
index 5bd1d814bd..0000000000
--- a/src/tree_sitter/get_changed_ranges.c
+++ /dev/null
@@ -1,482 +0,0 @@
-#include "./get_changed_ranges.h"
-#include "./subtree.h"
-#include "./language.h"
-#include "./error_costs.h"
-#include "./tree_cursor.h"
-#include <assert.h>
-
-// #define DEBUG_GET_CHANGED_RANGES
-
-static void ts_range_array_add(TSRangeArray *self, Length start, Length end) {
- if (self->size > 0) {
- TSRange *last_range = array_back(self);
- if (start.bytes <= last_range->end_byte) {
- last_range->end_byte = end.bytes;
- last_range->end_point = end.extent;
- return;
- }
- }
-
- if (start.bytes < end.bytes) {
- TSRange range = { start.extent, end.extent, start.bytes, end.bytes };
- array_push(self, range);
- }
-}
-
-bool ts_range_array_intersects(const TSRangeArray *self, unsigned start_index,
- uint32_t start_byte, uint32_t end_byte) {
- for (unsigned i = start_index; i < self->size; i++) {
- TSRange *range = &self->contents[i];
- if (range->end_byte > start_byte) {
- if (range->start_byte >= end_byte) break;
- return true;
- }
- }
- return false;
-}
-
-void ts_range_array_get_changed_ranges(
- const TSRange *old_ranges, unsigned old_range_count,
- const TSRange *new_ranges, unsigned new_range_count,
- TSRangeArray *differences
-) {
- unsigned new_index = 0;
- unsigned old_index = 0;
- Length current_position = length_zero();
- bool in_old_range = false;
- bool in_new_range = false;
-
- while (old_index < old_range_count || new_index < new_range_count) {
- const TSRange *old_range = &old_ranges[old_index];
- const TSRange *new_range = &new_ranges[new_index];
-
- Length next_old_position;
- if (in_old_range) {
- next_old_position = (Length) {old_range->end_byte, old_range->end_point};
- } else if (old_index < old_range_count) {
- next_old_position = (Length) {old_range->start_byte, old_range->start_point};
- } else {
- next_old_position = LENGTH_MAX;
- }
-
- Length next_new_position;
- if (in_new_range) {
- next_new_position = (Length) {new_range->end_byte, new_range->end_point};
- } else if (new_index < new_range_count) {
- next_new_position = (Length) {new_range->start_byte, new_range->start_point};
- } else {
- next_new_position = LENGTH_MAX;
- }
-
- if (next_old_position.bytes < next_new_position.bytes) {
- if (in_old_range != in_new_range) {
- ts_range_array_add(differences, current_position, next_old_position);
- }
- if (in_old_range) old_index++;
- current_position = next_old_position;
- in_old_range = !in_old_range;
- } else if (next_new_position.bytes < next_old_position.bytes) {
- if (in_old_range != in_new_range) {
- ts_range_array_add(differences, current_position, next_new_position);
- }
- if (in_new_range) new_index++;
- current_position = next_new_position;
- in_new_range = !in_new_range;
- } else {
- if (in_old_range != in_new_range) {
- ts_range_array_add(differences, current_position, next_new_position);
- }
- if (in_old_range) old_index++;
- if (in_new_range) new_index++;
- in_old_range = !in_old_range;
- in_new_range = !in_new_range;
- current_position = next_new_position;
- }
- }
-}
-
-typedef struct {
- TreeCursor cursor;
- const TSLanguage *language;
- unsigned visible_depth;
- bool in_padding;
-} Iterator;
-
-static Iterator iterator_new(TreeCursor *cursor, const Subtree *tree, const TSLanguage *language) {
- array_clear(&cursor->stack);
- array_push(&cursor->stack, ((TreeCursorEntry){
- .subtree = tree,
- .position = length_zero(),
- .child_index = 0,
- .structural_child_index = 0,
- }));
- return (Iterator) {
- .cursor = *cursor,
- .language = language,
- .visible_depth = 1,
- .in_padding = false,
- };
-}
-
-static bool iterator_done(Iterator *self) {
- return self->cursor.stack.size == 0;
-}
-
-static Length iterator_start_position(Iterator *self) {
- TreeCursorEntry entry = *array_back(&self->cursor.stack);
- if (self->in_padding) {
- return entry.position;
- } else {
- return length_add(entry.position, ts_subtree_padding(*entry.subtree));
- }
-}
-
-static Length iterator_end_position(Iterator *self) {
- TreeCursorEntry entry = *array_back(&self->cursor.stack);
- Length result = length_add(entry.position, ts_subtree_padding(*entry.subtree));
- if (self->in_padding) {
- return result;
- } else {
- return length_add(result, ts_subtree_size(*entry.subtree));
- }
-}
-
-static bool iterator_tree_is_visible(const Iterator *self) {
- TreeCursorEntry entry = *array_back(&self->cursor.stack);
- if (ts_subtree_visible(*entry.subtree)) return true;
- if (self->cursor.stack.size > 1) {
- Subtree parent = *self->cursor.stack.contents[self->cursor.stack.size - 2].subtree;
- const TSSymbol *alias_sequence = ts_language_alias_sequence(
- self->language,
- parent.ptr->production_id
- );
- return alias_sequence && alias_sequence[entry.structural_child_index] != 0;
- }
- return false;
-}
-
-static void iterator_get_visible_state(const Iterator *self, Subtree *tree,
- TSSymbol *alias_symbol, uint32_t *start_byte) {
- uint32_t i = self->cursor.stack.size - 1;
-
- if (self->in_padding) {
- if (i == 0) return;
- i--;
- }
-
- for (; i + 1 > 0; i--) {
- TreeCursorEntry entry = self->cursor.stack.contents[i];
-
- if (i > 0) {
- const Subtree *parent = self->cursor.stack.contents[i - 1].subtree;
- const TSSymbol *alias_sequence = ts_language_alias_sequence(
- self->language,
- parent->ptr->production_id
- );
- if (alias_sequence) {
- *alias_symbol = alias_sequence[entry.structural_child_index];
- }
- }
-
- if (ts_subtree_visible(*entry.subtree) || *alias_symbol) {
- *tree = *entry.subtree;
- *start_byte = entry.position.bytes;
- break;
- }
- }
-}
-
-static void iterator_ascend(Iterator *self) {
- if (iterator_done(self)) return;
- if (iterator_tree_is_visible(self) && !self->in_padding) self->visible_depth--;
- if (array_back(&self->cursor.stack)->child_index > 0) self->in_padding = false;
- self->cursor.stack.size--;
-}
-
-static bool iterator_descend(Iterator *self, uint32_t goal_position) {
- if (self->in_padding) return false;
-
- bool did_descend;
- do {
- did_descend = false;
- TreeCursorEntry entry = *array_back(&self->cursor.stack);
- Length position = entry.position;
- uint32_t structural_child_index = 0;
- for (uint32_t i = 0, n = ts_subtree_child_count(*entry.subtree); i < n; i++) {
- const Subtree *child = &entry.subtree->ptr->children[i];
- Length child_left = length_add(position, ts_subtree_padding(*child));
- Length child_right = length_add(child_left, ts_subtree_size(*child));
-
- if (child_right.bytes > goal_position) {
- array_push(&self->cursor.stack, ((TreeCursorEntry){
- .subtree = child,
- .position = position,
- .child_index = i,
- .structural_child_index = structural_child_index,
- }));
-
- if (iterator_tree_is_visible(self)) {
- if (child_left.bytes > goal_position) {
- self->in_padding = true;
- } else {
- self->visible_depth++;
- }
- return true;
- }
-
- did_descend = true;
- break;
- }
-
- position = child_right;
- if (!ts_subtree_extra(*child)) structural_child_index++;
- }
- } while (did_descend);
-
- return false;
-}
-
-static void iterator_advance(Iterator *self) {
- if (self->in_padding) {
- self->in_padding = false;
- if (iterator_tree_is_visible(self)) {
- self->visible_depth++;
- } else {
- iterator_descend(self, 0);
- }
- return;
- }
-
- for (;;) {
- if (iterator_tree_is_visible(self)) self->visible_depth--;
- TreeCursorEntry entry = array_pop(&self->cursor.stack);
- if (iterator_done(self)) return;
-
- const Subtree *parent = array_back(&self->cursor.stack)->subtree;
- uint32_t child_index = entry.child_index + 1;
- if (ts_subtree_child_count(*parent) > child_index) {
- Length position = length_add(entry.position, ts_subtree_total_size(*entry.subtree));
- uint32_t structural_child_index = entry.structural_child_index;
- if (!ts_subtree_extra(*entry.subtree)) structural_child_index++;
- const Subtree *next_child = &parent->ptr->children[child_index];
-
- array_push(&self->cursor.stack, ((TreeCursorEntry){
- .subtree = next_child,
- .position = position,
- .child_index = child_index,
- .structural_child_index = structural_child_index,
- }));
-
- if (iterator_tree_is_visible(self)) {
- if (ts_subtree_padding(*next_child).bytes > 0) {
- self->in_padding = true;
- } else {
- self->visible_depth++;
- }
- } else {
- iterator_descend(self, 0);
- }
- break;
- }
- }
-}
-
-typedef enum {
- IteratorDiffers,
- IteratorMayDiffer,
- IteratorMatches,
-} IteratorComparison;
-
-static IteratorComparison iterator_compare(const Iterator *old_iter, const Iterator *new_iter) {
- Subtree old_tree = NULL_SUBTREE;
- Subtree new_tree = NULL_SUBTREE;
- uint32_t old_start = 0;
- uint32_t new_start = 0;
- TSSymbol old_alias_symbol = 0;
- TSSymbol new_alias_symbol = 0;
- iterator_get_visible_state(old_iter, &old_tree, &old_alias_symbol, &old_start);
- iterator_get_visible_state(new_iter, &new_tree, &new_alias_symbol, &new_start);
-
- if (!old_tree.ptr && !new_tree.ptr) return IteratorMatches;
- if (!old_tree.ptr || !new_tree.ptr) return IteratorDiffers;
-
- if (
- old_alias_symbol == new_alias_symbol &&
- ts_subtree_symbol(old_tree) == ts_subtree_symbol(new_tree)
- ) {
- if (old_start == new_start &&
- !ts_subtree_has_changes(old_tree) &&
- ts_subtree_symbol(old_tree) != ts_builtin_sym_error &&
- ts_subtree_size(old_tree).bytes == ts_subtree_size(new_tree).bytes &&
- ts_subtree_parse_state(old_tree) != TS_TREE_STATE_NONE &&
- ts_subtree_parse_state(new_tree) != TS_TREE_STATE_NONE &&
- (ts_subtree_parse_state(old_tree) == ERROR_STATE) ==
- (ts_subtree_parse_state(new_tree) == ERROR_STATE)) {
- return IteratorMatches;
- } else {
- return IteratorMayDiffer;
- }
- }
-
- return IteratorDiffers;
-}
-
-#ifdef DEBUG_GET_CHANGED_RANGES
-static inline void iterator_print_state(Iterator *self) {
- TreeCursorEntry entry = *array_back(&self->cursor.stack);
- TSPoint start = iterator_start_position(self).extent;
- TSPoint end = iterator_end_position(self).extent;
- const char *name = ts_language_symbol_name(self->language, ts_subtree_symbol(*entry.subtree));
- printf(
- "(%-25s %s\t depth:%u [%u, %u] - [%u, %u])",
- name, self->in_padding ? "(p)" : " ",
- self->visible_depth,
- start.row + 1, start.column,
- end.row + 1, end.column
- );
-}
-#endif
-
-unsigned ts_subtree_get_changed_ranges(const Subtree *old_tree, const Subtree *new_tree,
- TreeCursor *cursor1, TreeCursor *cursor2,
- const TSLanguage *language,
- const TSRangeArray *included_range_differences,
- TSRange **ranges) {
- TSRangeArray results = array_new();
-
- Iterator old_iter = iterator_new(cursor1, old_tree, language);
- Iterator new_iter = iterator_new(cursor2, new_tree, language);
-
- unsigned included_range_difference_index = 0;
-
- Length position = iterator_start_position(&old_iter);
- Length next_position = iterator_start_position(&new_iter);
- if (position.bytes < next_position.bytes) {
- ts_range_array_add(&results, position, next_position);
- position = next_position;
- } else if (position.bytes > next_position.bytes) {
- ts_range_array_add(&results, next_position, position);
- next_position = position;
- }
-
- do {
- #ifdef DEBUG_GET_CHANGED_RANGES
- printf("At [%-2u, %-2u] Compare ", position.extent.row + 1, position.extent.column);
- iterator_print_state(&old_iter);
- printf("\tvs\t");
- iterator_print_state(&new_iter);
- puts("");
- #endif
-
- // Compare the old and new subtrees.
- IteratorComparison comparison = iterator_compare(&old_iter, &new_iter);
-
- // Even if the two subtrees appear to be identical, they could differ
- // internally if they contain a range of text that was previously
- // excluded from the parse, and is now included, or vice-versa.
- if (comparison == IteratorMatches && ts_range_array_intersects(
- included_range_differences,
- included_range_difference_index,
- position.bytes,
- iterator_end_position(&old_iter).bytes
- )) {
- comparison = IteratorMayDiffer;
- }
-
- bool is_changed = false;
- switch (comparison) {
- // If the subtrees are definitely identical, move to the end
- // of both subtrees.
- case IteratorMatches:
- next_position = iterator_end_position(&old_iter);
- break;
-
- // If the subtrees might differ internally, descend into both
- // subtrees, finding the first child that spans the current position.
- case IteratorMayDiffer:
- if (iterator_descend(&old_iter, position.bytes)) {
- if (!iterator_descend(&new_iter, position.bytes)) {
- is_changed = true;
- next_position = iterator_end_position(&old_iter);
- }
- } else if (iterator_descend(&new_iter, position.bytes)) {
- is_changed = true;
- next_position = iterator_end_position(&new_iter);
- } else {
- next_position = length_min(
- iterator_end_position(&old_iter),
- iterator_end_position(&new_iter)
- );
- }
- break;
-
- // If the subtrees are different, record a change and then move
- // to the end of both subtrees.
- case IteratorDiffers:
- is_changed = true;
- next_position = length_min(
- iterator_end_position(&old_iter),
- iterator_end_position(&new_iter)
- );
- break;
- }
-
- // Ensure that both iterators are caught up to the current position.
- while (
- !iterator_done(&old_iter) &&
- iterator_end_position(&old_iter).bytes <= next_position.bytes
- ) iterator_advance(&old_iter);
- while (
- !iterator_done(&new_iter) &&
- iterator_end_position(&new_iter).bytes <= next_position.bytes
- ) iterator_advance(&new_iter);
-
- // Ensure that both iterators are at the same depth in the tree.
- while (old_iter.visible_depth > new_iter.visible_depth) {
- iterator_ascend(&old_iter);
- }
- while (new_iter.visible_depth > old_iter.visible_depth) {
- iterator_ascend(&new_iter);
- }
-
- if (is_changed) {
- #ifdef DEBUG_GET_CHANGED_RANGES
- printf(
- " change: [[%u, %u] - [%u, %u]]\n",
- position.extent.row + 1, position.extent.column,
- next_position.extent.row + 1, next_position.extent.column
- );
- #endif
-
- ts_range_array_add(&results, position, next_position);
- }
-
- position = next_position;
-
- // Keep track of the current position in the included range differences
- // array in order to avoid scanning the entire array on each iteration.
- while (included_range_difference_index < included_range_differences->size) {
- const TSRange *range = &included_range_differences->contents[
- included_range_difference_index
- ];
- if (range->end_byte <= position.bytes) {
- included_range_difference_index++;
- } else {
- break;
- }
- }
- } while (!iterator_done(&old_iter) && !iterator_done(&new_iter));
-
- Length old_size = ts_subtree_total_size(*old_tree);
- Length new_size = ts_subtree_total_size(*new_tree);
- if (old_size.bytes < new_size.bytes) {
- ts_range_array_add(&results, old_size, new_size);
- } else if (new_size.bytes < old_size.bytes) {
- ts_range_array_add(&results, new_size, old_size);
- }
-
- *cursor1 = old_iter.cursor;
- *cursor2 = new_iter.cursor;
- *ranges = results.contents;
- return results.size;
-}
diff --git a/src/tree_sitter/get_changed_ranges.h b/src/tree_sitter/get_changed_ranges.h
deleted file mode 100644
index a1f1dbb430..0000000000
--- a/src/tree_sitter/get_changed_ranges.h
+++ /dev/null
@@ -1,36 +0,0 @@
-#ifndef TREE_SITTER_GET_CHANGED_RANGES_H_
-#define TREE_SITTER_GET_CHANGED_RANGES_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include "./tree_cursor.h"
-#include "./subtree.h"
-
-typedef Array(TSRange) TSRangeArray;
-
-void ts_range_array_get_changed_ranges(
- const TSRange *old_ranges, unsigned old_range_count,
- const TSRange *new_ranges, unsigned new_range_count,
- TSRangeArray *differences
-);
-
-bool ts_range_array_intersects(
- const TSRangeArray *self, unsigned start_index,
- uint32_t start_byte, uint32_t end_byte
-);
-
-unsigned ts_subtree_get_changed_ranges(
- const Subtree *old_tree, const Subtree *new_tree,
- TreeCursor *cursor1, TreeCursor *cursor2,
- const TSLanguage *language,
- const TSRangeArray *included_range_differences,
- TSRange **ranges
-);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // TREE_SITTER_GET_CHANGED_RANGES_H_
diff --git a/src/tree_sitter/language.c b/src/tree_sitter/language.c
deleted file mode 100644
index c00c49e3c0..0000000000
--- a/src/tree_sitter/language.c
+++ /dev/null
@@ -1,149 +0,0 @@
-#include "./language.h"
-#include "./subtree.h"
-#include "./error_costs.h"
-#include <string.h>
-
-uint32_t ts_language_symbol_count(const TSLanguage *self) {
- return self->symbol_count + self->alias_count;
-}
-
-uint32_t ts_language_version(const TSLanguage *self) {
- return self->version;
-}
-
-uint32_t ts_language_field_count(const TSLanguage *self) {
- if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS) {
- return self->field_count;
- } else {
- return 0;
- }
-}
-
-void ts_language_table_entry(
- const TSLanguage *self,
- TSStateId state,
- TSSymbol symbol,
- TableEntry *result
-) {
- if (symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat) {
- result->action_count = 0;
- result->is_reusable = false;
- result->actions = NULL;
- } else {
- assert(symbol < self->token_count);
- uint32_t action_index = ts_language_lookup(self, state, symbol);
- const TSParseActionEntry *entry = &self->parse_actions[action_index];
- result->action_count = entry->entry.count;
- result->is_reusable = entry->entry.reusable;
- result->actions = (const TSParseAction *)(entry + 1);
- }
-}
-
-TSSymbolMetadata ts_language_symbol_metadata(
- const TSLanguage *self,
- TSSymbol symbol
-) {
- if (symbol == ts_builtin_sym_error) {
- return (TSSymbolMetadata){.visible = true, .named = true};
- } else if (symbol == ts_builtin_sym_error_repeat) {
- return (TSSymbolMetadata){.visible = false, .named = false};
- } else {
- return self->symbol_metadata[symbol];
- }
-}
-
-TSSymbol ts_language_public_symbol(
- const TSLanguage *self,
- TSSymbol symbol
-) {
- if (symbol == ts_builtin_sym_error) return symbol;
- if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING) {
- return self->public_symbol_map[symbol];
- } else {
- return symbol;
- }
-}
-
-const char *ts_language_symbol_name(
- const TSLanguage *self,
- TSSymbol symbol
-) {
- if (symbol == ts_builtin_sym_error) {
- return "ERROR";
- } else if (symbol == ts_builtin_sym_error_repeat) {
- return "_ERROR";
- } else if (symbol < ts_language_symbol_count(self)) {
- return self->symbol_names[symbol];
- } else {
- return NULL;
- }
-}
-
-TSSymbol ts_language_symbol_for_name(
- const TSLanguage *self,
- const char *string,
- uint32_t length,
- bool is_named
-) {
- if (!strncmp(string, "ERROR", length)) return ts_builtin_sym_error;
- uint32_t count = ts_language_symbol_count(self);
- for (TSSymbol i = 0; i < count; i++) {
- TSSymbolMetadata metadata = ts_language_symbol_metadata(self, i);
- if (!metadata.visible || metadata.named != is_named) continue;
- const char *symbol_name = self->symbol_names[i];
- if (!strncmp(symbol_name, string, length) && !symbol_name[length]) {
- if (self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING) {
- return self->public_symbol_map[i];
- } else {
- return i;
- }
- }
- }
- return 0;
-}
-
-TSSymbolType ts_language_symbol_type(
- const TSLanguage *self,
- TSSymbol symbol
-) {
- TSSymbolMetadata metadata = ts_language_symbol_metadata(self, symbol);
- if (metadata.named) {
- return TSSymbolTypeRegular;
- } else if (metadata.visible) {
- return TSSymbolTypeAnonymous;
- } else {
- return TSSymbolTypeAuxiliary;
- }
-}
-
-const char *ts_language_field_name_for_id(
- const TSLanguage *self,
- TSFieldId id
-) {
- uint32_t count = ts_language_field_count(self);
- if (count && id <= count) {
- return self->field_names[id];
- } else {
- return NULL;
- }
-}
-
-TSFieldId ts_language_field_id_for_name(
- const TSLanguage *self,
- const char *name,
- uint32_t name_length
-) {
- uint32_t count = ts_language_field_count(self);
- for (TSSymbol i = 1; i < count + 1; i++) {
- switch (strncmp(name, self->field_names[i], name_length)) {
- case 0:
- if (self->field_names[i][name_length] == 0) return i;
- break;
- case -1:
- return 0;
- default:
- break;
- }
- }
- return 0;
-}
diff --git a/src/tree_sitter/language.h b/src/tree_sitter/language.h
deleted file mode 100644
index 341f0f85af..0000000000
--- a/src/tree_sitter/language.h
+++ /dev/null
@@ -1,143 +0,0 @@
-#ifndef TREE_SITTER_LANGUAGE_H_
-#define TREE_SITTER_LANGUAGE_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include "./subtree.h"
-#include "tree_sitter/parser.h"
-
-#define ts_builtin_sym_error_repeat (ts_builtin_sym_error - 1)
-#define TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS 10
-#define TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING 11
-#define TREE_SITTER_LANGUAGE_VERSION_WITH_SMALL_STATES 11
-
-typedef struct {
- const TSParseAction *actions;
- uint32_t action_count;
- bool is_reusable;
-} TableEntry;
-
-void ts_language_table_entry(const TSLanguage *, TSStateId, TSSymbol, TableEntry *);
-
-TSSymbolMetadata ts_language_symbol_metadata(const TSLanguage *, TSSymbol);
-
-TSSymbol ts_language_public_symbol(const TSLanguage *, TSSymbol);
-
-static inline bool ts_language_is_symbol_external(const TSLanguage *self, TSSymbol symbol) {
- return 0 < symbol && symbol < self->external_token_count + 1;
-}
-
-static inline const TSParseAction *ts_language_actions(
- const TSLanguage *self,
- TSStateId state,
- TSSymbol symbol,
- uint32_t *count
-) {
- TableEntry entry;
- ts_language_table_entry(self, state, symbol, &entry);
- *count = entry.action_count;
- return entry.actions;
-}
-
-static inline bool ts_language_has_actions(const TSLanguage *self,
- TSStateId state,
- TSSymbol symbol) {
- TableEntry entry;
- ts_language_table_entry(self, state, symbol, &entry);
- return entry.action_count > 0;
-}
-
-static inline bool ts_language_has_reduce_action(const TSLanguage *self,
- TSStateId state,
- TSSymbol symbol) {
- TableEntry entry;
- ts_language_table_entry(self, state, symbol, &entry);
- return entry.action_count > 0 && entry.actions[0].type == TSParseActionTypeReduce;
-}
-
-static inline uint16_t ts_language_lookup(
- const TSLanguage *self,
- TSStateId state,
- TSSymbol symbol
-) {
- if (
- self->version >= TREE_SITTER_LANGUAGE_VERSION_WITH_SMALL_STATES &&
- state >= self->large_state_count
- ) {
- uint32_t index = self->small_parse_table_map[state - self->large_state_count];
- const uint16_t *data = &self->small_parse_table[index];
- uint16_t section_count = *(data++);
- for (unsigned i = 0; i < section_count; i++) {
- uint16_t section_value = *(data++);
- uint16_t symbol_count = *(data++);
- for (unsigned i = 0; i < symbol_count; i++) {
- if (*(data++) == symbol) return section_value;
- }
- }
- return 0;
- } else {
- return self->parse_table[state * self->symbol_count + symbol];
- }
-}
-
-static inline TSStateId ts_language_next_state(const TSLanguage *self,
- TSStateId state,
- TSSymbol symbol) {
- if (symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat) {
- return 0;
- } else if (symbol < self->token_count) {
- uint32_t count;
- const TSParseAction *actions = ts_language_actions(self, state, symbol, &count);
- if (count > 0) {
- TSParseAction action = actions[count - 1];
- if (action.type == TSParseActionTypeShift) {
- return action.params.shift.extra ? state : action.params.shift.state;
- }
- }
- return 0;
- } else {
- return ts_language_lookup(self, state, symbol);
- }
-}
-
-static inline const bool *
-ts_language_enabled_external_tokens(const TSLanguage *self,
- unsigned external_scanner_state) {
- if (external_scanner_state == 0) {
- return NULL;
- } else {
- return self->external_scanner.states + self->external_token_count * external_scanner_state;
- }
-}
-
-static inline const TSSymbol *
-ts_language_alias_sequence(const TSLanguage *self, uint32_t production_id) {
- return production_id > 0 ?
- self->alias_sequences + production_id * self->max_alias_sequence_length :
- NULL;
-}
-
-static inline void ts_language_field_map(
- const TSLanguage *self,
- uint32_t production_id,
- const TSFieldMapEntry **start,
- const TSFieldMapEntry **end
-) {
- if (self->version < TREE_SITTER_LANGUAGE_VERSION_WITH_FIELDS || self->field_count == 0) {
- *start = NULL;
- *end = NULL;
- return;
- }
-
- TSFieldMapSlice slice = self->field_map_slices[production_id];
- *start = &self->field_map_entries[slice.index];
- *end = &self->field_map_entries[slice.index] + slice.length;
-}
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // TREE_SITTER_LANGUAGE_H_
diff --git a/src/tree_sitter/length.h b/src/tree_sitter/length.h
deleted file mode 100644
index 61de9fc1d5..0000000000
--- a/src/tree_sitter/length.h
+++ /dev/null
@@ -1,44 +0,0 @@
-#ifndef TREE_SITTER_LENGTH_H_
-#define TREE_SITTER_LENGTH_H_
-
-#include <stdlib.h>
-#include <stdbool.h>
-#include "./point.h"
-#include "tree_sitter/api.h"
-
-typedef struct {
- uint32_t bytes;
- TSPoint extent;
-} Length;
-
-static const Length LENGTH_UNDEFINED = {0, {0, 1}};
-static const Length LENGTH_MAX = {UINT32_MAX, {UINT32_MAX, UINT32_MAX}};
-
-static inline bool length_is_undefined(Length length) {
- return length.bytes == 0 && length.extent.column != 0;
-}
-
-static inline Length length_min(Length len1, Length len2) {
- return (len1.bytes < len2.bytes) ? len1 : len2;
-}
-
-static inline Length length_add(Length len1, Length len2) {
- Length result;
- result.bytes = len1.bytes + len2.bytes;
- result.extent = point_add(len1.extent, len2.extent);
- return result;
-}
-
-static inline Length length_sub(Length len1, Length len2) {
- Length result;
- result.bytes = len1.bytes - len2.bytes;
- result.extent = point_sub(len1.extent, len2.extent);
- return result;
-}
-
-static inline Length length_zero(void) {
- Length result = {0, {0, 0}};
- return result;
-}
-
-#endif
diff --git a/src/tree_sitter/lexer.c b/src/tree_sitter/lexer.c
deleted file mode 100644
index 3f8a4c0ae8..0000000000
--- a/src/tree_sitter/lexer.c
+++ /dev/null
@@ -1,391 +0,0 @@
-#include <stdio.h>
-#include "./lexer.h"
-#include "./subtree.h"
-#include "./length.h"
-#include "./unicode.h"
-
-#define LOG(message, character) \
- if (self->logger.log) { \
- snprintf( \
- self->debug_buffer, \
- TREE_SITTER_SERIALIZATION_BUFFER_SIZE, \
- 32 <= character && character < 127 ? \
- message " character:'%c'" : \
- message " character:%d", \
- character \
- ); \
- self->logger.log( \
- self->logger.payload, \
- TSLogTypeLex, \
- self->debug_buffer \
- ); \
- }
-
-static const int32_t BYTE_ORDER_MARK = 0xFEFF;
-
-static const TSRange DEFAULT_RANGE = {
- .start_point = {
- .row = 0,
- .column = 0,
- },
- .end_point = {
- .row = UINT32_MAX,
- .column = UINT32_MAX,
- },
- .start_byte = 0,
- .end_byte = UINT32_MAX
-};
-
-// Check if the lexer has reached EOF. This state is stored
-// by setting the lexer's `current_included_range_index` such that
-// it has consumed all of its available ranges.
-static bool ts_lexer__eof(const TSLexer *_self) {
- Lexer *self = (Lexer *)_self;
- return self->current_included_range_index == self->included_range_count;
-}
-
-// Clear the currently stored chunk of source code, because the lexer's
-// position has changed.
-static void ts_lexer__clear_chunk(Lexer *self) {
- self->chunk = NULL;
- self->chunk_size = 0;
- self->chunk_start = 0;
-}
-
-// Call the lexer's input callback to obtain a new chunk of source code
-// for the current position.
-static void ts_lexer__get_chunk(Lexer *self) {
- self->chunk_start = self->current_position.bytes;
- self->chunk = self->input.read(
- self->input.payload,
- self->current_position.bytes,
- self->current_position.extent,
- &self->chunk_size
- );
- if (!self->chunk_size) {
- self->current_included_range_index = self->included_range_count;
- self->chunk = NULL;
- }
-}
-
-// Decode the next unicode character in the current chunk of source code.
-// This assumes that the lexer has already retrieved a chunk of source
-// code that spans the current position.
-static void ts_lexer__get_lookahead(Lexer *self) {
- uint32_t position_in_chunk = self->current_position.bytes - self->chunk_start;
- const uint8_t *chunk = (const uint8_t *)self->chunk + position_in_chunk;
- uint32_t size = self->chunk_size - position_in_chunk;
-
- if (size == 0) {
- self->lookahead_size = 1;
- self->data.lookahead = '\0';
- return;
- }
-
- UnicodeDecodeFunction decode = self->input.encoding == TSInputEncodingUTF8
- ? ts_decode_utf8
- : ts_decode_utf16;
-
- self->lookahead_size = decode(chunk, size, &self->data.lookahead);
-
- // If this chunk ended in the middle of a multi-byte character,
- // try again with a fresh chunk.
- if (self->data.lookahead == TS_DECODE_ERROR && size < 4) {
- ts_lexer__get_chunk(self);
- chunk = (const uint8_t *)self->chunk;
- size = self->chunk_size;
- self->lookahead_size = decode(chunk, size, &self->data.lookahead);
- }
-
- if (self->data.lookahead == TS_DECODE_ERROR) {
- self->lookahead_size = 1;
- }
-}
-
-// Advance to the next character in the source code, retrieving a new
-// chunk of source code if needed.
-static void ts_lexer__advance(TSLexer *_self, bool skip) {
- Lexer *self = (Lexer *)_self;
- if (!self->chunk) return;
-
- if (skip) {
- LOG("skip", self->data.lookahead);
- } else {
- LOG("consume", self->data.lookahead);
- }
-
- if (self->lookahead_size) {
- self->current_position.bytes += self->lookahead_size;
- if (self->data.lookahead == '\n') {
- self->current_position.extent.row++;
- self->current_position.extent.column = 0;
- } else {
- self->current_position.extent.column += self->lookahead_size;
- }
- }
-
- const TSRange *current_range = NULL;
- if (self->current_included_range_index < self->included_range_count) {
- current_range = &self->included_ranges[self->current_included_range_index];
- if (self->current_position.bytes == current_range->end_byte) {
- self->current_included_range_index++;
- if (self->current_included_range_index < self->included_range_count) {
- current_range++;
- self->current_position = (Length) {
- current_range->start_byte,
- current_range->start_point,
- };
- } else {
- current_range = NULL;
- }
- }
- }
-
- if (skip) self->token_start_position = self->current_position;
-
- if (current_range) {
- if (self->current_position.bytes >= self->chunk_start + self->chunk_size) {
- ts_lexer__get_chunk(self);
- }
- ts_lexer__get_lookahead(self);
- } else {
- ts_lexer__clear_chunk(self);
- self->data.lookahead = '\0';
- self->lookahead_size = 1;
- }
-}
-
-// Mark that a token match has completed. This can be called multiple
-// times if a longer match is found later.
-static void ts_lexer__mark_end(TSLexer *_self) {
- Lexer *self = (Lexer *)_self;
- if (!ts_lexer__eof(&self->data)) {
- // If the lexer is right at the beginning of included range,
- // then the token should be considered to end at the *end* of the
- // previous included range, rather than here.
- TSRange *current_included_range = &self->included_ranges[
- self->current_included_range_index
- ];
- if (
- self->current_included_range_index > 0 &&
- self->current_position.bytes == current_included_range->start_byte
- ) {
- TSRange *previous_included_range = current_included_range - 1;
- self->token_end_position = (Length) {
- previous_included_range->end_byte,
- previous_included_range->end_point,
- };
- return;
- }
- }
- self->token_end_position = self->current_position;
-}
-
-static uint32_t ts_lexer__get_column(TSLexer *_self) {
- Lexer *self = (Lexer *)_self;
- uint32_t goal_byte = self->current_position.bytes;
-
- self->current_position.bytes -= self->current_position.extent.column;
- self->current_position.extent.column = 0;
-
- if (self->current_position.bytes < self->chunk_start) {
- ts_lexer__get_chunk(self);
- }
-
- uint32_t result = 0;
- while (self->current_position.bytes < goal_byte) {
- ts_lexer__advance(&self->data, false);
- result++;
- }
-
- return result;
-}
-
-// Is the lexer at a boundary between two disjoint included ranges of
-// source code? This is exposed as an API because some languages' external
-// scanners need to perform custom actions at these bounaries.
-static bool ts_lexer__is_at_included_range_start(const TSLexer *_self) {
- const Lexer *self = (const Lexer *)_self;
- if (self->current_included_range_index < self->included_range_count) {
- TSRange *current_range = &self->included_ranges[self->current_included_range_index];
- return self->current_position.bytes == current_range->start_byte;
- } else {
- return false;
- }
-}
-
-void ts_lexer_init(Lexer *self) {
- *self = (Lexer) {
- .data = {
- // The lexer's methods are stored as struct fields so that generated
- // parsers can call them without needing to be linked against this
- // library.
- .advance = ts_lexer__advance,
- .mark_end = ts_lexer__mark_end,
- .get_column = ts_lexer__get_column,
- .is_at_included_range_start = ts_lexer__is_at_included_range_start,
- .eof = ts_lexer__eof,
- .lookahead = 0,
- .result_symbol = 0,
- },
- .chunk = NULL,
- .chunk_size = 0,
- .chunk_start = 0,
- .current_position = {0, {0, 0}},
- .logger = {
- .payload = NULL,
- .log = NULL
- },
- .included_ranges = NULL,
- .included_range_count = 0,
- .current_included_range_index = 0,
- };
- ts_lexer_set_included_ranges(self, NULL, 0);
-}
-
-void ts_lexer_delete(Lexer *self) {
- ts_free(self->included_ranges);
-}
-
-static void ts_lexer_goto(Lexer *self, Length position) {
- self->current_position = position;
- bool found_included_range = false;
-
- // Move to the first valid position at or after the given position.
- for (unsigned i = 0; i < self->included_range_count; i++) {
- TSRange *included_range = &self->included_ranges[i];
- if (included_range->end_byte > position.bytes) {
- if (included_range->start_byte > position.bytes) {
- self->current_position = (Length) {
- .bytes = included_range->start_byte,
- .extent = included_range->start_point,
- };
- }
-
- self->current_included_range_index = i;
- found_included_range = true;
- break;
- }
- }
-
- if (found_included_range) {
- // If the current position is outside of the current chunk of text,
- // then clear out the current chunk of text.
- if (self->chunk && (
- position.bytes < self->chunk_start ||
- position.bytes >= self->chunk_start + self->chunk_size
- )) {
- ts_lexer__clear_chunk(self);
- }
-
- self->lookahead_size = 0;
- self->data.lookahead = '\0';
- }
-
- // If the given position is beyond any of included ranges, move to the EOF
- // state - past the end of the included ranges.
- else {
- self->current_included_range_index = self->included_range_count;
- TSRange *last_included_range = &self->included_ranges[self->included_range_count - 1];
- self->current_position = (Length) {
- .bytes = last_included_range->end_byte,
- .extent = last_included_range->end_point,
- };
- ts_lexer__clear_chunk(self);
- self->lookahead_size = 1;
- self->data.lookahead = '\0';
- }
-}
-
-void ts_lexer_set_input(Lexer *self, TSInput input) {
- self->input = input;
- ts_lexer__clear_chunk(self);
- ts_lexer_goto(self, self->current_position);
-}
-
-// Move the lexer to the given position. This doesn't do any work
-// if the parser is already at the given position.
-void ts_lexer_reset(Lexer *self, Length position) {
- if (position.bytes != self->current_position.bytes) {
- ts_lexer_goto(self, position);
- }
-}
-
-void ts_lexer_start(Lexer *self) {
- self->token_start_position = self->current_position;
- self->token_end_position = LENGTH_UNDEFINED;
- self->data.result_symbol = 0;
- if (!ts_lexer__eof(&self->data)) {
- if (!self->chunk_size) ts_lexer__get_chunk(self);
- if (!self->lookahead_size) ts_lexer__get_lookahead(self);
- if (
- self->current_position.bytes == 0 &&
- self->data.lookahead == BYTE_ORDER_MARK
- ) ts_lexer__advance(&self->data, true);
- }
-}
-
-void ts_lexer_finish(Lexer *self, uint32_t *lookahead_end_byte) {
- if (length_is_undefined(self->token_end_position)) {
- ts_lexer__mark_end(&self->data);
- }
-
- uint32_t current_lookahead_end_byte = self->current_position.bytes + 1;
-
- // In order to determine that a byte sequence is invalid UTF8 or UTF16,
- // the character decoding algorithm may have looked at the following byte.
- // Therefore, the next byte *after* the current (invalid) character
- // affects the interpretation of the current character.
- if (self->data.lookahead == TS_DECODE_ERROR) {
- current_lookahead_end_byte++;
- }
-
- if (current_lookahead_end_byte > *lookahead_end_byte) {
- *lookahead_end_byte = current_lookahead_end_byte;
- }
-}
-
-void ts_lexer_advance_to_end(Lexer *self) {
- while (self->chunk) {
- ts_lexer__advance(&self->data, false);
- }
-}
-
-void ts_lexer_mark_end(Lexer *self) {
- ts_lexer__mark_end(&self->data);
-}
-
-bool ts_lexer_set_included_ranges(
- Lexer *self,
- const TSRange *ranges,
- uint32_t count
-) {
- if (count == 0 || !ranges) {
- ranges = &DEFAULT_RANGE;
- count = 1;
- } else {
- uint32_t previous_byte = 0;
- for (unsigned i = 0; i < count; i++) {
- const TSRange *range = &ranges[i];
- if (
- range->start_byte < previous_byte ||
- range->end_byte < range->start_byte
- ) return false;
- previous_byte = range->end_byte;
- }
- }
-
- size_t size = count * sizeof(TSRange);
- self->included_ranges = ts_realloc(self->included_ranges, size);
- memcpy(self->included_ranges, ranges, size);
- self->included_range_count = count;
- ts_lexer_goto(self, self->current_position);
- return true;
-}
-
-TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count) {
- *count = self->included_range_count;
- return self->included_ranges;
-}
-
-#undef LOG
diff --git a/src/tree_sitter/lexer.h b/src/tree_sitter/lexer.h
deleted file mode 100644
index 5e39294529..0000000000
--- a/src/tree_sitter/lexer.h
+++ /dev/null
@@ -1,48 +0,0 @@
-#ifndef TREE_SITTER_LEXER_H_
-#define TREE_SITTER_LEXER_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include "./length.h"
-#include "./subtree.h"
-#include "tree_sitter/api.h"
-#include "tree_sitter/parser.h"
-
-typedef struct {
- TSLexer data;
- Length current_position;
- Length token_start_position;
- Length token_end_position;
-
- TSRange *included_ranges;
- size_t included_range_count;
- size_t current_included_range_index;
-
- const char *chunk;
- uint32_t chunk_start;
- uint32_t chunk_size;
- uint32_t lookahead_size;
-
- TSInput input;
- TSLogger logger;
- char debug_buffer[TREE_SITTER_SERIALIZATION_BUFFER_SIZE];
-} Lexer;
-
-void ts_lexer_init(Lexer *);
-void ts_lexer_delete(Lexer *);
-void ts_lexer_set_input(Lexer *, TSInput);
-void ts_lexer_reset(Lexer *, Length);
-void ts_lexer_start(Lexer *);
-void ts_lexer_finish(Lexer *, uint32_t *);
-void ts_lexer_advance_to_end(Lexer *);
-void ts_lexer_mark_end(Lexer *);
-bool ts_lexer_set_included_ranges(Lexer *self, const TSRange *ranges, uint32_t count);
-TSRange *ts_lexer_included_ranges(const Lexer *self, uint32_t *count);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // TREE_SITTER_LEXER_H_
diff --git a/src/tree_sitter/lib.c b/src/tree_sitter/lib.c
deleted file mode 100644
index 289d32f4c5..0000000000
--- a/src/tree_sitter/lib.c
+++ /dev/null
@@ -1,17 +0,0 @@
-// The Tree-sitter library can be built by compiling this one source file.
-//
-// The following directories must be added to the include path:
-// - include
-
-#define _POSIX_C_SOURCE 200112L
-
-#include "./get_changed_ranges.c"
-#include "./language.c"
-#include "./lexer.c"
-#include "./node.c"
-#include "./parser.c"
-#include "./query.c"
-#include "./stack.c"
-#include "./subtree.c"
-#include "./tree_cursor.c"
-#include "./tree.c"
diff --git a/src/tree_sitter/node.c b/src/tree_sitter/node.c
deleted file mode 100644
index 576f3ef38e..0000000000
--- a/src/tree_sitter/node.c
+++ /dev/null
@@ -1,677 +0,0 @@
-#include <stdbool.h>
-#include "./subtree.h"
-#include "./tree.h"
-#include "./language.h"
-
-typedef struct {
- Subtree parent;
- const TSTree *tree;
- Length position;
- uint32_t child_index;
- uint32_t structural_child_index;
- const TSSymbol *alias_sequence;
-} NodeChildIterator;
-
-// TSNode - constructors
-
-TSNode ts_node_new(
- const TSTree *tree,
- const Subtree *subtree,
- Length position,
- TSSymbol alias
-) {
- return (TSNode) {
- {position.bytes, position.extent.row, position.extent.column, alias},
- subtree,
- tree,
- };
-}
-
-static inline TSNode ts_node__null(void) {
- return ts_node_new(NULL, NULL, length_zero(), 0);
-}
-
-// TSNode - accessors
-
-uint32_t ts_node_start_byte(TSNode self) {
- return self.context[0];
-}
-
-TSPoint ts_node_start_point(TSNode self) {
- return (TSPoint) {self.context[1], self.context[2]};
-}
-
-static inline uint32_t ts_node__alias(const TSNode *self) {
- return self->context[3];
-}
-
-static inline Subtree ts_node__subtree(TSNode self) {
- return *(const Subtree *)self.id;
-}
-
-// NodeChildIterator
-
-static inline NodeChildIterator ts_node_iterate_children(const TSNode *node) {
- Subtree subtree = ts_node__subtree(*node);
- if (ts_subtree_child_count(subtree) == 0) {
- return (NodeChildIterator) {NULL_SUBTREE, node->tree, length_zero(), 0, 0, NULL};
- }
- const TSSymbol *alias_sequence = ts_language_alias_sequence(
- node->tree->language,
- subtree.ptr->production_id
- );
- return (NodeChildIterator) {
- .tree = node->tree,
- .parent = subtree,
- .position = {ts_node_start_byte(*node), ts_node_start_point(*node)},
- .child_index = 0,
- .structural_child_index = 0,
- .alias_sequence = alias_sequence,
- };
-}
-
-static inline bool ts_node_child_iterator_done(NodeChildIterator *self) {
- return self->child_index == self->parent.ptr->child_count;
-}
-
-static inline bool ts_node_child_iterator_next(
- NodeChildIterator *self,
- TSNode *result
-) {
- if (!self->parent.ptr || ts_node_child_iterator_done(self)) return false;
- const Subtree *child = &self->parent.ptr->children[self->child_index];
- TSSymbol alias_symbol = 0;
- if (!ts_subtree_extra(*child)) {
- if (self->alias_sequence) {
- alias_symbol = self->alias_sequence[self->structural_child_index];
- }
- self->structural_child_index++;
- }
- if (self->child_index > 0) {
- self->position = length_add(self->position, ts_subtree_padding(*child));
- }
- *result = ts_node_new(
- self->tree,
- child,
- self->position,
- alias_symbol
- );
- self->position = length_add(self->position, ts_subtree_size(*child));
- self->child_index++;
- return true;
-}
-
-// TSNode - private
-
-static inline bool ts_node__is_relevant(TSNode self, bool include_anonymous) {
- Subtree tree = ts_node__subtree(self);
- if (include_anonymous) {
- return ts_subtree_visible(tree) || ts_node__alias(&self);
- } else {
- TSSymbol alias = ts_node__alias(&self);
- if (alias) {
- return ts_language_symbol_metadata(self.tree->language, alias).named;
- } else {
- return ts_subtree_visible(tree) && ts_subtree_named(tree);
- }
- }
-}
-
-static inline uint32_t ts_node__relevant_child_count(
- TSNode self,
- bool include_anonymous
-) {
- Subtree tree = ts_node__subtree(self);
- if (ts_subtree_child_count(tree) > 0) {
- if (include_anonymous) {
- return tree.ptr->visible_child_count;
- } else {
- return tree.ptr->named_child_count;
- }
- } else {
- return 0;
- }
-}
-
-static inline TSNode ts_node__child(
- TSNode self,
- uint32_t child_index,
- bool include_anonymous
-) {
- TSNode result = self;
- bool did_descend = true;
-
- while (did_descend) {
- did_descend = false;
-
- TSNode child;
- uint32_t index = 0;
- NodeChildIterator iterator = ts_node_iterate_children(&result);
- while (ts_node_child_iterator_next(&iterator, &child)) {
- if (ts_node__is_relevant(child, include_anonymous)) {
- if (index == child_index) {
- if (ts_node__is_relevant(self, true)) {
- ts_tree_set_cached_parent(self.tree, &child, &self);
- }
- return child;
- }
- index++;
- } else {
- uint32_t grandchild_index = child_index - index;
- uint32_t grandchild_count = ts_node__relevant_child_count(child, include_anonymous);
- if (grandchild_index < grandchild_count) {
- did_descend = true;
- result = child;
- child_index = grandchild_index;
- break;
- }
- index += grandchild_count;
- }
- }
- }
-
- return ts_node__null();
-}
-
-static bool ts_subtree_has_trailing_empty_descendant(
- Subtree self,
- Subtree other
-) {
- for (unsigned i = ts_subtree_child_count(self) - 1; i + 1 > 0; i--) {
- Subtree child = self.ptr->children[i];
- if (ts_subtree_total_bytes(child) > 0) break;
- if (child.ptr == other.ptr || ts_subtree_has_trailing_empty_descendant(child, other)) {
- return true;
- }
- }
- return false;
-}
-
-static inline TSNode ts_node__prev_sibling(TSNode self, bool include_anonymous) {
- Subtree self_subtree = ts_node__subtree(self);
- bool self_is_empty = ts_subtree_total_bytes(self_subtree) == 0;
- uint32_t target_end_byte = ts_node_end_byte(self);
-
- TSNode node = ts_node_parent(self);
- TSNode earlier_node = ts_node__null();
- bool earlier_node_is_relevant = false;
-
- while (!ts_node_is_null(node)) {
- TSNode earlier_child = ts_node__null();
- bool earlier_child_is_relevant = false;
- bool found_child_containing_target = false;
-
- TSNode child;
- NodeChildIterator iterator = ts_node_iterate_children(&node);
- while (ts_node_child_iterator_next(&iterator, &child)) {
- if (child.id == self.id) break;
- if (iterator.position.bytes > target_end_byte) {
- found_child_containing_target = true;
- break;
- }
-
- if (iterator.position.bytes == target_end_byte &&
- (!self_is_empty ||
- ts_subtree_has_trailing_empty_descendant(ts_node__subtree(child), self_subtree))) {
- found_child_containing_target = true;
- break;
- }
-
- if (ts_node__is_relevant(child, include_anonymous)) {
- earlier_child = child;
- earlier_child_is_relevant = true;
- } else if (ts_node__relevant_child_count(child, include_anonymous) > 0) {
- earlier_child = child;
- earlier_child_is_relevant = false;
- }
- }
-
- if (found_child_containing_target) {
- if (!ts_node_is_null(earlier_child)) {
- earlier_node = earlier_child;
- earlier_node_is_relevant = earlier_child_is_relevant;
- }
- node = child;
- } else if (earlier_child_is_relevant) {
- return earlier_child;
- } else if (!ts_node_is_null(earlier_child)) {
- node = earlier_child;
- } else if (earlier_node_is_relevant) {
- return earlier_node;
- } else {
- node = earlier_node;
- }
- }
-
- return ts_node__null();
-}
-
-static inline TSNode ts_node__next_sibling(TSNode self, bool include_anonymous) {
- uint32_t target_end_byte = ts_node_end_byte(self);
-
- TSNode node = ts_node_parent(self);
- TSNode later_node = ts_node__null();
- bool later_node_is_relevant = false;
-
- while (!ts_node_is_null(node)) {
- TSNode later_child = ts_node__null();
- bool later_child_is_relevant = false;
- TSNode child_containing_target = ts_node__null();
-
- TSNode child;
- NodeChildIterator iterator = ts_node_iterate_children(&node);
- while (ts_node_child_iterator_next(&iterator, &child)) {
- if (iterator.position.bytes < target_end_byte) continue;
- if (ts_node_start_byte(child) <= ts_node_start_byte(self)) {
- if (ts_node__subtree(child).ptr != ts_node__subtree(self).ptr) {
- child_containing_target = child;
- }
- } else if (ts_node__is_relevant(child, include_anonymous)) {
- later_child = child;
- later_child_is_relevant = true;
- break;
- } else if (ts_node__relevant_child_count(child, include_anonymous) > 0) {
- later_child = child;
- later_child_is_relevant = false;
- break;
- }
- }
-
- if (!ts_node_is_null(child_containing_target)) {
- if (!ts_node_is_null(later_child)) {
- later_node = later_child;
- later_node_is_relevant = later_child_is_relevant;
- }
- node = child_containing_target;
- } else if (later_child_is_relevant) {
- return later_child;
- } else if (!ts_node_is_null(later_child)) {
- node = later_child;
- } else if (later_node_is_relevant) {
- return later_node;
- } else {
- node = later_node;
- }
- }
-
- return ts_node__null();
-}
-
-static inline TSNode ts_node__first_child_for_byte(
- TSNode self,
- uint32_t goal,
- bool include_anonymous
-) {
- TSNode node = self;
- bool did_descend = true;
-
- while (did_descend) {
- did_descend = false;
-
- TSNode child;
- NodeChildIterator iterator = ts_node_iterate_children(&node);
- while (ts_node_child_iterator_next(&iterator, &child)) {
- if (ts_node_end_byte(child) > goal) {
- if (ts_node__is_relevant(child, include_anonymous)) {
- return child;
- } else if (ts_node_child_count(child) > 0) {
- did_descend = true;
- node = child;
- break;
- }
- }
- }
- }
-
- return ts_node__null();
-}
-
-static inline TSNode ts_node__descendant_for_byte_range(
- TSNode self,
- uint32_t range_start,
- uint32_t range_end,
- bool include_anonymous
-) {
- TSNode node = self;
- TSNode last_visible_node = self;
-
- bool did_descend = true;
- while (did_descend) {
- did_descend = false;
-
- TSNode child;
- NodeChildIterator iterator = ts_node_iterate_children(&node);
- while (ts_node_child_iterator_next(&iterator, &child)) {
- uint32_t node_end = iterator.position.bytes;
-
- // The end of this node must extend far enough forward to touch
- // the end of the range and exceed the start of the range.
- if (node_end < range_end) continue;
- if (node_end <= range_start) continue;
-
- // The start of this node must extend far enough backward to
- // touch the start of the range.
- if (range_start < ts_node_start_byte(child)) break;
-
- node = child;
- if (ts_node__is_relevant(node, include_anonymous)) {
- ts_tree_set_cached_parent(self.tree, &child, &last_visible_node);
- last_visible_node = node;
- }
- did_descend = true;
- break;
- }
- }
-
- return last_visible_node;
-}
-
-static inline TSNode ts_node__descendant_for_point_range(
- TSNode self,
- TSPoint range_start,
- TSPoint range_end,
- bool include_anonymous
-) {
- TSNode node = self;
- TSNode last_visible_node = self;
-
- bool did_descend = true;
- while (did_descend) {
- did_descend = false;
-
- TSNode child;
- NodeChildIterator iterator = ts_node_iterate_children(&node);
- while (ts_node_child_iterator_next(&iterator, &child)) {
- TSPoint node_end = iterator.position.extent;
-
- // The end of this node must extend far enough forward to touch
- // the end of the range and exceed the start of the range.
- if (point_lt(node_end, range_end)) continue;
- if (point_lte(node_end, range_start)) continue;
-
- // The start of this node must extend far enough backward to
- // touch the start of the range.
- if (point_lt(range_start, ts_node_start_point(child))) break;
-
- node = child;
- if (ts_node__is_relevant(node, include_anonymous)) {
- ts_tree_set_cached_parent(self.tree, &child, &last_visible_node);
- last_visible_node = node;
- }
- did_descend = true;
- break;
- }
- }
-
- return last_visible_node;
-}
-
-// TSNode - public
-
-uint32_t ts_node_end_byte(TSNode self) {
- return ts_node_start_byte(self) + ts_subtree_size(ts_node__subtree(self)).bytes;
-}
-
-TSPoint ts_node_end_point(TSNode self) {
- return point_add(ts_node_start_point(self), ts_subtree_size(ts_node__subtree(self)).extent);
-}
-
-TSSymbol ts_node_symbol(TSNode self) {
- TSSymbol symbol = ts_node__alias(&self);
- if (!symbol) symbol = ts_subtree_symbol(ts_node__subtree(self));
- return ts_language_public_symbol(self.tree->language, symbol);
-}
-
-const char *ts_node_type(TSNode self) {
- TSSymbol symbol = ts_node__alias(&self);
- if (!symbol) symbol = ts_subtree_symbol(ts_node__subtree(self));
- return ts_language_symbol_name(self.tree->language, symbol);
-}
-
-char *ts_node_string(TSNode self) {
- return ts_subtree_string(ts_node__subtree(self), self.tree->language, false);
-}
-
-bool ts_node_eq(TSNode self, TSNode other) {
- return self.tree == other.tree && self.id == other.id;
-}
-
-bool ts_node_is_null(TSNode self) {
- return self.id == 0;
-}
-
-bool ts_node_is_extra(TSNode self) {
- return ts_subtree_extra(ts_node__subtree(self));
-}
-
-bool ts_node_is_named(TSNode self) {
- TSSymbol alias = ts_node__alias(&self);
- return alias
- ? ts_language_symbol_metadata(self.tree->language, alias).named
- : ts_subtree_named(ts_node__subtree(self));
-}
-
-bool ts_node_is_missing(TSNode self) {
- return ts_subtree_missing(ts_node__subtree(self));
-}
-
-bool ts_node_has_changes(TSNode self) {
- return ts_subtree_has_changes(ts_node__subtree(self));
-}
-
-bool ts_node_has_error(TSNode self) {
- return ts_subtree_error_cost(ts_node__subtree(self)) > 0;
-}
-
-TSNode ts_node_parent(TSNode self) {
- TSNode node = ts_tree_get_cached_parent(self.tree, &self);
- if (node.id) return node;
-
- node = ts_tree_root_node(self.tree);
- uint32_t end_byte = ts_node_end_byte(self);
- if (node.id == self.id) return ts_node__null();
-
- TSNode last_visible_node = node;
- bool did_descend = true;
- while (did_descend) {
- did_descend = false;
-
- TSNode child;
- NodeChildIterator iterator = ts_node_iterate_children(&node);
- while (ts_node_child_iterator_next(&iterator, &child)) {
- if (
- ts_node_start_byte(child) > ts_node_start_byte(self) ||
- child.id == self.id
- ) break;
- if (iterator.position.bytes >= end_byte) {
- node = child;
- if (ts_node__is_relevant(child, true)) {
- ts_tree_set_cached_parent(self.tree, &node, &last_visible_node);
- last_visible_node = node;
- }
- did_descend = true;
- break;
- }
- }
- }
-
- return last_visible_node;
-}
-
-TSNode ts_node_child(TSNode self, uint32_t child_index) {
- return ts_node__child(self, child_index, true);
-}
-
-TSNode ts_node_named_child(TSNode self, uint32_t child_index) {
- return ts_node__child(self, child_index, false);
-}
-
-TSNode ts_node_child_by_field_id(TSNode self, TSFieldId field_id) {
-recur:
- if (!field_id || ts_node_child_count(self) == 0) return ts_node__null();
-
- const TSFieldMapEntry *field_map, *field_map_end;
- ts_language_field_map(
- self.tree->language,
- ts_node__subtree(self).ptr->production_id,
- &field_map,
- &field_map_end
- );
- if (field_map == field_map_end) return ts_node__null();
-
- // The field mappings are sorted by their field id. Scan all
- // the mappings to find the ones for the given field id.
- while (field_map->field_id < field_id) {
- field_map++;
- if (field_map == field_map_end) return ts_node__null();
- }
- while (field_map_end[-1].field_id > field_id) {
- field_map_end--;
- if (field_map == field_map_end) return ts_node__null();
- }
-
- TSNode child;
- NodeChildIterator iterator = ts_node_iterate_children(&self);
- while (ts_node_child_iterator_next(&iterator, &child)) {
- if (!ts_subtree_extra(ts_node__subtree(child))) {
- uint32_t index = iterator.structural_child_index - 1;
- if (index < field_map->child_index) continue;
-
- // Hidden nodes' fields are "inherited" by their visible parent.
- if (field_map->inherited) {
-
- // If this is the *last* possible child node for this field,
- // then perform a tail call to avoid recursion.
- if (field_map + 1 == field_map_end) {
- self = child;
- goto recur;
- }
-
- // Otherwise, descend into this child, but if it doesn't contain
- // the field, continue searching subsequent children.
- else {
- TSNode result = ts_node_child_by_field_id(child, field_id);
- if (result.id) return result;
- field_map++;
- if (field_map == field_map_end) return ts_node__null();
- }
- }
-
- else if (ts_node__is_relevant(child, true)) {
- return child;
- }
-
- // If the field refers to a hidden node, return its first visible
- // child.
- else {
- return ts_node_child(child, 0);
- }
- }
- }
-
- return ts_node__null();
-}
-
-TSNode ts_node_child_by_field_name(
- TSNode self,
- const char *name,
- uint32_t name_length
-) {
- TSFieldId field_id = ts_language_field_id_for_name(
- self.tree->language,
- name,
- name_length
- );
- return ts_node_child_by_field_id(self, field_id);
-}
-
-uint32_t ts_node_child_count(TSNode self) {
- Subtree tree = ts_node__subtree(self);
- if (ts_subtree_child_count(tree) > 0) {
- return tree.ptr->visible_child_count;
- } else {
- return 0;
- }
-}
-
-uint32_t ts_node_named_child_count(TSNode self) {
- Subtree tree = ts_node__subtree(self);
- if (ts_subtree_child_count(tree) > 0) {
- return tree.ptr->named_child_count;
- } else {
- return 0;
- }
-}
-
-TSNode ts_node_next_sibling(TSNode self) {
- return ts_node__next_sibling(self, true);
-}
-
-TSNode ts_node_next_named_sibling(TSNode self) {
- return ts_node__next_sibling(self, false);
-}
-
-TSNode ts_node_prev_sibling(TSNode self) {
- return ts_node__prev_sibling(self, true);
-}
-
-TSNode ts_node_prev_named_sibling(TSNode self) {
- return ts_node__prev_sibling(self, false);
-}
-
-TSNode ts_node_first_child_for_byte(TSNode self, uint32_t byte) {
- return ts_node__first_child_for_byte(self, byte, true);
-}
-
-TSNode ts_node_first_named_child_for_byte(TSNode self, uint32_t byte) {
- return ts_node__first_child_for_byte(self, byte, false);
-}
-
-TSNode ts_node_descendant_for_byte_range(
- TSNode self,
- uint32_t start,
- uint32_t end
-) {
- return ts_node__descendant_for_byte_range(self, start, end, true);
-}
-
-TSNode ts_node_named_descendant_for_byte_range(
- TSNode self,
- uint32_t start,
- uint32_t end
-) {
- return ts_node__descendant_for_byte_range(self, start, end, false);
-}
-
-TSNode ts_node_descendant_for_point_range(
- TSNode self,
- TSPoint start,
- TSPoint end
-) {
- return ts_node__descendant_for_point_range(self, start, end, true);
-}
-
-TSNode ts_node_named_descendant_for_point_range(
- TSNode self,
- TSPoint start,
- TSPoint end
-) {
- return ts_node__descendant_for_point_range(self, start, end, false);
-}
-
-void ts_node_edit(TSNode *self, const TSInputEdit *edit) {
- uint32_t start_byte = ts_node_start_byte(*self);
- TSPoint start_point = ts_node_start_point(*self);
-
- if (start_byte >= edit->old_end_byte) {
- start_byte = edit->new_end_byte + (start_byte - edit->old_end_byte);
- start_point = point_add(edit->new_end_point, point_sub(start_point, edit->old_end_point));
- } else if (start_byte > edit->start_byte) {
- start_byte = edit->new_end_byte;
- start_point = edit->new_end_point;
- }
-
- self->context[0] = start_byte;
- self->context[1] = start_point.row;
- self->context[2] = start_point.column;
-}
diff --git a/src/tree_sitter/parser.c b/src/tree_sitter/parser.c
deleted file mode 100644
index dd222cd3c4..0000000000
--- a/src/tree_sitter/parser.c
+++ /dev/null
@@ -1,1879 +0,0 @@
-#include <time.h>
-#include <assert.h>
-#include <stdio.h>
-#include <limits.h>
-#include <stdbool.h>
-#include "tree_sitter/api.h"
-#include "./alloc.h"
-#include "./array.h"
-#include "./atomic.h"
-#include "./clock.h"
-#include "./error_costs.h"
-#include "./get_changed_ranges.h"
-#include "./language.h"
-#include "./length.h"
-#include "./lexer.h"
-#include "./reduce_action.h"
-#include "./reusable_node.h"
-#include "./stack.h"
-#include "./subtree.h"
-#include "./tree.h"
-
-#define LOG(...) \
- if (self->lexer.logger.log || self->dot_graph_file) { \
- snprintf(self->lexer.debug_buffer, TREE_SITTER_SERIALIZATION_BUFFER_SIZE, __VA_ARGS__); \
- ts_parser__log(self); \
- }
-
-#define LOG_STACK() \
- if (self->dot_graph_file) { \
- ts_stack_print_dot_graph(self->stack, self->language, self->dot_graph_file); \
- fputs("\n\n", self->dot_graph_file); \
- }
-
-#define LOG_TREE(tree) \
- if (self->dot_graph_file) { \
- ts_subtree_print_dot_graph(tree, self->language, self->dot_graph_file); \
- fputs("\n", self->dot_graph_file); \
- }
-
-#define SYM_NAME(symbol) ts_language_symbol_name(self->language, symbol)
-
-#define TREE_NAME(tree) SYM_NAME(ts_subtree_symbol(tree))
-
-static const unsigned MAX_VERSION_COUNT = 6;
-static const unsigned MAX_VERSION_COUNT_OVERFLOW = 4;
-static const unsigned MAX_SUMMARY_DEPTH = 16;
-static const unsigned MAX_COST_DIFFERENCE = 16 * ERROR_COST_PER_SKIPPED_TREE;
-static const unsigned OP_COUNT_PER_TIMEOUT_CHECK = 100;
-
-typedef struct {
- Subtree token;
- Subtree last_external_token;
- uint32_t byte_index;
-} TokenCache;
-
-struct TSParser {
- Lexer lexer;
- Stack *stack;
- SubtreePool tree_pool;
- const TSLanguage *language;
- ReduceActionSet reduce_actions;
- Subtree finished_tree;
- SubtreeHeapData scratch_tree_data;
- MutableSubtree scratch_tree;
- TokenCache token_cache;
- ReusableNode reusable_node;
- void *external_scanner_payload;
- FILE *dot_graph_file;
- TSClock end_clock;
- TSDuration timeout_duration;
- unsigned accept_count;
- unsigned operation_count;
- const volatile size_t *cancellation_flag;
- Subtree old_tree;
- TSRangeArray included_range_differences;
- unsigned included_range_difference_index;
-};
-
-typedef struct {
- unsigned cost;
- unsigned node_count;
- int dynamic_precedence;
- bool is_in_error;
-} ErrorStatus;
-
-typedef enum {
- ErrorComparisonTakeLeft,
- ErrorComparisonPreferLeft,
- ErrorComparisonNone,
- ErrorComparisonPreferRight,
- ErrorComparisonTakeRight,
-} ErrorComparison;
-
-typedef struct {
- const char *string;
- uint32_t length;
-} TSStringInput;
-
-// StringInput
-
-static const char *ts_string_input_read(
- void *_self,
- uint32_t byte,
- TSPoint pt,
- uint32_t *length
-) {
- (void)pt;
- TSStringInput *self = (TSStringInput *)_self;
- if (byte >= self->length) {
- *length = 0;
- return "";
- } else {
- *length = self->length - byte;
- return self->string + byte;
- }
-}
-
-// Parser - Private
-
-static void ts_parser__log(TSParser *self) {
- if (self->lexer.logger.log) {
- self->lexer.logger.log(
- self->lexer.logger.payload,
- TSLogTypeParse,
- self->lexer.debug_buffer
- );
- }
-
- if (self->dot_graph_file) {
- fprintf(self->dot_graph_file, "graph {\nlabel=\"");
- for (char *c = &self->lexer.debug_buffer[0]; *c != 0; c++) {
- if (*c == '"') fputc('\\', self->dot_graph_file);
- fputc(*c, self->dot_graph_file);
- }
- fprintf(self->dot_graph_file, "\"\n}\n\n");
- }
-}
-
-static bool ts_parser__breakdown_top_of_stack(
- TSParser *self,
- StackVersion version
-) {
- bool did_break_down = false;
- bool pending = false;
-
- do {
- StackSliceArray pop = ts_stack_pop_pending(self->stack, version);
- if (!pop.size) break;
-
- did_break_down = true;
- pending = false;
- for (uint32_t i = 0; i < pop.size; i++) {
- StackSlice slice = pop.contents[i];
- TSStateId state = ts_stack_state(self->stack, slice.version);
- Subtree parent = *array_front(&slice.subtrees);
-
- for (uint32_t j = 0, n = ts_subtree_child_count(parent); j < n; j++) {
- Subtree child = parent.ptr->children[j];
- pending = ts_subtree_child_count(child) > 0;
-
- if (ts_subtree_is_error(child)) {
- state = ERROR_STATE;
- } else if (!ts_subtree_extra(child)) {
- state = ts_language_next_state(self->language, state, ts_subtree_symbol(child));
- }
-
- ts_subtree_retain(child);
- ts_stack_push(self->stack, slice.version, child, pending, state);
- }
-
- for (uint32_t j = 1; j < slice.subtrees.size; j++) {
- Subtree tree = slice.subtrees.contents[j];
- ts_stack_push(self->stack, slice.version, tree, false, state);
- }
-
- ts_subtree_release(&self->tree_pool, parent);
- array_delete(&slice.subtrees);
-
- LOG("breakdown_top_of_stack tree:%s", TREE_NAME(parent));
- LOG_STACK();
- }
- } while (pending);
-
- return did_break_down;
-}
-
-static void ts_parser__breakdown_lookahead(
- TSParser *self,
- Subtree *lookahead,
- TSStateId state,
- ReusableNode *reusable_node
-) {
- bool did_descend = false;
- Subtree tree = reusable_node_tree(reusable_node);
- while (ts_subtree_child_count(tree) > 0 && ts_subtree_parse_state(tree) != state) {
- LOG("state_mismatch sym:%s", TREE_NAME(tree));
- reusable_node_descend(reusable_node);
- tree = reusable_node_tree(reusable_node);
- did_descend = true;
- }
-
- if (did_descend) {
- ts_subtree_release(&self->tree_pool, *lookahead);
- *lookahead = tree;
- ts_subtree_retain(*lookahead);
- }
-}
-
-static ErrorComparison ts_parser__compare_versions(
- TSParser *self,
- ErrorStatus a,
- ErrorStatus b
-) {
- (void)self;
- if (!a.is_in_error && b.is_in_error) {
- if (a.cost < b.cost) {
- return ErrorComparisonTakeLeft;
- } else {
- return ErrorComparisonPreferLeft;
- }
- }
-
- if (a.is_in_error && !b.is_in_error) {
- if (b.cost < a.cost) {
- return ErrorComparisonTakeRight;
- } else {
- return ErrorComparisonPreferRight;
- }
- }
-
- if (a.cost < b.cost) {
- if ((b.cost - a.cost) * (1 + a.node_count) > MAX_COST_DIFFERENCE) {
- return ErrorComparisonTakeLeft;
- } else {
- return ErrorComparisonPreferLeft;
- }
- }
-
- if (b.cost < a.cost) {
- if ((a.cost - b.cost) * (1 + b.node_count) > MAX_COST_DIFFERENCE) {
- return ErrorComparisonTakeRight;
- } else {
- return ErrorComparisonPreferRight;
- }
- }
-
- if (a.dynamic_precedence > b.dynamic_precedence) return ErrorComparisonPreferLeft;
- if (b.dynamic_precedence > a.dynamic_precedence) return ErrorComparisonPreferRight;
- return ErrorComparisonNone;
-}
-
-static ErrorStatus ts_parser__version_status(
- TSParser *self,
- StackVersion version
-) {
- unsigned cost = ts_stack_error_cost(self->stack, version);
- bool is_paused = ts_stack_is_paused(self->stack, version);
- if (is_paused) cost += ERROR_COST_PER_SKIPPED_TREE;
- return (ErrorStatus) {
- .cost = cost,
- .node_count = ts_stack_node_count_since_error(self->stack, version),
- .dynamic_precedence = ts_stack_dynamic_precedence(self->stack, version),
- .is_in_error = is_paused || ts_stack_state(self->stack, version) == ERROR_STATE
- };
-}
-
-static bool ts_parser__better_version_exists(
- TSParser *self,
- StackVersion version,
- bool is_in_error,
- unsigned cost
-) {
- if (self->finished_tree.ptr && ts_subtree_error_cost(self->finished_tree) <= cost) {
- return true;
- }
-
- Length position = ts_stack_position(self->stack, version);
- ErrorStatus status = {
- .cost = cost,
- .is_in_error = is_in_error,
- .dynamic_precedence = ts_stack_dynamic_precedence(self->stack, version),
- .node_count = ts_stack_node_count_since_error(self->stack, version),
- };
-
- for (StackVersion i = 0, n = ts_stack_version_count(self->stack); i < n; i++) {
- if (i == version ||
- !ts_stack_is_active(self->stack, i) ||
- ts_stack_position(self->stack, i).bytes < position.bytes) continue;
- ErrorStatus status_i = ts_parser__version_status(self, i);
- switch (ts_parser__compare_versions(self, status, status_i)) {
- case ErrorComparisonTakeRight:
- return true;
- case ErrorComparisonPreferRight:
- if (ts_stack_can_merge(self->stack, i, version)) return true;
- default:
- break;
- }
- }
-
- return false;
-}
-
-static void ts_parser__restore_external_scanner(
- TSParser *self,
- Subtree external_token
-) {
- if (external_token.ptr) {
- self->language->external_scanner.deserialize(
- self->external_scanner_payload,
- ts_external_scanner_state_data(&external_token.ptr->external_scanner_state),
- external_token.ptr->external_scanner_state.length
- );
- } else {
- self->language->external_scanner.deserialize(self->external_scanner_payload, NULL, 0);
- }
-}
-
-static bool ts_parser__can_reuse_first_leaf(
- TSParser *self,
- TSStateId state,
- Subtree tree,
- TableEntry *table_entry
-) {
- TSLexMode current_lex_mode = self->language->lex_modes[state];
- TSSymbol leaf_symbol = ts_subtree_leaf_symbol(tree);
- TSStateId leaf_state = ts_subtree_leaf_parse_state(tree);
- TSLexMode leaf_lex_mode = self->language->lex_modes[leaf_state];
-
- // At the end of a non-terminal extra node, the lexer normally returns
- // NULL, which indicates that the parser should look for a reduce action
- // at symbol `0`. Avoid reusing tokens in this situation to ensure that
- // the same thing happens when incrementally reparsing.
- if (current_lex_mode.lex_state == (uint16_t)(-1)) return false;
-
- // If the token was created in a state with the same set of lookaheads, it is reusable.
- if (
- table_entry->action_count > 0 &&
- memcmp(&leaf_lex_mode, &current_lex_mode, sizeof(TSLexMode)) == 0 &&
- (
- leaf_symbol != self->language->keyword_capture_token ||
- (!ts_subtree_is_keyword(tree) && ts_subtree_parse_state(tree) == state)
- )
- ) return true;
-
- // Empty tokens are not reusable in states with different lookaheads.
- if (ts_subtree_size(tree).bytes == 0 && leaf_symbol != ts_builtin_sym_end) return false;
-
- // If the current state allows external tokens or other tokens that conflict with this
- // token, this token is not reusable.
- return current_lex_mode.external_lex_state == 0 && table_entry->is_reusable;
-}
-
-static Subtree ts_parser__lex(
- TSParser *self,
- StackVersion version,
- TSStateId parse_state
-) {
- Length start_position = ts_stack_position(self->stack, version);
- Subtree external_token = ts_stack_last_external_token(self->stack, version);
- TSLexMode lex_mode = self->language->lex_modes[parse_state];
- if (lex_mode.lex_state == (uint16_t)-1) return NULL_SUBTREE;
- const bool *valid_external_tokens = ts_language_enabled_external_tokens(
- self->language,
- lex_mode.external_lex_state
- );
-
- bool found_external_token = false;
- bool error_mode = parse_state == ERROR_STATE;
- bool skipped_error = false;
- int32_t first_error_character = 0;
- Length error_start_position = length_zero();
- Length error_end_position = length_zero();
- uint32_t lookahead_end_byte = 0;
- ts_lexer_reset(&self->lexer, start_position);
-
- for (;;) {
- Length current_position = self->lexer.current_position;
-
- if (valid_external_tokens) {
- LOG(
- "lex_external state:%d, row:%u, column:%u",
- lex_mode.external_lex_state,
- current_position.extent.row + 1,
- current_position.extent.column
- );
- ts_lexer_start(&self->lexer);
- ts_parser__restore_external_scanner(self, external_token);
- bool found_token = self->language->external_scanner.scan(
- self->external_scanner_payload,
- &self->lexer.data,
- valid_external_tokens
- );
- ts_lexer_finish(&self->lexer, &lookahead_end_byte);
-
- // Zero-length external tokens are generally allowed, but they're not
- // allowed right after a syntax error. This is for two reasons:
- // 1. After a syntax error, the lexer is looking for any possible token,
- // as opposed to the specific set of tokens that are valid in some
- // parse state. In this situation, it's very easy for an external
- // scanner to produce unwanted zero-length tokens.
- // 2. The parser sometimes inserts *missing* tokens to recover from
- // errors. These tokens are also zero-length. If we allow more
- // zero-length tokens to be created after missing tokens, it
- // can lead to infinite loops. Forbidding zero-length tokens
- // right at the point of error recovery is a conservative strategy
- // for preventing this kind of infinite loop.
- if (found_token && (
- self->lexer.token_end_position.bytes > current_position.bytes ||
- (!error_mode && ts_stack_has_advanced_since_error(self->stack, version))
- )) {
- found_external_token = true;
- break;
- }
-
- ts_lexer_reset(&self->lexer, current_position);
- }
-
- LOG(
- "lex_internal state:%d, row:%u, column:%u",
- lex_mode.lex_state,
- current_position.extent.row + 1,
- current_position.extent.column
- );
- ts_lexer_start(&self->lexer);
- bool found_token = self->language->lex_fn(&self->lexer.data, lex_mode.lex_state);
- ts_lexer_finish(&self->lexer, &lookahead_end_byte);
- if (found_token) break;
-
- if (!error_mode) {
- error_mode = true;
- lex_mode = self->language->lex_modes[ERROR_STATE];
- valid_external_tokens = ts_language_enabled_external_tokens(
- self->language,
- lex_mode.external_lex_state
- );
- ts_lexer_reset(&self->lexer, start_position);
- continue;
- }
-
- if (!skipped_error) {
- LOG("skip_unrecognized_character");
- skipped_error = true;
- error_start_position = self->lexer.token_start_position;
- error_end_position = self->lexer.token_start_position;
- first_error_character = self->lexer.data.lookahead;
- }
-
- if (self->lexer.current_position.bytes == error_end_position.bytes) {
- if (self->lexer.data.eof(&self->lexer.data)) {
- self->lexer.data.result_symbol = ts_builtin_sym_error;
- break;
- }
- self->lexer.data.advance(&self->lexer.data, false);
- }
-
- error_end_position = self->lexer.current_position;
- }
-
- Subtree result;
- if (skipped_error) {
- Length padding = length_sub(error_start_position, start_position);
- Length size = length_sub(error_end_position, error_start_position);
- uint32_t lookahead_bytes = lookahead_end_byte - error_end_position.bytes;
- result = ts_subtree_new_error(
- &self->tree_pool,
- first_error_character,
- padding,
- size,
- lookahead_bytes,
- parse_state,
- self->language
- );
-
- LOG(
- "lexed_lookahead sym:%s, size:%u, character:'%c'",
- SYM_NAME(ts_subtree_symbol(result)),
- ts_subtree_total_size(result).bytes,
- first_error_character
- );
- } else {
- if (self->lexer.token_end_position.bytes < self->lexer.token_start_position.bytes) {
- self->lexer.token_start_position = self->lexer.token_end_position;
- }
-
- bool is_keyword = false;
- TSSymbol symbol = self->lexer.data.result_symbol;
- Length padding = length_sub(self->lexer.token_start_position, start_position);
- Length size = length_sub(self->lexer.token_end_position, self->lexer.token_start_position);
- uint32_t lookahead_bytes = lookahead_end_byte - self->lexer.token_end_position.bytes;
-
- if (found_external_token) {
- symbol = self->language->external_scanner.symbol_map[symbol];
- } else if (symbol == self->language->keyword_capture_token && symbol != 0) {
- uint32_t end_byte = self->lexer.token_end_position.bytes;
- ts_lexer_reset(&self->lexer, self->lexer.token_start_position);
- ts_lexer_start(&self->lexer);
- if (
- self->language->keyword_lex_fn(&self->lexer.data, 0) &&
- self->lexer.token_end_position.bytes == end_byte &&
- ts_language_has_actions(self->language, parse_state, self->lexer.data.result_symbol)
- ) {
- is_keyword = true;
- symbol = self->lexer.data.result_symbol;
- }
- }
-
- result = ts_subtree_new_leaf(
- &self->tree_pool,
- symbol,
- padding,
- size,
- lookahead_bytes,
- parse_state,
- found_external_token,
- is_keyword,
- self->language
- );
-
- if (found_external_token) {
- unsigned length = self->language->external_scanner.serialize(
- self->external_scanner_payload,
- self->lexer.debug_buffer
- );
- ts_external_scanner_state_init(
- &((SubtreeHeapData *)result.ptr)->external_scanner_state,
- self->lexer.debug_buffer,
- length
- );
- }
-
- LOG(
- "lexed_lookahead sym:%s, size:%u",
- SYM_NAME(ts_subtree_symbol(result)),
- ts_subtree_total_size(result).bytes
- );
- }
-
- return result;
-}
-
-static Subtree ts_parser__get_cached_token(
- TSParser *self,
- TSStateId state,
- size_t position,
- Subtree last_external_token,
- TableEntry *table_entry
-) {
- TokenCache *cache = &self->token_cache;
- if (
- cache->token.ptr && cache->byte_index == position &&
- ts_subtree_external_scanner_state_eq(cache->last_external_token, last_external_token)
- ) {
- ts_language_table_entry(self->language, state, ts_subtree_symbol(cache->token), table_entry);
- if (ts_parser__can_reuse_first_leaf(self, state, cache->token, table_entry)) {
- ts_subtree_retain(cache->token);
- return cache->token;
- }
- }
- return NULL_SUBTREE;
-}
-
-static void ts_parser__set_cached_token(
- TSParser *self,
- size_t byte_index,
- Subtree last_external_token,
- Subtree token
-) {
- TokenCache *cache = &self->token_cache;
- if (token.ptr) ts_subtree_retain(token);
- if (last_external_token.ptr) ts_subtree_retain(last_external_token);
- if (cache->token.ptr) ts_subtree_release(&self->tree_pool, cache->token);
- if (cache->last_external_token.ptr) ts_subtree_release(&self->tree_pool, cache->last_external_token);
- cache->token = token;
- cache->byte_index = byte_index;
- cache->last_external_token = last_external_token;
-}
-
-static bool ts_parser__has_included_range_difference(
- const TSParser *self,
- uint32_t start_position,
- uint32_t end_position
-) {
- return ts_range_array_intersects(
- &self->included_range_differences,
- self->included_range_difference_index,
- start_position,
- end_position
- );
-}
-
-static Subtree ts_parser__reuse_node(
- TSParser *self,
- StackVersion version,
- TSStateId *state,
- uint32_t position,
- Subtree last_external_token,
- TableEntry *table_entry
-) {
- Subtree result;
- while ((result = reusable_node_tree(&self->reusable_node)).ptr) {
- uint32_t byte_offset = reusable_node_byte_offset(&self->reusable_node);
- uint32_t end_byte_offset = byte_offset + ts_subtree_total_bytes(result);
-
- // Do not reuse an EOF node if the included ranges array has changes
- // later on in the file.
- if (ts_subtree_is_eof(result)) end_byte_offset = UINT32_MAX;
-
- if (byte_offset > position) {
- LOG("before_reusable_node symbol:%s", TREE_NAME(result));
- break;
- }
-
- if (byte_offset < position) {
- LOG("past_reusable_node symbol:%s", TREE_NAME(result));
- if (end_byte_offset <= position || !reusable_node_descend(&self->reusable_node)) {
- reusable_node_advance(&self->reusable_node);
- }
- continue;
- }
-
- if (!ts_subtree_external_scanner_state_eq(self->reusable_node.last_external_token, last_external_token)) {
- LOG("reusable_node_has_different_external_scanner_state symbol:%s", TREE_NAME(result));
- reusable_node_advance(&self->reusable_node);
- continue;
- }
-
- const char *reason = NULL;
- if (ts_subtree_has_changes(result)) {
- reason = "has_changes";
- } else if (ts_subtree_is_error(result)) {
- reason = "is_error";
- } else if (ts_subtree_missing(result)) {
- reason = "is_missing";
- } else if (ts_subtree_is_fragile(result)) {
- reason = "is_fragile";
- } else if (ts_parser__has_included_range_difference(self, byte_offset, end_byte_offset)) {
- reason = "contains_different_included_range";
- }
-
- if (reason) {
- LOG("cant_reuse_node_%s tree:%s", reason, TREE_NAME(result));
- if (!reusable_node_descend(&self->reusable_node)) {
- reusable_node_advance(&self->reusable_node);
- ts_parser__breakdown_top_of_stack(self, version);
- *state = ts_stack_state(self->stack, version);
- }
- continue;
- }
-
- TSSymbol leaf_symbol = ts_subtree_leaf_symbol(result);
- ts_language_table_entry(self->language, *state, leaf_symbol, table_entry);
- if (!ts_parser__can_reuse_first_leaf(self, *state, result, table_entry)) {
- LOG(
- "cant_reuse_node symbol:%s, first_leaf_symbol:%s",
- TREE_NAME(result),
- SYM_NAME(leaf_symbol)
- );
- reusable_node_advance_past_leaf(&self->reusable_node);
- break;
- }
-
- LOG("reuse_node symbol:%s", TREE_NAME(result));
- ts_subtree_retain(result);
- return result;
- }
-
- return NULL_SUBTREE;
-}
-
-static bool ts_parser__select_tree(TSParser *self, Subtree left, Subtree right) {
- if (!left.ptr) return true;
- if (!right.ptr) return false;
-
- if (ts_subtree_error_cost(right) < ts_subtree_error_cost(left)) {
- LOG("select_smaller_error symbol:%s, over_symbol:%s", TREE_NAME(right), TREE_NAME(left));
- return true;
- }
-
- if (ts_subtree_error_cost(left) < ts_subtree_error_cost(right)) {
- LOG("select_smaller_error symbol:%s, over_symbol:%s", TREE_NAME(left), TREE_NAME(right));
- return false;
- }
-
- if (ts_subtree_dynamic_precedence(right) > ts_subtree_dynamic_precedence(left)) {
- LOG("select_higher_precedence symbol:%s, prec:%u, over_symbol:%s, other_prec:%u",
- TREE_NAME(right), ts_subtree_dynamic_precedence(right), TREE_NAME(left),
- ts_subtree_dynamic_precedence(left));
- return true;
- }
-
- if (ts_subtree_dynamic_precedence(left) > ts_subtree_dynamic_precedence(right)) {
- LOG("select_higher_precedence symbol:%s, prec:%u, over_symbol:%s, other_prec:%u",
- TREE_NAME(left), ts_subtree_dynamic_precedence(left), TREE_NAME(right),
- ts_subtree_dynamic_precedence(right));
- return false;
- }
-
- if (ts_subtree_error_cost(left) > 0) return true;
-
- int comparison = ts_subtree_compare(left, right);
- switch (comparison) {
- case -1:
- LOG("select_earlier symbol:%s, over_symbol:%s", TREE_NAME(left), TREE_NAME(right));
- return false;
- break;
- case 1:
- LOG("select_earlier symbol:%s, over_symbol:%s", TREE_NAME(right), TREE_NAME(left));
- return true;
- default:
- LOG("select_existing symbol:%s, over_symbol:%s", TREE_NAME(left), TREE_NAME(right));
- return false;
- }
-}
-
-static void ts_parser__shift(
- TSParser *self,
- StackVersion version,
- TSStateId state,
- Subtree lookahead,
- bool extra
-) {
- Subtree subtree_to_push;
- if (extra != ts_subtree_extra(lookahead)) {
- MutableSubtree result = ts_subtree_make_mut(&self->tree_pool, lookahead);
- ts_subtree_set_extra(&result);
- subtree_to_push = ts_subtree_from_mut(result);
- } else {
- subtree_to_push = lookahead;
- }
-
- bool is_pending = ts_subtree_child_count(subtree_to_push) > 0;
- ts_stack_push(self->stack, version, subtree_to_push, is_pending, state);
- if (ts_subtree_has_external_tokens(subtree_to_push)) {
- ts_stack_set_last_external_token(
- self->stack, version, ts_subtree_last_external_token(subtree_to_push)
- );
- }
-}
-
-static bool ts_parser__replace_children(
- TSParser *self,
- MutableSubtree *tree,
- SubtreeArray *children
-) {
- *self->scratch_tree.ptr = *tree->ptr;
- self->scratch_tree.ptr->child_count = 0;
- ts_subtree_set_children(self->scratch_tree, children->contents, children->size, self->language);
- if (ts_parser__select_tree(self, ts_subtree_from_mut(*tree), ts_subtree_from_mut(self->scratch_tree))) {
- *tree->ptr = *self->scratch_tree.ptr;
- return true;
- } else {
- return false;
- }
-}
-
-static StackVersion ts_parser__reduce(
- TSParser *self,
- StackVersion version,
- TSSymbol symbol,
- uint32_t count,
- int dynamic_precedence,
- uint16_t production_id,
- bool is_fragile,
- bool is_extra
-) {
- uint32_t initial_version_count = ts_stack_version_count(self->stack);
- uint32_t removed_version_count = 0;
- StackSliceArray pop = ts_stack_pop_count(self->stack, version, count);
-
- for (uint32_t i = 0; i < pop.size; i++) {
- StackSlice slice = pop.contents[i];
- StackVersion slice_version = slice.version - removed_version_count;
-
- // Error recovery can sometimes cause lots of stack versions to merge,
- // such that a single pop operation can produce a lots of slices.
- // Avoid creating too many stack versions in that situation.
- if (i > 0 && slice_version > MAX_VERSION_COUNT + MAX_VERSION_COUNT_OVERFLOW) {
- ts_stack_remove_version(self->stack, slice_version);
- ts_subtree_array_delete(&self->tree_pool, &slice.subtrees);
- removed_version_count++;
- while (i + 1 < pop.size) {
- StackSlice next_slice = pop.contents[i + 1];
- if (next_slice.version != slice.version) break;
- ts_subtree_array_delete(&self->tree_pool, &next_slice.subtrees);
- i++;
- }
- continue;
- }
-
- // Extra tokens on top of the stack should not be included in this new parent
- // node. They will be re-pushed onto the stack after the parent node is
- // created and pushed.
- SubtreeArray children = slice.subtrees;
- while (children.size > 0 && ts_subtree_extra(children.contents[children.size - 1])) {
- children.size--;
- }
-
- MutableSubtree parent = ts_subtree_new_node(&self->tree_pool,
- symbol, &children, production_id, self->language
- );
-
- // This pop operation may have caused multiple stack versions to collapse
- // into one, because they all diverged from a common state. In that case,
- // choose one of the arrays of trees to be the parent node's children, and
- // delete the rest of the tree arrays.
- while (i + 1 < pop.size) {
- StackSlice next_slice = pop.contents[i + 1];
- if (next_slice.version != slice.version) break;
- i++;
-
- SubtreeArray children = next_slice.subtrees;
- while (children.size > 0 && ts_subtree_extra(children.contents[children.size - 1])) {
- children.size--;
- }
-
- if (ts_parser__replace_children(self, &parent, &children)) {
- ts_subtree_array_delete(&self->tree_pool, &slice.subtrees);
- slice = next_slice;
- } else {
- ts_subtree_array_delete(&self->tree_pool, &next_slice.subtrees);
- }
- }
-
- parent.ptr->dynamic_precedence += dynamic_precedence;
- parent.ptr->production_id = production_id;
-
- TSStateId state = ts_stack_state(self->stack, slice_version);
- TSStateId next_state = ts_language_next_state(self->language, state, symbol);
- if (is_extra) parent.ptr->extra = true;
- if (is_fragile || pop.size > 1 || initial_version_count > 1) {
- parent.ptr->fragile_left = true;
- parent.ptr->fragile_right = true;
- parent.ptr->parse_state = TS_TREE_STATE_NONE;
- } else {
- parent.ptr->parse_state = state;
- }
-
- // Push the parent node onto the stack, along with any extra tokens that
- // were previously on top of the stack.
- ts_stack_push(self->stack, slice_version, ts_subtree_from_mut(parent), false, next_state);
- for (uint32_t j = parent.ptr->child_count; j < slice.subtrees.size; j++) {
- ts_stack_push(self->stack, slice_version, slice.subtrees.contents[j], false, next_state);
- }
-
- for (StackVersion j = 0; j < slice_version; j++) {
- if (j == version) continue;
- if (ts_stack_merge(self->stack, j, slice_version)) {
- removed_version_count++;
- break;
- }
- }
- }
-
- // Return the first new stack version that was created.
- return ts_stack_version_count(self->stack) > initial_version_count
- ? initial_version_count
- : STACK_VERSION_NONE;
-}
-
-static void ts_parser__accept(
- TSParser *self,
- StackVersion version,
- Subtree lookahead
-) {
- assert(ts_subtree_is_eof(lookahead));
- ts_stack_push(self->stack, version, lookahead, false, 1);
-
- StackSliceArray pop = ts_stack_pop_all(self->stack, version);
- for (uint32_t i = 0; i < pop.size; i++) {
- SubtreeArray trees = pop.contents[i].subtrees;
-
- Subtree root = NULL_SUBTREE;
- for (uint32_t j = trees.size - 1; j + 1 > 0; j--) {
- Subtree child = trees.contents[j];
- if (!ts_subtree_extra(child)) {
- assert(!child.data.is_inline);
- uint32_t child_count = ts_subtree_child_count(child);
- for (uint32_t k = 0; k < child_count; k++) {
- ts_subtree_retain(child.ptr->children[k]);
- }
- array_splice(&trees, j, 1, child_count, child.ptr->children);
- root = ts_subtree_from_mut(ts_subtree_new_node(
- &self->tree_pool,
- ts_subtree_symbol(child),
- &trees,
- child.ptr->production_id,
- self->language
- ));
- ts_subtree_release(&self->tree_pool, child);
- break;
- }
- }
-
- assert(root.ptr);
- self->accept_count++;
-
- if (self->finished_tree.ptr) {
- if (ts_parser__select_tree(self, self->finished_tree, root)) {
- ts_subtree_release(&self->tree_pool, self->finished_tree);
- self->finished_tree = root;
- } else {
- ts_subtree_release(&self->tree_pool, root);
- }
- } else {
- self->finished_tree = root;
- }
- }
-
- ts_stack_remove_version(self->stack, pop.contents[0].version);
- ts_stack_halt(self->stack, version);
-}
-
-static bool ts_parser__do_all_potential_reductions(
- TSParser *self,
- StackVersion starting_version,
- TSSymbol lookahead_symbol
-) {
- uint32_t initial_version_count = ts_stack_version_count(self->stack);
-
- bool can_shift_lookahead_symbol = false;
- StackVersion version = starting_version;
- for (unsigned i = 0; true; i++) {
- uint32_t version_count = ts_stack_version_count(self->stack);
- if (version >= version_count) break;
-
- bool merged = false;
- for (StackVersion i = initial_version_count; i < version; i++) {
- if (ts_stack_merge(self->stack, i, version)) {
- merged = true;
- break;
- }
- }
- if (merged) continue;
-
- TSStateId state = ts_stack_state(self->stack, version);
- bool has_shift_action = false;
- array_clear(&self->reduce_actions);
-
- TSSymbol first_symbol, end_symbol;
- if (lookahead_symbol != 0) {
- first_symbol = lookahead_symbol;
- end_symbol = lookahead_symbol + 1;
- } else {
- first_symbol = 1;
- end_symbol = self->language->token_count;
- }
-
- for (TSSymbol symbol = first_symbol; symbol < end_symbol; symbol++) {
- TableEntry entry;
- ts_language_table_entry(self->language, state, symbol, &entry);
- for (uint32_t i = 0; i < entry.action_count; i++) {
- TSParseAction action = entry.actions[i];
- switch (action.type) {
- case TSParseActionTypeShift:
- case TSParseActionTypeRecover:
- if (!action.params.shift.extra && !action.params.shift.repetition) has_shift_action = true;
- break;
- case TSParseActionTypeReduce:
- if (action.params.reduce.child_count > 0)
- ts_reduce_action_set_add(&self->reduce_actions, (ReduceAction){
- .symbol = action.params.reduce.symbol,
- .count = action.params.reduce.child_count,
- .dynamic_precedence = action.params.reduce.dynamic_precedence,
- .production_id = action.params.reduce.production_id,
- });
- default:
- break;
- }
- }
- }
-
- StackVersion reduction_version = STACK_VERSION_NONE;
- for (uint32_t i = 0; i < self->reduce_actions.size; i++) {
- ReduceAction action = self->reduce_actions.contents[i];
-
- reduction_version = ts_parser__reduce(
- self, version, action.symbol, action.count,
- action.dynamic_precedence, action.production_id,
- true, false
- );
- }
-
- if (has_shift_action) {
- can_shift_lookahead_symbol = true;
- } else if (reduction_version != STACK_VERSION_NONE && i < MAX_VERSION_COUNT) {
- ts_stack_renumber_version(self->stack, reduction_version, version);
- continue;
- } else if (lookahead_symbol != 0) {
- ts_stack_remove_version(self->stack, version);
- }
-
- if (version == starting_version) {
- version = version_count;
- } else {
- version++;
- }
- }
-
- return can_shift_lookahead_symbol;
-}
-
-static void ts_parser__handle_error(
- TSParser *self,
- StackVersion version,
- TSSymbol lookahead_symbol
-) {
- uint32_t previous_version_count = ts_stack_version_count(self->stack);
-
- // Perform any reductions that can happen in this state, regardless of the lookahead. After
- // skipping one or more invalid tokens, the parser might find a token that would have allowed
- // a reduction to take place.
- ts_parser__do_all_potential_reductions(self, version, 0);
- uint32_t version_count = ts_stack_version_count(self->stack);
- Length position = ts_stack_position(self->stack, version);
-
- // Push a discontinuity onto the stack. Merge all of the stack versions that
- // were created in the previous step.
- bool did_insert_missing_token = false;
- for (StackVersion v = version; v < version_count;) {
- if (!did_insert_missing_token) {
- TSStateId state = ts_stack_state(self->stack, v);
- for (TSSymbol missing_symbol = 1;
- missing_symbol < self->language->token_count;
- missing_symbol++) {
- TSStateId state_after_missing_symbol = ts_language_next_state(
- self->language, state, missing_symbol
- );
- if (state_after_missing_symbol == 0 || state_after_missing_symbol == state) {
- continue;
- }
-
- if (ts_language_has_reduce_action(
- self->language,
- state_after_missing_symbol,
- lookahead_symbol
- )) {
- // In case the parser is currently outside of any included range, the lexer will
- // snap to the beginning of the next included range. The missing token's padding
- // must be assigned to position it within the next included range.
- ts_lexer_reset(&self->lexer, position);
- ts_lexer_mark_end(&self->lexer);
- Length padding = length_sub(self->lexer.token_end_position, position);
-
- StackVersion version_with_missing_tree = ts_stack_copy_version(self->stack, v);
- Subtree missing_tree = ts_subtree_new_missing_leaf(
- &self->tree_pool, missing_symbol, padding, self->language
- );
- ts_stack_push(
- self->stack, version_with_missing_tree,
- missing_tree, false,
- state_after_missing_symbol
- );
-
- if (ts_parser__do_all_potential_reductions(
- self, version_with_missing_tree,
- lookahead_symbol
- )) {
- LOG(
- "recover_with_missing symbol:%s, state:%u",
- SYM_NAME(missing_symbol),
- ts_stack_state(self->stack, version_with_missing_tree)
- );
- did_insert_missing_token = true;
- break;
- }
- }
- }
- }
-
- ts_stack_push(self->stack, v, NULL_SUBTREE, false, ERROR_STATE);
- v = (v == version) ? previous_version_count : v + 1;
- }
-
- for (unsigned i = previous_version_count; i < version_count; i++) {
- bool did_merge = ts_stack_merge(self->stack, version, previous_version_count);
- assert(did_merge);
- }
-
- ts_stack_record_summary(self->stack, version, MAX_SUMMARY_DEPTH);
- LOG_STACK();
-}
-
-static bool ts_parser__recover_to_state(
- TSParser *self,
- StackVersion version,
- unsigned depth,
- TSStateId goal_state
-) {
- StackSliceArray pop = ts_stack_pop_count(self->stack, version, depth);
- StackVersion previous_version = STACK_VERSION_NONE;
-
- for (unsigned i = 0; i < pop.size; i++) {
- StackSlice slice = pop.contents[i];
-
- if (slice.version == previous_version) {
- ts_subtree_array_delete(&self->tree_pool, &slice.subtrees);
- array_erase(&pop, i--);
- continue;
- }
-
- if (ts_stack_state(self->stack, slice.version) != goal_state) {
- ts_stack_halt(self->stack, slice.version);
- ts_subtree_array_delete(&self->tree_pool, &slice.subtrees);
- array_erase(&pop, i--);
- continue;
- }
-
- SubtreeArray error_trees = ts_stack_pop_error(self->stack, slice.version);
- if (error_trees.size > 0) {
- assert(error_trees.size == 1);
- Subtree error_tree = error_trees.contents[0];
- uint32_t error_child_count = ts_subtree_child_count(error_tree);
- if (error_child_count > 0) {
- array_splice(&slice.subtrees, 0, 0, error_child_count, error_tree.ptr->children);
- for (unsigned j = 0; j < error_child_count; j++) {
- ts_subtree_retain(slice.subtrees.contents[j]);
- }
- }
- ts_subtree_array_delete(&self->tree_pool, &error_trees);
- }
-
- SubtreeArray trailing_extras = ts_subtree_array_remove_trailing_extras(&slice.subtrees);
-
- if (slice.subtrees.size > 0) {
- Subtree error = ts_subtree_new_error_node(&self->tree_pool, &slice.subtrees, true, self->language);
- ts_stack_push(self->stack, slice.version, error, false, goal_state);
- } else {
- array_delete(&slice.subtrees);
- }
-
- for (unsigned j = 0; j < trailing_extras.size; j++) {
- Subtree tree = trailing_extras.contents[j];
- ts_stack_push(self->stack, slice.version, tree, false, goal_state);
- }
-
- previous_version = slice.version;
- array_delete(&trailing_extras);
- }
-
- return previous_version != STACK_VERSION_NONE;
-}
-
-static void ts_parser__recover(
- TSParser *self,
- StackVersion version,
- Subtree lookahead
-) {
- bool did_recover = false;
- unsigned previous_version_count = ts_stack_version_count(self->stack);
- Length position = ts_stack_position(self->stack, version);
- StackSummary *summary = ts_stack_get_summary(self->stack, version);
- unsigned node_count_since_error = ts_stack_node_count_since_error(self->stack, version);
- unsigned current_error_cost = ts_stack_error_cost(self->stack, version);
-
- // When the parser is in the error state, there are two strategies for recovering with a
- // given lookahead token:
- // 1. Find a previous state on the stack in which that lookahead token would be valid. Then,
- // create a new stack version that is in that state again. This entails popping all of the
- // subtrees that have been pushed onto the stack since that previous state, and wrapping
- // them in an ERROR node.
- // 2. Wrap the lookahead token in an ERROR node, push that ERROR node onto the stack, and
- // move on to the next lookahead token, remaining in the error state.
- //
- // First, try the strategy 1. Upon entering the error state, the parser recorded a summary
- // of the previous parse states and their depths. Look at each state in the summary, to see
- // if the current lookahead token would be valid in that state.
- if (summary && !ts_subtree_is_error(lookahead)) {
- for (unsigned i = 0; i < summary->size; i++) {
- StackSummaryEntry entry = summary->contents[i];
-
- if (entry.state == ERROR_STATE) continue;
- if (entry.position.bytes == position.bytes) continue;
- unsigned depth = entry.depth;
- if (node_count_since_error > 0) depth++;
-
- // Do not recover in ways that create redundant stack versions.
- bool would_merge = false;
- for (unsigned j = 0; j < previous_version_count; j++) {
- if (
- ts_stack_state(self->stack, j) == entry.state &&
- ts_stack_position(self->stack, j).bytes == position.bytes
- ) {
- would_merge = true;
- break;
- }
- }
- if (would_merge) continue;
-
- // Do not recover if the result would clearly be worse than some existing stack version.
- unsigned new_cost =
- current_error_cost +
- entry.depth * ERROR_COST_PER_SKIPPED_TREE +
- (position.bytes - entry.position.bytes) * ERROR_COST_PER_SKIPPED_CHAR +
- (position.extent.row - entry.position.extent.row) * ERROR_COST_PER_SKIPPED_LINE;
- if (ts_parser__better_version_exists(self, version, false, new_cost)) break;
-
- // If the current lookahead token is valid in some previous state, recover to that state.
- // Then stop looking for further recoveries.
- if (ts_language_has_actions(self->language, entry.state, ts_subtree_symbol(lookahead))) {
- if (ts_parser__recover_to_state(self, version, depth, entry.state)) {
- did_recover = true;
- LOG("recover_to_previous state:%u, depth:%u", entry.state, depth);
- LOG_STACK();
- break;
- }
- }
- }
- }
-
- // In the process of attemping to recover, some stack versions may have been created
- // and subsequently halted. Remove those versions.
- for (unsigned i = previous_version_count; i < ts_stack_version_count(self->stack); i++) {
- if (!ts_stack_is_active(self->stack, i)) {
- ts_stack_remove_version(self->stack, i--);
- }
- }
-
- // If strategy 1 succeeded, a new stack version will have been created which is able to handle
- // the current lookahead token. Now, in addition, try strategy 2 described above: skip the
- // current lookahead token by wrapping it in an ERROR node.
-
- // Don't pursue this additional strategy if there are already too many stack versions.
- if (did_recover && ts_stack_version_count(self->stack) > MAX_VERSION_COUNT) {
- ts_stack_halt(self->stack, version);
- ts_subtree_release(&self->tree_pool, lookahead);
- return;
- }
-
- // If the parser is still in the error state at the end of the file, just wrap everything
- // in an ERROR node and terminate.
- if (ts_subtree_is_eof(lookahead)) {
- LOG("recover_eof");
- SubtreeArray children = array_new();
- Subtree parent = ts_subtree_new_error_node(&self->tree_pool, &children, false, self->language);
- ts_stack_push(self->stack, version, parent, false, 1);
- ts_parser__accept(self, version, lookahead);
- return;
- }
-
- // Do not recover if the result would clearly be worse than some existing stack version.
- unsigned new_cost =
- current_error_cost + ERROR_COST_PER_SKIPPED_TREE +
- ts_subtree_total_bytes(lookahead) * ERROR_COST_PER_SKIPPED_CHAR +
- ts_subtree_total_size(lookahead).extent.row * ERROR_COST_PER_SKIPPED_LINE;
- if (ts_parser__better_version_exists(self, version, false, new_cost)) {
- ts_stack_halt(self->stack, version);
- ts_subtree_release(&self->tree_pool, lookahead);
- return;
- }
-
- // If the current lookahead token is an extra token, mark it as extra. This means it won't
- // be counted in error cost calculations.
- unsigned n;
- const TSParseAction *actions = ts_language_actions(self->language, 1, ts_subtree_symbol(lookahead), &n);
- if (n > 0 && actions[n - 1].type == TSParseActionTypeShift && actions[n - 1].params.shift.extra) {
- MutableSubtree mutable_lookahead = ts_subtree_make_mut(&self->tree_pool, lookahead);
- ts_subtree_set_extra(&mutable_lookahead);
- lookahead = ts_subtree_from_mut(mutable_lookahead);
- }
-
- // Wrap the lookahead token in an ERROR.
- LOG("skip_token symbol:%s", TREE_NAME(lookahead));
- SubtreeArray children = array_new();
- array_reserve(&children, 1);
- array_push(&children, lookahead);
- MutableSubtree error_repeat = ts_subtree_new_node(
- &self->tree_pool,
- ts_builtin_sym_error_repeat,
- &children,
- 0,
- self->language
- );
-
- // If other tokens have already been skipped, so there is already an ERROR at the top of the
- // stack, then pop that ERROR off the stack and wrap the two ERRORs together into one larger
- // ERROR.
- if (node_count_since_error > 0) {
- StackSliceArray pop = ts_stack_pop_count(self->stack, version, 1);
-
- // TODO: Figure out how to make this condition occur.
- // See https://github.com/atom/atom/issues/18450#issuecomment-439579778
- // If multiple stack versions have merged at this point, just pick one of the errors
- // arbitrarily and discard the rest.
- if (pop.size > 1) {
- for (unsigned i = 1; i < pop.size; i++) {
- ts_subtree_array_delete(&self->tree_pool, &pop.contents[i].subtrees);
- }
- while (ts_stack_version_count(self->stack) > pop.contents[0].version + 1) {
- ts_stack_remove_version(self->stack, pop.contents[0].version + 1);
- }
- }
-
- ts_stack_renumber_version(self->stack, pop.contents[0].version, version);
- array_push(&pop.contents[0].subtrees, ts_subtree_from_mut(error_repeat));
- error_repeat = ts_subtree_new_node(
- &self->tree_pool,
- ts_builtin_sym_error_repeat,
- &pop.contents[0].subtrees,
- 0,
- self->language
- );
- }
-
- // Push the new ERROR onto the stack.
- ts_stack_push(self->stack, version, ts_subtree_from_mut(error_repeat), false, ERROR_STATE);
- if (ts_subtree_has_external_tokens(lookahead)) {
- ts_stack_set_last_external_token(
- self->stack, version, ts_subtree_last_external_token(lookahead)
- );
- }
-}
-
-static bool ts_parser__advance(
- TSParser *self,
- StackVersion version,
- bool allow_node_reuse
-) {
- TSStateId state = ts_stack_state(self->stack, version);
- uint32_t position = ts_stack_position(self->stack, version).bytes;
- Subtree last_external_token = ts_stack_last_external_token(self->stack, version);
-
- bool did_reuse = true;
- Subtree lookahead = NULL_SUBTREE;
- TableEntry table_entry = {.action_count = 0};
-
- // If possible, reuse a node from the previous syntax tree.
- if (allow_node_reuse) {
- lookahead = ts_parser__reuse_node(
- self, version, &state, position, last_external_token, &table_entry
- );
- }
-
- // If no node from the previous syntax tree could be reused, then try to
- // reuse the token previously returned by the lexer.
- if (!lookahead.ptr) {
- did_reuse = false;
- lookahead = ts_parser__get_cached_token(
- self, state, position, last_external_token, &table_entry
- );
- }
-
- // Otherwise, re-run the lexer.
- if (!lookahead.ptr) {
- lookahead = ts_parser__lex(self, version, state);
- if (lookahead.ptr) {
- ts_parser__set_cached_token(self, position, last_external_token, lookahead);
- ts_language_table_entry(self->language, state, ts_subtree_symbol(lookahead), &table_entry);
- }
-
- // When parsing a non-terminal extra, a null lookahead indicates the
- // end of the rule. The reduction is stored in the EOF table entry.
- // After the reduction, the lexer needs to be run again.
- else {
- ts_language_table_entry(self->language, state, ts_builtin_sym_end, &table_entry);
- }
- }
-
- for (;;) {
- // If a cancellation flag or a timeout was provided, then check every
- // time a fixed number of parse actions has been processed.
- if (++self->operation_count == OP_COUNT_PER_TIMEOUT_CHECK) {
- self->operation_count = 0;
- }
- if (
- self->operation_count == 0 &&
- ((self->cancellation_flag && atomic_load(self->cancellation_flag)) ||
- (!clock_is_null(self->end_clock) && clock_is_gt(clock_now(), self->end_clock)))
- ) {
- ts_subtree_release(&self->tree_pool, lookahead);
- return false;
- }
-
- // Process each parse action for the current lookahead token in
- // the current state. If there are multiple actions, then this is
- // an ambiguous state. REDUCE actions always create a new stack
- // version, whereas SHIFT actions update the existing stack version
- // and terminate this loop.
- StackVersion last_reduction_version = STACK_VERSION_NONE;
- for (uint32_t i = 0; i < table_entry.action_count; i++) {
- TSParseAction action = table_entry.actions[i];
-
- switch (action.type) {
- case TSParseActionTypeShift: {
- if (action.params.shift.repetition) break;
- TSStateId next_state;
- if (action.params.shift.extra) {
-
- // TODO: remove when TREE_SITTER_LANGUAGE_VERSION 9 is out.
- if (state == ERROR_STATE) continue;
-
- next_state = state;
- LOG("shift_extra");
- } else {
- next_state = action.params.shift.state;
- LOG("shift state:%u", next_state);
- }
-
- if (ts_subtree_child_count(lookahead) > 0) {
- ts_parser__breakdown_lookahead(self, &lookahead, state, &self->reusable_node);
- next_state = ts_language_next_state(self->language, state, ts_subtree_symbol(lookahead));
- }
-
- ts_parser__shift(self, version, next_state, lookahead, action.params.shift.extra);
- if (did_reuse) reusable_node_advance(&self->reusable_node);
- return true;
- }
-
- case TSParseActionTypeReduce: {
- bool is_fragile = table_entry.action_count > 1;
- bool is_extra = lookahead.ptr == NULL;
- LOG("reduce sym:%s, child_count:%u", SYM_NAME(action.params.reduce.symbol), action.params.reduce.child_count);
- StackVersion reduction_version = ts_parser__reduce(
- self, version, action.params.reduce.symbol, action.params.reduce.child_count,
- action.params.reduce.dynamic_precedence, action.params.reduce.production_id,
- is_fragile, is_extra
- );
- if (reduction_version != STACK_VERSION_NONE) {
- last_reduction_version = reduction_version;
- }
- break;
- }
-
- case TSParseActionTypeAccept: {
- LOG("accept");
- ts_parser__accept(self, version, lookahead);
- return true;
- }
-
- case TSParseActionTypeRecover: {
- if (ts_subtree_child_count(lookahead) > 0) {
- ts_parser__breakdown_lookahead(self, &lookahead, ERROR_STATE, &self->reusable_node);
- }
-
- ts_parser__recover(self, version, lookahead);
- if (did_reuse) reusable_node_advance(&self->reusable_node);
- return true;
- }
- }
- }
-
- // If a reduction was performed, then replace the current stack version
- // with one of the stack versions created by a reduction, and continue
- // processing this version of the stack with the same lookahead symbol.
- if (last_reduction_version != STACK_VERSION_NONE) {
- ts_stack_renumber_version(self->stack, last_reduction_version, version);
- LOG_STACK();
- state = ts_stack_state(self->stack, version);
-
- // At the end of a non-terminal extra rule, the lexer will return a
- // null subtree, because the parser needs to perform a fixed reduction
- // regardless of the lookahead node. After performing that reduction,
- // (and completing the non-terminal extra rule) run the lexer again based
- // on the current parse state.
- if (!lookahead.ptr) {
- lookahead = ts_parser__lex(self, version, state);
- }
- ts_language_table_entry(
- self->language,
- state,
- ts_subtree_leaf_symbol(lookahead),
- &table_entry
- );
- continue;
- }
-
- // If there were no parse actions for the current lookahead token, then
- // it is not valid in this state. If the current lookahead token is a
- // keyword, then switch to treating it as the normal word token if that
- // token is valid in this state.
- if (
- ts_subtree_is_keyword(lookahead) &&
- ts_subtree_symbol(lookahead) != self->language->keyword_capture_token
- ) {
- ts_language_table_entry(self->language, state, self->language->keyword_capture_token, &table_entry);
- if (table_entry.action_count > 0) {
- LOG(
- "switch from_keyword:%s, to_word_token:%s",
- TREE_NAME(lookahead),
- SYM_NAME(self->language->keyword_capture_token)
- );
-
- MutableSubtree mutable_lookahead = ts_subtree_make_mut(&self->tree_pool, lookahead);
- ts_subtree_set_symbol(&mutable_lookahead, self->language->keyword_capture_token, self->language);
- lookahead = ts_subtree_from_mut(mutable_lookahead);
- continue;
- }
- }
-
- // If the current lookahead token is not valid and the parser is
- // already in the error state, restart the error recovery process.
- // TODO - can this be unified with the other `RECOVER` case above?
- if (state == ERROR_STATE) {
- ts_parser__recover(self, version, lookahead);
- return true;
- }
-
- // If the current lookahead token is not valid and the previous
- // subtree on the stack was reused from an old tree, it isn't actually
- // valid to reuse it. Remove it from the stack, and in its place,
- // push each of its children. Then try again to process the current
- // lookahead.
- if (ts_parser__breakdown_top_of_stack(self, version)) {
- continue;
- }
-
- // At this point, the current lookahead token is definitely not valid
- // for this parse stack version. Mark this version as paused and continue
- // processing any other stack versions that might exist. If some other
- // version advances successfully, then this version can simply be removed.
- // But if all versions end up paused, then error recovery is needed.
- LOG("detect_error");
- ts_stack_pause(self->stack, version, ts_subtree_leaf_symbol(lookahead));
- ts_subtree_release(&self->tree_pool, lookahead);
- return true;
- }
-}
-
-static unsigned ts_parser__condense_stack(TSParser *self) {
- bool made_changes = false;
- unsigned min_error_cost = UINT_MAX;
- for (StackVersion i = 0; i < ts_stack_version_count(self->stack); i++) {
- // Prune any versions that have been marked for removal.
- if (ts_stack_is_halted(self->stack, i)) {
- ts_stack_remove_version(self->stack, i);
- i--;
- continue;
- }
-
- // Keep track of the minimum error cost of any stack version so
- // that it can be returned.
- ErrorStatus status_i = ts_parser__version_status(self, i);
- if (!status_i.is_in_error && status_i.cost < min_error_cost) {
- min_error_cost = status_i.cost;
- }
-
- // Examine each pair of stack versions, removing any versions that
- // are clearly worse than another version. Ensure that the versions
- // are ordered from most promising to least promising.
- for (StackVersion j = 0; j < i; j++) {
- ErrorStatus status_j = ts_parser__version_status(self, j);
-
- switch (ts_parser__compare_versions(self, status_j, status_i)) {
- case ErrorComparisonTakeLeft:
- made_changes = true;
- ts_stack_remove_version(self->stack, i);
- i--;
- j = i;
- break;
-
- case ErrorComparisonPreferLeft:
- case ErrorComparisonNone:
- if (ts_stack_merge(self->stack, j, i)) {
- made_changes = true;
- i--;
- j = i;
- }
- break;
-
- case ErrorComparisonPreferRight:
- made_changes = true;
- if (ts_stack_merge(self->stack, j, i)) {
- i--;
- j = i;
- } else {
- ts_stack_swap_versions(self->stack, i, j);
- }
- break;
-
- case ErrorComparisonTakeRight:
- made_changes = true;
- ts_stack_remove_version(self->stack, j);
- i--;
- j--;
- break;
- }
- }
- }
-
- // Enfore a hard upper bound on the number of stack versions by
- // discarding the least promising versions.
- while (ts_stack_version_count(self->stack) > MAX_VERSION_COUNT) {
- ts_stack_remove_version(self->stack, MAX_VERSION_COUNT);
- made_changes = true;
- }
-
- // If the best-performing stack version is currently paused, or all
- // versions are paused, then resume the best paused version and begin
- // the error recovery process. Otherwise, remove the paused versions.
- if (ts_stack_version_count(self->stack) > 0) {
- bool has_unpaused_version = false;
- for (StackVersion i = 0, n = ts_stack_version_count(self->stack); i < n; i++) {
- if (ts_stack_is_paused(self->stack, i)) {
- if (!has_unpaused_version && self->accept_count < MAX_VERSION_COUNT) {
- LOG("resume version:%u", i);
- min_error_cost = ts_stack_error_cost(self->stack, i);
- TSSymbol lookahead_symbol = ts_stack_resume(self->stack, i);
- ts_parser__handle_error(self, i, lookahead_symbol);
- has_unpaused_version = true;
- } else {
- ts_stack_remove_version(self->stack, i);
- i--;
- n--;
- }
- } else {
- has_unpaused_version = true;
- }
- }
- }
-
- if (made_changes) {
- LOG("condense");
- LOG_STACK();
- }
-
- return min_error_cost;
-}
-
-static bool ts_parser_has_outstanding_parse(TSParser *self) {
- return (
- ts_stack_state(self->stack, 0) != 1 ||
- ts_stack_node_count_since_error(self->stack, 0) != 0
- );
-}
-
-// Parser - Public
-
-TSParser *ts_parser_new(void) {
- TSParser *self = ts_calloc(1, sizeof(TSParser));
- ts_lexer_init(&self->lexer);
- array_init(&self->reduce_actions);
- array_reserve(&self->reduce_actions, 4);
- self->tree_pool = ts_subtree_pool_new(32);
- self->stack = ts_stack_new(&self->tree_pool);
- self->finished_tree = NULL_SUBTREE;
- self->reusable_node = reusable_node_new();
- self->dot_graph_file = NULL;
- self->cancellation_flag = NULL;
- self->timeout_duration = 0;
- self->end_clock = clock_null();
- self->operation_count = 0;
- self->old_tree = NULL_SUBTREE;
- self->scratch_tree.ptr = &self->scratch_tree_data;
- self->included_range_differences = (TSRangeArray) array_new();
- self->included_range_difference_index = 0;
- ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE);
- return self;
-}
-
-void ts_parser_delete(TSParser *self) {
- if (!self) return;
-
- ts_parser_set_language(self, NULL);
- ts_stack_delete(self->stack);
- if (self->reduce_actions.contents) {
- array_delete(&self->reduce_actions);
- }
- if (self->included_range_differences.contents) {
- array_delete(&self->included_range_differences);
- }
- if (self->old_tree.ptr) {
- ts_subtree_release(&self->tree_pool, self->old_tree);
- self->old_tree = NULL_SUBTREE;
- }
- ts_lexer_delete(&self->lexer);
- ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE);
- ts_subtree_pool_delete(&self->tree_pool);
- reusable_node_delete(&self->reusable_node);
- ts_free(self);
-}
-
-const TSLanguage *ts_parser_language(const TSParser *self) {
- return self->language;
-}
-
-bool ts_parser_set_language(TSParser *self, const TSLanguage *language) {
- if (language) {
- if (language->version > TREE_SITTER_LANGUAGE_VERSION) return false;
- if (language->version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION) return false;
- }
-
- if (self->external_scanner_payload && self->language->external_scanner.destroy) {
- self->language->external_scanner.destroy(self->external_scanner_payload);
- }
-
- if (language && language->external_scanner.create) {
- self->external_scanner_payload = language->external_scanner.create();
- } else {
- self->external_scanner_payload = NULL;
- }
-
- self->language = language;
- ts_parser_reset(self);
- return true;
-}
-
-TSLogger ts_parser_logger(const TSParser *self) {
- return self->lexer.logger;
-}
-
-void ts_parser_set_logger(TSParser *self, TSLogger logger) {
- self->lexer.logger = logger;
-}
-
-void ts_parser_print_dot_graphs(TSParser *self, int fd) {
- if (self->dot_graph_file) {
- fclose(self->dot_graph_file);
- }
-
- if (fd >= 0) {
- self->dot_graph_file = fdopen(fd, "a");
- } else {
- self->dot_graph_file = NULL;
- }
-}
-
-const size_t *ts_parser_cancellation_flag(const TSParser *self) {
- return (const size_t *)self->cancellation_flag;
-}
-
-void ts_parser_set_cancellation_flag(TSParser *self, const size_t *flag) {
- self->cancellation_flag = (const volatile size_t *)flag;
-}
-
-uint64_t ts_parser_timeout_micros(const TSParser *self) {
- return duration_to_micros(self->timeout_duration);
-}
-
-void ts_parser_set_timeout_micros(TSParser *self, uint64_t timeout_micros) {
- self->timeout_duration = duration_from_micros(timeout_micros);
-}
-
-bool ts_parser_set_included_ranges(
- TSParser *self,
- const TSRange *ranges,
- uint32_t count
-) {
- return ts_lexer_set_included_ranges(&self->lexer, ranges, count);
-}
-
-const TSRange *ts_parser_included_ranges(const TSParser *self, uint32_t *count) {
- return ts_lexer_included_ranges(&self->lexer, count);
-}
-
-void ts_parser_reset(TSParser *self) {
- if (self->language && self->language->external_scanner.deserialize) {
- self->language->external_scanner.deserialize(self->external_scanner_payload, NULL, 0);
- }
-
- if (self->old_tree.ptr) {
- ts_subtree_release(&self->tree_pool, self->old_tree);
- self->old_tree = NULL_SUBTREE;
- }
-
- reusable_node_clear(&self->reusable_node);
- ts_lexer_reset(&self->lexer, length_zero());
- ts_stack_clear(self->stack);
- ts_parser__set_cached_token(self, 0, NULL_SUBTREE, NULL_SUBTREE);
- if (self->finished_tree.ptr) {
- ts_subtree_release(&self->tree_pool, self->finished_tree);
- self->finished_tree = NULL_SUBTREE;
- }
- self->accept_count = 0;
-}
-
-TSTree *ts_parser_parse(
- TSParser *self,
- const TSTree *old_tree,
- TSInput input
-) {
- if (!self->language || !input.read) return NULL;
-
- ts_lexer_set_input(&self->lexer, input);
-
- array_clear(&self->included_range_differences);
- self->included_range_difference_index = 0;
-
- if (ts_parser_has_outstanding_parse(self)) {
- LOG("resume_parsing");
- } else if (old_tree) {
- ts_subtree_retain(old_tree->root);
- self->old_tree = old_tree->root;
- ts_range_array_get_changed_ranges(
- old_tree->included_ranges, old_tree->included_range_count,
- self->lexer.included_ranges, self->lexer.included_range_count,
- &self->included_range_differences
- );
- reusable_node_reset(&self->reusable_node, old_tree->root);
- LOG("parse_after_edit");
- LOG_TREE(self->old_tree);
- for (unsigned i = 0; i < self->included_range_differences.size; i++) {
- TSRange *range = &self->included_range_differences.contents[i];
- LOG("different_included_range %u - %u", range->start_byte, range->end_byte);
- }
- } else {
- reusable_node_clear(&self->reusable_node);
- LOG("new_parse");
- }
-
- uint32_t position = 0, last_position = 0, version_count = 0;
- self->operation_count = 0;
- if (self->timeout_duration) {
- self->end_clock = clock_after(clock_now(), self->timeout_duration);
- } else {
- self->end_clock = clock_null();
- }
-
- do {
- for (StackVersion version = 0;
- version_count = ts_stack_version_count(self->stack), version < version_count;
- version++) {
- bool allow_node_reuse = version_count == 1;
- while (ts_stack_is_active(self->stack, version)) {
- LOG("process version:%d, version_count:%u, state:%d, row:%u, col:%u",
- version, ts_stack_version_count(self->stack),
- ts_stack_state(self->stack, version),
- ts_stack_position(self->stack, version).extent.row + 1,
- ts_stack_position(self->stack, version).extent.column);
-
- if (!ts_parser__advance(self, version, allow_node_reuse)) return NULL;
- LOG_STACK();
-
- position = ts_stack_position(self->stack, version).bytes;
- if (position > last_position || (version > 0 && position == last_position)) {
- last_position = position;
- break;
- }
- }
- }
-
- unsigned min_error_cost = ts_parser__condense_stack(self);
- if (self->finished_tree.ptr && ts_subtree_error_cost(self->finished_tree) < min_error_cost) {
- break;
- }
-
- while (self->included_range_difference_index < self->included_range_differences.size) {
- TSRange *range = &self->included_range_differences.contents[self->included_range_difference_index];
- if (range->end_byte <= position) {
- self->included_range_difference_index++;
- } else {
- break;
- }
- }
- } while (version_count != 0);
-
- ts_subtree_balance(self->finished_tree, &self->tree_pool, self->language);
- LOG("done");
- LOG_TREE(self->finished_tree);
-
- TSTree *result = ts_tree_new(
- self->finished_tree,
- self->language,
- self->lexer.included_ranges,
- self->lexer.included_range_count
- );
- self->finished_tree = NULL_SUBTREE;
- ts_parser_reset(self);
- return result;
-}
-
-TSTree *ts_parser_parse_string(
- TSParser *self,
- const TSTree *old_tree,
- const char *string,
- uint32_t length
-) {
- return ts_parser_parse_string_encoding(self, old_tree, string, length, TSInputEncodingUTF8);
-}
-
-TSTree *ts_parser_parse_string_encoding(TSParser *self, const TSTree *old_tree,
- const char *string, uint32_t length, TSInputEncoding encoding) {
- TSStringInput input = {string, length};
- return ts_parser_parse(self, old_tree, (TSInput) {
- &input,
- ts_string_input_read,
- encoding,
- });
-}
-
-#undef LOG
diff --git a/src/tree_sitter/parser.h b/src/tree_sitter/parser.h
deleted file mode 100644
index 11bf4fc42a..0000000000
--- a/src/tree_sitter/parser.h
+++ /dev/null
@@ -1,235 +0,0 @@
-#ifndef TREE_SITTER_PARSER_H_
-#define TREE_SITTER_PARSER_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <stdbool.h>
-#include <stdint.h>
-#include <stdlib.h>
-
-#define ts_builtin_sym_error ((TSSymbol)-1)
-#define ts_builtin_sym_end 0
-#define TREE_SITTER_SERIALIZATION_BUFFER_SIZE 1024
-
-#ifndef TREE_SITTER_API_H_
-typedef uint16_t TSSymbol;
-typedef uint16_t TSFieldId;
-typedef struct TSLanguage TSLanguage;
-#endif
-
-typedef struct {
- TSFieldId field_id;
- uint8_t child_index;
- bool inherited;
-} TSFieldMapEntry;
-
-typedef struct {
- uint16_t index;
- uint16_t length;
-} TSFieldMapSlice;
-
-typedef uint16_t TSStateId;
-
-typedef struct {
- bool visible : 1;
- bool named : 1;
-} TSSymbolMetadata;
-
-typedef struct TSLexer TSLexer;
-
-struct TSLexer {
- int32_t lookahead;
- TSSymbol result_symbol;
- void (*advance)(TSLexer *, bool);
- void (*mark_end)(TSLexer *);
- uint32_t (*get_column)(TSLexer *);
- bool (*is_at_included_range_start)(const TSLexer *);
- bool (*eof)(const TSLexer *);
-};
-
-typedef enum {
- TSParseActionTypeShift,
- TSParseActionTypeReduce,
- TSParseActionTypeAccept,
- TSParseActionTypeRecover,
-} TSParseActionType;
-
-typedef struct {
- union {
- struct {
- TSStateId state;
- bool extra : 1;
- bool repetition : 1;
- } shift;
- struct {
- TSSymbol symbol;
- int16_t dynamic_precedence;
- uint8_t child_count;
- uint8_t production_id;
- } reduce;
- } params;
- TSParseActionType type : 4;
-} TSParseAction;
-
-typedef struct {
- uint16_t lex_state;
- uint16_t external_lex_state;
-} TSLexMode;
-
-typedef union {
- TSParseAction action;
- struct {
- uint8_t count;
- bool reusable : 1;
- } entry;
-} TSParseActionEntry;
-
-struct TSLanguage {
- uint32_t version;
- uint32_t symbol_count;
- uint32_t alias_count;
- uint32_t token_count;
- uint32_t external_token_count;
- const char **symbol_names;
- const TSSymbolMetadata *symbol_metadata;
- const uint16_t *parse_table;
- const TSParseActionEntry *parse_actions;
- const TSLexMode *lex_modes;
- const TSSymbol *alias_sequences;
- uint16_t max_alias_sequence_length;
- bool (*lex_fn)(TSLexer *, TSStateId);
- bool (*keyword_lex_fn)(TSLexer *, TSStateId);
- TSSymbol keyword_capture_token;
- struct {
- const bool *states;
- const TSSymbol *symbol_map;
- void *(*create)(void);
- void (*destroy)(void *);
- bool (*scan)(void *, TSLexer *, const bool *symbol_whitelist);
- unsigned (*serialize)(void *, char *);
- void (*deserialize)(void *, const char *, unsigned);
- } external_scanner;
- uint32_t field_count;
- const TSFieldMapSlice *field_map_slices;
- const TSFieldMapEntry *field_map_entries;
- const char **field_names;
- uint32_t large_state_count;
- const uint16_t *small_parse_table;
- const uint32_t *small_parse_table_map;
- const TSSymbol *public_symbol_map;
-};
-
-/*
- * Lexer Macros
- */
-
-#define START_LEXER() \
- bool result = false; \
- bool skip = false; \
- bool eof = false; \
- int32_t lookahead; \
- goto start; \
- next_state: \
- lexer->advance(lexer, skip); \
- start: \
- skip = false; \
- lookahead = lexer->lookahead;
-
-#define ADVANCE(state_value) \
- { \
- state = state_value; \
- goto next_state; \
- }
-
-#define SKIP(state_value) \
- { \
- skip = true; \
- state = state_value; \
- goto next_state; \
- }
-
-#define ACCEPT_TOKEN(symbol_value) \
- result = true; \
- lexer->result_symbol = symbol_value; \
- lexer->mark_end(lexer);
-
-#define END_STATE() return result;
-
-/*
- * Parse Table Macros
- */
-
-#define SMALL_STATE(id) id - LARGE_STATE_COUNT
-
-#define STATE(id) id
-
-#define ACTIONS(id) id
-
-#define SHIFT(state_value) \
- { \
- { \
- .params = { \
- .shift = { \
- .state = state_value \
- } \
- }, \
- .type = TSParseActionTypeShift \
- } \
- }
-
-#define SHIFT_REPEAT(state_value) \
- { \
- { \
- .params = { \
- .shift = { \
- .state = state_value, \
- .repetition = true \
- } \
- }, \
- .type = TSParseActionTypeShift \
- } \
- }
-
-#define RECOVER() \
- { \
- { .type = TSParseActionTypeRecover } \
- }
-
-#define SHIFT_EXTRA() \
- { \
- { \
- .params = { \
- .shift = { \
- .extra = true \
- } \
- }, \
- .type = TSParseActionTypeShift \
- } \
- }
-
-#define REDUCE(symbol_val, child_count_val, ...) \
- { \
- { \
- .params = { \
- .reduce = { \
- .symbol = symbol_val, \
- .child_count = child_count_val, \
- __VA_ARGS__ \
- }, \
- }, \
- .type = TSParseActionTypeReduce \
- } \
- }
-
-#define ACCEPT_INPUT() \
- { \
- { .type = TSParseActionTypeAccept } \
- }
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // TREE_SITTER_PARSER_H_
diff --git a/src/tree_sitter/point.h b/src/tree_sitter/point.h
deleted file mode 100644
index a50d20214b..0000000000
--- a/src/tree_sitter/point.h
+++ /dev/null
@@ -1,54 +0,0 @@
-#ifndef TREE_SITTER_POINT_H_
-#define TREE_SITTER_POINT_H_
-
-#include "tree_sitter/api.h"
-
-#define POINT_ZERO ((TSPoint) {0, 0})
-#define POINT_MAX ((TSPoint) {UINT32_MAX, UINT32_MAX})
-
-static inline TSPoint point__new(unsigned row, unsigned column) {
- TSPoint result = {row, column};
- return result;
-}
-
-static inline TSPoint point_add(TSPoint a, TSPoint b) {
- if (b.row > 0)
- return point__new(a.row + b.row, b.column);
- else
- return point__new(a.row, a.column + b.column);
-}
-
-static inline TSPoint point_sub(TSPoint a, TSPoint b) {
- if (a.row > b.row)
- return point__new(a.row - b.row, a.column);
- else
- return point__new(0, a.column - b.column);
-}
-
-static inline bool point_lte(TSPoint a, TSPoint b) {
- return (a.row < b.row) || (a.row == b.row && a.column <= b.column);
-}
-
-static inline bool point_lt(TSPoint a, TSPoint b) {
- return (a.row < b.row) || (a.row == b.row && a.column < b.column);
-}
-
-static inline bool point_eq(TSPoint a, TSPoint b) {
- return a.row == b.row && a.column == b.column;
-}
-
-static inline TSPoint point_min(TSPoint a, TSPoint b) {
- if (a.row < b.row || (a.row == b.row && a.column < b.column))
- return a;
- else
- return b;
-}
-
-static inline TSPoint point_max(TSPoint a, TSPoint b) {
- if (a.row > b.row || (a.row == b.row && a.column > b.column))
- return a;
- else
- return b;
-}
-
-#endif
diff --git a/src/tree_sitter/query.c b/src/tree_sitter/query.c
deleted file mode 100644
index 59902dee3b..0000000000
--- a/src/tree_sitter/query.c
+++ /dev/null
@@ -1,2035 +0,0 @@
-#include "tree_sitter/api.h"
-#include "./alloc.h"
-#include "./array.h"
-#include "./bits.h"
-#include "./language.h"
-#include "./point.h"
-#include "./tree_cursor.h"
-#include "./unicode.h"
-#include <wctype.h>
-
-// #define LOG(...) fprintf(stderr, __VA_ARGS__)
-#define LOG(...)
-
-#define MAX_STATE_COUNT 256
-#define MAX_CAPTURE_LIST_COUNT 32
-#define MAX_STEP_CAPTURE_COUNT 3
-
-/*
- * Stream - A sequence of unicode characters derived from a UTF8 string.
- * This struct is used in parsing queries from S-expressions.
- */
-typedef struct {
- const char *input;
- const char *end;
- int32_t next;
- uint8_t next_size;
-} Stream;
-
-/*
- * QueryStep - A step in the process of matching a query. Each node within
- * a query S-expression maps to one of these steps. An entire pattern is
- * represented as a sequence of these steps. Fields:
- *
- * - `symbol` - The grammar symbol to match. A zero value represents the
- * wildcard symbol, '_'.
- * - `field` - The field name to match. A zero value means that a field name
- * was not specified.
- * - `capture_ids` - An array of integers representing the names of captures
- * associated with this node in the pattern, terminated by a `NONE` value.
- * - `depth` - The depth where this node occurs in the pattern. The root node
- * of the pattern has depth zero.
- * - `alternative_index` - The index of a different query step that serves as
- * an alternative to this step.
- */
-typedef struct {
- TSSymbol symbol;
- TSFieldId field;
- uint16_t capture_ids[MAX_STEP_CAPTURE_COUNT];
- uint16_t alternative_index;
- uint16_t depth;
- bool contains_captures: 1;
- bool is_pattern_start: 1;
- bool is_immediate: 1;
- bool is_last_child: 1;
- bool is_pass_through: 1;
- bool is_dead_end: 1;
- bool alternative_is_immediate: 1;
-} QueryStep;
-
-/*
- * Slice - A slice of an external array. Within a query, capture names,
- * literal string values, and predicate step informations are stored in three
- * contiguous arrays. Individual captures, string values, and predicates are
- * represented as slices of these three arrays.
- */
-typedef struct {
- uint32_t offset;
- uint32_t length;
-} Slice;
-
-/*
- * SymbolTable - a two-way mapping of strings to ids.
- */
-typedef struct {
- Array(char) characters;
- Array(Slice) slices;
-} SymbolTable;
-
-/*
- * PatternEntry - Information about the starting point for matching a
- * particular pattern, consisting of the index of the pattern within the query,
- * and the index of the patter's first step in the shared `steps` array. These
- * entries are stored in a 'pattern map' - a sorted array that makes it
- * possible to efficiently lookup patterns based on the symbol for their first
- * step.
- */
-typedef struct {
- uint16_t step_index;
- uint16_t pattern_index;
-} PatternEntry;
-
-/*
- * QueryState - The state of an in-progress match of a particular pattern
- * in a query. While executing, a `TSQueryCursor` must keep track of a number
- * of possible in-progress matches. Each of those possible matches is
- * represented as one of these states. Fields:
- * - `id` - A numeric id that is exposed to the public API. This allows the
- * caller to remove a given match, preventing any more of its captures
- * from being returned.
- * - `start_depth` - The depth in the tree where the first step of the state's
- * pattern was matched.
- * - `pattern_index` - The pattern that the state is matching.
- * - `consumed_capture_count` - The number of captures from this match that
- * have already been returned.
- * - `capture_list_id` - A numeric id that can be used to retrieve the state's
- * list of captures from the `CaptureListPool`.
- * - `seeking_immediate_match` - A flag that indicates that the state's next
- * step must be matched by the very next sibling. This is used when
- * processing repetitions.
- * - `has_in_progress_alternatives` - A flag that indicates that there is are
- * other states that have the same captures as this state, but are at
- * different steps in their pattern. This means that in order to obey the
- * 'longest-match' rule, this state should not be returned as a match until
- * it is clear that there can be no longer match.
- */
-typedef struct {
- uint32_t id;
- uint16_t start_depth;
- uint16_t step_index;
- uint16_t pattern_index;
- uint16_t capture_list_id;
- uint16_t consumed_capture_count: 14;
- bool seeking_immediate_match: 1;
- bool has_in_progress_alternatives: 1;
-} QueryState;
-
-typedef Array(TSQueryCapture) CaptureList;
-
-/*
- * CaptureListPool - A collection of *lists* of captures. Each QueryState
- * needs to maintain its own list of captures. To avoid repeated allocations,
- * the reuses a fixed set of capture lists, and keeps track of which ones
- * are currently in use.
- */
-typedef struct {
- CaptureList list[MAX_CAPTURE_LIST_COUNT];
- CaptureList empty_list;
- uint32_t usage_map;
-} CaptureListPool;
-
-/*
- * TSQuery - A tree query, compiled from a string of S-expressions. The query
- * itself is immutable. The mutable state used in the process of executing the
- * query is stored in a `TSQueryCursor`.
- */
-struct TSQuery {
- SymbolTable captures;
- SymbolTable predicate_values;
- Array(QueryStep) steps;
- Array(PatternEntry) pattern_map;
- Array(TSQueryPredicateStep) predicate_steps;
- Array(Slice) predicates_by_pattern;
- Array(uint32_t) start_bytes_by_pattern;
- const TSLanguage *language;
- uint16_t wildcard_root_pattern_count;
- TSSymbol *symbol_map;
-};
-
-/*
- * TSQueryCursor - A stateful struct used to execute a query on a tree.
- */
-struct TSQueryCursor {
- const TSQuery *query;
- TSTreeCursor cursor;
- Array(QueryState) states;
- Array(QueryState) finished_states;
- CaptureListPool capture_list_pool;
- uint32_t depth;
- uint32_t start_byte;
- uint32_t end_byte;
- uint32_t next_state_id;
- TSPoint start_point;
- TSPoint end_point;
- bool ascending;
-};
-
-static const TSQueryError PARENT_DONE = -1;
-static const uint16_t PATTERN_DONE_MARKER = UINT16_MAX;
-static const uint16_t NONE = UINT16_MAX;
-static const TSSymbol WILDCARD_SYMBOL = 0;
-static const TSSymbol NAMED_WILDCARD_SYMBOL = UINT16_MAX - 1;
-
-/**********
- * Stream
- **********/
-
-// Advance to the next unicode code point in the stream.
-static bool stream_advance(Stream *self) {
- self->input += self->next_size;
- if (self->input < self->end) {
- uint32_t size = ts_decode_utf8(
- (const uint8_t *)self->input,
- self->end - self->input,
- &self->next
- );
- if (size > 0) {
- self->next_size = size;
- return true;
- }
- } else {
- self->next_size = 0;
- self->next = '\0';
- }
- return false;
-}
-
-// Reset the stream to the given input position, represented as a pointer
-// into the input string.
-static void stream_reset(Stream *self, const char *input) {
- self->input = input;
- self->next_size = 0;
- stream_advance(self);
-}
-
-static Stream stream_new(const char *string, uint32_t length) {
- Stream self = {
- .next = 0,
- .input = string,
- .end = string + length,
- };
- stream_advance(&self);
- return self;
-}
-
-static void stream_skip_whitespace(Stream *stream) {
- for (;;) {
- if (iswspace(stream->next)) {
- stream_advance(stream);
- } else if (stream->next == ';') {
- // skip over comments
- stream_advance(stream);
- while (stream->next && stream->next != '\n') {
- if (!stream_advance(stream)) break;
- }
- } else {
- break;
- }
- }
-}
-
-static bool stream_is_ident_start(Stream *stream) {
- return iswalnum(stream->next) || stream->next == '_' || stream->next == '-';
-}
-
-static void stream_scan_identifier(Stream *stream) {
- do {
- stream_advance(stream);
- } while (
- iswalnum(stream->next) ||
- stream->next == '_' ||
- stream->next == '-' ||
- stream->next == '.' ||
- stream->next == '?' ||
- stream->next == '!'
- );
-}
-
-/******************
- * CaptureListPool
- ******************/
-
-static CaptureListPool capture_list_pool_new(void) {
- return (CaptureListPool) {
- .empty_list = array_new(),
- .usage_map = UINT32_MAX,
- };
-}
-
-static void capture_list_pool_reset(CaptureListPool *self) {
- self->usage_map = UINT32_MAX;
- for (unsigned i = 0; i < MAX_CAPTURE_LIST_COUNT; i++) {
- array_clear(&self->list[i]);
- }
-}
-
-static void capture_list_pool_delete(CaptureListPool *self) {
- for (unsigned i = 0; i < MAX_CAPTURE_LIST_COUNT; i++) {
- array_delete(&self->list[i]);
- }
-}
-
-static const CaptureList *capture_list_pool_get(const CaptureListPool *self, uint16_t id) {
- if (id >= MAX_CAPTURE_LIST_COUNT) return &self->empty_list;
- return &self->list[id];
-}
-
-static CaptureList *capture_list_pool_get_mut(CaptureListPool *self, uint16_t id) {
- assert(id < MAX_CAPTURE_LIST_COUNT);
- return &self->list[id];
-}
-
-static bool capture_list_pool_is_empty(const CaptureListPool *self) {
- return self->usage_map == 0;
-}
-
-static uint16_t capture_list_pool_acquire(CaptureListPool *self) {
- // In the usage_map bitmask, ones represent free lists, and zeros represent
- // lists that are in use. A free list id can quickly be found by counting
- // the leading zeros in the usage map. An id of zero corresponds to the
- // highest-order bit in the bitmask.
- uint16_t id = count_leading_zeros(self->usage_map);
- if (id >= MAX_CAPTURE_LIST_COUNT) return NONE;
- self->usage_map &= ~bitmask_for_index(id);
- array_clear(&self->list[id]);
- return id;
-}
-
-static void capture_list_pool_release(CaptureListPool *self, uint16_t id) {
- if (id >= MAX_CAPTURE_LIST_COUNT) return;
- array_clear(&self->list[id]);
- self->usage_map |= bitmask_for_index(id);
-}
-
-/**************
- * SymbolTable
- **************/
-
-static SymbolTable symbol_table_new(void) {
- return (SymbolTable) {
- .characters = array_new(),
- .slices = array_new(),
- };
-}
-
-static void symbol_table_delete(SymbolTable *self) {
- array_delete(&self->characters);
- array_delete(&self->slices);
-}
-
-static int symbol_table_id_for_name(
- const SymbolTable *self,
- const char *name,
- uint32_t length
-) {
- for (unsigned i = 0; i < self->slices.size; i++) {
- Slice slice = self->slices.contents[i];
- if (
- slice.length == length &&
- !strncmp(&self->characters.contents[slice.offset], name, length)
- ) return i;
- }
- return -1;
-}
-
-static const char *symbol_table_name_for_id(
- const SymbolTable *self,
- uint16_t id,
- uint32_t *length
-) {
- Slice slice = self->slices.contents[id];
- *length = slice.length;
- return &self->characters.contents[slice.offset];
-}
-
-static uint16_t symbol_table_insert_name(
- SymbolTable *self,
- const char *name,
- uint32_t length
-) {
- int id = symbol_table_id_for_name(self, name, length);
- if (id >= 0) return (uint16_t)id;
- Slice slice = {
- .offset = self->characters.size,
- .length = length,
- };
- array_grow_by(&self->characters, length + 1);
- memcpy(&self->characters.contents[slice.offset], name, length);
- self->characters.contents[self->characters.size - 1] = 0;
- array_push(&self->slices, slice);
- return self->slices.size - 1;
-}
-
-static uint16_t symbol_table_insert_name_with_escapes(
- SymbolTable *self,
- const char *escaped_name,
- uint32_t escaped_length
-) {
- Slice slice = {
- .offset = self->characters.size,
- .length = 0,
- };
- array_grow_by(&self->characters, escaped_length + 1);
-
- // Copy the contents of the literal into the characters buffer, processing escape
- // sequences like \n and \". This needs to be done before checking if the literal
- // is already present, in order to do the string comparison.
- bool is_escaped = false;
- for (unsigned i = 0; i < escaped_length; i++) {
- const char *src = &escaped_name[i];
- char *dest = &self->characters.contents[slice.offset + slice.length];
- if (is_escaped) {
- switch (*src) {
- case 'n':
- *dest = '\n';
- break;
- case 'r':
- *dest = '\r';
- break;
- case 't':
- *dest = '\t';
- break;
- case '0':
- *dest = '\0';
- break;
- default:
- *dest = *src;
- break;
- }
- is_escaped = false;
- slice.length++;
- } else {
- if (*src == '\\') {
- is_escaped = true;
- } else {
- *dest = *src;
- slice.length++;
- }
- }
- }
-
- // If the string is already present, remove the redundant content from the characters
- // buffer and return the existing id.
- int id = symbol_table_id_for_name(self, &self->characters.contents[slice.offset], slice.length);
- if (id >= 0) {
- self->characters.size -= (escaped_length + 1);
- return id;
- }
-
- self->characters.contents[slice.offset + slice.length] = 0;
- array_push(&self->slices, slice);
- return self->slices.size - 1;
-}
-
-/************
- * QueryStep
- ************/
-
-static QueryStep query_step__new(
- TSSymbol symbol,
- uint16_t depth,
- bool is_immediate
-) {
- return (QueryStep) {
- .symbol = symbol,
- .depth = depth,
- .field = 0,
- .capture_ids = {NONE, NONE, NONE},
- .alternative_index = NONE,
- .contains_captures = false,
- .is_last_child = false,
- .is_pattern_start = false,
- .is_pass_through = false,
- .is_dead_end = false,
- .is_immediate = is_immediate,
- .alternative_is_immediate = false,
- };
-}
-
-static void query_step__add_capture(QueryStep *self, uint16_t capture_id) {
- for (unsigned i = 0; i < MAX_STEP_CAPTURE_COUNT; i++) {
- if (self->capture_ids[i] == NONE) {
- self->capture_ids[i] = capture_id;
- break;
- }
- }
-}
-
-static void query_step__remove_capture(QueryStep *self, uint16_t capture_id) {
- for (unsigned i = 0; i < MAX_STEP_CAPTURE_COUNT; i++) {
- if (self->capture_ids[i] == capture_id) {
- self->capture_ids[i] = NONE;
- while (i + 1 < MAX_STEP_CAPTURE_COUNT) {
- if (self->capture_ids[i + 1] == NONE) break;
- self->capture_ids[i] = self->capture_ids[i + 1];
- self->capture_ids[i + 1] = NONE;
- i++;
- }
- break;
- }
- }
-}
-
-/*********
- * Query
- *********/
-
-// The `pattern_map` contains a mapping from TSSymbol values to indices in the
-// `steps` array. For a given syntax node, the `pattern_map` makes it possible
-// to quickly find the starting steps of all of the patterns whose root matches
-// that node. Each entry has two fields: a `pattern_index`, which identifies one
-// of the patterns in the query, and a `step_index`, which indicates the start
-// offset of that pattern's steps within the `steps` array.
-//
-// The entries are sorted by the patterns' root symbols, and lookups use a
-// binary search. This ensures that the cost of this initial lookup step
-// scales logarithmically with the number of patterns in the query.
-//
-// This returns `true` if the symbol is present and `false` otherwise.
-// If the symbol is not present `*result` is set to the index where the
-// symbol should be inserted.
-static inline bool ts_query__pattern_map_search(
- const TSQuery *self,
- TSSymbol needle,
- uint32_t *result
-) {
- uint32_t base_index = self->wildcard_root_pattern_count;
- uint32_t size = self->pattern_map.size - base_index;
- if (size == 0) {
- *result = base_index;
- return false;
- }
- while (size > 1) {
- uint32_t half_size = size / 2;
- uint32_t mid_index = base_index + half_size;
- TSSymbol mid_symbol = self->steps.contents[
- self->pattern_map.contents[mid_index].step_index
- ].symbol;
- if (needle > mid_symbol) base_index = mid_index;
- size -= half_size;
- }
-
- TSSymbol symbol = self->steps.contents[
- self->pattern_map.contents[base_index].step_index
- ].symbol;
-
- if (needle > symbol) {
- base_index++;
- if (base_index < self->pattern_map.size) {
- symbol = self->steps.contents[
- self->pattern_map.contents[base_index].step_index
- ].symbol;
- }
- }
-
- *result = base_index;
- return needle == symbol;
-}
-
-// Insert a new pattern's start index into the pattern map, maintaining
-// the pattern map's ordering invariant.
-static inline void ts_query__pattern_map_insert(
- TSQuery *self,
- TSSymbol symbol,
- uint32_t start_step_index,
- uint32_t pattern_index
-) {
- uint32_t index;
- ts_query__pattern_map_search(self, symbol, &index);
- array_insert(&self->pattern_map, index, ((PatternEntry) {
- .step_index = start_step_index,
- .pattern_index = pattern_index,
- }));
-}
-
-static void ts_query__finalize_steps(TSQuery *self) {
- for (unsigned i = 0; i < self->steps.size; i++) {
- QueryStep *step = &self->steps.contents[i];
- uint32_t depth = step->depth;
- if (step->capture_ids[0] != NONE) {
- step->contains_captures = true;
- } else {
- step->contains_captures = false;
- for (unsigned j = i + 1; j < self->steps.size; j++) {
- QueryStep *s = &self->steps.contents[j];
- if (s->depth == PATTERN_DONE_MARKER || s->depth <= depth) break;
- if (s->capture_ids[0] != NONE) step->contains_captures = true;
- }
- }
- }
-}
-
-// Parse a single predicate associated with a pattern, adding it to the
-// query's internal `predicate_steps` array. Predicates are arbitrary
-// S-expressions associated with a pattern which are meant to be handled at
-// a higher level of abstraction, such as the Rust/JavaScript bindings. They
-// can contain '@'-prefixed capture names, double-quoted strings, and bare
-// symbols, which also represent strings.
-static TSQueryError ts_query__parse_predicate(
- TSQuery *self,
- Stream *stream
-) {
- if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax;
- const char *predicate_name = stream->input;
- stream_scan_identifier(stream);
- uint32_t length = stream->input - predicate_name;
- uint16_t id = symbol_table_insert_name(
- &self->predicate_values,
- predicate_name,
- length
- );
- array_back(&self->predicates_by_pattern)->length++;
- array_push(&self->predicate_steps, ((TSQueryPredicateStep) {
- .type = TSQueryPredicateStepTypeString,
- .value_id = id,
- }));
- stream_skip_whitespace(stream);
-
- for (;;) {
- if (stream->next == ')') {
- stream_advance(stream);
- stream_skip_whitespace(stream);
- array_back(&self->predicates_by_pattern)->length++;
- array_push(&self->predicate_steps, ((TSQueryPredicateStep) {
- .type = TSQueryPredicateStepTypeDone,
- .value_id = 0,
- }));
- break;
- }
-
- // Parse an '@'-prefixed capture name
- else if (stream->next == '@') {
- stream_advance(stream);
-
- // Parse the capture name
- if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax;
- const char *capture_name = stream->input;
- stream_scan_identifier(stream);
- uint32_t length = stream->input - capture_name;
-
- // Add the capture id to the first step of the pattern
- int capture_id = symbol_table_id_for_name(
- &self->captures,
- capture_name,
- length
- );
- if (capture_id == -1) {
- stream_reset(stream, capture_name);
- return TSQueryErrorCapture;
- }
-
- array_back(&self->predicates_by_pattern)->length++;
- array_push(&self->predicate_steps, ((TSQueryPredicateStep) {
- .type = TSQueryPredicateStepTypeCapture,
- .value_id = capture_id,
- }));
- }
-
- // Parse a string literal
- else if (stream->next == '"') {
- stream_advance(stream);
-
- // Parse the string content
- bool is_escaped = false;
- const char *string_content = stream->input;
- for (;;) {
- if (is_escaped) {
- is_escaped = false;
- } else {
- if (stream->next == '\\') {
- is_escaped = true;
- } else if (stream->next == '"') {
- break;
- } else if (stream->next == '\n') {
- stream_reset(stream, string_content - 1);
- return TSQueryErrorSyntax;
- }
- }
- if (!stream_advance(stream)) {
- stream_reset(stream, string_content - 1);
- return TSQueryErrorSyntax;
- }
- }
- uint32_t length = stream->input - string_content;
-
- // Add a step for the node
- uint16_t id = symbol_table_insert_name_with_escapes(
- &self->predicate_values,
- string_content,
- length
- );
- array_back(&self->predicates_by_pattern)->length++;
- array_push(&self->predicate_steps, ((TSQueryPredicateStep) {
- .type = TSQueryPredicateStepTypeString,
- .value_id = id,
- }));
-
- if (stream->next != '"') return TSQueryErrorSyntax;
- stream_advance(stream);
- }
-
- // Parse a bare symbol
- else if (stream_is_ident_start(stream)) {
- const char *symbol_start = stream->input;
- stream_scan_identifier(stream);
- uint32_t length = stream->input - symbol_start;
- uint16_t id = symbol_table_insert_name(
- &self->predicate_values,
- symbol_start,
- length
- );
- array_back(&self->predicates_by_pattern)->length++;
- array_push(&self->predicate_steps, ((TSQueryPredicateStep) {
- .type = TSQueryPredicateStepTypeString,
- .value_id = id,
- }));
- }
-
- else {
- return TSQueryErrorSyntax;
- }
-
- stream_skip_whitespace(stream);
- }
-
- return 0;
-}
-
-// Read one S-expression pattern from the stream, and incorporate it into
-// the query's internal state machine representation. For nested patterns,
-// this function calls itself recursively.
-static TSQueryError ts_query__parse_pattern(
- TSQuery *self,
- Stream *stream,
- uint32_t depth,
- uint32_t *capture_count,
- bool is_immediate
-) {
- uint32_t starting_step_index = self->steps.size;
-
- if (stream->next == 0) return TSQueryErrorSyntax;
-
- // Finish the parent S-expression.
- if (stream->next == ')' || stream->next == ']') {
- return PARENT_DONE;
- }
-
- // An open bracket is the start of an alternation.
- else if (stream->next == '[') {
- stream_advance(stream);
- stream_skip_whitespace(stream);
-
- // Parse each branch, and add a placeholder step in between the branches.
- Array(uint32_t) branch_step_indices = array_new();
- for (;;) {
- uint32_t start_index = self->steps.size;
- TSQueryError e = ts_query__parse_pattern(
- self,
- stream,
- depth,
- capture_count,
- is_immediate
- );
-
- if (e == PARENT_DONE && stream->next == ']' && branch_step_indices.size > 0) {
- stream_advance(stream);
- break;
- } else if (e) {
- array_delete(&branch_step_indices);
- return e;
- }
-
- array_push(&branch_step_indices, start_index);
- array_push(&self->steps, query_step__new(0, depth, false));
- }
- (void)array_pop(&self->steps);
-
- // For all of the branches except for the last one, add the subsequent branch as an
- // alternative, and link the end of the branch to the current end of the steps.
- for (unsigned i = 0; i < branch_step_indices.size - 1; i++) {
- uint32_t step_index = branch_step_indices.contents[i];
- uint32_t next_step_index = branch_step_indices.contents[i + 1];
- QueryStep *start_step = &self->steps.contents[step_index];
- QueryStep *end_step = &self->steps.contents[next_step_index - 1];
- start_step->alternative_index = next_step_index;
- end_step->alternative_index = self->steps.size;
- end_step->is_dead_end = true;
- }
-
- array_delete(&branch_step_indices);
- }
-
- // An open parenthesis can be the start of three possible constructs:
- // * A grouped sequence
- // * A predicate
- // * A named node
- else if (stream->next == '(') {
- stream_advance(stream);
- stream_skip_whitespace(stream);
-
- // If this parenthesis is followed by a node, then it represents a grouped sequence.
- if (stream->next == '(' || stream->next == '"' || stream->next == '[') {
- bool child_is_immediate = false;
- for (;;) {
- if (stream->next == '.') {
- child_is_immediate = true;
- stream_advance(stream);
- stream_skip_whitespace(stream);
- }
- TSQueryError e = ts_query__parse_pattern(
- self,
- stream,
- depth,
- capture_count,
- child_is_immediate
- );
- if (e == PARENT_DONE && stream->next == ')') {
- stream_advance(stream);
- break;
- } else if (e) {
- return e;
- }
-
- child_is_immediate = false;
- }
- }
-
- // A pound character indicates the start of a predicate.
- else if (stream->next == '#') {
- stream_advance(stream);
- return ts_query__parse_predicate(self, stream);
- }
-
- // Otherwise, this parenthesis is the start of a named node.
- else {
- TSSymbol symbol;
-
- // Parse the wildcard symbol
- if (
- stream->next == '_' ||
-
- // TODO - remove.
- // For temporary backward compatibility, handle '*' as a wildcard.
- stream->next == '*'
- ) {
- symbol = depth > 0 ? NAMED_WILDCARD_SYMBOL : WILDCARD_SYMBOL;
- stream_advance(stream);
- }
-
- // Parse a normal node name
- else if (stream_is_ident_start(stream)) {
- const char *node_name = stream->input;
- stream_scan_identifier(stream);
- uint32_t length = stream->input - node_name;
-
- // TODO - remove.
- // For temporary backward compatibility, handle predicates without the leading '#' sign.
- if (length > 0 && (node_name[length - 1] == '!' || node_name[length - 1] == '?')) {
- stream_reset(stream, node_name);
- return ts_query__parse_predicate(self, stream);
- }
-
- symbol = ts_language_symbol_for_name(
- self->language,
- node_name,
- length,
- true
- );
- if (!symbol) {
- stream_reset(stream, node_name);
- return TSQueryErrorNodeType;
- }
- } else {
- return TSQueryErrorSyntax;
- }
-
- // Add a step for the node.
- array_push(&self->steps, query_step__new(symbol, depth, is_immediate));
-
- // Parse the child patterns
- stream_skip_whitespace(stream);
- bool child_is_immediate = false;
- uint16_t child_start_step_index = self->steps.size;
- for (;;) {
- if (stream->next == '.') {
- child_is_immediate = true;
- stream_advance(stream);
- stream_skip_whitespace(stream);
- }
-
- TSQueryError e = ts_query__parse_pattern(
- self,
- stream,
- depth + 1,
- capture_count,
- child_is_immediate
- );
- if (e == PARENT_DONE && stream->next == ')') {
- if (child_is_immediate) {
- self->steps.contents[child_start_step_index].is_last_child = true;
- }
- stream_advance(stream);
- break;
- } else if (e) {
- return e;
- }
-
- child_is_immediate = false;
- }
- }
- }
-
- // Parse a wildcard pattern
- else if (
- stream->next == '_' ||
-
- // TODO remove.
- // For temporary backward compatibility, handle '*' as a wildcard.
- stream->next == '*'
- ) {
- stream_advance(stream);
- stream_skip_whitespace(stream);
-
- // Add a step that matches any kind of node
- array_push(&self->steps, query_step__new(WILDCARD_SYMBOL, depth, is_immediate));
- }
-
- // Parse a double-quoted anonymous leaf node expression
- else if (stream->next == '"') {
- stream_advance(stream);
-
- // Parse the string content
- const char *string_content = stream->input;
- while (stream->next != '"') {
- if (!stream_advance(stream)) {
- stream_reset(stream, string_content - 1);
- return TSQueryErrorSyntax;
- }
- }
- uint32_t length = stream->input - string_content;
-
- // Add a step for the node
- TSSymbol symbol = ts_language_symbol_for_name(
- self->language,
- string_content,
- length,
- false
- );
- if (!symbol) {
- stream_reset(stream, string_content);
- return TSQueryErrorNodeType;
- }
- array_push(&self->steps, query_step__new(symbol, depth, is_immediate));
-
- if (stream->next != '"') return TSQueryErrorSyntax;
- stream_advance(stream);
- }
-
- // Parse a field-prefixed pattern
- else if (stream_is_ident_start(stream)) {
- // Parse the field name
- const char *field_name = stream->input;
- stream_scan_identifier(stream);
- uint32_t length = stream->input - field_name;
- stream_skip_whitespace(stream);
-
- if (stream->next != ':') {
- stream_reset(stream, field_name);
- return TSQueryErrorSyntax;
- }
- stream_advance(stream);
- stream_skip_whitespace(stream);
-
- // Parse the pattern
- uint32_t step_index = self->steps.size;
- TSQueryError e = ts_query__parse_pattern(
- self,
- stream,
- depth,
- capture_count,
- is_immediate
- );
- if (e == PARENT_DONE) return TSQueryErrorSyntax;
- if (e) return e;
-
- // Add the field name to the first step of the pattern
- TSFieldId field_id = ts_language_field_id_for_name(
- self->language,
- field_name,
- length
- );
- if (!field_id) {
- stream->input = field_name;
- return TSQueryErrorField;
- }
- self->steps.contents[step_index].field = field_id;
- }
-
- else {
- return TSQueryErrorSyntax;
- }
-
- stream_skip_whitespace(stream);
-
- // Parse suffixes modifiers for this pattern
- for (;;) {
- QueryStep *step = &self->steps.contents[starting_step_index];
-
- // Parse the one-or-more operator.
- if (stream->next == '+') {
- stream_advance(stream);
- stream_skip_whitespace(stream);
-
- QueryStep repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false);
- repeat_step.alternative_index = starting_step_index;
- repeat_step.is_pass_through = true;
- repeat_step.alternative_is_immediate = true;
- array_push(&self->steps, repeat_step);
- }
-
- // Parse the zero-or-more repetition operator.
- else if (stream->next == '*') {
- stream_advance(stream);
- stream_skip_whitespace(stream);
-
- QueryStep repeat_step = query_step__new(WILDCARD_SYMBOL, depth, false);
- repeat_step.alternative_index = starting_step_index;
- repeat_step.is_pass_through = true;
- repeat_step.alternative_is_immediate = true;
- array_push(&self->steps, repeat_step);
-
- while (step->alternative_index != NONE) {
- step = &self->steps.contents[step->alternative_index];
- }
- step->alternative_index = self->steps.size;
- }
-
- // Parse the optional operator.
- else if (stream->next == '?') {
- stream_advance(stream);
- stream_skip_whitespace(stream);
-
- while (step->alternative_index != NONE) {
- step = &self->steps.contents[step->alternative_index];
- }
- step->alternative_index = self->steps.size;
- }
-
- // Parse an '@'-prefixed capture pattern
- else if (stream->next == '@') {
- stream_advance(stream);
- if (!stream_is_ident_start(stream)) return TSQueryErrorSyntax;
- const char *capture_name = stream->input;
- stream_scan_identifier(stream);
- uint32_t length = stream->input - capture_name;
- stream_skip_whitespace(stream);
-
- // Add the capture id to the first step of the pattern
- uint16_t capture_id = symbol_table_insert_name(
- &self->captures,
- capture_name,
- length
- );
-
- for (;;) {
- query_step__add_capture(step, capture_id);
- if (
- step->alternative_index != NONE &&
- step->alternative_index > starting_step_index &&
- step->alternative_index < self->steps.size
- ) {
- starting_step_index = step->alternative_index;
- step = &self->steps.contents[starting_step_index];
- } else {
- break;
- }
- }
-
- (*capture_count)++;
- }
-
- // No more suffix modifiers
- else {
- break;
- }
- }
-
- return 0;
-}
-
-TSQuery *ts_query_new(
- const TSLanguage *language,
- const char *source,
- uint32_t source_len,
- uint32_t *error_offset,
- TSQueryError *error_type
-) {
- TSSymbol *symbol_map;
- if (ts_language_version(language) >= TREE_SITTER_LANGUAGE_VERSION_WITH_SYMBOL_DEDUPING) {
- symbol_map = NULL;
- } else {
- // Work around the fact that multiple symbols can currently be
- // associated with the same name, due to "simple aliases".
- // In the next language ABI version, this map will be contained
- // in the language's `public_symbol_map` field.
- uint32_t symbol_count = ts_language_symbol_count(language);
- symbol_map = ts_malloc(sizeof(TSSymbol) * symbol_count);
- for (unsigned i = 0; i < symbol_count; i++) {
- const char *name = ts_language_symbol_name(language, i);
- const TSSymbolType symbol_type = ts_language_symbol_type(language, i);
-
- symbol_map[i] = i;
-
- for (unsigned j = 0; j < i; j++) {
- if (ts_language_symbol_type(language, j) == symbol_type) {
- if (!strcmp(name, ts_language_symbol_name(language, j))) {
- symbol_map[i] = j;
- break;
- }
- }
- }
- }
- }
-
- TSQuery *self = ts_malloc(sizeof(TSQuery));
- *self = (TSQuery) {
- .steps = array_new(),
- .pattern_map = array_new(),
- .captures = symbol_table_new(),
- .predicate_values = symbol_table_new(),
- .predicate_steps = array_new(),
- .predicates_by_pattern = array_new(),
- .symbol_map = symbol_map,
- .wildcard_root_pattern_count = 0,
- .language = language,
- };
-
- // Parse all of the S-expressions in the given string.
- Stream stream = stream_new(source, source_len);
- stream_skip_whitespace(&stream);
- while (stream.input < stream.end) {
- uint32_t pattern_index = self->predicates_by_pattern.size;
- uint32_t start_step_index = self->steps.size;
- uint32_t capture_count = 0;
- array_push(&self->start_bytes_by_pattern, stream.input - source);
- array_push(&self->predicates_by_pattern, ((Slice) {
- .offset = self->predicate_steps.size,
- .length = 0,
- }));
- *error_type = ts_query__parse_pattern(self, &stream, 0, &capture_count, false);
- array_push(&self->steps, query_step__new(0, PATTERN_DONE_MARKER, false));
-
- // If any pattern could not be parsed, then report the error information
- // and terminate.
- if (*error_type) {
- if (*error_type == PARENT_DONE) *error_type = TSQueryErrorSyntax;
- *error_offset = stream.input - source;
- ts_query_delete(self);
- return NULL;
- }
-
- // If a pattern has a wildcard at its root, optimize the matching process
- // by skipping matching the wildcard.
- if (
- self->steps.contents[start_step_index].symbol == WILDCARD_SYMBOL
- ) {
- QueryStep *second_step = &self->steps.contents[start_step_index + 1];
- if (second_step->symbol != WILDCARD_SYMBOL && second_step->depth != PATTERN_DONE_MARKER) {
- start_step_index += 1;
- }
- }
-
- // Maintain a map that can look up patterns for a given root symbol.
- for (;;) {
- QueryStep *step = &self->steps.contents[start_step_index];
- step->is_pattern_start = true;
- ts_query__pattern_map_insert(self, step->symbol, start_step_index, pattern_index);
- if (step->symbol == WILDCARD_SYMBOL) {
- self->wildcard_root_pattern_count++;
- }
-
- // If there are alternatives or options at the root of the pattern,
- // then add multiple entries to the pattern map.
- if (step->alternative_index != NONE) {
- start_step_index = step->alternative_index;
- } else {
- break;
- }
- }
- }
-
- ts_query__finalize_steps(self);
- return self;
-}
-
-void ts_query_delete(TSQuery *self) {
- if (self) {
- array_delete(&self->steps);
- array_delete(&self->pattern_map);
- array_delete(&self->predicate_steps);
- array_delete(&self->predicates_by_pattern);
- array_delete(&self->start_bytes_by_pattern);
- symbol_table_delete(&self->captures);
- symbol_table_delete(&self->predicate_values);
- ts_free(self->symbol_map);
- ts_free(self);
- }
-}
-
-uint32_t ts_query_pattern_count(const TSQuery *self) {
- return self->predicates_by_pattern.size;
-}
-
-uint32_t ts_query_capture_count(const TSQuery *self) {
- return self->captures.slices.size;
-}
-
-uint32_t ts_query_string_count(const TSQuery *self) {
- return self->predicate_values.slices.size;
-}
-
-const char *ts_query_capture_name_for_id(
- const TSQuery *self,
- uint32_t index,
- uint32_t *length
-) {
- return symbol_table_name_for_id(&self->captures, index, length);
-}
-
-const char *ts_query_string_value_for_id(
- const TSQuery *self,
- uint32_t index,
- uint32_t *length
-) {
- return symbol_table_name_for_id(&self->predicate_values, index, length);
-}
-
-const TSQueryPredicateStep *ts_query_predicates_for_pattern(
- const TSQuery *self,
- uint32_t pattern_index,
- uint32_t *step_count
-) {
- Slice slice = self->predicates_by_pattern.contents[pattern_index];
- *step_count = slice.length;
- return &self->predicate_steps.contents[slice.offset];
-}
-
-uint32_t ts_query_start_byte_for_pattern(
- const TSQuery *self,
- uint32_t pattern_index
-) {
- return self->start_bytes_by_pattern.contents[pattern_index];
-}
-
-void ts_query_disable_capture(
- TSQuery *self,
- const char *name,
- uint32_t length
-) {
- // Remove capture information for any pattern step that previously
- // captured with the given name.
- int id = symbol_table_id_for_name(&self->captures, name, length);
- if (id != -1) {
- for (unsigned i = 0; i < self->steps.size; i++) {
- QueryStep *step = &self->steps.contents[i];
- query_step__remove_capture(step, id);
- }
- ts_query__finalize_steps(self);
- }
-}
-
-void ts_query_disable_pattern(
- TSQuery *self,
- uint32_t pattern_index
-) {
- // Remove the given pattern from the pattern map. Its steps will still
- // be in the `steps` array, but they will never be read.
- for (unsigned i = 0; i < self->pattern_map.size; i++) {
- PatternEntry *pattern = &self->pattern_map.contents[i];
- if (pattern->pattern_index == pattern_index) {
- array_erase(&self->pattern_map, i);
- i--;
- }
- }
-}
-
-/***************
- * QueryCursor
- ***************/
-
-TSQueryCursor *ts_query_cursor_new(void) {
- TSQueryCursor *self = ts_malloc(sizeof(TSQueryCursor));
- *self = (TSQueryCursor) {
- .ascending = false,
- .states = array_new(),
- .finished_states = array_new(),
- .capture_list_pool = capture_list_pool_new(),
- .start_byte = 0,
- .end_byte = UINT32_MAX,
- .start_point = {0, 0},
- .end_point = POINT_MAX,
- };
- array_reserve(&self->states, MAX_STATE_COUNT);
- array_reserve(&self->finished_states, MAX_CAPTURE_LIST_COUNT);
- return self;
-}
-
-void ts_query_cursor_delete(TSQueryCursor *self) {
- array_delete(&self->states);
- array_delete(&self->finished_states);
- ts_tree_cursor_delete(&self->cursor);
- capture_list_pool_delete(&self->capture_list_pool);
- ts_free(self);
-}
-
-void ts_query_cursor_exec(
- TSQueryCursor *self,
- const TSQuery *query,
- TSNode node
-) {
- array_clear(&self->states);
- array_clear(&self->finished_states);
- ts_tree_cursor_reset(&self->cursor, node);
- capture_list_pool_reset(&self->capture_list_pool);
- self->next_state_id = 0;
- self->depth = 0;
- self->ascending = false;
- self->query = query;
-}
-
-void ts_query_cursor_set_byte_range(
- TSQueryCursor *self,
- uint32_t start_byte,
- uint32_t end_byte
-) {
- if (end_byte == 0) {
- start_byte = 0;
- end_byte = UINT32_MAX;
- }
- self->start_byte = start_byte;
- self->end_byte = end_byte;
-}
-
-void ts_query_cursor_set_point_range(
- TSQueryCursor *self,
- TSPoint start_point,
- TSPoint end_point
-) {
- if (end_point.row == 0 && end_point.column == 0) {
- start_point = POINT_ZERO;
- end_point = POINT_MAX;
- }
- self->start_point = start_point;
- self->end_point = end_point;
-}
-
-// Search through all of the in-progress states, and find the captured
-// node that occurs earliest in the document.
-static bool ts_query_cursor__first_in_progress_capture(
- TSQueryCursor *self,
- uint32_t *state_index,
- uint32_t *byte_offset,
- uint32_t *pattern_index
-) {
- bool result = false;
- *state_index = UINT32_MAX;
- *byte_offset = UINT32_MAX;
- *pattern_index = UINT32_MAX;
- for (unsigned i = 0; i < self->states.size; i++) {
- const QueryState *state = &self->states.contents[i];
- const CaptureList *captures = capture_list_pool_get(
- &self->capture_list_pool,
- state->capture_list_id
- );
- if (captures->size > 0) {
- uint32_t capture_byte = ts_node_start_byte(captures->contents[0].node);
- if (
- !result ||
- capture_byte < *byte_offset ||
- (capture_byte == *byte_offset && state->pattern_index < *pattern_index)
- ) {
- result = true;
- *state_index = i;
- *byte_offset = capture_byte;
- *pattern_index = state->pattern_index;
- }
- }
- }
- return result;
-}
-
-// Determine which node is first in a depth-first traversal
-int ts_query_cursor__compare_nodes(TSNode left, TSNode right) {
- if (left.id != right.id) {
- uint32_t left_start = ts_node_start_byte(left);
- uint32_t right_start = ts_node_start_byte(right);
- if (left_start < right_start) return -1;
- if (left_start > right_start) return 1;
- uint32_t left_node_count = ts_node_end_byte(left);
- uint32_t right_node_count = ts_node_end_byte(right);
- if (left_node_count > right_node_count) return -1;
- if (left_node_count < right_node_count) return 1;
- }
- return 0;
-}
-
-// Determine if either state contains a superset of the other state's captures.
-void ts_query_cursor__compare_captures(
- TSQueryCursor *self,
- QueryState *left_state,
- QueryState *right_state,
- bool *left_contains_right,
- bool *right_contains_left
-) {
- const CaptureList *left_captures = capture_list_pool_get(
- &self->capture_list_pool,
- left_state->capture_list_id
- );
- const CaptureList *right_captures = capture_list_pool_get(
- &self->capture_list_pool,
- right_state->capture_list_id
- );
- *left_contains_right = true;
- *right_contains_left = true;
- unsigned i = 0, j = 0;
- for (;;) {
- if (i < left_captures->size) {
- if (j < right_captures->size) {
- TSQueryCapture *left = &left_captures->contents[i];
- TSQueryCapture *right = &right_captures->contents[j];
- if (left->node.id == right->node.id && left->index == right->index) {
- i++;
- j++;
- } else {
- switch (ts_query_cursor__compare_nodes(left->node, right->node)) {
- case -1:
- *right_contains_left = false;
- i++;
- break;
- case 1:
- *left_contains_right = false;
- j++;
- break;
- default:
- *right_contains_left = false;
- *left_contains_right = false;
- i++;
- j++;
- break;
- }
- }
- } else {
- *right_contains_left = false;
- break;
- }
- } else {
- if (j < right_captures->size) {
- *left_contains_right = false;
- }
- break;
- }
- }
-}
-
-static bool ts_query_cursor__add_state(
- TSQueryCursor *self,
- const PatternEntry *pattern
-) {
- if (self->states.size >= MAX_STATE_COUNT) {
- LOG(" too many states");
- return false;
- }
- LOG(
- " start state. pattern:%u, step:%u\n",
- pattern->pattern_index,
- pattern->step_index
- );
- QueryStep *step = &self->query->steps.contents[pattern->step_index];
- array_push(&self->states, ((QueryState) {
- .capture_list_id = NONE,
- .step_index = pattern->step_index,
- .pattern_index = pattern->pattern_index,
- .start_depth = self->depth - step->depth,
- .consumed_capture_count = 0,
- .seeking_immediate_match = false,
- }));
- return true;
-}
-
-// Duplicate the given state and insert the newly-created state immediately after
-// the given state in the `states` array.
-static QueryState *ts_query__cursor_copy_state(
- TSQueryCursor *self,
- const QueryState *state
-) {
- if (self->states.size >= MAX_STATE_COUNT) {
- LOG(" too many states");
- return NULL;
- }
-
- // If the state has captures, copy its capture list.
- QueryState copy = *state;
- copy.capture_list_id = state->capture_list_id;
- if (state->capture_list_id != NONE) {
- copy.capture_list_id = capture_list_pool_acquire(&self->capture_list_pool);
- if (copy.capture_list_id == NONE) {
- LOG(" too many capture lists");
- return NULL;
- }
- const CaptureList *old_captures = capture_list_pool_get(
- &self->capture_list_pool,
- state->capture_list_id
- );
- CaptureList *new_captures = capture_list_pool_get_mut(
- &self->capture_list_pool,
- copy.capture_list_id
- );
- array_push_all(new_captures, old_captures);
- }
-
- uint32_t index = (state - self->states.contents) + 1;
- array_insert(&self->states, index, copy);
- return &self->states.contents[index];
-}
-
-// Walk the tree, processing patterns until at least one pattern finishes,
-// If one or more patterns finish, return `true` and store their states in the
-// `finished_states` array. Multiple patterns can finish on the same node. If
-// there are no more matches, return `false`.
-static inline bool ts_query_cursor__advance(TSQueryCursor *self) {
- do {
- if (self->ascending) {
- LOG("leave node. type:%s\n", ts_node_type(ts_tree_cursor_current_node(&self->cursor)));
-
- // Leave this node by stepping to its next sibling or to its parent.
- bool did_move = true;
- if (ts_tree_cursor_goto_next_sibling(&self->cursor)) {
- self->ascending = false;
- } else if (ts_tree_cursor_goto_parent(&self->cursor)) {
- self->depth--;
- } else {
- did_move = false;
- }
-
- // After leaving a node, remove any states that cannot make further progress.
- uint32_t deleted_count = 0;
- for (unsigned i = 0, n = self->states.size; i < n; i++) {
- QueryState *state = &self->states.contents[i];
- QueryStep *step = &self->query->steps.contents[state->step_index];
-
- // If a state completed its pattern inside of this node, but was deferred from finishing
- // in order to search for longer matches, mark it as finished.
- if (step->depth == PATTERN_DONE_MARKER) {
- if (state->start_depth > self->depth || !did_move) {
- LOG(" finish pattern %u\n", state->pattern_index);
- state->id = self->next_state_id++;
- array_push(&self->finished_states, *state);
- deleted_count++;
- continue;
- }
- }
-
- // If a state needed to match something within this node, then remove that state
- // as it has failed to match.
- else if ((uint32_t)state->start_depth + (uint32_t)step->depth > self->depth) {
- LOG(
- " failed to match. pattern:%u, step:%u\n",
- state->pattern_index,
- state->step_index
- );
- capture_list_pool_release(
- &self->capture_list_pool,
- state->capture_list_id
- );
- deleted_count++;
- continue;
- }
-
- if (deleted_count > 0) {
- self->states.contents[i - deleted_count] = *state;
- }
- }
- self->states.size -= deleted_count;
-
- if (!did_move) {
- return self->finished_states.size > 0;
- }
- } else {
- // If this node is before the selected range, then avoid descending into it.
- TSNode node = ts_tree_cursor_current_node(&self->cursor);
- if (
- ts_node_end_byte(node) <= self->start_byte ||
- point_lte(ts_node_end_point(node), self->start_point)
- ) {
- if (!ts_tree_cursor_goto_next_sibling(&self->cursor)) {
- self->ascending = true;
- }
- continue;
- }
-
- // If this node is after the selected range, then stop walking.
- if (
- self->end_byte <= ts_node_start_byte(node) ||
- point_lte(self->end_point, ts_node_start_point(node))
- ) return false;
-
- // Get the properties of the current node.
- TSSymbol symbol = ts_node_symbol(node);
- bool is_named = ts_node_is_named(node);
- if (symbol != ts_builtin_sym_error && self->query->symbol_map) {
- symbol = self->query->symbol_map[symbol];
- }
- bool can_have_later_siblings;
- bool can_have_later_siblings_with_this_field;
- TSFieldId field_id = ts_tree_cursor_current_status(
- &self->cursor,
- &can_have_later_siblings,
- &can_have_later_siblings_with_this_field
- );
- LOG(
- "enter node. type:%s, field:%s, row:%u state_count:%u, finished_state_count:%u\n",
- ts_node_type(node),
- ts_language_field_name_for_id(self->query->language, field_id),
- ts_node_start_point(node).row,
- self->states.size,
- self->finished_states.size
- );
-
- // Add new states for any patterns whose root node is a wildcard.
- for (unsigned i = 0; i < self->query->wildcard_root_pattern_count; i++) {
- PatternEntry *pattern = &self->query->pattern_map.contents[i];
- QueryStep *step = &self->query->steps.contents[pattern->step_index];
-
- // If this node matches the first step of the pattern, then add a new
- // state at the start of this pattern.
- if (step->field && field_id != step->field) continue;
- if (!ts_query_cursor__add_state(self, pattern)) break;
- }
-
- // Add new states for any patterns whose root node matches this node.
- unsigned i;
- if (ts_query__pattern_map_search(self->query, symbol, &i)) {
- PatternEntry *pattern = &self->query->pattern_map.contents[i];
- QueryStep *step = &self->query->steps.contents[pattern->step_index];
- do {
- // If this node matches the first step of the pattern, then add a new
- // state at the start of this pattern.
- if (step->field && field_id != step->field) continue;
- if (!ts_query_cursor__add_state(self, pattern)) break;
-
- // Advance to the next pattern whose root node matches this node.
- i++;
- if (i == self->query->pattern_map.size) break;
- pattern = &self->query->pattern_map.contents[i];
- step = &self->query->steps.contents[pattern->step_index];
- } while (step->symbol == symbol);
- }
-
- // Update all of the in-progress states with current node.
- for (unsigned i = 0, copy_count = 0; i < self->states.size; i += 1 + copy_count) {
- QueryState *state = &self->states.contents[i];
- QueryStep *step = &self->query->steps.contents[state->step_index];
- state->has_in_progress_alternatives = false;
- copy_count = 0;
-
- // Check that the node matches all of the criteria for the next
- // step of the pattern.
- if ((uint32_t)state->start_depth + (uint32_t)step->depth != self->depth) continue;
-
- // Determine if this node matches this step of the pattern, and also
- // if this node can have later siblings that match this step of the
- // pattern.
- bool node_does_match =
- step->symbol == symbol ||
- step->symbol == WILDCARD_SYMBOL ||
- (step->symbol == NAMED_WILDCARD_SYMBOL && is_named);
- bool later_sibling_can_match = can_have_later_siblings;
- if ((step->is_immediate && is_named) || state->seeking_immediate_match) {
- later_sibling_can_match = false;
- }
- if (step->is_last_child && can_have_later_siblings) {
- node_does_match = false;
- }
- if (step->field) {
- if (step->field == field_id) {
- if (!can_have_later_siblings_with_this_field) {
- later_sibling_can_match = false;
- }
- } else {
- node_does_match = false;
- }
- }
-
- // Remove states immediately if it is ever clear that they cannot match.
- if (!node_does_match) {
- if (!later_sibling_can_match) {
- LOG(
- " discard state. pattern:%u, step:%u\n",
- state->pattern_index,
- state->step_index
- );
- capture_list_pool_release(
- &self->capture_list_pool,
- state->capture_list_id
- );
- array_erase(&self->states, i);
- i--;
- }
- continue;
- }
-
- // Some patterns can match their root node in multiple ways, capturing different
- // children. If this pattern step could match later children within the same
- // parent, then this query state cannot simply be updated in place. It must be
- // split into two states: one that matches this node, and one which skips over
- // this node, to preserve the possibility of matching later siblings.
- if (
- later_sibling_can_match &&
- !step->is_pattern_start &&
- step->contains_captures
- ) {
- if (ts_query__cursor_copy_state(self, state)) {
- LOG(
- " split state for capture. pattern:%u, step:%u\n",
- state->pattern_index,
- state->step_index
- );
- copy_count++;
- }
- }
-
- // If the current node is captured in this pattern, add it to the capture list.
- // For the first capture in a pattern, lazily acquire a capture list.
- if (step->capture_ids[0] != NONE) {
- if (state->capture_list_id == NONE) {
- state->capture_list_id = capture_list_pool_acquire(&self->capture_list_pool);
-
- // If there are no capture lists left in the pool, then terminate whichever
- // state has captured the earliest node in the document, and steal its
- // capture list.
- if (state->capture_list_id == NONE) {
- uint32_t state_index, byte_offset, pattern_index;
- if (ts_query_cursor__first_in_progress_capture(
- self,
- &state_index,
- &byte_offset,
- &pattern_index
- )) {
- LOG(
- " abandon state. index:%u, pattern:%u, offset:%u.\n",
- state_index, pattern_index, byte_offset
- );
- state->capture_list_id = self->states.contents[state_index].capture_list_id;
- array_erase(&self->states, state_index);
- if (state_index < i) {
- i--;
- state--;
- }
- } else {
- LOG(" too many finished states.\n");
- array_erase(&self->states, i);
- i--;
- continue;
- }
- }
- }
-
- CaptureList *capture_list = capture_list_pool_get_mut(
- &self->capture_list_pool,
- state->capture_list_id
- );
- for (unsigned j = 0; j < MAX_STEP_CAPTURE_COUNT; j++) {
- uint16_t capture_id = step->capture_ids[j];
- if (step->capture_ids[j] == NONE) break;
- array_push(capture_list, ((TSQueryCapture) { node, capture_id }));
- LOG(
- " capture node. pattern:%u, capture_id:%u, capture_count:%u\n",
- state->pattern_index,
- capture_id,
- capture_list->size
- );
- }
- }
-
- // Advance this state to the next step of its pattern.
- state->step_index++;
- state->seeking_immediate_match = false;
- LOG(
- " advance state. pattern:%u, step:%u\n",
- state->pattern_index,
- state->step_index
- );
-
- // If this state's next step has an 'alternative' step (the step is either optional,
- // or is the end of a repetition), then copy the state in order to pursue both
- // alternatives. The alternative step itself may have an alternative, so this is
- // an interative process.
- unsigned end_index = i + 1;
- for (unsigned j = i; j < end_index; j++) {
- QueryState *state = &self->states.contents[j];
- QueryStep *next_step = &self->query->steps.contents[state->step_index];
- if (next_step->alternative_index != NONE) {
- if (next_step->is_dead_end) {
- state->step_index = next_step->alternative_index;
- j--;
- continue;
- }
-
- QueryState *copy = ts_query__cursor_copy_state(self, state);
- if (next_step->is_pass_through) {
- state->step_index++;
- j--;
- }
- if (copy) {
- copy_count++;
- end_index++;
- copy->step_index = next_step->alternative_index;
- if (next_step->alternative_is_immediate) {
- copy->seeking_immediate_match = true;
- }
- LOG(
- " split state for branch. pattern:%u, step:%u, step:%u, immediate:%d\n",
- copy->pattern_index,
- state->step_index,
- copy->step_index,
- copy->seeking_immediate_match
- );
- }
- }
- }
- }
-
- for (unsigned i = 0; i < self->states.size; i++) {
- QueryState *state = &self->states.contents[i];
- bool did_remove = false;
-
- // Enfore the longest-match criteria. When a query pattern contains optional or
- // repeated nodes, this is necesssary to avoid multiple redundant states, where
- // one state has a strict subset of another state's captures.
- for (unsigned j = i + 1; j < self->states.size; j++) {
- QueryState *other_state = &self->states.contents[j];
- if (
- state->pattern_index == other_state->pattern_index &&
- state->start_depth == other_state->start_depth
- ) {
- bool left_contains_right, right_contains_left;
- ts_query_cursor__compare_captures(
- self,
- state,
- other_state,
- &left_contains_right,
- &right_contains_left
- );
- if (left_contains_right) {
- if (state->step_index == other_state->step_index) {
- LOG(
- " drop shorter state. pattern: %u, step_index: %u\n",
- state->pattern_index,
- state->step_index
- );
- capture_list_pool_release(&self->capture_list_pool, other_state->capture_list_id);
- array_erase(&self->states, j);
- j--;
- continue;
- }
- other_state->has_in_progress_alternatives = true;
- }
- if (right_contains_left) {
- if (state->step_index == other_state->step_index) {
- LOG(
- " drop shorter state. pattern: %u, step_index: %u\n",
- state->pattern_index,
- state->step_index
- );
- capture_list_pool_release(&self->capture_list_pool, state->capture_list_id);
- array_erase(&self->states, i);
- did_remove = true;
- break;
- }
- state->has_in_progress_alternatives = true;
- }
- }
- }
-
- // If there the state is at the end of its pattern, remove it from the list
- // of in-progress states and add it to the list of finished states.
- if (!did_remove) {
- QueryStep *next_step = &self->query->steps.contents[state->step_index];
- if (next_step->depth == PATTERN_DONE_MARKER) {
- if (state->has_in_progress_alternatives) {
- LOG(" defer finishing pattern %u\n", state->pattern_index);
- } else {
- LOG(" finish pattern %u\n", state->pattern_index);
- state->id = self->next_state_id++;
- array_push(&self->finished_states, *state);
- array_erase(&self->states, state - self->states.contents);
- i--;
- }
- }
- }
- }
-
- // Continue descending if possible.
- if (ts_tree_cursor_goto_first_child(&self->cursor)) {
- self->depth++;
- } else {
- self->ascending = true;
- }
- }
- } while (self->finished_states.size == 0);
-
- return true;
-}
-
-bool ts_query_cursor_next_match(
- TSQueryCursor *self,
- TSQueryMatch *match
-) {
- if (self->finished_states.size == 0) {
- if (!ts_query_cursor__advance(self)) {
- return false;
- }
- }
-
- QueryState *state = &self->finished_states.contents[0];
- match->id = state->id;
- match->pattern_index = state->pattern_index;
- const CaptureList *captures = capture_list_pool_get(
- &self->capture_list_pool,
- state->capture_list_id
- );
- match->captures = captures->contents;
- match->capture_count = captures->size;
- capture_list_pool_release(&self->capture_list_pool, state->capture_list_id);
- array_erase(&self->finished_states, 0);
- return true;
-}
-
-void ts_query_cursor_remove_match(
- TSQueryCursor *self,
- uint32_t match_id
-) {
- for (unsigned i = 0; i < self->finished_states.size; i++) {
- const QueryState *state = &self->finished_states.contents[i];
- if (state->id == match_id) {
- capture_list_pool_release(
- &self->capture_list_pool,
- state->capture_list_id
- );
- array_erase(&self->finished_states, i);
- return;
- }
- }
-}
-
-bool ts_query_cursor_next_capture(
- TSQueryCursor *self,
- TSQueryMatch *match,
- uint32_t *capture_index
-) {
- for (;;) {
- // The goal here is to return captures in order, even though they may not
- // be discovered in order, because patterns can overlap. If there are any
- // finished patterns, then try to find one that contains a capture that
- // is *definitely* before any capture in an *unfinished* pattern.
- if (self->finished_states.size > 0) {
- // First, identify the position of the earliest capture in an unfinished
- // match. For a finished capture to be returned, it must be *before*
- // this position.
- uint32_t first_unfinished_capture_byte;
- uint32_t first_unfinished_pattern_index;
- uint32_t first_unfinished_state_index;
- ts_query_cursor__first_in_progress_capture(
- self,
- &first_unfinished_state_index,
- &first_unfinished_capture_byte,
- &first_unfinished_pattern_index
- );
-
- // Find the earliest capture in a finished match.
- int first_finished_state_index = -1;
- uint32_t first_finished_capture_byte = first_unfinished_capture_byte;
- uint32_t first_finished_pattern_index = first_unfinished_pattern_index;
- for (unsigned i = 0; i < self->finished_states.size; i++) {
- const QueryState *state = &self->finished_states.contents[i];
- const CaptureList *captures = capture_list_pool_get(
- &self->capture_list_pool,
- state->capture_list_id
- );
- if (captures->size > state->consumed_capture_count) {
- uint32_t capture_byte = ts_node_start_byte(
- captures->contents[state->consumed_capture_count].node
- );
- if (
- capture_byte < first_finished_capture_byte ||
- (
- capture_byte == first_finished_capture_byte &&
- state->pattern_index < first_finished_pattern_index
- )
- ) {
- first_finished_state_index = i;
- first_finished_capture_byte = capture_byte;
- first_finished_pattern_index = state->pattern_index;
- }
- } else {
- capture_list_pool_release(
- &self->capture_list_pool,
- state->capture_list_id
- );
- array_erase(&self->finished_states, i);
- i--;
- }
- }
-
- // If there is finished capture that is clearly before any unfinished
- // capture, then return its match, and its capture index. Internally
- // record the fact that the capture has been 'consumed'.
- if (first_finished_state_index != -1) {
- QueryState *state = &self->finished_states.contents[
- first_finished_state_index
- ];
- match->id = state->id;
- match->pattern_index = state->pattern_index;
- const CaptureList *captures = capture_list_pool_get(
- &self->capture_list_pool,
- state->capture_list_id
- );
- match->captures = captures->contents;
- match->capture_count = captures->size;
- *capture_index = state->consumed_capture_count;
- state->consumed_capture_count++;
- return true;
- }
-
- if (capture_list_pool_is_empty(&self->capture_list_pool)) {
- LOG(
- " abandon state. index:%u, pattern:%u, offset:%u.\n",
- first_unfinished_state_index,
- first_unfinished_pattern_index,
- first_unfinished_capture_byte
- );
- capture_list_pool_release(
- &self->capture_list_pool,
- self->states.contents[first_unfinished_state_index].capture_list_id
- );
- array_erase(&self->states, first_unfinished_state_index);
- }
- }
-
- // If there are no finished matches that are ready to be returned, then
- // continue finding more matches.
- if (!ts_query_cursor__advance(self)) return false;
- }
-}
-
-#undef LOG
diff --git a/src/tree_sitter/reduce_action.h b/src/tree_sitter/reduce_action.h
deleted file mode 100644
index 72aff08d73..0000000000
--- a/src/tree_sitter/reduce_action.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#ifndef TREE_SITTER_REDUCE_ACTION_H_
-#define TREE_SITTER_REDUCE_ACTION_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include "./array.h"
-#include "tree_sitter/api.h"
-
-typedef struct {
- uint32_t count;
- TSSymbol symbol;
- int dynamic_precedence;
- unsigned short production_id;
-} ReduceAction;
-
-typedef Array(ReduceAction) ReduceActionSet;
-
-static inline void ts_reduce_action_set_add(ReduceActionSet *self,
- ReduceAction new_action) {
- for (uint32_t i = 0; i < self->size; i++) {
- ReduceAction action = self->contents[i];
- if (action.symbol == new_action.symbol && action.count == new_action.count)
- return;
- }
- array_push(self, new_action);
-}
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // TREE_SITTER_REDUCE_ACTION_H_
diff --git a/src/tree_sitter/reusable_node.h b/src/tree_sitter/reusable_node.h
deleted file mode 100644
index 9cba951909..0000000000
--- a/src/tree_sitter/reusable_node.h
+++ /dev/null
@@ -1,88 +0,0 @@
-#include "./subtree.h"
-
-typedef struct {
- Subtree tree;
- uint32_t child_index;
- uint32_t byte_offset;
-} StackEntry;
-
-typedef struct {
- Array(StackEntry) stack;
- Subtree last_external_token;
-} ReusableNode;
-
-static inline ReusableNode reusable_node_new(void) {
- return (ReusableNode) {array_new(), NULL_SUBTREE};
-}
-
-static inline void reusable_node_clear(ReusableNode *self) {
- array_clear(&self->stack);
- self->last_external_token = NULL_SUBTREE;
-}
-
-static inline void reusable_node_reset(ReusableNode *self, Subtree tree) {
- reusable_node_clear(self);
- array_push(&self->stack, ((StackEntry) {
- .tree = tree,
- .child_index = 0,
- .byte_offset = 0,
- }));
-}
-
-static inline Subtree reusable_node_tree(ReusableNode *self) {
- return self->stack.size > 0
- ? self->stack.contents[self->stack.size - 1].tree
- : NULL_SUBTREE;
-}
-
-static inline uint32_t reusable_node_byte_offset(ReusableNode *self) {
- return self->stack.size > 0
- ? self->stack.contents[self->stack.size - 1].byte_offset
- : UINT32_MAX;
-}
-
-static inline void reusable_node_delete(ReusableNode *self) {
- array_delete(&self->stack);
-}
-
-static inline void reusable_node_advance(ReusableNode *self) {
- StackEntry last_entry = *array_back(&self->stack);
- uint32_t byte_offset = last_entry.byte_offset + ts_subtree_total_bytes(last_entry.tree);
- if (ts_subtree_has_external_tokens(last_entry.tree)) {
- self->last_external_token = ts_subtree_last_external_token(last_entry.tree);
- }
-
- Subtree tree;
- uint32_t next_index;
- do {
- StackEntry popped_entry = array_pop(&self->stack);
- next_index = popped_entry.child_index + 1;
- if (self->stack.size == 0) return;
- tree = array_back(&self->stack)->tree;
- } while (ts_subtree_child_count(tree) <= next_index);
-
- array_push(&self->stack, ((StackEntry) {
- .tree = tree.ptr->children[next_index],
- .child_index = next_index,
- .byte_offset = byte_offset,
- }));
-}
-
-static inline bool reusable_node_descend(ReusableNode *self) {
- StackEntry last_entry = *array_back(&self->stack);
- if (ts_subtree_child_count(last_entry.tree) > 0) {
- array_push(&self->stack, ((StackEntry) {
- .tree = last_entry.tree.ptr->children[0],
- .child_index = 0,
- .byte_offset = last_entry.byte_offset,
- }));
- return true;
- } else {
- return false;
- }
-}
-
-static inline void reusable_node_advance_past_leaf(ReusableNode *self) {
- while (reusable_node_descend(self)) {}
- reusable_node_advance(self);
-}
diff --git a/src/tree_sitter/stack.c b/src/tree_sitter/stack.c
deleted file mode 100644
index 6ceee2577f..0000000000
--- a/src/tree_sitter/stack.c
+++ /dev/null
@@ -1,848 +0,0 @@
-#include "./alloc.h"
-#include "./language.h"
-#include "./subtree.h"
-#include "./array.h"
-#include "./stack.h"
-#include "./length.h"
-#include <assert.h>
-#include <stdio.h>
-
-#define MAX_LINK_COUNT 8
-#define MAX_NODE_POOL_SIZE 50
-#define MAX_ITERATOR_COUNT 64
-
-#if defined _WIN32 && !defined __GNUC__
-#define inline __forceinline
-#else
-#define inline static inline __attribute__((always_inline))
-#endif
-
-typedef struct StackNode StackNode;
-
-typedef struct {
- StackNode *node;
- Subtree subtree;
- bool is_pending;
-} StackLink;
-
-struct StackNode {
- TSStateId state;
- Length position;
- StackLink links[MAX_LINK_COUNT];
- short unsigned int link_count;
- uint32_t ref_count;
- unsigned error_cost;
- unsigned node_count;
- int dynamic_precedence;
-};
-
-typedef struct {
- StackNode *node;
- SubtreeArray subtrees;
- uint32_t subtree_count;
- bool is_pending;
-} StackIterator;
-
-typedef struct {
- void *payload;
- StackIterateCallback callback;
-} StackIterateSession;
-
-typedef Array(StackNode *) StackNodeArray;
-
-typedef enum {
- StackStatusActive,
- StackStatusPaused,
- StackStatusHalted,
-} StackStatus;
-
-typedef struct {
- StackNode *node;
- Subtree last_external_token;
- StackSummary *summary;
- unsigned node_count_at_last_error;
- TSSymbol lookahead_when_paused;
- StackStatus status;
-} StackHead;
-
-struct Stack {
- Array(StackHead) heads;
- StackSliceArray slices;
- Array(StackIterator) iterators;
- StackNodeArray node_pool;
- StackNode *base_node;
- SubtreePool *subtree_pool;
-};
-
-typedef unsigned StackAction;
-enum {
- StackActionNone,
- StackActionStop = 1,
- StackActionPop = 2,
-};
-
-typedef StackAction (*StackCallback)(void *, const StackIterator *);
-
-static void stack_node_retain(StackNode *self) {
- if (!self)
- return;
- assert(self->ref_count > 0);
- self->ref_count++;
- assert(self->ref_count != 0);
-}
-
-static void stack_node_release(StackNode *self, StackNodeArray *pool, SubtreePool *subtree_pool) {
-recur:
- assert(self->ref_count != 0);
- self->ref_count--;
- if (self->ref_count > 0) return;
-
- StackNode *first_predecessor = NULL;
- if (self->link_count > 0) {
- for (unsigned i = self->link_count - 1; i > 0; i--) {
- StackLink link = self->links[i];
- if (link.subtree.ptr) ts_subtree_release(subtree_pool, link.subtree);
- stack_node_release(link.node, pool, subtree_pool);
- }
- StackLink link = self->links[0];
- if (link.subtree.ptr) ts_subtree_release(subtree_pool, link.subtree);
- first_predecessor = self->links[0].node;
- }
-
- if (pool->size < MAX_NODE_POOL_SIZE) {
- array_push(pool, self);
- } else {
- ts_free(self);
- }
-
- if (first_predecessor) {
- self = first_predecessor;
- goto recur;
- }
-}
-
-static StackNode *stack_node_new(StackNode *previous_node, Subtree subtree,
- bool is_pending, TSStateId state, StackNodeArray *pool) {
- StackNode *node = pool->size > 0 ?
- array_pop(pool) :
- ts_malloc(sizeof(StackNode));
- *node = (StackNode){.ref_count = 1, .link_count = 0, .state = state};
-
- if (previous_node) {
- node->link_count = 1;
- node->links[0] = (StackLink){
- .node = previous_node,
- .subtree = subtree,
- .is_pending = is_pending,
- };
-
- node->position = previous_node->position;
- node->error_cost = previous_node->error_cost;
- node->dynamic_precedence = previous_node->dynamic_precedence;
- node->node_count = previous_node->node_count;
-
- if (subtree.ptr) {
- node->error_cost += ts_subtree_error_cost(subtree);
- node->position = length_add(node->position, ts_subtree_total_size(subtree));
- node->node_count += ts_subtree_node_count(subtree);
- node->dynamic_precedence += ts_subtree_dynamic_precedence(subtree);
- }
- } else {
- node->position = length_zero();
- node->error_cost = 0;
- }
-
- return node;
-}
-
-static bool stack__subtree_is_equivalent(Subtree left, Subtree right) {
- return
- left.ptr == right.ptr ||
- (left.ptr && right.ptr &&
- ts_subtree_symbol(left) == ts_subtree_symbol(right) &&
- ((ts_subtree_error_cost(left) > 0 && ts_subtree_error_cost(right) > 0) ||
- (ts_subtree_padding(left).bytes == ts_subtree_padding(right).bytes &&
- ts_subtree_size(left).bytes == ts_subtree_size(right).bytes &&
- ts_subtree_child_count(left) == ts_subtree_child_count(right) &&
- ts_subtree_extra(left) == ts_subtree_extra(right) &&
- ts_subtree_external_scanner_state_eq(left, right))));
-}
-
-static void stack_node_add_link(StackNode *self, StackLink link, SubtreePool *subtree_pool) {
- if (link.node == self) return;
-
- for (int i = 0; i < self->link_count; i++) {
- StackLink *existing_link = &self->links[i];
- if (stack__subtree_is_equivalent(existing_link->subtree, link.subtree)) {
- // In general, we preserve ambiguities until they are removed from the stack
- // during a pop operation where multiple paths lead to the same node. But in
- // the special case where two links directly connect the same pair of nodes,
- // we can safely remove the ambiguity ahead of time without changing behavior.
- if (existing_link->node == link.node) {
- if (
- ts_subtree_dynamic_precedence(link.subtree) >
- ts_subtree_dynamic_precedence(existing_link->subtree)
- ) {
- ts_subtree_retain(link.subtree);
- ts_subtree_release(subtree_pool, existing_link->subtree);
- existing_link->subtree = link.subtree;
- self->dynamic_precedence =
- link.node->dynamic_precedence + ts_subtree_dynamic_precedence(link.subtree);
- }
- return;
- }
-
- // If the previous nodes are mergeable, merge them recursively.
- if (existing_link->node->state == link.node->state &&
- existing_link->node->position.bytes == link.node->position.bytes) {
- for (int j = 0; j < link.node->link_count; j++) {
- stack_node_add_link(existing_link->node, link.node->links[j], subtree_pool);
- }
- int32_t dynamic_precedence = link.node->dynamic_precedence;
- if (link.subtree.ptr) {
- dynamic_precedence += ts_subtree_dynamic_precedence(link.subtree);
- }
- if (dynamic_precedence > self->dynamic_precedence) {
- self->dynamic_precedence = dynamic_precedence;
- }
- return;
- }
- }
- }
-
- if (self->link_count == MAX_LINK_COUNT) return;
-
- stack_node_retain(link.node);
- unsigned node_count = link.node->node_count;
- int dynamic_precedence = link.node->dynamic_precedence;
- self->links[self->link_count++] = link;
-
- if (link.subtree.ptr) {
- ts_subtree_retain(link.subtree);
- node_count += ts_subtree_node_count(link.subtree);
- dynamic_precedence += ts_subtree_dynamic_precedence(link.subtree);
- }
-
- if (node_count > self->node_count) self->node_count = node_count;
- if (dynamic_precedence > self->dynamic_precedence) self->dynamic_precedence = dynamic_precedence;
-}
-
-static void stack_head_delete(StackHead *self, StackNodeArray *pool, SubtreePool *subtree_pool) {
- if (self->node) {
- if (self->last_external_token.ptr) {
- ts_subtree_release(subtree_pool, self->last_external_token);
- }
- if (self->summary) {
- array_delete(self->summary);
- ts_free(self->summary);
- }
- stack_node_release(self->node, pool, subtree_pool);
- }
-}
-
-static StackVersion ts_stack__add_version(Stack *self, StackVersion original_version,
- StackNode *node) {
- StackHead head = {
- .node = node,
- .node_count_at_last_error = self->heads.contents[original_version].node_count_at_last_error,
- .last_external_token = self->heads.contents[original_version].last_external_token,
- .status = StackStatusActive,
- .lookahead_when_paused = 0,
- };
- array_push(&self->heads, head);
- stack_node_retain(node);
- if (head.last_external_token.ptr) ts_subtree_retain(head.last_external_token);
- return (StackVersion)(self->heads.size - 1);
-}
-
-static void ts_stack__add_slice(Stack *self, StackVersion original_version,
- StackNode *node, SubtreeArray *subtrees) {
- for (uint32_t i = self->slices.size - 1; i + 1 > 0; i--) {
- StackVersion version = self->slices.contents[i].version;
- if (self->heads.contents[version].node == node) {
- StackSlice slice = {*subtrees, version};
- array_insert(&self->slices, i + 1, slice);
- return;
- }
- }
-
- StackVersion version = ts_stack__add_version(self, original_version, node);
- StackSlice slice = { *subtrees, version };
- array_push(&self->slices, slice);
-}
-
-inline StackSliceArray stack__iter(Stack *self, StackVersion version,
- StackCallback callback, void *payload,
- int goal_subtree_count) {
- array_clear(&self->slices);
- array_clear(&self->iterators);
-
- StackHead *head = array_get(&self->heads, version);
- StackIterator iterator = {
- .node = head->node,
- .subtrees = array_new(),
- .subtree_count = 0,
- .is_pending = true,
- };
-
- bool include_subtrees = false;
- if (goal_subtree_count >= 0) {
- include_subtrees = true;
- array_reserve(&iterator.subtrees, goal_subtree_count);
- }
-
- array_push(&self->iterators, iterator);
-
- while (self->iterators.size > 0) {
- for (uint32_t i = 0, size = self->iterators.size; i < size; i++) {
- StackIterator *iterator = &self->iterators.contents[i];
- StackNode *node = iterator->node;
-
- StackAction action = callback(payload, iterator);
- bool should_pop = action & StackActionPop;
- bool should_stop = action & StackActionStop || node->link_count == 0;
-
- if (should_pop) {
- SubtreeArray subtrees = iterator->subtrees;
- if (!should_stop)
- ts_subtree_array_copy(subtrees, &subtrees);
- ts_subtree_array_reverse(&subtrees);
- ts_stack__add_slice(
- self,
- version,
- node,
- &subtrees
- );
- }
-
- if (should_stop) {
- if (!should_pop)
- ts_subtree_array_delete(self->subtree_pool, &iterator->subtrees);
- array_erase(&self->iterators, i);
- i--, size--;
- continue;
- }
-
- for (uint32_t j = 1; j <= node->link_count; j++) {
- StackIterator *next_iterator;
- StackLink link;
- if (j == node->link_count) {
- link = node->links[0];
- next_iterator = &self->iterators.contents[i];
- } else {
- if (self->iterators.size >= MAX_ITERATOR_COUNT) continue;
- link = node->links[j];
- StackIterator current_iterator = self->iterators.contents[i];
- array_push(&self->iterators, current_iterator);
- next_iterator = array_back(&self->iterators);
- ts_subtree_array_copy(next_iterator->subtrees, &next_iterator->subtrees);
- }
-
- next_iterator->node = link.node;
- if (link.subtree.ptr) {
- if (include_subtrees) {
- array_push(&next_iterator->subtrees, link.subtree);
- ts_subtree_retain(link.subtree);
- }
-
- if (!ts_subtree_extra(link.subtree)) {
- next_iterator->subtree_count++;
- if (!link.is_pending) {
- next_iterator->is_pending = false;
- }
- }
- } else {
- next_iterator->subtree_count++;
- next_iterator->is_pending = false;
- }
- }
- }
- }
-
- return self->slices;
-}
-
-Stack *ts_stack_new(SubtreePool *subtree_pool) {
- Stack *self = ts_calloc(1, sizeof(Stack));
-
- array_init(&self->heads);
- array_init(&self->slices);
- array_init(&self->iterators);
- array_init(&self->node_pool);
- array_reserve(&self->heads, 4);
- array_reserve(&self->slices, 4);
- array_reserve(&self->iterators, 4);
- array_reserve(&self->node_pool, MAX_NODE_POOL_SIZE);
-
- self->subtree_pool = subtree_pool;
- self->base_node = stack_node_new(NULL, NULL_SUBTREE, false, 1, &self->node_pool);
- ts_stack_clear(self);
-
- return self;
-}
-
-void ts_stack_delete(Stack *self) {
- if (self->slices.contents)
- array_delete(&self->slices);
- if (self->iterators.contents)
- array_delete(&self->iterators);
- stack_node_release(self->base_node, &self->node_pool, self->subtree_pool);
- for (uint32_t i = 0; i < self->heads.size; i++) {
- stack_head_delete(&self->heads.contents[i], &self->node_pool, self->subtree_pool);
- }
- array_clear(&self->heads);
- if (self->node_pool.contents) {
- for (uint32_t i = 0; i < self->node_pool.size; i++)
- ts_free(self->node_pool.contents[i]);
- array_delete(&self->node_pool);
- }
- array_delete(&self->heads);
- ts_free(self);
-}
-
-uint32_t ts_stack_version_count(const Stack *self) {
- return self->heads.size;
-}
-
-TSStateId ts_stack_state(const Stack *self, StackVersion version) {
- return array_get(&self->heads, version)->node->state;
-}
-
-Length ts_stack_position(const Stack *self, StackVersion version) {
- return array_get(&self->heads, version)->node->position;
-}
-
-Subtree ts_stack_last_external_token(const Stack *self, StackVersion version) {
- return array_get(&self->heads, version)->last_external_token;
-}
-
-void ts_stack_set_last_external_token(Stack *self, StackVersion version, Subtree token) {
- StackHead *head = array_get(&self->heads, version);
- if (token.ptr) ts_subtree_retain(token);
- if (head->last_external_token.ptr) ts_subtree_release(self->subtree_pool, head->last_external_token);
- head->last_external_token = token;
-}
-
-unsigned ts_stack_error_cost(const Stack *self, StackVersion version) {
- StackHead *head = array_get(&self->heads, version);
- unsigned result = head->node->error_cost;
- if (
- head->status == StackStatusPaused ||
- (head->node->state == ERROR_STATE && !head->node->links[0].subtree.ptr)) {
- result += ERROR_COST_PER_RECOVERY;
- }
- return result;
-}
-
-unsigned ts_stack_node_count_since_error(const Stack *self, StackVersion version) {
- StackHead *head = array_get(&self->heads, version);
- if (head->node->node_count < head->node_count_at_last_error) {
- head->node_count_at_last_error = head->node->node_count;
- }
- return head->node->node_count - head->node_count_at_last_error;
-}
-
-void ts_stack_push(Stack *self, StackVersion version, Subtree subtree,
- bool pending, TSStateId state) {
- StackHead *head = array_get(&self->heads, version);
- StackNode *new_node = stack_node_new(head->node, subtree, pending, state, &self->node_pool);
- if (!subtree.ptr) head->node_count_at_last_error = new_node->node_count;
- head->node = new_node;
-}
-
-inline StackAction iterate_callback(void *payload, const StackIterator *iterator) {
- StackIterateSession *session = payload;
- session->callback(
- session->payload,
- iterator->node->state,
- iterator->subtree_count
- );
- return StackActionNone;
-}
-
-void ts_stack_iterate(Stack *self, StackVersion version,
- StackIterateCallback callback, void *payload) {
- StackIterateSession session = {payload, callback};
- stack__iter(self, version, iterate_callback, &session, -1);
-}
-
-inline StackAction pop_count_callback(void *payload, const StackIterator *iterator) {
- unsigned *goal_subtree_count = payload;
- if (iterator->subtree_count == *goal_subtree_count) {
- return StackActionPop | StackActionStop;
- } else {
- return StackActionNone;
- }
-}
-
-StackSliceArray ts_stack_pop_count(Stack *self, StackVersion version, uint32_t count) {
- return stack__iter(self, version, pop_count_callback, &count, count);
-}
-
-inline StackAction pop_pending_callback(void *payload, const StackIterator *iterator) {
- (void)payload;
- if (iterator->subtree_count >= 1) {
- if (iterator->is_pending) {
- return StackActionPop | StackActionStop;
- } else {
- return StackActionStop;
- }
- } else {
- return StackActionNone;
- }
-}
-
-StackSliceArray ts_stack_pop_pending(Stack *self, StackVersion version) {
- StackSliceArray pop = stack__iter(self, version, pop_pending_callback, NULL, 0);
- if (pop.size > 0) {
- ts_stack_renumber_version(self, pop.contents[0].version, version);
- pop.contents[0].version = version;
- }
- return pop;
-}
-
-inline StackAction pop_error_callback(void *payload, const StackIterator *iterator) {
- if (iterator->subtrees.size > 0) {
- bool *found_error = payload;
- if (!*found_error && ts_subtree_is_error(iterator->subtrees.contents[0])) {
- *found_error = true;
- return StackActionPop | StackActionStop;
- } else {
- return StackActionStop;
- }
- } else {
- return StackActionNone;
- }
-}
-
-SubtreeArray ts_stack_pop_error(Stack *self, StackVersion version) {
- StackNode *node = array_get(&self->heads, version)->node;
- for (unsigned i = 0; i < node->link_count; i++) {
- if (node->links[i].subtree.ptr && ts_subtree_is_error(node->links[i].subtree)) {
- bool found_error = false;
- StackSliceArray pop = stack__iter(self, version, pop_error_callback, &found_error, 1);
- if (pop.size > 0) {
- assert(pop.size == 1);
- ts_stack_renumber_version(self, pop.contents[0].version, version);
- return pop.contents[0].subtrees;
- }
- break;
- }
- }
- return (SubtreeArray){.size = 0};
-}
-
-inline StackAction pop_all_callback(void *payload, const StackIterator *iterator) {
- (void)payload;
- return iterator->node->link_count == 0 ? StackActionPop : StackActionNone;
-}
-
-StackSliceArray ts_stack_pop_all(Stack *self, StackVersion version) {
- return stack__iter(self, version, pop_all_callback, NULL, 0);
-}
-
-typedef struct {
- StackSummary *summary;
- unsigned max_depth;
-} SummarizeStackSession;
-
-inline StackAction summarize_stack_callback(void *payload, const StackIterator *iterator) {
- SummarizeStackSession *session = payload;
- TSStateId state = iterator->node->state;
- unsigned depth = iterator->subtree_count;
- if (depth > session->max_depth) return StackActionStop;
- for (unsigned i = session->summary->size - 1; i + 1 > 0; i--) {
- StackSummaryEntry entry = session->summary->contents[i];
- if (entry.depth < depth) break;
- if (entry.depth == depth && entry.state == state) return StackActionNone;
- }
- array_push(session->summary, ((StackSummaryEntry){
- .position = iterator->node->position,
- .depth = depth,
- .state = state,
- }));
- return StackActionNone;
-}
-
-void ts_stack_record_summary(Stack *self, StackVersion version, unsigned max_depth) {
- SummarizeStackSession session = {
- .summary = ts_malloc(sizeof(StackSummary)),
- .max_depth = max_depth
- };
- array_init(session.summary);
- stack__iter(self, version, summarize_stack_callback, &session, -1);
- self->heads.contents[version].summary = session.summary;
-}
-
-StackSummary *ts_stack_get_summary(Stack *self, StackVersion version) {
- return array_get(&self->heads, version)->summary;
-}
-
-int ts_stack_dynamic_precedence(Stack *self, StackVersion version) {
- return array_get(&self->heads, version)->node->dynamic_precedence;
-}
-
-bool ts_stack_has_advanced_since_error(const Stack *self, StackVersion version) {
- const StackHead *head = array_get(&self->heads, version);
- const StackNode *node = head->node;
- if (node->error_cost == 0) return true;
- while (node) {
- if (node->link_count > 0) {
- Subtree subtree = node->links[0].subtree;
- if (subtree.ptr) {
- if (ts_subtree_total_bytes(subtree) > 0) {
- return true;
- } else if (
- node->node_count > head->node_count_at_last_error &&
- ts_subtree_error_cost(subtree) == 0
- ) {
- node = node->links[0].node;
- continue;
- }
- }
- }
- break;
- }
- return false;
-}
-
-void ts_stack_remove_version(Stack *self, StackVersion version) {
- stack_head_delete(array_get(&self->heads, version), &self->node_pool, self->subtree_pool);
- array_erase(&self->heads, version);
-}
-
-void ts_stack_renumber_version(Stack *self, StackVersion v1, StackVersion v2) {
- if (v1 == v2) return;
- assert(v2 < v1);
- assert((uint32_t)v1 < self->heads.size);
- StackHead *source_head = &self->heads.contents[v1];
- StackHead *target_head = &self->heads.contents[v2];
- if (target_head->summary && !source_head->summary) {
- source_head->summary = target_head->summary;
- target_head->summary = NULL;
- }
- stack_head_delete(target_head, &self->node_pool, self->subtree_pool);
- *target_head = *source_head;
- array_erase(&self->heads, v1);
-}
-
-void ts_stack_swap_versions(Stack *self, StackVersion v1, StackVersion v2) {
- StackHead temporary_head = self->heads.contents[v1];
- self->heads.contents[v1] = self->heads.contents[v2];
- self->heads.contents[v2] = temporary_head;
-}
-
-StackVersion ts_stack_copy_version(Stack *self, StackVersion version) {
- assert(version < self->heads.size);
- array_push(&self->heads, self->heads.contents[version]);
- StackHead *head = array_back(&self->heads);
- stack_node_retain(head->node);
- if (head->last_external_token.ptr) ts_subtree_retain(head->last_external_token);
- head->summary = NULL;
- return self->heads.size - 1;
-}
-
-bool ts_stack_merge(Stack *self, StackVersion version1, StackVersion version2) {
- if (!ts_stack_can_merge(self, version1, version2)) return false;
- StackHead *head1 = &self->heads.contents[version1];
- StackHead *head2 = &self->heads.contents[version2];
- for (uint32_t i = 0; i < head2->node->link_count; i++) {
- stack_node_add_link(head1->node, head2->node->links[i], self->subtree_pool);
- }
- if (head1->node->state == ERROR_STATE) {
- head1->node_count_at_last_error = head1->node->node_count;
- }
- ts_stack_remove_version(self, version2);
- return true;
-}
-
-bool ts_stack_can_merge(Stack *self, StackVersion version1, StackVersion version2) {
- StackHead *head1 = &self->heads.contents[version1];
- StackHead *head2 = &self->heads.contents[version2];
- return
- head1->status == StackStatusActive &&
- head2->status == StackStatusActive &&
- head1->node->state == head2->node->state &&
- head1->node->position.bytes == head2->node->position.bytes &&
- head1->node->error_cost == head2->node->error_cost &&
- ts_subtree_external_scanner_state_eq(head1->last_external_token, head2->last_external_token);
-}
-
-void ts_stack_halt(Stack *self, StackVersion version) {
- array_get(&self->heads, version)->status = StackStatusHalted;
-}
-
-void ts_stack_pause(Stack *self, StackVersion version, TSSymbol lookahead) {
- StackHead *head = array_get(&self->heads, version);
- head->status = StackStatusPaused;
- head->lookahead_when_paused = lookahead;
- head->node_count_at_last_error = head->node->node_count;
-}
-
-bool ts_stack_is_active(const Stack *self, StackVersion version) {
- return array_get(&self->heads, version)->status == StackStatusActive;
-}
-
-bool ts_stack_is_halted(const Stack *self, StackVersion version) {
- return array_get(&self->heads, version)->status == StackStatusHalted;
-}
-
-bool ts_stack_is_paused(const Stack *self, StackVersion version) {
- return array_get(&self->heads, version)->status == StackStatusPaused;
-}
-
-TSSymbol ts_stack_resume(Stack *self, StackVersion version) {
- StackHead *head = array_get(&self->heads, version);
- assert(head->status == StackStatusPaused);
- TSSymbol result = head->lookahead_when_paused;
- head->status = StackStatusActive;
- head->lookahead_when_paused = 0;
- return result;
-}
-
-void ts_stack_clear(Stack *self) {
- stack_node_retain(self->base_node);
- for (uint32_t i = 0; i < self->heads.size; i++) {
- stack_head_delete(&self->heads.contents[i], &self->node_pool, self->subtree_pool);
- }
- array_clear(&self->heads);
- array_push(&self->heads, ((StackHead){
- .node = self->base_node,
- .last_external_token = NULL_SUBTREE,
- .status = StackStatusActive,
- .lookahead_when_paused = 0,
- }));
-}
-
-bool ts_stack_print_dot_graph(Stack *self, const TSLanguage *language, FILE *f) {
- array_reserve(&self->iterators, 32);
- bool was_recording_allocations = ts_toggle_allocation_recording(false);
- if (!f) f = stderr;
-
- fprintf(f, "digraph stack {\n");
- fprintf(f, "rankdir=\"RL\";\n");
- fprintf(f, "edge [arrowhead=none]\n");
-
- Array(StackNode *) visited_nodes = array_new();
-
- array_clear(&self->iterators);
- for (uint32_t i = 0; i < self->heads.size; i++) {
- StackHead *head = &self->heads.contents[i];
- if (head->status == StackStatusHalted) continue;
-
- fprintf(f, "node_head_%u [shape=none, label=\"\"]\n", i);
- fprintf(f, "node_head_%u -> node_%p [", i, head->node);
-
- if (head->status == StackStatusPaused) {
- fprintf(f, "color=red ");
- }
- fprintf(f,
- "label=%u, fontcolor=blue, weight=10000, labeltooltip=\"node_count: %u\nerror_cost: %u",
- i,
- ts_stack_node_count_since_error(self, i),
- ts_stack_error_cost(self, i)
- );
-
- if (head->last_external_token.ptr) {
- const ExternalScannerState *state = &head->last_external_token.ptr->external_scanner_state;
- const char *data = ts_external_scanner_state_data(state);
- fprintf(f, "\nexternal_scanner_state:");
- for (uint32_t j = 0; j < state->length; j++) fprintf(f, " %2X", data[j]);
- }
-
- fprintf(f, "\"]\n");
- array_push(&self->iterators, ((StackIterator){.node = head->node }));
- }
-
- bool all_iterators_done = false;
- while (!all_iterators_done) {
- all_iterators_done = true;
-
- for (uint32_t i = 0; i < self->iterators.size; i++) {
- StackIterator iterator = self->iterators.contents[i];
- StackNode *node = iterator.node;
-
- for (uint32_t j = 0; j < visited_nodes.size; j++) {
- if (visited_nodes.contents[j] == node) {
- node = NULL;
- break;
- }
- }
-
- if (!node) continue;
- all_iterators_done = false;
-
- fprintf(f, "node_%p [", node);
- if (node->state == ERROR_STATE) {
- fprintf(f, "label=\"?\"");
- } else if (
- node->link_count == 1 &&
- node->links[0].subtree.ptr &&
- ts_subtree_extra(node->links[0].subtree)
- ) {
- fprintf(f, "shape=point margin=0 label=\"\"");
- } else {
- fprintf(f, "label=\"%d\"", node->state);
- }
-
- fprintf(
- f,
- " tooltip=\"position: %u,%u\nnode_count:%u\nerror_cost: %u\ndynamic_precedence: %d\"];\n",
- node->position.extent.row + 1,
- node->position.extent.column,
- node->node_count,
- node->error_cost,
- node->dynamic_precedence
- );
-
- for (int j = 0; j < node->link_count; j++) {
- StackLink link = node->links[j];
- fprintf(f, "node_%p -> node_%p [", node, link.node);
- if (link.is_pending) fprintf(f, "style=dashed ");
- if (link.subtree.ptr && ts_subtree_extra(link.subtree)) fprintf(f, "fontcolor=gray ");
-
- if (!link.subtree.ptr) {
- fprintf(f, "color=red");
- } else {
- fprintf(f, "label=\"");
- bool quoted = ts_subtree_visible(link.subtree) && !ts_subtree_named(link.subtree);
- if (quoted) fprintf(f, "'");
- const char *name = ts_language_symbol_name(language, ts_subtree_symbol(link.subtree));
- for (const char *c = name; *c; c++) {
- if (*c == '\"' || *c == '\\') fprintf(f, "\\");
- fprintf(f, "%c", *c);
- }
- if (quoted) fprintf(f, "'");
- fprintf(f, "\"");
- fprintf(
- f,
- "labeltooltip=\"error_cost: %u\ndynamic_precedence: %u\"",
- ts_subtree_error_cost(link.subtree),
- ts_subtree_dynamic_precedence(link.subtree)
- );
- }
-
- fprintf(f, "];\n");
-
- StackIterator *next_iterator;
- if (j == 0) {
- next_iterator = &self->iterators.contents[i];
- } else {
- array_push(&self->iterators, iterator);
- next_iterator = array_back(&self->iterators);
- }
- next_iterator->node = link.node;
- }
-
- array_push(&visited_nodes, node);
- }
- }
-
- fprintf(f, "}\n");
-
- array_delete(&visited_nodes);
- ts_toggle_allocation_recording(was_recording_allocations);
- return true;
-}
-
-#undef inline
diff --git a/src/tree_sitter/stack.h b/src/tree_sitter/stack.h
deleted file mode 100644
index ec7a69d2b4..0000000000
--- a/src/tree_sitter/stack.h
+++ /dev/null
@@ -1,135 +0,0 @@
-#ifndef TREE_SITTER_PARSE_STACK_H_
-#define TREE_SITTER_PARSE_STACK_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include "./array.h"
-#include "./subtree.h"
-#include "./error_costs.h"
-#include <stdio.h>
-
-typedef struct Stack Stack;
-
-typedef unsigned StackVersion;
-#define STACK_VERSION_NONE ((StackVersion)-1)
-
-typedef struct {
- SubtreeArray subtrees;
- StackVersion version;
-} StackSlice;
-typedef Array(StackSlice) StackSliceArray;
-
-typedef struct {
- Length position;
- unsigned depth;
- TSStateId state;
-} StackSummaryEntry;
-typedef Array(StackSummaryEntry) StackSummary;
-
-// Create a stack.
-Stack *ts_stack_new(SubtreePool *);
-
-// Release the memory reserved for a given stack.
-void ts_stack_delete(Stack *);
-
-// Get the stack's current number of versions.
-uint32_t ts_stack_version_count(const Stack *);
-
-// Get the state at the top of the given version of the stack. If the stack is
-// empty, this returns the initial state, 0.
-TSStateId ts_stack_state(const Stack *, StackVersion);
-
-// Get the last external token associated with a given version of the stack.
-Subtree ts_stack_last_external_token(const Stack *, StackVersion);
-
-// Set the last external token associated with a given version of the stack.
-void ts_stack_set_last_external_token(Stack *, StackVersion, Subtree );
-
-// Get the position of the given version of the stack within the document.
-Length ts_stack_position(const Stack *, StackVersion);
-
-// Push a tree and state onto the given version of the stack.
-//
-// This transfers ownership of the tree to the Stack. Callers that
-// need to retain ownership of the tree for their own purposes should
-// first retain the tree.
-void ts_stack_push(Stack *, StackVersion, Subtree , bool, TSStateId);
-
-// Pop the given number of entries from the given version of the stack. This
-// operation can increase the number of stack versions by revealing multiple
-// versions which had previously been merged. It returns an array that
-// specifies the index of each revealed version and the trees that were
-// removed from that version.
-StackSliceArray ts_stack_pop_count(Stack *, StackVersion, uint32_t count);
-
-// Remove an error at the top of the given version of the stack.
-SubtreeArray ts_stack_pop_error(Stack *, StackVersion);
-
-// Remove any pending trees from the top of the given version of the stack.
-StackSliceArray ts_stack_pop_pending(Stack *, StackVersion);
-
-// Remove any all trees from the given version of the stack.
-StackSliceArray ts_stack_pop_all(Stack *, StackVersion);
-
-// Get the maximum number of tree nodes reachable from this version of the stack
-// since the last error was detected.
-unsigned ts_stack_node_count_since_error(const Stack *, StackVersion);
-
-int ts_stack_dynamic_precedence(Stack *, StackVersion);
-
-bool ts_stack_has_advanced_since_error(const Stack *, StackVersion);
-
-// Compute a summary of all the parse states near the top of the given
-// version of the stack and store the summary for later retrieval.
-void ts_stack_record_summary(Stack *, StackVersion, unsigned max_depth);
-
-// Retrieve a summary of all the parse states near the top of the
-// given version of the stack.
-StackSummary *ts_stack_get_summary(Stack *, StackVersion);
-
-// Get the total cost of all errors on the given version of the stack.
-unsigned ts_stack_error_cost(const Stack *, StackVersion version);
-
-// Merge the given two stack versions if possible, returning true
-// if they were successfully merged and false otherwise.
-bool ts_stack_merge(Stack *, StackVersion, StackVersion);
-
-// Determine whether the given two stack versions can be merged.
-bool ts_stack_can_merge(Stack *, StackVersion, StackVersion);
-
-TSSymbol ts_stack_resume(Stack *, StackVersion);
-
-void ts_stack_pause(Stack *, StackVersion, TSSymbol);
-
-void ts_stack_halt(Stack *, StackVersion);
-
-bool ts_stack_is_active(const Stack *, StackVersion);
-
-bool ts_stack_is_paused(const Stack *, StackVersion);
-
-bool ts_stack_is_halted(const Stack *, StackVersion);
-
-void ts_stack_renumber_version(Stack *, StackVersion, StackVersion);
-
-void ts_stack_swap_versions(Stack *, StackVersion, StackVersion);
-
-StackVersion ts_stack_copy_version(Stack *, StackVersion);
-
-// Remove the given version from the stack.
-void ts_stack_remove_version(Stack *, StackVersion);
-
-void ts_stack_clear(Stack *);
-
-bool ts_stack_print_dot_graph(Stack *, const TSLanguage *, FILE *);
-
-typedef void (*StackIterateCallback)(void *, TSStateId, uint32_t);
-
-void ts_stack_iterate(Stack *, StackVersion, StackIterateCallback, void *);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // TREE_SITTER_PARSE_STACK_H_
diff --git a/src/tree_sitter/subtree.c b/src/tree_sitter/subtree.c
deleted file mode 100644
index ef92a32fe4..0000000000
--- a/src/tree_sitter/subtree.c
+++ /dev/null
@@ -1,982 +0,0 @@
-#include <assert.h>
-#include <ctype.h>
-#include <limits.h>
-#include <stdbool.h>
-#include <string.h>
-#include <stdio.h>
-#include "./alloc.h"
-#include "./atomic.h"
-#include "./subtree.h"
-#include "./length.h"
-#include "./language.h"
-#include "./error_costs.h"
-#include <stddef.h>
-
-typedef struct {
- Length start;
- Length old_end;
- Length new_end;
-} Edit;
-
-#define TS_MAX_INLINE_TREE_LENGTH UINT8_MAX
-#define TS_MAX_TREE_POOL_SIZE 32
-
-static const ExternalScannerState empty_state = {{.short_data = {0}}, .length = 0};
-
-// ExternalScannerState
-
-void ts_external_scanner_state_init(ExternalScannerState *self, const char *data, unsigned length) {
- self->length = length;
- if (length > sizeof(self->short_data)) {
- self->long_data = ts_malloc(length);
- memcpy(self->long_data, data, length);
- } else {
- memcpy(self->short_data, data, length);
- }
-}
-
-ExternalScannerState ts_external_scanner_state_copy(const ExternalScannerState *self) {
- ExternalScannerState result = *self;
- if (self->length > sizeof(self->short_data)) {
- result.long_data = ts_malloc(self->length);
- memcpy(result.long_data, self->long_data, self->length);
- }
- return result;
-}
-
-void ts_external_scanner_state_delete(ExternalScannerState *self) {
- if (self->length > sizeof(self->short_data)) {
- ts_free(self->long_data);
- }
-}
-
-const char *ts_external_scanner_state_data(const ExternalScannerState *self) {
- if (self->length > sizeof(self->short_data)) {
- return self->long_data;
- } else {
- return self->short_data;
- }
-}
-
-bool ts_external_scanner_state_eq(const ExternalScannerState *a, const ExternalScannerState *b) {
- return a == b || (
- a->length == b->length &&
- !memcmp(ts_external_scanner_state_data(a), ts_external_scanner_state_data(b), a->length)
- );
-}
-
-// SubtreeArray
-
-void ts_subtree_array_copy(SubtreeArray self, SubtreeArray *dest) {
- dest->size = self.size;
- dest->capacity = self.capacity;
- dest->contents = self.contents;
- if (self.capacity > 0) {
- dest->contents = ts_calloc(self.capacity, sizeof(Subtree));
- memcpy(dest->contents, self.contents, self.size * sizeof(Subtree));
- for (uint32_t i = 0; i < self.size; i++) {
- ts_subtree_retain(dest->contents[i]);
- }
- }
-}
-
-void ts_subtree_array_delete(SubtreePool *pool, SubtreeArray *self) {
- for (uint32_t i = 0; i < self->size; i++) {
- ts_subtree_release(pool, self->contents[i]);
- }
- array_delete(self);
-}
-
-SubtreeArray ts_subtree_array_remove_trailing_extras(SubtreeArray *self) {
- SubtreeArray result = array_new();
-
- uint32_t i = self->size - 1;
- for (; i + 1 > 0; i--) {
- Subtree child = self->contents[i];
- if (!ts_subtree_extra(child)) break;
- array_push(&result, child);
- }
-
- self->size = i + 1;
- ts_subtree_array_reverse(&result);
- return result;
-}
-
-void ts_subtree_array_reverse(SubtreeArray *self) {
- for (uint32_t i = 0, limit = self->size / 2; i < limit; i++) {
- size_t reverse_index = self->size - 1 - i;
- Subtree swap = self->contents[i];
- self->contents[i] = self->contents[reverse_index];
- self->contents[reverse_index] = swap;
- }
-}
-
-// SubtreePool
-
-SubtreePool ts_subtree_pool_new(uint32_t capacity) {
- SubtreePool self = {array_new(), array_new()};
- array_reserve(&self.free_trees, capacity);
- return self;
-}
-
-void ts_subtree_pool_delete(SubtreePool *self) {
- if (self->free_trees.contents) {
- for (unsigned i = 0; i < self->free_trees.size; i++) {
- ts_free(self->free_trees.contents[i].ptr);
- }
- array_delete(&self->free_trees);
- }
- if (self->tree_stack.contents) array_delete(&self->tree_stack);
-}
-
-static SubtreeHeapData *ts_subtree_pool_allocate(SubtreePool *self) {
- if (self->free_trees.size > 0) {
- return array_pop(&self->free_trees).ptr;
- } else {
- return ts_malloc(sizeof(SubtreeHeapData));
- }
-}
-
-static void ts_subtree_pool_free(SubtreePool *self, SubtreeHeapData *tree) {
- if (self->free_trees.capacity > 0 && self->free_trees.size + 1 <= TS_MAX_TREE_POOL_SIZE) {
- array_push(&self->free_trees, (MutableSubtree) {.ptr = tree});
- } else {
- ts_free(tree);
- }
-}
-
-// Subtree
-
-static inline bool ts_subtree_can_inline(Length padding, Length size, uint32_t lookahead_bytes) {
- return
- padding.bytes < TS_MAX_INLINE_TREE_LENGTH &&
- padding.extent.row < 16 &&
- padding.extent.column < TS_MAX_INLINE_TREE_LENGTH &&
- size.extent.row == 0 &&
- size.extent.column < TS_MAX_INLINE_TREE_LENGTH &&
- lookahead_bytes < 16;
-}
-
-Subtree ts_subtree_new_leaf(
- SubtreePool *pool, TSSymbol symbol, Length padding, Length size,
- uint32_t lookahead_bytes, TSStateId parse_state, bool has_external_tokens,
- bool is_keyword, const TSLanguage *language
-) {
- TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol);
- bool extra = symbol == ts_builtin_sym_end;
-
- bool is_inline = (
- symbol <= UINT8_MAX &&
- !has_external_tokens &&
- ts_subtree_can_inline(padding, size, lookahead_bytes)
- );
-
- if (is_inline) {
- return (Subtree) {{
- .parse_state = parse_state,
- .symbol = symbol,
- .padding_bytes = padding.bytes,
- .padding_rows = padding.extent.row,
- .padding_columns = padding.extent.column,
- .size_bytes = size.bytes,
- .lookahead_bytes = lookahead_bytes,
- .visible = metadata.visible,
- .named = metadata.named,
- .extra = extra,
- .has_changes = false,
- .is_missing = false,
- .is_keyword = is_keyword,
- .is_inline = true,
- }};
- } else {
- SubtreeHeapData *data = ts_subtree_pool_allocate(pool);
- *data = (SubtreeHeapData) {
- .ref_count = 1,
- .padding = padding,
- .size = size,
- .lookahead_bytes = lookahead_bytes,
- .error_cost = 0,
- .child_count = 0,
- .symbol = symbol,
- .parse_state = parse_state,
- .visible = metadata.visible,
- .named = metadata.named,
- .extra = extra,
- .fragile_left = false,
- .fragile_right = false,
- .has_changes = false,
- .has_external_tokens = has_external_tokens,
- .is_missing = false,
- .is_keyword = is_keyword,
- {{.first_leaf = {.symbol = 0, .parse_state = 0}}}
- };
- return (Subtree) {.ptr = data};
- }
-}
-
-void ts_subtree_set_symbol(
- MutableSubtree *self,
- TSSymbol symbol,
- const TSLanguage *language
-) {
- TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol);
- if (self->data.is_inline) {
- assert(symbol < UINT8_MAX);
- self->data.symbol = symbol;
- self->data.named = metadata.named;
- self->data.visible = metadata.visible;
- } else {
- self->ptr->symbol = symbol;
- self->ptr->named = metadata.named;
- self->ptr->visible = metadata.visible;
- }
-}
-
-Subtree ts_subtree_new_error(
- SubtreePool *pool, int32_t lookahead_char, Length padding, Length size,
- uint32_t bytes_scanned, TSStateId parse_state, const TSLanguage *language
-) {
- Subtree result = ts_subtree_new_leaf(
- pool, ts_builtin_sym_error, padding, size, bytes_scanned,
- parse_state, false, false, language
- );
- SubtreeHeapData *data = (SubtreeHeapData *)result.ptr;
- data->fragile_left = true;
- data->fragile_right = true;
- data->lookahead_char = lookahead_char;
- return result;
-}
-
-MutableSubtree ts_subtree_make_mut(SubtreePool *pool, Subtree self) {
- if (self.data.is_inline) return (MutableSubtree) {self.data};
- if (self.ptr->ref_count == 1) return ts_subtree_to_mut_unsafe(self);
-
- SubtreeHeapData *result = ts_subtree_pool_allocate(pool);
- memcpy(result, self.ptr, sizeof(SubtreeHeapData));
- if (result->child_count > 0) {
- result->children = ts_calloc(self.ptr->child_count, sizeof(Subtree));
- memcpy(result->children, self.ptr->children, result->child_count * sizeof(Subtree));
- for (uint32_t i = 0; i < result->child_count; i++) {
- ts_subtree_retain(result->children[i]);
- }
- } else if (result->has_external_tokens) {
- result->external_scanner_state = ts_external_scanner_state_copy(&self.ptr->external_scanner_state);
- }
- result->ref_count = 1;
- ts_subtree_release(pool, self);
- return (MutableSubtree) {.ptr = result};
-}
-
-static void ts_subtree__compress(MutableSubtree self, unsigned count, const TSLanguage *language,
- MutableSubtreeArray *stack) {
- unsigned initial_stack_size = stack->size;
-
- MutableSubtree tree = self;
- TSSymbol symbol = tree.ptr->symbol;
- for (unsigned i = 0; i < count; i++) {
- if (tree.ptr->ref_count > 1 || tree.ptr->child_count < 2) break;
-
- MutableSubtree child = ts_subtree_to_mut_unsafe(tree.ptr->children[0]);
- if (
- child.data.is_inline ||
- child.ptr->child_count < 2 ||
- child.ptr->ref_count > 1 ||
- child.ptr->symbol != symbol
- ) break;
-
- MutableSubtree grandchild = ts_subtree_to_mut_unsafe(child.ptr->children[0]);
- if (
- grandchild.data.is_inline ||
- grandchild.ptr->child_count < 2 ||
- grandchild.ptr->ref_count > 1 ||
- grandchild.ptr->symbol != symbol
- ) break;
-
- tree.ptr->children[0] = ts_subtree_from_mut(grandchild);
- child.ptr->children[0] = grandchild.ptr->children[grandchild.ptr->child_count - 1];
- grandchild.ptr->children[grandchild.ptr->child_count - 1] = ts_subtree_from_mut(child);
- array_push(stack, tree);
- tree = grandchild;
- }
-
- while (stack->size > initial_stack_size) {
- tree = array_pop(stack);
- MutableSubtree child = ts_subtree_to_mut_unsafe(tree.ptr->children[0]);
- MutableSubtree grandchild = ts_subtree_to_mut_unsafe(child.ptr->children[child.ptr->child_count - 1]);
- ts_subtree_set_children(grandchild, grandchild.ptr->children, grandchild.ptr->child_count, language);
- ts_subtree_set_children(child, child.ptr->children, child.ptr->child_count, language);
- ts_subtree_set_children(tree, tree.ptr->children, tree.ptr->child_count, language);
- }
-}
-
-void ts_subtree_balance(Subtree self, SubtreePool *pool, const TSLanguage *language) {
- array_clear(&pool->tree_stack);
-
- if (ts_subtree_child_count(self) > 0 && self.ptr->ref_count == 1) {
- array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(self));
- }
-
- while (pool->tree_stack.size > 0) {
- MutableSubtree tree = array_pop(&pool->tree_stack);
-
- if (tree.ptr->repeat_depth > 0) {
- Subtree child1 = tree.ptr->children[0];
- Subtree child2 = tree.ptr->children[tree.ptr->child_count - 1];
- long repeat_delta = (long)ts_subtree_repeat_depth(child1) - (long)ts_subtree_repeat_depth(child2);
- if (repeat_delta > 0) {
- unsigned n = repeat_delta;
- for (unsigned i = n / 2; i > 0; i /= 2) {
- ts_subtree__compress(tree, i, language, &pool->tree_stack);
- n -= i;
- }
- }
- }
-
- for (uint32_t i = 0; i < tree.ptr->child_count; i++) {
- Subtree child = tree.ptr->children[i];
- if (ts_subtree_child_count(child) > 0 && child.ptr->ref_count == 1) {
- array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(child));
- }
- }
- }
-}
-
-void ts_subtree_set_children(
- MutableSubtree self, Subtree *children, uint32_t child_count, const TSLanguage *language
-) {
- assert(!self.data.is_inline);
-
- if (self.ptr->child_count > 0 && children != self.ptr->children) {
- ts_free(self.ptr->children);
- }
-
- self.ptr->child_count = child_count;
- self.ptr->children = children;
- self.ptr->named_child_count = 0;
- self.ptr->visible_child_count = 0;
- self.ptr->error_cost = 0;
- self.ptr->repeat_depth = 0;
- self.ptr->node_count = 1;
- self.ptr->has_external_tokens = false;
- self.ptr->dynamic_precedence = 0;
-
- uint32_t non_extra_index = 0;
- const TSSymbol *alias_sequence = ts_language_alias_sequence(language, self.ptr->production_id);
- uint32_t lookahead_end_byte = 0;
-
- for (uint32_t i = 0; i < self.ptr->child_count; i++) {
- Subtree child = self.ptr->children[i];
-
- if (i == 0) {
- self.ptr->padding = ts_subtree_padding(child);
- self.ptr->size = ts_subtree_size(child);
- } else {
- self.ptr->size = length_add(self.ptr->size, ts_subtree_total_size(child));
- }
-
- uint32_t child_lookahead_end_byte =
- self.ptr->padding.bytes +
- self.ptr->size.bytes +
- ts_subtree_lookahead_bytes(child);
- if (child_lookahead_end_byte > lookahead_end_byte) lookahead_end_byte = child_lookahead_end_byte;
-
- if (ts_subtree_symbol(child) != ts_builtin_sym_error_repeat) {
- self.ptr->error_cost += ts_subtree_error_cost(child);
- }
-
- self.ptr->dynamic_precedence += ts_subtree_dynamic_precedence(child);
- self.ptr->node_count += ts_subtree_node_count(child);
-
- if (alias_sequence && alias_sequence[non_extra_index] != 0 && !ts_subtree_extra(child)) {
- self.ptr->visible_child_count++;
- if (ts_language_symbol_metadata(language, alias_sequence[non_extra_index]).named) {
- self.ptr->named_child_count++;
- }
- } else if (ts_subtree_visible(child)) {
- self.ptr->visible_child_count++;
- if (ts_subtree_named(child)) self.ptr->named_child_count++;
- } else if (ts_subtree_child_count(child) > 0) {
- self.ptr->visible_child_count += child.ptr->visible_child_count;
- self.ptr->named_child_count += child.ptr->named_child_count;
- }
-
- if (ts_subtree_has_external_tokens(child)) self.ptr->has_external_tokens = true;
-
- if (ts_subtree_is_error(child)) {
- self.ptr->fragile_left = self.ptr->fragile_right = true;
- self.ptr->parse_state = TS_TREE_STATE_NONE;
- }
-
- if (!ts_subtree_extra(child)) non_extra_index++;
- }
-
- self.ptr->lookahead_bytes = lookahead_end_byte - self.ptr->size.bytes - self.ptr->padding.bytes;
-
- if (self.ptr->symbol == ts_builtin_sym_error || self.ptr->symbol == ts_builtin_sym_error_repeat) {
- self.ptr->error_cost +=
- ERROR_COST_PER_RECOVERY +
- ERROR_COST_PER_SKIPPED_CHAR * self.ptr->size.bytes +
- ERROR_COST_PER_SKIPPED_LINE * self.ptr->size.extent.row;
- for (uint32_t i = 0; i < self.ptr->child_count; i++) {
- Subtree child = self.ptr->children[i];
- uint32_t grandchild_count = ts_subtree_child_count(child);
- if (ts_subtree_extra(child)) continue;
- if (ts_subtree_is_error(child) && grandchild_count == 0) continue;
- if (ts_subtree_visible(child)) {
- self.ptr->error_cost += ERROR_COST_PER_SKIPPED_TREE;
- } else if (grandchild_count > 0) {
- self.ptr->error_cost += ERROR_COST_PER_SKIPPED_TREE * child.ptr->visible_child_count;
- }
- }
- }
-
- if (self.ptr->child_count > 0) {
- Subtree first_child = self.ptr->children[0];
- Subtree last_child = self.ptr->children[self.ptr->child_count - 1];
-
- self.ptr->first_leaf.symbol = ts_subtree_leaf_symbol(first_child);
- self.ptr->first_leaf.parse_state = ts_subtree_leaf_parse_state(first_child);
-
- if (ts_subtree_fragile_left(first_child)) self.ptr->fragile_left = true;
- if (ts_subtree_fragile_right(last_child)) self.ptr->fragile_right = true;
-
- if (
- self.ptr->child_count >= 2 &&
- !self.ptr->visible &&
- !self.ptr->named &&
- ts_subtree_symbol(first_child) == self.ptr->symbol
- ) {
- if (ts_subtree_repeat_depth(first_child) > ts_subtree_repeat_depth(last_child)) {
- self.ptr->repeat_depth = ts_subtree_repeat_depth(first_child) + 1;
- } else {
- self.ptr->repeat_depth = ts_subtree_repeat_depth(last_child) + 1;
- }
- }
- }
-}
-
-MutableSubtree ts_subtree_new_node(SubtreePool *pool, TSSymbol symbol,
- SubtreeArray *children, unsigned production_id,
- const TSLanguage *language) {
- TSSymbolMetadata metadata = ts_language_symbol_metadata(language, symbol);
- bool fragile = symbol == ts_builtin_sym_error || symbol == ts_builtin_sym_error_repeat;
- SubtreeHeapData *data = ts_subtree_pool_allocate(pool);
- *data = (SubtreeHeapData) {
- .ref_count = 1,
- .symbol = symbol,
- .visible = metadata.visible,
- .named = metadata.named,
- .has_changes = false,
- .fragile_left = fragile,
- .fragile_right = fragile,
- .is_keyword = false,
- {{
- .node_count = 0,
- .production_id = production_id,
- .first_leaf = {.symbol = 0, .parse_state = 0},
- }}
- };
- MutableSubtree result = {.ptr = data};
- ts_subtree_set_children(result, children->contents, children->size, language);
- return result;
-}
-
-Subtree ts_subtree_new_error_node(SubtreePool *pool, SubtreeArray *children,
- bool extra, const TSLanguage *language) {
- MutableSubtree result = ts_subtree_new_node(
- pool, ts_builtin_sym_error, children, 0, language
- );
- result.ptr->extra = extra;
- return ts_subtree_from_mut(result);
-}
-
-Subtree ts_subtree_new_missing_leaf(SubtreePool *pool, TSSymbol symbol, Length padding,
- const TSLanguage *language) {
- Subtree result = ts_subtree_new_leaf(
- pool, symbol, padding, length_zero(), 0,
- 0, false, false, language
- );
-
- if (result.data.is_inline) {
- result.data.is_missing = true;
- } else {
- ((SubtreeHeapData *)result.ptr)->is_missing = true;
- }
-
- return result;
-}
-
-void ts_subtree_retain(Subtree self) {
- if (self.data.is_inline) return;
- assert(self.ptr->ref_count > 0);
- atomic_inc((volatile uint32_t *)&self.ptr->ref_count);
- assert(self.ptr->ref_count != 0);
-}
-
-void ts_subtree_release(SubtreePool *pool, Subtree self) {
- if (self.data.is_inline) return;
- array_clear(&pool->tree_stack);
-
- assert(self.ptr->ref_count > 0);
- if (atomic_dec((volatile uint32_t *)&self.ptr->ref_count) == 0) {
- array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(self));
- }
-
- while (pool->tree_stack.size > 0) {
- MutableSubtree tree = array_pop(&pool->tree_stack);
- if (tree.ptr->child_count > 0) {
- for (uint32_t i = 0; i < tree.ptr->child_count; i++) {
- Subtree child = tree.ptr->children[i];
- if (child.data.is_inline) continue;
- assert(child.ptr->ref_count > 0);
- if (atomic_dec((volatile uint32_t *)&child.ptr->ref_count) == 0) {
- array_push(&pool->tree_stack, ts_subtree_to_mut_unsafe(child));
- }
- }
- ts_free(tree.ptr->children);
- } else if (tree.ptr->has_external_tokens) {
- ts_external_scanner_state_delete(&tree.ptr->external_scanner_state);
- }
- ts_subtree_pool_free(pool, tree.ptr);
- }
-}
-
-bool ts_subtree_eq(Subtree self, Subtree other) {
- if (self.data.is_inline || other.data.is_inline) {
- return memcmp(&self, &other, sizeof(SubtreeInlineData)) == 0;
- }
-
- if (self.ptr) {
- if (!other.ptr) return false;
- } else {
- return !other.ptr;
- }
-
- if (self.ptr->symbol != other.ptr->symbol) return false;
- if (self.ptr->visible != other.ptr->visible) return false;
- if (self.ptr->named != other.ptr->named) return false;
- if (self.ptr->padding.bytes != other.ptr->padding.bytes) return false;
- if (self.ptr->size.bytes != other.ptr->size.bytes) return false;
- if (self.ptr->symbol == ts_builtin_sym_error) return self.ptr->lookahead_char == other.ptr->lookahead_char;
- if (self.ptr->child_count != other.ptr->child_count) return false;
- if (self.ptr->child_count > 0) {
- if (self.ptr->visible_child_count != other.ptr->visible_child_count) return false;
- if (self.ptr->named_child_count != other.ptr->named_child_count) return false;
-
- for (uint32_t i = 0; i < self.ptr->child_count; i++) {
- if (!ts_subtree_eq(self.ptr->children[i], other.ptr->children[i])) {
- return false;
- }
- }
- }
- return true;
-}
-
-int ts_subtree_compare(Subtree left, Subtree right) {
- if (ts_subtree_symbol(left) < ts_subtree_symbol(right)) return -1;
- if (ts_subtree_symbol(right) < ts_subtree_symbol(left)) return 1;
- if (ts_subtree_child_count(left) < ts_subtree_child_count(right)) return -1;
- if (ts_subtree_child_count(right) < ts_subtree_child_count(left)) return 1;
- for (uint32_t i = 0, n = ts_subtree_child_count(left); i < n; i++) {
- Subtree left_child = left.ptr->children[i];
- Subtree right_child = right.ptr->children[i];
- switch (ts_subtree_compare(left_child, right_child)) {
- case -1: return -1;
- case 1: return 1;
- default: break;
- }
- }
- return 0;
-}
-
-static inline void ts_subtree_set_has_changes(MutableSubtree *self) {
- if (self->data.is_inline) {
- self->data.has_changes = true;
- } else {
- self->ptr->has_changes = true;
- }
-}
-
-Subtree ts_subtree_edit(Subtree self, const TSInputEdit *edit, SubtreePool *pool) {
- typedef struct {
- Subtree *tree;
- Edit edit;
- } StackEntry;
-
- Array(StackEntry) stack = array_new();
- array_push(&stack, ((StackEntry) {
- .tree = &self,
- .edit = (Edit) {
- .start = {edit->start_byte, edit->start_point},
- .old_end = {edit->old_end_byte, edit->old_end_point},
- .new_end = {edit->new_end_byte, edit->new_end_point},
- },
- }));
-
- while (stack.size) {
- StackEntry entry = array_pop(&stack);
- Edit edit = entry.edit;
- bool is_noop = edit.old_end.bytes == edit.start.bytes && edit.new_end.bytes == edit.start.bytes;
- bool is_pure_insertion = edit.old_end.bytes == edit.start.bytes;
-
- Length size = ts_subtree_size(*entry.tree);
- Length padding = ts_subtree_padding(*entry.tree);
- uint32_t lookahead_bytes = ts_subtree_lookahead_bytes(*entry.tree);
- uint32_t end_byte = padding.bytes + size.bytes + lookahead_bytes;
- if (edit.start.bytes > end_byte || (is_noop && edit.start.bytes == end_byte)) continue;
-
- // If the edit is entirely within the space before this subtree, then shift this
- // subtree over according to the edit without changing its size.
- if (edit.old_end.bytes <= padding.bytes) {
- padding = length_add(edit.new_end, length_sub(padding, edit.old_end));
- }
-
- // If the edit starts in the space before this subtree and extends into this subtree,
- // shrink the subtree's content to compensate for the change in the space before it.
- else if (edit.start.bytes < padding.bytes) {
- size = length_sub(size, length_sub(edit.old_end, padding));
- padding = edit.new_end;
- }
-
- // If the edit is a pure insertion right at the start of the subtree,
- // shift the subtree over according to the insertion.
- else if (edit.start.bytes == padding.bytes && is_pure_insertion) {
- padding = edit.new_end;
- }
-
- // If the edit is within this subtree, resize the subtree to reflect the edit.
- else {
- uint32_t total_bytes = padding.bytes + size.bytes;
- if (edit.start.bytes < total_bytes ||
- (edit.start.bytes == total_bytes && is_pure_insertion)) {
- size = length_add(
- length_sub(edit.new_end, padding),
- length_sub(size, length_sub(edit.old_end, padding))
- );
- }
- }
-
- MutableSubtree result = ts_subtree_make_mut(pool, *entry.tree);
-
- if (result.data.is_inline) {
- if (ts_subtree_can_inline(padding, size, lookahead_bytes)) {
- result.data.padding_bytes = padding.bytes;
- result.data.padding_rows = padding.extent.row;
- result.data.padding_columns = padding.extent.column;
- result.data.size_bytes = size.bytes;
- } else {
- SubtreeHeapData *data = ts_subtree_pool_allocate(pool);
- data->ref_count = 1;
- data->padding = padding;
- data->size = size;
- data->lookahead_bytes = lookahead_bytes;
- data->error_cost = 0;
- data->child_count = 0;
- data->symbol = result.data.symbol;
- data->parse_state = result.data.parse_state;
- data->visible = result.data.visible;
- data->named = result.data.named;
- data->extra = result.data.extra;
- data->fragile_left = false;
- data->fragile_right = false;
- data->has_changes = false;
- data->has_external_tokens = false;
- data->is_missing = result.data.is_missing;
- data->is_keyword = result.data.is_keyword;
- result.ptr = data;
- }
- } else {
- result.ptr->padding = padding;
- result.ptr->size = size;
- }
-
- ts_subtree_set_has_changes(&result);
- *entry.tree = ts_subtree_from_mut(result);
-
- Length child_left, child_right = length_zero();
- for (uint32_t i = 0, n = ts_subtree_child_count(*entry.tree); i < n; i++) {
- Subtree *child = &result.ptr->children[i];
- Length child_size = ts_subtree_total_size(*child);
- child_left = child_right;
- child_right = length_add(child_left, child_size);
-
- // If this child ends before the edit, it is not affected.
- if (child_right.bytes + ts_subtree_lookahead_bytes(*child) < edit.start.bytes) continue;
-
- // If this child starts after the edit, then we're done processing children.
- if (child_left.bytes > edit.old_end.bytes ||
- (child_left.bytes == edit.old_end.bytes && child_size.bytes > 0 && i > 0)) break;
-
- // Transform edit into the child's coordinate space.
- Edit child_edit = {
- .start = length_sub(edit.start, child_left),
- .old_end = length_sub(edit.old_end, child_left),
- .new_end = length_sub(edit.new_end, child_left),
- };
-
- // Clamp child_edit to the child's bounds.
- if (edit.start.bytes < child_left.bytes) child_edit.start = length_zero();
- if (edit.old_end.bytes < child_left.bytes) child_edit.old_end = length_zero();
- if (edit.new_end.bytes < child_left.bytes) child_edit.new_end = length_zero();
- if (edit.old_end.bytes > child_right.bytes) child_edit.old_end = child_size;
-
- // Interpret all inserted text as applying to the *first* child that touches the edit.
- // Subsequent children are only never have any text inserted into them; they are only
- // shrunk to compensate for the edit.
- if (child_right.bytes > edit.start.bytes ||
- (child_right.bytes == edit.start.bytes && is_pure_insertion)) {
- edit.new_end = edit.start;
- }
-
- // Children that occur before the edit are not reshaped by the edit.
- else {
- child_edit.old_end = child_edit.start;
- child_edit.new_end = child_edit.start;
- }
-
- // Queue processing of this child's subtree.
- array_push(&stack, ((StackEntry) {
- .tree = child,
- .edit = child_edit,
- }));
- }
- }
-
- array_delete(&stack);
- return self;
-}
-
-Subtree ts_subtree_last_external_token(Subtree tree) {
- if (!ts_subtree_has_external_tokens(tree)) return NULL_SUBTREE;
- while (tree.ptr->child_count > 0) {
- for (uint32_t i = tree.ptr->child_count - 1; i + 1 > 0; i--) {
- Subtree child = tree.ptr->children[i];
- if (ts_subtree_has_external_tokens(child)) {
- tree = child;
- break;
- }
- }
- }
- return tree;
-}
-
-static size_t ts_subtree__write_char_to_string(char *s, size_t n, int32_t c) {
- if (c == -1)
- return snprintf(s, n, "INVALID");
- else if (c == '\0')
- return snprintf(s, n, "'\\0'");
- else if (c == '\n')
- return snprintf(s, n, "'\\n'");
- else if (c == '\t')
- return snprintf(s, n, "'\\t'");
- else if (c == '\r')
- return snprintf(s, n, "'\\r'");
- else if (0 < c && c < 128 && isprint(c))
- return snprintf(s, n, "'%c'", c);
- else
- return snprintf(s, n, "%d", c);
-}
-
-static void ts_subtree__write_dot_string(FILE *f, const char *string) {
- for (const char *c = string; *c; c++) {
- if (*c == '"') {
- fputs("\\\"", f);
- } else if (*c == '\n') {
- fputs("\\n", f);
- } else {
- fputc(*c, f);
- }
- }
-}
-
-static const char *ROOT_FIELD = "__ROOT__";
-
-static size_t ts_subtree__write_to_string(
- Subtree self, char *string, size_t limit,
- const TSLanguage *language, bool include_all,
- TSSymbol alias_symbol, bool alias_is_named, const char *field_name
-) {
- if (!self.ptr) return snprintf(string, limit, "(NULL)");
-
- char *cursor = string;
- char **writer = (limit > 0) ? &cursor : &string;
- bool is_root = field_name == ROOT_FIELD;
- bool is_visible =
- include_all ||
- ts_subtree_missing(self) ||
- (
- alias_symbol
- ? alias_is_named
- : ts_subtree_visible(self) && ts_subtree_named(self)
- );
-
- if (is_visible) {
- if (!is_root) {
- cursor += snprintf(*writer, limit, " ");
- if (field_name) {
- cursor += snprintf(*writer, limit, "%s: ", field_name);
- }
- }
-
- if (ts_subtree_is_error(self) && ts_subtree_child_count(self) == 0 && self.ptr->size.bytes > 0) {
- cursor += snprintf(*writer, limit, "(UNEXPECTED ");
- cursor += ts_subtree__write_char_to_string(*writer, limit, self.ptr->lookahead_char);
- } else {
- TSSymbol symbol = alias_symbol ? alias_symbol : ts_subtree_symbol(self);
- const char *symbol_name = ts_language_symbol_name(language, symbol);
- if (ts_subtree_missing(self)) {
- cursor += snprintf(*writer, limit, "(MISSING ");
- if (alias_is_named || ts_subtree_named(self)) {
- cursor += snprintf(*writer, limit, "%s", symbol_name);
- } else {
- cursor += snprintf(*writer, limit, "\"%s\"", symbol_name);
- }
- } else {
- cursor += snprintf(*writer, limit, "(%s", symbol_name);
- }
- }
- } else if (is_root) {
- TSSymbol symbol = ts_subtree_symbol(self);
- const char *symbol_name = ts_language_symbol_name(language, symbol);
- cursor += snprintf(*writer, limit, "(\"%s\")", symbol_name);
- }
-
- if (ts_subtree_child_count(self)) {
- const TSSymbol *alias_sequence = ts_language_alias_sequence(language, self.ptr->production_id);
- const TSFieldMapEntry *field_map, *field_map_end;
- ts_language_field_map(
- language,
- self.ptr->production_id,
- &field_map,
- &field_map_end
- );
-
- uint32_t structural_child_index = 0;
- for (uint32_t i = 0; i < self.ptr->child_count; i++) {
- Subtree child = self.ptr->children[i];
- if (ts_subtree_extra(child)) {
- cursor += ts_subtree__write_to_string(
- child, *writer, limit,
- language, include_all,
- 0, false, NULL
- );
- } else {
- TSSymbol alias_symbol = alias_sequence
- ? alias_sequence[structural_child_index]
- : 0;
- bool alias_is_named = alias_symbol
- ? ts_language_symbol_metadata(language, alias_symbol).named
- : false;
-
- const char *child_field_name = is_visible ? NULL : field_name;
- for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) {
- if (!i->inherited && i->child_index == structural_child_index) {
- child_field_name = language->field_names[i->field_id];
- break;
- }
- }
-
- cursor += ts_subtree__write_to_string(
- child, *writer, limit,
- language, include_all,
- alias_symbol, alias_is_named, child_field_name
- );
- structural_child_index++;
- }
- }
- }
-
- if (is_visible) cursor += snprintf(*writer, limit, ")");
-
- return cursor - string;
-}
-
-char *ts_subtree_string(
- Subtree self,
- const TSLanguage *language,
- bool include_all
-) {
- char scratch_string[1];
- size_t size = ts_subtree__write_to_string(
- self, scratch_string, 0,
- language, include_all,
- 0, false, ROOT_FIELD
- ) + 1;
- char *result = malloc(size * sizeof(char));
- ts_subtree__write_to_string(
- self, result, size,
- language, include_all,
- 0, false, ROOT_FIELD
- );
- return result;
-}
-
-void ts_subtree__print_dot_graph(const Subtree *self, uint32_t start_offset,
- const TSLanguage *language, TSSymbol alias_symbol,
- FILE *f) {
- TSSymbol subtree_symbol = ts_subtree_symbol(*self);
- TSSymbol symbol = alias_symbol ? alias_symbol : subtree_symbol;
- uint32_t end_offset = start_offset + ts_subtree_total_bytes(*self);
- fprintf(f, "tree_%p [label=\"", self);
- ts_subtree__write_dot_string(f, ts_language_symbol_name(language, symbol));
- fprintf(f, "\"");
-
- if (ts_subtree_child_count(*self) == 0) fprintf(f, ", shape=plaintext");
- if (ts_subtree_extra(*self)) fprintf(f, ", fontcolor=gray");
-
- fprintf(f, ", tooltip=\""
- "range: %u - %u\n"
- "state: %d\n"
- "error-cost: %u\n"
- "has-changes: %u\n"
- "repeat-depth: %u\n"
- "lookahead-bytes: %u",
- start_offset, end_offset,
- ts_subtree_parse_state(*self),
- ts_subtree_error_cost(*self),
- ts_subtree_has_changes(*self),
- ts_subtree_repeat_depth(*self),
- ts_subtree_lookahead_bytes(*self)
- );
-
- if (ts_subtree_is_error(*self) && ts_subtree_child_count(*self) == 0) {
- fprintf(f, "\ncharacter: '%c'", self->ptr->lookahead_char);
- }
-
- fprintf(f, "\"]\n");
-
- uint32_t child_start_offset = start_offset;
- uint32_t child_info_offset =
- language->max_alias_sequence_length *
- ts_subtree_production_id(*self);
- for (uint32_t i = 0, n = ts_subtree_child_count(*self); i < n; i++) {
- const Subtree *child = &self->ptr->children[i];
- TSSymbol alias_symbol = 0;
- if (!ts_subtree_extra(*child) && child_info_offset) {
- alias_symbol = language->alias_sequences[child_info_offset];
- child_info_offset++;
- }
- ts_subtree__print_dot_graph(child, child_start_offset, language, alias_symbol, f);
- fprintf(f, "tree_%p -> tree_%p [tooltip=%u]\n", self, child, i);
- child_start_offset += ts_subtree_total_bytes(*child);
- }
-}
-
-void ts_subtree_print_dot_graph(Subtree self, const TSLanguage *language, FILE *f) {
- fprintf(f, "digraph tree {\n");
- fprintf(f, "edge [arrowhead=none]\n");
- ts_subtree__print_dot_graph(&self, 0, language, 0, f);
- fprintf(f, "}\n");
-}
-
-bool ts_subtree_external_scanner_state_eq(Subtree self, Subtree other) {
- const ExternalScannerState *state1 = &empty_state;
- const ExternalScannerState *state2 = &empty_state;
- if (self.ptr && ts_subtree_has_external_tokens(self) && !self.ptr->child_count) {
- state1 = &self.ptr->external_scanner_state;
- }
- if (other.ptr && ts_subtree_has_external_tokens(other) && !other.ptr->child_count) {
- state2 = &other.ptr->external_scanner_state;
- }
- return ts_external_scanner_state_eq(state1, state2);
-}
diff --git a/src/tree_sitter/subtree.h b/src/tree_sitter/subtree.h
deleted file mode 100644
index 18c48dcbd0..0000000000
--- a/src/tree_sitter/subtree.h
+++ /dev/null
@@ -1,285 +0,0 @@
-#ifndef TREE_SITTER_SUBTREE_H_
-#define TREE_SITTER_SUBTREE_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <limits.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include "./length.h"
-#include "./array.h"
-#include "./error_costs.h"
-#include "tree_sitter/api.h"
-#include "tree_sitter/parser.h"
-
-static const TSStateId TS_TREE_STATE_NONE = USHRT_MAX;
-#define NULL_SUBTREE ((Subtree) {.ptr = NULL})
-
-typedef union Subtree Subtree;
-typedef union MutableSubtree MutableSubtree;
-
-typedef struct {
- union {
- char *long_data;
- char short_data[24];
- };
- uint32_t length;
-} ExternalScannerState;
-
-typedef struct {
- bool is_inline : 1;
- bool visible : 1;
- bool named : 1;
- bool extra : 1;
- bool has_changes : 1;
- bool is_missing : 1;
- bool is_keyword : 1;
- uint8_t symbol;
- uint8_t padding_bytes;
- uint8_t size_bytes;
- uint8_t padding_columns;
- uint8_t padding_rows : 4;
- uint8_t lookahead_bytes : 4;
- uint16_t parse_state;
-} SubtreeInlineData;
-
-typedef struct {
- volatile uint32_t ref_count;
- Length padding;
- Length size;
- uint32_t lookahead_bytes;
- uint32_t error_cost;
- uint32_t child_count;
- TSSymbol symbol;
- TSStateId parse_state;
-
- bool visible : 1;
- bool named : 1;
- bool extra : 1;
- bool fragile_left : 1;
- bool fragile_right : 1;
- bool has_changes : 1;
- bool has_external_tokens : 1;
- bool is_missing : 1;
- bool is_keyword : 1;
-
- union {
- // Non-terminal subtrees (`child_count > 0`)
- struct {
- Subtree *children;
- uint32_t visible_child_count;
- uint32_t named_child_count;
- uint32_t node_count;
- uint32_t repeat_depth;
- int32_t dynamic_precedence;
- uint16_t production_id;
- struct {
- TSSymbol symbol;
- TSStateId parse_state;
- } first_leaf;
- };
-
- // External terminal subtrees (`child_count == 0 && has_external_tokens`)
- ExternalScannerState external_scanner_state;
-
- // Error terminal subtrees (`child_count == 0 && symbol == ts_builtin_sym_error`)
- int32_t lookahead_char;
- };
-} SubtreeHeapData;
-
-union Subtree {
- SubtreeInlineData data;
- const SubtreeHeapData *ptr;
-};
-
-union MutableSubtree {
- SubtreeInlineData data;
- SubtreeHeapData *ptr;
-};
-
-typedef Array(Subtree) SubtreeArray;
-typedef Array(MutableSubtree) MutableSubtreeArray;
-
-typedef struct {
- MutableSubtreeArray free_trees;
- MutableSubtreeArray tree_stack;
-} SubtreePool;
-
-void ts_external_scanner_state_init(ExternalScannerState *, const char *, unsigned);
-const char *ts_external_scanner_state_data(const ExternalScannerState *);
-
-void ts_subtree_array_copy(SubtreeArray, SubtreeArray *);
-void ts_subtree_array_delete(SubtreePool *, SubtreeArray *);
-SubtreeArray ts_subtree_array_remove_trailing_extras(SubtreeArray *);
-void ts_subtree_array_reverse(SubtreeArray *);
-
-SubtreePool ts_subtree_pool_new(uint32_t capacity);
-void ts_subtree_pool_delete(SubtreePool *);
-
-Subtree ts_subtree_new_leaf(
- SubtreePool *, TSSymbol, Length, Length, uint32_t,
- TSStateId, bool, bool, const TSLanguage *
-);
-Subtree ts_subtree_new_error(
- SubtreePool *, int32_t, Length, Length, uint32_t, TSStateId, const TSLanguage *
-);
-MutableSubtree ts_subtree_new_node(SubtreePool *, TSSymbol, SubtreeArray *, unsigned, const TSLanguage *);
-Subtree ts_subtree_new_error_node(SubtreePool *, SubtreeArray *, bool, const TSLanguage *);
-Subtree ts_subtree_new_missing_leaf(SubtreePool *, TSSymbol, Length, const TSLanguage *);
-MutableSubtree ts_subtree_make_mut(SubtreePool *, Subtree);
-void ts_subtree_retain(Subtree);
-void ts_subtree_release(SubtreePool *, Subtree);
-bool ts_subtree_eq(Subtree, Subtree);
-int ts_subtree_compare(Subtree, Subtree);
-void ts_subtree_set_symbol(MutableSubtree *, TSSymbol, const TSLanguage *);
-void ts_subtree_set_children(MutableSubtree, Subtree *, uint32_t, const TSLanguage *);
-void ts_subtree_balance(Subtree, SubtreePool *, const TSLanguage *);
-Subtree ts_subtree_edit(Subtree, const TSInputEdit *edit, SubtreePool *);
-char *ts_subtree_string(Subtree, const TSLanguage *, bool include_all);
-void ts_subtree_print_dot_graph(Subtree, const TSLanguage *, FILE *);
-Subtree ts_subtree_last_external_token(Subtree);
-bool ts_subtree_external_scanner_state_eq(Subtree, Subtree);
-
-#define SUBTREE_GET(self, name) (self.data.is_inline ? self.data.name : self.ptr->name)
-
-static inline TSSymbol ts_subtree_symbol(Subtree self) { return SUBTREE_GET(self, symbol); }
-static inline bool ts_subtree_visible(Subtree self) { return SUBTREE_GET(self, visible); }
-static inline bool ts_subtree_named(Subtree self) { return SUBTREE_GET(self, named); }
-static inline bool ts_subtree_extra(Subtree self) { return SUBTREE_GET(self, extra); }
-static inline bool ts_subtree_has_changes(Subtree self) { return SUBTREE_GET(self, has_changes); }
-static inline bool ts_subtree_missing(Subtree self) { return SUBTREE_GET(self, is_missing); }
-static inline bool ts_subtree_is_keyword(Subtree self) { return SUBTREE_GET(self, is_keyword); }
-static inline TSStateId ts_subtree_parse_state(Subtree self) { return SUBTREE_GET(self, parse_state); }
-static inline uint32_t ts_subtree_lookahead_bytes(Subtree self) { return SUBTREE_GET(self, lookahead_bytes); }
-
-#undef SUBTREE_GET
-
-static inline void ts_subtree_set_extra(MutableSubtree *self) {
- if (self->data.is_inline) {
- self->data.extra = true;
- } else {
- self->ptr->extra = true;
- }
-}
-
-static inline TSSymbol ts_subtree_leaf_symbol(Subtree self) {
- if (self.data.is_inline) return self.data.symbol;
- if (self.ptr->child_count == 0) return self.ptr->symbol;
- return self.ptr->first_leaf.symbol;
-}
-
-static inline TSStateId ts_subtree_leaf_parse_state(Subtree self) {
- if (self.data.is_inline) return self.data.parse_state;
- if (self.ptr->child_count == 0) return self.ptr->parse_state;
- return self.ptr->first_leaf.parse_state;
-}
-
-static inline Length ts_subtree_padding(Subtree self) {
- if (self.data.is_inline) {
- Length result = {self.data.padding_bytes, {self.data.padding_rows, self.data.padding_columns}};
- return result;
- } else {
- return self.ptr->padding;
- }
-}
-
-static inline Length ts_subtree_size(Subtree self) {
- if (self.data.is_inline) {
- Length result = {self.data.size_bytes, {0, self.data.size_bytes}};
- return result;
- } else {
- return self.ptr->size;
- }
-}
-
-static inline Length ts_subtree_total_size(Subtree self) {
- return length_add(ts_subtree_padding(self), ts_subtree_size(self));
-}
-
-static inline uint32_t ts_subtree_total_bytes(Subtree self) {
- return ts_subtree_total_size(self).bytes;
-}
-
-static inline uint32_t ts_subtree_child_count(Subtree self) {
- return self.data.is_inline ? 0 : self.ptr->child_count;
-}
-
-static inline uint32_t ts_subtree_repeat_depth(Subtree self) {
- return self.data.is_inline ? 0 : self.ptr->repeat_depth;
-}
-
-static inline uint32_t ts_subtree_node_count(Subtree self) {
- return (self.data.is_inline || self.ptr->child_count == 0) ? 1 : self.ptr->node_count;
-}
-
-static inline uint32_t ts_subtree_visible_child_count(Subtree self) {
- if (ts_subtree_child_count(self) > 0) {
- return self.ptr->visible_child_count;
- } else {
- return 0;
- }
-}
-
-static inline uint32_t ts_subtree_error_cost(Subtree self) {
- if (ts_subtree_missing(self)) {
- return ERROR_COST_PER_MISSING_TREE + ERROR_COST_PER_RECOVERY;
- } else {
- return self.data.is_inline ? 0 : self.ptr->error_cost;
- }
-}
-
-static inline int32_t ts_subtree_dynamic_precedence(Subtree self) {
- return (self.data.is_inline || self.ptr->child_count == 0) ? 0 : self.ptr->dynamic_precedence;
-}
-
-static inline uint16_t ts_subtree_production_id(Subtree self) {
- if (ts_subtree_child_count(self) > 0) {
- return self.ptr->production_id;
- } else {
- return 0;
- }
-}
-
-static inline bool ts_subtree_fragile_left(Subtree self) {
- return self.data.is_inline ? false : self.ptr->fragile_left;
-}
-
-static inline bool ts_subtree_fragile_right(Subtree self) {
- return self.data.is_inline ? false : self.ptr->fragile_right;
-}
-
-static inline bool ts_subtree_has_external_tokens(Subtree self) {
- return self.data.is_inline ? false : self.ptr->has_external_tokens;
-}
-
-static inline bool ts_subtree_is_fragile(Subtree self) {
- return self.data.is_inline ? false : (self.ptr->fragile_left || self.ptr->fragile_right);
-}
-
-static inline bool ts_subtree_is_error(Subtree self) {
- return ts_subtree_symbol(self) == ts_builtin_sym_error;
-}
-
-static inline bool ts_subtree_is_eof(Subtree self) {
- return ts_subtree_symbol(self) == ts_builtin_sym_end;
-}
-
-static inline Subtree ts_subtree_from_mut(MutableSubtree self) {
- Subtree result;
- result.data = self.data;
- return result;
-}
-
-static inline MutableSubtree ts_subtree_to_mut_unsafe(Subtree self) {
- MutableSubtree result;
- result.data = self.data;
- return result;
-}
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // TREE_SITTER_SUBTREE_H_
diff --git a/src/tree_sitter/tree.c b/src/tree_sitter/tree.c
deleted file mode 100644
index 391fa7f592..0000000000
--- a/src/tree_sitter/tree.c
+++ /dev/null
@@ -1,148 +0,0 @@
-#include "tree_sitter/api.h"
-#include "./array.h"
-#include "./get_changed_ranges.h"
-#include "./subtree.h"
-#include "./tree_cursor.h"
-#include "./tree.h"
-
-static const unsigned PARENT_CACHE_CAPACITY = 32;
-
-TSTree *ts_tree_new(
- Subtree root, const TSLanguage *language,
- const TSRange *included_ranges, unsigned included_range_count
-) {
- TSTree *result = ts_malloc(sizeof(TSTree));
- result->root = root;
- result->language = language;
- result->parent_cache = NULL;
- result->parent_cache_start = 0;
- result->parent_cache_size = 0;
- result->included_ranges = ts_calloc(included_range_count, sizeof(TSRange));
- memcpy(result->included_ranges, included_ranges, included_range_count * sizeof(TSRange));
- result->included_range_count = included_range_count;
- return result;
-}
-
-TSTree *ts_tree_copy(const TSTree *self) {
- ts_subtree_retain(self->root);
- return ts_tree_new(self->root, self->language, self->included_ranges, self->included_range_count);
-}
-
-void ts_tree_delete(TSTree *self) {
- if (!self) return;
-
- SubtreePool pool = ts_subtree_pool_new(0);
- ts_subtree_release(&pool, self->root);
- ts_subtree_pool_delete(&pool);
- ts_free(self->included_ranges);
- if (self->parent_cache) ts_free(self->parent_cache);
- ts_free(self);
-}
-
-TSNode ts_tree_root_node(const TSTree *self) {
- return ts_node_new(self, &self->root, ts_subtree_padding(self->root), 0);
-}
-
-const TSLanguage *ts_tree_language(const TSTree *self) {
- return self->language;
-}
-
-void ts_tree_edit(TSTree *self, const TSInputEdit *edit) {
- for (unsigned i = 0; i < self->included_range_count; i++) {
- TSRange *range = &self->included_ranges[i];
- if (range->end_byte >= edit->old_end_byte) {
- if (range->end_byte != UINT32_MAX) {
- range->end_byte = edit->new_end_byte + (range->end_byte - edit->old_end_byte);
- range->end_point = point_add(
- edit->new_end_point,
- point_sub(range->end_point, edit->old_end_point)
- );
- if (range->end_byte < edit->new_end_byte) {
- range->end_byte = UINT32_MAX;
- range->end_point = POINT_MAX;
- }
- }
- if (range->start_byte >= edit->old_end_byte) {
- range->start_byte = edit->new_end_byte + (range->start_byte - edit->old_end_byte);
- range->start_point = point_add(
- edit->new_end_point,
- point_sub(range->start_point, edit->old_end_point)
- );
- if (range->start_byte < edit->new_end_byte) {
- range->start_byte = UINT32_MAX;
- range->start_point = POINT_MAX;
- }
- }
- }
- }
-
- SubtreePool pool = ts_subtree_pool_new(0);
- self->root = ts_subtree_edit(self->root, edit, &pool);
- self->parent_cache_start = 0;
- self->parent_cache_size = 0;
- ts_subtree_pool_delete(&pool);
-}
-
-TSRange *ts_tree_get_changed_ranges(const TSTree *self, const TSTree *other, uint32_t *count) {
- TreeCursor cursor1 = {NULL, array_new()};
- TreeCursor cursor2 = {NULL, array_new()};
- ts_tree_cursor_init(&cursor1, ts_tree_root_node(self));
- ts_tree_cursor_init(&cursor2, ts_tree_root_node(other));
-
- TSRangeArray included_range_differences = array_new();
- ts_range_array_get_changed_ranges(
- self->included_ranges, self->included_range_count,
- other->included_ranges, other->included_range_count,
- &included_range_differences
- );
-
- TSRange *result;
- *count = ts_subtree_get_changed_ranges(
- &self->root, &other->root, &cursor1, &cursor2,
- self->language, &included_range_differences, &result
- );
-
- array_delete(&included_range_differences);
- array_delete(&cursor1.stack);
- array_delete(&cursor2.stack);
- return result;
-}
-
-void ts_tree_print_dot_graph(const TSTree *self, FILE *file) {
- ts_subtree_print_dot_graph(self->root, self->language, file);
-}
-
-TSNode ts_tree_get_cached_parent(const TSTree *self, const TSNode *node) {
- for (uint32_t i = 0; i < self->parent_cache_size; i++) {
- uint32_t index = (self->parent_cache_start + i) % PARENT_CACHE_CAPACITY;
- ParentCacheEntry *entry = &self->parent_cache[index];
- if (entry->child == node->id) {
- return ts_node_new(self, entry->parent, entry->position, entry->alias_symbol);
- }
- }
- return ts_node_new(NULL, NULL, length_zero(), 0);
-}
-
-void ts_tree_set_cached_parent(const TSTree *_self, const TSNode *node, const TSNode *parent) {
- TSTree *self = (TSTree *)_self;
- if (!self->parent_cache) {
- self->parent_cache = ts_calloc(PARENT_CACHE_CAPACITY, sizeof(ParentCacheEntry));
- }
-
- uint32_t index = (self->parent_cache_start + self->parent_cache_size) % PARENT_CACHE_CAPACITY;
- self->parent_cache[index] = (ParentCacheEntry) {
- .child = node->id,
- .parent = (const Subtree *)parent->id,
- .position = {
- parent->context[0],
- {parent->context[1], parent->context[2]}
- },
- .alias_symbol = parent->context[3],
- };
-
- if (self->parent_cache_size == PARENT_CACHE_CAPACITY) {
- self->parent_cache_start++;
- } else {
- self->parent_cache_size++;
- }
-}
diff --git a/src/tree_sitter/tree.h b/src/tree_sitter/tree.h
deleted file mode 100644
index 92a7e64179..0000000000
--- a/src/tree_sitter/tree.h
+++ /dev/null
@@ -1,34 +0,0 @@
-#ifndef TREE_SITTER_TREE_H_
-#define TREE_SITTER_TREE_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-typedef struct {
- const Subtree *child;
- const Subtree *parent;
- Length position;
- TSSymbol alias_symbol;
-} ParentCacheEntry;
-
-struct TSTree {
- Subtree root;
- const TSLanguage *language;
- ParentCacheEntry *parent_cache;
- uint32_t parent_cache_start;
- uint32_t parent_cache_size;
- TSRange *included_ranges;
- unsigned included_range_count;
-};
-
-TSTree *ts_tree_new(Subtree root, const TSLanguage *language, const TSRange *, unsigned);
-TSNode ts_node_new(const TSTree *, const Subtree *, Length, TSSymbol);
-TSNode ts_tree_get_cached_parent(const TSTree *, const TSNode *);
-void ts_tree_set_cached_parent(const TSTree *, const TSNode *, const TSNode *);
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // TREE_SITTER_TREE_H_
diff --git a/src/tree_sitter/tree_cursor.c b/src/tree_sitter/tree_cursor.c
deleted file mode 100644
index 00b9679d73..0000000000
--- a/src/tree_sitter/tree_cursor.c
+++ /dev/null
@@ -1,367 +0,0 @@
-#include "tree_sitter/api.h"
-#include "./alloc.h"
-#include "./tree_cursor.h"
-#include "./language.h"
-#include "./tree.h"
-
-typedef struct {
- Subtree parent;
- const TSTree *tree;
- Length position;
- uint32_t child_index;
- uint32_t structural_child_index;
- const TSSymbol *alias_sequence;
-} CursorChildIterator;
-
-// CursorChildIterator
-
-static inline CursorChildIterator ts_tree_cursor_iterate_children(const TreeCursor *self) {
- TreeCursorEntry *last_entry = array_back(&self->stack);
- if (ts_subtree_child_count(*last_entry->subtree) == 0) {
- return (CursorChildIterator) {NULL_SUBTREE, self->tree, length_zero(), 0, 0, NULL};
- }
- const TSSymbol *alias_sequence = ts_language_alias_sequence(
- self->tree->language,
- last_entry->subtree->ptr->production_id
- );
- return (CursorChildIterator) {
- .tree = self->tree,
- .parent = *last_entry->subtree,
- .position = last_entry->position,
- .child_index = 0,
- .structural_child_index = 0,
- .alias_sequence = alias_sequence,
- };
-}
-
-static inline bool ts_tree_cursor_child_iterator_next(CursorChildIterator *self,
- TreeCursorEntry *result,
- bool *visible) {
- if (!self->parent.ptr || self->child_index == self->parent.ptr->child_count) return false;
- const Subtree *child = &self->parent.ptr->children[self->child_index];
- *result = (TreeCursorEntry) {
- .subtree = child,
- .position = self->position,
- .child_index = self->child_index,
- .structural_child_index = self->structural_child_index,
- };
- *visible = ts_subtree_visible(*child);
- bool extra = ts_subtree_extra(*child);
- if (!extra && self->alias_sequence) {
- *visible |= self->alias_sequence[self->structural_child_index];
- self->structural_child_index++;
- }
-
- self->position = length_add(self->position, ts_subtree_size(*child));
- self->child_index++;
-
- if (self->child_index < self->parent.ptr->child_count) {
- Subtree next_child = self->parent.ptr->children[self->child_index];
- self->position = length_add(self->position, ts_subtree_padding(next_child));
- }
-
- return true;
-}
-
-// TSTreeCursor - lifecycle
-
-TSTreeCursor ts_tree_cursor_new(TSNode node) {
- TSTreeCursor self = {NULL, NULL, {0, 0}};
- ts_tree_cursor_init((TreeCursor *)&self, node);
- return self;
-}
-
-void ts_tree_cursor_reset(TSTreeCursor *_self, TSNode node) {
- ts_tree_cursor_init((TreeCursor *)_self, node);
-}
-
-void ts_tree_cursor_init(TreeCursor *self, TSNode node) {
- self->tree = node.tree;
- array_clear(&self->stack);
- array_push(&self->stack, ((TreeCursorEntry) {
- .subtree = (const Subtree *)node.id,
- .position = {
- ts_node_start_byte(node),
- ts_node_start_point(node)
- },
- .child_index = 0,
- .structural_child_index = 0,
- }));
-}
-
-void ts_tree_cursor_delete(TSTreeCursor *_self) {
- TreeCursor *self = (TreeCursor *)_self;
- array_delete(&self->stack);
-}
-
-// TSTreeCursor - walking the tree
-
-bool ts_tree_cursor_goto_first_child(TSTreeCursor *_self) {
- TreeCursor *self = (TreeCursor *)_self;
-
- bool did_descend;
- do {
- did_descend = false;
-
- bool visible;
- TreeCursorEntry entry;
- CursorChildIterator iterator = ts_tree_cursor_iterate_children(self);
- while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) {
- if (visible) {
- array_push(&self->stack, entry);
- return true;
- }
-
- if (ts_subtree_visible_child_count(*entry.subtree) > 0) {
- array_push(&self->stack, entry);
- did_descend = true;
- break;
- }
- }
- } while (did_descend);
-
- return false;
-}
-
-int64_t ts_tree_cursor_goto_first_child_for_byte(TSTreeCursor *_self, uint32_t goal_byte) {
- TreeCursor *self = (TreeCursor *)_self;
- uint32_t initial_size = self->stack.size;
- uint32_t visible_child_index = 0;
-
- bool did_descend;
- do {
- did_descend = false;
-
- bool visible;
- TreeCursorEntry entry;
- CursorChildIterator iterator = ts_tree_cursor_iterate_children(self);
- while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) {
- uint32_t end_byte = entry.position.bytes + ts_subtree_size(*entry.subtree).bytes;
- bool at_goal = end_byte > goal_byte;
- uint32_t visible_child_count = ts_subtree_visible_child_count(*entry.subtree);
-
- if (at_goal) {
- if (visible) {
- array_push(&self->stack, entry);
- return visible_child_index;
- }
-
- if (visible_child_count > 0) {
- array_push(&self->stack, entry);
- did_descend = true;
- break;
- }
- } else if (visible) {
- visible_child_index++;
- } else {
- visible_child_index += visible_child_count;
- }
- }
- } while (did_descend);
-
- if (self->stack.size > initial_size &&
- ts_tree_cursor_goto_next_sibling((TSTreeCursor *)self)) {
- return visible_child_index;
- }
-
- self->stack.size = initial_size;
- return -1;
-}
-
-bool ts_tree_cursor_goto_next_sibling(TSTreeCursor *_self) {
- TreeCursor *self = (TreeCursor *)_self;
- uint32_t initial_size = self->stack.size;
-
- while (self->stack.size > 1) {
- TreeCursorEntry entry = array_pop(&self->stack);
- CursorChildIterator iterator = ts_tree_cursor_iterate_children(self);
- iterator.child_index = entry.child_index;
- iterator.structural_child_index = entry.structural_child_index;
- iterator.position = entry.position;
-
- bool visible = false;
- ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible);
- if (visible && self->stack.size + 1 < initial_size) break;
-
- while (ts_tree_cursor_child_iterator_next(&iterator, &entry, &visible)) {
- if (visible) {
- array_push(&self->stack, entry);
- return true;
- }
-
- if (ts_subtree_visible_child_count(*entry.subtree)) {
- array_push(&self->stack, entry);
- ts_tree_cursor_goto_first_child(_self);
- return true;
- }
- }
- }
-
- self->stack.size = initial_size;
- return false;
-}
-
-bool ts_tree_cursor_goto_parent(TSTreeCursor *_self) {
- TreeCursor *self = (TreeCursor *)_self;
- for (unsigned i = self->stack.size - 2; i + 1 > 0; i--) {
- TreeCursorEntry *entry = &self->stack.contents[i];
- bool is_aliased = false;
- if (i > 0) {
- TreeCursorEntry *parent_entry = &self->stack.contents[i - 1];
- const TSSymbol *alias_sequence = ts_language_alias_sequence(
- self->tree->language,
- parent_entry->subtree->ptr->production_id
- );
- is_aliased = alias_sequence && alias_sequence[entry->structural_child_index];
- }
- if (ts_subtree_visible(*entry->subtree) || is_aliased) {
- self->stack.size = i + 1;
- return true;
- }
- }
- return false;
-}
-
-TSNode ts_tree_cursor_current_node(const TSTreeCursor *_self) {
- const TreeCursor *self = (const TreeCursor *)_self;
- TreeCursorEntry *last_entry = array_back(&self->stack);
- TSSymbol alias_symbol = 0;
- if (self->stack.size > 1) {
- TreeCursorEntry *parent_entry = &self->stack.contents[self->stack.size - 2];
- const TSSymbol *alias_sequence = ts_language_alias_sequence(
- self->tree->language,
- parent_entry->subtree->ptr->production_id
- );
- if (alias_sequence && !ts_subtree_extra(*last_entry->subtree)) {
- alias_symbol = alias_sequence[last_entry->structural_child_index];
- }
- }
- return ts_node_new(
- self->tree,
- last_entry->subtree,
- last_entry->position,
- alias_symbol
- );
-}
-
-TSFieldId ts_tree_cursor_current_status(
- const TSTreeCursor *_self,
- bool *can_have_later_siblings,
- bool *can_have_later_siblings_with_this_field
-) {
- const TreeCursor *self = (const TreeCursor *)_self;
- TSFieldId result = 0;
- *can_have_later_siblings = false;
- *can_have_later_siblings_with_this_field = false;
-
- // Walk up the tree, visiting the current node and its invisible ancestors,
- // because fields can refer to nodes through invisible *wrapper* nodes,
- for (unsigned i = self->stack.size - 1; i > 0; i--) {
- TreeCursorEntry *entry = &self->stack.contents[i];
- TreeCursorEntry *parent_entry = &self->stack.contents[i - 1];
-
- // Stop walking up when a visible ancestor is found.
- if (i != self->stack.size - 1) {
- if (ts_subtree_visible(*entry->subtree)) break;
- const TSSymbol *alias_sequence = ts_language_alias_sequence(
- self->tree->language,
- parent_entry->subtree->ptr->production_id
- );
- if (alias_sequence && alias_sequence[entry->structural_child_index]) {
- break;
- }
- }
-
- if (ts_subtree_child_count(*parent_entry->subtree) > entry->child_index + 1) {
- *can_have_later_siblings = true;
- }
-
- if (ts_subtree_extra(*entry->subtree)) break;
-
- const TSFieldMapEntry *field_map, *field_map_end;
- ts_language_field_map(
- self->tree->language,
- parent_entry->subtree->ptr->production_id,
- &field_map, &field_map_end
- );
-
- // Look for a field name associated with the current node.
- if (!result) {
- for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) {
- if (!i->inherited && i->child_index == entry->structural_child_index) {
- result = i->field_id;
- *can_have_later_siblings_with_this_field = false;
- break;
- }
- }
- }
-
- // Determine if there other later siblings with the same field name.
- if (result) {
- for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) {
- if (i->field_id == result && i->child_index > entry->structural_child_index) {
- *can_have_later_siblings_with_this_field = true;
- break;
- }
- }
- }
- }
-
- return result;
-}
-
-TSFieldId ts_tree_cursor_current_field_id(const TSTreeCursor *_self) {
- const TreeCursor *self = (const TreeCursor *)_self;
-
- // Walk up the tree, visiting the current node and its invisible ancestors.
- for (unsigned i = self->stack.size - 1; i > 0; i--) {
- TreeCursorEntry *entry = &self->stack.contents[i];
- TreeCursorEntry *parent_entry = &self->stack.contents[i - 1];
-
- // Stop walking up when another visible node is found.
- if (i != self->stack.size - 1) {
- if (ts_subtree_visible(*entry->subtree)) break;
- const TSSymbol *alias_sequence = ts_language_alias_sequence(
- self->tree->language,
- parent_entry->subtree->ptr->production_id
- );
- if (alias_sequence && alias_sequence[entry->structural_child_index]) {
- break;
- }
- }
-
- if (ts_subtree_extra(*entry->subtree)) break;
-
- const TSFieldMapEntry *field_map, *field_map_end;
- ts_language_field_map(
- self->tree->language,
- parent_entry->subtree->ptr->production_id,
- &field_map, &field_map_end
- );
- for (const TSFieldMapEntry *i = field_map; i < field_map_end; i++) {
- if (!i->inherited && i->child_index == entry->structural_child_index) {
- return i->field_id;
- }
- }
- }
- return 0;
-}
-
-const char *ts_tree_cursor_current_field_name(const TSTreeCursor *_self) {
- TSFieldId id = ts_tree_cursor_current_field_id(_self);
- if (id) {
- const TreeCursor *self = (const TreeCursor *)_self;
- return self->tree->language->field_names[id];
- } else {
- return NULL;
- }
-}
-
-TSTreeCursor ts_tree_cursor_copy(const TSTreeCursor *_cursor) {
- const TreeCursor *cursor = (const TreeCursor *)_cursor;
- TSTreeCursor res = {NULL, NULL, {0, 0}};
- TreeCursor *copy = (TreeCursor *)&res;
- copy->tree = cursor->tree;
- array_push_all(&copy->stack, &cursor->stack);
- return res;
-}
diff --git a/src/tree_sitter/tree_cursor.h b/src/tree_sitter/tree_cursor.h
deleted file mode 100644
index 5a39dd278c..0000000000
--- a/src/tree_sitter/tree_cursor.h
+++ /dev/null
@@ -1,21 +0,0 @@
-#ifndef TREE_SITTER_TREE_CURSOR_H_
-#define TREE_SITTER_TREE_CURSOR_H_
-
-#include "./subtree.h"
-
-typedef struct {
- const Subtree *subtree;
- Length position;
- uint32_t child_index;
- uint32_t structural_child_index;
-} TreeCursorEntry;
-
-typedef struct {
- const TSTree *tree;
- Array(TreeCursorEntry) stack;
-} TreeCursor;
-
-void ts_tree_cursor_init(TreeCursor *, TSNode);
-TSFieldId ts_tree_cursor_current_status(const TSTreeCursor *, bool *, bool *);
-
-#endif // TREE_SITTER_TREE_CURSOR_H_
diff --git a/src/tree_sitter/treesitter_commit_hash.txt b/src/tree_sitter/treesitter_commit_hash.txt
deleted file mode 100644
index bd7fcfbe76..0000000000
--- a/src/tree_sitter/treesitter_commit_hash.txt
+++ /dev/null
@@ -1 +0,0 @@
-81d533d2d1b580fdb507accabc91ceddffb5b6f0
diff --git a/src/tree_sitter/unicode.h b/src/tree_sitter/unicode.h
deleted file mode 100644
index 2ab51c2a3a..0000000000
--- a/src/tree_sitter/unicode.h
+++ /dev/null
@@ -1,50 +0,0 @@
-#ifndef TREE_SITTER_UNICODE_H_
-#define TREE_SITTER_UNICODE_H_
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-#include <limits.h>
-#include <stdint.h>
-
-#define U_EXPORT
-#define U_EXPORT2
-#include "./unicode/utf8.h"
-#include "./unicode/utf16.h"
-
-static const int32_t TS_DECODE_ERROR = U_SENTINEL;
-
-// These functions read one unicode code point from the given string,
-// returning the number of bytes consumed.
-typedef uint32_t (*UnicodeDecodeFunction)(
- const uint8_t *string,
- uint32_t length,
- int32_t *code_point
-);
-
-static inline uint32_t ts_decode_utf8(
- const uint8_t *string,
- uint32_t length,
- int32_t *code_point
-) {
- uint32_t i = 0;
- U8_NEXT(string, i, length, *code_point);
- return i;
-}
-
-static inline uint32_t ts_decode_utf16(
- const uint8_t *string,
- uint32_t length,
- int32_t *code_point
-) {
- uint32_t i = 0;
- U16_NEXT(((uint16_t *)string), i, length, *code_point);
- return i * 2;
-}
-
-#ifdef __cplusplus
-}
-#endif
-
-#endif // TREE_SITTER_UNICODE_H_
diff --git a/src/tree_sitter/unicode/ICU_SHA b/src/tree_sitter/unicode/ICU_SHA
deleted file mode 100644
index 3622283ba3..0000000000
--- a/src/tree_sitter/unicode/ICU_SHA
+++ /dev/null
@@ -1 +0,0 @@
-552b01f61127d30d6589aa4bf99468224979b661
diff --git a/src/tree_sitter/unicode/LICENSE b/src/tree_sitter/unicode/LICENSE
deleted file mode 100644
index 2e01e36876..0000000000
--- a/src/tree_sitter/unicode/LICENSE
+++ /dev/null
@@ -1,414 +0,0 @@
-COPYRIGHT AND PERMISSION NOTICE (ICU 58 and later)
-
-Copyright © 1991-2019 Unicode, Inc. All rights reserved.
-Distributed under the Terms of Use in https://www.unicode.org/copyright.html.
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of the Unicode data files and any associated documentation
-(the "Data Files") or Unicode software and any associated documentation
-(the "Software") to deal in the Data Files or Software
-without restriction, including without limitation the rights to use,
-copy, modify, merge, publish, distribute, and/or sell copies of
-the Data Files or Software, and to permit persons to whom the Data Files
-or Software are furnished to do so, provided that either
-(a) this copyright and permission notice appear with all copies
-of the Data Files or Software, or
-(b) this copyright and permission notice appear in associated
-Documentation.
-
-THE DATA FILES AND SOFTWARE ARE PROVIDED "AS IS", WITHOUT WARRANTY OF
-ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
-WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT OF THIRD PARTY RIGHTS.
-IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS INCLUDED IN THIS
-NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL
-DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
-DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
-TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
-PERFORMANCE OF THE DATA FILES OR SOFTWARE.
-
-Except as contained in this notice, the name of a copyright holder
-shall not be used in advertising or otherwise to promote the sale,
-use or other dealings in these Data Files or Software without prior
-written authorization of the copyright holder.
-
----------------------
-
-Third-Party Software Licenses
-
-This section contains third-party software notices and/or additional
-terms for licensed third-party software components included within ICU
-libraries.
-
-1. ICU License - ICU 1.8.1 to ICU 57.1
-
-COPYRIGHT AND PERMISSION NOTICE
-
-Copyright (c) 1995-2016 International Business Machines Corporation and others
-All rights reserved.
-
-Permission is hereby granted, free of charge, to any person obtaining
-a copy of this software and associated documentation files (the
-"Software"), to deal in the Software without restriction, including
-without limitation the rights to use, copy, modify, merge, publish,
-distribute, and/or sell copies of the Software, and to permit persons
-to whom the Software is furnished to do so, provided that the above
-copyright notice(s) and this permission notice appear in all copies of
-the Software and that both the above copyright notice(s) and this
-permission notice appear in supporting documentation.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
-MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
-OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
-HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY
-SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
-RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF
-CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
-CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-
-Except as contained in this notice, the name of a copyright holder
-shall not be used in advertising or otherwise to promote the sale, use
-or other dealings in this Software without prior written authorization
-of the copyright holder.
-
-All trademarks and registered trademarks mentioned herein are the
-property of their respective owners.
-
-2. Chinese/Japanese Word Break Dictionary Data (cjdict.txt)
-
- # The Google Chrome software developed by Google is licensed under
- # the BSD license. Other software included in this distribution is
- # provided under other licenses, as set forth below.
- #
- # The BSD License
- # http://opensource.org/licenses/bsd-license.php
- # Copyright (C) 2006-2008, Google Inc.
- #
- # All rights reserved.
- #
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions are met:
- #
- # Redistributions of source code must retain the above copyright notice,
- # this list of conditions and the following disclaimer.
- # Redistributions in binary form must reproduce the above
- # copyright notice, this list of conditions and the following
- # disclaimer in the documentation and/or other materials provided with
- # the distribution.
- # Neither the name of Google Inc. nor the names of its
- # contributors may be used to endorse or promote products derived from
- # this software without specific prior written permission.
- #
- #
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
- # BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
- # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
- # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
- # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- #
- #
- # The word list in cjdict.txt are generated by combining three word lists
- # listed below with further processing for compound word breaking. The
- # frequency is generated with an iterative training against Google web
- # corpora.
- #
- # * Libtabe (Chinese)
- # - https://sourceforge.net/project/?group_id=1519
- # - Its license terms and conditions are shown below.
- #
- # * IPADIC (Japanese)
- # - http://chasen.aist-nara.ac.jp/chasen/distribution.html
- # - Its license terms and conditions are shown below.
- #
- # ---------COPYING.libtabe ---- BEGIN--------------------
- #
- # /*
- # * Copyright (c) 1999 TaBE Project.
- # * Copyright (c) 1999 Pai-Hsiang Hsiao.
- # * All rights reserved.
- # *
- # * Redistribution and use in source and binary forms, with or without
- # * modification, are permitted provided that the following conditions
- # * are met:
- # *
- # * . Redistributions of source code must retain the above copyright
- # * notice, this list of conditions and the following disclaimer.
- # * . Redistributions in binary form must reproduce the above copyright
- # * notice, this list of conditions and the following disclaimer in
- # * the documentation and/or other materials provided with the
- # * distribution.
- # * . Neither the name of the TaBE Project nor the names of its
- # * contributors may be used to endorse or promote products derived
- # * from this software without specific prior written permission.
- # *
- # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- # * OF THE POSSIBILITY OF SUCH DAMAGE.
- # */
- #
- # /*
- # * Copyright (c) 1999 Computer Systems and Communication Lab,
- # * Institute of Information Science, Academia
- # * Sinica. All rights reserved.
- # *
- # * Redistribution and use in source and binary forms, with or without
- # * modification, are permitted provided that the following conditions
- # * are met:
- # *
- # * . Redistributions of source code must retain the above copyright
- # * notice, this list of conditions and the following disclaimer.
- # * . Redistributions in binary form must reproduce the above copyright
- # * notice, this list of conditions and the following disclaimer in
- # * the documentation and/or other materials provided with the
- # * distribution.
- # * . Neither the name of the Computer Systems and Communication Lab
- # * nor the names of its contributors may be used to endorse or
- # * promote products derived from this software without specific
- # * prior written permission.
- # *
- # * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- # * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- # * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- # * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- # * REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
- # * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- # * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- # * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- # * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- # * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- # * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- # * OF THE POSSIBILITY OF SUCH DAMAGE.
- # */
- #
- # Copyright 1996 Chih-Hao Tsai @ Beckman Institute,
- # University of Illinois
- # c-tsai4@uiuc.edu http://casper.beckman.uiuc.edu/~c-tsai4
- #
- # ---------------COPYING.libtabe-----END--------------------------------
- #
- #
- # ---------------COPYING.ipadic-----BEGIN-------------------------------
- #
- # Copyright 2000, 2001, 2002, 2003 Nara Institute of Science
- # and Technology. All Rights Reserved.
- #
- # Use, reproduction, and distribution of this software is permitted.
- # Any copy of this software, whether in its original form or modified,
- # must include both the above copyright notice and the following
- # paragraphs.
- #
- # Nara Institute of Science and Technology (NAIST),
- # the copyright holders, disclaims all warranties with regard to this
- # software, including all implied warranties of merchantability and
- # fitness, in no event shall NAIST be liable for
- # any special, indirect or consequential damages or any damages
- # whatsoever resulting from loss of use, data or profits, whether in an
- # action of contract, negligence or other tortuous action, arising out
- # of or in connection with the use or performance of this software.
- #
- # A large portion of the dictionary entries
- # originate from ICOT Free Software. The following conditions for ICOT
- # Free Software applies to the current dictionary as well.
- #
- # Each User may also freely distribute the Program, whether in its
- # original form or modified, to any third party or parties, PROVIDED
- # that the provisions of Section 3 ("NO WARRANTY") will ALWAYS appear
- # on, or be attached to, the Program, which is distributed substantially
- # in the same form as set out herein and that such intended
- # distribution, if actually made, will neither violate or otherwise
- # contravene any of the laws and regulations of the countries having
- # jurisdiction over the User or the intended distribution itself.
- #
- # NO WARRANTY
- #
- # The program was produced on an experimental basis in the course of the
- # research and development conducted during the project and is provided
- # to users as so produced on an experimental basis. Accordingly, the
- # program is provided without any warranty whatsoever, whether express,
- # implied, statutory or otherwise. The term "warranty" used herein
- # includes, but is not limited to, any warranty of the quality,
- # performance, merchantability and fitness for a particular purpose of
- # the program and the nonexistence of any infringement or violation of
- # any right of any third party.
- #
- # Each user of the program will agree and understand, and be deemed to
- # have agreed and understood, that there is no warranty whatsoever for
- # the program and, accordingly, the entire risk arising from or
- # otherwise connected with the program is assumed by the user.
- #
- # Therefore, neither ICOT, the copyright holder, or any other
- # organization that participated in or was otherwise related to the
- # development of the program and their respective officials, directors,
- # officers and other employees shall be held liable for any and all
- # damages, including, without limitation, general, special, incidental
- # and consequential damages, arising out of or otherwise in connection
- # with the use or inability to use the program or any product, material
- # or result produced or otherwise obtained by using the program,
- # regardless of whether they have been advised of, or otherwise had
- # knowledge of, the possibility of such damages at any time during the
- # project or thereafter. Each user will be deemed to have agreed to the
- # foregoing by his or her commencement of use of the program. The term
- # "use" as used herein includes, but is not limited to, the use,
- # modification, copying and distribution of the program and the
- # production of secondary products from the program.
- #
- # In the case where the program, whether in its original form or
- # modified, was distributed or delivered to or received by a user from
- # any person, organization or entity other than ICOT, unless it makes or
- # grants independently of ICOT any specific warranty to the user in
- # writing, such person, organization or entity, will also be exempted
- # from and not be held liable to the user for any such damages as noted
- # above as far as the program is concerned.
- #
- # ---------------COPYING.ipadic-----END----------------------------------
-
-3. Lao Word Break Dictionary Data (laodict.txt)
-
- # Copyright (c) 2013 International Business Machines Corporation
- # and others. All Rights Reserved.
- #
- # Project: http://code.google.com/p/lao-dictionary/
- # Dictionary: http://lao-dictionary.googlecode.com/git/Lao-Dictionary.txt
- # License: http://lao-dictionary.googlecode.com/git/Lao-Dictionary-LICENSE.txt
- # (copied below)
- #
- # This file is derived from the above dictionary, with slight
- # modifications.
- # ----------------------------------------------------------------------
- # Copyright (C) 2013 Brian Eugene Wilson, Robert Martin Campbell.
- # All rights reserved.
- #
- # Redistribution and use in source and binary forms, with or without
- # modification,
- # are permitted provided that the following conditions are met:
- #
- #
- # Redistributions of source code must retain the above copyright notice, this
- # list of conditions and the following disclaimer. Redistributions in
- # binary form must reproduce the above copyright notice, this list of
- # conditions and the following disclaimer in the documentation and/or
- # other materials provided with the distribution.
- #
- #
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
- # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
- # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
- # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
- # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
- # INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
- # (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
- # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
- # STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
- # OF THE POSSIBILITY OF SUCH DAMAGE.
- # --------------------------------------------------------------------------
-
-4. Burmese Word Break Dictionary Data (burmesedict.txt)
-
- # Copyright (c) 2014 International Business Machines Corporation
- # and others. All Rights Reserved.
- #
- # This list is part of a project hosted at:
- # github.com/kanyawtech/myanmar-karen-word-lists
- #
- # --------------------------------------------------------------------------
- # Copyright (c) 2013, LeRoy Benjamin Sharon
- # All rights reserved.
- #
- # Redistribution and use in source and binary forms, with or without
- # modification, are permitted provided that the following conditions
- # are met: Redistributions of source code must retain the above
- # copyright notice, this list of conditions and the following
- # disclaimer. Redistributions in binary form must reproduce the
- # above copyright notice, this list of conditions and the following
- # disclaimer in the documentation and/or other materials provided
- # with the distribution.
- #
- # Neither the name Myanmar Karen Word Lists, nor the names of its
- # contributors may be used to endorse or promote products derived
- # from this software without specific prior written permission.
- #
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
- # CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
- # INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
- # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS
- # BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
- # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
- # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
- # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
- # ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
- # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
- # THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
- # SUCH DAMAGE.
- # --------------------------------------------------------------------------
-
-5. Time Zone Database
-
- ICU uses the public domain data and code derived from Time Zone
-Database for its time zone support. The ownership of the TZ database
-is explained in BCP 175: Procedure for Maintaining the Time Zone
-Database section 7.
-
- # 7. Database Ownership
- #
- # The TZ database itself is not an IETF Contribution or an IETF
- # document. Rather it is a pre-existing and regularly updated work
- # that is in the public domain, and is intended to remain in the
- # public domain. Therefore, BCPs 78 [RFC5378] and 79 [RFC3979] do
- # not apply to the TZ Database or contributions that individuals make
- # to it. Should any claims be made and substantiated against the TZ
- # Database, the organization that is providing the IANA
- # Considerations defined in this RFC, under the memorandum of
- # understanding with the IETF, currently ICANN, may act in accordance
- # with all competent court orders. No ownership claims will be made
- # by ICANN or the IETF Trust on the database or the code. Any person
- # making a contribution to the database or code waives all rights to
- # future claims in that contribution or in the TZ Database.
-
-6. Google double-conversion
-
-Copyright 2006-2011, the V8 project authors. All rights reserved.
-Redistribution and use in source and binary forms, with or without
-modification, are permitted provided that the following conditions are
-met:
-
- * Redistributions of source code must retain the above copyright
- notice, this list of conditions and the following disclaimer.
- * Redistributions in binary form must reproduce the above
- copyright notice, this list of conditions and the following
- disclaimer in the documentation and/or other materials provided
- with the distribution.
- * Neither the name of Google Inc. nor the names of its
- contributors may be used to endorse or promote products derived
- from this software without specific prior written permission.
-
-THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
-"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
-LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
-A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
-OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
-LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
-DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
-THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
-(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
-OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/src/tree_sitter/unicode/README.md b/src/tree_sitter/unicode/README.md
deleted file mode 100644
index 623b8e3843..0000000000
--- a/src/tree_sitter/unicode/README.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# ICU Parts
-
-This directory contains a small subset of files from the Unicode organization's [ICU repository](https://github.com/unicode-org/icu).
-
-### License
-
-The license for these files is contained in the `LICENSE` file within this directory.
-
-### Contents
-
-* Source files taken from the [`icu4c/source/common/unicode`](https://github.com/unicode-org/icu/tree/552b01f61127d30d6589aa4bf99468224979b661/icu4c/source/common/unicode) directory:
- * `utf8.h`
- * `utf16.h`
- * `umachine.h`
-* Empty source files that are referenced by the above source files, but whose original contents in `libicu` are not needed:
- * `ptypes.h`
- * `urename.h`
- * `utf.h`
-* `ICU_SHA` - File containing the Git SHA of the commit in the `icu` repository from which the files were obtained.
-* `LICENSE` - The license file from the [`icu4c`](https://github.com/unicode-org/icu/tree/552b01f61127d30d6589aa4bf99468224979b661/icu4c) directory of the `icu` repository.
-* `README.md` - This text file.
-
-### Updating ICU
-
-To incorporate changes from the upstream `icu` repository:
-
-* Update `ICU_SHA` with the new Git SHA.
-* Update `LICENSE` with the license text from the directory mentioned above.
-* Update `utf8.h`, `utf16.h`, and `umachine.h` with their new contents in the `icu` repository.
diff --git a/src/tree_sitter/unicode/ptypes.h b/src/tree_sitter/unicode/ptypes.h
deleted file mode 100644
index ac79ad0f98..0000000000
--- a/src/tree_sitter/unicode/ptypes.h
+++ /dev/null
@@ -1 +0,0 @@
-// This file must exist in order for `utf8.h` and `utf16.h` to be used.
diff --git a/src/tree_sitter/unicode/umachine.h b/src/tree_sitter/unicode/umachine.h
deleted file mode 100644
index bbf6ef9c8b..0000000000
--- a/src/tree_sitter/unicode/umachine.h
+++ /dev/null
@@ -1,448 +0,0 @@
-// © 2016 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html
-/*
-******************************************************************************
-*
-* Copyright (C) 1999-2015, International Business Machines
-* Corporation and others. All Rights Reserved.
-*
-******************************************************************************
-* file name: umachine.h
-* encoding: UTF-8
-* tab size: 8 (not used)
-* indentation:4
-*
-* created on: 1999sep13
-* created by: Markus W. Scherer
-*
-* This file defines basic types and constants for ICU to be
-* platform-independent. umachine.h and utf.h are included into
-* utypes.h to provide all the general definitions for ICU.
-* All of these definitions used to be in utypes.h before
-* the UTF-handling macros made this unmaintainable.
-*/
-
-#ifndef __UMACHINE_H__
-#define __UMACHINE_H__
-
-
-/**
- * \file
- * \brief Basic types and constants for UTF
- *
- * <h2> Basic types and constants for UTF </h2>
- * This file defines basic types and constants for utf.h to be
- * platform-independent. umachine.h and utf.h are included into
- * utypes.h to provide all the general definitions for ICU.
- * All of these definitions used to be in utypes.h before
- * the UTF-handling macros made this unmaintainable.
- *
- */
-/*==========================================================================*/
-/* Include platform-dependent definitions */
-/* which are contained in the platform-specific file platform.h */
-/*==========================================================================*/
-
-#include "./ptypes.h" /* platform.h is included in ptypes.h */
-
-/*
- * ANSI C headers:
- * stddef.h defines wchar_t
- */
-#include <stddef.h>
-
-/*==========================================================================*/
-/* For C wrappers, we use the symbol U_STABLE. */
-/* This works properly if the includer is C or C++. */
-/* Functions are declared U_STABLE return-type U_EXPORT2 function-name()... */
-/*==========================================================================*/
-
-/**
- * \def U_CFUNC
- * This is used in a declaration of a library private ICU C function.
- * @stable ICU 2.4
- */
-
-/**
- * \def U_CDECL_BEGIN
- * This is used to begin a declaration of a library private ICU C API.
- * @stable ICU 2.4
- */
-
-/**
- * \def U_CDECL_END
- * This is used to end a declaration of a library private ICU C API
- * @stable ICU 2.4
- */
-
-#ifdef __cplusplus
-# define U_CFUNC extern "C"
-# define U_CDECL_BEGIN extern "C" {
-# define U_CDECL_END }
-#else
-# define U_CFUNC extern
-# define U_CDECL_BEGIN
-# define U_CDECL_END
-#endif
-
-#ifndef U_ATTRIBUTE_DEPRECATED
-/**
- * \def U_ATTRIBUTE_DEPRECATED
- * This is used for GCC specific attributes
- * @internal
- */
-#if U_GCC_MAJOR_MINOR >= 302
-# define U_ATTRIBUTE_DEPRECATED __attribute__ ((deprecated))
-/**
- * \def U_ATTRIBUTE_DEPRECATED
- * This is used for Visual C++ specific attributes
- * @internal
- */
-#elif defined(_MSC_VER) && (_MSC_VER >= 1400)
-# define U_ATTRIBUTE_DEPRECATED __declspec(deprecated)
-#else
-# define U_ATTRIBUTE_DEPRECATED
-#endif
-#endif
-
-/** This is used to declare a function as a public ICU C API @stable ICU 2.0*/
-#define U_CAPI U_CFUNC U_EXPORT
-/** This is used to declare a function as a stable public ICU C API*/
-#define U_STABLE U_CAPI
-/** This is used to declare a function as a draft public ICU C API */
-#define U_DRAFT U_CAPI
-/** This is used to declare a function as a deprecated public ICU C API */
-#define U_DEPRECATED U_CAPI U_ATTRIBUTE_DEPRECATED
-/** This is used to declare a function as an obsolete public ICU C API */
-#define U_OBSOLETE U_CAPI
-/** This is used to declare a function as an internal ICU C API */
-#define U_INTERNAL U_CAPI
-
-/**
- * \def U_OVERRIDE
- * Defined to the C++11 "override" keyword if available.
- * Denotes a class or member which is an override of the base class.
- * May result in an error if it applied to something not an override.
- * @internal
- */
-#ifndef U_OVERRIDE
-#define U_OVERRIDE override
-#endif
-
-/**
- * \def U_FINAL
- * Defined to the C++11 "final" keyword if available.
- * Denotes a class or member which may not be overridden in subclasses.
- * May result in an error if subclasses attempt to override.
- * @internal
- */
-#if !defined(U_FINAL) || defined(U_IN_DOXYGEN)
-#define U_FINAL final
-#endif
-
-// Before ICU 65, function-like, multi-statement ICU macros were just defined as
-// series of statements wrapped in { } blocks and the caller could choose to
-// either treat them as if they were actual functions and end the invocation
-// with a trailing ; creating an empty statement after the block or else omit
-// this trailing ; using the knowledge that the macro would expand to { }.
-//
-// But doing so doesn't work well with macros that look like functions and
-// compiler warnings about empty statements (ICU-20601) and ICU 65 therefore
-// switches to the standard solution of wrapping such macros in do { } while.
-//
-// This will however break existing code that depends on being able to invoke
-// these macros without a trailing ; so to be able to remain compatible with
-// such code the wrapper is itself defined as macros so that it's possible to
-// build ICU 65 and later with the old macro behaviour, like this:
-//
-// CPPFLAGS='-DUPRV_BLOCK_MACRO_BEGIN="" -DUPRV_BLOCK_MACRO_END=""'
-// runConfigureICU ...
-
-/**
- * \def UPRV_BLOCK_MACRO_BEGIN
- * Defined as the "do" keyword by default.
- * @internal
- */
-#ifndef UPRV_BLOCK_MACRO_BEGIN
-#define UPRV_BLOCK_MACRO_BEGIN do
-#endif
-
-/**
- * \def UPRV_BLOCK_MACRO_END
- * Defined as "while (FALSE)" by default.
- * @internal
- */
-#ifndef UPRV_BLOCK_MACRO_END
-#define UPRV_BLOCK_MACRO_END while (FALSE)
-#endif
-
-/*==========================================================================*/
-/* limits for int32_t etc., like in POSIX inttypes.h */
-/*==========================================================================*/
-
-#ifndef INT8_MIN
-/** The smallest value an 8 bit signed integer can hold @stable ICU 2.0 */
-# define INT8_MIN ((int8_t)(-128))
-#endif
-#ifndef INT16_MIN
-/** The smallest value a 16 bit signed integer can hold @stable ICU 2.0 */
-# define INT16_MIN ((int16_t)(-32767-1))
-#endif
-#ifndef INT32_MIN
-/** The smallest value a 32 bit signed integer can hold @stable ICU 2.0 */
-# define INT32_MIN ((int32_t)(-2147483647-1))
-#endif
-
-#ifndef INT8_MAX
-/** The largest value an 8 bit signed integer can hold @stable ICU 2.0 */
-# define INT8_MAX ((int8_t)(127))
-#endif
-#ifndef INT16_MAX
-/** The largest value a 16 bit signed integer can hold @stable ICU 2.0 */
-# define INT16_MAX ((int16_t)(32767))
-#endif
-#ifndef INT32_MAX
-/** The largest value a 32 bit signed integer can hold @stable ICU 2.0 */
-# define INT32_MAX ((int32_t)(2147483647))
-#endif
-
-#ifndef UINT8_MAX
-/** The largest value an 8 bit unsigned integer can hold @stable ICU 2.0 */
-# define UINT8_MAX ((uint8_t)(255U))
-#endif
-#ifndef UINT16_MAX
-/** The largest value a 16 bit unsigned integer can hold @stable ICU 2.0 */
-# define UINT16_MAX ((uint16_t)(65535U))
-#endif
-#ifndef UINT32_MAX
-/** The largest value a 32 bit unsigned integer can hold @stable ICU 2.0 */
-# define UINT32_MAX ((uint32_t)(4294967295U))
-#endif
-
-#if defined(U_INT64_T_UNAVAILABLE)
-# error int64_t is required for decimal format and rule-based number format.
-#else
-# ifndef INT64_C
-/**
- * Provides a platform independent way to specify a signed 64-bit integer constant.
- * note: may be wrong for some 64 bit platforms - ensure your compiler provides INT64_C
- * @stable ICU 2.8
- */
-# define INT64_C(c) c ## LL
-# endif
-# ifndef UINT64_C
-/**
- * Provides a platform independent way to specify an unsigned 64-bit integer constant.
- * note: may be wrong for some 64 bit platforms - ensure your compiler provides UINT64_C
- * @stable ICU 2.8
- */
-# define UINT64_C(c) c ## ULL
-# endif
-# ifndef U_INT64_MIN
-/** The smallest value a 64 bit signed integer can hold @stable ICU 2.8 */
-# define U_INT64_MIN ((int64_t)(INT64_C(-9223372036854775807)-1))
-# endif
-# ifndef U_INT64_MAX
-/** The largest value a 64 bit signed integer can hold @stable ICU 2.8 */
-# define U_INT64_MAX ((int64_t)(INT64_C(9223372036854775807)))
-# endif
-# ifndef U_UINT64_MAX
-/** The largest value a 64 bit unsigned integer can hold @stable ICU 2.8 */
-# define U_UINT64_MAX ((uint64_t)(UINT64_C(18446744073709551615)))
-# endif
-#endif
-
-/*==========================================================================*/
-/* Boolean data type */
-/*==========================================================================*/
-
-/** The ICU boolean type @stable ICU 2.0 */
-typedef int8_t UBool;
-
-#ifndef TRUE
-/** The TRUE value of a UBool @stable ICU 2.0 */
-# define TRUE 1
-#endif
-#ifndef FALSE
-/** The FALSE value of a UBool @stable ICU 2.0 */
-# define FALSE 0
-#endif
-
-
-/*==========================================================================*/
-/* Unicode data types */
-/*==========================================================================*/
-
-/* wchar_t-related definitions -------------------------------------------- */
-
-/*
- * \def U_WCHAR_IS_UTF16
- * Defined if wchar_t uses UTF-16.
- *
- * @stable ICU 2.0
- */
-/*
- * \def U_WCHAR_IS_UTF32
- * Defined if wchar_t uses UTF-32.
- *
- * @stable ICU 2.0
- */
-#if !defined(U_WCHAR_IS_UTF16) && !defined(U_WCHAR_IS_UTF32)
-# ifdef __STDC_ISO_10646__
-# if (U_SIZEOF_WCHAR_T==2)
-# define U_WCHAR_IS_UTF16
-# elif (U_SIZEOF_WCHAR_T==4)
-# define U_WCHAR_IS_UTF32
-# endif
-# elif defined __UCS2__
-# if (U_PF_OS390 <= U_PLATFORM && U_PLATFORM <= U_PF_OS400) && (U_SIZEOF_WCHAR_T==2)
-# define U_WCHAR_IS_UTF16
-# endif
-# elif defined(__UCS4__) || (U_PLATFORM == U_PF_OS400 && defined(__UTF32__))
-# if (U_SIZEOF_WCHAR_T==4)
-# define U_WCHAR_IS_UTF32
-# endif
-# elif U_PLATFORM_IS_DARWIN_BASED || (U_SIZEOF_WCHAR_T==4 && U_PLATFORM_IS_LINUX_BASED)
-# define U_WCHAR_IS_UTF32
-# elif U_PLATFORM_HAS_WIN32_API
-# define U_WCHAR_IS_UTF16
-# endif
-#endif
-
-/* UChar and UChar32 definitions -------------------------------------------- */
-
-/** Number of bytes in a UChar. @stable ICU 2.0 */
-#define U_SIZEOF_UCHAR 2
-
-/**
- * \def U_CHAR16_IS_TYPEDEF
- * If 1, then char16_t is a typedef and not a real type (yet)
- * @internal
- */
-#if (U_PLATFORM == U_PF_AIX) && defined(__cplusplus) &&(U_CPLUSPLUS_VERSION < 11)
-// for AIX, uchar.h needs to be included
-# include <uchar.h>
-# define U_CHAR16_IS_TYPEDEF 1
-#elif defined(_MSC_VER) && (_MSC_VER < 1900)
-// Versions of Visual Studio/MSVC below 2015 do not support char16_t as a real type,
-// and instead use a typedef. https://msdn.microsoft.com/library/bb531344.aspx
-# define U_CHAR16_IS_TYPEDEF 1
-#else
-# define U_CHAR16_IS_TYPEDEF 0
-#endif
-
-
-/**
- * \var UChar
- *
- * The base type for UTF-16 code units and pointers.
- * Unsigned 16-bit integer.
- * Starting with ICU 59, C++ API uses char16_t directly, while C API continues to use UChar.
- *
- * UChar is configurable by defining the macro UCHAR_TYPE
- * on the preprocessor or compiler command line:
- * -DUCHAR_TYPE=uint16_t or -DUCHAR_TYPE=wchar_t (if U_SIZEOF_WCHAR_T==2) etc.
- * (The UCHAR_TYPE can also be \#defined earlier in this file, for outside the ICU library code.)
- * This is for transitional use from application code that uses uint16_t or wchar_t for UTF-16.
- *
- * The default is UChar=char16_t.
- *
- * C++11 defines char16_t as bit-compatible with uint16_t, but as a distinct type.
- *
- * In C, char16_t is a simple typedef of uint_least16_t.
- * ICU requires uint_least16_t=uint16_t for data memory mapping.
- * On macOS, char16_t is not available because the uchar.h standard header is missing.
- *
- * @stable ICU 4.4
- */
-
-#if 1
- // #if 1 is normal. UChar defaults to char16_t in C++.
- // For configuration testing of UChar=uint16_t temporarily change this to #if 0.
- // The intltest Makefile #defines UCHAR_TYPE=char16_t,
- // so we only #define it to uint16_t if it is undefined so far.
-#elif !defined(UCHAR_TYPE)
-# define UCHAR_TYPE uint16_t
-#endif
-
-#if defined(U_COMBINED_IMPLEMENTATION) || defined(U_COMMON_IMPLEMENTATION) || \
- defined(U_I18N_IMPLEMENTATION) || defined(U_IO_IMPLEMENTATION)
- // Inside the ICU library code, never configurable.
- typedef char16_t UChar;
-#elif defined(UCHAR_TYPE)
- typedef UCHAR_TYPE UChar;
-#elif defined(__cplusplus)
- typedef char16_t UChar;
-#else
- typedef uint16_t UChar;
-#endif
-
-/**
- * \var OldUChar
- * Default ICU 58 definition of UChar.
- * A base type for UTF-16 code units and pointers.
- * Unsigned 16-bit integer.
- *
- * Define OldUChar to be wchar_t if that is 16 bits wide.
- * If wchar_t is not 16 bits wide, then define UChar to be uint16_t.
- *
- * This makes the definition of OldUChar platform-dependent
- * but allows direct string type compatibility with platforms with
- * 16-bit wchar_t types.
- *
- * This is how UChar was defined in ICU 58, for transition convenience.
- * Exception: ICU 58 UChar was defined to UCHAR_TYPE if that macro was defined.
- * The current UChar responds to UCHAR_TYPE but OldUChar does not.
- *
- * @stable ICU 59
- */
-#if U_SIZEOF_WCHAR_T==2
- typedef wchar_t OldUChar;
-#elif defined(__CHAR16_TYPE__)
- typedef __CHAR16_TYPE__ OldUChar;
-#else
- typedef uint16_t OldUChar;
-#endif
-
-/**
- * Define UChar32 as a type for single Unicode code points.
- * UChar32 is a signed 32-bit integer (same as int32_t).
- *
- * The Unicode code point range is 0..0x10ffff.
- * All other values (negative or >=0x110000) are illegal as Unicode code points.
- * They may be used as sentinel values to indicate "done", "error"
- * or similar non-code point conditions.
- *
- * Before ICU 2.4 (Jitterbug 2146), UChar32 was defined
- * to be wchar_t if that is 32 bits wide (wchar_t may be signed or unsigned)
- * or else to be uint32_t.
- * That is, the definition of UChar32 was platform-dependent.
- *
- * @see U_SENTINEL
- * @stable ICU 2.4
- */
-typedef int32_t UChar32;
-
-/**
- * This value is intended for sentinel values for APIs that
- * (take or) return single code points (UChar32).
- * It is outside of the Unicode code point range 0..0x10ffff.
- *
- * For example, a "done" or "error" value in a new API
- * could be indicated with U_SENTINEL.
- *
- * ICU APIs designed before ICU 2.4 usually define service-specific "done"
- * values, mostly 0xffff.
- * Those may need to be distinguished from
- * actual U+ffff text contents by calling functions like
- * CharacterIterator::hasNext() or UnicodeString::length().
- *
- * @return -1
- * @see UChar32
- * @stable ICU 2.4
- */
-#define U_SENTINEL (-1)
-
-#include "./urename.h"
-
-#endif
diff --git a/src/tree_sitter/unicode/urename.h b/src/tree_sitter/unicode/urename.h
deleted file mode 100644
index ac79ad0f98..0000000000
--- a/src/tree_sitter/unicode/urename.h
+++ /dev/null
@@ -1 +0,0 @@
-// This file must exist in order for `utf8.h` and `utf16.h` to be used.
diff --git a/src/tree_sitter/unicode/utf.h b/src/tree_sitter/unicode/utf.h
deleted file mode 100644
index ac79ad0f98..0000000000
--- a/src/tree_sitter/unicode/utf.h
+++ /dev/null
@@ -1 +0,0 @@
-// This file must exist in order for `utf8.h` and `utf16.h` to be used.
diff --git a/src/tree_sitter/unicode/utf16.h b/src/tree_sitter/unicode/utf16.h
deleted file mode 100644
index b547922441..0000000000
--- a/src/tree_sitter/unicode/utf16.h
+++ /dev/null
@@ -1,733 +0,0 @@
-// © 2016 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html
-/*
-*******************************************************************************
-*
-* Copyright (C) 1999-2012, International Business Machines
-* Corporation and others. All Rights Reserved.
-*
-*******************************************************************************
-* file name: utf16.h
-* encoding: UTF-8
-* tab size: 8 (not used)
-* indentation:4
-*
-* created on: 1999sep09
-* created by: Markus W. Scherer
-*/
-
-/**
- * \file
- * \brief C API: 16-bit Unicode handling macros
- *
- * This file defines macros to deal with 16-bit Unicode (UTF-16) code units and strings.
- *
- * For more information see utf.h and the ICU User Guide Strings chapter
- * (http://userguide.icu-project.org/strings).
- *
- * <em>Usage:</em>
- * ICU coding guidelines for if() statements should be followed when using these macros.
- * Compound statements (curly braces {}) must be used for if-else-while...
- * bodies and all macro statements should be terminated with semicolon.
- */
-
-#ifndef __UTF16_H__
-#define __UTF16_H__
-
-#include "./umachine.h"
-#ifndef __UTF_H__
-# include "./utf.h"
-#endif
-
-/* single-code point definitions -------------------------------------------- */
-
-/**
- * Does this code unit alone encode a code point (BMP, not a surrogate)?
- * @param c 16-bit code unit
- * @return TRUE or FALSE
- * @stable ICU 2.4
- */
-#define U16_IS_SINGLE(c) !U_IS_SURROGATE(c)
-
-/**
- * Is this code unit a lead surrogate (U+d800..U+dbff)?
- * @param c 16-bit code unit
- * @return TRUE or FALSE
- * @stable ICU 2.4
- */
-#define U16_IS_LEAD(c) (((c)&0xfffffc00)==0xd800)
-
-/**
- * Is this code unit a trail surrogate (U+dc00..U+dfff)?
- * @param c 16-bit code unit
- * @return TRUE or FALSE
- * @stable ICU 2.4
- */
-#define U16_IS_TRAIL(c) (((c)&0xfffffc00)==0xdc00)
-
-/**
- * Is this code unit a surrogate (U+d800..U+dfff)?
- * @param c 16-bit code unit
- * @return TRUE or FALSE
- * @stable ICU 2.4
- */
-#define U16_IS_SURROGATE(c) U_IS_SURROGATE(c)
-
-/**
- * Assuming c is a surrogate code point (U16_IS_SURROGATE(c)),
- * is it a lead surrogate?
- * @param c 16-bit code unit
- * @return TRUE or FALSE
- * @stable ICU 2.4
- */
-#define U16_IS_SURROGATE_LEAD(c) (((c)&0x400)==0)
-
-/**
- * Assuming c is a surrogate code point (U16_IS_SURROGATE(c)),
- * is it a trail surrogate?
- * @param c 16-bit code unit
- * @return TRUE or FALSE
- * @stable ICU 4.2
- */
-#define U16_IS_SURROGATE_TRAIL(c) (((c)&0x400)!=0)
-
-/**
- * Helper constant for U16_GET_SUPPLEMENTARY.
- * @internal
- */
-#define U16_SURROGATE_OFFSET ((0xd800<<10UL)+0xdc00-0x10000)
-
-/**
- * Get a supplementary code point value (U+10000..U+10ffff)
- * from its lead and trail surrogates.
- * The result is undefined if the input values are not
- * lead and trail surrogates.
- *
- * @param lead lead surrogate (U+d800..U+dbff)
- * @param trail trail surrogate (U+dc00..U+dfff)
- * @return supplementary code point (U+10000..U+10ffff)
- * @stable ICU 2.4
- */
-#define U16_GET_SUPPLEMENTARY(lead, trail) \
- (((UChar32)(lead)<<10UL)+(UChar32)(trail)-U16_SURROGATE_OFFSET)
-
-
-/**
- * Get the lead surrogate (0xd800..0xdbff) for a
- * supplementary code point (0x10000..0x10ffff).
- * @param supplementary 32-bit code point (U+10000..U+10ffff)
- * @return lead surrogate (U+d800..U+dbff) for supplementary
- * @stable ICU 2.4
- */
-#define U16_LEAD(supplementary) (UChar)(((supplementary)>>10)+0xd7c0)
-
-/**
- * Get the trail surrogate (0xdc00..0xdfff) for a
- * supplementary code point (0x10000..0x10ffff).
- * @param supplementary 32-bit code point (U+10000..U+10ffff)
- * @return trail surrogate (U+dc00..U+dfff) for supplementary
- * @stable ICU 2.4
- */
-#define U16_TRAIL(supplementary) (UChar)(((supplementary)&0x3ff)|0xdc00)
-
-/**
- * How many 16-bit code units are used to encode this Unicode code point? (1 or 2)
- * The result is not defined if c is not a Unicode code point (U+0000..U+10ffff).
- * @param c 32-bit code point
- * @return 1 or 2
- * @stable ICU 2.4
- */
-#define U16_LENGTH(c) ((uint32_t)(c)<=0xffff ? 1 : 2)
-
-/**
- * The maximum number of 16-bit code units per Unicode code point (U+0000..U+10ffff).
- * @return 2
- * @stable ICU 2.4
- */
-#define U16_MAX_LENGTH 2
-
-/**
- * Get a code point from a string at a random-access offset,
- * without changing the offset.
- * "Unsafe" macro, assumes well-formed UTF-16.
- *
- * The offset may point to either the lead or trail surrogate unit
- * for a supplementary code point, in which case the macro will read
- * the adjacent matching surrogate as well.
- * The result is undefined if the offset points to a single, unpaired surrogate.
- * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT.
- *
- * @param s const UChar * string
- * @param i string offset
- * @param c output UChar32 variable
- * @see U16_GET
- * @stable ICU 2.4
- */
-#define U16_GET_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
- (c)=(s)[i]; \
- if(U16_IS_SURROGATE(c)) { \
- if(U16_IS_SURROGATE_LEAD(c)) { \
- (c)=U16_GET_SUPPLEMENTARY((c), (s)[(i)+1]); \
- } else { \
- (c)=U16_GET_SUPPLEMENTARY((s)[(i)-1], (c)); \
- } \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Get a code point from a string at a random-access offset,
- * without changing the offset.
- * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
- *
- * The offset may point to either the lead or trail surrogate unit
- * for a supplementary code point, in which case the macro will read
- * the adjacent matching surrogate as well.
- *
- * The length can be negative for a NUL-terminated string.
- *
- * If the offset points to a single, unpaired surrogate, then
- * c is set to that unpaired surrogate.
- * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT.
- *
- * @param s const UChar * string
- * @param start starting string offset (usually 0)
- * @param i string offset, must be start<=i<length
- * @param length string length
- * @param c output UChar32 variable
- * @see U16_GET_UNSAFE
- * @stable ICU 2.4
- */
-#define U16_GET(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \
- (c)=(s)[i]; \
- if(U16_IS_SURROGATE(c)) { \
- uint16_t __c2; \
- if(U16_IS_SURROGATE_LEAD(c)) { \
- if((i)+1!=(length) && U16_IS_TRAIL(__c2=(s)[(i)+1])) { \
- (c)=U16_GET_SUPPLEMENTARY((c), __c2); \
- } \
- } else { \
- if((i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \
- (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \
- } \
- } \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Get a code point from a string at a random-access offset,
- * without changing the offset.
- * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
- *
- * The offset may point to either the lead or trail surrogate unit
- * for a supplementary code point, in which case the macro will read
- * the adjacent matching surrogate as well.
- *
- * The length can be negative for a NUL-terminated string.
- *
- * If the offset points to a single, unpaired surrogate, then
- * c is set to U+FFFD.
- * Iteration through a string is more efficient with U16_NEXT_UNSAFE or U16_NEXT_OR_FFFD.
- *
- * @param s const UChar * string
- * @param start starting string offset (usually 0)
- * @param i string offset, must be start<=i<length
- * @param length string length
- * @param c output UChar32 variable
- * @see U16_GET_UNSAFE
- * @stable ICU 60
- */
-#define U16_GET_OR_FFFD(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \
- (c)=(s)[i]; \
- if(U16_IS_SURROGATE(c)) { \
- uint16_t __c2; \
- if(U16_IS_SURROGATE_LEAD(c)) { \
- if((i)+1!=(length) && U16_IS_TRAIL(__c2=(s)[(i)+1])) { \
- (c)=U16_GET_SUPPLEMENTARY((c), __c2); \
- } else { \
- (c)=0xfffd; \
- } \
- } else { \
- if((i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \
- (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \
- } else { \
- (c)=0xfffd; \
- } \
- } \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/* definitions with forward iteration --------------------------------------- */
-
-/**
- * Get a code point from a string at a code point boundary offset,
- * and advance the offset to the next code point boundary.
- * (Post-incrementing forward iteration.)
- * "Unsafe" macro, assumes well-formed UTF-16.
- *
- * The offset may point to the lead surrogate unit
- * for a supplementary code point, in which case the macro will read
- * the following trail surrogate as well.
- * If the offset points to a trail surrogate, then that itself
- * will be returned as the code point.
- * The result is undefined if the offset points to a single, unpaired lead surrogate.
- *
- * @param s const UChar * string
- * @param i string offset
- * @param c output UChar32 variable
- * @see U16_NEXT
- * @stable ICU 2.4
- */
-#define U16_NEXT_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
- (c)=(s)[(i)++]; \
- if(U16_IS_LEAD(c)) { \
- (c)=U16_GET_SUPPLEMENTARY((c), (s)[(i)++]); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Get a code point from a string at a code point boundary offset,
- * and advance the offset to the next code point boundary.
- * (Post-incrementing forward iteration.)
- * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
- *
- * The length can be negative for a NUL-terminated string.
- *
- * The offset may point to the lead surrogate unit
- * for a supplementary code point, in which case the macro will read
- * the following trail surrogate as well.
- * If the offset points to a trail surrogate or
- * to a single, unpaired lead surrogate, then c is set to that unpaired surrogate.
- *
- * @param s const UChar * string
- * @param i string offset, must be i<length
- * @param length string length
- * @param c output UChar32 variable
- * @see U16_NEXT_UNSAFE
- * @stable ICU 2.4
- */
-#define U16_NEXT(s, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \
- (c)=(s)[(i)++]; \
- if(U16_IS_LEAD(c)) { \
- uint16_t __c2; \
- if((i)!=(length) && U16_IS_TRAIL(__c2=(s)[(i)])) { \
- ++(i); \
- (c)=U16_GET_SUPPLEMENTARY((c), __c2); \
- } \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Get a code point from a string at a code point boundary offset,
- * and advance the offset to the next code point boundary.
- * (Post-incrementing forward iteration.)
- * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
- *
- * The length can be negative for a NUL-terminated string.
- *
- * The offset may point to the lead surrogate unit
- * for a supplementary code point, in which case the macro will read
- * the following trail surrogate as well.
- * If the offset points to a trail surrogate or
- * to a single, unpaired lead surrogate, then c is set to U+FFFD.
- *
- * @param s const UChar * string
- * @param i string offset, must be i<length
- * @param length string length
- * @param c output UChar32 variable
- * @see U16_NEXT_UNSAFE
- * @stable ICU 60
- */
-#define U16_NEXT_OR_FFFD(s, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \
- (c)=(s)[(i)++]; \
- if(U16_IS_SURROGATE(c)) { \
- uint16_t __c2; \
- if(U16_IS_SURROGATE_LEAD(c) && (i)!=(length) && U16_IS_TRAIL(__c2=(s)[(i)])) { \
- ++(i); \
- (c)=U16_GET_SUPPLEMENTARY((c), __c2); \
- } else { \
- (c)=0xfffd; \
- } \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Append a code point to a string, overwriting 1 or 2 code units.
- * The offset points to the current end of the string contents
- * and is advanced (post-increment).
- * "Unsafe" macro, assumes a valid code point and sufficient space in the string.
- * Otherwise, the result is undefined.
- *
- * @param s const UChar * string buffer
- * @param i string offset
- * @param c code point to append
- * @see U16_APPEND
- * @stable ICU 2.4
- */
-#define U16_APPEND_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
- if((uint32_t)(c)<=0xffff) { \
- (s)[(i)++]=(uint16_t)(c); \
- } else { \
- (s)[(i)++]=(uint16_t)(((c)>>10)+0xd7c0); \
- (s)[(i)++]=(uint16_t)(((c)&0x3ff)|0xdc00); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Append a code point to a string, overwriting 1 or 2 code units.
- * The offset points to the current end of the string contents
- * and is advanced (post-increment).
- * "Safe" macro, checks for a valid code point.
- * If a surrogate pair is written, checks for sufficient space in the string.
- * If the code point is not valid or a trail surrogate does not fit,
- * then isError is set to TRUE.
- *
- * @param s const UChar * string buffer
- * @param i string offset, must be i<capacity
- * @param capacity size of the string buffer
- * @param c code point to append
- * @param isError output UBool set to TRUE if an error occurs, otherwise not modified
- * @see U16_APPEND_UNSAFE
- * @stable ICU 2.4
- */
-#define U16_APPEND(s, i, capacity, c, isError) UPRV_BLOCK_MACRO_BEGIN { \
- if((uint32_t)(c)<=0xffff) { \
- (s)[(i)++]=(uint16_t)(c); \
- } else if((uint32_t)(c)<=0x10ffff && (i)+1<(capacity)) { \
- (s)[(i)++]=(uint16_t)(((c)>>10)+0xd7c0); \
- (s)[(i)++]=(uint16_t)(((c)&0x3ff)|0xdc00); \
- } else /* c>0x10ffff or not enough space */ { \
- (isError)=TRUE; \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Advance the string offset from one code point boundary to the next.
- * (Post-incrementing iteration.)
- * "Unsafe" macro, assumes well-formed UTF-16.
- *
- * @param s const UChar * string
- * @param i string offset
- * @see U16_FWD_1
- * @stable ICU 2.4
- */
-#define U16_FWD_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
- if(U16_IS_LEAD((s)[(i)++])) { \
- ++(i); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Advance the string offset from one code point boundary to the next.
- * (Post-incrementing iteration.)
- * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
- *
- * The length can be negative for a NUL-terminated string.
- *
- * @param s const UChar * string
- * @param i string offset, must be i<length
- * @param length string length
- * @see U16_FWD_1_UNSAFE
- * @stable ICU 2.4
- */
-#define U16_FWD_1(s, i, length) UPRV_BLOCK_MACRO_BEGIN { \
- if(U16_IS_LEAD((s)[(i)++]) && (i)!=(length) && U16_IS_TRAIL((s)[i])) { \
- ++(i); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Advance the string offset from one code point boundary to the n-th next one,
- * i.e., move forward by n code points.
- * (Post-incrementing iteration.)
- * "Unsafe" macro, assumes well-formed UTF-16.
- *
- * @param s const UChar * string
- * @param i string offset
- * @param n number of code points to skip
- * @see U16_FWD_N
- * @stable ICU 2.4
- */
-#define U16_FWD_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \
- int32_t __N=(n); \
- while(__N>0) { \
- U16_FWD_1_UNSAFE(s, i); \
- --__N; \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Advance the string offset from one code point boundary to the n-th next one,
- * i.e., move forward by n code points.
- * (Post-incrementing iteration.)
- * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
- *
- * The length can be negative for a NUL-terminated string.
- *
- * @param s const UChar * string
- * @param i int32_t string offset, must be i<length
- * @param length int32_t string length
- * @param n number of code points to skip
- * @see U16_FWD_N_UNSAFE
- * @stable ICU 2.4
- */
-#define U16_FWD_N(s, i, length, n) UPRV_BLOCK_MACRO_BEGIN { \
- int32_t __N=(n); \
- while(__N>0 && ((i)<(length) || ((length)<0 && (s)[i]!=0))) { \
- U16_FWD_1(s, i, length); \
- --__N; \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Adjust a random-access offset to a code point boundary
- * at the start of a code point.
- * If the offset points to the trail surrogate of a surrogate pair,
- * then the offset is decremented.
- * Otherwise, it is not modified.
- * "Unsafe" macro, assumes well-formed UTF-16.
- *
- * @param s const UChar * string
- * @param i string offset
- * @see U16_SET_CP_START
- * @stable ICU 2.4
- */
-#define U16_SET_CP_START_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
- if(U16_IS_TRAIL((s)[i])) { \
- --(i); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Adjust a random-access offset to a code point boundary
- * at the start of a code point.
- * If the offset points to the trail surrogate of a surrogate pair,
- * then the offset is decremented.
- * Otherwise, it is not modified.
- * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
- *
- * @param s const UChar * string
- * @param start starting string offset (usually 0)
- * @param i string offset, must be start<=i
- * @see U16_SET_CP_START_UNSAFE
- * @stable ICU 2.4
- */
-#define U16_SET_CP_START(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \
- if(U16_IS_TRAIL((s)[i]) && (i)>(start) && U16_IS_LEAD((s)[(i)-1])) { \
- --(i); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/* definitions with backward iteration -------------------------------------- */
-
-/**
- * Move the string offset from one code point boundary to the previous one
- * and get the code point between them.
- * (Pre-decrementing backward iteration.)
- * "Unsafe" macro, assumes well-formed UTF-16.
- *
- * The input offset may be the same as the string length.
- * If the offset is behind a trail surrogate unit
- * for a supplementary code point, then the macro will read
- * the preceding lead surrogate as well.
- * If the offset is behind a lead surrogate, then that itself
- * will be returned as the code point.
- * The result is undefined if the offset is behind a single, unpaired trail surrogate.
- *
- * @param s const UChar * string
- * @param i string offset
- * @param c output UChar32 variable
- * @see U16_PREV
- * @stable ICU 2.4
- */
-#define U16_PREV_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
- (c)=(s)[--(i)]; \
- if(U16_IS_TRAIL(c)) { \
- (c)=U16_GET_SUPPLEMENTARY((s)[--(i)], (c)); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Move the string offset from one code point boundary to the previous one
- * and get the code point between them.
- * (Pre-decrementing backward iteration.)
- * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
- *
- * The input offset may be the same as the string length.
- * If the offset is behind a trail surrogate unit
- * for a supplementary code point, then the macro will read
- * the preceding lead surrogate as well.
- * If the offset is behind a lead surrogate or behind a single, unpaired
- * trail surrogate, then c is set to that unpaired surrogate.
- *
- * @param s const UChar * string
- * @param start starting string offset (usually 0)
- * @param i string offset, must be start<i
- * @param c output UChar32 variable
- * @see U16_PREV_UNSAFE
- * @stable ICU 2.4
- */
-#define U16_PREV(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \
- (c)=(s)[--(i)]; \
- if(U16_IS_TRAIL(c)) { \
- uint16_t __c2; \
- if((i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \
- --(i); \
- (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \
- } \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Move the string offset from one code point boundary to the previous one
- * and get the code point between them.
- * (Pre-decrementing backward iteration.)
- * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
- *
- * The input offset may be the same as the string length.
- * If the offset is behind a trail surrogate unit
- * for a supplementary code point, then the macro will read
- * the preceding lead surrogate as well.
- * If the offset is behind a lead surrogate or behind a single, unpaired
- * trail surrogate, then c is set to U+FFFD.
- *
- * @param s const UChar * string
- * @param start starting string offset (usually 0)
- * @param i string offset, must be start<i
- * @param c output UChar32 variable
- * @see U16_PREV_UNSAFE
- * @stable ICU 60
- */
-#define U16_PREV_OR_FFFD(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \
- (c)=(s)[--(i)]; \
- if(U16_IS_SURROGATE(c)) { \
- uint16_t __c2; \
- if(U16_IS_SURROGATE_TRAIL(c) && (i)>(start) && U16_IS_LEAD(__c2=(s)[(i)-1])) { \
- --(i); \
- (c)=U16_GET_SUPPLEMENTARY(__c2, (c)); \
- } else { \
- (c)=0xfffd; \
- } \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Move the string offset from one code point boundary to the previous one.
- * (Pre-decrementing backward iteration.)
- * The input offset may be the same as the string length.
- * "Unsafe" macro, assumes well-formed UTF-16.
- *
- * @param s const UChar * string
- * @param i string offset
- * @see U16_BACK_1
- * @stable ICU 2.4
- */
-#define U16_BACK_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
- if(U16_IS_TRAIL((s)[--(i)])) { \
- --(i); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Move the string offset from one code point boundary to the previous one.
- * (Pre-decrementing backward iteration.)
- * The input offset may be the same as the string length.
- * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
- *
- * @param s const UChar * string
- * @param start starting string offset (usually 0)
- * @param i string offset, must be start<i
- * @see U16_BACK_1_UNSAFE
- * @stable ICU 2.4
- */
-#define U16_BACK_1(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \
- if(U16_IS_TRAIL((s)[--(i)]) && (i)>(start) && U16_IS_LEAD((s)[(i)-1])) { \
- --(i); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Move the string offset from one code point boundary to the n-th one before it,
- * i.e., move backward by n code points.
- * (Pre-decrementing backward iteration.)
- * The input offset may be the same as the string length.
- * "Unsafe" macro, assumes well-formed UTF-16.
- *
- * @param s const UChar * string
- * @param i string offset
- * @param n number of code points to skip
- * @see U16_BACK_N
- * @stable ICU 2.4
- */
-#define U16_BACK_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \
- int32_t __N=(n); \
- while(__N>0) { \
- U16_BACK_1_UNSAFE(s, i); \
- --__N; \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Move the string offset from one code point boundary to the n-th one before it,
- * i.e., move backward by n code points.
- * (Pre-decrementing backward iteration.)
- * The input offset may be the same as the string length.
- * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
- *
- * @param s const UChar * string
- * @param start start of string
- * @param i string offset, must be start<i
- * @param n number of code points to skip
- * @see U16_BACK_N_UNSAFE
- * @stable ICU 2.4
- */
-#define U16_BACK_N(s, start, i, n) UPRV_BLOCK_MACRO_BEGIN { \
- int32_t __N=(n); \
- while(__N>0 && (i)>(start)) { \
- U16_BACK_1(s, start, i); \
- --__N; \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Adjust a random-access offset to a code point boundary after a code point.
- * If the offset is behind the lead surrogate of a surrogate pair,
- * then the offset is incremented.
- * Otherwise, it is not modified.
- * The input offset may be the same as the string length.
- * "Unsafe" macro, assumes well-formed UTF-16.
- *
- * @param s const UChar * string
- * @param i string offset
- * @see U16_SET_CP_LIMIT
- * @stable ICU 2.4
- */
-#define U16_SET_CP_LIMIT_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
- if(U16_IS_LEAD((s)[(i)-1])) { \
- ++(i); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Adjust a random-access offset to a code point boundary after a code point.
- * If the offset is behind the lead surrogate of a surrogate pair,
- * then the offset is incremented.
- * Otherwise, it is not modified.
- * The input offset may be the same as the string length.
- * "Safe" macro, handles unpaired surrogates and checks for string boundaries.
- *
- * The length can be negative for a NUL-terminated string.
- *
- * @param s const UChar * string
- * @param start int32_t starting string offset (usually 0)
- * @param i int32_t string offset, start<=i<=length
- * @param length int32_t string length
- * @see U16_SET_CP_LIMIT_UNSAFE
- * @stable ICU 2.4
- */
-#define U16_SET_CP_LIMIT(s, start, i, length) UPRV_BLOCK_MACRO_BEGIN { \
- if((start)<(i) && ((i)<(length) || (length)<0) && U16_IS_LEAD((s)[(i)-1]) && U16_IS_TRAIL((s)[i])) { \
- ++(i); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-#endif
diff --git a/src/tree_sitter/unicode/utf8.h b/src/tree_sitter/unicode/utf8.h
deleted file mode 100644
index 3b37873e37..0000000000
--- a/src/tree_sitter/unicode/utf8.h
+++ /dev/null
@@ -1,881 +0,0 @@
-// © 2016 and later: Unicode, Inc. and others.
-// License & terms of use: http://www.unicode.org/copyright.html
-/*
-*******************************************************************************
-*
-* Copyright (C) 1999-2015, International Business Machines
-* Corporation and others. All Rights Reserved.
-*
-*******************************************************************************
-* file name: utf8.h
-* encoding: UTF-8
-* tab size: 8 (not used)
-* indentation:4
-*
-* created on: 1999sep13
-* created by: Markus W. Scherer
-*/
-
-/**
- * \file
- * \brief C API: 8-bit Unicode handling macros
- *
- * This file defines macros to deal with 8-bit Unicode (UTF-8) code units (bytes) and strings.
- *
- * For more information see utf.h and the ICU User Guide Strings chapter
- * (http://userguide.icu-project.org/strings).
- *
- * <em>Usage:</em>
- * ICU coding guidelines for if() statements should be followed when using these macros.
- * Compound statements (curly braces {}) must be used for if-else-while...
- * bodies and all macro statements should be terminated with semicolon.
- */
-
-#ifndef __UTF8_H__
-#define __UTF8_H__
-
-#include "./umachine.h"
-#ifndef __UTF_H__
-# include "./utf.h"
-#endif
-
-/* internal definitions ----------------------------------------------------- */
-
-/**
- * Counts the trail bytes for a UTF-8 lead byte.
- * Returns 0 for 0..0xc1 as well as for 0xf5..0xff.
- * leadByte might be evaluated multiple times.
- *
- * This is internal since it is not meant to be called directly by external clients;
- * however it is called by public macros in this file and thus must remain stable.
- *
- * @param leadByte The first byte of a UTF-8 sequence. Must be 0..0xff.
- * @internal
- */
-#define U8_COUNT_TRAIL_BYTES(leadByte) \
- (U8_IS_LEAD(leadByte) ? \
- ((uint8_t)(leadByte)>=0xe0)+((uint8_t)(leadByte)>=0xf0)+1 : 0)
-
-/**
- * Counts the trail bytes for a UTF-8 lead byte of a valid UTF-8 sequence.
- * Returns 0 for 0..0xc1. Undefined for 0xf5..0xff.
- * leadByte might be evaluated multiple times.
- *
- * This is internal since it is not meant to be called directly by external clients;
- * however it is called by public macros in this file and thus must remain stable.
- *
- * @param leadByte The first byte of a UTF-8 sequence. Must be 0..0xff.
- * @internal
- */
-#define U8_COUNT_TRAIL_BYTES_UNSAFE(leadByte) \
- (((uint8_t)(leadByte)>=0xc2)+((uint8_t)(leadByte)>=0xe0)+((uint8_t)(leadByte)>=0xf0))
-
-/**
- * Mask a UTF-8 lead byte, leave only the lower bits that form part of the code point value.
- *
- * This is internal since it is not meant to be called directly by external clients;
- * however it is called by public macros in this file and thus must remain stable.
- * @internal
- */
-#define U8_MASK_LEAD_BYTE(leadByte, countTrailBytes) ((leadByte)&=(1<<(6-(countTrailBytes)))-1)
-
-/**
- * Internal bit vector for 3-byte UTF-8 validity check, for use in U8_IS_VALID_LEAD3_AND_T1.
- * Each bit indicates whether one lead byte + first trail byte pair starts a valid sequence.
- * Lead byte E0..EF bits 3..0 are used as byte index,
- * first trail byte bits 7..5 are used as bit index into that byte.
- * @see U8_IS_VALID_LEAD3_AND_T1
- * @internal
- */
-#define U8_LEAD3_T1_BITS "\x20\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x30\x10\x30\x30"
-
-/**
- * Internal 3-byte UTF-8 validity check.
- * Non-zero if lead byte E0..EF and first trail byte 00..FF start a valid sequence.
- * @internal
- */
-#define U8_IS_VALID_LEAD3_AND_T1(lead, t1) (U8_LEAD3_T1_BITS[(lead)&0xf]&(1<<((uint8_t)(t1)>>5)))
-
-/**
- * Internal bit vector for 4-byte UTF-8 validity check, for use in U8_IS_VALID_LEAD4_AND_T1.
- * Each bit indicates whether one lead byte + first trail byte pair starts a valid sequence.
- * First trail byte bits 7..4 are used as byte index,
- * lead byte F0..F4 bits 2..0 are used as bit index into that byte.
- * @see U8_IS_VALID_LEAD4_AND_T1
- * @internal
- */
-#define U8_LEAD4_T1_BITS "\x00\x00\x00\x00\x00\x00\x00\x00\x1E\x0F\x0F\x0F\x00\x00\x00\x00"
-
-/**
- * Internal 4-byte UTF-8 validity check.
- * Non-zero if lead byte F0..F4 and first trail byte 00..FF start a valid sequence.
- * @internal
- */
-#define U8_IS_VALID_LEAD4_AND_T1(lead, t1) (U8_LEAD4_T1_BITS[(uint8_t)(t1)>>4]&(1<<((lead)&7)))
-
-/**
- * Function for handling "next code point" with error-checking.
- *
- * This is internal since it is not meant to be called directly by external clients;
- * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this
- * file and thus must remain stable, and should not be hidden when other internal
- * functions are hidden (otherwise public macros would fail to compile).
- * @internal
- */
-U_STABLE UChar32 U_EXPORT2
-utf8_nextCharSafeBody(const uint8_t *s, int32_t *pi, int32_t length, UChar32 c, UBool strict);
-
-/**
- * Function for handling "append code point" with error-checking.
- *
- * This is internal since it is not meant to be called directly by external clients;
- * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this
- * file and thus must remain stable, and should not be hidden when other internal
- * functions are hidden (otherwise public macros would fail to compile).
- * @internal
- */
-U_STABLE int32_t U_EXPORT2
-utf8_appendCharSafeBody(uint8_t *s, int32_t i, int32_t length, UChar32 c, UBool *pIsError);
-
-/**
- * Function for handling "previous code point" with error-checking.
- *
- * This is internal since it is not meant to be called directly by external clients;
- * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this
- * file and thus must remain stable, and should not be hidden when other internal
- * functions are hidden (otherwise public macros would fail to compile).
- * @internal
- */
-U_STABLE UChar32 U_EXPORT2
-utf8_prevCharSafeBody(const uint8_t *s, int32_t start, int32_t *pi, UChar32 c, UBool strict);
-
-/**
- * Function for handling "skip backward one code point" with error-checking.
- *
- * This is internal since it is not meant to be called directly by external clients;
- * however it is U_STABLE (not U_INTERNAL) since it is called by public macros in this
- * file and thus must remain stable, and should not be hidden when other internal
- * functions are hidden (otherwise public macros would fail to compile).
- * @internal
- */
-U_STABLE int32_t U_EXPORT2
-utf8_back1SafeBody(const uint8_t *s, int32_t start, int32_t i);
-
-/* single-code point definitions -------------------------------------------- */
-
-/**
- * Does this code unit (byte) encode a code point by itself (US-ASCII 0..0x7f)?
- * @param c 8-bit code unit (byte)
- * @return TRUE or FALSE
- * @stable ICU 2.4
- */
-#define U8_IS_SINGLE(c) (((c)&0x80)==0)
-
-/**
- * Is this code unit (byte) a UTF-8 lead byte? (0xC2..0xF4)
- * @param c 8-bit code unit (byte)
- * @return TRUE or FALSE
- * @stable ICU 2.4
- */
-#define U8_IS_LEAD(c) ((uint8_t)((c)-0xc2)<=0x32)
-// 0x32=0xf4-0xc2
-
-/**
- * Is this code unit (byte) a UTF-8 trail byte? (0x80..0xBF)
- * @param c 8-bit code unit (byte)
- * @return TRUE or FALSE
- * @stable ICU 2.4
- */
-#define U8_IS_TRAIL(c) ((int8_t)(c)<-0x40)
-
-/**
- * How many code units (bytes) are used for the UTF-8 encoding
- * of this Unicode code point?
- * @param c 32-bit code point
- * @return 1..4, or 0 if c is a surrogate or not a Unicode code point
- * @stable ICU 2.4
- */
-#define U8_LENGTH(c) \
- ((uint32_t)(c)<=0x7f ? 1 : \
- ((uint32_t)(c)<=0x7ff ? 2 : \
- ((uint32_t)(c)<=0xd7ff ? 3 : \
- ((uint32_t)(c)<=0xdfff || (uint32_t)(c)>0x10ffff ? 0 : \
- ((uint32_t)(c)<=0xffff ? 3 : 4)\
- ) \
- ) \
- ) \
- )
-
-/**
- * The maximum number of UTF-8 code units (bytes) per Unicode code point (U+0000..U+10ffff).
- * @return 4
- * @stable ICU 2.4
- */
-#define U8_MAX_LENGTH 4
-
-/**
- * Get a code point from a string at a random-access offset,
- * without changing the offset.
- * The offset may point to either the lead byte or one of the trail bytes
- * for a code point, in which case the macro will read all of the bytes
- * for the code point.
- * The result is undefined if the offset points to an illegal UTF-8
- * byte sequence.
- * Iteration through a string is more efficient with U8_NEXT_UNSAFE or U8_NEXT.
- *
- * @param s const uint8_t * string
- * @param i string offset
- * @param c output UChar32 variable
- * @see U8_GET
- * @stable ICU 2.4
- */
-#define U8_GET_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
- int32_t _u8_get_unsafe_index=(int32_t)(i); \
- U8_SET_CP_START_UNSAFE(s, _u8_get_unsafe_index); \
- U8_NEXT_UNSAFE(s, _u8_get_unsafe_index, c); \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Get a code point from a string at a random-access offset,
- * without changing the offset.
- * The offset may point to either the lead byte or one of the trail bytes
- * for a code point, in which case the macro will read all of the bytes
- * for the code point.
- *
- * The length can be negative for a NUL-terminated string.
- *
- * If the offset points to an illegal UTF-8 byte sequence, then
- * c is set to a negative value.
- * Iteration through a string is more efficient with U8_NEXT_UNSAFE or U8_NEXT.
- *
- * @param s const uint8_t * string
- * @param start int32_t starting string offset
- * @param i int32_t string offset, must be start<=i<length
- * @param length int32_t string length
- * @param c output UChar32 variable, set to <0 in case of an error
- * @see U8_GET_UNSAFE
- * @stable ICU 2.4
- */
-#define U8_GET(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \
- int32_t _u8_get_index=(i); \
- U8_SET_CP_START(s, start, _u8_get_index); \
- U8_NEXT(s, _u8_get_index, length, c); \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Get a code point from a string at a random-access offset,
- * without changing the offset.
- * The offset may point to either the lead byte or one of the trail bytes
- * for a code point, in which case the macro will read all of the bytes
- * for the code point.
- *
- * The length can be negative for a NUL-terminated string.
- *
- * If the offset points to an illegal UTF-8 byte sequence, then
- * c is set to U+FFFD.
- * Iteration through a string is more efficient with U8_NEXT_UNSAFE or U8_NEXT_OR_FFFD.
- *
- * This macro does not distinguish between a real U+FFFD in the text
- * and U+FFFD returned for an ill-formed sequence.
- * Use U8_GET() if that distinction is important.
- *
- * @param s const uint8_t * string
- * @param start int32_t starting string offset
- * @param i int32_t string offset, must be start<=i<length
- * @param length int32_t string length
- * @param c output UChar32 variable, set to U+FFFD in case of an error
- * @see U8_GET
- * @stable ICU 51
- */
-#define U8_GET_OR_FFFD(s, start, i, length, c) UPRV_BLOCK_MACRO_BEGIN { \
- int32_t _u8_get_index=(i); \
- U8_SET_CP_START(s, start, _u8_get_index); \
- U8_NEXT_OR_FFFD(s, _u8_get_index, length, c); \
-} UPRV_BLOCK_MACRO_END
-
-/* definitions with forward iteration --------------------------------------- */
-
-/**
- * Get a code point from a string at a code point boundary offset,
- * and advance the offset to the next code point boundary.
- * (Post-incrementing forward iteration.)
- * "Unsafe" macro, assumes well-formed UTF-8.
- *
- * The offset may point to the lead byte of a multi-byte sequence,
- * in which case the macro will read the whole sequence.
- * The result is undefined if the offset points to a trail byte
- * or an illegal UTF-8 sequence.
- *
- * @param s const uint8_t * string
- * @param i string offset
- * @param c output UChar32 variable
- * @see U8_NEXT
- * @stable ICU 2.4
- */
-#define U8_NEXT_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
- (c)=(uint8_t)(s)[(i)++]; \
- if(!U8_IS_SINGLE(c)) { \
- if((c)<0xe0) { \
- (c)=(((c)&0x1f)<<6)|((s)[(i)++]&0x3f); \
- } else if((c)<0xf0) { \
- /* no need for (c&0xf) because the upper bits are truncated after <<12 in the cast to (UChar) */ \
- (c)=(UChar)(((c)<<12)|(((s)[i]&0x3f)<<6)|((s)[(i)+1]&0x3f)); \
- (i)+=2; \
- } else { \
- (c)=(((c)&7)<<18)|(((s)[i]&0x3f)<<12)|(((s)[(i)+1]&0x3f)<<6)|((s)[(i)+2]&0x3f); \
- (i)+=3; \
- } \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Get a code point from a string at a code point boundary offset,
- * and advance the offset to the next code point boundary.
- * (Post-incrementing forward iteration.)
- * "Safe" macro, checks for illegal sequences and for string boundaries.
- *
- * The length can be negative for a NUL-terminated string.
- *
- * The offset may point to the lead byte of a multi-byte sequence,
- * in which case the macro will read the whole sequence.
- * If the offset points to a trail byte or an illegal UTF-8 sequence, then
- * c is set to a negative value.
- *
- * @param s const uint8_t * string
- * @param i int32_t string offset, must be i<length
- * @param length int32_t string length
- * @param c output UChar32 variable, set to <0 in case of an error
- * @see U8_NEXT_UNSAFE
- * @stable ICU 2.4
- */
-#define U8_NEXT(s, i, length, c) U8_INTERNAL_NEXT_OR_SUB(s, i, length, c, U_SENTINEL)
-
-/**
- * Get a code point from a string at a code point boundary offset,
- * and advance the offset to the next code point boundary.
- * (Post-incrementing forward iteration.)
- * "Safe" macro, checks for illegal sequences and for string boundaries.
- *
- * The length can be negative for a NUL-terminated string.
- *
- * The offset may point to the lead byte of a multi-byte sequence,
- * in which case the macro will read the whole sequence.
- * If the offset points to a trail byte or an illegal UTF-8 sequence, then
- * c is set to U+FFFD.
- *
- * This macro does not distinguish between a real U+FFFD in the text
- * and U+FFFD returned for an ill-formed sequence.
- * Use U8_NEXT() if that distinction is important.
- *
- * @param s const uint8_t * string
- * @param i int32_t string offset, must be i<length
- * @param length int32_t string length
- * @param c output UChar32 variable, set to U+FFFD in case of an error
- * @see U8_NEXT
- * @stable ICU 51
- */
-#define U8_NEXT_OR_FFFD(s, i, length, c) U8_INTERNAL_NEXT_OR_SUB(s, i, length, c, 0xfffd)
-
-/** @internal */
-#define U8_INTERNAL_NEXT_OR_SUB(s, i, length, c, sub) UPRV_BLOCK_MACRO_BEGIN { \
- (c)=(uint8_t)(s)[(i)++]; \
- if(!U8_IS_SINGLE(c)) { \
- uint8_t __t = 0; \
- if((i)!=(length) && \
- /* fetch/validate/assemble all but last trail byte */ \
- ((c)>=0xe0 ? \
- ((c)<0xf0 ? /* U+0800..U+FFFF except surrogates */ \
- U8_LEAD3_T1_BITS[(c)&=0xf]&(1<<((__t=(s)[i])>>5)) && \
- (__t&=0x3f, 1) \
- : /* U+10000..U+10FFFF */ \
- ((c)-=0xf0)<=4 && \
- U8_LEAD4_T1_BITS[(__t=(s)[i])>>4]&(1<<(c)) && \
- ((c)=((c)<<6)|(__t&0x3f), ++(i)!=(length)) && \
- (__t=(s)[i]-0x80)<=0x3f) && \
- /* valid second-to-last trail byte */ \
- ((c)=((c)<<6)|__t, ++(i)!=(length)) \
- : /* U+0080..U+07FF */ \
- (c)>=0xc2 && ((c)&=0x1f, 1)) && \
- /* last trail byte */ \
- (__t=(s)[i]-0x80)<=0x3f && \
- ((c)=((c)<<6)|__t, ++(i), 1)) { \
- } else { \
- (c)=(sub); /* ill-formed*/ \
- } \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Append a code point to a string, overwriting 1 to 4 bytes.
- * The offset points to the current end of the string contents
- * and is advanced (post-increment).
- * "Unsafe" macro, assumes a valid code point and sufficient space in the string.
- * Otherwise, the result is undefined.
- *
- * @param s const uint8_t * string buffer
- * @param i string offset
- * @param c code point to append
- * @see U8_APPEND
- * @stable ICU 2.4
- */
-#define U8_APPEND_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
- uint32_t __uc=(c); \
- if(__uc<=0x7f) { \
- (s)[(i)++]=(uint8_t)__uc; \
- } else { \
- if(__uc<=0x7ff) { \
- (s)[(i)++]=(uint8_t)((__uc>>6)|0xc0); \
- } else { \
- if(__uc<=0xffff) { \
- (s)[(i)++]=(uint8_t)((__uc>>12)|0xe0); \
- } else { \
- (s)[(i)++]=(uint8_t)((__uc>>18)|0xf0); \
- (s)[(i)++]=(uint8_t)(((__uc>>12)&0x3f)|0x80); \
- } \
- (s)[(i)++]=(uint8_t)(((__uc>>6)&0x3f)|0x80); \
- } \
- (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Append a code point to a string, overwriting 1 to 4 bytes.
- * The offset points to the current end of the string contents
- * and is advanced (post-increment).
- * "Safe" macro, checks for a valid code point.
- * If a non-ASCII code point is written, checks for sufficient space in the string.
- * If the code point is not valid or trail bytes do not fit,
- * then isError is set to TRUE.
- *
- * @param s const uint8_t * string buffer
- * @param i int32_t string offset, must be i<capacity
- * @param capacity int32_t size of the string buffer
- * @param c UChar32 code point to append
- * @param isError output UBool set to TRUE if an error occurs, otherwise not modified
- * @see U8_APPEND_UNSAFE
- * @stable ICU 2.4
- */
-#define U8_APPEND(s, i, capacity, c, isError) UPRV_BLOCK_MACRO_BEGIN { \
- uint32_t __uc=(c); \
- if(__uc<=0x7f) { \
- (s)[(i)++]=(uint8_t)__uc; \
- } else if(__uc<=0x7ff && (i)+1<(capacity)) { \
- (s)[(i)++]=(uint8_t)((__uc>>6)|0xc0); \
- (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \
- } else if((__uc<=0xd7ff || (0xe000<=__uc && __uc<=0xffff)) && (i)+2<(capacity)) { \
- (s)[(i)++]=(uint8_t)((__uc>>12)|0xe0); \
- (s)[(i)++]=(uint8_t)(((__uc>>6)&0x3f)|0x80); \
- (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \
- } else if(0xffff<__uc && __uc<=0x10ffff && (i)+3<(capacity)) { \
- (s)[(i)++]=(uint8_t)((__uc>>18)|0xf0); \
- (s)[(i)++]=(uint8_t)(((__uc>>12)&0x3f)|0x80); \
- (s)[(i)++]=(uint8_t)(((__uc>>6)&0x3f)|0x80); \
- (s)[(i)++]=(uint8_t)((__uc&0x3f)|0x80); \
- } else { \
- (isError)=TRUE; \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Advance the string offset from one code point boundary to the next.
- * (Post-incrementing iteration.)
- * "Unsafe" macro, assumes well-formed UTF-8.
- *
- * @param s const uint8_t * string
- * @param i string offset
- * @see U8_FWD_1
- * @stable ICU 2.4
- */
-#define U8_FWD_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
- (i)+=1+U8_COUNT_TRAIL_BYTES_UNSAFE((s)[i]); \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Advance the string offset from one code point boundary to the next.
- * (Post-incrementing iteration.)
- * "Safe" macro, checks for illegal sequences and for string boundaries.
- *
- * The length can be negative for a NUL-terminated string.
- *
- * @param s const uint8_t * string
- * @param i int32_t string offset, must be i<length
- * @param length int32_t string length
- * @see U8_FWD_1_UNSAFE
- * @stable ICU 2.4
- */
-#define U8_FWD_1(s, i, length) UPRV_BLOCK_MACRO_BEGIN { \
- uint8_t __b=(s)[(i)++]; \
- if(U8_IS_LEAD(__b) && (i)!=(length)) { \
- uint8_t __t1=(s)[i]; \
- if((0xe0<=__b && __b<0xf0)) { \
- if(U8_IS_VALID_LEAD3_AND_T1(__b, __t1) && \
- ++(i)!=(length) && U8_IS_TRAIL((s)[i])) { \
- ++(i); \
- } \
- } else if(__b<0xe0) { \
- if(U8_IS_TRAIL(__t1)) { \
- ++(i); \
- } \
- } else /* c>=0xf0 */ { \
- if(U8_IS_VALID_LEAD4_AND_T1(__b, __t1) && \
- ++(i)!=(length) && U8_IS_TRAIL((s)[i]) && \
- ++(i)!=(length) && U8_IS_TRAIL((s)[i])) { \
- ++(i); \
- } \
- } \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Advance the string offset from one code point boundary to the n-th next one,
- * i.e., move forward by n code points.
- * (Post-incrementing iteration.)
- * "Unsafe" macro, assumes well-formed UTF-8.
- *
- * @param s const uint8_t * string
- * @param i string offset
- * @param n number of code points to skip
- * @see U8_FWD_N
- * @stable ICU 2.4
- */
-#define U8_FWD_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \
- int32_t __N=(n); \
- while(__N>0) { \
- U8_FWD_1_UNSAFE(s, i); \
- --__N; \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Advance the string offset from one code point boundary to the n-th next one,
- * i.e., move forward by n code points.
- * (Post-incrementing iteration.)
- * "Safe" macro, checks for illegal sequences and for string boundaries.
- *
- * The length can be negative for a NUL-terminated string.
- *
- * @param s const uint8_t * string
- * @param i int32_t string offset, must be i<length
- * @param length int32_t string length
- * @param n number of code points to skip
- * @see U8_FWD_N_UNSAFE
- * @stable ICU 2.4
- */
-#define U8_FWD_N(s, i, length, n) UPRV_BLOCK_MACRO_BEGIN { \
- int32_t __N=(n); \
- while(__N>0 && ((i)<(length) || ((length)<0 && (s)[i]!=0))) { \
- U8_FWD_1(s, i, length); \
- --__N; \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Adjust a random-access offset to a code point boundary
- * at the start of a code point.
- * If the offset points to a UTF-8 trail byte,
- * then the offset is moved backward to the corresponding lead byte.
- * Otherwise, it is not modified.
- * "Unsafe" macro, assumes well-formed UTF-8.
- *
- * @param s const uint8_t * string
- * @param i string offset
- * @see U8_SET_CP_START
- * @stable ICU 2.4
- */
-#define U8_SET_CP_START_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
- while(U8_IS_TRAIL((s)[i])) { --(i); } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Adjust a random-access offset to a code point boundary
- * at the start of a code point.
- * If the offset points to a UTF-8 trail byte,
- * then the offset is moved backward to the corresponding lead byte.
- * Otherwise, it is not modified.
- *
- * "Safe" macro, checks for illegal sequences and for string boundaries.
- * Unlike U8_TRUNCATE_IF_INCOMPLETE(), this macro always reads s[i].
- *
- * @param s const uint8_t * string
- * @param start int32_t starting string offset (usually 0)
- * @param i int32_t string offset, must be start<=i
- * @see U8_SET_CP_START_UNSAFE
- * @see U8_TRUNCATE_IF_INCOMPLETE
- * @stable ICU 2.4
- */
-#define U8_SET_CP_START(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \
- if(U8_IS_TRAIL((s)[(i)])) { \
- (i)=utf8_back1SafeBody(s, start, (i)); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * If the string ends with a UTF-8 byte sequence that is valid so far
- * but incomplete, then reduce the length of the string to end before
- * the lead byte of that incomplete sequence.
- * For example, if the string ends with E1 80, the length is reduced by 2.
- *
- * In all other cases (the string ends with a complete sequence, or it is not
- * possible for any further trail byte to extend the trailing sequence)
- * the length remains unchanged.
- *
- * Useful for processing text split across multiple buffers
- * (save the incomplete sequence for later)
- * and for optimizing iteration
- * (check for string length only once per character).
- *
- * "Safe" macro, checks for illegal sequences and for string boundaries.
- * Unlike U8_SET_CP_START(), this macro never reads s[length].
- *
- * (In UTF-16, simply check for U16_IS_LEAD(last code unit).)
- *
- * @param s const uint8_t * string
- * @param start int32_t starting string offset (usually 0)
- * @param length int32_t string length (usually start<=length)
- * @see U8_SET_CP_START
- * @stable ICU 61
- */
-#define U8_TRUNCATE_IF_INCOMPLETE(s, start, length) UPRV_BLOCK_MACRO_BEGIN { \
- if((length)>(start)) { \
- uint8_t __b1=s[(length)-1]; \
- if(U8_IS_SINGLE(__b1)) { \
- /* common ASCII character */ \
- } else if(U8_IS_LEAD(__b1)) { \
- --(length); \
- } else if(U8_IS_TRAIL(__b1) && ((length)-2)>=(start)) { \
- uint8_t __b2=s[(length)-2]; \
- if(0xe0<=__b2 && __b2<=0xf4) { \
- if(__b2<0xf0 ? U8_IS_VALID_LEAD3_AND_T1(__b2, __b1) : \
- U8_IS_VALID_LEAD4_AND_T1(__b2, __b1)) { \
- (length)-=2; \
- } \
- } else if(U8_IS_TRAIL(__b2) && ((length)-3)>=(start)) { \
- uint8_t __b3=s[(length)-3]; \
- if(0xf0<=__b3 && __b3<=0xf4 && U8_IS_VALID_LEAD4_AND_T1(__b3, __b2)) { \
- (length)-=3; \
- } \
- } \
- } \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/* definitions with backward iteration -------------------------------------- */
-
-/**
- * Move the string offset from one code point boundary to the previous one
- * and get the code point between them.
- * (Pre-decrementing backward iteration.)
- * "Unsafe" macro, assumes well-formed UTF-8.
- *
- * The input offset may be the same as the string length.
- * If the offset is behind a multi-byte sequence, then the macro will read
- * the whole sequence.
- * If the offset is behind a lead byte, then that itself
- * will be returned as the code point.
- * The result is undefined if the offset is behind an illegal UTF-8 sequence.
- *
- * @param s const uint8_t * string
- * @param i string offset
- * @param c output UChar32 variable
- * @see U8_PREV
- * @stable ICU 2.4
- */
-#define U8_PREV_UNSAFE(s, i, c) UPRV_BLOCK_MACRO_BEGIN { \
- (c)=(uint8_t)(s)[--(i)]; \
- if(U8_IS_TRAIL(c)) { \
- uint8_t __b, __count=1, __shift=6; \
-\
- /* c is a trail byte */ \
- (c)&=0x3f; \
- for(;;) { \
- __b=(s)[--(i)]; \
- if(__b>=0xc0) { \
- U8_MASK_LEAD_BYTE(__b, __count); \
- (c)|=(UChar32)__b<<__shift; \
- break; \
- } else { \
- (c)|=(UChar32)(__b&0x3f)<<__shift; \
- ++__count; \
- __shift+=6; \
- } \
- } \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Move the string offset from one code point boundary to the previous one
- * and get the code point between them.
- * (Pre-decrementing backward iteration.)
- * "Safe" macro, checks for illegal sequences and for string boundaries.
- *
- * The input offset may be the same as the string length.
- * If the offset is behind a multi-byte sequence, then the macro will read
- * the whole sequence.
- * If the offset is behind a lead byte, then that itself
- * will be returned as the code point.
- * If the offset is behind an illegal UTF-8 sequence, then c is set to a negative value.
- *
- * @param s const uint8_t * string
- * @param start int32_t starting string offset (usually 0)
- * @param i int32_t string offset, must be start<i
- * @param c output UChar32 variable, set to <0 in case of an error
- * @see U8_PREV_UNSAFE
- * @stable ICU 2.4
- */
-#define U8_PREV(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \
- (c)=(uint8_t)(s)[--(i)]; \
- if(!U8_IS_SINGLE(c)) { \
- (c)=utf8_prevCharSafeBody((const uint8_t *)s, start, &(i), c, -1); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Move the string offset from one code point boundary to the previous one
- * and get the code point between them.
- * (Pre-decrementing backward iteration.)
- * "Safe" macro, checks for illegal sequences and for string boundaries.
- *
- * The input offset may be the same as the string length.
- * If the offset is behind a multi-byte sequence, then the macro will read
- * the whole sequence.
- * If the offset is behind a lead byte, then that itself
- * will be returned as the code point.
- * If the offset is behind an illegal UTF-8 sequence, then c is set to U+FFFD.
- *
- * This macro does not distinguish between a real U+FFFD in the text
- * and U+FFFD returned for an ill-formed sequence.
- * Use U8_PREV() if that distinction is important.
- *
- * @param s const uint8_t * string
- * @param start int32_t starting string offset (usually 0)
- * @param i int32_t string offset, must be start<i
- * @param c output UChar32 variable, set to U+FFFD in case of an error
- * @see U8_PREV
- * @stable ICU 51
- */
-#define U8_PREV_OR_FFFD(s, start, i, c) UPRV_BLOCK_MACRO_BEGIN { \
- (c)=(uint8_t)(s)[--(i)]; \
- if(!U8_IS_SINGLE(c)) { \
- (c)=utf8_prevCharSafeBody((const uint8_t *)s, start, &(i), c, -3); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Move the string offset from one code point boundary to the previous one.
- * (Pre-decrementing backward iteration.)
- * The input offset may be the same as the string length.
- * "Unsafe" macro, assumes well-formed UTF-8.
- *
- * @param s const uint8_t * string
- * @param i string offset
- * @see U8_BACK_1
- * @stable ICU 2.4
- */
-#define U8_BACK_1_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
- while(U8_IS_TRAIL((s)[--(i)])) {} \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Move the string offset from one code point boundary to the previous one.
- * (Pre-decrementing backward iteration.)
- * The input offset may be the same as the string length.
- * "Safe" macro, checks for illegal sequences and for string boundaries.
- *
- * @param s const uint8_t * string
- * @param start int32_t starting string offset (usually 0)
- * @param i int32_t string offset, must be start<i
- * @see U8_BACK_1_UNSAFE
- * @stable ICU 2.4
- */
-#define U8_BACK_1(s, start, i) UPRV_BLOCK_MACRO_BEGIN { \
- if(U8_IS_TRAIL((s)[--(i)])) { \
- (i)=utf8_back1SafeBody(s, start, (i)); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Move the string offset from one code point boundary to the n-th one before it,
- * i.e., move backward by n code points.
- * (Pre-decrementing backward iteration.)
- * The input offset may be the same as the string length.
- * "Unsafe" macro, assumes well-formed UTF-8.
- *
- * @param s const uint8_t * string
- * @param i string offset
- * @param n number of code points to skip
- * @see U8_BACK_N
- * @stable ICU 2.4
- */
-#define U8_BACK_N_UNSAFE(s, i, n) UPRV_BLOCK_MACRO_BEGIN { \
- int32_t __N=(n); \
- while(__N>0) { \
- U8_BACK_1_UNSAFE(s, i); \
- --__N; \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Move the string offset from one code point boundary to the n-th one before it,
- * i.e., move backward by n code points.
- * (Pre-decrementing backward iteration.)
- * The input offset may be the same as the string length.
- * "Safe" macro, checks for illegal sequences and for string boundaries.
- *
- * @param s const uint8_t * string
- * @param start int32_t index of the start of the string
- * @param i int32_t string offset, must be start<i
- * @param n number of code points to skip
- * @see U8_BACK_N_UNSAFE
- * @stable ICU 2.4
- */
-#define U8_BACK_N(s, start, i, n) UPRV_BLOCK_MACRO_BEGIN { \
- int32_t __N=(n); \
- while(__N>0 && (i)>(start)) { \
- U8_BACK_1(s, start, i); \
- --__N; \
- } \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Adjust a random-access offset to a code point boundary after a code point.
- * If the offset is behind a partial multi-byte sequence,
- * then the offset is incremented to behind the whole sequence.
- * Otherwise, it is not modified.
- * The input offset may be the same as the string length.
- * "Unsafe" macro, assumes well-formed UTF-8.
- *
- * @param s const uint8_t * string
- * @param i string offset
- * @see U8_SET_CP_LIMIT
- * @stable ICU 2.4
- */
-#define U8_SET_CP_LIMIT_UNSAFE(s, i) UPRV_BLOCK_MACRO_BEGIN { \
- U8_BACK_1_UNSAFE(s, i); \
- U8_FWD_1_UNSAFE(s, i); \
-} UPRV_BLOCK_MACRO_END
-
-/**
- * Adjust a random-access offset to a code point boundary after a code point.
- * If the offset is behind a partial multi-byte sequence,
- * then the offset is incremented to behind the whole sequence.
- * Otherwise, it is not modified.
- * The input offset may be the same as the string length.
- * "Safe" macro, checks for illegal sequences and for string boundaries.
- *
- * The length can be negative for a NUL-terminated string.
- *
- * @param s const uint8_t * string
- * @param start int32_t starting string offset (usually 0)
- * @param i int32_t string offset, must be start<=i<=length
- * @param length int32_t string length
- * @see U8_SET_CP_LIMIT_UNSAFE
- * @stable ICU 2.4
- */
-#define U8_SET_CP_LIMIT(s, start, i, length) UPRV_BLOCK_MACRO_BEGIN { \
- if((start)<(i) && ((i)<(length) || (length)<0)) { \
- U8_BACK_1(s, start, i); \
- U8_FWD_1(s, i, length); \
- } \
-} UPRV_BLOCK_MACRO_END
-
-#endif