aboutsummaryrefslogtreecommitdiff
path: root/src/nvim
diff options
context:
space:
mode:
authorJosh Rahm <joshuarahm@gmail.com>2024-11-19 22:57:13 +0000
committerJosh Rahm <joshuarahm@gmail.com>2024-11-19 22:57:13 +0000
commit9be89f131f87608f224f0ee06d199fcd09d32176 (patch)
tree11022dcfa9e08cb4ac5581b16734196128688d48 /src/nvim
parentff7ed8f586589d620a806c3758fac4a47a8e7e15 (diff)
parent88085c2e80a7e3ac29aabb6b5420377eed99b8b6 (diff)
downloadrneovim-9be89f131f87608f224f0ee06d199fcd09d32176.tar.gz
rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.tar.bz2
rneovim-9be89f131f87608f224f0ee06d199fcd09d32176.zip
Merge remote-tracking branch 'upstream/master' into mix_20240309
Diffstat (limited to 'src/nvim')
-rw-r--r--src/nvim/CMakeLists.txt138
-rw-r--r--src/nvim/api/autocmd.c10
-rw-r--r--src/nvim/api/buffer.c28
-rw-r--r--src/nvim/api/command.c40
-rw-r--r--src/nvim/api/deprecated.c12
-rw-r--r--src/nvim/api/extmark.c191
-rw-r--r--src/nvim/api/extmark.h15
-rw-r--r--src/nvim/api/keysets_defs.h53
-rw-r--r--src/nvim/api/options.c23
-rw-r--r--src/nvim/api/private/converter.c43
-rw-r--r--src/nvim/api/private/defs.h24
-rw-r--r--src/nvim/api/private/helpers.c86
-rw-r--r--src/nvim/api/private/helpers.h10
-rw-r--r--src/nvim/api/private/validate.c2
-rw-r--r--src/nvim/api/private/validate.h8
-rw-r--r--src/nvim/api/ui.c52
-rw-r--r--src/nvim/api/vim.c233
-rw-r--r--src/nvim/api/vimscript.c48
-rw-r--r--src/nvim/api/win_config.c101
-rw-r--r--src/nvim/api/window.c8
-rw-r--r--src/nvim/arglist.c14
-rw-r--r--src/nvim/ascii_defs.h37
-rw-r--r--src/nvim/auevents.lua2
-rw-r--r--src/nvim/autocmd.c45
-rw-r--r--src/nvim/base64.c4
-rw-r--r--src/nvim/buffer.c264
-rw-r--r--src/nvim/buffer.h19
-rw-r--r--src/nvim/buffer_defs.h21
-rw-r--r--src/nvim/bufwrite.c45
-rw-r--r--src/nvim/change.c53
-rw-r--r--src/nvim/channel.c131
-rw-r--r--src/nvim/channel.h44
-rw-r--r--src/nvim/channel_defs.h2
-rw-r--r--src/nvim/charset.c4
-rw-r--r--src/nvim/charset.h17
-rw-r--r--src/nvim/cmdexpand.c76
-rw-r--r--src/nvim/cmdexpand_defs.h1
-rw-r--r--src/nvim/cmdhist.c11
-rw-r--r--src/nvim/cmdhist.h2
-rw-r--r--src/nvim/context.c140
-rw-r--r--src/nvim/context.h23
-rw-r--r--src/nvim/cursor.c21
-rw-r--r--src/nvim/cursor_shape.c4
-rw-r--r--src/nvim/debugger.c1
-rw-r--r--src/nvim/decoration.c66
-rw-r--r--src/nvim/decoration.h3
-rw-r--r--src/nvim/decoration_defs.h1
-rw-r--r--src/nvim/diff.c211
-rw-r--r--src/nvim/digraph.c5
-rw-r--r--src/nvim/drawline.c76
-rw-r--r--src/nvim/drawscreen.c178
-rw-r--r--src/nvim/drawscreen.h6
-rw-r--r--src/nvim/edit.c259
-rw-r--r--src/nvim/errors.h193
-rw-r--r--src/nvim/eval.c1706
-rw-r--r--src/nvim/eval.h11
-rw-r--r--src/nvim/eval.lua946
-rw-r--r--src/nvim/eval/decode.c363
-rw-r--r--src/nvim/eval/decode.h2
-rw-r--r--src/nvim/eval/encode.c90
-rw-r--r--src/nvim/eval/encode.h4
-rw-r--r--src/nvim/eval/executor.c325
-rw-r--r--src/nvim/eval/fs.c1492
-rw-r--r--src/nvim/eval/fs.h8
-rw-r--r--src/nvim/eval/funcs.c1811
-rw-r--r--src/nvim/eval/typval.c175
-rw-r--r--src/nvim/eval/typval.h91
-rw-r--r--src/nvim/eval/typval_defs.h6
-rw-r--r--src/nvim/eval/typval_encode.c.h20
-rw-r--r--src/nvim/eval/typval_encode.h11
-rw-r--r--src/nvim/eval/userfunc.c132
-rw-r--r--src/nvim/eval/userfunc.h8
-rw-r--r--src/nvim/eval/vars.c9
-rw-r--r--src/nvim/eval/window.c3
-rw-r--r--src/nvim/eval_defs.h1
-rw-r--r--src/nvim/event/defs.h70
-rw-r--r--src/nvim/event/libuv_proc.c (renamed from src/nvim/event/libuv_process.c)34
-rw-r--r--src/nvim/event/libuv_proc.h (renamed from src/nvim/event/libuv_process.h)6
-rw-r--r--src/nvim/event/loop.c4
-rw-r--r--src/nvim/event/loop.h26
-rw-r--r--src/nvim/event/proc.c (renamed from src/nvim/event/process.c)194
-rw-r--r--src/nvim/event/proc.h (renamed from src/nvim/event/process.h)14
-rw-r--r--src/nvim/event/rstream.c200
-rw-r--r--src/nvim/event/socket.c18
-rw-r--r--src/nvim/event/stream.c42
-rw-r--r--src/nvim/event/wstream.c31
-rw-r--r--src/nvim/ex_cmds.c127
-rw-r--r--src/nvim/ex_cmds2.c5
-rw-r--r--src/nvim/ex_cmds_defs.h2
-rw-r--r--src/nvim/ex_docmd.c181
-rw-r--r--src/nvim/ex_eval.c11
-rw-r--r--src/nvim/ex_getln.c277
-rw-r--r--src/nvim/ex_session.c1
-rw-r--r--src/nvim/extmark.c40
-rw-r--r--src/nvim/file_search.c328
-rw-r--r--src/nvim/file_search.h12
-rw-r--r--src/nvim/fileio.c111
-rw-r--r--src/nvim/fold.c139
-rw-r--r--src/nvim/garray.c8
-rw-r--r--src/nvim/generators/c_grammar.lua12
-rw-r--r--src/nvim/generators/gen_api_dispatch.lua52
-rw-r--r--src/nvim/generators/gen_api_ui_events.lua15
-rw-r--r--src/nvim/generators/gen_declarations.lua28
-rw-r--r--src/nvim/generators/gen_eval.lua1
-rw-r--r--src/nvim/generators/gen_options.lua2
-rw-r--r--src/nvim/generators/gen_options_enum.lua2
-rw-r--r--src/nvim/generators/gen_unicode_tables.lua323
-rw-r--r--src/nvim/generators/gen_vimvim.lua7
-rw-r--r--src/nvim/generators/hashy.lua22
-rw-r--r--src/nvim/getchar.c341
-rw-r--r--src/nvim/globals.h206
-rw-r--r--src/nvim/grid.c37
-rw-r--r--src/nvim/hashtab.c5
-rw-r--r--src/nvim/help.c5
-rw-r--r--src/nvim/highlight.c32
-rw-r--r--src/nvim/highlight.h4
-rw-r--r--src/nvim/highlight_defs.h4
-rw-r--r--src/nvim/highlight_group.c97
-rw-r--r--src/nvim/indent.c64
-rw-r--r--src/nvim/indent_c.c2
-rw-r--r--src/nvim/input.c4
-rw-r--r--src/nvim/insexpand.c253
-rw-r--r--src/nvim/insexpand.h9
-rw-r--r--src/nvim/keycodes.c1
-rw-r--r--src/nvim/keycodes.h4
-rw-r--r--src/nvim/lib/queue_defs.h30
-rw-r--r--src/nvim/linematch.c95
-rw-r--r--src/nvim/linematch.h1
-rw-r--r--src/nvim/log.c30
-rw-r--r--src/nvim/lua/api_wrappers.c1
-rw-r--r--src/nvim/lua/converter.c179
-rw-r--r--src/nvim/lua/executor.c71
-rw-r--r--src/nvim/lua/secure.c5
-rw-r--r--src/nvim/lua/spell.c1
-rw-r--r--src/nvim/lua/stdlib.c100
-rw-r--r--src/nvim/lua/treesitter.c234
-rw-r--r--src/nvim/lua/xdiff.c15
-rw-r--r--src/nvim/main.c133
-rw-r--r--src/nvim/mapping.c122
-rw-r--r--src/nvim/mapping_defs.h3
-rw-r--r--src/nvim/mark.c54
-rw-r--r--src/nvim/mark.h8
-rw-r--r--src/nvim/mark_defs.h28
-rw-r--r--src/nvim/marktree.c14
-rw-r--r--src/nvim/marktree.h20
-rw-r--r--src/nvim/match.c4
-rw-r--r--src/nvim/math.c10
-rw-r--r--src/nvim/mbyte.c464
-rw-r--r--src/nvim/mbyte.h49
-rw-r--r--src/nvim/memfile.c56
-rw-r--r--src/nvim/memfile_defs.h2
-rw-r--r--src/nvim/memline.c69
-rw-r--r--src/nvim/memory.c20
-rw-r--r--src/nvim/memory.h4
-rw-r--r--src/nvim/memory_defs.h2
-rw-r--r--src/nvim/menu.c3
-rw-r--r--src/nvim/message.c21
-rw-r--r--src/nvim/mouse.c38
-rw-r--r--src/nvim/move.c67
-rw-r--r--src/nvim/msgpack_rpc/channel.c65
-rw-r--r--src/nvim/msgpack_rpc/channel.h2
-rw-r--r--src/nvim/msgpack_rpc/channel_defs.h3
-rw-r--r--src/nvim/msgpack_rpc/packer.c110
-rw-r--r--src/nvim/msgpack_rpc/packer.h5
-rw-r--r--src/nvim/msgpack_rpc/packer_defs.h2
-rw-r--r--src/nvim/msgpack_rpc/server.c67
-rw-r--r--src/nvim/msgpack_rpc/unpacker.c202
-rw-r--r--src/nvim/msgpack_rpc/unpacker.h2
-rw-r--r--src/nvim/normal.c139
-rw-r--r--src/nvim/ops.c215
-rw-r--r--src/nvim/ops.h26
-rw-r--r--src/nvim/option.c140
-rw-r--r--src/nvim/option_vars.h36
-rw-r--r--src/nvim/options.lua215
-rw-r--r--src/nvim/optionstr.c131
-rw-r--r--src/nvim/os/env.c59
-rw-r--r--src/nvim/os/fileio.c314
-rw-r--r--src/nvim/os/fileio.h6
-rw-r--r--src/nvim/os/fileio_defs.h24
-rw-r--r--src/nvim/os/fs.c21
-rw-r--r--src/nvim/os/input.c215
-rw-r--r--src/nvim/os/proc.c (renamed from src/nvim/os/process.c)16
-rw-r--r--src/nvim/os/proc.h (renamed from src/nvim/os/process.h)2
-rw-r--r--src/nvim/os/pty_conpty_win.c4
-rw-r--r--src/nvim/os/pty_proc.h7
-rw-r--r--src/nvim/os/pty_proc_unix.c (renamed from src/nvim/os/pty_process_unix.c)49
-rw-r--r--src/nvim/os/pty_proc_unix.h (renamed from src/nvim/os/pty_process_unix.h)8
-rw-r--r--src/nvim/os/pty_proc_win.c (renamed from src/nvim/os/pty_process_win.c)96
-rw-r--r--src/nvim/os/pty_proc_win.h (renamed from src/nvim/os/pty_process_win.h)12
-rw-r--r--src/nvim/os/pty_process.h7
-rw-r--r--src/nvim/os/shell.c163
-rw-r--r--src/nvim/os/stdpaths.c20
-rw-r--r--src/nvim/path.c150
-rw-r--r--src/nvim/path.h3
-rw-r--r--src/nvim/plines.c63
-rw-r--r--src/nvim/plines.h17
-rw-r--r--src/nvim/po/CMakeLists.txt6
-rw-r--r--src/nvim/po/af.po6
-rw-r--r--src/nvim/po/ca.po8
-rw-r--r--src/nvim/po/cs.cp1250.po6
-rw-r--r--src/nvim/po/cs.po6
-rw-r--r--src/nvim/po/da.po8
-rw-r--r--src/nvim/po/de.po8
-rw-r--r--src/nvim/po/en_GB.po4
-rw-r--r--src/nvim/po/eo.po8
-rw-r--r--src/nvim/po/es.po8
-rw-r--r--src/nvim/po/fi.po8
-rw-r--r--src/nvim/po/fr.po4
-rw-r--r--src/nvim/po/ga.po8
-rw-r--r--src/nvim/po/it.po8
-rw-r--r--src/nvim/po/ja.euc-jp.po8
-rw-r--r--src/nvim/po/ja.po8
-rw-r--r--src/nvim/po/ko.UTF-8.po8
-rw-r--r--src/nvim/po/nb.po7
-rw-r--r--src/nvim/po/nl.po8
-rw-r--r--src/nvim/po/no.po7
-rw-r--r--src/nvim/po/pl.UTF-8.po8
-rw-r--r--src/nvim/po/pt_BR.po8
-rw-r--r--src/nvim/po/ru.po8
-rw-r--r--src/nvim/po/sk.cp1250.po7
-rw-r--r--src/nvim/po/sk.po7
-rw-r--r--src/nvim/po/sr.po12
-rw-r--r--src/nvim/po/sv.po8
-rw-r--r--src/nvim/po/tr.po8
-rw-r--r--src/nvim/po/uk.po8
-rw-r--r--src/nvim/po/vi.po6
-rw-r--r--src/nvim/po/zh_CN.UTF-8.po8
-rw-r--r--src/nvim/po/zh_TW.UTF-8.po6
-rw-r--r--src/nvim/popupmenu.c273
-rw-r--r--src/nvim/popupmenu.h12
-rw-r--r--src/nvim/profile.c19
-rw-r--r--src/nvim/quickfix.c159
-rw-r--r--src/nvim/rbuffer.c230
-rw-r--r--src/nvim/rbuffer.h71
-rw-r--r--src/nvim/rbuffer_defs.h45
-rw-r--r--src/nvim/regexp.c151
-rw-r--r--src/nvim/runtime.c13
-rw-r--r--src/nvim/search.c107
-rw-r--r--src/nvim/search.h4
-rw-r--r--src/nvim/sha256.c22
-rw-r--r--src/nvim/shada.c1706
-rw-r--r--src/nvim/shada.h2
-rw-r--r--src/nvim/sign.c120
-rw-r--r--src/nvim/sign_defs.h1
-rw-r--r--src/nvim/spell.c181
-rw-r--r--src/nvim/spellfile.c25
-rw-r--r--src/nvim/spellsuggest.c87
-rw-r--r--src/nvim/state.c40
-rw-r--r--src/nvim/statusline.c156
-rw-r--r--src/nvim/strings.c129
-rw-r--r--src/nvim/strings.h38
-rw-r--r--src/nvim/syntax.c109
-rw-r--r--src/nvim/tag.c149
-rw-r--r--src/nvim/tag.h1
-rw-r--r--src/nvim/terminal.c165
-rw-r--r--src/nvim/testing.c11
-rw-r--r--src/nvim/textformat.c29
-rw-r--r--src/nvim/textobject.c15
-rw-r--r--src/nvim/tui/input.c209
-rw-r--r--src/nvim/tui/input.h15
-rw-r--r--src/nvim/tui/terminfo.c2
-rw-r--r--src/nvim/tui/termkey/README1
-rw-r--r--src/nvim/tui/termkey/driver-csi.c902
-rw-r--r--src/nvim/tui/termkey/driver-csi.h7
-rw-r--r--src/nvim/tui/termkey/driver-ti.c595
-rw-r--r--src/nvim/tui/termkey/driver-ti.h7
-rw-r--r--src/nvim/tui/termkey/termkey-internal.h109
-rw-r--r--src/nvim/tui/termkey/termkey.c1315
-rw-r--r--src/nvim/tui/termkey/termkey.h10
-rw-r--r--src/nvim/tui/termkey/termkey_defs.h199
-rw-r--r--src/nvim/tui/tui.c105
-rw-r--r--src/nvim/tui/tui_defs.h2
-rw-r--r--src/nvim/types_defs.h6
-rw-r--r--src/nvim/ui.c19
-rw-r--r--src/nvim/ui_client.c55
-rw-r--r--src/nvim/ui_client.h2
-rw-r--r--src/nvim/undo.c38
-rw-r--r--src/nvim/usercmd.c25
-rw-r--r--src/nvim/version.c4
-rw-r--r--src/nvim/viml/parser/expressions.c12
-rw-r--r--src/nvim/viml/parser/expressions.h2
-rw-r--r--src/nvim/viml/parser/parser.h22
-rw-r--r--src/nvim/window.c325
-rw-r--r--src/nvim/window.h11
-rw-r--r--src/nvim/winfloat.c36
285 files changed, 15375 insertions, 11266 deletions
diff --git a/src/nvim/CMakeLists.txt b/src/nvim/CMakeLists.txt
index 937cfaaa31..c2a358327a 100644
--- a/src/nvim/CMakeLists.txt
+++ b/src/nvim/CMakeLists.txt
@@ -30,19 +30,17 @@ target_link_libraries(main_lib INTERFACE ${LUV_LIBRARY})
find_package(Iconv REQUIRED)
find_package(Libuv 1.28.0 REQUIRED)
-find_package(Libvterm 0.3.3 REQUIRED)
find_package(Lpeg REQUIRED)
-find_package(Msgpack 1.0.0 REQUIRED)
-find_package(Treesitter 0.22.6 REQUIRED)
+find_package(Treesitter 0.23.0 REQUIRED)
find_package(Unibilium 2.0 REQUIRED)
+find_package(UTF8proc REQUIRED)
target_link_libraries(main_lib INTERFACE
iconv
- libvterm
lpeg
- msgpack
treesitter
- unibilium)
+ unibilium
+ utf8proc)
target_link_libraries(nlua0 PUBLIC lpeg)
if(ENABLE_LIBINTL)
@@ -50,7 +48,11 @@ if(ENABLE_LIBINTL)
target_link_libraries(main_lib INTERFACE libintl)
endif()
-target_compile_definitions(main_lib INTERFACE HAVE_UNIBILIUM)
+if(ENABLE_WASMTIME)
+ find_package(Wasmtime 25.0.1 EXACT REQUIRED)
+ target_link_libraries(main_lib INTERFACE wasmtime)
+ target_compile_definitions(nvim_bin PRIVATE HAVE_WASMTIME)
+endif()
# The unit test lib requires LuaJIT; it will be skipped if LuaJIT is missing.
option(PREFER_LUA "Prefer Lua over LuaJIT in the nvim executable." OFF)
@@ -149,7 +151,7 @@ if(UNIX)
endif()
if(CMAKE_SYSTEM_NAME MATCHES "Windows")
- target_compile_definitions(main_lib INTERFACE _WIN32_WINNT=0x0602 MSWIN)
+ target_compile_definitions(main_lib INTERFACE _WIN32_WINNT=0x0602 MSWIN WIN32_LEAN_AND_MEAN)
target_link_libraries(main_lib INTERFACE netapi32)
elseif(CMAKE_SYSTEM_NAME MATCHES "Darwin")
target_link_libraries(nvim_bin PRIVATE "-framework CoreServices")
@@ -294,7 +296,6 @@ set(GENERATOR_DIR ${CMAKE_CURRENT_LIST_DIR}/generators)
set(GEN_EVAL_TOUCH ${TOUCHES_DIR}/gen_doc_eval)
set(LUAJIT_RUNTIME_DIR ${DEPS_PREFIX}/share/luajit-2.1/jit)
set(NVIM_RUNTIME_DIR ${PROJECT_SOURCE_DIR}/runtime)
-set(UNICODE_DIR ${PROJECT_SOURCE_DIR}/src/unicode)
# GENERATOR_DIR
set(API_DISPATCH_GENERATOR ${GENERATOR_DIR}/gen_api_dispatch.lua)
@@ -309,7 +310,6 @@ set(GENERATOR_PRELOAD ${GENERATOR_DIR}/preload.lua)
set(HEADER_GENERATOR ${GENERATOR_DIR}/gen_declarations.lua)
set(OPTIONS_ENUM_GENERATOR ${GENERATOR_DIR}/gen_options_enum.lua)
set(OPTIONS_GENERATOR ${GENERATOR_DIR}/gen_options.lua)
-set(UNICODE_TABLES_GENERATOR ${GENERATOR_DIR}/gen_unicode_tables.lua)
# GENERATED_DIR and GENERATED_INCLUDES_DIR
set(GENERATED_API_DISPATCH ${GENERATED_DIR}/api/private/dispatch_wrappers.generated.h)
@@ -326,7 +326,6 @@ set(GENERATED_OPTIONS_MAP ${GENERATED_DIR}/options_map.generated.h)
set(GENERATED_UI_EVENTS_CALL ${GENERATED_DIR}/ui_events_call.generated.h)
set(GENERATED_UI_EVENTS_CLIENT ${GENERATED_DIR}/ui_events_client.generated.h)
set(GENERATED_UI_EVENTS_REMOTE ${GENERATED_DIR}/ui_events_remote.generated.h)
-set(GENERATED_UNICODE_TABLES ${GENERATED_DIR}/unicode_tables.generated.h)
set(LUA_API_C_BINDINGS ${GENERATED_DIR}/lua_api_c_bindings.generated.h)
set(VIM_MODULE_FILE ${GENERATED_DIR}/lua/vim_module.generated.h)
@@ -343,7 +342,6 @@ set(LUA_LOADER_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/loader.lua)
set(LUA_OPTIONS_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/_options.lua)
set(LUA_SHARED_MODULE_SOURCE ${NVIM_RUNTIME_DIR}/lua/vim/shared.lua)
-file(GLOB UNICODE_FILES CONFIGURE_DEPENDS ${UNICODE_DIR}/*.txt)
file(GLOB API_HEADERS CONFIGURE_DEPENDS api/*.h)
list(REMOVE_ITEM API_HEADERS ${CMAKE_CURRENT_LIST_DIR}/api/ui_events.in.h)
file(GLOB MSGPACK_RPC_HEADERS CONFIGURE_DEPENDS msgpack_rpc/*.h)
@@ -363,8 +361,8 @@ file(MAKE_DIRECTORY ${TOUCHES_DIR} ${GENERATED_DIR} ${GENERATED_INCLUDES_DIR})
file(GLOB NVIM_SOURCES CONFIGURE_DEPENDS *.c)
file(GLOB NVIM_HEADERS CONFIGURE_DEPENDS *.h)
-file(GLOB EXTERNAL_SOURCES CONFIGURE_DEPENDS ../xdiff/*.c ../mpack/*.c ../cjson/*.c ../klib/*.c ../termkey/*.c)
-file(GLOB EXTERNAL_HEADERS CONFIGURE_DEPENDS ../xdiff/*.h ../mpack/*.h ../cjson/*.h ../klib/*.h ../termkey/*.h)
+file(GLOB EXTERNAL_SOURCES CONFIGURE_DEPENDS ../xdiff/*.c ../mpack/*.c ../cjson/*.c ../klib/*.c ../vterm/*.c)
+file(GLOB EXTERNAL_HEADERS CONFIGURE_DEPENDS ../xdiff/*.h ../mpack/*.h ../cjson/*.h ../klib/*.h ../vterm/*.h)
file(GLOB NLUA0_SOURCES CONFIGURE_DEPENDS ../mpack/*.c)
@@ -375,6 +373,15 @@ if(PREFER_LUA)
target_compile_definitions(main_lib INTERFACE NVIM_VENDOR_BIT)
endif()
+# Inlined external projects, we don't maintain it. #9306
+if(MSVC)
+ set_source_files_properties(
+ ${EXTERNAL_SOURCES} PROPERTIES COMPILE_OPTIONS "-wd4090;-wd4244;-wd4267")
+else()
+ set_source_files_properties(
+ ${EXTERNAL_SOURCES} PROPERTIES COMPILE_OPTIONS "-Wno-conversion;-Wno-missing-noreturn;-Wno-missing-format-attribute;-Wno-double-promotion;-Wno-strict-prototypes;-Wno-misleading-indentation;-Wno-sign-compare;-Wno-implicit-fallthrough;-Wno-missing-prototypes;-Wno-missing-field-initializers")
+endif()
+
list(APPEND NLUA0_SOURCES ${PROJECT_SOURCE_DIR}/src/nlua0.c)
foreach(subdir
@@ -383,9 +390,11 @@ foreach(subdir
api/private
msgpack_rpc
tui
+ tui/termkey
event
eval
lua
+ lib
viml
viml/parser
)
@@ -403,34 +412,36 @@ endforeach()
list(SORT NVIM_SOURCES)
list(SORT NVIM_HEADERS)
-list(APPEND LINT_NVIM_SOURCES ${NVIM_SOURCES} ${NVIM_HEADERS})
-
foreach(sfile ${NVIM_SOURCES})
get_filename_component(f ${sfile} NAME)
- if(WIN32 AND ${f} MATCHES "^(pty_process_unix.c)$")
- list(APPEND to_remove ${sfile})
+ if(WIN32 AND ${f} MATCHES "^(pty_proc_unix.c)$")
+ list(REMOVE_ITEM NVIM_SOURCES ${sfile})
endif()
- if(NOT WIN32 AND ${f} MATCHES "^(pty_process_win.c)$")
- list(APPEND to_remove ${sfile})
+ if(NOT WIN32 AND ${f} MATCHES "^(pty_proc_win.c)$")
+ list(REMOVE_ITEM NVIM_SOURCES ${sfile})
endif()
if(NOT WIN32 AND ${f} MATCHES "^(pty_conpty_win.c)$")
- list(APPEND to_remove ${sfile})
+ list(REMOVE_ITEM NVIM_SOURCES ${sfile})
endif()
if(NOT WIN32 AND ${f} MATCHES "^(os_win_console.c)$")
- list(APPEND to_remove ${sfile})
+ list(REMOVE_ITEM NVIM_SOURCES ${sfile})
endif()
endforeach()
-list(REMOVE_ITEM NVIM_SOURCES ${to_remove})
+foreach(hfile ${NVIM_HEADERS})
+ get_filename_component(f ${hfile} NAME)
+ if(WIN32 AND ${f} MATCHES "^(unix_defs.h)$")
+ list(REMOVE_ITEM NVIM_HEADERS ${hfile})
+ endif()
+ if(WIN32 AND ${f} MATCHES "^(pty_proc_unix.h)$")
+ list(REMOVE_ITEM NVIM_HEADERS ${hfile})
+ endif()
+ if(NOT WIN32 AND ${f} MATCHES "^(win_defs.h)$")
+ list(REMOVE_ITEM NVIM_HEADERS ${hfile})
+ endif()
+endforeach()
-# xdiff, mpack, lua-cjson, termkey: inlined external project, we don't maintain it. #9306
-if(MSVC)
- set_source_files_properties(
- ${EXTERNAL_SOURCES} PROPERTIES COMPILE_OPTIONS "-wd4090;-wd4244;-wd4267")
-else()
- set_source_files_properties(
- ${EXTERNAL_SOURCES} PROPERTIES COMPILE_OPTIONS "-Wno-conversion;-Wno-missing-noreturn;-Wno-missing-format-attribute;-Wno-double-promotion;-Wno-strict-prototypes;-Wno-misleading-indentation;-Wno-sign-compare;-Wno-implicit-fallthrough;-Wno-missing-prototypes;-Wno-missing-field-initializers")
-endif()
+list(APPEND LINT_NVIM_SOURCES ${NVIM_SOURCES} ${NVIM_HEADERS})
# Log level (NVIM_LOG_DEBUG in log.h)
if(CI_BUILD)
@@ -477,7 +488,6 @@ endif()
if(MSVC)
list(APPEND gen_cflags -wd4003)
endif()
-list(APPEND gen_cflags -O2)
set(NVIM_VERSION_GIT_H ${PROJECT_BINARY_DIR}/cmake.config/auto/versiondef_git.h)
add_custom_target(update_version_stamp
@@ -507,6 +517,7 @@ set(LUA_GEN_DEPS ${GENERATOR_PRELOAD} $<TARGET_FILE:nlua0>)
# NVIM_GENERATED_FOR_SOURCES: generated headers to be included in sources
# These lists must be mutually exclusive.
foreach(sfile ${NVIM_SOURCES}
+ ${NVIM_HEADERS}
${GENERATED_API_DISPATCH}
"${GENERATED_UI_EVENTS_CALL}"
"${GENERATED_UI_EVENTS_REMOTE}"
@@ -519,18 +530,30 @@ foreach(sfile ${NVIM_SOURCES}
endif()
get_filename_component(f ${sfile} NAME)
get_filename_component(r ${sfile} NAME_WE)
+ get_filename_component(ext ${sfile} EXT)
if(NOT ${d} EQUAL ".")
set(f "${d}/${f}")
set(r "${d}/${r}")
endif()
- set(gf_c_h "${GENERATED_DIR}/${r}.c.generated.h")
- set(gf_h_h "${GENERATED_INCLUDES_DIR}/${r}.h.generated.h")
- set(gf_i "${GENERATED_DIR}/${r}.i")
+ if ("${ext}" STREQUAL ".c.h")
+ continue() # .c.h files are sussy baka, skip
+ elseif(${sfile} IN_LIST NVIM_HEADERS)
+ set(gf_basename "${r}.h.inline.generated.h")
+ set(gf_c_h "${GENERATED_INCLUDES_DIR}/${r}.h.inline.generated.h")
+ set(gf_h_h "SKIP")
+ set(gf_h_h_out "")
+ else()
+ set(gf_basename "${r}.c.generated.h")
+ set(gf_c_h "${GENERATED_DIR}/${r}.c.generated.h")
+ set(gf_h_h "${GENERATED_INCLUDES_DIR}/${r}.h.generated.h")
+ set(gf_h_h_out "${gf_h_h}")
+ endif()
+ set(gf_i "${GENERATED_DIR}/${f}.i")
if(MSVC)
set(PREPROC_OUTPUT /P /Fi${gf_i} /nologo)
else()
- set(PREPROC_OUTPUT -E -o ${gf_i})
+ set(PREPROC_OUTPUT -w -E -o ${gf_i})
endif()
set(depends "${HEADER_GENERATOR}" "${sfile}" "${LUA_GEN_DEPS}")
@@ -539,26 +562,19 @@ foreach(sfile ${NVIM_SOURCES}
list(APPEND depends update_version_stamp "${NVIM_VERSION_GIT_H}" "${NVIM_VERSION_DEF_H}")
endif()
add_custom_command(
- OUTPUT "${gf_c_h}" "${gf_h_h}"
+ OUTPUT "${gf_c_h}" ${gf_h_h_out}
COMMAND ${CMAKE_C_COMPILER} ${sfile} ${PREPROC_OUTPUT} ${gen_cflags}
- COMMAND ${LUA_GEN} "${HEADER_GENERATOR}" "${sfile}" "${gf_c_h}" "${gf_h_h}" "${gf_i}"
+ COMMAND ${LUA_GEN} "${HEADER_GENERATOR}" "${sfile}" "${gf_c_h}" "${gf_h_h}" "${gf_i}" "${gf_basename}"
DEPENDS ${depends})
list(APPEND NVIM_GENERATED_FOR_SOURCES "${gf_c_h}")
- list(APPEND NVIM_GENERATED_FOR_HEADERS "${gf_h_h}")
- if(${d} MATCHES "^api$" AND NOT ${f} MATCHES "^api/helpers.c$")
- list(APPEND API_HEADERS ${gf_h_h})
+ if (NOT ${sfile} IN_LIST NVIM_HEADERS)
+ list(APPEND NVIM_GENERATED_FOR_HEADERS "${gf_h_h}")
+ if(${d} MATCHES "^api$" AND NOT ${f} MATCHES "^api/helpers.c$")
+ list(APPEND API_HEADERS ${gf_h_h})
+ endif()
endif()
endforeach()
-add_custom_command(OUTPUT ${GENERATED_UNICODE_TABLES}
- COMMAND ${LUA_PRG} ${UNICODE_TABLES_GENERATOR}
- ${UNICODE_DIR}
- ${GENERATED_UNICODE_TABLES}
- DEPENDS
- ${UNICODE_TABLES_GENERATOR}
- ${UNICODE_FILES}
-)
-
set(NVIM_VERSION_LUA ${PROJECT_BINARY_DIR}/nvim_version.lua)
configure_file(${GENERATOR_DIR}/nvim_version.lua.in ${NVIM_VERSION_LUA})
@@ -650,7 +666,6 @@ list(APPEND NVIM_GENERATED_FOR_SOURCES
"${GENERATED_EVENTS_NAMES_MAP}"
"${GENERATED_OPTIONS}"
"${GENERATED_OPTIONS_MAP}"
- "${GENERATED_UNICODE_TABLES}"
"${VIM_MODULE_FILE}"
"${PROJECT_BINARY_DIR}/cmake.config/auto/pathdef.h"
)
@@ -706,6 +721,12 @@ target_sources(main_lib INTERFACE
${EXTERNAL_SOURCES}
${EXTERNAL_HEADERS})
+if(WIN32)
+ # add windows resource file pointing to the neovim icon
+ # this makes the icon appear for the neovim exe and associated filetypes
+ target_sources(nvim_bin PRIVATE ${NVIM_RUNTIME_DIR}/windows_icon.rc)
+endif()
+
target_sources(nlua0 PUBLIC ${NLUA0_SOURCES})
target_link_libraries(nvim_bin PRIVATE main_lib PUBLIC libuv)
@@ -808,19 +829,19 @@ find_program(CLANG_TIDY_PRG clang-tidy)
set(EXCLUDE_CLANG_TIDY typval_encode.c.h ui_events.in.h)
if(WIN32)
list(APPEND EXCLUDE_CLANG_TIDY
- os/pty_process_unix.h
+ os/pty_proc_unix.h
os/unix_defs.h)
else()
list(APPEND EXCLUDE_CLANG_TIDY
os/win_defs.h
- os/pty_process_win.h
+ os/pty_proc_win.h
os/pty_conpty_win.h
os/os_win_console.h)
endif()
add_glob_target(
TARGET lintc-clang-tidy
COMMAND ${CLANG_TIDY_PRG}
- FILES ${NVIM_SOURCES} ${NVIM_HEADERS}
+ FILES ${LINT_NVIM_SOURCES}
FLAGS --quiet
EXCLUDE ${EXCLUDE_CLANG_TIDY})
@@ -833,7 +854,7 @@ endif()
add_glob_target(
TARGET clang-analyzer
COMMAND ${CLANG_TIDY_PRG}
- FILES ${NVIM_SOURCES} ${NVIM_HEADERS}
+ FILES ${LINT_NVIM_SOURCES}
FLAGS --quiet
--checks='
-*,
@@ -841,8 +862,11 @@ add_glob_target(
-clang-analyzer-core.NullDereference,
-clang-analyzer-core.UndefinedBinaryOperatorResult,
-clang-analyzer-core.uninitialized.Assign,
+ -clang-analyzer-optin.core.EnumCastOutOfRange,
-clang-analyzer-optin.performance.Padding,
-clang-analyzer-security.insecureAPI.strcpy,
+ -clang-analyzer-unix.StdCLibraryFunctions,
+ -clang-analyzer-unix.Stream,
${CLANG_ANALYZER_IGNORE}
'
EXCLUDE ${EXCLUDE_CLANG_TIDY})
@@ -876,13 +900,13 @@ add_glob_target(
TARGET lintc-uncrustify
COMMAND ${UNCRUSTIFY_PRG}
FLAGS -c ${UNCRUSTIFY_CONFIG} -q --check
- FILES ${LINT_NVIM_SOURCES})
+ FILES ${NVIM_SOURCES} ${NVIM_HEADERS})
add_glob_target(
TARGET formatc
COMMAND ${UNCRUSTIFY_PRG}
FLAGS -c ${UNCRUSTIFY_CONFIG} --replace --no-backup
- FILES ${LINT_NVIM_SOURCES})
+ FILES ${NVIM_SOURCES} ${NVIM_HEADERS})
add_dependencies(lintc-uncrustify uncrustify_update_config)
add_dependencies(formatc uncrustify_update_config)
diff --git a/src/nvim/api/autocmd.c b/src/nvim/api/autocmd.c
index ca8367b7ce..22932fd1a2 100644
--- a/src/nvim/api/autocmd.c
+++ b/src/nvim/api/autocmd.c
@@ -67,7 +67,7 @@ static int64_t next_autocmd_id = 1;
/// NOTE: When multiple patterns or events are provided, it will find all the autocommands that
/// match any combination of them.
///
-/// @param opts Dictionary with at least one of the following:
+/// @param opts Dict with at least one of the following:
/// - group (string|integer): the autocommand group name or id to match against.
/// - event (string|array): event or events to match against |autocmd-events|.
/// - pattern (string|array): pattern or patterns to match against |autocmd-pattern|.
@@ -270,7 +270,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err)
}
}
- Dictionary autocmd_info = arena_dict(arena, 11);
+ Dict autocmd_info = arena_dict(arena, 11);
if (ap->group != AUGROUP_DEFAULT) {
PUT_C(autocmd_info, "group", INTEGER_OBJ(ap->group));
@@ -334,7 +334,7 @@ Array nvim_get_autocmds(Dict(get_autocmds) *opts, Arena *arena, Error *err)
// PUT_C(autocmd_info, "sid", INTEGER_OBJ(ac->script_ctx.sc_sid));
// PUT_C(autocmd_info, "lnum", INTEGER_OBJ(ac->script_ctx.sc_lnum));
- kvi_push(autocmd_list, DICTIONARY_OBJ(autocmd_info));
+ kvi_push(autocmd_list, DICT_OBJ(autocmd_info));
}
}
@@ -621,7 +621,7 @@ void nvim_clear_autocmds(Dict(clear_autocmds) *opts, Arena *arena, Error *err)
/// ```
///
/// @param name String: The name of the group
-/// @param opts Dictionary Parameters
+/// @param opts Dict Parameters
/// - clear (bool) optional: defaults to true. Clear existing
/// commands if the group already exists |autocmd-groups|.
/// @return Integer id of the created group.
@@ -686,7 +686,7 @@ void nvim_del_augroup_by_name(String name, Error *err)
/// Execute all autocommands for {event} that match the corresponding
/// {opts} |autocmd-execute|.
/// @param event (String|Array) The event or events to execute
-/// @param opts Dictionary of autocommand options:
+/// @param opts Dict of autocommand options:
/// - group (string|integer) optional: the autocommand group name or
/// id to match against. |autocmd-groups|.
/// - pattern (string|array) optional: defaults to "*" |autocmd-pattern|. Cannot be used
diff --git a/src/nvim/api/buffer.c b/src/nvim/api/buffer.c
index 7e64808709..9480292d9a 100644
--- a/src/nvim/api/buffer.c
+++ b/src/nvim/api/buffer.c
@@ -462,12 +462,8 @@ end:
/// = row` and `start_col = end_col = col`. To delete the text in a range, use
/// `replacement = {}`.
///
-/// Prefer |nvim_buf_set_lines()| if you are only adding or deleting entire lines.
-///
-/// Prefer |nvim_put()| if you want to insert text at the cursor position.
-///
-/// @see |nvim_buf_set_lines()|
-/// @see |nvim_put()|
+/// @note Prefer |nvim_buf_set_lines()| (for performance) to add or delete entire lines.
+/// @note Prefer |nvim_paste()| or |nvim_put()| to insert (instead of replace) text at cursor.
///
/// @param channel_id
/// @param buffer Buffer handle, or 0 for current buffer
@@ -866,7 +862,7 @@ Integer nvim_buf_get_changedtick(Buffer buffer, Error *err)
/// @param[out] err Error details, if any
/// @returns Array of |maparg()|-like dictionaries describing mappings.
/// The "buffer" key holds the associated buffer handle.
-ArrayOf(Dictionary) nvim_buf_get_keymap(Buffer buffer, String mode, Arena *arena, Error *err)
+ArrayOf(Dict) nvim_buf_get_keymap(Buffer buffer, String mode, Arena *arena, Error *err)
FUNC_API_SINCE(3)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
@@ -1183,12 +1179,12 @@ ArrayOf(Integer, 2) nvim_buf_get_mark(Buffer buffer, String name, Arena *arena,
return rv;
}
-/// call a function with buffer as temporary current buffer
+/// Call a function with buffer as temporary current buffer.
///
/// This temporarily switches current buffer to "buffer".
-/// If the current window already shows "buffer", the window is not switched
+/// 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.
+/// buffer, then one of these windows will be set as current window temporarily.
/// Otherwise a temporary scratch window (called the "autocmd window" for
/// historical reasons) will be used.
///
@@ -1221,14 +1217,14 @@ Object nvim_buf_call(Buffer buffer, LuaRef fun, Error *err)
}
/// @nodoc
-Dictionary nvim__buf_stats(Buffer buffer, Arena *arena, Error *err)
+Dict nvim__buf_stats(Buffer buffer, Arena *arena, Error *err)
{
buf_T *buf = find_buffer_by_handle(buffer, err);
if (!buf) {
- return (Dictionary)ARRAY_DICT_INIT;
+ return (Dict)ARRAY_DICT_INIT;
}
- Dictionary rv = arena_dict(arena, 7);
+ Dict rv = arena_dict(arena, 7);
// Number of times the cached line was flushed.
// This should generally not increase while editing the same
// line in the same mode.
@@ -1375,7 +1371,7 @@ static inline void init_line_array(lua_State *lstate, Array *a, size_t size, Are
/// @param s String to push
/// @param len Size of string
/// @param idx 0-based index to place s (only used for Lua)
-/// @param replace_nl Replace newlines ('\n') with null ('\0')
+/// @param replace_nl Replace newlines ('\n') with null (NUL)
static void push_linestr(lua_State *lstate, Array *a, const char *s, size_t len, int idx,
bool replace_nl, Arena *arena)
{
@@ -1384,7 +1380,7 @@ static void push_linestr(lua_State *lstate, Array *a, const char *s, size_t len,
if (s && replace_nl && strchr(s, '\n')) {
// TODO(bfredl): could manage scratch space in the arena, for the NUL case
char *tmp = xmemdupz(s, len);
- strchrsub(tmp, '\n', '\0');
+ strchrsub(tmp, '\n', NUL);
lua_pushlstring(lstate, tmp, len);
xfree(tmp);
} else {
@@ -1397,7 +1393,7 @@ static void push_linestr(lua_State *lstate, Array *a, const char *s, size_t len,
str = CBUF_TO_ARENA_STR(arena, s, len);
if (replace_nl) {
// Vim represents NULs as NLs, but this may confuse clients.
- strchrsub(str.data, '\n', '\0');
+ strchrsub(str.data, '\n', NUL);
}
}
diff --git a/src/nvim/api/command.c b/src/nvim/api/command.c
index 779e216c74..ab57d5c009 100644
--- a/src/nvim/api/command.c
+++ b/src/nvim/api/command.c
@@ -46,7 +46,7 @@
/// @param str Command line string to parse. Cannot contain "\n".
/// @param opts Optional parameters. Reserved for future use.
/// @param[out] err Error details, if any.
-/// @return Dictionary containing command information, with these keys:
+/// @return Dict containing command information, with these keys:
/// - cmd: (string) Command name.
/// - range: (array) (optional) Command range ([<line1>] [<line2>]).
/// Omitted if command doesn't accept a range.
@@ -63,13 +63,13 @@
/// - nargs: (string) Value of |:command-nargs|.
/// - nextcmd: (string) Next command if there are multiple commands separated by a |:bar|.
/// Empty if there isn't a next command.
-/// - magic: (dictionary) Which characters have special meaning in the command arguments.
+/// - magic: (dict) Which characters have special meaning in the command arguments.
/// - file: (boolean) The command expands filenames. Which means characters such as "%",
/// "#" and wildcards are expanded.
/// - bar: (boolean) The "|" character is treated as a command separator and the double
/// quote character (") is treated as the start of a comment.
-/// - mods: (dictionary) |:command-modifiers|.
-/// - filter: (dictionary) |:filter|.
+/// - mods: (dict) |:command-modifiers|.
+/// - filter: (dict) |:filter|.
/// - pattern: (string) Filter pattern. Empty string if there is no filter.
/// - force: (boolean) Whether filter is inverted or not.
/// - silent: (boolean) |:silent|.
@@ -193,7 +193,7 @@ Dict(cmd) nvim_parse_cmd(String str, Dict(empty) *opts, Arena *arena, Error *err
} else {
nargs[0] = '0';
}
- nargs[1] = '\0';
+ nargs[1] = NUL;
PUT_KEY(result, cmd, nargs, CSTR_TO_ARENA_OBJ(arena, nargs));
char *addr;
@@ -230,12 +230,12 @@ Dict(cmd) nvim_parse_cmd(String str, Dict(empty) *opts, Arena *arena, Error *err
PUT_KEY(result, cmd, nextcmd, CSTR_AS_OBJ(ea.nextcmd));
// TODO(bfredl): nested keydict would be nice..
- Dictionary mods = arena_dict(arena, 20);
+ Dict mods = arena_dict(arena, 20);
- Dictionary filter = arena_dict(arena, 2);
+ Dict filter = arena_dict(arena, 2);
PUT_C(filter, "pattern", CSTR_TO_ARENA_OBJ(arena, cmdinfo.cmdmod.cmod_filter_pat));
PUT_C(filter, "force", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_filter_force));
- PUT_C(mods, "filter", DICTIONARY_OBJ(filter));
+ PUT_C(mods, "filter", DICT_OBJ(filter));
PUT_C(mods, "silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_SILENT));
PUT_C(mods, "emsg_silent", BOOLEAN_OBJ(cmdinfo.cmdmod.cmod_flags & CMOD_ERRSILENT));
@@ -272,7 +272,7 @@ Dict(cmd) nvim_parse_cmd(String str, Dict(empty) *opts, Arena *arena, Error *err
PUT_KEY(result, cmd, mods, mods);
- Dictionary magic = arena_dict(arena, 2);
+ Dict magic = arena_dict(arena, 2);
PUT_C(magic, "file", BOOLEAN_OBJ(cmdinfo.magic.file));
PUT_C(magic, "bar", BOOLEAN_OBJ(cmdinfo.magic.bar));
PUT_KEY(result, cmd, magic, magic);
@@ -284,7 +284,7 @@ end:
/// Executes an Ex command.
///
-/// Unlike |nvim_command()| this command takes a structured Dictionary instead of a String. This
+/// Unlike |nvim_command()| this command takes a structured Dict instead of a String. This
/// allows for easier construction and manipulation of an Ex command. This also allows for things
/// such as having spaces inside a command argument, expanding filenames in a command that otherwise
/// doesn't expand filenames, etc. Command arguments may also be Number, Boolean or String.
@@ -298,7 +298,7 @@ end:
/// @see |nvim_exec2()|
/// @see |nvim_command()|
///
-/// @param cmd Command to execute. Must be a Dictionary that can contain the same values as
+/// @param cmd Command to execute. Must be a Dict that can contain the same values as
/// the return value of |nvim_parse_cmd()| except "addr", "nargs" and "nextcmd"
/// which are ignored if provided. All values except for "cmd" are optional.
/// @param opts Optional parameters.
@@ -391,7 +391,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Arena
case kObjectTypeBoolean:
data_str = arena_alloc(arena, 2, false);
data_str[0] = elem.data.boolean ? '1' : '0';
- data_str[1] = '\0';
+ data_str[1] = NUL;
ADD_C(args, CSTR_AS_OBJ(data_str));
break;
case kObjectTypeBuffer:
@@ -515,7 +515,7 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Arena
if (HAS_KEY(cmd, cmd, magic)) {
Dict(cmd_magic) magic[1] = KEYDICT_INIT;
- if (!api_dict_to_keydict(magic, KeyDict_cmd_magic_get_field, cmd->magic, err)) {
+ if (!api_dict_to_keydict(magic, DictHash(cmd_magic), cmd->magic, err)) {
goto end;
}
@@ -533,14 +533,14 @@ String nvim_cmd(uint64_t channel_id, Dict(cmd) *cmd, Dict(cmd_opts) *opts, Arena
if (HAS_KEY(cmd, cmd, mods)) {
Dict(cmd_mods) mods[1] = KEYDICT_INIT;
- if (!api_dict_to_keydict(mods, KeyDict_cmd_mods_get_field, cmd->mods, err)) {
+ if (!api_dict_to_keydict(mods, DictHash(cmd_mods), cmd->mods, err)) {
goto end;
}
if (HAS_KEY(mods, cmd_mods, filter)) {
Dict(cmd_mods_filter) filter[1] = KEYDICT_INIT;
- if (!api_dict_to_keydict(&filter, KeyDict_cmd_mods_filter_get_field,
+ if (!api_dict_to_keydict(&filter, DictHash(cmd_mods_filter),
mods->filter, err)) {
goto end;
}
@@ -1166,7 +1166,7 @@ err:
/// @param[out] err Error details, if any.
///
/// @returns Map of maps describing commands.
-Dictionary nvim_get_commands(Dict(get_commands) *opts, Arena *arena, Error *err)
+Dict nvim_get_commands(Dict(get_commands) *opts, Arena *arena, Error *err)
FUNC_API_SINCE(4)
{
return nvim_buf_get_commands(-1, opts, arena, err);
@@ -1179,25 +1179,25 @@ Dictionary nvim_get_commands(Dict(get_commands) *opts, Arena *arena, Error *err)
/// @param[out] err Error details, if any.
///
/// @returns Map of maps describing commands.
-Dictionary nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Arena *arena, Error *err)
+Dict nvim_buf_get_commands(Buffer buffer, Dict(get_commands) *opts, Arena *arena, Error *err)
FUNC_API_SINCE(4)
{
bool global = (buffer == -1);
if (ERROR_SET(err)) {
- return (Dictionary)ARRAY_DICT_INIT;
+ return (Dict)ARRAY_DICT_INIT;
}
if (global) {
if (opts->builtin) {
api_set_error(err, kErrorTypeValidation, "builtin=true not implemented");
- return (Dictionary)ARRAY_DICT_INIT;
+ return (Dict)ARRAY_DICT_INIT;
}
return commands_array(NULL, arena);
}
buf_T *buf = find_buffer_by_handle(buffer, err);
if (opts->builtin || !buf) {
- return (Dictionary)ARRAY_DICT_INIT;
+ return (Dict)ARRAY_DICT_INIT;
}
return commands_array(buf, arena);
}
diff --git a/src/nvim/api/deprecated.c b/src/nvim/api/deprecated.c
index af3bfe2c03..6376011106 100644
--- a/src/nvim/api/deprecated.c
+++ b/src/nvim/api/deprecated.c
@@ -170,7 +170,7 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A
DecorInline decor = { .ext = true, .data.ext.vt = vt, .data.ext.sh_idx = DECOR_ID_INVALID };
extmark_set(buf, ns_id, NULL, (int)line, 0, -1, -1, decor, 0, true,
- false, false, false, false, NULL);
+ false, false, false, NULL);
return src_id;
}
@@ -183,11 +183,11 @@ Integer nvim_buf_set_virtual_text(Buffer buffer, Integer src_id, Integer line, A
/// @param[out] err Error details, if any
/// @return Highlight definition map
/// @see nvim_get_hl_by_name
-Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Arena *arena, Error *err)
+Dict nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Arena *arena, Error *err)
FUNC_API_SINCE(3)
FUNC_API_DEPRECATED_SINCE(9)
{
- Dictionary dic = ARRAY_DICT_INIT;
+ Dict dic = ARRAY_DICT_INIT;
VALIDATE_INT((syn_get_final_id((int)hl_id) != 0), "highlight id", hl_id, {
return dic;
});
@@ -204,11 +204,11 @@ Dictionary nvim_get_hl_by_id(Integer hl_id, Boolean rgb, Arena *arena, Error *er
/// @param[out] err Error details, if any
/// @return Highlight definition map
/// @see nvim_get_hl_by_id
-Dictionary nvim_get_hl_by_name(String name, Boolean rgb, Arena *arena, Error *err)
+Dict nvim_get_hl_by_name(String name, Boolean rgb, Arena *arena, Error *err)
FUNC_API_SINCE(3)
FUNC_API_DEPRECATED_SINCE(9)
{
- Dictionary result = ARRAY_DICT_INIT;
+ Dict result = ARRAY_DICT_INIT;
int id = syn_name2id(name.data);
VALIDATE_S((id != 0), "highlight name", name.data, {
@@ -515,7 +515,7 @@ static int64_t convert_index(int64_t index)
/// @param name Option name
/// @param[out] err Error details, if any
/// @return Option Information
-Dictionary nvim_get_option_info(String name, Arena *arena, Error *err)
+Dict nvim_get_option_info(String name, Arena *arena, Error *err)
FUNC_API_SINCE(7)
FUNC_API_DEPRECATED_SINCE(11)
{
diff --git a/src/nvim/api/extmark.c b/src/nvim/api/extmark.c
index 85cce45560..7786c30624 100644
--- a/src/nvim/api/extmark.c
+++ b/src/nvim/api/extmark.c
@@ -18,6 +18,7 @@
#include "nvim/decoration_provider.h"
#include "nvim/drawscreen.h"
#include "nvim/extmark.h"
+#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/highlight_group.h"
#include "nvim/map_defs.h"
@@ -41,6 +42,7 @@ void api_extmark_free_all_mem(void)
xfree(name.data);
})
map_destroy(String, &namespace_ids);
+ set_destroy(uint32_t, &namespace_localscope);
}
/// Creates a new namespace or gets an existing one. [namespace]()
@@ -72,10 +74,10 @@ Integer nvim_create_namespace(String name)
/// Gets existing, non-anonymous |namespace|s.
///
/// @return dict that maps from names to namespace ids.
-Dictionary nvim_get_namespaces(Arena *arena)
+Dict nvim_get_namespaces(Arena *arena)
FUNC_API_SINCE(5)
{
- Dictionary retval = arena_dict(arena, map_size(&namespace_ids));
+ Dict retval = arena_dict(arena, map_size(&namespace_ids));
String name;
handle_T id;
@@ -156,7 +158,7 @@ static Array extmark_to_array(MTPair extmark, bool id, bool add_dict, bool hl_na
if (add_dict) {
// TODO(bfredl): coding the size like this is a bit fragile.
// We want ArrayOf(Dict(set_extmark)) as the return type..
- Dictionary dict = arena_dict(arena, ARRAY_SIZE(set_extmark_table));
+ Dict dict = arena_dict(arena, ARRAY_SIZE(set_extmark_table));
PUT_C(dict, "ns_id", INTEGER_OBJ((Integer)start.ns));
@@ -179,13 +181,9 @@ static Array extmark_to_array(MTPair extmark, bool id, bool add_dict, bool hl_na
PUT_C(dict, "invalid", BOOLEAN_OBJ(true));
}
- if (mt_scoped(start)) {
- PUT_C(dict, "scoped", BOOLEAN_OBJ(true));
- }
-
decor_to_dict_legacy(&dict, mt_decor(start), hl_name, arena);
- ADD_C(rv, DICTIONARY_OBJ(dict));
+ ADD_C(rv, DICT_OBJ(dict));
}
return rv;
@@ -489,8 +487,6 @@ Array nvim_buf_get_extmarks(Buffer buffer, Integer ns_id, Object start, Object e
/// used together with virt_text.
/// - url: A URL to associate with this extmark. In the TUI, the OSC 8 control
/// sequence is used to generate a clickable hyperlink to this URL.
-/// - scoped: boolean (EXPERIMENTAL) enables "scoping" for the extmark. See
-/// |nvim__win_add_ns()|
///
/// @param[out] err Error details, if any
/// @return Id of the created/updated extmark
@@ -575,7 +571,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
String c = opts->conceal;
if (c.size > 0) {
int ch;
- hl.conceal_char = utfc_ptr2schar_len(c.data, (int)c.size, &ch);
+ hl.conceal_char = utfc_ptr2schar(c.data, &ch);
if (!hl.conceal_char || !vim_isprintc(ch)) {
api_set_error(err, kErrorTypeValidation, "conceal char has to be printable");
goto error;
@@ -691,6 +687,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
if (HAS_KEY(opts, set_extmark, url)) {
url = string_to_cstr(opts->url);
+ has_hl = true;
}
if (opts->ui_watched) {
@@ -749,11 +746,6 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
}
if (opts->ephemeral && decor_state.win && decor_state.win->w_buffer == buf) {
- if (opts->scoped) {
- api_set_error(err, kErrorTypeException, "not yet implemented");
- goto error;
- }
-
int r = (int)line;
int c = (int)col;
if (line2 == -1) {
@@ -767,13 +759,9 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
if (kv_size(virt_lines.data.virt_lines)) {
decor_range_add_virt(&decor_state, r, c, line2, col2, decor_put_vt(virt_lines, NULL), true);
}
- if (url != NULL) {
- DecorSignHighlight sh = DECOR_SIGN_HIGHLIGHT_INIT;
- sh.url = url;
- decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, 0, 0);
- }
if (has_hl) {
DecorSignHighlight sh = decor_sh_from_inline(hl);
+ sh.url = url;
decor_range_add_sh(&decor_state, r, c, line2, col2, &sh, true, (uint32_t)ns_id, id);
}
} else {
@@ -797,12 +785,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
}
uint32_t decor_indexed = DECOR_ID_INVALID;
- if (url != NULL) {
- DecorSignHighlight sh = DECOR_SIGN_HIGHLIGHT_INIT;
- sh.url = url;
- sh.next = decor_indexed;
- decor_indexed = decor_put_sh(sh);
- }
+
if (sign.flags & kSHIsSign) {
sign.next = decor_indexed;
decor_indexed = decor_put_sh(sign);
@@ -815,9 +798,11 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
}
DecorInline decor = DECOR_INLINE_INIT;
- if (decor_alloc || decor_indexed != DECOR_ID_INVALID || schar_high(hl.conceal_char)) {
+ if (decor_alloc || decor_indexed != DECOR_ID_INVALID || url != NULL
+ || schar_high(hl.conceal_char)) {
if (has_hl) {
DecorSignHighlight sh = decor_sh_from_inline(hl);
+ sh.url = url;
sh.next = decor_indexed;
decor_indexed = decor_put_sh(sh);
}
@@ -834,7 +819,7 @@ Integer nvim_buf_set_extmark(Buffer buffer, Integer ns_id, Integer line, Integer
extmark_set(buf, (uint32_t)ns_id, &id, (int)line, (colnr_T)col, line2, col2,
decor, decor_flags, right_gravity, opts->end_right_gravity,
!GET_BOOL_OR_TRUE(opts, set_extmark, undo_restore),
- opts->invalidate, opts->scoped, err);
+ opts->invalidate, err);
if (ERROR_SET(err)) {
decor_free(decor);
return 0;
@@ -960,7 +945,7 @@ Integer nvim_buf_add_highlight(Buffer buffer, Integer ns_id, String hl_group, In
decor.data.hl.hl_id = hl_id;
extmark_set(buf, ns, NULL, (int)line, (colnr_T)col_start, end_line, (colnr_T)col_end,
- decor, MT_FLAG_DECOR_HL, true, false, false, false, false, NULL);
+ decor, MT_FLAG_DECOR_HL, true, false, false, false, NULL);
return ns_id;
}
@@ -1011,7 +996,7 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// 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.
-/// Similarly, return `false` in `on_win` will skip the `on_lines` calls
+/// Similarly, return `false` in `on_win` will skip the `on_line` 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`
@@ -1020,10 +1005,10 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// Note: doing anything other than setting extmarks is considered experimental.
/// Doing things like changing options are not explicitly 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
+/// Doing `vim.rpcnotify` should be OK, but `vim.rpcrequest` is quite dubious
/// for the moment.
///
-/// Note: It is not allowed to remove or update extmarks in 'on_line' callbacks.
+/// Note: It is not allowed to remove or update extmarks in `on_line` callbacks.
///
/// @param ns_id Namespace id from |nvim_create_namespace()|
/// @param opts Table of callbacks:
@@ -1038,7 +1023,7 @@ void nvim_buf_clear_namespace(Buffer buffer, Integer ns_id, Integer line_start,
/// ```
/// - on_win: called when starting to redraw a specific window.
/// ```
-/// ["win", winid, bufnr, topline, botline]
+/// ["win", winid, bufnr, toprow, botrow]
/// ```
/// - on_line: called for each buffer line being redrawn.
/// (The interaction with fold lines is subject to change)
@@ -1217,77 +1202,119 @@ String nvim__buf_debug_extmarks(Buffer buffer, Boolean keys, Boolean dot, Error
/// EXPERIMENTAL: this API will change in the future.
///
-/// Scopes a namespace to the a window, so extmarks in the namespace will be active only in the
-/// given window.
+/// Set some properties for namespace
///
-/// @param window Window handle, or 0 for current window
/// @param ns_id Namespace
-/// @return true if the namespace was added, else false
-Boolean nvim__win_add_ns(Window window, Integer ns_id, Error *err)
+/// @param opts Optional parameters to set:
+/// - wins: a list of windows to be scoped in
+///
+void nvim__ns_set(Integer ns_id, Dict(ns_opts) *opts, Error *err)
{
- win_T *win = find_window_by_handle(window, err);
- if (!win) {
- return false;
- }
-
VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
- return false;
+ return;
});
- set_put(uint32_t, &win->w_ns_set, (uint32_t)ns_id);
+ bool set_scoped = true;
- if (map_has(uint32_t, win->w_buffer->b_extmark_ns, (uint32_t)ns_id)) {
- changed_window_setting(win);
- }
+ if (HAS_KEY(opts, ns_opts, wins)) {
+ if (opts->wins.size == 0) {
+ set_scoped = false;
+ }
- return true;
-}
+ Set(ptr_t) windows = SET_INIT;
+ for (size_t i = 0; i < opts->wins.size; i++) {
+ Integer win = opts->wins.items[i].data.integer;
-/// EXPERIMENTAL: this API will change in the future.
-///
-/// Gets the namespace scopes for a given window.
-///
-/// @param window Window handle, or 0 for current window
-/// @return a list of namespaces ids
-ArrayOf(Integer) nvim__win_get_ns(Window window, Arena *arena, Error *err)
-{
- win_T *win = find_window_by_handle(window, err);
- if (!win) {
- return (Array)ARRAY_DICT_INIT;
+ win_T *wp = find_window_by_handle((Window)win, err);
+ if (!wp) {
+ return;
+ }
+
+ set_put(ptr_t, &windows, wp);
+ }
+
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (set_has(ptr_t, &windows, wp) && !set_has(uint32_t, &wp->w_ns_set, (uint32_t)ns_id)) {
+ set_put(uint32_t, &wp->w_ns_set, (uint32_t)ns_id);
+
+ if (map_has(uint32_t, wp->w_buffer->b_extmark_ns, (uint32_t)ns_id)) {
+ changed_window_setting(wp);
+ }
+ }
+
+ if (set_has(uint32_t, &wp->w_ns_set, (uint32_t)ns_id) && !set_has(ptr_t, &windows, wp)) {
+ set_del(uint32_t, &wp->w_ns_set, (uint32_t)ns_id);
+
+ if (map_has(uint32_t, wp->w_buffer->b_extmark_ns, (uint32_t)ns_id)) {
+ changed_window_setting(wp);
+ }
+ }
+ }
+
+ set_destroy(ptr_t, &windows);
}
- Array rv = arena_array(arena, set_size(&win->w_ns_set));
- uint32_t i;
- set_foreach(&win->w_ns_set, i, {
- ADD_C(rv, INTEGER_OBJ((Integer)(i)));
- });
+ if (set_scoped && !set_has(uint32_t, &namespace_localscope, (uint32_t)ns_id)) {
+ set_put(uint32_t, &namespace_localscope, (uint32_t)ns_id);
- return rv;
+ // When a namespace becomes scoped, any window which contains
+ // elements associated with namespace needs to be redrawn
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (map_has(uint32_t, wp->w_buffer->b_extmark_ns, (uint32_t)ns_id)) {
+ changed_window_setting(wp);
+ }
+ }
+ } else if (!set_scoped && set_has(uint32_t, &namespace_localscope, (uint32_t)ns_id)) {
+ set_del(uint32_t, &namespace_localscope, (uint32_t)ns_id);
+
+ // When a namespace becomes unscoped, any window which does not
+ // contain elements associated with namespace needs to be redrawn
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (map_has(uint32_t, wp->w_buffer->b_extmark_ns, (uint32_t)ns_id)) {
+ changed_window_setting(wp);
+ }
+ }
+ }
}
/// EXPERIMENTAL: this API will change in the future.
///
-/// Unscopes a namespace (un-binds it from the given scope).
+/// Get the properties for namespace
///
-/// @param window Window handle, or 0 for current window
-/// @param ns_id the namespace to remove
-/// @return true if the namespace was removed, else false
-Boolean nvim__win_del_ns(Window window, Integer ns_id, Error *err)
+/// @param ns_id Namespace
+/// @return Map defining the namespace properties, see |nvim__ns_set()|
+Dict(ns_opts) nvim__ns_get(Integer ns_id, Arena *arena, Error *err)
{
- win_T *win = find_window_by_handle(window, err);
- if (!win) {
- return false;
+ Dict(ns_opts) opts = KEYDICT_INIT;
+
+ Array windows = ARRAY_DICT_INIT;
+
+ PUT_KEY(opts, ns_opts, wins, windows);
+
+ VALIDATE_INT(ns_initialized((uint32_t)ns_id), "ns_id", ns_id, {
+ return opts;
+ });
+
+ if (!set_has(uint32_t, &namespace_localscope, (uint32_t)ns_id)) {
+ return opts;
}
- if (!set_has(uint32_t, &win->w_ns_set, (uint32_t)ns_id)) {
- return false;
+ size_t count = 0;
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (set_has(uint32_t, &wp->w_ns_set, (uint32_t)ns_id)) {
+ count++;
+ }
}
- set_del(uint32_t, &win->w_ns_set, (uint32_t)ns_id);
+ windows = arena_array(arena, count);
- if (map_has(uint32_t, win->w_buffer->b_extmark_ns, (uint32_t)ns_id)) {
- changed_window_setting(win);
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (set_has(uint32_t, &wp->w_ns_set, (uint32_t)ns_id)) {
+ ADD(windows, INTEGER_OBJ(wp->handle));
+ }
}
- return true;
+ PUT_KEY(opts, ns_opts, wins, windows);
+
+ return opts;
}
diff --git a/src/nvim/api/extmark.h b/src/nvim/api/extmark.h
index 124feaabfb..af2d51c95c 100644
--- a/src/nvim/api/extmark.h
+++ b/src/nvim/api/extmark.h
@@ -4,14 +4,29 @@
#include "nvim/api/keysets_defs.h" // IWYU pragma: keep
#include "nvim/api/private/defs.h" // IWYU pragma: keep
+#include "nvim/buffer_defs.h"
#include "nvim/decoration_defs.h" // IWYU pragma: keep
#include "nvim/macros_defs.h"
#include "nvim/map_defs.h"
#include "nvim/types_defs.h"
EXTERN Map(String, int) namespace_ids INIT( = MAP_INIT);
+/// Non-global namespaces. A locally-scoped namespace may be "orphaned" if all
+/// window(s) it was scoped to, are destroyed. Such orphans are tracked here to
+/// avoid being mistaken as "global scope".
+EXTERN Set(uint32_t) namespace_localscope INIT( = SET_INIT);
EXTERN handle_T next_namespace_id INIT( = 1);
+/// Returns true if the namespace is global or scoped in the given window.
+static inline bool ns_in_win(uint32_t ns_id, win_T *wp)
+{
+ if (!set_has(uint32_t, &namespace_localscope, ns_id)) {
+ return true;
+ }
+
+ return set_has(uint32_t, &wp->w_ns_set, ns_id);
+}
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "api/extmark.h.generated.h"
#endif
diff --git a/src/nvim/api/keysets_defs.h b/src/nvim/api/keysets_defs.h
index 00d8aa8428..552612dd13 100644
--- a/src/nvim/api/keysets_defs.h
+++ b/src/nvim/api/keysets_defs.h
@@ -103,7 +103,7 @@ typedef struct {
Object nargs;
Object preview;
Object range;
- Boolean register_;
+ Boolean register_ DictKey(register);
} Dict(user_command);
typedef struct {
@@ -170,7 +170,7 @@ typedef struct {
Boolean reverse;
Boolean altfont;
Boolean nocombine;
- Boolean default_;
+ Boolean default_ DictKey(default);
Object cterm;
Object foreground;
Object fg;
@@ -275,8 +275,8 @@ typedef struct {
String reg;
Boolean bang;
Array args;
- Dictionary magic;
- Dictionary mods;
+ Dict magic;
+ Dict mods;
Object nargs;
Object addr;
Object nextcmd;
@@ -293,7 +293,7 @@ typedef struct {
Boolean silent;
Boolean emsg_silent;
Boolean unsilent;
- Dictionary filter;
+ Dict filter;
Boolean sandbox;
Boolean noautocmd;
Boolean browse;
@@ -387,3 +387,46 @@ typedef struct {
Window win;
Buffer buf;
} Dict(redraw);
+
+typedef struct {
+ OptionalKeys is_set__ns_opts_;
+ Array wins;
+} Dict(ns_opts);
+
+typedef struct {
+ OptionalKeys is_set___shada_search_pat_;
+ Boolean magic DictKey(sm);
+ Boolean smartcase DictKey(sc);
+ Boolean has_line_offset DictKey(sl);
+ Boolean place_cursor_at_end DictKey(se);
+ Boolean is_last_used DictKey(su);
+ Boolean is_substitute_pattern DictKey(ss);
+ Boolean highlighted DictKey(sh);
+ Boolean search_backward DictKey(sb);
+ Integer offset DictKey(so);
+ String pat DictKey(sp);
+} Dict(_shada_search_pat);
+
+typedef struct {
+ OptionalKeys is_set___shada_mark_;
+ Integer n;
+ Integer l;
+ Integer c;
+ String f;
+} Dict(_shada_mark);
+
+typedef struct {
+ OptionalKeys is_set___shada_register_;
+ StringArray rc;
+ Boolean ru;
+ Integer rt;
+ Integer n;
+ Integer rw;
+} Dict(_shada_register);
+
+typedef struct {
+ OptionalKeys is_set___shada_buflist_item_;
+ Integer l;
+ Integer c;
+ String f;
+} Dict(_shada_buflist_item);
diff --git a/src/nvim/api/options.c b/src/nvim/api/options.c
index d9bc0ccc92..1a0edd551e 100644
--- a/src/nvim/api/options.c
+++ b/src/nvim/api/options.c
@@ -54,6 +54,10 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex *
}
if (HAS_KEY_X(opts, buf)) {
+ VALIDATE(!(HAS_KEY_X(opts, scope) && *scope == OPT_GLOBAL), "%s",
+ "cannot use both global 'scope' and 'buf'", {
+ return FAIL;
+ });
*scope = OPT_LOCAL;
*req_scope = kOptReqBuf;
*from = find_buffer_by_handle(opts->buf, err);
@@ -68,11 +72,6 @@ static int validate_option_value_args(Dict(option) *opts, char *name, OptIndex *
return FAIL;
});
- VALIDATE((!HAS_KEY_X(opts, scope) || !HAS_KEY_X(opts, buf)), "%s",
- "cannot use both 'scope' and 'buf'", {
- return FAIL;
- });
-
VALIDATE((!HAS_KEY_X(opts, win) || !HAS_KEY_X(opts, buf)),
"%s", "cannot use both 'buf' and 'win'", {
return FAIL;
@@ -257,13 +256,13 @@ void nvim_set_option_value(uint64_t channel_id, String name, Object value, Dict(
/// Gets the option information for all options.
///
-/// The dictionary has the full option names as keys and option metadata
-/// dictionaries as detailed at |nvim_get_option_info2()|.
+/// The dict has the full option names as keys and option metadata dicts as detailed at
+/// |nvim_get_option_info2()|.
///
/// @see |nvim_get_commands()|
///
-/// @return dictionary of all options
-Dictionary nvim_get_all_options_info(Arena *arena, Error *err)
+/// @return dict of all options
+Dict nvim_get_all_options_info(Arena *arena, Error *err)
FUNC_API_SINCE(7)
{
return get_all_vimoptions(arena);
@@ -271,7 +270,7 @@ Dictionary nvim_get_all_options_info(Arena *arena, Error *err)
/// Gets the option information for one option from arbitrary buffer or window
///
-/// Resulting dictionary has keys:
+/// Resulting dict has keys:
/// - name: Name of the option (like 'filetype')
/// - shortname: Shortened name of the option (like 'ft')
/// - type: type of option ("string", "number" or "boolean")
@@ -302,7 +301,7 @@ Dictionary nvim_get_all_options_info(Arena *arena, Error *err)
/// Implies {scope} is "local".
/// @param[out] err Error details, if any
/// @return Option Information
-Dictionary nvim_get_option_info2(String name, Dict(option) *opts, Arena *arena, Error *err)
+Dict nvim_get_option_info2(String name, Dict(option) *opts, Arena *arena, Error *err)
FUNC_API_SINCE(11)
{
OptIndex opt_idx = 0;
@@ -311,7 +310,7 @@ Dictionary nvim_get_option_info2(String name, Dict(option) *opts, Arena *arena,
void *from = NULL;
if (!validate_option_value_args(opts, name.data, &opt_idx, &scope, &req_scope, &from, NULL,
err)) {
- return (Dictionary)ARRAY_DICT_INIT;
+ return (Dict)ARRAY_DICT_INIT;
}
buf_T *buf = (req_scope == kOptReqBuf) ? (buf_T *)from : curbuf;
diff --git a/src/nvim/api/private/converter.c b/src/nvim/api/private/converter.c
index a78d78c057..59e7373f68 100644
--- a/src/nvim/api/private/converter.c
+++ b/src/nvim/api/private/converter.c
@@ -7,7 +7,9 @@
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/ascii_defs.h"
#include "nvim/assert_defs.h"
+#include "nvim/eval/decode.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
@@ -28,6 +30,7 @@ typedef struct {
#endif
#define TYPVAL_ENCODE_ALLOW_SPECIALS false
+#define TYPVAL_ENCODE_CHECK_BEFORE
#define TYPVAL_ENCODE_CONV_NIL(tv) \
kvi_push(edata->stack, NIL)
@@ -91,8 +94,7 @@ static Object typval_cbuf_to_obj(EncodedData *edata, const char *data, size_t le
kvi_push(edata->stack, ARRAY_OBJ(((Array) { .capacity = 0, .size = 0 })))
#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \
- kvi_push(edata->stack, \
- DICTIONARY_OBJ(((Dictionary) { .capacity = 0, .size = 0 })))
+ kvi_push(edata->stack, DICT_OBJ(((Dict) { .capacity = 0, .size = 0 })))
static inline void typval_encode_list_start(EncodedData *const edata, const size_t len)
FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
@@ -134,7 +136,7 @@ static inline void typval_encode_list_end(EncodedData *const edata)
static inline void typval_encode_dict_start(EncodedData *const edata, const size_t len)
FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
{
- kvi_push(edata->stack, DICTIONARY_OBJ(arena_dict(edata->arena, len)));
+ kvi_push(edata->stack, DICT_OBJ(arena_dict(edata->arena, len)));
}
#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \
@@ -149,13 +151,13 @@ static inline void typval_encode_after_key(EncodedData *const edata)
{
Object key = kv_pop(edata->stack);
Object *const dict = &kv_last(edata->stack);
- assert(dict->type == kObjectTypeDictionary);
- assert(dict->data.dictionary.size < dict->data.dictionary.capacity);
+ assert(dict->type == kObjectTypeDict);
+ assert(dict->data.dict.size < dict->data.dict.capacity);
if (key.type == kObjectTypeString) {
- dict->data.dictionary.items[dict->data.dictionary.size].key
+ dict->data.dict.items[dict->data.dict.size].key
= key.data.string;
} else {
- dict->data.dictionary.items[dict->data.dictionary.size].key
+ dict->data.dict.items[dict->data.dict.size].key
= STATIC_CSTR_AS_STRING("__INVALID_KEY__");
}
}
@@ -168,9 +170,9 @@ static inline void typval_encode_between_dict_items(EncodedData *const edata)
{
Object val = kv_pop(edata->stack);
Object *const dict = &kv_last(edata->stack);
- assert(dict->type == kObjectTypeDictionary);
- assert(dict->data.dictionary.size < dict->data.dictionary.capacity);
- dict->data.dictionary.items[dict->data.dictionary.size++].value = val;
+ assert(dict->type == kObjectTypeDict);
+ assert(dict->data.dict.size < dict->data.dict.capacity);
+ dict->data.dict.items[dict->data.dict.size++].value = val;
}
#define TYPVAL_ENCODE_CONV_DICT_BETWEEN_ITEMS(tv, dict) \
@@ -182,7 +184,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata)
typval_encode_between_dict_items(edata);
#ifndef NDEBUG
const Object *const dict = &kv_last(edata->stack);
- assert(dict->data.dictionary.size == dict->data.dictionary.capacity);
+ assert(dict->data.dict.size == dict->data.dict.capacity);
#endif
}
@@ -217,6 +219,7 @@ static inline void typval_encode_dict_end(EncodedData *const edata)
#undef TYPVAL_ENCODE_CONV_LIST_START
#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START
#undef TYPVAL_ENCODE_CONV_EMPTY_DICT
+#undef TYPVAL_ENCODE_CHECK_BEFORE
#undef TYPVAL_ENCODE_CONV_NIL
#undef TYPVAL_ENCODE_CONV_BOOL
#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
@@ -300,15 +303,11 @@ void object_to_vim_take_luaref(Object *obj, typval_T *tv, bool take_luaref, Erro
tv->vval.v_float = obj->data.floating;
break;
- case kObjectTypeString:
- tv->v_type = VAR_STRING;
- if (obj->data.string.data == NULL) {
- tv->vval.v_string = NULL;
- } else {
- tv->vval.v_string = xmemdupz(obj->data.string.data,
- obj->data.string.size);
- }
+ case kObjectTypeString: {
+ String s = obj->data.string;
+ *tv = decode_string(s.data, s.size, false, false);
break;
+ }
case kObjectTypeArray: {
list_T *const list = tv_list_alloc((ptrdiff_t)obj->data.array.size);
@@ -325,11 +324,11 @@ void object_to_vim_take_luaref(Object *obj, typval_T *tv, bool take_luaref, Erro
break;
}
- case kObjectTypeDictionary: {
+ case kObjectTypeDict: {
dict_T *const dict = tv_dict_alloc();
- for (uint32_t i = 0; i < obj->data.dictionary.size; i++) {
- KeyValuePair *item = &obj->data.dictionary.items[i];
+ for (uint32_t i = 0; i < obj->data.dict.size; i++) {
+ KeyValuePair *item = &obj->data.dict.items[i];
String key = item->key;
dictitem_T *const di = tv_dict_item_alloc(key.data);
object_to_vim_take_luaref(&item->value, &di->di_tv, take_luaref, err);
diff --git a/src/nvim/api/private/defs.h b/src/nvim/api/private/defs.h
index ca088d7a55..26d5ac09a8 100644
--- a/src/nvim/api/private/defs.h
+++ b/src/nvim/api/private/defs.h
@@ -5,7 +5,6 @@
#include <string.h>
#include "klib/kvec.h"
-#include "nvim/func_attr.h"
#include "nvim/types_defs.h"
#define ARRAY_DICT_INIT KV_INITIAL_VALUE
@@ -18,8 +17,11 @@
#ifdef INCLUDE_GENERATED_DECLARATIONS
# define ArrayOf(...) Array
-# define DictionaryOf(...) Dictionary
+# define DictOf(...) Dict
# define Dict(name) KeyDict_##name
+# define DictHash(name) KeyDict_##name##_get_field
+# define DictKey(name)
+# include "api/private/defs.h.inline.generated.h"
#endif
// Basic types
@@ -47,15 +49,13 @@ typedef enum {
/// Internal call from Lua code
#define LUA_INTERNAL_CALL (VIML_INTERNAL_CALL + 1)
-static inline bool is_internal_call(uint64_t channel_id)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_CONST;
-
/// Check whether call is internal
///
/// @param[in] channel_id Channel id.
///
/// @return true if channel_id refers to internal channel.
static inline bool is_internal_call(const uint64_t channel_id)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST
{
return !!(channel_id & INTERNAL_CALL_MASK);
}
@@ -88,7 +88,9 @@ typedef struct object Object;
typedef kvec_t(Object) Array;
typedef struct key_value_pair KeyValuePair;
-typedef kvec_t(KeyValuePair) Dictionary;
+typedef kvec_t(KeyValuePair) Dict;
+
+typedef kvec_t(String) StringArray;
typedef enum {
kObjectTypeNil = 0,
@@ -97,7 +99,7 @@ typedef enum {
kObjectTypeFloat,
kObjectTypeString,
kObjectTypeArray,
- kObjectTypeDictionary,
+ kObjectTypeDict,
kObjectTypeLuaRef,
// EXT types, cannot be split or reordered, see #EXT_OBJECT_TYPE_SHIFT
kObjectTypeBuffer,
@@ -105,6 +107,10 @@ typedef enum {
kObjectTypeTabpage,
} ObjectType;
+typedef enum {
+ kUnpackTypeStringArray = -1,
+} UnpackType;
+
/// Value by which objects represented as EXT type are shifted
///
/// Subtracted when packing, added when unpacking. Used to allow moving
@@ -121,7 +127,7 @@ struct object {
Float floating;
String string;
Array array;
- Dictionary dictionary;
+ Dict dict;
LuaRef luaref;
} data;
};
@@ -142,7 +148,7 @@ typedef struct {
typedef struct {
char *str;
size_t ptr_off;
- ObjectType type; // kObjectTypeNil == untyped
+ int type; // ObjectType or UnpackType. kObjectTypeNil == untyped
int opt_index;
bool is_hlgroup;
} KeySetLink;
diff --git a/src/nvim/api/private/helpers.c b/src/nvim/api/private/helpers.c
index a17e78cc31..e1fb4ed732 100644
--- a/src/nvim/api/private/helpers.c
+++ b/src/nvim/api/private/helpers.c
@@ -1,6 +1,5 @@
#include <assert.h>
#include <limits.h>
-#include <msgpack/unpack.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
@@ -199,7 +198,7 @@ dictitem_T *dict_check_writable(dict_T *dict, String key, bool del, Error *err)
api_set_error(err, kErrorTypeException, "Key is fixed: %s", key.data);
}
} else if (dict->dv_lock) {
- api_set_error(err, kErrorTypeException, "Dictionary is locked");
+ api_set_error(err, kErrorTypeException, "Dict is locked");
} else if (key.size == 0) {
api_set_error(err, kErrorTypeValidation, "Key name is empty");
} else if (key.size > INT_MAX) {
@@ -529,29 +528,19 @@ String buf_get_text(buf_T *buf, int64_t lnum, int64_t start_col, int64_t end_col
start_col = start_col < 0 ? line_length + start_col + 1 : start_col;
end_col = end_col < 0 ? line_length + end_col + 1 : end_col;
- if (start_col >= MAXCOL || end_col >= MAXCOL) {
- api_set_error(err, kErrorTypeValidation, "Column index is too high");
- return rv;
- }
+ start_col = MIN(MAX(0, start_col), line_length);
+ end_col = MIN(MAX(0, end_col), line_length);
if (start_col > end_col) {
- api_set_error(err, kErrorTypeValidation, "start_col must be less than end_col");
- return rv;
- }
-
- if (start_col >= line_length) {
+ api_set_error(err, kErrorTypeValidation, "start_col must be less than or equal to end_col");
return rv;
}
- return cstrn_as_string(&bufstr[start_col], (size_t)(end_col - start_col));
+ return cbuf_as_string(bufstr + start_col, (size_t)(end_col - start_col));
}
void api_free_string(String value)
{
- if (!value.data) {
- return;
- }
-
xfree(value.data);
}
@@ -562,9 +551,9 @@ Array arena_array(Arena *arena, size_t max_size)
return arr;
}
-Dictionary arena_dict(Arena *arena, size_t max_size)
+Dict arena_dict(Arena *arena, size_t max_size)
{
- Dictionary dict = ARRAY_DICT_INIT;
+ Dict dict = ARRAY_DICT_INIT;
kv_fixsize_arena(arena, dict, max_size);
return dict;
}
@@ -607,8 +596,8 @@ void api_free_object(Object value)
api_free_array(value.data.array);
break;
- case kObjectTypeDictionary:
- api_free_dictionary(value.data.dictionary);
+ case kObjectTypeDict:
+ api_free_dict(value.data.dict);
break;
case kObjectTypeLuaRef:
@@ -626,7 +615,7 @@ void api_free_array(Array value)
xfree(value.items);
}
-void api_free_dictionary(Dictionary value)
+void api_free_dict(Dict value)
{
for (size_t i = 0; i < value.size; i++) {
api_free_string(value.items[i].key);
@@ -659,7 +648,7 @@ Object api_metadata(void)
Arena arena = ARENA_EMPTY;
Error err = ERROR_INIT;
metadata = unpack((char *)packed_api_metadata, sizeof(packed_api_metadata), &arena, &err);
- if (ERROR_SET(&err) || metadata.type != kObjectTypeDictionary) {
+ if (ERROR_SET(&err) || metadata.type != kObjectTypeDict) {
abort();
}
mem_for_metadata = arena_finish(&arena);
@@ -695,9 +684,9 @@ Array copy_array(Array array, Arena *arena)
return rv;
}
-Dictionary copy_dictionary(Dictionary dict, Arena *arena)
+Dict copy_dict(Dict dict, Arena *arena)
{
- Dictionary rv = arena_dict(arena, dict.size);
+ Dict rv = arena_dict(arena, dict.size);
for (size_t i = 0; i < dict.size; i++) {
KeyValuePair item = dict.items[i];
PUT_C(rv, copy_string(item.key, arena).data, copy_object(item.value, arena));
@@ -724,8 +713,8 @@ Object copy_object(Object obj, Arena *arena)
case kObjectTypeArray:
return ARRAY_OBJ(copy_array(obj.data.array, arena));
- case kObjectTypeDictionary:
- return DICTIONARY_OBJ(copy_dictionary(obj.data.dictionary, arena));
+ case kObjectTypeDict:
+ return DICT_OBJ(copy_dict(obj.data.dict, arena));
case kObjectTypeLuaRef:
return LUAREF_OBJ(api_new_luaref(obj.data.luaref));
@@ -779,7 +768,8 @@ int object_to_hl_id(Object obj, const char *what, Error *err)
String str = obj.data.string;
return str.size ? syn_check_group(str.data, str.size) : 0;
} else if (obj.type == kObjectTypeInteger) {
- return MAX((int)obj.data.integer, 0);
+ int id = (int)obj.data.integer;
+ return (1 <= id && id <= highlight_num_groups()) ? id : 0;
} else {
api_set_error(err, kErrorTypeValidation, "Invalid highlight: %s", what);
return 0;
@@ -801,7 +791,7 @@ char *api_typename(ObjectType t)
return "String";
case kObjectTypeArray:
return "Array";
- case kObjectTypeDictionary:
+ case kObjectTypeDict:
return "Dict";
case kObjectTypeLuaRef:
return "Function";
@@ -854,7 +844,7 @@ free_exit:
}
// see also nlua_pop_keydict for the lua specific implementation
-bool api_dict_to_keydict(void *retval, FieldHashfn hashy, Dictionary dict, Error *err)
+bool api_dict_to_keydict(void *retval, FieldHashfn hashy, Dict dict, Error *err)
{
for (size_t i = 0; i < dict.size; i++) {
String k = dict.items[i].key;
@@ -918,23 +908,25 @@ bool api_dict_to_keydict(void *retval, FieldHashfn hashy, Dictionary dict, Error
return false;
});
*(Array *)mem = value->data.array;
- } else if (field->type == kObjectTypeDictionary) {
- Dictionary *val = (Dictionary *)mem;
+ } else if (field->type == kObjectTypeDict) {
+ Dict *val = (Dict *)mem;
// allow empty array as empty dict for lua (directly or via lua-client RPC)
if (value->type == kObjectTypeArray && value->data.array.size == 0) {
- *val = (Dictionary)ARRAY_DICT_INIT;
- } else if (value->type == kObjectTypeDictionary) {
- *val = value->data.dictionary;
+ *val = (Dict)ARRAY_DICT_INIT;
+ } else if (value->type == kObjectTypeDict) {
+ *val = value->data.dict;
} else {
- api_err_exp(err, field->str, api_typename(field->type), api_typename(value->type));
+ api_err_exp(err, field->str, api_typename((ObjectType)field->type),
+ api_typename(value->type));
return false;
}
} else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow
|| field->type == kObjectTypeTabpage) {
- if (value->type == kObjectTypeInteger || value->type == field->type) {
+ if (value->type == kObjectTypeInteger || value->type == (ObjectType)field->type) {
*(handle_T *)mem = (handle_T)value->data.integer;
} else {
- api_err_exp(err, field->str, api_typename(field->type), api_typename(value->type));
+ api_err_exp(err, field->str, api_typename((ObjectType)field->type),
+ api_typename(value->type));
return false;
}
} else if (field->type == kObjectTypeLuaRef) {
@@ -949,9 +941,9 @@ bool api_dict_to_keydict(void *retval, FieldHashfn hashy, Dictionary dict, Error
return true;
}
-Dictionary api_keydict_to_dict(void *value, KeySetLink *table, size_t max_size, Arena *arena)
+Dict api_keydict_to_dict(void *value, KeySetLink *table, size_t max_size, Arena *arena)
{
- Dictionary rv = arena_dict(arena, max_size);
+ Dict rv = arena_dict(arena, max_size);
for (size_t i = 0; table[i].str; i++) {
KeySetLink *field = &table[i];
bool is_set = true;
@@ -979,12 +971,12 @@ Dictionary api_keydict_to_dict(void *value, KeySetLink *table, size_t max_size,
val = STRING_OBJ(*(String *)mem);
} else if (field->type == kObjectTypeArray) {
val = ARRAY_OBJ(*(Array *)mem);
- } else if (field->type == kObjectTypeDictionary) {
- val = DICTIONARY_OBJ(*(Dictionary *)mem);
+ } else if (field->type == kObjectTypeDict) {
+ val = DICT_OBJ(*(Dict *)mem);
} else if (field->type == kObjectTypeBuffer || field->type == kObjectTypeWindow
|| field->type == kObjectTypeTabpage) {
val.data.integer = *(handle_T *)mem;
- val.type = field->type;
+ val.type = (ObjectType)field->type;
} else if (field->type == kObjectTypeLuaRef) {
// do nothing
} else {
@@ -1010,8 +1002,8 @@ void api_luarefs_free_object(Object value)
api_luarefs_free_array(value.data.array);
break;
- case kObjectTypeDictionary:
- api_luarefs_free_dict(value.data.dictionary);
+ case kObjectTypeDict:
+ api_luarefs_free_dict(value.data.dict);
break;
default:
@@ -1027,8 +1019,8 @@ void api_luarefs_free_keydict(void *dict, KeySetLink *table)
api_luarefs_free_object(*(Object *)mem);
} else if (table[i].type == kObjectTypeLuaRef) {
api_free_luaref(*(LuaRef *)mem);
- } else if (table[i].type == kObjectTypeDictionary) {
- api_luarefs_free_dict(*(Dictionary *)mem);
+ } else if (table[i].type == kObjectTypeDict) {
+ api_luarefs_free_dict(*(Dict *)mem);
}
}
}
@@ -1040,7 +1032,7 @@ void api_luarefs_free_array(Array value)
}
}
-void api_luarefs_free_dict(Dictionary value)
+void api_luarefs_free_dict(Dict value)
{
for (size_t i = 0; i < value.size; i++) {
api_luarefs_free_object(value.items[i].value);
diff --git a/src/nvim/api/private/helpers.h b/src/nvim/api/private/helpers.h
index 7eda8ffaf6..57932e067e 100644
--- a/src/nvim/api/private/helpers.h
+++ b/src/nvim/api/private/helpers.h
@@ -53,9 +53,9 @@
.type = kObjectTypeArray, \
.data.array = a })
-#define DICTIONARY_OBJ(d) ((Object) { \
- .type = kObjectTypeDictionary, \
- .data.dictionary = d })
+#define DICT_OBJ(d) ((Object) { \
+ .type = kObjectTypeDict, \
+ .data.dict = d })
#define LUAREF_OBJ(r) ((Object) { \
.type = kObjectTypeLuaRef, \
@@ -90,7 +90,7 @@
name.items = name##__items; \
#define MAXSIZE_TEMP_DICT(name, maxsize) \
- Dictionary name = ARRAY_DICT_INIT; \
+ Dict name = ARRAY_DICT_INIT; \
KeyValuePair name##__items[maxsize]; \
name.capacity = maxsize; \
name.items = name##__items; \
@@ -121,7 +121,7 @@ typedef kvec_withinit_t(Object, 16) ArrayBuilder;
#define api_init_tabpage
#define api_init_object = NIL
#define api_init_array = ARRAY_DICT_INIT
-#define api_init_dictionary = ARRAY_DICT_INIT
+#define api_init_dict = ARRAY_DICT_INIT
#define KEYDICT_INIT { 0 }
diff --git a/src/nvim/api/private/validate.c b/src/nvim/api/private/validate.c
index e198c671eb..9fd7d3bfa6 100644
--- a/src/nvim/api/private/validate.c
+++ b/src/nvim/api/private/validate.c
@@ -17,7 +17,7 @@ void api_err_invalid(Error *err, const char *name, const char *val_s, int64_t va
char *has_space = strchr(name, ' ');
// No value.
- if (val_s && val_s[0] == '\0') {
+ if (val_s && val_s[0] == NUL) {
api_set_error(err, errtype, has_space ? "Invalid %s" : "Invalid '%s'", name);
return;
}
diff --git a/src/nvim/api/private/validate.h b/src/nvim/api/private/validate.h
index 2c1d1a241d..67af8adea8 100644
--- a/src/nvim/api/private/validate.h
+++ b/src/nvim/api/private/validate.h
@@ -42,7 +42,7 @@
#define VALIDATE_T(name, expected_t, actual_t, code) \
do { \
- STATIC_ASSERT(expected_t != kObjectTypeDictionary, "use VALIDATE_T_DICT"); \
+ STATIC_ASSERT(expected_t != kObjectTypeDict, "use VALIDATE_T_DICT"); \
if (expected_t != actual_t) { \
api_err_exp(err, name, api_typename(expected_t), api_typename(actual_t)); \
code; \
@@ -52,7 +52,7 @@
/// Checks that `obj_` has type `expected_t`.
#define VALIDATE_T2(obj_, expected_t, code) \
do { \
- STATIC_ASSERT(expected_t != kObjectTypeDictionary, "use VALIDATE_T_DICT"); \
+ STATIC_ASSERT(expected_t != kObjectTypeDict, "use VALIDATE_T_DICT"); \
if ((obj_).type != expected_t) { \
api_err_exp(err, STR(obj_), api_typename(expected_t), api_typename((obj_).type)); \
code; \
@@ -62,11 +62,11 @@
/// Checks that `obj_` has Dict type. Also allows empty Array in a Lua context.
#define VALIDATE_T_DICT(name, obj_, code) \
do { \
- if ((obj_).type != kObjectTypeDictionary \
+ if ((obj_).type != kObjectTypeDict \
&& !(channel_id == LUA_INTERNAL_CALL \
&& (obj_).type == kObjectTypeArray \
&& (obj_).data.array.size == 0)) { \
- api_err_exp(err, name, api_typename(kObjectTypeDictionary), api_typename((obj_).type)); \
+ api_err_exp(err, name, api_typename(kObjectTypeDict), api_typename((obj_).type)); \
code; \
} \
} while (0)
diff --git a/src/nvim/api/ui.c b/src/nvim/api/ui.c
index fdf25c75d7..b09a9ed253 100644
--- a/src/nvim/api/ui.c
+++ b/src/nvim/api/ui.c
@@ -1,6 +1,5 @@
#include <assert.h>
#include <inttypes.h>
-#include <msgpack/pack.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
@@ -94,15 +93,15 @@ void remote_ui_free_all_mem(void)
}
#endif
-/// Wait until ui has connected on stdio channel if only_stdio
-/// is true, otherwise any channel.
+/// Wait until UI has connected.
+///
+/// @param only_stdio UI is expected to connect on stdio.
void remote_ui_wait_for_attach(bool only_stdio)
{
if (only_stdio) {
Channel *channel = find_channel(CHAN_STDIO);
if (!channel) {
- // this function should only be called in --embed mode, stdio channel
- // can be assumed.
+ // `only_stdio` implies --embed mode, thus stdio channel can be assumed.
abort();
}
@@ -129,8 +128,7 @@ void remote_ui_wait_for_attach(bool only_stdio)
/// @param height Requested screen rows
/// @param options |ui-option| map
/// @param[out] err Error details, if any
-void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dictionary options,
- Error *err)
+void nvim_ui_attach(uint64_t channel_id, Integer width, Integer height, Dict options, Error *err)
FUNC_API_SINCE(1) FUNC_API_REMOTE_ONLY
{
if (map_has(uint64_t, &connected_uis, channel_id)) {
@@ -688,8 +686,8 @@ void remote_ui_hl_attr_define(RemoteUI *ui, Integer id, HlAttrs rgb_attrs, HlAtt
PUT_C(rgb, "url", CSTR_AS_OBJ(url));
}
- ADD_C(args, DICTIONARY_OBJ(rgb));
- ADD_C(args, DICTIONARY_OBJ(cterm));
+ ADD_C(args, DICT_OBJ(rgb));
+ ADD_C(args, DICT_OBJ(cterm));
if (ui->ui_ext[kUIHlState]) {
ADD_C(args, ARRAY_OBJ(info));
@@ -710,7 +708,7 @@ void remote_ui_highlight_set(RemoteUI *ui, int id)
MAXSIZE_TEMP_DICT(dict, HLATTRS_DICT_SIZE);
hlattrs2dict(&dict, NULL, syn_attr2entry(id), ui->rgb, false);
MAXSIZE_TEMP_ARRAY(args, 1);
- ADD_C(args, DICTIONARY_OBJ(dict));
+ ADD_C(args, DICT_OBJ(dict));
push_call(ui, "highlight_set", args);
}
@@ -778,16 +776,26 @@ void remote_ui_raw_line(RemoteUI *ui, Integer grid, Integer row, Integer startco
for (size_t i = 0; i < ncells; i++) {
repeat++;
if (i == ncells - 1 || attrs[i] != attrs[i + 1] || chunk[i] != chunk[i + 1]) {
- if (UI_BUF_SIZE - BUF_POS(ui) < 2 * (1 + 2 + MAX_SCHAR_SIZE + 5 + 5) + 1
+ if (
+ // Close to overflowing the redraw buffer. Finish this event, flush,
+ // and start a new "grid_line" event at the current position.
+ // For simplicity leave place for the final "clear" element as well,
+ // hence the factor of 2 in the check.
+ UI_BUF_SIZE - BUF_POS(ui) < 2 * (1 + 2 + MAX_SCHAR_SIZE + 5 + 5) + 1
+ // Also if there is a lot of packed cells, pass them off to the UI to
+ // let it start processing them.
|| ui->ncells_pending >= 500) {
- // close to overflowing the redraw buffer. finish this event,
- // flush, and start a new "grid_line" event at the current position.
- // For simplicity leave place for the final "clear" element
- // as well, hence the factor of 2 in the check.
- // Also if there is a lot of packed cells, pass them of to the UI to
- // let it start processing them
+ // If the last chunk was all spaces, add an empty clearing chunk,
+ // so it's clear that the last chunk wasn't a clearing chunk.
+ if (was_space) {
+ nelem++;
+ ui->ncells_pending += 1;
+ mpack_array(buf, 3);
+ mpack_str_small(buf, S_LEN(" "));
+ mpack_uint(buf, (uint32_t)clearattr);
+ mpack_uint(buf, 0);
+ }
mpack_w2(&lenpos, nelem);
-
// We only ever set the wrap field on the final "grid_line" event for the line.
mpack_bool(buf, false);
ui_flush_buf(ui);
@@ -838,7 +846,7 @@ void remote_ui_raw_line(RemoteUI *ui, Integer grid, Integer row, Integer startco
char sc_buf[MAX_SCHAR_SIZE];
schar_get(sc_buf, chunk[i]);
remote_ui_put(ui, sc_buf);
- if (utf_ambiguous_width(utf_ptr2char(sc_buf))) {
+ if (utf_ambiguous_width(sc_buf)) {
ui->client_col = -1; // force cursor update
}
}
@@ -911,11 +919,11 @@ static Array translate_contents(RemoteUI *ui, Array contents, Arena *arena)
Array new_item = arena_array(arena, 2);
int attr = (int)item.items[0].data.integer;
if (attr) {
- Dictionary rgb_attrs = arena_dict(arena, HLATTRS_DICT_SIZE);
+ Dict rgb_attrs = arena_dict(arena, HLATTRS_DICT_SIZE);
hlattrs2dict(&rgb_attrs, NULL, syn_attr2entry(attr), ui->rgb, false);
- ADD_C(new_item, DICTIONARY_OBJ(rgb_attrs));
+ ADD_C(new_item, DICT_OBJ(rgb_attrs));
} else {
- ADD_C(new_item, DICTIONARY_OBJ((Dictionary)ARRAY_DICT_INIT));
+ ADD_C(new_item, DICT_OBJ((Dict)ARRAY_DICT_INIT));
}
ADD_C(new_item, item.items[1]);
ADD_C(new_contents, ARRAY_OBJ(new_item));
diff --git a/src/nvim/api/vim.c b/src/nvim/api/vim.c
index fc780e1248..8c88a19147 100644
--- a/src/nvim/api/vim.c
+++ b/src/nvim/api/vim.c
@@ -28,6 +28,8 @@
#include "nvim/cursor.h"
#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
+#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -68,7 +70,7 @@
#include "nvim/optionstr.h"
#include "nvim/os/input.h"
#include "nvim/os/os_defs.h"
-#include "nvim/os/process.h"
+#include "nvim/os/proc.h"
#include "nvim/popupmenu.h"
#include "nvim/pos_defs.h"
#include "nvim/runtime.h"
@@ -115,7 +117,7 @@ Integer nvim_get_hl_id_by_name(String name)
/// @param[out] err Error details, if any.
/// @return Highlight groups as a map from group name to a highlight definition map as in |nvim_set_hl()|,
/// or only a single highlight definition map if requested by name or id.
-Dictionary nvim_get_hl(Integer ns_id, Dict(get_highlight) *opts, Arena *arena, Error *err)
+Dict nvim_get_hl(Integer ns_id, Dict(get_highlight) *opts, Arena *arena, Error *err)
FUNC_API_SINCE(11)
{
return ns_get_hl_defs((NS)ns_id, opts, arena, err);
@@ -313,7 +315,7 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
keys_esc = keys.data;
}
if (lowlevel) {
- input_enqueue_raw(cstr_as_string(keys_esc));
+ input_enqueue_raw(keys_esc, strlen(keys_esc));
} else {
ins_typebuf(keys_esc, (remap ? REMAP_YES : REMAP_NONE),
insert ? 0 : typebuf.tb_len, !typed, false);
@@ -342,9 +344,10 @@ void nvim_feedkeys(String keys, String mode, Boolean escape_ks)
}
}
-/// Queues raw user-input. Unlike |nvim_feedkeys()|, this uses a low-level
-/// input buffer and the call is non-blocking (input is processed
-/// asynchronously by the eventloop).
+/// Queues raw user-input. Unlike |nvim_feedkeys()|, this uses a low-level input buffer and the call
+/// is non-blocking (input is processed asynchronously by the eventloop).
+///
+/// To input blocks of text, |nvim_paste()| is much faster and should be preferred.
///
/// On execution error: does not fail, but updates v:errmsg.
///
@@ -524,13 +527,13 @@ Object nvim_exec_lua(String code, Array args, Arena *arena, Error *err)
/// @param log_level The log level
/// @param opts Reserved for future use.
/// @param[out] err Error details, if any
-Object nvim_notify(String msg, Integer log_level, Dictionary opts, Arena *arena, Error *err)
+Object nvim_notify(String msg, Integer log_level, Dict opts, Arena *arena, Error *err)
FUNC_API_SINCE(7)
{
MAXSIZE_TEMP_ARRAY(args, 3);
ADD_C(args, STRING_OBJ(msg));
ADD_C(args, INTEGER_OBJ(log_level));
- ADD_C(args, DICTIONARY_OBJ(opts));
+ ADD_C(args, DICT_OBJ(opts));
return NLUA_EXEC_STATIC("return vim.notify(...)", args, kRetObject, arena, err);
}
@@ -571,10 +574,10 @@ typedef struct {
Arena *arena;
} RuntimeCookie;
-/// Find files in runtime directories
+/// Finds files in runtime directories, in 'runtimepath' order.
///
/// "name" can contain wildcards. For example
-/// nvim_get_runtime_file("colors/*.vim", true) will return all color
+/// `nvim_get_runtime_file("colors/*.{vim,lua}", true)` will return all color
/// scheme files. Always use forward slashes (/) in the search pattern for
/// subdirectories regardless of platform.
///
@@ -1210,17 +1213,30 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
}
}
-/// Pastes at cursor, in any mode.
+/// Pastes at cursor (in any mode), and sets "redo" so dot (|.|) will repeat the input. UIs call
+/// this to implement "paste", but it's also intended for use by scripts to input large,
+/// dot-repeatable blocks of text (as opposed to |nvim_input()| which is subject to mappings/events
+/// and is thus much slower).
+///
+/// Invokes the |vim.paste()| handler, which handles each mode appropriately.
///
-/// Invokes the `vim.paste` handler, which handles each mode appropriately.
-/// Sets redo/undo. Faster than |nvim_input()|. Lines break at LF ("\n").
+/// Errors ('nomodifiable', `vim.paste()` failure, …) are reflected in `err` but do not affect the
+/// return value (which is strictly decided by `vim.paste()`). On error or cancel, subsequent calls
+/// are ignored ("drained") until the next paste is initiated (phase 1 or -1).
///
-/// Errors ('nomodifiable', `vim.paste()` failure, …) are reflected in `err`
-/// but do not affect the return value (which is strictly decided by
-/// `vim.paste()`). On error, subsequent calls are ignored ("drained") until
-/// the next paste is initiated (phase 1 or -1).
+/// Useful in mappings and scripts to insert multiline text. Example:
+///
+/// ```lua
+/// vim.keymap.set('n', 'x', function()
+/// vim.api.nvim_paste([[
+/// line1
+/// line2
+/// line3
+/// ]], false, -1)
+/// end, { buffer = true })
+/// ```
///
-/// @param data Multiline input. May be binary (containing NUL bytes).
+/// @param data Multiline input. Lines break at LF ("\n"). May be binary (containing NUL bytes).
/// @param crlf Also break lines at CR and CRLF.
/// @param phase -1: paste in a single call (i.e. without streaming).
/// To "stream" a paste, call `nvim_paste` sequentially with
@@ -1231,20 +1247,20 @@ void nvim_set_current_tabpage(Tabpage tabpage, Error *err)
/// @param[out] err Error details, if any
/// @return
/// - true: Client may continue pasting.
-/// - false: Client must cancel the paste.
-Boolean nvim_paste(String data, Boolean crlf, Integer phase, Arena *arena, Error *err)
+/// - false: Client should cancel the paste.
+Boolean nvim_paste(uint64_t channel_id, String data, Boolean crlf, Integer phase, Arena *arena,
+ Error *err)
FUNC_API_SINCE(6)
FUNC_API_TEXTLOCK_ALLOW_CMDWIN
{
- static bool draining = false;
- bool cancel = false;
+ static bool cancelled = false;
VALIDATE_INT((phase >= -1 && phase <= 3), "phase", phase, {
return false;
});
if (phase == -1 || phase == 1) { // Start of paste-stream.
- draining = false;
- } else if (draining) {
+ cancelled = false;
+ } else if (cancelled) {
// Skip remaining chunks. Report error only once per "stream".
goto theend;
}
@@ -1253,40 +1269,29 @@ Boolean nvim_paste(String data, Boolean crlf, Integer phase, Arena *arena, Error
ADD_C(args, ARRAY_OBJ(lines));
ADD_C(args, INTEGER_OBJ(phase));
Object rv = NLUA_EXEC_STATIC("return vim.paste(...)", args, kRetNilBool, arena, err);
- if (ERROR_SET(err)) {
- draining = true;
- goto theend;
+ // vim.paste() decides if client should cancel.
+ if (ERROR_SET(err) || (rv.type == kObjectTypeBoolean && !rv.data.boolean)) {
+ cancelled = true;
}
- if (!(State & (MODE_CMDLINE | MODE_INSERT)) && (phase == -1 || phase == 1)) {
- ResetRedobuff();
- AppendCharToRedobuff('a'); // Dot-repeat.
- }
- // vim.paste() decides if client should cancel. Errors do NOT cancel: we
- // want to drain remaining chunks (rather than divert them to main input).
- cancel = (rv.type == kObjectTypeBoolean && !rv.data.boolean);
- if (!cancel && !(State & MODE_CMDLINE)) { // Dot-repeat.
- for (size_t i = 0; i < lines.size; i++) {
- String s = lines.items[i].data.string;
- assert(s.size <= INT_MAX);
- AppendToRedobuffLit(s.data, (int)s.size);
- // readfile()-style: "\n" is indicated by presence of N+1 item.
- if (i + 1 < lines.size) {
- AppendCharToRedobuff(NL);
- }
- }
+ if (!cancelled && (phase == -1 || phase == 1)) {
+ paste_store(channel_id, kFalse, NULL_STRING, crlf);
+ }
+ if (!cancelled) {
+ paste_store(channel_id, kNone, data, crlf);
}
- if (!(State & (MODE_CMDLINE | MODE_INSERT)) && (phase == -1 || phase == 3)) {
- AppendCharToRedobuff(ESC); // Dot-repeat.
+ if (phase == 3 || phase == (cancelled ? 2 : -1)) {
+ paste_store(channel_id, kTrue, NULL_STRING, crlf);
}
theend:
- if (cancel || phase == -1 || phase == 3) { // End of paste-stream.
- draining = false;
+ ;
+ bool retval = !cancelled;
+ if (phase == -1 || phase == 3) { // End of paste-stream.
+ cancelled = false;
}
-
- return !cancel;
+ return retval;
}
-/// Puts text at cursor, in any mode.
+/// Puts text at cursor, in any mode. For dot-repeatable input, use |nvim_paste()|.
///
/// Compare |:put| and |p| which are always linewise.
///
@@ -1359,10 +1364,10 @@ Integer nvim_get_color_by_name(String name)
/// (e.g. 65535).
///
/// @return Map of color names and RGB values.
-Dictionary nvim_get_color_map(Arena *arena)
+Dict nvim_get_color_map(Arena *arena)
FUNC_API_SINCE(1)
{
- Dictionary colors = arena_dict(arena, ARRAY_SIZE(color_name_table));
+ Dict colors = arena_dict(arena, ARRAY_SIZE(color_name_table));
for (int i = 0; color_name_table[i].name != NULL; i++) {
PUT_C(colors, color_name_table[i].name, INTEGER_OBJ(color_name_table[i].color));
@@ -1378,7 +1383,7 @@ Dictionary nvim_get_color_map(Arena *arena)
/// @param[out] err Error details, if any
///
/// @return map of global |context|.
-Dictionary nvim_get_context(Dict(context) *opts, Arena *arena, Error *err)
+Dict nvim_get_context(Dict(context) *opts, Arena *arena, Error *err)
FUNC_API_SINCE(6)
{
Array types = ARRAY_DICT_INIT;
@@ -1405,7 +1410,7 @@ Dictionary nvim_get_context(Dict(context) *opts, Arena *arena, Error *err)
int_types |= kCtxFuncs;
} else {
VALIDATE_S(false, "type", s, {
- return (Dictionary)ARRAY_DICT_INIT;
+ return (Dict)ARRAY_DICT_INIT;
});
}
}
@@ -1414,7 +1419,7 @@ Dictionary nvim_get_context(Dict(context) *opts, Arena *arena, Error *err)
Context ctx = CONTEXT_INIT;
ctx_save(&ctx, int_types);
- Dictionary dict = ctx_to_dict(&ctx, arena);
+ Dict dict = ctx_to_dict(&ctx, arena);
ctx_free(&ctx);
return dict;
}
@@ -1422,7 +1427,7 @@ Dictionary nvim_get_context(Dict(context) *opts, Arena *arena, Error *err)
/// Sets the current editor state from the given |context| map.
///
/// @param dict |Context| map.
-Object nvim_load_context(Dictionary dict, Error *err)
+Object nvim_load_context(Dict dict, Error *err)
FUNC_API_SINCE(6)
{
Context ctx = CONTEXT_INIT;
@@ -1444,11 +1449,11 @@ Object nvim_load_context(Dictionary dict, Error *err)
/// Gets the current mode. |mode()|
/// "blocking" is true if Nvim is waiting for input.
///
-/// @returns Dictionary { "mode": String, "blocking": Boolean }
-Dictionary nvim_get_mode(Arena *arena)
+/// @returns Dict { "mode": String, "blocking": Boolean }
+Dict nvim_get_mode(Arena *arena)
FUNC_API_SINCE(2) FUNC_API_FAST
{
- Dictionary rv = arena_dict(arena, 2);
+ Dict rv = arena_dict(arena, 2);
char *modestr = arena_alloc(arena, MODE_MAX_LENGTH, false);
get_mode(modestr);
bool blocked = input_blocking();
@@ -1464,7 +1469,7 @@ Dictionary nvim_get_mode(Arena *arena)
/// @param mode Mode short-name ("n", "i", "v", ...)
/// @returns Array of |maparg()|-like dictionaries describing mappings.
/// The "buffer" key is always zero.
-ArrayOf(Dictionary) nvim_get_keymap(String mode, Arena *arena)
+ArrayOf(Dict) nvim_get_keymap(String mode, Arena *arena)
FUNC_API_SINCE(3)
{
return keymap_array(mode, NULL, arena);
@@ -1523,7 +1528,7 @@ void nvim_del_keymap(uint64_t channel_id, String mode, String lhs, Error *err)
}
/// Returns a 2-tuple (Array), where item 0 is the current channel id and item
-/// 1 is the |api-metadata| map (Dictionary).
+/// 1 is the |api-metadata| map (Dict).
///
/// @returns 2-tuple `[{channel-id}, {api-metadata}]`
Array nvim_get_api_info(uint64_t channel_id, Arena *arena)
@@ -1552,7 +1557,7 @@ Array nvim_get_api_info(uint64_t channel_id, Arena *arena)
///
/// @param channel_id
/// @param name Short name for the connected client
-/// @param version Dictionary describing the version, with these
+/// @param version Dict describing the version, with these
/// (optional) keys:
/// - "major" major version (defaults to 0 if not set, for no release yet)
/// - "minor" minor version
@@ -1584,14 +1589,15 @@ Array nvim_get_api_info(uint64_t channel_id, Arena *arena)
///
/// @param attributes Arbitrary string:string map of informal client properties.
/// Suggested keys:
+/// - "pid": Process id.
/// - "website": Client homepage URL (e.g. GitHub repository)
/// - "license": License description ("Apache 2", "GPLv3", "MIT", …)
/// - "logo": URI or path to image, preferably small logo or icon.
/// .png or .svg format is preferred.
///
/// @param[out] err Error details, if any
-void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version, String type,
- Dictionary methods, Dictionary attributes, Arena *arena, Error *err)
+void nvim_set_client_info(uint64_t channel_id, String name, Dict version, String type, Dict methods,
+ Dict attributes, Arena *arena, Error *err)
FUNC_API_SINCE(4) FUNC_API_REMOTE_ONLY
{
MAXSIZE_TEMP_DICT(info, 5);
@@ -1605,7 +1611,7 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version,
}
}
if (!has_major) {
- Dictionary v = arena_dict(arena, version.size + 1);
+ Dict v = arena_dict(arena, version.size + 1);
if (version.size) {
memcpy(v.items, version.items, version.size * sizeof(v.items[0]));
v.size = version.size;
@@ -1613,19 +1619,19 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version,
PUT_C(v, "major", INTEGER_OBJ(0));
version = v;
}
- PUT_C(info, "version", DICTIONARY_OBJ(version));
+ PUT_C(info, "version", DICT_OBJ(version));
PUT_C(info, "type", STRING_OBJ(type));
- PUT_C(info, "methods", DICTIONARY_OBJ(methods));
- PUT_C(info, "attributes", DICTIONARY_OBJ(attributes));
+ PUT_C(info, "methods", DICT_OBJ(methods));
+ PUT_C(info, "attributes", DICT_OBJ(attributes));
- rpc_set_client_info(channel_id, copy_dictionary(info, NULL));
+ rpc_set_client_info(channel_id, copy_dict(info, NULL));
}
/// Gets information about a channel.
///
/// @param chan channel_id, or 0 for current channel
-/// @returns Dictionary describing a channel, with these keys:
+/// @returns Channel info dict with these keys:
/// - "id" Channel id.
/// - "argv" (optional) Job arguments list.
/// - "stream" Stream underlying the channel.
@@ -1637,20 +1643,18 @@ void nvim_set_client_info(uint64_t channel_id, String name, Dictionary version,
/// - "bytes" Send and receive raw bytes.
/// - "terminal" |terminal| instance interprets ASCII sequences.
/// - "rpc" |RPC| communication on the channel is active.
-/// - "pty" (optional) Name of pseudoterminal. On a POSIX system this
-/// is a device path like "/dev/pts/1". If the name is unknown,
-/// the key will still be present if a pty is used (e.g. for
-/// conpty on Windows).
-/// - "buffer" (optional) Buffer with connected |terminal| instance.
-/// - "client" (optional) Info about the peer (client on the other end of
-/// the RPC channel), if provided by it via
-/// |nvim_set_client_info()|.
-///
-Dictionary nvim_get_chan_info(uint64_t channel_id, Integer chan, Arena *arena, Error *err)
+/// - "pty" (optional) Name of pseudoterminal. On a POSIX system this is a device path like
+/// "/dev/pts/1". If unknown, the key will still be present if a pty is used (e.g.
+/// for conpty on Windows).
+/// - "buffer" (optional) Buffer connected to |terminal| instance.
+/// - "client" (optional) Info about the peer (client on the other end of the RPC channel),
+/// which it provided via |nvim_set_client_info()|.
+///
+Dict nvim_get_chan_info(uint64_t channel_id, Integer chan, Arena *arena, Error *err)
FUNC_API_SINCE(4)
{
if (chan < 0) {
- return (Dictionary)ARRAY_DICT_INIT;
+ return (Dict)ARRAY_DICT_INIT;
}
if (chan == 0 && !is_internal_call(channel_id)) {
@@ -1747,17 +1751,17 @@ Array nvim__id_array(Array arr, Arena *arena)
return copy_array(arr, arena);
}
-/// Returns dictionary given as argument.
+/// Returns dict given as argument.
///
/// This API function is used for testing. One should not rely on its presence
/// in plugins.
///
-/// @param[in] dct Dictionary to return.
+/// @param[in] dct Dict to return.
///
/// @return its argument.
-Dictionary nvim__id_dictionary(Dictionary dct, Arena *arena)
+Dict nvim__id_dict(Dict dct, Arena *arena)
{
- return copy_dictionary(dct, arena);
+ return copy_dict(dct, arena);
}
/// Returns floating-point value given as argument.
@@ -1776,9 +1780,9 @@ Float nvim__id_float(Float flt)
/// Gets internal stats.
///
/// @return Map of various internal stats.
-Dictionary nvim__stats(Arena *arena)
+Dict nvim__stats(Arena *arena)
{
- Dictionary rv = arena_dict(arena, 6);
+ Dict rv = arena_dict(arena, 6);
PUT_C(rv, "fsync", INTEGER_OBJ(g_stats.fsync));
PUT_C(rv, "log_skip", INTEGER_OBJ(g_stats.log_skip));
PUT_C(rv, "lua_refcount", INTEGER_OBJ(nlua_get_global_ref_count()));
@@ -1855,8 +1859,8 @@ Object nvim_get_proc(Integer pid, Arena *arena, Error *err)
});
#ifdef MSWIN
- rvobj = DICTIONARY_OBJ(os_proc_info((int)pid, arena));
- if (rvobj.data.dictionary.size == 0) { // Process not found.
+ rvobj = DICT_OBJ(os_proc_info((int)pid, arena));
+ if (rvobj.data.dict.size == 0) { // Process not found.
return NIL;
}
#else
@@ -1866,7 +1870,7 @@ Object nvim_get_proc(Integer pid, Arena *arena, Error *err)
Object o = NLUA_EXEC_STATIC("return vim._os_proc_info(...)", a, kRetObject, arena, err);
if (o.type == kObjectTypeArray && o.data.array.size == 0) {
return NIL; // Process not found.
- } else if (o.type == kObjectTypeDictionary) {
+ } else if (o.type == kObjectTypeDict) {
rvobj = o;
} else if (!ERROR_SET(err)) {
api_set_error(err, kErrorTypeException,
@@ -1930,7 +1934,7 @@ Array nvim__inspect_cell(Integer grid, Integer row, Integer col, Arena *arena, E
schar_get(sc_buf, g->chars[off]);
ADD_C(ret, CSTR_AS_OBJ(sc_buf));
int attr = g->attrs[off];
- ADD_C(ret, DICTIONARY_OBJ(hl_get_attr_by_id(attr, true, arena, err)));
+ ADD_C(ret, DICT_OBJ(hl_get_attr_by_id(attr, true, arena, err)));
// will not work first time
if (!highlight_use_hlstate()) {
ADD_C(ret, ARRAY_OBJ(hl_inspect(attr, arena)));
@@ -2073,18 +2077,18 @@ Array nvim_get_mark(String name, Dict(empty) *opts, Arena *arena, Error *err)
/// - use_statuscol_lnum: (number) Evaluate statuscolumn for this line number instead of statusline.
///
/// @param[out] err Error details, if any.
-/// @return Dictionary containing statusline information, with these keys:
+/// @return Dict containing statusline information, with these keys:
/// - str: (string) Characters that will be displayed on the statusline.
/// - width: (number) Display width of the statusline.
/// - highlights: Array containing highlight information of the statusline. Only included when
/// the "highlights" key in {opts} is true. Each element of the array is a
-/// |Dictionary| with these keys:
+/// |Dict| with these keys:
/// - start: (number) Byte index (0-based) of first character that uses the highlight.
/// - group: (string) Name of highlight group.
-Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, Error *err)
+Dict nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *arena, Error *err)
FUNC_API_SINCE(8) FUNC_API_FAST
{
- Dictionary result = ARRAY_DICT_INIT;
+ Dict result = ARRAY_DICT_INIT;
int maxwidth;
schar_T fillchar = 0;
@@ -2210,18 +2214,18 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *
// If first character doesn't have a defined highlight,
// add the default highlight at the beginning of the highlight list
if (hltab->start == NULL || (hltab->start - buf) != 0) {
- Dictionary hl_info = arena_dict(arena, 2);
+ Dict hl_info = arena_dict(arena, 2);
const char *grpname = get_default_stl_hl(opts->use_tabline ? NULL : wp,
opts->use_winbar, stc_hl_id);
PUT_C(hl_info, "start", INTEGER_OBJ(0));
PUT_C(hl_info, "group", CSTR_AS_OBJ(grpname));
- ADD_C(hl_values, DICTIONARY_OBJ(hl_info));
+ ADD_C(hl_values, DICT_OBJ(hl_info));
}
for (stl_hlrec_t *sp = hltab; sp->start != NULL; sp++) {
- Dictionary hl_info = arena_dict(arena, 2);
+ Dict hl_info = arena_dict(arena, 2);
PUT_C(hl_info, "start", INTEGER_OBJ(sp->start - buf));
@@ -2235,7 +2239,7 @@ Dictionary nvim_eval_statusline(String str, Dict(eval_statusline) *opts, Arena *
grpname = arena_memdupz(arena, user_group, strlen(user_group));
}
PUT_C(hl_info, "group", CSTR_AS_OBJ(grpname));
- ADD_C(hl_values, DICTIONARY_OBJ(hl_info));
+ ADD_C(hl_values, DICT_OBJ(hl_info));
}
PUT_C(result, "highlights", ARRAY_OBJ(hl_values));
}
@@ -2261,12 +2265,12 @@ void nvim_error_event(uint64_t channel_id, Integer lvl, String data)
/// @param index Completion candidate index
/// @param opts Optional parameters.
/// - info: (string) info text.
-/// @return Dictionary containing these keys:
+/// @return Dict containing these keys:
/// - winid: (number) floating window id
/// - bufnr: (number) buffer id in floating window
-Dictionary nvim__complete_set(Integer index, Dict(complete_set) *opts, Arena *arena)
+Dict nvim__complete_set(Integer index, Dict(complete_set) *opts, Arena *arena)
{
- Dictionary rv = arena_dict(arena, 2);
+ Dict rv = arena_dict(arena, 2);
if (HAS_KEY(opts, complete_set, info)) {
win_T *wp = pum_set_info((int)index, opts->info.data);
if (wp) {
@@ -2354,8 +2358,8 @@ void nvim__redraw(Dict(redraw) *opts, Error *err)
}
}
- int count = (win != NULL) + (buf != NULL);
- VALIDATE(popcount(opts->is_set__redraw_) > count, "%s", "at least one action required", {
+ unsigned count = (win != NULL) + (buf != NULL);
+ VALIDATE(xpopcount(opts->is_set__redraw_) > count, "%s", "at least one action required", {
return;
});
@@ -2392,10 +2396,6 @@ void nvim__redraw(Dict(redraw) *opts, Error *err)
redraw_buf_range_later(rbuf, first, last);
}
- if (opts->cursor) {
- setcursor_mayforce(win ? win : curwin, true);
- }
-
bool flush = opts->flush;
if (opts->tabline) {
// Flush later in case tabline was just hidden or shown for the first time.
@@ -2422,11 +2422,22 @@ void nvim__redraw(Dict(redraw) *opts, Error *err)
}
}
- // Flush pending screen updates if "flush" or "clear" is true, or when
- // redrawing a status component may have changed the grid dimensions.
+ win_T *cwin = win ? win : curwin;
+ // Allow moving cursor to recently opened window and make sure it is drawn #28868.
+ if (opts->cursor && (!cwin->w_grid.target || !cwin->w_grid.target->valid)) {
+ flush = true;
+ }
+
+ // Redraw pending screen updates when explicitly requested or when determined
+ // that it is necessary to properly draw other requested components.
if (flush && !cmdpreview) {
update_screen();
}
+
+ if (opts->cursor) {
+ setcursor_mayforce(cwin, true);
+ }
+
ui_flush();
RedrawingDisabled = save_rd;
diff --git a/src/nvim/api/vimscript.c b/src/nvim/api/vimscript.c
index 477cbe2428..165cc93fbe 100644
--- a/src/nvim/api/vimscript.c
+++ b/src/nvim/api/vimscript.c
@@ -48,12 +48,12 @@
/// - output: (boolean, default false) Whether to capture and return
/// all (non-error, non-shell |:!|) output.
/// @param[out] err Error details (Vim error), if any
-/// @return Dictionary containing information about execution, with these keys:
+/// @return Dict containing information about execution, with these keys:
/// - output: (string|nil) Output if `opts.output` is true.
-Dictionary nvim_exec2(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error *err)
+Dict nvim_exec2(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error *err)
FUNC_API_SINCE(11) FUNC_API_RET_ALLOC
{
- Dictionary result = ARRAY_DICT_INIT;
+ Dict result = ARRAY_DICT_INIT;
String output = exec_impl(channel_id, src, opts, err);
if (ERROR_SET(err)) {
@@ -109,7 +109,7 @@ String exec_impl(uint64_t channel_id, String src, Dict(exec_opts) *opts, Error *
// redir usually (except :echon) prepends a newline.
if (s.data[0] == '\n') {
memmove(s.data, s.data + 1, s.size - 1);
- s.data[s.size - 1] = '\0';
+ s.data[s.size - 1] = NUL;
s.size = s.size - 1;
}
return s; // Caller will free the memory.
@@ -140,8 +140,7 @@ void nvim_command(String command, Error *err)
try_end(err);
}
-/// Evaluates a Vimscript |expression|.
-/// Dictionaries and Lists are recursively expanded.
+/// Evaluates a Vimscript |expression|. Dicts and Lists are recursively expanded.
///
/// On execution error: fails with Vimscript error, updates v:errmsg.
///
@@ -270,7 +269,7 @@ Object nvim_call_function(String fn, Array args, Arena *arena, Error *err)
///
/// On execution error: fails with Vimscript error, updates v:errmsg.
///
-/// @param dict Dictionary, or String evaluating to a Vimscript |self| dict
+/// @param dict Dict, or String evaluating to a Vimscript |self| dict
/// @param fn Name of the function defined on the Vimscript dict
/// @param args Function arguments packed in an Array
/// @param[out] err Error details, if any
@@ -297,12 +296,11 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Arena *arena,
// refcount of a dict. Not necessary for a RPC dict.
mustfree = true;
break;
- case kObjectTypeDictionary:
+ case kObjectTypeDict:
object_to_vim(dict, &rettv, err);
break;
default:
- api_set_error(err, kErrorTypeValidation,
- "dict argument type must be String or Dictionary");
+ api_set_error(err, kErrorTypeValidation, "dict argument type must be String or Dict");
return rv;
}
dict_T *self_dict = rettv.vval.v_dict;
@@ -311,7 +309,7 @@ Object nvim_call_dict_function(Object dict, String fn, Array args, Arena *arena,
goto end;
}
- if (fn.data && fn.size > 0 && dict.type != kObjectTypeDictionary) {
+ if (fn.data && fn.size > 0 && dict.type != kObjectTypeDict) {
dictitem_T *const di = tv_dict_find(self_dict, fn.data, (ptrdiff_t)fn.size);
if (di == NULL) {
api_set_error(err, kErrorTypeValidation, "Not found: %s", fn.data);
@@ -377,8 +375,8 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack;
/// one should highlight region [start_col, end_col)).
///
/// @return
-/// - AST: top-level dictionary with these keys:
-/// - "error": Dictionary with error, present only if parser saw some
+/// - AST: top-level dict with these keys:
+/// - "error": Dict with error, present only if parser saw some
/// error. Contains the following keys:
/// - "message": String, error message in printf format, translated.
/// Must contain exactly one "%.*s".
@@ -387,7 +385,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack;
/// that should be equal to the length of expr string.
/// ("Successfully parsed" here means "participated in AST
/// creation", not "till the first error".)
-/// - "ast": AST, either nil or a dictionary with these keys:
+/// - "ast": AST, either nil or a dict with these keys:
/// - "type": node type, one of the value names from ExprASTNodeType
/// stringified without "kExprNode" prefix.
/// - "start": a pair `[line, column]` describing where node is "started"
@@ -427,8 +425,7 @@ typedef kvec_withinit_t(ExprASTConvStackItem, 16) ExprASTConvStack;
/// - "svalue": String, value for "SingleQuotedString" and
/// "DoubleQuotedString" nodes.
/// @param[out] err Error details, if any
-Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, Arena *arena,
- Error *err)
+Dict nvim_parse_expression(String expr, String flags, Boolean highlight, Arena *arena, Error *err)
FUNC_API_SINCE(4) FUNC_API_FAST
{
int pflags = 0;
@@ -443,11 +440,11 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, A
case NUL:
api_set_error(err, kErrorTypeValidation, "Invalid flag: '\\0' (%u)",
(unsigned)flags.data[i]);
- return (Dictionary)ARRAY_DICT_INIT;
+ return (Dict)ARRAY_DICT_INIT;
default:
api_set_error(err, kErrorTypeValidation, "Invalid flag: '%c' (%u)",
flags.data[i], (unsigned)flags.data[i]);
- return (Dictionary)ARRAY_DICT_INIT;
+ return (Dict)ARRAY_DICT_INIT;
}
}
ParserLine parser_lines[] = {
@@ -471,15 +468,15 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, A
+ (size_t)highlight // "highlight"
+ 0);
- Dictionary ret = arena_dict(arena, ret_size);
+ Dict ret = arena_dict(arena, ret_size);
PUT_C(ret, "len", INTEGER_OBJ((Integer)(pstate.pos.line == 1
? parser_lines[0].size
: pstate.pos.col)));
if (east.err.msg != NULL) {
- Dictionary err_dict = arena_dict(arena, 2);
+ Dict err_dict = arena_dict(arena, 2);
PUT_C(err_dict, "message", CSTR_TO_ARENA_OBJ(arena, east.err.msg));
PUT_C(err_dict, "arg", CBUF_TO_ARENA_OBJ(arena, east.err.arg, (size_t)east.err.arg_len));
- PUT_C(ret, "error", DICTIONARY_OBJ(err_dict));
+ PUT_C(ret, "error", DICT_OBJ(err_dict));
}
if (highlight) {
Array hl = arena_array(arena, kv_size(colors));
@@ -530,10 +527,10 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, A
|| node->type == kExprNodeSingleQuotedString) // "svalue"
+ (node->type == kExprNodeAssignment) // "augmentation"
+ 0);
- Dictionary ret_node = arena_dict(arena, items_size);
- *cur_item.ret_node_p = DICTIONARY_OBJ(ret_node);
+ Dict ret_node = arena_dict(arena, items_size);
+ *cur_item.ret_node_p = DICT_OBJ(ret_node);
}
- Dictionary *ret_node = &cur_item.ret_node_p->data.dictionary;
+ Dict *ret_node = &cur_item.ret_node_p->data.dict;
if (node->children != NULL) {
const size_t num_children = 1 + (node->children->next != NULL);
Array children_array = arena_array(arena, num_children);
@@ -638,8 +635,7 @@ Dictionary nvim_parse_expression(String expr, String flags, Boolean highlight, A
case kExprNodeMod:
break;
}
- assert(cur_item.ret_node_p->data.dictionary.size
- == cur_item.ret_node_p->data.dictionary.capacity);
+ assert(cur_item.ret_node_p->data.dict.size == cur_item.ret_node_p->data.dict.capacity);
xfree(*cur_item.node_p);
*cur_item.node_p = NULL;
}
diff --git a/src/nvim/api/win_config.c b/src/nvim/api/win_config.c
index 3a9986a7d1..f63fdc5381 100644
--- a/src/nvim/api/win_config.c
+++ b/src/nvim/api/win_config.c
@@ -17,6 +17,7 @@
#include "nvim/decoration.h"
#include "nvim/decoration_defs.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval/window.h"
#include "nvim/extmark_defs.h"
#include "nvim/globals.h"
@@ -189,13 +190,13 @@
/// ```
/// - title: Title (optional) in window border, string or list.
/// List should consist of `[text, highlight]` tuples.
-/// If string, the default highlight group is `FloatTitle`.
+/// If string, or a tuple lacks a highlight, the default highlight group is `FloatTitle`.
/// - title_pos: Title position. Must be set with `title` option.
/// Value can be one of "left", "center", or "right".
/// Default is `"left"`.
/// - footer: Footer (optional) in window border, string or list.
/// List should consist of `[text, highlight]` tuples.
-/// If string, the default highlight group is `FloatFooter`.
+/// If string, or a tuple lacks a highlight, the default highlight group is `FloatFooter`.
/// - footer_pos: Footer position. Must be set with `footer` option.
/// Value can be one of "left", "center", or "right".
/// Default is `"left"`.
@@ -447,7 +448,7 @@ void nvim_win_set_config(Window window, Dict(win_config) *config, Error *err)
}
}
}
- win->w_config = fconfig;
+ merge_win_config(&win->w_config, fconfig);
// If there's no "vertical" or "split" set, or if "split" is unchanged,
// then we can just change the size of the window.
@@ -851,27 +852,16 @@ static void parse_bordertext(Object bordertext, BorderTextType bordertext_type,
bool *is_present;
VirtText *chunks;
int *width;
- int default_hl_id;
switch (bordertext_type) {
case kBorderTextTitle:
- if (fconfig->title) {
- clear_virttext(&fconfig->title_chunks);
- }
-
is_present = &fconfig->title;
chunks = &fconfig->title_chunks;
width = &fconfig->title_width;
- default_hl_id = syn_check_group(S_LEN("FloatTitle"));
break;
case kBorderTextFooter:
- if (fconfig->footer) {
- clear_virttext(&fconfig->footer_chunks);
- }
-
is_present = &fconfig->footer;
chunks = &fconfig->footer_chunks;
width = &fconfig->footer_width;
- default_hl_id = syn_check_group(S_LEN("FloatFooter"));
break;
}
@@ -880,8 +870,9 @@ static void parse_bordertext(Object bordertext, BorderTextType bordertext_type,
*is_present = false;
return;
}
+ kv_init(*chunks);
kv_push(*chunks, ((VirtTextChunk){ .text = xstrdup(bordertext.data.string.data),
- .hl_id = default_hl_id }));
+ .hl_id = -1 }));
*width = (int)mb_string2cells(bordertext.data.string.data);
*is_present = true;
return;
@@ -1040,7 +1031,7 @@ static void parse_border_style(Object style, WinConfig *fconfig, Error *err)
static void generate_api_error(win_T *wp, const char *attribute, Error *err)
{
- if (wp->w_floating) {
+ if (wp != NULL && wp->w_floating) {
api_set_error(err, kErrorTypeValidation,
"Missing 'relative' field when reconfiguring floating window %d",
wp->handle);
@@ -1057,13 +1048,13 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
if (config->relative.size > 0) {
if (!parse_float_relative(config->relative, &fconfig->relative)) {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'relative' key");
- return false;
+ goto fail;
}
if (config->relative.size > 0 && !(HAS_KEY_X(config, row) && HAS_KEY_X(config, col))
&& !HAS_KEY_X(config, bufpos)) {
api_set_error(err, kErrorTypeValidation, "'relative' requires 'row'/'col' or 'bufpos'");
- return false;
+ goto fail;
}
has_relative = true;
@@ -1078,39 +1069,39 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
} else if (wp == NULL) { // new win
api_set_error(err, kErrorTypeValidation,
"Must specify 'relative' or 'external' when creating a float");
- return false;
+ goto fail;
}
}
if (HAS_KEY_X(config, vertical)) {
if (!is_split) {
api_set_error(err, kErrorTypeValidation, "floating windows cannot have 'vertical'");
- return false;
+ goto fail;
}
}
if (HAS_KEY_X(config, split)) {
if (!is_split) {
api_set_error(err, kErrorTypeValidation, "floating windows cannot have 'split'");
- return false;
+ goto fail;
}
if (!parse_config_split(config->split, &fconfig->split)) {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'split' key");
- return false;
+ goto fail;
}
}
if (HAS_KEY_X(config, anchor)) {
if (!parse_float_anchor(config->anchor, &fconfig->anchor)) {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'anchor' key");
- return false;
+ goto fail;
}
}
if (HAS_KEY_X(config, row)) {
if (!has_relative || is_split) {
generate_api_error(wp, "row", err);
- return false;
+ goto fail;
}
fconfig->row = config->row;
}
@@ -1118,7 +1109,7 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
if (HAS_KEY_X(config, col)) {
if (!has_relative || is_split) {
generate_api_error(wp, "col", err);
- return false;
+ goto fail;
}
fconfig->col = config->col;
}
@@ -1126,11 +1117,11 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
if (HAS_KEY_X(config, bufpos)) {
if (!has_relative || is_split) {
generate_api_error(wp, "bufpos", err);
- return false;
+ goto fail;
} else {
if (!parse_float_bufpos(config->bufpos, &fconfig->bufpos)) {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'bufpos' key");
- return false;
+ goto fail;
}
if (!HAS_KEY_X(config, row)) {
@@ -1147,11 +1138,11 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
fconfig->width = (int)config->width;
} else {
api_set_error(err, kErrorTypeValidation, "'width' key must be a positive Integer");
- return false;
+ goto fail;
}
} else if (!reconf && !is_split) {
api_set_error(err, kErrorTypeValidation, "Must specify 'width'");
- return false;
+ goto fail;
}
if (HAS_KEY_X(config, height)) {
@@ -1159,23 +1150,23 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
fconfig->height = (int)config->height;
} else {
api_set_error(err, kErrorTypeValidation, "'height' key must be a positive Integer");
- return false;
+ goto fail;
}
} else if (!reconf && !is_split) {
api_set_error(err, kErrorTypeValidation, "Must specify 'height'");
- return false;
+ goto fail;
}
if (relative_is_win || is_split) {
if (reconf && relative_is_win) {
win_T *target_win = find_window_by_handle(config->win, err);
if (!target_win) {
- return false;
+ goto fail;
}
if (target_win == wp) {
api_set_error(err, kErrorTypeException, "floating window cannot be relative to itself");
- return false;
+ goto fail;
}
}
fconfig->window = curwin->handle;
@@ -1188,11 +1179,11 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
if (has_relative) {
api_set_error(err, kErrorTypeValidation,
"'win' key is only valid with relative='win' and relative=''");
- return false;
+ goto fail;
} else if (!is_split) {
api_set_error(err, kErrorTypeValidation,
"non-float with 'win' requires at least 'split' or 'vertical'");
- return false;
+ goto fail;
}
}
@@ -1201,11 +1192,11 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
if (has_relative && fconfig->external) {
api_set_error(err, kErrorTypeValidation,
"Only one of 'relative' and 'external' must be used");
- return false;
+ goto fail;
}
if (fconfig->external && !ui_has(kUIMultigrid)) {
api_set_error(err, kErrorTypeValidation, "UI doesn't support external windows");
- return false;
+ goto fail;
}
}
@@ -1216,78 +1207,78 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
if (HAS_KEY_X(config, zindex)) {
if (is_split) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'zindex'");
- return false;
+ goto fail;
}
if (config->zindex > 0) {
fconfig->zindex = (int)config->zindex;
} else {
api_set_error(err, kErrorTypeValidation, "'zindex' key must be a positive Integer");
- return false;
+ goto fail;
}
}
if (HAS_KEY_X(config, title)) {
if (is_split) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'title'");
- return false;
+ goto fail;
}
// title only work with border
if (!HAS_KEY_X(config, border) && !fconfig->border) {
api_set_error(err, kErrorTypeException, "title requires border to be set");
- return false;
+ goto fail;
}
parse_bordertext(config->title, kBorderTextTitle, fconfig, err);
if (ERROR_SET(err)) {
- return false;
+ goto fail;
}
// handles unset 'title_pos' same as empty string
if (!parse_bordertext_pos(config->title_pos, kBorderTextTitle, fconfig, err)) {
- return false;
+ goto fail;
}
} else {
if (HAS_KEY_X(config, title_pos)) {
api_set_error(err, kErrorTypeException, "title_pos requires title to be set");
- return false;
+ goto fail;
}
}
if (HAS_KEY_X(config, footer)) {
if (is_split) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'footer'");
- return false;
+ goto fail;
}
// footer only work with border
if (!HAS_KEY_X(config, border) && !fconfig->border) {
api_set_error(err, kErrorTypeException, "footer requires border to be set");
- return false;
+ goto fail;
}
parse_bordertext(config->footer, kBorderTextFooter, fconfig, err);
if (ERROR_SET(err)) {
- return false;
+ goto fail;
}
// handles unset 'footer_pos' same as empty string
if (!parse_bordertext_pos(config->footer_pos, kBorderTextFooter, fconfig, err)) {
- return false;
+ goto fail;
}
} else {
if (HAS_KEY_X(config, footer_pos)) {
api_set_error(err, kErrorTypeException, "footer_pos requires footer to be set");
- return false;
+ goto fail;
}
}
if (HAS_KEY_X(config, border)) {
if (is_split) {
api_set_error(err, kErrorTypeValidation, "non-float cannot have 'border'");
- return false;
+ goto fail;
}
parse_border_style(config->border, fconfig, err);
if (ERROR_SET(err)) {
- return false;
+ goto fail;
}
}
@@ -1298,14 +1289,14 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
fconfig->style = kWinStyleMinimal;
} else {
api_set_error(err, kErrorTypeValidation, "Invalid value of 'style' key");
- return false;
+ goto fail;
}
}
if (HAS_KEY_X(config, noautocmd)) {
if (wp) {
api_set_error(err, kErrorTypeValidation, "'noautocmd' cannot be used with existing windows");
- return false;
+ goto fail;
}
fconfig->noautocmd = config->noautocmd;
}
@@ -1319,5 +1310,9 @@ static bool parse_win_config(win_T *wp, Dict(win_config) *config, WinConfig *fco
}
return true;
+
+fail:
+ merge_win_config(fconfig, wp != NULL ? wp->w_config : WIN_CONFIG_INIT);
+ return false;
#undef HAS_KEY_X
}
diff --git a/src/nvim/api/window.c b/src/nvim/api/window.c
index 54a19513db..5a4972ef23 100644
--- a/src/nvim/api/window.c
+++ b/src/nvim/api/window.c
@@ -13,6 +13,7 @@
#include "nvim/buffer_defs.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval/window.h"
#include "nvim/ex_docmd.h"
#include "nvim/gettext_defs.h"
@@ -502,16 +503,15 @@ void nvim_win_set_hl_ns(Window window, Integer ns_id, Error *err)
/// - end_vcol: Ending virtual column index on "end_row",
/// 0-based exclusive, rounded up to full screen lines.
/// When omitted include the whole line.
-/// @return Dictionary containing text height information, with these keys:
+/// @return Dict containing text height information, with these keys:
/// - all: The total number of screen lines occupied by the range.
/// - fill: The number of diff filler or virtual lines among them.
///
/// @see |virtcol()| for text width.
-Dictionary nvim_win_text_height(Window window, Dict(win_text_height) *opts, Arena *arena,
- Error *err)
+Dict nvim_win_text_height(Window window, Dict(win_text_height) *opts, Arena *arena, Error *err)
FUNC_API_SINCE(12)
{
- Dictionary rv = arena_dict(arena, 2);
+ Dict rv = arena_dict(arena, 2);
win_T *const win = find_window_by_handle(window, err);
if (!win) {
diff --git a/src/nvim/arglist.c b/src/nvim/arglist.c
index 4d493c9d03..bb639edc07 100644
--- a/src/nvim/arglist.c
+++ b/src/nvim/arglist.c
@@ -13,6 +13,7 @@
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand_defs.h"
+#include "nvim/errors.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/window.h"
@@ -202,6 +203,8 @@ void alist_set(alist_T *al, int count, char **files, int use_curbuf, int *fnum_l
/// Add file "fname" to argument list "al".
/// "fname" must have been allocated and "al" must have been checked for room.
///
+/// May trigger Buf* autocommands
+///
/// @param set_fnum 1: set buffer number; 2: re-use curbuf
void alist_add(alist_T *al, char *fname, int set_fnum)
{
@@ -212,6 +215,7 @@ void alist_add(alist_T *al, char *fname, int set_fnum)
return;
}
arglist_locked = true;
+ curwin->w_locked = true;
#ifdef BACKSLASH_IN_FILENAME
slash_adjust(fname);
@@ -224,6 +228,7 @@ void alist_add(alist_T *al, char *fname, int set_fnum)
al->al_ga.ga_len++;
arglist_locked = false;
+ curwin->w_locked = false;
}
#if defined(BACKSLASH_IN_FILENAME)
@@ -345,23 +350,20 @@ static void alist_add_list(int count, char **files, int after, bool will_edit)
int old_argcount = ARGCOUNT;
ga_grow(&ALIST(curwin)->al_ga, count);
if (check_arglist_locked() != FAIL) {
- if (after < 0) {
- after = 0;
- }
- if (after > ARGCOUNT) {
- after = ARGCOUNT;
- }
+ after = MIN(MAX(after, 0), ARGCOUNT);
if (after < ARGCOUNT) {
memmove(&(ARGLIST[after + count]), &(ARGLIST[after]),
(size_t)(ARGCOUNT - after) * sizeof(aentry_T));
}
arglist_locked = true;
+ curwin->w_locked = true;
for (int i = 0; i < count; i++) {
const int flags = BLN_LISTED | (will_edit ? BLN_CURBUF : 0);
ARGLIST[after + i].ae_fname = files[i];
ARGLIST[after + i].ae_fnum = buflist_add(files[i], flags);
}
arglist_locked = false;
+ curwin->w_locked = false;
ALIST(curwin)->al_ga.ga_len += count;
if (old_argcount > 0 && curwin->w_arg_idx >= after) {
curwin->w_arg_idx += count;
diff --git a/src/nvim/ascii_defs.h b/src/nvim/ascii_defs.h
index 0cd7ccfec4..155a18fb95 100644
--- a/src/nvim/ascii_defs.h
+++ b/src/nvim/ascii_defs.h
@@ -2,9 +2,12 @@
#include <stdbool.h>
-#include "nvim/func_attr.h"
#include "nvim/os/os_defs.h"
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ascii_defs.h.inline.generated.h"
+#endif
+
// Definitions of various common control characters.
#define CHAR_ORD(x) ((uint8_t)(x) < 'a' \
@@ -82,31 +85,24 @@
# define PATHSEPSTR "/"
#endif
-static inline bool ascii_iswhite(int c)
- REAL_FATTR_CONST
- REAL_FATTR_ALWAYS_INLINE;
/// Checks if `c` is a space or tab character.
///
/// @see {ascii_isdigit}
static inline bool ascii_iswhite(int c)
+ FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE
{
return c == ' ' || c == '\t';
}
-static inline bool ascii_iswhite_or_nul(int c)
- REAL_FATTR_CONST
- REAL_FATTR_ALWAYS_INLINE;
/// Checks if `c` is a space or tab character or NUL.
///
/// @see {ascii_isdigit}
static inline bool ascii_iswhite_or_nul(int c)
+ FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE
{
return ascii_iswhite(c) || c == NUL;
}
-static inline bool ascii_isdigit(int c)
- REAL_FATTR_CONST
- REAL_FATTR_ALWAYS_INLINE;
/// Check whether character is a decimal digit.
///
/// Library isdigit() function is officially locale-dependent and, for
@@ -117,64 +113,55 @@ static inline bool ascii_isdigit(int c)
/// what may be used for some optimizations (e.g. simple `return
/// isdigit_table[c];`).
static inline bool ascii_isdigit(int c)
+ FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE
{
return c >= '0' && c <= '9';
}
-static inline bool ascii_isxdigit(int c)
- REAL_FATTR_CONST
- REAL_FATTR_ALWAYS_INLINE;
/// Checks if `c` is a hexadecimal digit, that is, one of 0-9, a-f, A-F.
///
/// @see {ascii_isdigit}
static inline bool ascii_isxdigit(int c)
+ FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE
{
return (c >= '0' && c <= '9')
|| (c >= 'a' && c <= 'f')
|| (c >= 'A' && c <= 'F');
}
-static inline bool ascii_isident(int c)
- REAL_FATTR_CONST
- REAL_FATTR_ALWAYS_INLINE;
/// Checks if `c` is an “identifier” character
///
/// That is, whether it is alphanumeric character or underscore.
static inline bool ascii_isident(int c)
+ FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE
{
return ASCII_ISALNUM(c) || c == '_';
}
-static inline bool ascii_isbdigit(int c)
- REAL_FATTR_CONST
- REAL_FATTR_ALWAYS_INLINE;
/// Checks if `c` is a binary digit, that is, 0-1.
///
/// @see {ascii_isdigit}
static inline bool ascii_isbdigit(int c)
+ FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE
{
return (c == '0' || c == '1');
}
-static inline bool ascii_isodigit(int c)
- REAL_FATTR_CONST
- REAL_FATTR_ALWAYS_INLINE;
/// Checks if `c` is an octal digit, that is, 0-7.
///
/// @see {ascii_isdigit}
static inline bool ascii_isodigit(int c)
+ FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE
{
return (c >= '0' && c <= '7');
}
-static inline bool ascii_isspace(int c)
- REAL_FATTR_CONST
- REAL_FATTR_ALWAYS_INLINE;
/// Checks if `c` is a white-space character, that is,
/// one of \f, \n, \r, \t, \v.
///
/// @see {ascii_isdigit}
static inline bool ascii_isspace(int c)
+ FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE
{
return (c >= 9 && c <= 13) || c == ' ';
}
diff --git a/src/nvim/auevents.lua b/src/nvim/auevents.lua
index 94c5080f8d..d509837de7 100644
--- a/src/nvim/auevents.lua
+++ b/src/nvim/auevents.lua
@@ -36,6 +36,7 @@ return {
'CursorHold', -- cursor in same position for a while
'CursorHoldI', -- idem, in Insert mode
'CursorMoved', -- cursor was moved
+ 'CursorMovedC', -- cursor was moved in Cmdline mode
'CursorMovedI', -- cursor was moved in Insert mode
'DiagnosticChanged', -- diagnostics in a buffer were modified
'DiffUpdated', -- diffs have been updated
@@ -117,6 +118,7 @@ return {
'TextChangedP', -- text was modified in Insert mode(popup)
'TextChangedT', -- text was modified in Terminal mode
'TextYankPost', -- after a yank or delete was done (y, d, c)
+ 'TextPutPre', -- before a put was done (p, P)
'TextPutPost', -- after a put was done (p, P)
'UIEnter', -- after UI attaches
'UILeave', -- after UI detaches
diff --git a/src/nvim/autocmd.c b/src/nvim/autocmd.c
index c5d81d4cd2..5a4ade913d 100644
--- a/src/nvim/autocmd.c
+++ b/src/nvim/autocmd.c
@@ -15,6 +15,7 @@
#include "nvim/charset.h"
#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
@@ -710,7 +711,7 @@ char *au_event_disable(char *what)
if (*what == ',' && *p_ei == NUL) {
STRCPY(new_ei, what + 1);
} else {
- STRCAT(new_ei, what);
+ strcat(new_ei, what);
}
set_option_direct(kOptEventignore, CSTR_AS_OPTVAL(new_ei), 0, SID_NONE);
xfree(new_ei);
@@ -1699,19 +1700,33 @@ bool apply_autocmds_group(event_T event, char *fname, char *fname_io, bool force
} else {
sfname = xstrdup(fname);
// Don't try expanding the following events.
- if (event == EVENT_CMDLINECHANGED || event == EVENT_CMDLINEENTER
- || event == EVENT_CMDLINELEAVE || event == EVENT_CMDUNDEFINED
- || event == EVENT_CMDWINENTER || event == EVENT_CMDWINLEAVE
- || event == EVENT_COLORSCHEME || event == EVENT_COLORSCHEMEPRE
- || event == EVENT_DIRCHANGED || event == EVENT_DIRCHANGEDPRE
- || event == EVENT_FILETYPE || event == EVENT_FUNCUNDEFINED
- || event == EVENT_MENUPOPUP || event == EVENT_MODECHANGED
- || event == EVENT_OPTIONSET || event == EVENT_QUICKFIXCMDPOST
- || event == EVENT_QUICKFIXCMDPRE || event == EVENT_REMOTEREPLY
- || event == EVENT_SIGNAL || event == EVENT_SPELLFILEMISSING
- || event == EVENT_SYNTAX || event == EVENT_TABCLOSED
- || event == EVENT_USER || event == EVENT_WINCLOSED
- || event == EVENT_WINRESIZED || event == EVENT_WINSCROLLED) {
+ if (event == EVENT_CMDLINECHANGED
+ || event == EVENT_CMDLINEENTER
+ || event == EVENT_CMDLINELEAVE
+ || event == EVENT_CMDUNDEFINED
+ || event == EVENT_CURSORMOVEDC
+ || event == EVENT_CMDWINENTER
+ || event == EVENT_CMDWINLEAVE
+ || event == EVENT_COLORSCHEME
+ || event == EVENT_COLORSCHEMEPRE
+ || event == EVENT_DIRCHANGED
+ || event == EVENT_DIRCHANGEDPRE
+ || event == EVENT_FILETYPE
+ || event == EVENT_FUNCUNDEFINED
+ || event == EVENT_MENUPOPUP
+ || event == EVENT_MODECHANGED
+ || event == EVENT_OPTIONSET
+ || event == EVENT_QUICKFIXCMDPOST
+ || event == EVENT_QUICKFIXCMDPRE
+ || event == EVENT_REMOTEREPLY
+ || event == EVENT_SIGNAL
+ || event == EVENT_SPELLFILEMISSING
+ || event == EVENT_SYNTAX
+ || event == EVENT_TABCLOSED
+ || event == EVENT_USER
+ || event == EVENT_WINCLOSED
+ || event == EVENT_WINRESIZED
+ || event == EVENT_WINSCROLLED) {
fname = xstrdup(fname);
autocmd_fname_full = true; // don't expand it later
} else {
@@ -2037,7 +2052,7 @@ static bool call_autocmd_callback(const AutoCmd *ac, const AutoPatCmd *apc)
}
MAXSIZE_TEMP_ARRAY(args, 1);
- ADD_C(args, DICTIONARY_OBJ(data));
+ ADD_C(args, DICT_OBJ(data));
Object result = nlua_call_ref(callback.data.luaref, NULL, args, kRetNilBool, NULL, NULL);
return LUARET_TRUTHY(result);
diff --git a/src/nvim/base64.c b/src/nvim/base64.c
index a645c64fe3..99d3c5a33e 100644
--- a/src/nvim/base64.c
+++ b/src/nvim/base64.c
@@ -4,6 +4,7 @@
#include <string.h>
#include "auto/config.h" // IWYU pragma: keep
+#include "nvim/ascii_defs.h"
#include "nvim/base64.h"
#include "nvim/memory.h"
@@ -125,7 +126,7 @@ char *base64_encode(const char *src, size_t src_len)
dest[out_i] = '=';
}
- dest[out_len] = '\0';
+ dest[out_len] = NUL;
return dest;
}
@@ -141,6 +142,7 @@ char *base64_encode(const char *src, size_t src_len)
/// @param [out] out_lenp Returns the length of the decoded string
/// @return Decoded string
char *base64_decode(const char *src, size_t src_len, size_t *out_lenp)
+ FUNC_ATTR_NONNULL_ALL
{
assert(src != NULL);
assert(out_lenp != NULL);
diff --git a/src/nvim/buffer.c b/src/nvim/buffer.c
index 39d0d24d47..4a87cebfa7 100644
--- a/src/nvim/buffer.c
+++ b/src/nvim/buffer.c
@@ -43,6 +43,7 @@
#include "nvim/diff.h"
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/vars.h"
@@ -477,6 +478,11 @@ static bool can_unload_buffer(buf_T *buf)
return can_unload;
}
+bool buf_locked(buf_T *buf)
+{
+ return buf->b_locked || buf->b_locked_split;
+}
+
/// Close the link to a buffer.
///
/// @param win If not NULL, set b_last_cursor.
@@ -698,6 +704,9 @@ bool close_buffer(win_T *win, buf_T *buf, int action, bool abort_if_last, bool i
if (buf->b_nwindows > 0) {
return false;
}
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ mark_forget_file(wp, buf->b_fnum);
+ }
if (buf->b_sfname != buf->b_ffname) {
XFREE_CLEAR(buf->b_sfname);
} else {
@@ -864,7 +873,7 @@ static void free_buffer(buf_T *buf)
}
unref_var_dict(buf->b_vars);
aubuflocal_remove(buf);
- tv_dict_unref(buf->additional_data);
+ xfree(buf->additional_data);
xfree(buf->b_prompt_text);
callback_free(&buf->b_prompt_callback);
callback_free(&buf->b_prompt_interrupt);
@@ -938,6 +947,21 @@ static void free_buffer_stuff(buf_T *buf, int free_flags)
void goto_buffer(exarg_T *eap, int start, int dir, int count)
{
const int save_sea = swap_exists_action;
+ bool skip_help_buf;
+
+ switch (eap->cmdidx) {
+ case CMD_bnext:
+ case CMD_sbnext:
+ case CMD_bNext:
+ case CMD_bprevious:
+ case CMD_sbNext:
+ case CMD_sbprevious:
+ skip_help_buf = true;
+ break;
+ default:
+ skip_help_buf = false;
+ break;
+ }
bufref_T old_curbuf;
set_bufref(&old_curbuf, curbuf);
@@ -945,8 +969,9 @@ void goto_buffer(exarg_T *eap, int start, int dir, int count)
if (swap_exists_action == SEA_NONE) {
swap_exists_action = SEA_DIALOG;
}
- do_buffer(*eap->cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO,
- start, dir, count, eap->forceit);
+ (void)do_buffer_ext(*eap->cmd == 's' ? DOBUF_SPLIT : DOBUF_GOTO, start, dir, count,
+ (eap->forceit ? DOBUF_FORCEIT : 0) |
+ (skip_help_buf ? DOBUF_SKIPHELP : 0));
if (swap_exists_action == SEA_QUIT && *eap->cmd == 's') {
cleanup_T cs;
@@ -1183,32 +1208,6 @@ static int empty_curbuf(bool close_others, int forceit, int action)
return retval;
}
-/// Remove every jump list entry referring to a given buffer.
-/// This function will also adjust the current jump list index.
-void buf_remove_from_jumplist(buf_T *deleted_buf)
-{
- // Remove all jump list entries that match the deleted buffer.
- for (int i = curwin->w_jumplistlen - 1; i >= 0; i--) {
- buf_T *buf = buflist_findnr(curwin->w_jumplist[i].fmark.fnum);
-
- if (buf == deleted_buf) {
- // Found an entry that we want to delete.
- curwin->w_jumplistlen -= 1;
-
- // If the current jump list index behind the entry we want to
- // delete, move it back by one.
- if (curwin->w_jumplistidx > i && curwin->w_jumplistidx > 0) {
- curwin->w_jumplistidx -= 1;
- }
-
- // Actually remove the entry from the jump list.
- for (int d = i; d < curwin->w_jumplistlen; d++) {
- curwin->w_jumplist[d] = curwin->w_jumplist[d + 1];
- }
- }
- }
-}
-
/// Implementation of the commands for the buffer list.
///
/// action == DOBUF_GOTO go to specified buffer
@@ -1224,10 +1223,10 @@ void buf_remove_from_jumplist(buf_T *deleted_buf)
///
/// @param dir FORWARD or BACKWARD
/// @param count buffer number or number of buffers
-/// @param forceit true for :...!
+/// @param flags see @ref dobuf_flags_value
///
/// @return FAIL or OK.
-int do_buffer(int action, int start, int dir, int count, int forceit)
+static int do_buffer_ext(int action, int start, int dir, int count, int flags)
{
buf_T *buf;
buf_T *bp;
@@ -1268,19 +1267,14 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
if (bp == NULL) {
bp = buf;
}
- if (dir == FORWARD) {
- buf = buf->b_next;
- if (buf == NULL) {
- buf = firstbuf;
- }
- } else {
- buf = buf->b_prev;
- if (buf == NULL) {
- buf = lastbuf;
- }
- }
- // don't count unlisted buffers
- if (unload || buf->b_p_bl) {
+ buf = dir == FORWARD ? (buf->b_next != NULL ? buf->b_next : firstbuf)
+ : (buf->b_prev != NULL ? buf->b_prev : lastbuf);
+ // Don't count unlisted buffers.
+ // Avoid non-help buffers if the starting point was a non-help buffer and
+ // vice-versa.
+ if (unload
+ || (buf->b_p_bl
+ && ((flags & DOBUF_SKIPHELP) == 0 || buf->b_help == bp->b_help))) {
count--;
bp = NULL; // use this buffer as new starting point
}
@@ -1306,7 +1300,9 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
return FAIL;
}
- if (action == DOBUF_GOTO && buf != curbuf && !check_can_set_curbuf_forceit(forceit)) {
+ if (action == DOBUF_GOTO
+ && buf != curbuf
+ && !check_can_set_curbuf_forceit((flags & DOBUF_FORCEIT) ? true : false)) {
// disallow navigating to another buffer when 'winfixbuf' is applied
return FAIL;
}
@@ -1332,7 +1328,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
return FAIL;
}
- if (!forceit && bufIsChanged(buf)) {
+ if ((flags & DOBUF_FORCEIT) == 0 && bufIsChanged(buf)) {
if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write) {
dialog_changed(buf, false);
if (!bufref_valid(&bufref)) {
@@ -1352,7 +1348,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
}
}
- if (!forceit && buf->terminal && terminal_running(buf->terminal)) {
+ if (!(flags & DOBUF_FORCEIT) && buf->terminal && terminal_running(buf->terminal)) {
if (p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) {
if (!dialog_close_terminal(buf)) {
return FAIL;
@@ -1363,6 +1359,8 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
}
}
+ int buf_fnum = buf->b_fnum;
+
// When closing the current buffer stop Visual mode.
if (buf == curbuf && VIsual_active) {
end_visual_mode();
@@ -1378,7 +1376,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
}
}
if (bp == NULL && buf == curbuf) {
- return empty_curbuf(true, forceit, action);
+ return empty_curbuf(true, (flags & DOBUF_FORCEIT), action);
}
// If the deleted buffer is the current one, close the current window
@@ -1386,7 +1384,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
// When the autocommand window is involved win_close() may need to print an error message.
// Repeat this so long as we end up in a window with this buffer.
while (buf == curbuf
- && !(curwin->w_closing || curwin->w_buffer->b_locked > 0)
+ && !(win_locked(curwin) || curwin->w_buffer->b_locked > 0)
&& (is_aucmd_win(lastwin) || !last_window(curwin))) {
if (win_close(curwin, false, false) == FAIL) {
break;
@@ -1395,8 +1393,10 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
// If the buffer to be deleted is not the current one, delete it here.
if (buf != curbuf) {
- // Remove the buffer to be deleted from the jump list.
- buf_remove_from_jumplist(buf);
+ if (jop_flags & JOP_CLEAN) {
+ // Remove the buffer to be deleted from the jump list.
+ mark_jumplist_forget_file(curwin, buf_fnum);
+ }
close_windows(buf, false);
@@ -1419,28 +1419,37 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
if (au_new_curbuf.br_buf != NULL && bufref_valid(&au_new_curbuf)) {
buf = au_new_curbuf.br_buf;
} else if (curwin->w_jumplistlen > 0) {
- // Remove the current buffer from the jump list.
- buf_remove_from_jumplist(curbuf);
+ if (jop_flags & JOP_CLEAN) {
+ // Remove the buffer from the jump list.
+ mark_jumplist_forget_file(curwin, buf_fnum);
+ }
// It's possible that we removed all jump list entries, in that case we need to try another
// approach
if (curwin->w_jumplistlen > 0) {
- // If the index is the same as the length, the current position was not yet added to the jump
- // list. So we can safely go back to the last entry and search from there.
- if (curwin->w_jumplistidx == curwin->w_jumplistlen) {
- curwin->w_jumplistidx = curwin->w_jumplistlen - 1;
- }
-
int jumpidx = curwin->w_jumplistidx;
+ if (jop_flags & JOP_CLEAN) {
+ // If the index is the same as the length, the current position was not yet added to the
+ // jump list. So we can safely go back to the last entry and search from there.
+ if (jumpidx == curwin->w_jumplistlen) {
+ jumpidx = curwin->w_jumplistidx = curwin->w_jumplistlen - 1;
+ }
+ } else {
+ jumpidx--;
+ if (jumpidx < 0) {
+ jumpidx = curwin->w_jumplistlen - 1;
+ }
+ }
+
forward = jumpidx;
- do {
+ while ((jop_flags & JOP_CLEAN) || jumpidx != curwin->w_jumplistidx) {
buf = buflist_findnr(curwin->w_jumplist[jumpidx].fmark.fnum);
if (buf != NULL) {
- // Skip unlisted bufs. Also skip a quickfix
+ // Skip current and unlisted bufs. Also skip a quickfix
// buffer, it might be deleted soon.
- if (!buf->b_p_bl || bt_quickfix(buf)) {
+ if (buf == curbuf || !buf->b_p_bl || bt_quickfix(buf)) {
buf = NULL;
} else if (buf->b_ml.ml_mfp == NULL) {
// skip unloaded buf, but may keep it for later
@@ -1451,8 +1460,10 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
}
}
if (buf != NULL) { // found a valid buffer: stop searching
- curwin->w_jumplistidx = jumpidx;
- update_jumplist = false;
+ if (jop_flags & JOP_CLEAN) {
+ curwin->w_jumplistidx = jumpidx;
+ update_jumplist = false;
+ }
break;
}
// advance to older entry in jump list
@@ -1465,7 +1476,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
if (jumpidx == forward) { // List exhausted for sure
break;
}
- } while (jumpidx != curwin->w_jumplistidx);
+ }
}
}
@@ -1490,11 +1501,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
bp = buf;
}
}
- if (forward) {
- buf = buf->b_next;
- } else {
- buf = buf->b_prev;
- }
+ buf = forward ? buf->b_next : buf->b_prev;
}
}
if (buf == NULL) { // No loaded buffer, use unloaded one
@@ -1509,11 +1516,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
}
}
if (buf == NULL) { // Still no buffer, just take one
- if (curbuf->b_next != NULL) {
- buf = curbuf->b_next;
- } else {
- buf = curbuf->b_prev;
- }
+ buf = curbuf->b_next != NULL ? curbuf->b_next : curbuf->b_prev;
if (bt_quickfix(buf)) {
buf = NULL;
}
@@ -1523,7 +1526,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
if (buf == NULL) {
// Autocommands must have wiped out all other buffers. Only option
// now is to make the current buffer empty.
- return empty_curbuf(false, forceit, action);
+ return empty_curbuf(false, (flags & DOBUF_FORCEIT), action);
}
// make "buf" the current buffer
@@ -1544,7 +1547,7 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
}
// Check if the current buffer may be abandoned.
- if (action == DOBUF_GOTO && !can_abandon(curbuf, forceit)) {
+ if (action == DOBUF_GOTO && !can_abandon(curbuf, (flags & DOBUF_FORCEIT))) {
if ((p_confirm || (cmdmod.cmod_flags & CMOD_CONFIRM)) && p_write) {
bufref_T bufref;
set_bufref(&bufref, buf);
@@ -1574,6 +1577,11 @@ int do_buffer(int action, int start, int dir, int count, int forceit)
return OK;
}
+int do_buffer(int action, int start, int dir, int count, int forceit)
+{
+ return do_buffer_ext(action, start, dir, count, forceit ? DOBUF_FORCEIT : 0);
+}
+
/// Set current buffer to "buf". Executes autocommands and closes current
/// buffer.
///
@@ -1659,11 +1667,7 @@ void set_curbuf(buf_T *buf, int action, bool update_jumplist)
}
// If the buffer is not valid but curwin->w_buffer is NULL we must
// enter some buffer. Using the last one is hopefully OK.
- if (!valid) {
- enter_buffer(lastbuf);
- } else {
- enter_buffer(buf);
- }
+ enter_buffer(valid ? buf : lastbuf);
if (old_tw != curbuf->b_p_tw) {
check_colorcolumn(curwin);
}
@@ -2076,6 +2080,7 @@ void free_buf_options(buf_T *buf, bool free_p_ff)
clear_string_option(&buf->b_p_lop);
clear_string_option(&buf->b_p_cinsd);
clear_string_option(&buf->b_p_cinw);
+ clear_string_option(&buf->b_p_cot);
clear_string_option(&buf->b_p_cpt);
clear_string_option(&buf->b_p_cfu);
callback_free(&buf->b_cfu_cb);
@@ -2114,7 +2119,6 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit)
{
win_T *wp = NULL;
fmark_T *fm = NULL;
- colnr_T col;
buf_T *buf = buflist_findnr(n);
if (buf == NULL) {
@@ -2135,6 +2139,7 @@ int buflist_getfile(int n, linenr_T lnum, int options, int forceit)
return FAIL;
}
+ colnr_T col;
bool restore_view = false;
// altfpos may be changed by getfile(), get it now
if (lnum == 0) {
@@ -2270,11 +2275,7 @@ int buflist_findpat(const char *pattern, const char *pattern_end, bool unlisted,
int match = -1;
if (pattern_end == pattern + 1 && (*pattern == '%' || *pattern == '#')) {
- if (*pattern == '%') {
- match = curbuf->b_fnum;
- } else {
- match = curwin->w_alt_fnum;
- }
+ match = *pattern == '%' ? curbuf->b_fnum : curwin->w_alt_fnum;
buf_T *found_buf = buflist_findnr(match);
if (diffmode && !(found_buf && diff_mode_buf(found_buf))) {
match = -1;
@@ -2885,9 +2886,7 @@ void buflist_list(exarg_T *eap)
changed_char,
NameBuff);
- if (len > IOSIZE - 20) {
- len = IOSIZE - 20;
- }
+ len = MIN(len, IOSIZE - 20);
// put "line 999" in column 40 or after the file name
int i = 40 - vim_strsize(IObuff);
@@ -2965,7 +2964,17 @@ int setfname(buf_T *buf, char *ffname_arg, char *sfname_arg, bool message)
obuf = buflist_findname_file_id(ffname, &file_id, file_id_valid);
}
if (obuf != NULL && obuf != buf) {
- if (obuf->b_ml.ml_mfp != NULL) { // it's loaded, fail
+ bool in_use = false;
+
+ // during startup a window may use a buffer that is not loaded yet
+ FOR_ALL_TAB_WINDOWS(tab, win) {
+ if (win->w_buffer == obuf) {
+ in_use = true;
+ }
+ }
+
+ // it's loaded or used in a window, fail
+ if (obuf->b_ml.ml_mfp != NULL || in_use) {
if (message) {
emsg(_("E95: Buffer with this name already exists"));
}
@@ -3191,8 +3200,6 @@ static bool buf_same_file_id(buf_T *buf, FileID *file_id)
/// @param fullname when non-zero print full path
void fileinfo(int fullname, int shorthelp, bool dont_truncate)
{
- char *name;
- int n;
char *p;
char *buffer = xmalloc(IOSIZE);
@@ -3208,11 +3215,9 @@ void fileinfo(int fullname, int shorthelp, bool dont_truncate)
if (buf_spname(curbuf) != NULL) {
xstrlcpy(p, buf_spname(curbuf), (size_t)(IOSIZE - (p - buffer)));
} else {
- if (!fullname && curbuf->b_fname != NULL) {
- name = curbuf->b_fname;
- } else {
- name = curbuf->b_ffname;
- }
+ char *name = (!fullname && curbuf->b_fname != NULL)
+ ? curbuf->b_fname
+ : curbuf->b_ffname;
home_replace(shorthelp ? curbuf : NULL, name, p,
(size_t)(IOSIZE - (p - buffer)), true);
}
@@ -3233,6 +3238,7 @@ void fileinfo(int fullname, int shorthelp, bool dont_truncate)
|| (curbuf->b_flags & BF_WRITE_MASK)
|| curbuf->b_p_ro)
? " " : "");
+ int n;
// With 32 bit longs and more than 21,474,836 lines multiplying by 100
// causes an overflow, thus for large numbers divide instead.
if (curwin->w_cursor.lnum > 1000000) {
@@ -3321,10 +3327,7 @@ void maketitle(void)
if (p_title) {
if (p_titlelen > 0) {
- maxlen = (int)(p_titlelen * Columns / 100);
- if (maxlen < 10) {
- maxlen = 10;
- }
+ maxlen = MAX((int)(p_titlelen * Columns / 100), 10);
}
if (*p_titlestring != NUL) {
@@ -3340,7 +3343,7 @@ void maketitle(void)
#define SPACE_FOR_FNAME (sizeof(buf) - 100)
#define SPACE_FOR_DIR (sizeof(buf) - 20)
-#define SPACE_FOR_ARGNR (sizeof(buf) - 10) // At least room for " - NVIM".
+#define SPACE_FOR_ARGNR (sizeof(buf) - 10) // At least room for " - Nvim".
char *buf_p = buf;
if (curbuf->b_fname == NULL) {
const size_t size = xstrlcpy(buf_p, _("[No Name]"),
@@ -3414,7 +3417,7 @@ void maketitle(void)
append_arg_number(curwin, buf_p, (int)(SPACE_FOR_ARGNR - (size_t)(buf_p - buf)));
- xstrlcat(buf_p, " - NVIM", (sizeof(buf) - (size_t)(buf_p - buf)));
+ xstrlcat(buf_p, " - Nvim", (sizeof(buf) - (size_t)(buf_p - buf)));
if (maxlen > 0) {
// Make it shorter by removing a bit in the middle.
@@ -3440,12 +3443,9 @@ void maketitle(void)
icon_str = p_iconstring;
}
} else {
- char *buf_p;
- if (buf_spname(curbuf) != NULL) {
- buf_p = buf_spname(curbuf);
- } else { // use file name only in icon
- buf_p = path_tail(curbuf->b_ffname);
- }
+ char *buf_p = buf_spname(curbuf) != NULL
+ ? buf_spname(curbuf)
+ : path_tail(curbuf->b_ffname); // use file name only in icon
*icon_str = NUL;
// Truncate name at 100 bytes.
int len = (int)strlen(buf_p);
@@ -3610,23 +3610,18 @@ bool bt_prompt(buf_T *buf)
/// Open a window for a number of buffers.
void ex_buffer_all(exarg_T *eap)
{
- win_T *wp, *wpnext;
+ win_T *wpnext;
int split_ret = OK;
int open_wins = 0;
- linenr_T count; // Maximum number of windows to open.
- int all; // When true also load inactive buffers.
int had_tab = cmdmod.cmod_tab;
- if (eap->addr_count == 0) { // make as many windows as possible
- count = 9999;
- } else {
- count = eap->line2; // make as many windows as specified
- }
- if (eap->cmdidx == CMD_unhide || eap->cmdidx == CMD_sunhide) {
- all = false;
- } else {
- all = true;
- }
+ // Maximum number of windows to open.
+ linenr_T count = eap->addr_count == 0
+ ? 9999 // make as many windows as possible
+ : eap->line2; // make as many windows as specified
+
+ // When true also load inactive buffers.
+ int all = eap->cmdidx != CMD_unhide && eap->cmdidx != CMD_sunhide;
// Stop Visual mode, the cursor and "VIsual" may very well be invalid after
// switching to another buffer.
@@ -3642,7 +3637,7 @@ void ex_buffer_all(exarg_T *eap)
while (true) {
tabpage_T *tpnext = curtab->tp_next;
// Try to close floating windows first
- for (wp = lastwin->w_floating ? lastwin : firstwin; wp != NULL; wp = wpnext) {
+ for (win_T *wp = lastwin->w_floating ? lastwin : firstwin; wp != NULL; wp = wpnext) {
wpnext = wp->w_floating
? wp->w_prev->w_floating ? wp->w_prev : firstwin
: (wp->w_next == NULL || wp->w_next->w_floating) ? NULL : wp->w_next;
@@ -3654,7 +3649,7 @@ void ex_buffer_all(exarg_T *eap)
: wp->w_width != Columns)
|| (had_tab > 0 && wp != firstwin))
&& !ONE_WINDOW
- && !(wp->w_closing || wp->w_buffer->b_locked > 0)
+ && !(win_locked(curwin) || wp->w_buffer->b_locked > 0)
&& !is_aucmd_win(wp)) {
if (win_close(wp, false, false) == FAIL) {
break;
@@ -3691,13 +3686,12 @@ void ex_buffer_all(exarg_T *eap)
continue;
}
+ win_T *wp;
if (had_tab != 0) {
// With the ":tab" modifier don't move the window.
- if (buf->b_nwindows > 0) {
- wp = lastwin; // buffer has a window, skip it
- } else {
- wp = NULL;
- }
+ wp = buf->b_nwindows > 0
+ ? lastwin // buffer has a window, skip it
+ : NULL;
} else {
// Check if this buffer already has a window
for (wp = firstwin; wp != NULL; wp = wp->w_next) {
@@ -3726,7 +3720,7 @@ void ex_buffer_all(exarg_T *eap)
// Open the buffer in this window.
swap_exists_action = SEA_DIALOG;
- set_curbuf(buf, DOBUF_GOTO, false);
+ set_curbuf(buf, DOBUF_GOTO, !(jop_flags & JOP_CLEAN));
if (!bufref_valid(&bufref)) {
// Autocommands deleted the buffer.
swap_exists_action = SEA_NONE;
@@ -3773,7 +3767,7 @@ void ex_buffer_all(exarg_T *eap)
autocmd_no_leave--;
// Close superfluous windows.
- for (wp = lastwin; open_wins > count;) {
+ for (win_T *wp = lastwin; open_wins > count;) {
bool r = (buf_hide(wp->w_buffer) || !bufIsChanged(wp->w_buffer)
|| autowrite(wp->w_buffer, false) == OK) && !is_aucmd_win(wp);
if (!win_valid(wp)) {
@@ -3841,7 +3835,6 @@ static int chk_modeline(linenr_T lnum, int flags)
{
char *s;
char *e;
- char *linecopy; // local copy of any modeline found
intmax_t vers;
int retval = OK;
@@ -3886,6 +3879,7 @@ static int chk_modeline(linenr_T lnum, int flags)
s++;
} while (s[-1] != ':');
+ char *linecopy; // local copy of any modeline found
s = linecopy = xstrdup(s); // copy the line, it will change
// prepare for emsg()
@@ -4207,7 +4201,7 @@ int buf_open_scratch(handle_T bufnr, char *bufname)
bool buf_is_empty(buf_T *buf)
{
- return buf->b_ml.ml_line_count == 1 && *ml_get_buf(buf, 1) == '\0';
+ return buf->b_ml.ml_line_count == 1 && *ml_get_buf(buf, 1) == NUL;
}
/// Increment b:changedtick value
diff --git a/src/nvim/buffer.h b/src/nvim/buffer.h
index 4c5023d39a..2936c297fe 100644
--- a/src/nvim/buffer.h
+++ b/src/nvim/buffer.h
@@ -5,7 +5,6 @@
#include "nvim/buffer_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
-#include "nvim/func_attr.h"
#include "nvim/gettext_defs.h" // IWYU pragma: keep
#include "nvim/macros_defs.h"
#include "nvim/marktree_defs.h"
@@ -39,7 +38,7 @@ enum bln_values {
BLN_NOCURWIN = 128, ///< buffer is not associated with curwin
};
-/// Values for action argument for do_buffer()
+/// Values for action argument for do_buffer_ext() and close_buffer()
enum dobuf_action_values {
DOBUF_GOTO = 0, ///< go to specified buffer
DOBUF_SPLIT = 1, ///< split window and go to specified buffer
@@ -48,7 +47,7 @@ enum dobuf_action_values {
DOBUF_WIPE = 4, ///< delete specified buffer(s) really
};
-/// Values for start argument for do_buffer()
+/// Values for start argument for do_buffer_ext()
enum dobuf_start_values {
DOBUF_CURRENT = 0, ///< "count" buffer from current buffer
DOBUF_FIRST = 1, ///< "count" buffer from first buffer
@@ -56,6 +55,13 @@ enum dobuf_start_values {
DOBUF_MOD = 3, ///< "count" mod. buffer from current buffer
};
+/// Values for flags argument of do_buffer_ext()
+enum dobuf_flags_value {
+ DOBUF_FORCEIT = 1, ///< :cmd!
+ DOBUF_SKIPHELP = 4, ///< skip or keep help buffers depending on b_help of the
+ ///< starting buffer
+};
+
/// flags for buf_freeall()
enum bfa_values {
BFA_DEL = 1, ///< buffer is going to be deleted
@@ -69,18 +75,17 @@ EXTERN char *msg_qflist INIT( = N_("[Quickfix List]"));
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "buffer.h.generated.h"
+# include "buffer.h.inline.generated.h"
#endif
-static inline varnumber_T buf_get_changedtick(const buf_T *buf)
- REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE
- REAL_FATTR_WARN_UNUSED_RESULT;
-
/// Get b:changedtick value
///
/// Faster then querying b:.
///
/// @param[in] buf Buffer to get b:changedtick from.
static inline varnumber_T buf_get_changedtick(const buf_T *const buf)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
return buf->changedtick_di.di_tv.vval.v_number;
}
diff --git a/src/nvim/buffer_defs.h b/src/nvim/buffer_defs.h
index 8f5f2c44f7..a68e841f3e 100644
--- a/src/nvim/buffer_defs.h
+++ b/src/nvim/buffer_defs.h
@@ -393,7 +393,7 @@ struct file_buffer {
/// Change-identifier incremented for each change, including undo.
///
- /// This is a dictionary item used to store b:changedtick.
+ /// This is a dict item used to store b:changedtick.
ChangedtickDictItem changedtick_di;
varnumber_T b_last_changedtick; // b:changedtick when TextChanged was
@@ -533,6 +533,8 @@ struct file_buffer {
char *b_p_cinsd; ///< 'cinscopedecls'
char *b_p_com; ///< 'comments'
char *b_p_cms; ///< 'commentstring'
+ char *b_p_cot; ///< 'completeopt' local value
+ unsigned b_cot_flags; ///< flags for 'completeopt'
char *b_p_cpt; ///< 'complete'
#ifdef BACKSLASH_IN_FILENAME
char *b_p_csl; ///< 'completeslash'
@@ -671,8 +673,8 @@ struct file_buffer {
int b_bad_char; // "++bad=" argument when edit started or 0
int b_start_bomb; // 'bomb' when it was read
- ScopeDictDictItem b_bufvar; ///< Variable for "b:" Dictionary.
- dict_T *b_vars; ///< b: scope dictionary.
+ ScopeDictDictItem b_bufvar; ///< Variable for "b:" Dict.
+ dict_T *b_vars; ///< b: scope Dict.
// When a buffer is created, it starts without a swap file. b_may_swap is
// then set to indicate that a swap file may be opened later. It is reset
@@ -711,7 +713,7 @@ struct file_buffer {
Terminal *terminal; // Terminal instance associated with the buffer
- dict_T *additional_data; // Additional data from shada file if any.
+ AdditionalData *additional_data; // Additional data from shada file if any.
int b_mapped_ctrl_c; // modes where CTRL-C is mapped
@@ -738,8 +740,6 @@ struct file_buffer {
// The number for times the current line has been flushed in the memline.
int flush_count;
-
- int b_diff_failed; // internal diff failed for this buffer
};
// Stuff for diff mode.
@@ -793,7 +793,7 @@ struct tabpage_S {
int tp_diff_invalid; ///< list of diffs is outdated
int tp_diff_update; ///< update diffs before redrawing
frame_T *(tp_snapshot[SNAP_COUNT]); ///< window layout snapshots
- ScopeDictDictItem tp_winvar; ///< Variable for "t:" Dictionary.
+ ScopeDictDictItem tp_winvar; ///< Variable for "t:" Dict.
dict_T *tp_vars; ///< Internal variables, local to tab page.
char *tp_localdir; ///< Absolute path of local cwd or NULL.
char *tp_prevdir; ///< Previous directory.
@@ -1046,8 +1046,7 @@ struct window_S {
win_T *w_prev; ///< link to previous window
win_T *w_next; ///< link to next window
- bool w_closing; ///< window is being closed, don't let
- ///< autocommands close it too.
+ bool w_locked; ///< don't let autocommands close the window
frame_T *w_frame; ///< frame containing this window
@@ -1265,8 +1264,8 @@ struct window_S {
int w_scbind_pos;
- ScopeDictDictItem w_winvar; ///< Variable for "w:" dictionary.
- dict_T *w_vars; ///< Dictionary with w: variables.
+ ScopeDictDictItem w_winvar; ///< Variable for "w:" dict.
+ dict_T *w_vars; ///< Dict with w: variables.
// The w_prev_pcmark field is used to check whether we really did jump to
// a new line after setting the w_pcmark. If not, then we revert to
diff --git a/src/nvim/bufwrite.c b/src/nvim/bufwrite.c
index 27de03954a..fa0a55e7b6 100644
--- a/src/nvim/bufwrite.c
+++ b/src/nvim/bufwrite.c
@@ -18,6 +18,7 @@
#include "nvim/bufwrite.h"
#include "nvim/change.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds.h"
@@ -260,11 +261,8 @@ static int buf_write_convert(struct bw_info *ip, char **bufp, int *lenp)
ip->bw_restlen += *lenp;
break;
}
- if (n > 1) {
- c = (unsigned)utf_ptr2char((char *)ip->bw_rest);
- } else {
- c = ip->bw_rest[0];
- }
+ c = (n > 1) ? (unsigned)utf_ptr2char((char *)ip->bw_rest)
+ : ip->bw_rest[0];
if (n >= ip->bw_restlen) {
n -= ip->bw_restlen;
ip->bw_restlen = 0;
@@ -288,11 +286,8 @@ static int buf_write_convert(struct bw_info *ip, char **bufp, int *lenp)
(size_t)ip->bw_restlen);
break;
}
- if (n > 1) {
- c = (unsigned)utf_ptr2char(*bufp + wlen);
- } else {
- c = (uint8_t)(*bufp)[wlen];
- }
+ c = n > 1 ? (unsigned)utf_ptr2char(*bufp + wlen)
+ : (uint8_t)(*bufp)[wlen];
}
if (ucs2bytes(c, &p, flags) && !ip->bw_conv_error) {
@@ -820,10 +815,6 @@ static int buf_write_make_backup(char *fname, bool append, FileInfo *file_info_o
// Isolate one directory name, using an entry in 'bdir'.
size_t dir_len = copy_option_part(&dirp, IObuff, IOSIZE, ",");
char *p = IObuff + dir_len;
- bool trailing_pathseps = after_pathsep(IObuff, p) && p[-1] == p[-2];
- if (trailing_pathseps) {
- IObuff[dir_len - 2] = NUL;
- }
if (*dirp == NUL && !os_isdir(IObuff)) {
int ret;
char *failed_dir;
@@ -833,9 +824,9 @@ static int buf_write_make_backup(char *fname, bool append, FileInfo *file_info_o
xfree(failed_dir);
}
}
- if (trailing_pathseps) {
+ if (after_pathsep(IObuff, p) && p[-1] == p[-2]) {
// Ends with '//', Use Full path
- if ((p = make_percent_swname(IObuff, fname))
+ if ((p = make_percent_swname(IObuff, p, fname))
!= NULL) {
*backupp = modname(p, backup_ext, no_prepend_dot);
xfree(p);
@@ -879,9 +870,7 @@ static int buf_write_make_backup(char *fname, bool append, FileInfo *file_info_o
// Change one character, just before the extension.
//
char *wp = *backupp + strlen(*backupp) - 1 - strlen(backup_ext);
- if (wp < *backupp) { // empty file name ???
- wp = *backupp;
- }
+ wp = MAX(wp, *backupp); // empty file name ???
*wp = 'z';
while (*wp > 'a' && os_fileinfo(*backupp, &file_info_new)) {
(*wp)--;
@@ -962,10 +951,6 @@ nobackup:
// Isolate one directory name and make the backup file name.
size_t dir_len = copy_option_part(&dirp, IObuff, IOSIZE, ",");
char *p = IObuff + dir_len;
- bool trailing_pathseps = after_pathsep(IObuff, p) && p[-1] == p[-2];
- if (trailing_pathseps) {
- IObuff[dir_len - 2] = NUL;
- }
if (*dirp == NUL && !os_isdir(IObuff)) {
int ret;
char *failed_dir;
@@ -975,9 +960,9 @@ nobackup:
xfree(failed_dir);
}
}
- if (trailing_pathseps) {
+ if (after_pathsep(IObuff, p) && p[-1] == p[-2]) {
// path ends with '//', use full path
- if ((p = make_percent_swname(IObuff, fname))
+ if ((p = make_percent_swname(IObuff, p, fname))
!= NULL) {
*backupp = modname(p, backup_ext, no_prepend_dot);
xfree(p);
@@ -1000,9 +985,7 @@ nobackup:
// Change one character, just before the extension.
if (!p_bk && os_path_exists(*backupp)) {
p = *backupp + strlen(*backupp) - 1 - strlen(backup_ext);
- if (p < *backupp) { // empty file name ???
- p = *backupp;
- }
+ p = MAX(p, *backupp); // empty file name ???
*p = 'z';
while (*p > 'a' && os_path_exists(*backupp)) {
(*p)--;
@@ -1065,7 +1048,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en
bool whole = (start == 1 && end == buf->b_ml.ml_line_count);
bool write_undo_file = false;
context_sha256_T sha_ctx;
- unsigned bkc = get_bkc_value(buf);
+ unsigned bkc = get_bkc_flags(buf);
if (fname == NULL || *fname == NUL) { // safety check
return FAIL;
@@ -1262,9 +1245,7 @@ int buf_write(buf_T *buf, char *fname, char *sfname, linenr_T start, linenr_T en
status_redraw_all(); // redraw status lines later
}
- if (end > buf->b_ml.ml_line_count) {
- end = buf->b_ml.ml_line_count;
- }
+ end = MIN(end, buf->b_ml.ml_line_count);
if (buf->b_ml.ml_flags & ML_EMPTY) {
start = end + 1;
}
diff --git a/src/nvim/change.c b/src/nvim/change.c
index 05772d39e9..51a13b80e7 100644
--- a/src/nvim/change.c
+++ b/src/nvim/change.c
@@ -392,6 +392,10 @@ static void changed_common(buf_T *buf, linenr_T lnum, colnr_T col, linenr_T lnum
}
}
}
+
+ if (wp == curwin && xtra != 0 && search_hl_has_cursor_lnum >= lnum) {
+ search_hl_has_cursor_lnum += xtra;
+ }
}
// Call update_screen() later, which checks out what needs to be redrawn,
@@ -519,19 +523,13 @@ void changed_lines_redraw_buf(buf_T *buf, linenr_T lnum, linenr_T lnume, linenr_
{
if (buf->b_mod_set) {
// find the maximum area that must be redisplayed
- if (lnum < buf->b_mod_top) {
- buf->b_mod_top = lnum;
- }
+ buf->b_mod_top = MIN(buf->b_mod_top, lnum);
if (lnum < buf->b_mod_bot) {
// adjust old bot position for xtra lines
buf->b_mod_bot += xtra;
- if (buf->b_mod_bot < lnum) {
- buf->b_mod_bot = lnum;
- }
- }
- if (lnume + xtra > buf->b_mod_bot) {
- buf->b_mod_bot = lnume + xtra;
+ buf->b_mod_bot = MAX(buf->b_mod_bot, lnum);
}
+ buf->b_mod_bot = MAX(buf->b_mod_bot, lnume + xtra);
buf->b_mod_xlines += xtra;
} else {
// set the area that must be redisplayed
@@ -758,10 +756,8 @@ void ins_char_bytes(char *buf, size_t charlen)
// put back when BS is used. The bytes of a multi-byte character are
// done the other way around, so that the first byte is popped off
// first (it tells the byte length of the character).
- replace_push(NUL);
- for (size_t i = 0; i < oldlen; i++) {
- i += (size_t)replace_push_mb(oldp + col + i) - 1;
- }
+ replace_push_nul();
+ replace_push(oldp + col, oldlen);
}
char *newp = xmalloc(linelen + newlen - oldlen);
@@ -898,14 +894,15 @@ int del_bytes(colnr_T count, bool fixpos_arg, bool use_delcombine)
// delete the last combining character.
if (p_deco && use_delcombine && utfc_ptr2len(oldp + col) >= count) {
char *p0 = oldp + col;
- if (utf_composinglike(p0, p0 + utf_ptr2len(p0))) {
+ GraphemeState state = GRAPHEME_STATE_INIT;
+ if (utf_composinglike(p0, p0 + utf_ptr2len(p0), &state)) {
// Find the last composing char, there can be several.
int n = col;
do {
col = n;
count = utf_ptr2len(oldp + n);
n += count;
- } while (utf_composinglike(oldp + col, oldp + n));
+ } while (utf_composinglike(oldp + col, oldp + n, &state));
fixpos = false;
}
}
@@ -1138,12 +1135,10 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// on the line onto the replace stack. We'll push any other characters
// that might be replaced at the start of the next line (due to
// autoindent etc) a bit later.
- replace_push(NUL); // Call twice because BS over NL expects it
- replace_push(NUL);
+ replace_push_nul(); // Call twice because BS over NL expects it
+ replace_push_nul();
p = saved_line + curwin->w_cursor.col;
- while (*p != NUL) {
- p += replace_push_mb(p);
- }
+ replace_push(p, strlen(p));
saved_line[curwin->w_cursor.col] = NUL;
}
@@ -1692,13 +1687,13 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// stack, preceded by a NUL, so they can be put back when a BS is
// entered.
if (REPLACE_NORMAL(State)) {
- replace_push(NUL); // end of extra blanks
+ replace_push_nul(); // end of extra blanks
}
if (curbuf->b_p_ai || (flags & OPENLINE_DELSPACES)) {
while ((*p_extra == ' ' || *p_extra == '\t')
- && !utf_iscomposing(utf_ptr2char(p_extra + 1))) {
+ && !utf_iscomposing_first(utf_ptr2char(p_extra + 1))) {
if (REPLACE_NORMAL(State)) {
- replace_push(*p_extra);
+ replace_push(p_extra, 1); // always ascii, len = 1
}
p_extra++;
less_cols_off++;
@@ -1723,12 +1718,12 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// Below, set_indent(newindent, SIN_INSERT) will insert the
// whitespace needed before the comment char.
for (int i = 0; i < padding; i++) {
- STRCAT(leader, " ");
+ strcat(leader, " ");
less_cols--;
newcol++;
}
}
- STRCAT(leader, p_extra);
+ strcat(leader, p_extra);
p_extra = leader;
did_ai = true; // So truncating blanks works with comments
less_cols -= lead_len;
@@ -1795,7 +1790,7 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// must be a NUL on the replace stack, for when it is deleted with BS
if (REPLACE_NORMAL(State)) {
for (colnr_T n = 0; n < curwin->w_cursor.col; n++) {
- replace_push(NUL);
+ replace_push_nul();
}
}
newcol += curwin->w_cursor.col;
@@ -1809,7 +1804,7 @@ bool open_line(int dir, int flags, int second_line_indent, bool *did_do_comment)
// must be a NUL on the replace stack, for when it is deleted with BS.
if (REPLACE_NORMAL(State)) {
while (lead_len-- > 0) {
- replace_push(NUL);
+ replace_push_nul();
}
}
@@ -2258,9 +2253,7 @@ int get_last_leader_offset(char *line, char **flags)
for (int off = (len2 > i ? i : len2); off > 0 && off + len1 > len2;) {
off--;
if (!strncmp(string + off, com_leader, (size_t)(len2 - off))) {
- if (i - off < lower_check_bound) {
- lower_check_bound = i - off;
- }
+ lower_check_bound = MIN(lower_check_bound, i - off);
}
}
}
diff --git a/src/nvim/channel.c b/src/nvim/channel.c
index 41635747f8..021fdd4b79 100644
--- a/src/nvim/channel.c
+++ b/src/nvim/channel.c
@@ -13,12 +13,13 @@
#include "nvim/autocmd_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/channel.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
#include "nvim/event/rstream.h"
#include "nvim/event/socket.h"
#include "nvim/event/stream.h"
@@ -38,8 +39,6 @@
#include "nvim/os/os_defs.h"
#include "nvim/os/shell.h"
#include "nvim/path.h"
-#include "nvim/rbuffer.h"
-#include "nvim/rbuffer_defs.h"
#include "nvim/terminal.h"
#include "nvim/types_defs.h"
@@ -89,7 +88,7 @@ void channel_free_all_mem(void)
bool channel_close(uint64_t id, ChannelPart part, const char **error)
{
Channel *chan;
- Process *proc;
+ Proc *proc;
const char *dummy;
if (!error) {
@@ -126,32 +125,32 @@ bool channel_close(uint64_t id, ChannelPart part, const char **error)
*error = e_invstream;
return false;
}
- stream_may_close(&chan->stream.socket);
+ rstream_may_close(&chan->stream.socket);
break;
case kChannelStreamProc:
proc = &chan->stream.proc;
if (part == kChannelPartStdin || close_main) {
- stream_may_close(&proc->in);
+ wstream_may_close(&proc->in);
}
if (part == kChannelPartStdout || close_main) {
- stream_may_close(&proc->out);
+ rstream_may_close(&proc->out);
}
if (part == kChannelPartStderr || part == kChannelPartAll) {
- stream_may_close(&proc->err);
+ rstream_may_close(&proc->err);
}
- if (proc->type == kProcessTypePty && part == kChannelPartAll) {
- pty_process_close_master(&chan->stream.pty);
+ if (proc->type == kProcTypePty && part == kChannelPartAll) {
+ pty_proc_close_master(&chan->stream.pty);
}
break;
case kChannelStreamStdio:
if (part == kChannelPartStdin || close_main) {
- stream_may_close(&chan->stream.stdio.in);
+ rstream_may_close(&chan->stream.stdio.in);
}
if (part == kChannelPartStdout || close_main) {
- stream_may_close(&chan->stream.stdio.out);
+ wstream_may_close(&chan->stream.stdio.out);
}
if (part == kChannelPartStderr) {
*error = e_invstream;
@@ -240,11 +239,11 @@ void channel_create_event(Channel *chan, const char *ext_source)
assert(chan->id <= VARNUMBER_MAX);
Arena arena = ARENA_EMPTY;
- Dictionary info = channel_info(chan->id, &arena);
+ Dict info = channel_info(chan->id, &arena);
typval_T tv = TV_INITIAL_VALUE;
// TODO(bfredl): do the conversion in one step. Also would be nice
// to pretty print top level dict in defined order
- object_to_vim(DICTIONARY_OBJ(info), &tv, NULL);
+ object_to_vim(DICT_OBJ(info), &tv, NULL);
assert(tv.v_type == VAR_DICT);
char *str = encode_tv2json(&tv, NULL);
ILOG("new channel %" PRIu64 " (%s) : %s", chan->id, source, str);
@@ -290,7 +289,7 @@ static void channel_destroy(Channel *chan)
}
if (chan->streamtype == kChannelStreamProc) {
- process_free(&chan->stream.proc);
+ proc_free(&chan->stream.proc);
}
callback_reader_free(&chan->on_data);
@@ -377,7 +376,7 @@ Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_s
*status_out = 0;
return NULL;
}
- chan->stream.pty = pty_process_init(&main_loop, chan);
+ chan->stream.pty = pty_proc_init(&main_loop, chan);
if (pty_width > 0) {
chan->stream.pty.width = pty_width;
}
@@ -385,22 +384,22 @@ Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_s
chan->stream.pty.height = pty_height;
}
} else {
- chan->stream.uv = libuv_process_init(&main_loop, chan);
+ chan->stream.uv = libuv_proc_init(&main_loop, chan);
}
- Process *proc = &chan->stream.proc;
+ Proc *proc = &chan->stream.proc;
proc->argv = argv;
proc->exepath = exepath;
- proc->cb = channel_process_exit_cb;
+ proc->cb = channel_proc_exit_cb;
proc->events = chan->events;
proc->detach = detach;
proc->cwd = cwd;
proc->env = env;
proc->overlapped = overlapped;
- char *cmd = xstrdup(process_get_exepath(proc));
+ char *cmd = xstrdup(proc_get_exepath(proc));
bool has_out, has_err;
- if (proc->type == kProcessTypePty) {
+ if (proc->type == kProcTypePty) {
has_out = true;
has_err = false;
} else {
@@ -411,7 +410,7 @@ Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_s
bool has_in = stdin_mode == kChannelStdinPipe;
- int status = process_spawn(proc, has_in, has_out, has_err);
+ int status = proc_spawn(proc, has_in, has_out, has_err);
if (status) {
semsg(_(e_jobspawn), os_strerror(status), cmd);
xfree(cmd);
@@ -431,7 +430,7 @@ Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_s
wstream_init(&proc->in, 0);
}
if (has_out) {
- rstream_init(&proc->out, 0);
+ rstream_init(&proc->out);
}
if (rpc) {
@@ -446,7 +445,7 @@ Channel *channel_job_start(char **argv, const char *exepath, CallbackReader on_s
if (has_err) {
callback_reader_start(&chan->on_stderr, "stderr");
- rstream_init(&proc->err, 0);
+ rstream_init(&proc->err);
rstream_start(&proc->err, on_job_stderr, chan);
}
@@ -480,10 +479,10 @@ uint64_t channel_connect(bool tcp, const char *address, bool rpc, CallbackReader
return 0;
}
- channel->stream.socket.internal_close_cb = close_cb;
- channel->stream.socket.internal_data = channel;
- wstream_init(&channel->stream.socket, 0);
- rstream_init(&channel->stream.socket, 0);
+ channel->stream.socket.s.internal_close_cb = close_cb;
+ channel->stream.socket.s.internal_data = channel;
+ wstream_init(&channel->stream.socket.s, 0);
+ rstream_init(&channel->stream.socket);
if (rpc) {
rpc_start(channel);
@@ -505,10 +504,10 @@ void channel_from_connection(SocketWatcher *watcher)
{
Channel *channel = channel_alloc(kChannelStreamSocket);
socket_watcher_accept(watcher, &channel->stream.socket);
- channel->stream.socket.internal_close_cb = close_cb;
- channel->stream.socket.internal_data = channel;
- wstream_init(&channel->stream.socket, 0);
- rstream_init(&channel->stream.socket, 0);
+ channel->stream.socket.s.internal_close_cb = close_cb;
+ channel->stream.socket.s.internal_data = channel;
+ wstream_init(&channel->stream.socket.s, 0);
+ rstream_init(&channel->stream.socket);
rpc_start(channel);
channel_create_event(channel, watcher->addr);
}
@@ -553,7 +552,7 @@ uint64_t channel_from_stdio(bool rpc, CallbackReader on_output, const char **err
dup2(STDERR_FILENO, STDIN_FILENO);
}
#endif
- rstream_init_fd(&main_loop, &channel->stream.stdio.in, stdin_dup_fd, 0);
+ rstream_init_fd(&main_loop, &channel->stream.stdio.in, stdin_dup_fd);
wstream_init_fd(&main_loop, &channel->stream.stdio.out, stdout_dup_fd, 0);
if (rpc) {
@@ -647,51 +646,38 @@ static inline list_T *buffer_to_tv_list(const char *const buf, const size_t len)
return l;
}
-void on_channel_data(Stream *stream, RBuffer *buf, size_t count, void *data, bool eof)
+size_t on_channel_data(RStream *stream, const char *buf, size_t count, void *data, bool eof)
{
Channel *chan = data;
- on_channel_output(stream, chan, buf, eof, &chan->on_data);
+ return on_channel_output(stream, chan, buf, count, eof, &chan->on_data);
}
-void on_job_stderr(Stream *stream, RBuffer *buf, size_t count, void *data, bool eof)
+size_t on_job_stderr(RStream *stream, const char *buf, size_t count, void *data, bool eof)
{
Channel *chan = data;
- on_channel_output(stream, chan, buf, eof, &chan->on_stderr);
+ return on_channel_output(stream, chan, buf, count, eof, &chan->on_stderr);
}
-static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, bool eof,
- CallbackReader *reader)
+static size_t on_channel_output(RStream *stream, Channel *chan, const char *buf, size_t count,
+ bool eof, CallbackReader *reader)
{
- size_t count;
- char *output = rbuffer_read_ptr(buf, &count);
-
if (chan->term) {
- if (!eof) {
- char *p = output;
- char *end = output + count;
+ if (count) {
+ const char *p = buf;
+ const char *end = buf + count;
while (p < end) {
// Don't pass incomplete UTF-8 sequences to libvterm. #16245
// Composing chars can be passed separately, so utf_ptr2len_len() is enough.
int clen = utf_ptr2len_len(p, (int)(end - p));
if (clen > end - p) {
- count = (size_t)(p - output);
+ count = (size_t)(p - buf);
break;
}
p += clen;
}
}
- terminal_receive(chan->term, output, count);
- }
-
- if (count) {
- rbuffer_consumed(buf, count);
- }
- // Move remaining data to start of buffer, so the buffer can never wrap around.
- rbuffer_reset(buf);
-
- if (callback_reader_set(*reader)) {
- ga_concat_len(&reader->buffer, output, count);
+ terminal_receive(chan->term, buf, count);
}
if (eof) {
@@ -699,8 +685,11 @@ static void on_channel_output(Stream *stream, Channel *chan, RBuffer *buf, bool
}
if (callback_reader_set(*reader)) {
+ ga_concat_len(&reader->buffer, buf, count);
schedule_channel_event(chan);
}
+
+ return count;
}
/// schedule the necessary callbacks to be invoked as a deferred event
@@ -771,7 +760,7 @@ void channel_reader_callbacks(Channel *chan, CallbackReader *reader)
}
}
-static void channel_process_exit_cb(Process *proc, int status, void *data)
+static void channel_proc_exit_cb(Proc *proc, int status, void *data)
{
Channel *chan = data;
if (chan->term) {
@@ -858,13 +847,13 @@ static void term_write(const char *buf, size_t size, void *data)
static void term_resize(uint16_t width, uint16_t height, void *data)
{
Channel *chan = data;
- pty_process_resize(&chan->stream.pty, width, height);
+ pty_proc_resize(&chan->stream.pty, width, height);
}
static inline void term_delayed_free(void **argv)
{
Channel *chan = argv[0];
- if (chan->stream.proc.in.pending_reqs || chan->stream.proc.out.pending_reqs) {
+ if (chan->stream.proc.in.pending_reqs || chan->stream.proc.out.s.pending_reqs) {
multiqueue_put(chan->events, term_delayed_free, chan);
return;
}
@@ -878,7 +867,7 @@ static inline void term_delayed_free(void **argv)
static void term_close(void *data)
{
Channel *chan = data;
- process_stop(&chan->stream.proc);
+ proc_stop(&chan->stream.proc);
multiqueue_put(chan->events, term_delayed_free, data);
}
@@ -899,9 +888,9 @@ static void set_info_event(void **argv)
save_v_event_T save_v_event;
dict_T *dict = get_v_event(&save_v_event);
Arena arena = ARENA_EMPTY;
- Dictionary info = channel_info(chan->id, &arena);
+ Dict info = channel_info(chan->id, &arena);
typval_T retval;
- object_to_vim(DICTIONARY_OBJ(info), &retval, NULL);
+ object_to_vim(DICT_OBJ(info), &retval, NULL);
assert(retval.v_type == VAR_DICT);
tv_dict_add_dict(dict, S_LEN("info"), retval.vval.v_dict);
tv_dict_set_keys_readonly(dict);
@@ -918,25 +907,25 @@ bool channel_job_running(uint64_t id)
Channel *chan = find_channel(id);
return (chan
&& chan->streamtype == kChannelStreamProc
- && !process_is_stopped(&chan->stream.proc));
+ && !proc_is_stopped(&chan->stream.proc));
}
-Dictionary channel_info(uint64_t id, Arena *arena)
+Dict channel_info(uint64_t id, Arena *arena)
{
Channel *chan = find_channel(id);
if (!chan) {
- return (Dictionary)ARRAY_DICT_INIT;
+ return (Dict)ARRAY_DICT_INIT;
}
- Dictionary info = arena_dict(arena, 8);
+ Dict info = arena_dict(arena, 8);
PUT_C(info, "id", INTEGER_OBJ((Integer)chan->id));
const char *stream_desc, *mode_desc;
switch (chan->streamtype) {
case kChannelStreamProc: {
stream_desc = "job";
- if (chan->stream.proc.type == kProcessTypePty) {
- const char *name = pty_process_tty_name(&chan->stream.pty);
+ if (chan->stream.proc.type == kProcTypePty) {
+ const char *name = pty_proc_tty_name(&chan->stream.pty);
PUT_C(info, "pty", CSTR_TO_ARENA_OBJ(arena, name));
}
@@ -974,7 +963,7 @@ Dictionary channel_info(uint64_t id, Arena *arena)
if (chan->is_rpc) {
mode_desc = "rpc";
- PUT_C(info, "client", DICTIONARY_OBJ(chan->rpc.info));
+ PUT_C(info, "client", DICT_OBJ(chan->rpc.info));
} else if (chan->term) {
mode_desc = "terminal";
PUT_C(info, "buffer", BUFFER_OBJ(terminal_buf(chan->term)));
@@ -1007,7 +996,7 @@ Array channel_all_info(Arena *arena)
Array ret = arena_array(arena, ids.size);
for (size_t i = 0; i < ids.size; i++) {
- ADD_C(ret, DICTIONARY_OBJ(channel_info((uint64_t)ids.items[i], arena)));
+ ADD_C(ret, DICT_OBJ(channel_info((uint64_t)ids.items[i], arena)));
}
return ret;
}
diff --git a/src/nvim/channel.h b/src/nvim/channel.h
index 35d369e513..2327216826 100644
--- a/src/nvim/channel.h
+++ b/src/nvim/channel.h
@@ -7,19 +7,13 @@
#include "nvim/channel_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h"
#include "nvim/event/defs.h"
-#include "nvim/event/libuv_process.h"
-#include "nvim/func_attr.h"
+#include "nvim/event/libuv_proc.h"
#include "nvim/macros_defs.h"
#include "nvim/map_defs.h"
#include "nvim/msgpack_rpc/channel_defs.h"
-#include "nvim/os/pty_process.h"
+#include "nvim/os/pty_proc.h"
#include "nvim/types_defs.h"
-static inline bool callback_reader_set(CallbackReader reader)
-{
- return reader.cb.type != kCallbackNone || reader.self;
-}
-
struct Channel {
uint64_t id;
size_t refcount;
@@ -27,10 +21,10 @@ struct Channel {
ChannelStreamType streamtype;
union {
- Process proc;
- LibuvProcess uv;
- PtyProcess pty;
- Stream socket;
+ Proc proc;
+ LibuvProc uv;
+ PtyProc pty;
+ RStream socket;
StdioPair stdio;
StderrState err;
InternalState internal;
@@ -49,14 +43,20 @@ struct Channel {
bool callback_scheduled;
};
-EXTERN PMap(uint64_t) channels INIT( = MAP_INIT);
-
-EXTERN Callback on_print INIT( = CALLBACK_INIT);
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "channel.h.generated.h"
+# include "channel.h.inline.generated.h"
#endif
+static inline bool callback_reader_set(CallbackReader reader)
+{
+ return reader.cb.type != kCallbackNone || reader.self;
+}
+
+EXTERN PMap(uint64_t) channels INIT( = MAP_INIT);
+
+EXTERN Callback on_print INIT( = CALLBACK_INIT);
+
/// @returns Channel with the id or NULL if not found
static inline Channel *find_channel(uint64_t id)
{
@@ -64,16 +64,14 @@ static inline Channel *find_channel(uint64_t id)
}
static inline Stream *channel_instream(Channel *chan)
- REAL_FATTR_NONNULL_ALL;
-
-static inline Stream *channel_instream(Channel *chan)
+ FUNC_ATTR_NONNULL_ALL
{
switch (chan->streamtype) {
case kChannelStreamProc:
return &chan->stream.proc.in;
case kChannelStreamSocket:
- return &chan->stream.socket;
+ return &chan->stream.socket.s;
case kChannelStreamStdio:
return &chan->stream.stdio.out;
@@ -85,10 +83,8 @@ static inline Stream *channel_instream(Channel *chan)
abort();
}
-static inline Stream *channel_outstream(Channel *chan)
- REAL_FATTR_NONNULL_ALL;
-
-static inline Stream *channel_outstream(Channel *chan)
+static inline RStream *channel_outstream(Channel *chan)
+ FUNC_ATTR_NONNULL_ALL
{
switch (chan->streamtype) {
case kChannelStreamProc:
diff --git a/src/nvim/channel_defs.h b/src/nvim/channel_defs.h
index d4f1895420..2df6edea7a 100644
--- a/src/nvim/channel_defs.h
+++ b/src/nvim/channel_defs.h
@@ -30,7 +30,7 @@ typedef enum {
} ChannelStdinMode;
typedef struct {
- Stream in;
+ RStream in;
Stream out;
} StdioPair;
diff --git a/src/nvim/charset.c b/src/nvim/charset.c
index c611d4cfd6..430f6b15fe 100644
--- a/src/nvim/charset.c
+++ b/src/nvim/charset.c
@@ -1470,7 +1470,7 @@ start:
*dst++ = *p++;
}
}
- *dst = '\0';
+ *dst = NUL;
}
}
@@ -1492,6 +1492,6 @@ char *backslash_halve_save(const char *p)
*dst++ = *p++;
}
}
- *dst = '\0';
+ *dst = NUL;
return res;
}
diff --git a/src/nvim/charset.h b/src/nvim/charset.h
index 62a38660a8..1407e21785 100644
--- a/src/nvim/charset.h
+++ b/src/nvim/charset.h
@@ -3,20 +3,9 @@
#include <stdbool.h>
#include <stdint.h>
-#include "nvim/func_attr.h"
#include "nvim/option_vars.h"
#include "nvim/strings.h" // IWYU pragma: keep
-/// Return the folded-case equivalent of the given character
-///
-/// @param[in] c Character to transform.
-///
-/// @return Folded variant.
-#define CH_FOLD(c) \
- utf_fold((sizeof(c) == sizeof(char)) \
- ? ((int)(uint8_t)(c)) \
- : ((int)(c)))
-
/// Flags for vim_str2nr()
typedef enum {
STR2NR_DEC = 0,
@@ -41,15 +30,13 @@ typedef enum {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "charset.h.generated.h"
+# include "charset.h.inline.generated.h"
#endif
-static inline bool vim_isbreak(int c)
- REAL_FATTR_CONST
- REAL_FATTR_ALWAYS_INLINE;
-
/// Check if `c` is one of the characters in 'breakat'.
/// Used very often if 'linebreak' is set
static inline bool vim_isbreak(int c)
+ FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE
{
return breakat_flags[(uint8_t)c];
}
diff --git a/src/nvim/cmdexpand.c b/src/nvim/cmdexpand.c
index 808df44941..402a891099 100644
--- a/src/nvim/cmdexpand.c
+++ b/src/nvim/cmdexpand.c
@@ -19,6 +19,7 @@
#include "nvim/cmdexpand.h"
#include "nvim/cmdhist.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
@@ -104,6 +105,7 @@ static bool cmdline_fuzzy_completion_supported(const expand_T *const xp)
&& xp->xp_context != EXPAND_COLORS
&& xp->xp_context != EXPAND_COMPILER
&& xp->xp_context != EXPAND_DIRECTORIES
+ && xp->xp_context != EXPAND_DIRS_IN_CDPATH
&& xp->xp_context != EXPAND_FILES
&& xp->xp_context != EXPAND_FILES_IN_PATH
&& xp->xp_context != EXPAND_FILETYPE
@@ -158,7 +160,8 @@ static void wildescape(expand_T *xp, const char *str, int numfiles, char **files
|| xp->xp_context == EXPAND_FILES_IN_PATH
|| xp->xp_context == EXPAND_SHELLCMD
|| xp->xp_context == EXPAND_BUFFERS
- || xp->xp_context == EXPAND_DIRECTORIES) {
+ || xp->xp_context == EXPAND_DIRECTORIES
+ || xp->xp_context == EXPAND_DIRS_IN_CDPATH) {
// Insert a backslash into a file name before a space, \, %, #
// and wildmatch characters, except '~'.
for (int i = 0; i < numfiles; i++) {
@@ -239,6 +242,9 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
if (xp->xp_numfiles == -1) {
set_expand_context(xp);
+ if (xp->xp_context == EXPAND_LUA) {
+ nlua_expand_pat(xp);
+ }
cmd_showtail = expand_showtail(xp);
}
@@ -285,12 +291,6 @@ int nextwild(expand_T *xp, int type, int options, bool escape)
p2 = ExpandOne(xp, p1, xstrnsave(&ccline->cmdbuff[i], xp->xp_pattern_len),
use_options, type);
xfree(p1);
-
- // xp->xp_pattern might have been modified by ExpandOne (for example,
- // in lua completion), so recompute the pattern index and length
- i = (int)(xp->xp_pattern - ccline->cmdbuff);
- xp->xp_pattern_len = (size_t)ccline->cmdpos - (size_t)i;
-
// Longest match: make sure it is not shorter, happens with :help.
if (p2 != NULL && type == WILD_LONGEST) {
int j;
@@ -356,6 +356,7 @@ static int cmdline_pum_create(CmdlineInfo *ccline, expand_T *xp, char **matches,
.pum_info = NULL,
.pum_extra = NULL,
.pum_kind = NULL,
+ .pum_user_hlattr = -1,
};
}
@@ -401,6 +402,20 @@ void cmdline_pum_cleanup(CmdlineInfo *cclp)
wildmenu_cleanup(cclp);
}
+/// Returns the current cmdline completion pattern.
+char *cmdline_compl_pattern(void)
+{
+ expand_T *xp = get_cmdline_info()->xpc;
+ return xp == NULL ? NULL : xp->xp_orig;
+}
+
+/// Returns true if fuzzy cmdline completion is active, false otherwise.
+bool cmdline_compl_is_fuzzy(void)
+{
+ expand_T *xp = get_cmdline_info()->xpc;
+ return xp != NULL && cmdline_fuzzy_completion_supported(xp);
+}
+
/// Return the number of characters that should be skipped in the wildmenu
/// These are backslashes used for escaping. Do show backslashes in help tags.
static int skip_wildmenu_char(expand_T *xp, char *s)
@@ -655,10 +670,7 @@ static char *get_next_or_prev_match(int mode, expand_T *xp)
ht -= 2;
}
findex -= ht;
- if (findex < 0) {
- // few entries left, select the first entry
- findex = 0;
- }
+ findex = MAX(findex, 0); // few entries left, select the first entry
}
} else if (mode == WILD_PAGEDOWN) {
if (findex == xp->xp_numfiles - 1) {
@@ -686,18 +698,10 @@ static char *get_next_or_prev_match(int mode, expand_T *xp)
// When wrapping around, return the original string, set findex to -1.
if (findex < 0) {
- if (xp->xp_orig == NULL) {
- findex = xp->xp_numfiles - 1;
- } else {
- findex = -1;
- }
+ findex = xp->xp_orig == NULL ? xp->xp_numfiles - 1 : -1;
}
if (findex >= xp->xp_numfiles) {
- if (xp->xp_orig == NULL) {
- findex = 0;
- } else {
- findex = -1;
- }
+ findex = xp->xp_orig == NULL ? 0 : -1;
}
if (compl_match_array) {
compl_selected = findex;
@@ -1046,6 +1050,9 @@ int showmatches(expand_T *xp, bool wildmenu)
if (xp->xp_numfiles == -1) {
set_expand_context(xp);
+ if (xp->xp_context == EXPAND_LUA) {
+ nlua_expand_pat(xp);
+ }
int i = expand_cmdline(xp, ccline->cmdbuff, ccline->cmdpos,
&numMatches, &matches);
showtail = expand_showtail(xp);
@@ -1094,9 +1101,7 @@ int showmatches(expand_T *xp, bool wildmenu)
} else {
j = vim_strsize(SHOW_MATCH(i));
}
- if (j > maxlen) {
- maxlen = j;
- }
+ maxlen = MAX(maxlen, j);
}
if (xp->xp_context == EXPAND_TAGS_LISTFILES) {
@@ -1213,7 +1218,8 @@ char *addstar(char *fname, size_t len, int context)
if (context != EXPAND_FILES
&& context != EXPAND_FILES_IN_PATH
&& context != EXPAND_SHELLCMD
- && context != EXPAND_DIRECTORIES) {
+ && context != EXPAND_DIRECTORIES
+ && context != EXPAND_DIRS_IN_CDPATH) {
// Matching will be done internally (on something other than files).
// So we convert the file-matching-type wildcards into our kind for
// use with vim_regcomp(). First work out how long it will be:
@@ -1827,7 +1833,7 @@ static const char *set_context_by_cmdname(const char *cmd, cmdidx_T cmdidx, expa
case CMD_tcd:
case CMD_tchdir:
if (xp->xp_context == EXPAND_FILES) {
- xp->xp_context = EXPAND_DIRECTORIES;
+ xp->xp_context = EXPAND_DIRS_IN_CDPATH;
}
break;
case CMD_help:
@@ -2491,6 +2497,8 @@ static int expand_files_and_dirs(expand_T *xp, char *pat, char ***matches, int *
flags |= EW_FILE;
} else if (xp->xp_context == EXPAND_FILES_IN_PATH) {
flags |= (EW_FILE | EW_PATH);
+ } else if (xp->xp_context == EXPAND_DIRS_IN_CDPATH) {
+ flags = (flags | EW_DIR | EW_CDPATH) & ~EW_FILE;
} else {
flags = (flags | EW_DIR) & ~EW_FILE;
}
@@ -2703,7 +2711,8 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM
if (xp->xp_context == EXPAND_FILES
|| xp->xp_context == EXPAND_DIRECTORIES
- || xp->xp_context == EXPAND_FILES_IN_PATH) {
+ || xp->xp_context == EXPAND_FILES_IN_PATH
+ || xp->xp_context == EXPAND_DIRS_IN_CDPATH) {
return expand_files_and_dirs(xp, pat, matches, numMatches, flags, options);
}
@@ -2782,8 +2791,7 @@ static int ExpandFromContext(expand_T *xp, char *pat, char ***matches, int *numM
}
if (xp->xp_context == EXPAND_LUA) {
- ILOG("PAT %s", pat);
- return nlua_expand_pat(xp, pat, numMatches, matches);
+ return nlua_expand_get_matches(numMatches, matches);
}
if (!fuzzy) {
@@ -3074,7 +3082,7 @@ static void *call_user_expand_func(user_expand_func_T user_expand_func, expand_T
typval_T args[4];
const sctx_T save_current_sctx = current_sctx;
- if (xp->xp_arg == NULL || xp->xp_arg[0] == '\0' || xp->xp_line == NULL) {
+ if (xp->xp_arg == NULL || xp->xp_arg[0] == NUL || xp->xp_line == NULL) {
return NULL;
}
@@ -3256,7 +3264,7 @@ void globpath(char *path, char *file, garray_T *ga, int expand_options, bool dir
copy_option_part(&path, buf, MAXPATHL, ",");
if (strlen(buf) + strlen(file) + 2 < MAXPATHL) {
add_pathsep(buf);
- STRCAT(buf, file);
+ strcat(buf, file);
char **p;
int num_p = 0;
@@ -3595,7 +3603,11 @@ void f_getcompletion(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
theend:
- ;
+ if (xpc.xp_context == EXPAND_LUA) {
+ xpc.xp_col = (int)strlen(xpc.xp_line);
+ nlua_expand_pat(&xpc);
+ xpc.xp_pattern_len = strlen(xpc.xp_pattern);
+ }
char *pat;
if (cmdline_fuzzy_completion_supported(&xpc)) {
// when fuzzy matching, don't modify the search string
diff --git a/src/nvim/cmdexpand_defs.h b/src/nvim/cmdexpand_defs.h
index c1fb85859c..3369790151 100644
--- a/src/nvim/cmdexpand_defs.h
+++ b/src/nvim/cmdexpand_defs.h
@@ -105,6 +105,7 @@ enum {
EXPAND_SETTING_SUBTRACT,
EXPAND_ARGOPT,
EXPAND_KEYMAP,
+ EXPAND_DIRS_IN_CDPATH,
EXPAND_CHECKHEALTH,
EXPAND_LUA,
};
diff --git a/src/nvim/cmdhist.c b/src/nvim/cmdhist.c
index 983ab8b59b..47a4ffba9e 100644
--- a/src/nvim/cmdhist.c
+++ b/src/nvim/cmdhist.c
@@ -11,6 +11,7 @@
#include "nvim/charset.h"
#include "nvim/cmdexpand_defs.h"
#include "nvim/cmdhist.h"
+#include "nvim/errors.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
@@ -189,7 +190,7 @@ static inline void hist_free_entry(histentry_T *hisptr)
FUNC_ATTR_NONNULL_ALL
{
xfree(hisptr->hisstr);
- tv_list_unref(hisptr->additional_elements);
+ xfree(hisptr->additional_data);
clear_hist_entry(hisptr);
}
@@ -236,7 +237,7 @@ static int in_history(int type, const char *str, int move_to_front, int sep)
return false;
}
- list_T *const list = history[type][i].additional_elements;
+ AdditionalData *ad = history[type][i].additional_data;
char *const save_hisstr = history[type][i].hisstr;
while (i != hisidx[type]) {
if (++i >= hislen) {
@@ -245,11 +246,11 @@ static int in_history(int type, const char *str, int move_to_front, int sep)
history[type][last_i] = history[type][i];
last_i = i;
}
- tv_list_unref(list);
+ xfree(ad);
history[type][i].hisnum = ++hisnum[type];
history[type][i].hisstr = save_hisstr;
history[type][i].timestamp = os_time();
- history[type][i].additional_elements = NULL;
+ history[type][i].additional_data = NULL;
return true;
}
@@ -336,7 +337,7 @@ void add_to_history(int histype, const char *new_entry, size_t new_entrylen, boo
// Store the separator after the NUL of the string.
hisptr->hisstr = xstrnsave(new_entry, new_entrylen + 2);
hisptr->timestamp = os_time();
- hisptr->additional_elements = NULL;
+ hisptr->additional_data = NULL;
hisptr->hisstr[new_entrylen + 1] = (char)sep;
hisptr->hisnum = ++hisnum[histype];
diff --git a/src/nvim/cmdhist.h b/src/nvim/cmdhist.h
index 43be397cee..4df4b09e68 100644
--- a/src/nvim/cmdhist.h
+++ b/src/nvim/cmdhist.h
@@ -24,7 +24,7 @@ typedef struct {
int hisnum; ///< Entry identifier number.
char *hisstr; ///< Actual entry, separator char after the NUL.
Timestamp timestamp; ///< Time when entry was added.
- list_T *additional_elements; ///< Additional entries from ShaDa file.
+ AdditionalData *additional_data; ///< Additional entries from ShaDa file.
} histentry_T;
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/context.c b/src/nvim/context.c
index 95e2618f62..461b10a9d5 100644
--- a/src/nvim/context.c
+++ b/src/nvim/context.c
@@ -66,21 +66,11 @@ Context *ctx_get(size_t index)
void ctx_free(Context *ctx)
FUNC_ATTR_NONNULL_ALL
{
- if (ctx->regs.data) {
- msgpack_sbuffer_destroy(&ctx->regs);
- }
- if (ctx->jumps.data) {
- msgpack_sbuffer_destroy(&ctx->jumps);
- }
- if (ctx->bufs.data) {
- msgpack_sbuffer_destroy(&ctx->bufs);
- }
- if (ctx->gvars.data) {
- msgpack_sbuffer_destroy(&ctx->gvars);
- }
- if (ctx->funcs.items) {
- api_free_array(ctx->funcs);
- }
+ api_free_string(ctx->regs);
+ api_free_string(ctx->jumps);
+ api_free_string(ctx->bufs);
+ api_free_string(ctx->gvars);
+ api_free_array(ctx->funcs);
}
/// Saves the editor state to a context.
@@ -98,19 +88,19 @@ void ctx_save(Context *ctx, const int flags)
}
if (flags & kCtxRegs) {
- ctx_save_regs(ctx);
+ ctx->regs = shada_encode_regs();
}
if (flags & kCtxJumps) {
- ctx_save_jumps(ctx);
+ ctx->jumps = shada_encode_jumps();
}
if (flags & kCtxBufs) {
- ctx_save_bufs(ctx);
+ ctx->bufs = shada_encode_buflist();
}
if (flags & kCtxGVars) {
- ctx_save_gvars(ctx);
+ ctx->gvars = shada_encode_gvars();
}
if (flags & kCtxFuncs) {
@@ -173,33 +163,13 @@ bool ctx_restore(Context *ctx, const int flags)
return true;
}
-/// Saves the global registers to a context.
-///
-/// @param ctx Save to this context.
-static inline void ctx_save_regs(Context *ctx)
- FUNC_ATTR_NONNULL_ALL
-{
- msgpack_sbuffer_init(&ctx->regs);
- shada_encode_regs(&ctx->regs);
-}
-
/// Restores the global registers from a context.
///
/// @param ctx Restore from this context.
static inline void ctx_restore_regs(Context *ctx)
FUNC_ATTR_NONNULL_ALL
{
- shada_read_sbuf(&ctx->regs, kShaDaWantInfo | kShaDaForceit);
-}
-
-/// Saves the jumplist to a context.
-///
-/// @param ctx Save to this context.
-static inline void ctx_save_jumps(Context *ctx)
- FUNC_ATTR_NONNULL_ALL
-{
- msgpack_sbuffer_init(&ctx->jumps);
- shada_encode_jumps(&ctx->jumps);
+ shada_read_string(ctx->regs, kShaDaWantInfo | kShaDaForceit);
}
/// Restores the jumplist from a context.
@@ -208,17 +178,7 @@ static inline void ctx_save_jumps(Context *ctx)
static inline void ctx_restore_jumps(Context *ctx)
FUNC_ATTR_NONNULL_ALL
{
- shada_read_sbuf(&ctx->jumps, kShaDaWantInfo | kShaDaForceit);
-}
-
-/// Saves the buffer list to a context.
-///
-/// @param ctx Save to this context.
-static inline void ctx_save_bufs(Context *ctx)
- FUNC_ATTR_NONNULL_ALL
-{
- msgpack_sbuffer_init(&ctx->bufs);
- shada_encode_buflist(&ctx->bufs);
+ shada_read_string(ctx->jumps, kShaDaWantInfo | kShaDaForceit);
}
/// Restores the buffer list from a context.
@@ -227,17 +187,7 @@ static inline void ctx_save_bufs(Context *ctx)
static inline void ctx_restore_bufs(Context *ctx)
FUNC_ATTR_NONNULL_ALL
{
- shada_read_sbuf(&ctx->bufs, kShaDaWantInfo | kShaDaForceit);
-}
-
-/// Saves global variables to a context.
-///
-/// @param ctx Save to this context.
-static inline void ctx_save_gvars(Context *ctx)
- FUNC_ATTR_NONNULL_ALL
-{
- msgpack_sbuffer_init(&ctx->gvars);
- shada_encode_gvars(&ctx->gvars);
+ shada_read_string(ctx->bufs, kShaDaWantInfo | kShaDaForceit);
}
/// Restores global variables from a context.
@@ -246,7 +196,7 @@ static inline void ctx_save_gvars(Context *ctx)
static inline void ctx_restore_gvars(Context *ctx)
FUNC_ATTR_NONNULL_ALL
{
- shada_read_sbuf(&ctx->gvars, kShaDaWantInfo | kShaDaForceit);
+ shada_read_string(ctx->gvars, kShaDaWantInfo | kShaDaForceit);
}
/// Saves functions to a context.
@@ -291,41 +241,16 @@ static inline void ctx_restore_funcs(Context *ctx)
}
}
-/// Convert msgpack_sbuffer to readfile()-style array.
-///
-/// @param[in] sbuf msgpack_sbuffer to convert.
-///
-/// @return readfile()-style array representation of "sbuf".
-static inline Array sbuf_to_array(msgpack_sbuffer sbuf, Arena *arena)
-{
- list_T *const list = tv_list_alloc(kListLenMayKnow);
- tv_list_append_string(list, "", 0);
- if (sbuf.size > 0) {
- encode_list_write(list, sbuf.data, sbuf.size);
- }
-
- typval_T list_tv = (typval_T) {
- .v_lock = VAR_UNLOCKED,
- .v_type = VAR_LIST,
- .vval.v_list = list
- };
-
- Array array = vim_to_object(&list_tv, arena, false).data.array;
- tv_clear(&list_tv);
- return array;
-}
-
-/// Convert readfile()-style array to msgpack_sbuffer.
+/// Convert readfile()-style array to String
///
/// @param[in] array readfile()-style array to convert.
/// @param[out] err Error object.
///
-/// @return msgpack_sbuffer with conversion result.
-static inline msgpack_sbuffer array_to_sbuf(Array array, Error *err)
+/// @return String with conversion result.
+static inline String array_to_string(Array array, Error *err)
FUNC_ATTR_NONNULL_ALL
{
- msgpack_sbuffer sbuf;
- msgpack_sbuffer_init(&sbuf);
+ String sbuf = STRING_INIT;
typval_T list_tv;
object_to_vim(ARRAY_OBJ(array), &list_tv, err);
@@ -335,41 +260,40 @@ static inline msgpack_sbuffer array_to_sbuf(Array array, Error *err)
api_set_error(err, kErrorTypeException, "%s",
"E474: Failed to convert list to msgpack string buffer");
}
- sbuf.alloc = sbuf.size;
tv_clear(&list_tv);
return sbuf;
}
-/// Converts Context to Dictionary representation.
+/// Converts Context to Dict representation.
///
/// @param[in] ctx Context to convert.
///
-/// @return Dictionary representing "ctx".
-Dictionary ctx_to_dict(Context *ctx, Arena *arena)
+/// @return Dict representing "ctx".
+Dict ctx_to_dict(Context *ctx, Arena *arena)
FUNC_ATTR_NONNULL_ALL
{
assert(ctx != NULL);
- Dictionary rv = arena_dict(arena, 5);
+ Dict rv = arena_dict(arena, 5);
- PUT_C(rv, "regs", ARRAY_OBJ(sbuf_to_array(ctx->regs, arena)));
- PUT_C(rv, "jumps", ARRAY_OBJ(sbuf_to_array(ctx->jumps, arena)));
- PUT_C(rv, "bufs", ARRAY_OBJ(sbuf_to_array(ctx->bufs, arena)));
- PUT_C(rv, "gvars", ARRAY_OBJ(sbuf_to_array(ctx->gvars, arena)));
+ PUT_C(rv, "regs", ARRAY_OBJ(string_to_array(ctx->regs, false, arena)));
+ PUT_C(rv, "jumps", ARRAY_OBJ(string_to_array(ctx->jumps, false, arena)));
+ PUT_C(rv, "bufs", ARRAY_OBJ(string_to_array(ctx->bufs, false, arena)));
+ PUT_C(rv, "gvars", ARRAY_OBJ(string_to_array(ctx->gvars, false, arena)));
PUT_C(rv, "funcs", ARRAY_OBJ(copy_array(ctx->funcs, arena)));
return rv;
}
-/// Converts Dictionary representation of Context back to Context object.
+/// Converts Dict representation of Context back to Context object.
///
-/// @param[in] dict Context Dictionary representation.
+/// @param[in] dict Context Dict representation.
/// @param[out] ctx Context object to store conversion result into.
/// @param[out] err Error object.
///
/// @return types of included context items.
-int ctx_from_dict(Dictionary dict, Context *ctx, Error *err)
+int ctx_from_dict(Dict dict, Context *ctx, Error *err)
FUNC_ATTR_NONNULL_ALL
{
assert(ctx != NULL);
@@ -382,16 +306,16 @@ int ctx_from_dict(Dictionary dict, Context *ctx, Error *err)
}
if (strequal(item.key.data, "regs")) {
types |= kCtxRegs;
- ctx->regs = array_to_sbuf(item.value.data.array, err);
+ ctx->regs = array_to_string(item.value.data.array, err);
} else if (strequal(item.key.data, "jumps")) {
types |= kCtxJumps;
- ctx->jumps = array_to_sbuf(item.value.data.array, err);
+ ctx->jumps = array_to_string(item.value.data.array, err);
} else if (strequal(item.key.data, "bufs")) {
types |= kCtxBufs;
- ctx->bufs = array_to_sbuf(item.value.data.array, err);
+ ctx->bufs = array_to_string(item.value.data.array, err);
} else if (strequal(item.key.data, "gvars")) {
types |= kCtxGVars;
- ctx->gvars = array_to_sbuf(item.value.data.array, err);
+ ctx->gvars = array_to_string(item.value.data.array, err);
} else if (strequal(item.key.data, "funcs")) {
types |= kCtxFuncs;
ctx->funcs = copy_object(item.value, NULL).data.array;
diff --git a/src/nvim/context.h b/src/nvim/context.h
index 1c18a1af7c..4375030fbc 100644
--- a/src/nvim/context.h
+++ b/src/nvim/context.h
@@ -1,31 +1,24 @@
#pragma once
-#include <msgpack/sbuffer.h>
#include <stddef.h>
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
typedef struct {
- msgpack_sbuffer regs; ///< Registers.
- msgpack_sbuffer jumps; ///< Jumplist.
- msgpack_sbuffer bufs; ///< Buffer list.
- msgpack_sbuffer gvars; ///< Global variables.
+ String regs; ///< Registers.
+ String jumps; ///< Jumplist.
+ String bufs; ///< Buffer list.
+ String gvars; ///< Global variables.
Array funcs; ///< Functions.
} Context;
typedef kvec_t(Context) ContextVec;
-#define MSGPACK_SBUFFER_INIT (msgpack_sbuffer) { \
- .size = 0, \
- .data = NULL, \
- .alloc = 0, \
-}
-
#define CONTEXT_INIT (Context) { \
- .regs = MSGPACK_SBUFFER_INIT, \
- .jumps = MSGPACK_SBUFFER_INIT, \
- .bufs = MSGPACK_SBUFFER_INIT, \
- .gvars = MSGPACK_SBUFFER_INIT, \
+ .regs = STRING_INIT, \
+ .jumps = STRING_INIT, \
+ .bufs = STRING_INIT, \
+ .gvars = STRING_INIT, \
.funcs = ARRAY_DICT_INIT, \
}
diff --git a/src/nvim/cursor.c b/src/nvim/cursor.c
index 2e9e68843a..35afca2fe9 100644
--- a/src/nvim/cursor.c
+++ b/src/nvim/cursor.c
@@ -216,12 +216,7 @@ static int coladvance2(win_T *wp, pos_T *pos, bool addspaces, bool finetune, col
}
}
- if (idx < 0) {
- pos->col = 0;
- } else {
- pos->col = idx;
- }
-
+ pos->col = MAX(idx, 0);
pos->coladd = 0;
if (finetune) {
@@ -310,15 +305,9 @@ linenr_T get_cursor_rel_lnum(win_T *wp, linenr_T lnum)
/// This allows for the col to be on the NUL byte.
void check_pos(buf_T *buf, pos_T *pos)
{
- if (pos->lnum > buf->b_ml.ml_line_count) {
- pos->lnum = buf->b_ml.ml_line_count;
- }
-
+ pos->lnum = MIN(pos->lnum, buf->b_ml.ml_line_count);
if (pos->col > 0) {
- colnr_T len = ml_get_buf_len(buf, pos->lnum);
- if (pos->col > len) {
- pos->col = len;
- }
+ pos->col = MIN(pos->col, ml_get_buf_len(buf, pos->lnum));
}
}
@@ -385,9 +374,7 @@ void check_cursor_col(win_T *win)
int cs, ce;
getvcol(win, &win->w_cursor, &cs, NULL, &ce);
- if (win->w_cursor.coladd > ce - cs) {
- win->w_cursor.coladd = ce - cs;
- }
+ win->w_cursor.coladd = MIN(win->w_cursor.coladd, ce - cs);
}
} else {
// avoid weird number when there is a miscalculation or overflow
diff --git a/src/nvim/cursor_shape.c b/src/nvim/cursor_shape.c
index 67bb34d4ea..1f11367618 100644
--- a/src/nvim/cursor_shape.c
+++ b/src/nvim/cursor_shape.c
@@ -57,7 +57,7 @@ Array mode_style_array(Arena *arena)
for (int i = 0; i < SHAPE_IDX_COUNT; i++) {
cursorentry_T *cur = &shape_table[i];
- Dictionary dic = arena_dict(arena, 3 + ((cur->used_for & SHAPE_CURSOR) ? 9 : 0));
+ Dict dic = arena_dict(arena, 3 + ((cur->used_for & SHAPE_CURSOR) ? 9 : 0));
PUT_C(dic, "name", CSTR_AS_OBJ(cur->full_name));
PUT_C(dic, "short_name", CSTR_AS_OBJ(cur->name));
if (cur->used_for & SHAPE_MOUSE) {
@@ -86,7 +86,7 @@ Array mode_style_array(Arena *arena)
PUT_C(dic, "attr_id_lm", INTEGER_OBJ(cur->id_lm ? syn_id2attr(cur->id_lm) : 0));
}
- ADD_C(all, DICTIONARY_OBJ(dic));
+ ADD_C(all, DICT_OBJ(dic));
}
return all;
diff --git a/src/nvim/debugger.c b/src/nvim/debugger.c
index 7d87b61ce5..b71ff23f57 100644
--- a/src/nvim/debugger.c
+++ b/src/nvim/debugger.c
@@ -13,6 +13,7 @@
#include "nvim/cmdexpand_defs.h"
#include "nvim/debugger.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
diff --git a/src/nvim/decoration.c b/src/nvim/decoration.c
index 303d0318b5..3633940b14 100644
--- a/src/nvim/decoration.c
+++ b/src/nvim/decoration.c
@@ -15,6 +15,7 @@
#include "nvim/drawscreen.h"
#include "nvim/extmark.h"
#include "nvim/fold.h"
+#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/grid_defs.h"
#include "nvim/highlight.h"
@@ -30,9 +31,6 @@
# include "decoration.c.generated.h"
#endif
-// TODO(bfredl): These should maybe be per-buffer, so that all resources
-// associated with a buffer can be freed when the buffer is unloaded.
-kvec_t(DecorSignHighlight) decor_items = KV_INITIAL_VALUE;
uint32_t decor_freelist = UINT32_MAX;
// Decorations might be requested to be deleted in a callback in the middle of redrawing.
@@ -88,7 +86,7 @@ void bufhl_add_hl_pos_offset(buf_T *buf, int src_id, int hl_id, lpos_T pos_start
extmark_set(buf, (uint32_t)src_id, NULL,
(int)lnum - 1, hl_start, (int)lnum - 1 + end_off, hl_end,
- decor, MT_FLAG_DECOR_HL, true, false, true, false, false, NULL);
+ decor, MT_FLAG_DECOR_HL, true, false, true, false, NULL);
}
}
@@ -184,6 +182,21 @@ void buf_put_decor(buf_T *buf, DecorInline decor, int row, int row2)
}
}
+/// When displaying signs in the 'number' column, if the width of the number
+/// column is less than 2, then force recomputing the width after placing or
+/// unplacing the first sign in "buf".
+static void may_force_numberwidth_recompute(buf_T *buf, bool unplace)
+{
+ FOR_ALL_TAB_WINDOWS(tp, wp) {
+ if (wp->w_buffer == buf
+ && wp->w_minscwidth == SCL_NUM
+ && (wp->w_p_nu || wp->w_p_rnu)
+ && (unplace || wp->w_nrwidth_width < 2)) {
+ wp->w_nrwidth_line_count = 0;
+ }
+ }
+}
+
static int sign_add_id = 0;
void buf_put_decor_sh(buf_T *buf, DecorSignHighlight *sh, int row1, int row2)
{
@@ -191,6 +204,7 @@ void buf_put_decor_sh(buf_T *buf, DecorSignHighlight *sh, int row1, int row2)
sh->sign_add_id = sign_add_id++;
if (sh->text[0]) {
buf_signcols_count_range(buf, row1, row2, 1, kFalse);
+ may_force_numberwidth_recompute(buf, false);
}
}
}
@@ -218,6 +232,7 @@ void buf_remove_decor_sh(buf_T *buf, int row1, int row2, DecorSignHighlight *sh)
if (buf_meta_total(buf, kMTMetaSignText)) {
buf_signcols_count_range(buf, row1, row2, -1, kFalse);
} else {
+ may_force_numberwidth_recompute(buf, true);
buf->b_signcols.resized = true;
buf->b_signcols.max = buf->b_signcols.count[0] = 0;
}
@@ -274,7 +289,7 @@ static void decor_free_inner(DecorVirtText *vt, uint32_t first_idx)
while (idx != DECOR_ID_INVALID) {
DecorSignHighlight *sh = &kv_A(decor_items, idx);
if (sh->flags & kSHIsSign) {
- xfree(sh->sign_name);
+ XFREE_CLEAR(sh->sign_name);
}
sh->flags = 0;
if (sh->url != NULL) {
@@ -580,11 +595,7 @@ int decor_redraw_col(win_T *wp, int col, int win_col, bool hidden, DecorState *s
break;
}
- if (!mt_scoped_in_win(mark, wp)) {
- goto next_mark;
- }
-
- if (mt_invalid(mark) || mt_end(mark) || !mt_decor_any(mark)) {
+ if (mt_invalid(mark) || mt_end(mark) || !mt_decor_any(mark) || !ns_in_win(mark.ns, wp)) {
goto next_mark;
}
@@ -728,8 +739,7 @@ void decor_redraw_signs(win_T *wp, buf_T *buf, int row, SignTextAttrs sattrs[],
if (mark.pos.row != row) {
break;
}
- if (!mt_end(mark) && !mt_invalid(mark) && mt_decor_sign(mark)
- && mt_scoped_in_win(mark, wp)) {
+ if (!mt_invalid(mark) && !mt_end(mark) && mt_decor_sign(mark) && ns_in_win(mark.ns, wp)) {
DecorSignHighlight *sh = decor_find_sign(mt_decor(mark));
num_text += (sh->text[0] != NUL);
kv_push(signs, ((SignItem){ sh, mark.id }));
@@ -877,8 +887,8 @@ bool decor_redraw_eol(win_T *wp, DecorState *state, int *eol_attr, int eol_col)
static const uint32_t lines_filter[4] = {[kMTMetaLines] = kMTFilterSelect };
-/// @param has_fold whether line "lnum" has a fold, or kNone when not calculated yet
-int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fold)
+/// @param apply_folds Only count virtual lines that are not in folds.
+int decor_virt_lines(win_T *wp, int start_row, int end_row, VirtLines *lines, bool apply_folds)
{
buf_T *buf = wp->w_buffer;
if (!buf_meta_total(buf, kMTMetaLines)) {
@@ -887,34 +897,26 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo
return 0;
}
- assert(lnum > 0);
- bool below_fold = lnum > 1 && hasFolding(wp, lnum - 1, NULL, NULL);
- if (has_fold == kNone) {
- has_fold = hasFolding(wp, lnum, NULL, NULL);
- }
-
- const int row = lnum - 1;
- const int start_row = below_fold ? row : MAX(row - 1, 0);
- const int end_row = has_fold ? row : row + 1;
- if (start_row >= end_row) {
- return 0;
- }
-
MarkTreeIter itr[1] = { 0 };
- if (!marktree_itr_get_filter(buf->b_marktree, start_row, 0, end_row, 0, lines_filter, itr)) {
+ if (!marktree_itr_get_filter(buf->b_marktree, MAX(start_row - 1, 0), 0, end_row, 0,
+ lines_filter, itr)) {
return 0;
}
+ assert(start_row >= 0);
+
int virt_lines = 0;
while (true) {
MTKey mark = marktree_itr_current(itr);
DecorVirtText *vt = mt_decor_virt(mark);
- if (mt_scoped_in_win(mark, wp)) {
+ if (!mt_invalid(mark) && ns_in_win(mark.ns, wp)) {
while (vt) {
if (vt->flags & kVTIsLines) {
bool above = vt->flags & kVTLinesAbove;
- int draw_row = mark.pos.row + (above ? 0 : 1);
- if (draw_row == row) {
+ int mrow = mark.pos.row;
+ int draw_row = mrow + (above ? 0 : 1);
+ if (draw_row >= start_row && draw_row < end_row
+ && (!apply_folds || !hasFolding(wp, mrow + 1, NULL, NULL))) {
virt_lines += (int)kv_size(vt->data.virt_lines);
if (lines) {
kv_splice(*lines, vt->data.virt_lines);
@@ -936,7 +938,7 @@ int decor_virt_lines(win_T *wp, linenr_T lnum, VirtLines *lines, TriState has_fo
/// This assumes maximum one entry of each kind, which will not always be the case.
///
/// NB: assumes caller has allocated enough space in dict for all fields!
-void decor_to_dict_legacy(Dictionary *dict, DecorInline decor, bool hl_name, Arena *arena)
+void decor_to_dict_legacy(Dict *dict, DecorInline decor, bool hl_name, Arena *arena)
{
DecorSignHighlight sh_hl = DECOR_SIGN_HIGHLIGHT_INIT;
DecorSignHighlight sh_sign = DECOR_SIGN_HIGHLIGHT_INIT;
diff --git a/src/nvim/decoration.h b/src/nvim/decoration.h
index 86d4de79f0..1b595fb86f 100644
--- a/src/nvim/decoration.h
+++ b/src/nvim/decoration.h
@@ -77,6 +77,9 @@ typedef struct {
} DecorState;
EXTERN DecorState decor_state INIT( = { 0 });
+// TODO(bfredl): These should maybe be per-buffer, so that all resources
+// associated with a buffer can be freed when the buffer is unloaded.
+EXTERN kvec_t(DecorSignHighlight) decor_items INIT( = KV_INITIAL_VALUE);
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "decoration.h.generated.h"
diff --git a/src/nvim/decoration_defs.h b/src/nvim/decoration_defs.h
index c9475257b1..49dc4f9168 100644
--- a/src/nvim/decoration_defs.h
+++ b/src/nvim/decoration_defs.h
@@ -56,6 +56,7 @@ typedef struct {
} DecorHighlightInline;
#define DECOR_HIGHLIGHT_INLINE_INIT { 0, DECOR_PRIORITY_BASE, 0, 0 }
+
typedef struct {
uint16_t flags;
DecorPriority priority;
diff --git a/src/nvim/diff.c b/src/nvim/diff.c
index ea846b46ec..d22fb65827 100644
--- a/src/nvim/diff.c
+++ b/src/nvim/diff.c
@@ -26,6 +26,7 @@
#include "nvim/cursor.h"
#include "nvim/diff.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
@@ -139,7 +140,7 @@ typedef enum {
void diff_buf_delete(buf_T *buf)
{
FOR_ALL_TABS(tp) {
- int i = diff_buf_idx_tp(buf, tp);
+ int i = diff_buf_idx(buf, tp);
if (i != DB_COUNT) {
tp->tp_diffbuf[i] = NULL;
@@ -172,7 +173,7 @@ void diff_buf_adjust(win_T *win)
}
if (!found_win) {
- int i = diff_buf_idx(win->w_buffer);
+ int i = diff_buf_idx(win->w_buffer, curtab);
if (i != DB_COUNT) {
curtab->tp_diffbuf[i] = NULL;
curtab->tp_diff_invalid = true;
@@ -195,7 +196,7 @@ void diff_buf_adjust(win_T *win)
/// @param buf The buffer to add.
void diff_buf_add(buf_T *buf)
{
- if (diff_buf_idx(buf) != DB_COUNT) {
+ if (diff_buf_idx(buf, curtab) != DB_COUNT) {
// It's already there.
return;
}
@@ -212,9 +213,7 @@ void diff_buf_add(buf_T *buf)
semsg(_("E96: Cannot diff more than %" PRId64 " buffers"), (int64_t)DB_COUNT);
}
-///
/// Remove all buffers to make diffs for.
-///
static void diff_buf_clear(void)
{
for (int i = 0; i < DB_COUNT; i++) {
@@ -226,23 +225,13 @@ static void diff_buf_clear(void)
}
}
-/// Find buffer "buf" in the list of diff buffers for the current tab page.
-///
-/// @param buf The buffer to find.
-///
-/// @return Its index or DB_COUNT if not found.
-static int diff_buf_idx(buf_T *buf)
-{
- return diff_buf_idx_tp(buf, curtab);
-}
-
/// Find buffer "buf" in the list of diff buffers for tab page "tp".
///
/// @param buf
/// @param tp
///
/// @return its index or DB_COUNT if not found.
-static int diff_buf_idx_tp(buf_T *buf, tabpage_T *tp)
+static int diff_buf_idx(buf_T *buf, tabpage_T *tp)
{
int idx;
for (idx = 0; idx < DB_COUNT; idx++) {
@@ -260,7 +249,7 @@ static int diff_buf_idx_tp(buf_T *buf, tabpage_T *tp)
void diff_invalidate(buf_T *buf)
{
FOR_ALL_TABS(tp) {
- int i = diff_buf_idx_tp(buf, tp);
+ int i = diff_buf_idx(buf, tp);
if (i != DB_COUNT) {
tp->tp_diff_invalid = true;
if (tp == curtab) {
@@ -281,7 +270,7 @@ void diff_mark_adjust(buf_T *buf, linenr_T line1, linenr_T line2, linenr_T amoun
{
// Handle all tab pages that use "buf" in a diff.
FOR_ALL_TABS(tp) {
- int idx = diff_buf_idx_tp(buf, tp);
+ int idx = diff_buf_idx(buf, tp);
if (idx != DB_COUNT) {
diff_mark_adjust_tp(tp, idx, line1, line2, amount, amount_after);
}
@@ -366,7 +355,6 @@ static void diff_mark_adjust_tp(tabpage_T *tp, int idx, linenr_T line1, linenr_T
break;
}
- //
// Check for these situations:
// 1 2 3
// 1 2 3
@@ -696,7 +684,7 @@ void diff_redraw(bool dofold)
if (((wp != curwin) && (wp->w_topfill > 0)) || (n > 0)) {
if (wp->w_topfill > n) {
- wp->w_topfill = (n < 0 ? 0 : n);
+ wp->w_topfill = MAX(n, 0);
} else if ((n > 0) && (n > wp->w_topfill)) {
wp->w_topfill = n;
if (wp == curwin) {
@@ -748,6 +736,12 @@ static void clear_diffout(diffout_T *dout)
/// @return FAIL for failure.
static int diff_write_buffer(buf_T *buf, mmfile_t *m, linenr_T start, linenr_T end)
{
+ if (buf->b_ml.ml_flags & ML_EMPTY) {
+ m->ptr = NULL;
+ m->size = 0;
+ return OK;
+ }
+
size_t len = 0;
if (end < 0) {
@@ -758,20 +752,7 @@ static int diff_write_buffer(buf_T *buf, mmfile_t *m, linenr_T start, linenr_T e
for (linenr_T lnum = start; lnum <= end; lnum++) {
len += (size_t)ml_get_buf_len(buf, lnum) + 1;
}
- char *ptr = try_malloc(len);
- if (ptr == NULL) {
- // Allocating memory failed. This can happen, because we try to read
- // the whole buffer text into memory. Set the failed flag, the diff
- // will be retried with external diff. The flag is never reset.
- buf->b_diff_failed = true;
- if (p_verbose > 0) {
- verbose_enter();
- smsg(0, _("Not enough memory to use internal diff for buffer \"%s\""),
- buf->b_fname);
- verbose_leave();
- }
- return FAIL;
- }
+ char *ptr = xmalloc(len);
m->ptr = ptr;
m->size = (int)len;
@@ -834,7 +815,6 @@ static int diff_write(buf_T *buf, diffin_T *din)
return r;
}
-///
/// Update the diffs for all buffers involved.
///
/// @param dio
@@ -854,34 +834,33 @@ static void diff_try_update(diffio_T *dio, int idx_orig, exarg_T *eap)
|| dio->dio_diff.dout_fname == NULL) {
goto theend;
}
+ // Check external diff is actually working.
+ if (check_external_diff(dio) == FAIL) {
+ goto theend;
+ }
}
- // Check external diff is actually working.
- if (!dio->dio_internal && check_external_diff(dio) == FAIL) {
- goto theend;
- }
-
- buf_T *buf;
-
// :diffupdate!
if (eap != NULL && eap->forceit) {
for (int idx_new = idx_orig; idx_new < DB_COUNT; idx_new++) {
- buf = curtab->tp_diffbuf[idx_new];
+ buf_T *buf = curtab->tp_diffbuf[idx_new];
if (buf_valid(buf)) {
buf_check_timestamp(buf);
}
}
}
- // Write the first buffer to a tempfile or mmfile_t.
- buf = curtab->tp_diffbuf[idx_orig];
- if (diff_write(buf, &dio->dio_orig) == FAIL) {
- goto theend;
+ {
+ // Write the first buffer to a tempfile or mmfile_t.
+ buf_T *buf = curtab->tp_diffbuf[idx_orig];
+ if (diff_write(buf, &dio->dio_orig) == FAIL) {
+ goto theend;
+ }
}
// Make a difference between the first buffer and every other.
for (int idx_new = idx_orig + 1; idx_new < DB_COUNT; idx_new++) {
- buf = curtab->tp_diffbuf[idx_new];
+ buf_T *buf = curtab->tp_diffbuf[idx_new];
if (buf == NULL || buf->b_ml.ml_mfp == NULL) {
continue; // skip buffer that isn't loaded
}
@@ -908,32 +887,15 @@ theend:
xfree(dio->dio_diff.dout_fname);
}
-///
/// Return true if the options are set to use the internal diff library.
/// Note that if the internal diff failed for one of the buffers, the external
/// diff will be used anyway.
-///
int diff_internal(void)
FUNC_ATTR_PURE
{
return (diff_flags & DIFF_INTERNAL) != 0 && *p_dex == NUL;
}
-///
-/// Return true if the internal diff failed for one of the diff buffers.
-///
-static int diff_internal_failed(void)
-{
- // Only need to do something when there is another buffer.
- for (int idx = 0; idx < DB_COUNT; idx++) {
- if (curtab->tp_diffbuf[idx] != NULL
- && curtab->tp_diffbuf[idx]->b_diff_failed) {
- return true;
- }
- }
- return false;
-}
-
/// Completely update the diffs for the buffers involved.
///
/// When using the external "diff" command the buffers are written to a file,
@@ -981,14 +943,9 @@ void ex_diffupdate(exarg_T *eap)
// Only use the internal method if it did not fail for one of the buffers.
diffio_T diffio;
CLEAR_FIELD(diffio);
- diffio.dio_internal = diff_internal() && !diff_internal_failed();
+ diffio.dio_internal = diff_internal();
diff_try_update(&diffio, idx_orig, eap);
- if (diffio.dio_internal && diff_internal_failed()) {
- // Internal diff failed, use external diff instead.
- CLEAR_FIELD(diffio);
- diff_try_update(&diffio, idx_orig, eap);
- }
// force updating cursor position on screen
curwin->w_valid_cursor.lnum = 0;
@@ -1002,11 +959,9 @@ theend:
}
}
-///
/// Do a quick test if "diff" really works. Otherwise it looks like there
/// are no differences. Can't use the return value, it's non-zero when
/// there are differences.
-///
static int check_external_diff(diffio_T *diffio)
{
// May try twice, first with "-a" and then without.
@@ -1032,10 +987,9 @@ static int check_external_diff(diffio_T *diffio)
io_error = true;
}
fclose(fd);
- fd = NULL;
- if (diff_file(diffio) == OK) {
- fd = os_fopen(diffio->dio_diff.dout_fname, "r");
- }
+ fd = diff_file(diffio) == OK
+ ? os_fopen(diffio->dio_diff.dout_fname, "r")
+ : NULL;
if (fd == NULL) {
io_error = true;
@@ -1090,9 +1044,7 @@ static int check_external_diff(diffio_T *diffio)
return OK;
}
-///
/// Invoke the xdiff function.
-///
static int diff_file_internal(diffio_T *diffio)
{
xpparam_t param;
@@ -1272,10 +1224,10 @@ void ex_diffpatch(exarg_T *eap)
// Delete any .orig or .rej file created.
STRCPY(buf, tmp_new);
- STRCAT(buf, ".orig");
+ strcat(buf, ".orig");
os_remove(buf);
STRCPY(buf, tmp_new);
- STRCAT(buf, ".rej");
+ strcat(buf, ".rej");
os_remove(buf);
// Only continue if the output file was created.
@@ -1287,7 +1239,7 @@ void ex_diffpatch(exarg_T *eap)
} else {
if (curbuf->b_fname != NULL) {
newname = xstrnsave(curbuf->b_fname, strlen(curbuf->b_fname) + 4);
- STRCAT(newname, ".new");
+ strcat(newname, ".new");
}
// don't use a new tab page, each tab page has its own diffs
@@ -1657,6 +1609,7 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne
for (int i = idx_orig; i < idx_new; i++) {
if (curtab->tp_diffbuf[i] != NULL) {
dp->df_lnum[i] -= off;
+ dp->df_count[i] += off;
}
}
dp->df_lnum[idx_new] = hunk->lnum_new;
@@ -1667,11 +1620,7 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne
dp->df_count[idx_new] = (linenr_T)hunk->count_new - off;
} else {
// second overlap of new block with existing block
- dp->df_count[idx_new] += (linenr_T)hunk->count_new - (linenr_T)hunk->count_orig
- + dpl->df_lnum[idx_orig] +
- dpl->df_count[idx_orig]
- - (dp->df_lnum[idx_orig] +
- dp->df_count[idx_orig]);
+ dp->df_count[idx_new] += (linenr_T)hunk->count_new;
}
// Adjust the size of the block to include all the lines to the
@@ -1680,11 +1629,8 @@ static void process_hunk(diff_T **dpp, diff_T **dprevp, int idx_orig, int idx_ne
- (dpl->df_lnum[idx_orig] + dpl->df_count[idx_orig]);
if (off < 0) {
- // new change ends in existing block, adjust the end if not
- // done already
- if (*notsetp) {
- dp->df_count[idx_new] += -off;
- }
+ // new change ends in existing block, adjust the end
+ dp->df_count[idx_new] += -off;
off = 0;
}
@@ -1812,9 +1758,7 @@ void diff_clear(tabpage_T *tp)
tp->tp_first_diff = NULL;
}
-///
-/// return true if the options are set to use diff linematch
-///
+/// Return true if the options are set to use diff linematch.
bool diff_linematch(diff_T *dp)
{
if (!(diff_flags & DIFF_LINEMATCH)) {
@@ -2061,7 +2005,7 @@ static void run_linematch_algorithm(diff_T *dp)
{
// define buffers for diff algorithm
mmfile_t diffbufs_mm[DB_COUNT];
- const char *diffbufs[DB_COUNT];
+ const mmfile_t *diffbufs[DB_COUNT];
int diff_length[DB_COUNT];
size_t ndiffs = 0;
for (int i = 0; i < DB_COUNT; i++) {
@@ -2071,9 +2015,7 @@ static void run_linematch_algorithm(diff_T *dp)
diff_write_buffer(curtab->tp_diffbuf[i], &diffbufs_mm[ndiffs],
dp->df_lnum[i], dp->df_lnum[i] + dp->df_count[i] - 1);
- // we want to get the char* to the diff buffer that was just written
- // we add it to the array of char*, diffbufs
- diffbufs[ndiffs] = diffbufs_mm[ndiffs].ptr;
+ diffbufs[ndiffs] = &diffbufs_mm[ndiffs];
// keep track of the length of this diff block to pass it to the linematch
// algorithm
@@ -2132,7 +2074,7 @@ int diff_check_with_linestatus(win_T *wp, linenr_T lnum, int *linestatus)
return 0;
}
- int idx = diff_buf_idx(buf); // index in tp_diffbuf[] for this buffer
+ int idx = diff_buf_idx(buf, curtab); // index in tp_diffbuf[] for this buffer
if (idx == DB_COUNT) {
// no diffs for buffer "buf"
@@ -2156,7 +2098,11 @@ int diff_check_with_linestatus(win_T *wp, linenr_T lnum, int *linestatus)
return 0;
}
- if (!dp->is_linematched && diff_linematch(dp)) {
+ // Don't run linematch when lnum is offscreen.
+ // Useful for scrollbind calculations which need to count all the filler lines
+ // above the screen.
+ if (lnum >= wp->w_topline && lnum < wp->w_botline
+ && !dp->is_linematched && diff_linematch(dp)) {
run_linematch_algorithm(dp);
}
@@ -2349,7 +2295,7 @@ void diff_set_topline(win_T *fromwin, win_T *towin)
buf_T *frombuf = fromwin->w_buffer;
linenr_T lnum = fromwin->w_topline;
- int fromidx = diff_buf_idx(frombuf);
+ int fromidx = diff_buf_idx(frombuf, curtab);
if (fromidx == DB_COUNT) {
// safety check
return;
@@ -2376,7 +2322,7 @@ void diff_set_topline(win_T *fromwin, win_T *towin)
- (frombuf->b_ml.ml_line_count - lnum);
} else {
// Find index for "towin".
- int toidx = diff_buf_idx(towin->w_buffer);
+ int toidx = diff_buf_idx(towin->w_buffer, curtab);
if (toidx == DB_COUNT) {
// safety check
@@ -2621,7 +2567,7 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp)
// Make a copy of the line, the next ml_get() will invalidate it.
char *line_org = xstrdup(ml_get_buf(wp->w_buffer, lnum));
- int idx = diff_buf_idx(wp->w_buffer);
+ int idx = diff_buf_idx(wp->w_buffer, curtab);
if (idx == DB_COUNT) {
// cannot happen
xfree(line_org);
@@ -2692,9 +2638,7 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp)
si_org -= utf_head_off(line_org, line_org + si_org);
si_new -= utf_head_off(line_new, line_new + si_new);
- if (*startp > si_org) {
- *startp = si_org;
- }
+ *startp = MIN(*startp, si_org);
// Search for end of difference, if any.
if ((line_org[si_org] != NUL) || (line_new[si_new] != NUL)) {
@@ -2734,9 +2678,7 @@ bool diff_find_change(win_T *wp, linenr_T lnum, int *startp, int *endp)
}
}
- if (*endp < ei_org) {
- *endp = ei_org;
- }
+ *endp = MAX(*endp, ei_org);
}
}
}
@@ -2847,7 +2789,7 @@ void ex_diffgetput(exarg_T *eap)
int idx_other;
// Find the current buffer in the list of diff buffers.
- int idx_cur = diff_buf_idx(curbuf);
+ int idx_cur = diff_buf_idx(curbuf, curtab);
if (idx_cur == DB_COUNT) {
emsg(_("E99: Current buffer is not in diff mode"));
return;
@@ -2919,7 +2861,7 @@ void ex_diffgetput(exarg_T *eap)
// nothing to do
return;
}
- idx_other = diff_buf_idx(buf);
+ idx_other = diff_buf_idx(buf, curtab);
if (idx_other == DB_COUNT) {
semsg(_("E103: Buffer \"%s\" is not in diff mode"), eap->arg);
@@ -2961,7 +2903,7 @@ void ex_diffgetput(exarg_T *eap)
// everything up.
if (!curbuf->b_changed) {
change_warning(curbuf, 0);
- if (diff_buf_idx(curbuf) != idx_to) {
+ if (diff_buf_idx(curbuf, curtab) != idx_to) {
emsg(_("E787: Buffer changed unexpectedly"));
goto theend;
}
@@ -3064,19 +3006,11 @@ static void diffgetput(const int addr_count, const int idx_cur, const int idx_fr
// range ends above end of current/from diff block
if (idx_cur == idx_from) {
// :diffput
- int i = dp->df_count[idx_cur] - start_skip - end_skip;
-
- if (count > i) {
- count = i;
- }
+ count = MIN(count, dp->df_count[idx_cur] - start_skip - end_skip);
} else {
// :diffget
count -= end_skip;
- end_skip = dp->df_count[idx_from] - start_skip - count;
-
- if (end_skip < 0) {
- end_skip = 0;
- }
+ end_skip = MAX(dp->df_count[idx_from] - start_skip - count, 0);
}
} else {
end_skip = 0;
@@ -3208,7 +3142,7 @@ bool diff_mode_buf(buf_T *buf)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
{
FOR_ALL_TABS(tp) {
- if (diff_buf_idx_tp(buf, tp) != DB_COUNT) {
+ if (diff_buf_idx(buf, tp) != DB_COUNT) {
return true;
}
}
@@ -3224,7 +3158,7 @@ bool diff_mode_buf(buf_T *buf)
int diff_move_to(int dir, int count)
{
linenr_T lnum = curwin->w_cursor.lnum;
- int idx = diff_buf_idx(curbuf);
+ int idx = diff_buf_idx(curbuf, curtab);
if ((idx == DB_COUNT) || (curtab->tp_first_diff == NULL)) {
return FAIL;
}
@@ -3262,9 +3196,7 @@ int diff_move_to(int dir, int count)
}
// don't end up past the end of the file
- if (lnum > curbuf->b_ml.ml_line_count) {
- lnum = curbuf->b_ml.ml_line_count;
- }
+ lnum = MIN(lnum, curbuf->b_ml.ml_line_count);
// When the cursor didn't move at all we fail.
if (lnum == curwin->w_cursor.lnum) {
@@ -3284,8 +3216,8 @@ static linenr_T diff_get_corresponding_line_int(buf_T *buf1, linenr_T lnum1)
{
linenr_T baseline = 0;
- int idx1 = diff_buf_idx(buf1);
- int idx2 = diff_buf_idx(curbuf);
+ int idx1 = diff_buf_idx(buf1, curtab);
+ int idx2 = diff_buf_idx(curbuf, curtab);
if ((idx1 == DB_COUNT)
|| (idx2 == DB_COUNT)
@@ -3310,10 +3242,7 @@ static linenr_T diff_get_corresponding_line_int(buf_T *buf1, linenr_T lnum1)
if ((dp->df_lnum[idx1] + dp->df_count[idx1]) > lnum1) {
// Inside the diffblock
baseline = lnum1 - dp->df_lnum[idx1];
-
- if (baseline > dp->df_count[idx2]) {
- baseline = dp->df_count[idx2];
- }
+ baseline = MIN(baseline, dp->df_count[idx2]);
return dp->df_lnum[idx2] + baseline;
}
@@ -3348,10 +3277,7 @@ linenr_T diff_get_corresponding_line(buf_T *buf1, linenr_T lnum1)
linenr_T lnum = diff_get_corresponding_line_int(buf1, lnum1);
// don't end up past the end of the file
- if (lnum > curbuf->b_ml.ml_line_count) {
- return curbuf->b_ml.ml_line_count;
- }
- return lnum;
+ return MIN(lnum, curbuf->b_ml.ml_line_count);
}
/// For line "lnum" in the current window find the equivalent lnum in window
@@ -3360,7 +3286,7 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp)
{
diff_T *dp;
- int idx = diff_buf_idx(curbuf);
+ int idx = diff_buf_idx(curbuf, curtab);
if (idx == DB_COUNT) {
// safety check
@@ -3386,7 +3312,7 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp)
}
// Find index for "wp".
- int i = diff_buf_idx(wp->w_buffer);
+ int i = diff_buf_idx(wp->w_buffer, curtab);
if (i == DB_COUNT) {
// safety check
@@ -3394,10 +3320,7 @@ linenr_T diff_lnum_win(linenr_T lnum, win_T *wp)
}
linenr_T n = lnum + (dp->df_lnum[i] - dp->df_lnum[idx]);
- if (n > dp->df_lnum[i] + dp->df_count[i]) {
- n = dp->df_lnum[i] + dp->df_count[i];
- }
- return n;
+ return MIN(n, dp->df_lnum[i] + dp->df_count[i]);
}
/// Handle an ED style diff line.
diff --git a/src/nvim/digraph.c b/src/nvim/digraph.c
index a358a1723a..7413d33fe4 100644
--- a/src/nvim/digraph.c
+++ b/src/nvim/digraph.c
@@ -12,6 +12,7 @@
#include "nvim/charset.h"
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds_defs.h"
@@ -1864,7 +1865,7 @@ static void printdigraph(const digr_T *dp, result_T *previous)
p = buf;
// add a space to draw a composing char on
- if (utf_iscomposing(dp->result)) {
+ if (utf_iscomposing_first(dp->result)) {
*p++ = ' ';
}
p += utf_char2bytes(dp->result, p);
@@ -2197,7 +2198,7 @@ bool get_keymap_str(win_T *wp, char *fmt, char *buf, int len)
curwin = wp;
STRCPY(buf, "b:keymap_name"); // must be writable
emsg_skip++;
- char *s = p = eval_to_string(buf, false);
+ char *s = p = eval_to_string(buf, false, false);
emsg_skip--;
curbuf = old_curbuf;
curwin = old_curwin;
diff --git a/src/nvim/drawline.c b/src/nvim/drawline.c
index 07944081da..3b88dd2e90 100644
--- a/src/nvim/drawline.c
+++ b/src/nvim/drawline.c
@@ -170,28 +170,26 @@ static void margin_columns_win(win_T *wp, int *left_col, int *right_col)
// cache previous calculations depending on w_virtcol
static int saved_w_virtcol;
static win_T *prev_wp;
+ static int prev_width1;
+ static int prev_width2;
static int prev_left_col;
static int prev_right_col;
- static int prev_col_off;
int cur_col_off = win_col_off(wp);
- int width1;
- int width2;
+ int width1 = wp->w_width_inner - cur_col_off;
+ int width2 = width1 + win_col_off2(wp);
if (saved_w_virtcol == wp->w_virtcol && prev_wp == wp
- && prev_col_off == cur_col_off) {
+ && prev_width1 == width1 && prev_width2 == width2) {
*right_col = prev_right_col;
*left_col = prev_left_col;
return;
}
- width1 = wp->w_width_inner - cur_col_off;
- width2 = width1 + win_col_off2(wp);
-
*left_col = 0;
*right_col = width1;
- if (wp->w_virtcol >= (colnr_T)width1) {
+ if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) {
*right_col = width1 + ((wp->w_virtcol - width1) / width2 + 1) * width2;
}
if (wp->w_virtcol >= (colnr_T)width1 && width2 > 0) {
@@ -202,8 +200,9 @@ static void margin_columns_win(win_T *wp, int *left_col, int *right_col)
prev_left_col = *left_col;
prev_right_col = *right_col;
prev_wp = wp;
+ prev_width1 = width1;
+ prev_width2 = width2;
saved_w_virtcol = wp->w_virtcol;
- prev_col_off = cur_col_off;
}
/// Put a single char from an UTF-8 buffer into a line buffer.
@@ -465,6 +464,7 @@ static void draw_sign(bool nrcol, win_T *wp, winlinevars_T *wlv, int sign_idx, i
int fill = nrcol ? number_width(wp) + 1 : SIGN_WIDTH;
draw_col_fill(wlv, schar_from_ascii(' '), fill, attr);
int sign_pos = wlv->off - SIGN_WIDTH - (int)nrcol;
+ assert(sign_pos >= 0);
linebuf_char[sign_pos] = sattr.text[0];
linebuf_char[sign_pos + 1] = sattr.text[1];
} else {
@@ -586,12 +586,13 @@ static void draw_statuscol(win_T *wp, winlinevars_T *wlv, linenr_T lnum, int vir
char buf[MAXPATHL];
// When a buffer's line count has changed, make a best estimate for the full
- // width of the status column by building with "w_nrwidth_line_count". Add
- // potentially truncated width and rebuild before drawing anything.
+ // width of the status column by building with the largest possible line number.
+ // Add potentially truncated width and rebuild before drawing anything.
if (wp->w_statuscol_line_count != wp->w_nrwidth_line_count) {
wp->w_statuscol_line_count = wp->w_nrwidth_line_count;
set_vim_var_nr(VV_VIRTNUM, 0);
- int width = build_statuscol_str(wp, wp->w_nrwidth_line_count, 0, buf, stcp);
+ int width = build_statuscol_str(wp, wp->w_nrwidth_line_count,
+ wp->w_nrwidth_line_count, buf, stcp);
if (width > stcp->width) {
int addwidth = MIN(width - stcp->width, MAX_STCWIDTH - stcp->width);
wp->w_nrwidth += addwidth;
@@ -884,9 +885,7 @@ static int get_rightmost_vcol(win_T *wp, const int *color_cols)
if (color_cols) {
// determine rightmost colorcolumn to possibly draw
for (int i = 0; color_cols[i] >= 0; i++) {
- if (ret < color_cols[i]) {
- ret = color_cols[i];
- }
+ ret = MAX(ret, color_cols[i]);
}
}
@@ -1156,7 +1155,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
area_highlighting = true;
}
VirtLines virt_lines = KV_INITIAL_VALUE;
- wlv.n_virt_lines = decor_virt_lines(wp, lnum, &virt_lines, has_fold);
+ wlv.n_virt_lines = decor_virt_lines(wp, lnum - 1, lnum, &virt_lines, true);
wlv.filler_lines += wlv.n_virt_lines;
if (lnum == wp->w_topline) {
wlv.filler_lines = wp->w_topfill;
@@ -1421,7 +1420,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
line = ml_get_buf(wp->w_buffer, lnum);
ptr = line + linecol;
- if (len == 0 || (int)wp->w_cursor.col > ptr - line) {
+ if (len == 0 || wp->w_cursor.col > linecol) {
// no bad word found at line start, don't check until end of a
// word
spell_hlf = HLF_COUNT;
@@ -1551,7 +1550,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
// When only updating the columns and that's done, stop here.
if (col_rows > 0) {
- wlv_put_linebuf(wp, &wlv, wlv.off, false, bg_attr, 0);
+ wlv_put_linebuf(wp, &wlv, MIN(wlv.off, grid->cols), false, bg_attr, 0);
// Need to update more screen lines if:
// - 'statuscolumn' needs to be drawn, or
// - LineNrAbove or LineNrBelow is used, or
@@ -1596,6 +1595,10 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
// hide virt_text on text hidden by 'nowrap' or 'smoothscroll'
decor_redraw_col(wp, (colnr_T)(ptr - line) - 1, wlv.off, true, &decor_state);
}
+ if (wlv.col >= grid->cols) {
+ wlv.col = wlv.off = grid->cols;
+ goto end_check;
+ }
}
if (cul_screenline && wlv.filler_todo <= 0
@@ -1822,7 +1825,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
// If a double-width char doesn't fit display a '>' in the last column.
// Don't advance the pointer but put the character at the start of the next line.
- if (wlv.col >= grid->cols - 1 && utf_char2cells(mb_c) == 2) {
+ if (wlv.col >= grid->cols - 1 && schar_cells(mb_schar) == 2) {
mb_c = '>';
mb_l = 1;
(void)mb_l;
@@ -1918,7 +1921,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
// If a double-width char doesn't fit display a '>' in the
// last column; the character is displayed at the start of the
// next line.
- if (wlv.col >= grid->cols - 1 && utf_char2cells(mb_c) == 2) {
+ if (wlv.col >= grid->cols - 1 && schar_cells(mb_schar) == 2) {
mb_schar = schar_from_ascii('>');
mb_c = '>';
mb_l = 1;
@@ -2389,6 +2392,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
|| (decor_conceal && decor_state.conceal_char)
|| wp->w_p_cole == 1)
&& wp->w_p_cole != 3) {
+ if (schar_cells(mb_schar) > 1) {
+ // When the first char to be concealed is double-width,
+ // need to advance one more virtual column.
+ wlv.n_extra++;
+ }
+
// First time at this concealed item: display one
// character.
if (has_match_conc && match_conc) {
@@ -2406,12 +2415,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
mb_schar = schar_from_ascii(' ');
}
- if (utf_char2cells(mb_c) > 1) {
- // When the first char to be concealed is double-width,
- // need to advance one more virtual column.
- wlv.n_extra++;
- }
-
mb_c = schar_get_first_codepoint(mb_schar);
prev_syntax_id = syntax_seqnr;
@@ -2480,7 +2483,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
&& mb_schar != NUL) {
mb_schar = wp->w_p_lcs_chars.prec;
lcs_prec_todo = NUL;
- if (utf_char2cells(mb_c) > 1) {
+ if (schar_cells(mb_schar) > 1) {
// Double-width character being overwritten by the "precedes"
// character, need to fill up half the character.
wlv.sc_extra = schar_from_ascii(MB_FILLER_CHAR);
@@ -2554,9 +2557,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
// Highlight 'cursorcolumn' & 'colorcolumn' past end of the line.
// check if line ends before left margin
- if (wlv.vcol < start_col + wlv.col - win_col_off(wp)) {
- wlv.vcol = start_col + wlv.col - win_col_off(wp);
- }
+ wlv.vcol = MAX(wlv.vcol, start_col + wlv.col - win_col_off(wp));
// Get rid of the boguscols now, we want to draw until the right
// edge for 'cursorcolumn'.
wlv.col -= wlv.boguscols;
@@ -2650,13 +2651,6 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
conceal_cursor_used = conceal_cursor_line(curwin);
}
- // When the window is too narrow draw all "@" lines.
- if (leftcols_width >= wp->w_grid.cols && is_wrapped) {
- win_draw_end(wp, schar_from_ascii('@'), true, wlv.row, wp->w_grid.rows, HLF_AT);
- set_empty_rows(wp, wlv.row);
- wlv.row = endrow;
- }
-
break;
}
@@ -2730,7 +2724,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
linebuf_vcol[wlv.off] = wlv.vcol;
- if (utf_char2cells(mb_c) > 1) {
+ if (schar_cells(mb_schar) > 1) {
// Need to fill two screen columns.
wlv.off++;
wlv.col++;
@@ -2749,7 +2743,7 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
wlv.off++;
wlv.col++;
} else if (wp->w_p_cole > 0 && is_concealing) {
- bool concealed_wide = utf_char2cells(mb_c) > 1;
+ bool concealed_wide = schar_cells(mb_schar) > 1;
wlv.skip_cells--;
wlv.vcol_off_co++;
@@ -2844,10 +2838,12 @@ int win_line(win_T *wp, linenr_T lnum, int startrow, int endrow, int col_rows, s
}
}
+end_check:
// At end of screen line and there is more to come: Display the line
// so far. If there is no more to display it is caught above.
if (wlv.col >= grid->cols && (!has_foldtext || virt_line_offset >= 0)
- && (*ptr != NUL
+ && (wlv.col <= leftcols_width
+ || *ptr != NUL
|| wlv.filler_todo > 0
|| (wp->w_p_list && wp->w_p_lcs_chars.eol != NUL && lcs_eol_todo)
|| (wlv.n_extra != 0 && (wlv.sc_extra != NUL || *wlv.p_extra != NUL))
diff --git a/src/nvim/drawscreen.c b/src/nvim/drawscreen.c
index bda0ccc870..aa5c66465b 100644
--- a/src/nvim/drawscreen.c
+++ b/src/nvim/drawscreen.c
@@ -65,6 +65,7 @@
#include "nvim/autocmd.h"
#include "nvim/autocmd_defs.h"
#include "nvim/buffer.h"
+#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/cursor.h"
@@ -394,18 +395,9 @@ void screen_resize(int width, int height)
void check_screensize(void)
{
// Limit Rows and Columns to avoid an overflow in Rows * Columns.
- if (Rows < min_rows()) {
- // need room for one window and command line
- Rows = min_rows();
- } else if (Rows > 1000) {
- Rows = 1000;
- }
-
- if (Columns < MIN_COLUMNS) {
- Columns = MIN_COLUMNS;
- } else if (Columns > 10000) {
- Columns = 10000;
- }
+ // need room for one window and command line
+ Rows = MIN(MAX(Rows, min_rows()), 1000);
+ Columns = MIN(MAX(Columns, MIN_COLUMNS), 10000);
}
/// Return true if redrawing should currently be done.
@@ -715,14 +707,17 @@ void end_search_hl(void)
screen_search_hl.rm.regprog = NULL;
}
-static void win_redr_bordertext(win_T *wp, VirtText vt, int col)
+static void win_redr_bordertext(win_T *wp, VirtText vt, int col, BorderTextType bt)
{
for (size_t i = 0; i < kv_size(vt);) {
- int attr = 0;
+ int attr = -1;
char *text = next_virt_text_chunk(vt, &i, &attr);
if (text == NULL) {
break;
}
+ if (attr == -1) { // No highlight specified.
+ attr = wp->w_ns_hl_attr[bt == kBorderTextTitle ? HLF_BTITLE : HLF_BFOOTER];
+ }
attr = hl_apply_winblend(wp, attr);
col += grid_line_puts(col, text, -1, attr);
}
@@ -773,7 +768,7 @@ static void win_redr_border(win_T *wp)
if (wp->w_config.title) {
int title_col = win_get_bordertext_col(icol, wp->w_config.title_width,
wp->w_config.title_pos);
- win_redr_bordertext(wp, wp->w_config.title_chunks, title_col);
+ win_redr_bordertext(wp, wp->w_config.title_chunks, title_col, kBorderTextTitle);
}
if (adj[1]) {
grid_line_put_schar(icol + adj[3], chars[2], attrs[2]);
@@ -809,7 +804,7 @@ static void win_redr_border(win_T *wp)
if (wp->w_config.footer) {
int footer_col = win_get_bordertext_col(icol, wp->w_config.footer_width,
wp->w_config.footer_pos);
- win_redr_bordertext(wp, wp->w_config.footer_chunks, footer_col);
+ win_redr_bordertext(wp, wp->w_config.footer_chunks, footer_col, kBorderTextFooter);
}
if (adj[1]) {
grid_line_put_schar(icol + adj[3], chars[4], attrs[4]);
@@ -927,13 +922,7 @@ int showmode(void)
msg_ext_clear(true);
}
- // Don't make non-flushed message part of the showmode and reset global
- // variables before flushing to to avoid recursiveness.
- bool draw_mode = redraw_mode;
- bool clear_cmd = clear_cmdline;
- redraw_cmdline = false;
- redraw_mode = false;
- clear_cmdline = false;
+ // Don't make non-flushed message part of the showmode.
msg_ext_ui_flush();
msg_grid_validate();
@@ -956,8 +945,8 @@ int showmode(void)
msg_check_for_delay(false);
// if the cmdline is more than one line high, erase top lines
- bool need_clear = clear_cmd;
- if (clear_cmd && cmdline_row < Rows - 1) {
+ bool need_clear = clear_cmdline;
+ if (clear_cmdline && cmdline_row < Rows - 1) {
msg_clr_cmdline(); // will reset clear_cmdline
}
@@ -998,12 +987,9 @@ int showmode(void)
}
if (edit_submode_extra != NULL) {
msg_puts_attr(" ", attr); // Add a space in between.
- int sub_attr;
- if (edit_submode_highl < HLF_COUNT) {
- sub_attr = win_hl_attr(curwin, (int)edit_submode_highl);
- } else {
- sub_attr = attr;
- }
+ int sub_attr = edit_submode_highl < HLF_COUNT
+ ? win_hl_attr(curwin, (int)edit_submode_highl)
+ : attr;
msg_puts_attr(edit_submode_extra, sub_attr);
}
}
@@ -1079,7 +1065,7 @@ int showmode(void)
}
mode_displayed = true;
- if (need_clear || clear_cmd || draw_mode) {
+ if (need_clear || clear_cmdline || redraw_mode) {
msg_clr_eos();
}
msg_didout = false; // overwrite this message
@@ -1088,10 +1074,10 @@ int showmode(void)
msg_no_more = false;
lines_left = save_lines_left;
need_wait_return = nwr_save; // never ask for hit-return for this
- } else if (clear_cmd && msg_silent == 0) {
+ } else if (clear_cmdline && msg_silent == 0) {
// Clear the whole command line. Will reset "clear_cmdline".
msg_clr_cmdline();
- } else if (draw_mode) {
+ } else if (redraw_mode) {
msg_pos_mode();
msg_clr_eos();
}
@@ -1114,6 +1100,10 @@ int showmode(void)
grid_line_flush();
}
+ redraw_cmdline = false;
+ redraw_mode = false;
+ clear_cmdline = false;
+
return length;
}
@@ -1544,6 +1534,7 @@ static void win_update(win_T *wp)
// Force redraw when width of 'number' or 'relativenumber' column changes.
if (wp->w_nrwidth != nrwidth_new) {
type = UPD_NOT_VALID;
+ changed_line_abv_curs_win(wp);
wp->w_nrwidth = nrwidth_new;
} else {
// Set mod_top to the first line that needs displaying because of
@@ -1561,9 +1552,7 @@ static void win_update(win_T *wp)
// in a pattern match.
if (syntax_present(wp)) {
mod_top -= buf->b_s.b_syn_sync_linebreaks;
- if (mod_top < 1) {
- mod_top = 1;
- }
+ mod_top = MAX(mod_top, 1);
}
}
if (mod_bot == 0 || mod_bot < buf->b_mod_bot) {
@@ -1590,6 +1579,18 @@ static void win_update(win_T *wp)
}
}
}
+
+ if (search_hl_has_cursor_lnum > 0) {
+ // CurSearch was used last time, need to redraw the line with it to
+ // avoid having two matches highlighted with CurSearch.
+ if (mod_top == 0 || mod_top > search_hl_has_cursor_lnum) {
+ mod_top = search_hl_has_cursor_lnum;
+ }
+ if (mod_bot == 0 || mod_bot < search_hl_has_cursor_lnum + 1) {
+ mod_bot = search_hl_has_cursor_lnum + 1;
+ }
+ }
+
if (mod_top != 0 && hasAnyFolding(wp)) {
// A change in a line can cause lines above it to become folded or
// unfolded. Find the top most buffer line that may be affected.
@@ -1620,17 +1621,13 @@ static void win_update(win_T *wp)
}
hasFolding(wp, mod_top, &mod_top, NULL);
- if (mod_top > lnumt) {
- mod_top = lnumt;
- }
+ mod_top = MIN(mod_top, lnumt);
// Now do the same for the bottom line (one above mod_bot).
mod_bot--;
hasFolding(wp, mod_bot, NULL, &mod_bot);
mod_bot++;
- if (mod_bot < lnumb) {
- mod_bot = lnumb;
- }
+ mod_bot = MAX(mod_bot, lnumb);
}
// When a change starts above w_topline and the end is below
@@ -1648,6 +1645,7 @@ static void win_update(win_T *wp)
wp->w_redraw_top = 0; // reset for next time
wp->w_redraw_bot = 0;
+ search_hl_has_cursor_lnum = 0;
// When only displaying the lines at the top, set top_end. Used when
// window has scrolled down for msg_scrolled.
@@ -1807,8 +1805,10 @@ static void win_update(win_T *wp)
// Correct the first entry for filler lines at the top
// when it won't get updated below.
if (win_may_fill(wp) && bot_start > 0) {
- wp->w_lines[0].wl_size = (uint16_t)(plines_win_nofill(wp, wp->w_topline, true)
- + wp->w_topfill);
+ int n = plines_win_nofill(wp, wp->w_topline, false) + wp->w_topfill
+ - adjust_plines_for_skipcol(wp);
+ n = MIN(n, wp->w_height_inner);
+ wp->w_lines[0].wl_size = (uint16_t)n;
}
}
}
@@ -1849,18 +1849,8 @@ static void win_update(win_T *wp)
to = curwin->w_cursor.lnum;
}
// redraw more when the cursor moved as well
- if (wp->w_old_cursor_lnum < from) {
- from = wp->w_old_cursor_lnum;
- }
- if (wp->w_old_cursor_lnum > to) {
- to = wp->w_old_cursor_lnum;
- }
- if (wp->w_old_visual_lnum < from) {
- from = wp->w_old_visual_lnum;
- }
- if (wp->w_old_visual_lnum > to) {
- to = wp->w_old_visual_lnum;
- }
+ from = MIN(MIN(from, wp->w_old_cursor_lnum), wp->w_old_visual_lnum);
+ to = MAX(MAX(to, wp->w_old_cursor_lnum), wp->w_old_visual_lnum);
} else {
// Find the line numbers that need to be updated: The lines
// between the old cursor position and the current cursor
@@ -1882,15 +1872,8 @@ static void win_update(win_T *wp)
&& wp->w_old_visual_lnum != 0) {
from = wp->w_old_visual_lnum;
}
- if (wp->w_old_visual_lnum > to) {
- to = wp->w_old_visual_lnum;
- }
- if (VIsual.lnum < from) {
- from = VIsual.lnum;
- }
- if (VIsual.lnum > to) {
- to = VIsual.lnum;
- }
+ to = MAX(MAX(to, wp->w_old_visual_lnum), VIsual.lnum);
+ from = MIN(from, VIsual.lnum);
}
}
@@ -1925,9 +1908,7 @@ static void win_update(win_T *wp)
pos.col = (colnr_T)strlen(ml_get_buf(wp->w_buffer, pos.lnum));
getvvcol(wp, &pos, NULL, NULL, &t);
- if (toc < t) {
- toc = t;
- }
+ toc = MAX(toc, t);
}
toc++;
} else {
@@ -1937,12 +1918,8 @@ static void win_update(win_T *wp)
if (fromc != wp->w_old_cursor_fcol
|| toc != wp->w_old_cursor_lcol) {
- if (from > VIsual.lnum) {
- from = VIsual.lnum;
- }
- if (to < VIsual.lnum) {
- to = VIsual.lnum;
- }
+ from = MIN(from, VIsual.lnum);
+ to = MAX(to, VIsual.lnum);
}
wp->w_old_cursor_fcol = fromc;
wp->w_old_cursor_lcol = toc;
@@ -1959,19 +1936,13 @@ static void win_update(win_T *wp)
}
// There is no need to update lines above the top of the window.
- if (from < wp->w_topline) {
- from = wp->w_topline;
- }
+ from = MAX(from, wp->w_topline);
// If we know the value of w_botline, use it to restrict the update to
// the lines that are visible in the window.
if (wp->w_valid & VALID_BOTLINE) {
- if (from >= wp->w_botline) {
- from = wp->w_botline - 1;
- }
- if (to >= wp->w_botline) {
- to = wp->w_botline - 1;
- }
+ from = MIN(from, wp->w_botline - 1);
+ to = MIN(to, wp->w_botline - 1);
}
// Find the minimal part to be updated.
@@ -2159,11 +2130,9 @@ static void win_update(win_T *wp)
if (hasFolding(wp, l, NULL, &l)) {
new_rows++;
} else if (l == wp->w_topline) {
- int n = plines_win_nofill(wp, l, false) + wp->w_topfill;
- n -= adjust_plines_for_skipcol(wp);
- if (n > wp->w_height_inner) {
- n = wp->w_height_inner;
- }
+ int n = plines_win_nofill(wp, l, false) + wp->w_topfill
+ - adjust_plines_for_skipcol(wp);
+ n = MIN(n, wp->w_height_inner);
new_rows += n;
} else {
new_rows += plines_win(wp, l, true);
@@ -2228,16 +2197,12 @@ static void win_update(win_T *wp)
x += wp->w_lines[j++].wl_size;
i++;
}
- if (bot_start > x) {
- bot_start = x;
- }
+ bot_start = MIN(bot_start, x);
} else { // j > i
// move entries in w_lines[] downwards
j -= i;
wp->w_lines_valid += (linenr_T)j;
- if (wp->w_lines_valid > wp->w_grid.rows) {
- wp->w_lines_valid = wp->w_grid.rows;
- }
+ wp->w_lines_valid = MIN(wp->w_lines_valid, wp->w_grid.rows);
for (i = wp->w_lines_valid; i - j >= idx; i--) {
wp->w_lines[i] = wp->w_lines[i - j];
}
@@ -2369,9 +2334,7 @@ redr_statuscol:
wp->w_last_cursor_lnum_rnu = wp->w_p_rnu ? wp->w_cursor.lnum : 0;
- if (idx > wp->w_lines_valid) {
- wp->w_lines_valid = idx;
- }
+ wp->w_lines_valid = MAX(wp->w_lines_valid, idx);
// Let the syntax stuff know we stop parsing here.
if (syntax_last_parsed != 0 && syntax_present(wp)) {
@@ -2492,10 +2455,12 @@ redr_statuscol:
recursive = true;
curwin->w_valid &= ~VALID_TOPLINE;
update_topline(curwin); // may invalidate w_botline again
+ // New redraw either due to updated topline or reset skipcol.
if (must_redraw != 0) {
// Don't update for changes in buffer again.
int mod_set = curbuf->b_mod_set;
curbuf->b_mod_set = false;
+ curs_columns(curwin, true);
win_update(curwin);
must_redraw = 0;
curbuf->b_mod_set = mod_set;
@@ -2586,10 +2551,7 @@ int compute_foldcolumn(win_T *wp, int col)
int wmw = wp == curwin && p_wmw == 0 ? 1 : (int)p_wmw;
int wwidth = wp->w_grid.cols;
- if (fdc > wwidth - (col + wmw)) {
- fdc = wwidth - (col + wmw);
- }
- return fdc;
+ return MIN(fdc, wwidth - (col + wmw));
}
/// Return the width of the 'number' and 'relativenumber' column.
@@ -2626,9 +2588,7 @@ int number_width(win_T *wp)
} while (lnum > 0);
// 'numberwidth' gives the minimal width plus one
- if (n < wp->w_p_nuw - 1) {
- n = (int)wp->w_p_nuw - 1;
- }
+ n = MAX(n, (int)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.
@@ -2653,9 +2613,7 @@ void redraw_later(win_T *wp, int type)
if (type >= UPD_NOT_VALID) {
wp->w_lines_valid = 0;
}
- if (must_redraw < type) { // must_redraw is the maximum of all windows
- must_redraw = type;
- }
+ must_redraw = MAX(must_redraw, type); // must_redraw is the maximum of all windows
}
}
@@ -2673,8 +2631,8 @@ void redraw_all_later(int type)
/// or it is currently not allowed.
void set_must_redraw(int type)
{
- if (!redraw_not_allowed && must_redraw < type) {
- must_redraw = type;
+ if (!redraw_not_allowed) {
+ must_redraw = MAX(must_redraw, type);
}
}
diff --git a/src/nvim/drawscreen.h b/src/nvim/drawscreen.h
index f804345bca..36ba8099fd 100644
--- a/src/nvim/drawscreen.h
+++ b/src/nvim/drawscreen.h
@@ -25,7 +25,11 @@ EXTERN bool updating_screen INIT( = false);
/// must_redraw to be set.
EXTERN bool redraw_not_allowed INIT( = false);
-EXTERN match_T screen_search_hl INIT( = { 0 }); ///< used for 'hlsearch' highlight matching
+/// used for 'hlsearch' highlight matching
+EXTERN match_T screen_search_hl INIT( = { 0 });
+
+/// last lnum where CurSearch was displayed
+EXTERN linenr_T search_hl_has_cursor_lnum INIT( = 0);
#define W_ENDCOL(wp) ((wp)->w_wincol + (wp)->w_width)
#define W_ENDROW(wp) ((wp)->w_winrow + (wp)->w_height)
diff --git a/src/nvim/edit.c b/src/nvim/edit.c
index 220b92d099..f06dc124f0 100644
--- a/src/nvim/edit.c
+++ b/src/nvim/edit.c
@@ -18,6 +18,7 @@
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
@@ -135,6 +136,8 @@ static TriState dont_sync_undo = kFalse; // CTRL-G U prevents syncing undo
static linenr_T o_lnum = 0;
+static kvec_t(char) replace_stack = KV_INITIAL_VALUE;
+
static void insert_enter(InsertState *s)
{
s->did_backspace = true;
@@ -270,11 +273,7 @@ static void insert_enter(InsertState *s)
if (restart_edit != 0 && stuff_empty()) {
// After a paste we consider text typed to be part of the insert for
// the pasted text. You can backspace over the pasted text too.
- if (where_paste_started.lnum) {
- arrow_used = false;
- } else {
- arrow_used = true;
- }
+ arrow_used = where_paste_started.lnum == 0;
restart_edit = 0;
// If the cursor was after the end-of-line before the CTRL-O and it is
@@ -467,13 +466,15 @@ static int insert_check(VimState *state)
&& !curwin->w_p_sms
&& !s->did_backspace
&& curwin->w_topline == s->old_topline
- && curwin->w_topfill == s->old_topfill) {
+ && curwin->w_topfill == s->old_topfill
+ && s->count <= 1) {
s->mincol = curwin->w_wcol;
validate_cursor_col(curwin);
if (curwin->w_wcol < s->mincol - tabstop_at(get_nolist_virtcol(),
curbuf->b_p_ts,
- curbuf->b_p_vts_array)
+ curbuf->b_p_vts_array,
+ false)
&& curwin->w_wrow == curwin->w_height_inner - 1 - get_scrolloff_value(curwin)
&& (curwin->w_cursor.lnum != curwin->w_topline
|| curwin->w_topfill > 0)) {
@@ -488,11 +489,15 @@ static int insert_check(VimState *state)
}
// May need to adjust w_topline to show the cursor.
- update_topline(curwin);
+ if (s->count <= 1) {
+ update_topline(curwin);
+ }
s->did_backspace = false;
- validate_cursor(curwin); // may set must_redraw
+ if (s->count <= 1) {
+ validate_cursor(curwin); // may set must_redraw
+ }
// Redraw the display when no characters are waiting.
// Also shows mode, ruler and positions cursor.
@@ -506,7 +511,9 @@ static int insert_check(VimState *state)
do_check_cursorbind();
}
- update_curswant();
+ if (s->count <= 1) {
+ update_curswant();
+ }
s->old_topline = curwin->w_topline;
s->old_topfill = curwin->w_topfill;
@@ -900,6 +907,10 @@ static int insert_handle_key(InsertState *s)
case K_IGNORE: // Something mapped to nothing
break;
+ case K_PASTE_START:
+ paste_repeat(1);
+ goto check_pum;
+
case K_EVENT: // some event
state_handle_k_event();
// If CTRL-G U was used apply it to the next typed key.
@@ -1539,9 +1550,7 @@ static void init_prompt(int cmdchar_todo)
if (cmdchar_todo == 'A') {
coladvance(curwin, MAXCOL);
}
- if (curwin->w_cursor.col < (colnr_T)strlen(prompt)) {
- curwin->w_cursor.col = (colnr_T)strlen(prompt);
- }
+ curwin->w_cursor.col = MAX(curwin->w_cursor.col, (colnr_T)strlen(prompt));
// Make sure the cursor is in a valid position.
check_cursor(curwin);
}
@@ -1576,7 +1585,7 @@ void edit_unputchar(void)
/// text. Only works when cursor is in the line that changes.
void display_dollar(colnr_T col_arg)
{
- colnr_T col = col_arg < 0 ? 0 : col_arg;
+ colnr_T col = MAX(col_arg, 0);
if (!redrawing()) {
return;
@@ -1615,9 +1624,8 @@ void undisplay_dollar(void)
/// type == INDENT_SET set indent to "amount"
///
/// @param round if true, round the indent to 'shiftwidth' (only with _INC and _Dec).
-/// @param replaced replaced character, put on replace stack
/// @param call_changed_bytes call changed_bytes()
-void change_indent(int type, int amount, int round, int replaced, bool call_changed_bytes)
+void change_indent(int type, int amount, int round, bool call_changed_bytes)
{
int insstart_less; // reduction for Insstart.col
colnr_T orig_col = 0; // init for GCC
@@ -1734,12 +1742,7 @@ void change_indent(int type, int amount, int round, int replaced, bool call_chan
}
curwin->w_p_list = save_p_list;
-
- if (new_cursor_col <= 0) {
- curwin->w_cursor.col = 0;
- } else {
- curwin->w_cursor.col = (colnr_T)new_cursor_col;
- }
+ curwin->w_cursor.col = MAX(0, (colnr_T)new_cursor_col);
curwin->w_set_curswant = true;
changed_cline_bef_curs(curwin);
@@ -1769,12 +1772,8 @@ void change_indent(int type, int amount, int round, int replaced, bool call_chan
replace_join(0); // remove a NUL from the replace stack
start_col--;
}
- while (start_col < (int)curwin->w_cursor.col || replaced) {
- replace_push(NUL);
- if (replaced) {
- replace_push(replaced);
- replaced = NUL;
- }
+ while (start_col < (int)curwin->w_cursor.col) {
+ replace_push_nul();
start_col++;
}
}
@@ -2025,7 +2024,6 @@ static void insert_special(int c, int allow_modmask, int ctrlv)
// '0' and '^' are special, because they can be followed by CTRL-D.
#define ISSPECIAL(c) ((c) < ' ' || (c) >= DEL || (c) == '0' || (c) == '^')
-///
/// "flags": INSCHAR_FORMAT - force formatting
/// INSCHAR_CTRLV - char typed just after CTRL-V
/// INSCHAR_NO_FEX - don't use 'formatexpr'
@@ -2328,7 +2326,7 @@ int stop_arrow(void)
static void stop_insert(pos_T *end_insert_pos, int esc, int nomove)
{
stop_redo_ins();
- replace_flush(); // abandon replace stack
+ kv_destroy(replace_stack); // abandon replace stack (reinitializes)
// Save the inserted text for later redo with ^@ and CTRL-A.
// Don't do it when "restart_edit" was set and nothing was inserted,
@@ -2606,9 +2604,7 @@ void cursor_up_inner(win_T *wp, linenr_T n)
hasFolding(wp, lnum, &lnum, NULL);
}
}
- if (lnum < 1) {
- lnum = 1;
- }
+ lnum = MAX(lnum, 1);
} else {
lnum -= n;
}
@@ -2649,7 +2645,6 @@ void cursor_down_inner(win_T *wp, int n)
// count each sequence of folded lines as one logical line
while (n--) {
- // Move to last line of fold, will fail if it's the end-of-file.
if (hasFoldingWin(wp, lnum, NULL, &last, true, NULL)) {
lnum = last + 1;
} else {
@@ -2659,9 +2654,7 @@ void cursor_down_inner(win_T *wp, int n)
break;
}
}
- if (lnum > line_count) {
- lnum = line_count;
- }
+ lnum = MIN(lnum, line_count);
} else {
lnum += (linenr_T)n;
}
@@ -2672,8 +2665,10 @@ void cursor_down_inner(win_T *wp, int n)
/// @param upd_topline When true: update topline
int cursor_down(int n, bool upd_topline)
{
- // This fails if the cursor is already in the last line.
- if (n > 0 && curwin->w_cursor.lnum >= curwin->w_buffer->b_ml.ml_line_count) {
+ linenr_T lnum = curwin->w_cursor.lnum;
+ // This fails if the cursor is already in the last (folded) line.
+ hasFoldingWin(curwin, lnum, NULL, &lnum, true, NULL);
+ if (n > 0 && lnum >= curwin->w_buffer->b_ml.ml_line_count) {
return FAIL;
}
cursor_down_inner(curwin, n);
@@ -2808,55 +2803,51 @@ static bool echeck_abbr(int c)
// that the NL replaced. The extra one stores the characters after the cursor
// that were deleted (always white space).
-static uint8_t *replace_stack = NULL;
-static ssize_t replace_stack_nr = 0; // next entry in replace stack
-static ssize_t replace_stack_len = 0; // max. number of entries
-
/// Push character that is replaced onto the replace stack.
///
/// replace_offset is normally 0, in which case replace_push will add a new
/// character at the end of the stack. If replace_offset is not 0, that many
/// characters will be left on the stack above the newly inserted character.
///
-/// @param c character that is replaced (NUL is none)
-void replace_push(int c)
+/// @param str character that is replaced (NUL is none)
+/// @param len length of character in bytes
+void replace_push(char *str, size_t len)
{
- if (replace_stack_nr < replace_offset) { // nothing to do
+ // TODO(bfredl): replace_offset is suss af, if we don't need it, this
+ // function is just kv_concat() :p
+ if (kv_size(replace_stack) < (size_t)replace_offset) { // nothing to do
return;
}
- if (replace_stack_len <= replace_stack_nr) {
- replace_stack_len += 50;
- replace_stack = xrealloc(replace_stack, (size_t)replace_stack_len);
- }
- uint8_t *p = replace_stack + replace_stack_nr - replace_offset;
+ kv_ensure_space(replace_stack, len);
+
+ char *p = replace_stack.items + kv_size(replace_stack) - replace_offset;
if (replace_offset) {
- memmove(p + 1, p, (size_t)replace_offset);
+ memmove(p + len, p, (size_t)replace_offset);
}
- *p = (uint8_t)c;
- replace_stack_nr++;
+ memcpy(p, str, len);
+ kv_size(replace_stack) += len;
}
-/// Push a character onto the replace stack. Handles a multi-byte character in
-/// reverse byte order, so that the first byte is popped off first.
-///
-/// @return the number of bytes done (includes composing characters).
-int replace_push_mb(char *p)
+/// push NUL as separator between entries in the stack
+void replace_push_nul(void)
{
- int l = utfc_ptr2len(p);
-
- for (int j = l - 1; j >= 0; j--) {
- replace_push(p[j]);
- }
- return l;
+ replace_push("", 1);
}
-/// Pop one item from the replace stack.
+/// Check top of replace stack, pop it if it was NUL
+///
+/// when a non-NUL byte is found, use mb_replace_pop_ins() to
+/// pop one complete multibyte character.
///
-/// @return -1 if stack is empty, replaced character or NUL otherwise
-static int replace_pop(void)
+/// @return -1 if stack is empty, last byte of char or NUL otherwise
+static int replace_pop_if_nul(void)
{
- return (replace_stack_nr == 0) ? -1 : (int)replace_stack[--replace_stack_nr];
+ int ch = (kv_size(replace_stack)) ? (uint8_t)kv_A(replace_stack, kv_size(replace_stack) - 1) : -1;
+ if (ch == NUL) {
+ kv_size(replace_stack)--;
+ }
+ return ch;
}
/// Join the top two items on the replace stack. This removes to "off"'th NUL
@@ -2865,11 +2856,11 @@ static int replace_pop(void)
/// @param off offset for which NUL to remove
static void replace_join(int off)
{
- for (ssize_t i = replace_stack_nr; --i >= 0;) {
- if (replace_stack[i] == NUL && off-- <= 0) {
- replace_stack_nr--;
- memmove(replace_stack + i, replace_stack + i + 1,
- (size_t)(replace_stack_nr - i));
+ for (ssize_t i = (ssize_t)kv_size(replace_stack); --i >= 0;) {
+ if (kv_A(replace_stack, i) == NUL && off-- <= 0) {
+ kv_size(replace_stack)--;
+ memmove(&kv_A(replace_stack, i), &kv_A(replace_stack, i + 1),
+ (kv_size(replace_stack) - (size_t)i));
return;
}
}
@@ -2879,70 +2870,25 @@ static void replace_join(int off)
/// before the cursor. Can only be used in MODE_REPLACE or MODE_VREPLACE state.
static void replace_pop_ins(void)
{
- int cc;
int oldState = State;
State = MODE_NORMAL; // don't want MODE_REPLACE here
- while ((cc = replace_pop()) > 0) {
- mb_replace_pop_ins(cc);
+ while ((replace_pop_if_nul()) > 0) {
+ mb_replace_pop_ins();
dec_cursor();
}
State = oldState;
}
-// Insert bytes popped from the replace stack. "cc" is the first byte. If it
-// indicates a multi-byte char, pop the other bytes too.
-static void mb_replace_pop_ins(int cc)
-{
- int n;
- uint8_t buf[MB_MAXBYTES + 1];
-
- if ((n = MB_BYTE2LEN(cc)) > 1) {
- buf[0] = (uint8_t)cc;
- for (int i = 1; i < n; i++) {
- buf[i] = (uint8_t)replace_pop();
- }
- ins_bytes_len((char *)buf, (size_t)n);
- } else {
- ins_char(cc);
- }
-
- // Handle composing chars.
- while (true) {
- int c = replace_pop();
- if (c == -1) { // stack empty
- break;
- }
- if ((n = MB_BYTE2LEN(c)) == 1) {
- // Not a multi-byte char, put it back.
- replace_push(c);
- break;
- }
-
- buf[0] = (uint8_t)c;
- assert(n > 1);
- for (int i = 1; i < n; i++) {
- buf[i] = (uint8_t)replace_pop();
- }
- if (utf_iscomposing(utf_ptr2char((char *)buf))) {
- ins_bytes_len((char *)buf, (size_t)n);
- } else {
- // Not a composing char, put it back.
- for (int i = n - 1; i >= 0; i--) {
- replace_push(buf[i]);
- }
- break;
- }
- }
-}
-
-// make the replace stack empty
-// (called when exiting replace mode)
-static void replace_flush(void)
+/// Insert multibyte char popped from the replace stack.
+///
+/// caller must already have checked the top of the stack is not NUL!!
+static void mb_replace_pop_ins(void)
{
- XFREE_CLEAR(replace_stack);
- replace_stack_len = 0;
- replace_stack_nr = 0;
+ int len = utf_head_off(&kv_A(replace_stack, 0),
+ &kv_A(replace_stack, kv_size(replace_stack) - 1)) + 1;
+ kv_size(replace_stack) -= (size_t)len;
+ ins_bytes_len(&kv_A(replace_stack, kv_size(replace_stack)), (size_t)len);
}
// Handle doing a BS for one character.
@@ -2957,7 +2903,7 @@ static void replace_do_bs(int limit_col)
colnr_T start_vcol;
const int l_State = State;
- int cc = replace_pop();
+ int cc = replace_pop_if_nul();
if (cc > 0) {
int orig_len = 0;
int orig_vcols = 0;
@@ -2971,7 +2917,6 @@ static void replace_do_bs(int limit_col)
if (l_State & VREPLACE_FLAG) {
orig_len = get_cursor_pos_len();
}
- replace_push(cc);
replace_pop_ins();
if (l_State & VREPLACE_FLAG) {
@@ -3630,9 +3575,9 @@ static void ins_shift(int c, int lastc)
if (lastc == '^') {
old_indent = get_indent(); // remember curr. indent
}
- change_indent(INDENT_SET, 0, true, 0, true);
+ change_indent(INDENT_SET, 0, true, true);
} else {
- change_indent(c == Ctrl_D ? INDENT_DEC : INDENT_INC, 0, true, 0, true);
+ change_indent(c == Ctrl_D ? INDENT_DEC : INDENT_INC, 0, true, true);
}
if (did_ai && *skipwhite(get_cursor_line_ptr()) != NUL) {
@@ -3751,7 +3696,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
// cc >= 0: NL was replaced, put original characters back
cc = -1;
if (State & REPLACE_FLAG) {
- cc = replace_pop(); // returns -1 if NL was inserted
+ cc = replace_pop_if_nul(); // returns -1 if NL was inserted
}
// In replace mode, in the line we started replacing, we only move the
// cursor.
@@ -3797,9 +3742,9 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
// restore characters (blanks) deleted after cursor
while (cc > 0) {
colnr_T save_col = curwin->w_cursor.col;
- mb_replace_pop_ins(cc);
+ mb_replace_pop_ins();
curwin->w_cursor.col = save_col;
- cc = replace_pop();
+ cc = replace_pop_if_nul();
}
// restore the characters that NL replaced
replace_pop_ins();
@@ -3856,7 +3801,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
space_sci = sci;
space_vcol = vcol;
}
- vcol += charsize_nowrap(curbuf, use_ts, vcol, sci.chr.value);
+ vcol += charsize_nowrap(curbuf, sci.ptr, use_ts, vcol, sci.chr.value);
sci = utfc_next(sci);
prev_space = cur_space;
}
@@ -3872,7 +3817,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
// Find the position to stop backspacing.
// Use charsize_nowrap() so that virtual text and wrapping are ignored.
while (true) {
- int size = charsize_nowrap(curbuf, use_ts, space_vcol, space_sci.chr.value);
+ int size = charsize_nowrap(curbuf, space_sci.ptr, use_ts, space_vcol, space_sci.chr.value);
if (space_vcol + size > want_vcol) {
break;
}
@@ -3908,7 +3853,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
} else {
ins_str(" ");
if ((State & REPLACE_FLAG)) {
- replace_push(NUL);
+ replace_push_nul();
}
}
}
@@ -3943,7 +3888,7 @@ static bool ins_bs(int c, int mode, int *inserted_space_p)
bool has_composing = false;
if (p_deco) {
char *p0 = get_cursor_pos_ptr();
- has_composing = utf_composinglike(p0, p0 + utf_ptr2len(p0));
+ has_composing = utf_composinglike(p0, p0 + utf_ptr2len(p0), NULL);
}
del_char(false);
// If there are combining characters and 'delcombine' is set
@@ -4318,7 +4263,7 @@ static bool ins_tab(void)
} else {
ins_str(" ");
if (State & REPLACE_FLAG) { // no char replaced
- replace_push(NUL);
+ replace_push_nul();
}
}
}
@@ -4419,18 +4364,30 @@ static bool ins_tab(void)
// Delete following spaces.
int i = cursor->col - fpos.col;
if (i > 0) {
- STRMOVE(ptr, ptr + i);
+ if (!(State & VREPLACE_FLAG)) {
+ char *newp = xmalloc((size_t)(curbuf->b_ml.ml_line_len - i));
+ ptrdiff_t col = ptr - curbuf->b_ml.ml_line_ptr;
+ if (col > 0) {
+ memmove(newp, ptr - col, (size_t)col);
+ }
+ memmove(newp + col, ptr + i, (size_t)(curbuf->b_ml.ml_line_len - col - i));
+ if (curbuf->b_ml.ml_flags & (ML_LINE_DIRTY | ML_ALLOCATED)) {
+ xfree(curbuf->b_ml.ml_line_ptr);
+ }
+ curbuf->b_ml.ml_line_ptr = newp;
+ curbuf->b_ml.ml_line_len -= i;
+ curbuf->b_ml.ml_flags = (curbuf->b_ml.ml_flags | ML_LINE_DIRTY) & ~ML_EMPTY;
+ inserted_bytes(fpos.lnum, change_col,
+ cursor->col - change_col, fpos.col - change_col);
+ } else {
+ STRMOVE(ptr, ptr + i);
+ }
// correct replace stack.
if ((State & REPLACE_FLAG) && !(State & VREPLACE_FLAG)) {
for (temp = i; --temp >= 0;) {
replace_join(repl_off);
}
}
- if (!(State & VREPLACE_FLAG)) {
- curbuf->b_ml.ml_line_len -= i;
- inserted_bytes(fpos.lnum, change_col,
- cursor->col - change_col, fpos.col - change_col);
- }
}
cursor->col -= i;
@@ -4473,7 +4430,7 @@ bool ins_eol(int c)
// character under the cursor. Only push a NUL on the replace stack,
// nothing to put back when the NL is deleted.
if ((State & REPLACE_FLAG) && !(State & VREPLACE_FLAG)) {
- replace_push(NUL);
+ replace_push_nul();
}
// In MODE_VREPLACE state, a NL replaces the rest of the line, and starts
@@ -4674,7 +4631,7 @@ static void ins_try_si(int c)
i = get_indent();
curwin->w_cursor = old_pos;
if (State & VREPLACE_FLAG) {
- change_indent(INDENT_SET, i, false, NUL, true);
+ change_indent(INDENT_SET, i, false, true);
} else {
set_indent(i, SIN_CHANGED);
}
@@ -4712,9 +4669,7 @@ static void ins_try_si(int c)
}
// Adjust ai_col, the char at this position can be deleted.
- if (ai_col > curwin->w_cursor.col) {
- ai_col = curwin->w_cursor.col;
- }
+ ai_col = MIN(ai_col, curwin->w_cursor.col);
}
// Get the value that w_virtcol would have when 'list' is off.
diff --git a/src/nvim/errors.h b/src/nvim/errors.h
new file mode 100644
index 0000000000..39095db952
--- /dev/null
+++ b/src/nvim/errors.h
@@ -0,0 +1,193 @@
+#pragma once
+
+#include "nvim/gettext_defs.h"
+#include "nvim/macros_defs.h"
+
+//
+// Shared error messages. Excludes errors only used once and debugging messages.
+//
+// uncrustify:off
+EXTERN const char e_abort[] INIT(= N_("E470: Command aborted"));
+EXTERN const char e_afterinit[] INIT(= N_("E905: Cannot set this option after startup"));
+EXTERN const char e_api_spawn_failed[] INIT(= N_("E903: Could not spawn API job"));
+EXTERN const char e_argreq[] INIT(= N_("E471: Argument required"));
+EXTERN const char e_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &"));
+EXTERN const char e_cmdwin[] INIT(= N_("E11: Invalid in command-line window; <CR> executes, CTRL-C quits"));
+EXTERN const char e_curdir[] INIT(= N_("E12: Command not allowed in secure mode in current dir or tag search"));
+EXTERN const char e_invalid_buffer_name_str[] INIT(= N_("E158: Invalid buffer name: %s"));
+EXTERN const char e_command_too_recursive[] INIT(= N_("E169: Command too recursive"));
+EXTERN const char e_buffer_is_not_loaded[] INIT(= N_("E681: Buffer is not loaded"));
+EXTERN const char e_endif[] INIT(= N_("E171: Missing :endif"));
+EXTERN const char e_endtry[] INIT(= N_("E600: Missing :endtry"));
+EXTERN const char e_endwhile[] INIT(= N_("E170: Missing :endwhile"));
+EXTERN const char e_endfor[] INIT(= N_("E170: Missing :endfor"));
+EXTERN const char e_while[] INIT(= N_("E588: :endwhile without :while"));
+EXTERN const char e_for[] INIT(= N_("E588: :endfor without :for"));
+EXTERN const char e_exists[] INIT(= N_("E13: File exists (add ! to override)"));
+EXTERN const char e_failed[] INIT(= N_("E472: Command failed"));
+EXTERN const char e_internal[] INIT(= N_("E473: Internal error"));
+EXTERN const char e_intern2[] INIT(= N_("E685: Internal error: %s"));
+EXTERN const char e_interr[] INIT(= N_("Interrupted"));
+EXTERN const char e_invarg[] INIT(= N_("E474: Invalid argument"));
+EXTERN const char e_invarg2[] INIT(= N_("E475: Invalid argument: %s"));
+EXTERN const char e_invargval[] INIT(= N_("E475: Invalid value for argument %s"));
+EXTERN const char e_invargNval[] INIT(= N_("E475: Invalid value for argument %s: %s"));
+EXTERN const char e_duparg2[] INIT(= N_("E983: Duplicate argument: %s"));
+EXTERN const char e_invexpr2[] INIT(= N_("E15: Invalid expression: \"%s\""));
+EXTERN const char e_invrange[] INIT(= N_("E16: Invalid range"));
+EXTERN const char e_invcmd[] INIT(= N_("E476: Invalid command"));
+EXTERN const char e_isadir2[] INIT(= N_("E17: \"%s\" is a directory"));
+EXTERN const char e_no_spell[] INIT(= N_("E756: Spell checking is not possible"));
+EXTERN const char e_invchan[] INIT(= N_("E900: Invalid channel id"));
+EXTERN const char e_invchanjob[] INIT(= N_("E900: Invalid channel id: not a job"));
+EXTERN const char e_jobtblfull[] INIT(= N_("E901: Job table is full"));
+EXTERN const char e_jobspawn[] INIT(= N_("E903: Process failed to start: %s: \"%s\""));
+EXTERN const char e_channotpty[] INIT(= N_("E904: channel is not a pty"));
+EXTERN const char e_stdiochan2[] INIT(= N_("E905: Couldn't open stdio channel: %s"));
+EXTERN const char e_invstream[] INIT(= N_("E906: invalid stream for channel"));
+EXTERN const char e_invstreamrpc[] INIT(= N_("E906: invalid stream for rpc channel, use 'rpc'"));
+EXTERN const char e_streamkey[] INIT(= N_("E5210: dict key '%s' already set for buffered stream in channel %" PRIu64));
+EXTERN const char e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\""));
+EXTERN const char e_fsync[] INIT(= N_("E667: Fsync failed: %s"));
+EXTERN const char e_mkdir[] INIT(= N_("E739: Cannot create directory %s: %s"));
+EXTERN const char e_markinval[] INIT(= N_("E19: Mark has invalid line number"));
+EXTERN const char e_marknotset[] INIT(= N_("E20: Mark not set"));
+EXTERN const char e_modifiable[] INIT(= N_("E21: Cannot make changes, 'modifiable' is off"));
+EXTERN const char e_nesting[] INIT(= N_("E22: Scripts nested too deep"));
+EXTERN const char e_noalt[] INIT(= N_("E23: No alternate file"));
+EXTERN const char e_noabbr[] INIT(= N_("E24: No such abbreviation"));
+EXTERN const char e_nobang[] INIT(= N_("E477: No ! allowed"));
+EXTERN const char e_nogroup[] INIT(= N_("E28: No such highlight group name: %s"));
+EXTERN const char e_noinstext[] INIT(= N_("E29: No inserted text yet"));
+EXTERN const char e_nolastcmd[] INIT(= N_("E30: No previous command line"));
+EXTERN const char e_nomap[] INIT(= N_("E31: No such mapping"));
+EXTERN const char e_nomatch[] INIT(= N_("E479: No match"));
+EXTERN const char e_nomatch2[] INIT(= N_("E480: No match: %s"));
+EXTERN const char e_noname[] INIT(= N_("E32: No file name"));
+EXTERN const char e_nopresub[] INIT(= N_("E33: No previous substitute regular expression"));
+EXTERN const char e_noprev[] INIT(= N_("E34: No previous command"));
+EXTERN const char e_noprevre[] INIT(= N_("E35: No previous regular expression"));
+EXTERN const char e_norange[] INIT(= N_("E481: No range allowed"));
+EXTERN const char e_noroom[] INIT(= N_("E36: Not enough room"));
+EXTERN const char e_notmp[] INIT(= N_("E483: Can't get temp file name"));
+EXTERN const char e_notopen[] INIT(= N_("E484: Can't open file %s"));
+EXTERN const char e_notopen_2[] INIT(= N_("E484: Can't open file %s: %s"));
+EXTERN const char e_notread[] INIT(= N_("E485: Can't read file %s"));
+EXTERN const char e_null[] INIT(= N_("E38: Null argument"));
+EXTERN const char e_number_exp[] INIT(= N_("E39: Number expected"));
+EXTERN const char e_openerrf[] INIT(= N_("E40: Can't open errorfile %s"));
+EXTERN const char e_outofmem[] INIT(= N_("E41: Out of memory!"));
+EXTERN const char e_patnotf[] INIT(= N_("Pattern not found"));
+EXTERN const char e_patnotf2[] INIT(= N_("E486: Pattern not found: %s"));
+EXTERN const char e_positive[] INIT(= N_("E487: Argument must be positive"));
+EXTERN const char e_prev_dir[] INIT(= N_("E459: Cannot go back to previous directory"));
+
+EXTERN const char e_no_errors[] INIT(= N_("E42: No Errors"));
+EXTERN const char e_loclist[] INIT(= N_("E776: No location list"));
+EXTERN const char e_re_damg[] INIT(= N_("E43: Damaged match string"));
+EXTERN const char e_re_corr[] INIT(= N_("E44: Corrupted regexp program"));
+EXTERN const char e_readonly[] INIT(= N_("E45: 'readonly' option is set (add ! to override)"));
+EXTERN const char e_letwrong[] INIT(= N_("E734: Wrong variable type for %s="));
+EXTERN const char e_illvar[] INIT(= N_("E461: Illegal variable name: %s"));
+EXTERN const char e_cannot_mod[] INIT(= N_("E995: Cannot modify existing variable"));
+EXTERN const char e_readonlyvar[] INIT(= N_("E46: Cannot change read-only variable \"%.*s\""));
+EXTERN const char e_stringreq[] INIT(= N_("E928: String required"));
+EXTERN const char e_dictreq[] INIT(= N_("E715: Dictionary required"));
+EXTERN const char e_blobidx[] INIT(= N_("E979: Blob index out of range: %" PRId64));
+EXTERN const char e_invalblob[] INIT(= N_("E978: Invalid operation for Blob"));
+EXTERN const char e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s"));
+EXTERN const char e_toofewarg[] INIT(= N_("E119: Not enough arguments for function: %s"));
+EXTERN const char e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: \"%s\""));
+EXTERN const char e_dictkey_len[] INIT(= N_("E716: Key not present in Dictionary: \"%.*s\""));
+EXTERN const char e_listreq[] INIT(= N_("E714: List required"));
+EXTERN const char e_listblobreq[] INIT(= N_("E897: List or Blob required"));
+EXTERN const char e_listdictarg[] INIT(= N_("E712: Argument of %s must be a List or Dictionary"));
+EXTERN const char e_listdictblobarg[] INIT(= N_("E896: Argument of %s must be a List, Dictionary or Blob"));
+EXTERN const char e_readerrf[] INIT(= N_("E47: Error while reading errorfile"));
+EXTERN const char e_sandbox[] INIT(= N_("E48: Not allowed in sandbox"));
+EXTERN const char e_secure[] INIT(= N_("E523: Not allowed here"));
+EXTERN const char e_textlock[] INIT(= N_("E565: Not allowed to change text or change window"));
+EXTERN const char e_screenmode[] INIT(= N_("E359: Screen mode setting not supported"));
+EXTERN const char e_scroll[] INIT(= N_("E49: Invalid scroll size"));
+EXTERN const char e_shellempty[] INIT(= N_("E91: 'shell' option is empty"));
+EXTERN const char e_signdata[] INIT(= N_("E255: Couldn't read in sign data!"));
+EXTERN const char e_swapclose[] INIT(= N_("E72: Close error on swap file"));
+EXTERN const char e_toocompl[] INIT(= N_("E74: Command too complex"));
+EXTERN const char e_longname[] INIT(= N_("E75: Name too long"));
+EXTERN const char e_toomsbra[] INIT(= N_("E76: Too many ["));
+EXTERN const char e_toomany[] INIT(= N_("E77: Too many file names"));
+EXTERN const char e_trailing[] INIT(= N_("E488: Trailing characters"));
+EXTERN const char e_trailing_arg[] INIT(= N_("E488: Trailing characters: %s"));
+EXTERN const char e_umark[] INIT(= N_("E78: Unknown mark"));
+EXTERN const char e_wildexpand[] INIT(= N_("E79: Cannot expand wildcards"));
+EXTERN const char e_winheight[] INIT(= N_("E591: 'winheight' cannot be smaller than 'winminheight'"));
+EXTERN const char e_winwidth[] INIT(= N_("E592: 'winwidth' cannot be smaller than 'winminwidth'"));
+EXTERN const char e_write[] INIT(= N_("E80: Error while writing"));
+EXTERN const char e_zerocount[] INIT(= N_("E939: Positive count required"));
+EXTERN const char e_usingsid[] INIT(= N_("E81: Using <SID> not in a script context"));
+EXTERN const char e_missingparen[] INIT(= N_("E107: Missing parentheses: %s"));
+EXTERN const char e_empty_buffer[] INIT(= N_("E749: Empty buffer"));
+EXTERN const char e_nobufnr[] INIT(= N_("E86: Buffer %" PRId64 " does not exist"));
+
+EXTERN const char e_str_not_inside_function[] INIT(= N_("E193: %s not inside a function"));
+
+EXTERN const char e_invalpat[] INIT(= N_("E682: Invalid search pattern or delimiter"));
+EXTERN const char e_bufloaded[] INIT(= N_("E139: File is loaded in another buffer"));
+EXTERN const char e_notset[] INIT(= N_("E764: Option '%s' is not set"));
+EXTERN const char e_invalidreg[] INIT(= N_("E850: Invalid register name"));
+EXTERN const char e_dirnotf[] INIT(= N_("E919: Directory not found in '%s': \"%s\""));
+EXTERN const char e_au_recursive[] INIT(= N_("E952: Autocommand caused recursive behavior"));
+EXTERN const char e_menu_only_exists_in_another_mode[]
+INIT(= N_("E328: Menu only exists in another mode"));
+EXTERN const char e_autocmd_close[] INIT(= N_("E813: Cannot close autocmd window"));
+EXTERN const char e_listarg[] INIT(= N_("E686: Argument of %s must be a List"));
+EXTERN const char e_unsupportedoption[] INIT(= N_("E519: Option not supported"));
+EXTERN const char e_fnametoolong[] INIT(= N_("E856: Filename too long"));
+EXTERN const char e_using_float_as_string[] INIT(= N_("E806: Using a Float as a String"));
+EXTERN const char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now"));
+EXTERN const char e_using_number_as_bool_nr[] INIT(= N_("E1023: Using a Number as a Bool: %d"));
+EXTERN const char e_not_callable_type_str[] INIT(= N_("E1085: Not a callable type: %s"));
+EXTERN const char e_auabort[] INIT(= N_("E855: Autocommands caused command to abort"));
+
+EXTERN const char e_api_error[] INIT(= N_("E5555: API call: %s"));
+
+EXTERN const char e_luv_api_disabled[] INIT(= N_("E5560: %s must not be called in a lua loop callback"));
+
+EXTERN const char e_floatonly[] INIT(= N_("E5601: Cannot close window, only floating window would remain"));
+EXTERN const char e_floatexchange[] INIT(= N_("E5602: Cannot exchange or rotate float"));
+
+EXTERN const char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events"));
+
+EXTERN const char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long"));
+
+EXTERN const char e_line_number_out_of_range[] INIT(= N_("E1247: Line number out of range"));
+
+EXTERN const char e_highlight_group_name_invalid_char[] INIT(= N_("E5248: Invalid character in group name"));
+
+EXTERN const char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long"));
+
+EXTERN const char e_invalid_column_number_nr[] INIT( = N_("E964: Invalid column number: %ld"));
+EXTERN const char e_invalid_line_number_nr[] INIT(= N_("E966: Invalid line number: %ld"));
+
+EXTERN const char e_stray_closing_curly_str[]
+INIT(= N_("E1278: Stray '}' without a matching '{': %s"));
+EXTERN const char e_missing_close_curly_str[]
+INIT(= N_("E1279: Missing '}': %s"));
+
+EXTERN const char e_val_too_large[] INIT(= N_("E1510: Value too large: %s"));
+
+EXTERN const char e_undobang_cannot_redo_or_move_branch[]
+INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch"));
+
+EXTERN const char e_winfixbuf_cannot_go_to_buffer[]
+INIT(= N_("E1513: Cannot switch buffer. 'winfixbuf' is enabled"));
+
+EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));
+
+EXTERN const char e_unknown_option2[] INIT(= N_("E355: Unknown option: %s"));
+
+EXTERN const char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM"));
+EXTERN const char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP"));
+
+EXTERN const char line_msg[] INIT(= N_(" line "));
+// uncrustify:on
diff --git a/src/nvim/eval.c b/src/nvim/eval.c
index 64883e69a2..8682139b32 100644
--- a/src/nvim/eval.c
+++ b/src/nvim/eval.c
@@ -6,7 +6,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/stat.h>
#include <uv.h>
#include "auto/config.h"
@@ -21,9 +20,9 @@
#include "nvim/channel.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand_defs.h"
-#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/executor.h"
@@ -33,18 +32,15 @@
#include "nvim/eval/vars.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
#include "nvim/event/time.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
-#include "nvim/ex_getln.h"
#include "nvim/garray.h"
#include "nvim/garray_defs.h"
-#include "nvim/getchar.h"
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
-#include "nvim/grid_defs.h"
#include "nvim/hashtab.h"
#include "nvim/highlight_group.h"
#include "nvim/insexpand.h"
@@ -66,14 +62,11 @@
#include "nvim/option.h"
#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
-#include "nvim/os/fileio.h"
#include "nvim/os/fs.h"
-#include "nvim/os/fs_defs.h"
#include "nvim/os/lang.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/os/shell.h"
-#include "nvim/os/stdpaths_defs.h"
#include "nvim/path.h"
#include "nvim/pos_defs.h"
#include "nvim/profile.h"
@@ -82,14 +75,9 @@
#include "nvim/regexp_defs.h"
#include "nvim/runtime.h"
#include "nvim/runtime_defs.h"
-#include "nvim/search.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
#include "nvim/types_defs.h"
-#include "nvim/ui.h"
-#include "nvim/ui_compositor.h"
-#include "nvim/ui_defs.h"
-#include "nvim/usercmd.h"
#include "nvim/version.h"
#include "nvim/vim_defs.h"
#include "nvim/window.h"
@@ -106,7 +94,6 @@ static const char e_cannot_index_special_variable[]
= N_("E909: Cannot index a special variable");
static const char *e_nowhitespace
= N_("E274: No white space allowed before parenthesis");
-static const char *e_write2 = N_("E80: Error while writing: %s");
static const char e_cannot_index_a_funcref[]
= N_("E695: Cannot index a Funcref");
static const char e_variable_nested_too_deep_for_making_copy[]
@@ -121,6 +108,8 @@ static const char e_empty_function_name[]
= N_("E1192: Empty function name");
static const char e_argument_of_str_must_be_list_string_dictionary_or_blob[]
= N_("E1250: Argument of %s must be a List, String, Dictionary or Blob");
+static const char e_cannot_use_partial_here[]
+ = N_("E1265: Cannot use a partial here");
static char * const namespace_char = "abglstvw";
@@ -151,6 +140,12 @@ typedef struct {
int fi_byte_idx; // byte index in fi_string
} forinfo_T;
+typedef enum {
+ GLV_FAIL,
+ GLV_OK,
+ GLV_STOP,
+} glv_status_T;
+
// values for vv_flags:
#define VV_COMPAT 1 // compatible, also used without "v:"
#define VV_RO 2 // read-only
@@ -330,7 +325,6 @@ static const char *const msgpack_type_names[] = {
[kMPInteger] = "integer",
[kMPFloat] = "float",
[kMPString] = "string",
- [kMPBinary] = "binary",
[kMPArray] = "array",
[kMPMap] = "map",
[kMPExt] = "ext",
@@ -341,7 +335,6 @@ const list_T *eval_msgpack_type_lists[] = {
[kMPInteger] = NULL,
[kMPFloat] = NULL,
[kMPString] = NULL,
- [kMPBinary] = NULL,
[kMPArray] = NULL,
[kMPMap] = NULL,
[kMPExt] = NULL,
@@ -696,7 +689,7 @@ int eval_charconvert(const char *const enc_from, const char *const enc_to,
}
bool err = false;
- if (eval_to_bool(p_ccv, &err, NULL, false)) {
+ if (eval_to_bool(p_ccv, &err, NULL, false, true)) {
err = true;
}
@@ -725,7 +718,7 @@ void eval_diff(const char *const origfile, const char *const newfile, const char
}
// errors are ignored
- typval_T *tv = eval_expr(p_dex, NULL);
+ typval_T *tv = eval_expr_ext(p_dex, NULL, true);
tv_free(tv);
set_vim_var_string(VV_FNAME_IN, NULL, -1);
@@ -747,7 +740,7 @@ void eval_patch(const char *const origfile, const char *const difffile, const ch
}
// errors are ignored
- typval_T *tv = eval_expr(p_pex, NULL);
+ typval_T *tv = eval_expr_ext(p_pex, NULL, true);
tv_free(tv);
set_vim_var_string(VV_FNAME_IN, NULL, -1);
@@ -776,7 +769,8 @@ void fill_evalarg_from_eap(evalarg_T *evalarg, exarg_T *eap, bool skip)
/// @param skip only parse, don't execute
///
/// @return true or false.
-bool eval_to_bool(char *arg, bool *error, exarg_T *eap, bool skip)
+bool eval_to_bool(char *arg, bool *error, exarg_T *eap, const bool skip,
+ const bool use_simple_function)
{
typval_T tv;
bool retval = false;
@@ -787,7 +781,9 @@ bool eval_to_bool(char *arg, bool *error, exarg_T *eap, bool skip)
if (skip) {
emsg_skip++;
}
- if (eval0(arg, &tv, eap, &evalarg) == FAIL) {
+ int r = use_simple_function ? eval0_simple_funccal(arg, &tv, eap, &evalarg)
+ : eval0(arg, &tv, eap, &evalarg);
+ if (r == FAIL) {
*error = true;
} else {
*error = false;
@@ -840,6 +836,80 @@ bool eval_expr_valid_arg(const typval_T *const tv)
&& (tv->v_type != VAR_STRING || (tv->vval.v_string != NULL && *tv->vval.v_string != NUL));
}
+/// Evaluate a partial.
+/// Pass arguments "argv[argc]".
+/// Return the result in "rettv" and OK or FAIL.
+static int eval_expr_partial(const typval_T *expr, typval_T *argv, int argc, typval_T *rettv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ partial_T *const partial = expr->vval.v_partial;
+ if (partial == NULL) {
+ return FAIL;
+ }
+
+ const char *const s = partial_name(partial);
+ if (s == NULL || *s == NUL) {
+ return FAIL;
+ }
+
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_evaluate = true;
+ funcexe.fe_partial = partial;
+ if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) {
+ return FAIL;
+ }
+
+ return OK;
+}
+
+/// Evaluate an expression which is a function.
+/// Pass arguments "argv[argc]".
+/// Return the result in "rettv" and OK or FAIL.
+static int eval_expr_func(const typval_T *expr, typval_T *argv, int argc, typval_T *rettv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ char buf[NUMBUFLEN];
+ const char *const s = (expr->v_type == VAR_FUNC
+ ? expr->vval.v_string
+ : tv_get_string_buf_chk(expr, buf));
+ if (s == NULL || *s == NUL) {
+ return FAIL;
+ }
+
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_evaluate = true;
+ if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) {
+ return FAIL;
+ }
+
+ return OK;
+}
+
+/// Evaluate an expression, which is a string.
+/// Return the result in "rettv" and OK or FAIL.
+static int eval_expr_string(const typval_T *expr, typval_T *rettv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ char buf[NUMBUFLEN];
+ char *s = (char *)tv_get_string_buf_chk(expr, buf);
+ if (s == NULL) {
+ return FAIL;
+ }
+
+ s = skipwhite(s);
+ if (eval1_emsg(&s, rettv, NULL) == FAIL) {
+ return FAIL;
+ }
+
+ if (*skipwhite(s) != NUL) { // check for trailing chars after expr
+ tv_clear(rettv);
+ semsg(_(e_invexpr2), s);
+ return FAIL;
+ }
+
+ return OK;
+}
+
/// Evaluate an expression, which can be a function, partial or string.
/// Pass arguments "argv[argc]".
/// Return the result in "rettv" and OK or FAIL.
@@ -849,49 +919,14 @@ int eval_expr_typval(const typval_T *expr, bool want_func, typval_T *argv, int a
typval_T *rettv)
FUNC_ATTR_NONNULL_ALL
{
- char buf[NUMBUFLEN];
- funcexe_T funcexe = FUNCEXE_INIT;
-
if (expr->v_type == VAR_PARTIAL) {
- partial_T *const partial = expr->vval.v_partial;
- if (partial == NULL) {
- return FAIL;
- }
- const char *const s = partial_name(partial);
- if (s == NULL || *s == NUL) {
- return FAIL;
- }
- funcexe.fe_evaluate = true;
- funcexe.fe_partial = partial;
- if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) {
- return FAIL;
- }
+ return eval_expr_partial(expr, argv, argc, rettv);
} else if (expr->v_type == VAR_FUNC || want_func) {
- const char *const s = (expr->v_type == VAR_FUNC
- ? expr->vval.v_string
- : tv_get_string_buf_chk(expr, buf));
- if (s == NULL || *s == NUL) {
- return FAIL;
- }
- funcexe.fe_evaluate = true;
- if (call_func(s, -1, rettv, argc, argv, &funcexe) == FAIL) {
- return FAIL;
- }
+ return eval_expr_func(expr, argv, argc, rettv);
} else {
- char *s = (char *)tv_get_string_buf_chk(expr, buf);
- if (s == NULL) {
- return FAIL;
- }
- s = skipwhite(s);
- if (eval1_emsg(&s, rettv, NULL) == FAIL) {
- return FAIL;
- }
- if (*skipwhite(s) != NUL) { // check for trailing chars after expr
- tv_clear(rettv);
- semsg(_(e_invexpr2), s);
- return FAIL;
- }
+ return eval_expr_string(expr, rettv);
}
+
return OK;
}
@@ -996,14 +1031,17 @@ static char *typval2string(typval_T *tv, bool join_list)
/// @param join_list when true convert a List into a sequence of lines.
///
/// @return pointer to allocated memory, or NULL for failure.
-char *eval_to_string_eap(char *arg, bool join_list, exarg_T *eap)
+char *eval_to_string_eap(char *arg, const bool join_list, exarg_T *eap,
+ const bool use_simple_function)
{
typval_T tv;
char *retval;
evalarg_T evalarg;
fill_evalarg_from_eap(&evalarg, eap, eap != NULL && eap->skip);
- if (eval0(arg, &tv, NULL, &evalarg) == FAIL) {
+ int r = use_simple_function ? eval0_simple_funccal(arg, &tv, NULL, &evalarg)
+ : eval0(arg, &tv, NULL, &evalarg);
+ if (r == FAIL) {
retval = NULL;
} else {
retval = typval2string(&tv, join_list);
@@ -1014,16 +1052,16 @@ char *eval_to_string_eap(char *arg, bool join_list, exarg_T *eap)
return retval;
}
-char *eval_to_string(char *arg, bool join_list)
+char *eval_to_string(char *arg, const bool join_list, const bool use_simple_function)
{
- return eval_to_string_eap(arg, join_list, NULL);
+ return eval_to_string_eap(arg, join_list, NULL, use_simple_function);
}
/// Call eval_to_string() without using current local variables and using
/// textlock.
///
/// @param use_sandbox when true, use the sandbox.
-char *eval_to_string_safe(char *arg, const bool use_sandbox)
+char *eval_to_string_safe(char *arg, const bool use_sandbox, const bool use_simple_function)
{
char *retval;
funccal_entry_T funccal_entry;
@@ -1033,7 +1071,7 @@ char *eval_to_string_safe(char *arg, const bool use_sandbox)
sandbox++;
}
textlock++;
- retval = eval_to_string(arg, false);
+ retval = eval_to_string(arg, false, use_simple_function);
if (use_sandbox) {
sandbox--;
}
@@ -1046,15 +1084,22 @@ char *eval_to_string_safe(char *arg, const bool use_sandbox)
/// Evaluates "expr" silently.
///
/// @return -1 for an error.
-varnumber_T eval_to_number(char *expr)
+varnumber_T eval_to_number(char *expr, const bool use_simple_function)
{
typval_T rettv;
varnumber_T retval;
char *p = skipwhite(expr);
+ int r = NOTDONE;
emsg_off++;
- if (eval1(&p, &rettv, &EVALARG_EVALUATE) == FAIL) {
+ if (use_simple_function) {
+ r = may_call_simple_func(expr, &rettv);
+ }
+ if (r == NOTDONE) {
+ r = eval1(&p, &rettv, &EVALARG_EVALUATE);
+ }
+ if (r == FAIL) {
retval = -1;
} else {
retval = tv_get_number_chk(&rettv, NULL);
@@ -1071,12 +1116,26 @@ varnumber_T eval_to_number(char *expr)
/// NULL when there is an error.
typval_T *eval_expr(char *arg, exarg_T *eap)
{
+ return eval_expr_ext(arg, eap, false);
+}
+
+static typval_T *eval_expr_ext(char *arg, exarg_T *eap, const bool use_simple_function)
+{
typval_T *tv = xmalloc(sizeof(*tv));
evalarg_T evalarg;
fill_evalarg_from_eap(&evalarg, eap, eap != NULL && eap->skip);
- if (eval0(arg, tv, eap, &evalarg) == FAIL) {
+ int r = NOTDONE;
+
+ if (use_simple_function) {
+ r = eval0_simple_funccal(arg, tv, eap, &evalarg);
+ }
+ if (r == NOTDONE) {
+ r = eval0(arg, tv, eap, &evalarg);
+ }
+
+ if (r == FAIL) {
XFREE_CLEAR(tv);
}
@@ -1162,7 +1221,11 @@ list_T *eval_spell_expr(char *badword, char *expr)
current_sctx = *ctx;
}
- if (eval1(&p, &rettv, &EVALARG_EVALUATE) == OK) {
+ int r = may_call_simple_func(p, &rettv);
+ if (r == NOTDONE) {
+ r = eval1(&p, &rettv, &EVALARG_EVALUATE);
+ }
+ if (r == OK) {
if (rettv.v_type != VAR_LIST) {
tv_clear(&rettv);
} else {
@@ -1303,7 +1366,7 @@ int eval_foldexpr(win_T *wp, int *cp)
const sctx_T saved_sctx = current_sctx;
const bool use_sandbox = was_set_insecurely(wp, kOptFoldexpr, OPT_LOCAL);
- char *arg = wp->w_p_fde;
+ char *arg = skipwhite(wp->w_p_fde);
current_sctx = wp->w_p_script_ctx[WV_FDE].script_ctx;
emsg_off++;
@@ -1315,7 +1378,9 @@ int eval_foldexpr(win_T *wp, int *cp)
typval_T tv;
varnumber_T retval;
- if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) {
+ // Evaluate the expression. If the expression is "FuncName()" call the
+ // function directly.
+ if (eval0_simple_funccal(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) {
retval = 0;
} else {
// If the result is a number, just return the number.
@@ -1361,7 +1426,7 @@ Object eval_foldtext(win_T *wp)
typval_T tv;
Object retval;
- if (eval0(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) {
+ if (eval0_simple_funccal(arg, &tv, NULL, &EVALARG_EVALUATE) == FAIL) {
retval = STRING_OBJ(NULL_STRING);
} else {
if (tv.v_type == VAR_LIST) {
@@ -1382,22 +1447,232 @@ Object eval_foldtext(win_T *wp)
return retval;
}
+/// Find the end of a variable or function name. Unlike find_name_end() this
+/// does not recognize magic braces.
+/// When "use_namespace" is true recognize "b:", "s:", etc.
+/// Return a pointer to just after the name. Equal to "arg" if there is no
+/// valid name.
+static const char *to_name_end(const char *arg, bool use_namespace)
+{
+ // Quick check for valid starting character.
+ if (!eval_isnamec1(*arg)) {
+ return arg;
+ }
+
+ const char *p;
+ for (p = arg + 1; *p != NUL && eval_isnamec(*p); MB_PTR_ADV(p)) {
+ // Include a namespace such as "s:var" and "v:var". But "n:" is not
+ // and can be used in slice "[n:]".
+ if (*p == ':' && (p != arg + 1
+ || !use_namespace
+ || vim_strchr("bgstvw", *arg) == NULL)) {
+ break;
+ }
+ }
+ return p;
+}
+
+/// Get an Dict lval variable that can be assigned a value to: "name",
+/// "name[expr]", "name[expr][expr]", "name.key", "name.key[expr]" etc.
+/// "name" points to the start of the name.
+/// If "rettv" is not NULL it points to the value to be assigned.
+/// "unlet" is true for ":unlet": slightly different behavior when something is
+/// wrong; must end in space or cmd separator.
+///
+/// flags:
+/// GLV_QUIET: do not give error messages
+/// GLV_READ_ONLY: will not change the variable
+/// GLV_NO_AUTOLOAD: do not use script autoloading
+///
+/// The Dict is returned in 'lp'. Returns GLV_OK on success and GLV_FAIL on
+/// failure. Returns GLV_STOP to stop processing the characters following
+/// 'key_end'.
+static glv_status_T get_lval_dict_item(lval_T *lp, char *name, char *key, int len, char **key_end,
+ typval_T *var1, int flags, bool unlet, typval_T *rettv)
+{
+ bool quiet = flags & GLV_QUIET;
+ char *p = *key_end;
+
+ if (len == -1) {
+ // "[key]": get key from "var1"
+ key = (char *)tv_get_string(var1); // is number or string
+ }
+ lp->ll_list = NULL;
+
+ // a NULL dict is equivalent with an empty dict
+ if (lp->ll_tv->vval.v_dict == NULL) {
+ lp->ll_tv->vval.v_dict = tv_dict_alloc();
+ lp->ll_tv->vval.v_dict->dv_refcount++;
+ }
+ lp->ll_dict = lp->ll_tv->vval.v_dict;
+
+ lp->ll_di = tv_dict_find(lp->ll_dict, key, len);
+
+ // When assigning to a scope dictionary check that a function and
+ // variable name is valid (only variable name unless it is l: or
+ // g: dictionary). Disallow overwriting a builtin function.
+ if (rettv != NULL && lp->ll_dict->dv_scope != 0) {
+ char prevval;
+ if (len != -1) {
+ prevval = key[len];
+ key[len] = NUL;
+ } else {
+ prevval = 0; // Avoid compiler warning.
+ }
+ bool wrong = ((lp->ll_dict->dv_scope == VAR_DEF_SCOPE
+ && tv_is_func(*rettv)
+ && var_wrong_func_name(key, lp->ll_di == NULL))
+ || !valid_varname(key));
+ if (len != -1) {
+ key[len] = prevval;
+ }
+ if (wrong) {
+ return GLV_FAIL;
+ }
+ }
+
+ if (lp->ll_di != NULL && tv_is_luafunc(&lp->ll_di->di_tv)
+ && len == -1 && rettv == NULL) {
+ semsg(e_illvar, "v:['lua']");
+ return GLV_FAIL;
+ }
+
+ if (lp->ll_di == NULL) {
+ // Can't add "v:" or "a:" variable.
+ if (lp->ll_dict == &vimvardict
+ || &lp->ll_dict->dv_hashtab == get_funccal_args_ht()) {
+ semsg(_(e_illvar), name);
+ return GLV_FAIL;
+ }
+
+ // Key does not exist in dict: may need to add it.
+ if (*p == '[' || *p == '.' || unlet) {
+ if (!quiet) {
+ semsg(_(e_dictkey), key);
+ }
+ return GLV_FAIL;
+ }
+ if (len == -1) {
+ lp->ll_newkey = xstrdup(key);
+ } else {
+ lp->ll_newkey = xmemdupz(key, (size_t)len);
+ }
+ *key_end = p;
+ return GLV_STOP;
+ // existing variable, need to check if it can be changed
+ } else if (!(flags & GLV_READ_ONLY)
+ && (var_check_ro(lp->ll_di->di_flags, name, (size_t)(p - name))
+ || var_check_lock(lp->ll_di->di_flags, name, (size_t)(p - name)))) {
+ return GLV_FAIL;
+ }
+
+ lp->ll_tv = &lp->ll_di->di_tv;
+
+ return GLV_OK;
+}
+
+/// Get an blob lval variable that can be assigned a value to: "name",
+/// "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", etc.
+///
+/// 'var1' specifies the starting blob index and 'var2' specifies the ending
+/// blob index. If the first index is not specified in a range, then 'empty1'
+/// is true. If 'quiet' is true, then error messages are not displayed for
+/// invalid indexes.
+///
+/// The blob is returned in 'lp'. Returns OK on success and FAIL on failure.
+static int get_lval_blob(lval_T *lp, typval_T *var1, typval_T *var2, bool empty1, bool quiet)
+{
+ const int bloblen = tv_blob_len(lp->ll_tv->vval.v_blob);
+
+ // Get the number and item for the only or first index of the List.
+ if (empty1) {
+ lp->ll_n1 = 0;
+ } else {
+ // Is number or string.
+ lp->ll_n1 = (int)tv_get_number(var1);
+ }
+
+ if (tv_blob_check_index(bloblen, lp->ll_n1, quiet) == FAIL) {
+ return FAIL;
+ }
+ if (lp->ll_range && !lp->ll_empty2) {
+ lp->ll_n2 = (int)tv_get_number(var2);
+ if (tv_blob_check_range(bloblen, lp->ll_n1, lp->ll_n2, quiet) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ lp->ll_blob = lp->ll_tv->vval.v_blob;
+ lp->ll_tv = NULL;
+
+ return OK;
+}
+
+/// Get a List lval variable that can be assigned a value to: "name",
+/// "na{me}", "name[expr]", "name[expr:expr]", "name[expr][expr]", etc.
+///
+/// 'var1' specifies the starting List index and 'var2' specifies the ending
+/// List index. If the first index is not specified in a range, then 'empty1'
+/// is true. If 'quiet' is true, then error messages are not displayed for
+/// invalid indexes.
+///
+/// The List is returned in 'lp'. Returns OK on success and FAIL on failure.
+static int get_lval_list(lval_T *lp, typval_T *var1, typval_T *var2, bool empty1, int flags,
+ bool quiet)
+{
+ // Get the number and item for the only or first index of the List.
+ if (empty1) {
+ lp->ll_n1 = 0;
+ } else {
+ // Is number or string.
+ lp->ll_n1 = (int)tv_get_number(var1);
+ }
+
+ lp->ll_dict = NULL;
+ lp->ll_list = lp->ll_tv->vval.v_list;
+ lp->ll_li = tv_list_check_range_index_one(lp->ll_list, &lp->ll_n1, quiet);
+ if (lp->ll_li == NULL) {
+ return FAIL;
+ }
+
+ // May need to find the item or absolute index for the second
+ // index of a range.
+ // When no index given: "lp->ll_empty2" is true.
+ // Otherwise "lp->ll_n2" is set to the second index.
+ if (lp->ll_range && !lp->ll_empty2) {
+ lp->ll_n2 = (int)tv_get_number(var2); // Is number or string.
+ if (tv_list_check_range_index_two(lp->ll_list,
+ &lp->ll_n1, lp->ll_li,
+ &lp->ll_n2, quiet) == FAIL) {
+ return FAIL;
+ }
+ }
+
+ lp->ll_tv = TV_LIST_ITEM_TV(lp->ll_li);
+
+ return OK;
+}
+
/// Get the lval of a list/dict/blob subitem starting at "p". Loop
/// until no more [idx] or .key is following.
///
+/// If "rettv" is not NULL it points to the value to be assigned.
+/// "unlet" is true for ":unlet".
+///
/// @param[in] flags @see GetLvalFlags.
///
/// @return A pointer to the character after the subscript on success or NULL on
/// failure.
static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv, hashtab_T *ht,
- dictitem_T *v, int unlet, int flags)
+ dictitem_T *v, bool unlet, int flags)
{
- int quiet = flags & GLV_QUIET;
+ bool quiet = flags & GLV_QUIET;
typval_T var1;
var1.v_type = VAR_UNKNOWN;
typval_T var2;
var2.v_type = VAR_UNKNOWN;
bool empty1 = false;
+ int rc = FAIL;
// Loop until no more [idx] or .key is following.
while (*p == '[' || (*p == '.' && p[1] != '=' && p[1] != '.')) {
@@ -1427,7 +1702,7 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv
if (!quiet) {
emsg(_("E708: [:] must come last"));
}
- return NULL;
+ goto done;
}
int len = -1;
@@ -1451,12 +1726,11 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv
} else {
empty1 = false;
if (eval1(&p, &var1, &EVALARG_EVALUATE) == FAIL) { // Recursive!
- return NULL;
+ goto done;
}
if (!tv_check_str(&var1)) {
// Not a number or string.
- tv_clear(&var1);
- return NULL;
+ goto done;
}
p = skipwhite(p);
}
@@ -1467,8 +1741,7 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv
if (!quiet) {
emsg(_(e_cannot_slice_dictionary));
}
- tv_clear(&var1);
- return NULL;
+ goto done;
}
if (rettv != NULL
&& !(rettv->v_type == VAR_LIST && rettv->vval.v_list != NULL)
@@ -1476,8 +1749,7 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv
if (!quiet) {
emsg(_("E709: [:] requires a List or Blob value"));
}
- tv_clear(&var1);
- return NULL;
+ goto done;
}
p = skipwhite(p + 1);
if (*p == ']') {
@@ -1486,14 +1758,11 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv
lp->ll_empty2 = false;
// Recursive!
if (eval1(&p, &var2, &EVALARG_EVALUATE) == FAIL) {
- tv_clear(&var1);
- return NULL;
+ goto done;
}
if (!tv_check_str(&var2)) {
// Not a number or string.
- tv_clear(&var1);
- tv_clear(&var2);
- return NULL;
+ goto done;
}
}
lp->ll_range = true;
@@ -1505,9 +1774,7 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv
if (!quiet) {
emsg(_(e_missbrac));
}
- tv_clear(&var1);
- tv_clear(&var2);
- return NULL;
+ goto done;
}
// Skip to past ']'.
@@ -1515,142 +1782,37 @@ static char *get_lval_subscript(lval_T *lp, char *p, char *name, typval_T *rettv
}
if (lp->ll_tv->v_type == VAR_DICT) {
- if (len == -1) {
- // "[key]": get key from "var1"
- key = (char *)tv_get_string(&var1); // is number or string
- }
- lp->ll_list = NULL;
- lp->ll_dict = lp->ll_tv->vval.v_dict;
- lp->ll_di = tv_dict_find(lp->ll_dict, key, len);
-
- // When assigning to a scope dictionary check that a function and
- // variable name is valid (only variable name unless it is l: or
- // g: dictionary). Disallow overwriting a builtin function.
- if (rettv != NULL && lp->ll_dict->dv_scope != 0) {
- char prevval;
- if (len != -1) {
- prevval = key[len];
- key[len] = NUL;
- } else {
- prevval = 0; // Avoid compiler warning.
- }
- bool wrong = ((lp->ll_dict->dv_scope == VAR_DEF_SCOPE
- && tv_is_func(*rettv)
- && var_wrong_func_name(key, lp->ll_di == NULL))
- || !valid_varname(key));
- if (len != -1) {
- key[len] = prevval;
- }
- if (wrong) {
- tv_clear(&var1);
- return NULL;
- }
- }
-
- if (lp->ll_di != NULL && tv_is_luafunc(&lp->ll_di->di_tv)
- && len == -1 && rettv == NULL) {
- tv_clear(&var1);
- semsg(e_illvar, "v:['lua']");
- return NULL;
+ glv_status_T glv_status = get_lval_dict_item(lp, name, key, len, &p, &var1,
+ flags, unlet, rettv);
+ if (glv_status == GLV_FAIL) {
+ goto done;
}
-
- if (lp->ll_di == NULL) {
- // Can't add "v:" or "a:" variable.
- if (lp->ll_dict == &vimvardict
- || &lp->ll_dict->dv_hashtab == get_funccal_args_ht()) {
- semsg(_(e_illvar), name);
- tv_clear(&var1);
- return NULL;
- }
-
- // Key does not exist in dict: may need to add it.
- if (*p == '[' || *p == '.' || unlet) {
- if (!quiet) {
- semsg(_(e_dictkey), key);
- }
- tv_clear(&var1);
- return NULL;
- }
- if (len == -1) {
- lp->ll_newkey = xstrdup(key);
- } else {
- lp->ll_newkey = xmemdupz(key, (size_t)len);
- }
- tv_clear(&var1);
+ if (glv_status == GLV_STOP) {
break;
- // existing variable, need to check if it can be changed
- } else if (!(flags & GLV_READ_ONLY)
- && (var_check_ro(lp->ll_di->di_flags, name, (size_t)(p - name))
- || var_check_lock(lp->ll_di->di_flags, name, (size_t)(p - name)))) {
- tv_clear(&var1);
- return NULL;
}
-
- tv_clear(&var1);
- lp->ll_tv = &lp->ll_di->di_tv;
} else if (lp->ll_tv->v_type == VAR_BLOB) {
- // Get the number and item for the only or first index of the List.
- if (empty1) {
- lp->ll_n1 = 0;
- } else {
- // Is number or string.
- lp->ll_n1 = (int)tv_get_number(&var1);
+ if (get_lval_blob(lp, &var1, &var2, empty1, quiet) == FAIL) {
+ goto done;
}
- tv_clear(&var1);
-
- const int bloblen = tv_blob_len(lp->ll_tv->vval.v_blob);
- if (tv_blob_check_index(bloblen, lp->ll_n1, quiet) == FAIL) {
- tv_clear(&var2);
- return NULL;
- }
- if (lp->ll_range && !lp->ll_empty2) {
- lp->ll_n2 = (int)tv_get_number(&var2);
- tv_clear(&var2);
- if (tv_blob_check_range(bloblen, lp->ll_n1, lp->ll_n2, quiet) == FAIL) {
- return NULL;
- }
- }
- lp->ll_blob = lp->ll_tv->vval.v_blob;
- lp->ll_tv = NULL;
break;
} else {
- // Get the number and item for the only or first index of the List.
- if (empty1) {
- lp->ll_n1 = 0;
- } else {
- // Is number or string.
- lp->ll_n1 = (int)tv_get_number(&var1);
- }
- tv_clear(&var1);
-
- lp->ll_dict = NULL;
- lp->ll_list = lp->ll_tv->vval.v_list;
- lp->ll_li = tv_list_check_range_index_one(lp->ll_list, &lp->ll_n1, quiet);
- if (lp->ll_li == NULL) {
- tv_clear(&var2);
- return NULL;
- }
-
- // May need to find the item or absolute index for the second
- // index of a range.
- // When no index given: "lp->ll_empty2" is true.
- // Otherwise "lp->ll_n2" is set to the second index.
- if (lp->ll_range && !lp->ll_empty2) {
- lp->ll_n2 = (int)tv_get_number(&var2); // Is number or string.
- tv_clear(&var2);
- if (tv_list_check_range_index_two(lp->ll_list,
- &lp->ll_n1, lp->ll_li,
- &lp->ll_n2, quiet) == FAIL) {
- return NULL;
- }
+ if (get_lval_list(lp, &var1, &var2, empty1, flags, quiet) == FAIL) {
+ goto done;
}
-
- lp->ll_tv = TV_LIST_ITEM_TV(lp->ll_li);
}
+
+ tv_clear(&var1);
+ tv_clear(&var2);
+ var1.v_type = VAR_UNKNOWN;
+ var2.v_type = VAR_UNKNOWN;
}
+ rc = OK;
+
+done:
tv_clear(&var1);
- return p;
+ tv_clear(&var2);
+ return rc == OK ? p : NULL;
}
/// Get an lvalue
@@ -2381,9 +2543,10 @@ void clear_evalarg(evalarg_T *evalarg, exarg_T *eap)
}
}
-/// The "evaluate" argument: When false, the argument is only parsed but not
-/// executed. The function may return OK, but the rettv will be of type
-/// VAR_UNKNOWN. The function still returns FAIL for a syntax error.
+/// The "eval" functions have an "evalarg" argument: When NULL or
+/// "evalarg->eval_flags" does not have EVAL_EVALUATE, then the argument is only
+/// parsed but not executed. The functions may return OK, but the rettv will be
+/// of type VAR_UNKNOWN. The functions still returns FAIL for a syntax error.
/// Handle zero level expression.
/// This calls eval1() and handles error message and nextcmd.
@@ -2442,6 +2605,42 @@ int eval0(char *arg, typval_T *rettv, exarg_T *eap, evalarg_T *const evalarg)
return ret;
}
+/// If "arg" is a simple function call without arguments then call it and return
+/// the result. Otherwise return NOTDONE.
+static int may_call_simple_func(const char *arg, typval_T *rettv)
+{
+ const char *parens = strstr(arg, "()");
+ int r = NOTDONE;
+
+ // If the expression is "FuncName()" then we can skip a lot of overhead.
+ if (parens != NULL && *skipwhite(parens + 2) == NUL) {
+ if (strnequal(arg, "v:lua.", 6)) {
+ const char *p = arg + 6;
+ if (p != parens && skip_luafunc_name(p) == parens) {
+ r = call_simple_luafunc(p, (size_t)(parens - p), rettv);
+ }
+ } else {
+ const char *p = strncmp(arg, "<SNR>", 5) == 0 ? skipdigits(arg + 5) : arg;
+ if (to_name_end(p, true) == parens) {
+ r = call_simple_func(arg, (size_t)(parens - arg), rettv);
+ }
+ }
+ }
+ return r;
+}
+
+/// Handle zero level expression with optimization for a simple function call.
+/// Same arguments and return value as eval0().
+static int eval0_simple_funccal(char *arg, typval_T *rettv, exarg_T *eap, evalarg_T *const evalarg)
+{
+ int r = may_call_simple_func(arg, rettv);
+
+ if (r == NOTDONE) {
+ r = eval0(arg, rettv, eap, evalarg);
+ }
+ return r;
+}
+
/// Handle top level expression:
/// expr2 ? expr1 : expr1
/// expr2 ?? expr1
@@ -2832,6 +3031,93 @@ static int eval_addlist(typval_T *tv1, typval_T *tv2)
return OK;
}
+/// Concatenate strings "tv1" and "tv2" and store the result in "tv1".
+static int eval_concat_str(typval_T *tv1, typval_T *tv2)
+{
+ char buf1[NUMBUFLEN];
+ char buf2[NUMBUFLEN];
+ // s1 already checked
+ const char *const s1 = tv_get_string_buf(tv1, buf1);
+ const char *const s2 = tv_get_string_buf_chk(tv2, buf2);
+ if (s2 == NULL) { // Type error?
+ tv_clear(tv1);
+ tv_clear(tv2);
+ return FAIL;
+ }
+
+ char *p = concat_str(s1, s2);
+ tv_clear(tv1);
+ tv1->v_type = VAR_STRING;
+ tv1->vval.v_string = p;
+
+ return OK;
+}
+
+/// Add or subtract numbers "tv1" and "tv2" and store the result in "tv1".
+/// The numbers can be whole numbers or floats.
+static int eval_addsub_number(typval_T *tv1, typval_T *tv2, int op)
+{
+ bool error = false;
+ varnumber_T n1, n2;
+ float_T f1 = 0;
+ float_T f2 = 0;
+
+ if (tv1->v_type == VAR_FLOAT) {
+ f1 = tv1->vval.v_float;
+ n1 = 0;
+ } else {
+ n1 = tv_get_number_chk(tv1, &error);
+ if (error) {
+ // This can only happen for "list + non-list" or
+ // "blob + non-blob". For "non-list + ..." or
+ // "something - ...", we returned before evaluating the
+ // 2nd operand.
+ tv_clear(tv1);
+ tv_clear(tv2);
+ return FAIL;
+ }
+ if (tv2->v_type == VAR_FLOAT) {
+ f1 = (float_T)n1;
+ }
+ }
+ if (tv2->v_type == VAR_FLOAT) {
+ f2 = tv2->vval.v_float;
+ n2 = 0;
+ } else {
+ n2 = tv_get_number_chk(tv2, &error);
+ if (error) {
+ tv_clear(tv1);
+ tv_clear(tv2);
+ return FAIL;
+ }
+ if (tv1->v_type == VAR_FLOAT) {
+ f2 = (float_T)n2;
+ }
+ }
+ tv_clear(tv1);
+
+ // If there is a float on either side the result is a float.
+ if (tv1->v_type == VAR_FLOAT || tv2->v_type == VAR_FLOAT) {
+ if (op == '+') {
+ f1 = f1 + f2;
+ } else {
+ f1 = f1 - f2;
+ }
+ tv1->v_type = VAR_FLOAT;
+ tv1->vval.v_float = f1;
+ } else {
+ if (op == '+') {
+ n1 = n1 + n2;
+ } else {
+ n1 = n1 - n2;
+ }
+ tv1->v_type = VAR_NUMBER;
+ tv1->vval.v_number = n1;
+ }
+
+ return OK;
+}
+
/// Handle fourth level expression:
/// + number addition, concatenation of list or blob
/// - number subtraction
@@ -2887,20 +3173,9 @@ static int eval5(char **arg, typval_T *rettv, evalarg_T *const evalarg)
if (evaluate) {
// Compute the result.
if (op == '.') {
- char buf1[NUMBUFLEN];
- char buf2[NUMBUFLEN];
- // s1 already checked
- const char *const s1 = tv_get_string_buf(rettv, buf1);
- const char *const s2 = tv_get_string_buf_chk(&var2, buf2);
- if (s2 == NULL) { // Type error?
- tv_clear(rettv);
- tv_clear(&var2);
+ if (eval_concat_str(rettv, &var2) == FAIL) {
return FAIL;
}
- char *p = concat_str(s1, s2);
- tv_clear(rettv);
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = p;
} else if (op == '+' && rettv->v_type == VAR_BLOB && var2.v_type == VAR_BLOB) {
eval_addblob(rettv, &var2);
} else if (op == '+' && rettv->v_type == VAR_LIST && var2.v_type == VAR_LIST) {
@@ -2908,62 +3183,8 @@ static int eval5(char **arg, typval_T *rettv, evalarg_T *const evalarg)
return FAIL;
}
} else {
- bool error = false;
- varnumber_T n1, n2;
- float_T f1 = 0;
- float_T f2 = 0;
-
- if (rettv->v_type == VAR_FLOAT) {
- f1 = rettv->vval.v_float;
- n1 = 0;
- } else {
- n1 = tv_get_number_chk(rettv, &error);
- if (error) {
- // This can only happen for "list + non-list" or
- // "blob + non-blob". For "non-list + ..." or
- // "something - ...", we returned before evaluating the
- // 2nd operand.
- tv_clear(rettv);
- tv_clear(&var2);
- return FAIL;
- }
- if (var2.v_type == VAR_FLOAT) {
- f1 = (float_T)n1;
- }
- }
- if (var2.v_type == VAR_FLOAT) {
- f2 = var2.vval.v_float;
- n2 = 0;
- } else {
- n2 = tv_get_number_chk(&var2, &error);
- if (error) {
- tv_clear(rettv);
- tv_clear(&var2);
- return FAIL;
- }
- if (rettv->v_type == VAR_FLOAT) {
- f2 = (float_T)n2;
- }
- }
- tv_clear(rettv);
-
- // If there is a float on either side the result is a float.
- if (rettv->v_type == VAR_FLOAT || var2.v_type == VAR_FLOAT) {
- if (op == '+') {
- f1 = f1 + f2;
- } else {
- f1 = f1 - f2;
- }
- rettv->v_type = VAR_FLOAT;
- rettv->vval.v_float = f1;
- } else {
- if (op == '+') {
- n1 = n1 + n2;
- } else {
- n1 = n1 - n2;
- }
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = n1;
+ if (eval_addsub_number(rettv, &var2, op) == FAIL) {
+ return FAIL;
}
}
tv_clear(&var2);
@@ -2972,6 +3193,85 @@ static int eval5(char **arg, typval_T *rettv, evalarg_T *const evalarg)
return OK;
}
+/// Multiply or divide or compute the modulo of numbers "tv1" and "tv2" and
+/// store the result in "tv1". The numbers can be whole numbers or floats.
+static int eval_multdiv_number(typval_T *tv1, typval_T *tv2, int op)
+ FUNC_ATTR_NO_SANITIZE_UNDEFINED
+{
+ varnumber_T n1, n2;
+ bool use_float = false;
+
+ float_T f1 = 0;
+ float_T f2 = 0;
+ bool error = false;
+ if (tv1->v_type == VAR_FLOAT) {
+ f1 = tv1->vval.v_float;
+ use_float = true;
+ n1 = 0;
+ } else {
+ n1 = tv_get_number_chk(tv1, &error);
+ }
+ tv_clear(tv1);
+ if (error) {
+ tv_clear(tv2);
+ return FAIL;
+ }
+
+ if (tv2->v_type == VAR_FLOAT) {
+ if (!use_float) {
+ f1 = (float_T)n1;
+ use_float = true;
+ }
+ f2 = tv2->vval.v_float;
+ n2 = 0;
+ } else {
+ n2 = tv_get_number_chk(tv2, &error);
+ tv_clear(tv2);
+ if (error) {
+ return FAIL;
+ }
+ if (use_float) {
+ f2 = (float_T)n2;
+ }
+ }
+
+ // Compute the result.
+ // When either side is a float the result is a float.
+ if (use_float) {
+ if (op == '*') {
+ f1 = f1 * f2;
+ } else if (op == '/') {
+ // uncrustify:off
+
+ // Division by zero triggers error from AddressSanitizer
+ f1 = (f2 == 0 ? (
+#ifdef NAN
+ f1 == 0 ? (float_T)NAN :
+#endif
+ (f1 > 0 ? (float_T)INFINITY : (float_T)-INFINITY)) : f1 / f2);
+
+ // uncrustify:on
+ } else {
+ emsg(_("E804: Cannot use '%' with Float"));
+ return FAIL;
+ }
+ tv1->v_type = VAR_FLOAT;
+ tv1->vval.v_float = f1;
+ } else {
+ if (op == '*') {
+ n1 = n1 * n2;
+ } else if (op == '/') {
+ n1 = num_divide(n1, n2);
+ } else {
+ n1 = num_modulus(n1, n2);
+ }
+ tv1->v_type = VAR_NUMBER;
+ tv1->vval.v_number = n1;
+ }
+
+ return OK;
+}
+
/// Handle fifth level expression:
/// - * number multiplication
/// - / number division
@@ -2985,10 +3285,7 @@ static int eval5(char **arg, typval_T *rettv, evalarg_T *const evalarg)
/// float
/// @return OK or FAIL.
static int eval6(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool want_string)
- FUNC_ATTR_NO_SANITIZE_UNDEFINED
{
- bool use_float = false;
-
// Get the first variable.
if (eval7(arg, rettv, evalarg, want_string) == FAIL) {
return FAIL;
@@ -3001,26 +3298,7 @@ static int eval6(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool wan
break;
}
- varnumber_T n1, n2;
- float_T f1 = 0;
- float_T f2 = 0;
- bool error = false;
const bool evaluate = evalarg == NULL ? 0 : (evalarg->eval_flags & EVAL_EVALUATE);
- if (evaluate) {
- if (rettv->v_type == VAR_FLOAT) {
- f1 = rettv->vval.v_float;
- use_float = true;
- n1 = 0;
- } else {
- n1 = tv_get_number_chk(rettv, &error);
- }
- tv_clear(rettv);
- if (error) {
- return FAIL;
- }
- } else {
- n1 = 0;
- }
// Get the second variable.
*arg = skipwhite(*arg + 1);
@@ -3030,56 +3308,9 @@ static int eval6(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool wan
}
if (evaluate) {
- if (var2.v_type == VAR_FLOAT) {
- if (!use_float) {
- f1 = (float_T)n1;
- use_float = true;
- }
- f2 = var2.vval.v_float;
- n2 = 0;
- } else {
- n2 = tv_get_number_chk(&var2, &error);
- tv_clear(&var2);
- if (error) {
- return FAIL;
- }
- if (use_float) {
- f2 = (float_T)n2;
- }
- }
-
// Compute the result.
- // When either side is a float the result is a float.
- if (use_float) {
- if (op == '*') {
- f1 = f1 * f2;
- } else if (op == '/') {
- // uncrustify:off
-
- // Division by zero triggers error from AddressSanitizer
- f1 = (f2 == 0 ? (
-#ifdef NAN
- f1 == 0 ? (float_T)NAN :
-#endif
- (f1 > 0 ? (float_T)INFINITY : (float_T)-INFINITY)) : f1 / f2);
-
- // uncrustify:on
- } else {
- emsg(_("E804: Cannot use '%' with Float"));
- return FAIL;
- }
- rettv->v_type = VAR_FLOAT;
- rettv->vval.v_float = f1;
- } else {
- if (op == '*') {
- n1 = n1 * n2;
- } else if (op == '/') {
- n1 = num_divide(n1, n2);
- } else {
- n1 = num_modulus(n1, n2);
- }
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = n1;
+ if (eval_multdiv_number(rettv, &var2, op) == FAIL) {
+ return FAIL;
}
}
}
@@ -3183,14 +3414,9 @@ static int eval7(char **arg, typval_T *rettv, evalarg_T *const evalarg, bool wan
ret = eval_list(arg, rettv, evalarg);
break;
- // Dictionary: #{key: val, key: val}
+ // Literal Dictionary: #{key: val, key: val}
case '#':
- if ((*arg)[1] == '{') {
- (*arg)++;
- ret = eval_dict(arg, rettv, evalarg, true);
- } else {
- ret = NOTDONE;
- }
+ ret = eval_lit_dict(arg, rettv, evalarg);
break;
// Lambda: {arg, arg -> expr}
@@ -3480,20 +3706,22 @@ static int eval_method(char **const arg, typval_T *const rettv, evalarg_T *const
int len;
char *name = *arg;
char *lua_funcname = NULL;
+ char *alias = NULL;
if (strnequal(name, "v:lua.", 6)) {
lua_funcname = name + 6;
*arg = (char *)skip_luafunc_name(lua_funcname);
*arg = skipwhite(*arg); // to detect trailing whitespace later
len = (int)(*arg - lua_funcname);
} else {
- char *alias;
len = get_name_len((const char **)arg, &alias, evaluate, true);
if (alias != NULL) {
name = alias;
}
}
- int ret;
+ char *tofree = NULL;
+ int ret = OK;
+
if (len <= 0) {
if (verbose) {
if (lua_funcname == NULL) {
@@ -3504,25 +3732,79 @@ static int eval_method(char **const arg, typval_T *const rettv, evalarg_T *const
}
ret = FAIL;
} else {
- if (**arg != '(') {
- if (verbose) {
- semsg(_(e_missingparen), name);
- }
- ret = FAIL;
- } else if (ascii_iswhite((*arg)[-1])) {
- if (verbose) {
- emsg(_(e_nowhitespace));
+ *arg = skipwhite(*arg);
+
+ // If there is no "(" immediately following, but there is further on,
+ // it can be "dict.Func()", "list[nr]", etc.
+ // Does not handle anything where "(" is part of the expression.
+ char *paren;
+ if (**arg != '(' && lua_funcname == NULL && alias == NULL
+ && (paren = vim_strchr(*arg, '(')) != NULL) {
+ *arg = name;
+ *paren = NUL;
+ typval_T ref;
+ ref.v_type = VAR_UNKNOWN;
+ if (eval7(arg, &ref, evalarg, false) == FAIL) {
+ *arg = name + len;
+ ret = FAIL;
+ } else if (*skipwhite(*arg) != NUL) {
+ if (verbose) {
+ semsg(_(e_trailing_arg), *arg);
+ }
+ ret = FAIL;
+ } else if (ref.v_type == VAR_FUNC && ref.vval.v_string != NULL) {
+ name = ref.vval.v_string;
+ ref.vval.v_string = NULL;
+ tofree = name;
+ len = (int)strlen(name);
+ } else if (ref.v_type == VAR_PARTIAL && ref.vval.v_partial != NULL) {
+ if (ref.vval.v_partial->pt_argc > 0 || ref.vval.v_partial->pt_dict != NULL) {
+ if (verbose) {
+ emsg(_(e_cannot_use_partial_here));
+ }
+ ret = FAIL;
+ } else {
+ name = xstrdup(partial_name(ref.vval.v_partial));
+ tofree = name;
+ if (name == NULL) {
+ ret = FAIL;
+ name = *arg;
+ } else {
+ len = (int)strlen(name);
+ }
+ }
+ } else {
+ if (verbose) {
+ semsg(_(e_not_callable_type_str), name);
+ }
+ ret = FAIL;
}
- ret = FAIL;
- } else if (lua_funcname != NULL) {
- if (evaluate) {
- rettv->v_type = VAR_PARTIAL;
- rettv->vval.v_partial = vvlua_partial;
- rettv->vval.v_partial->pt_refcount++;
+ tv_clear(&ref);
+ *paren = '(';
+ }
+
+ if (ret == OK) {
+ if (**arg != '(') {
+ if (verbose) {
+ semsg(_(e_missingparen), name);
+ }
+ ret = FAIL;
+ } else if (ascii_iswhite((*arg)[-1])) {
+ if (verbose) {
+ emsg(_(e_nowhitespace));
+ }
+ ret = FAIL;
+ } else if (lua_funcname != NULL) {
+ if (evaluate) {
+ rettv->v_type = VAR_PARTIAL;
+ rettv->vval.v_partial = vvlua_partial;
+ rettv->vval.v_partial->pt_refcount++;
+ }
+ ret = call_func_rettv(arg, evalarg, rettv, evaluate, NULL, &base, lua_funcname);
+ } else {
+ ret = eval_func(arg, evalarg, name, len, rettv,
+ evaluate ? EVAL_EVALUATE : 0, &base);
}
- ret = call_func_rettv(arg, evalarg, rettv, evaluate, NULL, &base, lua_funcname);
- } else {
- ret = eval_func(arg, evalarg, name, len, rettv, evaluate ? EVAL_EVALUATE : 0, &base);
}
}
@@ -3531,6 +3813,11 @@ static int eval_method(char **const arg, typval_T *const rettv, evalarg_T *const
if (evaluate) {
tv_clear(&base);
}
+ xfree(tofree);
+
+ if (alias != NULL) {
+ xfree(alias);
+ }
return ret;
}
@@ -3669,7 +3956,7 @@ static int check_can_index(typval_T *rettv, bool evaluate, bool verbose)
/// slice() function
void f_slice(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- if (check_can_index(argvars, true, false) != OK) {
+ if (check_can_index(&argvars[0], true, false) != OK) {
return;
}
@@ -4386,7 +4673,7 @@ bool func_equal(typval_T *tv1, typval_T *tv2, bool ic)
if (d1 != d2) {
return false;
}
- } else if (!tv_dict_equal(d1, d2, ic, true)) {
+ } else if (!tv_dict_equal(d1, d2, ic)) {
return false;
}
@@ -4398,7 +4685,7 @@ bool func_equal(typval_T *tv1, typval_T *tv2, bool ic)
}
for (int i = 0; i < a1; i++) {
if (!tv_equal(tv1->vval.v_partial->pt_argv + i,
- tv2->vval.v_partial->pt_argv + i, ic, true)) {
+ tv2->vval.v_partial->pt_argv + i, ic)) {
return false;
}
}
@@ -4493,19 +4780,6 @@ bool garbage_collect(bool testing)
FOR_ALL_BUFFERS(buf) {
// buffer-local variables
ABORTING(set_ref_in_item)(&buf->b_bufvar.di_tv, copyID, NULL, NULL);
- // buffer marks (ShaDa additional data)
- ABORTING(set_ref_in_fmark)(buf->b_last_cursor, copyID);
- ABORTING(set_ref_in_fmark)(buf->b_last_insert, copyID);
- ABORTING(set_ref_in_fmark)(buf->b_last_change, copyID);
- for (size_t i = 0; i < NMARKS; i++) {
- ABORTING(set_ref_in_fmark)(buf->b_namedm[i], copyID);
- }
- // buffer change list (ShaDa additional data)
- for (int i = 0; i < buf->b_changelistlen; i++) {
- ABORTING(set_ref_in_fmark)(buf->b_changelist[i], copyID);
- }
- // buffer ShaDa additional data
- ABORTING(set_ref_dict)(buf->additional_data, copyID);
// buffer callback functions
ABORTING(set_ref_in_callback)(&buf->b_prompt_callback, copyID, NULL, NULL);
@@ -4528,10 +4802,6 @@ bool garbage_collect(bool testing)
FOR_ALL_TAB_WINDOWS(tp, wp) {
// window-local variables
ABORTING(set_ref_in_item)(&wp->w_winvar.di_tv, copyID, NULL, NULL);
- // window jump list (ShaDa additional data)
- for (int i = 0; i < wp->w_jumplistlen; i++) {
- ABORTING(set_ref_in_fmark)(wp->w_jumplist[i].fmark, copyID);
- }
}
// window-local variables in autocmd windows
for (int i = 0; i < AUCMD_WIN_COUNT; i++) {
@@ -4548,9 +4818,6 @@ bool garbage_collect(bool testing)
char name = NUL;
bool is_unnamed = false;
reg_iter = op_global_reg_iter(reg_iter, &name, &reg, &is_unnamed);
- if (name != NUL) {
- ABORTING(set_ref_dict)(reg.additional_data, copyID);
- }
} while (reg_iter != ITER_REGISTER_NULL);
}
@@ -4561,9 +4828,6 @@ bool garbage_collect(bool testing)
xfmark_T fm;
char name = NUL;
mark_iter = mark_global_iter(mark_iter, &name, &fm);
- if (name != NUL) {
- ABORTING(set_ref_dict)(fm.fmark.additional_data, copyID);
- }
} while (mark_iter != NULL);
}
@@ -4605,36 +4869,6 @@ bool garbage_collect(bool testing)
// v: vars
ABORTING(set_ref_in_ht)(&vimvarht, copyID, NULL);
- // history items (ShaDa additional elements)
- if (p_hi) {
- for (int i = 0; i < HIST_COUNT; i++) {
- const void *iter = NULL;
- do {
- histentry_T hist;
- iter = hist_iter(iter, (uint8_t)i, false, &hist);
- if (hist.hisstr != NULL) {
- ABORTING(set_ref_list)(hist.additional_elements, copyID);
- }
- } while (iter != NULL);
- }
- }
-
- // previously used search/substitute patterns (ShaDa additional data)
- {
- SearchPattern pat;
- get_search_pattern(&pat);
- ABORTING(set_ref_dict)(pat.additional_data, copyID);
- get_substitute_pattern(&pat);
- ABORTING(set_ref_dict)(pat.additional_data, copyID);
- }
-
- // previously used replacement string
- {
- SubReplacementString sub;
- sub_get_replacement(&sub);
- ABORTING(set_ref_list)(sub.additional_elements, copyID);
- }
-
ABORTING(set_ref_in_quickfix)(copyID);
bool did_free = false;
@@ -4916,52 +5150,6 @@ bool set_ref_in_item(typval_T *tv, int copyID, ht_stack_T **ht_stack, list_stack
return abort;
}
-/// Mark all lists and dicts referenced in given mark
-///
-/// @return true if setting references failed somehow.
-static inline bool set_ref_in_fmark(fmark_T fm, int copyID)
- FUNC_ATTR_WARN_UNUSED_RESULT
-{
- if (fm.additional_data != NULL
- && fm.additional_data->dv_copyID != copyID) {
- fm.additional_data->dv_copyID = copyID;
- return set_ref_in_ht(&fm.additional_data->dv_hashtab, copyID, NULL);
- }
- return false;
-}
-
-/// Mark all lists and dicts referenced in given list and the list itself
-///
-/// @return true if setting references failed somehow.
-static inline bool set_ref_list(list_T *list, int copyID)
- FUNC_ATTR_WARN_UNUSED_RESULT
-{
- if (list != NULL) {
- typval_T tv = (typval_T) {
- .v_type = VAR_LIST,
- .vval = { .v_list = list }
- };
- return set_ref_in_item(&tv, copyID, NULL, NULL);
- }
- return false;
-}
-
-/// Mark all lists and dicts referenced in given dict and the dict itself
-///
-/// @return true if setting references failed somehow.
-static inline bool set_ref_dict(dict_T *dict, int copyID)
- FUNC_ATTR_WARN_UNUSED_RESULT
-{
- if (dict != NULL) {
- typval_T tv = (typval_T) {
- .v_type = VAR_DICT,
- .vval = { .v_dict = dict }
- };
- return set_ref_in_item(&tv, copyID, NULL, NULL);
- }
- return false;
-}
-
/// Get the key for #{key: val} into "tv" and advance "arg".
///
/// @return FAIL when there is no valid key.
@@ -5093,6 +5281,24 @@ failret:
return OK;
}
+/// Evaluate a literal dictionary: #{key: val, key: val}
+/// "*arg" points to the "#".
+/// On return, "*arg" points to the character after the Dict.
+/// Return OK or FAIL. Returns NOTDONE for {expr}.
+static int eval_lit_dict(char **arg, typval_T *rettv, evalarg_T *const evalarg)
+{
+ int ret = OK;
+
+ if ((*arg)[1] == '{') {
+ (*arg)++;
+ ret = eval_dict(arg, rettv, evalarg, true);
+ } else {
+ ret = NOTDONE;
+ }
+
+ return ret;
+}
+
/// Convert the string to a floating point number
///
/// This uses strtod(). setlocale(LC_NUMERIC, "C") has been used earlier to
@@ -5569,317 +5775,6 @@ void f_foreach(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
filter_map(argvars, rettv, FILTERMAP_FOREACH);
}
-/// "function()" function
-/// "funcref()" function
-void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref)
-{
- char *s;
- char *name;
- bool use_string = false;
- partial_T *arg_pt = NULL;
- char *trans_name = NULL;
-
- if (argvars[0].v_type == VAR_FUNC) {
- // function(MyFunc, [arg], dict)
- s = argvars[0].vval.v_string;
- } else if (argvars[0].v_type == VAR_PARTIAL
- && argvars[0].vval.v_partial != NULL) {
- // function(dict.MyFunc, [arg])
- arg_pt = argvars[0].vval.v_partial;
- s = partial_name(arg_pt);
- // TODO(bfredl): do the entire nlua_is_table_from_lua dance
- } else {
- // function('MyFunc', [arg], dict)
- s = (char *)tv_get_string(&argvars[0]);
- use_string = true;
- }
-
- if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) {
- name = s;
- trans_name = save_function_name(&name, false,
- TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL);
- if (*name != NUL) {
- s = NULL;
- }
- }
- if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s))
- || (is_funcref && trans_name == NULL)) {
- semsg(_(e_invarg2), (use_string ? tv_get_string(&argvars[0]) : s));
- // Don't check an autoload name for existence here.
- } else if (trans_name != NULL
- && (is_funcref
- ? find_func(trans_name) == NULL
- : !translated_function_exists(trans_name))) {
- semsg(_("E700: Unknown function: %s"), s);
- } else {
- int dict_idx = 0;
- int arg_idx = 0;
- list_T *list = NULL;
- if (strncmp(s, "s:", 2) == 0 || strncmp(s, "<SID>", 5) == 0) {
- // Expand s: and <SID> into <SNR>nr_, so that the function can
- // also be called from another script. Using trans_function_name()
- // would also work, but some plugins depend on the name being
- // printable text.
- name = get_scriptlocal_funcname(s);
- } else {
- name = xstrdup(s);
- }
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_UNKNOWN) {
- // function(name, [args], dict)
- arg_idx = 1;
- dict_idx = 2;
- } else if (argvars[1].v_type == VAR_DICT) {
- // function(name, dict)
- dict_idx = 1;
- } else {
- // function(name, [args])
- arg_idx = 1;
- }
- if (dict_idx > 0) {
- if (tv_check_for_dict_arg(argvars, dict_idx) == FAIL) {
- xfree(name);
- goto theend;
- }
- if (argvars[dict_idx].vval.v_dict == NULL) {
- dict_idx = 0;
- }
- }
- if (arg_idx > 0) {
- if (argvars[arg_idx].v_type != VAR_LIST) {
- emsg(_("E923: Second argument of function() must be "
- "a list or a dict"));
- xfree(name);
- goto theend;
- }
- list = argvars[arg_idx].vval.v_list;
- if (tv_list_len(list) == 0) {
- arg_idx = 0;
- } else if (tv_list_len(list) > MAX_FUNC_ARGS) {
- emsg_funcname(e_toomanyarg, s);
- xfree(name);
- goto theend;
- }
- }
- }
- if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL || is_funcref) {
- partial_T *const pt = xcalloc(1, sizeof(*pt));
-
- // result is a VAR_PARTIAL
- if (arg_idx > 0 || (arg_pt != NULL && arg_pt->pt_argc > 0)) {
- const int arg_len = (arg_pt == NULL ? 0 : arg_pt->pt_argc);
- const int lv_len = tv_list_len(list);
-
- pt->pt_argc = arg_len + lv_len;
- pt->pt_argv = xmalloc(sizeof(pt->pt_argv[0]) * (size_t)pt->pt_argc);
- int i = 0;
- for (; i < arg_len; i++) {
- tv_copy(&arg_pt->pt_argv[i], &pt->pt_argv[i]);
- }
- if (lv_len > 0) {
- TV_LIST_ITER(list, li, {
- tv_copy(TV_LIST_ITEM_TV(li), &pt->pt_argv[i++]);
- });
- }
- }
-
- // For "function(dict.func, [], dict)" and "func" is a partial
- // use "dict". That is backwards compatible.
- if (dict_idx > 0) {
- // The dict is bound explicitly, pt_auto is false
- pt->pt_dict = argvars[dict_idx].vval.v_dict;
- (pt->pt_dict->dv_refcount)++;
- } else if (arg_pt != NULL) {
- // If the dict was bound automatically the result is also
- // bound automatically.
- pt->pt_dict = arg_pt->pt_dict;
- pt->pt_auto = arg_pt->pt_auto;
- if (pt->pt_dict != NULL) {
- (pt->pt_dict->dv_refcount)++;
- }
- }
-
- pt->pt_refcount = 1;
- if (arg_pt != NULL && arg_pt->pt_func != NULL) {
- pt->pt_func = arg_pt->pt_func;
- func_ptr_ref(pt->pt_func);
- xfree(name);
- } else if (is_funcref) {
- pt->pt_func = find_func(trans_name);
- func_ptr_ref(pt->pt_func);
- xfree(name);
- } else {
- pt->pt_name = name;
- func_ref(name);
- }
-
- rettv->v_type = VAR_PARTIAL;
- rettv->vval.v_partial = pt;
- } else {
- // result is a VAR_FUNC
- rettv->v_type = VAR_FUNC;
- rettv->vval.v_string = name;
- func_ref(name);
- }
- }
-theend:
- xfree(trans_name);
-}
-
-/// Get the line number from Vimscript object
-///
-/// @note Unlike tv_get_lnum(), this one supports only "$" special string.
-///
-/// @param[in] tv Object to get value from. Is expected to be a number or
-/// a special string "$".
-/// @param[in] buf Buffer to take last line number from in case tv is "$". May
-/// be NULL, in this case "$" results in zero return.
-///
-/// @return Line number or 0 in case of error.
-linenr_T tv_get_lnum_buf(const typval_T *const tv, const buf_T *const buf)
- FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT
-{
- if (tv->v_type == VAR_STRING
- && tv->vval.v_string != NULL
- && tv->vval.v_string[0] == '$'
- && tv->vval.v_string[1] == NUL
- && buf != NULL) {
- return buf->b_ml.ml_line_count;
- }
- return (linenr_T)tv_get_number_chk(tv, NULL);
-}
-
-/// This function is used by f_input() and f_inputdialog() functions. The third
-/// argument to f_input() specifies the type of completion to use at the
-/// prompt. The third argument to f_inputdialog() specifies the value to return
-/// when the user cancels the prompt.
-void get_user_input(const typval_T *const argvars, typval_T *const rettv, const bool inputdialog,
- const bool secret)
- FUNC_ATTR_NONNULL_ALL
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- const char *prompt;
- const char *defstr = "";
- typval_T *cancelreturn = NULL;
- typval_T cancelreturn_strarg2 = TV_INITIAL_VALUE;
- const char *xp_name = NULL;
- Callback input_callback = { .type = kCallbackNone };
- char prompt_buf[NUMBUFLEN];
- char defstr_buf[NUMBUFLEN];
- char cancelreturn_buf[NUMBUFLEN];
- char xp_name_buf[NUMBUFLEN];
- char def[1] = { 0 };
- if (argvars[0].v_type == VAR_DICT) {
- if (argvars[1].v_type != VAR_UNKNOWN) {
- emsg(_("E5050: {opts} must be the only argument"));
- return;
- }
- dict_T *const dict = argvars[0].vval.v_dict;
- prompt = tv_dict_get_string_buf_chk(dict, S_LEN("prompt"), prompt_buf, "");
- if (prompt == NULL) {
- return;
- }
- defstr = tv_dict_get_string_buf_chk(dict, S_LEN("default"), defstr_buf, "");
- if (defstr == NULL) {
- return;
- }
- dictitem_T *cancelreturn_di = tv_dict_find(dict, S_LEN("cancelreturn"));
- if (cancelreturn_di != NULL) {
- cancelreturn = &cancelreturn_di->di_tv;
- }
- xp_name = tv_dict_get_string_buf_chk(dict, S_LEN("completion"),
- xp_name_buf, def);
- if (xp_name == NULL) { // error
- return;
- }
- if (xp_name == def) { // default to NULL
- xp_name = NULL;
- }
- if (!tv_dict_get_callback(dict, S_LEN("highlight"), &input_callback)) {
- return;
- }
- } else {
- prompt = tv_get_string_buf_chk(&argvars[0], prompt_buf);
- if (prompt == NULL) {
- return;
- }
- if (argvars[1].v_type != VAR_UNKNOWN) {
- defstr = tv_get_string_buf_chk(&argvars[1], defstr_buf);
- if (defstr == NULL) {
- return;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- const char *const strarg2 = tv_get_string_buf_chk(&argvars[2], cancelreturn_buf);
- if (strarg2 == NULL) {
- return;
- }
- if (inputdialog) {
- cancelreturn_strarg2.v_type = VAR_STRING;
- cancelreturn_strarg2.vval.v_string = (char *)strarg2;
- cancelreturn = &cancelreturn_strarg2;
- } else {
- xp_name = strarg2;
- }
- }
- }
- }
-
- int xp_type = EXPAND_NOTHING;
- char *xp_arg = NULL;
- if (xp_name != NULL) {
- // input() with a third argument: completion
- const int xp_namelen = (int)strlen(xp_name);
-
- uint32_t argt = 0;
- if (parse_compl_arg(xp_name, xp_namelen, &xp_type,
- &argt, &xp_arg) == FAIL) {
- return;
- }
- }
-
- const bool cmd_silent_save = cmd_silent;
-
- cmd_silent = false; // Want to see the prompt.
- // Only the part of the message after the last NL is considered as
- // prompt for the command line, unlsess cmdline is externalized
- const char *p = prompt;
- if (!ui_has(kUICmdline)) {
- const char *lastnl = strrchr(prompt, '\n');
- if (lastnl != NULL) {
- p = lastnl + 1;
- msg_start();
- msg_clr_eos();
- msg_puts_len(prompt, p - prompt, echo_attr);
- msg_didout = false;
- msg_starthere();
- }
- }
- cmdline_row = msg_row;
-
- stuffReadbuffSpec(defstr);
-
- const int save_ex_normal_busy = ex_normal_busy;
- ex_normal_busy = 0;
- rettv->vval.v_string = getcmdline_prompt(secret ? NUL : '@', p, echo_attr, xp_type, xp_arg,
- input_callback);
- ex_normal_busy = save_ex_normal_busy;
- callback_free(&input_callback);
-
- if (rettv->vval.v_string == NULL && cancelreturn != NULL) {
- tv_copy(cancelreturn, rettv);
- }
-
- xfree(xp_arg);
-
- // Since the user typed this, no need to wait for return.
- need_wait_return = false;
- msg_didout = false;
- cmd_silent = cmd_silent_save;
-}
-
/// Builds a process argument vector from a Vimscript object (typval_T).
///
/// @param[in] cmd_tv Vimscript object
@@ -5948,56 +5843,6 @@ char **tv_to_argv(typval_T *cmd_tv, const char **cmd, bool *executable)
return argv;
}
-void return_register(int regname, typval_T *rettv)
-{
- char buf[2] = { (char)regname, 0 };
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = xstrdup(buf);
-}
-
-void screenchar_adjust(ScreenGrid **grid, int *row, int *col)
-{
- // TODO(bfredl): this is a hack for legacy tests which use screenchar()
- // to check printed messages on the screen (but not floats etc
- // as these are not legacy features). If the compositor is refactored to
- // have its own buffer, this should just read from it instead.
- msg_scroll_flush();
-
- *grid = ui_comp_get_grid_at_coord(*row, *col);
-
- // Make `row` and `col` relative to the grid
- *row -= (*grid)->comp_row;
- *col -= (*grid)->comp_col;
-}
-
-/// "stdpath()" helper for list results
-void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
- FUNC_ATTR_NONNULL_ALL
-{
- list_T *const list = tv_list_alloc(kListLenShouldKnow);
- rettv->v_type = VAR_LIST;
- rettv->vval.v_list = list;
- tv_list_ref(list);
- char *const dirs = stdpaths_get_xdg_var(xdg);
- if (dirs == NULL) {
- return;
- }
- const void *iter = NULL;
- const char *appname = get_appname();
- do {
- size_t dir_len;
- const char *dir;
- 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, appname, true);
- tv_list_append_allocated_string(list, dir_with_nvim);
- }
- } while (iter != NULL);
- xfree(dirs);
-}
-
static list_T *string_to_list(const char *str, size_t len, const bool keepempty)
{
if (!keepempty && str[len - 1] == NL) {
@@ -6417,149 +6262,6 @@ void timer_teardown(void)
timer_stop_all();
}
-/// Write "list" of strings to file "fd".
-///
-/// @param fp File to write to.
-/// @param[in] list List to write.
-/// @param[in] binary Whether to write in binary mode.
-///
-/// @return true in case of success, false otherwise.
-bool write_list(FileDescriptor *const fp, const list_T *const list, const bool binary)
- FUNC_ATTR_NONNULL_ARG(1)
-{
- int error = 0;
- TV_LIST_ITER_CONST(list, li, {
- const char *const s = tv_get_string_chk(TV_LIST_ITEM_TV(li));
- if (s == NULL) {
- return false;
- }
- const char *hunk_start = s;
- for (const char *p = hunk_start;; p++) {
- if (*p == NUL || *p == NL) {
- if (p != hunk_start) {
- const ptrdiff_t written = file_write(fp, hunk_start,
- (size_t)(p - hunk_start));
- if (written < 0) {
- error = (int)written;
- goto write_list_error;
- }
- }
- if (*p == NUL) {
- break;
- } else {
- hunk_start = p + 1;
- const ptrdiff_t written = file_write(fp, (char[]){ NUL }, 1);
- if (written < 0) {
- error = (int)written;
- break;
- }
- }
- }
- }
- if (!binary || TV_LIST_ITEM_NEXT(list, li) != NULL) {
- const ptrdiff_t written = file_write(fp, "\n", 1);
- if (written < 0) {
- error = (int)written;
- goto write_list_error;
- }
- }
- });
- if ((error = file_flush(fp)) != 0) {
- goto write_list_error;
- }
- return true;
-write_list_error:
- semsg(_(e_write2), os_strerror(error));
- return false;
-}
-
-/// Write a blob to file with descriptor `fp`.
-///
-/// @param[in] fp File to write to.
-/// @param[in] blob Blob to write.
-///
-/// @return true on success, or false on failure.
-bool write_blob(FileDescriptor *const fp, const blob_T *const blob)
- FUNC_ATTR_NONNULL_ARG(1)
-{
- int error = 0;
- const int len = tv_blob_len(blob);
- if (len > 0) {
- const ptrdiff_t written = file_write(fp, blob->bv_ga.ga_data, (size_t)len);
- if (written < (ptrdiff_t)len) {
- error = (int)written;
- goto write_blob_error;
- }
- }
- error = file_flush(fp);
- if (error != 0) {
- goto write_blob_error;
- }
- return true;
-write_blob_error:
- semsg(_(e_write2), os_strerror(error));
- return false;
-}
-
-/// Read blob from file "fd".
-/// Caller has allocated a blob in "rettv".
-///
-/// @param[in] fd File to read from.
-/// @param[in,out] rettv Blob to write to.
-/// @param[in] offset Read the file from the specified offset.
-/// @param[in] size Read the specified size, or -1 if no limit.
-///
-/// @return OK on success, or FAIL on failure.
-int read_blob(FILE *const fd, typval_T *rettv, off_T offset, off_T size_arg)
- FUNC_ATTR_NONNULL_ALL
-{
- blob_T *const blob = rettv->vval.v_blob;
- FileInfo file_info;
- if (!os_fileinfo_fd(fileno(fd), &file_info)) {
- return FAIL; // can't read the file, error
- }
-
- int whence;
- off_T size = size_arg;
- const off_T file_size = (off_T)os_fileinfo_size(&file_info);
- if (offset >= 0) {
- // The size defaults to the whole file. If a size is given it is
- // limited to not go past the end of the file.
- if (size == -1 || (size > file_size - offset && !S_ISCHR(file_info.stat.st_mode))) {
- // size may become negative, checked below
- size = (off_T)os_fileinfo_size(&file_info) - offset;
- }
- whence = SEEK_SET;
- } else {
- // limit the offset to not go before the start of the file
- if (-offset > file_size && !S_ISCHR(file_info.stat.st_mode)) {
- offset = -file_size;
- }
- // Size defaults to reading until the end of the file.
- if (size == -1 || size > -offset) {
- size = -offset;
- }
- whence = SEEK_END;
- }
- if (size <= 0) {
- return OK;
- }
- if (offset != 0 && vim_fseek(fd, offset, whence) != 0) {
- return OK;
- }
-
- ga_grow(&blob->bv_ga, (int)size);
- blob->bv_ga.ga_len = (int)size;
- if (fread(blob->bv_ga.ga_data, 1, (size_t)blob->bv_ga.ga_len, fd)
- < (size_t)blob->bv_ga.ga_len) {
- // An empty blob is returned on error.
- tv_blob_free(rettv->vval.v_blob);
- rettv->vval.v_blob = NULL;
- return FAIL;
- }
- return OK;
-}
-
/// Saves a typval_T as a string.
///
/// For lists or buffers, replaces NLs with NUL and separates items with NLs.
@@ -7146,13 +6848,13 @@ static char *make_expanded_name(const char *in_start, char *expr_start, char *ex
char c1 = *in_end;
*in_end = NUL;
- char *temp_result = eval_to_string(expr_start + 1, false);
+ char *temp_result = eval_to_string(expr_start + 1, false, false);
if (temp_result != NULL) {
retval = xmalloc(strlen(temp_result) + (size_t)(expr_start - in_start)
+ (size_t)(in_end - expr_end) + 1);
STRCPY(retval, in_start);
- STRCAT(retval, temp_result);
- STRCAT(retval, expr_end + 1);
+ strcat(retval, temp_result);
+ strcat(retval, expr_end + 1);
}
xfree(temp_result);
@@ -8201,6 +7903,12 @@ void ex_echohl(exarg_T *eap)
echo_attr = syn_name2attr(eap->arg);
}
+/// Returns the :echo attribute
+int get_echo_attr(void)
+{
+ return echo_attr;
+}
+
/// ":execute expr1 ..." execute the result of an expression.
/// ":echomsg expr1 ..." Print a message
/// ":echoerr expr1 ..." Print an error
@@ -8796,7 +8504,7 @@ Channel *find_job(uint64_t id, bool show_error)
{
Channel *data = find_channel(id);
if (!data || data->streamtype != kChannelStreamProc
- || process_is_stopped(&data->stream.proc)) {
+ || proc_is_stopped(&data->stream.proc)) {
if (show_error) {
if (data && data->streamtype != kChannelStreamProc) {
emsg(_(e_invchanjob));
@@ -8907,7 +8615,7 @@ bool eval_has_provider(const char *feat, bool throw_if_fast)
char name[32]; // Normalized: "python3_compiled" => "python3".
snprintf(name, sizeof(name), "%s", feat);
- strchrsub(name, '_', '\0'); // Chop any "_xx" suffix.
+ strchrsub(name, '_', NUL); // Chop any "_xx" suffix.
char buf[256];
typval_T tv;
@@ -9062,7 +8770,7 @@ int typval_compare(typval_T *typ1, typval_T *typ2, exprtype_T type, bool ic)
return FAIL;
} else {
// Compare two Lists for being equal or unequal.
- n1 = tv_list_equal(typ1->vval.v_list, typ2->vval.v_list, ic, false);
+ n1 = tv_list_equal(typ1->vval.v_list, typ2->vval.v_list, ic);
if (type == EXPR_NEQUAL) {
n1 = !n1;
}
@@ -9085,7 +8793,7 @@ int typval_compare(typval_T *typ1, typval_T *typ2, exprtype_T type, bool ic)
return FAIL;
} else {
// Compare two Dictionaries for being equal or unequal.
- n1 = tv_dict_equal(typ1->vval.v_dict, typ2->vval.v_dict, ic, false);
+ n1 = tv_dict_equal(typ1->vval.v_dict, typ2->vval.v_dict, ic);
if (type == EXPR_NEQUAL) {
n1 = !n1;
}
@@ -9106,14 +8814,14 @@ int typval_compare(typval_T *typ1, typval_T *typ2, exprtype_T type, bool ic)
if (typ1->v_type == VAR_FUNC && typ2->v_type == VAR_FUNC) {
// strings are considered the same if their value is
// the same
- n1 = tv_equal(typ1, typ2, ic, false);
+ n1 = tv_equal(typ1, typ2, ic);
} else if (typ1->v_type == VAR_PARTIAL && typ2->v_type == VAR_PARTIAL) {
n1 = typ1->vval.v_partial == typ2->vval.v_partial;
} else {
n1 = false;
}
} else {
- n1 = tv_equal(typ1, typ2, ic, false);
+ n1 = tv_equal(typ1, typ2, ic);
}
if (type == EXPR_NEQUAL || type == EXPR_ISNOT) {
n1 = !n1;
diff --git a/src/nvim/eval.h b/src/nvim/eval.h
index d83af70ef7..bb9b00abc7 100644
--- a/src/nvim/eval.h
+++ b/src/nvim/eval.h
@@ -61,7 +61,7 @@ typedef struct {
bool ll_empty2; ///< Second index is empty: [i:].
int ll_n1; ///< First index for list.
int ll_n2; ///< Second index for list range.
- dict_T *ll_dict; ///< The Dictionary or NULL.
+ dict_T *ll_dict; ///< The Dict or NULL.
dictitem_T *ll_di; ///< The dictitem or NULL.
char *ll_newkey; ///< New key for Dict in allocated memory or NULL.
blob_T *ll_blob; ///< The Blob or NULL.
@@ -172,7 +172,7 @@ typedef enum {
VV_MSGPACK_TYPES,
VV__NULL_STRING, // String with NULL value. For test purposes only.
VV__NULL_LIST, // List with NULL value. For test purposes only.
- VV__NULL_DICT, // Dictionary with NULL value. For test purposes only.
+ VV__NULL_DICT, // Dict with NULL value. For test purposes only.
VV__NULL_BLOB, // Blob with NULL value. For test purposes only.
VV_LUA,
VV_RELNUM,
@@ -237,13 +237,6 @@ typedef enum {
EXPR_ISNOT, ///< isnot
} exprtype_T;
-/// Type for dict_list function
-typedef enum {
- kDictListKeys, ///< List dictionary keys.
- kDictListValues, ///< List dictionary values.
- kDictListItems, ///< List dictionary contents: [keys, values].
-} DictListType;
-
// Used for checking if local variables or arguments used in a lambda.
extern bool *eval_lavars_used;
diff --git a/src/nvim/eval.lua b/src/nvim/eval.lua
index 7d4438ded6..50aaf9e03b 100644
--- a/src/nvim/eval.lua
+++ b/src/nvim/eval.lua
@@ -19,7 +19,9 @@
--- @field returns_desc? string
--- @field signature? string
--- @field desc? string
---- @field params {[1]:string, [2]:string, [3]:string}[]
+--- @field params [string, string, string][]
+--- @field notes? string[]
+--- @field see? string[]
--- @field lua? false Do not render type information
--- @field tags? string[] Extra tags
--- @field data? string Used by gen_eval.lua
@@ -52,7 +54,7 @@ M.funcs = {
]=],
name = 'abs',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
signature = 'abs({expr})',
returns = 'number',
},
@@ -75,7 +77,7 @@ M.funcs = {
]=],
float_func = 'acos',
name = 'acos',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
returns = 'number',
signature = 'acos({expr})',
},
@@ -97,6 +99,7 @@ M.funcs = {
name = 'add',
params = { { 'object', 'any' }, { 'expr', 'any' } },
returns = 'any',
+ returns_desc = [=[Resulting |List| or |Blob|, or 1 if {object} is not a |List| or a |Blob|.]=],
signature = 'add({object}, {expr})',
},
['and'] = {
@@ -111,7 +114,7 @@ M.funcs = {
<
]=],
name = 'and',
- params = { { 'expr', 'any' }, { 'expr', 'any' } },
+ params = { { 'expr', 'number' }, { 'expr', 'number' } },
returns = 'integer',
signature = 'and({expr}, {expr})',
},
@@ -149,7 +152,7 @@ M.funcs = {
]=],
name = 'append',
- params = { { 'lnum', 'integer' }, { 'text', 'any' } },
+ params = { { 'lnum', 'integer' }, { 'text', 'string|string[]' } },
returns = '0|1',
signature = 'append({lnum}, {text})',
},
@@ -179,7 +182,7 @@ M.funcs = {
]=],
name = 'appendbufline',
- params = { { 'buf', 'any' }, { 'lnum', 'integer' }, { 'text', 'string' } },
+ params = { { 'buf', 'integer|string' }, { 'lnum', 'integer' }, { 'text', 'string' } },
returns = '0|1',
signature = 'appendbufline({buf}, {lnum}, {text})',
},
@@ -289,7 +292,7 @@ M.funcs = {
]=],
name = 'assert_beeps',
- params = { { 'cmd', 'any' } },
+ params = { { 'cmd', 'string' } },
returns = '0|1',
signature = 'assert_beeps({cmd})',
},
@@ -301,16 +304,17 @@ M.funcs = {
added to |v:errors| and 1 is returned. Otherwise zero is
returned. |assert-return|
The error is in the form "Expected {expected} but got
- {actual}". When {msg} is present it is prefixed to that.
+ {actual}". When {msg} is present it is prefixed to that,
+ along with the location of the assert when run from a script.
There is no automatic conversion, the String "4" is different
from the Number 4. And the number 4 is different from the
Float 4.0. The value of 'ignorecase' is not used here, case
always matters.
Example: >vim
- assert_equal('foo', 'bar')
- <Will result in a string to be added to |v:errors|:
- test.vim line 12: Expected 'foo' but got 'bar' ~
+ call assert_equal('foo', 'bar', 'baz')
+ <Will add the following to |v:errors|:
+ test.vim line 12: baz: Expected 'foo' but got 'bar' ~
]=],
name = 'assert_equal',
@@ -330,7 +334,7 @@ M.funcs = {
]=],
name = 'assert_equalfile',
- params = {},
+ params = { { 'fname-one', 'string' }, { 'fname-two', 'string' } },
returns = '0|1',
signature = 'assert_equalfile({fname-one}, {fname-two})',
},
@@ -366,25 +370,25 @@ M.funcs = {
When {error} is a string it must be found literally in the
first reported error. Most often this will be the error code,
including the colon, e.g. "E123:". >vim
- assert_fails('bad cmd', 'E987:')
+ call assert_fails('bad cmd', 'E987:')
<
When {error} is a |List| with one or two strings, these are
used as patterns. The first pattern is matched against the
first reported error: >vim
- assert_fails('cmd', ['E987:.*expected bool'])
+ call assert_fails('cmd', ['E987:.*expected bool'])
<The second pattern, if present, is matched against the last
reported error. To only match the last error use an empty
string for the first error: >vim
- assert_fails('cmd', ['', 'E987:'])
+ call assert_fails('cmd', ['', 'E987:'])
<
If {msg} is empty then it is not used. Do this to get the
default message when passing the {lnum} argument.
-
+ *E1115*
When {lnum} is present and not negative, and the {error}
argument is present and matches, then this is compared with
the line number at which the error was reported. That can be
the line number in a function or in a script.
-
+ *E1116*
When {context} is present it is used as a pattern and matched
against the context (script name or function name) where
{lnum} is located in.
@@ -395,7 +399,7 @@ M.funcs = {
]=],
name = 'assert_fails',
params = {
- { 'cmd', 'any' },
+ { 'cmd', 'string' },
{ 'error', 'any' },
{ 'msg', 'any' },
{ 'lnum', 'integer' },
@@ -411,7 +415,8 @@ M.funcs = {
When {actual} is not false an error message is added to
|v:errors|, like with |assert_equal()|.
The error is in the form "Expected False but got {actual}".
- When {msg} is present it is prepended to that.
+ When {msg} is present it is prefixed to that, along with the
+ location of the assert when run from a script.
Also see |assert-return|.
A value is false when it is zero. When {actual} is not a
@@ -435,7 +440,12 @@ M.funcs = {
that.
]=],
name = 'assert_inrange',
- params = { { 'lower', 'any' }, { 'upper', 'any' }, { 'actual', 'any' }, { 'msg', 'any' } },
+ params = {
+ { 'lower', 'number' },
+ { 'upper', 'number' },
+ { 'actual', 'number' },
+ { 'msg', 'string' },
+ },
returns = '0|1',
signature = 'assert_inrange({lower}, {upper}, {actual} [, {msg}])',
},
@@ -446,7 +456,8 @@ M.funcs = {
When {pattern} does not match {actual} an error message is
added to |v:errors|. Also see |assert-return|.
The error is in the form "Pattern {pattern} does not match
- {actual}". When {msg} is present it is prefixed to that.
+ {actual}". When {msg} is present it is prefixed to that,
+ along with the location of the assert when run from a script.
{pattern} is used as with |expr-=~|: The matching is always done
like 'magic' was set and 'cpoptions' is empty, no matter what
@@ -457,13 +468,13 @@ M.funcs = {
Use both to match the whole text.
Example: >vim
- assert_match('^f.*o$', 'foobar')
+ call assert_match('^f.*o$', 'foobar')
<Will result in a string to be added to |v:errors|:
test.vim line 12: Pattern '^f.*o$' does not match 'foobar' ~
]=],
name = 'assert_match',
- params = { { 'pattern', 'any' }, { 'actual', 'any' }, { 'msg', 'any' } },
+ params = { { 'pattern', 'string' }, { 'actual', 'string' }, { 'msg', 'string' } },
returns = '0|1',
signature = 'assert_match({pattern}, {actual} [, {msg}])',
},
@@ -477,7 +488,7 @@ M.funcs = {
]=],
name = 'assert_nobeep',
- params = { { 'cmd', 'any' } },
+ params = { { 'cmd', 'string' } },
returns = '0|1',
signature = 'assert_nobeep({cmd})',
},
@@ -505,7 +516,7 @@ M.funcs = {
]=],
name = 'assert_notmatch',
- params = { { 'pattern', 'any' }, { 'actual', 'any' }, { 'msg', 'any' } },
+ params = { { 'pattern', 'string' }, { 'actual', 'string' }, { 'msg', 'string' } },
returns = '0|1',
signature = 'assert_notmatch({pattern}, {actual} [, {msg}])',
},
@@ -518,7 +529,7 @@ M.funcs = {
]=],
name = 'assert_report',
- params = { { 'msg', 'any' } },
+ params = { { 'msg', 'string' } },
returns = '0|1',
signature = 'assert_report({msg})',
},
@@ -531,11 +542,12 @@ M.funcs = {
Also see |assert-return|.
A value is |TRUE| when it is a non-zero number or |v:true|.
When {actual} is not a number or |v:true| the assert fails.
- When {msg} is given it precedes the default message.
+ When {msg} is given it is prefixed to the default message,
+ along with the location of the assert when run from a script.
]=],
name = 'assert_true',
- params = { { 'actual', 'any' }, { 'msg', 'any' } },
+ params = { { 'actual', 'any' }, { 'msg', 'string' } },
returns = '0|1',
signature = 'assert_true({actual} [, {msg}])',
},
@@ -556,7 +568,7 @@ M.funcs = {
]=],
float_func = 'atan',
name = 'atan',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
returns = 'number',
signature = 'atan({expr})',
},
@@ -577,7 +589,7 @@ M.funcs = {
]=],
name = 'atan2',
- params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ params = { { 'expr1', 'number' }, { 'expr2', 'number' } },
returns = 'number',
signature = 'atan2({expr1}, {expr2})',
},
@@ -612,7 +624,12 @@ M.funcs = {
something went wrong, or browsing is not possible.
]=],
name = 'browse',
- params = { { 'save', 'any' }, { 'title', 'any' }, { 'initdir', 'any' }, { 'default', 'any' } },
+ params = {
+ { 'save', 'any' },
+ { 'title', 'string' },
+ { 'initdir', 'string' },
+ { 'default', 'string' },
+ },
returns = '0|1',
signature = 'browse({save}, {title}, {initdir}, {default})',
},
@@ -631,7 +648,7 @@ M.funcs = {
browsing is not possible, an empty string is returned.
]=],
name = 'browsedir',
- params = { { 'title', 'any' }, { 'initdir', 'any' } },
+ params = { { 'title', 'string' }, { 'initdir', 'string' } },
returns = '0|1',
signature = 'browsedir({title}, {initdir})',
},
@@ -1000,7 +1017,7 @@ M.funcs = {
]=],
float_func = 'ceil',
name = 'ceil',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
returns = 'number',
signature = 'ceil({expr})',
},
@@ -1017,7 +1034,7 @@ M.funcs = {
omitted.
]=],
name = 'chanclose',
- params = { { 'id', 'any' }, { 'stream', 'any' } },
+ params = { { 'id', 'integer' }, { 'stream', 'string' } },
returns = '0|1',
signature = 'chanclose({id} [, {stream}])',
},
@@ -1057,7 +1074,7 @@ M.funcs = {
messages, use |rpcnotify()| and |rpcrequest()| instead.
]=],
name = 'chansend',
- params = { { 'id', 'any' }, { 'data', 'any' } },
+ params = { { 'id', 'number' }, { 'data', 'string|string[]' } },
returns = '0|1',
signature = 'chansend({id}, {data})',
},
@@ -1116,10 +1133,11 @@ M.funcs = {
With the cursor on '세' in line 5 with text "여보세요": >vim
echo charcol('.') " returns 3
echo col('.') " returns 7
+ <
]=],
name = 'charcol',
- params = { { 'expr', 'any' }, { 'winid', 'integer' } },
+ params = { { 'expr', 'string|integer[]' }, { 'winid', 'integer' } },
returns = 'integer',
signature = 'charcol({expr} [, {winid}])',
},
@@ -1164,8 +1182,8 @@ M.funcs = {
params = {
{ 'string', 'string' },
{ 'idx', 'integer' },
- { 'countcc', 'any' },
- { 'utf16', 'any' },
+ { 'countcc', 'boolean' },
+ { 'utf16', 'boolean' },
},
returns = 'integer',
signature = 'charidx({string}, {idx} [, {countcc} [, {utf16}]])',
@@ -1194,6 +1212,7 @@ M.funcs = {
" ... do some work
call chdir(save_dir)
endif
+ <
]=],
name = 'chdir',
@@ -1229,7 +1248,7 @@ M.funcs = {
]=],
name = 'clearmatches',
- params = { { 'win', 'any' } },
+ params = { { 'win', 'integer' } },
returns = false,
signature = 'clearmatches([{win}])',
},
@@ -1238,33 +1257,33 @@ M.funcs = {
base = 1,
desc = [=[
The result is a Number, which is the byte index of the column
- position given with {expr}. The accepted positions are:
- . the cursor position
- $ the end of the cursor line (the result is the
- number of bytes in the cursor line plus one)
- 'x position of mark x (if the mark is not set, 0 is
- returned)
- v In Visual mode: the start of the Visual area (the
- cursor is the end). When not in Visual mode
- returns the cursor position. Differs from |'<| in
- that it's updated right away.
+ position given with {expr}.
+ For accepted positions see |getpos()|.
+ When {expr} is "$", it means the end of the cursor line, so
+ the result is the number of bytes in the cursor line plus one.
Additionally {expr} can be [lnum, col]: a |List| with the line
and column number. Most useful when the column is "$", to get
the last column of a specific line. When "lnum" or "col" is
out of range then col() returns zero.
+
With the optional {winid} argument the values are obtained for
that window instead of the current window.
+
To get the line number use |line()|. To get both use
|getpos()|.
+
For the screen column position use |virtcol()|. For the
character position use |charcol()|.
+
Note that only marks in the current file can be used.
+
Examples: >vim
echo col(".") " column of cursor
echo col("$") " length of cursor line plus one
echo col("'t") " column of mark t
echo col("'" .. markname) " column of mark markname
- <The first column is 1. Returns 0 if {expr} is invalid or when
+ <
+ The first column is 1. Returns 0 if {expr} is invalid or when
the window with ID {winid} is not found.
For an uppercase mark the column may actually be in another
buffer.
@@ -1273,10 +1292,11 @@ M.funcs = {
line. Also, when using a <Cmd> mapping the cursor isn't
moved, this can be used to obtain the column in Insert mode: >vim
imap <F2> <Cmd>echo col(".").."\n"<CR>
+ <
]=],
name = 'col',
- params = { { 'expr', 'any' }, { 'winid', 'integer' } },
+ params = { { 'expr', 'string|integer[]' }, { 'winid', 'integer' } },
returns = 'integer',
signature = 'col({expr} [, {winid}])',
},
@@ -1315,7 +1335,7 @@ M.funcs = {
]=],
name = 'complete',
- params = { { 'startcol', 'any' }, { 'matches', 'any' } },
+ params = { { 'startcol', 'integer' }, { 'matches', 'any[]' } },
returns = false,
signature = 'complete({startcol}, {matches})',
tags = { 'E785' },
@@ -1414,10 +1434,11 @@ M.funcs = {
call complete_info(['mode'])
" Get only 'mode' and 'pum_visible'
call complete_info(['mode', 'pum_visible'])
+ <
]=],
name = 'complete_info',
- params = { { 'what', 'any' } },
+ params = { { 'what', 'any[]' } },
returns = 'table',
signature = 'complete_info([{what}])',
},
@@ -1478,7 +1499,12 @@ M.funcs = {
]=],
name = 'confirm',
- params = { { 'msg', 'any' }, { 'choices', 'any' }, { 'default', 'any' }, { 'type', 'any' } },
+ params = {
+ { 'msg', 'string' },
+ { 'choices', 'string' },
+ { 'default', 'integer' },
+ { 'type', 'string' },
+ },
returns = 'integer',
signature = 'confirm({msg} [, {choices} [, {default} [, {type}]]])',
},
@@ -1516,7 +1542,7 @@ M.funcs = {
]=],
float_func = 'cos',
name = 'cos',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
returns = 'number',
signature = 'cos({expr})',
},
@@ -1537,7 +1563,7 @@ M.funcs = {
]=],
float_func = 'cosh',
name = 'cosh',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
returns = 'number',
signature = 'cosh({expr})',
},
@@ -1560,7 +1586,12 @@ M.funcs = {
]=],
name = 'count',
- params = { { 'comp', 'any' }, { 'expr', 'any' }, { 'ic', 'any' }, { 'start', 'any' } },
+ params = {
+ { 'comp', 'string|table|any[]' },
+ { 'expr', 'any' },
+ { 'ic', 'boolean' },
+ { 'start', 'integer' },
+ },
returns = 'integer',
signature = 'count({comp}, {expr} [, {ic} [, {start}]])',
},
@@ -1572,7 +1603,7 @@ M.funcs = {
If {index} is not given, it is assumed to be 0 (i.e.: top).
]=],
name = 'ctxget',
- params = { { 'index', 'any' } },
+ params = { { 'index', 'integer' } },
returns = 'table',
signature = 'ctxget([{index}])',
},
@@ -1595,7 +1626,7 @@ M.funcs = {
Otherwise, all context types are included.
]=],
name = 'ctxpush',
- params = { { 'types', 'any' } },
+ params = { { 'types', 'string[]' } },
signature = 'ctxpush([{types}])',
},
ctxset = {
@@ -1607,7 +1638,7 @@ M.funcs = {
If {index} is not given, it is assumed to be 0 (i.e.: top).
]=],
name = 'ctxset',
- params = { { 'context', 'any' }, { 'index', 'any' } },
+ params = { { 'context', 'table' }, { 'index', 'integer' } },
signature = 'ctxset({context} [, {index}])',
},
ctxsize = {
@@ -1622,7 +1653,7 @@ M.funcs = {
args = { 1, 3 },
base = 1,
name = 'cursor',
- params = { { 'lnum', 'integer' }, { 'col', 'integer' }, { 'off', 'any' } },
+ params = { { 'lnum', 'integer' }, { 'col', 'integer' }, { 'off', 'integer' } },
signature = 'cursor({lnum}, {col} [, {off}])',
},
cursor__1 = {
@@ -1662,7 +1693,7 @@ M.funcs = {
]=],
name = 'cursor',
- params = { { 'list', 'any' } },
+ params = { { 'list', 'integer[]' } },
signature = 'cursor({list})',
},
debugbreak = {
@@ -1679,7 +1710,7 @@ M.funcs = {
]=],
name = 'debugbreak',
- params = { { 'pid', 'any' } },
+ params = { { 'pid', 'integer' } },
signature = 'debugbreak({pid})',
},
deepcopy = {
@@ -1708,7 +1739,7 @@ M.funcs = {
]=],
name = 'deepcopy',
- params = { { 'expr', 'any' }, { 'noref', 'any' } },
+ params = { { 'expr', 'any' }, { 'noref', 'boolean' } },
signature = 'deepcopy({expr} [, {noref}])',
},
delete = {
@@ -1758,7 +1789,11 @@ M.funcs = {
]=],
name = 'deletebufline',
- params = { { 'buf', 'any' }, { 'first', 'any' }, { 'last', 'any' } },
+ params = {
+ { 'buf', 'integer|string' },
+ { 'first', 'integer|string' },
+ { 'last', 'integer|string' },
+ },
signature = 'deletebufline({buf}, {first} [, {last}])',
},
dictwatcheradd = {
@@ -1804,7 +1839,7 @@ M.funcs = {
validation and parsing logic.
]=],
name = 'dictwatcheradd',
- params = { { 'dict', 'any' }, { 'pattern', 'any' }, { 'callback', 'any' } },
+ params = { { 'dict', 'table' }, { 'pattern', 'string' }, { 'callback', 'function' } },
signature = 'dictwatcheradd({dict}, {pattern}, {callback})',
},
dictwatcherdel = {
@@ -1815,7 +1850,7 @@ M.funcs = {
order for the watcher to be successfully deleted.
]=],
name = 'dictwatcherdel',
- params = { { 'dict', 'any' }, { 'pattern', 'any' }, { 'callback', 'any' } },
+ params = { { 'dict', 'any' }, { 'pattern', 'string' }, { 'callback', 'function' } },
signature = 'dictwatcherdel({dict}, {pattern}, {callback})',
},
did_filetype = {
@@ -1894,7 +1929,7 @@ M.funcs = {
<
]=],
name = 'digraph_get',
- params = { { 'chars', 'any' } },
+ params = { { 'chars', 'string' } },
signature = 'digraph_get({chars})',
},
digraph_getlist = {
@@ -1916,7 +1951,7 @@ M.funcs = {
<
]=],
name = 'digraph_getlist',
- params = { { 'listall', 'any' } },
+ params = { { 'listall', 'boolean' } },
signature = 'digraph_getlist([{listall}])',
},
digraph_set = {
@@ -1939,12 +1974,9 @@ M.funcs = {
Example: >vim
call digraph_set(' ', 'あ')
<
- Can be used as a |method|: >vim
- GetString()->digraph_set('あ')
- <
]=],
name = 'digraph_set',
- params = { { 'chars', 'any' }, { 'digraph', 'any' } },
+ params = { { 'chars', 'string' }, { 'digraph', 'string' } },
signature = 'digraph_set({chars}, {digraph})',
},
digraph_setlist = {
@@ -1964,13 +1996,9 @@ M.funcs = {
endfor
<Except that the function returns after the first error,
following digraphs will not be added.
-
- Can be used as a |method|: >vim
- GetList()->digraph_setlist()
- <
]=],
name = 'digraph_setlist',
- params = { { 'digraphlist', 'any' } },
+ params = { { 'digraphlist', 'table<integer,string[]>' } },
signature = 'digraph_setlist({digraphlist})',
},
empty = {
@@ -2019,7 +2047,7 @@ M.funcs = {
]=],
fast = true,
name = 'escape',
- params = { { 'string', 'string' }, { 'chars', 'any' } },
+ params = { { 'string', 'string' }, { 'chars', 'string' } },
signature = 'escape({string}, {chars})',
},
eval = {
@@ -2055,19 +2083,26 @@ M.funcs = {
This function checks if an executable with the name {expr}
exists. {expr} must be the name of the program without any
arguments.
+
executable() uses the value of $PATH and/or the normal
- searchpath for programs. *PATHEXT*
+ searchpath for programs.
+ *PATHEXT*
On MS-Windows the ".exe", ".bat", etc. can optionally be
included. Then the extensions in $PATHEXT are tried. Thus if
"foo.exe" does not exist, "foo.exe.bat" can be found. If
- $PATHEXT is not set then ".exe;.com;.bat;.cmd" is used. A dot
+ $PATHEXT is not set then ".com;.exe;.bat;.cmd" is used. A dot
by itself can be used in $PATHEXT to try using the name
without an extension. When 'shell' looks like a Unix shell,
then the name is also tried without adding an extension.
On MS-Windows it only checks if the file exists and is not a
directory, not if it's really executable.
- On Windows an executable in the same directory as Vim is
- always found (it is added to $PATH at |startup|).
+ On MS-Windows an executable in the same directory as the Vim
+ executable is always found (it's added to $PATH at |startup|).
+ *NoDefaultCurrentDirectoryInExePath*
+ On MS-Windows an executable in Vim's current working directory
+ is also normally found, but this can be disabled by setting
+ the $NoDefaultCurrentDirectoryInExePath environment variable.
+
The result is a Number:
1 exists
0 does not exist
@@ -2076,7 +2111,7 @@ M.funcs = {
]=],
fast = true,
name = 'executable',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'string' } },
returns = '0|1',
signature = 'executable({expr})',
},
@@ -2131,8 +2166,9 @@ M.funcs = {
]=],
name = 'exepath',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'string' } },
signature = 'exepath({expr})',
+ returns = 'string',
},
exists = {
args = 1,
@@ -2228,7 +2264,7 @@ M.funcs = {
]=],
name = 'exists',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'string' } },
returns = '0|1',
signature = 'exists({expr})',
},
@@ -2249,7 +2285,7 @@ M.funcs = {
]=],
float_func = 'exp',
name = 'exp',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
signature = 'exp({expr})',
},
expand = {
@@ -2435,7 +2471,7 @@ M.funcs = {
]=],
name = 'extend',
- params = { { 'expr1', 'any' }, { 'expr2', 'any' }, { 'expr3', 'any' } },
+ params = { { 'expr1', 'table' }, { 'expr2', 'table' }, { 'expr3', 'table' } },
signature = 'extend({expr1}, {expr2} [, {expr3}])',
},
extendnew = {
@@ -2447,7 +2483,7 @@ M.funcs = {
unchanged.
]=],
name = 'extendnew',
- params = { { 'expr1', 'any' }, { 'expr2', 'any' }, { 'expr3', 'any' } },
+ params = { { 'expr1', 'table' }, { 'expr2', 'table' }, { 'expr3', 'table' } },
signature = 'extendnew({expr1}, {expr2} [, {expr3}])',
},
feedkeys = {
@@ -2516,6 +2552,23 @@ M.funcs = {
params = { { 'file', 'string' } },
signature = 'file_readable({file})',
},
+ filecopy = {
+ args = 2,
+ base = 1,
+ desc = [[
+ Copy the file pointed to by the name {from} to {to}. The
+ result is a Number, which is |TRUE| if the file was copied
+ successfully, and |FALSE| when it failed.
+ If a file with name {to} already exists, it will fail.
+ Note that it does not handle directories (yet).
+
+ This function is not available in the |sandbox|.
+ ]],
+ name = 'filecopy',
+ params = { { 'from', 'string' }, { 'to', 'string' } },
+ returns = '0|1',
+ signature = 'filecopy({from}, {to})',
+ },
filereadable = {
args = 1,
base = 1,
@@ -2617,7 +2670,7 @@ M.funcs = {
]=],
name = 'filter',
- params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ params = { { 'expr1', 'string|table' }, { 'expr2', 'string|function' } },
signature = 'filter({expr1}, {expr2})',
},
finddir = {
@@ -2643,7 +2696,7 @@ M.funcs = {
]=],
name = 'finddir',
- params = { { 'name', 'string' }, { 'path', 'string' }, { 'count', 'any' } },
+ params = { { 'name', 'string' }, { 'path', 'string' }, { 'count', 'integer' } },
signature = 'finddir({name} [, {path} [, {count}]])',
},
findfile = {
@@ -2686,7 +2739,7 @@ M.funcs = {
]=],
name = 'flatten',
- params = { { 'list', 'any' }, { 'maxdepth', 'any' } },
+ params = { { 'list', 'any[]' }, { 'maxdepth', 'integer' } },
returns = 'any[]|0',
signature = 'flatten({list} [, {maxdepth}])',
},
@@ -2697,7 +2750,7 @@ M.funcs = {
Like |flatten()| but first make a copy of {list}.
]=],
name = 'flattennew',
- params = { { 'list', 'any' }, { 'maxdepth', 'any' } },
+ params = { { 'list', 'any[]' }, { 'maxdepth', 'integer' } },
returns = 'any[]|0',
signature = 'flattennew({list} [, {maxdepth}])',
},
@@ -2728,7 +2781,7 @@ M.funcs = {
]=],
name = 'float2nr',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
signature = 'float2nr({expr})',
},
floor = {
@@ -2750,7 +2803,7 @@ M.funcs = {
]=],
float_func = 'floor',
name = 'floor',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
signature = 'floor({expr})',
},
fmod = {
@@ -2774,7 +2827,7 @@ M.funcs = {
]=],
name = 'fmod',
- params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ params = { { 'expr1', 'number' }, { 'expr2', 'number' } },
signature = 'fmod({expr1}, {expr2})',
},
fnameescape = {
@@ -2963,7 +3016,7 @@ M.funcs = {
unless it was defined with the "abort" flag.
]=],
name = 'foreach',
- params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ params = { { 'expr1', 'string|table' }, { 'expr2', 'string|function' } },
signature = 'foreach({expr1}, {expr2})',
},
foreground = {
@@ -3126,7 +3179,7 @@ M.funcs = {
type a character.
]=],
name = 'garbagecollect',
- params = { { 'atexit', 'any' } },
+ params = { { 'atexit', 'boolean' } },
signature = 'garbagecollect([{atexit}])',
},
get = {
@@ -3140,6 +3193,7 @@ M.funcs = {
name = 'get',
params = { { 'list', 'any[]' }, { 'idx', 'integer' }, { 'default', 'any' } },
signature = 'get({list}, {idx} [, {default}])',
+ tags = { 'get()-list' },
},
get__1 = {
args = { 2, 3 },
@@ -3152,6 +3206,7 @@ M.funcs = {
name = 'get',
params = { { 'blob', 'string' }, { 'idx', 'integer' }, { 'default', 'any' } },
signature = 'get({blob}, {idx} [, {default}])',
+ tags = { 'get()-blob' },
},
get__2 = {
args = { 2, 3 },
@@ -3167,23 +3222,38 @@ M.funcs = {
name = 'get',
params = { { 'dict', 'table<string,any>' }, { 'key', 'string' }, { 'default', 'any' } },
signature = 'get({dict}, {key} [, {default}])',
+ tags = { 'get()-dict' },
},
get__3 = {
args = { 2, 3 },
base = 1,
desc = [=[
- Get item {what} from Funcref {func}. Possible values for
+ Get item {what} from |Funcref| {func}. Possible values for
{what} are:
- "name" The function name
- "func" The function
- "dict" The dictionary
- "args" The list with arguments
+ "name" The function name
+ "func" The function
+ "dict" The dictionary
+ "args" The list with arguments
+ "arity" A dictionary with information about the number of
+ arguments accepted by the function (minus the
+ {arglist}) with the following fields:
+ required the number of positional arguments
+ optional the number of optional arguments,
+ in addition to the required ones
+ varargs |TRUE| if the function accepts a
+ variable number of arguments |...|
+
+ Note: There is no error, if the {arglist} of
+ the Funcref contains more arguments than the
+ Funcref expects, it's not validated.
+
Returns zero on error.
]=],
name = 'get',
params = { { 'func', 'function' }, { 'what', 'string' } },
returns = 'any',
signature = 'get({func}, {what})',
+ tags = { 'get()-func' },
},
getbufinfo = {
args = { 0, 1 },
@@ -3296,10 +3366,11 @@ M.funcs = {
Example: >vim
let lines = getbufline(bufnr("myfile"), 1, "$")
+ <
]=],
name = 'getbufline',
- params = { { 'buf', 'any' }, { 'lnum', 'integer' }, { 'end', 'integer' } },
+ params = { { 'buf', 'integer|string' }, { 'lnum', 'integer' }, { 'end', 'integer' } },
signature = 'getbufline({buf}, {lnum} [, {end}])',
},
getbufoneline = {
@@ -3340,7 +3411,7 @@ M.funcs = {
]=],
name = 'getbufvar',
- params = { { 'buf', 'any' }, { 'varname', 'string' }, { 'def', 'any' } },
+ params = { { 'buf', 'integer|string' }, { 'varname', 'string' }, { 'def', 'any' } },
signature = 'getbufvar({buf}, {varname} [, {def}])',
},
getcellwidths = {
@@ -3447,7 +3518,7 @@ M.funcs = {
<
]=],
name = 'getchar',
- params = {},
+ params = { { 'expr', '0|1' } },
returns = 'integer',
signature = 'getchar([{expr}])',
},
@@ -3491,7 +3562,7 @@ M.funcs = {
<
]=],
name = 'getcharpos',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'string' } },
returns = 'integer[]',
signature = 'getcharpos({expr})',
},
@@ -3518,7 +3589,7 @@ M.funcs = {
]=],
name = 'getcharsearch',
params = {},
- returns = 'table[]',
+ returns = 'table',
signature = 'getcharsearch()',
},
getcharstr = {
@@ -3536,7 +3607,7 @@ M.funcs = {
result is converted to a string.
]=],
name = 'getcharstr',
- params = {},
+ params = { { 'expr', '0|1' } },
returns = 'string',
signature = 'getcharstr([{expr}])',
},
@@ -3546,8 +3617,8 @@ M.funcs = {
Only works when the command line is being edited, thus
requires use of |c_CTRL-\_e| or |c_CTRL-R_=|.
See |:command-completion| for the return string.
- Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()| and
- |setcmdline()|.
+ Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|,
+ |getcmdprompt()| and |setcmdline()|.
Returns an empty string when completion is not defined.
]=],
name = 'getcmdcompltype',
@@ -3557,13 +3628,13 @@ M.funcs = {
},
getcmdline = {
desc = [=[
- Return the current command-line. Only works when the command
- line is being edited, thus requires use of |c_CTRL-\_e| or
- |c_CTRL-R_=|.
+ Return the current command-line input. Only works when the
+ command line is being edited, thus requires use of
+ |c_CTRL-\_e| or |c_CTRL-R_=|.
Example: >vim
cmap <F7> <C-\>eescape(getcmdline(), ' \')<CR>
- <Also see |getcmdtype()|, |getcmdpos()|, |setcmdpos()| and
- |setcmdline()|.
+ <Also see |getcmdtype()|, |getcmdpos()|, |setcmdpos()|,
+ |getcmdprompt()| and |setcmdline()|.
Returns an empty string when entering a password or using
|inputsecret()|.
]=],
@@ -3579,14 +3650,28 @@ M.funcs = {
Only works when editing the command line, thus requires use of
|c_CTRL-\_e| or |c_CTRL-R_=| or an expression mapping.
Returns 0 otherwise.
- Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()| and
- |setcmdline()|.
+ Also see |getcmdtype()|, |setcmdpos()|, |getcmdline()|,
+ |getcmdprompt()| and |setcmdline()|.
]=],
name = 'getcmdpos',
params = {},
returns = 'integer',
signature = 'getcmdpos()',
},
+ getcmdprompt = {
+ desc = [=[
+ Return the current command-line prompt when using functions
+ like |input()| or |confirm()|.
+ Only works when the command line is being edited, thus
+ requires use of |c_CTRL-\_e| or |c_CTRL-R_=|.
+ Also see |getcmdtype()|, |getcmdline()|, |getcmdpos()|,
+ |setcmdpos()| and |setcmdline()|.
+ ]=],
+ name = 'getcmdprompt',
+ params = {},
+ returns = 'string',
+ signature = 'getcmdprompt()',
+ },
getcmdscreenpos = {
desc = [=[
Return the screen position of the cursor in the command line
@@ -3654,6 +3739,7 @@ M.funcs = {
customlist,{func} custom completion, defined via {func}
diff_buffer |:diffget| and |:diffput| completion
dir directory names
+ dir_in_path directory names in |'cdpath'|
environment environment variable names
event autocommand events
expression Vim expression
@@ -3708,7 +3794,7 @@ M.funcs = {
]=],
name = 'getcompletion',
- params = { { 'pat', 'any' }, { 'type', 'any' }, { 'filtered', 'any' } },
+ params = { { 'pat', 'string' }, { 'type', 'string' }, { 'filtered', 'boolean' } },
returns = 'string[]',
signature = 'getcompletion({pat}, {type} [, {filtered}])',
},
@@ -4019,7 +4105,7 @@ M.funcs = {
<
]=],
name = 'getloclist',
- params = { { 'nr', 'integer' }, { 'what', 'any' } },
+ params = { { 'nr', 'integer' }, { 'what', 'table' } },
signature = 'getloclist({nr} [, {what}])',
},
getmarklist = {
@@ -4046,8 +4132,9 @@ M.funcs = {
]=],
name = 'getmarklist',
- params = { { 'buf', 'any' } },
+ params = { { 'buf', 'integer?' } },
signature = 'getmarklist([{buf}])',
+ returns = 'vim.fn.getmarklist.ret.item[]',
},
getmatches = {
args = { 0, 1 },
@@ -4084,7 +4171,7 @@ M.funcs = {
<
]=],
name = 'getmatches',
- params = { { 'win', 'any' } },
+ params = { { 'win', 'integer' } },
signature = 'getmatches([{win}])',
},
getmousepos = {
@@ -4139,9 +4226,34 @@ M.funcs = {
args = 1,
base = 1,
desc = [=[
- Get the position for String {expr}. For possible values of
- {expr} see |line()|. For getting the cursor position see
- |getcurpos()|.
+ Get the position for String {expr}.
+ The accepted values for {expr} are:
+ . The cursor position.
+ $ The last line in the current buffer.
+ 'x Position of mark x (if the mark is not set, 0 is
+ returned for all values).
+ w0 First line visible in current window (one if the
+ display isn't updated, e.g. in silent Ex mode).
+ w$ Last line visible in current window (this is one
+ less than "w0" if no lines are visible).
+ v When not in Visual mode, returns the cursor
+ position. In Visual mode, returns the other end
+ of the Visual area. A good way to think about
+ this is that in Visual mode "v" and "." complement
+ each other. While "." refers to the cursor
+ position, "v" refers to where |v_o| would move the
+ cursor. As a result, you can use "v" and "."
+ together to work on all of a selection in
+ characterwise Visual mode. If the cursor is at
+ the end of a characterwise Visual area, "v" refers
+ to the start of the same Visual area. And if the
+ cursor is at the start of a characterwise Visual
+ area, "v" refers to the end of the same Visual
+ area. "v" differs from |'<| and |'>| in that it's
+ updated right away.
+ Note that a mark in another file can be used. The line number
+ then applies to another buffer.
+
The result is a |List| with four numbers:
[bufnum, lnum, col, off]
"bufnum" is zero, unless a mark like '0 or 'A is used, then it
@@ -4152,20 +4264,25 @@ M.funcs = {
it is the offset in screen columns from the start of the
character. E.g., a position within a <Tab> or after the last
character.
- Note that for '< and '> Visual mode matters: when it is "V"
- (visual line mode) the column of '< is zero and the column of
- '> is a large number equal to |v:maxcol|.
+
+ For getting the cursor position see |getcurpos()|.
The column number in the returned List is the byte position
within the line. To get the character position in the line,
use |getcharpos()|.
+
+ Note that for '< and '> Visual mode matters: when it is "V"
+ (visual line mode) the column of '< is zero and the column of
+ '> is a large number equal to |v:maxcol|.
A very large column number equal to |v:maxcol| can be returned,
in which case it means "after the end of the line".
If {expr} is invalid, returns a list with all zeros.
+
This can be used to save and restore the position of a mark: >vim
let save_a_mark = getpos("'a")
" ...
call setpos("'a", save_a_mark)
- <Also see |getcharpos()|, |getcurpos()| and |setpos()|.
+ <
+ Also see |getcharpos()|, |getcurpos()| and |setpos()|.
]=],
name = 'getpos',
@@ -4280,7 +4397,7 @@ M.funcs = {
<
]=],
name = 'getqflist',
- params = { { 'what', 'any' } },
+ params = { { 'what', 'table' } },
signature = 'getqflist([{what}])',
},
getreg = {
@@ -4370,14 +4487,14 @@ M.funcs = {
The optional argument {opts} is a Dict and supports the
following items:
- type Specify the region's selection type
- (default: "v"):
- "v" for |charwise| mode
- "V" for |linewise| mode
- "<CTRL-V>" for |blockwise-visual| mode
+ type Specify the region's selection type.
+ See |getregtype()| for possible values,
+ except that the width can be omitted
+ and an empty string cannot be used.
+ (default: "v")
exclusive If |TRUE|, use exclusive selection
- for the end position
+ for the end position.
(default: follow 'selection')
You can get the last selection type by |visualmode()|.
@@ -4403,8 +4520,8 @@ M.funcs = {
difference if the buffer is displayed in a window with
different 'virtualedit' or 'list' values.
- Examples: >
- :xnoremap <CR>
+ Examples: >vim
+ xnoremap <CR>
\ <Cmd>echom getregion(
\ getpos('v'), getpos('.'), #{ type: mode() })<CR>
<
@@ -4647,7 +4764,7 @@ M.funcs = {
strings.
]=],
name = 'gettext',
- params = { { 'text', 'any' } },
+ params = { { 'text', 'string' } },
signature = 'gettext({text})',
},
getwininfo = {
@@ -4800,7 +4917,12 @@ M.funcs = {
]=],
name = 'glob',
- params = { { 'expr', 'any' }, { 'nosuf', 'boolean' }, { 'list', 'any' }, { 'alllinks', 'any' } },
+ params = {
+ { 'expr', 'string' },
+ { 'nosuf', 'boolean' },
+ { 'list', 'boolean' },
+ { 'alllinks', 'boolean' },
+ },
signature = 'glob({expr} [, {nosuf} [, {list} [, {alllinks}]]])',
},
glob2regpat = {
@@ -4869,10 +4991,10 @@ M.funcs = {
name = 'globpath',
params = {
{ 'path', 'string' },
- { 'expr', 'any' },
+ { 'expr', 'string' },
{ 'nosuf', 'boolean' },
- { 'list', 'any' },
- { 'allinks', 'any' },
+ { 'list', 'boolean' },
+ { 'allinks', 'boolean' },
},
signature = 'globpath({path}, {expr} [, {nosuf} [, {list} [, {allinks}]]])',
},
@@ -4948,7 +5070,7 @@ M.funcs = {
]=],
fast = true,
name = 'has',
- params = { { 'feature', 'any' } },
+ params = { { 'feature', 'string' } },
returns = '0|1',
signature = 'has({feature})',
},
@@ -4962,7 +5084,7 @@ M.funcs = {
]=],
name = 'has_key',
- params = { { 'dict', 'any' }, { 'key', 'any' } },
+ params = { { 'dict', 'table' }, { 'key', 'string' } },
returns = '0|1',
signature = 'has_key({dict}, {key})',
},
@@ -5028,7 +5150,7 @@ M.funcs = {
]=],
name = 'hasmapto',
- params = { { 'what', 'any' }, { 'mode', 'string' }, { 'abbr', 'any' } },
+ params = { { 'what', 'any' }, { 'mode', 'string' }, { 'abbr', 'boolean' } },
returns = '0|1',
signature = 'hasmapto({what} [, {mode} [, {abbr}]])',
},
@@ -5080,7 +5202,7 @@ M.funcs = {
]=],
name = 'histadd',
- params = { { 'history', 'any' }, { 'item', 'any' } },
+ params = { { 'history', 'string' }, { 'item', 'any' } },
returns = '0|1',
signature = 'histadd({history}, {item})',
},
@@ -5121,7 +5243,7 @@ M.funcs = {
<
]=],
name = 'histdel',
- params = { { 'history', 'any' }, { 'item', 'any' } },
+ params = { { 'history', 'string' }, { 'item', 'any' } },
returns = '0|1',
signature = 'histdel({history} [, {item}])',
},
@@ -5145,7 +5267,7 @@ M.funcs = {
<
]=],
name = 'histget',
- params = { { 'history', 'any' }, { 'index', 'any' } },
+ params = { { 'history', 'string' }, { 'index', 'integer|string' } },
returns = 'string',
signature = 'histget({history} [, {index}])',
},
@@ -5159,10 +5281,11 @@ M.funcs = {
Example: >vim
let inp_index = histnr("expr")
+ <
]=],
name = 'histnr',
- params = { { 'history', 'any' } },
+ params = { { 'history', 'string' } },
returns = 'integer',
signature = 'histnr({history})',
},
@@ -5230,7 +5353,7 @@ M.funcs = {
]=],
fast = true,
name = 'iconv',
- params = { { 'string', 'string' }, { 'from', 'any' }, { 'to', 'any' } },
+ params = { { 'string', 'string' }, { 'from', 'string' }, { 'to', 'string' } },
signature = 'iconv({string}, {from}, {to})',
},
id = {
@@ -5243,7 +5366,7 @@ M.funcs = {
Note that `v:_null_string`, `v:_null_list`, `v:_null_dict` and
`v:_null_blob` have the same `id()` with different types
because they are internally represented as NULL pointers.
- `id()` returns a hexadecimal representanion of the pointers to
+ `id()` returns a hexadecimal representation of the pointers to
the containers (i.e. like `0x994a40`), same as `printf("%p",
{expr})`, but it is advised against counting on the exact
format of the return value.
@@ -5301,10 +5424,11 @@ M.funcs = {
if index(numbers, 123) >= 0
" ...
endif
+ <
]=],
name = 'index',
- params = { { 'object', 'any' }, { 'expr', 'any' }, { 'start', 'any' }, { 'ic', 'any' } },
+ params = { { 'object', 'any' }, { 'expr', 'any' }, { 'start', 'integer' }, { 'ic', 'boolean' } },
signature = 'index({object}, {expr} [, {start} [, {ic}]])',
},
indexof = {
@@ -5347,6 +5471,7 @@ M.funcs = {
echo indexof(l, "v:val.n == 20")
echo indexof(l, {i, v -> v.n == 30})
echo indexof(l, "v:val.n == 20", #{startidx: 1})
+ <
]=],
name = 'indexof',
@@ -5358,7 +5483,7 @@ M.funcs = {
base = 1,
desc = '',
name = 'input',
- params = { { 'prompt', 'any' }, { 'text', 'any' }, { 'completion', 'any' } },
+ params = { { 'prompt', 'string' }, { 'text', 'string' }, { 'completion', 'string' } },
signature = 'input({prompt} [, {text} [, {completion}]])',
},
input__1 = {
@@ -5473,6 +5598,7 @@ M.funcs = {
let g:Foo = input("enter search pattern: ")
call inputrestore()
endfunction
+ <
]=],
name = 'input',
@@ -5511,7 +5637,7 @@ M.funcs = {
]=],
name = 'inputlist',
- params = { { 'textlist', 'any' } },
+ params = { { 'textlist', 'string[]' } },
signature = 'inputlist({textlist})',
},
inputrestore = {
@@ -5554,7 +5680,7 @@ M.funcs = {
]=],
name = 'inputsecret',
- params = { { 'prompt', 'any' }, { 'text', 'any' } },
+ params = { { 'prompt', 'string' }, { 'text', 'string' } },
signature = 'inputsecret({prompt} [, {text}])',
},
insert = {
@@ -5612,9 +5738,33 @@ M.funcs = {
<
]=],
name = 'invert',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
signature = 'invert({expr})',
},
+ isabsolutepath = {
+ args = 1,
+ base = 1,
+ desc = [=[
+ The result is a Number, which is |TRUE| when {path} is an
+ absolute path.
+ On Unix, a path is considered absolute when it starts with '/'.
+ On MS-Windows, it is considered absolute when it starts with an
+ optional drive prefix and is followed by a '\' or '/'. UNC paths
+ are always absolute.
+ Example: >vim
+ echo isabsolutepath('/usr/share/') " 1
+ echo isabsolutepath('./foobar') " 0
+ echo isabsolutepath('C:\Windows') " 1
+ echo isabsolutepath('foobar') " 0
+ echo isabsolutepath('\\remote\file') " 1
+ <
+ ]=],
+ fast = true,
+ name = 'isabsolutepath',
+ params = { { 'path', 'string' } },
+ returns = '0|1',
+ signature = 'isabsolutepath({path})',
+ },
isdirectory = {
args = 1,
base = 1,
@@ -5627,7 +5777,7 @@ M.funcs = {
]=],
fast = true,
name = 'isdirectory',
- params = { { 'directory', 'any' } },
+ params = { { 'directory', 'string' } },
returns = '0|1',
signature = 'isdirectory({directory})',
},
@@ -5644,7 +5794,7 @@ M.funcs = {
]=],
name = 'isinf',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
returns = '1|0|-1',
signature = 'isinf({expr})',
},
@@ -5682,7 +5832,7 @@ M.funcs = {
]=],
name = 'isnan',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
returns = '0|1',
signature = 'isnan({expr})',
},
@@ -5698,7 +5848,10 @@ M.funcs = {
for [key, value] in items(mydict)
echo key .. ': ' .. value
endfor
-
+ <
+ A List or a String argument is also supported. In these
+ cases, items() returns a List with the index and the value at
+ the index.
]=],
name = 'items',
params = { { 'dict', 'any' } },
@@ -5720,7 +5873,7 @@ M.funcs = {
Return the PID (process id) of |job-id| {job}.
]=],
name = 'jobpid',
- params = { { 'job', 'any' } },
+ params = { { 'job', 'integer' } },
returns = 'integer',
signature = 'jobpid({job})',
},
@@ -5732,7 +5885,7 @@ M.funcs = {
Fails if the job was not started with `"pty":v:true`.
]=],
name = 'jobresize',
- params = { { 'job', 'any' }, { 'width', 'integer' }, { 'height', 'integer' } },
+ params = { { 'job', 'integer' }, { 'width', 'integer' }, { 'height', 'integer' } },
signature = 'jobresize({job}, {width}, {height})',
},
jobsend = {
@@ -5834,7 +5987,7 @@ M.funcs = {
See also |job-control|, |channel|, |msgpack-rpc|.
]=],
name = 'jobstart',
- params = { { 'cmd', 'any' }, { 'opts', 'table' } },
+ params = { { 'cmd', 'string|string[]' }, { 'opts', 'table' } },
signature = 'jobstart({cmd} [, {opts}])',
},
jobstop = {
@@ -5850,7 +6003,7 @@ M.funcs = {
exited or stopped.
]=],
name = 'jobstop',
- params = { { 'id', 'any' } },
+ params = { { 'id', 'integer' } },
signature = 'jobstop({id})',
},
jobwait = {
@@ -5877,7 +6030,7 @@ M.funcs = {
-3 if the job-id is invalid
]=],
name = 'jobwait',
- params = { { 'jobs', 'any' }, { 'timeout', 'integer' } },
+ params = { { 'jobs', 'integer[]' }, { 'timeout', 'integer' } },
signature = 'jobwait({jobs} [, {timeout}])',
},
join = {
@@ -5896,7 +6049,7 @@ M.funcs = {
]=],
name = 'join',
- params = { { 'list', 'any' }, { 'sep', 'any' } },
+ params = { { 'list', 'any[]' }, { 'sep', 'string' } },
signature = 'join({list} [, {sep}])',
},
json_decode = {
@@ -5951,7 +6104,7 @@ M.funcs = {
]=],
name = 'keys',
- params = { { 'dict', 'any' } },
+ params = { { 'dict', 'table' } },
signature = 'keys({dict})',
},
keytrans = {
@@ -6068,28 +6221,16 @@ M.funcs = {
args = { 1, 2 },
base = 1,
desc = [=[
- The result is a Number, which is the line number of the file
- position given with {expr}. The {expr} argument is a string.
- The accepted positions are:
- . the cursor position
- $ the last line in the current buffer
- 'x position of mark x (if the mark is not set, 0 is
- returned)
- w0 first line visible in current window (one if the
- display isn't updated, e.g. in silent Ex mode)
- w$ last line visible in current window (this is one
- less than "w0" if no lines are visible)
- v In Visual mode: the start of the Visual area (the
- cursor is the end). When not in Visual mode
- returns the cursor position. Differs from |'<| in
- that it's updated right away.
- Note that a mark in another file can be used. The line number
- then applies to another buffer.
+ See |getpos()| for accepted positions.
+
To get the column number use |col()|. To get both use
|getpos()|.
+
With the optional {winid} argument the values are obtained for
that window instead of the current window.
+
Returns 0 for invalid values of {expr} and {winid}.
+
Examples: >vim
echo line(".") " line number of the cursor
echo line(".", winid) " idem, in window "winid"
@@ -6101,7 +6242,7 @@ M.funcs = {
]=],
name = 'line',
- params = { { 'expr', 'any' }, { 'winid', 'integer' } },
+ params = { { 'expr', 'string|integer[]' }, { 'winid', 'integer' } },
returns = 'integer',
signature = 'line({expr} [, {winid}])',
},
@@ -6157,7 +6298,7 @@ M.funcs = {
]=],
name = 'list2blob',
- params = { { 'list', 'any' } },
+ params = { { 'list', 'any[]' } },
signature = 'list2blob({list})',
},
list2str = {
@@ -6181,7 +6322,7 @@ M.funcs = {
]=],
name = 'list2str',
- params = { { 'list', 'any' }, { 'utf8', 'any' } },
+ params = { { 'list', 'any[]' }, { 'utf8', 'boolean' } },
signature = 'list2str({list} [, {utf8}])',
},
localtime = {
@@ -6210,7 +6351,7 @@ M.funcs = {
]=],
float_func = 'log',
name = 'log',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
signature = 'log({expr})',
},
log10 = {
@@ -6229,7 +6370,7 @@ M.funcs = {
]=],
float_func = 'log10',
name = 'log10',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
signature = 'log10({expr})',
},
luaeval = {
@@ -6242,7 +6383,7 @@ M.funcs = {
]=],
lua = false,
name = 'luaeval',
- params = { { 'expr', 'any' }, { 'expr', 'any' } },
+ params = { { 'expr', 'string' }, { 'expr', 'any[]' } },
signature = 'luaeval({expr} [, {expr}])',
},
map = {
@@ -6304,7 +6445,7 @@ M.funcs = {
]=],
name = 'map',
- params = { { 'expr1', 'any' }, { 'expr2', 'any' } },
+ params = { { 'expr1', 'string|table|any[]' }, { 'expr2', 'string|function' } },
signature = 'map({expr1}, {expr2})',
},
maparg = {
@@ -6349,6 +6490,7 @@ M.funcs = {
"lhsrawalt" The {lhs} of the mapping as raw bytes, alternate
form, only present when it differs from "lhsraw"
"rhs" The {rhs} of the mapping as typed.
+ "callback" Lua function, if RHS was defined as such.
"silent" 1 for a |:map-silent| mapping, else 0.
"noremap" 1 if the {rhs} of the mapping is not remappable.
"script" 1 if mapping was defined with <script>.
@@ -6381,6 +6523,7 @@ M.funcs = {
This function can be used to map a key even when it's already
mapped, and have it do the original mapping too. Sketch: >vim
exe 'nnoremap <Tab> ==' .. maparg('<Tab>', 'n')
+ <
]=],
name = 'maparg',
@@ -6443,7 +6586,7 @@ M.funcs = {
]=],
name = 'mapcheck',
- params = { { 'name', 'string' }, { 'mode', 'string' }, { 'abbr', 'any' } },
+ params = { { 'name', 'string' }, { 'mode', 'string' }, { 'abbr', 'boolean' } },
signature = 'mapcheck({name} [, {mode} [, {abbr}]])',
},
maplist = {
@@ -6478,9 +6621,11 @@ M.funcs = {
\ {_, m -> m.lhs == 'xyzzy'})[0].mode_bits
ounmap xyzzy
echo printf("Operator-pending mode bit: 0x%x", op_bit)
+ <
]],
name = 'maplist',
- params = {},
+ params = { { 'abbr', '0|1' } },
+ returns = 'table[]',
signature = 'maplist([{abbr}])',
},
mapnew = {
@@ -6500,7 +6645,7 @@ M.funcs = {
args = { 1, 3 },
base = 1,
name = 'mapset',
- params = { { 'mode', 'string' }, { 'abbr', 'any' }, { 'dict', 'any' } },
+ params = { { 'mode', 'string' }, { 'abbr', 'boolean' }, { 'dict', 'boolean' } },
signature = 'mapset({mode}, {abbr}, {dict})',
},
mapset__1 = {
@@ -6541,9 +6686,10 @@ M.funcs = {
for d in save_maps
call mapset(d)
endfor
+ <
]=],
name = 'mapset',
- params = { { 'dict', 'any' } },
+ params = { { 'dict', 'boolean' } },
signature = 'mapset({dict})',
},
match = {
@@ -6614,7 +6760,12 @@ M.funcs = {
]=],
name = 'match',
- params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
+ params = {
+ { 'expr', 'string|any[]' },
+ { 'pat', 'string' },
+ { 'start', 'integer' },
+ { 'count', 'integer' },
+ },
signature = 'match({expr}, {pat} [, {start} [, {count}]])',
},
matchadd = {
@@ -6681,11 +6832,11 @@ M.funcs = {
]=],
name = 'matchadd',
params = {
- { 'group', 'any' },
- { 'pattern', 'any' },
- { 'priority', 'any' },
- { 'id', 'any' },
- { 'dict', 'any' },
+ { 'group', 'integer|string' },
+ { 'pattern', 'string' },
+ { 'priority', 'integer' },
+ { 'id', 'integer' },
+ { 'dict', 'string' },
},
signature = 'matchadd({group}, {pattern} [, {priority} [, {id} [, {dict}]]])',
tags = { 'E798', 'E799', 'E801', 'E957' },
@@ -6696,10 +6847,10 @@ M.funcs = {
desc = [=[
Same as |matchadd()|, but requires a list of positions {pos}
instead of a pattern. This command is faster than |matchadd()|
- because it does not require to handle regular expressions and
- sets buffer line boundaries to redraw screen. It is supposed
- to be used when fast match additions and deletions are
- required, for example to highlight matching parentheses.
+ because it does not handle regular expressions and it sets
+ buffer line boundaries to redraw screen. It is supposed to be
+ used when fast match additions and deletions are required, for
+ example to highlight matching parentheses.
*E5030* *E5031*
{pos} is a list of positions. Each position can be one of
these:
@@ -6733,11 +6884,11 @@ M.funcs = {
]=],
name = 'matchaddpos',
params = {
- { 'group', 'any' },
- { 'pos', 'any' },
- { 'priority', 'any' },
- { 'id', 'any' },
- { 'dict', 'any' },
+ { 'group', 'integer|string' },
+ { 'pos', 'any[]' },
+ { 'priority', 'integer' },
+ { 'id', 'integer' },
+ { 'dict', 'string' },
},
signature = 'matchaddpos({group}, {pos} [, {priority} [, {id} [, {dict}]]])',
},
@@ -6792,19 +6943,19 @@ M.funcs = {
Examples: >vim
" Assuming line 3 in buffer 5 contains "a"
- :echo matchbufline(5, '\<\k\+\>', 3, 3)
- [{'lnum': 3, 'byteidx': 0, 'text': 'a'}]
+ echo matchbufline(5, '\<\k\+\>', 3, 3)
+ < `[{'lnum': 3, 'byteidx': 0, 'text': 'a'}]` >vim
" Assuming line 4 in buffer 10 contains "tik tok"
- :echo matchbufline(10, '\<\k\+\>', 1, 4)
- [{'lnum': 4, 'byteidx': 0, 'text': 'tik'}, {'lnum': 4, 'byteidx': 4, 'text': 'tok'}]
- <
+ echo matchbufline(10, '\<\k\+\>', 1, 4)
+ < `[{'lnum': 4, 'byteidx': 0, 'text': 'tik'}, {'lnum': 4, 'byteidx': 4, 'text': 'tok'}]`
+
If {submatch} is present and is v:true, then submatches like
"\1", "\2", etc. are also returned. Example: >vim
" Assuming line 2 in buffer 2 contains "acd"
- :echo matchbufline(2, '\(a\)\?\(b\)\?\(c\)\?\(.*\)', 2, 2
+ echo matchbufline(2, '\(a\)\?\(b\)\?\(c\)\?\(.*\)', 2, 2
\ {'submatches': v:true})
- [{'lnum': 2, 'byteidx': 0, 'text': 'acd', 'submatches': ['a', '', 'c', 'd', '', '', '', '', '']}]
- <The "submatches" List always contains 9 items. If a submatch
+ < `[{'lnum': 2, 'byteidx': 0, 'text': 'acd', 'submatches': ['a', '', 'c', 'd', '', '', '', '', '']}]`
+ The "submatches" List always contains 9 items. If a submatch
is not found, then an empty string is returned for that
submatch.
]=],
@@ -6831,7 +6982,7 @@ M.funcs = {
]=],
name = 'matchdelete',
- params = { { 'id', 'any' }, { 'win', 'any' } },
+ params = { { 'id', 'integer' }, { 'win', 'integer' } },
signature = 'matchdelete({id} [, {win}])',
tags = { 'E802', 'E803' },
},
@@ -6859,7 +7010,12 @@ M.funcs = {
]=],
name = 'matchend',
- params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
+ params = {
+ { 'expr', 'any' },
+ { 'pat', 'string' },
+ { 'start', 'integer' },
+ { 'count', 'integer' },
+ },
signature = 'matchend({expr}, {pat} [, {start} [, {count}]])',
},
matchfuzzy = {
@@ -6929,7 +7085,7 @@ M.funcs = {
<results in `['two one']`.
]=],
name = 'matchfuzzy',
- params = { { 'list', 'any' }, { 'str', 'any' }, { 'dict', 'any' } },
+ params = { { 'list', 'any[]' }, { 'str', 'string' }, { 'dict', 'string' } },
signature = 'matchfuzzy({list}, {str} [, {dict}])',
},
matchfuzzypos = {
@@ -6958,7 +7114,7 @@ M.funcs = {
<results in `[[{"id": 10, "text": "hello"}], [[2, 3]], [127]]`
]=],
name = 'matchfuzzypos',
- params = { { 'list', 'any' }, { 'str', 'any' }, { 'dict', 'any' } },
+ params = { { 'list', 'any[]' }, { 'str', 'string' }, { 'dict', 'string' } },
signature = 'matchfuzzypos({list}, {str} [, {dict}])',
},
matchlist = {
@@ -6978,7 +7134,12 @@ M.funcs = {
]=],
name = 'matchlist',
- params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
+ params = {
+ { 'expr', 'any' },
+ { 'pat', 'string' },
+ { 'start', 'integer' },
+ { 'count', 'integer' },
+ },
signature = 'matchlist({expr}, {pat} [, {start} [, {count}]])',
},
matchstr = {
@@ -6999,7 +7160,12 @@ M.funcs = {
]=],
name = 'matchstr',
- params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
+ params = {
+ { 'expr', 'any' },
+ { 'pat', 'string' },
+ { 'start', 'integer' },
+ { 'count', 'integer' },
+ },
signature = 'matchstr({expr}, {pat} [, {start} [, {count}]])',
},
matchstrlist = {
@@ -7024,17 +7190,17 @@ M.funcs = {
option settings on the pattern.
Example: >vim
- :echo matchstrlist(['tik tok'], '\<\k\+\>')
- [{'idx': 0, 'byteidx': 0, 'text': 'tik'}, {'idx': 0, 'byteidx': 4, 'text': 'tok'}]
- :echo matchstrlist(['a', 'b'], '\<\k\+\>')
- [{'idx': 0, 'byteidx': 0, 'text': 'a'}, {'idx': 1, 'byteidx': 0, 'text': 'b'}]
- <
+ echo matchstrlist(['tik tok'], '\<\k\+\>')
+ < `[{'idx': 0, 'byteidx': 0, 'text': 'tik'}, {'idx': 0, 'byteidx': 4, 'text': 'tok'}]` >vim
+ echo matchstrlist(['a', 'b'], '\<\k\+\>')
+ < `[{'idx': 0, 'byteidx': 0, 'text': 'a'}, {'idx': 1, 'byteidx': 0, 'text': 'b'}]`
+
If "submatches" is present and is v:true, then submatches like
"\1", "\2", etc. are also returned. Example: >vim
- :echo matchstrlist(['acd'], '\(a\)\?\(b\)\?\(c\)\?\(.*\)',
+ echo matchstrlist(['acd'], '\(a\)\?\(b\)\?\(c\)\?\(.*\)',
\ #{submatches: v:true})
- [{'idx': 0, 'byteidx': 0, 'text': 'acd', 'submatches': ['a', '', 'c', 'd', '', '', '', '', '']}]
- <The "submatches" List always contains 9 items. If a submatch
+ < `[{'idx': 0, 'byteidx': 0, 'text': 'acd', 'submatches': ['a', '', 'c', 'd', '', '', '', '', '']}]`
+ The "submatches" List always contains 9 items. If a submatch
is not found, then an empty string is returned for that
submatch.
]=],
@@ -7065,7 +7231,12 @@ M.funcs = {
]=],
name = 'matchstrpos',
- params = { { 'expr', 'any' }, { 'pat', 'any' }, { 'start', 'any' }, { 'count', 'any' } },
+ params = {
+ { 'expr', 'any' },
+ { 'pat', 'string' },
+ { 'start', 'integer' },
+ { 'count', 'integer' },
+ },
signature = 'matchstrpos({expr}, {pat} [, {start} [, {count}]])',
},
max = {
@@ -7135,7 +7306,7 @@ M.funcs = {
<
]=],
name = 'menu_get',
- params = { { 'path', 'string' }, { 'modes', 'any' } },
+ params = { { 'path', 'string' }, { 'modes', 'string' } },
signature = 'menu_get({path} [, {modes}])',
},
menu_info = {
@@ -7243,17 +7414,14 @@ M.funcs = {
When {flags} is present it must be a string. An empty string
has no effect.
- If {flags} contains "p" then intermediate directories are
- created as necessary.
+ {flags} can contain these character flags:
+ "p" intermediate directories will be created as necessary
+ "D" {name} will be deleted at the end of the current
+ function, but not recursively |:defer|
+ "R" {name} will be deleted recursively at the end of the
+ current function |:defer|
- If {flags} contains "D" then {name} is deleted at the end of
- the current function, as with: >vim
- defer delete({name}, 'd')
- <
- If {flags} contains "R" then {name} is deleted recursively at
- the end of the current function, as with: >vim
- defer delete({name}, 'rf')
- <Note that when {name} has more than one part and "p" is used
+ Note that when {name} has more than one part and "p" is used
some directories may already exist. Only the first one that
is created and what it contains is scheduled to be deleted.
E.g. when using: >vim
@@ -7282,7 +7450,7 @@ M.funcs = {
]=],
name = 'mkdir',
- params = { { 'name', 'string' }, { 'flags', 'string' }, { 'prot', 'any' } },
+ params = { { 'name', 'string' }, { 'flags', 'string' }, { 'prot', 'string' } },
signature = 'mkdir({name} [, {flags} [, {prot}]])',
tags = { 'E739' },
},
@@ -7426,12 +7594,7 @@ M.funcs = {
C parser does not support such values.
float |Float|. This value cannot possibly appear in
|msgpackparse()| output.
- string |readfile()|-style list of strings. This value will
- appear in |msgpackparse()| output if string contains
- zero byte or if string is a mapping key and mapping is
- being represented as special dictionary for other
- reasons.
- binary |String|, or |Blob| if binary string contains zero
+ string |String|, or |Blob| if binary string contains zero
byte. This value cannot appear in |msgpackparse()|
output since blobs were introduced.
array |List|. This value cannot appear in |msgpackparse()|
@@ -7489,7 +7652,7 @@ M.funcs = {
]=],
name = 'nr2char',
- params = { { 'expr', 'any' }, { 'utf8', 'any' } },
+ params = { { 'expr', 'integer' }, { 'utf8', 'boolean' } },
signature = 'nr2char({expr} [, {utf8}])',
},
nvim_api__ = {
@@ -7528,7 +7691,7 @@ M.funcs = {
"|" is an operator or a command separator.
]=],
name = 'or',
- params = { { 'expr', 'any' }, { 'expr', 'any' } },
+ params = { { 'expr', 'number' }, { 'expr', 'number' } },
signature = 'or({expr}, {expr})',
},
pathshorten = {
@@ -7550,7 +7713,7 @@ M.funcs = {
]=],
name = 'pathshorten',
- params = { { 'path', 'string' }, { 'len', 'any' } },
+ params = { { 'path', 'string' }, { 'len', 'integer' } },
signature = 'pathshorten({path} [, {len}])',
},
perleval = {
@@ -7593,7 +7756,7 @@ M.funcs = {
]=],
name = 'pow',
- params = { { 'x', 'any' }, { 'y', 'any' } },
+ params = { { 'x', 'number' }, { 'y', 'number' } },
signature = 'pow({x}, {y})',
},
prevnonblank = {
@@ -7941,7 +8104,7 @@ M.funcs = {
]=],
name = 'printf',
- params = { { 'fmt', 'any' }, { 'expr1', 'any' } },
+ params = { { 'fmt', 'string' }, { 'expr1', 'any' } },
signature = 'printf({fmt}, {expr1} ...)',
returns = 'string',
},
@@ -7957,7 +8120,7 @@ M.funcs = {
]=],
name = 'prompt_getprompt',
- params = { { 'buf', 'any' } },
+ params = { { 'buf', 'integer|string' } },
signature = 'prompt_getprompt({buf})',
},
prompt_setcallback = {
@@ -7997,7 +8160,7 @@ M.funcs = {
]=],
name = 'prompt_setcallback',
- params = { { 'buf', 'any' }, { 'expr', 'any' } },
+ params = { { 'buf', 'integer|string' }, { 'expr', 'string|function' } },
signature = 'prompt_setcallback({buf}, {expr})',
},
prompt_setinterrupt = {
@@ -8014,7 +8177,7 @@ M.funcs = {
]=],
name = 'prompt_setinterrupt',
- params = { { 'buf', 'any' }, { 'expr', 'any' } },
+ params = { { 'buf', 'integer|string' }, { 'expr', 'string|function' } },
signature = 'prompt_setinterrupt({buf}, {expr})',
},
prompt_setprompt = {
@@ -8029,7 +8192,7 @@ M.funcs = {
<
]=],
name = 'prompt_setprompt',
- params = { { 'buf', 'any' }, { 'text', 'any' } },
+ params = { { 'buf', 'integer|string' }, { 'text', 'string' } },
signature = 'prompt_setprompt({buf}, {text})',
},
pum_getpos = {
@@ -8133,7 +8296,7 @@ M.funcs = {
<
]=],
name = 'rand',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
signature = 'rand([{expr}])',
},
range = {
@@ -8159,7 +8322,7 @@ M.funcs = {
<
]=],
name = 'range',
- params = { { 'expr', 'any' }, { 'max', 'any' }, { 'stride', 'any' } },
+ params = { { 'expr', 'any' }, { 'max', 'integer' }, { 'stride', 'integer' } },
signature = 'range({expr} [, {max} [, {stride}]])',
tags = { 'E726', 'E727' },
},
@@ -8191,7 +8354,7 @@ M.funcs = {
Also see |readfile()| and |writefile()|.
]=],
name = 'readblob',
- params = { { 'fname', 'string' }, { 'offset', 'any' }, { 'size', 'any' } },
+ params = { { 'fname', 'string' }, { 'offset', 'integer' }, { 'size', 'integer' } },
signature = 'readblob({fname} [, {offset} [, {size}]])',
},
readdir = {
@@ -8229,7 +8392,7 @@ M.funcs = {
]=],
name = 'readdir',
- params = { { 'directory', 'any' }, { 'expr', 'any' } },
+ params = { { 'directory', 'string' }, { 'expr', 'integer' } },
signature = 'readdir({directory} [, {expr}])',
},
readfile = {
@@ -8270,7 +8433,7 @@ M.funcs = {
]=],
name = 'readfile',
- params = { { 'fname', 'string' }, { 'type', 'any' }, { 'max', 'any' } },
+ params = { { 'fname', 'string' }, { 'type', 'string' }, { 'max', 'integer' } },
signature = 'readfile({fname} [, {type} [, {max}]])',
},
reduce = {
@@ -8296,7 +8459,7 @@ M.funcs = {
<
]=],
name = 'reduce',
- params = { { 'object', 'any' }, { 'func', 'any' }, { 'initial', 'any' } },
+ params = { { 'object', 'any' }, { 'func', 'function' }, { 'initial', 'any' } },
signature = 'reduce({object}, {func} [, {initial}])',
},
reg_executing = {
@@ -8442,7 +8605,7 @@ M.funcs = {
]=],
name = 'remove',
- params = { { 'list', 'any' }, { 'idx', 'integer' }, { 'end', 'any' } },
+ params = { { 'list', 'any[]' }, { 'idx', 'integer' }, { 'end', 'integer' } },
signature = 'remove({list}, {idx}, {end})',
},
remove__2 = {
@@ -8469,7 +8632,7 @@ M.funcs = {
<
]=],
name = 'remove',
- params = { { 'blob', 'any' }, { 'idx', 'integer' }, { 'end', 'any' } },
+ params = { { 'blob', 'any' }, { 'idx', 'integer' }, { 'end', 'integer' } },
signature = 'remove({blob}, {idx}, {end})',
},
remove__4 = {
@@ -8483,7 +8646,7 @@ M.funcs = {
Returns zero on error.
]=],
name = 'remove',
- params = { { 'dict', 'any' }, { 'key', 'any' } },
+ params = { { 'dict', 'any' }, { 'key', 'string' } },
signature = 'remove({dict}, {key})',
},
rename = {
@@ -8499,7 +8662,7 @@ M.funcs = {
]=],
name = 'rename',
- params = { { 'from', 'any' }, { 'to', 'any' } },
+ params = { { 'from', 'string' }, { 'to', 'string' } },
signature = 'rename({from}, {to})',
},
['repeat'] = {
@@ -8518,7 +8681,7 @@ M.funcs = {
]=],
fast = true,
name = 'repeat',
- params = { { 'expr', 'any' }, { 'count', 'any' } },
+ params = { { 'expr', 'any' }, { 'count', 'integer' } },
signature = 'repeat({expr}, {count})',
},
resolve = {
@@ -8541,7 +8704,7 @@ M.funcs = {
]=],
fast = true,
name = 'resolve',
- params = { { 'filename', 'any' } },
+ params = { { 'filename', 'string' } },
signature = 'resolve({filename})',
},
reverse = {
@@ -8582,7 +8745,7 @@ M.funcs = {
]=],
float_func = 'round',
name = 'round',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
signature = 'round({expr})',
},
rpcnotify = {
@@ -8595,7 +8758,7 @@ M.funcs = {
<
]=],
name = 'rpcnotify',
- params = { { 'channel', 'any' }, { 'event', 'any' }, { 'args', 'any' } },
+ params = { { 'channel', 'integer' }, { 'event', 'string' }, { 'args', 'any' } },
signature = 'rpcnotify({channel}, {event} [, {args}...])',
},
rpcrequest = {
@@ -8608,10 +8771,11 @@ M.funcs = {
<
]=],
name = 'rpcrequest',
- params = { { 'channel', 'any' }, { 'method', 'any' }, { 'args', 'any' } },
+ params = { { 'channel', 'integer' }, { 'method', 'string' }, { 'args', 'any' } },
signature = 'rpcrequest({channel}, {method} [, {args}...])',
},
rpcstart = {
+ deprecated = true,
args = { 1, 2 },
desc = [=[
Deprecated. Replace >vim
@@ -8621,7 +8785,7 @@ M.funcs = {
<
]=],
name = 'rpcstart',
- params = { { 'prog', 'any' }, { 'argv', 'any' } },
+ params = { { 'prog', 'string' }, { 'argv', 'any' } },
signature = 'rpcstart({prog} [, {argv}])',
},
rpcstop = {
@@ -8665,7 +8829,7 @@ M.funcs = {
]=],
name = 'screenattr',
- params = { { 'row', 'any' }, { 'col', 'integer' } },
+ params = { { 'row', 'integer' }, { 'col', 'integer' } },
signature = 'screenattr({row}, {col})',
},
screenchar = {
@@ -8683,7 +8847,7 @@ M.funcs = {
]=],
name = 'screenchar',
- params = { { 'row', 'any' }, { 'col', 'integer' } },
+ params = { { 'row', 'integer' }, { 'col', 'integer' } },
signature = 'screenchar({row}, {col})',
},
screenchars = {
@@ -8698,7 +8862,7 @@ M.funcs = {
]=],
name = 'screenchars',
- params = { { 'row', 'any' }, { 'col', 'integer' } },
+ params = { { 'row', 'integer' }, { 'col', 'integer' } },
signature = 'screenchars({row}, {col})',
},
screencol = {
@@ -8714,7 +8878,7 @@ M.funcs = {
the following mappings: >vim
nnoremap <expr> GG ":echom " .. screencol() .. "\n"
nnoremap <silent> GG :echom screencol()<CR>
- noremap GG <Cmd>echom screencol()<Cr>
+ noremap GG <Cmd>echom screencol()<CR>
<
]=],
name = 'screencol',
@@ -8779,7 +8943,7 @@ M.funcs = {
]=],
name = 'screenstring',
- params = { { 'row', 'any' }, { 'col', 'integer' } },
+ params = { { 'row', 'integer' }, { 'col', 'integer' } },
signature = 'screenstring({row}, {col})',
},
search = {
@@ -8840,6 +9004,9 @@ M.funcs = {
The value must not be negative. A zero value is like not
giving the argument.
+ Note: the timeout is only considered when searching, not
+ while evaluating the {skip} expression.
+
If the {skip} expression is given it is evaluated with the
cursor positioned on the start of a match. If it evaluates to
non-zero this match is skipped. This can be used, for
@@ -8890,11 +9057,11 @@ M.funcs = {
]=],
name = 'search',
params = {
- { 'pattern', 'any' },
+ { 'pattern', 'string' },
{ 'flags', 'string' },
- { 'stopline', 'any' },
+ { 'stopline', 'integer' },
{ 'timeout', 'integer' },
- { 'skip', 'any' },
+ { 'skip', 'string|function' },
},
signature = 'search({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])',
},
@@ -9050,7 +9217,7 @@ M.funcs = {
<
]=],
name = 'searchdecl',
- params = { { 'name', 'string' }, { 'global', 'any' }, { 'thisblock', 'any' } },
+ params = { { 'name', 'string' }, { 'global', 'boolean' }, { 'thisblock', 'boolean' } },
signature = 'searchdecl({name} [, {global} [, {thisblock}]])',
},
searchpair = {
@@ -9141,7 +9308,16 @@ M.funcs = {
<
]=],
name = 'searchpair',
- params = {},
+ params = {
+ { 'start', 'string' },
+ { 'middle', 'string' },
+ { 'end', 'string' },
+ { 'flags', 'string' },
+ { 'skip', 'string|function' },
+ { 'stopline', 'integer' },
+ { 'timeout', 'integer' },
+ },
+ returns = 'integer',
signature = 'searchpair({start}, {middle}, {end} [, {flags} [, {skip} [, {stopline} [, {timeout}]]]])',
},
searchpairpos = {
@@ -9158,7 +9334,16 @@ M.funcs = {
See |match-parens| for a bigger and more useful example.
]=],
name = 'searchpairpos',
- params = {},
+ params = {
+ { 'start', 'string' },
+ { 'middle', 'string' },
+ { 'end', 'string' },
+ { 'flags', 'string' },
+ { 'skip', 'string|function' },
+ { 'stopline', 'integer' },
+ { 'timeout', 'integer' },
+ },
+ returns = '[integer, integer]',
signature = 'searchpairpos({start}, {middle}, {end} [, {flags} [, {skip} [, {stopline} [, {timeout}]]]])',
},
searchpos = {
@@ -9182,11 +9367,11 @@ M.funcs = {
]=],
name = 'searchpos',
params = {
- { 'pattern', 'any' },
+ { 'pattern', 'string' },
{ 'flags', 'string' },
- { 'stopline', 'any' },
+ { 'stopline', 'integer' },
{ 'timeout', 'integer' },
- { 'skip', 'any' },
+ { 'skip', 'string|function' },
},
signature = 'searchpos({pattern} [, {flags} [, {stopline} [, {timeout} [, {skip}]]]])',
},
@@ -9239,7 +9424,7 @@ M.funcs = {
<
]=],
name = 'serverstart',
- params = { { 'address', 'any' } },
+ params = { { 'address', 'string' } },
signature = 'serverstart([{address}])',
},
serverstop = {
@@ -9251,7 +9436,7 @@ M.funcs = {
address in |serverlist()|.
]=],
name = 'serverstop',
- params = { { 'address', 'any' } },
+ params = { { 'address', 'string' } },
signature = 'serverstop({address})',
},
setbufline = {
@@ -9284,7 +9469,7 @@ M.funcs = {
]=],
name = 'setbufline',
- params = { { 'buf', 'any' }, { 'lnum', 'integer' }, { 'text', 'any' } },
+ params = { { 'buf', 'integer|string' }, { 'lnum', 'integer' }, { 'text', 'string|string[]' } },
signature = 'setbufline({buf}, {lnum}, {text})',
},
setbufvar = {
@@ -9306,7 +9491,7 @@ M.funcs = {
]=],
name = 'setbufvar',
- params = { { 'buf', 'any' }, { 'varname', 'string' }, { 'val', 'any' } },
+ params = { { 'buf', 'integer|string' }, { 'varname', 'string' }, { 'val', 'any' } },
signature = 'setbufvar({buf}, {varname}, {val})',
},
setcellwidths = {
@@ -9340,14 +9525,14 @@ M.funcs = {
To clear the overrides pass an empty {list}: >vim
call setcellwidths([])
- <You can use the script $VIMRUNTIME/tools/emoji_list.vim to see
+ <You can use the script $VIMRUNTIME/tools/emoji_list.lua to see
the effect for known emoji characters. Move the cursor
through the text to check if the cell widths of your terminal
match with what Vim knows about each emoji. If it doesn't
look right you need to adjust the {list} argument.
]=],
name = 'setcellwidths',
- params = { { 'list', 'any' } },
+ params = { { 'list', 'any[]' } },
signature = 'setcellwidths({list})',
},
setcharpos = {
@@ -9366,7 +9551,7 @@ M.funcs = {
]=],
name = 'setcharpos',
- params = { { 'expr', 'any' }, { 'list', 'any' } },
+ params = { { 'expr', 'string' }, { 'list', 'integer[]' } },
signature = 'setcharpos({expr}, {list})',
},
setcharsearch = {
@@ -9394,7 +9579,7 @@ M.funcs = {
]=],
name = 'setcharsearch',
- params = { { 'dict', 'any' } },
+ params = { { 'dict', 'string' } },
signature = 'setcharsearch({dict})',
},
setcmdline = {
@@ -9409,7 +9594,7 @@ M.funcs = {
]=],
name = 'setcmdline',
- params = { { 'str', 'any' }, { 'pos', 'any' } },
+ params = { { 'str', 'string' }, { 'pos', 'integer' } },
signature = 'setcmdline({str} [, {pos}])',
},
setcmdpos = {
@@ -9432,14 +9617,14 @@ M.funcs = {
]=],
name = 'setcmdpos',
- params = { { 'pos', 'any' } },
+ params = { { 'pos', 'integer' } },
signature = 'setcmdpos({pos})',
},
setcursorcharpos = {
args = { 1, 3 },
base = 1,
name = 'setcursorcharpos',
- params = { { 'lnum', 'integer' }, { 'col', 'integer' }, { 'off', 'any' } },
+ params = { { 'lnum', 'integer' }, { 'col', 'integer' }, { 'off', 'integer' } },
signature = 'setcursorcharpos({lnum}, {col} [, {off}])',
},
setcursorcharpos__1 = {
@@ -9458,7 +9643,7 @@ M.funcs = {
]=],
name = 'setcursorcharpos',
- params = { { 'list', 'any' } },
+ params = { { 'list', 'integer[]' } },
signature = 'setcursorcharpos({list})',
},
setenv = {
@@ -9473,7 +9658,7 @@ M.funcs = {
]=],
name = 'setenv',
- params = { { 'name', 'string' }, { 'val', 'any' } },
+ params = { { 'name', 'string' }, { 'val', 'string' } },
signature = 'setenv({name}, {val})',
},
setfperm = {
@@ -9558,7 +9743,7 @@ M.funcs = {
]=],
name = 'setloclist',
- params = { { 'nr', 'integer' }, { 'list', 'any' }, { 'action', 'any' }, { 'what', 'any' } },
+ params = { { 'nr', 'integer' }, { 'list', 'any' }, { 'action', 'string' }, { 'what', 'table' } },
signature = 'setloclist({nr}, {list} [, {action} [, {what}]])',
},
setmatches = {
@@ -9574,7 +9759,7 @@ M.funcs = {
]=],
name = 'setmatches',
- params = { { 'list', 'any' }, { 'win', 'any' } },
+ params = { { 'list', 'any' }, { 'win', 'integer' } },
signature = 'setmatches({list} [, {win}])',
},
setpos = {
@@ -9631,7 +9816,7 @@ M.funcs = {
]=],
name = 'setpos',
- params = { { 'expr', 'any' }, { 'list', 'any' } },
+ params = { { 'expr', 'string' }, { 'list', 'integer[]' } },
signature = 'setpos({expr}, {list})',
},
setqflist = {
@@ -9753,7 +9938,7 @@ M.funcs = {
]=],
name = 'setqflist',
- params = { { 'list', 'any' }, { 'action', 'any' }, { 'what', 'any' } },
+ params = { { 'list', 'any[]' }, { 'action', 'string' }, { 'what', 'table' } },
signature = 'setqflist({list} [, {action} [, {what}]])',
},
setreg = {
@@ -9903,7 +10088,7 @@ M.funcs = {
<
]=],
name = 'settagstack',
- params = { { 'nr', 'integer' }, { 'dict', 'any' }, { 'action', 'any' } },
+ params = { { 'nr', 'integer' }, { 'dict', 'any' }, { 'action', 'string' } },
signature = 'settagstack({nr}, {dict} [, {action}])',
},
setwinvar = {
@@ -9969,7 +10154,7 @@ M.funcs = {
]=],
name = 'shellescape',
- params = { { 'string', 'string' }, { 'special', 'any' } },
+ params = { { 'string', 'string' }, { 'special', 'boolean' } },
signature = 'shellescape({string} [, {special}])',
},
shiftwidth = {
@@ -10026,6 +10211,7 @@ M.funcs = {
icon full path to the bitmap file for the sign.
linehl highlight group used for the whole line the
sign is placed in.
+ priority default priority value of the sign
numhl highlight group used for the line number where
the sign is placed.
text text that is displayed when there is no icon
@@ -10081,6 +10267,7 @@ M.funcs = {
linehl highlight group used for the whole line the
sign is placed in; not present if not set.
name name of the sign
+ priority default priority value of the sign
numhl highlight group used for the line number where
the sign is placed; not present if not set.
text text that is displayed when there is no icon
@@ -10173,7 +10360,7 @@ M.funcs = {
<
]=],
name = 'sign_getplaced',
- params = { { 'buf', 'any' }, { 'dict', 'vim.fn.sign_getplaced.dict' } },
+ params = { { 'buf', 'integer|string' }, { 'dict', 'vim.fn.sign_getplaced.dict' } },
signature = 'sign_getplaced([{buf} [, {dict}]])',
returns = 'vim.fn.sign_getplaced.ret.item[]',
},
@@ -10255,10 +10442,10 @@ M.funcs = {
]=],
name = 'sign_place',
params = {
- { 'id', 'any' },
- { 'group', 'any' },
+ { 'id', 'integer' },
+ { 'group', 'string' },
{ 'name', 'string' },
- { 'buf', 'any' },
+ { 'buf', 'integer|string' },
{ 'dict', 'vim.fn.sign_place.dict' },
},
signature = 'sign_place({id}, {group}, {name}, {buf} [, {dict}])',
@@ -10291,7 +10478,8 @@ M.funcs = {
priority Priority of the sign. When multiple signs are
placed on a line, the sign with the highest
priority is used. If not specified, the
- default value of 10 is used. See
+ default value of 10 is used, unless specified
+ otherwise by the sign definition. See
|sign-priority| for more information.
If {id} refers to an existing sign, then the existing sign is
@@ -10480,7 +10668,7 @@ M.funcs = {
]=],
name = 'simplify',
- params = { { 'filename', 'any' } },
+ params = { { 'filename', 'string' } },
signature = 'simplify({filename})',
},
sin = {
@@ -10499,7 +10687,7 @@ M.funcs = {
]=],
float_func = 'sin',
name = 'sin',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
signature = 'sin({expr})',
},
sinh = {
@@ -10519,7 +10707,7 @@ M.funcs = {
]=],
float_func = 'sinh',
name = 'sinh',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
signature = 'sinh({expr})',
},
slice = {
@@ -10537,7 +10725,7 @@ M.funcs = {
]=],
name = 'slice',
- params = { { 'expr', 'any' }, { 'start', 'any' }, { 'end', 'any' } },
+ params = { { 'expr', 'any' }, { 'start', 'integer' }, { 'end', 'integer' } },
signature = 'slice({expr}, {start} [, {end}])',
},
sockconnect = {
@@ -10568,7 +10756,7 @@ M.funcs = {
- 0 on invalid arguments or connection failure.
]=],
name = 'sockconnect',
- params = { { 'mode', 'string' }, { 'address', 'any' }, { 'opts', 'table' } },
+ params = { { 'mode', 'string' }, { 'address', 'string' }, { 'opts', 'table' } },
signature = 'sockconnect({mode}, {address} [, {opts}])',
},
sort = {
@@ -10649,7 +10837,7 @@ M.funcs = {
<
]=],
name = 'sort',
- params = { { 'list', 'any' }, { 'how', 'any' }, { 'dict', 'any' } },
+ params = { { 'list', 'any' }, { 'how', 'string|function' }, { 'dict', 'any' } },
signature = 'sort({list} [, {how} [, {dict}]])',
},
soundfold = {
@@ -10665,7 +10853,7 @@ M.funcs = {
]=],
name = 'soundfold',
- params = { { 'word', 'any' } },
+ params = { { 'word', 'string' } },
signature = 'soundfold({word})',
},
spellbadword = {
@@ -10697,7 +10885,7 @@ M.funcs = {
]=],
name = 'spellbadword',
- params = { { 'sentence', 'any' } },
+ params = { { 'sentence', 'string' } },
signature = 'spellbadword([{sentence}])',
},
spellsuggest = {
@@ -10726,7 +10914,7 @@ M.funcs = {
]=],
name = 'spellsuggest',
- params = { { 'word', 'any' }, { 'max', 'any' }, { 'capital', 'any' } },
+ params = { { 'word', 'string' }, { 'max', 'integer' }, { 'capital', 'boolean' } },
signature = 'spellsuggest({word} [, {max} [, {capital}]])',
},
split = {
@@ -10734,8 +10922,8 @@ M.funcs = {
base = 1,
desc = [=[
Make a |List| out of {string}. When {pattern} is omitted or
- empty each white-separated sequence of characters becomes an
- item.
+ empty each white space separated sequence of characters
+ becomes an item.
Otherwise the string is split where {pattern} matches,
removing the matched characters. 'ignorecase' is not used
here, add \c to ignore case. |/\c|
@@ -10759,7 +10947,7 @@ M.funcs = {
]=],
name = 'split',
- params = { { 'string', 'string' }, { 'pattern', 'any' }, { 'keepempty', 'any' } },
+ params = { { 'string', 'string' }, { 'pattern', 'string' }, { 'keepempty', 'boolean' } },
signature = 'split({string} [, {pattern} [, {keepempty}]])',
},
sqrt = {
@@ -10781,7 +10969,7 @@ M.funcs = {
]=],
float_func = 'sqrt',
name = 'sqrt',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
signature = 'sqrt({expr})',
},
srand = {
@@ -10803,7 +10991,7 @@ M.funcs = {
<
]=],
name = 'srand',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
signature = 'srand([{expr}])',
},
stdioopen = {
@@ -10926,7 +11114,7 @@ M.funcs = {
]=],
name = 'str2float',
- params = { { 'string', 'string' }, { 'quoted', 'any' } },
+ params = { { 'string', 'string' }, { 'quoted', 'boolean' } },
signature = 'str2float({string} [, {quoted}])',
},
str2list = {
@@ -10946,7 +11134,7 @@ M.funcs = {
]=],
name = 'str2list',
- params = { { 'string', 'string' }, { 'utf8', 'any' } },
+ params = { { 'string', 'string' }, { 'utf8', 'boolean' } },
signature = 'str2list({string} [, {utf8}])',
},
str2nr = {
@@ -10973,7 +11161,7 @@ M.funcs = {
]=],
name = 'str2nr',
- params = { { 'string', 'string' }, { 'base', 'any' } },
+ params = { { 'string', 'string' }, { 'base', 'integer' } },
signature = 'str2nr({string} [, {base}])',
},
strcharlen = {
@@ -11015,7 +11203,12 @@ M.funcs = {
]=],
fast = true,
name = 'strcharpart',
- params = { { 'src', 'any' }, { 'start', 'any' }, { 'len', 'any' }, { 'skipcc', 'any' } },
+ params = {
+ { 'src', 'string' },
+ { 'start', 'integer' },
+ { 'len', 'integer' },
+ { 'skipcc', 'boolean' },
+ },
signature = 'strcharpart({src}, {start} [, {len} [, {skipcc}]])',
},
strchars = {
@@ -11051,7 +11244,7 @@ M.funcs = {
<
]=],
name = 'strchars',
- params = { { 'string', 'string' }, { 'skipcc', 'any' } },
+ params = { { 'string', 'string' }, { 'skipcc', 'boolean' } },
returns = 'integer',
signature = 'strchars({string} [, {skipcc}])',
},
@@ -11100,7 +11293,7 @@ M.funcs = {
]=],
name = 'strftime',
- params = { { 'format', 'any' }, { 'time', 'any' } },
+ params = { { 'format', 'string' }, { 'time', 'number' } },
returns = 'string',
signature = 'strftime({format} [, {time}])',
},
@@ -11671,7 +11864,7 @@ M.funcs = {
]=],
name = 'synconcealed',
params = { { 'lnum', 'integer' }, { 'col', 'integer' } },
- returns = '{[1]: integer, [2]: string, [3]: integer}',
+ returns = '[integer, string, integer]',
signature = 'synconcealed({lnum}, {col})',
},
synstack = {
@@ -11809,7 +12002,7 @@ M.funcs = {
]=],
name = 'tabpagebuflist',
- params = { { 'arg', 'any' } },
+ params = { { 'arg', 'integer' } },
signature = 'tabpagebuflist([{arg}])',
},
tabpagenr = {
@@ -11990,7 +12183,7 @@ M.funcs = {
described in |terminal|.
]=],
name = 'termopen',
- params = { { 'cmd', 'any' }, { 'opts', 'table' } },
+ params = { { 'cmd', 'string|string[]' }, { 'opts', 'table' } },
signature = 'termopen({cmd} [, {opts}])',
},
test_garbagecollect_now = {
@@ -11999,7 +12192,7 @@ M.funcs = {
Like |garbagecollect()|, but executed right away. This must
only be called directly to avoid any structure to exist
internally, and |v:testing| must have been set before calling
- any function.
+ any function. *E1142*
]=],
params = {},
signature = 'test_garbagecollect_now()',
@@ -12030,7 +12223,7 @@ M.funcs = {
]=],
name = 'timer_info',
- params = { { 'id', 'any' } },
+ params = { { 'id', 'integer' } },
signature = 'timer_info([{id}])',
},
timer_pause = {
@@ -12051,7 +12244,7 @@ M.funcs = {
]=],
name = 'timer_pause',
- params = { { 'timer', 'any' }, { 'paused', 'any' } },
+ params = { { 'timer', 'integer' }, { 'paused', 'boolean' } },
signature = 'timer_pause({timer}, {paused})',
},
timer_start = {
@@ -12090,7 +12283,7 @@ M.funcs = {
]=],
name = 'timer_start',
- params = { { 'time', 'any' }, { 'callback', 'any' }, { 'options', 'table' } },
+ params = { { 'time', 'number' }, { 'callback', 'string|function' }, { 'options', 'table' } },
signature = 'timer_start({time}, {callback} [, {options}])',
},
timer_stop = {
@@ -12103,7 +12296,7 @@ M.funcs = {
]=],
name = 'timer_stop',
- params = { { 'timer', 'any' } },
+ params = { { 'timer', 'integer' } },
signature = 'timer_stop({timer})',
},
timer_stopall = {
@@ -12128,7 +12321,7 @@ M.funcs = {
]=],
fast = true,
name = 'tolower',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'string' } },
returns = 'string',
signature = 'tolower({expr})',
},
@@ -12143,7 +12336,7 @@ M.funcs = {
]=],
fast = true,
name = 'toupper',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'string' } },
returns = 'string',
signature = 'toupper({expr})',
},
@@ -12205,7 +12398,7 @@ M.funcs = {
]=],
name = 'trim',
- params = { { 'text', 'any' }, { 'mask', 'string' }, { 'dir', '0|1|2' } },
+ params = { { 'text', 'string' }, { 'mask', 'string' }, { 'dir', '0|1|2' } },
returns = 'string',
signature = 'trim({text} [, {mask} [, {dir}]])',
},
@@ -12228,7 +12421,7 @@ M.funcs = {
]=],
float_func = 'trunc',
name = 'trunc',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'number' } },
returns = 'integer',
signature = 'trunc({expr})',
},
@@ -12261,6 +12454,7 @@ M.funcs = {
if myvar is v:null | endif
<To check if the v:t_ variables exist use this: >vim
if exists('v:t_number') | endif
+ <
]=],
fast = true,
@@ -12338,6 +12532,7 @@ M.funcs = {
]=],
name = 'undotree',
params = { { 'buf', 'integer|string' } },
+ returns = 'vim.fn.undotree.ret',
signature = 'undotree([{buf}])',
},
uniq = {
@@ -12395,8 +12590,8 @@ M.funcs = {
params = {
{ 'string', 'string' },
{ 'idx', 'integer' },
- { 'countcc', 'any' },
- { 'charidx', 'any' },
+ { 'countcc', 'boolean' },
+ { 'charidx', 'boolean' },
},
returns = 'integer',
signature = 'utf16idx({string}, {idx} [, {countcc} [, {charidx}]])',
@@ -12427,7 +12622,9 @@ M.funcs = {
set to 8, it returns 8. |conceal| is ignored.
For the byte position use |col()|.
- For the use of {expr} see |col()|.
+ For the use of {expr} see |getpos()| and |col()|.
+ When {expr} is "$", it means the end of the cursor line, so
+ the result is the number of cells in the cursor line plus one.
When 'virtualedit' is used {expr} can be [lnum, col, off],
where "off" is the offset in screen columns from the start of
@@ -12437,18 +12634,6 @@ M.funcs = {
beyond the end of the line can be returned. Also see
|'virtualedit'|
- The accepted positions are:
- . the cursor position
- $ the end of the cursor line (the result is the
- number of displayed characters in the cursor line
- plus one)
- 'x position of mark x (if the mark is not set, 0 is
- returned)
- v In Visual mode: the start of the Visual area (the
- cursor is the end). When not in Visual mode
- returns the cursor position. Differs from |'<| in
- that it's updated right away.
-
If {list} is present and non-zero then virtcol() returns a
List with the first and last screen position occupied by the
character.
@@ -12467,14 +12652,17 @@ M.funcs = {
" With text " there", with 't at 'h':
echo virtcol("'t") " returns 6
- <The first column is 1. 0 or [0, 0] is returned for an error.
+ <
+ The first column is 1. 0 or [0, 0] is returned for an error.
+
A more advanced example that echoes the maximum length of
all lines: >vim
echo max(map(range(1, line('$')), "virtcol([v:val, '$'])"))
+ <
]=],
name = 'virtcol',
- params = { { 'expr', 'any' }, { 'list', 'any' }, { 'winid', 'integer' } },
+ params = { { 'expr', 'string|integer[]' }, { 'list', 'boolean' }, { 'winid', 'integer' } },
signature = 'virtcol({expr} [, {list} [, {winid}]])',
},
virtcol2col = {
@@ -12528,7 +12716,7 @@ M.funcs = {
the old value is returned. See |non-zero-arg|.
]=],
name = 'visualmode',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'boolean' } },
signature = 'visualmode([{expr}])',
},
wait = {
@@ -12550,7 +12738,7 @@ M.funcs = {
-3 if an error occurred
]=],
name = 'wait',
- params = { { 'timeout', 'integer' }, { 'condition', 'any' }, { 'interval', 'any' } },
+ params = { { 'timeout', 'integer' }, { 'condition', 'any' }, { 'interval', 'number' } },
signature = 'wait({timeout}, {condition} [, {interval}])',
},
wildmenumode = {
@@ -12588,7 +12776,7 @@ M.funcs = {
]=],
name = 'win_execute',
- params = { { 'id', 'any' }, { 'command', 'any' }, { 'silent', 'boolean' } },
+ params = { { 'id', 'integer' }, { 'command', 'string' }, { 'silent', 'boolean' } },
signature = 'win_execute({id}, {command} [, {silent}])',
},
win_findbuf = {
@@ -12600,7 +12788,7 @@ M.funcs = {
]=],
name = 'win_findbuf',
- params = { { 'bufnr', 'any' } },
+ params = { { 'bufnr', 'integer' } },
returns = 'integer[]',
signature = 'win_findbuf({bufnr})',
},
@@ -12618,7 +12806,7 @@ M.funcs = {
]=],
name = 'win_getid',
- params = { { 'win', 'any' }, { 'tab', 'any' } },
+ params = { { 'win', 'integer' }, { 'tab', 'integer' } },
returns = 'integer',
signature = 'win_getid([{win} [, {tab}]])',
},
@@ -12659,7 +12847,7 @@ M.funcs = {
]=],
name = 'win_gotoid',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'integer' } },
returns = '0|1',
signature = 'win_gotoid({expr})',
},
@@ -12673,7 +12861,7 @@ M.funcs = {
]=],
name = 'win_id2tabwin',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'integer' } },
signature = 'win_id2tabwin({expr})',
},
win_id2win = {
@@ -12685,7 +12873,7 @@ M.funcs = {
]=],
name = 'win_id2win',
- params = { { 'expr', 'any' } },
+ params = { { 'expr', 'integer' } },
signature = 'win_id2win({expr})',
},
win_move_separator = {
@@ -12708,7 +12896,7 @@ M.funcs = {
]=],
name = 'win_move_separator',
- params = { { 'nr', 'integer' }, { 'offset', 'any' } },
+ params = { { 'nr', 'integer' }, { 'offset', 'integer' } },
signature = 'win_move_separator({nr}, {offset})',
},
win_move_statusline = {
@@ -12728,7 +12916,7 @@ M.funcs = {
]=],
name = 'win_move_statusline',
- params = { { 'nr', 'integer' }, { 'offset', 'any' } },
+ params = { { 'nr', 'integer' }, { 'offset', 'integer' } },
signature = 'win_move_statusline({nr}, {offset})',
},
win_screenpos = {
@@ -12771,7 +12959,7 @@ M.funcs = {
]=],
name = 'win_splitmove',
- params = { { 'nr', 'integer' }, { 'target', 'any' }, { 'options', 'table' } },
+ params = { { 'nr', 'integer' }, { 'target', 'integer' }, { 'options', 'table' } },
signature = 'win_splitmove({nr}, {target} [, {options}])',
},
winbufnr = {
@@ -12829,6 +13017,7 @@ M.funcs = {
This excludes any window toolbar line.
Examples: >vim
echo "The current window has " .. winheight(0) .. " lines."
+ <
]=],
name = 'winheight',
@@ -12926,10 +13115,10 @@ M.funcs = {
let window_count = winnr('$')
let prev_window = winnr('#')
let wnum = winnr('3k')
-
+ <
]=],
name = 'winnr',
- params = { { 'arg', 'any' } },
+ params = { { 'arg', 'string|integer' } },
signature = 'winnr([{arg}])',
},
winrestcmd = {
@@ -13101,6 +13290,7 @@ M.funcs = {
To copy a file byte for byte: >vim
let fl = readfile("foo", "b")
call writefile(fl, "foocopy", "b")
+ <
]=],
name = 'writefile',
@@ -13119,7 +13309,7 @@ M.funcs = {
<
]=],
name = 'xor',
- params = { { 'expr', 'any' }, { 'expr', 'any' } },
+ params = { { 'expr', 'number' }, { 'expr', 'number' } },
signature = 'xor({expr}, {expr})',
},
}
diff --git a/src/nvim/eval/decode.c b/src/nvim/eval/decode.c
index d7df7bb150..afc2efddf6 100644
--- a/src/nvim/eval/decode.c
+++ b/src/nvim/eval/decode.c
@@ -1,5 +1,4 @@
#include <assert.h>
-#include <msgpack/object.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
@@ -7,6 +6,7 @@
#include <string.h>
#include "klib/kvec.h"
+#include "mpack/object.h"
#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/eval.h"
@@ -247,45 +247,29 @@ list_T *decode_create_map_special_dict(typval_T *const ret_tv, const ptrdiff_t l
///
/// @param[in] s String to decode.
/// @param[in] len String length.
-/// @param[in] hasnul Whether string has NUL byte, not or it was not yet
-/// determined.
-/// @param[in] binary Determines decode type if string has NUL bytes.
-/// If true convert string to VAR_BLOB, otherwise to the
-/// kMPString special type.
+/// @param[in] force_blob whether string always should be decoded as a blob,
+/// or only when embedded NUL bytes were present
/// @param[in] s_allocated If true, then `s` was allocated and can be saved in
/// a returned structure. If it is not saved there, it
/// will be freed.
///
/// @return Decoded string.
-typval_T decode_string(const char *const s, const size_t len, const TriState hasnul,
- const bool binary, const bool s_allocated)
+typval_T decode_string(const char *const s, const size_t len, bool force_blob,
+ const bool s_allocated)
FUNC_ATTR_WARN_UNUSED_RESULT
{
assert(s != NULL || len == 0);
- const bool really_hasnul = (hasnul == kNone
- ? ((s != NULL) && (memchr(s, NUL, len) != NULL))
- : (bool)hasnul);
- if (really_hasnul) {
+ const bool use_blob = force_blob || ((s != NULL) && (memchr(s, NUL, len) != NULL));
+ if (use_blob) {
typval_T tv;
tv.v_lock = VAR_UNLOCKED;
- if (binary) {
- tv_blob_alloc_ret(&tv);
- ga_concat_len(&tv.vval.v_blob->bv_ga, s, len);
+ blob_T *b = tv_blob_alloc_ret(&tv);
+ if (s_allocated) {
+ b->bv_ga.ga_data = (void *)s;
+ b->bv_ga.ga_len = (int)len;
+ b->bv_ga.ga_maxlen = (int)len;
} else {
- list_T *const list = tv_list_alloc(kListLenMayKnow);
- tv_list_ref(list);
- create_special_dict(&tv, kMPString,
- (typval_T){ .v_type = VAR_LIST,
- .v_lock = VAR_UNLOCKED,
- .vval = { .v_list = list } });
- const int elw_ret = encode_list_write((void *)list, s, len);
- if (s_allocated) {
- xfree((void *)s);
- }
- if (elw_ret == -1) {
- tv_clear(&tv);
- return (typval_T) { .v_type = VAR_UNKNOWN, .v_lock = VAR_UNLOCKED };
- }
+ ga_concat_len(&b->bv_ga, s, len);
}
return tv;
}
@@ -405,7 +389,6 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len,
char *str = xmalloc(len + 1);
int fst_in_pair = 0;
char *str_end = str;
- bool hasnul = false;
#define PUT_FST_IN_PAIR(fst_in_pair, str_end) \
do { \
if ((fst_in_pair) != 0) { \
@@ -426,9 +409,6 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len,
uvarnumber_T ch;
vim_str2nr(ubuf, NULL, NULL,
STR2NR_HEX | STR2NR_FORCE, NULL, &ch, 4, true, NULL);
- if (ch == 0) {
- hasnul = true;
- }
if (SURROGATE_HI_START <= ch && ch <= SURROGATE_HI_END) {
PUT_FST_IN_PAIR(fst_in_pair, str_end);
fst_in_pair = (int)ch;
@@ -476,10 +456,7 @@ static inline int parse_json_string(const char *const buf, const size_t buf_len,
PUT_FST_IN_PAIR(fst_in_pair, str_end);
#undef PUT_FST_IN_PAIR
*str_end = NUL;
- typval_T obj = decode_string(str, (size_t)(str_end - str), hasnul ? kTrue : kFalse, false, true);
- if (obj.v_type == VAR_UNKNOWN) {
- goto parse_json_string_fail;
- }
+ typval_T obj = decode_string(str, (size_t)(str_end - str), false, true);
POP(obj, obj.v_type != VAR_STRING);
goto parse_json_string_ret;
parse_json_string_fail:
@@ -908,182 +885,250 @@ json_decode_string_ret:
#undef DICT_LEN
-/// Convert msgpack object to a Vimscript one
-int msgpack_to_vim(const msgpack_object mobj, typval_T *const rettv)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+static void positive_integer_to_special_typval(typval_T *rettv, uint64_t val)
{
- switch (mobj.type) {
- case MSGPACK_OBJECT_NIL:
+ if (val <= VARNUMBER_MAX) {
*rettv = (typval_T) {
+ .v_type = VAR_NUMBER,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_number = (varnumber_T)val },
+ };
+ } else {
+ list_T *const list = tv_list_alloc(4);
+ tv_list_ref(list);
+ create_special_dict(rettv, kMPInteger, ((typval_T) {
+ .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_list = list },
+ }));
+ tv_list_append_number(list, 1);
+ tv_list_append_number(list, (varnumber_T)((val >> 62) & 0x3));
+ tv_list_append_number(list, (varnumber_T)((val >> 31) & 0x7FFFFFFF));
+ tv_list_append_number(list, (varnumber_T)(val & 0x7FFFFFFF));
+ }
+}
+
+static void typval_parse_enter(mpack_parser_t *parser, mpack_node_t *node)
+{
+ typval_T *result = NULL;
+
+ mpack_node_t *parent = MPACK_PARENT_NODE(node);
+ if (parent) {
+ switch (parent->tok.type) {
+ case MPACK_TOKEN_ARRAY: {
+ list_T *list = parent->data[1].p;
+ result = tv_list_append_owned_tv(list, (typval_T) { .v_type = VAR_UNKNOWN });
+ break;
+ }
+ case MPACK_TOKEN_MAP: {
+ typval_T(*items)[2] = parent->data[1].p;
+ result = &items[parent->pos][parent->key_visited];
+ break;
+ }
+
+ case MPACK_TOKEN_STR:
+ case MPACK_TOKEN_BIN:
+ case MPACK_TOKEN_EXT:
+ assert(node->tok.type == MPACK_TOKEN_CHUNK);
+ break;
+
+ default:
+ abort();
+ }
+ } else {
+ result = parser->data.p;
+ }
+
+ // for types that are completed in typval_parse_exit
+ node->data[0].p = result;
+ node->data[1].p = NULL; // free on error if non-NULL
+
+ switch (node->tok.type) {
+ case MPACK_TOKEN_NIL:
+ *result = (typval_T) {
.v_type = VAR_SPECIAL,
.v_lock = VAR_UNLOCKED,
.vval = { .v_special = kSpecialVarNull },
};
break;
- case MSGPACK_OBJECT_BOOLEAN:
- *rettv = (typval_T) {
+ case MPACK_TOKEN_BOOLEAN:
+ *result = (typval_T) {
.v_type = VAR_BOOL,
.v_lock = VAR_UNLOCKED,
.vval = {
- .v_bool = mobj.via.boolean ? kBoolVarTrue : kBoolVarFalse
+ .v_bool = mpack_unpack_boolean(node->tok) ? kBoolVarTrue : kBoolVarFalse
},
};
break;
- case MSGPACK_OBJECT_POSITIVE_INTEGER:
- if (mobj.via.u64 <= VARNUMBER_MAX) {
- *rettv = (typval_T) {
- .v_type = VAR_NUMBER,
- .v_lock = VAR_UNLOCKED,
- .vval = { .v_number = (varnumber_T)mobj.via.u64 },
- };
- } else {
- list_T *const list = tv_list_alloc(4);
- tv_list_ref(list);
- create_special_dict(rettv, kMPInteger, ((typval_T) {
- .v_type = VAR_LIST,
- .v_lock = VAR_UNLOCKED,
- .vval = { .v_list = list },
- }));
- uint64_t n = mobj.via.u64;
- tv_list_append_number(list, 1);
- tv_list_append_number(list, (varnumber_T)((n >> 62) & 0x3));
- tv_list_append_number(list, (varnumber_T)((n >> 31) & 0x7FFFFFFF));
- tv_list_append_number(list, (varnumber_T)(n & 0x7FFFFFFF));
- }
+ case MPACK_TOKEN_SINT: {
+ *result = (typval_T) {
+ .v_type = VAR_NUMBER,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_number = mpack_unpack_sint(node->tok) },
+ };
break;
- case MSGPACK_OBJECT_NEGATIVE_INTEGER:
- if (mobj.via.i64 >= VARNUMBER_MIN) {
- *rettv = (typval_T) {
- .v_type = VAR_NUMBER,
- .v_lock = VAR_UNLOCKED,
- .vval = { .v_number = (varnumber_T)mobj.via.i64 },
- };
- } else {
- list_T *const list = tv_list_alloc(4);
- tv_list_ref(list);
- create_special_dict(rettv, kMPInteger, ((typval_T) {
- .v_type = VAR_LIST,
- .v_lock = VAR_UNLOCKED,
- .vval = { .v_list = list },
- }));
- uint64_t n = -((uint64_t)mobj.via.i64);
- tv_list_append_number(list, -1);
- tv_list_append_number(list, (varnumber_T)((n >> 62) & 0x3));
- tv_list_append_number(list, (varnumber_T)((n >> 31) & 0x7FFFFFFF));
- tv_list_append_number(list, (varnumber_T)(n & 0x7FFFFFFF));
- }
+ }
+ case MPACK_TOKEN_UINT:
+ positive_integer_to_special_typval(result, mpack_unpack_uint(node->tok));
break;
- case MSGPACK_OBJECT_FLOAT32:
- case MSGPACK_OBJECT_FLOAT64:
- *rettv = (typval_T) {
+ case MPACK_TOKEN_FLOAT:
+ *result = (typval_T) {
.v_type = VAR_FLOAT,
.v_lock = VAR_UNLOCKED,
- .vval = { .v_float = mobj.via.f64 },
+ .vval = { .v_float = mpack_unpack_float(node->tok) },
};
break;
- case MSGPACK_OBJECT_STR:
- *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kTrue, false,
- false);
- if (rettv->v_type == VAR_UNKNOWN) {
- return FAIL;
- }
+
+ case MPACK_TOKEN_BIN:
+ case MPACK_TOKEN_STR:
+ case MPACK_TOKEN_EXT:
+ // actually converted in typval_parse_exit after the data chunks
+ node->data[1].p = xmallocz(node->tok.length);
break;
- case MSGPACK_OBJECT_BIN:
- *rettv = decode_string(mobj.via.bin.ptr, mobj.via.bin.size, kNone, true,
- false);
- if (rettv->v_type == VAR_UNKNOWN) {
- return FAIL;
- }
+ case MPACK_TOKEN_CHUNK: {
+ char *data = parent->data[1].p;
+ memcpy(data + parent->pos,
+ node->tok.data.chunk_ptr, node->tok.length);
break;
- case MSGPACK_OBJECT_ARRAY: {
- list_T *const list = tv_list_alloc((ptrdiff_t)mobj.via.array.size);
+ }
+
+ case MPACK_TOKEN_ARRAY: {
+ list_T *const list = tv_list_alloc((ptrdiff_t)node->tok.length);
tv_list_ref(list);
- *rettv = (typval_T) {
+ *result = (typval_T) {
.v_type = VAR_LIST,
.v_lock = VAR_UNLOCKED,
.vval = { .v_list = list },
};
- for (size_t i = 0; i < mobj.via.array.size; i++) {
- // Not populated yet, need to create list item to push.
- tv_list_append_owned_tv(list, (typval_T) { .v_type = VAR_UNKNOWN });
- if (msgpack_to_vim(mobj.via.array.ptr[i],
- TV_LIST_ITEM_TV(tv_list_last(list)))
- == FAIL) {
- return FAIL;
- }
+ node->data[1].p = list;
+ break;
+ }
+ case MPACK_TOKEN_MAP:
+ // we don't know if this will be safe to convert to a typval dict yet
+ node->data[1].p = xmallocz(node->tok.length * 2 * sizeof(typval_T));
+ break;
+ }
+}
+
+/// Free node which was entered but never exited, due to a nested error
+///
+/// Don't bother with typvals as these will be GC:d eventually
+void typval_parser_error_free(mpack_parser_t *parser)
+{
+ for (uint32_t i = 0; i < parser->size; i++) {
+ mpack_node_t *node = &parser->items[i];
+ switch (node->tok.type) {
+ case MPACK_TOKEN_BIN:
+ case MPACK_TOKEN_STR:
+ case MPACK_TOKEN_EXT:
+ case MPACK_TOKEN_MAP:
+ XFREE_CLEAR(node->data[1].p);
+ break;
+ default:
+ break;
}
+ }
+}
+
+static void typval_parse_exit(mpack_parser_t *parser, mpack_node_t *node)
+{
+ typval_T *result = node->data[0].p;
+ switch (node->tok.type) {
+ case MPACK_TOKEN_BIN:
+ case MPACK_TOKEN_STR:
+ *result = decode_string(node->data[1].p, node->tok.length, false, true);
+ node->data[1].p = NULL;
break;
+
+ case MPACK_TOKEN_EXT: {
+ list_T *const list = tv_list_alloc(2);
+ tv_list_ref(list);
+ tv_list_append_number(list, node->tok.data.ext_type);
+ list_T *const ext_val_list = tv_list_alloc(kListLenMayKnow);
+ tv_list_append_list(list, ext_val_list);
+ create_special_dict(result, kMPExt, ((typval_T) { .v_type = VAR_LIST,
+ .v_lock = VAR_UNLOCKED,
+ .vval = { .v_list = list } }));
+ // TODO(bfredl): why not use BLOB?
+ encode_list_write((void *)ext_val_list, node->data[1].p, node->tok.length);
+ XFREE_CLEAR(node->data[1].p);
}
- case MSGPACK_OBJECT_MAP: {
- for (size_t i = 0; i < mobj.via.map.size; i++) {
- if (mobj.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR
- || mobj.via.map.ptr[i].key.via.str.size == 0
- || memchr(mobj.via.map.ptr[i].key.via.str.ptr, NUL,
- mobj.via.map.ptr[i].key.via.str.size) != NULL) {
+ break;
+
+ case MPACK_TOKEN_MAP: {
+ typval_T(*items)[2] = node->data[1].p;
+ for (size_t i = 0; i < node->tok.length; i++) {
+ typval_T *key = &items[i][0];
+ if (key->v_type != VAR_STRING
+ || key->vval.v_string == NULL
+ || key->vval.v_string[0] == NUL) {
goto msgpack_to_vim_generic_map;
}
}
dict_T *const dict = tv_dict_alloc();
dict->dv_refcount++;
- *rettv = (typval_T) {
+ *result = (typval_T) {
.v_type = VAR_DICT,
.v_lock = VAR_UNLOCKED,
.vval = { .v_dict = dict },
};
- for (size_t i = 0; i < mobj.via.map.size; i++) {
- dictitem_T *const di = xmallocz(offsetof(dictitem_T, di_key)
- + mobj.via.map.ptr[i].key.via.str.size);
- memcpy(&di->di_key[0], mobj.via.map.ptr[i].key.via.str.ptr,
- mobj.via.map.ptr[i].key.via.str.size);
+ for (size_t i = 0; i < node->tok.length; i++) {
+ char *key = items[i][0].vval.v_string;
+ size_t keylen = strlen(key);
+ dictitem_T *const di = xmallocz(offsetof(dictitem_T, di_key) + keylen);
+ memcpy(&di->di_key[0], key, keylen);
di->di_tv.v_type = VAR_UNKNOWN;
if (tv_dict_add(dict, di) == FAIL) {
// Duplicate key: fallback to generic map
- tv_clear(rettv);
+ TV_DICT_ITER(dict, d, {
+ d->di_tv.v_type = VAR_SPECIAL; // don't free values in tv_clear(), they will be reused
+ d->di_tv.vval.v_special = kSpecialVarNull;
+ });
+ tv_clear(result);
xfree(di);
goto msgpack_to_vim_generic_map;
}
- if (msgpack_to_vim(mobj.via.map.ptr[i].val, &di->di_tv) == FAIL) {
- return FAIL;
- }
+ di->di_tv = items[i][1];
+ }
+ for (size_t i = 0; i < node->tok.length; i++) {
+ xfree(items[i][0].vval.v_string);
}
+ XFREE_CLEAR(node->data[1].p);
break;
msgpack_to_vim_generic_map: {}
- list_T *const list = decode_create_map_special_dict(rettv, (ptrdiff_t)mobj.via.map.size);
- for (size_t i = 0; i < mobj.via.map.size; i++) {
+ list_T *const list = decode_create_map_special_dict(result, node->tok.length);
+ for (size_t i = 0; i < node->tok.length; i++) {
list_T *const kv_pair = tv_list_alloc(2);
tv_list_append_list(list, kv_pair);
- typval_T key_tv = { .v_type = VAR_UNKNOWN };
- if (msgpack_to_vim(mobj.via.map.ptr[i].key, &key_tv) == FAIL) {
- tv_clear(&key_tv);
- return FAIL;
- }
- tv_list_append_owned_tv(kv_pair, key_tv);
-
- typval_T val_tv = { .v_type = VAR_UNKNOWN };
- if (msgpack_to_vim(mobj.via.map.ptr[i].val, &val_tv) == FAIL) {
- tv_clear(&val_tv);
- return FAIL;
- }
- tv_list_append_owned_tv(kv_pair, val_tv);
+ tv_list_append_owned_tv(kv_pair, items[i][0]);
+ tv_list_append_owned_tv(kv_pair, items[i][1]);
}
+ XFREE_CLEAR(node->data[1].p);
break;
}
- case MSGPACK_OBJECT_EXT: {
- list_T *const list = tv_list_alloc(2);
- tv_list_ref(list);
- tv_list_append_number(list, mobj.via.ext.type);
- list_T *const ext_val_list = tv_list_alloc(kListLenMayKnow);
- tv_list_append_list(list, ext_val_list);
- create_special_dict(rettv, kMPExt, ((typval_T) { .v_type = VAR_LIST,
- .v_lock = VAR_UNLOCKED,
- .vval = { .v_list = list } }));
- if (encode_list_write((void *)ext_val_list, mobj.via.ext.ptr,
- mobj.via.ext.size) == -1) {
- return FAIL;
- }
+
+ default:
+ // other kinds are handled completely in typval_parse_enter
break;
}
+}
+
+int mpack_parse_typval(mpack_parser_t *parser, const char **data, size_t *size)
+{
+ return mpack_parse(parser, data, size, typval_parse_enter, typval_parse_exit);
+}
+
+int unpack_typval(const char **data, size_t *size, typval_T *ret)
+{
+ ret->v_type = VAR_UNKNOWN;
+ mpack_parser_t parser;
+ mpack_parser_init(&parser, 0);
+ parser.data.p = ret;
+ int status = mpack_parse_typval(&parser, data, size);
+ if (status != MPACK_OK) {
+ typval_parser_error_free(&parser);
+ tv_clear(ret);
}
- return OK;
+ return status;
}
diff --git a/src/nvim/eval/decode.h b/src/nvim/eval/decode.h
index c0d10a469a..485cc65561 100644
--- a/src/nvim/eval/decode.h
+++ b/src/nvim/eval/decode.h
@@ -1,8 +1,8 @@
#pragma once
-#include <msgpack.h> // IWYU pragma: keep
#include <stddef.h> // IWYU pragma: keep
+#include "mpack/object.h"
#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
#include "nvim/types_defs.h" // IWYU pragma: keep
diff --git a/src/nvim/eval/encode.c b/src/nvim/eval/encode.c
index d35ac4eb7b..79f334601d 100644
--- a/src/nvim/eval/encode.c
+++ b/src/nvim/eval/encode.c
@@ -13,7 +13,7 @@
#include <string.h>
#include "klib/kvec.h"
-#include "msgpack/pack.h"
+#include "nvim/api/private/helpers.h"
#include "nvim/ascii_defs.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
@@ -28,6 +28,7 @@
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
+#include "nvim/msgpack_rpc/packer.h"
#include "nvim/strings.h"
#include "nvim/types_defs.h"
#include "nvim/vim_defs.h" // For _()
@@ -54,11 +55,11 @@ int encode_blob_write(void *const data, const char *const buf, const size_t len)
}
/// Msgpack callback for writing to readfile()-style list
-int encode_list_write(void *const data, const char *const buf, const size_t len)
+void encode_list_write(void *const data, const char *const buf, const size_t len)
FUNC_ATTR_NONNULL_ARG(1)
{
if (len == 0) {
- return 0;
+ return;
}
list_T *const list = (list_T *)data;
const char *const end = buf + len;
@@ -96,7 +97,6 @@ int encode_list_write(void *const data, const char *const buf, const size_t len)
if (line_end == end) {
tv_list_append_allocated_string(list, NULL);
}
- return 0;
}
/// Abort conversion to string after a recursion error.
@@ -412,6 +412,8 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s
#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \
ga_concat(gap, "{}")
+#define TYPVAL_ENCODE_CHECK_BEFORE
+
#define TYPVAL_ENCODE_CONV_NIL(tv) \
ga_concat(gap, "v:null")
@@ -536,6 +538,8 @@ int encode_read_from_list(ListReaderState *const state, char *const buf, const s
#undef TYPVAL_ENCODE_ALLOW_SPECIALS
#define TYPVAL_ENCODE_ALLOW_SPECIALS true
+#define TYPVAL_ENCODE_CHECK_BEFORE
+
#undef TYPVAL_ENCODE_CONV_NIL
#define TYPVAL_ENCODE_CONV_NIL(tv) \
ga_concat(gap, "null")
@@ -771,8 +775,7 @@ bool encode_check_json_key(const typval_T *const tv)
const dictitem_T *val_di;
if ((type_di = tv_dict_find(spdict, S_LEN("_TYPE"))) == NULL
|| type_di->di_tv.v_type != VAR_LIST
- || (type_di->di_tv.vval.v_list != eval_msgpack_type_lists[kMPString]
- && type_di->di_tv.vval.v_list != eval_msgpack_type_lists[kMPBinary])
+ || type_di->di_tv.vval.v_list != eval_msgpack_type_lists[kMPString]
|| (val_di = tv_dict_find(spdict, S_LEN("_VAL"))) == NULL
|| val_di->di_tv.v_type != VAR_LIST) {
return false;
@@ -821,6 +824,7 @@ bool encode_check_json_key(const typval_T *const tv)
#undef TYPVAL_ENCODE_CONV_LIST_START
#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START
#undef TYPVAL_ENCODE_CONV_EMPTY_DICT
+#undef TYPVAL_ENCODE_CHECK_BEFORE
#undef TYPVAL_ENCODE_CONV_NIL
#undef TYPVAL_ENCODE_CONV_BOOL
#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
@@ -855,7 +859,7 @@ char *encode_tv2string(typval_T *tv, size_t *len)
if (len != NULL) {
*len = (size_t)ga.ga_len;
}
- ga_append(&ga, '\0');
+ ga_append(&ga, NUL);
return (char *)ga.ga_data;
}
@@ -883,7 +887,7 @@ char *encode_tv2echo(typval_T *tv, size_t *len)
if (len != NULL) {
*len = (size_t)ga.ga_len;
}
- ga_append(&ga, '\0');
+ ga_append(&ga, NUL);
return (char *)ga.ga_data;
}
@@ -908,57 +912,27 @@ char *encode_tv2json(typval_T *tv, size_t *len)
if (len != NULL) {
*len = (size_t)ga.ga_len;
}
- ga_append(&ga, '\0');
+ ga_append(&ga, NUL);
return (char *)ga.ga_data;
}
#define TYPVAL_ENCODE_CONV_STRING(tv, buf, len) \
- do { \
- if ((buf) == NULL) { \
- msgpack_pack_bin(packer, 0); \
- } else { \
- const size_t len_ = (len); \
- msgpack_pack_bin(packer, len_); \
- msgpack_pack_bin_body(packer, buf, len_); \
- } \
- } while (0)
+ mpack_bin(cbuf_as_string(buf, (len)), packer); \
#define TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len) \
- do { \
- if ((buf) == NULL) { \
- msgpack_pack_str(packer, 0); \
- } else { \
- const size_t len_ = (len); \
- msgpack_pack_str(packer, len_); \
- msgpack_pack_str_body(packer, buf, len_); \
- } \
- } while (0)
+ mpack_str(cbuf_as_string(buf, (len)), packer); \
#define TYPVAL_ENCODE_CONV_EXT_STRING(tv, buf, len, type) \
- do { \
- if ((buf) == NULL) { \
- msgpack_pack_ext(packer, 0, (int8_t)(type)); \
- } else { \
- const size_t len_ = (len); \
- msgpack_pack_ext(packer, len_, (int8_t)(type)); \
- msgpack_pack_ext_body(packer, buf, len_); \
- } \
- } while (0)
+ mpack_ext(buf, (len), (int8_t)(type), packer); \
#define TYPVAL_ENCODE_CONV_BLOB(tv, blob, len) \
- do { \
- const size_t len_ = (size_t)(len); \
- msgpack_pack_bin(packer, len_); \
- if (len_ > 0) { \
- msgpack_pack_bin_body(packer, (blob)->bv_ga.ga_data, len_); \
- } \
- } while (0)
+ mpack_bin(cbuf_as_string((blob) ? (blob)->bv_ga.ga_data : NULL, (size_t)(len)), packer);
#define TYPVAL_ENCODE_CONV_NUMBER(tv, num) \
- msgpack_pack_int64(packer, (int64_t)(num))
+ mpack_integer(&packer->ptr, (Integer)(num))
#define TYPVAL_ENCODE_CONV_FLOAT(tv, flt) \
- msgpack_pack_double(packer, (double)(flt))
+ mpack_float8(&packer->ptr, (double)(flt))
#define TYPVAL_ENCODE_CONV_FUNC_START(tv, fun) \
return conv_error(_("E5004: Error while dumping %s, %s: " \
@@ -970,33 +944,30 @@ char *encode_tv2json(typval_T *tv, size_t *len)
#define TYPVAL_ENCODE_CONV_FUNC_END(tv)
#define TYPVAL_ENCODE_CONV_EMPTY_LIST(tv) \
- msgpack_pack_array(packer, 0)
+ mpack_array(&packer->ptr, 0)
#define TYPVAL_ENCODE_CONV_LIST_START(tv, len) \
- msgpack_pack_array(packer, (size_t)(len))
+ mpack_array(&packer->ptr, (uint32_t)(len))
#define TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START(tv, mpsv)
#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \
- msgpack_pack_map(packer, 0)
+ mpack_map(&packer->ptr, 0)
+
+#define TYPVAL_ENCODE_CHECK_BEFORE \
+ mpack_check_buffer(packer)
#define TYPVAL_ENCODE_CONV_NIL(tv) \
- msgpack_pack_nil(packer)
+ mpack_nil(&packer->ptr)
#define TYPVAL_ENCODE_CONV_BOOL(tv, num) \
- do { \
- if (num) { \
- msgpack_pack_true(packer); \
- } else { \
- msgpack_pack_false(packer); \
- } \
- } while (0)
+ mpack_bool(&packer->ptr, (bool)num); \
#define TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER(tv, num) \
- msgpack_pack_uint64(packer, (num))
+ mpack_uint64(&packer->ptr, (num))
#define TYPVAL_ENCODE_CONV_DICT_START(tv, dict, len) \
- msgpack_pack_map(packer, (size_t)(len))
+ mpack_map(&packer->ptr, (uint32_t)(len))
#define TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START(tv, dict, mpsv)
@@ -1021,7 +992,7 @@ char *encode_tv2json(typval_T *tv, size_t *len)
#define TYPVAL_ENCODE_SCOPE
#define TYPVAL_ENCODE_NAME msgpack
-#define TYPVAL_ENCODE_FIRST_ARG_TYPE msgpack_packer *const
+#define TYPVAL_ENCODE_FIRST_ARG_TYPE PackerBuffer *const
#define TYPVAL_ENCODE_FIRST_ARG_NAME packer
#include "nvim/eval/typval_encode.c.h"
#undef TYPVAL_ENCODE_SCOPE
@@ -1043,6 +1014,7 @@ char *encode_tv2json(typval_T *tv, size_t *len)
#undef TYPVAL_ENCODE_CONV_LIST_START
#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START
#undef TYPVAL_ENCODE_CONV_EMPTY_DICT
+#undef TYPVAL_ENCODE_CHECK_BEFORE
#undef TYPVAL_ENCODE_CONV_NIL
#undef TYPVAL_ENCODE_CONV_BOOL
#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
diff --git a/src/nvim/eval/encode.h b/src/nvim/eval/encode.h
index 6d1c0b61c5..2bacc82b0d 100644
--- a/src/nvim/eval/encode.h
+++ b/src/nvim/eval/encode.h
@@ -1,10 +1,10 @@
#pragma once
-#include <msgpack/pack.h>
#include <string.h>
#include "nvim/eval/typval_defs.h"
#include "nvim/garray_defs.h"
+#include "nvim/msgpack_rpc/packer_defs.h"
/// Convert Vimscript value to msgpack string
///
@@ -13,7 +13,7 @@
/// @param[in] objname Object name, used for error message.
///
/// @return OK in case of success, FAIL otherwise.
-int encode_vim_to_msgpack(msgpack_packer *packer, typval_T *tv, const char *objname);
+int encode_vim_to_msgpack(PackerBuffer *packer, typval_T *tv, const char *objname);
/// Convert Vimscript value to :echo output
///
diff --git a/src/nvim/eval/executor.c b/src/nvim/eval/executor.c
index 1b8c057d7c..5b92f217d1 100644
--- a/src/nvim/eval/executor.c
+++ b/src/nvim/eval/executor.c
@@ -1,6 +1,7 @@
#include <inttypes.h>
#include <stdlib.h>
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/executor.h"
#include "nvim/eval/typval.h"
@@ -20,6 +21,174 @@
char *e_list_index_out_of_range_nr
= N_("E684: List index out of range: %" PRId64);
+/// Handle "blob1 += blob2".
+/// Returns OK or FAIL.
+static int tv_op_blob(typval_T *tv1, const typval_T *tv2, const char *op)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (*op != '+' || tv2->v_type != VAR_BLOB) {
+ return FAIL;
+ }
+
+ // Blob += Blob
+ if (tv2->vval.v_blob == NULL) {
+ return OK;
+ }
+
+ if (tv1->vval.v_blob == NULL) {
+ tv1->vval.v_blob = tv2->vval.v_blob;
+ tv1->vval.v_blob->bv_refcount++;
+ return OK;
+ }
+
+ blob_T *const b1 = tv1->vval.v_blob;
+ blob_T *const b2 = tv2->vval.v_blob;
+ const int len = tv_blob_len(b2);
+
+ for (int i = 0; i < len; i++) {
+ ga_append(&b1->bv_ga, tv_blob_get(b2, i));
+ }
+
+ return OK;
+}
+
+/// Handle "list1 += list2".
+/// Returns OK or FAIL.
+static int tv_op_list(typval_T *tv1, const typval_T *tv2, const char *op)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (*op != '+' || tv2->v_type != VAR_LIST) {
+ return FAIL;
+ }
+
+ // List += List
+ if (tv2->vval.v_list == NULL) {
+ return OK;
+ }
+
+ if (tv1->vval.v_list == NULL) {
+ tv1->vval.v_list = tv2->vval.v_list;
+ tv_list_ref(tv1->vval.v_list);
+ } else {
+ tv_list_extend(tv1->vval.v_list, tv2->vval.v_list, NULL);
+ }
+
+ return OK;
+}
+
+/// Handle number operations:
+/// nr += nr , nr -= nr , nr *=nr , nr /= nr , nr %= nr
+///
+/// Returns OK or FAIL.
+static int tv_op_number(typval_T *tv1, const typval_T *tv2, const char *op)
+ FUNC_ATTR_NONNULL_ALL
+{
+ varnumber_T n = tv_get_number(tv1);
+ if (tv2->v_type == VAR_FLOAT) {
+ float_T f = (float_T)n;
+ if (*op == '%') {
+ return FAIL;
+ }
+ switch (*op) {
+ case '+':
+ f += tv2->vval.v_float; break;
+ case '-':
+ f -= tv2->vval.v_float; break;
+ case '*':
+ f *= tv2->vval.v_float; break;
+ case '/':
+ f /= tv2->vval.v_float; break;
+ }
+ tv_clear(tv1);
+ tv1->v_type = VAR_FLOAT;
+ tv1->vval.v_float = f;
+ } else {
+ switch (*op) {
+ case '+':
+ n += tv_get_number(tv2); break;
+ case '-':
+ n -= tv_get_number(tv2); break;
+ case '*':
+ n *= tv_get_number(tv2); break;
+ case '/':
+ n = num_divide(n, tv_get_number(tv2)); break;
+ case '%':
+ n = num_modulus(n, tv_get_number(tv2)); break;
+ }
+ tv_clear(tv1);
+ tv1->v_type = VAR_NUMBER;
+ tv1->vval.v_number = n;
+ }
+
+ return OK;
+}
+
+/// Handle "str1 .= str2"
+/// Returns OK or FAIL.
+static int tv_op_string(typval_T *tv1, const typval_T *tv2, const char *op)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (tv2->v_type == VAR_FLOAT) {
+ return FAIL;
+ }
+
+ // str .= str
+ const char *tvs = tv_get_string(tv1);
+ char numbuf[NUMBUFLEN];
+ char *const s = concat_str(tvs, tv_get_string_buf(tv2, numbuf));
+ tv_clear(tv1);
+ tv1->v_type = VAR_STRING;
+ tv1->vval.v_string = s;
+
+ return OK;
+}
+
+/// Handle "tv1 += tv2", "tv1 -= tv2", "tv1 *= tv2", "tv1 /= tv2", "tv1 %= tv2"
+/// and "tv1 .= tv2"
+/// Returns OK or FAIL.
+static int tv_op_nr_or_string(typval_T *tv1, const typval_T *tv2, const char *op)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (tv2->v_type == VAR_LIST) {
+ return FAIL;
+ }
+
+ if (vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
+ return tv_op_number(tv1, tv2, op);
+ }
+
+ return tv_op_string(tv1, tv2, op);
+}
+
+/// Handle "f1 += f2", "f1 -= f2", "f1 *= f2", "f1 /= f2".
+/// Returns OK or FAIL.
+static int tv_op_float(typval_T *tv1, const typval_T *tv2, const char *op)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (*op == '%' || *op == '.'
+ || (tv2->v_type != VAR_FLOAT
+ && tv2->v_type != VAR_NUMBER
+ && tv2->v_type != VAR_STRING)) {
+ return FAIL;
+ }
+
+ const float_T f = (tv2->v_type == VAR_FLOAT
+ ? tv2->vval.v_float
+ : (float_T)tv_get_number(tv2));
+ switch (*op) {
+ case '+':
+ tv1->vval.v_float += f; break;
+ case '-':
+ tv1->vval.v_float -= f; break;
+ case '*':
+ tv1->vval.v_float *= f; break;
+ case '/':
+ tv1->vval.v_float /= f; break;
+ }
+
+ return OK;
+}
+
/// Handle tv1 += tv2, -=, *=, /=, %=, .=
///
/// @param[in,out] tv1 First operand, modified typval.
@@ -28,125 +197,45 @@ char *e_list_index_out_of_range_nr
///
/// @return OK or FAIL.
int eexe_mod_op(typval_T *const tv1, const typval_T *const tv2, const char *const op)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NO_SANITIZE_UNDEFINED
+ FUNC_ATTR_NONNULL_ALL
{
- // Can't do anything with a Funcref, a Dict or special value on the right.
- if (tv2->v_type != VAR_FUNC && tv2->v_type != VAR_DICT
- && tv2->v_type != VAR_BOOL && tv2->v_type != VAR_SPECIAL) {
- switch (tv1->v_type) {
- case VAR_DICT:
- case VAR_FUNC:
- case VAR_PARTIAL:
- case VAR_BOOL:
- case VAR_SPECIAL:
- break;
- case VAR_BLOB:
- if (*op != '+' || tv2->v_type != VAR_BLOB) {
- break;
- }
- // Blob += Blob
- if (tv1->vval.v_blob != NULL && tv2->vval.v_blob != NULL) {
- blob_T *const b1 = tv1->vval.v_blob;
- blob_T *const b2 = tv2->vval.v_blob;
- for (int i = 0; i < tv_blob_len(b2); i++) {
- ga_append(&b1->bv_ga, tv_blob_get(b2, i));
- }
- }
- return OK;
- case VAR_LIST:
- if (*op != '+' || tv2->v_type != VAR_LIST) {
- break;
- }
- // List += List
- if (tv1->vval.v_list != NULL && tv2->vval.v_list != NULL) {
- tv_list_extend(tv1->vval.v_list, tv2->vval.v_list, NULL);
- }
- return OK;
- case VAR_NUMBER:
- case VAR_STRING:
- if (tv2->v_type == VAR_LIST) {
- break;
- }
- if (vim_strchr("+-*/%", (uint8_t)(*op)) != NULL) {
- // nr += nr or nr -= nr, nr *= nr, nr /= nr, nr %= nr
- varnumber_T n = tv_get_number(tv1);
- if (tv2->v_type == VAR_FLOAT) {
- float_T f = (float_T)n;
-
- if (*op == '%') {
- break;
- }
- switch (*op) {
- case '+':
- f += tv2->vval.v_float; break;
- case '-':
- f -= tv2->vval.v_float; break;
- case '*':
- f *= tv2->vval.v_float; break;
- case '/':
- f /= tv2->vval.v_float; break;
- }
- tv_clear(tv1);
- tv1->v_type = VAR_FLOAT;
- tv1->vval.v_float = f;
- } else {
- switch (*op) {
- case '+':
- n += tv_get_number(tv2); break;
- case '-':
- n -= tv_get_number(tv2); break;
- case '*':
- n *= tv_get_number(tv2); break;
- case '/':
- n = num_divide(n, tv_get_number(tv2)); break;
- case '%':
- n = num_modulus(n, tv_get_number(tv2)); break;
- }
- tv_clear(tv1);
- tv1->v_type = VAR_NUMBER;
- tv1->vval.v_number = n;
- }
- } else {
- // str .= str
- if (tv2->v_type == VAR_FLOAT) {
- break;
- }
- const char *tvs = tv_get_string(tv1);
- char numbuf[NUMBUFLEN];
- char *const s =
- concat_str(tvs, tv_get_string_buf(tv2, numbuf));
- tv_clear(tv1);
- tv1->v_type = VAR_STRING;
- tv1->vval.v_string = s;
- }
- return OK;
- case VAR_FLOAT: {
- if (*op == '%' || *op == '.'
- || (tv2->v_type != VAR_FLOAT
- && tv2->v_type != VAR_NUMBER
- && tv2->v_type != VAR_STRING)) {
- break;
- }
- const float_T f = (tv2->v_type == VAR_FLOAT
- ? tv2->vval.v_float
- : (float_T)tv_get_number(tv2));
- switch (*op) {
- case '+':
- tv1->vval.v_float += f; break;
- case '-':
- tv1->vval.v_float -= f; break;
- case '*':
- tv1->vval.v_float *= f; break;
- case '/':
- tv1->vval.v_float /= f; break;
- }
- return OK;
- }
- case VAR_UNKNOWN:
- abort();
- }
+ // Can't do anything with a Funcref or Dict on the right.
+ // v:true and friends only work with "..=".
+ if (tv2->v_type == VAR_FUNC || tv2->v_type == VAR_DICT
+ || ((tv2->v_type == VAR_BOOL || tv2->v_type == VAR_SPECIAL) && *op == '.')) {
+ semsg(_(e_letwrong), op);
+ return FAIL;
+ }
+
+ int retval = FAIL;
+
+ switch (tv1->v_type) {
+ case VAR_DICT:
+ case VAR_FUNC:
+ case VAR_PARTIAL:
+ case VAR_BOOL:
+ case VAR_SPECIAL:
+ break;
+ case VAR_BLOB:
+ retval = tv_op_blob(tv1, tv2, op);
+ break;
+ case VAR_LIST:
+ retval = tv_op_list(tv1, tv2, op);
+ break;
+ case VAR_NUMBER:
+ case VAR_STRING:
+ retval = tv_op_nr_or_string(tv1, tv2, op);
+ break;
+ case VAR_FLOAT:
+ retval = tv_op_float(tv1, tv2, op);
+ break;
+ case VAR_UNKNOWN:
+ abort();
+ }
+
+ if (retval != OK) {
+ semsg(_(e_letwrong), op);
}
- semsg(_(e_letwrong), op);
- return FAIL;
+ return retval;
}
diff --git a/src/nvim/eval/fs.c b/src/nvim/eval/fs.c
new file mode 100644
index 0000000000..f5b33c804e
--- /dev/null
+++ b/src/nvim/eval/fs.c
@@ -0,0 +1,1492 @@
+// eval/fs.c: Filesystem related builtin functions
+
+#include <assert.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+
+#include "auto/config.h"
+#include "nvim/ascii_defs.h"
+#include "nvim/buffer_defs.h"
+#include "nvim/cmdexpand.h"
+#include "nvim/cmdexpand_defs.h"
+#include "nvim/errors.h"
+#include "nvim/eval.h"
+#include "nvim/eval/fs.h"
+#include "nvim/eval/typval.h"
+#include "nvim/eval/userfunc.h"
+#include "nvim/eval/window.h"
+#include "nvim/ex_cmds.h"
+#include "nvim/ex_docmd.h"
+#include "nvim/file_search.h"
+#include "nvim/fileio.h"
+#include "nvim/garray.h"
+#include "nvim/garray_defs.h"
+#include "nvim/gettext_defs.h"
+#include "nvim/globals.h"
+#include "nvim/macros_defs.h"
+#include "nvim/memory.h"
+#include "nvim/message.h"
+#include "nvim/option_vars.h"
+#include "nvim/os/fileio.h"
+#include "nvim/os/fileio_defs.h"
+#include "nvim/os/fs.h"
+#include "nvim/os/fs_defs.h"
+#include "nvim/os/os_defs.h"
+#include "nvim/path.h"
+#include "nvim/pos_defs.h"
+#include "nvim/strings.h"
+#include "nvim/types_defs.h"
+#include "nvim/vim_defs.h"
+#include "nvim/window.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval/fs.c.generated.h"
+#endif
+
+static const char e_error_while_writing_str[] = N_("E80: Error while writing: %s");
+
+/// "chdir(dir)" function
+void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ if (argvars[0].v_type != VAR_STRING) {
+ // Returning an empty string means it failed.
+ // No error message, for historic reasons.
+ return;
+ }
+
+ // Return the current directory
+ char *cwd = xmalloc(MAXPATHL);
+ if (os_dirname(cwd, MAXPATHL) != FAIL) {
+#ifdef BACKSLASH_IN_FILENAME
+ slash_adjust(cwd);
+#endif
+ rettv->vval.v_string = xstrdup(cwd);
+ }
+ xfree(cwd);
+
+ CdScope scope = kCdScopeGlobal;
+ if (curwin->w_localdir != NULL) {
+ scope = kCdScopeWindow;
+ } else if (curtab->tp_localdir != NULL) {
+ scope = kCdScopeTabpage;
+ }
+
+ if (!changedir_func(argvars[0].vval.v_string, scope)) {
+ // Directory change failed
+ XFREE_CLEAR(rettv->vval.v_string);
+ }
+}
+
+/// "delete()" function
+void f_delete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+ if (check_secure()) {
+ return;
+ }
+
+ const char *const name = tv_get_string(&argvars[0]);
+ if (*name == NUL) {
+ emsg(_(e_invarg));
+ return;
+ }
+
+ char nbuf[NUMBUFLEN];
+ const char *flags;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ flags = tv_get_string_buf(&argvars[1], nbuf);
+ } else {
+ flags = "";
+ }
+
+ if (*flags == NUL) {
+ // delete a file
+ rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1;
+ } else if (strcmp(flags, "d") == 0) {
+ // delete an empty directory
+ rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1;
+ } else if (strcmp(flags, "rf") == 0) {
+ // delete a directory recursively
+ rettv->vval.v_number = delete_recursive(name);
+ } else {
+ semsg(_(e_invexpr2), flags);
+ }
+}
+
+/// "executable()" function
+void f_executable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ if (tv_check_for_string_arg(argvars, 0) == FAIL) {
+ return;
+ }
+
+ // Check in $PATH and also check directly if there is a directory name
+ rettv->vval.v_number = os_can_exe(tv_get_string(&argvars[0]), NULL, true);
+}
+
+/// "exepath()" function
+void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ if (tv_check_for_nonempty_string_arg(argvars, 0) == FAIL) {
+ return;
+ }
+
+ char *path = NULL;
+
+ os_can_exe(tv_get_string(&argvars[0]), &path, true);
+
+#ifdef BACKSLASH_IN_FILENAME
+ if (path != NULL) {
+ slash_adjust(path);
+ }
+#endif
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = path;
+}
+
+/// "filecopy()" function
+void f_filecopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = false;
+
+ if (check_secure()
+ || tv_check_for_string_arg(argvars, 0) == FAIL
+ || tv_check_for_string_arg(argvars, 1) == FAIL) {
+ return;
+ }
+
+ const char *from = tv_get_string(&argvars[0]);
+
+ FileInfo from_info;
+ if (os_fileinfo_link(from, &from_info)
+ && (S_ISREG(from_info.stat.st_mode) || S_ISLNK(from_info.stat.st_mode))) {
+ rettv->vval.v_number
+ = vim_copyfile(tv_get_string(&argvars[0]), tv_get_string(&argvars[1])) == OK;
+ }
+}
+
+/// "filereadable()" function
+void f_filereadable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *const p = tv_get_string(&argvars[0]);
+ rettv->vval.v_number = (*p && !os_isdir(p) && os_file_is_readable(p));
+}
+
+/// @return 0 for not writable
+/// 1 for writable file
+/// 2 for a dir which we have rights to write into.
+void f_filewritable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *filename = tv_get_string(&argvars[0]);
+ rettv->vval.v_number = os_file_is_writable(filename);
+}
+
+static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
+{
+ char *fresult = NULL;
+ char *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path;
+ int count = 1;
+ bool first = true;
+ bool error = false;
+
+ rettv->vval.v_string = NULL;
+ rettv->v_type = VAR_STRING;
+
+ const char *fname = tv_get_string(&argvars[0]);
+
+ char pathbuf[NUMBUFLEN];
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf);
+ if (p == NULL) {
+ error = true;
+ } else {
+ if (*p != NUL) {
+ path = (char *)p;
+ }
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ count = (int)tv_get_number_chk(&argvars[2], &error);
+ }
+ }
+ }
+
+ if (count < 0) {
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ }
+
+ if (*fname != NUL && !error) {
+ char *file_to_find = NULL;
+ char *search_ctx = NULL;
+
+ do {
+ if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) {
+ xfree(fresult);
+ }
+ fresult = find_file_in_path_option(first ? (char *)fname : NULL,
+ first ? strlen(fname) : 0,
+ 0, first, path,
+ find_what, curbuf->b_ffname,
+ (find_what == FINDFILE_DIR
+ ? ""
+ : curbuf->b_p_sua),
+ &file_to_find, &search_ctx);
+ first = false;
+
+ if (fresult != NULL && rettv->v_type == VAR_LIST) {
+ tv_list_append_string(rettv->vval.v_list, fresult, -1);
+ }
+ } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL);
+
+ xfree(file_to_find);
+ vim_findfile_cleanup(search_ctx);
+ }
+
+ if (rettv->v_type == VAR_STRING) {
+ rettv->vval.v_string = fresult;
+ }
+}
+
+/// "finddir({fname}[, {path}[, {count}]])" function
+void f_finddir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ findfilendir(argvars, rettv, FINDFILE_DIR);
+}
+
+/// "findfile({fname}[, {path}[, {count}]])" function
+void f_findfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ findfilendir(argvars, rettv, FINDFILE_FILE);
+}
+
+/// `getcwd([{win}[, {tab}]])` function
+///
+/// Every scope not specified implies the currently selected scope object.
+///
+/// @pre The arguments must be of type number.
+/// @pre There may not be more than two arguments.
+/// @pre An argument may not be -1 if preceding arguments are not all -1.
+///
+/// @post The return value will be a string.
+void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ // Possible scope of working directory to return.
+ CdScope scope = kCdScopeInvalid;
+
+ // Numbers of the scope objects (window, tab) we want the working directory
+ // of. A `-1` means to skip this scope, a `0` means the current object.
+ int scope_number[] = {
+ [kCdScopeWindow] = 0, // Number of window to look at.
+ [kCdScopeTabpage] = 0, // Number of tab to look at.
+ };
+
+ char *cwd = NULL; // Current working directory to print
+ char *from = NULL; // The original string to copy
+
+ tabpage_T *tp = curtab; // The tabpage to look at.
+ win_T *win = curwin; // The window to look at.
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ // Pre-conditions and scope extraction together
+ for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
+ // If there is no argument there are no more scopes after it, break out.
+ if (argvars[i].v_type == VAR_UNKNOWN) {
+ break;
+ }
+ if (argvars[i].v_type != VAR_NUMBER) {
+ emsg(_(e_invarg));
+ return;
+ }
+ scope_number[i] = (int)argvars[i].vval.v_number;
+ // It is an error for the scope number to be less than `-1`.
+ if (scope_number[i] < -1) {
+ emsg(_(e_invarg));
+ return;
+ }
+ // Use the narrowest scope the user requested
+ if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
+ // The scope is the current iteration step.
+ scope = i;
+ } else if (scope_number[i] < 0) {
+ scope = i + 1;
+ }
+ }
+
+ // Find the tabpage by number
+ if (scope_number[kCdScopeTabpage] > 0) {
+ tp = find_tabpage(scope_number[kCdScopeTabpage]);
+ if (!tp) {
+ emsg(_("E5000: Cannot find tab number."));
+ return;
+ }
+ }
+
+ // Find the window in `tp` by number, `NULL` if none.
+ if (scope_number[kCdScopeWindow] >= 0) {
+ if (scope_number[kCdScopeTabpage] < 0) {
+ emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
+ return;
+ }
+
+ if (scope_number[kCdScopeWindow] > 0) {
+ win = find_win_by_nr(&argvars[0], tp);
+ if (!win) {
+ emsg(_("E5002: Cannot find window number."));
+ return;
+ }
+ }
+ }
+
+ cwd = xmalloc(MAXPATHL);
+
+ switch (scope) {
+ case kCdScopeWindow:
+ assert(win);
+ from = win->w_localdir;
+ if (from) {
+ break;
+ }
+ FALLTHROUGH;
+ case kCdScopeTabpage:
+ assert(tp);
+ from = tp->tp_localdir;
+ if (from) {
+ break;
+ }
+ FALLTHROUGH;
+ case kCdScopeGlobal:
+ if (globaldir) { // `globaldir` is not always set.
+ from = globaldir;
+ break;
+ }
+ FALLTHROUGH; // In global directory, just need to get OS CWD.
+ case kCdScopeInvalid: // If called without any arguments, get OS CWD.
+ if (os_dirname(cwd, MAXPATHL) == FAIL) {
+ from = ""; // Return empty string on failure.
+ }
+ }
+
+ if (from) {
+ xstrlcpy(cwd, from, MAXPATHL);
+ }
+
+ rettv->vval.v_string = xstrdup(cwd);
+#ifdef BACKSLASH_IN_FILENAME
+ slash_adjust(rettv->vval.v_string);
+#endif
+
+ xfree(cwd);
+}
+
+/// "getfperm({fname})" function
+void f_getfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char *perm = NULL;
+ char flags[] = "rwx";
+
+ const char *filename = tv_get_string(&argvars[0]);
+ int32_t file_perm = os_getperm(filename);
+ if (file_perm >= 0) {
+ perm = xstrdup("---------");
+ for (int i = 0; i < 9; i++) {
+ if (file_perm & (1 << (8 - i))) {
+ perm[i] = flags[i % 3];
+ }
+ }
+ }
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = perm;
+}
+
+/// "getfsize({fname})" function
+void f_getfsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *fname = tv_get_string(&argvars[0]);
+
+ rettv->v_type = VAR_NUMBER;
+
+ FileInfo file_info;
+ if (os_fileinfo(fname, &file_info)) {
+ uint64_t filesize = os_fileinfo_size(&file_info);
+ if (os_isdir(fname)) {
+ rettv->vval.v_number = 0;
+ } else {
+ rettv->vval.v_number = (varnumber_T)filesize;
+
+ // non-perfect check for overflow
+ if ((uint64_t)rettv->vval.v_number != filesize) {
+ rettv->vval.v_number = -2;
+ }
+ }
+ } else {
+ rettv->vval.v_number = -1;
+ }
+}
+
+/// "getftime({fname})" function
+void f_getftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *fname = tv_get_string(&argvars[0]);
+
+ FileInfo file_info;
+ if (os_fileinfo(fname, &file_info)) {
+ rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec;
+ } else {
+ rettv->vval.v_number = -1;
+ }
+}
+
+/// "getftype({fname})" function
+void f_getftype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ char *type = NULL;
+ char *t;
+
+ const char *fname = tv_get_string(&argvars[0]);
+
+ rettv->v_type = VAR_STRING;
+ FileInfo file_info;
+ if (os_fileinfo_link(fname, &file_info)) {
+ uint64_t mode = file_info.stat.st_mode;
+ if (S_ISREG(mode)) {
+ t = "file";
+ } else if (S_ISDIR(mode)) {
+ t = "dir";
+ } else if (S_ISLNK(mode)) {
+ t = "link";
+ } else if (S_ISBLK(mode)) {
+ t = "bdev";
+ } else if (S_ISCHR(mode)) {
+ t = "cdev";
+ } else if (S_ISFIFO(mode)) {
+ t = "fifo";
+ } else if (S_ISSOCK(mode)) {
+ t = "socket";
+ } else {
+ t = "other";
+ }
+ type = xstrdup(t);
+ }
+ rettv->vval.v_string = type;
+}
+
+/// "glob()" function
+void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ int options = WILD_SILENT|WILD_USE_NL;
+ expand_T xpc;
+ bool error = false;
+
+ // When the optional second argument is non-zero, don't remove matches
+ // for 'wildignore' and don't put matches for 'suffixes' at the end.
+ rettv->v_type = VAR_STRING;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (tv_get_number_chk(&argvars[1], &error)) {
+ options |= WILD_KEEP_ALL;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ if (tv_get_number_chk(&argvars[2], &error)) {
+ tv_list_set_ret(rettv, NULL);
+ }
+ if (argvars[3].v_type != VAR_UNKNOWN
+ && tv_get_number_chk(&argvars[3], &error)) {
+ options |= WILD_ALLLINKS;
+ }
+ }
+ }
+ if (!error) {
+ ExpandInit(&xpc);
+ xpc.xp_context = EXPAND_FILES;
+ if (p_wic) {
+ options += WILD_ICASE;
+ }
+ if (rettv->v_type == VAR_STRING) {
+ rettv->vval.v_string = ExpandOne(&xpc, (char *)
+ tv_get_string(&argvars[0]), NULL, options,
+ WILD_ALL);
+ } else {
+ ExpandOne(&xpc, (char *)tv_get_string(&argvars[0]), NULL, options,
+ WILD_ALL_KEEP);
+ tv_list_alloc_ret(rettv, xpc.xp_numfiles);
+ for (int i = 0; i < xpc.xp_numfiles; i++) {
+ tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1);
+ }
+ ExpandCleanup(&xpc);
+ }
+ } else {
+ rettv->vval.v_string = NULL;
+ }
+}
+
+/// "globpath()" function
+void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ 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.
+ rettv->v_type = VAR_STRING;
+
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ // When the optional second argument is non-zero, don't remove matches
+ // for 'wildignore' and don't put matches for 'suffixes' at the end.
+ if (tv_get_number_chk(&argvars[2], &error)) {
+ flags |= WILD_KEEP_ALL;
+ }
+
+ if (argvars[3].v_type != VAR_UNKNOWN) {
+ if (tv_get_number_chk(&argvars[3], &error)) {
+ tv_list_set_ret(rettv, NULL);
+ }
+ if (argvars[4].v_type != VAR_UNKNOWN
+ && tv_get_number_chk(&argvars[4], &error)) {
+ flags |= WILD_ALLLINKS;
+ }
+ }
+ }
+
+ char buf1[NUMBUFLEN];
+ const char *const file = tv_get_string_buf_chk(&argvars[1], buf1);
+ if (file != NULL && !error) {
+ garray_T ga;
+ ga_init(&ga, (int)sizeof(char *), 10);
+ globpath((char *)tv_get_string(&argvars[0]), (char *)file, &ga, flags, false);
+
+ if (rettv->v_type == VAR_STRING) {
+ rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n");
+ } else {
+ tv_list_alloc_ret(rettv, ga.ga_len);
+ for (int i = 0; i < ga.ga_len; i++) {
+ tv_list_append_string(rettv->vval.v_list,
+ ((const char **)(ga.ga_data))[i], -1);
+ }
+ }
+
+ ga_clear_strings(&ga);
+ } else {
+ rettv->vval.v_string = NULL;
+ }
+}
+
+/// "glob2regpat()" function
+void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = pat == NULL ? NULL : file_pat_to_reg_pat(pat, NULL, NULL, false);
+}
+
+/// `haslocaldir([{win}[, {tab}]])` function
+///
+/// Returns `1` if the scope object has a local directory, `0` otherwise. If a
+/// scope object is not specified the current one is implied. This function
+/// share a lot of code with `f_getcwd`.
+///
+/// @pre The arguments must be of type number.
+/// @pre There may not be more than two arguments.
+/// @pre An argument may not be -1 if preceding arguments are not all -1.
+///
+/// @post The return value will be either the number `1` or `0`.
+void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ // Possible scope of working directory to return.
+ CdScope scope = kCdScopeInvalid;
+
+ // Numbers of the scope objects (window, tab) we want the working directory
+ // of. A `-1` means to skip this scope, a `0` means the current object.
+ int scope_number[] = {
+ [kCdScopeWindow] = 0, // Number of window to look at.
+ [kCdScopeTabpage] = 0, // Number of tab to look at.
+ };
+
+ tabpage_T *tp = curtab; // The tabpage to look at.
+ win_T *win = curwin; // The window to look at.
+
+ rettv->v_type = VAR_NUMBER;
+ rettv->vval.v_number = 0;
+
+ // Pre-conditions and scope extraction together
+ for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
+ if (argvars[i].v_type == VAR_UNKNOWN) {
+ break;
+ }
+ if (argvars[i].v_type != VAR_NUMBER) {
+ emsg(_(e_invarg));
+ return;
+ }
+ scope_number[i] = (int)argvars[i].vval.v_number;
+ if (scope_number[i] < -1) {
+ emsg(_(e_invarg));
+ return;
+ }
+ // Use the narrowest scope the user requested
+ if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
+ // The scope is the current iteration step.
+ scope = i;
+ } else if (scope_number[i] < 0) {
+ scope = i + 1;
+ }
+ }
+
+ // If the user didn't specify anything, default to window scope
+ if (scope == kCdScopeInvalid) {
+ scope = MIN_CD_SCOPE;
+ }
+
+ // Find the tabpage by number
+ if (scope_number[kCdScopeTabpage] > 0) {
+ tp = find_tabpage(scope_number[kCdScopeTabpage]);
+ if (!tp) {
+ emsg(_("E5000: Cannot find tab number."));
+ return;
+ }
+ }
+
+ // Find the window in `tp` by number, `NULL` if none.
+ if (scope_number[kCdScopeWindow] >= 0) {
+ if (scope_number[kCdScopeTabpage] < 0) {
+ emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
+ return;
+ }
+
+ if (scope_number[kCdScopeWindow] > 0) {
+ win = find_win_by_nr(&argvars[0], tp);
+ if (!win) {
+ emsg(_("E5002: Cannot find window number."));
+ return;
+ }
+ }
+ }
+
+ switch (scope) {
+ case kCdScopeWindow:
+ assert(win);
+ rettv->vval.v_number = win->w_localdir ? 1 : 0;
+ break;
+ case kCdScopeTabpage:
+ assert(tp);
+ rettv->vval.v_number = tp->tp_localdir ? 1 : 0;
+ break;
+ case kCdScopeGlobal:
+ // The global scope never has a local directory
+ break;
+ case kCdScopeInvalid:
+ // We should never get here
+ abort();
+ }
+}
+
+/// "isabsolutepath()" function
+void f_isabsolutepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = path_is_absolute(tv_get_string(&argvars[0]));
+}
+
+/// "isdirectory()" function
+void f_isdirectory(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = os_isdir(tv_get_string(&argvars[0]));
+}
+
+/// "mkdir()" function
+void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ int prot = 0755;
+
+ rettv->vval.v_number = FAIL;
+ if (check_secure()) {
+ return;
+ }
+
+ char buf[NUMBUFLEN];
+ const char *const dir = tv_get_string_buf(&argvars[0], buf);
+ if (*dir == NUL) {
+ return;
+ }
+
+ if (*path_tail(dir) == NUL) {
+ // Remove trailing slashes.
+ *path_tail_with_sep((char *)dir) = NUL;
+ }
+
+ bool defer = false;
+ bool defer_recurse = false;
+ char *created = NULL;
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ prot = (int)tv_get_number_chk(&argvars[2], NULL);
+ if (prot == -1) {
+ return;
+ }
+ }
+ const char *arg2 = tv_get_string(&argvars[1]);
+ defer = vim_strchr(arg2, 'D') != NULL;
+ defer_recurse = vim_strchr(arg2, 'R') != NULL;
+ if ((defer || defer_recurse) && !can_add_defer()) {
+ return;
+ }
+
+ if (vim_strchr(arg2, 'p') != NULL) {
+ char *failed_dir;
+ int ret = os_mkdir_recurse(dir, prot, &failed_dir,
+ defer || defer_recurse ? &created : NULL);
+ if (ret != 0) {
+ semsg(_(e_mkdir), failed_dir, os_strerror(ret));
+ xfree(failed_dir);
+ rettv->vval.v_number = FAIL;
+ return;
+ }
+ rettv->vval.v_number = OK;
+ }
+ }
+ if (rettv->vval.v_number == FAIL) {
+ rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
+ }
+
+ // Handle "D" and "R": deferred deletion of the created directory.
+ if (rettv->vval.v_number == OK
+ && created == NULL && (defer || defer_recurse)) {
+ created = FullName_save(dir, false);
+ }
+ if (created != NULL) {
+ typval_T tv[2];
+ tv[0].v_type = VAR_STRING;
+ tv[0].v_lock = VAR_UNLOCKED;
+ tv[0].vval.v_string = created;
+ tv[1].v_type = VAR_STRING;
+ tv[1].v_lock = VAR_UNLOCKED;
+ tv[1].vval.v_string = xstrdup(defer_recurse ? "rf" : "d");
+ add_defer("delete", 2, tv);
+ }
+}
+
+/// "pathshorten()" function
+void f_pathshorten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ int trim_len = 1;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ trim_len = (int)tv_get_number(&argvars[1]);
+ if (trim_len < 1) {
+ trim_len = 1;
+ }
+ }
+
+ rettv->v_type = VAR_STRING;
+ const char *p = tv_get_string_chk(&argvars[0]);
+ if (p == NULL) {
+ rettv->vval.v_string = NULL;
+ } else {
+ rettv->vval.v_string = xstrdup(p);
+ shorten_dir_len(rettv->vval.v_string, trim_len);
+ }
+}
+
+/// Evaluate "expr" (= "context") for readdir().
+static varnumber_T readdir_checkitem(void *context, const char *name)
+ FUNC_ATTR_NONNULL_ALL
+{
+ typval_T *expr = (typval_T *)context;
+ typval_T argv[2];
+ varnumber_T retval = 0;
+ bool error = false;
+
+ if (expr->v_type == VAR_UNKNOWN) {
+ return 1;
+ }
+
+ typval_T save_val;
+ prepare_vimvar(VV_VAL, &save_val);
+ set_vim_var_string(VV_VAL, name, -1);
+ argv[0].v_type = VAR_STRING;
+ argv[0].vval.v_string = (char *)name;
+
+ typval_T rettv;
+ if (eval_expr_typval(expr, false, argv, 1, &rettv) == FAIL) {
+ goto theend;
+ }
+
+ retval = tv_get_number_chk(&rettv, &error);
+ if (error) {
+ retval = -1;
+ }
+
+ tv_clear(&rettv);
+
+theend:
+ set_vim_var_string(VV_VAL, NULL, 0);
+ restore_vimvar(VV_VAL, &save_val);
+ return retval;
+}
+
+/// "readdir()" function
+void f_readdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+
+ const char *path = tv_get_string(&argvars[0]);
+ typval_T *expr = &argvars[1];
+ garray_T ga;
+ int ret = readdir_core(&ga, path, (void *)expr, readdir_checkitem);
+ if (ret == OK && ga.ga_len > 0) {
+ for (int i = 0; i < ga.ga_len; i++) {
+ const char *p = ((const char **)ga.ga_data)[i];
+ tv_list_append_string(rettv->vval.v_list, p, -1);
+ }
+ }
+ ga_clear_strings(&ga);
+}
+
+/// Read blob from file "fd".
+/// Caller has allocated a blob in "rettv".
+///
+/// @param[in] fd File to read from.
+/// @param[in,out] rettv Blob to write to.
+/// @param[in] offset Read the file from the specified offset.
+/// @param[in] size Read the specified size, or -1 if no limit.
+///
+/// @return OK on success, or FAIL on failure.
+static int read_blob(FILE *const fd, typval_T *rettv, off_T offset, off_T size_arg)
+ FUNC_ATTR_NONNULL_ALL
+{
+ blob_T *const blob = rettv->vval.v_blob;
+ FileInfo file_info;
+ if (!os_fileinfo_fd(fileno(fd), &file_info)) {
+ return FAIL; // can't read the file, error
+ }
+
+ int whence;
+ off_T size = size_arg;
+ const off_T file_size = (off_T)os_fileinfo_size(&file_info);
+ if (offset >= 0) {
+ // The size defaults to the whole file. If a size is given it is
+ // limited to not go past the end of the file.
+ if (size == -1 || (size > file_size - offset && !S_ISCHR(file_info.stat.st_mode))) {
+ // size may become negative, checked below
+ size = (off_T)os_fileinfo_size(&file_info) - offset;
+ }
+ whence = SEEK_SET;
+ } else {
+ // limit the offset to not go before the start of the file
+ if (-offset > file_size && !S_ISCHR(file_info.stat.st_mode)) {
+ offset = -file_size;
+ }
+ // Size defaults to reading until the end of the file.
+ if (size == -1 || size > -offset) {
+ size = -offset;
+ }
+ whence = SEEK_END;
+ }
+ if (size <= 0) {
+ return OK;
+ }
+ if (offset != 0 && vim_fseek(fd, offset, whence) != 0) {
+ return OK;
+ }
+
+ ga_grow(&blob->bv_ga, (int)size);
+ blob->bv_ga.ga_len = (int)size;
+ if (fread(blob->bv_ga.ga_data, 1, (size_t)blob->bv_ga.ga_len, fd)
+ < (size_t)blob->bv_ga.ga_len) {
+ // An empty blob is returned on error.
+ tv_blob_free(rettv->vval.v_blob);
+ rettv->vval.v_blob = NULL;
+ return FAIL;
+ }
+ return OK;
+}
+
+/// "readfile()" or "readblob()" function
+static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_blob)
+{
+ bool binary = false;
+ bool blob = always_blob;
+ FILE *fd;
+ char buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1
+ int io_size = sizeof(buf);
+ char *prev = NULL; // previously read bytes, if any
+ ptrdiff_t prevlen = 0; // length of data in prev
+ ptrdiff_t prevsize = 0; // size of prev buffer
+ int64_t maxline = MAXLNUM;
+ off_T offset = 0;
+ off_T size = -1;
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (always_blob) {
+ offset = (off_T)tv_get_number(&argvars[1]);
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ size = (off_T)tv_get_number(&argvars[2]);
+ }
+ } else {
+ if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
+ binary = true;
+ } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) {
+ blob = true;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ maxline = tv_get_number(&argvars[2]);
+ }
+ }
+ }
+
+ if (blob) {
+ tv_blob_alloc_ret(rettv);
+ } else {
+ tv_list_alloc_ret(rettv, kListLenUnknown);
+ }
+
+ // Always open the file in binary mode, library functions have a mind of
+ // their own about CR-LF conversion.
+ const char *const fname = tv_get_string(&argvars[0]);
+
+ if (os_isdir(fname)) {
+ semsg(_(e_isadir2), fname);
+ return;
+ }
+ if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) {
+ semsg(_(e_notopen), *fname == NUL ? _("<empty>") : fname);
+ return;
+ }
+
+ if (blob) {
+ if (read_blob(fd, rettv, offset, size) == FAIL) {
+ semsg(_(e_notread), fname);
+ }
+ fclose(fd);
+ return;
+ }
+
+ list_T *const l = rettv->vval.v_list;
+
+ while (maxline < 0 || tv_list_len(l) < maxline) {
+ int readlen = (int)fread(buf, 1, (size_t)io_size, fd);
+
+ // This for loop processes what was read, but is also entered at end
+ // of file so that either:
+ // - an incomplete line gets written
+ // - a "binary" file gets an empty line at the end if it ends in a
+ // newline.
+ char *p; // Position in buf.
+ char *start; // Start of current line.
+ for (p = buf, start = buf;
+ p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary));
+ p++) {
+ if (readlen <= 0 || *p == '\n') {
+ char *s = NULL;
+ size_t len = (size_t)(p - start);
+
+ // Finished a line. Remove CRs before NL.
+ if (readlen > 0 && !binary) {
+ while (len > 0 && start[len - 1] == '\r') {
+ len--;
+ }
+ // removal may cross back to the "prev" string
+ if (len == 0) {
+ while (prevlen > 0 && prev[prevlen - 1] == '\r') {
+ prevlen--;
+ }
+ }
+ }
+ if (prevlen == 0) {
+ assert(len < INT_MAX);
+ s = xmemdupz(start, len);
+ } else {
+ // Change "prev" buffer to be the right size. This way
+ // the bytes are only copied once, and very long lines are
+ // allocated only once.
+ s = xrealloc(prev, (size_t)prevlen + len + 1);
+ memcpy(s + prevlen, start, len);
+ s[(size_t)prevlen + len] = NUL;
+ prev = NULL; // the list will own the string
+ prevlen = prevsize = 0;
+ }
+
+ tv_list_append_owned_tv(l, (typval_T) {
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = s,
+ });
+
+ start = p + 1; // Step over newline.
+ if (maxline < 0) {
+ if (tv_list_len(l) > -maxline) {
+ assert(tv_list_len(l) == 1 + (-maxline));
+ tv_list_item_remove(l, tv_list_first(l));
+ }
+ } else if (tv_list_len(l) >= maxline) {
+ assert(tv_list_len(l) == maxline);
+ break;
+ }
+ if (readlen <= 0) {
+ break;
+ }
+ } else if (*p == NUL) {
+ *p = '\n';
+ // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this
+ // when finding the BF and check the previous two bytes.
+ } else if ((uint8_t)(*p) == 0xbf && !binary) {
+ // Find the two bytes before the 0xbf. If p is at buf, or buf + 1,
+ // these may be in the "prev" string.
+ char back1 = p >= buf + 1 ? p[-1]
+ : prevlen >= 1 ? prev[prevlen - 1] : NUL;
+ char back2 = p >= buf + 2 ? p[-2]
+ : (p == buf + 1 && prevlen >= 1
+ ? prev[prevlen - 1]
+ : prevlen >= 2 ? prev[prevlen - 2] : NUL);
+
+ if ((uint8_t)back2 == 0xef && (uint8_t)back1 == 0xbb) {
+ char *dest = p - 2;
+
+ // Usually a BOM is at the beginning of a file, and so at
+ // the beginning of a line; then we can just step over it.
+ if (start == dest) {
+ start = p + 1;
+ } else {
+ // have to shuffle buf to close gap
+ int adjust_prevlen = 0;
+
+ if (dest < buf) {
+ // adjust_prevlen must be 1 or 2.
+ adjust_prevlen = (int)(buf - dest);
+ dest = buf;
+ }
+ if (readlen > p - buf + 1) {
+ memmove(dest, p + 1, (size_t)readlen - (size_t)(p - buf) - 1);
+ }
+ readlen -= 3 - adjust_prevlen;
+ prevlen -= adjust_prevlen;
+ p = dest - 1;
+ }
+ }
+ }
+ } // for
+
+ if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) {
+ break;
+ }
+ if (start < p) {
+ // There's part of a line in buf, store it in "prev".
+ if (p - start + prevlen >= prevsize) {
+ // A common use case is ordinary text files and "prev" gets a
+ // fragment of a line, so the first allocation is made
+ // small, to avoid repeatedly 'allocing' large and
+ // 'reallocing' small.
+ if (prevsize == 0) {
+ prevsize = p - start;
+ } else {
+ ptrdiff_t grow50pc = (prevsize * 3) / 2;
+ ptrdiff_t growmin = (p - start) * 2 + prevlen;
+ prevsize = grow50pc > growmin ? grow50pc : growmin;
+ }
+ prev = xrealloc(prev, (size_t)prevsize);
+ }
+ // Add the line part to end of "prev".
+ memmove(prev + prevlen, start, (size_t)(p - start));
+ prevlen += p - start;
+ }
+ } // while
+
+ xfree(prev);
+ fclose(fd);
+}
+
+/// "readblob()" function
+void f_readblob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ read_file_or_blob(argvars, rettv, true);
+}
+
+/// "readfile()" function
+void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ read_file_or_blob(argvars, rettv, false);
+}
+
+/// "rename({from}, {to})" function
+void f_rename(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ if (check_secure()) {
+ rettv->vval.v_number = -1;
+ } else {
+ char buf[NUMBUFLEN];
+ rettv->vval.v_number = vim_rename(tv_get_string(&argvars[0]),
+ tv_get_string_buf(&argvars[1], buf));
+ }
+}
+
+/// "resolve()" function
+void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ const char *fname = tv_get_string(&argvars[0]);
+#ifdef MSWIN
+ char *v = os_resolve_shortcut(fname);
+ if (v == NULL) {
+ if (os_is_reparse_point_include(fname)) {
+ v = os_realpath(fname, NULL, MAXPATHL + 1);
+ }
+ }
+ rettv->vval.v_string = (v == NULL ? xstrdup(fname) : v);
+#else
+# ifdef HAVE_READLINK
+ {
+ bool is_relative_to_current = false;
+ bool has_trailing_pathsep = false;
+ int limit = 100;
+
+ char *p = xstrdup(fname);
+
+ if (p[0] == '.' && (vim_ispathsep(p[1])
+ || (p[1] == '.' && (vim_ispathsep(p[2]))))) {
+ is_relative_to_current = true;
+ }
+
+ ptrdiff_t len = (ptrdiff_t)strlen(p);
+ if (len > 1 && after_pathsep(p, p + len)) {
+ has_trailing_pathsep = true;
+ p[len - 1] = NUL; // The trailing slash breaks readlink().
+ }
+
+ char *q = (char *)path_next_component(p);
+ char *remain = NULL;
+ if (*q != NUL) {
+ // Separate the first path component in "p", and keep the
+ // remainder (beginning with the path separator).
+ remain = xstrdup(q - 1);
+ q[-1] = NUL;
+ }
+
+ char *const buf = xmallocz(MAXPATHL);
+
+ char *cpy;
+ while (true) {
+ while (true) {
+ len = readlink(p, buf, MAXPATHL);
+ if (len <= 0) {
+ break;
+ }
+ buf[len] = NUL;
+
+ if (limit-- == 0) {
+ xfree(p);
+ xfree(remain);
+ emsg(_("E655: Too many symbolic links (cycle?)"));
+ rettv->vval.v_string = NULL;
+ xfree(buf);
+ return;
+ }
+
+ // Ensure that the result will have a trailing path separator
+ // if the argument has one.
+ if (remain == NULL && has_trailing_pathsep) {
+ add_pathsep(buf);
+ }
+
+ // Separate the first path component in the link value and
+ // concatenate the remainders.
+ q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf);
+ if (*q != NUL) {
+ cpy = remain;
+ remain = remain != NULL ? concat_str(q - 1, remain) : xstrdup(q - 1);
+ xfree(cpy);
+ q[-1] = NUL;
+ }
+
+ q = path_tail(p);
+ if (q > p && *q == NUL) {
+ // Ignore trailing path separator.
+ p[q - p - 1] = NUL;
+ q = path_tail(p);
+ }
+ if (q > p && !path_is_absolute(buf)) {
+ // Symlink is relative to directory of argument. Replace the
+ // symlink with the resolved name in the same directory.
+ const size_t p_len = strlen(p);
+ const size_t buf_len = strlen(buf);
+ p = xrealloc(p, p_len + buf_len + 1);
+ memcpy(path_tail(p), buf, buf_len + 1);
+ } else {
+ xfree(p);
+ p = xstrdup(buf);
+ }
+ }
+
+ if (remain == NULL) {
+ break;
+ }
+
+ // Append the first path component of "remain" to "p".
+ q = (char *)path_next_component(remain + 1);
+ len = q - remain - (*q != NUL);
+ const size_t p_len = strlen(p);
+ cpy = xmallocz(p_len + (size_t)len);
+ memcpy(cpy, p, p_len + 1);
+ xstrlcat(cpy + p_len, remain, (size_t)len + 1);
+ xfree(p);
+ p = cpy;
+
+ // Shorten "remain".
+ if (*q != NUL) {
+ STRMOVE(remain, q - 1);
+ } else {
+ XFREE_CLEAR(remain);
+ }
+ }
+
+ // If the result is a relative path name, make it explicitly relative to
+ // the current directory if and only if the argument had this form.
+ if (!vim_ispathsep(*p)) {
+ if (is_relative_to_current
+ && *p != NUL
+ && !(p[0] == '.'
+ && (p[1] == NUL
+ || vim_ispathsep(p[1])
+ || (p[1] == '.'
+ && (p[2] == NUL
+ || vim_ispathsep(p[2])))))) {
+ // Prepend "./".
+ cpy = concat_str("./", p);
+ xfree(p);
+ p = cpy;
+ } else if (!is_relative_to_current) {
+ // Strip leading "./".
+ q = p;
+ while (q[0] == '.' && vim_ispathsep(q[1])) {
+ q += 2;
+ }
+ if (q > p) {
+ STRMOVE(p, p + 2);
+ }
+ }
+ }
+
+ // Ensure that the result will have no trailing path separator
+ // if the argument had none. But keep "/" or "//".
+ if (!has_trailing_pathsep) {
+ q = p + strlen(p);
+ if (after_pathsep(p, q)) {
+ *path_tail_with_sep(p) = NUL;
+ }
+ }
+
+ rettv->vval.v_string = p;
+ xfree(buf);
+ }
+# else
+ char *v = os_realpath(fname, NULL, MAXPATHL + 1);
+ rettv->vval.v_string = v == NULL ? xstrdup(fname) : v;
+# endif
+#endif
+
+ simplify_filename(rettv->vval.v_string);
+}
+
+/// "simplify()" function
+void f_simplify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ const char *const p = tv_get_string(&argvars[0]);
+ rettv->vval.v_string = xstrdup(p);
+ simplify_filename(rettv->vval.v_string); // Simplify in place.
+ rettv->v_type = VAR_STRING;
+}
+
+/// "tempname()" function
+void f_tempname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = vim_tempname();
+}
+
+/// Write "list" of strings to file "fd".
+///
+/// @param fp File to write to.
+/// @param[in] list List to write.
+/// @param[in] binary Whether to write in binary mode.
+///
+/// @return true in case of success, false otherwise.
+static bool write_list(FileDescriptor *const fp, const list_T *const list, const bool binary)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ int error = 0;
+ TV_LIST_ITER_CONST(list, li, {
+ const char *const s = tv_get_string_chk(TV_LIST_ITEM_TV(li));
+ if (s == NULL) {
+ return false;
+ }
+ const char *hunk_start = s;
+ for (const char *p = hunk_start;; p++) {
+ if (*p == NUL || *p == NL) {
+ if (p != hunk_start) {
+ const ptrdiff_t written = file_write(fp, hunk_start,
+ (size_t)(p - hunk_start));
+ if (written < 0) {
+ error = (int)written;
+ goto write_list_error;
+ }
+ }
+ if (*p == NUL) {
+ break;
+ } else {
+ hunk_start = p + 1;
+ const ptrdiff_t written = file_write(fp, (char[]){ NUL }, 1);
+ if (written < 0) {
+ error = (int)written;
+ break;
+ }
+ }
+ }
+ }
+ if (!binary || TV_LIST_ITEM_NEXT(list, li) != NULL) {
+ const ptrdiff_t written = file_write(fp, "\n", 1);
+ if (written < 0) {
+ error = (int)written;
+ goto write_list_error;
+ }
+ }
+ });
+ if ((error = file_flush(fp)) != 0) {
+ goto write_list_error;
+ }
+ return true;
+write_list_error:
+ semsg(_(e_error_while_writing_str), os_strerror(error));
+ return false;
+}
+
+/// Write a blob to file with descriptor `fp`.
+///
+/// @param[in] fp File to write to.
+/// @param[in] blob Blob to write.
+///
+/// @return true on success, or false on failure.
+static bool write_blob(FileDescriptor *const fp, const blob_T *const blob)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ int error = 0;
+ const int len = tv_blob_len(blob);
+ if (len > 0) {
+ const ptrdiff_t written = file_write(fp, blob->bv_ga.ga_data, (size_t)len);
+ if (written < (ptrdiff_t)len) {
+ error = (int)written;
+ goto write_blob_error;
+ }
+ }
+ error = file_flush(fp);
+ if (error != 0) {
+ goto write_blob_error;
+ }
+ return true;
+write_blob_error:
+ semsg(_(e_error_while_writing_str), os_strerror(error));
+ return false;
+}
+
+/// "writefile()" function
+void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ rettv->vval.v_number = -1;
+
+ if (check_secure()) {
+ return;
+ }
+
+ if (argvars[0].v_type == VAR_LIST) {
+ TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
+ if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) {
+ return;
+ }
+ });
+ } else if (argvars[0].v_type != VAR_BLOB) {
+ semsg(_(e_invarg2),
+ _("writefile() first argument must be a List or a Blob"));
+ return;
+ }
+
+ bool binary = false;
+ bool append = false;
+ bool defer = false;
+ bool do_fsync = !!p_fs;
+ bool mkdir_p = false;
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ const char *const flags = tv_get_string_chk(&argvars[2]);
+ if (flags == NULL) {
+ return;
+ }
+ for (const char *p = flags; *p; p++) {
+ switch (*p) {
+ case 'b':
+ binary = true; break;
+ case 'a':
+ append = true; break;
+ case 'D':
+ defer = true; break;
+ case 's':
+ do_fsync = true; break;
+ case 'S':
+ do_fsync = false; break;
+ case 'p':
+ mkdir_p = true; break;
+ default:
+ // Using %s, p and not %c, *p to preserve multibyte characters
+ semsg(_("E5060: Unknown flag: %s"), p);
+ return;
+ }
+ }
+ }
+
+ char buf[NUMBUFLEN];
+ const char *const fname = tv_get_string_buf_chk(&argvars[1], buf);
+ if (fname == NULL) {
+ return;
+ }
+
+ if (defer && !can_add_defer()) {
+ return;
+ }
+
+ FileDescriptor fp;
+ int error;
+ if (*fname == NUL) {
+ emsg(_("E482: Can't open file with an empty name"));
+ } else if ((error = file_open(&fp, fname,
+ ((append ? kFileAppend : kFileTruncate)
+ | (mkdir_p ? kFileMkDir : kFileCreate)
+ | kFileCreate), 0666)) != 0) {
+ semsg(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error));
+ } else {
+ if (defer) {
+ typval_T tv = {
+ .v_type = VAR_STRING,
+ .v_lock = VAR_UNLOCKED,
+ .vval.v_string = FullName_save(fname, false),
+ };
+ add_defer("delete", 1, &tv);
+ }
+
+ bool write_ok;
+ if (argvars[0].v_type == VAR_BLOB) {
+ write_ok = write_blob(&fp, argvars[0].vval.v_blob);
+ } else {
+ write_ok = write_list(&fp, argvars[0].vval.v_list, binary);
+ }
+ if (write_ok) {
+ rettv->vval.v_number = 0;
+ }
+ if ((error = file_close(&fp, do_fsync)) != 0) {
+ semsg(_("E80: Error when closing file %s: %s"),
+ fname, os_strerror(error));
+ }
+ }
+}
diff --git a/src/nvim/eval/fs.h b/src/nvim/eval/fs.h
new file mode 100644
index 0000000000..ae6a93d0dc
--- /dev/null
+++ b/src/nvim/eval/fs.h
@@ -0,0 +1,8 @@
+#pragma once
+
+#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
+#include "nvim/types_defs.h" // IWYU pragma: keep
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval/fs.h.generated.h"
+#endif
diff --git a/src/nvim/eval/funcs.c b/src/nvim/eval/funcs.c
index e3afc1cf54..6d1cb4b2c3 100644
--- a/src/nvim/eval/funcs.c
+++ b/src/nvim/eval/funcs.c
@@ -1,23 +1,19 @@
#include <assert.h>
-#include <fcntl.h>
#include <float.h>
#include <inttypes.h>
#include <limits.h>
#include <math.h>
-#include <msgpack/object.h>
-#include <msgpack/pack.h>
-#include <msgpack/unpack.h>
#include <signal.h>
#include <stdarg.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <sys/stat.h>
#include <time.h>
#include <uv.h>
#include "auto/config.h"
+#include "mpack/object.h"
#include "nvim/api/private/converter.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
@@ -38,6 +34,7 @@
#include "nvim/cursor.h"
#include "nvim/diff.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/buffer.h"
#include "nvim/eval/decode.h"
@@ -52,15 +49,13 @@
#include "nvim/event/defs.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
#include "nvim/event/time.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
-#include "nvim/file_search.h"
-#include "nvim/fileio.h"
#include "nvim/garray.h"
#include "nvim/garray_defs.h"
#include "nvim/getchar.h"
@@ -93,6 +88,7 @@
#include "nvim/move.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/msgpack_rpc/channel_defs.h"
+#include "nvim/msgpack_rpc/packer.h"
#include "nvim/msgpack_rpc/server.h"
#include "nvim/normal.h"
#include "nvim/normal_defs.h"
@@ -102,13 +98,10 @@
#include "nvim/option_vars.h"
#include "nvim/optionstr.h"
#include "nvim/os/dl.h"
-#include "nvim/os/fileio.h"
-#include "nvim/os/fileio_defs.h"
#include "nvim/os/fs.h"
-#include "nvim/os/fs_defs.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
-#include "nvim/os/pty_process.h"
+#include "nvim/os/pty_proc.h"
#include "nvim/os/shell.h"
#include "nvim/os/stdpaths_defs.h"
#include "nvim/os/time.h"
@@ -132,6 +125,7 @@
#include "nvim/tag.h"
#include "nvim/types_defs.h"
#include "nvim/ui.h"
+#include "nvim/ui_compositor.h"
#include "nvim/version.h"
#include "nvim/vim_defs.h"
#include "nvim/window.h"
@@ -772,41 +766,6 @@ static void f_charcol(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
get_col(argvars, rettv, true);
}
-/// "chdir(dir)" function
-static void f_chdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- if (argvars[0].v_type != VAR_STRING) {
- // Returning an empty string means it failed.
- // No error message, for historic reasons.
- return;
- }
-
- // Return the current directory
- char *cwd = xmalloc(MAXPATHL);
- if (os_dirname(cwd, MAXPATHL) != FAIL) {
-#ifdef BACKSLASH_IN_FILENAME
- slash_adjust(cwd);
-#endif
- rettv->vval.v_string = xstrdup(cwd);
- }
- xfree(cwd);
-
- CdScope scope = kCdScopeGlobal;
- if (curwin->w_localdir != NULL) {
- scope = kCdScopeWindow;
- } else if (curtab->tp_localdir != NULL) {
- scope = kCdScopeTabpage;
- }
-
- if (!changedir_func(argvars[0].vval.v_string, scope)) {
- // Directory change failed
- XFREE_CLEAR(rettv->vval.v_string);
- }
-}
-
/// "cindent(lnum)" function
static void f_cindent(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -951,7 +910,7 @@ static varnumber_T count_list(list_T *l, typval_T *needle, int64_t idx, bool ic)
varnumber_T n = 0;
for (; li != NULL; li = TV_LIST_ITEM_NEXT(l, li)) {
- if (tv_equal(TV_LIST_ITEM_TV(li), needle, ic, false)) {
+ if (tv_equal(TV_LIST_ITEM_TV(li), needle, ic)) {
n++;
}
}
@@ -971,7 +930,7 @@ static varnumber_T count_dict(dict_T *d, typval_T *needle, bool ic)
varnumber_T n = 0;
TV_DICT_ITER(d, di, {
- if (tv_equal(&di->di_tv, needle, ic, false)) {
+ if (tv_equal(&di->di_tv, needle, ic)) {
n++;
}
});
@@ -1036,9 +995,9 @@ static void f_ctxget(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
Arena arena = ARENA_EMPTY;
- Dictionary ctx_dict = ctx_to_dict(ctx, &arena);
+ Dict ctx_dict = ctx_to_dict(ctx, &arena);
Error err = ERROR_INIT;
- object_to_vim(DICTIONARY_OBJ(ctx_dict), rettv, &err);
+ object_to_vim(DICT_OBJ(ctx_dict), rettv, &err);
arena_mem_free(arena_finish(&arena));
api_clear_error(&err);
}
@@ -1108,7 +1067,7 @@ static void f_ctxset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
did_emsg = false;
Arena arena = ARENA_EMPTY;
- Dictionary dict = vim_to_object(&argvars[0], &arena, true).data.dictionary;
+ Dict dict = vim_to_object(&argvars[0], &arena, true).data.dict;
Context tmp = CONTEXT_INIT;
Error err = ERROR_INIT;
ctx_from_dict(dict, &tmp, &err);
@@ -1250,42 +1209,6 @@ static void f_deepcopy(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
var_item_copy(NULL, &argvars[0], rettv, true, (noref == 0 ? get_copyID() : 0));
}
-/// "delete()" function
-static void f_delete(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = -1;
- if (check_secure()) {
- return;
- }
-
- const char *const name = tv_get_string(&argvars[0]);
- if (*name == NUL) {
- emsg(_(e_invarg));
- return;
- }
-
- char nbuf[NUMBUFLEN];
- const char *flags;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- flags = tv_get_string_buf(&argvars[1], nbuf);
- } else {
- flags = "";
- }
-
- if (*flags == NUL) {
- // delete a file
- rettv->vval.v_number = os_remove(name) == 0 ? 0 : -1;
- } else if (strcmp(flags, "d") == 0) {
- // delete an empty directory
- rettv->vval.v_number = os_rmdir(name) == 0 ? 0 : -1;
- } else if (strcmp(flags, "rf") == 0) {
- // delete a directory recursively
- rettv->vval.v_number = delete_recursive(name);
- } else {
- semsg(_(e_invexpr2), flags);
- }
-}
-
/// dictwatcheradd(dict, key, funcref) function
static void f_dictwatcheradd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -1568,17 +1491,6 @@ static void f_eventhandler(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
rettv->vval.v_number = vgetc_busy;
}
-/// "executable()" function
-static void f_executable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- if (tv_check_for_string_arg(argvars, 0) == FAIL) {
- return;
- }
-
- // Check in $PATH and also check directly if there is a directory name
- rettv->vval.v_number = os_can_exe(tv_get_string(&argvars[0]), NULL, true);
-}
-
typedef struct {
const list_T *const l;
const listitem_T *li;
@@ -1682,27 +1594,6 @@ static void f_execute(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
execute_common(argvars, rettv, 0);
}
-/// "exepath()" function
-static void f_exepath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- if (tv_check_for_nonempty_string_arg(argvars, 0) == FAIL) {
- return;
- }
-
- char *path = NULL;
-
- os_can_exe(tv_get_string(&argvars[0]), &path, true);
-
-#ifdef BACKSLASH_IN_FILENAME
- if (path != NULL) {
- slash_adjust(path);
- }
-#endif
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = path;
-}
-
/// "exists()" function
static void f_exists(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -2104,100 +1995,6 @@ static void f_feedkeys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
cstr_as_string(flags), true);
}
-/// "filereadable()" function
-static void f_filereadable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *const p = tv_get_string(&argvars[0]);
- rettv->vval.v_number =
- (*p && !os_isdir(p) && os_file_is_readable(p));
-}
-
-/// @return 0 for not writable
-/// 1 for writable file
-/// 2 for a dir which we have rights to write into.
-static void f_filewritable(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *filename = tv_get_string(&argvars[0]);
- rettv->vval.v_number = os_file_is_writable(filename);
-}
-
-static void findfilendir(typval_T *argvars, typval_T *rettv, int find_what)
-{
- char *fresult = NULL;
- char *path = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path;
- int count = 1;
- bool first = true;
- bool error = false;
-
- rettv->vval.v_string = NULL;
- rettv->v_type = VAR_STRING;
-
- const char *fname = tv_get_string(&argvars[0]);
-
- char pathbuf[NUMBUFLEN];
- if (argvars[1].v_type != VAR_UNKNOWN) {
- const char *p = tv_get_string_buf_chk(&argvars[1], pathbuf);
- if (p == NULL) {
- error = true;
- } else {
- if (*p != NUL) {
- path = (char *)p;
- }
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- count = (int)tv_get_number_chk(&argvars[2], &error);
- }
- }
- }
-
- if (count < 0) {
- tv_list_alloc_ret(rettv, kListLenUnknown);
- }
-
- if (*fname != NUL && !error) {
- char *file_to_find = NULL;
- char *search_ctx = NULL;
-
- do {
- if (rettv->v_type == VAR_STRING || rettv->v_type == VAR_LIST) {
- xfree(fresult);
- }
- fresult = find_file_in_path_option(first ? (char *)fname : NULL,
- first ? strlen(fname) : 0,
- 0, first, path,
- find_what, curbuf->b_ffname,
- (find_what == FINDFILE_DIR
- ? ""
- : curbuf->b_p_sua),
- &file_to_find, &search_ctx);
- first = false;
-
- if (fresult != NULL && rettv->v_type == VAR_LIST) {
- tv_list_append_string(rettv->vval.v_list, fresult, -1);
- }
- } while ((rettv->v_type == VAR_LIST || --count > 0) && fresult != NULL);
-
- xfree(file_to_find);
- vim_findfile_cleanup(search_ctx);
- }
-
- if (rettv->v_type == VAR_STRING) {
- rettv->vval.v_string = fresult;
- }
-}
-
-/// "finddir({fname}[, {path}[, {count}]])" function
-static void f_finddir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- findfilendir(argvars, rettv, FINDFILE_DIR);
-}
-
-/// "findfile({fname}[, {path}[, {count}]])" function
-static void f_findfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- findfilendir(argvars, rettv, FINDFILE_FILE);
-}
-
/// "float2nr({float})" function
static void f_float2nr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -2270,6 +2067,164 @@ static void f_foreground(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
}
+/// "function()" function
+/// "funcref()" function
+static void common_function(typval_T *argvars, typval_T *rettv, bool is_funcref)
+{
+ char *s;
+ char *name;
+ bool use_string = false;
+ partial_T *arg_pt = NULL;
+ char *trans_name = NULL;
+
+ if (argvars[0].v_type == VAR_FUNC) {
+ // function(MyFunc, [arg], dict)
+ s = argvars[0].vval.v_string;
+ } else if (argvars[0].v_type == VAR_PARTIAL
+ && argvars[0].vval.v_partial != NULL) {
+ // function(dict.MyFunc, [arg])
+ arg_pt = argvars[0].vval.v_partial;
+ s = partial_name(arg_pt);
+ // TODO(bfredl): do the entire nlua_is_table_from_lua dance
+ } else {
+ // function('MyFunc', [arg], dict)
+ s = (char *)tv_get_string(&argvars[0]);
+ use_string = true;
+ }
+
+ if ((use_string && vim_strchr(s, AUTOLOAD_CHAR) == NULL) || is_funcref) {
+ name = s;
+ trans_name = save_function_name(&name, false,
+ TFN_INT | TFN_QUIET | TFN_NO_AUTOLOAD | TFN_NO_DEREF, NULL);
+ if (*name != NUL) {
+ s = NULL;
+ }
+ }
+ if (s == NULL || *s == NUL || (use_string && ascii_isdigit(*s))
+ || (is_funcref && trans_name == NULL)) {
+ semsg(_(e_invarg2), (use_string ? tv_get_string(&argvars[0]) : s));
+ // Don't check an autoload name for existence here.
+ } else if (trans_name != NULL
+ && (is_funcref
+ ? find_func(trans_name) == NULL
+ : !translated_function_exists(trans_name))) {
+ semsg(_("E700: Unknown function: %s"), s);
+ } else {
+ int dict_idx = 0;
+ int arg_idx = 0;
+ list_T *list = NULL;
+ if (strncmp(s, "s:", 2) == 0 || strncmp(s, "<SID>", 5) == 0) {
+ // Expand s: and <SID> into <SNR>nr_, so that the function can
+ // also be called from another script. Using trans_function_name()
+ // would also work, but some plugins depend on the name being
+ // printable text.
+ name = get_scriptlocal_funcname(s);
+ } else {
+ name = xstrdup(s);
+ }
+
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ // function(name, [args], dict)
+ arg_idx = 1;
+ dict_idx = 2;
+ } else if (argvars[1].v_type == VAR_DICT) {
+ // function(name, dict)
+ dict_idx = 1;
+ } else {
+ // function(name, [args])
+ arg_idx = 1;
+ }
+ if (dict_idx > 0) {
+ if (tv_check_for_dict_arg(argvars, dict_idx) == FAIL) {
+ xfree(name);
+ goto theend;
+ }
+ if (argvars[dict_idx].vval.v_dict == NULL) {
+ dict_idx = 0;
+ }
+ }
+ if (arg_idx > 0) {
+ if (argvars[arg_idx].v_type != VAR_LIST) {
+ emsg(_("E923: Second argument of function() must be "
+ "a list or a dict"));
+ xfree(name);
+ goto theend;
+ }
+ list = argvars[arg_idx].vval.v_list;
+ if (tv_list_len(list) == 0) {
+ arg_idx = 0;
+ } else if (tv_list_len(list) > MAX_FUNC_ARGS) {
+ emsg_funcname(e_toomanyarg, s);
+ xfree(name);
+ goto theend;
+ }
+ }
+ }
+ if (dict_idx > 0 || arg_idx > 0 || arg_pt != NULL || is_funcref) {
+ partial_T *const pt = xcalloc(1, sizeof(*pt));
+
+ // result is a VAR_PARTIAL
+ if (arg_idx > 0 || (arg_pt != NULL && arg_pt->pt_argc > 0)) {
+ const int arg_len = (arg_pt == NULL ? 0 : arg_pt->pt_argc);
+ const int lv_len = tv_list_len(list);
+
+ pt->pt_argc = arg_len + lv_len;
+ pt->pt_argv = xmalloc(sizeof(pt->pt_argv[0]) * (size_t)pt->pt_argc);
+ int i = 0;
+ for (; i < arg_len; i++) {
+ tv_copy(&arg_pt->pt_argv[i], &pt->pt_argv[i]);
+ }
+ if (lv_len > 0) {
+ TV_LIST_ITER(list, li, {
+ tv_copy(TV_LIST_ITEM_TV(li), &pt->pt_argv[i++]);
+ });
+ }
+ }
+
+ // For "function(dict.func, [], dict)" and "func" is a partial
+ // use "dict". That is backwards compatible.
+ if (dict_idx > 0) {
+ // The dict is bound explicitly, pt_auto is false
+ pt->pt_dict = argvars[dict_idx].vval.v_dict;
+ (pt->pt_dict->dv_refcount)++;
+ } else if (arg_pt != NULL) {
+ // If the dict was bound automatically the result is also
+ // bound automatically.
+ pt->pt_dict = arg_pt->pt_dict;
+ pt->pt_auto = arg_pt->pt_auto;
+ if (pt->pt_dict != NULL) {
+ (pt->pt_dict->dv_refcount)++;
+ }
+ }
+
+ pt->pt_refcount = 1;
+ if (arg_pt != NULL && arg_pt->pt_func != NULL) {
+ pt->pt_func = arg_pt->pt_func;
+ func_ptr_ref(pt->pt_func);
+ xfree(name);
+ } else if (is_funcref) {
+ pt->pt_func = find_func(trans_name);
+ func_ptr_ref(pt->pt_func);
+ xfree(name);
+ } else {
+ pt->pt_name = name;
+ func_ref(name);
+ }
+
+ rettv->v_type = VAR_PARTIAL;
+ rettv->vval.v_partial = pt;
+ } else {
+ // result is a VAR_FUNC
+ rettv->v_type = VAR_FUNC;
+ rettv->vval.v_string = name;
+ func_ref(name);
+ }
+ }
+theend:
+ xfree(trans_name);
+}
+
static void f_funcref(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
common_function(argvars, rettv, true);
@@ -2370,6 +2325,33 @@ static void f_get(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
for (int i = 0; i < pt->pt_argc; i++) {
tv_list_append_tv(rettv->vval.v_list, &pt->pt_argv[i]);
}
+ } else if (strcmp(what, "arity") == 0) {
+ int required = 0;
+ int optional = 0;
+ bool varargs = false;
+ const char *name = partial_name(pt);
+
+ get_func_arity(name, &required, &optional, &varargs);
+
+ rettv->v_type = VAR_DICT;
+ tv_dict_alloc_ret(rettv);
+ dict_T *dict = rettv->vval.v_dict;
+
+ // Take into account the arguments of the partial, if any.
+ // Note that it is possible to supply more arguments than the function
+ // accepts.
+ if (pt->pt_argc >= required + optional) {
+ required = optional = 0;
+ } else if (pt->pt_argc > required) {
+ optional -= pt->pt_argc - required;
+ required = 0;
+ } else {
+ required -= pt->pt_argc;
+ }
+
+ tv_dict_add_nr(dict, S_LEN("required"), required);
+ tv_dict_add_nr(dict, S_LEN("optional"), optional);
+ tv_dict_add_bool(dict, S_LEN("varargs"), varargs);
} else {
semsg(_(e_invarg2), what);
}
@@ -2516,127 +2498,6 @@ static void f_getcharsearch(typval_T *argvars, typval_T *rettv, EvalFuncData fpt
tv_dict_add_nr(dict, S_LEN("until"), last_csearch_until());
}
-/// `getcwd([{win}[, {tab}]])` function
-///
-/// Every scope not specified implies the currently selected scope object.
-///
-/// @pre The arguments must be of type number.
-/// @pre There may not be more than two arguments.
-/// @pre An argument may not be -1 if preceding arguments are not all -1.
-///
-/// @post The return value will be a string.
-static void f_getcwd(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- // Possible scope of working directory to return.
- CdScope scope = kCdScopeInvalid;
-
- // Numbers of the scope objects (window, tab) we want the working directory
- // of. A `-1` means to skip this scope, a `0` means the current object.
- int scope_number[] = {
- [kCdScopeWindow] = 0, // Number of window to look at.
- [kCdScopeTabpage] = 0, // Number of tab to look at.
- };
-
- char *cwd = NULL; // Current working directory to print
- char *from = NULL; // The original string to copy
-
- tabpage_T *tp = curtab; // The tabpage to look at.
- win_T *win = curwin; // The window to look at.
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = NULL;
-
- // Pre-conditions and scope extraction together
- for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
- // If there is no argument there are no more scopes after it, break out.
- if (argvars[i].v_type == VAR_UNKNOWN) {
- break;
- }
- if (argvars[i].v_type != VAR_NUMBER) {
- emsg(_(e_invarg));
- return;
- }
- scope_number[i] = (int)argvars[i].vval.v_number;
- // It is an error for the scope number to be less than `-1`.
- if (scope_number[i] < -1) {
- emsg(_(e_invarg));
- return;
- }
- // Use the narrowest scope the user requested
- if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
- // The scope is the current iteration step.
- scope = i;
- } else if (scope_number[i] < 0) {
- scope = i + 1;
- }
- }
-
- // Find the tabpage by number
- if (scope_number[kCdScopeTabpage] > 0) {
- tp = find_tabpage(scope_number[kCdScopeTabpage]);
- if (!tp) {
- emsg(_("E5000: Cannot find tab number."));
- return;
- }
- }
-
- // Find the window in `tp` by number, `NULL` if none.
- if (scope_number[kCdScopeWindow] >= 0) {
- if (scope_number[kCdScopeTabpage] < 0) {
- emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
- return;
- }
-
- if (scope_number[kCdScopeWindow] > 0) {
- win = find_win_by_nr(&argvars[0], tp);
- if (!win) {
- emsg(_("E5002: Cannot find window number."));
- return;
- }
- }
- }
-
- cwd = xmalloc(MAXPATHL);
-
- switch (scope) {
- case kCdScopeWindow:
- assert(win);
- from = win->w_localdir;
- if (from) {
- break;
- }
- FALLTHROUGH;
- case kCdScopeTabpage:
- assert(tp);
- from = tp->tp_localdir;
- if (from) {
- break;
- }
- FALLTHROUGH;
- case kCdScopeGlobal:
- if (globaldir) { // `globaldir` is not always set.
- from = globaldir;
- break;
- }
- FALLTHROUGH; // In global directory, just need to get OS CWD.
- case kCdScopeInvalid: // If called without any arguments, get OS CWD.
- if (os_dirname(cwd, MAXPATHL) == FAIL) {
- from = ""; // Return empty string on failure.
- }
- }
-
- if (from) {
- xstrlcpy(cwd, from, MAXPATHL);
- }
-
- rettv->vval.v_string = xstrdup(cwd);
-#ifdef BACKSLASH_IN_FILENAME
- slash_adjust(rettv->vval.v_string);
-#endif
-
- xfree(cwd);
-}
-
/// "getfontname()" function
static void f_getfontname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -2644,98 +2505,6 @@ static void f_getfontname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_string = NULL;
}
-/// "getfperm({fname})" function
-static void f_getfperm(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- char *perm = NULL;
- char flags[] = "rwx";
-
- const char *filename = tv_get_string(&argvars[0]);
- int32_t file_perm = os_getperm(filename);
- if (file_perm >= 0) {
- perm = xstrdup("---------");
- for (int i = 0; i < 9; i++) {
- if (file_perm & (1 << (8 - i))) {
- perm[i] = flags[i % 3];
- }
- }
- }
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = perm;
-}
-
-/// "getfsize({fname})" function
-static void f_getfsize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *fname = tv_get_string(&argvars[0]);
-
- rettv->v_type = VAR_NUMBER;
-
- FileInfo file_info;
- if (os_fileinfo(fname, &file_info)) {
- uint64_t filesize = os_fileinfo_size(&file_info);
- if (os_isdir(fname)) {
- rettv->vval.v_number = 0;
- } else {
- rettv->vval.v_number = (varnumber_T)filesize;
-
- // non-perfect check for overflow
- if ((uint64_t)rettv->vval.v_number != filesize) {
- rettv->vval.v_number = -2;
- }
- }
- } else {
- rettv->vval.v_number = -1;
- }
-}
-
-/// "getftime({fname})" function
-static void f_getftime(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *fname = tv_get_string(&argvars[0]);
-
- FileInfo file_info;
- if (os_fileinfo(fname, &file_info)) {
- rettv->vval.v_number = (varnumber_T)file_info.stat.st_mtim.tv_sec;
- } else {
- rettv->vval.v_number = -1;
- }
-}
-
-/// "getftype({fname})" function
-static void f_getftype(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- char *type = NULL;
- char *t;
-
- const char *fname = tv_get_string(&argvars[0]);
-
- rettv->v_type = VAR_STRING;
- FileInfo file_info;
- if (os_fileinfo_link(fname, &file_info)) {
- uint64_t mode = file_info.stat.st_mode;
- if (S_ISREG(mode)) {
- t = "file";
- } else if (S_ISDIR(mode)) {
- t = "dir";
- } else if (S_ISLNK(mode)) {
- t = "link";
- } else if (S_ISBLK(mode)) {
- t = "bdev";
- } else if (S_ISCHR(mode)) {
- t = "cdev";
- } else if (S_ISFIFO(mode)) {
- t = "fifo";
- } else if (S_ISSOCK(mode)) {
- t = "socket";
- } else {
- t = "other";
- }
- type = xstrdup(t);
- }
- rettv->vval.v_string = type;
-}
-
/// "getjumplist()" function
static void f_getjumplist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -2824,7 +2593,7 @@ static char *block_def2str(struct block_def *bd)
}
static int getregionpos(typval_T *argvars, typval_T *rettv, pos_T *p1, pos_T *p2,
- bool *const inclusive, MotionType *region_type, oparg_T *oa)
+ bool *const inclusive, MotionType *region_type, oparg_T *oap)
FUNC_ATTR_NONNULL_ALL
{
tv_list_alloc_ret(rettv, kListLenMayKnow);
@@ -2858,11 +2627,17 @@ static int getregionpos(typval_T *argvars, typval_T *rettv, pos_T *p1, pos_T *p2
type = default_type;
}
+ int block_width = 0;
if (type[0] == 'v' && type[1] == NUL) {
*region_type = kMTCharWise;
} else if (type[0] == 'V' && type[1] == NUL) {
*region_type = kMTLineWise;
- } else if (type[0] == Ctrl_V && type[1] == NUL) {
+ } else if (type[0] == Ctrl_V) {
+ char *p = type + 1;
+ if (*p != NUL && ((block_width = getdigits_int(&p, false, 0)) <= 0 || *p != NUL)) {
+ semsg(_(e_invargNval), "type", type);
+ return FAIL;
+ }
*region_type = kMTBlockWise;
} else {
semsg(_(e_invargNval), "type", type);
@@ -2926,16 +2701,18 @@ static int getregionpos(typval_T *argvars, typval_T *rettv, pos_T *p1, pos_T *p2
colnr_T sc1, ec1, sc2, ec2;
getvvcol(curwin, p1, &sc1, NULL, &ec1);
getvvcol(curwin, p2, &sc2, NULL, &ec2);
- oa->motion_type = kMTBlockWise;
- oa->inclusive = true;
- oa->op_type = OP_NOP;
- oa->start = *p1;
- oa->end = *p2;
- oa->start_vcol = MIN(sc1, sc2);
- if (is_select_exclusive && ec1 < sc2 && 0 < sc2 && ec2 > ec1) {
- oa->end_vcol = sc2 - 1;
+ oap->motion_type = kMTBlockWise;
+ oap->inclusive = true;
+ oap->op_type = OP_NOP;
+ oap->start = *p1;
+ oap->end = *p2;
+ oap->start_vcol = MIN(sc1, sc2);
+ if (block_width > 0) {
+ oap->end_vcol = oap->start_vcol + block_width - 1;
+ } else if (is_select_exclusive && ec1 < sc2 && 0 < sc2 && ec2 > ec1) {
+ oap->end_vcol = sc2 - 1;
} else {
- oa->end_vcol = MAX(ec1, ec2);
+ oap->end_vcol = MAX(ec1, ec2);
}
}
@@ -3034,6 +2811,7 @@ static void f_getregionpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
for (linenr_T lnum = p1.lnum; lnum <= p2.lnum; lnum++) {
pos_T ret_p1, ret_p2;
+ char *line = ml_get(lnum);
colnr_T line_len = ml_get_len(lnum);
if (region_type == kMTLineWise) {
@@ -3052,7 +2830,7 @@ static void f_getregionpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
if (bd.is_oneChar) { // selection entirely inside one char
if (region_type == kMTBlockWise) {
- ret_p1.col = bd.textcol;
+ ret_p1.col = (colnr_T)(mb_prevptr(line, bd.textstart) - line) + 1;
ret_p1.coladd = bd.start_char_vcols - (bd.start_vcol - oa.start_vcol);
} else {
ret_p1.col = p1.col + 1;
@@ -3064,7 +2842,7 @@ static void f_getregionpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr
ret_p1.coladd = oa.start_vcol - bd.start_vcol;
bd.is_oneChar = true;
} else if (bd.startspaces > 0) {
- ret_p1.col = bd.textcol;
+ ret_p1.col = (colnr_T)(mb_prevptr(line, bd.textstart) - line) + 1;
ret_p1.coladd = bd.start_char_vcols - bd.startspaces;
} else {
ret_p1.col = bd.textcol + 1;
@@ -3269,113 +3047,6 @@ static void f_wait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
time_watcher_close(tw, dummy_timer_close_cb);
}
-/// "glob()" function
-static void f_glob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- int options = WILD_SILENT|WILD_USE_NL;
- expand_T xpc;
- bool error = false;
-
- // When the optional second argument is non-zero, don't remove matches
- // for 'wildignore' and don't put matches for 'suffixes' at the end.
- rettv->v_type = VAR_STRING;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (tv_get_number_chk(&argvars[1], &error)) {
- options |= WILD_KEEP_ALL;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- if (tv_get_number_chk(&argvars[2], &error)) {
- tv_list_set_ret(rettv, NULL);
- }
- if (argvars[3].v_type != VAR_UNKNOWN
- && tv_get_number_chk(&argvars[3], &error)) {
- options |= WILD_ALLLINKS;
- }
- }
- }
- if (!error) {
- ExpandInit(&xpc);
- xpc.xp_context = EXPAND_FILES;
- if (p_wic) {
- options += WILD_ICASE;
- }
- if (rettv->v_type == VAR_STRING) {
- rettv->vval.v_string = ExpandOne(&xpc, (char *)
- tv_get_string(&argvars[0]), NULL, options,
- WILD_ALL);
- } else {
- ExpandOne(&xpc, (char *)tv_get_string(&argvars[0]), NULL, options,
- WILD_ALL_KEEP);
- tv_list_alloc_ret(rettv, xpc.xp_numfiles);
- for (int i = 0; i < xpc.xp_numfiles; i++) {
- tv_list_append_string(rettv->vval.v_list, xpc.xp_files[i], -1);
- }
- ExpandCleanup(&xpc);
- }
- } else {
- rettv->vval.v_string = NULL;
- }
-}
-
-/// "globpath()" function
-static void f_globpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- 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.
- rettv->v_type = VAR_STRING;
-
- if (argvars[2].v_type != VAR_UNKNOWN) {
- // When the optional second argument is non-zero, don't remove matches
- // for 'wildignore' and don't put matches for 'suffixes' at the end.
- if (tv_get_number_chk(&argvars[2], &error)) {
- flags |= WILD_KEEP_ALL;
- }
-
- if (argvars[3].v_type != VAR_UNKNOWN) {
- if (tv_get_number_chk(&argvars[3], &error)) {
- tv_list_set_ret(rettv, NULL);
- }
- if (argvars[4].v_type != VAR_UNKNOWN
- && tv_get_number_chk(&argvars[4], &error)) {
- flags |= WILD_ALLLINKS;
- }
- }
- }
-
- char buf1[NUMBUFLEN];
- const char *const file = tv_get_string_buf_chk(&argvars[1], buf1);
- if (file != NULL && !error) {
- garray_T ga;
- ga_init(&ga, (int)sizeof(char *), 10);
- globpath((char *)tv_get_string(&argvars[0]), (char *)file, &ga, flags, false);
-
- if (rettv->v_type == VAR_STRING) {
- rettv->vval.v_string = ga_concat_strings_sep(&ga, "\n");
- } else {
- tv_list_alloc_ret(rettv, ga.ga_len);
- for (int i = 0; i < ga.ga_len; i++) {
- tv_list_append_string(rettv->vval.v_list,
- ((const char **)(ga.ga_data))[i], -1);
- }
- }
-
- ga_clear_strings(&ga);
- } else {
- rettv->vval.v_string = NULL;
- }
-}
-
-/// "glob2regpat()" function
-static void f_glob2regpat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *const pat = tv_get_string_chk(&argvars[0]); // NULL on type error
-
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = (pat == NULL) ? NULL : file_pat_to_reg_pat(pat, NULL, NULL, false);
-}
-
/// "gettext()" function
static void f_gettext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -3596,106 +3267,6 @@ static bool has_wsl(void)
return has_wsl == kTrue;
}
-/// `haslocaldir([{win}[, {tab}]])` function
-///
-/// Returns `1` if the scope object has a local directory, `0` otherwise. If a
-/// scope object is not specified the current one is implied. This function
-/// share a lot of code with `f_getcwd`.
-///
-/// @pre The arguments must be of type number.
-/// @pre There may not be more than two arguments.
-/// @pre An argument may not be -1 if preceding arguments are not all -1.
-///
-/// @post The return value will be either the number `1` or `0`.
-static void f_haslocaldir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- // Possible scope of working directory to return.
- CdScope scope = kCdScopeInvalid;
-
- // Numbers of the scope objects (window, tab) we want the working directory
- // of. A `-1` means to skip this scope, a `0` means the current object.
- int scope_number[] = {
- [kCdScopeWindow] = 0, // Number of window to look at.
- [kCdScopeTabpage] = 0, // Number of tab to look at.
- };
-
- tabpage_T *tp = curtab; // The tabpage to look at.
- win_T *win = curwin; // The window to look at.
-
- rettv->v_type = VAR_NUMBER;
- rettv->vval.v_number = 0;
-
- // Pre-conditions and scope extraction together
- for (int i = MIN_CD_SCOPE; i < MAX_CD_SCOPE; i++) {
- if (argvars[i].v_type == VAR_UNKNOWN) {
- break;
- }
- if (argvars[i].v_type != VAR_NUMBER) {
- emsg(_(e_invarg));
- return;
- }
- scope_number[i] = (int)argvars[i].vval.v_number;
- if (scope_number[i] < -1) {
- emsg(_(e_invarg));
- return;
- }
- // Use the narrowest scope the user requested
- if (scope_number[i] >= 0 && scope == kCdScopeInvalid) {
- // The scope is the current iteration step.
- scope = i;
- } else if (scope_number[i] < 0) {
- scope = i + 1;
- }
- }
-
- // If the user didn't specify anything, default to window scope
- if (scope == kCdScopeInvalid) {
- scope = MIN_CD_SCOPE;
- }
-
- // Find the tabpage by number
- if (scope_number[kCdScopeTabpage] > 0) {
- tp = find_tabpage(scope_number[kCdScopeTabpage]);
- if (!tp) {
- emsg(_("E5000: Cannot find tab number."));
- return;
- }
- }
-
- // Find the window in `tp` by number, `NULL` if none.
- if (scope_number[kCdScopeWindow] >= 0) {
- if (scope_number[kCdScopeTabpage] < 0) {
- emsg(_("E5001: Higher scope cannot be -1 if lower scope is >= 0."));
- return;
- }
-
- if (scope_number[kCdScopeWindow] > 0) {
- win = find_win_by_nr(&argvars[0], tp);
- if (!win) {
- emsg(_("E5002: Cannot find window number."));
- return;
- }
- }
- }
-
- switch (scope) {
- case kCdScopeWindow:
- assert(win);
- rettv->vval.v_number = win->w_localdir ? 1 : 0;
- break;
- case kCdScopeTabpage:
- assert(tp);
- rettv->vval.v_number = tp->tp_localdir ? 1 : 0;
- break;
- case kCdScopeGlobal:
- // The global scope never has a local directory
- break;
- case kCdScopeInvalid:
- // We should never get here
- abort();
- }
-}
-
/// "highlightID(name)" function
static void f_hlID(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -3760,7 +3331,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
typval_T tv;
tv.v_type = VAR_NUMBER;
tv.vval.v_number = tv_blob_get(b, idx);
- if (tv_equal(&tv, &argvars[1], ic, false)) {
+ if (tv_equal(&tv, &argvars[1], ic)) {
rettv->vval.v_number = idx;
return;
}
@@ -3797,7 +3368,7 @@ static void f_index(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) {
- if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic, false)) {
+ if (tv_equal(TV_LIST_ITEM_TV(item), &argvars[1], ic)) {
rettv->vval.v_number = idx;
break;
}
@@ -3843,6 +3414,7 @@ static varnumber_T indexof_blob(blob_T *b, varnumber_T startidx, typval_T *expr)
}
}
+ const int called_emsg_start = called_emsg;
for (varnumber_T idx = startidx; idx < tv_blob_len(b); idx++) {
set_vim_var_nr(VV_KEY, idx);
set_vim_var_nr(VV_VAL, tv_blob_get(b, (int)idx));
@@ -3850,6 +3422,10 @@ static varnumber_T indexof_blob(blob_T *b, varnumber_T startidx, typval_T *expr)
if (indexof_eval_expr(expr)) {
return idx;
}
+
+ if (called_emsg != called_emsg_start) {
+ return -1;
+ }
}
return -1;
@@ -3879,6 +3455,7 @@ static varnumber_T indexof_list(list_T *l, varnumber_T startidx, typval_T *expr)
}
}
+ const int called_emsg_start = called_emsg;
for (; item != NULL; item = TV_LIST_ITEM_NEXT(l, item), idx++) {
set_vim_var_nr(VV_KEY, idx);
tv_copy(TV_LIST_ITEM_TV(item), get_vim_var_tv(VV_VAL));
@@ -3889,6 +3466,10 @@ static varnumber_T indexof_list(list_T *l, varnumber_T startidx, typval_T *expr)
if (found) {
return idx;
}
+
+ if (called_emsg != called_emsg_start) {
+ return -1;
+ }
}
return -1;
@@ -3905,7 +3486,8 @@ static void f_indexof(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- if ((argvars[1].v_type == VAR_STRING && argvars[1].vval.v_string == NULL)
+ if ((argvars[1].v_type == VAR_STRING
+ && (argvars[1].vval.v_string == NULL || *argvars[1].vval.v_string == NUL))
|| (argvars[1].v_type == VAR_FUNC && argvars[1].vval.v_partial == NULL)) {
return;
}
@@ -4101,12 +3683,6 @@ static void f_invert(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = ~tv_get_number_chk(&argvars[0], NULL);
}
-/// "isdirectory()" function
-static void f_isdirectory(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = os_isdir(tv_get_string(&argvars[0]));
-}
-
/// "islocked()" function
static void f_islocked(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -4195,7 +3771,7 @@ static void f_jobpid(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- Process *proc = &data->stream.proc;
+ Proc *proc = &data->stream.proc;
rettv->vval.v_number = proc->pid;
}
@@ -4221,13 +3797,13 @@ static void f_jobresize(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- if (data->stream.proc.type != kProcessTypePty) {
+ if (data->stream.proc.type != kProcTypePty) {
emsg(_(e_channotpty));
return;
}
- pty_process_resize(&data->stream.pty, (uint16_t)argvars[1].vval.v_number,
- (uint16_t)argvars[2].vval.v_number);
+ pty_proc_resize(&data->stream.pty, (uint16_t)argvars[1].vval.v_number,
+ (uint16_t)argvars[2].vval.v_number);
rettv->vval.v_number = 1;
}
@@ -4309,7 +3885,7 @@ static dict_T *create_environment(const dictitem_T *job_env, const bool clear_en
// Set $NVIM (in the child process) to v:servername. #3118
char *nvim_addr = get_vim_var_str(VV_SEND_SERVER);
- if (nvim_addr[0] != '\0') {
+ if (nvim_addr[0] != NUL) {
dictitem_T *dv = tv_dict_find(env, S_LEN("NVIM"));
if (dv) {
tv_dict_item_remove(env, dv);
@@ -4502,7 +4078,7 @@ static void f_jobstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
// Ignore return code, but show error later.
channel_close(data->id, kChannelPartRpc, &error);
}
- process_stop(&data->stream.proc);
+ proc_stop(&data->stream.proc);
rettv->vval.v_number = 1;
if (error) {
emsg(error);
@@ -4538,10 +4114,10 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
|| !(chan = find_channel((uint64_t)TV_LIST_ITEM_TV(arg)->vval.v_number))
|| chan->streamtype != kChannelStreamProc) {
jobs[i] = NULL; // Invalid job.
- } else if (process_is_stopped(&chan->stream.proc)) {
+ } else if (proc_is_stopped(&chan->stream.proc)) {
// Job is stopped but not fully destroyed.
// Ensure all callbacks on its event queue are executed. #15402
- process_wait(&chan->stream.proc, -1, NULL);
+ proc_wait(&chan->stream.proc, -1, NULL);
jobs[i] = NULL; // Invalid job.
} else {
jobs[i] = chan;
@@ -4569,8 +4145,8 @@ static void f_jobwait(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (jobs[i] == NULL) {
continue; // Invalid job, will assign status=-3 below.
}
- int status = process_wait(&jobs[i]->stream.proc, remaining,
- waiting_jobs);
+ int status = proc_wait(&jobs[i]->stream.proc, remaining,
+ waiting_jobs);
if (status < 0) {
break; // Interrupted (CTRL-C) or timeout, skip remaining jobs.
}
@@ -5350,78 +4926,6 @@ static void f_min(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
max_min(argvars, rettv, false);
}
-/// "mkdir()" function
-static void f_mkdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- int prot = 0755;
-
- rettv->vval.v_number = FAIL;
- if (check_secure()) {
- return;
- }
-
- char buf[NUMBUFLEN];
- const char *const dir = tv_get_string_buf(&argvars[0], buf);
- if (*dir == NUL) {
- return;
- }
-
- if (*path_tail(dir) == NUL) {
- // Remove trailing slashes.
- *path_tail_with_sep((char *)dir) = NUL;
- }
-
- bool defer = false;
- bool defer_recurse = false;
- char *created = NULL;
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (argvars[2].v_type != VAR_UNKNOWN) {
- prot = (int)tv_get_number_chk(&argvars[2], NULL);
- if (prot == -1) {
- return;
- }
- }
- const char *arg2 = tv_get_string(&argvars[1]);
- defer = vim_strchr(arg2, 'D') != NULL;
- defer_recurse = vim_strchr(arg2, 'R') != NULL;
- if ((defer || defer_recurse) && !can_add_defer()) {
- return;
- }
-
- if (vim_strchr(arg2, 'p') != NULL) {
- char *failed_dir;
- int ret = os_mkdir_recurse(dir, prot, &failed_dir,
- defer || defer_recurse ? &created : NULL);
- if (ret != 0) {
- semsg(_(e_mkdir), failed_dir, os_strerror(ret));
- xfree(failed_dir);
- rettv->vval.v_number = FAIL;
- return;
- }
- rettv->vval.v_number = OK;
- }
- }
- if (rettv->vval.v_number == FAIL) {
- rettv->vval.v_number = vim_mkdir_emsg(dir, prot);
- }
-
- // Handle "D" and "R": deferred deletion of the created directory.
- if (rettv->vval.v_number == OK
- && created == NULL && (defer || defer_recurse)) {
- created = FullName_save(dir, false);
- }
- if (created != NULL) {
- typval_T tv[2];
- tv[0].v_type = VAR_STRING;
- tv[0].v_lock = VAR_UNLOCKED;
- tv[0].vval.v_string = created;
- tv[1].v_type = VAR_STRING;
- tv[1].v_lock = VAR_UNLOCKED;
- tv[1].vval.v_string = xstrdup(defer_recurse ? "rf" : "d");
- add_defer("delete", 2, tv);
- }
-}
-
/// "mode()" function
static void f_mode(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -5492,15 +4996,7 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
list_T *const list = argvars[0].vval.v_list;
- msgpack_packer *packer;
- if (argvars[1].v_type != VAR_UNKNOWN
- && strequal(tv_get_string(&argvars[1]), "B")) {
- tv_blob_alloc_ret(rettv);
- packer = msgpack_packer_new(rettv->vval.v_blob, &encode_blob_write);
- } else {
- packer = msgpack_packer_new(tv_list_alloc_ret(rettv, kListLenMayKnow),
- &encode_list_write);
- }
+ PackerBuffer packer = packer_string_buffer();
const char *const msg = _("msgpackdump() argument, index %i");
// Assume that translation will not take more then 4 times more space
char msgbuf[sizeof("msgpackdump() argument, index ") * 4 + NUMBUFLEN];
@@ -5508,43 +5004,40 @@ static void f_msgpackdump(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
TV_LIST_ITER(list, li, {
vim_snprintf(msgbuf, sizeof(msgbuf), msg, idx);
idx++;
- if (encode_vim_to_msgpack(packer, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {
+ if (encode_vim_to_msgpack(&packer, TV_LIST_ITEM_TV(li), msgbuf) == FAIL) {
break;
}
});
- msgpack_packer_free(packer);
+ String data = packer_take_string(&packer);
+ if (argvars[1].v_type != VAR_UNKNOWN && strequal(tv_get_string(&argvars[1]), "B")) {
+ blob_T *b = tv_blob_alloc_ret(rettv);
+ b->bv_ga.ga_data = data.data;
+ b->bv_ga.ga_len = (int)data.size;
+ b->bv_ga.ga_maxlen = (int)(packer.endptr - packer.startptr);
+ } else {
+ encode_list_write(tv_list_alloc_ret(rettv, kListLenMayKnow), data.data, data.size);
+ api_free_string(data);
+ }
}
-static int msgpackparse_convert_item(const msgpack_object data, const msgpack_unpack_return result,
- list_T *const ret_list, const bool fail_if_incomplete)
- FUNC_ATTR_NONNULL_ALL
+static void emsg_mpack_error(int status)
{
- switch (result) {
- case MSGPACK_UNPACK_PARSE_ERROR:
+ switch (status) {
+ case MPACK_ERROR:
semsg(_(e_invarg2), "Failed to parse msgpack string");
- return FAIL;
- case MSGPACK_UNPACK_NOMEM_ERROR:
- emsg(_(e_outofmem));
- return FAIL;
- case MSGPACK_UNPACK_CONTINUE:
- if (fail_if_incomplete) {
- semsg(_(e_invarg2), "Incomplete msgpack string");
- return FAIL;
- }
- return NOTDONE;
- case MSGPACK_UNPACK_SUCCESS: {
- typval_T tv = { .v_type = VAR_UNKNOWN };
- if (msgpack_to_vim(data, &tv) == FAIL) {
- semsg(_(e_invarg2), "Failed to convert msgpack string");
- return FAIL;
- }
- tv_list_append_owned_tv(ret_list, tv);
- return OK;
- }
- case MSGPACK_UNPACK_EXTRA_BYTES:
- abort();
+ break;
+
+ case MPACK_EOF:
+ semsg(_(e_invarg2), "Incomplete msgpack string");
+ break;
+
+ case MPACK_NOMEM:
+ semsg(_(e_invarg2), "object was too deep to unpack");
+ break;
+
+ default:
+ break;
}
- UNREACHABLE;
}
static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret_list)
@@ -5558,48 +5051,57 @@ static void msgpackparse_unpack_list(const list_T *const list, list_T *const ret
return;
}
ListReaderState lrstate = encode_init_lrstate(list);
- msgpack_unpacker *const unpacker = msgpack_unpacker_new(IOSIZE);
- if (unpacker == NULL) {
- emsg(_(e_outofmem));
- return;
- }
- msgpack_unpacked unpacked;
- msgpack_unpacked_init(&unpacked);
+ char *buf = alloc_block();
+ size_t buf_size = 0;
+
+ typval_T cur_item = { .v_type = VAR_UNKNOWN };
+ mpack_parser_t parser;
+ mpack_parser_init(&parser, 0);
+ parser.data.p = &cur_item;
+
+ int status = MPACK_OK;
while (true) {
- if (!msgpack_unpacker_reserve_buffer(unpacker, IOSIZE)) {
- emsg(_(e_outofmem));
- goto end;
- }
size_t read_bytes;
- const int rlret = encode_read_from_list(&lrstate, msgpack_unpacker_buffer(unpacker), IOSIZE,
+ const int rlret = encode_read_from_list(&lrstate, buf + buf_size, ARENA_BLOCK_SIZE - buf_size,
&read_bytes);
if (rlret == FAIL) {
semsg(_(e_invarg2), "List item is not a string");
goto end;
}
- msgpack_unpacker_buffer_consumed(unpacker, read_bytes);
- if (read_bytes == 0) {
- break;
- }
- while (unpacker->off < unpacker->used) {
- const msgpack_unpack_return result
- = msgpack_unpacker_next(unpacker, &unpacked);
- const int conv_result = msgpackparse_convert_item(unpacked.data, result,
- ret_list, rlret == OK);
- if (conv_result == NOTDONE) {
+ buf_size += read_bytes;
+
+ const char *ptr = buf;
+ while (buf_size) {
+ status = mpack_parse_typval(&parser, &ptr, &buf_size);
+ if (status == MPACK_OK) {
+ tv_list_append_owned_tv(ret_list, cur_item);
+ cur_item.v_type = VAR_UNKNOWN;
+ } else {
break;
- } else if (conv_result == FAIL) {
- goto end;
}
}
+
if (rlret == OK) {
break;
}
+
+ if (status == MPACK_EOF) {
+ // move remaining data to front of buffer
+ if (buf_size && ptr > buf) {
+ memmove(buf, ptr, buf_size);
+ }
+ } else if (status != MPACK_OK) {
+ break;
+ }
+ }
+
+ if (status != MPACK_OK) {
+ typval_parser_error_free(&parser);
+ emsg_mpack_error(status);
}
end:
- msgpack_unpacker_free(unpacker);
- msgpack_unpacked_destroy(&unpacked);
+ free_block(buf);
}
static void msgpackparse_unpack_blob(const blob_T *const blob, list_T *const ret_list)
@@ -5609,18 +5111,19 @@ static void msgpackparse_unpack_blob(const blob_T *const blob, list_T *const ret
if (len == 0) {
return;
}
- msgpack_unpacked unpacked;
- msgpack_unpacked_init(&unpacked);
- for (size_t offset = 0; offset < (size_t)len;) {
- const msgpack_unpack_return result
- = msgpack_unpack_next(&unpacked, blob->bv_ga.ga_data, (size_t)len, &offset);
- if (msgpackparse_convert_item(unpacked.data, result, ret_list, true)
- != OK) {
- break;
+
+ const char *data = blob->bv_ga.ga_data;
+ size_t remaining = (size_t)len;
+ while (remaining) {
+ typval_T tv;
+ int status = unpack_typval(&data, &remaining, &tv);
+ if (status != MPACK_OK) {
+ emsg_mpack_error(status);
+ return;
}
- }
- msgpack_unpacked_destroy(&unpacked);
+ tv_list_append_owned_tv(ret_list, tv);
+ }
}
/// "msgpackparse" function
@@ -5694,28 +5197,6 @@ static void f_or(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
| tv_get_number_chk(&argvars[1], NULL);
}
-/// "pathshorten()" function
-static void f_pathshorten(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- int trim_len = 1;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- trim_len = (int)tv_get_number(&argvars[1]);
- if (trim_len < 1) {
- trim_len = 1;
- }
- }
-
- rettv->v_type = VAR_STRING;
- const char *p = tv_get_string_chk(&argvars[0]);
- if (p == NULL) {
- rettv->vval.v_string = NULL;
- } else {
- rettv->vval.v_string = xstrdup(p);
- shorten_dir_len(rettv->vval.v_string, trim_len);
- }
-}
-
/// "pow()" function
static void f_pow(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -5872,42 +5353,20 @@ static void f_py3eval(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
static void init_srand(uint32_t *const x)
FUNC_ATTR_NONNULL_ALL
{
-#ifndef MSWIN
- static int dev_urandom_state = NOTDONE; // FAIL or OK once tried
-
- if (dev_urandom_state != FAIL) {
- const int fd = os_open("/dev/urandom", O_RDONLY, 0);
- struct {
- union {
- uint32_t number;
- char bytes[sizeof(uint32_t)];
- } contents;
- } buf;
-
- // Attempt reading /dev/urandom.
- if (fd == -1) {
- dev_urandom_state = FAIL;
- } else {
- buf.contents.number = 0;
- if (read(fd, buf.contents.bytes, sizeof(uint32_t)) != sizeof(uint32_t)) {
- dev_urandom_state = FAIL;
- } else {
- dev_urandom_state = OK;
- *x = buf.contents.number;
- }
- os_close(fd);
- }
- }
- if (dev_urandom_state != OK) {
- // Reading /dev/urandom doesn't work, fall back to os_hrtime() XOR with process ID
-#endif
- // uncrustify:off
- *x = (uint32_t)os_hrtime();
- *x ^= (uint32_t)os_get_pid();
-#ifndef MSWIN
+ union {
+ uint32_t number;
+ uint8_t bytes[sizeof(uint32_t)];
+ } buf;
+
+ if (uv_random(NULL, NULL, buf.bytes, sizeof(buf.bytes), 0, NULL) == 0) {
+ *x = buf.number;
+ return;
}
-#endif
- // uncrustify:on
+
+ // The system's random number generator doesn't work,
+ // fall back to os_hrtime() XOR with process ID
+ *x = (uint32_t)os_hrtime();
+ *x ^= (uint32_t)os_get_pid();
}
static inline uint32_t splitmix32(uint32_t *const x)
@@ -6076,267 +5535,6 @@ static void f_range(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
-/// Evaluate "expr" (= "context") for readdir().
-static varnumber_T readdir_checkitem(void *context, const char *name)
- FUNC_ATTR_NONNULL_ALL
-{
- typval_T *expr = (typval_T *)context;
- typval_T argv[2];
- varnumber_T retval = 0;
- bool error = false;
-
- if (expr->v_type == VAR_UNKNOWN) {
- return 1;
- }
-
- typval_T save_val;
- prepare_vimvar(VV_VAL, &save_val);
- set_vim_var_string(VV_VAL, name, -1);
- argv[0].v_type = VAR_STRING;
- argv[0].vval.v_string = (char *)name;
-
- typval_T rettv;
- if (eval_expr_typval(expr, false, argv, 1, &rettv) == FAIL) {
- goto theend;
- }
-
- retval = tv_get_number_chk(&rettv, &error);
- if (error) {
- retval = -1;
- }
-
- tv_clear(&rettv);
-
-theend:
- set_vim_var_string(VV_VAL, NULL, 0);
- restore_vimvar(VV_VAL, &save_val);
- return retval;
-}
-
-/// "readdir()" function
-static void f_readdir(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- tv_list_alloc_ret(rettv, kListLenUnknown);
-
- const char *path = tv_get_string(&argvars[0]);
- typval_T *expr = &argvars[1];
- garray_T ga;
- int ret = readdir_core(&ga, path, (void *)expr, readdir_checkitem);
- if (ret == OK && ga.ga_len > 0) {
- for (int i = 0; i < ga.ga_len; i++) {
- const char *p = ((const char **)ga.ga_data)[i];
- tv_list_append_string(rettv->vval.v_list, p, -1);
- }
- }
- ga_clear_strings(&ga);
-}
-
-/// "readfile()" or "readblob()" function
-static void read_file_or_blob(typval_T *argvars, typval_T *rettv, bool always_blob)
-{
- bool binary = false;
- bool blob = always_blob;
- FILE *fd;
- char buf[(IOSIZE/256) * 256]; // rounded to avoid odd + 1
- int io_size = sizeof(buf);
- char *prev = NULL; // previously read bytes, if any
- ptrdiff_t prevlen = 0; // length of data in prev
- ptrdiff_t prevsize = 0; // size of prev buffer
- int64_t maxline = MAXLNUM;
- off_T offset = 0;
- off_T size = -1;
-
- if (argvars[1].v_type != VAR_UNKNOWN) {
- if (always_blob) {
- offset = (off_T)tv_get_number(&argvars[1]);
- if (argvars[2].v_type != VAR_UNKNOWN) {
- size = (off_T)tv_get_number(&argvars[2]);
- }
- } else {
- if (strcmp(tv_get_string(&argvars[1]), "b") == 0) {
- binary = true;
- } else if (strcmp(tv_get_string(&argvars[1]), "B") == 0) {
- blob = true;
- }
- if (argvars[2].v_type != VAR_UNKNOWN) {
- maxline = tv_get_number(&argvars[2]);
- }
- }
- }
-
- if (blob) {
- tv_blob_alloc_ret(rettv);
- } else {
- tv_list_alloc_ret(rettv, kListLenUnknown);
- }
-
- // Always open the file in binary mode, library functions have a mind of
- // their own about CR-LF conversion.
- const char *const fname = tv_get_string(&argvars[0]);
-
- if (os_isdir(fname)) {
- semsg(_(e_isadir2), fname);
- return;
- }
- if (*fname == NUL || (fd = os_fopen(fname, READBIN)) == NULL) {
- semsg(_(e_notopen), *fname == NUL ? _("<empty>") : fname);
- return;
- }
-
- if (blob) {
- if (read_blob(fd, rettv, offset, size) == FAIL) {
- semsg(_(e_notread), fname);
- }
- fclose(fd);
- return;
- }
-
- list_T *const l = rettv->vval.v_list;
-
- while (maxline < 0 || tv_list_len(l) < maxline) {
- int readlen = (int)fread(buf, 1, (size_t)io_size, fd);
-
- // This for loop processes what was read, but is also entered at end
- // of file so that either:
- // - an incomplete line gets written
- // - a "binary" file gets an empty line at the end if it ends in a
- // newline.
- char *p; // Position in buf.
- char *start; // Start of current line.
- for (p = buf, start = buf;
- p < buf + readlen || (readlen <= 0 && (prevlen > 0 || binary));
- p++) {
- if (readlen <= 0 || *p == '\n') {
- char *s = NULL;
- size_t len = (size_t)(p - start);
-
- // Finished a line. Remove CRs before NL.
- if (readlen > 0 && !binary) {
- while (len > 0 && start[len - 1] == '\r') {
- len--;
- }
- // removal may cross back to the "prev" string
- if (len == 0) {
- while (prevlen > 0 && prev[prevlen - 1] == '\r') {
- prevlen--;
- }
- }
- }
- if (prevlen == 0) {
- assert(len < INT_MAX);
- s = xmemdupz(start, len);
- } else {
- // Change "prev" buffer to be the right size. This way
- // the bytes are only copied once, and very long lines are
- // allocated only once.
- s = xrealloc(prev, (size_t)prevlen + len + 1);
- memcpy(s + prevlen, start, len);
- s[(size_t)prevlen + len] = NUL;
- prev = NULL; // the list will own the string
- prevlen = prevsize = 0;
- }
-
- tv_list_append_owned_tv(l, (typval_T) {
- .v_type = VAR_STRING,
- .v_lock = VAR_UNLOCKED,
- .vval.v_string = s,
- });
-
- start = p + 1; // Step over newline.
- if (maxline < 0) {
- if (tv_list_len(l) > -maxline) {
- assert(tv_list_len(l) == 1 + (-maxline));
- tv_list_item_remove(l, tv_list_first(l));
- }
- } else if (tv_list_len(l) >= maxline) {
- assert(tv_list_len(l) == maxline);
- break;
- }
- if (readlen <= 0) {
- break;
- }
- } else if (*p == NUL) {
- *p = '\n';
- // Check for utf8 "bom"; U+FEFF is encoded as EF BB BF. Do this
- // when finding the BF and check the previous two bytes.
- } else if ((uint8_t)(*p) == 0xbf && !binary) {
- // Find the two bytes before the 0xbf. If p is at buf, or buf + 1,
- // these may be in the "prev" string.
- char back1 = p >= buf + 1 ? p[-1]
- : prevlen >= 1 ? prev[prevlen - 1] : NUL;
- char back2 = p >= buf + 2 ? p[-2]
- : (p == buf + 1 && prevlen >= 1
- ? prev[prevlen - 1]
- : prevlen >= 2 ? prev[prevlen - 2] : NUL);
-
- if ((uint8_t)back2 == 0xef && (uint8_t)back1 == 0xbb) {
- char *dest = p - 2;
-
- // Usually a BOM is at the beginning of a file, and so at
- // the beginning of a line; then we can just step over it.
- if (start == dest) {
- start = p + 1;
- } else {
- // have to shuffle buf to close gap
- int adjust_prevlen = 0;
-
- if (dest < buf) {
- // adjust_prevlen must be 1 or 2.
- adjust_prevlen = (int)(buf - dest);
- dest = buf;
- }
- if (readlen > p - buf + 1) {
- memmove(dest, p + 1, (size_t)readlen - (size_t)(p - buf) - 1);
- }
- readlen -= 3 - adjust_prevlen;
- prevlen -= adjust_prevlen;
- p = dest - 1;
- }
- }
- }
- } // for
-
- if ((maxline >= 0 && tv_list_len(l) >= maxline) || readlen <= 0) {
- break;
- }
- if (start < p) {
- // There's part of a line in buf, store it in "prev".
- if (p - start + prevlen >= prevsize) {
- // A common use case is ordinary text files and "prev" gets a
- // fragment of a line, so the first allocation is made
- // small, to avoid repeatedly 'allocing' large and
- // 'reallocing' small.
- if (prevsize == 0) {
- prevsize = p - start;
- } else {
- ptrdiff_t grow50pc = (prevsize * 3) / 2;
- ptrdiff_t growmin = (p - start) * 2 + prevlen;
- prevsize = grow50pc > growmin ? grow50pc : growmin;
- }
- prev = xrealloc(prev, (size_t)prevsize);
- }
- // Add the line part to end of "prev".
- memmove(prev + prevlen, start, (size_t)(p - start));
- prevlen += p - start;
- }
- } // while
-
- xfree(prev);
- fclose(fd);
-}
-
-/// "readblob()" function
-static void f_readblob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- read_file_or_blob(argvars, rettv, true);
-}
-
-/// "readfile()" function
-static void f_readfile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- read_file_or_blob(argvars, rettv, false);
-}
-
/// "getreginfo()" function
static void f_getreginfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -6387,6 +5585,14 @@ static void f_getreginfo(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
+static void return_register(int regname, typval_T *rettv)
+{
+ char buf[2] = { (char)regname, 0 };
+
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = xstrdup(buf);
+}
+
/// "reg_executing()" function
static void f_reg_executing(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -6511,18 +5717,6 @@ static void f_remove(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
-/// "rename({from}, {to})" function
-static void f_rename(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- if (check_secure()) {
- rettv->vval.v_number = -1;
- } else {
- char buf[NUMBUFLEN];
- rettv->vval.v_number = vim_rename(tv_get_string(&argvars[0]),
- tv_get_string_buf(&argvars[1], buf));
- }
-}
-
/// "repeat()" function
static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -6591,175 +5785,6 @@ static void f_repeat(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
-/// "resolve()" function
-static void f_resolve(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->v_type = VAR_STRING;
- const char *fname = tv_get_string(&argvars[0]);
-#ifdef MSWIN
- char *v = os_resolve_shortcut(fname);
- if (v == NULL) {
- if (os_is_reparse_point_include(fname)) {
- v = os_realpath(fname, NULL, MAXPATHL + 1);
- }
- }
- rettv->vval.v_string = (v == NULL ? xstrdup(fname) : v);
-#else
-# ifdef HAVE_READLINK
- {
- bool is_relative_to_current = false;
- bool has_trailing_pathsep = false;
- int limit = 100;
-
- char *p = xstrdup(fname);
-
- if (p[0] == '.' && (vim_ispathsep(p[1])
- || (p[1] == '.' && (vim_ispathsep(p[2]))))) {
- is_relative_to_current = true;
- }
-
- ptrdiff_t len = (ptrdiff_t)strlen(p);
- if (len > 1 && after_pathsep(p, p + len)) {
- has_trailing_pathsep = true;
- p[len - 1] = NUL; // The trailing slash breaks readlink().
- }
-
- char *q = (char *)path_next_component(p);
- char *remain = NULL;
- if (*q != NUL) {
- // Separate the first path component in "p", and keep the
- // remainder (beginning with the path separator).
- remain = xstrdup(q - 1);
- q[-1] = NUL;
- }
-
- char *const buf = xmallocz(MAXPATHL);
-
- char *cpy;
- while (true) {
- while (true) {
- len = readlink(p, buf, MAXPATHL);
- if (len <= 0) {
- break;
- }
- buf[len] = NUL;
-
- if (limit-- == 0) {
- xfree(p);
- xfree(remain);
- emsg(_("E655: Too many symbolic links (cycle?)"));
- rettv->vval.v_string = NULL;
- xfree(buf);
- return;
- }
-
- // Ensure that the result will have a trailing path separator
- // if the argument has one.
- if (remain == NULL && has_trailing_pathsep) {
- add_pathsep(buf);
- }
-
- // Separate the first path component in the link value and
- // concatenate the remainders.
- q = (char *)path_next_component(vim_ispathsep(*buf) ? buf + 1 : buf);
- if (*q != NUL) {
- cpy = remain;
- remain = (remain
- ? concat_str(q - 1, remain)
- : xstrdup(q - 1));
- xfree(cpy);
- q[-1] = NUL;
- }
-
- q = path_tail(p);
- if (q > p && *q == NUL) {
- // Ignore trailing path separator.
- p[q - p - 1] = NUL;
- q = path_tail(p);
- }
- if (q > p && !path_is_absolute(buf)) {
- // Symlink is relative to directory of argument. Replace the
- // symlink with the resolved name in the same directory.
- const size_t p_len = strlen(p);
- const size_t buf_len = strlen(buf);
- p = xrealloc(p, p_len + buf_len + 1);
- memcpy(path_tail(p), buf, buf_len + 1);
- } else {
- xfree(p);
- p = xstrdup(buf);
- }
- }
-
- if (remain == NULL) {
- break;
- }
-
- // Append the first path component of "remain" to "p".
- q = (char *)path_next_component(remain + 1);
- len = q - remain - (*q != NUL);
- const size_t p_len = strlen(p);
- cpy = xmallocz(p_len + (size_t)len);
- memcpy(cpy, p, p_len + 1);
- xstrlcat(cpy + p_len, remain, (size_t)len + 1);
- xfree(p);
- p = cpy;
-
- // Shorten "remain".
- if (*q != NUL) {
- STRMOVE(remain, q - 1);
- } else {
- XFREE_CLEAR(remain);
- }
- }
-
- // If the result is a relative path name, make it explicitly relative to
- // the current directory if and only if the argument had this form.
- if (!vim_ispathsep(*p)) {
- if (is_relative_to_current
- && *p != NUL
- && !(p[0] == '.'
- && (p[1] == NUL
- || vim_ispathsep(p[1])
- || (p[1] == '.'
- && (p[2] == NUL
- || vim_ispathsep(p[2])))))) {
- // Prepend "./".
- cpy = concat_str("./", p);
- xfree(p);
- p = cpy;
- } else if (!is_relative_to_current) {
- // Strip leading "./".
- q = p;
- while (q[0] == '.' && vim_ispathsep(q[1])) {
- q += 2;
- }
- if (q > p) {
- STRMOVE(p, p + 2);
- }
- }
- }
-
- // Ensure that the result will have no trailing path separator
- // if the argument had none. But keep "/" or "//".
- if (!has_trailing_pathsep) {
- q = p + strlen(p);
- if (after_pathsep(p, q)) {
- *path_tail_with_sep(p) = NUL;
- }
- }
-
- rettv->vval.v_string = p;
- xfree(buf);
- }
-# else
- char *v = os_realpath(fname, NULL, MAXPATHL + 1);
- rettv->vval.v_string = v == NULL ? xstrdup(fname) : v;
-# endif
-#endif
-
- simplify_filename(rettv->vval.v_string);
-}
-
/// "reverse({list})" function
static void f_reverse(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -7412,6 +6437,21 @@ static void f_rpcstop(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
}
}
+static void screenchar_adjust(ScreenGrid **grid, int *row, int *col)
+{
+ // TODO(bfredl): this is a hack for legacy tests which use screenchar()
+ // to check printed messages on the screen (but not floats etc
+ // as these are not legacy features). If the compositor is refactored to
+ // have its own buffer, this should just read from it instead.
+ msg_scroll_flush();
+
+ *grid = ui_comp_get_grid_at_coord(*row, *col);
+
+ // Make `row` and `col` relative to the grid
+ *row -= (*grid)->comp_row;
+ *col -= (*grid)->comp_col;
+}
+
/// "screenattr()" function
static void f_screenattr(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -8293,21 +7333,12 @@ static void f_shiftwidth(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (col < 0) {
return; // type error; errmsg already given
}
- rettv->vval.v_number = get_sw_value_col(curbuf, col);
+ rettv->vval.v_number = get_sw_value_col(curbuf, col, false);
return;
}
rettv->vval.v_number = get_sw_value(curbuf);
}
-/// "simplify()" function
-static void f_simplify(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- const char *const p = tv_get_string(&argvars[0]);
- rettv->vval.v_string = xstrdup(p);
- simplify_filename(rettv->vval.v_string); // Simplify in place.
- rettv->v_type = VAR_STRING;
-}
-
/// "sockconnect()" function
static void f_sockconnect(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -8598,6 +7629,33 @@ theend:
p_cpo = save_cpo;
}
+/// "stdpath()" helper for list results
+static void get_xdg_var_list(const XDGVarType xdg, typval_T *rettv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ list_T *const list = tv_list_alloc(kListLenShouldKnow);
+ rettv->v_type = VAR_LIST;
+ rettv->vval.v_list = list;
+ tv_list_ref(list);
+ char *const dirs = stdpaths_get_xdg_var(xdg);
+ if (dirs == NULL) {
+ return;
+ }
+ const void *iter = NULL;
+ const char *appname = get_appname(false);
+ do {
+ size_t dir_len;
+ const char *dir;
+ 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, appname, true);
+ tv_list_append_allocated_string(list, dir_with_nvim);
+ }
+ } while (iter != NULL);
+ xfree(dirs);
+}
+
/// "stdpath(type)" function
static void f_stdpath(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -9066,13 +8124,6 @@ static void f_taglist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
(char *)tag_pattern, (char *)fname);
}
-/// "tempname()" function
-static void f_tempname(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->v_type = VAR_STRING;
- rettv->vval.v_string = vim_tempname();
-}
-
/// "termopen(cmd[, cwd])" function
static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -9157,7 +8208,7 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
return;
}
- int pid = chan->stream.pty.process.pid;
+ int pid = chan->stream.pty.proc.pid;
// "./…" => "/home/foo/…"
vim_FullName(cwd, NameBuff, sizeof(NameBuff), false);
@@ -9165,13 +8216,13 @@ static void f_termopen(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
size_t len = home_replace(NULL, NameBuff, IObuff, sizeof(IObuff), true);
// Trim slash.
if (len != 1 && (IObuff[len - 1] == '\\' || IObuff[len - 1] == '/')) {
- IObuff[len - 1] = '\0';
+ IObuff[len - 1] = NUL;
}
if (len == 1 && IObuff[0] == '/') {
// Avoid ambiguity in the URI when CWD is root directory.
IObuff[1] = '.';
- IObuff[2] = '\0';
+ IObuff[2] = NUL;
}
// Terminal URI: "term://$CWD//$PID:$CMD"
@@ -9418,104 +8469,6 @@ static void f_wordcount(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
cursor_pos_info(rettv->vval.v_dict);
}
-/// "writefile()" function
-static void f_writefile(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
-{
- rettv->vval.v_number = -1;
-
- if (check_secure()) {
- return;
- }
-
- if (argvars[0].v_type == VAR_LIST) {
- TV_LIST_ITER_CONST(argvars[0].vval.v_list, li, {
- if (!tv_check_str_or_nr(TV_LIST_ITEM_TV(li))) {
- return;
- }
- });
- } else if (argvars[0].v_type != VAR_BLOB) {
- semsg(_(e_invarg2),
- _("writefile() first argument must be a List or a Blob"));
- return;
- }
-
- bool binary = false;
- bool append = false;
- bool defer = false;
- bool do_fsync = !!p_fs;
- bool mkdir_p = false;
- if (argvars[2].v_type != VAR_UNKNOWN) {
- const char *const flags = tv_get_string_chk(&argvars[2]);
- if (flags == NULL) {
- return;
- }
- for (const char *p = flags; *p; p++) {
- switch (*p) {
- case 'b':
- binary = true; break;
- case 'a':
- append = true; break;
- case 'D':
- defer = true; break;
- case 's':
- do_fsync = true; break;
- case 'S':
- do_fsync = false; break;
- case 'p':
- mkdir_p = true; break;
- default:
- // Using %s, p and not %c, *p to preserve multibyte characters
- semsg(_("E5060: Unknown flag: %s"), p);
- return;
- }
- }
- }
-
- char buf[NUMBUFLEN];
- const char *const fname = tv_get_string_buf_chk(&argvars[1], buf);
- if (fname == NULL) {
- return;
- }
-
- if (defer && !can_add_defer()) {
- return;
- }
-
- FileDescriptor fp;
- int error;
- if (*fname == NUL) {
- emsg(_("E482: Can't open file with an empty name"));
- } else if ((error = file_open(&fp, fname,
- ((append ? kFileAppend : kFileTruncate)
- | (mkdir_p ? kFileMkDir : kFileCreate)
- | kFileCreate), 0666)) != 0) {
- semsg(_("E482: Can't open file %s for writing: %s"), fname, os_strerror(error));
- } else {
- if (defer) {
- typval_T tv = {
- .v_type = VAR_STRING,
- .v_lock = VAR_UNLOCKED,
- .vval.v_string = FullName_save(fname, false),
- };
- add_defer("delete", 1, &tv);
- }
-
- bool write_ok;
- if (argvars[0].v_type == VAR_BLOB) {
- write_ok = write_blob(&fp, argvars[0].vval.v_blob);
- } else {
- write_ok = write_list(&fp, argvars[0].vval.v_list, binary);
- }
- if (write_ok) {
- rettv->vval.v_number = 0;
- }
- if ((error = file_close(&fp, do_fsync)) != 0) {
- semsg(_("E80: Error when closing file %s: %s"),
- fname, os_strerror(error));
- }
- }
-}
-
/// "xor(expr, expr)" function
static void f_xor(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
diff --git a/src/nvim/eval/typval.c b/src/nvim/eval/typval.c
index eb8c89c36e..e7b6a0feee 100644
--- a/src/nvim/eval/typval.c
+++ b/src/nvim/eval/typval.c
@@ -10,11 +10,13 @@
#include "nvim/ascii_defs.h"
#include "nvim/assert_defs.h"
#include "nvim/charset.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/executor.h"
#include "nvim/eval/gc.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/eval/typval_encode.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
@@ -58,6 +60,13 @@ typedef struct {
typedef int (*ListSorter)(const void *, const void *);
+/// Type for tv_dict2list() function
+typedef enum {
+ kDict2ListKeys, ///< List dictionary keys.
+ kDict2ListValues, ///< List dictionary values.
+ kDict2ListItems, ///< List dictionary contents: [keys, values].
+} DictListType;
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "eval/typval.c.generated.h"
#endif
@@ -84,6 +93,8 @@ static const char e_string_or_number_required_for_argument_nr[]
= N_("E1220: String or Number required for argument %d");
static const char e_string_or_list_required_for_argument_nr[]
= N_("E1222: String or List required for argument %d");
+static const char e_string_list_or_dict_required_for_argument_nr[]
+ = N_("E1225: String, List or Dictionary required for argument %d");
static const char e_list_or_blob_required_for_argument_nr[]
= N_("E1226: List or Blob required for argument %d");
static const char e_blob_required_for_argument_nr[]
@@ -474,13 +485,14 @@ void tv_list_append_tv(list_T *const l, typval_T *const tv)
/// Like tv_list_append_tv(), but tv is moved to a list
///
/// This means that it is no longer valid to use contents of the typval_T after
-/// function exits.
-void tv_list_append_owned_tv(list_T *const l, typval_T tv)
+/// function exits. A pointer is returned to the allocated typval which can be used
+typval_T *tv_list_append_owned_tv(list_T *const l, typval_T tv)
FUNC_ATTR_NONNULL_ALL
{
listitem_T *const li = tv_list_item_alloc();
*TV_LIST_ITEM_TV(li) = tv;
tv_list_append(l, li);
+ return TV_LIST_ITEM_TV(li);
}
/// Append a list to a list as one item
@@ -781,6 +793,51 @@ void tv_list_flatten(list_T *list, listitem_T *first, int64_t maxitems, int64_t
}
}
+/// "items(list)" function
+/// Caller must have already checked that argvars[0] is a List.
+static void tv_list2items(typval_T *argvars, typval_T *rettv)
+{
+ list_T *l = argvars[0].vval.v_list;
+
+ tv_list_alloc_ret(rettv, tv_list_len(l));
+ if (l == NULL) {
+ return; // null list behaves like an empty list
+ }
+
+ varnumber_T idx = 0;
+ TV_LIST_ITER(l, li, {
+ list_T *l2 = tv_list_alloc(2);
+ tv_list_append_list(rettv->vval.v_list, l2);
+ tv_list_append_number(l2, idx);
+ tv_list_append_tv(l2, TV_LIST_ITEM_TV(li));
+ idx++;
+ });
+}
+
+/// "items(string)" function
+/// Caller must have already checked that argvars[0] is a String.
+static void tv_string2items(typval_T *argvars, typval_T *rettv)
+{
+ const char *p = argvars[0].vval.v_string;
+
+ tv_list_alloc_ret(rettv, kListLenMayKnow);
+ if (p == NULL) {
+ return; // null string behaves like an empty string
+ }
+
+ for (varnumber_T idx = 0; *p != NUL; idx++) {
+ int len = utfc_ptr2len(p);
+ if (len == 0) {
+ break;
+ }
+ list_T *l2 = tv_list_alloc(2);
+ tv_list_append_list(rettv->vval.v_list, l2);
+ tv_list_append_number(l2, idx);
+ tv_list_append_string(l2, p, len);
+ p += len;
+ }
+}
+
/// Extend first list with the second
///
/// @param[out] l1 List to extend.
@@ -1459,10 +1516,9 @@ void f_uniq(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// @param[in] l1 First list to compare.
/// @param[in] l2 Second list to compare.
/// @param[in] ic True if case is to be ignored.
-/// @param[in] recursive True when used recursively.
///
/// @return True if lists are equal, false otherwise.
-bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, const bool recursive)
+bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic)
FUNC_ATTR_WARN_UNUSED_RESULT
{
if (l1 == l2) {
@@ -1484,8 +1540,7 @@ bool tv_list_equal(list_T *const l1, list_T *const l2, const bool ic, const bool
for (; item1 != NULL && item2 != NULL
; (item1 = TV_LIST_ITEM_NEXT(l1, item1),
item2 = TV_LIST_ITEM_NEXT(l2, item2))) {
- if (!tv_equal(TV_LIST_ITEM_TV(item1), TV_LIST_ITEM_TV(item2), ic,
- recursive)) {
+ if (!tv_equal(TV_LIST_ITEM_TV(item1), TV_LIST_ITEM_TV(item2), ic)) {
return false;
}
}
@@ -1823,7 +1878,7 @@ char *callback_to_string(Callback *cb, Arena *arena)
snprintf(msg, msglen, "<vim partial: %s>", cb->data.partial->pt_name);
break;
default:
- *msg = '\0';
+ *msg = NUL;
break;
}
return msg;
@@ -2678,8 +2733,9 @@ void tv_dict_extend(dict_T *const d1, dict_T *const d2, const char *const action
/// @param[in] d1 First dictionary.
/// @param[in] d2 Second dictionary.
/// @param[in] ic True if case is to be ignored.
-/// @param[in] recursive True when used recursively.
-bool tv_dict_equal(dict_T *const d1, dict_T *const d2, const bool ic, const bool recursive)
+///
+/// @return True if dictionaries are equal, false otherwise.
+bool tv_dict_equal(dict_T *const d1, dict_T *const d2, const bool ic)
FUNC_ATTR_WARN_UNUSED_RESULT
{
if (d1 == d2) {
@@ -2701,7 +2757,7 @@ bool tv_dict_equal(dict_T *const d1, dict_T *const d2, const bool ic, const bool
if (di2 == NULL) {
return false;
}
- if (!tv_equal(&di1->di_tv, &di2->di_tv, ic, recursive)) {
+ if (!tv_equal(&di1->di_tv, &di2->di_tv, ic)) {
return false;
}
});
@@ -3063,8 +3119,7 @@ void f_blob2list(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
/// list2blob() function
void f_list2blob(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- tv_blob_alloc_ret(rettv);
- blob_T *const blob = rettv->vval.v_blob;
+ blob_T *blob = tv_blob_alloc_ret(rettv);
if (tv_check_for_list_arg(argvars, 0) == FAIL) {
return;
@@ -3135,49 +3190,45 @@ void tv_dict_alloc_ret(typval_T *const ret_tv)
/// Turn a dictionary into a list
///
-/// @param[in] tv Dictionary to convert. Is checked for actually being
+/// @param[in] argvars Arguments to items(). The first argument is check for being
/// a dictionary, will give an error if not.
/// @param[out] rettv Location where result will be saved.
/// @param[in] what What to save in rettv.
-static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictListType what)
+static void tv_dict2list(typval_T *const argvars, typval_T *const rettv, const DictListType what)
{
- if (tv->v_type != VAR_DICT) {
- emsg(_(e_dictreq));
+ if ((what == kDict2ListItems
+ ? tv_check_for_string_or_list_or_dict_arg(argvars, 0)
+ : tv_check_for_dict_arg(argvars, 0)) == FAIL) {
+ tv_list_alloc_ret(rettv, 0);
return;
}
- tv_list_alloc_ret(rettv, tv_dict_len(tv->vval.v_dict));
- if (tv->vval.v_dict == NULL) {
+ dict_T *d = argvars[0].vval.v_dict;
+ tv_list_alloc_ret(rettv, tv_dict_len(d));
+ if (d == NULL) {
// NULL dict behaves like an empty dict
return;
}
- TV_DICT_ITER(tv->vval.v_dict, di, {
+ TV_DICT_ITER(d, di, {
typval_T tv_item = { .v_lock = VAR_UNLOCKED };
switch (what) {
- case kDictListKeys:
+ case kDict2ListKeys:
tv_item.v_type = VAR_STRING;
tv_item.vval.v_string = xstrdup(di->di_key);
break;
- case kDictListValues:
+ case kDict2ListValues:
tv_copy(&di->di_tv, &tv_item);
break;
- case kDictListItems: {
+ case kDict2ListItems: {
// items()
list_T *const sub_l = tv_list_alloc(2);
tv_item.v_type = VAR_LIST;
tv_item.vval.v_list = sub_l;
tv_list_ref(sub_l);
-
- tv_list_append_owned_tv(sub_l, (typval_T) {
- .v_type = VAR_STRING,
- .v_lock = VAR_UNLOCKED,
- .vval.v_string = xstrdup(di->di_key),
- });
-
+ tv_list_append_string(sub_l, di->di_key, -1);
tv_list_append_tv(sub_l, &di->di_tv);
-
break;
}
}
@@ -3189,19 +3240,25 @@ static void tv_dict_list(typval_T *const tv, typval_T *const rettv, const DictLi
/// "items(dict)" function
void f_items(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- tv_dict_list(argvars, rettv, 2);
+ if (argvars[0].v_type == VAR_STRING) {
+ tv_string2items(argvars, rettv);
+ } else if (argvars[0].v_type == VAR_LIST) {
+ tv_list2items(argvars, rettv);
+ } else {
+ tv_dict2list(argvars, rettv, kDict2ListItems);
+ }
}
/// "keys()" function
void f_keys(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- tv_dict_list(argvars, rettv, 0);
+ tv_dict2list(argvars, rettv, kDict2ListKeys);
}
/// "values(dict)" function
void f_values(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
- tv_dict_list(argvars, rettv, 1);
+ tv_dict2list(argvars, rettv, kDict2ListValues);
}
/// "has_key()" function
@@ -3251,11 +3308,12 @@ void tv_dict_remove(typval_T *argvars, typval_T *rettv, const char *arg_errmsg)
/// Also sets reference count.
///
/// @param[out] ret_tv Structure where blob is saved.
-void tv_blob_alloc_ret(typval_T *const ret_tv)
+blob_T *tv_blob_alloc_ret(typval_T *const ret_tv)
FUNC_ATTR_NONNULL_ALL
{
blob_T *const b = tv_blob_alloc();
tv_blob_set_ret(ret_tv, b);
+ return b;
}
/// Copy a blob typval to a different typval.
@@ -3283,6 +3341,7 @@ void tv_blob_copy(blob_T *const from, typval_T *const to)
//{{{3 Clear
#define TYPVAL_ENCODE_ALLOW_SPECIALS false
+#define TYPVAL_ENCODE_CHECK_BEFORE
#define TYPVAL_ENCODE_CONV_NIL(tv) \
do { \
@@ -3499,6 +3558,7 @@ static inline void _nothing_conv_dict_end(typval_T *const tv, dict_T **const dic
#undef TYPVAL_ENCODE_FIRST_ARG_NAME
#undef TYPVAL_ENCODE_ALLOW_SPECIALS
+#undef TYPVAL_ENCODE_CHECK_BEFORE
#undef TYPVAL_ENCODE_CONV_NIL
#undef TYPVAL_ENCODE_CONV_BOOL
#undef TYPVAL_ENCODE_CONV_NUMBER
@@ -3835,10 +3895,9 @@ static int tv_equal_recurse_limit;
/// @param[in] tv1 First value to compare.
/// @param[in] tv2 Second value to compare.
/// @param[in] ic True if case is to be ignored.
-/// @param[in] recursive True when used recursively.
///
/// @return true if values are equal.
-bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, const bool recursive)
+bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
// TODO(ZyX-I): Make this not recursive
@@ -3854,7 +3913,7 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, const boo
// Reduce the limit every time running into it. That should work fine for
// deeply linked structures that are not recursively linked and catch
// recursiveness quickly.
- if (!recursive) {
+ if (recursive_cnt == 0) {
tv_equal_recurse_limit = 1000;
}
if (recursive_cnt >= tv_equal_recurse_limit) {
@@ -3865,15 +3924,13 @@ bool tv_equal(typval_T *const tv1, typval_T *const tv2, const bool ic, const boo
switch (tv1->v_type) {
case VAR_LIST: {
recursive_cnt++;
- const bool r = tv_list_equal(tv1->vval.v_list, tv2->vval.v_list, ic,
- true);
+ const bool r = tv_list_equal(tv1->vval.v_list, tv2->vval.v_list, ic);
recursive_cnt--;
return r;
}
case VAR_DICT: {
recursive_cnt++;
- const bool r = tv_dict_equal(tv1->vval.v_dict, tv2->vval.v_dict, ic,
- true);
+ const bool r = tv_dict_equal(tv1->vval.v_dict, tv2->vval.v_dict, ic);
recursive_cnt--;
return r;
}
@@ -4152,6 +4209,29 @@ linenr_T tv_get_lnum(const typval_T *const tv)
return lnum;
}
+/// Get the line number from Vimscript object
+///
+/// @note Unlike tv_get_lnum(), this one supports only "$" special string.
+///
+/// @param[in] tv Object to get value from. Is expected to be a number or
+/// a special string "$".
+/// @param[in] buf Buffer to take last line number from in case tv is "$". May
+/// be NULL, in this case "$" results in zero return.
+///
+/// @return Line number or 0 in case of error.
+linenr_T tv_get_lnum_buf(const typval_T *const tv, const buf_T *const buf)
+ FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if (tv->v_type == VAR_STRING
+ && tv->vval.v_string != NULL
+ && tv->vval.v_string[0] == '$'
+ && tv->vval.v_string[1] == NUL
+ && buf != NULL) {
+ return buf->b_ml.ml_line_count;
+ }
+ return (linenr_T)tv_get_number_chk(tv, NULL);
+}
+
/// Get the floating-point value of a Vimscript object
///
/// Raises an error if object is not number or floating-point.
@@ -4399,6 +4479,19 @@ int tv_check_for_opt_string_or_list_arg(const typval_T *const args, const int id
|| tv_check_for_string_or_list_arg(args, idx) != FAIL) ? OK : FAIL;
}
+/// Give an error and return FAIL unless "args[idx]" is a string or a list or a dict
+int tv_check_for_string_or_list_or_dict_arg(const typval_T *const args, const int idx)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_PURE
+{
+ if (args[idx].v_type != VAR_STRING
+ && args[idx].v_type != VAR_LIST
+ && args[idx].v_type != VAR_DICT) {
+ semsg(_(e_string_list_or_dict_required_for_argument_nr), idx + 1);
+ return FAIL;
+ }
+ return OK;
+}
+
/// Give an error and return FAIL unless "args[idx]" is a string
/// or a function reference.
int tv_check_for_string_or_func_arg(const typval_T *const args, const int idx)
diff --git a/src/nvim/eval/typval.h b/src/nvim/eval/typval.h
index f9ebd2f778..ff70eadaaa 100644
--- a/src/nvim/eval/typval.h
+++ b/src/nvim/eval/typval.h
@@ -7,7 +7,6 @@
#include <string.h>
#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
-#include "nvim/func_attr.h"
#include "nvim/gettext_defs.h"
#include "nvim/hashtab.h"
#include "nvim/lib/queue_defs.h"
@@ -16,6 +15,10 @@
#include "nvim/message.h"
#include "nvim/types_defs.h"
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval/typval.h.inline.generated.h"
+#endif
+
// In a hashtab item "hi_key" points to "di_key" in a dictitem.
// This avoids adding a pointer to the hashtab item.
@@ -23,15 +26,13 @@
#define TV_DICT_HI2DI(hi) \
((dictitem_T *)((hi)->hi_key - offsetof(dictitem_T, di_key)))
-static inline void tv_list_ref(list_T *l)
- REAL_FATTR_ALWAYS_INLINE;
-
/// Increase reference count for a given list
///
/// Does nothing for NULL lists.
///
/// @param[in,out] l List to modify.
static inline void tv_list_ref(list_T *const l)
+ FUNC_ATTR_ALWAYS_INLINE
{
if (l == NULL) {
return;
@@ -39,29 +40,25 @@ static inline void tv_list_ref(list_T *const l)
l->lv_refcount++;
}
-static inline void tv_list_set_ret(typval_T *tv, list_T *l)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1);
-
/// Set a list as the return value. Increments the reference count.
///
/// @param[out] tv Object to receive the list
/// @param[in,out] l List to pass to the object
static inline void tv_list_set_ret(typval_T *const tv, list_T *const l)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1)
{
tv->v_type = VAR_LIST;
tv->vval.v_list = l;
tv_list_ref(l);
}
-static inline VarLockStatus tv_list_locked(const list_T *l)
- REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT;
-
/// Get list lock status
///
/// Returns VAR_FIXED for NULL lists.
///
/// @param[in] l List to check.
static inline VarLockStatus tv_list_locked(const list_T *const l)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
if (l == NULL) {
return VAR_FIXED;
@@ -84,9 +81,6 @@ static inline void tv_list_set_lock(list_T *const l, const VarLockStatus lock)
l->lv_lock = lock;
}
-static inline void tv_list_set_copyid(list_T *l, int copyid)
- REAL_FATTR_NONNULL_ALL;
-
/// Set list copyID
///
/// Does not expect NULL list, be careful.
@@ -94,17 +88,16 @@ static inline void tv_list_set_copyid(list_T *l, int copyid)
/// @param[out] l List to modify.
/// @param[in] copyid New copyID.
static inline void tv_list_set_copyid(list_T *const l, const int copyid)
+ FUNC_ATTR_NONNULL_ALL
{
l->lv_copyID = copyid;
}
-static inline int tv_list_len(const list_T *l)
- REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT;
-
/// Get the number of items in a list
///
/// @param[in] l List to check.
static inline int tv_list_len(const list_T *const l)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
if (l == NULL) {
return 0;
@@ -112,22 +105,17 @@ static inline int tv_list_len(const list_T *const l)
return l->lv_len;
}
-static inline int tv_list_copyid(const list_T *l)
- REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL;
-
/// Get list copyID
///
/// Does not expect NULL list, be careful.
///
/// @param[in] l List to check.
static inline int tv_list_copyid(const list_T *const l)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
return l->lv_copyID;
}
-static inline list_T *tv_list_latest_copy(const list_T *l)
- REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL;
-
/// Get latest list copy
///
/// Gets lv_copylist field assigned by tv_list_copy() earlier.
@@ -136,13 +124,11 @@ static inline list_T *tv_list_latest_copy(const list_T *l)
///
/// @param[in] l List to check.
static inline list_T *tv_list_latest_copy(const list_T *const l)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
return l->lv_copylist;
}
-static inline int tv_list_uidx(const list_T *l, int n)
- REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT;
-
/// Normalize index: that is, return either -1 or non-negative index
///
/// @param[in] l List to index. Used to get length.
@@ -150,6 +136,7 @@ static inline int tv_list_uidx(const list_T *l, int n)
///
/// @return -1 or list index in range [0, tv_list_len(l)).
static inline int tv_list_uidx(const list_T *const l, int n)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
// Negative index is relative to the end.
if (n < 0) {
@@ -163,9 +150,6 @@ static inline int tv_list_uidx(const list_T *const l, int n)
return n;
}
-static inline bool tv_list_has_watchers(const list_T *l)
- REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT;
-
/// Check whether list has watchers
///
/// E.g. is referenced by a :for loop.
@@ -174,19 +158,18 @@ static inline bool tv_list_has_watchers(const list_T *l)
///
/// @return true if there are watchers, false otherwise.
static inline bool tv_list_has_watchers(const list_T *const l)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return l && l->lv_watch;
}
-static inline listitem_T *tv_list_first(const list_T *l)
- REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT;
-
/// Get first list item
///
/// @param[in] l List to get item from.
///
/// @return List item or NULL in case of an empty list.
static inline listitem_T *tv_list_first(const list_T *const l)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
if (l == NULL) {
return NULL;
@@ -194,15 +177,13 @@ static inline listitem_T *tv_list_first(const list_T *const l)
return l->lv_first;
}
-static inline listitem_T *tv_list_last(const list_T *l)
- REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT;
-
/// Get last list item
///
/// @param[in] l List to get item from.
///
/// @return List item or NULL in case of an empty list.
static inline listitem_T *tv_list_last(const list_T *const l)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
if (l == NULL) {
return NULL;
@@ -210,14 +191,12 @@ static inline listitem_T *tv_list_last(const list_T *const l)
return l->lv_last;
}
-static inline void tv_dict_set_ret(typval_T *tv, dict_T *d)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1);
-
/// Set a dictionary as the return value
///
/// @param[out] tv Object to receive the dictionary
/// @param[in,out] d Dictionary to pass to the object
static inline void tv_dict_set_ret(typval_T *const tv, dict_T *const d)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1)
{
tv->v_type = VAR_DICT;
tv->vval.v_dict = d;
@@ -226,13 +205,11 @@ static inline void tv_dict_set_ret(typval_T *const tv, dict_T *const d)
}
}
-static inline long tv_dict_len(const dict_T *d)
- REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT;
-
/// Get the number of items in a Dictionary
///
/// @param[in] d Dictionary to check.
static inline long tv_dict_len(const dict_T *const d)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
if (d == NULL) {
return 0;
@@ -240,22 +217,17 @@ static inline long tv_dict_len(const dict_T *const d)
return (long)d->dv_hashtab.ht_used;
}
-static inline bool tv_dict_is_watched(const dict_T *d)
- REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT;
-
/// Check if dictionary is watched
///
/// @param[in] d Dictionary to check.
///
/// @return true if there is at least one watcher.
static inline bool tv_dict_is_watched(const dict_T *const d)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return d && !QUEUE_EMPTY(&d->watchers);
}
-static inline void tv_blob_set_ret(typval_T *tv, blob_T *b)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1);
-
/// Set a blob as the return value.
///
/// Increments the reference count.
@@ -263,6 +235,7 @@ static inline void tv_blob_set_ret(typval_T *tv, blob_T *b)
/// @param[out] tv Object to receive the blob.
/// @param[in,out] b Blob to pass to the object.
static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1)
{
tv->v_type = VAR_BLOB;
tv->vval.v_blob = b;
@@ -271,13 +244,11 @@ static inline void tv_blob_set_ret(typval_T *const tv, blob_T *const b)
}
}
-static inline int tv_blob_len(const blob_T *b)
- REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT;
-
/// Get the length of the data in the blob, in bytes.
///
/// @param[in] b Blob to check.
static inline int tv_blob_len(const blob_T *const b)
+ FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
if (b == NULL) {
return 0;
@@ -285,9 +256,6 @@ static inline int tv_blob_len(const blob_T *const b)
return b->bv_ga.ga_len;
}
-static inline uint8_t tv_blob_get(const blob_T *b, int idx)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT;
-
/// Get the byte at index `idx` in the blob.
///
/// @param[in] b Blob to index. Cannot be NULL.
@@ -295,19 +263,18 @@ static inline uint8_t tv_blob_get(const blob_T *b, int idx)
///
/// @return Byte value at the given index.
static inline uint8_t tv_blob_get(const blob_T *const b, int idx)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
return ((uint8_t *)b->bv_ga.ga_data)[idx];
}
-static inline void tv_blob_set(blob_T *blob, int idx, uint8_t c)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL;
-
/// Store the byte `c` at index `idx` in the blob.
///
/// @param[in] b Blob to index. Cannot be NULL.
/// @param[in] idx Index in a blob. Must be valid.
/// @param[in] c Value to store.
static inline void tv_blob_set(blob_T *const blob, int idx, uint8_t c)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
{
((uint8_t *)blob->bv_ga.ga_data)[idx] = c;
}
@@ -417,9 +384,6 @@ extern bool tv_in_free_unref_items;
} \
})
-static inline bool tv_get_float_chk(const typval_T *tv, float_T *ret_f)
- REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT;
-
/// Get the float value
///
/// Raises an error if object is not number or floating-point.
@@ -429,6 +393,7 @@ static inline bool tv_get_float_chk(const typval_T *tv, float_T *ret_f)
///
/// @return true in case of success, false if tv is not a number or float.
static inline bool tv_get_float_chk(const typval_T *const tv, float_T *const ret_f)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
if (tv->v_type == VAR_FLOAT) {
*ret_f = tv->vval.v_float;
@@ -442,22 +407,17 @@ static inline bool tv_get_float_chk(const typval_T *const tv, float_T *const ret
return false;
}
-static inline DictWatcher *tv_dict_watcher_node_data(QUEUE *q)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL REAL_FATTR_NONNULL_RET
- REAL_FATTR_NO_SANITIZE_ADDRESS REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT;
-
/// Compute the `DictWatcher` address from a QUEUE node.
///
/// This only exists for .asan-blacklist (ASAN doesn't handle QUEUE_DATA pointer
/// arithmetic).
static inline DictWatcher *tv_dict_watcher_node_data(QUEUE *q)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
+ FUNC_ATTR_NO_SANITIZE_ADDRESS FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return QUEUE_DATA(q, DictWatcher, node);
}
-static inline bool tv_is_func(typval_T tv)
- REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE REAL_FATTR_CONST;
-
/// Check whether given typval_T contains a function
///
/// That is, whether it contains VAR_FUNC or VAR_PARTIAL.
@@ -466,6 +426,7 @@ static inline bool tv_is_func(typval_T tv)
///
/// @return True if it is a function or a partial, false otherwise.
static inline bool tv_is_func(const typval_T tv)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_CONST
{
return tv.v_type == VAR_FUNC || tv.v_type == VAR_PARTIAL;
}
diff --git a/src/nvim/eval/typval_defs.h b/src/nvim/eval/typval_defs.h
index e88e6a222a..1ec7631c4b 100644
--- a/src/nvim/eval/typval_defs.h
+++ b/src/nvim/eval/typval_defs.h
@@ -109,7 +109,7 @@ typedef enum {
VAR_STRING, ///< String, .v_string is used.
VAR_FUNC, ///< Function reference, .v_string is used as function name.
VAR_LIST, ///< List, .v_list is used.
- VAR_DICT, ///< Dictionary, .v_dict is used.
+ VAR_DICT, ///< Dict, .v_dict is used.
VAR_FLOAT, ///< Floating-point value, .v_float is used.
VAR_BOOL, ///< true, false
VAR_SPECIAL, ///< Special value (null), .v_special is used.
@@ -141,7 +141,7 @@ typedef struct {
float_T v_float; ///< Floating-point number, for VAR_FLOAT.
char *v_string; ///< String, for VAR_STRING and VAR_FUNC, can be NULL.
list_T *v_list; ///< List for VAR_LIST, can be NULL.
- dict_T *v_dict; ///< Dictionary for VAR_DICT, can be NULL.
+ dict_T *v_dict; ///< Dict for VAR_DICT, can be NULL.
partial_T *v_partial; ///< Closure: function with args.
blob_T *v_blob; ///< Blob for VAR_BLOB, can be NULL.
} vval; ///< Actual value.
@@ -259,7 +259,7 @@ struct dictvar_S {
dict_T *dv_copydict; ///< Copied dict used by deepcopy().
dict_T *dv_used_next; ///< Next dictionary in used dictionaries list.
dict_T *dv_used_prev; ///< Previous dictionary in used dictionaries list.
- QUEUE watchers; ///< Dictionary key watchers set by user code.
+ QUEUE watchers; ///< Dict key watchers set by user code.
LuaRef lua_table_ref;
};
diff --git a/src/nvim/eval/typval_encode.c.h b/src/nvim/eval/typval_encode.c.h
index c0cd0ce557..c0e994562c 100644
--- a/src/nvim/eval/typval_encode.c.h
+++ b/src/nvim/eval/typval_encode.c.h
@@ -6,6 +6,11 @@
/// something else. For these macros to work the following macros must be
/// defined:
+/// @def TYPVAL_ENCODE_CHECK_BEFORE
+/// @brief Macro used before any specific CONV function
+///
+/// can be used for a common check, like flushing a buffer if necessary
+
/// @def TYPVAL_ENCODE_CONV_NIL
/// @brief Macros used to convert NIL value
///
@@ -168,8 +173,7 @@
/// point to a special dictionary.
/// @param dict Converted dictionary, lvalue or #TYPVAL_ENCODE_NODICT_VAR
/// (for dictionaries represented as special lists).
-/// @param len Dictionary length. Is an expression which evaluates to an
-/// integer.
+/// @param len Dict length. Is an expression which evaluates to an integer.
/// @def TYPVAL_ENCODE_CONV_REAL_DICT_AFTER_START
/// @brief Macros used after pushing dictionary onto the stack
@@ -323,6 +327,7 @@ static int TYPVAL_ENCODE_CONVERT_ONE_VALUE(
TYPVAL_ENCODE_FIRST_ARG_TYPE TYPVAL_ENCODE_FIRST_ARG_NAME, MPConvStack *const mpstack,
MPConvStackVal *const cur_mpsv, typval_T *const tv, const int copyID, const char *const objname)
{
+ TYPVAL_ENCODE_CHECK_BEFORE;
switch (tv->v_type) {
case VAR_STRING:
TYPVAL_ENCODE_CONV_STRING(tv, tv->vval.v_string, tv_strlen(tv));
@@ -420,6 +425,7 @@ static int TYPVAL_ENCODE_CONVERT_ONE_VALUE(
break;
}
}
+ TYPVAL_ENCODE_CHECK_BEFORE;
if (i == ARRAY_SIZE(eval_msgpack_type_lists)) {
goto _convert_one_value_regular_dict;
}
@@ -494,9 +500,7 @@ static int TYPVAL_ENCODE_CONVERT_ONE_VALUE(
}
TYPVAL_ENCODE_CONV_FLOAT(tv, val_di->di_tv.vval.v_float);
break;
- case kMPString:
- case kMPBinary: {
- const bool is_string = ((MessagePackType)i == kMPString);
+ case kMPString: {
if (val_di->di_tv.v_type != VAR_LIST) {
goto _convert_one_value_regular_dict;
}
@@ -506,11 +510,7 @@ static int TYPVAL_ENCODE_CONVERT_ONE_VALUE(
&buf)) {
goto _convert_one_value_regular_dict;
}
- if (is_string) {
- TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len);
- } else {
- TYPVAL_ENCODE_CONV_STRING(tv, buf, len);
- }
+ TYPVAL_ENCODE_CONV_STR_STRING(tv, buf, len);
xfree(buf);
break;
}
diff --git a/src/nvim/eval/typval_encode.h b/src/nvim/eval/typval_encode.h
index a6e0bd4b2b..8547783213 100644
--- a/src/nvim/eval/typval_encode.h
+++ b/src/nvim/eval/typval_encode.h
@@ -11,7 +11,10 @@
#include "klib/kvec.h"
#include "nvim/eval/typval_defs.h"
-#include "nvim/func_attr.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "eval/typval_encode.h.inline.generated.h"
+#endif
/// Type of the stack entry
typedef enum {
@@ -62,10 +65,6 @@ typedef struct {
/// Stack used to convert Vimscript values to messagepack.
typedef kvec_withinit_t(MPConvStackVal, 8) MPConvStack;
-static inline size_t tv_strlen(const typval_T *tv)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT
- REAL_FATTR_NONNULL_ALL;
-
/// Length of the string stored in typval_T
///
/// @param[in] tv String for which to compute length for. Must be typval_T
@@ -74,6 +73,8 @@ static inline size_t tv_strlen(const typval_T *tv)
/// @return Length of the string stored in typval_T, including 0 for NULL
/// string.
static inline size_t tv_strlen(const typval_T *const tv)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
+ FUNC_ATTR_NONNULL_ALL
{
assert(tv->v_type == VAR_STRING);
return (tv->vval.v_string == NULL ? 0 : strlen(tv->vval.v_string));
diff --git a/src/nvim/eval/userfunc.c b/src/nvim/eval/userfunc.c
index 0ec07399b4..0050a77fe3 100644
--- a/src/nvim/eval/userfunc.c
+++ b/src/nvim/eval/userfunc.c
@@ -15,6 +15,7 @@
#include "nvim/charset.h"
#include "nvim/cmdexpand_defs.h"
#include "nvim/debugger.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/funcs.h"
@@ -264,7 +265,7 @@ static void set_ufunc_name(ufunc_T *fp, char *name)
if ((uint8_t)name[0] == K_SPECIAL) {
fp->uf_name_exp = xmalloc(strlen(name) + 3);
STRCPY(fp->uf_name_exp, "<SNR>");
- STRCAT(fp->uf_name_exp, fp->uf_name + 3);
+ strcat(fp->uf_name_exp, fp->uf_name + 3);
}
}
@@ -641,6 +642,44 @@ static char *fname_trans_sid(const char *const name, char *const fname_buf, char
return fname;
}
+int get_func_arity(const char *name, int *required, int *optional, bool *varargs)
+{
+ int argcount = 0;
+ int min_argcount = 0;
+
+ const EvalFuncDef *fdef = find_internal_func(name);
+ if (fdef != NULL) {
+ argcount = fdef->max_argc;
+ min_argcount = fdef->min_argc;
+ *varargs = false;
+ } else {
+ char fname_buf[FLEN_FIXED + 1];
+ char *tofree = NULL;
+ int error = FCERR_NONE;
+
+ // May need to translate <SNR>123_ to K_SNR.
+ char *fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+ ufunc_T *ufunc = NULL;
+ if (error == FCERR_NONE) {
+ ufunc = find_func(fname);
+ }
+ xfree(tofree);
+
+ if (ufunc == NULL) {
+ return FAIL;
+ }
+
+ argcount = ufunc->uf_args.ga_len;
+ min_argcount = ufunc->uf_args.ga_len - ufunc->uf_def_args.ga_len;
+ *varargs = ufunc->uf_varargs;
+ }
+
+ *required = min_argcount;
+ *optional = argcount - min_argcount;
+
+ return OK;
+}
+
/// Find a function by name, return pointer to it in ufuncs.
///
/// @return NULL for unknown function.
@@ -916,7 +955,7 @@ void remove_funccal(void)
/// @param[out] rettv Return value.
/// @param[in] firstline First line of range.
/// @param[in] lastline Last line of range.
-/// @param selfdict Dictionary for "self" for dictionary functions.
+/// @param selfdict Dict for "self" for dictionary functions.
void call_user_func(ufunc_T *fp, int argcount, typval_T *argvars, typval_T *rettv,
linenr_T firstline, linenr_T lastline, dict_T *selfdict)
FUNC_ATTR_NONNULL_ARG(1, 3, 4)
@@ -1528,12 +1567,12 @@ varnumber_T callback_call_retnr(Callback *callback, int argcount, typval_T *argv
/// 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 *name, funcexe_T *funcexe)
+static void user_func_error(int error, const char *name, bool found_var)
FUNC_ATTR_NONNULL_ARG(2)
{
switch (error) {
case FCERR_UNKNOWN:
- if (funcexe->fe_found_var) {
+ if (found_var) {
semsg(_(e_not_callable_type_str), name);
} else {
emsg_funcname(e_unknown_function_str, name);
@@ -1653,12 +1692,9 @@ int call_func(const char *funcname, int len, typval_T *rettv, int argcount_in, t
}
if (error == FCERR_NONE && funcexe->fe_evaluate) {
- char *rfname = fname;
-
- // Ignore "g:" before a function name.
- if (fp == NULL && fname[0] == 'g' && fname[1] == ':') {
- rfname = fname + 2;
- }
+ // Skip "g:" before a function name.
+ bool is_global = fp == NULL && fname[0] == 'g' && fname[1] == ':';
+ char *rfname = is_global ? fname + 2 : fname;
rettv->v_type = VAR_NUMBER; // default rettv is number zero
rettv->vval.v_number = 0;
@@ -1732,7 +1768,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()) {
- user_func_error(error, (name != NULL) ? name : funcname, funcexe);
+ user_func_error(error, (name != NULL) ? name : funcname, funcexe->fe_found_var);
}
// clear the copies made from the partial
@@ -1746,6 +1782,70 @@ theend:
return ret;
}
+int call_simple_luafunc(const char *funcname, size_t len, typval_T *rettv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ rettv->v_type = VAR_NUMBER; // default rettv is number zero
+ rettv->vval.v_number = 0;
+
+ typval_T argvars[1];
+ argvars[0].v_type = VAR_UNKNOWN;
+ nlua_typval_call(funcname, len, argvars, 0, rettv);
+ return OK;
+}
+
+/// Call a function without arguments, partial or dict.
+/// This is like call_func() when the call is only "FuncName()".
+/// To be used by "expr" options.
+/// Returns NOTDONE when the function could not be found.
+///
+/// @param funcname name of the function
+/// @param len length of "name"
+/// @param rettv return value goes here
+int call_simple_func(const char *funcname, size_t len, typval_T *rettv)
+ FUNC_ATTR_NONNULL_ALL
+{
+ int ret = FAIL;
+
+ rettv->v_type = VAR_NUMBER; // default rettv is number zero
+ rettv->vval.v_number = 0;
+
+ // Make a copy of the name, an option can be changed in the function.
+ char *name = xstrnsave(funcname, len);
+
+ int error = FCERR_NONE;
+ char *tofree = NULL;
+ char fname_buf[FLEN_FIXED + 1];
+ char *fname = fname_trans_sid(name, fname_buf, &tofree, &error);
+
+ // Skip "g:" before a function name.
+ bool is_global = fname[0] == 'g' && fname[1] == ':';
+ char *rfname = is_global ? fname + 2 : fname;
+
+ ufunc_T *fp = find_func(rfname);
+ if (fp == NULL) {
+ ret = NOTDONE;
+ } else if (fp != NULL && (fp->uf_flags & FC_DELETED)) {
+ error = FCERR_DELETED;
+ } else if (fp != NULL) {
+ typval_T argvars[1];
+ argvars[0].v_type = VAR_UNKNOWN;
+ funcexe_T funcexe = FUNCEXE_INIT;
+ funcexe.fe_evaluate = true;
+
+ error = call_user_func_check(fp, 0, argvars, rettv, &funcexe, NULL);
+ if (error == FCERR_NONE) {
+ ret = OK;
+ }
+ }
+
+ user_func_error(error, name, false);
+ xfree(tofree);
+ xfree(name);
+
+ return ret;
+}
+
char *printable_func_name(ufunc_T *fp)
{
return fp->uf_name_exp != NULL ? fp->uf_name_exp : fp->uf_name;
@@ -1828,7 +1928,7 @@ static int list_func_head(ufunc_T *fp, bool indent, bool force)
}
/// Get a function name, translating "<SID>" and "<SNR>".
-/// Also handles a Funcref in a List or Dictionary.
+/// Also handles a Funcref in a List or Dict.
/// flags:
/// TFN_INT: internal function name OK
/// TFN_QUIET: be quiet
@@ -2051,7 +2151,7 @@ char *get_scriptlocal_funcname(char *funcname)
if (strncmp(funcname, "s:", 2) != 0
&& strncmp(funcname, "<SID>", 5) != 0) {
- // The function name is not a script-local function name
+ // The function name does not have a script-local prefix.
return NULL;
}
@@ -2067,7 +2167,7 @@ char *get_scriptlocal_funcname(char *funcname)
const int off = *funcname == 's' ? 2 : 5;
char *newname = xmalloc(strlen(sid_buf) + strlen(funcname + off) + 1);
STRCPY(newname, sid_buf);
- STRCAT(newname, funcname + off);
+ strcat(newname, funcname + off);
return newname;
}
@@ -3148,7 +3248,7 @@ static int ex_call_inner(exarg_T *eap, char *name, char **arg, char *startarg,
break;
}
- // Handle a function returning a Funcref, Dictionary or List.
+ // Handle a function returning a Funcref, Dict or List.
if (handle_subscript((const char **)arg, &rettv, &EVALARG_EVALUATE, true) == FAIL) {
failed = true;
break;
@@ -3215,7 +3315,7 @@ static int ex_defer_inner(char *name, char **arg, const partial_T *const partial
if (ufunc != NULL) {
int error = check_user_func_argcount(ufunc, argcount);
if (error != FCERR_UNKNOWN) {
- user_func_error(error, name, NULL);
+ user_func_error(error, name, false);
r = FAIL;
}
}
diff --git a/src/nvim/eval/userfunc.h b/src/nvim/eval/userfunc.h
index b3488b15a7..0a27403a4b 100644
--- a/src/nvim/eval/userfunc.h
+++ b/src/nvim/eval/userfunc.h
@@ -32,9 +32,9 @@
/// Structure used by trans_function_name()
typedef struct {
- dict_T *fd_dict; ///< Dictionary used.
- char *fd_newkey; ///< New key in "dict" in allocated memory.
- dictitem_T *fd_di; ///< Dictionary item used.
+ dict_T *fd_dict; ///< Dict used.
+ char *fd_newkey; ///< New key in "dict" in allocated memory.
+ dictitem_T *fd_di; ///< Dict item used.
} funcdict_T;
typedef struct funccal_entry funccal_entry_T;
@@ -69,7 +69,7 @@ typedef struct {
bool *fe_doesrange; ///< [out] if not NULL: function handled range
bool fe_evaluate; ///< actually evaluate expressions
partial_T *fe_partial; ///< for extra arguments
- dict_T *fe_selfdict; ///< Dictionary for "self"
+ dict_T *fe_selfdict; ///< Dict for "self"
typval_T *fe_basetv; ///< base for base->method()
bool fe_found_var; ///< if the function is not found then give an
///< error that a variable is not callable.
diff --git a/src/nvim/eval/vars.c b/src/nvim/eval/vars.c
index 2eca209ea3..0e0088bfad 100644
--- a/src/nvim/eval/vars.c
+++ b/src/nvim/eval/vars.c
@@ -15,6 +15,7 @@
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/funcs.h"
@@ -88,7 +89,7 @@ char *eval_one_expr_in_str(char *p, garray_T *gap, bool evaluate)
}
if (evaluate) {
*block_end = NUL;
- char *expr_val = eval_to_string(block_start, false);
+ char *expr_val = eval_to_string(block_start, false, false);
*block_end = '}';
if (expr_val == NULL) {
return NULL;
@@ -104,7 +105,7 @@ char *eval_one_expr_in_str(char *p, garray_T *gap, bool evaluate)
/// string in allocated memory. "{{" is reduced to "{" and "}}" to "}".
/// Used for a heredoc assignment.
/// Returns NULL for an error.
-char *eval_all_expr_in_str(char *str)
+static char *eval_all_expr_in_str(char *str)
{
garray_T ga;
ga_init(&ga, 1, 80);
@@ -1112,7 +1113,7 @@ static int do_unlet_var(lval_T *lp, char *name_end, exarg_T *eap, int deep FUNC_
// unlet a List item.
tv_list_item_remove(lp->ll_list, lp->ll_li);
} else {
- // unlet a Dictionary item.
+ // unlet a Dict item.
dict_T *d = lp->ll_dict;
assert(d != NULL);
dictitem_T *di = lp->ll_di;
@@ -1287,7 +1288,7 @@ static int do_lock_var(lval_T *lp, char *name_end FUNC_ATTR_UNUSED, exarg_T *eap
// (un)lock a List item.
tv_item_lock(TV_LIST_ITEM_TV(lp->ll_li), deep, lock, false);
} else {
- // (un)lock a Dictionary item.
+ // (un)lock a Dict item.
tv_item_lock(&lp->ll_di->di_tv, deep, lock, false);
}
diff --git a/src/nvim/eval/window.c b/src/nvim/eval/window.c
index 68de40f983..86495f1cb6 100644
--- a/src/nvim/eval/window.c
+++ b/src/nvim/eval/window.c
@@ -10,6 +10,7 @@
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/cursor.h"
+#include "nvim/errors.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -271,7 +272,7 @@ static int get_winnr(tabpage_T *tp, typval_T *argvar)
// if count is not specified, default to 1
count = 1;
}
- if (endp != NULL && *endp != '\0') {
+ if (endp != NULL && *endp != NUL) {
if (strequal(endp, "j")) {
twin = win_vert_neighbor(tp, twin, false, count);
} else if (strequal(endp, "k")) {
diff --git a/src/nvim/eval_defs.h b/src/nvim/eval_defs.h
index 4bbebb14f5..7adea6dfa9 100644
--- a/src/nvim/eval_defs.h
+++ b/src/nvim/eval_defs.h
@@ -9,7 +9,6 @@ typedef enum {
kMPInteger,
kMPFloat,
kMPString,
- kMPBinary,
kMPArray,
kMPMap,
kMPExt,
diff --git a/src/nvim/event/defs.h b/src/nvim/event/defs.h
index 9b7d8708be..20724f9263 100644
--- a/src/nvim/event/defs.h
+++ b/src/nvim/event/defs.h
@@ -6,7 +6,6 @@
#include <uv.h>
#include "nvim/eval/typval_defs.h"
-#include "nvim/rbuffer_defs.h"
#include "nvim/types_defs.h"
enum { EVENT_HANDLER_MAX_ARGC = 10, };
@@ -55,14 +54,17 @@ struct wbuffer {
};
typedef struct stream Stream;
-/// Type of function called when the Stream buffer is filled with data
+typedef struct rstream RStream;
+/// Type of function called when the RStream buffer is filled with data
///
/// @param stream The Stream instance
-/// @param buf The associated RBuffer instance
+/// @param read_data data that was read
/// @param count Number of bytes that was read.
/// @param data User-defined data
/// @param eof If the stream reached EOF.
-typedef void (*stream_read_cb)(Stream *stream, RBuffer *buf, size_t count, void *data, bool eof);
+/// @return number of bytes which were consumed
+typedef size_t (*stream_read_cb)(RStream *stream, const char *read_data, size_t count, void *data,
+ bool eof);
/// Type of function called when the Stream has information about a write
/// request.
@@ -71,11 +73,11 @@ typedef void (*stream_read_cb)(Stream *stream, RBuffer *buf, size_t count, void
/// @param data User-defined data
/// @param status 0 on success, anything else indicates failure
typedef void (*stream_write_cb)(Stream *stream, void *data, int status);
+
typedef void (*stream_close_cb)(Stream *stream, void *data);
struct stream {
bool closed;
- bool did_eof;
union {
uv_pipe_t pipe;
uv_tcp_t tcp;
@@ -84,21 +86,33 @@ struct stream {
uv_tty_t tty;
#endif
} uv;
- uv_stream_t *uvstream;
- uv_buf_t uvbuf;
- RBuffer *buffer;
- uv_file fd;
- stream_read_cb read_cb;
- stream_write_cb write_cb;
+ uv_stream_t *uvstream; ///< NULL when the stream is a file
+ uv_file fd; ///< When the stream is a file, this is its file descriptor
+ int64_t fpos; ///< When the stream is a file, this is the position in file
void *cb_data;
stream_close_cb close_cb, internal_close_cb;
void *close_cb_data, *internal_data;
- size_t fpos;
+ size_t pending_reqs;
+ MultiQueue *events;
+
+ // only used for writing:
+ stream_write_cb write_cb;
size_t curmem;
size_t maxmem;
- size_t pending_reqs;
+};
+
+struct rstream {
+ Stream s;
+ bool did_eof;
+ bool want_read;
+ bool pending_read;
+ bool paused_full;
+ char *buffer; // ARENA_BLOCK_SIZE
+ char *read_pos;
+ char *write_pos;
+ uv_buf_t uvbuf;
+ stream_read_cb read_cb;
size_t num_bytes;
- MultiQueue *events;
};
#define ADDRESS_MAX_SIZE 256
@@ -128,29 +142,31 @@ struct socket_watcher {
};
typedef enum {
- kProcessTypeUv,
- kProcessTypePty,
-} ProcessType;
+ kProcTypeUv,
+ kProcTypePty,
+} ProcType;
-typedef struct process Process;
-typedef void (*process_exit_cb)(Process *proc, int status, void *data);
-typedef void (*internal_process_cb)(Process *proc);
+/// OS process
+typedef struct proc Proc;
+typedef void (*proc_exit_cb)(Proc *proc, int status, void *data);
+typedef void (*internal_proc_cb)(Proc *proc);
-struct process {
- ProcessType type;
+struct proc {
+ ProcType type;
Loop *loop;
void *data;
int pid, status, refcount;
uint8_t exit_signal; // Signal used when killing (on Windows).
- uint64_t stopped_time; // process_stop() timestamp
+ uint64_t stopped_time; // proc_stop() timestamp
const char *cwd;
char **argv;
const char *exepath;
dict_T *env;
- Stream in, out, err;
- /// Exit handler. If set, user must call process_free().
- process_exit_cb cb;
- internal_process_cb internal_exit_cb, internal_close_cb;
+ Stream in;
+ RStream out, err;
+ /// Exit handler. If set, user must call proc_free().
+ proc_exit_cb cb;
+ internal_proc_cb internal_exit_cb, internal_close_cb;
bool closed, detach, overlapped, fwd_err;
MultiQueue *events;
};
diff --git a/src/nvim/event/libuv_process.c b/src/nvim/event/libuv_proc.c
index f77d686c10..5b445cdda7 100644
--- a/src/nvim/event/libuv_process.c
+++ b/src/nvim/event/libuv_proc.c
@@ -5,9 +5,9 @@
#include "nvim/eval/typval.h"
#include "nvim/event/defs.h"
-#include "nvim/event/libuv_process.h"
+#include "nvim/event/libuv_proc.h"
#include "nvim/event/loop.h"
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
#include "nvim/log.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
@@ -15,15 +15,15 @@
#include "nvim/ui_client.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "event/libuv_process.c.generated.h"
+# include "event/libuv_proc.c.generated.h"
#endif
/// @returns zero on success, or negative error code
-int libuv_process_spawn(LibuvProcess *uvproc)
+int libuv_proc_spawn(LibuvProc *uvproc)
FUNC_ATTR_NONNULL_ALL
{
- Process *proc = (Process *)uvproc;
- uvproc->uvopts.file = process_get_exepath(proc);
+ Proc *proc = (Proc *)uvproc;
+ uvproc->uvopts.file = proc_get_exepath(proc);
uvproc->uvopts.args = proc->argv;
uvproc->uvopts.flags = UV_PROCESS_WINDOWS_HIDE;
#ifdef MSWIN
@@ -70,19 +70,19 @@ int libuv_process_spawn(LibuvProcess *uvproc)
uvproc->uvstdio[0].data.stream = (uv_stream_t *)(&proc->in.uv.pipe);
}
- if (!proc->out.closed) {
+ if (!proc->out.s.closed) {
uvproc->uvstdio[1].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
#ifdef MSWIN
// pipe must be readable for IOCP to work on Windows.
uvproc->uvstdio[1].flags |= proc->overlapped
? (UV_READABLE_PIPE | UV_OVERLAPPED_PIPE) : 0;
#endif
- uvproc->uvstdio[1].data.stream = (uv_stream_t *)(&proc->out.uv.pipe);
+ uvproc->uvstdio[1].data.stream = (uv_stream_t *)(&proc->out.s.uv.pipe);
}
- if (!proc->err.closed) {
+ if (!proc->err.s.closed) {
uvproc->uvstdio[2].flags = UV_CREATE_PIPE | UV_WRITABLE_PIPE;
- uvproc->uvstdio[2].data.stream = (uv_stream_t *)(&proc->err.uv.pipe);
+ uvproc->uvstdio[2].data.stream = (uv_stream_t *)(&proc->err.s.uv.pipe);
} else if (proc->fwd_err) {
uvproc->uvstdio[2].flags = UV_INHERIT_FD;
uvproc->uvstdio[2].data.fd = STDERR_FILENO;
@@ -101,7 +101,7 @@ int libuv_process_spawn(LibuvProcess *uvproc)
return status;
}
-void libuv_process_close(LibuvProcess *uvproc)
+void libuv_proc_close(LibuvProc *uvproc)
FUNC_ATTR_NONNULL_ARG(1)
{
uv_close((uv_handle_t *)&uvproc->uv, close_cb);
@@ -109,11 +109,11 @@ void libuv_process_close(LibuvProcess *uvproc)
static void close_cb(uv_handle_t *handle)
{
- Process *proc = handle->data;
+ Proc *proc = handle->data;
if (proc->internal_close_cb) {
proc->internal_close_cb(proc);
}
- LibuvProcess *uvproc = (LibuvProcess *)proc;
+ LibuvProc *uvproc = (LibuvProc *)proc;
if (uvproc->uvopts.env) {
os_free_fullenv(uvproc->uvopts.env);
}
@@ -121,7 +121,7 @@ static void close_cb(uv_handle_t *handle)
static void exit_cb(uv_process_t *handle, int64_t status, int term_signal)
{
- Process *proc = handle->data;
+ Proc *proc = handle->data;
#if defined(MSWIN)
// Use stored/expected signal.
term_signal = proc->exit_signal;
@@ -130,10 +130,10 @@ static void exit_cb(uv_process_t *handle, int64_t status, int term_signal)
proc->internal_exit_cb(proc);
}
-LibuvProcess libuv_process_init(Loop *loop, void *data)
+LibuvProc libuv_proc_init(Loop *loop, void *data)
{
- LibuvProcess rv = {
- .process = process_init(loop, kProcessTypeUv, data)
+ LibuvProc rv = {
+ .proc = proc_init(loop, kProcTypeUv, data)
};
return rv;
}
diff --git a/src/nvim/event/libuv_process.h b/src/nvim/event/libuv_proc.h
index 12401dbb35..3127e166c0 100644
--- a/src/nvim/event/libuv_process.h
+++ b/src/nvim/event/libuv_proc.h
@@ -5,12 +5,12 @@
#include "nvim/event/defs.h"
typedef struct {
- Process process;
+ Proc proc;
uv_process_t uv;
uv_process_options_t uvopts;
uv_stdio_container_t uvstdio[4];
-} LibuvProcess;
+} LibuvProc;
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "event/libuv_process.h.generated.h"
+# include "event/libuv_proc.h.generated.h"
#endif
diff --git a/src/nvim/event/loop.c b/src/nvim/event/loop.c
index e1ebcecbd6..15d993cc62 100644
--- a/src/nvim/event/loop.c
+++ b/src/nvim/event/loop.c
@@ -20,7 +20,7 @@ void loop_init(Loop *loop, void *data)
loop->recursive = 0;
loop->closing = false;
loop->uv.data = loop;
- loop->children = kl_init(WatcherPtr);
+ kv_init(loop->children);
loop->events = multiqueue_new_parent(loop_on_put, loop);
loop->fast_events = multiqueue_new_child(loop->events);
loop->thread_events = multiqueue_new_parent(NULL, NULL);
@@ -187,7 +187,7 @@ bool loop_close(Loop *loop, bool wait)
multiqueue_free(loop->fast_events);
multiqueue_free(loop->thread_events);
multiqueue_free(loop->events);
- kl_destroy(WatcherPtr, loop->children);
+ kv_destroy(loop->children);
return rv;
}
diff --git a/src/nvim/event/loop.h b/src/nvim/event/loop.h
index 6ecc7cb781..563b254a0b 100644
--- a/src/nvim/event/loop.h
+++ b/src/nvim/event/loop.h
@@ -3,32 +3,26 @@
#include <stdbool.h>
#include <uv.h>
-#include "klib/klist.h"
+#include "klib/kvec.h"
#include "nvim/event/defs.h" // IWYU pragma: keep
#include "nvim/types_defs.h" // IWYU pragma: keep
-typedef void *WatcherPtr;
-
-#define _NOOP(x)
-KLIST_INIT(WatcherPtr, WatcherPtr, _NOOP)
-
struct loop {
uv_loop_t uv;
MultiQueue *events;
MultiQueue *thread_events;
- // Immediate events:
- // "Processed after exiting uv_run() (to avoid recursion), but before
- // returning from loop_poll_events()." 502aee690c98
- // Practical consequence (for main_loop): these events are processed by
- // state_enter()..os_inchar()
- // whereas "regular" events (main_loop.events) are processed by
- // state_enter()..VimState.execute()
- // But state_enter()..os_inchar() can be "too early" if you want the event
- // to trigger UI updates and other user-activity-related side-effects.
+ // Immediate events.
+ // - "Processed after exiting `uv_run()` (to avoid recursion), but before returning from
+ // `loop_poll_events()`." 502aee690c98
+ // - Practical consequence (for `main_loop`):
+ // - these are processed by `state_enter()..input_get()` whereas "regular" events
+ // (`main_loop.events`) are processed by `state_enter()..VimState.execute()`
+ // - `state_enter()..input_get()` can be "too early" if you want the event to trigger UI
+ // updates and other user-activity-related side-effects.
MultiQueue *fast_events;
// used by process/job-control subsystem
- klist_t(WatcherPtr) *children;
+ kvec_t(Proc *) children;
uv_signal_t children_watcher;
uv_timer_t children_kill_timer;
diff --git a/src/nvim/event/process.c b/src/nvim/event/proc.c
index 7460e92766..5ae3bd8c2d 100644
--- a/src/nvim/event/process.c
+++ b/src/nvim/event/proc.c
@@ -3,24 +3,24 @@
#include <signal.h>
#include <uv.h>
-#include "klib/klist.h"
-#include "nvim/event/libuv_process.h"
+#include "nvim/event/libuv_proc.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
+#include "nvim/event/rstream.h"
#include "nvim/event/stream.h"
+#include "nvim/event/wstream.h"
#include "nvim/globals.h"
#include "nvim/log.h"
#include "nvim/main.h"
-#include "nvim/os/process.h"
-#include "nvim/os/pty_process.h"
+#include "nvim/os/proc.h"
+#include "nvim/os/pty_proc.h"
#include "nvim/os/shell.h"
#include "nvim/os/time.h"
-#include "nvim/rbuffer_defs.h"
#include "nvim/ui_client.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "event/process.c.generated.h"
+# include "event/proc.c.generated.h"
#endif
// Time for a process to exit cleanly before we send KILL.
@@ -32,13 +32,13 @@
void __gcov_flush(void);
#endif
-static bool process_is_tearing_down = false;
+static bool proc_is_tearing_down = false;
// Delay exit until handles are closed, to avoid deadlocks
static int exit_need_delay = 0;
/// @returns zero on success, or negative error code
-int process_spawn(Process *proc, bool in, bool out, bool err)
+int proc_spawn(Proc *proc, bool in, bool out, bool err)
FUNC_ATTR_NONNULL_ALL
{
// forwarding stderr contradicts with processing it internally
@@ -51,15 +51,15 @@ int process_spawn(Process *proc, bool in, bool out, bool err)
}
if (out) {
- uv_pipe_init(&proc->loop->uv, &proc->out.uv.pipe, 0);
+ uv_pipe_init(&proc->loop->uv, &proc->out.s.uv.pipe, 0);
} else {
- proc->out.closed = true;
+ proc->out.s.closed = true;
}
if (err) {
- uv_pipe_init(&proc->loop->uv, &proc->err.uv.pipe, 0);
+ uv_pipe_init(&proc->loop->uv, &proc->err.s.uv.pipe, 0);
} else {
- proc->err.closed = true;
+ proc->err.s.closed = true;
}
#ifdef USE_GCOV
@@ -69,11 +69,11 @@ int process_spawn(Process *proc, bool in, bool out, bool err)
int status;
switch (proc->type) {
- case kProcessTypeUv:
- status = libuv_process_spawn((LibuvProcess *)proc);
+ case kProcTypeUv:
+ status = libuv_proc_spawn((LibuvProc *)proc);
break;
- case kProcessTypePty:
- status = pty_process_spawn((PtyProcess *)proc);
+ case kProcTypePty:
+ status = pty_proc_spawn((PtyProc *)proc);
break;
}
@@ -82,18 +82,18 @@ int process_spawn(Process *proc, bool in, bool out, bool err)
uv_close((uv_handle_t *)&proc->in.uv.pipe, NULL);
}
if (out) {
- uv_close((uv_handle_t *)&proc->out.uv.pipe, NULL);
+ uv_close((uv_handle_t *)&proc->out.s.uv.pipe, NULL);
}
if (err) {
- uv_close((uv_handle_t *)&proc->err.uv.pipe, NULL);
+ uv_close((uv_handle_t *)&proc->err.s.uv.pipe, NULL);
}
- if (proc->type == kProcessTypeUv) {
- uv_close((uv_handle_t *)&(((LibuvProcess *)proc)->uv), NULL);
+ if (proc->type == kProcTypeUv) {
+ uv_close((uv_handle_t *)&(((LibuvProc *)proc)->uv), NULL);
} else {
- process_close(proc);
+ proc_close(proc);
}
- process_free(proc);
+ proc_free(proc);
proc->status = -1;
return status;
}
@@ -101,56 +101,56 @@ int process_spawn(Process *proc, bool in, bool out, bool err)
if (in) {
stream_init(NULL, &proc->in, -1, (uv_stream_t *)&proc->in.uv.pipe);
proc->in.internal_data = proc;
- proc->in.internal_close_cb = on_process_stream_close;
+ proc->in.internal_close_cb = on_proc_stream_close;
proc->refcount++;
}
if (out) {
- stream_init(NULL, &proc->out, -1, (uv_stream_t *)&proc->out.uv.pipe);
- proc->out.internal_data = proc;
- proc->out.internal_close_cb = on_process_stream_close;
+ stream_init(NULL, &proc->out.s, -1, (uv_stream_t *)&proc->out.s.uv.pipe);
+ proc->out.s.internal_data = proc;
+ proc->out.s.internal_close_cb = on_proc_stream_close;
proc->refcount++;
}
if (err) {
- stream_init(NULL, &proc->err, -1, (uv_stream_t *)&proc->err.uv.pipe);
- proc->err.internal_data = proc;
- proc->err.internal_close_cb = on_process_stream_close;
+ stream_init(NULL, &proc->err.s, -1, (uv_stream_t *)&proc->err.s.uv.pipe);
+ proc->err.s.internal_data = proc;
+ proc->err.s.internal_close_cb = on_proc_stream_close;
proc->refcount++;
}
- proc->internal_exit_cb = on_process_exit;
+ proc->internal_exit_cb = on_proc_exit;
proc->internal_close_cb = decref;
proc->refcount++;
- kl_push(WatcherPtr, proc->loop->children, proc);
- DLOG("new: pid=%d exepath=[%s]", proc->pid, process_get_exepath(proc));
+ kv_push(proc->loop->children, proc);
+ DLOG("new: pid=%d exepath=[%s]", proc->pid, proc_get_exepath(proc));
return 0;
}
-void process_teardown(Loop *loop) FUNC_ATTR_NONNULL_ALL
+void proc_teardown(Loop *loop) FUNC_ATTR_NONNULL_ALL
{
- process_is_tearing_down = true;
- kl_iter(WatcherPtr, loop->children, current) {
- Process *proc = (*current)->data;
- if (proc->detach || proc->type == kProcessTypePty) {
+ proc_is_tearing_down = true;
+ for (size_t i = 0; i < kv_size(loop->children); i++) {
+ Proc *proc = kv_A(loop->children, i);
+ if (proc->detach || proc->type == kProcTypePty) {
// Close handles to process without killing it.
- CREATE_EVENT(loop->events, process_close_handles, proc);
+ CREATE_EVENT(loop->events, proc_close_handles, proc);
} else {
- process_stop(proc);
+ proc_stop(proc);
}
}
// Wait until all children exit and all close events are processed.
LOOP_PROCESS_EVENTS_UNTIL(loop, loop->events, -1,
- kl_empty(loop->children) && multiqueue_empty(loop->events));
- pty_process_teardown(loop);
+ kv_size(loop->children) == 0 && multiqueue_empty(loop->events));
+ pty_proc_teardown(loop);
}
-void process_close_streams(Process *proc) FUNC_ATTR_NONNULL_ALL
+void proc_close_streams(Proc *proc) FUNC_ATTR_NONNULL_ALL
{
- stream_may_close(&proc->in);
- stream_may_close(&proc->out);
- stream_may_close(&proc->err);
+ wstream_may_close(&proc->in);
+ rstream_may_close(&proc->out);
+ rstream_may_close(&proc->err);
}
/// Synchronously wait for a process to finish
@@ -161,7 +161,7 @@ void process_close_streams(Process *proc) FUNC_ATTR_NONNULL_ALL
/// @return Exit code of the process. proc->status will have the same value.
/// -1 if the timeout expired while the process is still running.
/// -2 if the user interrupted the wait.
-int process_wait(Process *proc, int ms, MultiQueue *events)
+int proc_wait(Proc *proc, int ms, MultiQueue *events)
FUNC_ATTR_NONNULL_ARG(1)
{
if (!proc->refcount) {
@@ -185,7 +185,7 @@ int process_wait(Process *proc, int ms, MultiQueue *events)
// Assume that a user hitting CTRL-C does not like the current job. Kill it.
if (got_int) {
got_int = false;
- process_stop(proc);
+ proc_stop(proc);
if (ms == -1) {
// We can only return if all streams/handles are closed and the job
// exited.
@@ -213,7 +213,7 @@ int process_wait(Process *proc, int ms, MultiQueue *events)
}
/// Ask a process to terminate and eventually kill if it doesn't respond
-void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL
+void proc_stop(Proc *proc) FUNC_ATTR_NONNULL_ALL
{
bool exited = (proc->status >= 0);
if (exited || proc->stopped_time) {
@@ -223,13 +223,13 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL
proc->exit_signal = SIGTERM;
switch (proc->type) {
- case kProcessTypeUv:
+ case kProcTypeUv:
os_proc_tree_kill(proc->pid, SIGTERM);
break;
- case kProcessTypePty:
+ case kProcTypePty:
// close all streams for pty processes to send SIGHUP to the process
- process_close_streams(proc);
- pty_process_close_master((PtyProcess *)proc);
+ proc_close_streams(proc);
+ pty_proc_close_master((PtyProc *)proc);
break;
}
@@ -239,7 +239,7 @@ void process_stop(Process *proc) FUNC_ATTR_NONNULL_ALL
}
/// Frees process-owned resources.
-void process_free(Process *proc) FUNC_ATTR_NONNULL_ALL
+void proc_free(Proc *proc) FUNC_ATTR_NONNULL_ALL
{
if (proc->argv != NULL) {
shell_free_argv(proc->argv);
@@ -248,19 +248,19 @@ void process_free(Process *proc) FUNC_ATTR_NONNULL_ALL
}
/// Sends SIGKILL (or SIGTERM..SIGKILL for PTY jobs) to processes that did
-/// not terminate after process_stop().
+/// not terminate after proc_stop().
static void children_kill_cb(uv_timer_t *handle)
{
Loop *loop = handle->loop->data;
- kl_iter(WatcherPtr, loop->children, current) {
- Process *proc = (*current)->data;
+ for (size_t i = 0; i < kv_size(loop->children); i++) {
+ Proc *proc = kv_A(loop->children, i);
bool exited = (proc->status >= 0);
if (exited || !proc->stopped_time) {
continue;
}
uint64_t term_sent = UINT64_MAX == proc->stopped_time;
- if (kProcessTypePty != proc->type || term_sent) {
+ if (kProcTypePty != proc->type || term_sent) {
proc->exit_signal = SIGKILL;
os_proc_tree_kill(proc->pid, SIGKILL);
} else {
@@ -274,41 +274,45 @@ static void children_kill_cb(uv_timer_t *handle)
}
}
-static void process_close_event(void **argv)
+static void proc_close_event(void **argv)
{
- Process *proc = argv[0];
+ Proc *proc = argv[0];
if (proc->cb) {
// User (hint: channel_job_start) is responsible for calling
- // process_free().
+ // proc_free().
proc->cb(proc, proc->status, proc->data);
} else {
- process_free(proc);
+ proc_free(proc);
}
}
-static void decref(Process *proc)
+static void decref(Proc *proc)
{
if (--proc->refcount != 0) {
return;
}
Loop *loop = proc->loop;
- kliter_t(WatcherPtr) **node = NULL;
- kl_iter(WatcherPtr, loop->children, current) {
- if ((*current)->data == proc) {
- node = current;
+ size_t i;
+ for (i = 0; i < kv_size(loop->children); i++) {
+ Proc *current = kv_A(loop->children, i);
+ if (current == proc) {
break;
}
}
- assert(node);
- kl_shift_at(WatcherPtr, loop->children, node);
- CREATE_EVENT(proc->events, process_close_event, proc);
+ assert(i < kv_size(loop->children)); // element found
+ if (i < kv_size(loop->children) - 1) {
+ memmove(&kv_A(loop->children, i), &kv_A(loop->children, i + 1),
+ sizeof(&kv_A(loop->children, i)) * (kv_size(loop->children) - (i + 1)));
+ }
+ kv_size(loop->children)--;
+ CREATE_EVENT(proc->events, proc_close_event, proc);
}
-static void process_close(Process *proc)
+static void proc_close(Proc *proc)
FUNC_ATTR_NONNULL_ARG(1)
{
- if (process_is_tearing_down && (proc->detach || proc->type == kProcessTypePty)
+ if (proc_is_tearing_down && (proc->detach || proc->type == kProcTypePty)
&& proc->closed) {
// If a detached/pty process dies while tearing down it might get closed
// twice.
@@ -318,17 +322,17 @@ static void process_close(Process *proc)
proc->closed = true;
if (proc->detach) {
- if (proc->type == kProcessTypeUv) {
- uv_unref((uv_handle_t *)&(((LibuvProcess *)proc)->uv));
+ if (proc->type == kProcTypeUv) {
+ uv_unref((uv_handle_t *)&(((LibuvProc *)proc)->uv));
}
}
switch (proc->type) {
- case kProcessTypeUv:
- libuv_process_close((LibuvProcess *)proc);
+ case kProcTypeUv:
+ libuv_proc_close((LibuvProc *)proc);
break;
- case kProcessTypePty:
- pty_process_close((PtyProcess *)proc);
+ case kProcTypePty:
+ pty_proc_close((PtyProc *)proc);
break;
}
}
@@ -337,10 +341,10 @@ static void process_close(Process *proc)
///
/// @param proc Process, for which an output stream should be flushed.
/// @param stream Stream to flush.
-static void flush_stream(Process *proc, Stream *stream)
+static void flush_stream(Proc *proc, RStream *stream)
FUNC_ATTR_NONNULL_ARG(1)
{
- if (!stream || stream->closed) {
+ if (!stream || stream->s.closed) {
return;
}
@@ -350,23 +354,23 @@ static void flush_stream(Process *proc, Stream *stream)
// keeps sending data, we only accept as much data as the system buffer size.
// Otherwise this would block cleanup/teardown.
int system_buffer_size = 0;
- int err = uv_recv_buffer_size((uv_handle_t *)&stream->uv.pipe,
+ int err = uv_recv_buffer_size((uv_handle_t *)&stream->s.uv.pipe,
&system_buffer_size);
if (err) {
- system_buffer_size = (int)rbuffer_capacity(stream->buffer);
+ system_buffer_size = ARENA_BLOCK_SIZE;
}
size_t max_bytes = stream->num_bytes + (size_t)system_buffer_size;
// Read remaining data.
- while (!stream->closed && stream->num_bytes < max_bytes) {
+ while (!stream->s.closed && stream->num_bytes < max_bytes) {
// Remember number of bytes before polling
size_t num_bytes = stream->num_bytes;
// Poll for data and process the generated events.
loop_poll_events(proc->loop, 0);
- if (stream->events) {
- multiqueue_process_events(stream->events);
+ if (stream->s.events) {
+ multiqueue_process_events(stream->s.events);
}
// Stream can be closed if it is empty.
@@ -374,23 +378,23 @@ static void flush_stream(Process *proc, Stream *stream)
if (stream->read_cb && !stream->did_eof) {
// Stream callback could miss EOF handling if a child keeps the stream
// open. But only send EOF if we haven't already.
- stream->read_cb(stream, stream->buffer, 0, stream->cb_data, true);
+ stream->read_cb(stream, stream->buffer, 0, stream->s.cb_data, true);
}
break;
}
}
}
-static void process_close_handles(void **argv)
+static void proc_close_handles(void **argv)
{
- Process *proc = argv[0];
+ Proc *proc = argv[0];
exit_need_delay++;
flush_stream(proc, &proc->out);
flush_stream(proc, &proc->err);
- process_close_streams(proc);
- process_close(proc);
+ proc_close_streams(proc);
+ proc_close(proc);
exit_need_delay--;
}
@@ -425,7 +429,7 @@ void exit_from_channel(int status)
multiqueue_put(main_loop.fast_events, exit_event, (void *)(intptr_t)status);
}
-static void on_process_exit(Process *proc)
+static void on_proc_exit(Proc *proc)
{
Loop *loop = proc->loop;
ILOG("exited: pid=%d status=%d stoptime=%" PRIu64, proc->pid, proc->status,
@@ -438,13 +442,13 @@ static void on_process_exit(Process *proc)
// Process has terminated, but there could still be data to be read from the
// OS. We are still in the libuv loop, so we cannot call code that polls for
// more data directly. Instead delay the reading after the libuv loop by
- // queueing process_close_handles() as an event.
+ // queueing proc_close_handles() as an event.
MultiQueue *queue = proc->events ? proc->events : loop->events;
- CREATE_EVENT(queue, process_close_handles, proc);
+ CREATE_EVENT(queue, proc_close_handles, proc);
}
-static void on_process_stream_close(Stream *stream, void *data)
+static void on_proc_stream_close(Stream *stream, void *data)
{
- Process *proc = data;
+ Proc *proc = data;
decref(proc);
}
diff --git a/src/nvim/event/process.h b/src/nvim/event/proc.h
index 421a470244..f525d46f87 100644
--- a/src/nvim/event/process.h
+++ b/src/nvim/event/proc.h
@@ -6,9 +6,9 @@
#include "nvim/event/defs.h" // IWYU pragma: keep
#include "nvim/types_defs.h"
-static inline Process process_init(Loop *loop, ProcessType type, void *data)
+static inline Proc proc_init(Loop *loop, ProcType type, void *data)
{
- return (Process) {
+ return (Proc) {
.type = type,
.data = data,
.loop = loop,
@@ -21,8 +21,8 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data)
.argv = NULL,
.exepath = NULL,
.in = { .closed = false },
- .out = { .closed = false },
- .err = { .closed = false },
+ .out = { .s.closed = false },
+ .err = { .s.closed = false },
.cb = NULL,
.closed = false,
.internal_close_cb = NULL,
@@ -33,17 +33,17 @@ static inline Process process_init(Loop *loop, ProcessType type, void *data)
}
/// Get the path to the executable of the process.
-static inline const char *process_get_exepath(Process *proc)
+static inline const char *proc_get_exepath(Proc *proc)
{
return proc->exepath != NULL ? proc->exepath : proc->argv[0];
}
-static inline bool process_is_stopped(Process *proc)
+static inline bool proc_is_stopped(Proc *proc)
{
bool exited = (proc->status >= 0);
return exited || (proc->stopped_time != 0);
}
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "event/process.h.generated.h"
+# include "event/proc.h.generated.h"
#endif
diff --git a/src/nvim/event/rstream.c b/src/nvim/event/rstream.c
index 6b4ab472e4..15bdc547d5 100644
--- a/src/nvim/event/rstream.c
+++ b/src/nvim/event/rstream.c
@@ -11,75 +11,80 @@
#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/os/os_defs.h"
-#include "nvim/rbuffer.h"
-#include "nvim/rbuffer_defs.h"
#include "nvim/types_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "event/rstream.c.generated.h"
#endif
-void rstream_init_fd(Loop *loop, Stream *stream, int fd, size_t bufsize)
+void rstream_init_fd(Loop *loop, RStream *stream, int fd)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
- stream_init(loop, stream, fd, NULL);
- rstream_init(stream, bufsize);
+ stream_init(loop, &stream->s, fd, NULL);
+ rstream_init(stream);
}
-void rstream_init_stream(Stream *stream, uv_stream_t *uvstream, size_t bufsize)
+void rstream_init_stream(RStream *stream, uv_stream_t *uvstream)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
- stream_init(NULL, stream, -1, uvstream);
- rstream_init(stream, bufsize);
+ stream_init(NULL, &stream->s, -1, uvstream);
+ rstream_init(stream);
}
-void rstream_init(Stream *stream, size_t bufsize)
+void rstream_init(RStream *stream)
FUNC_ATTR_NONNULL_ARG(1)
{
- stream->buffer = rbuffer_new(bufsize);
- stream->buffer->data = stream;
- stream->buffer->full_cb = on_rbuffer_full;
- stream->buffer->nonfull_cb = on_rbuffer_nonfull;
+ stream->read_cb = NULL;
+ stream->num_bytes = 0;
+ stream->buffer = alloc_block();
+ stream->read_pos = stream->write_pos = stream->buffer;
+}
+
+void rstream_start_inner(RStream *stream)
+ FUNC_ATTR_NONNULL_ARG(1)
+{
+ if (stream->s.uvstream) {
+ uv_read_start(stream->s.uvstream, alloc_cb, read_cb);
+ } else {
+ uv_idle_start(&stream->s.uv.idle, fread_idle_cb);
+ }
}
/// Starts watching for events from a `Stream` instance.
///
/// @param stream The `Stream` instance
-void rstream_start(Stream *stream, stream_read_cb cb, void *data)
+void rstream_start(RStream *stream, stream_read_cb cb, void *data)
FUNC_ATTR_NONNULL_ARG(1)
{
stream->read_cb = cb;
- stream->cb_data = data;
- if (stream->uvstream) {
- uv_read_start(stream->uvstream, alloc_cb, read_cb);
- } else {
- uv_idle_start(&stream->uv.idle, fread_idle_cb);
+ stream->s.cb_data = data;
+ stream->want_read = true;
+ if (!stream->paused_full) {
+ rstream_start_inner(stream);
}
}
/// Stops watching for events from a `Stream` instance.
///
/// @param stream The `Stream` instance
-void rstream_stop(Stream *stream)
+void rstream_stop_inner(RStream *stream)
FUNC_ATTR_NONNULL_ALL
{
- if (stream->uvstream) {
- uv_read_stop(stream->uvstream);
+ if (stream->s.uvstream) {
+ uv_read_stop(stream->s.uvstream);
} else {
- uv_idle_stop(&stream->uv.idle);
+ uv_idle_stop(&stream->s.uv.idle);
}
}
-static void on_rbuffer_full(RBuffer *buf, void *data)
-{
- rstream_stop(data);
-}
-
-static void on_rbuffer_nonfull(RBuffer *buf, void *data)
+/// Stops watching for events from a `Stream` instance.
+///
+/// @param stream The `Stream` instance
+void rstream_stop(RStream *stream)
+ FUNC_ATTR_NONNULL_ALL
{
- Stream *stream = data;
- assert(stream->read_cb);
- rstream_start(stream, stream->read_cb, stream->cb_data);
+ rstream_stop_inner(stream);
+ stream->want_read = false;
}
// Callbacks used by libuv
@@ -87,11 +92,10 @@ static void on_rbuffer_nonfull(RBuffer *buf, void *data)
/// Called by libuv to allocate memory for reading.
static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf)
{
- Stream *stream = handle->data;
- // `uv_buf_t.len` happens to have different size on Windows.
- size_t write_count;
- buf->base = rbuffer_write_ptr(stream->buffer, &write_count);
- buf->len = UV_BUF_LEN(write_count);
+ RStream *stream = handle->data;
+ buf->base = stream->write_pos;
+ // `uv_buf_t.len` happens to have different size on Windows (as a treat)
+ buf->len = UV_BUF_LEN(rstream_space(stream));
}
/// Callback invoked by libuv after it copies the data into the buffer provided
@@ -99,27 +103,27 @@ static void alloc_cb(uv_handle_t *handle, size_t suggested, uv_buf_t *buf)
/// 0-length buffer.
static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf)
{
- Stream *stream = uvstream->data;
+ RStream *stream = uvstream->data;
if (cnt <= 0) {
// cnt == 0 means libuv asked for a buffer and decided it wasn't needed:
// http://docs.libuv.org/en/latest/stream.html#c.uv_read_start.
//
- // We don't need to do anything with the RBuffer because the next call
+ // We don't need to do anything with the buffer because the next call
// to `alloc_cb` will return the same unused pointer (`rbuffer_produced`
// won't be called)
if (cnt == UV_ENOBUFS || cnt == 0) {
return;
} else if (cnt == UV_EOF && uvstream->type == UV_TTY) {
// The TTY driver might signal EOF without closing the stream
- invoke_read_cb(stream, 0, true);
+ invoke_read_cb(stream, true);
} else {
DLOG("closing Stream (%p): %s (%s)", (void *)stream,
uv_err_name((int)cnt), os_strerror((int)cnt));
// Read error or EOF, either way stop the stream and invoke the callback
// with eof == true
uv_read_stop(uvstream);
- invoke_read_cb(stream, 0, true);
+ invoke_read_cb(stream, true);
}
return;
}
@@ -127,10 +131,13 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf)
// at this point we're sure that cnt is positive, no error occurred
size_t nread = (size_t)cnt;
stream->num_bytes += nread;
- // Data was already written, so all we need is to update 'wpos' to reflect
- // the space actually used in the buffer.
- rbuffer_produced(stream->buffer, nread);
- invoke_read_cb(stream, nread, false);
+ stream->write_pos += cnt;
+ invoke_read_cb(stream, false);
+}
+
+static size_t rstream_space(RStream *stream)
+{
+ return (size_t)((stream->buffer + ARENA_BLOCK_SIZE) - stream->write_pos);
}
/// Called by the by the 'idle' handle to emulate a reading event
@@ -141,66 +148,91 @@ static void read_cb(uv_stream_t *uvstream, ssize_t cnt, const uv_buf_t *buf)
static void fread_idle_cb(uv_idle_t *handle)
{
uv_fs_t req;
- Stream *stream = handle->data;
+ RStream *stream = handle->data;
+ stream->uvbuf.base = stream->write_pos;
// `uv_buf_t.len` happens to have different size on Windows.
- size_t write_count;
- stream->uvbuf.base = rbuffer_write_ptr(stream->buffer, &write_count);
- stream->uvbuf.len = UV_BUF_LEN(write_count);
-
- // the offset argument to uv_fs_read is int64_t, could someone really try
- // to read more than 9 quintillion (9e18) bytes?
- // upcast is meant to avoid tautological condition warning on 32 bits
- uintmax_t fpos_intmax = stream->fpos;
- if (fpos_intmax > INT64_MAX) {
- ELOG("stream offset overflow");
- preserve_exit("stream offset overflow");
- }
+ stream->uvbuf.len = UV_BUF_LEN(rstream_space(stream));
// Synchronous read
- uv_fs_read(handle->loop,
- &req,
- stream->fd,
- &stream->uvbuf,
- 1,
- (int64_t)stream->fpos,
- NULL);
+ uv_fs_read(handle->loop, &req, stream->s.fd, &stream->uvbuf, 1, stream->s.fpos, NULL);
uv_fs_req_cleanup(&req);
if (req.result <= 0) {
- uv_idle_stop(&stream->uv.idle);
- invoke_read_cb(stream, 0, true);
+ uv_idle_stop(&stream->s.uv.idle);
+ invoke_read_cb(stream, true);
return;
}
- // no errors (req.result (ssize_t) is positive), it's safe to cast.
- size_t nread = (size_t)req.result;
- rbuffer_produced(stream->buffer, nread);
- stream->fpos += nread;
- invoke_read_cb(stream, nread, false);
+ // no errors (req.result (ssize_t) is positive), it's safe to use.
+ stream->write_pos += req.result;
+ stream->s.fpos += req.result;
+ invoke_read_cb(stream, false);
}
static void read_event(void **argv)
{
- Stream *stream = argv[0];
+ RStream *stream = argv[0];
+ stream->pending_read = false;
if (stream->read_cb) {
- size_t count = (uintptr_t)argv[1];
- bool eof = (uintptr_t)argv[2];
- stream->did_eof = eof;
- stream->read_cb(stream, stream->buffer, count, stream->cb_data, eof);
+ size_t available = rstream_available(stream);
+ size_t consumed = stream->read_cb(stream, stream->read_pos, available, stream->s.cb_data,
+ stream->did_eof);
+ assert(consumed <= available);
+ rstream_consume(stream, consumed);
+ }
+ stream->s.pending_reqs--;
+ if (stream->s.closed && !stream->s.pending_reqs) {
+ stream_close_handle(&stream->s, true);
+ }
+}
+
+size_t rstream_available(RStream *stream)
+{
+ return (size_t)(stream->write_pos - stream->read_pos);
+}
+
+void rstream_consume(RStream *stream, size_t consumed)
+{
+ stream->read_pos += consumed;
+ size_t remaining = (size_t)(stream->write_pos - stream->read_pos);
+ if (remaining > 0 && stream->read_pos > stream->buffer) {
+ memmove(stream->buffer, stream->read_pos, remaining);
+ stream->read_pos = stream->buffer;
+ stream->write_pos = stream->buffer + remaining;
+ } else if (remaining == 0) {
+ stream->read_pos = stream->write_pos = stream->buffer;
}
- stream->pending_reqs--;
- if (stream->closed && !stream->pending_reqs) {
- stream_close_handle(stream);
+
+ if (stream->want_read && stream->paused_full && rstream_space(stream)) {
+ assert(stream->read_cb);
+ stream->paused_full = false;
+ rstream_start_inner(stream);
}
}
-static void invoke_read_cb(Stream *stream, size_t count, bool eof)
+static void invoke_read_cb(RStream *stream, bool eof)
{
+ stream->did_eof |= eof;
+
+ if (!rstream_space(stream)) {
+ rstream_stop_inner(stream);
+ stream->paused_full = true;
+ }
+
+ // we cannot use pending_reqs as a socket can have both pending reads and writes
+ if (stream->pending_read) {
+ return;
+ }
+
// Don't let the stream be closed before the event is processed.
- stream->pending_reqs++;
+ stream->s.pending_reqs++;
+ stream->pending_read = true;
+ CREATE_EVENT(stream->s.events, read_event, stream);
+}
- CREATE_EVENT(stream->events, read_event,
- stream, (void *)(uintptr_t *)count, (void *)(uintptr_t)eof);
+void rstream_may_close(RStream *stream)
+{
+ stream_may_close(&stream->s, true);
}
diff --git a/src/nvim/event/socket.c b/src/nvim/event/socket.c
index 4e878a2ecf..1214c3e336 100644
--- a/src/nvim/event/socket.c
+++ b/src/nvim/event/socket.c
@@ -35,7 +35,7 @@ int socket_watcher_init(Loop *loop, SocketWatcher *watcher, const char *endpoint
if (host_end && addr != host_end) {
// Split user specified address into two strings, addr(hostname) and port.
// The port part in watcher->addr will be updated later.
- *host_end = '\0';
+ *host_end = NUL;
char *port = host_end + 1;
intmax_t iport;
@@ -135,17 +135,17 @@ int socket_watcher_start(SocketWatcher *watcher, int backlog, socket_cb cb)
return 0;
}
-int socket_watcher_accept(SocketWatcher *watcher, Stream *stream)
+int socket_watcher_accept(SocketWatcher *watcher, RStream *stream)
FUNC_ATTR_NONNULL_ARG(1) FUNC_ATTR_NONNULL_ARG(2)
{
uv_stream_t *client;
if (watcher->stream->type == UV_TCP) {
- client = (uv_stream_t *)(&stream->uv.tcp);
+ client = (uv_stream_t *)(&stream->s.uv.tcp);
uv_tcp_init(watcher->uv.tcp.handle.loop, (uv_tcp_t *)client);
uv_tcp_nodelay((uv_tcp_t *)client, true);
} else {
- client = (uv_stream_t *)&stream->uv.pipe;
+ client = (uv_stream_t *)&stream->s.uv.pipe;
uv_pipe_init(watcher->uv.pipe.handle.loop, (uv_pipe_t *)client, 0);
}
@@ -156,7 +156,7 @@ int socket_watcher_accept(SocketWatcher *watcher, Stream *stream)
return result;
}
- stream_init(NULL, stream, -1, client);
+ stream_init(NULL, &stream->s, -1, client);
return 0;
}
@@ -197,7 +197,7 @@ static void connect_cb(uv_connect_t *req, int status)
}
}
-bool socket_connect(Loop *loop, Stream *stream, bool is_tcp, const char *address, int timeout,
+bool socket_connect(Loop *loop, RStream *stream, bool is_tcp, const char *address, int timeout,
const char **error)
{
bool success = false;
@@ -206,7 +206,7 @@ bool socket_connect(Loop *loop, Stream *stream, bool is_tcp, const char *address
req.data = &status;
uv_stream_t *uv_stream;
- uv_tcp_t *tcp = &stream->uv.tcp;
+ uv_tcp_t *tcp = &stream->s.uv.tcp;
uv_getaddrinfo_t addr_req;
addr_req.addrinfo = NULL;
const struct addrinfo *addrinfo = NULL;
@@ -237,7 +237,7 @@ tcp_retry:
uv_tcp_connect(&req, tcp, addrinfo->ai_addr, connect_cb);
uv_stream = (uv_stream_t *)tcp;
} else {
- uv_pipe_t *pipe = &stream->uv.pipe;
+ uv_pipe_t *pipe = &stream->s.uv.pipe;
uv_pipe_init(&loop->uv, pipe, 0);
uv_pipe_connect(&req, pipe, address, connect_cb);
uv_stream = (uv_stream_t *)pipe;
@@ -245,7 +245,7 @@ tcp_retry:
status = 1;
LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, timeout, status != 1);
if (status == 0) {
- stream_init(NULL, stream, -1, uv_stream);
+ stream_init(NULL, &stream->s, -1, uv_stream);
success = true;
} else if (is_tcp && addrinfo->ai_next) {
addrinfo = addrinfo->ai_next;
diff --git a/src/nvim/event/stream.c b/src/nvim/event/stream.c
index 0b9ed4f25b..71de6ee1ba 100644
--- a/src/nvim/event/stream.c
+++ b/src/nvim/event/stream.c
@@ -8,7 +8,6 @@
#include "nvim/event/loop.h"
#include "nvim/event/stream.h"
#include "nvim/log.h"
-#include "nvim/rbuffer.h"
#include "nvim/types_defs.h"
#ifdef MSWIN
# include "nvim/os/os_win_console.h"
@@ -45,6 +44,8 @@ int stream_set_blocking(int fd, bool blocking)
void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream)
FUNC_ATTR_NONNULL_ARG(2)
{
+ // The underlying stream is either a file or an existing uv stream.
+ assert(uvstream == NULL ? fd >= 0 : fd < 0);
stream->uvstream = uvstream;
if (fd >= 0) {
@@ -84,29 +85,29 @@ void stream_init(Loop *loop, Stream *stream, int fd, uv_stream_t *uvstream)
stream->uvstream->data = stream;
}
- stream->internal_data = NULL;
stream->fpos = 0;
+ stream->internal_data = NULL;
stream->curmem = 0;
stream->maxmem = 0;
stream->pending_reqs = 0;
- stream->read_cb = NULL;
stream->write_cb = NULL;
stream->close_cb = NULL;
stream->internal_close_cb = NULL;
stream->closed = false;
- stream->buffer = NULL;
stream->events = NULL;
- stream->num_bytes = 0;
}
-void stream_close(Stream *stream, stream_close_cb on_stream_close, void *data)
+void stream_may_close(Stream *stream, bool rstream)
FUNC_ATTR_NONNULL_ARG(1)
{
+ if (stream->closed) {
+ return;
+ }
assert(!stream->closed);
DLOG("closing Stream: %p", (void *)stream);
stream->closed = true;
- stream->close_cb = on_stream_close;
- stream->close_cb_data = data;
+ stream->close_cb = NULL;
+ stream->close_cb_data = NULL;
#ifdef MSWIN
if (UV_TTY == uv_guess_handle(stream->fd)) {
@@ -116,18 +117,11 @@ void stream_close(Stream *stream, stream_close_cb on_stream_close, void *data)
#endif
if (!stream->pending_reqs) {
- stream_close_handle(stream);
- }
-}
-
-void stream_may_close(Stream *stream)
-{
- if (!stream->closed) {
- stream_close(stream, NULL, NULL);
+ stream_close_handle(stream, rstream);
}
}
-void stream_close_handle(Stream *stream)
+void stream_close_handle(Stream *stream, bool rstream)
FUNC_ATTR_NONNULL_ALL
{
uv_handle_t *handle = NULL;
@@ -145,16 +139,22 @@ void stream_close_handle(Stream *stream)
assert(handle != NULL);
if (!uv_is_closing(handle)) {
- uv_close(handle, close_cb);
+ uv_close(handle, rstream ? rstream_close_cb : close_cb);
}
}
-static void close_cb(uv_handle_t *handle)
+static void rstream_close_cb(uv_handle_t *handle)
{
- Stream *stream = handle->data;
+ RStream *stream = handle->data;
if (stream->buffer) {
- rbuffer_free(stream->buffer);
+ free_block(stream->buffer);
}
+ close_cb(handle);
+}
+
+static void close_cb(uv_handle_t *handle)
+{
+ Stream *stream = handle->data;
if (stream->close_cb) {
stream->close_cb(stream, stream->close_cb_data);
}
diff --git a/src/nvim/event/wstream.c b/src/nvim/event/wstream.c
index c67a9b96ed..5005c4e84f 100644
--- a/src/nvim/event/wstream.c
+++ b/src/nvim/event/wstream.c
@@ -73,6 +73,26 @@ bool wstream_write(Stream *stream, WBuffer *buffer)
// This should not be called after a stream was freed
assert(!stream->closed);
+ uv_buf_t uvbuf;
+ uvbuf.base = buffer->data;
+ uvbuf.len = UV_BUF_LEN(buffer->size);
+
+ if (!stream->uvstream) {
+ uv_fs_t req;
+
+ // Synchronous write
+ uv_fs_write(stream->uv.idle.loop, &req, stream->fd, &uvbuf, 1, stream->fpos, NULL);
+
+ uv_fs_req_cleanup(&req);
+
+ wstream_release_wbuffer(buffer);
+
+ assert(stream->write_cb == NULL);
+
+ stream->fpos += MAX(req.result, 0);
+ return req.result > 0;
+ }
+
if (stream->curmem > stream->maxmem) {
goto err;
}
@@ -84,10 +104,6 @@ bool wstream_write(Stream *stream, WBuffer *buffer)
data->buffer = buffer;
data->uv_req.data = data;
- uv_buf_t uvbuf;
- uvbuf.base = buffer->data;
- uvbuf.len = UV_BUF_LEN(buffer->size);
-
if (uv_write(&data->uv_req, stream->uvstream, &uvbuf, 1, write_cb)) {
xfree(data);
goto err;
@@ -141,7 +157,7 @@ static void write_cb(uv_write_t *req, int status)
if (data->stream->closed && data->stream->pending_reqs == 0) {
// Last pending write, free the stream;
- stream_close_handle(data->stream);
+ stream_close_handle(data->stream, false);
}
xfree(data);
@@ -158,3 +174,8 @@ void wstream_release_wbuffer(WBuffer *buffer)
xfree(buffer);
}
}
+
+void wstream_may_close(Stream *stream)
+{
+ stream_may_close(stream, false);
+}
diff --git a/src/nvim/ex_cmds.c b/src/nvim/ex_cmds.c
index 834cc6698a..a98de05815 100644
--- a/src/nvim/ex_cmds.c
+++ b/src/nvim/ex_cmds.c
@@ -34,6 +34,7 @@
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -203,7 +204,7 @@ void do_ascii(exarg_T *eap)
IObuff[iobuff_len++] = ' ';
}
IObuff[iobuff_len++] = '<';
- if (utf_iscomposing(c)) {
+ if (utf_iscomposing_first(c)) {
IObuff[iobuff_len++] = ' '; // Draw composing char on top of a space.
}
iobuff_len += (size_t)utf_char2bytes(c, IObuff + iobuff_len);
@@ -310,9 +311,7 @@ void ex_align(exarg_T *eap)
}
}
}
- if (new_indent < 0) {
- new_indent = 0;
- }
+ new_indent = MAX(new_indent, 0);
set_indent(new_indent, 0); // set indent
}
changed_lines(curbuf, eap->line1, 0, eap->line2 + 1, 0, true);
@@ -542,9 +541,7 @@ void ex_sort(exarg_T *eap)
for (linenr_T lnum = eap->line1; lnum <= eap->line2; lnum++) {
char *s = ml_get(lnum);
int len = ml_get_len(lnum);
- if (maxlen < len) {
- maxlen = len;
- }
+ maxlen = MAX(maxlen, len);
colnr_T start_col = 0;
colnr_T end_col = len;
@@ -704,11 +701,6 @@ sortend:
/// @return FAIL for failure, OK otherwise
int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
{
- linenr_T l;
- linenr_T extra; // Num lines added before line1
- linenr_T num_lines; // Num lines moved
- linenr_T last_line; // Last line in file after adding new text
-
if (dest >= line1 && dest < line2) {
emsg(_("E134: Cannot move a range of lines into itself"));
return FAIL;
@@ -719,11 +711,9 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
if (dest == line1 - 1 || dest == line2) {
// Move the cursor as if lines were moved (see below) to be backwards
// compatible.
- if (dest >= line1) {
- curwin->w_cursor.lnum = dest;
- } else {
- curwin->w_cursor.lnum = dest + (line2 - line1) + 1;
- }
+ curwin->w_cursor.lnum = dest >= line1
+ ? dest
+ : dest + (line2 - line1) + 1;
return OK;
}
@@ -732,13 +722,16 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
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;
+ linenr_T num_lines = line2 - line1 + 1; // Num lines moved
// First we copy the old text to its new location -- webb
// Also copy the flag that ":global" command uses.
if (u_save(dest, dest + 1) == FAIL) {
return FAIL;
}
+
+ linenr_T l;
+ linenr_T extra; // Num lines added before line1
for (extra = 0, l = line1; l <= line2; l++) {
char *str = xstrnsave(ml_get(l + extra), (size_t)ml_get_len(l + extra));
ml_append(dest + l - line1, str, 0, false);
@@ -761,7 +754,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
//
// And Finally we adjust the marks we put at the end of the file back to
// their final destination at the new text position -- webb
- last_line = curbuf->b_ml.ml_line_count;
+ linenr_T last_line = curbuf->b_ml.ml_line_count; // Last line in file after adding new text
mark_adjust_nofold(line1, line2, last_line - line2, 0, kExtmarkNOOP);
disable_fold_update++;
@@ -837,9 +830,7 @@ int do_move(linenr_T line1, linenr_T line2, linenr_T dest)
if (line1 < dest) {
dest += num_lines + 1;
last_line = curbuf->b_ml.ml_line_count;
- if (dest > last_line + 1) {
- dest = last_line + 1;
- }
+ dest = MIN(dest, last_line + 1);
changed_lines(curbuf, line1, 0, dest, 0, false);
} else {
changed_lines(curbuf, dest + 1, 0, line1 + num_lines, 0, false);
@@ -969,13 +960,13 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
char *t = xmalloc(len);
*t = NUL;
if (newcmd != NULL) {
- STRCAT(t, newcmd);
+ strcat(t, newcmd);
}
if (ins_prevcmd) {
- STRCAT(t, prevcmd);
+ strcat(t, prevcmd);
}
char *p = t + strlen(t);
- STRCAT(t, trailarg);
+ strcat(t, trailarg);
xfree(newcmd);
newcmd = t;
@@ -1028,8 +1019,8 @@ void do_bang(int addr_count, exarg_T *eap, bool forceit, bool do_in, bool do_out
}
newcmd = xmalloc(strlen(prevcmd) + 2 * strlen(p_shq) + 1);
STRCPY(newcmd, p_shq);
- STRCAT(newcmd, prevcmd);
- STRCAT(newcmd, p_shq);
+ strcat(newcmd, prevcmd);
+ strcat(newcmd, p_shq);
free_newcmd = true;
}
if (addr_count == 0) { // :!
@@ -1169,7 +1160,7 @@ static void do_filter(linenr_T line1, linenr_T line2, exarg_T *eap, char *cmd, b
linenr_T read_linecount = curbuf->b_ml.ml_line_count;
// Pass on the kShellOptDoOut flag when the output is being redirected.
- call_shell(cmd_buf, (ShellOpts)(kShellOptFilter | shell_flags), NULL);
+ call_shell(cmd_buf, kShellOptFilter | shell_flags, NULL);
xfree(cmd_buf);
did_check_timestamps = false;
@@ -1314,7 +1305,7 @@ void do_shell(char *cmd, int flags)
// This ui_cursor_goto is required for when the '\n' resulted in a "delete line
// 1" command to the terminal.
ui_cursor_goto(msg_row, msg_col);
- call_shell(cmd, (ShellOpts)flags, NULL);
+ call_shell(cmd, flags, NULL);
if (msg_silent == 0) {
msg_didout = true;
}
@@ -2270,6 +2261,16 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
if (buf == NULL) {
goto theend;
}
+ // autocommands try to edit a file that is goind to be removed, abort
+ if (buf_locked(buf)) {
+ // window was split, but not editing the new buffer, reset b_nwindows again
+ if (oldwin == NULL
+ && curwin->w_buffer != NULL
+ && curwin->w_buffer->b_nwindows > 1) {
+ curwin->w_buffer->b_nwindows--;
+ }
+ goto theend;
+ }
if (curwin->w_alt_fnum == buf->b_fnum && prev_alt_fnum != 0) {
// reusing the buffer, keep the old alternate file
curwin->w_alt_fnum = prev_alt_fnum;
@@ -2356,9 +2357,9 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
win_T *the_curwin = curwin;
buf_T *was_curbuf = curbuf;
- // Set w_closing to avoid that autocommands close the window.
+ // Set w_locked to avoid that autocommands close the window.
// Set b_locked for the same reason.
- the_curwin->w_closing = true;
+ the_curwin->w_locked = true;
buf->b_locked++;
if (curbuf == old_curbuf.br_buf) {
@@ -2374,7 +2375,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
// Autocommands may have closed the window.
if (win_valid(the_curwin)) {
- the_curwin->w_closing = false;
+ the_curwin->w_locked = false;
}
buf->b_locked--;
@@ -2704,7 +2705,7 @@ int do_ecmd(int fnum, char *ffname, char *sfname, exarg_T *eap, linenr_T newlnum
*so_ptr = 999; // force cursor to be vertically centered in the window
}
update_topline(curwin);
- curwin->w_scbind_pos = curwin->w_topline;
+ curwin->w_scbind_pos = plines_m_win_fill(curwin, 1, curwin->w_topline);
*so_ptr = n;
redraw_curbuf_later(UPD_NOT_VALID); // redraw this buffer later
}
@@ -2783,10 +2784,14 @@ void ex_append(exarg_T *eap)
indent = get_indent_lnum(lnum);
}
}
- if (eap->ea_getline == NULL) {
+ if (*eap->arg == '|') {
+ // Get the text after the trailing bar.
+ theline = xstrdup(eap->arg + 1);
+ *eap->arg = NUL;
+ } else if (eap->ea_getline == NULL) {
// No getline() function, use the lines that follow. This ends
// when there is no more.
- if (eap->nextcmd == NULL || *eap->nextcmd == NUL) {
+ if (eap->nextcmd == NULL) {
break;
}
p = vim_strchr(eap->nextcmd, NL);
@@ -2796,6 +2801,8 @@ void ex_append(exarg_T *eap)
theline = xmemdupz(eap->nextcmd, (size_t)(p - eap->nextcmd));
if (*p != NUL) {
p++;
+ } else {
+ p = NULL;
}
eap->nextcmd = p;
} else {
@@ -2926,9 +2933,7 @@ void ex_z(exarg_T *eap)
} else {
bigness = curwin->w_height_inner - 3;
}
- if (bigness < 1) {
- bigness = 1;
- }
+ bigness = MAX(bigness, 1);
char *x = eap->arg;
char *kind = x;
@@ -3001,19 +3006,9 @@ void ex_z(exarg_T *eap)
break;
}
- if (start < 1) {
- start = 1;
- }
-
- if (end > curbuf->b_ml.ml_line_count) {
- end = curbuf->b_ml.ml_line_count;
- }
-
- if (curs > curbuf->b_ml.ml_line_count) {
- curs = curbuf->b_ml.ml_line_count;
- } else if (curs < 1) {
- curs = 1;
- }
+ start = MAX(start, 1);
+ end = MIN(end, curbuf->b_ml.ml_line_count);
+ curs = MIN(MAX(curs, 1), curbuf->b_ml.ml_line_count);
for (linenr_T i = start; i <= end; i++) {
if (minus && i == lnum) {
@@ -3083,8 +3078,8 @@ void sub_get_replacement(SubReplacementString *const ret_sub)
void sub_set_replacement(SubReplacementString sub)
{
xfree(old_sub.sub);
- if (sub.additional_elements != old_sub.additional_elements) {
- tv_list_unref(old_sub.additional_elements);
+ if (sub.additional_data != old_sub.additional_data) {
+ xfree(old_sub.additional_data);
}
old_sub = sub;
}
@@ -3100,7 +3095,7 @@ void sub_set_replacement(SubReplacementString sub)
///
/// @returns true if :substitute can be replaced with a join command
static bool sub_joining_lines(exarg_T *eap, char *pat, size_t patlen, const char *sub,
- const char *cmd, bool save)
+ const char *cmd, bool save, bool keeppatterns)
FUNC_ATTR_NONNULL_ARG(1, 4, 5)
{
// TODO(vim): find a generic solution to make line-joining operations more
@@ -3138,7 +3133,7 @@ static bool sub_joining_lines(exarg_T *eap, char *pat, size_t patlen, const char
}
if (save) {
- if ((cmdmod.cmod_flags & CMOD_KEEPPATTERNS) == 0) {
+ if (!keeppatterns) {
save_re_pat(RE_SUBST, pat, patlen, magic_isset());
}
// put pattern in history
@@ -3346,6 +3341,7 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n
linenr_T old_line_count = curbuf->b_ml.ml_line_count;
char *sub_firstline; // allocated copy of first sub line
bool endcolumn = false; // cursor in last column when done
+ const bool keeppatterns = cmdmod.cmod_flags & CMOD_KEEPPATTERNS;
PreviewLines preview_lines = { KV_INITIAL_VALUE, 0 };
static int pre_hl_id = 0;
pos_T old_cursor = curwin->w_cursor;
@@ -3392,12 +3388,12 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n
which_pat = RE_LAST; // use last used regexp
delimiter = (uint8_t)(*cmd++); // remember delimiter character
pat = cmd; // remember start of search pat
- patlen = strlen(pat);
cmd = skip_regexp_ex(cmd, delimiter, magic_isset(), &eap->arg, NULL, NULL);
if (cmd[0] == delimiter) { // end delimiter found
*cmd++ = NUL; // replace it with a NUL
has_second_delim = true;
}
+ patlen = strlen(pat);
}
// Small incompatibility: vi sees '\n' as end of the command, but in
@@ -3406,11 +3402,11 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n
cmd = skip_substitute(cmd, delimiter);
sub = xstrdup(p);
- if (!eap->skip && cmdpreview_ns <= 0) {
+ if (!eap->skip && !keeppatterns && cmdpreview_ns <= 0) {
sub_set_replacement((SubReplacementString) {
.sub = xstrdup(sub),
.timestamp = os_time(),
- .additional_elements = NULL,
+ .additional_data = NULL,
});
}
} else if (!eap->skip) { // use previous pattern and substitution
@@ -3427,7 +3423,8 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n
endcolumn = (curwin->w_curswant == MAXCOL);
}
- if (sub != NULL && sub_joining_lines(eap, pat, patlen, sub, cmd, cmdpreview_ns <= 0)) {
+ if (sub != NULL && sub_joining_lines(eap, pat, patlen, sub, cmd, cmdpreview_ns <= 0,
+ keeppatterns)) {
xfree(sub);
return 0;
}
@@ -3454,9 +3451,7 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n
}
eap->line1 = eap->line2;
eap->line2 += (linenr_T)i - 1;
- if (eap->line2 > curbuf->b_ml.ml_line_count) {
- eap->line2 = curbuf->b_ml.ml_line_count;
- }
+ eap->line2 = MIN(eap->line2, curbuf->b_ml.ml_line_count);
}
// check for trailing command or garbage
@@ -3720,10 +3715,8 @@ static int do_sub(exarg_T *eap, const proftime_T timeout, const int cmdpreview_n
print_line_no_prefix(lnum, subflags.do_number, subflags.do_list);
getvcol(curwin, &curwin->w_cursor, &sc, NULL, NULL);
- curwin->w_cursor.col = regmatch.endpos[0].col - 1;
- if (curwin->w_cursor.col < 0) {
- curwin->w_cursor.col = 0;
- }
+ curwin->w_cursor.col = MAX(regmatch.endpos[0].col - 1, 0);
+
getvcol(curwin, &curwin->w_cursor, NULL, NULL, &ec);
curwin->w_cursor.col = regmatch.startpos[0].col;
if (subflags.do_number || curwin->w_p_nu) {
@@ -4107,7 +4100,7 @@ skip:
// the line as reference, because the substitute may
// have changed the number of characters. Same for
// "prev_matchcol".
- STRCAT(new_start, sub_firstline + copycol);
+ strcat(new_start, sub_firstline + copycol);
matchcol = (colnr_T)strlen(sub_firstline) - matchcol;
prev_matchcol = (colnr_T)strlen(sub_firstline)
- prev_matchcol;
diff --git a/src/nvim/ex_cmds2.c b/src/nvim/ex_cmds2.c
index f4a6e61831..aff9dce7c1 100644
--- a/src/nvim/ex_cmds2.c
+++ b/src/nvim/ex_cmds2.c
@@ -17,6 +17,7 @@
#include "nvim/bufwrite.h"
#include "nvim/change.h"
#include "nvim/channel.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -226,7 +227,7 @@ void dialog_changed(buf_T *buf, bool checkall)
// restore to empty when write failed
if (empty_bufname) {
- XFREE_CLEAR(buf->b_fname);
+ buf->b_fname = NULL;
XFREE_CLEAR(buf->b_ffname);
XFREE_CLEAR(buf->b_sfname);
unchanged(buf, true, false);
@@ -589,7 +590,7 @@ void ex_listdo(exarg_T *eap)
break;
}
assert(wp);
- execute = !wp->w_floating || wp->w_config.focusable;
+ execute = !wp->w_floating || (!wp->w_config.hide && wp->w_config.focusable);
if (execute) {
win_goto(wp);
if (curwin != wp) {
diff --git a/src/nvim/ex_cmds_defs.h b/src/nvim/ex_cmds_defs.h
index 07f92ca169..4924e86470 100644
--- a/src/nvim/ex_cmds_defs.h
+++ b/src/nvim/ex_cmds_defs.h
@@ -233,5 +233,5 @@ typedef struct {
typedef struct {
char *sub; ///< Previous replacement string.
Timestamp timestamp; ///< Time when it was last set.
- list_T *additional_elements; ///< Additional data left from ShaDa file.
+ AdditionalData *additional_data; ///< Additional data left from ShaDa file.
} SubReplacementString;
diff --git a/src/nvim/ex_docmd.c b/src/nvim/ex_docmd.c
index 1fcfc505df..293aaac036 100644
--- a/src/nvim/ex_docmd.c
+++ b/src/nvim/ex_docmd.c
@@ -29,6 +29,7 @@
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -80,6 +81,7 @@
#include "nvim/os/os_defs.h"
#include "nvim/os/shell.h"
#include "nvim/path.h"
+#include "nvim/plines.h"
#include "nvim/popupmenu.h"
#include "nvim/pos_defs.h"
#include "nvim/profile.h"
@@ -971,8 +973,13 @@ void handle_did_throw(void)
current_exception->throw_name = NULL;
discard_current_exception(); // uses IObuff if 'verbose'
- suppress_errthrow = true;
- force_abort = true;
+
+ // If "silent!" is active the uncaught exception is not fatal.
+ if (emsg_silent == 0) {
+ suppress_errthrow = true;
+ force_abort = true;
+ }
+
msg_ext_set_kind("emsg"); // kind=emsg for :throw, exceptions. #9993
if (messages != NULL) {
@@ -1073,7 +1080,6 @@ void *getline_cookie(LineGetter fgetline, void *cookie)
/// @return the buffer number.
static int compute_buffer_local_count(cmd_addr_T addr_type, linenr_T lnum, int offset)
{
- buf_T *nextbuf;
int count = offset;
buf_T *buf = firstbuf;
@@ -1082,7 +1088,7 @@ static int compute_buffer_local_count(cmd_addr_T addr_type, linenr_T lnum, int o
}
while (count != 0) {
count += (count < 0) ? 1 : -1;
- nextbuf = (offset < 0) ? buf->b_prev : buf->b_next;
+ buf_T *nextbuf = (offset < 0) ? buf->b_prev : buf->b_next;
if (nextbuf == NULL) {
break;
}
@@ -1101,7 +1107,7 @@ static int compute_buffer_local_count(cmd_addr_T addr_type, linenr_T lnum, int o
// we might have gone too far, last buffer is not loaded
if (addr_type == ADDR_LOADED_BUFFERS) {
while (buf->b_ml.ml_mfp == NULL) {
- nextbuf = (offset >= 0) ? buf->b_prev : buf->b_next;
+ buf_T *nextbuf = (offset >= 0) ? buf->b_prev : buf->b_next;
if (nextbuf == NULL) {
break;
}
@@ -1405,7 +1411,11 @@ void set_cmd_count(exarg_T *eap, linenr_T count, bool validate)
}
} else {
eap->line1 = eap->line2;
- eap->line2 += count - 1;
+ if (eap->line2 >= INT32_MAX - (count - 1)) {
+ eap->line2 = INT32_MAX;
+ } else {
+ eap->line2 += count - 1;
+ }
eap->addr_count++;
// Be vi compatible: no error message for out of range.
if (validate && eap->line2 > curbuf->b_ml.ml_line_count) {
@@ -1423,7 +1433,7 @@ static int parse_count(exarg_T *eap, const char **errormsg, bool validate)
if ((eap->argt & EX_COUNT) && ascii_isdigit(*eap->arg)
&& (!(eap->argt & EX_BUFNAME) || *(p = skipdigits(eap->arg + 1)) == NUL
|| ascii_iswhite(*p))) {
- linenr_T n = getdigits_int32(&eap->arg, false, -1);
+ linenr_T n = getdigits_int32(&eap->arg, false, INT32_MAX);
eap->arg = skipwhite(eap->arg);
if (eap->args != NULL) {
@@ -1686,9 +1696,7 @@ static int execute_cmd0(int *retv, exarg_T *eap, const char **errormsg, bool pre
// ":silent! try" was used, it should only apply to :try itself.
if (eap->cmdidx == CMD_try && cmdmod.cmod_did_esilent > 0) {
emsg_silent -= cmdmod.cmod_did_esilent;
- if (emsg_silent < 0) {
- emsg_silent = 0;
- }
+ emsg_silent = MAX(emsg_silent, 0);
cmdmod.cmod_did_esilent = 0;
}
@@ -1728,12 +1736,6 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview)
}
const char *errormsg = NULL;
-#undef ERROR
-#define ERROR(msg) \
- do { \
- errormsg = msg; \
- goto end; \
- } while (0)
cmdmod_T save_cmdmod = cmdmod;
cmdmod = cmdinfo->cmdmod;
@@ -1744,16 +1746,19 @@ int execute_cmd(exarg_T *eap, CmdParseInfo *cmdinfo, bool preview)
if (!MODIFIABLE(curbuf) && (eap->argt & EX_MODIFY)
// allow :put in terminals
&& !(curbuf->terminal && eap->cmdidx == CMD_put)) {
- ERROR(_(e_modifiable));
+ errormsg = _(e_modifiable);
+ goto end;
}
if (!IS_USER_CMDIDX(eap->cmdidx)) {
if (cmdwin_type != 0 && !(eap->argt & EX_CMDWIN)) {
// Command not allowed in the command line window
- ERROR(_(e_cmdwin));
+ errormsg = _(e_cmdwin);
+ goto end;
}
if (text_locked() && !(eap->argt & EX_LOCK_OK)) {
// Command not allowed when text is locked
- ERROR(_(get_text_locked_msg()));
+ errormsg = _(get_text_locked_msg());
+ goto end;
}
}
// Disallow editing another buffer when "curbuf->b_ro_locked" is set.
@@ -1801,7 +1806,6 @@ end:
do_cmdline_end();
return retv;
-#undef ERROR
}
static void profile_cmd(const exarg_T *eap, cstack_T *cstack, LineGetter fgetline, void *cookie)
@@ -2073,29 +2077,8 @@ static char *do_one_cmd(char **cmdlinep, int flags, cstack_T *cstack, LineGetter
if (ea.skip) { // skip this if inside :if
goto doend;
}
- if (*ea.cmd == '|' || (exmode_active && ea.line1 != ea.line2)) {
- ea.cmdidx = CMD_print;
- ea.argt = EX_RANGE | EX_COUNT | EX_TRLBAR;
- if ((errormsg = invalid_range(&ea)) == NULL) {
- correct_range(&ea);
- ex_print(&ea);
- }
- } else if (ea.addr_count != 0) {
- if (ea.line2 > curbuf->b_ml.ml_line_count) {
- ea.line2 = curbuf->b_ml.ml_line_count;
- }
-
- if (ea.line2 < 0) {
- errormsg = _(e_invrange);
- } else {
- if (ea.line2 == 0) {
- curwin->w_cursor.lnum = 1;
- } else {
- curwin->w_cursor.lnum = ea.line2;
- }
- beginline(BL_SOL | BL_FIX);
- }
- }
+ assert(errormsg == NULL);
+ errormsg = ex_range_without_command(&ea);
goto doend;
}
@@ -2441,6 +2424,40 @@ char *ex_errmsg(const char *const msg, const char *const arg)
return ex_error_buf;
}
+/// The "+" string used in place of an empty command in Ex mode.
+/// This string is used in pointer comparison.
+static char exmode_plus[] = "+";
+
+/// Handle a range without a command.
+/// Returns an error message on failure.
+static char *ex_range_without_command(exarg_T *eap)
+{
+ char *errormsg = NULL;
+
+ if (*eap->cmd == '|' || (exmode_active && eap->cmd != exmode_plus + 1)) {
+ eap->cmdidx = CMD_print;
+ eap->argt = EX_RANGE | EX_COUNT | EX_TRLBAR;
+ if ((errormsg = invalid_range(eap)) == NULL) {
+ correct_range(eap);
+ ex_print(eap);
+ }
+ } else if (eap->addr_count != 0) {
+ eap->line2 = MIN(eap->line2, curbuf->b_ml.ml_line_count);
+
+ if (eap->line2 < 0) {
+ errormsg = _(e_invrange);
+ } else {
+ if (eap->line2 == 0) {
+ curwin->w_cursor.lnum = 1;
+ } else {
+ curwin->w_cursor.lnum = eap->line2;
+ }
+ beginline(BL_SOL | BL_FIX);
+ }
+ }
+ return errormsg;
+}
+
/// Parse and skip over command modifiers:
/// - update eap->cmd
/// - store flags in "cmod".
@@ -2474,7 +2491,7 @@ int parse_command_modifiers(exarg_T *eap, const char **errormsg, cmdmod_T *cmod,
if (*eap->cmd == NUL && exmode_active
&& getline_equal(eap->ea_getline, eap->cookie, getexline)
&& curwin->w_cursor.lnum < curbuf->b_ml.ml_line_count) {
- eap->cmd = "+";
+ eap->cmd = exmode_plus;
if (!skip_only) {
ex_pressedreturn = true;
}
@@ -2482,6 +2499,15 @@ int parse_command_modifiers(exarg_T *eap, const char **errormsg, cmdmod_T *cmod,
// ignore comment and empty lines
if (*eap->cmd == '"') {
+ // a comment ends at a NL
+ eap->nextcmd = vim_strchr(eap->cmd, '\n');
+ if (eap->nextcmd != NULL) {
+ eap->nextcmd++;
+ }
+ return FAIL;
+ }
+ if (*eap->cmd == '\n') {
+ eap->nextcmd = eap->cmd + 1;
return FAIL;
}
if (*eap->cmd == NUL) {
@@ -2695,7 +2721,7 @@ int parse_command_modifiers(exarg_T *eap, const char **errormsg, cmdmod_T *cmod,
/// Apply the command modifiers. Saves current state in "cmdmod", call
/// undo_cmdmod() later.
-static void apply_cmdmod(cmdmod_T *cmod)
+void apply_cmdmod(cmdmod_T *cmod)
{
if ((cmod->cmod_flags & CMOD_SANDBOX) && !cmod->cmod_did_sandbox) {
sandbox++;
@@ -2764,9 +2790,7 @@ void undo_cmdmod(cmdmod_T *cmod)
msg_silent = cmod->cmod_save_msg_silent - 1;
}
emsg_silent -= cmod->cmod_did_esilent;
- if (emsg_silent < 0) {
- emsg_silent = 0;
- }
+ emsg_silent = MAX(emsg_silent, 0);
// Restore msg_scroll, it's set by file I/O commands, even when no
// message is actually displayed.
msg_scroll = cmod->cmod_save_msg_scroll;
@@ -3499,11 +3523,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, bool
// This makes sure we never match in the current
// line, and can match anywhere in the
// next/previous line.
- if (c == '/' && curwin->w_cursor.lnum > 0) {
- curwin->w_cursor.col = MAXCOL;
- } else {
- curwin->w_cursor.col = 0;
- }
+ curwin->w_cursor.col = (c == '/' && curwin->w_cursor.lnum > 0) ? MAXCOL : 0;
searchcmdlen = 0;
flags = silent ? 0 : SEARCH_HIS | SEARCH_MSG;
if (!do_search(NULL, c, c, cmd, strlen(cmd), 1, flags, NULL)) {
@@ -3613,6 +3633,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, bool
n = getdigits_int32(&cmd, false, MAXLNUM);
if (n == MAXLNUM) {
*errormsg = _(e_line_number_out_of_range);
+ cmd = NULL;
goto error;
}
}
@@ -3635,6 +3656,7 @@ static linenr_T get_address(exarg_T *eap, char **ptr, cmd_addr_T addr_type, bool
} else {
if (lnum >= 0 && n >= INT32_MAX - lnum) {
*errormsg = _(e_line_number_out_of_range);
+ cmd = NULL;
goto error;
}
lnum += n;
@@ -3829,8 +3851,8 @@ char *replace_makeprg(exarg_T *eap, char *arg, char **cmdlinep)
// No $* in arg, build "<makeprg> <arg>" instead
new_cmdline = xmalloc(strlen(program) + strlen(arg) + 2);
STRCPY(new_cmdline, program);
- STRCAT(new_cmdline, " ");
- STRCAT(new_cmdline, arg);
+ strcat(new_cmdline, " ");
+ strcat(new_cmdline, arg);
}
msg_make(arg);
@@ -4089,7 +4111,12 @@ void separate_nextcmd(exarg_T *eap)
&& !(eap->argt & EX_NOTRLCOM)
&& (eap->cmdidx != CMD_at || p != eap->arg)
&& (eap->cmdidx != CMD_redir
- || p != eap->arg + 1 || p[-1] != '@')) || *p == '|' || *p == '\n') {
+ || p != eap->arg + 1 || p[-1] != '@'))
+ || (*p == '|'
+ && eap->cmdidx != CMD_append
+ && eap->cmdidx != CMD_change
+ && eap->cmdidx != CMD_insert)
+ || *p == '\n') {
// We remove the '\' before the '|', unless EX_CTRLV is used
// AND 'b' is present in 'cpoptions'.
if ((vim_strchr(p_cpo, CPO_BAR) == NULL
@@ -4117,7 +4144,7 @@ static char *getargcmd(char **argp)
if (*arg == '+') { // +[command]
arg++;
- if (ascii_isspace(*arg) || *arg == '\0') {
+ if (ascii_isspace(*arg) || *arg == NUL) {
command = dollar_command;
} else {
command = arg;
@@ -4626,7 +4653,7 @@ static void ex_colorscheme(exarg_T *eap)
char *expr = xstrdup("g:colors_name");
emsg_off++;
- char *p = eval_to_string(expr, false);
+ char *p = eval_to_string(expr, false, false);
emsg_off--;
xfree(expr);
@@ -4765,11 +4792,9 @@ static void ex_cquit(exarg_T *eap)
int before_quit_all(exarg_T *eap)
{
if (cmdwin_type != 0) {
- if (eap->forceit) {
- cmdwin_result = K_XF1; // open_cmdwin() takes care of this
- } else {
- cmdwin_result = K_XF2;
- }
+ cmdwin_result = eap->forceit
+ ? K_XF1 // open_cmdwin() takes care of this
+ : K_XF2;
return FAIL;
}
@@ -5565,45 +5590,43 @@ static void ex_swapname(exarg_T *eap)
/// (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>)
static void ex_syncbind(exarg_T *eap)
{
- linenr_T topline;
- int y;
+ linenr_T vtopline; // Target topline (including fill)
+
linenr_T old_linenr = curwin->w_cursor.lnum;
setpcmark();
- // determine max topline
+ // determine max (virtual) topline
if (curwin->w_p_scb) {
- topline = curwin->w_topline;
+ vtopline = get_vtopline(curwin);
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_p_scb && wp->w_buffer) {
- y = wp->w_buffer->b_ml.ml_line_count - get_scrolloff_value(curwin);
- if (topline > y) {
- topline = y;
- }
+ linenr_T y = plines_m_win_fill(wp, 1, wp->w_buffer->b_ml.ml_line_count)
+ - get_scrolloff_value(curwin);
+ vtopline = MIN(vtopline, y);
}
}
- if (topline < 1) {
- topline = 1;
- }
+ vtopline = MAX(vtopline, 1);
} else {
- topline = 1;
+ vtopline = 1;
}
// Set all scrollbind windows to the same topline.
FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
if (wp->w_p_scb) {
- y = topline - wp->w_topline;
+ int y = vtopline - get_vtopline(wp);
if (y > 0) {
scrollup(wp, y, true);
} else {
scrolldown(wp, -y, true);
}
- wp->w_scbind_pos = topline;
+ wp->w_scbind_pos = vtopline;
redraw_later(wp, UPD_VALID);
cursor_correct(wp);
wp->w_redr_status = true;
}
}
+
if (curwin->w_p_scb) {
did_syncbind = true;
checkpcmark();
@@ -7239,7 +7262,7 @@ char *expand_sfile(char *arg)
memmove(newres, result, (size_t)(p - result));
STRCPY(newres + (p - result), repl);
len = strlen(newres);
- STRCAT(newres, p + srclen);
+ strcat(newres, p + srclen);
xfree(repl);
xfree(result);
result = newres;
@@ -7316,7 +7339,7 @@ static void ex_filetype(exarg_T *eap)
break;
}
if (strcmp(arg, "on") == 0 || strcmp(arg, "detect") == 0) {
- if (*arg == 'o' || !filetype_detect) {
+ if (*arg == 'o' || filetype_detect != kTrue) {
source_runtime(FILETYPE_FILE, DIP_ALL);
filetype_detect = kTrue;
if (plugin) {
diff --git a/src/nvim/ex_eval.c b/src/nvim/ex_eval.c
index 472741d537..f9936dd88e 100644
--- a/src/nvim/ex_eval.c
+++ b/src/nvim/ex_eval.c
@@ -11,6 +11,7 @@
#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
#include "nvim/debugger.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -405,7 +406,7 @@ char *get_exception_string(void *value, except_type_T type, char *cmdname, bool
|| (ascii_isdigit(p[3])
&& p[4] == ':')))))) {
if (*p == NUL || p == mesg) {
- STRCAT(val, mesg); // 'E123' missing or at beginning
+ strcat(val, mesg); // 'E123' missing or at beginning
} else {
// '"filename" E123: message text'
if (mesg[0] != '"' || p - 2 < &mesg[1]
@@ -414,7 +415,7 @@ char *get_exception_string(void *value, except_type_T type, char *cmdname, bool
continue;
}
- STRCAT(val, p);
+ strcat(val, p);
p[-2] = NUL;
snprintf(val + strlen(p), strlen(" (%s)"), " (%s)", &mesg[1]);
p[-2] = '"';
@@ -848,7 +849,7 @@ void ex_if(exarg_T *eap)
bool skip = CHECK_SKIP;
bool error;
- bool result = eval_to_bool(eap->arg, &error, eap, skip);
+ bool result = eval_to_bool(eap->arg, &error, eap, skip, false);
if (!skip && !error) {
if (result) {
@@ -943,7 +944,7 @@ void ex_else(exarg_T *eap)
if (skip && *eap->arg != '"' && ends_excmd(*eap->arg)) {
semsg(_(e_invexpr2), eap->arg);
} else {
- result = eval_to_bool(eap->arg, &error, eap, skip);
+ result = eval_to_bool(eap->arg, &error, eap, skip, false);
}
// When throwing error exceptions, we want to throw always the first
@@ -989,7 +990,7 @@ void ex_while(exarg_T *eap)
int skip = CHECK_SKIP;
if (eap->cmdidx == CMD_while) { // ":while bool-expr"
- result = eval_to_bool(eap->arg, &error, eap, skip);
+ result = eval_to_bool(eap->arg, &error, eap, skip, false);
} else { // ":for var in list-expr"
evalarg_T evalarg;
fill_evalarg_from_eap(&evalarg, eap, skip);
diff --git a/src/nvim/ex_getln.c b/src/nvim/ex_getln.c
index f18dc0f747..7d87e609ca 100644
--- a/src/nvim/ex_getln.c
+++ b/src/nvim/ex_getln.c
@@ -28,6 +28,7 @@
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/vars.h"
@@ -132,7 +133,8 @@ typedef struct {
int did_wild_list; // did wild_list() recently
int wim_index; // index in wim_flags[]
int save_msg_scroll;
- int save_State; // remember State when called
+ int save_State; // remember State when called
+ int prev_cmdpos;
char *save_p_icm;
int some_key_typed; // one of the keys was typed
// mouse drag and release events are ignored, unless they are
@@ -222,6 +224,12 @@ static int cmdpreview_ns = 0;
static const char e_active_window_or_buffer_changed_or_deleted[]
= N_("E199: Active window or buffer changed or deleted");
+static void trigger_cmd_autocmd(int typechar, event_T evt)
+{
+ char typestr[2] = { (char)typechar, NUL };
+ apply_autocmds(evt, typestr, typestr, false, curbuf);
+}
+
static void save_viewstate(win_T *wp, viewstate_T *vs)
FUNC_ATTR_NONNULL_ALL
{
@@ -259,6 +267,18 @@ static void init_incsearch_state(incsearch_state_T *s)
save_viewstate(curwin, &s->old_viewstate);
}
+static void set_search_match(pos_T *t)
+{
+ // First move cursor to end of match, then to the start. This
+ // moves the whole match onto the screen when 'nowrap' is set.
+ t->lnum += search_match_lines;
+ t->col = search_match_endcol;
+ if (t->lnum > curbuf->b_ml.ml_line_count) {
+ t->lnum = curbuf->b_ml.ml_line_count;
+ coladvance(curwin, MAXCOL);
+ }
+}
+
// 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, int *search_delim, incsearch_state_T *s,
@@ -382,13 +402,8 @@ static bool do_incsearch_highlighting(int firstc, int *search_delim, incsearch_s
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;
- }
+ search_first_line = MIN(ea.line1, ea.line1);
+ search_last_line = MAX(ea.line2, ea.line1);
} else if (cmd[0] == 's' && cmd[1] != 'o') {
// :s defaults to the current line
search_first_line = curwin->w_cursor.lnum;
@@ -683,6 +698,7 @@ static uint8_t *command_line_enter(int firstc, int count, int indent, bool clear
.indent = indent,
.save_msg_scroll = msg_scroll,
.save_State = State,
+ .prev_cmdpos = -1,
.ignore_drag_release = true,
};
CommandLineState *s = &state;
@@ -1025,11 +1041,7 @@ static int command_line_handle_ctrl_bsl(CommandLineState *s)
// Restore the cursor or use the position set with
// set_cmdline_pos().
- if (new_cmdpos > ccline.cmdlen) {
- ccline.cmdpos = ccline.cmdlen;
- } else {
- ccline.cmdpos = new_cmdpos;
- }
+ ccline.cmdpos = MIN(ccline.cmdlen, new_cmdpos);
KeyTyped = false; // Don't do p_wc completion.
redrawcmd();
@@ -1649,11 +1661,7 @@ static int command_line_insert_reg(CommandLineState *s)
KeyTyped = false; // Don't do p_wc completion.
if (new_cmdpos >= 0) {
// set_cmdline_pos() was used
- if (new_cmdpos > ccline.cmdlen) {
- ccline.cmdpos = ccline.cmdlen;
- } else {
- ccline.cmdpos = new_cmdpos;
- }
+ ccline.cmdpos = MIN(ccline.cmdlen, new_cmdpos);
}
}
new_cmdpos = save_new_cmdpos;
@@ -2110,7 +2118,7 @@ static int command_line_handle_key(CommandLineState *s)
s->do_abbr = false; // don't do abbreviation now
ccline.special_char = NUL;
// may need to remove ^ when composing char was typed
- if (utf_iscomposing(s->c) && !cmd_silent) {
+ if (utf_iscomposing_first(s->c) && !cmd_silent) {
if (ui_has(kUICmdline)) {
// TODO(bfredl): why not make unputcmdline also work with true?
unputcmdline();
@@ -2174,6 +2182,12 @@ static int command_line_handle_key(CommandLineState *s)
static int command_line_not_changed(CommandLineState *s)
{
+ // Trigger CursorMovedC autocommands.
+ if (ccline.cmdpos != s->prev_cmdpos) {
+ trigger_cmd_autocmd(get_cmdline_type(), EVENT_CURSORMOVEDC);
+ s->prev_cmdpos = ccline.cmdpos;
+ }
+
// Incremental searches for "/" and "?":
// Enter command_line_not_changed() when a character has been read but the
// command line did not change. Then we only search and redraw if something
@@ -2532,6 +2546,10 @@ static bool cmdpreview_may_show(CommandLineState *s)
goto end;
}
+ // Flush now: external cmdline may itself wish to update the screen which is
+ // currently disallowed during cmdpreview(no longer needed in case that changes).
+ cmdline_ui_flush();
+
// Swap invalid command range if needed
if ((ea.argt & EX_RANGE) && ea.line1 > ea.line2) {
linenr_T lnum = ea.line1;
@@ -2647,6 +2665,12 @@ static int command_line_changed(CommandLineState *s)
// Trigger CmdlineChanged autocommands.
do_autocmd_cmdlinechanged(s->firstc > 0 ? s->firstc : '-');
+ // Trigger CursorMovedC autocommands.
+ if (ccline.cmdpos != s->prev_cmdpos) {
+ trigger_cmd_autocmd(get_cmdline_type(), EVENT_CURSORMOVEDC);
+ s->prev_cmdpos = ccline.cmdpos;
+ }
+
const bool prev_cmdpreview = cmdpreview;
if (s->firstc == ':'
&& current_sctx.sc_sid == 0 // only if interactive
@@ -3004,7 +3028,6 @@ void realloc_cmdbuff(int len)
// there, thus copy up to the NUL and add a NUL.
memmove(ccline.cmdbuff, p, (size_t)ccline.cmdlen);
ccline.cmdbuff[ccline.cmdlen] = NUL;
- xfree(p);
if (ccline.xpc != NULL
&& ccline.xpc->xp_pattern != NULL
@@ -3018,6 +3041,8 @@ void realloc_cmdbuff(int len)
ccline.xpc->xp_pattern = ccline.cmdbuff + i;
}
}
+
+ xfree(p);
}
enum { MAX_CB_ERRORS = 1, };
@@ -3450,11 +3475,9 @@ void cmdline_screen_cleared(void)
/// called by ui_flush, do what redraws necessary to keep cmdline updated.
void cmdline_ui_flush(void)
{
- static bool flushing = false;
- if (!ui_has(kUICmdline) || flushing) {
+ if (!ui_has(kUICmdline)) {
return;
}
- flushing = true;
int level = ccline.level;
CmdlineInfo *line = &ccline;
while (level > 0 && line) {
@@ -3469,7 +3492,6 @@ void cmdline_ui_flush(void)
}
line = line->prev_ccline;
}
- flushing = false;
}
// Put a character on the command line. Shifts the following text to the
@@ -3524,10 +3546,6 @@ void unputcmdline(void)
// called afterwards.
void put_on_cmdline(const char *str, int len, bool redraw)
{
- int i;
- int m;
- int c;
-
if (len < 0) {
len = (int)strlen(str);
}
@@ -3541,7 +3559,8 @@ void put_on_cmdline(const char *str, int len, bool redraw)
ccline.cmdlen += len;
} else {
// Count nr of characters in the new string.
- m = 0;
+ int m = 0;
+ int i;
for (i = 0; i < len; i += utfc_ptr2len(str + i)) {
m++;
}
@@ -3565,9 +3584,11 @@ void put_on_cmdline(const char *str, int len, bool redraw)
{
// When the inserted text starts with a composing character,
// backup to the character before it. There could be two of them.
- i = 0;
- c = utf_ptr2char(ccline.cmdbuff + ccline.cmdpos);
- while (ccline.cmdpos > 0 && utf_iscomposing(c)) {
+ int i = 0;
+ int c = utf_ptr2char(ccline.cmdbuff + ccline.cmdpos);
+ // TODO(bfredl): this can be corrected/simplified as utf_head_off implements the
+ // correct grapheme cluster breaks
+ while (ccline.cmdpos > 0 && utf_iscomposing_legacy(c)) {
i = utf_head_off(ccline.cmdbuff, ccline.cmdbuff + ccline.cmdpos - 1) + 1;
ccline.cmdpos -= i;
len += i;
@@ -3597,7 +3618,7 @@ void put_on_cmdline(const char *str, int len, bool redraw)
if (redraw && !cmd_silent) {
msg_no_more = true;
- i = cmdline_row;
+ int i = cmdline_row;
cursorcmd();
draw_cmdline(ccline.cmdpos, ccline.cmdlen - ccline.cmdpos);
// Avoid clearing the rest of the line too often.
@@ -3606,6 +3627,7 @@ void put_on_cmdline(const char *str, int len, bool redraw)
}
msg_no_more = false;
}
+ int m;
if (KeyTyped) {
m = Columns * Rows;
if (m < 0) { // overflow, Columns or Rows at weird value
@@ -3614,8 +3636,8 @@ void put_on_cmdline(const char *str, int len, bool redraw)
} else {
m = MAXCOL;
}
- for (i = 0; i < len; i++) {
- c = cmdline_charsize(ccline.cmdpos);
+ for (int i = 0; i < len; i++) {
+ int c = cmdline_charsize(ccline.cmdpos);
// count ">" for a double-wide char that doesn't fit.
correct_screencol(ccline.cmdpos, c, &ccline.cmdspos);
// Stop cursor at the end of the screen, but do increment the
@@ -3625,9 +3647,7 @@ void put_on_cmdline(const char *str, int len, bool redraw)
ccline.cmdspos += c;
}
c = utfc_ptr2len(ccline.cmdbuff + ccline.cmdpos) - 1;
- if (c > len - i - 1) {
- c = len - i - 1;
- }
+ c = MIN(c, len - i - 1);
ccline.cmdpos += c;
i += c;
ccline.cmdpos++;
@@ -3864,17 +3884,13 @@ void cursorcmd(void)
}
if (ui_has(kUICmdline)) {
- if (ccline.redraw_state < kCmdRedrawPos) {
- ccline.redraw_state = kCmdRedrawPos;
- }
+ ccline.redraw_state = MAX(ccline.redraw_state, kCmdRedrawPos);
return;
}
msg_row = cmdline_row + (ccline.cmdspos / Columns);
msg_col = ccline.cmdspos % Columns;
- if (msg_row >= Rows) {
- msg_row = Rows - 1;
- }
+ msg_row = MIN(msg_row, Rows - 1);
msg_cursor_goto(msg_row, msg_col);
}
@@ -4070,18 +4086,22 @@ static char *get_cmdline_completion(void)
return NULL;
}
- set_expand_context(p->xpc);
- if (p->xpc->xp_context == EXPAND_UNSUCCESSFUL) {
+ int xp_context = p->xpc->xp_context;
+ if (xp_context == EXPAND_NOTHING) {
+ set_expand_context(p->xpc);
+ xp_context = p->xpc->xp_context;
+ p->xpc->xp_context = EXPAND_NOTHING;
+ }
+ if (xp_context == EXPAND_UNSUCCESSFUL) {
return NULL;
}
- char *cmd_compl = get_user_cmd_complete(p->xpc, p->xpc->xp_context);
+ char *cmd_compl = get_user_cmd_complete(NULL, xp_context);
if (cmd_compl == NULL) {
return NULL;
}
- if (p->xpc->xp_context == EXPAND_USER_LIST
- || p->xpc->xp_context == EXPAND_USER_DEFINED) {
+ if (xp_context == EXPAND_USER_LIST || xp_context == EXPAND_USER_DEFINED) {
size_t buflen = strlen(cmd_compl) + strlen(p->xpc->xp_arg) + 2;
char *buffer = xmalloc(buflen);
snprintf(buffer, buflen, "%s,%s", cmd_compl, p->xpc->xp_arg);
@@ -4112,6 +4132,15 @@ void f_getcmdpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = p != NULL ? p->cmdpos + 1 : 0;
}
+/// "getcmdprompt()" function
+void f_getcmdprompt(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
+{
+ CmdlineInfo *p = get_ccline_ptr();
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = p != NULL && p->cmdprompt != NULL
+ ? xstrdup(p->cmdprompt) : NULL;
+}
+
/// "getcmdscreenpos()" function
void f_getcmdscreenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
{
@@ -4166,11 +4195,8 @@ static int set_cmdline_pos(int pos)
// The position is not set directly but after CTRL-\ e or CTRL-R = has
// changed the command line.
- if (pos < 0) {
- new_cmdpos = 0;
- } else {
- new_cmdpos = pos;
- }
+ new_cmdpos = MAX(0, pos);
+
return 0;
}
@@ -4276,7 +4302,7 @@ const char *did_set_cedit(optset_T *args)
cedit_key = -1;
} else {
int n = string_to_key(p_cedit);
- if (vim_isprintc(n)) {
+ if (n == 0 || vim_isprintc(n)) {
return e_invarg;
}
cedit_key = n;
@@ -4297,7 +4323,6 @@ static int open_cmdwin(void)
win_T *old_curwin = curwin;
int i;
garray_T winsizes;
- char typestr[2];
int save_restart_edit = restart_edit;
int save_State = State;
bool save_exmode = exmode_active;
@@ -4440,9 +4465,7 @@ static int open_cmdwin(void)
cmdwin_result = 0;
// Trigger CmdwinEnter autocommands.
- typestr[0] = (char)cmdwin_type;
- typestr[1] = NUL;
- apply_autocmds(EVENT_CMDWINENTER, typestr, typestr, false, curbuf);
+ trigger_cmd_autocmd(cmdwin_type, EVENT_CMDWINENTER);
if (restart_edit != 0) { // autocmd with ":startinsert"
stuffcharReadbuff(K_NOP);
}
@@ -4460,7 +4483,7 @@ static int open_cmdwin(void)
const bool save_KeyTyped = KeyTyped;
// Trigger CmdwinLeave autocommands.
- apply_autocmds(EVENT_CMDWINLEAVE, typestr, typestr, false, curbuf);
+ trigger_cmd_autocmd(cmdwin_type, EVENT_CMDWINLEAVE);
// Restore KeyTyped in case it is modified by autocommands
KeyTyped = save_KeyTyped;
@@ -4623,14 +4646,132 @@ char *script_get(exarg_T *const eap, size_t *const lenp)
return (char *)ga.ga_data;
}
-static void set_search_match(pos_T *t)
+/// This function is used by f_input() and f_inputdialog() functions. The third
+/// argument to f_input() specifies the type of completion to use at the
+/// prompt. The third argument to f_inputdialog() specifies the value to return
+/// when the user cancels the prompt.
+void get_user_input(const typval_T *const argvars, typval_T *const rettv, const bool inputdialog,
+ const bool secret)
+ FUNC_ATTR_NONNULL_ALL
{
- // First move cursor to end of match, then to the start. This
- // moves the whole match onto the screen when 'nowrap' is set.
- t->lnum += search_match_lines;
- t->col = search_match_endcol;
- if (t->lnum > curbuf->b_ml.ml_line_count) {
- t->lnum = curbuf->b_ml.ml_line_count;
- coladvance(curwin, MAXCOL);
+ rettv->v_type = VAR_STRING;
+ rettv->vval.v_string = NULL;
+
+ const char *prompt;
+ const char *defstr = "";
+ typval_T *cancelreturn = NULL;
+ typval_T cancelreturn_strarg2 = TV_INITIAL_VALUE;
+ const char *xp_name = NULL;
+ Callback input_callback = { .type = kCallbackNone };
+ char prompt_buf[NUMBUFLEN];
+ char defstr_buf[NUMBUFLEN];
+ char cancelreturn_buf[NUMBUFLEN];
+ char xp_name_buf[NUMBUFLEN];
+ char def[1] = { 0 };
+ if (argvars[0].v_type == VAR_DICT) {
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ emsg(_("E5050: {opts} must be the only argument"));
+ return;
+ }
+ dict_T *const dict = argvars[0].vval.v_dict;
+ prompt = tv_dict_get_string_buf_chk(dict, S_LEN("prompt"), prompt_buf, "");
+ if (prompt == NULL) {
+ return;
+ }
+ defstr = tv_dict_get_string_buf_chk(dict, S_LEN("default"), defstr_buf, "");
+ if (defstr == NULL) {
+ return;
+ }
+ dictitem_T *cancelreturn_di = tv_dict_find(dict, S_LEN("cancelreturn"));
+ if (cancelreturn_di != NULL) {
+ cancelreturn = &cancelreturn_di->di_tv;
+ }
+ xp_name = tv_dict_get_string_buf_chk(dict, S_LEN("completion"),
+ xp_name_buf, def);
+ if (xp_name == NULL) { // error
+ return;
+ }
+ if (xp_name == def) { // default to NULL
+ xp_name = NULL;
+ }
+ if (!tv_dict_get_callback(dict, S_LEN("highlight"), &input_callback)) {
+ return;
+ }
+ } else {
+ prompt = tv_get_string_buf_chk(&argvars[0], prompt_buf);
+ if (prompt == NULL) {
+ return;
+ }
+ if (argvars[1].v_type != VAR_UNKNOWN) {
+ defstr = tv_get_string_buf_chk(&argvars[1], defstr_buf);
+ if (defstr == NULL) {
+ return;
+ }
+ if (argvars[2].v_type != VAR_UNKNOWN) {
+ const char *const strarg2 = tv_get_string_buf_chk(&argvars[2], cancelreturn_buf);
+ if (strarg2 == NULL) {
+ return;
+ }
+ if (inputdialog) {
+ cancelreturn_strarg2.v_type = VAR_STRING;
+ cancelreturn_strarg2.vval.v_string = (char *)strarg2;
+ cancelreturn = &cancelreturn_strarg2;
+ } else {
+ xp_name = strarg2;
+ }
+ }
+ }
+ }
+
+ int xp_type = EXPAND_NOTHING;
+ char *xp_arg = NULL;
+ if (xp_name != NULL) {
+ // input() with a third argument: completion
+ const int xp_namelen = (int)strlen(xp_name);
+
+ uint32_t argt = 0;
+ if (parse_compl_arg(xp_name, xp_namelen, &xp_type,
+ &argt, &xp_arg) == FAIL) {
+ return;
+ }
+ }
+
+ const bool cmd_silent_save = cmd_silent;
+
+ cmd_silent = false; // Want to see the prompt.
+ // Only the part of the message after the last NL is considered as
+ // prompt for the command line, unlsess cmdline is externalized
+ const char *p = prompt;
+ if (!ui_has(kUICmdline)) {
+ const char *lastnl = strrchr(prompt, '\n');
+ if (lastnl != NULL) {
+ p = lastnl + 1;
+ msg_start();
+ msg_clr_eos();
+ msg_puts_len(prompt, p - prompt, get_echo_attr());
+ msg_didout = false;
+ msg_starthere();
+ }
+ }
+ cmdline_row = msg_row;
+
+ stuffReadbuffSpec(defstr);
+
+ const int save_ex_normal_busy = ex_normal_busy;
+ ex_normal_busy = 0;
+ rettv->vval.v_string = getcmdline_prompt(secret ? NUL : '@', p, get_echo_attr(),
+ xp_type, xp_arg, input_callback);
+ ex_normal_busy = save_ex_normal_busy;
+ callback_free(&input_callback);
+
+ if (rettv->vval.v_string == NULL && cancelreturn != NULL) {
+ tv_copy(cancelreturn, rettv);
}
+
+ xfree(xp_arg);
+
+ // Since the user typed this, no need to wait for return.
+ need_wait_return = false;
+ msg_didout = false;
+ cmd_silent = cmd_silent_save;
}
diff --git a/src/nvim/ex_session.c b/src/nvim/ex_session.c
index 0e5d2fe4f5..50ee197ef4 100644
--- a/src/nvim/ex_session.c
+++ b/src/nvim/ex_session.c
@@ -15,6 +15,7 @@
#include "nvim/autocmd.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
diff --git a/src/nvim/extmark.c b/src/nvim/extmark.c
index 3236590010..c20c7dea23 100644
--- a/src/nvim/extmark.c
+++ b/src/nvim/extmark.c
@@ -54,12 +54,12 @@
/// must not be used during iteration!
void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col, int end_row,
colnr_T end_col, DecorInline decor, uint16_t decor_flags, bool right_gravity,
- bool end_right_gravity, bool no_undo, bool invalidate, bool scoped, Error *err)
+ bool end_right_gravity, bool no_undo, bool invalidate, Error *err)
{
uint32_t *ns = map_put_ref(uint32_t, uint32_t)(buf->b_extmark_ns, ns_id, NULL, NULL);
uint32_t id = idp ? *idp : 0;
- uint16_t flags = mt_flags(right_gravity, no_undo, invalidate, decor.ext, scoped) | decor_flags;
+ uint16_t flags = mt_flags(right_gravity, no_undo, invalidate, decor.ext) | decor_flags;
if (id == 0) {
id = ++*ns;
} else {
@@ -70,24 +70,19 @@ void extmark_set(buf_T *buf, uint32_t ns_id, uint32_t *idp, int row, colnr_T col
extmark_del_id(buf, ns_id, id);
} else {
assert(marktree_itr_valid(itr));
- bool invalid = mt_invalid(old_mark);
if (old_mark.pos.row == row && old_mark.pos.col == col) {
// not paired: we can revise in place
- if (!invalid && mt_decor_any(old_mark)) {
- // TODO(bfredl): conflict of concerns: buf_decor_remove() must process
- // the buffer as if MT_FLAG_DECOR_SIGNTEXT is already removed, however
- // marktree must precisely adjust the set of flags from the old set to the new
- uint16_t save_flags = mt_itr_rawkey(itr).flags;
- mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_DECOR_SIGNTEXT;
+ if (!mt_invalid(old_mark) && mt_decor_any(old_mark)) {
+ mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_EXTERNAL_MASK;
buf_decor_remove(buf, row, row, col, mt_decor(old_mark), true);
- mt_itr_rawkey(itr).flags = save_flags;
}
- marktree_revise_flags(buf->b_marktree, itr, flags);
+ mt_itr_rawkey(itr).flags |= flags;
mt_itr_rawkey(itr).decor_data = decor.data;
+ marktree_revise_meta(buf->b_marktree, itr, old_mark);
goto revised;
}
marktree_del_itr(buf->b_marktree, itr, false);
- if (!invalid) {
+ if (!mt_invalid(old_mark)) {
buf_decor_remove(buf, old_mark.pos.row, old_mark.pos.row, old_mark.pos.col,
mt_decor(old_mark), true);
}
@@ -116,9 +111,10 @@ static void extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col, bool
{
MarkTreeIter itr[1] = { 0 };
MTKey key = marktree_lookup(buf->b_marktree, mark, itr);
- if (key.pos.row < 0 || (key.pos.row == row && key.pos.col == col)) {
- return;
- }
+ bool move = key.pos.row >= 0 && (key.pos.row != row || key.pos.col != col);
+ // Already valid keys were being revalidated, presumably when encountering a
+ // SavePos from a modified mark. Avoid adding that to the decor again.
+ invalid = invalid && mt_invalid(key);
// Only the position before undo needs to be redrawn here,
// as the position after undo should be marked as changed.
@@ -130,19 +126,22 @@ static void extmark_setraw(buf_T *buf, uint64_t mark, int row, colnr_T col, bool
int row2 = 0;
if (invalid) {
mt_itr_rawkey(itr).flags &= (uint16_t) ~MT_FLAG_INVALID;
- } else if (key.flags & MT_FLAG_DECOR_SIGNTEXT && buf->b_signcols.autom) {
+ marktree_revise_meta(buf->b_marktree, itr, key);
+ } else if (move && key.flags & MT_FLAG_DECOR_SIGNTEXT && buf->b_signcols.autom) {
MTPos end = marktree_get_altpos(buf->b_marktree, key, NULL);
row1 = MIN(end.row, MIN(key.pos.row, row));
row2 = MAX(end.row, MAX(key.pos.row, row));
buf_signcols_count_range(buf, row1, row2, 0, kTrue);
}
- marktree_move(buf->b_marktree, itr, row, col);
+ if (move) {
+ marktree_move(buf->b_marktree, itr, row, col);
+ }
if (invalid) {
- MTPos end = marktree_get_altpos(buf->b_marktree, key, NULL);
- buf_put_decor(buf, mt_decor(key), row, end.row);
- } else if (key.flags & MT_FLAG_DECOR_SIGNTEXT && buf->b_signcols.autom) {
+ row2 = mt_paired(key) ? marktree_get_altpos(buf->b_marktree, key, NULL).row : row;
+ buf_put_decor(buf, mt_decor(key), row, row2);
+ } else if (move && key.flags & MT_FLAG_DECOR_SIGNTEXT && buf->b_signcols.autom) {
buf_signcols_count_range(buf, row1, row2, 0, kNone);
}
}
@@ -391,6 +390,7 @@ void extmark_splice_delete(buf_T *buf, int l_row, colnr_T l_col, int u_row, coln
} else {
invalidated = true;
mt_itr_rawkey(itr).flags |= MT_FLAG_INVALID;
+ marktree_revise_meta(buf->b_marktree, itr, mark);
buf_decor_remove(buf, mark.pos.row, endpos.row, mark.pos.col, mt_decor(mark), false);
}
}
diff --git a/src/nvim/file_search.c b/src/nvim/file_search.c
index a8b0dbddee..cdfd281718 100644
--- a/src/nvim/file_search.c
+++ b/src/nvim/file_search.c
@@ -52,7 +52,9 @@
#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/autocmd_defs.h"
-#include "nvim/buffer_defs.h"
+#include "nvim/charset.h"
+#include "nvim/cursor.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -63,6 +65,7 @@
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
+#include "nvim/normal.h"
#include "nvim/option.h"
#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
@@ -349,23 +352,24 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i
search_ctx->ffsc_stopdirs_v = xmalloc(sizeof(char *));
do {
- char *helper;
- void *ptr;
-
- helper = walker;
- ptr = xrealloc(search_ctx->ffsc_stopdirs_v,
- (dircount + 1) * sizeof(char *));
+ char *helper = walker;
+ void *ptr = xrealloc(search_ctx->ffsc_stopdirs_v,
+ (dircount + 1) * sizeof(char *));
search_ctx->ffsc_stopdirs_v = ptr;
walker = vim_strchr(walker, ';');
+ assert(!walker || walker - helper >= 0);
+ size_t len = walker ? (size_t)(walker - helper) : strlen(helper);
+ // "" means ascent till top of directory tree.
+ if (*helper != NUL && !vim_isAbsName(helper) && len + 1 < MAXPATHL) {
+ // Make the stop dir an absolute path name.
+ xmemcpyz(ff_expand_buffer, helper, len);
+ search_ctx->ffsc_stopdirs_v[dircount - 1] = FullName_save(helper, len);
+ } else {
+ search_ctx->ffsc_stopdirs_v[dircount - 1] = xmemdupz(helper, len);
+ }
if (walker) {
- assert(walker - helper >= 0);
- search_ctx->ffsc_stopdirs_v[dircount - 1] = xstrnsave(helper, (size_t)(walker - helper));
walker++;
- } else {
- // this might be "", which means ascent till top of directory tree.
- search_ctx->ffsc_stopdirs_v[dircount - 1] = xstrdup(helper);
}
-
dircount++;
} while (walker != NULL);
search_ctx->ffsc_stopdirs_v[dircount - 1] = NULL;
@@ -451,7 +455,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i
STRCPY(buf, ff_expand_buffer);
STRCPY(buf + eb_len, search_ctx->ffsc_fix_path);
if (os_isdir(buf)) {
- STRCAT(ff_expand_buffer, search_ctx->ffsc_fix_path);
+ strcat(ff_expand_buffer, search_ctx->ffsc_fix_path);
add_pathsep(ff_expand_buffer);
} else {
char *p = path_tail(search_ctx->ffsc_fix_path);
@@ -479,7 +483,7 @@ void *vim_findfile_init(char *path, char *filename, char *stopdirs, int level, i
+ strlen(search_ctx->ffsc_fix_path + len)
+ 1);
STRCPY(temp, search_ctx->ffsc_fix_path + len);
- STRCAT(temp, search_ctx->ffsc_wc_path);
+ strcat(temp, search_ctx->ffsc_wc_path);
xfree(search_ctx->ffsc_wc_path);
xfree(wc_path);
search_ctx->ffsc_wc_path = temp;
@@ -505,24 +509,36 @@ error_return:
/// @return the stopdir string. Check that ';' is not escaped.
char *vim_findfile_stopdir(char *buf)
{
- char *r_ptr = buf;
-
- while (*r_ptr != NUL && *r_ptr != ';') {
- if (r_ptr[0] == '\\' && r_ptr[1] == ';') {
- // Overwrite the escape char,
- // use strlen(r_ptr) to move the trailing '\0'.
- STRMOVE(r_ptr, r_ptr + 1);
- r_ptr++;
+ for (; *buf != NUL && *buf != ';' && (buf[0] != '\\' || buf[1] != ';'); buf++) {}
+ char *dst = buf;
+ if (*buf == ';') {
+ goto is_semicolon;
+ }
+ if (*buf == NUL) {
+ goto is_nul;
+ }
+ goto start;
+ while (*buf != NUL && *buf != ';') {
+ if (buf[0] == '\\' && buf[1] == ';') {
+start:
+ // Overwrite the escape char.
+ *dst++ = ';';
+ buf += 2;
+ } else {
+ *dst++ = *buf++;
}
- r_ptr++;
}
- if (*r_ptr == ';') {
- *r_ptr = 0;
- r_ptr++;
- } else if (*r_ptr == NUL) {
- r_ptr = NULL;
+ assert(dst < buf);
+ *dst = NUL;
+ if (*buf == ';') {
+is_semicolon:
+ *buf = NUL;
+ buf++;
+ } else { // if (*buf == NUL)
+is_nul:
+ buf = NULL;
}
- return r_ptr;
+ return buf;
}
/// Clean up the given search context. Can handle a NULL pointer.
@@ -669,7 +685,7 @@ char *vim_findfile(void *search_ctx_arg)
ff_free_stack_element(stackp);
goto fail;
}
- STRCAT(file_path, stackp->ffs_fix_path);
+ strcat(file_path, stackp->ffs_fix_path);
if (!add_pathsep(file_path)) {
ff_free_stack_element(stackp);
goto fail;
@@ -769,7 +785,7 @@ char *vim_findfile(void *search_ctx_arg)
ff_free_stack_element(stackp);
goto fail;
}
- STRCAT(file_path, search_ctx->ffsc_file_to_search);
+ strcat(file_path, search_ctx->ffsc_file_to_search);
// Try without extra suffix and then with suffixes
// from 'suffixesadd'.
@@ -880,11 +896,12 @@ char *vim_findfile(void *search_ctx_arg)
if (search_ctx->ffsc_start_dir
&& search_ctx->ffsc_stopdirs_v != NULL && !got_int) {
ff_stack_T *sptr;
+ // path_end may point to the NUL or the previous path separator
+ ptrdiff_t plen = (path_end - search_ctx->ffsc_start_dir) + (*path_end != NUL);
// is the last starting directory in the stop list?
if (ff_path_in_stoplist(search_ctx->ffsc_start_dir,
- (int)(path_end - search_ctx->ffsc_start_dir),
- search_ctx->ffsc_stopdirs_v)) {
+ (size_t)plen, search_ctx->ffsc_stopdirs_v)) {
break;
}
@@ -910,7 +927,7 @@ char *vim_findfile(void *search_ctx_arg)
if (!add_pathsep(file_path)) {
goto fail;
}
- STRCAT(file_path, search_ctx->ffsc_fix_path);
+ strcat(file_path, search_ctx->ffsc_fix_path);
// create a new stack entry
sptr = ff_create_stack_element(file_path,
@@ -1219,7 +1236,7 @@ static void ff_clear(ff_search_ctx_T *search_ctx)
/// check if the given path is in the stopdirs
///
/// @return true if yes else false
-static bool ff_path_in_stoplist(char *path, int path_len, char **stopdirs_v)
+static bool ff_path_in_stoplist(char *path, size_t path_len, char **stopdirs_v)
{
// eat up trailing path separators, except the first
while (path_len > 1 && vim_ispathsep(path[path_len - 1])) {
@@ -1232,20 +1249,16 @@ static bool ff_path_in_stoplist(char *path, int path_len, char **stopdirs_v)
}
for (int i = 0; stopdirs_v[i] != NULL; i++) {
- if ((int)strlen(stopdirs_v[i]) > path_len) {
- // match for parent directory. So '/home' also matches
- // '/home/rks'. Check for PATHSEP in stopdirs_v[i], else
- // '/home/r' would also match '/home/rks'
- if (path_fnamencmp(stopdirs_v[i], path, (size_t)path_len) == 0
- && vim_ispathsep(stopdirs_v[i][path_len])) {
- return true;
- }
- } else {
- if (path_fnamecmp(stopdirs_v[i], path) == 0) {
- return true;
- }
+ // match for parent directory. So '/home' also matches
+ // '/home/rks'. Check for PATHSEP in stopdirs_v[i], else
+ // '/home/r' would also match '/home/rks'
+ if (path_fnamencmp(stopdirs_v[i], path, path_len) == 0
+ && (strlen(stopdirs_v[i]) <= path_len
+ || vim_ispathsep(stopdirs_v[i][path_len]))) {
+ return true;
}
}
+
return false;
}
@@ -1493,6 +1506,225 @@ theend:
return file_name;
}
+/// Get the file name at the cursor.
+/// If Visual mode is active, use the selected text if it's in one line.
+/// Returns the name in allocated memory, NULL for failure.
+char *grab_file_name(int count, linenr_T *file_lnum)
+{
+ int options = FNAME_MESS | FNAME_EXP | FNAME_REL | FNAME_UNESC;
+ if (VIsual_active) {
+ size_t len;
+ char *ptr;
+ if (get_visual_text(NULL, &ptr, &len) == FAIL) {
+ return NULL;
+ }
+ // Only recognize ":123" here
+ if (file_lnum != NULL && ptr[len] == ':' && isdigit((uint8_t)ptr[len + 1])) {
+ char *p = ptr + len + 1;
+
+ *file_lnum = getdigits_int32(&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);
+}
+
+/// Return the file name under or after the cursor.
+///
+/// The 'path' option is searched if the file name is not absolute.
+/// The string returned has been alloc'ed and should be freed by the caller.
+/// NULL is returned if the file name or file is not found.
+///
+/// options:
+/// FNAME_MESS give error messages
+/// FNAME_EXP expand to path
+/// FNAME_HYP check for hypertext link
+/// FNAME_INCL apply "includeexpr"
+char *file_name_at_cursor(int options, int count, linenr_T *file_lnum)
+{
+ return file_name_in_line(get_cursor_line_ptr(),
+ curwin->w_cursor.col, options, count, curbuf->b_ffname,
+ file_lnum);
+}
+
+/// @param rel_fname file we are searching relative to
+/// @param file_lnum line number after the file name
+///
+/// @return the name of the file under or after ptr[col].
+///
+/// Otherwise like file_name_at_cursor().
+char *file_name_in_line(char *line, int col, int options, int count, char *rel_fname,
+ linenr_T *file_lnum)
+{
+ // search forward for what could be the start of a file name
+ char *ptr = line + col;
+ while (*ptr != NUL && !vim_isfilec((uint8_t)(*ptr))) {
+ MB_PTR_ADV(ptr);
+ }
+ if (*ptr == NUL) { // nothing found
+ if (options & FNAME_MESS) {
+ emsg(_("E446: No file name under cursor"));
+ }
+ return NULL;
+ }
+
+ size_t len;
+ bool in_type = true;
+ bool is_url = false;
+
+ // Search backward for first char of the file name.
+ // Go one char back to ":" before "//", or to the drive letter before ":\" (even if ":"
+ // is not in 'isfname').
+ while (ptr > line) {
+ if ((len = (size_t)(utf_head_off(line, ptr - 1))) > 0) {
+ ptr -= len + 1;
+ } else if (vim_isfilec((uint8_t)ptr[-1]) || ((options & FNAME_HYP) && path_is_url(ptr - 1))) {
+ ptr--;
+ } else {
+ break;
+ }
+ }
+
+ // Search forward for the last char of the file name.
+ // Also allow ":/" when ':' is not in 'isfname'.
+ len = path_has_drive_letter(ptr) ? 2 : 0;
+ while (vim_isfilec((uint8_t)ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ')
+ || ((options & FNAME_HYP) && path_is_url(ptr + len))
+ || (is_url && vim_strchr(":?&=", (uint8_t)ptr[len]) != NULL)) {
+ // After type:// we also include :, ?, & and = as valid characters, so that
+ // http://google.com:8080?q=this&that=ok works.
+ if ((ptr[len] >= 'A' && ptr[len] <= 'Z') || (ptr[len] >= 'a' && ptr[len] <= 'z')) {
+ if (in_type && path_is_url(ptr + len + 1)) {
+ is_url = true;
+ }
+ } else {
+ in_type = false;
+ }
+
+ if (ptr[len] == '\\' && ptr[len + 1] == ' ') {
+ // Skip over the "\" in "\ ".
+ len++;
+ }
+ len += (size_t)(utfc_ptr2len(ptr + len));
+ }
+
+ // If there is trailing punctuation, remove it.
+ // But don't remove "..", could be a directory name.
+ if (len > 2 && vim_strchr(".,:;!", (uint8_t)ptr[len - 1]) != NULL
+ && ptr[len - 2] != '.') {
+ len--;
+ }
+
+ if (file_lnum != NULL) {
+ const char *line_english = " line ";
+ const char *line_transl = _(line_msg);
+
+ // Get the number after the file name and a separator character.
+ // Also accept " line 999" with and without the same translation as
+ // used in last_set_msg().
+ char *p = ptr + len;
+ if (strncmp(p, line_english, strlen(line_english)) == 0) {
+ p += strlen(line_english);
+ } else if (strncmp(p, line_transl, strlen(line_transl)) == 0) {
+ p += strlen(line_transl);
+ } else {
+ p = skipwhite(p);
+ }
+ if (*p != NUL) {
+ if (!isdigit((uint8_t)(*p))) {
+ p++; // skip the separator
+ }
+ p = skipwhite(p);
+ if (isdigit((uint8_t)(*p))) {
+ *file_lnum = (linenr_T)getdigits_long(&p, false, 0);
+ }
+ }
+ }
+
+ return find_file_name_in_path(ptr, len, options, count, rel_fname);
+}
+
+static char *eval_includeexpr(const char *const ptr, const size_t len)
+{
+ const sctx_T save_sctx = current_sctx;
+ set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t)len);
+ current_sctx = curbuf->b_p_script_ctx[BV_INEX].script_ctx;
+
+ char *res = eval_to_string_safe(curbuf->b_p_inex,
+ was_set_insecurely(curwin, kOptIncludeexpr, OPT_LOCAL),
+ true);
+
+ set_vim_var_string(VV_FNAME, NULL, 0);
+ current_sctx = save_sctx;
+ return res;
+}
+
+/// Return the name of the file ptr[len] in 'path'.
+/// Otherwise like file_name_at_cursor().
+///
+/// @param rel_fname file we are searching relative to
+char *find_file_name_in_path(char *ptr, size_t len, int options, long count, char *rel_fname)
+{
+ char *file_name;
+ char *tofree = NULL;
+
+ if (len == 0) {
+ return NULL;
+ }
+
+ if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL) {
+ tofree = eval_includeexpr(ptr, len);
+ if (tofree != NULL) {
+ ptr = tofree;
+ len = strlen(ptr);
+ }
+ }
+
+ if (options & FNAME_EXP) {
+ char *file_to_find = NULL;
+ char *search_ctx = NULL;
+
+ file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
+ true, rel_fname, &file_to_find, &search_ctx);
+
+ // If the file could not be found in a normal way, try applying
+ // 'includeexpr' (unless done already).
+ if (file_name == NULL
+ && !(options & FNAME_INCL) && *curbuf->b_p_inex != NUL) {
+ tofree = eval_includeexpr(ptr, len);
+ if (tofree != NULL) {
+ ptr = tofree;
+ len = strlen(ptr);
+ file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
+ true, rel_fname, &file_to_find, &search_ctx);
+ }
+ }
+ if (file_name == NULL && (options & FNAME_MESS)) {
+ char c = ptr[len];
+ ptr[len] = NUL;
+ semsg(_("E447: Can't find file \"%s\" in path"), ptr);
+ ptr[len] = c;
+ }
+
+ // Repeat finding the file "count" times. This matters when it
+ // appears several times in the path.
+ while (file_name != NULL && --count > 0) {
+ xfree(file_name);
+ file_name = find_file_in_path(ptr, len, options, false, rel_fname,
+ &file_to_find, &search_ctx);
+ }
+
+ xfree(file_to_find);
+ vim_findfile_cleanup(search_ctx);
+ } else {
+ file_name = xstrnsave(ptr, len);
+ }
+
+ xfree(tofree);
+
+ return file_name;
+}
+
void do_autocmd_dirchanged(char *new_dir, CdScope scope, CdCause cause, bool pre)
{
static bool recursive = false;
diff --git a/src/nvim/file_search.h b/src/nvim/file_search.h
index 2450472681..f21d2b8468 100644
--- a/src/nvim/file_search.h
+++ b/src/nvim/file_search.h
@@ -2,6 +2,7 @@
#include <stddef.h> // IWYU pragma: keep
+#include "nvim/pos_defs.h" // IWYU pragma: keep
#include "nvim/types_defs.h" // IWYU pragma: keep
#include "nvim/vim_defs.h" // IWYU pragma: keep
@@ -12,6 +13,17 @@ enum {
FINDFILE_BOTH = 2, ///< files and directories
};
+/// Values for file_name_in_line()
+enum {
+ FNAME_MESS = 1, ///< give error message
+ FNAME_EXP = 2, ///< expand to path
+ FNAME_HYP = 4, ///< check for hypertext link
+ FNAME_INCL = 8, ///< apply 'includeexpr'
+ FNAME_REL = 16, ///< ".." and "./" are relative to the (current)
+ ///< file instead of the current directory
+ FNAME_UNESC = 32, ///< remove backslashes used for escaping
+};
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "file_search.h.generated.h"
#endif
diff --git a/src/nvim/fileio.c b/src/nvim/fileio.c
index df9c4928c9..724d754ca7 100644
--- a/src/nvim/fileio.c
+++ b/src/nvim/fileio.c
@@ -27,6 +27,7 @@
#include "nvim/diff.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_eval.h"
@@ -887,10 +888,7 @@ retry:
// 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 = 0x10000 + linerest;
- if (size > 0x100000) {
- size = 0x100000;
- }
+ size = MIN(0x10000 + linerest, 0x100000);
}
// Protect against the argument of lalloc() going negative.
@@ -2418,7 +2416,7 @@ char *modname(const char *fname, const char *ext, bool prepend_dot)
// the file name has at most BASENAMELEN characters.
if (strlen(ptr) > BASENAMELEN) {
- ptr[BASENAMELEN] = '\0';
+ ptr[BASENAMELEN] = NUL;
}
char *s = ptr + strlen(ptr);
@@ -2656,7 +2654,6 @@ static int rename_with_tmp(const char *const from, const char *const to)
int vim_rename(const char *from, const char *to)
FUNC_ATTR_NONNULL_ALL
{
- char *errmsg = NULL;
bool use_tmp_file = false;
// When the names are identical, there is nothing to do. When they refer
@@ -2700,61 +2697,57 @@ int vim_rename(const char *from, const char *to)
}
// Rename() failed, try copying the file.
- int perm = os_getperm(from);
- // For systems that support ACL: get the ACL from the original file.
- vim_acl_T acl = os_get_acl(from);
- int fd_in = os_open(from, O_RDONLY, 0);
- if (fd_in < 0) {
- os_free_acl(acl);
+ int ret = vim_copyfile(from, to);
+ if (ret != OK) {
return -1;
}
- // Create the new file with same permissions as the original.
- int fd_out = os_open(to, O_CREAT|O_EXCL|O_WRONLY|O_NOFOLLOW, perm);
- if (fd_out < 0) {
- close(fd_in);
- os_free_acl(acl);
- return -1;
+ if (os_fileinfo(from, &from_info)) {
+ os_remove(from);
}
- // Avoid xmalloc() here as vim_rename() is called by buf_write() when nvim
- // is `preserve_exit()`ing.
- char *buffer = try_malloc(WRITEBUFSIZE);
- if (buffer == NULL) {
- close(fd_out);
- close(fd_in);
- os_free_acl(acl);
- return -1;
- }
+ return 0;
+}
- int n;
- while ((n = read_eintr(fd_in, buffer, WRITEBUFSIZE)) > 0) {
- if (write_eintr(fd_out, buffer, (size_t)n) != n) {
- errmsg = _("E208: Error writing to \"%s\"");
- break;
+/// Create the new file with same permissions as the original.
+/// Return FAIL for failure, OK for success.
+int vim_copyfile(const char *from, const char *to)
+{
+ char *errmsg = NULL;
+
+#ifdef HAVE_READLINK
+ FileInfo from_info;
+ if (os_fileinfo_link(from, &from_info) && S_ISLNK(from_info.stat.st_mode)) {
+ int ret = -1;
+
+ char linkbuf[MAXPATHL + 1];
+ ssize_t len = readlink(from, linkbuf, MAXPATHL);
+ if (len > 0) {
+ linkbuf[len] = NUL;
+
+ // Create link
+ ret = symlink(linkbuf, to);
}
- }
- xfree(buffer);
- close(fd_in);
- if (close(fd_out) < 0) {
- errmsg = _("E209: Error closing \"%s\"");
- }
- if (n < 0) {
- errmsg = _("E210: Error reading \"%s\"");
- to = from;
+ return ret == 0 ? OK : FAIL;
}
-#ifndef UNIX // For Unix os_open() already set the permission.
- os_setperm(to, perm);
#endif
+
+ // For systems that support ACL: get the ACL from the original file.
+ vim_acl_T acl = os_get_acl(from);
+
+ if (os_copy(from, to, UV_FS_COPYFILE_EXCL) != 0) {
+ os_free_acl(acl);
+ return FAIL;
+ }
+
os_set_acl(to, acl);
os_free_acl(acl);
if (errmsg != NULL) {
semsg(errmsg, to);
- return -1;
+ return FAIL;
}
- os_remove(from);
- return 0;
+ return OK;
}
static bool already_warned = false;
@@ -2799,9 +2792,7 @@ int check_timestamps(int focus)
bufref_T bufref;
set_bufref(&bufref, buf);
const int n = buf_check_timestamp(buf);
- if (didit < n) {
- didit = n;
- }
+ didit = MAX(didit, n);
if (n > 0 && !bufref_valid(&bufref)) {
// Autocommands have removed the buffer, start at the first one again.
buf = firstbuf;
@@ -3191,11 +3182,7 @@ void buf_reload(buf_T *buf, int orig_mode, bool reload_options)
// Restore the topline and cursor position and check it (lines may
// have been removed).
- if (old_topline > curbuf->b_ml.ml_line_count) {
- curwin->w_topline = curbuf->b_ml.ml_line_count;
- } else {
- curwin->w_topline = old_topline;
- }
+ curwin->w_topline = MIN(old_topline, curbuf->b_ml.ml_line_count);
curwin->w_cursor = old_cursor;
check_cursor(curwin);
update_topline(curwin);
@@ -3277,18 +3264,12 @@ static void vim_mktempdir(void)
char tmp[TEMP_FILE_PATH_MAXLEN];
char path[TEMP_FILE_PATH_MAXLEN];
char user[40] = { 0 };
- char appname[40] = { 0 };
os_get_username(user, sizeof(user));
// Usernames may contain slashes! #19240
memchrsub(user, '/', '_', sizeof(user));
memchrsub(user, '\\', '_', sizeof(user));
- // Appname may be a relative path, replace slashes to make it name-like.
- xstrlcpy(appname, get_appname(), sizeof(appname));
- memchrsub(appname, '/', '%', sizeof(appname));
- memchrsub(appname, '\\', '%', sizeof(appname));
-
// Make sure the umask doesn't remove the executable bit.
// "repl" has been reported to use "0177".
mode_t umask_save = umask(0077);
@@ -3296,14 +3277,15 @@ static void vim_mktempdir(void)
// Expand environment variables, leave room for "/tmp/nvim.<user>/XXXXXX/999999999".
expand_env((char *)temp_dirs[i], tmp, TEMP_FILE_PATH_MAXLEN - 64);
if (!os_isdir(tmp)) {
+ if (strequal("$TMPDIR", temp_dirs[i])) {
+ WLOG("$TMPDIR tempdir not a directory (or does not exist): %s", tmp);
+ }
continue;
}
// "/tmp/" exists, now try to create "/tmp/nvim.<user>/".
add_pathsep(tmp);
-
- xstrlcat(tmp, appname, sizeof(tmp));
- xstrlcat(tmp, ".", sizeof(tmp));
+ xstrlcat(tmp, "nvim.", sizeof(tmp));
xstrlcat(tmp, user, sizeof(tmp));
os_mkdir(tmp, 0700); // Always create, to avoid a race.
bool owned = os_file_owned(tmp);
@@ -3329,7 +3311,7 @@ static void vim_mktempdir(void)
#endif
// If our "root" tempdir is invalid or fails, proceed without "<user>/".
// Else user1 could break user2 by creating "/tmp/nvim.user2/".
- tmp[strlen(tmp) - strlen(user)] = '\0';
+ tmp[strlen(tmp) - strlen(user)] = NUL;
}
// Now try to create "/tmp/nvim.<user>/XXXXXX".
@@ -3636,6 +3618,7 @@ bool match_file_list(char *list, char *sfname, char *ffname)
/// @param pat_end first char after pattern or NULL
/// @param allow_dirs Result passed back out in here
/// @param no_bslash Don't use a backward slash as pathsep
+/// (only makes a difference when BACKSLASH_IN_FILENAME in defined)
///
/// @return NULL on failure.
char *file_pat_to_reg_pat(const char *pat, const char *pat_end, char *allow_dirs, int no_bslash)
diff --git a/src/nvim/fold.c b/src/nvim/fold.c
index 59a4dc6aad..e7231c31ab 100644
--- a/src/nvim/fold.c
+++ b/src/nvim/fold.c
@@ -22,6 +22,7 @@
#include "nvim/decoration.h"
#include "nvim/diff.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_session.h"
@@ -247,9 +248,7 @@ bool hasFoldingWin(win_T *const win, const linenr_T lnum, linenr_T *const firstp
return false;
}
- if (last > win->w_buffer->b_ml.ml_line_count) {
- last = win->w_buffer->b_ml.ml_line_count;
- }
+ last = MIN(last, win->w_buffer->b_ml.ml_line_count);
if (lastp != NULL) {
*lastp = last;
}
@@ -617,15 +616,11 @@ void foldCreate(win_T *wp, pos_T start, pos_T end)
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.lnum > fp->fd_top) {
- start_rel.lnum = fp->fd_top;
- }
+ start_rel.lnum = MIN(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;
- }
+ end_rel.lnum = MAX(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;
@@ -721,12 +716,8 @@ void deleteFold(win_T *const wp, const linenr_T start, const linenr_T end, const
(int)(found_fp - (fold_T *)found_ga->ga_data),
recursive);
} else {
- if (first_lnum > found_fp->fd_top + found_off) {
- first_lnum = found_fp->fd_top + found_off;
- }
- if (last_lnum < lnum) {
- last_lnum = lnum;
- }
+ first_lnum = MIN(first_lnum, found_fp->fd_top + found_off);
+ last_lnum = MAX(last_lnum, lnum);
if (!did_one) {
parseMarker(wp);
}
@@ -787,14 +778,10 @@ void foldUpdate(win_T *wp, linenr_T top, linenr_T bot)
}
if (wp->w_folds.ga_len > 0) {
- linenr_T maybe_small_start = top;
- linenr_T maybe_small_end = bot;
-
// Mark all folds from top to bot (or bot to top) as maybe-small.
- if (top > bot) {
- maybe_small_start = bot;
- maybe_small_end = top;
- }
+ linenr_T maybe_small_start = MIN(top, bot);
+ linenr_T maybe_small_end = MAX(top, bot);
+
fold_T *fp;
foldFind(&wp->w_folds, maybe_small_start, &fp);
while (fp < (fold_T *)wp->w_folds.ga_data + wp->w_folds.ga_len
@@ -1224,11 +1211,7 @@ static linenr_T setManualFoldWin(win_T *wp, linenr_T lnum, bool opening, bool re
// Change from level-dependent folding to manual.
if (use_level || fp->fd_flags == FD_LEVEL) {
use_level = true;
- if (level >= wp->w_p_fdl) {
- fp->fd_flags = FD_CLOSED;
- } else {
- fp->fd_flags = FD_OPEN;
- }
+ fp->fd_flags = level >= wp->w_p_fdl ? FD_CLOSED : FD_OPEN;
fp2 = (fold_T *)fp->fd_nested.ga_data;
for (int j = 0; j < fp->fd_nested.ga_len; j++) {
fp2[j].fd_flags = FD_LEVEL;
@@ -1353,6 +1336,9 @@ void deleteFoldRecurse(buf_T *bp, garray_T *gap)
// foldMarkAdjust() {{{2
/// Update line numbers of folds for inserted/deleted lines.
+///
+/// We are adjusting the folds in the range from line1 til line2,
+/// make sure that line2 does not get smaller than line1
void foldMarkAdjust(win_T *wp, linenr_T line1, linenr_T line2, linenr_T amount,
linenr_T amount_after)
{
@@ -1361,6 +1347,9 @@ void foldMarkAdjust(win_T *wp, linenr_T line1, linenr_T line2, linenr_T amount,
if (amount == MAXLNUM && line2 >= line1 && line2 - line1 >= -amount_after) {
line2 = line1 - amount_after - 1;
}
+ if (line2 < line1) {
+ line2 = line1;
+ }
// If appending a line in Insert mode, it should be included in the fold
// just above the line.
if ((State & MODE_INSERT) && amount == 1 && line2 == MAXLNUM) {
@@ -1377,15 +1366,11 @@ static void foldMarkAdjustRecurse(win_T *wp, garray_T *gap, linenr_T line1, line
return;
}
- linenr_T top;
-
// In Insert mode an inserted line at the top of a fold is considered part
// of the fold, otherwise it isn't.
- if ((State & MODE_INSERT) && amount == 1 && line2 == MAXLNUM) {
- top = line1 + 1;
- } else {
- top = line1;
- }
+ linenr_T top = ((State & MODE_INSERT) && amount == 1 && line2 == MAXLNUM)
+ ? line1 + 1
+ : line1;
// Find the fold containing or just below "line1".
fold_T *fp;
@@ -1479,9 +1464,7 @@ static int getDeepestNestingRecurse(garray_T *gap)
fold_T *fp = (fold_T *)gap->ga_data;
for (int i = 0; i < gap->ga_len; i++) {
int level = getDeepestNestingRecurse(&fp[i].fd_nested) + 1;
- if (level > maxlevel) {
- maxlevel = level;
- }
+ maxlevel = MAX(maxlevel, level);
}
return maxlevel;
@@ -1597,7 +1580,6 @@ static void foldCreateMarkers(win_T *wp, pos_T start, pos_T end)
static void foldAddMarker(buf_T *buf, pos_T pos, const char *marker, size_t markerlen)
{
char *cms = buf->b_p_cms;
- char *newline;
char *p = strstr(buf->b_p_cms, "%s");
bool line_is_comment = false;
linenr_T lnum = pos.lnum;
@@ -1613,7 +1595,7 @@ static void foldAddMarker(buf_T *buf, pos_T pos, const char *marker, size_t mark
// Check if the line ends with an unclosed comment
skip_comment(line, false, false, &line_is_comment);
- newline = xmalloc(line_len + markerlen + strlen(cms) + 1);
+ char *newline = xmalloc(line_len + markerlen + strlen(cms) + 1);
STRCPY(newline, line);
// Append the marker to the end of the line
if (p == NULL || line_is_comment) {
@@ -1736,10 +1718,7 @@ char *get_foldtext(win_T *wp, linenr_T lnum, linenr_T lnume, foldinfo_T foldinfo
// Set "v:folddashes" to a string of "level" dashes.
// Set "v:foldlevel" to "level".
- int level = foldinfo.fi_level;
- if (level > (int)sizeof(dashes) - 1) {
- level = (int)sizeof(dashes) - 1;
- }
+ int level = MIN(foldinfo.fi_level, (int)sizeof(dashes) - 1);
memset(dashes, '-', (size_t)level);
dashes[level] = NUL;
set_vim_var_string(VV_FOLDDASHES, dashes, -1);
@@ -1936,9 +1915,7 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot)
// When deleting lines at the end of the buffer "top" can be past the end
// of the buffer.
- if (top > wp->w_buffer->b_ml.ml_line_count) {
- top = wp->w_buffer->b_ml.ml_line_count;
- }
+ top = MIN(top, wp->w_buffer->b_ml.ml_line_count);
fline_T fline;
@@ -2046,9 +2023,7 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot)
if (fpn != NULL && current_fdl == fline.lvl) {
linenr_T fold_end_lnum = fold_start_lnum + fpn->fd_len;
- if (fold_end_lnum > bot) {
- bot = fold_end_lnum;
- }
+ bot = MAX(bot, fold_end_lnum);
}
}
@@ -2126,9 +2101,7 @@ static void foldUpdateIEMS(win_T *const wp, linenr_T top, linenr_T bot)
if (wp->w_redraw_top == 0 || wp->w_redraw_top > top) {
wp->w_redraw_top = top;
}
- if (wp->w_redraw_bot < end) {
- wp->w_redraw_bot = end;
- }
+ wp->w_redraw_bot = MAX(wp->w_redraw_bot, end);
}
invalid_top = 0;
@@ -2204,10 +2177,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level,
// and after the first line of the fold, set the level to zero to
// force the fold to end. Do the same when had_end is set: Previous
// line was marked as end of a fold.
- lvl = flp->lvl;
- if (lvl > MAX_LEVEL) {
- lvl = MAX_LEVEL;
- }
+ lvl = MIN(flp->lvl, MAX_LEVEL);
if (flp->lnum > firstlnum
&& (level > lvl - flp->start || level >= flp->had_end)) {
lvl = 0;
@@ -2262,12 +2232,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level,
while (!got_int) {
// set concat to 1 if it's allowed to concatenate this fold
// with a previous one that touches it.
- int concat;
- if (flp->start != 0 || flp->had_end <= MAX_LEVEL) {
- concat = 0;
- } else {
- concat = 1;
- }
+ int concat = (flp->start != 0 || flp->had_end <= MAX_LEVEL) ? 0 : 1;
// Find an existing fold to re-use. Preferably one that
// includes startlnum, otherwise one that ends just before
@@ -2423,9 +2388,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level,
if (lvl > level && fp != NULL) {
// There is a nested fold, handle it recursively.
// At least do one line (can happen when finish is true).
- if (bot < flp->lnum) {
- bot = flp->lnum;
- }
+ bot = MAX(bot, flp->lnum);
// Line numbers in the nested fold are relative to the start of
// this fold.
@@ -2555,9 +2518,7 @@ static linenr_T foldUpdateIEMSRecurse(garray_T *const gap, const int level,
// Need to redraw the lines we inspected, which might be further down than
// was asked for.
- if (bot < flp->lnum - 1) {
- bot = flp->lnum - 1;
- }
+ bot = MAX(bot, flp->lnum - 1);
return bot;
}
@@ -2897,17 +2858,11 @@ static void foldlevelIndent(fline_T *flp)
// depends on surrounding lines
if (*s == NUL || vim_strchr(flp->wp->w_p_fdi, (uint8_t)(*s)) != NULL) {
// first and last line can't be undefined, use level 0
- if (lnum == 1 || lnum == buf->b_ml.ml_line_count) {
- flp->lvl = 0;
- } else {
- flp->lvl = -1;
- }
+ flp->lvl = (lnum == 1 || lnum == buf->b_ml.ml_line_count) ? 0 : -1;
} else {
flp->lvl = get_indent_buf(buf, lnum) / get_sw_value(buf);
}
- if (flp->lvl > flp->wp->w_p_fdn) {
- flp->lvl = (int)MAX(0, flp->wp->w_p_fdn);
- }
+ flp->lvl = MIN(flp->lvl, (int)MAX(0, flp->wp->w_p_fdn));
}
// foldlevelDiff() {{{2
@@ -2915,11 +2870,7 @@ static void foldlevelIndent(fline_T *flp)
/// Doesn't use any caching.
static void foldlevelDiff(fline_T *flp)
{
- if (diff_infold(flp->wp, flp->lnum + flp->off)) {
- flp->lvl = 1;
- } else {
- flp->lvl = 0;
- }
+ flp->lvl = (diff_infold(flp->wp, flp->lnum + flp->off)) ? 1 : 0;
}
// foldlevelExpr() {{{2
@@ -3066,11 +3017,7 @@ static void foldlevelMarker(fline_T *flp)
if (n > 0) {
flp->lvl = n;
flp->lvl_next = n;
- if (n <= start_lvl) {
- flp->start = 1;
- } else {
- flp->start = n - start_lvl;
- }
+ flp->start = MAX(n - start_lvl, 1);
}
} else {
flp->lvl++;
@@ -3087,9 +3034,7 @@ static void foldlevelMarker(fline_T *flp)
flp->lvl = n;
flp->lvl_next = n - 1;
// never start a fold with an end marker
- if (flp->lvl_next > start_lvl) {
- flp->lvl_next = start_lvl;
- }
+ flp->lvl_next = MIN(flp->lvl_next, start_lvl);
}
} else {
flp->lvl_next--;
@@ -3100,9 +3045,7 @@ static void foldlevelMarker(fline_T *flp)
}
// The level can't go negative, must be missing a start marker.
- if (flp->lvl_next < 0) {
- flp->lvl_next = 0;
- }
+ flp->lvl_next = MAX(flp->lvl_next, 0);
}
// foldlevelSyntax() {{{2
@@ -3243,11 +3186,7 @@ static void foldclosed_both(typval_T *argvars, typval_T *rettv, bool end)
linenr_T first;
linenr_T last;
if (hasFoldingWin(curwin, lnum, &first, &last, false, NULL)) {
- if (end) {
- rettv->vval.v_number = (varnumber_T)last;
- } else {
- rettv->vval.v_number = (varnumber_T)first;
- }
+ rettv->vval.v_number = (varnumber_T)(end ? last : first);
return;
}
}
@@ -3314,7 +3253,7 @@ void f_foldtext(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
char *r = xmalloc(len);
snprintf(r, len, txt, dashes, count);
len = strlen(r);
- STRCAT(r, s);
+ strcat(r, s);
// remove 'foldmarker' and 'commentstring'
foldtext_cleanup(r + len);
rettv->vval.v_string = r;
@@ -3335,9 +3274,7 @@ void f_foldtextresult(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
entered = true;
linenr_T lnum = tv_get_lnum(argvars);
// Treat illegal types and illegal string values for {lnum} the same.
- if (lnum < 0) {
- lnum = 0;
- }
+ lnum = MAX(lnum, 0);
foldinfo_T info = fold_info(curwin, lnum);
if (info.fi_lines > 0) {
diff --git a/src/nvim/garray.c b/src/nvim/garray.c
index f87a196361..4f8ba30522 100644
--- a/src/nvim/garray.c
+++ b/src/nvim/garray.c
@@ -78,16 +78,12 @@ void ga_grow(garray_T *gap, int n)
}
// the garray grows by at least growsize
- if (n < gap->ga_growsize) {
- n = gap->ga_growsize;
- }
+ n = MAX(n, gap->ga_growsize);
// A linear growth is very inefficient when the array grows big. This
// is a compromise between allocating memory that won't be used and too
// many copy operations. A factor of 1.5 seems reasonable.
- if (n < gap->ga_len / 2) {
- n = gap->ga_len / 2;
- }
+ n = MAX(n, gap->ga_len / 2);
int new_maxlen = gap->ga_len + n;
diff --git a/src/nvim/generators/c_grammar.lua b/src/nvim/generators/c_grammar.lua
index 9ce9f3d7a6..ed6e30ea10 100644
--- a/src/nvim/generators/c_grammar.lua
+++ b/src/nvim/generators/c_grammar.lua
@@ -35,11 +35,7 @@ local cdoc_comment = P('///') * opt(Ct(Cg(rep(space) * rep(not_nl), 'comment')))
local c_preproc = P('#') * rep(not_nl)
local dllexport = P('DLLEXPORT') * rep1(ws)
-local typed_container = (
- (P('ArrayOf(') + P('DictionaryOf(') + P('Dict('))
- * rep1(any - P(')'))
- * P(')')
-)
+local typed_container = ((P('ArrayOf(') + P('DictOf(') + P('Dict(')) * rep1(any - P(')')) * P(')'))
local c_id = (typed_container + (letter * rep(alpha)))
local c_void = P('void')
@@ -91,7 +87,9 @@ local c_proto = Ct(
* (P(';') + (P('{') * nl + (impl_line ^ 0) * P('}')))
)
-local c_field = Ct(Cg(c_id, 'type') * ws * Cg(c_id, 'name') * fill * P(';') * fill)
+local dict_key = P('DictKey(') * Cg(rep1(any - P(')')), 'dict_key') * P(')')
+local keyset_field =
+ Ct(Cg(c_id, 'type') * ws * Cg(c_id, 'name') * fill * (dict_key ^ -1) * fill * P(';') * fill)
local c_keyset = Ct(
P('typedef')
* ws
@@ -99,7 +97,7 @@ local c_keyset = Ct(
* fill
* P('{')
* fill
- * Cg(Ct(c_field ^ 1), 'fields')
+ * Cg(Ct(keyset_field ^ 1), 'fields')
* P('}')
* fill
* P('Dict')
diff --git a/src/nvim/generators/gen_api_dispatch.lua b/src/nvim/generators/gen_api_dispatch.lua
index 62c99ce082..9e0aa407a1 100644
--- a/src/nvim/generators/gen_api_dispatch.lua
+++ b/src/nvim/generators/gen_api_dispatch.lua
@@ -72,14 +72,19 @@ local keysets = {}
local function add_keyset(val)
local keys = {}
local types = {}
+ local c_names = {}
local is_set_name = 'is_set__' .. val.keyset_name .. '_'
local has_optional = false
for i, field in ipairs(val.fields) do
+ local dict_key = field.dict_key or field.name
if field.type ~= 'Object' then
- types[field.name] = field.type
+ types[dict_key] = field.type
end
if field.name ~= is_set_name and field.type ~= 'OptionalKeys' then
- table.insert(keys, field.name)
+ table.insert(keys, dict_key)
+ if dict_key ~= field.name then
+ c_names[dict_key] = field.name
+ end
else
if i > 1 then
error("'is_set__{type}_' must be first if present")
@@ -94,6 +99,7 @@ local function add_keyset(val)
table.insert(keysets, {
name = val.keyset_name,
keys = keys,
+ c_names = c_names,
types = types,
has_optional = has_optional,
})
@@ -210,15 +216,15 @@ for _, f in ipairs(functions) do
end
f_exported.parameters = {}
for i, param in ipairs(f.parameters) do
- if param[1] == 'DictionaryOf(LuaRef)' then
- param = { 'Dictionary', param[2] }
+ if param[1] == 'DictOf(LuaRef)' then
+ param = { 'Dict', param[2] }
elseif startswith(param[1], 'Dict(') then
- param = { 'Dictionary', param[2] }
+ param = { 'Dict', param[2] }
end
f_exported.parameters[i] = param
end
if startswith(f.return_type, 'Dict(') then
- f_exported.return_type = 'Dictionary'
+ f_exported.return_type = 'Dict'
end
exported_functions[#exported_functions + 1] = f_exported
end
@@ -306,6 +312,7 @@ local keysets_defs = assert(io.open(keysets_outputf, 'wb'))
-- so that the dispatcher can find the C functions that you are creating!
-- ===========================================================================
output:write([[
+#include "nvim/errors.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/globals.h"
@@ -331,19 +338,6 @@ output:write([[
keysets_defs:write('// IWYU pragma: private, include "nvim/api/private/dispatch.h"\n\n')
for _, k in ipairs(keysets) do
- local c_name = {}
-
- for i = 1, #k.keys do
- -- some keys, like "register" are c keywords and get
- -- escaped with a trailing _ in the struct.
- if vim.endswith(k.keys[i], '_') then
- local orig = k.keys[i]
- k.keys[i] = string.sub(k.keys[i], 1, #k.keys[i] - 1)
- c_name[k.keys[i]] = orig
- k.types[k.keys[i]] = k.types[orig]
- end
- end
-
local neworder, hashfun = hashy.hashy_hash(k.name, k.keys, function(idx)
return k.name .. '_table[' .. idx .. '].str'
end)
@@ -353,6 +347,8 @@ for _, k in ipairs(keysets) do
local function typename(type)
if type == 'HLGroupID' then
return 'kObjectTypeInteger'
+ elseif type == 'StringArray' then
+ return 'kUnpackTypeStringArray'
elseif type ~= nil then
return 'kObjectType' .. type
else
@@ -373,7 +369,7 @@ for _, k in ipairs(keysets) do
.. '", offsetof(KeyDict_'
.. k.name
.. ', '
- .. (c_name[key] or key)
+ .. (k.c_names[key] or key)
.. '), '
.. typename(k.types[key])
.. ', '
@@ -410,7 +406,7 @@ local function real_type(type)
if rv:match('Array') then
rv = 'Array'
else
- rv = 'Dictionary'
+ rv = 'Dict'
end
end
return rv
@@ -470,7 +466,7 @@ for i = 1, #functions do
output:write('\n ' .. converted .. ' = args.items[' .. (j - 1) .. '];\n')
elseif rt:match('^KeyDict_') then
converted = '&' .. converted
- output:write('\n if (args.items[' .. (j - 1) .. '].type == kObjectTypeDictionary) {') --luacheck: ignore 631
+ output:write('\n if (args.items[' .. (j - 1) .. '].type == kObjectTypeDict) {') --luacheck: ignore 631
output:write('\n memset(' .. converted .. ', 0, sizeof(*' .. converted .. '));') -- TODO: neeeee
output:write(
'\n if (!api_dict_to_keydict('
@@ -479,7 +475,7 @@ for i = 1, #functions do
.. rt
.. '_get_field, args.items['
.. (j - 1)
- .. '].data.dictionary, error)) {'
+ .. '].data.dict, error)) {'
)
output:write('\n goto cleanup;')
output:write('\n }')
@@ -558,7 +554,7 @@ for i = 1, #functions do
)
end
-- accept empty lua tables as empty dictionaries
- if rt:match('^Dictionary') then
+ if rt:match('^Dict') then
output:write(
'\n } else if (args.items['
.. (j - 1)
@@ -566,7 +562,7 @@ for i = 1, #functions do
.. (j - 1)
.. '].data.array.size == 0) {'
) --luacheck: ignore 631
- output:write('\n ' .. converted .. ' = (Dictionary)ARRAY_DICT_INIT;')
+ output:write('\n ' .. converted .. ' = (Dict)ARRAY_DICT_INIT;')
end
output:write('\n } else {')
output:write(
@@ -647,7 +643,7 @@ for i = 1, #functions do
if string.match(ret_type, '^KeyDict_') then
local table = string.sub(ret_type, 9) .. '_table'
output:write(
- '\n ret = DICTIONARY_OBJ(api_keydict_to_dict(&rv, '
+ '\n ret = DICT_OBJ(api_keydict_to_dict(&rv, '
.. table
.. ', ARRAY_SIZE('
.. table
@@ -783,12 +779,12 @@ local function process_function(fn)
local param = fn.parameters[j]
local cparam = string.format('arg%u', j)
local param_type = real_type(param[1])
- local extra = param_type == 'Dictionary' and 'false, ' or ''
+ local extra = param_type == 'Dict' and 'false, ' or ''
local arg_free_code = ''
if param[1] == 'Object' then
extra = 'true, '
arg_free_code = 'api_luarefs_free_object(' .. cparam .. ');'
- elseif param[1] == 'DictionaryOf(LuaRef)' then
+ elseif param[1] == 'DictOf(LuaRef)' then
extra = 'true, '
arg_free_code = 'api_luarefs_free_dict(' .. cparam .. ');'
elseif param[1] == 'LuaRef' then
diff --git a/src/nvim/generators/gen_api_ui_events.lua b/src/nvim/generators/gen_api_ui_events.lua
index 516b5ad5ae..3e8ae19c9a 100644
--- a/src/nvim/generators/gen_api_ui_events.lua
+++ b/src/nvim/generators/gen_api_ui_events.lua
@@ -54,7 +54,7 @@ local function call_ui_event_method(output, ev)
local kind = ev.parameters[j][1]
if kind ~= 'Object' then
if kind == 'HlAttrs' then
- kind = 'Dictionary'
+ kind = 'Dict'
end
output:write('\n || args.items[' .. (j - 1) .. '].type != kObjectType' .. kind .. '')
end
@@ -74,7 +74,7 @@ local function call_ui_event_method(output, ev)
output:write(
'ui_client_dict2hlattrs(args.items['
.. (j - 1)
- .. '].data.dictionary, '
+ .. '].data.dict, '
.. (hlattrs_args_count == 0 and 'true' or 'false')
.. ');\n'
)
@@ -128,8 +128,16 @@ for i = 1, #events do
write_signature(call_output, ev, '')
call_output:write('\n{\n')
if ev.remote_only then
+ -- Lua callbacks may emit other events or the same event again. Avoid the latter
+ -- by adding a recursion guard to each generated function that may call a Lua callback.
+ call_output:write(' static bool entered = false;\n')
+ call_output:write(' if (entered) {\n')
+ call_output:write(' return;\n')
+ call_output:write(' }\n')
+ call_output:write(' entered = true;\n')
write_arglist(call_output, ev)
call_output:write(' ui_call_event("' .. ev.name .. '", ' .. args .. ');\n')
+ call_output:write(' entered = false;\n')
elseif ev.compositor_impl then
call_output:write(' ui_comp_' .. ev.name)
write_signature(call_output, ev, '', true)
@@ -197,7 +205,8 @@ for _, ev in ipairs(events) do
ev_exported[attr] = ev[attr]
end
for _, p in ipairs(ev_exported.parameters) do
- if p[1] == 'HlAttrs' then
+ if p[1] == 'HlAttrs' or p[1] == 'Dict' then
+ -- TODO(justinmk): for back-compat, but do clients actually look at this?
p[1] = 'Dictionary'
end
end
diff --git a/src/nvim/generators/gen_declarations.lua b/src/nvim/generators/gen_declarations.lua
index 5d1e586fe6..2ec0e9ab68 100644
--- a/src/nvim/generators/gen_declarations.lua
+++ b/src/nvim/generators/gen_declarations.lua
@@ -2,6 +2,7 @@ local fname = arg[1]
local static_fname = arg[2]
local non_static_fname = arg[3]
local preproc_fname = arg[4]
+local static_basename = arg[5]
local lpeg = vim.lpeg
@@ -69,7 +70,7 @@ local raw_word = concat(w, any_amount(aw))
local right_word = concat(raw_word, neg_look_ahead(aw))
local word = branch(
concat(
- branch(lit('ArrayOf('), lit('DictionaryOf('), lit('Dict(')), -- typed container macro
+ branch(lit('ArrayOf('), lit('DictOf('), lit('Dict(')), -- typed container macro
one_or_more(any_character - lit(')')),
lit(')')
),
@@ -207,6 +208,10 @@ if fname:find('.*/src/nvim/.*%.c$') then
// IWYU pragma: private, include "%s"
]]):format(header_fname:gsub('.*/src/nvim/', 'nvim/')) .. non_static
end
+elseif fname:find('.*/src/nvim/.*%.h$') then
+ static = ([[
+// IWYU pragma: private, include "%s"
+]]):format(fname:gsub('.*/src/nvim/', 'nvim/')) .. static
elseif non_static_fname:find('/include/api/private/dispatch_wrappers%.h%.generated%.h$') then
non_static = [[
// IWYU pragma: private, include "nvim/api/private/dispatch.h"
@@ -235,6 +240,7 @@ local declendpos = 0
local curdir = nil
local is_needed_file = false
local init_is_nl = true
+local any_static = false
while init ~= nil do
if init_is_nl and text:sub(init, init) == '#' then
local line, dir, file = text:match(filepattern, init)
@@ -276,6 +282,9 @@ while init ~= nil do
end
declaration = declaration .. '\n'
if declaration:sub(1, 6) == 'static' then
+ if declaration:find('FUNC_ATTR_') then
+ any_static = true
+ end
static = static .. declaration
else
declaration = 'DLLEXPORT ' .. declaration
@@ -303,6 +312,21 @@ F = io.open(static_fname, 'w')
F:write(static)
F:close()
+if any_static then
+ F = io.open(fname, 'r')
+ local orig_text = F:read('*a')
+ local pat = '\n#%s?include%s+"' .. static_basename .. '"\n'
+ local pat_comment = '\n#%s?include%s+"' .. static_basename .. '"%s*//'
+ if not string.find(orig_text, pat) and not string.find(orig_text, pat_comment) then
+ error('fail: missing include for ' .. static_basename .. ' in ' .. fname)
+ end
+ F:close()
+end
+
+if non_static_fname == 'SKIP' then
+ return -- only want static declarations
+end
+
-- Before generating the non-static headers, check if the current file (if
-- exists) is different from the new one. If they are the same, we won't touch
-- the current version to avoid triggering an unnecessary rebuilds of modules
@@ -312,7 +336,7 @@ if F ~= nil then
if F:read('*a') == non_static then
os.exit(0)
end
- io.close(F)
+ F:close()
end
F = io.open(non_static_fname, 'w')
diff --git a/src/nvim/generators/gen_eval.lua b/src/nvim/generators/gen_eval.lua
index 1ce7f1af9d..443c68e008 100644
--- a/src/nvim/generators/gen_eval.lua
+++ b/src/nvim/generators/gen_eval.lua
@@ -19,6 +19,7 @@ hashpipe:write([[
#include "nvim/digraph.h"
#include "nvim/eval.h"
#include "nvim/eval/buffer.h"
+#include "nvim/eval/fs.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/vars.h"
diff --git a/src/nvim/generators/gen_options.lua b/src/nvim/generators/gen_options.lua
index 0718d965c6..591a6b93df 100644
--- a/src/nvim/generators/gen_options.lua
+++ b/src/nvim/generators/gen_options.lua
@@ -148,7 +148,7 @@ local get_defaults = function(d, n)
return value_dumpers[type(d)](d)
end
---- @type {[1]:string,[2]:string}[]
+--- @type [string,string][]
local defines = {}
--- @param i integer
diff --git a/src/nvim/generators/gen_options_enum.lua b/src/nvim/generators/gen_options_enum.lua
index 9a3953fcbc..d1419137d3 100644
--- a/src/nvim/generators/gen_options_enum.lua
+++ b/src/nvim/generators/gen_options_enum.lua
@@ -80,7 +80,7 @@ enum_w('')
--- @type { [string]: string }
local option_index = {}
--- Generate option index enum and populate the `option_index` dictionary.
+-- Generate option index enum and populate the `option_index` dict.
enum_w('typedef enum {')
enum_w(' kOptInvalid = -1,')
diff --git a/src/nvim/generators/gen_unicode_tables.lua b/src/nvim/generators/gen_unicode_tables.lua
deleted file mode 100644
index 6cedb5db50..0000000000
--- a/src/nvim/generators/gen_unicode_tables.lua
+++ /dev/null
@@ -1,323 +0,0 @@
--- Script creates the following tables in unicode_tables.generated.h:
---
--- 1. doublewidth and ambiguous tables: sorted list of non-overlapping closed
--- intervals. Codepoints in these intervals have double (W or F) or ambiguous
--- (A) east asian width respectively.
--- 2. combining table: same as the above, but characters inside are combining
--- characters (i.e. have general categories equal to Mn, Mc or Me).
--- 3. foldCase, toLower and toUpper tables used to convert characters to
--- folded/lower/upper variants. In these tables first two values are
--- character ranges: like in previous tables they are sorted and must be
--- non-overlapping. Third value means step inside the range: e.g. if it is
--- 2 then interval applies only to first, third, fifth, … character in range.
--- Fourth value is number that should be added to the codepoint to yield
--- folded/lower/upper codepoint.
--- 4. emoji_wide and emoji_all tables: sorted lists of non-overlapping closed
--- intervals of Emoji characters. emoji_wide contains all the characters
--- which don't have ambiguous or double width, and emoji_all has all Emojis.
-if arg[1] == '--help' then
- print('Usage:')
- print(' gen_unicode_tables.lua unicode/ unicode_tables.generated.h')
- os.exit(0)
-end
-
-local basedir = arg[1]
-local pathsep = package.config:sub(1, 1)
-local get_path = function(fname)
- return basedir .. pathsep .. fname
-end
-
-local unicodedata_fname = get_path('UnicodeData.txt')
-local casefolding_fname = get_path('CaseFolding.txt')
-local eastasianwidth_fname = get_path('EastAsianWidth.txt')
-local emoji_fname = get_path('emoji-data.txt')
-
-local utf_tables_fname = arg[2]
-
-local split_on_semicolons = function(s)
- local ret = {}
- local idx = 1
- while idx <= #s + 1 do
- local item = s:match('^[^;]*', idx)
- idx = idx + #item + 1
- if idx <= #s + 1 then
- assert(s:sub(idx - 1, idx - 1) == ';')
- end
- item = item:gsub('^%s*', '')
- item = item:gsub('%s*$', '')
- table.insert(ret, item)
- end
- return ret
-end
-
-local fp_lines_to_lists = function(fp, n, has_comments)
- local ret = {}
- local line
- local i = 0
- while true do
- i = i + 1
- line = fp:read('*l')
- if not line then
- break
- end
- if not has_comments or (line:sub(1, 1) ~= '#' and not line:match('^%s*$')) then
- local l = split_on_semicolons(line)
- if #l ~= n then
- io.stderr:write(('Found %s items in line %u, expected %u\n'):format(#l, i, n))
- io.stderr:write('Line: ' .. line .. '\n')
- return nil
- end
- table.insert(ret, l)
- end
- end
- return ret
-end
-
-local parse_data_to_props = function(ud_fp)
- return fp_lines_to_lists(ud_fp, 15, false)
-end
-
-local parse_fold_props = function(cf_fp)
- return fp_lines_to_lists(cf_fp, 4, true)
-end
-
-local parse_width_props = function(eaw_fp)
- return fp_lines_to_lists(eaw_fp, 2, true)
-end
-
-local parse_emoji_props = function(emoji_fp)
- return fp_lines_to_lists(emoji_fp, 2, true)
-end
-
-local make_range = function(start, end_, step, add)
- if step and add then
- return (' {0x%x, 0x%x, %d, %d},\n'):format(start, end_, step == 0 and -1 or step, add)
- else
- return (' {0x%04x, 0x%04x},\n'):format(start, end_)
- end
-end
-
-local build_convert_table = function(ut_fp, props, cond_func, nl_index, table_name)
- ut_fp:write('static const convertStruct ' .. table_name .. '[] = {\n')
- local start = -1
- local end_ = -1
- local step = 0
- local add = -1
- for _, p in ipairs(props) do
- if cond_func(p) then
- local n = tonumber(p[1], 16)
- local nl = tonumber(p[nl_index], 16)
- if start >= 0 and add == (nl - n) and (step == 0 or n - end_ == step) then
- -- Continue with the same range.
- step = n - end_
- end_ = n
- else
- if start >= 0 then
- -- Produce previous range.
- ut_fp:write(make_range(start, end_, step, add))
- end
- start = n
- end_ = n
- step = 0
- add = nl - n
- end
- end
- end
- if start >= 0 then
- ut_fp:write(make_range(start, end_, step, add))
- end
- ut_fp:write('};\n')
-end
-
-local build_case_table = function(ut_fp, dataprops, table_name, index)
- local cond_func = function(p)
- return p[index] ~= ''
- end
- return build_convert_table(ut_fp, dataprops, cond_func, index, 'to' .. table_name)
-end
-
-local build_fold_table = function(ut_fp, foldprops)
- local cond_func = function(p)
- return (p[2] == 'C' or p[2] == 'S')
- end
- return build_convert_table(ut_fp, foldprops, cond_func, 3, 'foldCase')
-end
-
-local build_combining_table = function(ut_fp, dataprops)
- ut_fp:write('static const struct interval combining[] = {\n')
- local start = -1
- local end_ = -1
- for _, p in ipairs(dataprops) do
- -- The 'Mc' property was removed, it does take up space.
- if ({ Mn = true, Me = true })[p[3]] then
- local n = tonumber(p[1], 16)
- if start >= 0 and end_ + 1 == n then
- -- Continue with the same range.
- end_ = n
- else
- if start >= 0 then
- -- Produce previous range.
- ut_fp:write(make_range(start, end_))
- end
- start = n
- end_ = n
- end
- end
- end
- if start >= 0 then
- ut_fp:write(make_range(start, end_))
- end
- ut_fp:write('};\n')
-end
-
-local build_width_table = function(ut_fp, dataprops, widthprops, widths, table_name)
- ut_fp:write('static const struct interval ' .. table_name .. '[] = {\n')
- local start = -1
- local end_ = -1
- local dataidx = 1
- local ret = {}
- for _, p in ipairs(widthprops) do
- if widths[p[2]:sub(1, 1)] then
- local rng_start, rng_end = p[1]:find('%.%.')
- local n, n_last
- if rng_start then
- -- It is a range. We don’t check for composing char then.
- n = tonumber(p[1]:sub(1, rng_start - 1), 16)
- n_last = tonumber(p[1]:sub(rng_end + 1), 16)
- else
- n = tonumber(p[1], 16)
- n_last = n
- end
- local dn
- while true do
- dn = tonumber(dataprops[dataidx][1], 16)
- if dn >= n then
- break
- end
- dataidx = dataidx + 1
- end
- if dn ~= n and n_last == n then
- io.stderr:write('Cannot find character ' .. n .. ' in data table.\n')
- end
- -- Only use the char when it’s not a composing char.
- -- But use all chars from a range.
- local dp = dataprops[dataidx]
- if (n_last > n) or not ({ Mn = true, Mc = true, Me = true })[dp[3]] then
- if start >= 0 and end_ + 1 == n then -- luacheck: ignore 542
- -- Continue with the same range.
- else
- if start >= 0 then
- ut_fp:write(make_range(start, end_))
- table.insert(ret, { start, end_ })
- end
- start = n
- end
- end_ = n_last
- end
- end
- end
- if start >= 0 then
- ut_fp:write(make_range(start, end_))
- table.insert(ret, { start, end_ })
- end
- ut_fp:write('};\n')
- return ret
-end
-
-local build_emoji_table = function(ut_fp, emojiprops, doublewidth, ambiwidth)
- local emojiwidth = {}
- local emoji = {}
- for _, p in ipairs(emojiprops) do
- if p[2]:match('Emoji%s+#') then
- local rng_start, rng_end = p[1]:find('%.%.')
- local n
- local n_last
- if rng_start then
- n = tonumber(p[1]:sub(1, rng_start - 1), 16)
- n_last = tonumber(p[1]:sub(rng_end + 1), 16)
- else
- n = tonumber(p[1], 16)
- n_last = n
- end
- if #emoji > 0 and n - 1 == emoji[#emoji][2] then
- emoji[#emoji][2] = n_last
- else
- table.insert(emoji, { n, n_last })
- end
-
- -- Characters below 1F000 may be considered single width traditionally,
- -- making them double width causes problems.
- if n >= 0x1f000 then
- -- exclude characters that are in the ambiguous/doublewidth table
- for _, ambi in ipairs(ambiwidth) do
- if n >= ambi[1] and n <= ambi[2] then
- n = ambi[2] + 1
- end
- if n_last >= ambi[1] and n_last <= ambi[2] then
- n_last = ambi[1] - 1
- end
- end
- for _, double in ipairs(doublewidth) do
- if n >= double[1] and n <= double[2] then
- n = double[2] + 1
- end
- if n_last >= double[1] and n_last <= double[2] then
- n_last = double[1] - 1
- end
- end
-
- if n <= n_last then
- if #emojiwidth > 0 and n - 1 == emojiwidth[#emojiwidth][2] then
- emojiwidth[#emojiwidth][2] = n_last
- else
- table.insert(emojiwidth, { n, n_last })
- end
- end
- end
- end
- end
-
- ut_fp:write('static const struct interval emoji_all[] = {\n')
- for _, p in ipairs(emoji) do
- ut_fp:write(make_range(p[1], p[2]))
- end
- ut_fp:write('};\n')
-
- ut_fp:write('static const struct interval emoji_wide[] = {\n')
- for _, p in ipairs(emojiwidth) do
- ut_fp:write(make_range(p[1], p[2]))
- end
- ut_fp:write('};\n')
-end
-
-local ud_fp = io.open(unicodedata_fname, 'r')
-local dataprops = parse_data_to_props(ud_fp)
-ud_fp:close()
-
-local ut_fp = io.open(utf_tables_fname, 'w')
-
-build_case_table(ut_fp, dataprops, 'Lower', 14)
-build_case_table(ut_fp, dataprops, 'Upper', 13)
-build_combining_table(ut_fp, dataprops)
-
-local cf_fp = io.open(casefolding_fname, 'r')
-local foldprops = parse_fold_props(cf_fp)
-cf_fp:close()
-
-build_fold_table(ut_fp, foldprops)
-
-local eaw_fp = io.open(eastasianwidth_fname, 'r')
-local widthprops = parse_width_props(eaw_fp)
-eaw_fp:close()
-
-local doublewidth =
- build_width_table(ut_fp, dataprops, widthprops, { W = true, F = true }, 'doublewidth')
-local ambiwidth = build_width_table(ut_fp, dataprops, widthprops, { A = true }, 'ambiguous')
-
-local emoji_fp = io.open(emoji_fname, 'r')
-local emojiprops = parse_emoji_props(emoji_fp)
-emoji_fp:close()
-
-build_emoji_table(ut_fp, emojiprops, doublewidth, ambiwidth)
-
-ut_fp:close()
diff --git a/src/nvim/generators/gen_vimvim.lua b/src/nvim/generators/gen_vimvim.lua
index fcdc5bddc2..0675f04b73 100644
--- a/src/nvim/generators/gen_vimvim.lua
+++ b/src/nvim/generators/gen_vimvim.lua
@@ -80,12 +80,13 @@ for _, cmd_desc in ipairs(ex_cmds.cmds) do
end
local vimopt_start = 'syn keyword vimOption contained '
+local vimopt_end = ' skipwhite nextgroup=vimSetEqual,vimSetMod'
w('\n\n' .. vimopt_start)
for _, opt_desc in ipairs(options.options) do
if not opt_desc.immutable then
if lld.line_length > 850 then
- w('\n' .. vimopt_start)
+ w(vimopt_end .. '\n' .. vimopt_start)
end
w(' ' .. opt_desc.full_name)
if opt_desc.abbreviation then
@@ -102,7 +103,9 @@ for _, opt_desc in ipairs(options.options) do
end
end
-w('\n\nsyn case ignore')
+w(vimopt_end .. '\n')
+
+w('\nsyn case ignore')
local vimau_start = 'syn keyword vimAutoEvent contained '
w('\n\n' .. vimau_start)
diff --git a/src/nvim/generators/hashy.lua b/src/nvim/generators/hashy.lua
index 711e695742..ea35064962 100644
--- a/src/nvim/generators/hashy.lua
+++ b/src/nvim/generators/hashy.lua
@@ -73,11 +73,15 @@ function M.switcher(put, tab, maxlen, worst_buck_size)
vim.list_extend(neworder, buck)
local endidx = #neworder
put(" case '" .. c .. "': ")
- put('low = ' .. startidx .. '; ')
- if bucky then
- put('high = ' .. endidx .. '; ')
+ if len == 1 then
+ put('return ' .. startidx .. ';\n')
+ else
+ put('low = ' .. startidx .. '; ')
+ if bucky then
+ put('high = ' .. endidx .. '; ')
+ end
+ put 'break;\n'
end
- put 'break;\n'
end
put ' default: break;\n'
put ' }\n '
@@ -105,13 +109,19 @@ function M.hashy_hash(name, strings, access)
end
local len_pos_buckets, maxlen, worst_buck_size = M.build_pos_hash(strings)
put('int ' .. name .. '_hash(const char *str, size_t len)\n{\n')
- if worst_buck_size > 1 then
+ if maxlen == 1 then
+ put('\n') -- nothing
+ elseif worst_buck_size > 1 then
put(' int low = 0, high = 0;\n')
else
put(' int low = -1;\n')
end
local neworder = M.switcher(put, len_pos_buckets, maxlen, worst_buck_size)
- if worst_buck_size > 1 then
+ if maxlen == 1 then
+ put([[
+ return -1;
+]])
+ elseif worst_buck_size > 1 then
put([[
for (int i = low; i < high; i++) {
if (!memcmp(str, ]] .. access('i') .. [[, len)) {
diff --git a/src/nvim/getchar.c b/src/nvim/getchar.c
index 9b19644b7e..472bc3a850 100644
--- a/src/nvim/getchar.c
+++ b/src/nvim/getchar.c
@@ -13,12 +13,14 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/api/vim.h"
#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -104,10 +106,10 @@ static buffheader_T readbuf1 = { { NULL, { NUL } }, NULL, 0, 0 };
static buffheader_T readbuf2 = { { NULL, { NUL } }, NULL, 0, 0 };
/// Buffer used to store typed characters for vim.on_key().
-static kvec_withinit_t(char, MAXMAPLEN) on_key_buf = KVI_INITIAL_VALUE(on_key_buf);
+static kvec_withinit_t(char, MAXMAPLEN + 1) on_key_buf = KVI_INITIAL_VALUE(on_key_buf);
/// Number of following bytes that should not be stored for vim.on_key().
-static size_t no_on_key_len = 0;
+static size_t on_key_ignore_len = 0;
static int typeahead_char = 0; ///< typeahead char that's not flushed
@@ -267,17 +269,12 @@ static void add_buff(buffheader_T *const buf, const char *const s, ptrdiff_t sle
}
buf->bh_index = 0;
- size_t len;
if (buf->bh_space >= (size_t)slen) {
- len = strlen(buf->bh_curr->b_str);
+ size_t len = strlen(buf->bh_curr->b_str);
xmemcpyz(buf->bh_curr->b_str + len, s, (size_t)slen);
buf->bh_space -= (size_t)slen;
} else {
- if (slen < MINIMAL_SIZE) {
- len = MINIMAL_SIZE;
- } else {
- len = (size_t)slen;
- }
+ size_t len = MAX(MINIMAL_SIZE, (size_t)slen);
buffblock_T *p = xmalloc(offsetof(buffblock_T, b_str) + len + 1);
buf->bh_space = len - (size_t)slen;
xmemcpyz(p->b_str, s, (size_t)slen);
@@ -312,6 +309,24 @@ static void add_num_buff(buffheader_T *buf, int n)
add_buff(buf, number, -1);
}
+/// Add byte or special key 'c' to buffer "buf".
+/// Translates special keys, NUL and K_SPECIAL.
+static void add_byte_buff(buffheader_T *buf, int c)
+{
+ char temp[4];
+ if (IS_SPECIAL(c) || c == K_SPECIAL || c == NUL) {
+ // Translate special key code into three byte sequence.
+ temp[0] = (char)K_SPECIAL;
+ temp[1] = (char)K_SECOND(c);
+ temp[2] = (char)K_THIRD(c);
+ temp[3] = NUL;
+ } else {
+ temp[0] = (char)c;
+ temp[1] = NUL;
+ }
+ add_buff(buf, temp, -1);
+}
+
/// Add character 'c' to buffer "buf".
/// Translates special keys, NUL, K_SPECIAL and multibyte characters.
static void add_char_buff(buffheader_T *buf, int c)
@@ -329,19 +344,7 @@ static void add_char_buff(buffheader_T *buf, int c)
if (!IS_SPECIAL(c)) {
c = bytes[i];
}
-
- char temp[4];
- if (IS_SPECIAL(c) || c == K_SPECIAL || c == NUL) {
- // Translate special key code into three byte sequence.
- temp[0] = (char)K_SPECIAL;
- temp[1] = (char)K_SECOND(c);
- temp[2] = (char)K_THIRD(c);
- temp[3] = NUL;
- } else {
- temp[0] = (char)c;
- temp[1] = NUL;
- }
- add_buff(buf, temp, -1);
+ add_byte_buff(buf, c);
}
}
@@ -1009,18 +1012,18 @@ int ins_typebuf(char *str, int noremap, int offset, bool nottyped, bool silent)
/// Uses cmd_silent, KeyTyped and KeyNoremap to restore the flags belonging to
/// the char.
///
-/// @param no_on_key don't store these bytes for vim.on_key()
+/// @param on_key_ignore don't store these bytes for vim.on_key()
///
/// @return the length of what was inserted
-int ins_char_typebuf(int c, int modifiers, bool no_on_key)
+int ins_char_typebuf(int c, int modifiers, bool on_key_ignore)
{
char buf[MB_MAXBYTES * 3 + 4];
unsigned len = special_to_buf(c, modifiers, true, buf);
assert(len < sizeof(buf));
buf[len] = NUL;
ins_typebuf(buf, KeyNoremap, 0, !KeyTyped, cmd_silent);
- if (KeyTyped && no_on_key) {
- no_on_key_len += len;
+ if (KeyTyped && on_key_ignore) {
+ on_key_ignore_len += len;
}
return (int)len;
}
@@ -1188,22 +1191,21 @@ static void gotchars(const uint8_t *chars, size_t len)
updatescript(state.buf[i]);
}
- state.buf[state.buflen] = NUL;
+ if (state.buflen > on_key_ignore_len) {
+ kvi_concat_len(on_key_buf, (char *)state.buf + on_key_ignore_len,
+ state.buflen - on_key_ignore_len);
+ on_key_ignore_len = 0;
+ } else {
+ on_key_ignore_len -= state.buflen;
+ }
if (reg_recording != 0) {
+ state.buf[state.buflen] = NUL;
add_buff(&recordbuff, (char *)state.buf, (ptrdiff_t)state.buflen);
// remember how many chars were last recorded
last_recorded_len += state.buflen;
}
- if (state.buflen > no_on_key_len) {
- vim_unescape_ks((char *)state.buf + no_on_key_len);
- kvi_concat(on_key_buf, (char *)state.buf + no_on_key_len);
- no_on_key_len = 0;
- } else {
- no_on_key_len -= state.buflen;
- }
-
state.buflen = 0;
}
@@ -1221,7 +1223,7 @@ static void gotchars(const uint8_t *chars, size_t len)
void gotchars_ignore(void)
{
uint8_t nop_buf[3] = { K_SPECIAL, KS_EXTRA, KE_IGNORE };
- no_on_key_len += 3;
+ on_key_ignore_len += 3;
gotchars(nop_buf, 3);
}
@@ -1634,8 +1636,52 @@ int vgetc(void)
c = TO_SPECIAL(c2, c);
}
- // a keypad or special function key was not mapped, use it like
- // its ASCII equivalent
+ // For a multi-byte character get all the bytes and return the
+ // converted character.
+ // Note: This will loop until enough bytes are received!
+ int n;
+ if ((n = MB_BYTE2LEN_CHECK(c)) > 1) {
+ no_mapping++;
+ buf[0] = (uint8_t)c;
+ for (int i = 1; i < n; i++) {
+ buf[i] = (uint8_t)vgetorpeek(true);
+ if (buf[i] == K_SPECIAL) {
+ // Must be a K_SPECIAL - KS_SPECIAL - KE_FILLER sequence,
+ // which represents a K_SPECIAL (0x80).
+ vgetorpeek(true); // skip KS_SPECIAL
+ vgetorpeek(true); // skip KE_FILLER
+ }
+ }
+ no_mapping--;
+ c = utf_ptr2char((char *)buf);
+ }
+
+ // If mappings are enabled (i.e., not i_CTRL-V) and the user directly typed
+ // something with MOD_MASK_ALT (<M-/<A- modifier) that was not mapped, interpret
+ // <M-x> as <Esc>x rather than as an unbound <M-x> keypress. #8213
+ // In Terminal mode, however, this is not desirable. #16202 #16220
+ // Also do not do this for mouse keys, as terminals encode mouse events as
+ // CSI sequences, and MOD_MASK_ALT has a meaning even for unmapped mouse keys.
+ if (!no_mapping && KeyTyped && mod_mask == MOD_MASK_ALT
+ && !(State & MODE_TERMINAL) && !is_mouse_key(c)) {
+ mod_mask = 0;
+ int len = ins_char_typebuf(c, 0, false);
+ ins_char_typebuf(ESC, 0, false);
+ int old_len = len + 3; // K_SPECIAL KS_MODIFIER MOD_MASK_ALT takes 3 more bytes
+ ungetchars(old_len);
+ if (on_key_buf.size >= (size_t)old_len) {
+ on_key_buf.size -= (size_t)old_len;
+ }
+ continue;
+ }
+
+ if (vgetc_char == 0) {
+ vgetc_mod_mask = mod_mask;
+ vgetc_char = c;
+ }
+
+ // A keypad or special function key was not mapped, use it like
+ // its ASCII equivalent.
switch (c) {
case K_KPLUS:
c = '+'; break;
@@ -1713,50 +1759,6 @@ int vgetc(void)
c = K_RIGHT; break;
}
- // For a multi-byte character get all the bytes and return the
- // converted character.
- // Note: This will loop until enough bytes are received!
- int n;
- if ((n = MB_BYTE2LEN_CHECK(c)) > 1) {
- no_mapping++;
- buf[0] = (uint8_t)c;
- for (int i = 1; i < n; i++) {
- buf[i] = (uint8_t)vgetorpeek(true);
- if (buf[i] == K_SPECIAL) {
- // Must be a K_SPECIAL - KS_SPECIAL - KE_FILLER sequence,
- // which represents a K_SPECIAL (0x80).
- vgetorpeek(true); // skip KS_SPECIAL
- vgetorpeek(true); // skip KE_FILLER
- }
- }
- no_mapping--;
- c = utf_ptr2char((char *)buf);
- }
-
- if (vgetc_char == 0) {
- vgetc_mod_mask = mod_mask;
- vgetc_char = c;
- }
-
- // If mappings are enabled (i.e., not i_CTRL-V) and the user directly typed something with
- // MOD_MASK_ALT (<M-/<A- modifier) that was not mapped, interpret <M-x> as <Esc>x rather
- // than as an unbound <M-x> keypress. #8213
- // In Terminal mode, however, this is not desirable. #16202 #16220
- // Also do not do this for mouse keys, as terminals encode mouse events as CSI sequences, and
- // MOD_MASK_ALT has a meaning even for unmapped mouse keys.
- if (!no_mapping && KeyTyped && mod_mask == MOD_MASK_ALT && !(State & MODE_TERMINAL)
- && !is_mouse_key(c)) {
- mod_mask = 0;
- int len = ins_char_typebuf(c, 0, false);
- ins_char_typebuf(ESC, 0, false);
- int old_len = len + 3; // K_SPECIAL KS_MODIFIER MOD_MASK_ALT takes 3 more bytes
- ungetchars(old_len);
- if (on_key_buf.size >= (size_t)old_len) {
- on_key_buf.size -= (size_t)old_len;
- }
- continue;
- }
-
break;
}
@@ -1769,7 +1771,8 @@ int vgetc(void)
may_garbage_collect = false;
// Execute Lua on_key callbacks.
- nlua_execute_on_key(c, on_key_buf.items, on_key_buf.size);
+ kvi_push(on_key_buf, NUL);
+ nlua_execute_on_key(c, on_key_buf.items);
kvi_destroy(on_key_buf);
kvi_init(on_key_buf);
@@ -1862,7 +1865,7 @@ static void getchar_common(typval_T *argvars, typval_T *rettv)
if (!char_avail()) {
// Flush screen updates before blocking.
ui_flush();
- os_inchar(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events);
+ input_get(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events);
if (!multiqueue_empty(main_loop.events)) {
state_handle_k_event();
continue;
@@ -2237,9 +2240,7 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
}
} else {
// No match; may have to check for termcode at next character.
- if (max_mlen < mlen) {
- max_mlen = mlen;
- }
+ max_mlen = MAX(max_mlen, mlen);
}
}
}
@@ -2340,8 +2341,8 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
const int save_m_noremap = mp->m_noremap;
const bool save_m_silent = mp->m_silent;
char *save_m_keys = NULL; // only saved when needed
- char *save_m_str = NULL; // only saved when needed
- const LuaRef save_m_luaref = mp->m_luaref;
+ char *save_alt_m_keys = NULL; // only saved when needed
+ const int save_alt_m_keylen = mp->m_alt != NULL ? mp->m_alt->m_keylen : 0;
// Handle ":map <expr>": evaluate the {rhs} as an
// expression. Also save and restore the command line
@@ -2354,10 +2355,10 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
vgetc_busy = 0;
may_garbage_collect = false;
- save_m_keys = xstrdup(mp->m_keys);
- if (save_m_luaref == LUA_NOREF) {
- save_m_str = xstrdup(mp->m_str);
- }
+ save_m_keys = xmemdupz(mp->m_keys, (size_t)mp->m_keylen);
+ save_alt_m_keys = mp->m_alt != NULL
+ ? xmemdupz(mp->m_alt->m_keys, (size_t)save_alt_m_keylen)
+ : NULL;
map_str = eval_map_expr(mp, NUL);
if ((map_str == NULL || *map_str == NUL)) {
@@ -2374,9 +2375,7 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
if (State & MODE_CMDLINE) {
// redraw the command below the error
msg_didout = true;
- if (msg_row < cmdline_row) {
- msg_row = cmdline_row;
- }
+ msg_row = MAX(msg_row, cmdline_row);
redrawcmd();
}
} else if (State & (MODE_NORMAL | MODE_INSERT)) {
@@ -2408,11 +2407,18 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
if (save_m_noremap != REMAP_YES) {
noremap = save_m_noremap;
- } else if (strncmp(map_str, save_m_keys != NULL ? save_m_keys : mp->m_keys,
- (size_t)keylen) != 0) {
- noremap = REMAP_YES;
- } else {
+ } else if (save_m_expr
+ ? strncmp(map_str, save_m_keys, (size_t)keylen) == 0
+ || (save_alt_m_keys != NULL
+ && strncmp(map_str, save_alt_m_keys,
+ (size_t)save_alt_m_keylen) == 0)
+ : strncmp(map_str, mp->m_keys, (size_t)keylen) == 0
+ || (mp->m_alt != NULL
+ && strncmp(map_str, mp->m_alt->m_keys,
+ (size_t)mp->m_alt->m_keylen) == 0)) {
noremap = REMAP_SKIP;
+ } else {
+ noremap = REMAP_YES;
}
i = ins_typebuf(map_str, noremap, 0, true, cmd_silent || save_m_silent);
if (save_m_expr) {
@@ -2420,7 +2426,7 @@ static int handle_mapping(int *keylenp, const bool *timedout, int *mapdepth)
}
}
xfree(save_m_keys);
- xfree(save_m_str);
+ xfree(save_alt_m_keys);
*keylenp = keylen;
if (i == FAIL) {
return map_result_fail;
@@ -2728,16 +2734,12 @@ static int vgetorpeek(bool advance)
timedout = true;
continue;
}
- // In Ex-mode \n is compatible with original Vim behaviour.
+
// For the command line only CTRL-C always breaks it.
// For the cmdline window: Alternate between ESC and
// CTRL-C: ESC for most situations and CTRL-C to close the
// cmdline window.
- if ((State & MODE_CMDLINE) || (cmdwin_type > 0 && tc == ESC)) {
- c = Ctrl_C;
- } else {
- c = ESC;
- }
+ c = ((State & MODE_CMDLINE) || (cmdwin_type > 0 && tc == ESC)) ? Ctrl_C : ESC;
tc = c;
// set a flag to indicate this wasn't a normal char
@@ -2986,7 +2988,7 @@ int inchar(uint8_t *buf, int maxlen, long wait_time)
uint8_t dum[DUM_LEN + 1];
while (true) {
- len = os_inchar(dum, DUM_LEN, 0, 0, NULL);
+ len = input_get(dum, DUM_LEN, 0, 0, NULL);
if (len == 0 || (len == 1 && dum[0] == Ctrl_C)) {
break;
}
@@ -3002,7 +3004,7 @@ int inchar(uint8_t *buf, int maxlen, long wait_time)
// Fill up to a third of the buffer, because each character may be
// tripled below.
- len = os_inchar(buf, maxlen / 3, (int)wait_time, tb_change_cnt, NULL);
+ len = input_get(buf, maxlen / 3, (int)wait_time, tb_change_cnt, NULL);
}
// If the typebuf was changed further down, it is like nothing was added by
@@ -3179,7 +3181,7 @@ bool map_execute_lua(bool may_repeat)
Error err = ERROR_INIT;
Array args = ARRAY_DICT_INIT;
nlua_call_ref(ref, NULL, args, kRetNilBool, NULL, &err);
- if (err.type != kErrorTypeNone) {
+ if (ERROR_SET(&err)) {
semsg_multiline("E5108: %s", err.msg);
api_clear_error(&err);
}
@@ -3187,3 +3189,122 @@ bool map_execute_lua(bool may_repeat)
ga_clear(&line_ga);
return true;
}
+
+/// Wraps pasted text stream with K_PASTE_START and K_PASTE_END, and
+/// appends to redo buffer and/or record buffer if needed.
+/// Escapes all K_SPECIAL and NUL bytes in the content.
+///
+/// @param state kFalse for the start of a paste
+/// kTrue for the end of a paste
+/// kNone for the content of a paste
+/// @param str the content of the paste (only used when state is kNone)
+void paste_store(const uint64_t channel_id, const TriState state, const String str, const bool crlf)
+{
+ if (State & MODE_CMDLINE) {
+ return;
+ }
+
+ const bool need_redo = !block_redo;
+ const bool need_record = reg_recording != 0 && !is_internal_call(channel_id);
+
+ if (!need_redo && !need_record) {
+ return;
+ }
+
+ if (state != kNone) {
+ const int c = state == kFalse ? K_PASTE_START : K_PASTE_END;
+ if (need_redo) {
+ if (state == kFalse && !(State & MODE_INSERT)) {
+ ResetRedobuff();
+ }
+ add_char_buff(&redobuff, c);
+ }
+ if (need_record) {
+ add_char_buff(&recordbuff, c);
+ }
+ return;
+ }
+
+ const char *s = str.data;
+ const char *const str_end = str.data + str.size;
+
+ while (s < str_end) {
+ const char *start = s;
+ while (s < str_end && (uint8_t)(*s) != K_SPECIAL && *s != NUL
+ && *s != NL && !(crlf && *s == CAR)) {
+ s++;
+ }
+
+ if (s > start) {
+ if (need_redo) {
+ add_buff(&redobuff, start, s - start);
+ }
+ if (need_record) {
+ add_buff(&recordbuff, start, s - start);
+ }
+ }
+
+ if (s < str_end) {
+ int c = (uint8_t)(*s++);
+ if (crlf && c == CAR) {
+ if (s < str_end && *s == NL) {
+ s++;
+ }
+ c = NL;
+ }
+ if (need_redo) {
+ add_byte_buff(&redobuff, c);
+ }
+ if (need_record) {
+ add_byte_buff(&recordbuff, c);
+ }
+ }
+ }
+}
+
+/// Gets a paste stored by paste_store() from typeahead and repeats it.
+void paste_repeat(int count)
+{
+ garray_T ga = GA_INIT(1, 32);
+ bool aborted = false;
+
+ no_mapping++;
+
+ got_int = false;
+ while (!aborted) {
+ ga_grow(&ga, 32);
+ uint8_t c1 = (uint8_t)vgetorpeek(true);
+ if (c1 == K_SPECIAL) {
+ c1 = (uint8_t)vgetorpeek(true);
+ uint8_t c2 = (uint8_t)vgetorpeek(true);
+ int c = TO_SPECIAL(c1, c2);
+ if (c == K_PASTE_END) {
+ break;
+ } else if (c == K_ZERO) {
+ ga_append(&ga, NUL);
+ } else if (c == K_SPECIAL) {
+ ga_append(&ga, K_SPECIAL);
+ } else {
+ ga_append(&ga, K_SPECIAL);
+ ga_append(&ga, c1);
+ ga_append(&ga, c2);
+ }
+ } else {
+ ga_append(&ga, c1);
+ }
+ aborted = got_int;
+ }
+
+ no_mapping--;
+
+ String str = cbuf_as_string(ga.ga_data, (size_t)ga.ga_len);
+ Arena arena = ARENA_EMPTY;
+ Error err = ERROR_INIT;
+ for (int i = 0; !aborted && i < count; i++) {
+ nvim_paste(LUA_INTERNAL_CALL, str, false, -1, &arena, &err);
+ aborted = ERROR_SET(&err);
+ }
+ api_clear_error(&err);
+ arena_mem_free(arena_finish(&arena));
+ ga_clear(&ga);
+}
diff --git a/src/nvim/globals.h b/src/nvim/globals.h
index 83fef1fe75..45a31ce6b0 100644
--- a/src/nvim/globals.h
+++ b/src/nvim/globals.h
@@ -106,7 +106,8 @@ EXTERN int Columns INIT( = DFLT_COLS); // nr of columns in the screen
// held down based on the MOD_MASK_* symbols that are read first.
EXTERN int mod_mask INIT( = 0); // current key modifiers
-// The value of "mod_mask" and the unmodified character before calling merge_modifiers().
+// The value of "mod_mask" and the unmodified character in vgetc() after it has
+// called vgetorpeek() enough times.
EXTERN int vgetc_mod_mask INIT( = 0);
EXTERN int vgetc_char INIT( = 0);
@@ -146,8 +147,7 @@ EXTERN hlf_T edit_submode_highl; // highl. method for extra info
EXTERN bool cmdmsg_rl INIT( = false); // cmdline is drawn right to left
EXTERN int msg_col;
EXTERN int msg_row;
-EXTERN int msg_scrolled; // Number of screen lines that windows have
- // scrolled because of printing messages.
+EXTERN int msg_scrolled; ///< Number of screen lines that messages have scrolled.
// when true don't set need_wait_return in msg_puts_attr()
// when msg_scrolled is non-zero
EXTERN bool msg_scrolled_ign INIT( = false);
@@ -178,8 +178,8 @@ EXTERN long emsg_assert_fails_lnum INIT( = 0);
EXTERN char *emsg_assert_fails_context INIT( = NULL);
EXTERN bool did_endif INIT( = false); // just had ":endif"
-EXTERN dict_T vimvardict; // Dictionary with v: variables
-EXTERN dict_T globvardict; // Dictionary with g: variables
+EXTERN dict_T vimvardict; // Dict with v: variables
+EXTERN dict_T globvardict; // Dict with g: variables
/// g: value
#define globvarht globvardict.dv_hashtab
EXTERN int did_emsg; // incremented by emsg() when a
@@ -793,195 +793,7 @@ EXTERN disptick_T display_tick INIT( = 0);
// cursor position in Insert mode.
EXTERN linenr_T spell_redraw_lnum INIT( = 0);
-// uncrustify:off
-
-// The error messages that can be shared are included here.
-// Excluded are errors that are only used once and debugging messages.
-EXTERN const char e_abort[] INIT(= N_("E470: Command aborted"));
-EXTERN const char e_afterinit[] INIT(= N_("E905: Cannot set this option after startup"));
-EXTERN const char e_api_spawn_failed[] INIT(= N_("E903: Could not spawn API job"));
-EXTERN const char e_argreq[] INIT(= N_("E471: Argument required"));
-EXTERN const char e_backslash[] INIT(= N_("E10: \\ should be followed by /, ? or &"));
-EXTERN const char e_cmdwin[] INIT(= N_("E11: Invalid in command-line window; <CR> executes, CTRL-C quits"));
-EXTERN const char e_curdir[] INIT(= N_("E12: Command not allowed in secure mode in current dir or tag search"));
-EXTERN const char e_invalid_buffer_name_str[] INIT(= N_("E158: Invalid buffer name: %s"));
-EXTERN const char e_command_too_recursive[] INIT(= N_("E169: Command too recursive"));
-EXTERN const char e_buffer_is_not_loaded[] INIT(= N_("E681: Buffer is not loaded"));
-EXTERN const char e_endif[] INIT(= N_("E171: Missing :endif"));
-EXTERN const char e_endtry[] INIT(= N_("E600: Missing :endtry"));
-EXTERN const char e_endwhile[] INIT(= N_("E170: Missing :endwhile"));
-EXTERN const char e_endfor[] INIT(= N_("E170: Missing :endfor"));
-EXTERN const char e_while[] INIT(= N_("E588: :endwhile without :while"));
-EXTERN const char e_for[] INIT(= N_("E588: :endfor without :for"));
-EXTERN const char e_exists[] INIT(= N_("E13: File exists (add ! to override)"));
-EXTERN const char e_failed[] INIT(= N_("E472: Command failed"));
-EXTERN const char e_internal[] INIT(= N_("E473: Internal error"));
-EXTERN const char e_intern2[] INIT(= N_("E685: Internal error: %s"));
-EXTERN const char e_interr[] INIT(= N_("Interrupted"));
-EXTERN const char e_invarg[] INIT(= N_("E474: Invalid argument"));
-EXTERN const char e_invarg2[] INIT(= N_("E475: Invalid argument: %s"));
-EXTERN const char e_invargval[] INIT(= N_("E475: Invalid value for argument %s"));
-EXTERN const char e_invargNval[] INIT(= N_("E475: Invalid value for argument %s: %s"));
-EXTERN const char e_duparg2[] INIT(= N_("E983: Duplicate argument: %s"));
-EXTERN const char e_invexpr2[] INIT(= N_("E15: Invalid expression: \"%s\""));
-EXTERN const char e_invrange[] INIT(= N_("E16: Invalid range"));
-EXTERN const char e_invcmd[] INIT(= N_("E476: Invalid command"));
-EXTERN const char e_isadir2[] INIT(= N_("E17: \"%s\" is a directory"));
-EXTERN const char e_no_spell[] INIT(= N_("E756: Spell checking is not possible"));
-EXTERN const char e_invchan[] INIT(= N_("E900: Invalid channel id"));
-EXTERN const char e_invchanjob[] INIT(= N_("E900: Invalid channel id: not a job"));
-EXTERN const char e_jobtblfull[] INIT(= N_("E901: Job table is full"));
-EXTERN const char e_jobspawn[] INIT(= N_("E903: Process failed to start: %s: \"%s\""));
-EXTERN const char e_channotpty[] INIT(= N_("E904: channel is not a pty"));
-EXTERN const char e_stdiochan2[] INIT(= N_("E905: Couldn't open stdio channel: %s"));
-EXTERN const char e_invstream[] INIT(= N_("E906: invalid stream for channel"));
-EXTERN const char e_invstreamrpc[] INIT(= N_("E906: invalid stream for rpc channel, use 'rpc'"));
-EXTERN const char e_streamkey[] INIT(= N_("E5210: dict key '%s' already set for buffered stream in channel %" PRIu64));
-EXTERN const char e_libcall[] INIT(= N_("E364: Library call failed for \"%s()\""));
-EXTERN const char e_fsync[] INIT(= N_("E667: Fsync failed: %s"));
-EXTERN const char e_mkdir[] INIT(= N_("E739: Cannot create directory %s: %s"));
-EXTERN const char e_markinval[] INIT(= N_("E19: Mark has invalid line number"));
-EXTERN const char e_marknotset[] INIT(= N_("E20: Mark not set"));
-EXTERN const char e_modifiable[] INIT(= N_("E21: Cannot make changes, 'modifiable' is off"));
-EXTERN const char e_nesting[] INIT(= N_("E22: Scripts nested too deep"));
-EXTERN const char e_noalt[] INIT(= N_("E23: No alternate file"));
-EXTERN const char e_noabbr[] INIT(= N_("E24: No such abbreviation"));
-EXTERN const char e_nobang[] INIT(= N_("E477: No ! allowed"));
-EXTERN const char e_nogroup[] INIT(= N_("E28: No such highlight group name: %s"));
-EXTERN const char e_noinstext[] INIT(= N_("E29: No inserted text yet"));
-EXTERN const char e_nolastcmd[] INIT(= N_("E30: No previous command line"));
-EXTERN const char e_nomap[] INIT(= N_("E31: No such mapping"));
-EXTERN const char e_nomatch[] INIT(= N_("E479: No match"));
-EXTERN const char e_nomatch2[] INIT(= N_("E480: No match: %s"));
-EXTERN const char e_noname[] INIT(= N_("E32: No file name"));
-EXTERN const char e_nopresub[] INIT(= N_("E33: No previous substitute regular expression"));
-EXTERN const char e_noprev[] INIT(= N_("E34: No previous command"));
-EXTERN const char e_noprevre[] INIT(= N_("E35: No previous regular expression"));
-EXTERN const char e_norange[] INIT(= N_("E481: No range allowed"));
-EXTERN const char e_noroom[] INIT(= N_("E36: Not enough room"));
-EXTERN const char e_notmp[] INIT(= N_("E483: Can't get temp file name"));
-EXTERN const char e_notopen[] INIT(= N_("E484: Can't open file %s"));
-EXTERN const char e_notopen_2[] INIT(= N_("E484: Can't open file %s: %s"));
-EXTERN const char e_notread[] INIT(= N_("E485: Can't read file %s"));
-EXTERN const char e_null[] INIT(= N_("E38: Null argument"));
-EXTERN const char e_number_exp[] INIT(= N_("E39: Number expected"));
-EXTERN const char e_openerrf[] INIT(= N_("E40: Can't open errorfile %s"));
-EXTERN const char e_outofmem[] INIT(= N_("E41: Out of memory!"));
-EXTERN const char e_patnotf[] INIT(= N_("Pattern not found"));
-EXTERN const char e_patnotf2[] INIT(= N_("E486: Pattern not found: %s"));
-EXTERN const char e_positive[] INIT(= N_("E487: Argument must be positive"));
-EXTERN const char e_prev_dir[] INIT(= N_("E459: Cannot go back to previous directory"));
-
-EXTERN const char e_no_errors[] INIT(= N_("E42: No Errors"));
-EXTERN const char e_loclist[] INIT(= N_("E776: No location list"));
-EXTERN const char e_re_damg[] INIT(= N_("E43: Damaged match string"));
-EXTERN const char e_re_corr[] INIT(= N_("E44: Corrupted regexp program"));
-EXTERN const char e_readonly[] INIT(= N_("E45: 'readonly' option is set (add ! to override)"));
-EXTERN const char e_letwrong[] INIT(= N_("E734: Wrong variable type for %s="));
-EXTERN const char e_illvar[] INIT(= N_("E461: Illegal variable name: %s"));
-EXTERN const char e_cannot_mod[] INIT(= N_("E995: Cannot modify existing variable"));
-EXTERN const char e_readonlyvar[] INIT(= N_("E46: Cannot change read-only variable \"%.*s\""));
-EXTERN const char e_stringreq[] INIT(= N_("E928: String required"));
-EXTERN const char e_dictreq[] INIT(= N_("E715: Dictionary required"));
-EXTERN const char e_blobidx[] INIT(= N_("E979: Blob index out of range: %" PRId64));
-EXTERN const char e_invalblob[] INIT(= N_("E978: Invalid operation for Blob"));
-EXTERN const char e_toomanyarg[] INIT(= N_("E118: Too many arguments for function: %s"));
-EXTERN const char e_toofewarg[] INIT(= N_("E119: Not enough arguments for function: %s"));
-EXTERN const char e_dictkey[] INIT(= N_("E716: Key not present in Dictionary: \"%s\""));
-EXTERN const char e_dictkey_len[] INIT(= N_("E716: Key not present in Dictionary: \"%.*s\""));
-EXTERN const char e_listreq[] INIT(= N_("E714: List required"));
-EXTERN const char e_listblobreq[] INIT(= N_("E897: List or Blob required"));
-EXTERN const char e_listdictarg[] INIT(= N_("E712: Argument of %s must be a List or Dictionary"));
-EXTERN const char e_listdictblobarg[] INIT(= N_("E896: Argument of %s must be a List, Dictionary or Blob"));
-EXTERN const char e_readerrf[] INIT(= N_("E47: Error while reading errorfile"));
-EXTERN const char e_sandbox[] INIT(= N_("E48: Not allowed in sandbox"));
-EXTERN const char e_secure[] INIT(= N_("E523: Not allowed here"));
-EXTERN const char e_textlock[] INIT(= N_("E565: Not allowed to change text or change window"));
-EXTERN const char e_screenmode[] INIT(= N_("E359: Screen mode setting not supported"));
-EXTERN const char e_scroll[] INIT(= N_("E49: Invalid scroll size"));
-EXTERN const char e_shellempty[] INIT(= N_("E91: 'shell' option is empty"));
-EXTERN const char e_signdata[] INIT(= N_("E255: Couldn't read in sign data!"));
-EXTERN const char e_swapclose[] INIT(= N_("E72: Close error on swap file"));
-EXTERN const char e_toocompl[] INIT(= N_("E74: Command too complex"));
-EXTERN const char e_longname[] INIT(= N_("E75: Name too long"));
-EXTERN const char e_toomsbra[] INIT(= N_("E76: Too many ["));
-EXTERN const char e_toomany[] INIT(= N_("E77: Too many file names"));
-EXTERN const char e_trailing[] INIT(= N_("E488: Trailing characters"));
-EXTERN const char e_trailing_arg[] INIT(= N_("E488: Trailing characters: %s"));
-EXTERN const char e_umark[] INIT(= N_("E78: Unknown mark"));
-EXTERN const char e_wildexpand[] INIT(= N_("E79: Cannot expand wildcards"));
-EXTERN const char e_winheight[] INIT(= N_("E591: 'winheight' cannot be smaller than 'winminheight'"));
-EXTERN const char e_winwidth[] INIT(= N_("E592: 'winwidth' cannot be smaller than 'winminwidth'"));
-EXTERN const char e_write[] INIT(= N_("E80: Error while writing"));
-EXTERN const char e_zerocount[] INIT(= N_("E939: Positive count required"));
-EXTERN const char e_usingsid[] INIT(= N_("E81: Using <SID> not in a script context"));
-EXTERN const char e_missingparen[] INIT(= N_("E107: Missing parentheses: %s"));
-EXTERN const char e_empty_buffer[] INIT(= N_("E749: Empty buffer"));
-EXTERN const char e_nobufnr[] INIT(= N_("E86: Buffer %" PRId64 " does not exist"));
-
-EXTERN const char e_str_not_inside_function[] INIT(= N_("E193: %s not inside a function"));
-
-EXTERN const char e_invalpat[] INIT(= N_("E682: Invalid search pattern or delimiter"));
-EXTERN const char e_bufloaded[] INIT(= N_("E139: File is loaded in another buffer"));
-EXTERN const char e_notset[] INIT(= N_("E764: Option '%s' is not set"));
-EXTERN const char e_invalidreg[] INIT(= N_("E850: Invalid register name"));
-EXTERN const char e_dirnotf[] INIT(= N_("E919: Directory not found in '%s': \"%s\""));
-EXTERN const char e_au_recursive[] INIT(= N_("E952: Autocommand caused recursive behavior"));
-EXTERN const char e_menu_only_exists_in_another_mode[]
-INIT(= N_("E328: Menu only exists in another mode"));
-EXTERN const char e_autocmd_close[] INIT(= N_("E813: Cannot close autocmd window"));
-EXTERN const char e_listarg[] INIT(= N_("E686: Argument of %s must be a List"));
-EXTERN const char e_unsupportedoption[] INIT(= N_("E519: Option not supported"));
-EXTERN const char e_fnametoolong[] INIT(= N_("E856: Filename too long"));
-EXTERN const char e_using_float_as_string[] INIT(= N_("E806: Using a Float as a String"));
-EXTERN const char e_cannot_edit_other_buf[] INIT(= N_("E788: Not allowed to edit another buffer now"));
-EXTERN const char e_using_number_as_bool_nr[] INIT(= N_("E1023: Using a Number as a Bool: %d"));
-EXTERN const char e_not_callable_type_str[] INIT(= N_("E1085: Not a callable type: %s"));
-EXTERN const char e_auabort[] INIT(= N_("E855: Autocommands caused command to abort"));
-
-EXTERN const char e_api_error[] INIT(= N_("E5555: API call: %s"));
-
-EXTERN const char e_luv_api_disabled[] INIT(= N_("E5560: %s must not be called in a lua loop callback"));
-
-EXTERN const char e_floatonly[] INIT(= N_("E5601: Cannot close window, only floating window would remain"));
-EXTERN const char e_floatexchange[] INIT(= N_("E5602: Cannot exchange or rotate float"));
-
-EXTERN const char e_cannot_define_autocommands_for_all_events[] INIT(= N_("E1155: Cannot define autocommands for ALL events"));
-
-EXTERN const char e_resulting_text_too_long[] INIT(= N_("E1240: Resulting text too long"));
-
-EXTERN const char e_line_number_out_of_range[] INIT(= N_("E1247: Line number out of range"));
-
-EXTERN const char e_highlight_group_name_invalid_char[] INIT(= N_("E5248: Invalid character in group name"));
-
-EXTERN const char e_highlight_group_name_too_long[] INIT(= N_("E1249: Highlight group name too long"));
-
-EXTERN const char e_invalid_column_number_nr[] INIT( = N_("E964: Invalid column number: %ld"));
-EXTERN const char e_invalid_line_number_nr[] INIT(= N_("E966: Invalid line number: %ld"));
-
-EXTERN const char e_stray_closing_curly_str[]
-INIT(= N_("E1278: Stray '}' without a matching '{': %s"));
-EXTERN const char e_missing_close_curly_str[]
-INIT(= N_("E1279: Missing '}': %s"));
-
-EXTERN const char e_val_too_large[] INIT(= N_("E1510: Value too large: %s"));
-
-EXTERN const char e_undobang_cannot_redo_or_move_branch[]
-INIT(= N_("E5767: Cannot use :undo! to redo or move to a different undo branch"));
-
-EXTERN const char e_winfixbuf_cannot_go_to_buffer[]
-INIT(= N_("E1513: Cannot switch buffer. 'winfixbuf' is enabled"));
-
-EXTERN const char e_trustfile[] INIT(= N_("E5570: Cannot update trust file: %s"));
-
-EXTERN const char e_unknown_option2[] INIT(= N_("E355: Unknown option: %s"));
-
-EXTERN const char top_bot_msg[] INIT(= N_("search hit TOP, continuing at BOTTOM"));
-EXTERN const char bot_top_msg[] INIT(= N_("search hit BOTTOM, continuing at TOP"));
-
-EXTERN const char line_msg[] INIT(= N_(" line "));
-
-EXTERN FILE *time_fd INIT(= NULL); // Where to write --startuptime report.
+EXTERN FILE *time_fd INIT( = NULL); // Where to write --startuptime report.
// Some compilers warn for not using a return value, but in some situations we
// can't do anything useful with the value. Assign to this variable to avoid
@@ -989,11 +801,9 @@ EXTERN FILE *time_fd INIT(= NULL); // Where to write --startuptime report.
EXTERN int vim_ignored;
// stdio is an RPC channel (--embed).
-EXTERN bool embedded_mode INIT(= false);
+EXTERN bool embedded_mode INIT( = false);
// Do not start UI (--headless, -l) nor read/write to stdio (unless embedding).
-EXTERN bool headless_mode INIT(= false);
-
-// uncrustify:on
+EXTERN bool headless_mode INIT( = false);
/// Only filled for Win32.
EXTERN char windowsVersion[20] INIT( = { 0 });
diff --git a/src/nvim/grid.c b/src/nvim/grid.c
index 56246bf001..acb336c725 100644
--- a/src/nvim/grid.c
+++ b/src/nvim/grid.c
@@ -186,6 +186,24 @@ size_t schar_len(schar_T sc)
}
}
+int schar_cells(schar_T sc)
+{
+ // hot path
+#ifdef ORDER_BIG_ENDIAN
+ if (!(sc & 0x80FFFFFF)) {
+ return 1;
+ }
+#else
+ if (sc < 0x80) {
+ return 1;
+ }
+#endif
+
+ char sc_buf[MAX_SCHAR_SIZE];
+ schar_get(sc_buf, sc);
+ return utf_ptr2cells(sc_buf);
+}
+
/// gets first raw UTF-8 byte of an schar
static char schar_get_first_byte(schar_T sc)
{
@@ -428,14 +446,19 @@ int grid_line_puts(int col, const char *text, int textlen, int attr)
const int max_col = grid_line_maxcol;
while (col < max_col && (len < 0 || (int)(ptr - text) < len) && *ptr != NUL) {
// check if this is the first byte of a multibyte
- int mbyte_blen = len > 0
- ? utfc_ptr2len_len(ptr, (int)((text + len) - ptr))
- : utfc_ptr2len(ptr);
+ int mbyte_blen;
+ if (len >= 0) {
+ int maxlen = (int)((text + len) - ptr);
+ mbyte_blen = utfc_ptr2len_len(ptr, maxlen);
+ if (mbyte_blen > maxlen) {
+ mbyte_blen = 1;
+ }
+ } else {
+ mbyte_blen = utfc_ptr2len(ptr);
+ }
int firstc;
- schar_T schar = len >= 0
- ? utfc_ptr2schar_len(ptr, (int)((text + len) - ptr), &firstc)
- : utfc_ptr2schar(ptr, &firstc);
- int mbyte_cells = utf_char2cells(firstc);
+ schar_T schar = utfc_ptrlen2schar(ptr, mbyte_blen, &firstc);
+ int mbyte_cells = utf_ptr2cells_len(ptr, mbyte_blen);
if (mbyte_cells > 2 || schar == 0) {
mbyte_cells = 1;
schar = schar_from_char(0xFFFD);
diff --git a/src/nvim/hashtab.c b/src/nvim/hashtab.c
index a8a998a361..95df5e5383 100644
--- a/src/nvim/hashtab.c
+++ b/src/nvim/hashtab.c
@@ -316,10 +316,7 @@ static void hash_may_resize(hashtab_T *ht, size_t minitems)
}
} else {
// Use specified size.
- if (minitems < ht->ht_used) {
- // just in case...
- minitems = ht->ht_used;
- }
+ minitems = MAX(minitems, ht->ht_used);
// array is up to 2/3 full
minsize = minitems * 3 / 2;
}
diff --git a/src/nvim/help.c b/src/nvim/help.c
index e9f67ca3e4..0da846bb9f 100644
--- a/src/nvim/help.c
+++ b/src/nvim/help.c
@@ -13,6 +13,7 @@
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/cmdexpand_defs.h"
+#include "nvim/errors.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
@@ -957,8 +958,8 @@ static void helptags_one(char *dir, const char *ext, const char *tagfname, bool
if (s == p2
&& (p1 == IObuff || p1[-1] == ' ' || p1[-1] == '\t')
&& (vim_strchr(" \t\n\r", (uint8_t)s[1]) != NULL
- || s[1] == '\0')) {
- *p2 = '\0';
+ || s[1] == NUL)) {
+ *p2 = NUL;
p1++;
size_t s_len = (size_t)(p2 - p1) + strlen(fname) + 2;
s = xmalloc(s_len);
diff --git a/src/nvim/highlight.c b/src/nvim/highlight.c
index 8729c74ce8..d39ffcd177 100644
--- a/src/nvim/highlight.c
+++ b/src/nvim/highlight.c
@@ -219,11 +219,10 @@ int ns_get_hl(NS *ns_hl, int hl_id, bool link, bool nodefault)
bool fallback = true;
int tmp = false;
HlAttrs attrs = HLATTRS_INIT;
- if (ret.type == kObjectTypeDictionary) {
+ if (ret.type == kObjectTypeDict) {
fallback = false;
Dict(highlight) dict = KEYDICT_INIT;
- if (api_dict_to_keydict(&dict, KeyDict_highlight_get_field,
- ret.data.dictionary, &err)) {
+ if (api_dict_to_keydict(&dict, KeyDict_highlight_get_field, ret.data.dict, &err)) {
attrs = dict2hlattrs(&dict, true, &it.link_id, &err);
fallback = GET_BOOL_OR_TRUE(&dict, highlight, fallback);
tmp = dict.fallback; // or false
@@ -370,12 +369,15 @@ void update_window_hl(win_T *wp, bool invalid)
// determine window specific background set in 'winhighlight'
bool float_win = wp->w_floating && !wp->w_config.external;
- if (float_win && hl_def[HLF_NFLOAT] != 0) {
+ if (float_win && hl_def[HLF_NFLOAT] != 0 && ns_id > 0) {
wp->w_hl_attr_normal = hl_def[HLF_NFLOAT];
} else if (hl_def[HLF_COUNT] > 0) {
wp->w_hl_attr_normal = hl_def[HLF_COUNT];
+ } else if (float_win) {
+ wp->w_hl_attr_normal = HL_ATTR(HLF_NFLOAT) > 0
+ ? HL_ATTR(HLF_NFLOAT) : highlight_attr[HLF_NFLOAT];
} else {
- wp->w_hl_attr_normal = float_win ? HL_ATTR(HLF_NFLOAT) : 0;
+ wp->w_hl_attr_normal = 0;
}
if (wp->w_floating) {
@@ -872,9 +874,9 @@ HlAttrs syn_attr2entry(int attr)
}
/// Gets highlight description for id `attr_id` as a map.
-Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Arena *arena, Error *err)
+Dict hl_get_attr_by_id(Integer attr_id, Boolean rgb, Arena *arena, Error *err)
{
- Dictionary dic = ARRAY_DICT_INIT;
+ Dict dic = ARRAY_DICT_INIT;
if (attr_id == 0) {
return dic;
@@ -885,19 +887,19 @@ Dictionary hl_get_attr_by_id(Integer attr_id, Boolean rgb, Arena *arena, Error *
"Invalid attribute id: %" PRId64, attr_id);
return dic;
}
- Dictionary retval = arena_dict(arena, HLATTRS_DICT_SIZE);
+ Dict retval = arena_dict(arena, HLATTRS_DICT_SIZE);
hlattrs2dict(&retval, NULL, syn_attr2entry((int)attr_id), rgb, false);
return retval;
}
-/// Converts an HlAttrs into Dictionary
+/// Converts an HlAttrs into Dict
///
-/// @param[in/out] hl Dictionary with pre-allocated space for HLATTRS_DICT_SIZE elements
+/// @param[in/out] hl Dict with pre-allocated space for HLATTRS_DICT_SIZE elements
/// @param[in] aep data to convert
/// @param use_rgb use 'gui*' settings if true, else resorts to 'cterm*'
/// @param short_keys change (foreground, background, special) to (fg, bg, sp) for 'gui*' settings
/// (foreground, background) to (ctermfg, ctermbg) for 'cterm*' settings
-void hlattrs2dict(Dictionary *hl, Dictionary *hl_attrs, HlAttrs ae, bool use_rgb, bool short_keys)
+void hlattrs2dict(Dict *hl, Dict *hl_attrs, HlAttrs ae, bool use_rgb, bool short_keys)
{
hl_attrs = hl_attrs ? hl_attrs : hl;
assert(hl->capacity >= HLATTRS_DICT_SIZE); // at most 16 items
@@ -1085,10 +1087,10 @@ HlAttrs dict2hlattrs(Dict(highlight) *dict, bool use_rgb, int *link_id, Error *e
}
// Handle cterm attrs
- if (dict->cterm.type == kObjectTypeDictionary) {
+ if (dict->cterm.type == kObjectTypeDict) {
Dict(highlight_cterm) cterm[1] = KEYDICT_INIT;
if (!api_dict_to_keydict(cterm, KeyDict_highlight_cterm_get_field,
- dict->cterm.data.dictionary, err)) {
+ dict->cterm.data.dict, err)) {
return hlattrs;
}
@@ -1203,7 +1205,7 @@ static size_t hl_inspect_size(int attr)
static void hl_inspect_impl(Array *arr, int attr, Arena *arena)
{
- Dictionary item = ARRAY_DICT_INIT;
+ Dict item = ARRAY_DICT_INIT;
if (attr <= 0 || attr >= (int)set_size(&attr_entries)) {
return;
}
@@ -1242,5 +1244,5 @@ static void hl_inspect_impl(Array *arr, int attr, Arena *arena)
return;
}
PUT_C(item, "id", INTEGER_OBJ(attr));
- ADD_C(*arr, DICTIONARY_OBJ(item));
+ ADD_C(*arr, DICT_OBJ(item));
}
diff --git a/src/nvim/highlight.h b/src/nvim/highlight.h
index cb3a84bcaf..2ba6cf5371 100644
--- a/src/nvim/highlight.h
+++ b/src/nvim/highlight.h
@@ -54,6 +54,8 @@ EXTERN const char *hlf_names[] INIT( = {
[HLF_SPL] = "SpellLocal",
[HLF_PNI] = "Pmenu",
[HLF_PSI] = "PmenuSel",
+ [HLF_PMNI] = "PmenuMatch",
+ [HLF_PMSI] = "PmenuMatchSel",
[HLF_PNK] = "PmenuKind",
[HLF_PSK] = "PmenuKindSel",
[HLF_PNX] = "PmenuExtra",
@@ -78,6 +80,8 @@ EXTERN const char *hlf_names[] INIT( = {
[HLF_CU] = "Cursor",
[HLF_BTITLE] = "FloatTitle",
[HLF_BFOOTER] = "FloatFooter",
+ [HLF_TS] = "StatusLineTerm",
+ [HLF_TSNC] = "StatusLineTermNC",
});
EXTERN int highlight_attr[HLF_COUNT + 1]; // Highl. attr for each context.
diff --git a/src/nvim/highlight_defs.h b/src/nvim/highlight_defs.h
index 25ab9dc2d9..e0cce81166 100644
--- a/src/nvim/highlight_defs.h
+++ b/src/nvim/highlight_defs.h
@@ -101,6 +101,8 @@ typedef enum {
HLF_SPL, ///< SpellLocal
HLF_PNI, ///< popup menu normal item
HLF_PSI, ///< popup menu selected item
+ HLF_PMNI, ///< popup menu matched text in normal item
+ HLF_PMSI, ///< popup menu matched text in selected item
HLF_PNK, ///< popup menu normal item "kind"
HLF_PSK, ///< popup menu selected item "kind"
HLF_PNX, ///< popup menu normal item "menu" (extra text)
@@ -125,6 +127,8 @@ typedef enum {
HLF_CU, ///< Cursor
HLF_BTITLE, ///< Float Border Title
HLF_BFOOTER, ///< Float Border Footer
+ HLF_TS, ///< status line for terminal window
+ HLF_TSNC, ///< status line for non-current terminal window
HLF_COUNT, ///< MUST be the last one
} hlf_T;
diff --git a/src/nvim/highlight_group.c b/src/nvim/highlight_group.c
index ccb093c116..1a0ae3ac49 100644
--- a/src/nvim/highlight_group.c
+++ b/src/nvim/highlight_group.c
@@ -22,6 +22,7 @@
#include "nvim/cursor_shape.h"
#include "nvim/decoration_provider.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
@@ -143,6 +144,7 @@ static const char e_missing_argument_str[]
static const char *highlight_init_both[] = {
"Cursor guifg=bg guibg=fg",
"CursorLineNr gui=bold cterm=bold",
+ "PmenuSel gui=reverse cterm=reverse,underline blend=0",
"RedrawDebugNormal gui=reverse cterm=reverse",
"TabLineSel gui=bold cterm=bold",
"TermCursor gui=reverse cterm=reverse",
@@ -150,34 +152,38 @@ static const char *highlight_init_both[] = {
"lCursor guifg=bg guibg=fg",
// UI
- "default link CursorIM Cursor",
- "default link CursorLineFold FoldColumn",
- "default link CursorLineSign SignColumn",
- "default link EndOfBuffer NonText",
- "default link FloatBorder NormalFloat",
- "default link FloatFooter FloatTitle",
- "default link FloatTitle Title",
- "default link FoldColumn SignColumn",
- "default link IncSearch CurSearch",
- "default link LineNrAbove LineNr",
- "default link LineNrBelow LineNr",
- "default link MsgSeparator StatusLine",
- "default link MsgArea NONE",
- "default link NormalNC NONE",
- "default link PmenuExtra Pmenu",
- "default link PmenuExtraSel PmenuSel",
- "default link PmenuKind Pmenu",
- "default link PmenuKindSel PmenuSel",
- "default link PmenuSbar Pmenu",
- "default link Substitute Search",
- "default link TabLine StatusLineNC",
- "default link TabLineFill TabLine",
- "default link TermCursorNC NONE",
- "default link VertSplit WinSeparator",
- "default link VisualNOS Visual",
- "default link Whitespace NonText",
- "default link WildMenu PmenuSel",
- "default link WinSeparator Normal",
+ "default link CursorIM Cursor",
+ "default link CursorLineFold FoldColumn",
+ "default link CursorLineSign SignColumn",
+ "default link EndOfBuffer NonText",
+ "default link FloatBorder NormalFloat",
+ "default link FloatFooter FloatTitle",
+ "default link FloatTitle Title",
+ "default link FoldColumn SignColumn",
+ "default link IncSearch CurSearch",
+ "default link LineNrAbove LineNr",
+ "default link LineNrBelow LineNr",
+ "default link MsgSeparator StatusLine",
+ "default link MsgArea NONE",
+ "default link NormalNC NONE",
+ "default link PmenuExtra Pmenu",
+ "default link PmenuExtraSel PmenuSel",
+ "default link PmenuKind Pmenu",
+ "default link PmenuKindSel PmenuSel",
+ "default link PmenuMatch Pmenu",
+ "default link PmenuMatchSel PmenuSel",
+ "default link PmenuSbar Pmenu",
+ "default link Substitute Search",
+ "default link StatusLineTerm StatusLine",
+ "default link StatusLineTermNC StatusLineNC",
+ "default link TabLine StatusLineNC",
+ "default link TabLineFill TabLine",
+ "default link TermCursorNC NONE",
+ "default link VertSplit WinSeparator",
+ "default link VisualNOS Visual",
+ "default link Whitespace NonText",
+ "default link WildMenu PmenuSel",
+ "default link WinSeparator Normal",
// Syntax
"default link Character Constant",
@@ -295,6 +301,11 @@ static const char *highlight_init_both[] = {
"default link @tag Tag",
"default link @tag.builtin Special",
+ // :help
+ // Higlight "===" and "---" heading delimiters specially.
+ "default @markup.heading.1.delimiter.vimdoc guibg=bg guifg=bg guisp=fg gui=underdouble,nocombine ctermbg=NONE ctermfg=NONE cterm=underdouble,nocombine",
+ "default @markup.heading.2.delimiter.vimdoc guibg=bg guifg=bg guisp=fg gui=underline,nocombine ctermbg=NONE ctermfg=NONE cterm=underline,nocombine",
+
// LSP semantic tokens
"default link @lsp.type.class @type",
"default link @lsp.type.comment @comment",
@@ -353,7 +364,6 @@ static const char *highlight_init_light[] = {
"NonText guifg=NvimLightGrey4",
"NormalFloat guibg=NvimLightGrey1",
"Pmenu guibg=NvimLightGrey3 cterm=reverse",
- "PmenuSel guifg=NvimLightGrey3 guibg=NvimDarkGrey2 cterm=reverse,underline blend=0",
"PmenuThumb guibg=NvimLightGrey4",
"Question guifg=NvimDarkCyan ctermfg=6",
"QuickFixLine guifg=NvimDarkCyan ctermfg=6",
@@ -438,7 +448,6 @@ static const char *highlight_init_dark[] = {
"NonText guifg=NvimDarkGrey4",
"NormalFloat guibg=NvimDarkGrey1",
"Pmenu guibg=NvimDarkGrey3 cterm=reverse",
- "PmenuSel guifg=NvimDarkGrey3 guibg=NvimLightGrey2 cterm=reverse,underline blend=0",
"PmenuThumb guibg=NvimDarkGrey4",
"Question guifg=NvimLightCyan ctermfg=14",
"QuickFixLine guifg=NvimLightCyan ctermfg=14",
@@ -848,7 +857,7 @@ static int color_numbers_8[28] = { 0, 4, 2, 6,
// color_names[].
// "boldp" will be set to kTrue or kFalse for a foreground color when using 8
// colors, otherwise it will be unchanged.
-int lookup_color(const int idx, const bool foreground, TriState *const boldp)
+static int lookup_color(const int idx, const bool foreground, TriState *const boldp)
{
int color = color_numbers_16[idx];
@@ -1173,7 +1182,7 @@ void do_highlight(const char *line, const bool forceit, const bool init)
break;
}
vim_memcpy_up(key, key_start, key_len);
- key[key_len] = '\0';
+ key[key_len] = NUL;
linep = skipwhite(linep);
if (strcmp(key, "NONE") == 0) {
@@ -1625,7 +1634,7 @@ static void highlight_list_one(const int id)
}
}
-static bool hlgroup2dict(Dictionary *hl, NS ns_id, int hl_id, Arena *arena)
+static bool hlgroup2dict(Dict *hl, NS ns_id, int hl_id, Arena *arena)
{
HlGroup *sgp = &hl_table[hl_id - 1];
int link = ns_id == 0 ? sgp->sg_link : ns_get_hl(&ns_id, hl_id, true, sgp->sg_set);
@@ -1643,18 +1652,19 @@ static bool hlgroup2dict(Dictionary *hl, NS ns_id, int hl_id, Arena *arena)
PUT_C(*hl, "default", BOOLEAN_OBJ(true));
}
if (link > 0) {
+ assert(1 <= link && link <= highlight_ga.ga_len);
PUT_C(*hl, "link", CSTR_AS_OBJ(hl_table[link - 1].sg_name));
}
- Dictionary hl_cterm = arena_dict(arena, HLATTRS_DICT_SIZE);
+ Dict hl_cterm = arena_dict(arena, HLATTRS_DICT_SIZE);
hlattrs2dict(hl, NULL, attr, true, true);
hlattrs2dict(hl, &hl_cterm, attr, false, true);
if (kv_size(hl_cterm)) {
- PUT_C(*hl, "cterm", DICTIONARY_OBJ(hl_cterm));
+ PUT_C(*hl, "cterm", DICT_OBJ(hl_cterm));
}
return true;
}
-Dictionary ns_get_hl_defs(NS ns_id, Dict(get_highlight) *opts, Arena *arena, Error *err)
+Dict ns_get_hl_defs(NS ns_id, Dict(get_highlight) *opts, Arena *arena, Error *err)
{
Boolean link = GET_BOOL_OR_TRUE(opts, get_highlight, link);
int id = -1;
@@ -1663,7 +1673,7 @@ Dictionary ns_get_hl_defs(NS ns_id, Dict(get_highlight) *opts, Arena *arena, Err
id = create ? syn_check_group(opts->name.data, opts->name.size)
: syn_name2id_len(opts->name.data, opts->name.size);
if (id == 0 && !create) {
- Dictionary attrs = ARRAY_DICT_INIT;
+ Dict attrs = ARRAY_DICT_INIT;
return attrs;
}
} else if (HAS_KEY(opts, get_highlight, id)) {
@@ -1674,7 +1684,7 @@ Dictionary ns_get_hl_defs(NS ns_id, Dict(get_highlight) *opts, Arena *arena, Err
VALIDATE(1 <= id && id <= highlight_ga.ga_len, "%s", "Highlight id out of bounds", {
goto cleanup;
});
- Dictionary attrs = ARRAY_DICT_INIT;
+ Dict attrs = ARRAY_DICT_INIT;
hlgroup2dict(&attrs, ns_id, link ? id : syn_get_final_id(id), arena);
return attrs;
}
@@ -1682,19 +1692,19 @@ Dictionary ns_get_hl_defs(NS ns_id, Dict(get_highlight) *opts, Arena *arena, Err
goto cleanup;
}
- Dictionary rv = arena_dict(arena, (size_t)highlight_ga.ga_len);
+ Dict rv = arena_dict(arena, (size_t)highlight_ga.ga_len);
for (int i = 1; i <= highlight_ga.ga_len; i++) {
- Dictionary attrs = ARRAY_DICT_INIT;
+ Dict attrs = ARRAY_DICT_INIT;
if (!hlgroup2dict(&attrs, ns_id, i, arena)) {
continue;
}
- PUT_C(rv, hl_table[(link ? i : syn_get_final_id(i)) - 1].sg_name, DICTIONARY_OBJ(attrs));
+ PUT_C(rv, hl_table[(link ? i : syn_get_final_id(i)) - 1].sg_name, DICT_OBJ(attrs));
}
return rv;
cleanup:
- return (Dictionary)ARRAY_DICT_INIT;
+ return (Dict)ARRAY_DICT_INIT;
}
/// Outputs a highlight when doing ":hi MyHighlight"
@@ -1966,7 +1976,7 @@ int syn_name2id_len(const char *name, size_t len)
// Avoid using stricmp() too much, it's slow on some systems
// Avoid alloc()/free(), these are slow too.
vim_memcpy_up(name_u, name, len);
- name_u[len] = '\0';
+ name_u[len] = NUL;
// map_get(..., int) returns 0 when no key is present, which is
// the expected value for missing highlight group.
@@ -2268,7 +2278,6 @@ void highlight_changed(void)
// sentinel value. used when no highlight namespace is active
highlight_attr[HLF_COUNT] = 0;
- //
// Setup the user highlights
//
// Temporarily utilize 10 more hl entries:
diff --git a/src/nvim/indent.c b/src/nvim/indent.c
index d635c7d7bf..b7e3842aad 100644
--- a/src/nvim/indent.c
+++ b/src/nvim/indent.c
@@ -13,6 +13,7 @@
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
@@ -146,25 +147,42 @@ int tabstop_padding(colnr_T col, OptInt ts_arg, const colnr_T *vts)
}
/// Find the size of the tab that covers a particular column.
-int tabstop_at(colnr_T col, OptInt ts, const colnr_T *vts)
+///
+/// If this is being called as part of a shift operation, col is not the cursor
+/// column but is the column number to the left of the first non-whitespace
+/// character in the line. If the shift is to the left (left == true), then
+/// return the size of the tab interval to the left of the column.
+int tabstop_at(colnr_T col, OptInt ts, const colnr_T *vts, bool left)
{
- colnr_T tabcol = 0;
- int t;
- int tab_size = 0;
-
if (vts == NULL || vts[0] == 0) {
return (int)ts;
}
- const int tabcount = vts[0];
+ colnr_T tabcol = 0; // Column of the tab stop under consideration.
+ int t; // Tabstop index in the list of variable tab stops.
+ int tab_size = 0; // Size of the tab stop interval to the right or left of the col.
+ const int tabcount // Number of tab stops in the list of variable tab stops.
+ = vts[0];
for (t = 1; t <= tabcount; t++) {
tabcol += vts[t];
if (tabcol > col) {
- tab_size = vts[t];
+ // If shifting left (left == true), and if the column to the left of
+ // the first first non-blank character (col) in the line is
+ // already to the left of the first tabstop, set the shift amount
+ // (tab_size) to just enough to shift the line to the left margin.
+ // The value doesn't seem to matter as long as it is at least that
+ // distance.
+ if (left && (t == 1)) {
+ tab_size = col;
+ } else {
+ tab_size = vts[t - (left ? 1 : 0)];
+ }
break;
}
}
- if (t > tabcount) {
+ if (t > tabcount) { // If the value of the index t is beyond the
+ // end of the list, use the tab stop value at
+ // the end of the list.
tab_size = vts[tabcount];
}
@@ -311,35 +329,35 @@ int tabstop_first(colnr_T *ts)
/// 'tabstop' value when 'shiftwidth' is zero.
int get_sw_value(buf_T *buf)
{
- int result = get_sw_value_col(buf, 0);
+ int result = get_sw_value_col(buf, 0, false);
return result;
}
/// Idem, using "pos".
-int get_sw_value_pos(buf_T *buf, pos_T *pos)
+int get_sw_value_pos(buf_T *buf, pos_T *pos, bool left)
{
pos_T save_cursor = curwin->w_cursor;
curwin->w_cursor = *pos;
- int sw_value = get_sw_value_col(buf, get_nolist_virtcol());
+ int sw_value = get_sw_value_col(buf, get_nolist_virtcol(), left);
curwin->w_cursor = save_cursor;
return sw_value;
}
/// Idem, using the first non-black in the current line.
-int get_sw_value_indent(buf_T *buf)
+int get_sw_value_indent(buf_T *buf, bool left)
{
pos_T pos = curwin->w_cursor;
pos.col = (colnr_T)getwhitecols_curline();
- return get_sw_value_pos(buf, &pos);
+ return get_sw_value_pos(buf, &pos, left);
}
/// Idem, using virtual column "col".
-int get_sw_value_col(buf_T *buf, colnr_T col)
+int get_sw_value_col(buf_T *buf, colnr_T col, bool left)
{
return buf->b_p_sw ? (int)buf->b_p_sw
- : tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array);
+ : tabstop_at(col, buf->b_p_ts, buf->b_p_vts_array, left);
}
/// Return the effective softtabstop value for the current buffer,
@@ -873,7 +891,17 @@ int get_breakindent_win(win_T *wp, char *line)
if (wp->w_briopt_list > 0) {
prev_list += wp->w_briopt_list;
} else {
- prev_indent = (int)(*regmatch.endp - *regmatch.startp);
+ char *ptr = *regmatch.startp;
+ char *end_ptr = *regmatch.endp;
+ int indent = 0;
+ // Compute the width of the matched text.
+ // Use win_chartabsize() so that TAB size is correct,
+ // while wrapping is ignored.
+ while (ptr < end_ptr) {
+ indent += win_chartabsize(wp, ptr, indent);
+ MB_PTR_ADV(ptr);
+ }
+ prev_indent = indent;
}
}
vim_regfree(regmatch.regprog);
@@ -1143,7 +1171,7 @@ int get_expr_indent(void)
// Need to make a copy, the 'indentexpr' option could be changed while
// evaluating it.
char *inde_copy = xstrdup(curbuf->b_p_inde);
- int indent = (int)eval_to_number(inde_copy);
+ int indent = (int)eval_to_number(inde_copy, true);
xfree(inde_copy);
if (use_sandbox) {
@@ -1389,7 +1417,7 @@ void fixthisline(IndentGetter get_the_indent)
return;
}
- change_indent(INDENT_SET, amount, false, 0, true);
+ change_indent(INDENT_SET, amount, false, true);
if (linewhite(curwin->w_cursor.lnum)) {
did_ai = true; // delete the indent if the line stays empty
}
diff --git a/src/nvim/indent_c.c b/src/nvim/indent_c.c
index 65b5f46333..98b0d6003a 100644
--- a/src/nvim/indent_c.c
+++ b/src/nvim/indent_c.c
@@ -3419,9 +3419,7 @@ term_again:
break;
}
- //
// Skip preprocessor directives and blank lines.
- //
if (cin_ispreproc_cont(&l, &curwin->w_cursor.lnum, &amount)) {
continue;
}
diff --git a/src/nvim/input.c b/src/nvim/input.c
index e14bfe7539..ef400710fe 100644
--- a/src/nvim/input.c
+++ b/src/nvim/input.c
@@ -37,7 +37,7 @@
/// @param[in] str Prompt: question to ask user. Is always followed by
/// " (y/n)?".
/// @param[in] direct Determines what function to use to get user input. If
-/// true then os_inchar() will be used, otherwise vgetc().
+/// true then input_get() will be used, otherwise vgetc().
/// I.e. when direct is true then characters are obtained
/// directly from the user without buffers involved.
///
@@ -111,7 +111,7 @@ int get_keystroke(MultiQueue *events)
// First time: blocking wait. Second time: wait up to 100ms for a
// terminal code to complete.
- n = os_inchar(buf + len, maxlen, len == 0 ? -1 : 100, 0, events);
+ n = input_get(buf + len, maxlen, len == 0 ? -1 : 100, 0, events);
if (n > 0) {
// Replace zero and K_SPECIAL by a special key code.
n = fix_input_buffer(buf + len, n);
diff --git a/src/nvim/insexpand.c b/src/nvim/insexpand.c
index b557b9802e..84dd55fa78 100644
--- a/src/nvim/insexpand.c
+++ b/src/nvim/insexpand.c
@@ -22,6 +22,7 @@
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -38,10 +39,12 @@
#include "nvim/globals.h"
#include "nvim/highlight.h"
#include "nvim/highlight_defs.h"
+#include "nvim/highlight_group.h"
#include "nvim/indent.h"
#include "nvim/indent_c.h"
#include "nvim/insexpand.h"
#include "nvim/keycodes.h"
+#include "nvim/lua/executor.h"
#include "nvim/macros_defs.h"
#include "nvim/mark_defs.h"
#include "nvim/mbyte.h"
@@ -121,7 +124,7 @@ static char *ctrl_x_msgs[] = {
N_(" Command-line completion (^V^N^P)"),
N_(" User defined completion (^U^N^P)"),
N_(" Omni completion (^O^N^P)"),
- N_(" Spelling suggestion (s^N^P)"),
+ N_(" Spelling suggestion (^S^N^P)"),
N_(" Keyword Local completion (^N^P)"),
NULL, // CTRL_X_EVAL doesn't use msg.
N_(" Command-line completion (^V^N^P)"),
@@ -148,13 +151,6 @@ static char *ctrl_x_mode_names[] = {
"cmdline",
};
-// Array indexes used for cp_text[].
-#define CPT_ABBR 0 ///< "abbr"
-#define CPT_MENU 1 ///< "menu"
-#define CPT_KIND 2 ///< "kind"
-#define CPT_INFO 3 ///< "info"
-#define CPT_COUNT 4 ///< Number of entries
-
/// Structure used to store one match for insert completion.
typedef struct compl_S compl_T;
struct compl_S {
@@ -167,6 +163,9 @@ struct compl_S {
///< cp_flags has CP_FREE_FNAME
int cp_flags; ///< CP_ values
int cp_number; ///< sequence number
+ int cp_score; ///< fuzzy match score
+ int cp_user_hlattr; ///< highlight attribute to combine with
+ int cp_user_kind_hlattr; ///< highlight attribute for kind
};
/// state information used for getting the next set of insert completion
@@ -224,13 +223,6 @@ static char *compl_leader = NULL;
static bool compl_get_longest = false; ///< put longest common string in compl_leader
-static bool compl_no_insert = false; ///< false: select & insert
- ///< true: noinsert
-static bool compl_no_select = false; ///< false: select & insert
- ///< true: noselect
-static bool compl_longest = false; ///< false: insert full match
- ///< true: insert longest prefix
-
/// Selected one of the matches. When false the match was edited or using the
/// longest common string.
static bool compl_used_match;
@@ -287,7 +279,7 @@ static bool compl_opt_refresh_always = false;
static size_t spell_bad_len = 0; // length of located bad word
-static int pum_selected_item = -1;
+static int compl_selected_item = -1;
/// CTRL-X pressed in Insert mode.
void ins_ctrl_x(void)
@@ -766,7 +758,7 @@ int ins_compl_add_infercase(char *str_arg, int len, bool icase, char *fname, Dir
flags |= CP_ICASE;
}
- int res = ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false);
+ int res = ins_compl_add(str, len, fname, NULL, false, NULL, dir, flags, false, -1, -1);
xfree(tofree);
return res;
}
@@ -806,7 +798,7 @@ static inline void free_cptext(char *const *const cptext)
/// returned in case of error.
static int ins_compl_add(char *const str, int len, char *const fname, char *const *const cptext,
const bool cptext_allocated, typval_T *user_data, const Direction cdir,
- int flags_arg, const bool adup)
+ int flags_arg, const bool adup, int user_hlattr, int user_kind_hlattr)
FUNC_ATTR_NONNULL_ARG(1)
{
compl_T *match;
@@ -872,6 +864,8 @@ static int ins_compl_add(char *const str, int len, char *const fname, char *cons
match->cp_fname = NULL;
}
match->cp_flags = flags;
+ match->cp_user_hlattr = user_hlattr;
+ match->cp_user_kind_hlattr = user_kind_hlattr;
if (cptext != NULL) {
int i;
@@ -1005,7 +999,7 @@ static void ins_compl_add_matches(int num_matches, char **matches, int icase)
for (int i = 0; i < num_matches && add_r != FAIL; i++) {
if ((add_r = ins_compl_add(matches[i], -1, NULL, NULL, false, NULL, dir,
CP_FAST | (icase ? CP_ICASE : 0),
- false)) == OK) {
+ false, -1, -1)) == OK) {
// If dir was BACKWARD then honor it just once.
dir = FORWARD;
}
@@ -1048,22 +1042,10 @@ bool ins_compl_long_shown_match(void)
&& (colnr_T)strlen(compl_shown_match->cp_str) > curwin->w_cursor.col - compl_col;
}
-/// Set variables that store noselect and noinsert behavior from the
-/// 'completeopt' value.
-void completeopt_was_set(void)
+/// Get the local or global value of 'completeopt' flags.
+unsigned get_cot_flags(void)
{
- compl_no_insert = false;
- compl_no_select = false;
- compl_longest = false;
- if (strstr(p_cot, "noselect") != NULL) {
- compl_no_select = true;
- }
- if (strstr(p_cot, "noinsert") != NULL) {
- compl_no_insert = true;
- }
- if (strstr(p_cot, "longest") != NULL) {
- compl_longest = true;
- }
+ return curbuf->b_cot_flags != 0 ? curbuf->b_cot_flags : cot_flags;
}
/// "compl_match_array" points the currently displayed list of entries in the
@@ -1087,7 +1069,7 @@ bool pum_wanted(void)
FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
// "completeopt" must contain "menu" or "menuone"
- return vim_strchr(p_cot, 'm') != NULL;
+ return (get_cot_flags() & COT_ANY_MENU) != 0;
}
/// Check that there are two or more matches to be shown in the popup menu.
@@ -1106,7 +1088,7 @@ static bool pum_enough_matches(void)
comp = comp->cp_next;
} while (!is_first_match(comp));
- if (strstr(p_cot, "menuone") != NULL) {
+ if (get_cot_flags() & COT_MENUONE) {
return i >= 1;
}
return i >= 2;
@@ -1160,6 +1142,16 @@ static void trigger_complete_changed_event(int cur)
restore_v_event(v_event, &save_v_event);
}
+/// pumitem qsort compare func
+static int ins_compl_fuzzy_cmp(const void *a, const void *b)
+{
+ const int sa = (*(pumitem_T *)a).pum_score;
+ const int sb = (*(pumitem_T *)b).pum_score;
+ const int ia = (*(pumitem_T *)a).pum_idx;
+ const int ib = (*(pumitem_T *)b).pum_idx;
+ return sa == sb ? (ia == ib ? 0 : (ia < ib ? -1 : 1)) : (sa < sb ? 1 : -1);
+}
+
/// Build a popup menu to show the completion matches.
///
/// @return the popup menu entry that should be selected,
@@ -1177,11 +1169,22 @@ static int ins_compl_build_pum(void)
}
const int lead_len = compl_leader != NULL ? (int)strlen(compl_leader) : 0;
+ int max_fuzzy_score = 0;
+ unsigned cur_cot_flags = get_cot_flags();
+ bool compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0;
+ bool compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0;
do {
+ // When 'completeopt' contains "fuzzy" and leader is not NULL or empty,
+ // set the cp_score for later comparisons.
+ if (compl_fuzzy_match && compl_leader != NULL && lead_len > 0) {
+ comp->cp_score = fuzzy_match_str(comp->cp_str, compl_leader);
+ }
+
if (!match_at_original_text(comp)
&& (compl_leader == NULL
- || ins_compl_equal(comp, compl_leader, (size_t)lead_len))) {
+ || ins_compl_equal(comp, compl_leader, (size_t)lead_len)
+ || (compl_fuzzy_match && comp->cp_score > 0))) {
compl_match_arraysize++;
}
comp = comp->cp_next;
@@ -1198,6 +1201,10 @@ static int ins_compl_build_pum(void)
// match after it, don't highlight anything.
bool shown_match_ok = match_at_original_text(compl_shown_match);
+ if (strequal(compl_leader, compl_orig_text) && !shown_match_ok) {
+ compl_shown_match = compl_no_select ? compl_first_match : compl_first_match->cp_next;
+ }
+
compl_T *shown_compl = NULL;
bool did_find_shown_match = false;
int cur = -1;
@@ -1206,8 +1213,9 @@ static int ins_compl_build_pum(void)
do {
if (!match_at_original_text(comp)
&& (compl_leader == NULL
- || ins_compl_equal(comp, compl_leader, (size_t)lead_len))) {
- if (!shown_match_ok) {
+ || ins_compl_equal(comp, compl_leader, (size_t)lead_len)
+ || (compl_fuzzy_match && comp->cp_score > 0))) {
+ if (!shown_match_ok && !compl_fuzzy_match) {
if (comp == compl_shown_match || did_find_shown_match) {
// This item is the shown match or this is the
// first displayed item after the shown match.
@@ -1220,6 +1228,36 @@ static int ins_compl_build_pum(void)
shown_compl = comp;
}
cur = i;
+ } else if (compl_fuzzy_match) {
+ if (i == 0) {
+ shown_compl = comp;
+ }
+ // Update the maximum fuzzy score and the shown match
+ // if the current item's score is higher
+ if (comp->cp_score > max_fuzzy_score) {
+ did_find_shown_match = true;
+ max_fuzzy_score = comp->cp_score;
+ if (!compl_no_select) {
+ compl_shown_match = comp;
+ }
+ }
+
+ if (!shown_match_ok && comp == compl_shown_match && !compl_no_select) {
+ cur = i;
+ shown_match_ok = true;
+ }
+
+ // If there is no "no select" condition and the max fuzzy
+ // score is positive, or there is no completion leader or the
+ // leader length is zero, mark the shown match as valid and
+ // reset the current index.
+ if (!compl_no_select
+ && (max_fuzzy_score > 0
+ || (compl_leader == NULL || lead_len == 0))) {
+ if (match_at_original_text(compl_shown_match)) {
+ compl_shown_match = shown_compl;
+ }
+ }
}
if (comp->cp_text[CPT_ABBR] != NULL) {
@@ -1229,6 +1267,9 @@ static int ins_compl_build_pum(void)
}
compl_match_array[i].pum_kind = comp->cp_text[CPT_KIND];
compl_match_array[i].pum_info = comp->cp_text[CPT_INFO];
+ compl_match_array[i].pum_score = comp->cp_score;
+ compl_match_array[i].pum_user_hlattr = comp->cp_user_hlattr;
+ compl_match_array[i].pum_user_kind_hlattr = comp->cp_user_kind_hlattr;
if (comp->cp_text[CPT_MENU] != NULL) {
compl_match_array[i++].pum_extra = comp->cp_text[CPT_MENU];
} else {
@@ -1236,7 +1277,7 @@ static int ins_compl_build_pum(void)
}
}
- if (comp == compl_shown_match) {
+ if (comp == compl_shown_match && !compl_fuzzy_match) {
did_find_shown_match = true;
// When the original text is the shown match don't set
@@ -1255,6 +1296,16 @@ static int ins_compl_build_pum(void)
comp = comp->cp_next;
} while (comp != NULL && !is_first_match(comp));
+ if (compl_fuzzy_match && compl_leader != NULL && lead_len > 0) {
+ for (i = 0; i < compl_match_arraysize; i++) {
+ compl_match_array[i].pum_idx = i;
+ }
+ // sort by the largest score of fuzzy match
+ qsort(compl_match_array, (size_t)compl_match_arraysize, sizeof(pumitem_T),
+ ins_compl_fuzzy_cmp);
+ shown_match_ok = true;
+ }
+
if (!shown_match_ok) { // no displayed match at all
cur = -1;
}
@@ -1306,7 +1357,7 @@ void ins_compl_show_pum(void)
// Use the cursor to get all wrapping and other settings right.
const colnr_T col = curwin->w_cursor.col;
curwin->w_cursor.col = compl_col;
- pum_selected_item = cur;
+ compl_selected_item = cur;
pum_display(compl_match_array, compl_match_arraysize, cur, array_changed, 0);
curwin->w_cursor.col = col;
@@ -1349,6 +1400,12 @@ bool compl_match_curr_select(int selected)
#define DICT_FIRST (1) ///< use just first element in "dict"
#define DICT_EXACT (2) ///< "dict" is the exact name of a file
+/// Get current completion leader
+char *ins_compl_leader(void)
+{
+ return compl_leader != NULL ? compl_leader : compl_orig_text;
+}
+
/// Add any identifiers that match the given pattern "pat" in the list of
/// dictionary files "dict_start" to the list of completions.
///
@@ -1730,6 +1787,13 @@ int ins_compl_bs(void)
return NUL;
}
+/// Check if the complete function returned "always" in the "refresh" dictionary item.
+static bool ins_compl_refresh_always(void)
+ FUNC_ATTR_PURE
+{
+ return (ctrl_x_mode_function() || ctrl_x_mode_omni()) && compl_opt_refresh_always;
+}
+
/// Check that we need to find matches again, ins_compl_restart() is to
/// be called.
static bool ins_compl_need_restart(void)
@@ -1737,9 +1801,7 @@ static bool ins_compl_need_restart(void)
{
// Return true if we didn't complete finding matches or when the
// "completefunc" returned "always" in the "refresh" dictionary item.
- return compl_was_interrupted
- || ((ctrl_x_mode_function() || ctrl_x_mode_omni())
- && compl_opt_refresh_always);
+ return compl_was_interrupted || ins_compl_refresh_always();
}
/// Called after changing "compl_leader".
@@ -1772,7 +1834,7 @@ static void ins_compl_new_leader(void)
// Don't let Enter select the original text when there is no popup menu.
// Don't let Enter select when use user function and refresh_always is set
- if (compl_match_array == NULL || ins_compl_need_restart()) {
+ if (compl_match_array == NULL || ins_compl_refresh_always()) {
compl_enter_selects = false;
}
}
@@ -2174,7 +2236,7 @@ bool ins_compl_prep(int c)
// Set "compl_get_longest" when finding the first matches.
if (ctrl_x_mode_not_defined_yet()
|| (ctrl_x_mode_normal() && !compl_started)) {
- compl_get_longest = compl_longest;
+ compl_get_longest = (get_cot_flags() & COT_LONGEST) != 0;
compl_used_match = true;
}
@@ -2480,6 +2542,14 @@ theend:
}
}
+static inline int get_user_highlight_attr(const char *hlname)
+{
+ if (hlname != NULL && *hlname != NUL) {
+ return syn_name2attr(hlname);
+ }
+ return -1;
+}
+
/// Add a match to the list of matches from Vimscript object
///
/// @param[in] tv Object to get matches from.
@@ -2497,6 +2567,10 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast)
bool empty = false;
int flags = fast ? CP_FAST : 0;
char *(cptext[CPT_COUNT]);
+ char *user_hlname = NULL;
+ char *user_kind_hlname = NULL;
+ int user_hlattr = -1;
+ int user_kind_hlattr = -1;
typval_T user_data;
user_data.v_type = VAR_UNKNOWN;
@@ -2506,6 +2580,13 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast)
cptext[CPT_MENU] = tv_dict_get_string(tv->vval.v_dict, "menu", true);
cptext[CPT_KIND] = tv_dict_get_string(tv->vval.v_dict, "kind", true);
cptext[CPT_INFO] = tv_dict_get_string(tv->vval.v_dict, "info", true);
+
+ user_hlname = tv_dict_get_string(tv->vval.v_dict, "hl_group", false);
+ user_hlattr = get_user_highlight_attr(user_hlname);
+
+ user_kind_hlname = tv_dict_get_string(tv->vval.v_dict, "kind_hlgroup", false);
+ user_kind_hlattr = get_user_highlight_attr(user_kind_hlname);
+
tv_dict_get_tv(tv->vval.v_dict, "user_data", &user_data);
if (tv_dict_get_number(tv->vval.v_dict, "icase")) {
@@ -2527,7 +2608,7 @@ static int ins_compl_add_tv(typval_T *const tv, const Direction dir, bool fast)
return FAIL;
}
int status = ins_compl_add((char *)word, -1, NULL, cptext, true,
- &user_data, dir, flags, dup);
+ &user_data, dir, flags, dup, user_hlattr, user_kind_hlattr);
if (status != OK) {
tv_clear(&user_data);
}
@@ -2594,6 +2675,10 @@ static void restore_orig_extmarks(void)
static void set_completion(colnr_T startcol, list_T *list)
{
int flags = CP_ORIGINAL_TEXT;
+ unsigned cur_cot_flags = get_cot_flags();
+ bool compl_longest = (cur_cot_flags & COT_LONGEST) != 0;
+ bool compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0;
+ bool compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0;
// If already doing completions stop it.
if (ctrl_x_mode_not_default()) {
@@ -2616,7 +2701,7 @@ static void set_completion(colnr_T startcol, list_T *list)
flags |= CP_ICASE;
}
if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0,
- flags | CP_FAST, false) != OK) {
+ flags | CP_FAST, false, -1, -1) != OK) {
return;
}
@@ -3361,7 +3446,7 @@ static void get_next_bufname_token(void)
char *tail = path_tail(b->b_sfname);
if (strncmp(tail, compl_orig_text, strlen(compl_orig_text)) == 0) {
ins_compl_add(tail, (int)strlen(tail), NULL, NULL, false, NULL, 0,
- p_ic ? CP_ICASE : 0, false);
+ p_ic ? CP_ICASE : 0, false, -1, -1);
}
}
}
@@ -3584,6 +3669,44 @@ static void ins_compl_show_filename(void)
redraw_cmdline = false; // don't overwrite!
}
+/// Find a completion item when 'completeopt' contains "fuzzy".
+static compl_T *find_comp_when_fuzzy(void)
+{
+ int target_idx = -1;
+ const bool is_forward = compl_shows_dir_forward();
+ const bool is_backward = compl_shows_dir_backward();
+ compl_T *comp = NULL;
+
+ assert(compl_match_array != NULL);
+ if ((is_forward && compl_selected_item == compl_match_arraysize - 1)
+ || (is_backward && compl_selected_item == 0)) {
+ return compl_first_match != compl_shown_match
+ ? compl_first_match
+ : (compl_first_match->cp_prev ? compl_first_match->cp_prev : NULL);
+ }
+
+ if (is_forward) {
+ target_idx = compl_selected_item + 1;
+ } else if (is_backward) {
+ target_idx = compl_selected_item == -1 ? compl_match_arraysize - 1
+ : compl_selected_item - 1;
+ }
+
+ const int score = compl_match_array[target_idx].pum_score;
+ char *const str = compl_match_array[target_idx].pum_text;
+
+ comp = compl_first_match;
+ do {
+ if (comp->cp_score == score
+ && (str == comp->cp_str || str == comp->cp_text[CPT_ABBR])) {
+ return comp;
+ }
+ comp = comp->cp_next;
+ } while (comp != NULL && !is_first_match(comp));
+
+ return NULL;
+}
+
/// Find the next set of matches for completion. Repeat the completion "todo"
/// times. The number of matches found is returned in 'num_matches'.
///
@@ -3601,17 +3724,22 @@ static int find_next_completion_match(bool allow_get_expansion, int todo, bool a
{
bool found_end = false;
compl_T *found_compl = NULL;
+ unsigned cur_cot_flags = get_cot_flags();
+ bool compl_no_select = (cur_cot_flags & COT_NOSELECT) != 0;
+ bool compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0;
while (--todo >= 0) {
if (compl_shows_dir_forward() && compl_shown_match->cp_next != NULL) {
- compl_shown_match = compl_shown_match->cp_next;
+ compl_shown_match = compl_fuzzy_match && compl_match_array != NULL
+ ? find_comp_when_fuzzy() : compl_shown_match->cp_next;
found_end = (compl_first_match != NULL
&& (is_first_match(compl_shown_match->cp_next)
|| is_first_match(compl_shown_match)));
} else if (compl_shows_dir_backward()
&& compl_shown_match->cp_prev != NULL) {
found_end = is_first_match(compl_shown_match);
- compl_shown_match = compl_shown_match->cp_prev;
+ compl_shown_match = compl_fuzzy_match && compl_match_array != NULL
+ ? find_comp_when_fuzzy() : compl_shown_match->cp_prev;
found_end |= is_first_match(compl_shown_match);
} else {
if (!allow_get_expansion) {
@@ -3655,7 +3783,8 @@ static int find_next_completion_match(bool allow_get_expansion, int todo, bool a
if (!match_at_original_text(compl_shown_match)
&& compl_leader != NULL
&& !ins_compl_equal(compl_shown_match,
- compl_leader, strlen(compl_leader))) {
+ compl_leader, strlen(compl_leader))
+ && !(compl_fuzzy_match && compl_shown_match->cp_score > 0)) {
todo++;
} else {
// Remember a matching item.
@@ -3700,6 +3829,9 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
int todo = count;
const bool started = compl_started;
buf_T *const orig_curbuf = curbuf;
+ unsigned cur_cot_flags = get_cot_flags();
+ bool compl_no_insert = (cur_cot_flags & COT_NOINSERT) != 0;
+ bool compl_fuzzy_match = (cur_cot_flags & COT_FUZZY) != 0;
// When user complete function return -1 for findstart which is next
// time of 'always', compl_shown_match become NULL.
@@ -3707,7 +3839,9 @@ static int ins_compl_next(bool allow_get_expansion, int count, bool insert_match
return -1;
}
- if (compl_leader != NULL && !match_at_original_text(compl_shown_match)) {
+ if (compl_leader != NULL
+ && !match_at_original_text(compl_shown_match)
+ && !compl_fuzzy_match) {
// Update "compl_shown_match" to the actually shown match
ins_compl_update_shown_match();
}
@@ -3836,7 +3970,7 @@ void ins_compl_check_keys(int frequency, bool in_compl_func)
}
}
}
- if (compl_pending != 0 && !got_int && !compl_no_insert) {
+ if (compl_pending != 0 && !got_int && !(cot_flags & COT_NOINSERT)) {
int todo = compl_pending > 0 ? compl_pending : -compl_pending;
compl_pending = 0;
@@ -3849,7 +3983,7 @@ void ins_compl_check_keys(int frequency, bool in_compl_func)
static int ins_compl_key2dir(int c)
{
if (c == K_EVENT || c == K_COMMAND || c == K_LUA) {
- return pum_want.item < pum_selected_item ? BACKWARD : FORWARD;
+ return pum_want.item < compl_selected_item ? BACKWARD : FORWARD;
}
if (c == Ctrl_P || c == Ctrl_L
|| c == K_PAGEUP || c == K_KPAGEUP
@@ -3875,7 +4009,7 @@ static bool ins_compl_pum_key(int c)
static int ins_compl_key2count(int c)
{
if (c == K_EVENT || c == K_COMMAND || c == K_LUA) {
- int offset = pum_want.item - pum_selected_item;
+ int offset = pum_want.item - compl_selected_item;
return abs(offset);
}
@@ -3974,7 +4108,7 @@ static int get_normal_compl_info(char *line, int startcol, colnr_T curs_col)
compl_pattern = xmalloc(7);
STRCPY(compl_pattern, "\\<");
quote_meta(compl_pattern + 2, line + compl_col, 1);
- STRCAT(compl_pattern, "\\k");
+ strcat(compl_pattern, "\\k");
} else {
compl_pattern = xmalloc(quote_meta(NULL, line + compl_col, compl_length) + 2);
STRCPY(compl_pattern, "\\<");
@@ -4042,6 +4176,9 @@ static int get_cmdline_compl_info(char *line, colnr_T curs_col)
compl_pattern = xstrnsave(line, (size_t)curs_col);
compl_patternlen = (size_t)curs_col;
set_cmd_context(&compl_xp, compl_pattern, (int)compl_patternlen, curs_col, false);
+ if (compl_xp.xp_context == EXPAND_LUA) {
+ nlua_expand_pat(&compl_xp);
+ }
if (compl_xp.xp_context == EXPAND_UNSUCCESSFUL
|| compl_xp.xp_context == EXPAND_NOTHING) {
// No completion possible, use an empty pattern to get a
@@ -4347,7 +4484,7 @@ static int ins_compl_start(void)
flags |= CP_ICASE;
}
if (ins_compl_add(compl_orig_text, -1, NULL, NULL, false, NULL, 0,
- flags, false) != OK) {
+ flags, false, -1, -1) != OK) {
XFREE_CLEAR(compl_pattern);
compl_patternlen = 0;
XFREE_CLEAR(compl_orig_text);
diff --git a/src/nvim/insexpand.h b/src/nvim/insexpand.h
index b880e64ea4..8c05590b79 100644
--- a/src/nvim/insexpand.h
+++ b/src/nvim/insexpand.h
@@ -8,3 +8,12 @@
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "insexpand.h.generated.h"
#endif
+
+/// Array indexes used for cp_text[].
+typedef enum {
+ CPT_ABBR, ///< "abbr"
+ CPT_KIND, ///< "kind"
+ CPT_MENU, ///< "menu"
+ CPT_INFO, ///< "info"
+ CPT_COUNT, ///< Number of entries
+} cpitem_T;
diff --git a/src/nvim/keycodes.c b/src/nvim/keycodes.c
index 44ddfbba00..f7215d3d12 100644
--- a/src/nvim/keycodes.c
+++ b/src/nvim/keycodes.c
@@ -8,6 +8,7 @@
#include "nvim/ascii_defs.h"
#include "nvim/charset.h"
+#include "nvim/errors.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
#include "nvim/gettext_defs.h"
diff --git a/src/nvim/keycodes.h b/src/nvim/keycodes.h
index 18af3f87d6..5a7ddd4847 100644
--- a/src/nvim/keycodes.h
+++ b/src/nvim/keycodes.h
@@ -380,6 +380,10 @@ enum key_extra {
#define K_KENTER TERMCAP2KEY('K', 'A') // keypad Enter
#define K_KPOINT TERMCAP2KEY('K', 'B') // keypad . or ,
+// Delimits pasted text (to repeat nvim_paste). Internal-only, not sent by UIs.
+#define K_PASTE_START TERMCAP2KEY('P', 'S') // paste start
+#define K_PASTE_END TERMCAP2KEY('P', 'E') // paste end
+
#define K_K0 TERMCAP2KEY('K', 'C') // keypad 0
#define K_K1 TERMCAP2KEY('K', 'D') // keypad 1
#define K_K2 TERMCAP2KEY('K', 'E') // keypad 2
diff --git a/src/nvim/lib/queue_defs.h b/src/nvim/lib/queue_defs.h
index 1f113a057a..4f32f5fcb6 100644
--- a/src/nvim/lib/queue_defs.h
+++ b/src/nvim/lib/queue_defs.h
@@ -21,13 +21,15 @@
#include <stddef.h>
-#include "nvim/func_attr.h"
-
typedef struct queue {
struct queue *next;
struct queue *prev;
} QUEUE;
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "lib/queue_defs.h.inline.generated.h"
+#endif
+
// Public macros.
#define QUEUE_DATA(ptr, type, field) \
((type *)((char *)(ptr) - offsetof(type, field)))
@@ -44,29 +46,23 @@ typedef struct queue {
}
// ffi.cdef is unable to swallow `bool` in place of `int` here.
-static inline int QUEUE_EMPTY(const QUEUE *q)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT;
-
static inline int QUEUE_EMPTY(const QUEUE *const q)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT
{
return q == q->next;
}
#define QUEUE_HEAD(q) (q)->next
-static inline void QUEUE_INIT(QUEUE *q)
- REAL_FATTR_ALWAYS_INLINE;
-
static inline void QUEUE_INIT(QUEUE *const q)
+ FUNC_ATTR_ALWAYS_INLINE
{
q->next = q;
q->prev = q;
}
-static inline void QUEUE_ADD(QUEUE *h, QUEUE *n)
- REAL_FATTR_ALWAYS_INLINE;
-
static inline void QUEUE_ADD(QUEUE *const h, QUEUE *const n)
+ FUNC_ATTR_ALWAYS_INLINE
{
h->prev->next = n->next;
n->next->prev = h->prev;
@@ -74,10 +70,8 @@ static inline void QUEUE_ADD(QUEUE *const h, QUEUE *const n)
h->prev->next = h;
}
-static inline void QUEUE_INSERT_HEAD(QUEUE *h, QUEUE *q)
- REAL_FATTR_ALWAYS_INLINE;
-
static inline void QUEUE_INSERT_HEAD(QUEUE *const h, QUEUE *const q)
+ FUNC_ATTR_ALWAYS_INLINE
{
q->next = h->next;
q->prev = h;
@@ -85,10 +79,8 @@ static inline void QUEUE_INSERT_HEAD(QUEUE *const h, QUEUE *const q)
h->next = q;
}
-static inline void QUEUE_INSERT_TAIL(QUEUE *h, QUEUE *q)
- REAL_FATTR_ALWAYS_INLINE;
-
static inline void QUEUE_INSERT_TAIL(QUEUE *const h, QUEUE *const q)
+ FUNC_ATTR_ALWAYS_INLINE
{
q->next = h;
q->prev = h->prev;
@@ -96,10 +88,8 @@ static inline void QUEUE_INSERT_TAIL(QUEUE *const h, QUEUE *const q)
h->prev = q;
}
-static inline void QUEUE_REMOVE(QUEUE *q)
- REAL_FATTR_ALWAYS_INLINE;
-
static inline void QUEUE_REMOVE(QUEUE *const q)
+ FUNC_ATTR_ALWAYS_INLINE
{
q->prev->next = q->next;
q->next->prev = q->prev;
diff --git a/src/nvim/linematch.c b/src/nvim/linematch.c
index c34b303193..8943e6e8a6 100644
--- a/src/nvim/linematch.c
+++ b/src/nvim/linematch.c
@@ -5,10 +5,13 @@
#include <stdint.h>
#include <string.h>
+#include "nvim/ascii_defs.h"
#include "nvim/linematch.h"
#include "nvim/macros_defs.h"
#include "nvim/memory.h"
#include "nvim/pos_defs.h"
+#include "nvim/strings.h"
+#include "xdiff/xdiff.h"
#define LN_MAX_BUFS 8
#define LN_DECISION_MAX 255 // pow(2, LN_MAX_BUFS(8)) - 1 = 255
@@ -28,49 +31,49 @@ struct diffcmppath_S {
# include "linematch.c.generated.h"
#endif
-static size_t line_len(const char *s)
+static size_t line_len(const mmfile_t *m)
{
- char *end = strchr(s, '\n');
+ char *s = m->ptr;
+ size_t n = (size_t)m->size;
+ char *end = strnchr(s, &n, '\n');
if (end) {
return (size_t)(end - s);
}
- return strlen(s);
+ return (size_t)m->size;
}
+#define MATCH_CHAR_MAX_LEN 800
+
/// Same as matching_chars but ignore whitespace
///
/// @param s1
/// @param s2
-static int matching_chars_iwhite(const char *s1, const char *s2)
+static int matching_chars_iwhite(const mmfile_t *s1, const mmfile_t *s2)
{
// the newly processed strings that will be compared
- // delete the white space characters, and/or replace all upper case with lower
- char *strsproc[2];
- const char *strsorig[2] = { s1, s2 };
+ // delete the white space characters
+ mmfile_t sp[2];
+ char p[2][MATCH_CHAR_MAX_LEN];
for (int k = 0; k < 2; k++) {
- size_t d = 0;
- size_t i = 0;
- size_t slen = line_len(strsorig[k]);
- strsproc[k] = xmalloc((slen + 1) * sizeof(char));
- while (d + i < slen) {
- char e = strsorig[k][i + d];
+ const mmfile_t *s = k == 0 ? s1 : s2;
+ size_t pi = 0;
+ size_t slen = MIN(MATCH_CHAR_MAX_LEN - 1, line_len(s));
+ for (size_t i = 0; i <= slen; i++) {
+ char e = s->ptr[i];
if (e != ' ' && e != '\t') {
- strsproc[k][i] = e;
- i++;
- } else {
- d++;
+ p[k][pi] = e;
+ pi++;
}
}
- strsproc[k][i] = '\0';
+
+ sp[k] = (mmfile_t){
+ .ptr = p[k],
+ .size = (int)pi,
+ };
}
- int matching = matching_chars(strsproc[0], strsproc[1]);
- xfree(strsproc[0]);
- xfree(strsproc[1]);
- return matching;
+ return matching_chars(&sp[0], &sp[1]);
}
-#define MATCH_CHAR_MAX_LEN 800
-
/// Return matching characters between "s1" and "s2" whilst respecting sequence order.
/// Consider the case of two strings 'AAACCC' and 'CCCAAA', the
/// return value from this function will be 3, either to match
@@ -82,12 +85,14 @@ static int matching_chars_iwhite(const char *s1, const char *s2)
/// matching_chars("abcdefg", "gfedcba") -> 1 // all characters in common,
/// // but only at most 1 in sequence
///
-/// @param s1
-/// @param s2
-static int matching_chars(const char *s1, const char *s2)
+/// @param m1
+/// @param m2
+static int matching_chars(const mmfile_t *m1, const mmfile_t *m2)
{
- size_t s1len = MIN(MATCH_CHAR_MAX_LEN - 1, line_len(s1));
- size_t s2len = MIN(MATCH_CHAR_MAX_LEN - 1, line_len(s2));
+ size_t s1len = MIN(MATCH_CHAR_MAX_LEN - 1, line_len(m1));
+ size_t s2len = MIN(MATCH_CHAR_MAX_LEN - 1, line_len(m2));
+ char *s1 = m1->ptr;
+ char *s2 = m2->ptr;
int matrix[2][MATCH_CHAR_MAX_LEN] = { 0 };
bool icur = 1; // save space by storing only two rows for i axis
for (size_t i = 0; i < s1len; i++) {
@@ -118,13 +123,13 @@ static int matching_chars(const char *s1, const char *s2)
/// @param sp
/// @param fomvals
/// @param n
-static int count_n_matched_chars(const char **sp, const size_t n, bool iwhite)
+static int count_n_matched_chars(mmfile_t **sp, const size_t n, bool iwhite)
{
int matched_chars = 0;
int matched = 0;
for (size_t i = 0; i < n; i++) {
for (size_t j = i + 1; j < n; j++) {
- if (sp[i] != NULL && sp[j] != NULL) {
+ if (sp[i]->ptr != NULL && sp[j]->ptr != NULL) {
matched++;
// TODO(lewis6991): handle whitespace ignoring higher up in the stack
matched_chars += iwhite ? matching_chars_iwhite(sp[i], sp[j])
@@ -142,15 +147,17 @@ static int count_n_matched_chars(const char **sp, const size_t n, bool iwhite)
return matched_chars;
}
-void fastforward_buf_to_lnum(const char **s, linenr_T lnum)
+mmfile_t fastforward_buf_to_lnum(mmfile_t s, linenr_T lnum)
{
for (int i = 0; i < lnum - 1; i++) {
- *s = strchr(*s, '\n');
- if (!*s) {
- return;
+ s.ptr = strnchr(s.ptr, (size_t *)&s.size, '\n');
+ if (!s.ptr) {
+ break;
}
- (*s)++;
+ s.ptr++;
+ s.size--;
}
+ return s;
}
/// try all the different ways to compare these lines and use the one that
@@ -166,25 +173,25 @@ void fastforward_buf_to_lnum(const char **s, linenr_T lnum)
/// @param diff_blk
static void try_possible_paths(const int *df_iters, const size_t *paths, const int npaths,
const int path_idx, int *choice, diffcmppath_T *diffcmppath,
- const int *diff_len, const size_t ndiffs, const char **diff_blk,
+ const int *diff_len, const size_t ndiffs, const mmfile_t **diff_blk,
bool iwhite)
{
if (path_idx == npaths) {
if ((*choice) > 0) {
int from_vals[LN_MAX_BUFS] = { 0 };
const int *to_vals = df_iters;
- const char *current_lines[LN_MAX_BUFS];
+ mmfile_t mm[LN_MAX_BUFS]; // stack memory for current_lines
+ mmfile_t *current_lines[LN_MAX_BUFS];
for (size_t k = 0; k < ndiffs; k++) {
from_vals[k] = df_iters[k];
// get the index at all of the places
if ((*choice) & (1 << k)) {
from_vals[k]--;
- const char *p = diff_blk[k];
- fastforward_buf_to_lnum(&p, df_iters[k]);
- current_lines[k] = p;
+ mm[k] = fastforward_buf_to_lnum(*diff_blk[k], df_iters[k]);
} else {
- current_lines[k] = NULL;
+ mm[k] = (mmfile_t){ 0 };
}
+ current_lines[k] = &mm[k];
}
size_t unwrapped_idx_from = unwrap_indexes(from_vals, diff_len, ndiffs);
size_t unwrapped_idx_to = unwrap_indexes(to_vals, diff_len, ndiffs);
@@ -243,7 +250,7 @@ static size_t unwrap_indexes(const int *values, const int *diff_len, const size_
/// @param ndiffs
/// @param diff_blk
static void populate_tensor(int *df_iters, const size_t ch_dim, diffcmppath_T *diffcmppath,
- const int *diff_len, const size_t ndiffs, const char **diff_blk,
+ const int *diff_len, const size_t ndiffs, const mmfile_t **diff_blk,
bool iwhite)
{
if (ch_dim == ndiffs) {
@@ -326,7 +333,7 @@ static void populate_tensor(int *df_iters, const size_t ch_dim, diffcmppath_T *d
/// @param ndiffs
/// @param [out] [allocated] decisions
/// @return the length of decisions
-size_t linematch_nbuffers(const char **diff_blk, const int *diff_len, const size_t ndiffs,
+size_t linematch_nbuffers(const mmfile_t **diff_blk, const int *diff_len, const size_t ndiffs,
int **decisions, bool iwhite)
{
assert(ndiffs <= LN_MAX_BUFS);
diff --git a/src/nvim/linematch.h b/src/nvim/linematch.h
index eaf0d54bec..5f6667a7df 100644
--- a/src/nvim/linematch.h
+++ b/src/nvim/linematch.h
@@ -3,6 +3,7 @@
#include <stddef.h> // IWYU pragma: keep
#include "nvim/pos_defs.h" // IWYU pragma: keep
+#include "xdiff/xdiff.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "linematch.h.generated.h"
diff --git a/src/nvim/log.c b/src/nvim/log.c
index fbb3e0385a..ef5e21aa0a 100644
--- a/src/nvim/log.c
+++ b/src/nvim/log.c
@@ -29,6 +29,7 @@
#include "nvim/os/stdpaths_defs.h"
#include "nvim/os/time.h"
#include "nvim/path.h"
+#include "nvim/ui_client.h"
/// Cached location of the expanded log file path decided by log_path_init().
static char log_file_path[MAXPATHL + 1] = { 0 };
@@ -46,7 +47,7 @@ static uv_mutex_t mutex;
static bool log_try_create(char *fname)
{
- if (fname == NULL || fname[0] == '\0') {
+ if (fname == NULL || fname[0] == NUL) {
return false;
}
FILE *log_file = fopen(fname, "a");
@@ -67,7 +68,7 @@ static void log_path_init(void)
size_t size = sizeof(log_file_path);
expand_env("$" ENV_LOGFILE, log_file_path, (int)size - 1);
if (strequal("$" ENV_LOGFILE, log_file_path)
- || log_file_path[0] == '\0'
+ || log_file_path[0] == NUL
|| os_isdir(log_file_path)
|| !log_try_create(log_file_path)) {
// Make $XDG_STATE_HOME if it does not exist.
@@ -88,7 +89,7 @@ static void log_path_init(void)
}
// Fall back to stderr
if (len >= size || !log_try_create(log_file_path)) {
- log_file_path[0] = '\0';
+ log_file_path[0] = NUL;
return;
}
os_setenv(ENV_LOGFILE, log_file_path, true);
@@ -257,6 +258,7 @@ void log_callstack_to_file(FILE *log_file, const char *const func_name, const in
do_log_to_file(log_file, LOGLVL_DBG, NULL, func_name, line_num, true, "trace:");
FILE *fp = popen(cmdbuf, "r");
+ assert(fp);
char linebuf[IOSIZE];
while (fgets(linebuf, sizeof(linebuf) - 1, fp) != NULL) {
fprintf(log_file, " %s", linebuf);
@@ -322,20 +324,28 @@ static bool v_do_log_to_file(FILE *log_file, int log_level, const char *context,
millis = (int)curtime.tv_usec / 1000;
}
+ bool ui = !!ui_client_channel_id; // Running as a UI client (--remote-ui).
+
+ // Regenerate the name when:
+ // - UI client (to ensure "ui" is in the name)
+ // - not set yet
+ // - no v:servername yet
+ bool regen = ui || name[0] == NUL || name[0] == '?';
+
// Get a name for this Nvim instance.
// TODO(justinmk): expose this as v:name ?
- if (name[0] == '\0') {
- // Parent servername.
+ if (regen) {
+ // Parent servername ($NVIM).
const char *parent = path_tail(os_getenv(ENV_NVIM));
// Servername. Empty until starting=false.
const char *serv = path_tail(get_vim_var_str(VV_SEND_SERVER));
if (parent[0] != NUL) {
- snprintf(name, sizeof(name), "%s/c", parent); // "/c" indicates child.
+ snprintf(name, sizeof(name), ui ? "ui/c/%s" : "c/%s", parent); // "c/" = child of $NVIM.
} else if (serv[0] != NUL) {
- snprintf(name, sizeof(name), "%s", serv);
+ snprintf(name, sizeof(name), ui ? "ui/%s" : "%s", serv);
} else {
int64_t pid = os_get_pid();
- snprintf(name, sizeof(name), "?.%-5" PRId64, pid);
+ snprintf(name, sizeof(name), "%s.%-5" PRId64, ui ? "ui" : "?", pid);
}
}
@@ -348,10 +358,6 @@ static bool v_do_log_to_file(FILE *log_file, int log_level, const char *context,
log_levels[log_level], date_time, millis, name,
(context == NULL ? "" : context),
func_name, line_num);
- if (name[0] == '?') {
- // No v:servername yet. Clear `name` so that the next log can try again.
- name[0] = '\0';
- }
if (rv < 0) {
return false;
diff --git a/src/nvim/lua/api_wrappers.c b/src/nvim/lua/api_wrappers.c
index 2b7b0c6471..36847d1fc9 100644
--- a/src/nvim/lua/api_wrappers.c
+++ b/src/nvim/lua/api_wrappers.c
@@ -5,6 +5,7 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/dispatch.h"
#include "nvim/api/private/helpers.h"
+#include "nvim/errors.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
#include "nvim/func_attr.h"
diff --git a/src/nvim/lua/converter.c b/src/nvim/lua/converter.c
index 38ccb03cfc..45ead154bc 100644
--- a/src/nvim/lua/converter.c
+++ b/src/nvim/lua/converter.c
@@ -26,13 +26,13 @@
#include "nvim/types_defs.h"
#include "nvim/vim_defs.h"
-/// Determine, which keys lua table contains
+/// Determine, which keys Lua table contains
typedef struct {
size_t maxidx; ///< Maximum positive integral value found.
size_t string_keys_num; ///< Number of string keys.
bool has_string_with_nul; ///< True if there is string key with NUL byte.
ObjectType type; ///< If has_type_key is true then attached value. Otherwise
- ///< either kObjectTypeNil, kObjectTypeDictionary or
+ ///< either kObjectTypeNil, kObjectTypeDict or
///< kObjectTypeArray, depending on other properties.
lua_Number val; ///< If has_val_key and val_type == LUA_TNUMBER: value.
bool has_type_key; ///< True if type key is present.
@@ -52,7 +52,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
size_t tsize = 0; // Total number of keys.
- int val_type = 0; // If has_val_key: lua type of the value.
+ int val_type = 0; // If has_val_key: Lua type of the value.
bool has_val_key = false; // True if val key was found,
// @see nlua_push_val_idx().
size_t other_keys_num = 0; // Number of keys that are not string, integral
@@ -96,7 +96,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate)
lua_Number n = lua_tonumber(lstate, -1);
if (n == (lua_Number)kObjectTypeFloat
|| n == (lua_Number)kObjectTypeArray
- || n == (lua_Number)kObjectTypeDictionary) {
+ || n == (lua_Number)kObjectTypeDict) {
ret.has_type_key = true;
ret.type = (ObjectType)n;
} else {
@@ -122,6 +122,7 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate)
lua_pop(lstate, 1);
}
if (ret.has_type_key) {
+ assert(tsize > 0);
if (ret.type == kObjectTypeFloat
&& (!has_val_key || val_type != LUA_TNUMBER)) {
ret.type = kObjectTypeNil;
@@ -156,12 +157,12 @@ static LuaTableProps nlua_traverse_table(lua_State *const lstate)
if (tsize == 0 && lua_getmetatable(lstate, -1)) {
nlua_pushref(lstate, nlua_global_refs->empty_dict_ref);
if (lua_rawequal(lstate, -2, -1)) {
- ret.type = kObjectTypeDictionary;
+ ret.type = kObjectTypeDict;
}
lua_pop(lstate, 2);
}
} else if (ret.string_keys_num == tsize) {
- ret.type = kObjectTypeDictionary;
+ ret.type = kObjectTypeDict;
} else {
ret.type = kObjectTypeNil;
}
@@ -174,14 +175,14 @@ typedef struct {
typval_T *tv; ///< Location where conversion result is saved.
size_t list_len; ///< Maximum length when tv is a list.
bool container; ///< True if tv is a container.
- bool special; ///< If true then tv is a _VAL part of special dictionary
+ bool special; ///< If true then tv is a _VAL part of special dict.
///< that represents mapping.
int idx; ///< Container index (used to detect self-referencing structures).
} TVPopStackItem;
-/// Convert lua object to Vimscript typval_T
+/// Convert Lua object to Vimscript typval_T
///
-/// Should pop exactly one value from lua stack.
+/// Should pop exactly one value from Lua stack.
///
/// @param lstate Lua state.
/// @param[out] ret_tv Where to put the result.
@@ -219,12 +220,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
if (cur.special) {
list_T *const kv_pair = tv_list_alloc(2);
- typval_T s_tv = decode_string(s, len, kTrue, false, false);
- if (s_tv.v_type == VAR_UNKNOWN) {
- ret = false;
- tv_list_unref(kv_pair);
- continue;
- }
+ typval_T s_tv = decode_string(s, len, true, false);
tv_list_append_owned_tv(kv_pair, s_tv);
// Value: not populated yet, need to create list item to push.
@@ -280,10 +276,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
case LUA_TSTRING: {
size_t len;
const char *s = lua_tolstring(lstate, -1, &len);
- *cur.tv = decode_string(s, len, kNone, true, false);
- if (cur.tv->v_type == VAR_UNKNOWN) {
- ret = false;
- }
+ *cur.tv = decode_string(s, len, false, false);
break;
}
case LUA_TNUMBER: {
@@ -330,7 +323,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
kvi_push(stack, cur);
}
break;
- case kObjectTypeDictionary:
+ case kObjectTypeDict:
if (table_props.string_keys_num == 0) {
cur.tv->v_type = VAR_DICT;
cur.tv->vval.v_dict = tv_dict_alloc();
@@ -365,7 +358,7 @@ bool nlua_pop_typval(lua_State *lstate, typval_T *ret_tv)
cur.tv->vval.v_float = (float_T)table_props.val;
break;
case kObjectTypeNil:
- emsg(_("E5100: Cannot convert given lua table: table should "
+ emsg(_("E5100: Cannot convert given Lua table: table should "
"contain either only integer keys or only string keys"));
ret = false;
break;
@@ -393,13 +386,13 @@ nlua_pop_typval_table_processing_end:
cur.tv->v_type = VAR_SPECIAL;
cur.tv->vval.v_special = kSpecialVarNull;
} else {
- emsg(_("E5101: Cannot convert given lua type"));
+ emsg(_("E5101: Cannot convert given Lua type"));
ret = false;
}
break;
}
default:
- emsg(_("E5101: Cannot convert given lua type"));
+ emsg(_("E5101: Cannot convert given Lua type"));
ret = false;
break;
}
@@ -425,6 +418,8 @@ static bool typval_conv_special = false;
#define TYPVAL_ENCODE_ALLOW_SPECIALS true
+#define TYPVAL_ENCODE_CHECK_BEFORE
+
#define TYPVAL_ENCODE_CONV_NIL(tv) \
do { \
if (typval_conv_special) { \
@@ -480,7 +475,7 @@ static bool typval_conv_special = false;
#define TYPVAL_ENCODE_CONV_EMPTY_DICT(tv, dict) \
do { \
if (typval_conv_special) { \
- nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary); \
+ nlua_create_typed_table(lstate, 0, 0, kObjectTypeDict); \
} else { \
lua_createtable(lstate, 0, 0); \
nlua_pushref(lstate, nlua_global_refs->empty_dict_ref); \
@@ -574,6 +569,7 @@ static bool typval_conv_special = false;
#undef TYPVAL_ENCODE_CONV_LIST_START
#undef TYPVAL_ENCODE_CONV_REAL_LIST_AFTER_START
#undef TYPVAL_ENCODE_CONV_EMPTY_DICT
+#undef TYPVAL_ENCODE_CHECK_BEFORE
#undef TYPVAL_ENCODE_CONV_NIL
#undef TYPVAL_ENCODE_CONV_BOOL
#undef TYPVAL_ENCODE_CONV_UNSIGNED_NUMBER
@@ -588,10 +584,9 @@ static bool typval_conv_special = false;
#undef TYPVAL_ENCODE_CONV_RECURSE
#undef TYPVAL_ENCODE_ALLOW_SPECIALS
-/// Convert Vimscript typval_T to lua value
+/// Convert Vimscript typval_T to Lua value
///
-/// Should leave single value in lua stack. May only fail if lua failed to grow
-/// stack.
+/// Should leave single value in Lua stack. May only fail if Lua failed to grow stack.
///
/// @param lstate Lua interpreter state.
/// @param[in] tv typval_T to convert.
@@ -659,7 +654,7 @@ static inline void nlua_create_typed_table(lua_State *lstate, const size_t narr,
lua_rawset(lstate, -3);
}
-/// Convert given String to lua string
+/// Convert given String to Lua string
///
/// Leaves converted string on top of the stack.
void nlua_push_String(lua_State *lstate, const String s, int flags)
@@ -668,7 +663,7 @@ void nlua_push_String(lua_State *lstate, const String s, int flags)
lua_pushlstring(lstate, s.data, s.size);
}
-/// Convert given Integer to lua number
+/// Convert given Integer to Lua number
///
/// Leaves converted number on top of the stack.
void nlua_push_Integer(lua_State *lstate, const Integer n, int flags)
@@ -677,7 +672,7 @@ void nlua_push_Integer(lua_State *lstate, const Integer n, int flags)
lua_pushnumber(lstate, (lua_Number)n);
}
-/// Convert given Float to lua table
+/// Convert given Float to Lua table
///
/// Leaves converted table on top of the stack.
void nlua_push_Float(lua_State *lstate, const Float f, int flags)
@@ -693,7 +688,7 @@ void nlua_push_Float(lua_State *lstate, const Float f, int flags)
}
}
-/// Convert given Float to lua boolean
+/// Convert given Float to Lua boolean
///
/// Leaves converted value on top of the stack.
void nlua_push_Boolean(lua_State *lstate, const Boolean b, int flags)
@@ -702,20 +697,16 @@ void nlua_push_Boolean(lua_State *lstate, const Boolean b, int flags)
lua_pushboolean(lstate, b);
}
-/// Convert given Dictionary to lua table
+/// Convert given Dict to Lua table
///
/// Leaves converted table on top of the stack.
-void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict, int flags)
+void nlua_push_Dict(lua_State *lstate, const Dict dict, int flags)
FUNC_ATTR_NONNULL_ALL
{
- if (dict.size == 0 && (flags & kNluaPushSpecial)) {
- nlua_create_typed_table(lstate, 0, 0, kObjectTypeDictionary);
- } else {
- lua_createtable(lstate, 0, (int)dict.size);
- if (dict.size == 0 && !(flags & kNluaPushSpecial)) {
- nlua_pushref(lstate, nlua_global_refs->empty_dict_ref);
- lua_setmetatable(lstate, -2);
- }
+ lua_createtable(lstate, 0, (int)dict.size);
+ if (dict.size == 0) {
+ nlua_pushref(lstate, nlua_global_refs->empty_dict_ref);
+ lua_setmetatable(lstate, -2);
}
for (size_t i = 0; i < dict.size; i++) {
nlua_push_String(lstate, dict.items[i].key, flags);
@@ -724,7 +715,7 @@ void nlua_push_Dictionary(lua_State *lstate, const Dictionary dict, int flags)
}
}
-/// Convert given Array to lua table
+/// Convert given Array to Lua table
///
/// Leaves converted table on top of the stack.
void nlua_push_Array(lua_State *lstate, const Array array, int flags)
@@ -750,7 +741,7 @@ GENERATE_INDEX_FUNCTION(Tabpage)
#undef GENERATE_INDEX_FUNCTION
-/// Convert given Object to lua value
+/// Convert given Object to Lua value
///
/// Leaves converted value on top of the stack.
void nlua_push_Object(lua_State *lstate, Object *obj, int flags)
@@ -777,12 +768,12 @@ void nlua_push_Object(lua_State *lstate, Object *obj, int flags)
nlua_push_##type(lstate, obj->data.data_key, flags); \
break; \
}
- ADD_TYPE(Boolean, boolean)
- ADD_TYPE(Integer, integer)
- ADD_TYPE(Float, floating)
- ADD_TYPE(String, string)
- ADD_TYPE(Array, array)
- ADD_TYPE(Dictionary, dictionary)
+ ADD_TYPE(Boolean, boolean)
+ ADD_TYPE(Integer, integer)
+ ADD_TYPE(Float, floating)
+ ADD_TYPE(String, string)
+ ADD_TYPE(Array, array)
+ ADD_TYPE(Dict, dict)
#undef ADD_TYPE
#define ADD_REMOTE_TYPE(type) \
case kObjectType##type: { \
@@ -796,7 +787,7 @@ void nlua_push_Object(lua_State *lstate, Object *obj, int flags)
}
}
-/// Convert lua value to string
+/// Convert Lua value to string
///
/// Always pops one value from the stack.
String nlua_pop_String(lua_State *lstate, Arena *arena, Error *err)
@@ -811,16 +802,16 @@ String nlua_pop_String(lua_State *lstate, Arena *arena, Error *err)
ret.data = (char *)lua_tolstring(lstate, -1, &(ret.size));
assert(ret.data != NULL);
- // TODO(bfredl): it would be "nice" to just use the memory of the lua string
+ // TODO(bfredl): it would be "nice" to just use the memory of the Lua string
// directly, although ensuring the lifetime of such strings is a bit tricky
- // (an API call could invoke nested lua, which triggers GC, and kaboom?)
+ // (an API call could invoke nested Lua, which triggers GC, and kaboom?)
ret.data = arena_memdupz(arena, ret.data, ret.size);
lua_pop(lstate, 1);
return ret;
}
-/// Convert lua value to integer
+/// Convert Lua value to integer
///
/// Always pops one value from the stack.
Integer nlua_pop_Integer(lua_State *lstate, Arena *arena, Error *err)
@@ -841,10 +832,10 @@ Integer nlua_pop_Integer(lua_State *lstate, Arena *arena, Error *err)
return (Integer)n;
}
-/// Convert lua value to boolean
+/// Convert Lua value to boolean
///
-/// Despite the name of the function, this uses lua semantics for booleans.
-/// thus `err` is never set as any lua value can be co-erced into a lua bool
+/// Despite the name of the function, this uses Lua semantics for booleans.
+/// thus `err` is never set as any Lua value can be co-erced into a Lua bool
///
/// Always pops one value from the stack.
Boolean nlua_pop_Boolean(lua_State *lstate, Arena *arena, Error *err)
@@ -855,7 +846,7 @@ Boolean nlua_pop_Boolean(lua_State *lstate, Arena *arena, Error *err)
return ret;
}
-/// Convert lua value to boolean
+/// Convert Lua value to boolean
///
/// This follows API conventions for a Boolean value, compare api_object_to_bool
///
@@ -905,9 +896,9 @@ static inline LuaTableProps nlua_check_type(lua_State *const lstate, Error *cons
}
LuaTableProps table_props = nlua_traverse_table(lstate);
- if (type == kObjectTypeDictionary && table_props.type == kObjectTypeArray
+ if (type == kObjectTypeDict && table_props.type == kObjectTypeArray
&& table_props.maxidx == 0 && !table_props.has_type_key) {
- table_props.type = kObjectTypeDictionary;
+ table_props.type = kObjectTypeDict;
}
if (table_props.type != type) {
@@ -919,7 +910,7 @@ static inline LuaTableProps nlua_check_type(lua_State *const lstate, Error *cons
return table_props;
}
-/// Convert lua table to float
+/// Convert Lua table to float
///
/// Always pops one value from the stack.
Float nlua_pop_Float(lua_State *lstate, Arena *arena, Error *err)
@@ -940,7 +931,7 @@ Float nlua_pop_Float(lua_State *lstate, Arena *arena, Error *err)
return (Float)table_props.val;
}
-/// Convert lua table to array without determining whether it is array
+/// Convert Lua table to array without determining whether it is array
///
/// @param lstate Lua state.
/// @param[in] table_props nlua_traverse_table() output.
@@ -975,7 +966,7 @@ static Array nlua_pop_Array_unchecked(lua_State *const lstate, const LuaTablePro
return ret;
}
-/// Convert lua table to array
+/// Convert Lua table to array
///
/// Always pops one value from the stack.
Array nlua_pop_Array(lua_State *lstate, Arena *arena, Error *err)
@@ -988,7 +979,7 @@ Array nlua_pop_Array(lua_State *lstate, Arena *arena, Error *err)
return nlua_pop_Array_unchecked(lstate, table_props, arena, err);
}
-/// Convert lua table to dictionary
+/// Convert Lua table to dictionary
///
/// Always pops one value from the stack. Does not check whether whether topmost
/// value on the stack is a table.
@@ -996,11 +987,11 @@ Array nlua_pop_Array(lua_State *lstate, Arena *arena, Error *err)
/// @param lstate Lua interpreter state.
/// @param[in] table_props nlua_traverse_table() output.
/// @param[out] err Location where error will be saved.
-static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, const LuaTableProps table_props,
- bool ref, Arena *arena, Error *err)
+static Dict nlua_pop_Dict_unchecked(lua_State *lstate, const LuaTableProps table_props, bool ref,
+ Arena *arena, Error *err)
FUNC_ATTR_NONNULL_ARG(1, 5) FUNC_ATTR_WARN_UNUSED_RESULT
{
- Dictionary ret = arena_dict(arena, table_props.string_keys_num);
+ Dict ret = arena_dict(arena, table_props.string_keys_num);
if (table_props.string_keys_num == 0) {
lua_pop(lstate, 1);
@@ -1029,11 +1020,11 @@ static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, const LuaTabl
if (ERROR_SET(err)) {
if (!arena) {
- api_free_dictionary(ret);
+ api_free_dict(ret);
}
lua_pop(lstate, 2);
// stack:
- return (Dictionary) { .size = 0, .items = NULL };
+ return (Dict) { .size = 0, .items = NULL };
}
i++;
} else {
@@ -1046,20 +1037,20 @@ static Dictionary nlua_pop_Dictionary_unchecked(lua_State *lstate, const LuaTabl
return ret;
}
-/// Convert lua table to dictionary
+/// Convert Lua table to dictionary
///
/// Always pops one value from the stack.
-Dictionary nlua_pop_Dictionary(lua_State *lstate, bool ref, Arena *arena, Error *err)
+Dict nlua_pop_Dict(lua_State *lstate, bool ref, Arena *arena, Error *err)
FUNC_ATTR_NONNULL_ARG(1, 4) FUNC_ATTR_WARN_UNUSED_RESULT
{
const LuaTableProps table_props = nlua_check_type(lstate, err,
- kObjectTypeDictionary);
- if (table_props.type != kObjectTypeDictionary) {
+ kObjectTypeDict);
+ if (table_props.type != kObjectTypeDict) {
lua_pop(lstate, 1);
- return (Dictionary) { .size = 0, .items = NULL };
+ return (Dict) { .size = 0, .items = NULL };
}
- return nlua_pop_Dictionary_unchecked(lstate, table_props, ref, arena, err);
+ return nlua_pop_Dict_unchecked(lstate, table_props, ref, arena, err);
}
/// Helper structure for nlua_pop_Object
@@ -1068,7 +1059,7 @@ typedef struct {
bool container; ///< True if tv is a container.
} ObjPopStackItem;
-/// Convert lua table to object
+/// Convert Lua table to object
///
/// Always pops one value from the stack.
Object nlua_pop_Object(lua_State *const lstate, bool ref, Arena *arena, Error *const err)
@@ -1086,9 +1077,9 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Arena *arena, Error *c
api_set_error(err, kErrorTypeException, "Lua failed to grow stack");
break;
}
- if (cur.obj->type == kObjectTypeDictionary) {
+ if (cur.obj->type == kObjectTypeDict) {
// stack: …, dict, key
- if (cur.obj->data.dictionary.size == cur.obj->data.dictionary.capacity) {
+ if (cur.obj->data.dict.size == cur.obj->data.dict.capacity) {
lua_pop(lstate, 2);
continue;
}
@@ -1106,10 +1097,10 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Arena *arena, Error *c
// stack: …, dict, new key, val
size_t len;
const char *s = lua_tolstring(lstate, -2, &len);
- const size_t idx = cur.obj->data.dictionary.size++;
- cur.obj->data.dictionary.items[idx].key = CBUF_TO_ARENA_STR(arena, s, len);
+ const size_t idx = cur.obj->data.dict.size++;
+ cur.obj->data.dict.items[idx].key = CBUF_TO_ARENA_STR(arena, s, len);
kvi_push(stack, cur);
- cur = (ObjPopStackItem){ .obj = &cur.obj->data.dictionary.items[idx].value };
+ cur = (ObjPopStackItem){ .obj = &cur.obj->data.dict.items[idx].value };
} else {
// stack: …, dict
lua_pop(lstate, 1);
@@ -1160,14 +1151,16 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Arena *arena, Error *c
if (table_props.maxidx != 0) {
cur.obj->data.array = arena_array(arena, table_props.maxidx);
cur.container = true;
+ assert(kv_size(stack) < SIZE_MAX);
kvi_push(stack, cur);
}
break;
- case kObjectTypeDictionary:
- *cur.obj = DICTIONARY_OBJ(((Dictionary)ARRAY_DICT_INIT));
+ case kObjectTypeDict:
+ *cur.obj = DICT_OBJ(((Dict)ARRAY_DICT_INIT));
if (table_props.string_keys_num != 0) {
- cur.obj->data.dictionary = arena_dict(arena, table_props.string_keys_num);
+ cur.obj->data.dict = arena_dict(arena, table_props.string_keys_num);
cur.container = true;
+ assert(kv_size(stack) < SIZE_MAX);
kvi_push(stack, cur);
lua_pushnil(lstate);
}
@@ -1200,16 +1193,14 @@ Object nlua_pop_Object(lua_State *const lstate, bool ref, Arena *arena, Error *c
if (is_nil) {
*cur.obj = NIL;
} else {
- api_set_error(err, kErrorTypeValidation,
- "Cannot convert userdata");
+ api_set_error(err, kErrorTypeValidation, "Cannot convert userdata");
}
break;
}
default:
type_error:
- api_set_error(err, kErrorTypeValidation,
- "Cannot convert given lua type");
+ api_set_error(err, kErrorTypeValidation, "Cannot convert given Lua type");
break;
}
if (!cur.container) {
@@ -1287,16 +1278,16 @@ void nlua_init_types(lua_State *const lstate)
lua_rawset(lstate, -3);
LUA_PUSH_STATIC_STRING(lstate, "dictionary");
- lua_pushnumber(lstate, (lua_Number)kObjectTypeDictionary);
+ lua_pushnumber(lstate, (lua_Number)kObjectTypeDict);
lua_rawset(lstate, -3);
- lua_pushnumber(lstate, (lua_Number)kObjectTypeDictionary);
+ lua_pushnumber(lstate, (lua_Number)kObjectTypeDict);
LUA_PUSH_STATIC_STRING(lstate, "dictionary");
lua_rawset(lstate, -3);
lua_rawset(lstate, -3);
}
-// lua specific variant of api_dict_to_keydict
+// Lua specific variant of api_dict_to_keydict
void nlua_pop_keydict(lua_State *L, void *retval, FieldHashfn hashy, char **err_opt, Arena *arena,
Error *err)
{
@@ -1346,8 +1337,8 @@ void nlua_pop_keydict(lua_State *L, void *retval, FieldHashfn hashy, char **err_
*(handle_T *)mem = nlua_pop_handle(L, arena, err);
} else if (field->type == kObjectTypeArray) {
*(Array *)mem = nlua_pop_Array(L, arena, err);
- } else if (field->type == kObjectTypeDictionary) {
- *(Dictionary *)mem = nlua_pop_Dictionary(L, false, arena, err);
+ } else if (field->type == kObjectTypeDict) {
+ *(Dict *)mem = nlua_pop_Dict(L, false, arena, err);
} else if (field->type == kObjectTypeLuaRef) {
*(LuaRef *)mem = nlua_pop_LuaRef(L, arena, err);
} else {
@@ -1396,8 +1387,8 @@ void nlua_push_keydict(lua_State *L, void *value, KeySetLink *table)
nlua_push_String(L, *(String *)mem, 0);
} else if (field->type == kObjectTypeArray) {
nlua_push_Array(L, *(Array *)mem, 0);
- } else if (field->type == kObjectTypeDictionary) {
- nlua_push_Dictionary(L, *(Dictionary *)mem, 0);
+ } else if (field->type == kObjectTypeDict) {
+ nlua_push_Dict(L, *(Dict *)mem, 0);
} else if (field->type == kObjectTypeLuaRef) {
nlua_pushref(L, *(LuaRef *)mem);
} else {
diff --git a/src/nvim/lua/executor.c b/src/nvim/lua/executor.c
index a76b8213e5..d4940f3add 100644
--- a/src/nvim/lua/executor.c
+++ b/src/nvim/lua/executor.c
@@ -22,6 +22,7 @@
#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
@@ -923,6 +924,7 @@ void nlua_free_all_mem(void)
lua_State *lstate = global_lstate;
nlua_unref_global(lstate, require_ref);
nlua_common_free_all_mem(lstate);
+ tslua_free();
}
static void nlua_common_free_all_mem(lua_State *lstate)
@@ -1901,8 +1903,13 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_pushcfunction(lstate, tslua_push_querycursor);
lua_setfield(lstate, -2, "_create_ts_querycursor");
- lua_pushcfunction(lstate, tslua_add_language);
- lua_setfield(lstate, -2, "_ts_add_language");
+ lua_pushcfunction(lstate, tslua_add_language_from_object);
+ lua_setfield(lstate, -2, "_ts_add_language_from_object");
+
+#ifdef HAVE_WASMTIME
+ lua_pushcfunction(lstate, tslua_add_language_from_wasm);
+ lua_setfield(lstate, -2, "_ts_add_language_from_wasm");
+#endif
lua_pushcfunction(lstate, tslua_has_language);
lua_setfield(lstate, -2, "_ts_has_language");
@@ -1923,10 +1930,14 @@ static void nlua_add_treesitter(lua_State *const lstate) FUNC_ATTR_NONNULL_ALL
lua_setfield(lstate, -2, "_ts_get_minimum_language_version");
}
-int nlua_expand_pat(expand_T *xp, char *pat, int *num_results, char ***results)
+static garray_T expand_result_array = GA_EMPTY_INIT_VALUE;
+
+/// Finds matches for Lua cmdline completion and advances xp->xp_pattern after prefix.
+/// This should be called before xp->xp_pattern is first used.
+void nlua_expand_pat(expand_T *xp)
{
lua_State *const lstate = global_lstate;
- int ret = OK;
+ int status = FAIL;
// [ vim ]
lua_getglobal(lstate, "vim");
@@ -1935,60 +1946,59 @@ int nlua_expand_pat(expand_T *xp, char *pat, int *num_results, char ***results)
lua_getfield(lstate, -1, "_expand_pat");
luaL_checktype(lstate, -1, LUA_TFUNCTION);
- // [ vim, vim._expand_pat, buf ]
- lua_pushlstring(lstate, pat, strlen(pat));
+ // [ vim, vim._expand_pat, pat ]
+ const char *pat = xp->xp_pattern;
+ assert(xp->xp_line + xp->xp_col >= pat);
+ ptrdiff_t patlen = xp->xp_line + xp->xp_col - pat;
+ lua_pushlstring(lstate, pat, (size_t)patlen);
if (nlua_pcall(lstate, 1, 2) != 0) {
- nlua_error(lstate,
- _("Error executing vim._expand_pat: %.*s"));
- return FAIL;
+ nlua_error(lstate, _("Error executing vim._expand_pat: %.*s"));
+ return;
}
Error err = ERROR_INIT;
- *num_results = 0;
- *results = NULL;
-
Arena arena = ARENA_EMPTY;
- int prefix_len = (int)nlua_pop_Integer(lstate, &arena, &err);
- if (ERROR_SET(&err)) {
- ret = FAIL;
+ ptrdiff_t prefix_len = nlua_pop_Integer(lstate, &arena, &err);
+ if (ERROR_SET(&err) || prefix_len > patlen) {
goto cleanup;
}
Array completions = nlua_pop_Array(lstate, &arena, &err);
if (ERROR_SET(&err)) {
- ret = FAIL;
goto cleanup_array;
}
- garray_T result_array;
- ga_init(&result_array, (int)sizeof(char *), 80);
+ ga_clear(&expand_result_array);
+ ga_init(&expand_result_array, (int)sizeof(char *), 80);
+
for (size_t i = 0; i < completions.size; i++) {
Object v = completions.items[i];
-
if (v.type != kObjectTypeString) {
- ret = FAIL;
goto cleanup_array;
}
-
- GA_APPEND(char *, &result_array, string_to_cstr(v.data.string));
+ GA_APPEND(char *, &expand_result_array, string_to_cstr(v.data.string));
}
xp->xp_pattern += prefix_len;
- *results = result_array.ga_data;
- *num_results = result_array.ga_len;
+ status = OK;
cleanup_array:
arena_mem_free(arena_finish(&arena));
cleanup:
-
- if (ret == FAIL) {
- ga_clear(&result_array);
+ if (status == FAIL) {
+ ga_clear(&expand_result_array);
}
+}
- return ret;
+int nlua_expand_get_matches(int *num_results, char ***results)
+{
+ *results = expand_result_array.ga_data;
+ *num_results = expand_result_array.ga_len;
+ expand_result_array = (garray_T)GA_EMPTY_INIT_VALUE;
+ return *num_results > 0;
}
static int nlua_is_thread(lua_State *lstate)
@@ -2053,10 +2063,11 @@ char *nlua_register_table_as_callable(const typval_T *const arg)
return name;
}
-void nlua_execute_on_key(int c, char *typed_buf, size_t typed_len)
+void nlua_execute_on_key(int c, char *typed_buf)
{
char buf[MB_MAXBYTES * 3 + 4];
size_t buf_len = special_to_buf(c, mod_mask, false, buf);
+ vim_unescape_ks(typed_buf);
lua_State *const lstate = global_lstate;
@@ -2075,7 +2086,7 @@ void nlua_execute_on_key(int c, char *typed_buf, size_t typed_len)
lua_pushlstring(lstate, buf, buf_len);
// [ vim, vim._on_key, buf, typed_buf ]
- lua_pushlstring(lstate, typed_buf, typed_len);
+ lua_pushstring(lstate, typed_buf);
int save_got_int = got_int;
got_int = false; // avoid interrupts when the key typed is Ctrl-C
diff --git a/src/nvim/lua/secure.c b/src/nvim/lua/secure.c
index f62e0ace04..61277949c4 100644
--- a/src/nvim/lua/secure.c
+++ b/src/nvim/lua/secure.c
@@ -3,6 +3,7 @@
#include <string.h>
#include "nvim/charset.h"
+#include "nvim/errors.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
@@ -103,12 +104,12 @@ void ex_trust(exarg_T *eap)
action = "deny";
} else if (strcmp(arg1, "++remove") == 0) {
action = "remove";
- } else if (*arg1 != '\0') {
+ } else if (*arg1 != NUL) {
semsg(e_invarg2, arg1);
goto theend;
}
- if (path[0] == '\0') {
+ if (path[0] == NUL) {
path = NULL;
}
diff --git a/src/nvim/lua/spell.c b/src/nvim/lua/spell.c
index ba83239dc5..f4dacd7a55 100644
--- a/src/nvim/lua/spell.c
+++ b/src/nvim/lua/spell.c
@@ -7,6 +7,7 @@
#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
+#include "nvim/errors.h"
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
#include "nvim/highlight_defs.h"
diff --git a/src/nvim/lua/stdlib.c b/src/nvim/lua/stdlib.c
index 22ee0a1c98..ee0eabbebb 100644
--- a/src/nvim/lua/stdlib.c
+++ b/src/nvim/lua/stdlib.c
@@ -17,10 +17,13 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/ascii_defs.h"
+#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
+#include "nvim/eval/window.h"
+#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/fold.h"
#include "nvim/globals.h"
@@ -40,6 +43,7 @@
#include "nvim/runtime.h"
#include "nvim/strings.h"
#include "nvim/types_defs.h"
+#include "nvim/window.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "lua/stdlib.c.generated.h"
@@ -568,6 +572,99 @@ static int nlua_foldupdate(lua_State *lstate)
return 0;
}
+static int nlua_with(lua_State *L)
+{
+ int flags = 0;
+ buf_T *buf = NULL;
+ win_T *win = NULL;
+
+#define APPLY_FLAG(key, flag) \
+ if (strequal((key), k) && (v)) { \
+ flags |= (flag); \
+ }
+
+ luaL_argcheck(L, lua_istable(L, 1), 1, "table expected");
+ lua_pushnil(L); // [dict, ..., nil]
+ while (lua_next(L, 1)) {
+ // [dict, ..., key, value]
+ if (lua_type(L, -2) == LUA_TSTRING) {
+ const char *k = lua_tostring(L, -2);
+ bool v = lua_toboolean(L, -1);
+ if (strequal("buf", k)) { \
+ buf = handle_get_buffer((int)luaL_checkinteger(L, -1));
+ } else if (strequal("win", k)) { \
+ win = handle_get_window((int)luaL_checkinteger(L, -1));
+ } else {
+ APPLY_FLAG("sandbox", CMOD_SANDBOX);
+ APPLY_FLAG("silent", CMOD_SILENT);
+ APPLY_FLAG("emsg_silent", CMOD_ERRSILENT);
+ APPLY_FLAG("unsilent", CMOD_UNSILENT);
+ APPLY_FLAG("noautocmd", CMOD_NOAUTOCMD);
+ APPLY_FLAG("hide", CMOD_HIDE);
+ APPLY_FLAG("keepalt", CMOD_KEEPALT);
+ APPLY_FLAG("keepmarks", CMOD_KEEPMARKS);
+ APPLY_FLAG("keepjumps", CMOD_KEEPJUMPS);
+ APPLY_FLAG("lockmarks", CMOD_LOCKMARKS);
+ APPLY_FLAG("keeppatterns", CMOD_KEEPPATTERNS);
+ }
+ }
+ // pop the value; lua_next will pop the key.
+ lua_pop(L, 1); // [dict, ..., key]
+ }
+ int status = 0;
+ int rets = 0;
+
+ cmdmod_T save_cmdmod = cmdmod;
+ cmdmod.cmod_flags = flags;
+ apply_cmdmod(&cmdmod);
+
+ if (buf || win) {
+ try_start();
+ }
+
+ aco_save_T aco;
+ win_execute_T win_execute_args;
+ Error err = ERROR_INIT;
+
+ if (win) {
+ tabpage_T *tabpage = win_find_tabpage(win);
+ if (!win_execute_before(&win_execute_args, win, tabpage)) {
+ goto end;
+ }
+ } else if (buf) {
+ aucmd_prepbuf(&aco, buf);
+ }
+
+ int s = lua_gettop(L);
+ lua_pushvalue(L, 2);
+ status = lua_pcall(L, 0, LUA_MULTRET, 0);
+ rets = lua_gettop(L) - s;
+
+ if (win) {
+ win_execute_after(&win_execute_args);
+ } else if (buf) {
+ aucmd_restbuf(&aco);
+ }
+
+end:
+ if (buf || win) {
+ try_end(&err);
+ }
+
+ undo_cmdmod(&cmdmod);
+ cmdmod = save_cmdmod;
+
+ if (status) {
+ return lua_error(L);
+ } else if (ERROR_SET(&err)) {
+ nlua_push_errstr(L, "%s", err.msg);
+ api_clear_error(&err);
+ return lua_error(L);
+ }
+
+ return rets;
+}
+
// Access to internal functions. For use in runtime/
static void nlua_state_add_internal(lua_State *const lstate)
{
@@ -582,6 +679,9 @@ static void nlua_state_add_internal(lua_State *const lstate)
// _updatefolds
lua_pushcfunction(lstate, &nlua_foldupdate);
lua_setfield(lstate, -2, "_foldupdate");
+
+ lua_pushcfunction(lstate, &nlua_with);
+ lua_setfield(lstate, -2, "_with_c");
}
void nlua_state_add_stdlib(lua_State *const lstate, bool is_thread)
diff --git a/src/nvim/lua/treesitter.c b/src/nvim/lua/treesitter.c
index e87cf756a8..ab97704dfe 100644
--- a/src/nvim/lua/treesitter.c
+++ b/src/nvim/lua/treesitter.c
@@ -15,6 +15,10 @@
#include <tree_sitter/api.h>
#include <uv.h>
+#ifdef HAVE_WASMTIME
+# include <wasm.h>
+#endif
+
#include "klib/kvec.h"
#include "nvim/api/private/helpers.h"
#include "nvim/buffer_defs.h"
@@ -24,6 +28,7 @@
#include "nvim/map_defs.h"
#include "nvim/memline.h"
#include "nvim/memory.h"
+#include "nvim/os/fs.h"
#include "nvim/pos_defs.h"
#include "nvim/strings.h"
#include "nvim/types_defs.h"
@@ -34,7 +39,6 @@
#define TS_META_QUERY "treesitter_query"
#define TS_META_QUERYCURSOR "treesitter_querycursor"
#define TS_META_QUERYMATCH "treesitter_querymatch"
-#define TS_META_TREECURSOR "treesitter_treecursor"
typedef struct {
LuaRef cb;
@@ -53,6 +57,11 @@ typedef struct {
static PMap(cstr_t) langs = MAP_INIT;
+#ifdef HAVE_WASMTIME
+static wasm_engine_t *wasmengine;
+static TSWasmStore *ts_wasmstore;
+#endif
+
// TSLanguage
int tslua_has_language(lua_State *L)
@@ -62,8 +71,59 @@ int tslua_has_language(lua_State *L)
return 1;
}
-static TSLanguage *load_language(lua_State *L, const char *path, const char *lang_name,
- const char *symbol)
+#ifdef HAVE_WASMTIME
+static char *read_file(const char *path, size_t *len)
+ FUNC_ATTR_MALLOC
+{
+ FILE *file = os_fopen(path, "r");
+ if (file == NULL) {
+ return NULL;
+ }
+ fseek(file, 0L, SEEK_END);
+ *len = (size_t)ftell(file);
+ fseek(file, 0L, SEEK_SET);
+ char *data = xmalloc(*len);
+ if (fread(data, *len, 1, file) != 1) {
+ xfree(data);
+ fclose(file);
+ return NULL;
+ }
+ fclose(file);
+ return data;
+}
+
+static const char *wasmerr_to_str(TSWasmErrorKind werr)
+{
+ switch (werr) {
+ case TSWasmErrorKindParse:
+ return "PARSE";
+ case TSWasmErrorKindCompile:
+ return "COMPILE";
+ case TSWasmErrorKindInstantiate:
+ return "INSTANTIATE";
+ case TSWasmErrorKindAllocate:
+ return "ALLOCATE";
+ default:
+ return "UNKNOWN";
+ }
+}
+#endif
+
+int tslua_add_language_from_wasm(lua_State *L)
+{
+ return add_language(L, true);
+}
+
+// Creates the language into the internal language map.
+//
+// Returns true if the language is correctly loaded in the language map
+int tslua_add_language_from_object(lua_State *L)
+{
+ return add_language(L, false);
+}
+
+static const TSLanguage *load_language_from_object(lua_State *L, const char *path,
+ const char *lang_name, const char *symbol)
{
uv_lib_t lib;
if (uv_dlopen(path, &lib)) {
@@ -91,16 +151,59 @@ static TSLanguage *load_language(lua_State *L, const char *path, const char *lan
return lang;
}
-// Creates the language into the internal language map.
-//
-// Returns true if the language is correctly loaded in the language map
-int tslua_add_language(lua_State *L)
+static const TSLanguage *load_language_from_wasm(lua_State *L, const char *path,
+ const char *lang_name)
+{
+#ifndef HAVE_WASMTIME
+ luaL_error(L, "Not supported");
+ return NULL;
+#else
+ if (wasmengine == NULL) {
+ wasmengine = wasm_engine_new();
+ }
+ assert(wasmengine != NULL);
+
+ TSWasmError werr = { 0 };
+ if (ts_wasmstore == NULL) {
+ ts_wasmstore = ts_wasm_store_new(wasmengine, &werr);
+ }
+
+ if (werr.kind > 0) {
+ luaL_error(L, "Error creating wasm store: (%s) %s", wasmerr_to_str(werr.kind), werr.message);
+ }
+
+ size_t file_size = 0;
+ char *data = read_file(path, &file_size);
+
+ if (data == NULL) {
+ luaL_error(L, "Unable to read file", path);
+ }
+
+ const TSLanguage *lang = ts_wasm_store_load_language(ts_wasmstore, lang_name, data,
+ (uint32_t)file_size, &werr);
+
+ xfree(data);
+
+ if (werr.kind > 0) {
+ luaL_error(L, "Failed to load WASM parser %s: (%s) %s", path, wasmerr_to_str(werr.kind),
+ werr.message);
+ }
+
+ if (lang == NULL) {
+ luaL_error(L, "Failed to load parser %s: internal error", path);
+ }
+
+ return lang;
+#endif
+}
+
+static int add_language(lua_State *L, bool is_wasm)
{
const char *path = luaL_checkstring(L, 1);
const char *lang_name = luaL_checkstring(L, 2);
const char *symbol_name = lang_name;
- if (lua_gettop(L) >= 3 && !lua_isnil(L, 3)) {
+ if (!is_wasm && lua_gettop(L) >= 3 && !lua_isnil(L, 3)) {
symbol_name = luaL_checkstring(L, 3);
}
@@ -109,7 +212,9 @@ int tslua_add_language(lua_State *L)
return 1;
}
- TSLanguage *lang = load_language(L, path, lang_name, symbol_name);
+ const TSLanguage *lang = is_wasm
+ ? load_language_from_wasm(L, path, lang_name)
+ : load_language_from_object(L, path, lang_name, symbol_name);
uint32_t lang_version = ts_language_version(lang);
if (lang_version < TREE_SITTER_MIN_COMPATIBLE_LANGUAGE_VERSION
@@ -121,7 +226,7 @@ int tslua_add_language(lua_State *L)
TREE_SITTER_LANGUAGE_VERSION, lang_version);
}
- pmap_put(cstr_t)(&langs, xstrdup(lang_name), lang);
+ pmap_put(cstr_t)(&langs, xstrdup(lang_name), (TSLanguage *)lang);
lua_pushboolean(L, true);
return 1;
@@ -186,6 +291,9 @@ int tslua_inspect_lang(lua_State *L)
lua_setfield(L, -2, "fields"); // [retval]
+ lua_pushboolean(L, ts_language_is_wasm(lang));
+ lua_setfield(L, -2, "_wasm");
+
lua_pushinteger(L, ts_language_version(lang)); // [retval, version]
lua_setfield(L, -2, "_abi_version");
@@ -215,6 +323,13 @@ int tslua_push_parser(lua_State *L)
TSParser **parser = lua_newuserdata(L, sizeof(TSParser *));
*parser = ts_parser_new();
+#ifdef HAVE_WASMTIME
+ if (ts_language_is_wasm(lang)) {
+ assert(wasmengine != NULL);
+ ts_parser_set_wasm_store(*parser, ts_wasmstore);
+ }
+#endif
+
if (!ts_parser_set_language(*parser, lang)) {
ts_parser_delete(*parser);
const char *lang_name = luaL_checkstring(L, 1);
@@ -279,7 +394,7 @@ static const char *input_cb(void *payload, uint32_t byte_index, TSPoint position
memcpy(buf, line + position.column, tocopy);
// Translate embedded \n to NUL
- memchrsub(buf, '\n', '\0', tocopy);
+ memchrsub(buf, '\n', NUL, 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
@@ -686,20 +801,6 @@ static int tree_root(lua_State *L)
return 1;
}
-// TSTreeCursor
-
-static struct luaL_Reg treecursor_meta[] = {
- { "__gc", treecursor_gc },
- { NULL, NULL }
-};
-
-static int treecursor_gc(lua_State *L)
-{
- TSTreeCursor *cursor = luaL_checkudata(L, 1, TS_META_TREECURSOR);
- ts_tree_cursor_delete(cursor);
- return 0;
-}
-
// TSNode
static struct luaL_Reg node_meta[] = {
{ "__tostring", node_tostring },
@@ -890,23 +991,14 @@ static int node_field(lua_State *L)
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]
- size_t curr_index = 0;
-
- if (ts_tree_cursor_goto_first_child(&cursor)) {
- do {
- const char *current_field = ts_tree_cursor_current_field_name(&cursor);
- if (current_field != NULL && !strcmp(field_name, current_field)) {
- push_node(L, ts_tree_cursor_current_node(&cursor), 1); // [table, node]
- lua_rawseti(L, -2, (int)++curr_index);
- }
- } while (ts_tree_cursor_goto_next_sibling(&cursor));
+ TSNode field = ts_node_child_by_field_name(node, field_name, (uint32_t)name_len);
+ if (!ts_node_is_null(field)) {
+ push_node(L, field, 1); // [table, node]
+ lua_rawseti(L, -2, 1);
}
- ts_tree_cursor_delete(&cursor);
return 1;
}
@@ -1002,45 +1094,35 @@ static int node_named_descendant_for_range(lua_State *L)
static int node_next_child(lua_State *L)
{
- TSTreeCursor *cursor = luaL_checkudata(L, lua_upvalueindex(1), TS_META_TREECURSOR);
+ uint32_t *child_index = lua_touserdata(L, lua_upvalueindex(1));
TSNode source = node_check(L, lua_upvalueindex(2));
- // First call should return first child
- if (ts_node_eq(source, ts_tree_cursor_current_node(cursor))) {
- if (ts_tree_cursor_goto_first_child(cursor)) {
- goto push;
- } else {
- return 0;
- }
- }
-
- if (!ts_tree_cursor_goto_next_sibling(cursor)) {
+ if (*child_index >= ts_node_child_count(source)) {
return 0;
}
-push:
- push_node(L, ts_tree_cursor_current_node(cursor), lua_upvalueindex(2)); // [node]
-
- const char *field = ts_tree_cursor_current_field_name(cursor);
+ TSNode child = ts_node_child(source, *child_index);
+ push_node(L, child, lua_upvalueindex(2));
+ const char *field = ts_node_field_name_for_child(source, *child_index);
if (field != NULL) {
- lua_pushstring(L, ts_tree_cursor_current_field_name(cursor));
+ lua_pushstring(L, field);
} else {
lua_pushnil(L);
} // [node, field_name_or_nil]
+
+ (*child_index)++;
+
return 2;
}
static int node_iter_children(lua_State *L)
{
- TSNode node = node_check(L, 1);
-
- TSTreeCursor *ud = lua_newuserdata(L, sizeof(TSTreeCursor)); // [udata]
- *ud = ts_tree_cursor_new(node);
+ node_check(L, 1);
+ uint32_t *child_index = lua_newuserdata(L, sizeof(uint32_t)); // [source_node,..., udata]
+ *child_index = 0;
- lua_getfield(L, LUA_REGISTRYINDEX, TS_META_TREECURSOR); // [udata, mt]
- lua_setmetatable(L, -2); // [udata]
- lua_pushvalue(L, 1); // [udata, source_node]
+ lua_pushvalue(L, 1); // [source_node, ..., udata, source_node]
lua_pushcclosure(L, node_next_child, 2);
return 1;
@@ -1132,22 +1214,19 @@ static int node_prev_named_sibling(lua_State *L)
static int node_named_children(lua_State *L)
{
TSNode source = node_check(L, 1);
- TSTreeCursor cursor = ts_tree_cursor_new(source);
lua_newtable(L);
int curr_index = 0;
- if (ts_tree_cursor_goto_first_child(&cursor)) {
- do {
- TSNode node = ts_tree_cursor_current_node(&cursor);
- if (ts_node_is_named(node)) {
- push_node(L, node, 1);
- lua_rawseti(L, -2, ++curr_index);
- }
- } while (ts_tree_cursor_goto_next_sibling(&cursor));
+ uint32_t n = ts_node_child_count(source);
+ for (uint32_t i = 0; i < n; i++) {
+ TSNode child = ts_node_child(source, i);
+ if (ts_node_is_named(child)) {
+ push_node(L, child, 1);
+ lua_rawseti(L, -2, ++curr_index);
+ }
}
- ts_tree_cursor_delete(&cursor);
return 1;
}
@@ -1557,7 +1636,18 @@ void tslua_init(lua_State *L)
build_meta(L, TS_META_QUERY, query_meta);
build_meta(L, TS_META_QUERYCURSOR, querycursor_meta);
build_meta(L, TS_META_QUERYMATCH, querymatch_meta);
- build_meta(L, TS_META_TREECURSOR, treecursor_meta);
ts_set_allocator(xmalloc, xcalloc, xrealloc, xfree);
}
+
+void tslua_free(void)
+{
+#ifdef HAVE_WASMTIME
+ if (wasmengine != NULL) {
+ wasm_engine_delete(wasmengine);
+ }
+ if (ts_wasmstore != NULL) {
+ ts_wasm_store_delete(ts_wasmstore);
+ }
+#endif
+}
diff --git a/src/nvim/lua/xdiff.c b/src/nvim/lua/xdiff.c
index 035c171a14..b9f96abf73 100644
--- a/src/nvim/lua/xdiff.c
+++ b/src/nvim/lua/xdiff.c
@@ -67,11 +67,11 @@ static void get_linematch_results(lua_State *lstate, mmfile_t *ma, mmfile_t *mb,
int count_a, int start_b, int count_b, bool iwhite)
{
// get the pointer to char of the start of the diff to pass it to linematch algorithm
- const char *diff_begin[2] = { ma->ptr, mb->ptr };
- int diff_length[2] = { count_a, count_b };
+ mmfile_t ma0 = fastforward_buf_to_lnum(*ma, (linenr_T)start_a + 1);
+ mmfile_t mb0 = fastforward_buf_to_lnum(*mb, (linenr_T)start_b + 1);
- fastforward_buf_to_lnum(&diff_begin[0], (linenr_T)start_a + 1);
- fastforward_buf_to_lnum(&diff_begin[1], (linenr_T)start_b + 1);
+ const mmfile_t *diff_begin[2] = { &ma0, &mb0 };
+ int diff_length[2] = { count_a, count_b };
int *decisions = NULL;
size_t decisions_length = linematch_nbuffers(diff_begin, diff_length, 2, &decisions, iwhite);
@@ -185,7 +185,12 @@ static mmfile_t get_string_arg(lua_State *lstate, int idx)
luaL_argerror(lstate, idx, "expected string");
}
mmfile_t mf;
- mf.ptr = (char *)lua_tolstring(lstate, idx, (size_t *)&mf.size);
+ size_t size;
+ mf.ptr = (char *)lua_tolstring(lstate, idx, &size);
+ if (size > INT_MAX) {
+ luaL_argerror(lstate, idx, "string too long");
+ }
+ mf.size = (int)size;
return mf;
}
diff --git a/src/nvim/main.c b/src/nvim/main.c
index 30b6b6e86b..dc102f6f6d 100644
--- a/src/nvim/main.c
+++ b/src/nvim/main.c
@@ -6,7 +6,6 @@
#endif
#include <assert.h>
#include <limits.h>
-#include <msgpack/pack.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
@@ -37,13 +36,14 @@
#include "nvim/diff.h"
#include "nvim/drawline.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
#include "nvim/event/stream.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_docmd.h"
@@ -154,7 +154,6 @@ void event_init(void)
loop_init(&main_loop, NULL);
resize_events = multiqueue_new_child(main_loop.events);
- input_init();
signal_init();
// mspgack-rpc initialization
channel_init();
@@ -175,7 +174,7 @@ bool event_teardown(void)
loop_poll_events(&main_loop, 0); // Drain thread_events, fast_events.
input_stop();
channel_teardown();
- process_teardown(&main_loop);
+ proc_teardown(&main_loop);
timer_teardown();
server_teardown();
signal_teardown();
@@ -267,7 +266,7 @@ int main(int argc, char **argv)
if (argc > 1 && STRICMP(argv[1], "-ll") == 0) {
if (argc == 2) {
- print_mainerr(err_arg_missing, argv[1]);
+ print_mainerr(err_arg_missing, argv[1], NULL);
exit(1);
}
nlua_run_script(argv, argc, 3);
@@ -333,12 +332,6 @@ int main(int argc, char **argv)
#endif
bool use_builtin_ui = (has_term && !headless_mode && !embedded_mode && !silent_mode);
- // don't bind the server yet, if we are using builtin ui.
- // This will be done when nvim server has been forked from the ui process
- if (!use_builtin_ui) {
- server_init(params.listen_addr);
- }
-
if (params.remote) {
remote_request(&params, params.remote, params.server_addr, argc, argv,
use_builtin_ui);
@@ -356,11 +349,16 @@ int main(int argc, char **argv)
ui_client_channel_id = rv;
}
+ // NORETURN: Start builtin UI client.
if (ui_client_channel_id) {
- time_finish();
ui_client_run(remote_ui); // NORETURN
}
assert(!ui_client_channel_id && !use_builtin_ui);
+ // Nvim server...
+
+ if (!server_init(params.listen_addr)) {
+ mainerr(IObuff, NULL, NULL);
+ }
TIME_MSG("expanding arguments");
@@ -545,10 +543,8 @@ int main(int argc, char **argv)
no_wait_return = true;
- //
// Create the requested number of windows and edit buffers in them.
// Also does recovery if "recoverymode" set.
- //
create_windows(&params);
TIME_MSG("opening buffers");
@@ -971,8 +967,8 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr,
os_exit(2);
}
- if (o.type == kObjectTypeDictionary) {
- rvobj.data.dictionary = o.data.dictionary;
+ if (o.type == kObjectTypeDict) {
+ rvobj.data.dict = o.data.dict;
} else {
fprintf(stderr, "vim._cs_remote returned unexpected value\n");
os_exit(2);
@@ -981,32 +977,32 @@ static void remote_request(mparm_T *params, int remote_args, char *server_addr,
TriState should_exit = kNone;
TriState tabbed = kNone;
- for (size_t i = 0; i < rvobj.data.dictionary.size; i++) {
- if (strequal(rvobj.data.dictionary.items[i].key.data, "errmsg")) {
- if (rvobj.data.dictionary.items[i].value.type != kObjectTypeString) {
+ for (size_t i = 0; i < rvobj.data.dict.size; i++) {
+ if (strequal(rvobj.data.dict.items[i].key.data, "errmsg")) {
+ if (rvobj.data.dict.items[i].value.type != kObjectTypeString) {
fprintf(stderr, "vim._cs_remote returned an unexpected type for 'errmsg'\n");
os_exit(2);
}
- fprintf(stderr, "%s\n", rvobj.data.dictionary.items[i].value.data.string.data);
+ fprintf(stderr, "%s\n", rvobj.data.dict.items[i].value.data.string.data);
os_exit(2);
- } else if (strequal(rvobj.data.dictionary.items[i].key.data, "result")) {
- if (rvobj.data.dictionary.items[i].value.type != kObjectTypeString) {
+ } else if (strequal(rvobj.data.dict.items[i].key.data, "result")) {
+ if (rvobj.data.dict.items[i].value.type != kObjectTypeString) {
fprintf(stderr, "vim._cs_remote returned an unexpected type for 'result'\n");
os_exit(2);
}
- printf("%s", rvobj.data.dictionary.items[i].value.data.string.data);
- } else if (strequal(rvobj.data.dictionary.items[i].key.data, "tabbed")) {
- if (rvobj.data.dictionary.items[i].value.type != kObjectTypeBoolean) {
+ printf("%s", rvobj.data.dict.items[i].value.data.string.data);
+ } else if (strequal(rvobj.data.dict.items[i].key.data, "tabbed")) {
+ if (rvobj.data.dict.items[i].value.type != kObjectTypeBoolean) {
fprintf(stderr, "vim._cs_remote returned an unexpected type for 'tabbed'\n");
os_exit(2);
}
- tabbed = rvobj.data.dictionary.items[i].value.data.boolean ? kTrue : kFalse;
- } else if (strequal(rvobj.data.dictionary.items[i].key.data, "should_exit")) {
- if (rvobj.data.dictionary.items[i].value.type != kObjectTypeBoolean) {
+ tabbed = rvobj.data.dict.items[i].value.data.boolean ? kTrue : kFalse;
+ } else if (strequal(rvobj.data.dict.items[i].key.data, "should_exit")) {
+ if (rvobj.data.dict.items[i].value.type != kObjectTypeBoolean) {
fprintf(stderr, "vim._cs_remote returned an unexpected type for 'should_exit'\n");
os_exit(2);
}
- should_exit = rvobj.data.dictionary.items[i].value.data.boolean ? kTrue : kFalse;
+ should_exit = rvobj.data.dict.items[i].value.data.boolean ? kTrue : kFalse;
}
}
if (should_exit == kNone || tabbed == kNone) {
@@ -1054,7 +1050,7 @@ static void command_line_scan(mparm_T *parmp)
// "+" or "+{number}" or "+/{pat}" or "+{command}" argument.
if (argv[0][0] == '+' && !had_minmin) {
if (parmp->n_commands >= MAX_ARG_CMDS) {
- mainerr(err_extra_cmd, NULL);
+ mainerr(err_extra_cmd, NULL, NULL);
}
argv_idx = -1; // skip to next argument
if (argv[0][1] == NUL) {
@@ -1075,7 +1071,7 @@ static void command_line_scan(mparm_T *parmp)
parmp->no_swap_file = true;
} else {
if (parmp->edit_type > EDIT_STDIN) {
- mainerr(err_too_many_args, argv[0]);
+ mainerr(err_too_many_args, argv[0], NULL);
}
parmp->had_stdin_file = true;
parmp->edit_type = EDIT_STDIN;
@@ -1100,23 +1096,13 @@ static void command_line_scan(mparm_T *parmp)
// set stdout to binary to avoid crlf in --api-info output
_setmode(STDOUT_FILENO, _O_BINARY);
#endif
- FileDescriptor fp;
- const int fof_ret = file_open_fd(&fp, STDOUT_FILENO,
- kFileWriteOnly);
- if (fof_ret != 0) {
- semsg(_("E5421: Failed to open stdin: %s"), os_strerror(fof_ret));
- }
String data = api_metadata_raw();
- const ptrdiff_t written_bytes = file_write(&fp, data.data, data.size);
+ const ptrdiff_t written_bytes = os_write(STDOUT_FILENO, data.data, data.size, false);
if (written_bytes < 0) {
- msgpack_file_write_error((int)written_bytes);
+ semsg(_("E5420: Failed to write to file: %s"), os_strerror((int)written_bytes));
}
- const int ff_ret = file_flush(&fp);
- if (ff_ret < 0) {
- msgpack_file_write_error(ff_ret);
- }
os_exit(0);
} else if (STRICMP(argv[0] + argv_idx, "headless") == 0) {
headless_mode = true;
@@ -1148,7 +1134,7 @@ static void command_line_scan(mparm_T *parmp)
nlua_disable_preload = true;
} else {
if (argv[0][argv_idx]) {
- mainerr(err_opt_unknown, argv[0]);
+ mainerr(err_opt_unknown, argv[0], NULL);
}
had_minmin = true;
}
@@ -1222,7 +1208,7 @@ static void command_line_scan(mparm_T *parmp)
break;
case 'q': // "-q" QuickFix mode
if (parmp->edit_type != EDIT_NONE) {
- mainerr(err_too_many_args, argv[0]);
+ mainerr(err_too_many_args, argv[0], NULL);
}
parmp->edit_type = EDIT_QF;
if (argv[0][argv_idx]) { // "-q{errorfile}"
@@ -1251,7 +1237,7 @@ static void command_line_scan(mparm_T *parmp)
break;
case 't': // "-t {tag}" or "-t{tag}" jump to tag
if (parmp->edit_type != EDIT_NONE) {
- mainerr(err_too_many_args, argv[0]);
+ mainerr(err_too_many_args, argv[0], NULL);
}
parmp->edit_type = EDIT_TAG;
if (argv[0][argv_idx]) { // "-t{tag}"
@@ -1285,7 +1271,7 @@ static void command_line_scan(mparm_T *parmp)
case 'c': // "-c{command}" or "-c {command}" exec command
if (argv[0][argv_idx] != NUL) {
if (parmp->n_commands >= MAX_ARG_CMDS) {
- mainerr(err_extra_cmd, NULL);
+ mainerr(err_extra_cmd, NULL, NULL);
}
parmp->commands[parmp->n_commands++] = argv[0] + argv_idx;
argv_idx = -1;
@@ -1302,19 +1288,19 @@ static void command_line_scan(mparm_T *parmp)
break;
default:
- mainerr(err_opt_unknown, argv[0]);
+ mainerr(err_opt_unknown, argv[0], NULL);
}
// Handle option arguments with argument.
if (want_argument) {
// Check for garbage immediately after the option letter.
if (argv[0][argv_idx] != NUL) {
- mainerr(err_opt_garbage, argv[0]);
+ mainerr(err_opt_garbage, argv[0], NULL);
}
argc--;
if (argc < 1 && c != 'S') { // -S has an optional argument
- mainerr(err_arg_missing, argv[0]);
+ mainerr(err_arg_missing, argv[0], NULL);
}
argv++;
argv_idx = -1;
@@ -1323,7 +1309,7 @@ static void command_line_scan(mparm_T *parmp)
case 'c': // "-c {command}" execute command
case 'S': // "-S {file}" execute Vim script
if (parmp->n_commands >= MAX_ARG_CMDS) {
- mainerr(err_extra_cmd, NULL);
+ mainerr(err_extra_cmd, NULL, NULL);
}
if (c == 'S') {
char *a;
@@ -1354,7 +1340,7 @@ static void command_line_scan(mparm_T *parmp)
if (strequal(argv[-1], "--cmd")) {
// "--cmd {command}" execute command
if (parmp->n_pre_commands >= MAX_ARG_CMDS) {
- mainerr(err_extra_cmd, NULL);
+ mainerr(err_extra_cmd, NULL, NULL);
}
parmp->pre_commands[parmp->n_pre_commands++] = argv[0];
} else if (strequal(argv[-1], "--listen")) {
@@ -1436,7 +1422,7 @@ scripterror:
// Check for only one type of editing.
if (parmp->edit_type > EDIT_STDIN) {
- mainerr(err_too_many_args, argv[0]);
+ mainerr(err_too_many_args, argv[0], NULL);
}
parmp->edit_type = EDIT_FILE;
@@ -1444,6 +1430,17 @@ scripterror:
ga_grow(&global_alist.al_ga, 1);
char *p = xstrdup(argv[0]);
+ // On Windows expand "~\" or "~/" prefix in file names to profile directory.
+#ifdef MSWIN
+ if (*p == '~' && (p[1] == '\\' || p[1] == '/')) {
+ size_t size = strlen(os_homedir()) + strlen(p);
+ char *tilde_expanded = xmalloc(size);
+ snprintf(tilde_expanded, size, "%s%s", os_homedir(), p + 1);
+ xfree(p);
+ p = tilde_expanded;
+ }
+#endif
+
if (parmp->diff_mode && os_isdir(p) && GARGCOUNT > 0
&& !os_isdir(alist_name(&GARGLIST[0]))) {
char *r = concat_fnames(p, path_tail(alist_name(&GARGLIST[0])), true);
@@ -1472,7 +1469,7 @@ scripterror:
}
if (embedded_mode && (silent_mode || parmp->luaf)) {
- mainerr(_("--embed conflicts with -es/-Es/-l"), NULL);
+ mainerr(_("--embed conflicts with -es/-Es/-l"), NULL, NULL);
}
// If there is a "+123" or "-c" command, set v:swapcommand to the first one.
@@ -1516,7 +1513,7 @@ static void init_startuptime(mparm_T *paramp)
}
for (int i = 1; i < paramp->argc - 1; i++) {
if (STRICMP(paramp->argv[i], "--startuptime") == 0) {
- time_init(paramp->argv[i + 1], is_embed ? "Embedded" : "Primary/TUI");
+ time_init(paramp->argv[i + 1], is_embed ? "Embedded" : "Primary (or UI client)");
time_start("--- NVIM STARTING ---");
break;
}
@@ -2135,28 +2132,30 @@ static int execute_env(char *env)
return OK;
}
-/// Prints the following then exits:
-/// - An error message `errstr`
-/// - A string `str` if not null
+/// Prints a message of the form "{msg1}: {msg2}: {msg3}", then exits with code 1.
///
-/// @param errstr string containing an error message
-/// @param str string to append to the primary error message, or NULL
-static void mainerr(const char *errstr, const char *str)
+/// @param msg1 error message
+/// @param msg2 extra message, or NULL
+/// @param msg3 extra message, or NULL
+static void mainerr(const char *msg1, const char *msg2, const char *msg3)
FUNC_ATTR_NORETURN
{
- print_mainerr(errstr, str);
+ print_mainerr(msg1, msg2, msg3);
os_exit(1);
}
-static void print_mainerr(const char *errstr, const char *str)
+static void print_mainerr(const char *msg1, const char *msg2, const char *msg3)
{
char *prgname = path_tail(argv0);
signal_stop(); // kill us with CTRL-C here, if you like
- fprintf(stderr, "%s: %s", prgname, _(errstr));
- if (str != NULL) {
- fprintf(stderr, ": \"%s\"", str);
+ fprintf(stderr, "%s: %s", prgname, _(msg1));
+ if (msg2 != NULL) {
+ fprintf(stderr, ": \"%s\"", msg2);
+ }
+ if (msg3 != NULL) {
+ fprintf(stderr, ": \"%s\"", msg3);
}
fprintf(stderr, _("\nMore info with \""));
fprintf(stderr, "%s -h\"\n", prgname);
@@ -2207,7 +2206,7 @@ static void usage(void)
printf(_(" --headless Don't start a user interface\n"));
printf(_(" --listen <address> Serve RPC API from this address\n"));
printf(_(" --remote[-subcommand] Execute commands remotely on a server\n"));
- printf(_(" --server <address> Specify RPC server to send commands to\n"));
+ printf(_(" --server <address> Connect to this Nvim server\n"));
printf(_(" --startuptime <file> Write startup timing messages to <file>\n"));
printf(_("\nSee \":help startup-options\" for all options.\n"));
}
diff --git a/src/nvim/mapping.c b/src/nvim/mapping.c
index 9320390d68..e055ebc2fa 100644
--- a/src/nvim/mapping.c
+++ b/src/nvim/mapping.c
@@ -19,6 +19,7 @@
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/cmdexpand_defs.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -150,12 +151,14 @@ static void mapblock_free(mapblock_T **mpp)
{
mapblock_T *mp = *mpp;
xfree(mp->m_keys);
- if (!mp->m_simplified) {
+ if (mp->m_alt != NULL) {
+ mp->m_alt->m_alt = NULL;
+ } else {
NLUA_CLEAR_REF(mp->m_luaref);
xfree(mp->m_str);
xfree(mp->m_orig_str);
+ xfree(mp->m_desc);
}
- xfree(mp->m_desc);
*mpp = mp->m_next;
xfree(mp);
}
@@ -492,13 +495,13 @@ static int str_to_mapargs(const char *strargs, bool is_unmap, MapArguments *mapa
return 0;
}
-/// @param args "rhs", "rhs_lua", "orig_rhs", "expr", "silent", "nowait", "replace_keycodes" and
-/// and "desc" fields are used.
-/// "rhs", "rhs_lua", "orig_rhs" fields are cleared if "simplified" is false.
+/// @param args "rhs", "rhs_lua", "orig_rhs", "expr", "silent", "nowait",
+/// "replace_keycodes" and "desc" fields are used.
/// @param sid 0 to use current_sctx
-static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table, const char *keys,
- MapArguments *args, int noremap, int mode, bool is_abbr, scid_T sid,
- linenr_T lnum, bool simplified)
+static mapblock_T *map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table,
+ const char *keys, MapArguments *args, int noremap, int mode,
+ bool is_abbr, scid_T sid, linenr_T lnum, bool simplified)
+ FUNC_ATTR_NONNULL_RET
{
mapblock_T *mp = xcalloc(1, sizeof(mapblock_T));
@@ -515,11 +518,6 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table,
mp->m_str = args->rhs;
mp->m_orig_str = args->orig_rhs;
mp->m_luaref = args->rhs_lua;
- if (!simplified) {
- args->rhs = NULL;
- args->orig_rhs = NULL;
- args->rhs_lua = LUA_NOREF;
- }
mp->m_keylen = (int)strlen(mp->m_keys);
mp->m_noremap = noremap;
mp->m_nowait = args->nowait;
@@ -536,10 +534,7 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table,
mp->m_script_ctx.sc_lnum += SOURCING_LNUM;
nlua_set_sctx(&mp->m_script_ctx);
}
- mp->m_desc = NULL;
- if (args->desc != NULL) {
- mp->m_desc = xstrdup(args->desc);
- }
+ mp->m_desc = args->desc;
// add the new entry in front of the abbrlist or maphash[] list
if (is_abbr) {
@@ -550,6 +545,7 @@ static void map_add(buf_T *buf, mapblock_T **map_table, mapblock_T **abbr_table,
mp->m_next = map_table[n];
map_table[n] = mp;
}
+ return mp;
}
/// Sets or removes a mapping or abbreviation in buffer `buf`.
@@ -570,6 +566,7 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
// mappings/abbreviations, not the globals.
mapblock_T **map_table = args->buffer ? buf->b_maphash : maphash;
mapblock_T **abbr_table = args->buffer ? &buf->b_first_abbr : &first_abbr;
+ mapblock_T *mp_result[2] = { NULL, NULL };
// For ":noremap" don't remap, otherwise do remap.
int noremap = args->script ? REMAP_SCRIPT
@@ -803,20 +800,17 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
// new rhs for existing entry
mp->m_mode &= ~mode; // remove mode bits
if (mp->m_mode == 0 && !did_it) { // reuse entry
- XFREE_CLEAR(mp->m_desc);
- if (!mp->m_simplified) {
+ if (mp->m_alt != NULL) {
+ mp->m_alt = mp->m_alt->m_alt = NULL;
+ } else {
NLUA_CLEAR_REF(mp->m_luaref);
- XFREE_CLEAR(mp->m_str);
- XFREE_CLEAR(mp->m_orig_str);
+ xfree(mp->m_str);
+ xfree(mp->m_orig_str);
+ xfree(mp->m_desc);
}
mp->m_str = args->rhs;
mp->m_orig_str = args->orig_rhs;
mp->m_luaref = args->rhs_lua;
- if (!keyround1_simplified) {
- args->rhs = NULL;
- args->orig_rhs = NULL;
- args->rhs_lua = LUA_NOREF;
- }
mp->m_noremap = noremap;
mp->m_nowait = args->nowait;
mp->m_silent = args->silent;
@@ -827,9 +821,8 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
mp->m_script_ctx = current_sctx;
mp->m_script_ctx.sc_lnum += SOURCING_LNUM;
nlua_set_sctx(&mp->m_script_ctx);
- if (args->desc != NULL) {
- mp->m_desc = xstrdup(args->desc);
- }
+ mp->m_desc = args->desc;
+ mp_result[keyround - 1] = mp;
did_it = true;
}
}
@@ -888,13 +881,25 @@ static int buf_do_map(int maptype, MapArguments *args, int mode, bool is_abbrev,
}
// Get here when adding a new entry to the maphash[] list or abbrlist.
- map_add(buf, map_table, abbr_table, lhs, args, noremap, mode, is_abbrev,
- 0, // sid
- 0, // lnum
- keyround1_simplified);
+ mp_result[keyround - 1] = map_add(buf, map_table, abbr_table, lhs,
+ args, noremap, mode, is_abbrev,
+ 0, // sid
+ 0, // lnum
+ keyround1_simplified);
+ }
+
+ if (mp_result[0] != NULL && mp_result[1] != NULL) {
+ mp_result[0]->m_alt = mp_result[1];
+ mp_result[1]->m_alt = mp_result[0];
}
theend:
+ if (mp_result[0] != NULL || mp_result[1] != NULL) {
+ args->rhs = NULL;
+ args->orig_rhs = NULL;
+ args->rhs_lua = LUA_NOREF;
+ args->desc = NULL;
+ }
return retval;
}
@@ -1646,12 +1651,12 @@ char *eval_map_expr(mapblock_T *mp, int c)
p = string_to_cstr(ret.data.string);
}
api_free_object(ret);
- if (err.type != kErrorTypeNone) {
+ if (ERROR_SET(&err)) {
semsg_multiline("E5108: %s", err.msg);
api_clear_error(&err);
}
} else {
- p = eval_to_string(expr, false);
+ p = eval_to_string(expr, false, false);
xfree(expr);
}
expr_map_lock--;
@@ -2056,20 +2061,20 @@ void f_hasmapto(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
rettv->vval.v_number = map_to_exists(name, mode, abbr);
}
-/// Fill a Dictionary with all applicable maparg() like dictionaries
+/// Fill a Dict with all applicable maparg() like dictionaries
///
/// @param mp The maphash that contains the mapping information
/// @param buffer_value The "buffer" value
/// @param abbr True if abbreviation
/// @param compatible True for compatible with old maparg() dict
///
-/// @return A Dictionary.
-static Dictionary mapblock_fill_dict(const mapblock_T *const mp, const char *lhsrawalt,
- const int buffer_value, const bool abbr, const bool compatible,
- Arena *arena)
+/// @return Dict.
+static Dict mapblock_fill_dict(const mapblock_T *const mp, const char *lhsrawalt,
+ const int buffer_value, const bool abbr, const bool compatible,
+ Arena *arena)
FUNC_ATTR_NONNULL_ARG(1)
{
- Dictionary dict = arena_dict(arena, 19);
+ Dict dict = arena_dict(arena, 19);
char *const lhs = str2special_arena(mp->m_keys, compatible, !compatible, arena);
char *mapmode = arena_alloc(arena, 7, false);
map_mode_to_chars(mp->m_mode, mapmode);
@@ -2188,9 +2193,9 @@ static void get_maparg(typval_T *argvars, typval_T *rettv, int exact)
// Return a dictionary.
if (mp != NULL && (rhs != NULL || rhs_lua != LUA_NOREF)) {
Arena arena = ARENA_EMPTY;
- Dictionary dict = mapblock_fill_dict(mp, did_simplify ? keys_simplified : NULL,
- buffer_local, abbr, true, &arena);
- object_to_vim_take_luaref(&DICTIONARY_OBJ(dict), rettv, true, NULL);
+ Dict dict = mapblock_fill_dict(mp, did_simplify ? keys_simplified : NULL,
+ buffer_local, abbr, true, &arena);
+ object_to_vim_take_luaref(&DICT_OBJ(dict), rettv, true, NULL);
arena_mem_free(arena_finish(&arena));
} else {
// Return an empty dictionary.
@@ -2327,7 +2332,7 @@ void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
.silent = tv_dict_get_number(d, "silent") != 0,
.nowait = tv_dict_get_number(d, "nowait") != 0,
.replace_keycodes = tv_dict_get_number(d, "replace_keycodes") != 0,
- .desc = tv_dict_get_string(d, "desc", false),
+ .desc = tv_dict_get_string(d, "desc", true),
};
scid_T sid = (scid_T)tv_dict_get_number(d, "sid");
linenr_T lnum = (linenr_T)tv_dict_get_number(d, "lnum");
@@ -2347,12 +2352,19 @@ void f_mapset(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
xfree(unmap_args.rhs);
xfree(unmap_args.orig_rhs);
+ mapblock_T *mp_result[2] = { NULL, NULL };
+
+ mp_result[0] = map_add(curbuf, map_table, abbr_table, lhsraw, &args,
+ noremap, mode, is_abbr, sid, lnum, false);
if (lhsrawalt != NULL) {
- map_add(curbuf, map_table, abbr_table, lhsrawalt, &args, noremap, mode, is_abbr,
- sid, lnum, true);
+ mp_result[1] = map_add(curbuf, map_table, abbr_table, lhsrawalt, &args,
+ noremap, mode, is_abbr, sid, lnum, true);
+ }
+
+ if (mp_result[0] != NULL && mp_result[1] != NULL) {
+ mp_result[0]->m_alt = mp_result[1];
+ mp_result[1]->m_alt = mp_result[0];
}
- map_add(curbuf, map_table, abbr_table, lhsraw, &args, noremap, mode, is_abbr,
- sid, lnum, false);
}
/// "maplist()" function
@@ -2394,10 +2406,10 @@ void f_maplist(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
replace_termcodes(lhs, strlen(lhs), &keys_buf, 0, flags, &did_simplify,
p_cpo);
- Dictionary dict = mapblock_fill_dict(mp, did_simplify ? keys_buf : NULL,
- buffer_local, abbr, true, &arena);
+ Dict dict = mapblock_fill_dict(mp, did_simplify ? keys_buf : NULL, buffer_local, abbr, true,
+ &arena);
typval_T d = TV_INITIAL_VALUE;
- object_to_vim_take_luaref(&DICTIONARY_OBJ(dict), &d, true, NULL);
+ object_to_vim_take_luaref(&DICT_OBJ(dict), &d, true, NULL);
assert(d.v_type == VAR_DICT);
tv_list_append_dict(rettv->vval.v_list, d.vval.v_dict);
arena_mem_free(arena_finish(&arena));
@@ -2802,7 +2814,7 @@ fail_and_free:
/// @param mode The abbreviation for the mode
/// @param buf The buffer to get the mapping array. NULL for global
/// @returns Array of maparg()-like dictionaries describing mappings
-ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, Arena *arena)
+ArrayOf(Dict) keymap_array(String mode, buf_T *buf, Arena *arena)
{
ArrayBuilder mappings = KV_INITIAL_VALUE;
kvi_init(mappings);
@@ -2831,8 +2843,8 @@ ArrayOf(Dictionary) keymap_array(String mode, buf_T *buf, Arena *arena)
}
// Check for correct mode
if (int_mode & current_maphash->m_mode) {
- kvi_push(mappings, DICTIONARY_OBJ(mapblock_fill_dict(current_maphash, NULL, buffer_value,
- is_abbrev, false, arena)));
+ kvi_push(mappings, DICT_OBJ(mapblock_fill_dict(current_maphash, NULL, buffer_value,
+ is_abbrev, false, arena)));
}
}
}
diff --git a/src/nvim/mapping_defs.h b/src/nvim/mapping_defs.h
index 05b8aef23a..4a04b1ebfb 100644
--- a/src/nvim/mapping_defs.h
+++ b/src/nvim/mapping_defs.h
@@ -10,6 +10,9 @@ enum { MAXMAPLEN = 50, }; ///< Maximum length of key sequence to be mapped.
typedef struct mapblock mapblock_T;
struct mapblock {
mapblock_T *m_next; ///< next mapblock in list
+ mapblock_T *m_alt; ///< pointer to mapblock of the same mapping
+ ///< with an alternative form of m_keys, or NULL
+ ///< if there is no such mapblock
char *m_keys; ///< mapped from, lhs
char *m_str; ///< mapped to, rhs
char *m_orig_str; ///< rhs as entered by the user
diff --git a/src/nvim/mark.c b/src/nvim/mark.c
index 6ce42bb7fe..a09ade2b03 100644
--- a/src/nvim/mark.c
+++ b/src/nvim/mark.c
@@ -14,6 +14,7 @@
#include "nvim/cursor.h"
#include "nvim/diff.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h"
@@ -42,6 +43,7 @@
#include "nvim/pos_defs.h"
#include "nvim/quickfix.h"
#include "nvim/strings.h"
+#include "nvim/tag.h"
#include "nvim/textobject.h"
#include "nvim/types_defs.h"
#include "nvim/vim_defs.h"
@@ -69,7 +71,7 @@ int setmark(int c)
/// Free fmark_T item
void free_fmark(fmark_T fm)
{
- tv_dict_unref(fm.additional_data);
+ xfree(fm.additional_data);
}
/// Free xfmark_T item
@@ -164,6 +166,56 @@ int setmark_pos(int c, pos_T *pos, int fnum, fmarkv_T *view_pt)
return FAIL;
}
+/// Remove every jump list entry referring to a given buffer.
+/// This function will also adjust the current jump list index.
+void mark_jumplist_forget_file(win_T *wp, int fnum)
+{
+ // Remove all jump list entries that match the deleted buffer.
+ for (int i = wp->w_jumplistlen - 1; i >= 0; i--) {
+ if (wp->w_jumplist[i].fmark.fnum == fnum) {
+ // Found an entry that we want to delete.
+ free_xfmark(wp->w_jumplist[i]);
+
+ // If the current jump list index is behind the entry we want to delete,
+ // move it back by one.
+ if (wp->w_jumplistidx > i) {
+ wp->w_jumplistidx--;
+ }
+
+ // Actually remove the entry from the jump list.
+ wp->w_jumplistlen--;
+ memmove(&wp->w_jumplist[i], &wp->w_jumplist[i + 1],
+ (size_t)(wp->w_jumplistlen - i) * sizeof(wp->w_jumplist[i]));
+ }
+ }
+}
+
+/// Delete every entry referring to file "fnum" from both the jumplist and the
+/// tag stack.
+void mark_forget_file(win_T *wp, int fnum)
+{
+ mark_jumplist_forget_file(wp, fnum);
+
+ // Remove all tag stack entries that match the deleted buffer.
+ for (int i = wp->w_tagstacklen - 1; i >= 0; i--) {
+ if (wp->w_tagstack[i].fmark.fnum == fnum) {
+ // Found an entry that we want to delete.
+ tagstack_clear_entry(&wp->w_tagstack[i]);
+
+ // If the current tag stack index is behind the entry we want to delete,
+ // move it back by one.
+ if (wp->w_tagstackidx > i) {
+ wp->w_tagstackidx--;
+ }
+
+ // Actually remove the entry from the tag stack.
+ wp->w_tagstacklen--;
+ memmove(&wp->w_tagstack[i], &wp->w_tagstack[i + 1],
+ (size_t)(wp->w_tagstacklen - i) * sizeof(wp->w_tagstack[i]));
+ }
+ }
+}
+
// Set the previous context mark to the current position and add it to the
// jump list.
void setpcmark(void)
diff --git a/src/nvim/mark.h b/src/nvim/mark.h
index c3661e2e22..fdd87f3e96 100644
--- a/src/nvim/mark.h
+++ b/src/nvim/mark.h
@@ -5,19 +5,18 @@
#include "nvim/ascii_defs.h"
#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#include "nvim/extmark_defs.h" // IWYU pragma: keep
-#include "nvim/func_attr.h"
#include "nvim/macros_defs.h"
#include "nvim/mark_defs.h" // IWYU pragma: keep
#include "nvim/os/time.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mark.h.generated.h"
+# include "mark.h.inline.generated.h"
#endif
-static inline int mark_global_index(char name)
- REAL_FATTR_CONST;
/// Convert mark name to the offset
static inline int mark_global_index(const char name)
+ FUNC_ATTR_CONST
{
return (ASCII_ISUPPER(name)
? (name - 'A')
@@ -26,10 +25,9 @@ static inline int mark_global_index(const char name)
: -1));
}
-static inline int mark_local_index(char name)
- REAL_FATTR_CONST;
/// Convert local mark name to the offset
static inline int mark_local_index(const char name)
+ FUNC_ATTR_CONST
{
return (ASCII_ISLOWER(name)
? (name - 'a')
diff --git a/src/nvim/mark_defs.h b/src/nvim/mark_defs.h
index 98bdb6ee04..f953e26e4e 100644
--- a/src/nvim/mark_defs.h
+++ b/src/nvim/mark_defs.h
@@ -1,7 +1,15 @@
#pragma once
-#include "nvim/eval/typval_defs.h"
+#include <stdbool.h>
+
+#include "nvim/func_attr.h"
#include "nvim/os/time_defs.h"
+#include "nvim/pos_defs.h"
+#include "nvim/types_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "mark_defs.h.inline.generated.h"
+#endif
// marks: positions in a file
// (a normal mark is a lnum/col pair, the same as a file position)
@@ -71,7 +79,7 @@ typedef struct {
int fnum; ///< File number.
Timestamp timestamp; ///< Time when this mark was last set.
fmarkv_T view; ///< View the mark was created on
- dict_T *additional_data; ///< Additional data from ShaDa file.
+ AdditionalData *additional_data; ///< Additional data from ShaDa file.
} fmark_T;
#define INIT_FMARK { { 0, 0, 0 }, 0, 0, INIT_FMARKV, NULL }
@@ -84,11 +92,9 @@ typedef struct {
#define INIT_XFMARK { INIT_FMARK, NULL }
-/// Set fmark using given value
-static inline bool lt(pos_T a, pos_T b)
- REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE;
/// Return true if position a is before (less than) position b.
static inline bool lt(pos_T a, pos_T b)
+ FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE
{
if (a.lnum != b.lnum) {
return a.lnum < b.lnum;
@@ -100,25 +106,19 @@ static inline bool lt(pos_T a, pos_T b)
}
static inline bool equalpos(pos_T a, pos_T b)
- REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE;
-/// Return true if position a and b are equal.
-static inline bool equalpos(pos_T a, pos_T b)
+ FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE
{
return (a.lnum == b.lnum) && (a.col == b.col) && (a.coladd == b.coladd);
}
static inline bool ltoreq(pos_T a, pos_T b)
- REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE;
-/// Return true if position a is less than or equal to b.
-static inline bool ltoreq(pos_T a, pos_T b)
+ FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE
{
return lt(a, b) || equalpos(a, b);
}
static inline void clearpos(pos_T *a)
- REAL_FATTR_ALWAYS_INLINE;
-/// Clear the pos_T structure pointed to by a.
-static inline void clearpos(pos_T *a)
+ FUNC_ATTR_ALWAYS_INLINE
{
a->lnum = 0;
a->col = 0;
diff --git a/src/nvim/marktree.c b/src/nvim/marktree.c
index 34d6cd118f..555fef5bbd 100644
--- a/src/nvim/marktree.c
+++ b/src/nvim/marktree.c
@@ -446,7 +446,7 @@ static MTNode *marktree_alloc_node(MarkTree *b, bool internal)
// really meta_inc[kMTMetaCount]
static void meta_describe_key_inc(uint32_t *meta_inc, MTKey *k)
{
- if (!mt_end(*k)) {
+ if (!mt_end(*k) && !mt_invalid(*k)) {
meta_inc[kMTMetaInline] += (k->flags & MT_FLAG_DECOR_VIRT_TEXT_INLINE) ? 1 : 0;
meta_inc[kMTMetaLines] += (k->flags & MT_FLAG_DECOR_VIRT_LINES) ? 1 : 0;
meta_inc[kMTMetaSignHL] += (k->flags & MT_FLAG_DECOR_SIGNHL) ? 1 : 0;
@@ -774,14 +774,10 @@ uint64_t marktree_del_itr(MarkTree *b, MarkTreeIter *itr, bool rev)
return other;
}
-void marktree_revise_flags(MarkTree *b, MarkTreeIter *itr, uint16_t new_flags)
+void marktree_revise_meta(MarkTree *b, MarkTreeIter *itr, MTKey old_key)
{
- uint32_t meta_old[4];
- meta_describe_key(meta_old, rawkey(itr));
- rawkey(itr).flags &= (uint16_t) ~MT_FLAG_EXTERNAL_MASK;
- rawkey(itr).flags |= new_flags;
-
- uint32_t meta_new[4];
+ uint32_t meta_old[4], meta_new[4];
+ meta_describe_key(meta_old, old_key);
meta_describe_key(meta_new, rawkey(itr));
if (!memcmp(meta_old, meta_new, sizeof(meta_old))) {
@@ -2286,7 +2282,7 @@ static void marktree_itr_fix_pos(MarkTree *b, MarkTreeIter *itr)
void marktree_put_test(MarkTree *b, uint32_t ns, uint32_t id, int row, int col, bool right_gravity,
int end_row, int end_col, bool end_right, bool meta_inline)
{
- uint16_t flags = mt_flags(right_gravity, false, false, false, false);
+ uint16_t flags = mt_flags(right_gravity, false, false, false);
// The specific choice is irrelevant here, we pick one counted decor
// type to test the counting and filtering logic.
flags |= meta_inline ? MT_FLAG_DECOR_VIRT_TEXT_INLINE : 0;
diff --git a/src/nvim/marktree.h b/src/nvim/marktree.h
index 8e5cf30ff3..15df57ef63 100644
--- a/src/nvim/marktree.h
+++ b/src/nvim/marktree.h
@@ -35,8 +35,6 @@
#define MT_FLAG_DECOR_VIRT_LINES (((uint16_t)1) << 11)
#define MT_FLAG_DECOR_VIRT_TEXT_INLINE (((uint16_t)1) << 12)
-#define MT_FLAG_SCOPED (((uint16_t)1) << 13)
-
// These _must_ be last to preserve ordering of marks
#define MT_FLAG_RIGHT_GRAVITY (((uint16_t)1) << 14)
#define MT_FLAG_LAST (((uint16_t)1) << 15)
@@ -46,7 +44,7 @@
| MT_FLAG_DECOR_VIRT_TEXT_INLINE)
#define MT_FLAG_EXTERNAL_MASK (MT_FLAG_DECOR_MASK | MT_FLAG_NO_UNDO \
- | MT_FLAG_INVALIDATE | MT_FLAG_INVALID | MT_FLAG_SCOPED)
+ | MT_FLAG_INVALIDATE | MT_FLAG_INVALID)
// this is defined so that start and end of the same range have adjacent ids
#define MARKTREE_END_FLAG ((uint64_t)1)
@@ -110,24 +108,12 @@ static inline bool mt_decor_sign(MTKey key)
return key.flags & (MT_FLAG_DECOR_SIGNTEXT | MT_FLAG_DECOR_SIGNHL);
}
-static inline bool mt_scoped(MTKey key)
-{
- return key.flags & MT_FLAG_SCOPED;
-}
-
-static inline bool mt_scoped_in_win(MTKey key, win_T *wp)
-{
- return !mt_scoped(key) || set_has(uint32_t, &wp->w_ns_set, key.ns);
-}
-
-static inline uint16_t mt_flags(bool right_gravity, bool no_undo, bool invalidate, bool decor_ext,
- bool scoped)
+static inline uint16_t mt_flags(bool right_gravity, bool no_undo, bool invalidate, bool decor_ext)
{
return (uint16_t)((right_gravity ? MT_FLAG_RIGHT_GRAVITY : 0)
| (no_undo ? MT_FLAG_NO_UNDO : 0)
| (invalidate ? MT_FLAG_INVALIDATE : 0)
- | (decor_ext ? MT_FLAG_DECOR_EXT : 0)
- | (scoped ? MT_FLAG_SCOPED : 0));
+ | (decor_ext ? MT_FLAG_DECOR_EXT : 0));
}
static inline MTPair mtpair_from(MTKey start, MTKey end)
diff --git a/src/nvim/match.c b/src/nvim/match.c
index 580d7d1069..86cab5221d 100644
--- a/src/nvim/match.c
+++ b/src/nvim/match.c
@@ -10,6 +10,7 @@
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/window.h"
@@ -705,6 +706,9 @@ int update_search_hl(win_T *wp, linenr_T lnum, colnr_T col, char **line, match_T
// group.
if (shl == search_hl && shl->has_cursor) {
shl->attr_cur = win_hl_attr(wp, HLF_LC);
+ if (shl->attr_cur != shl->attr) {
+ search_hl_has_cursor_lnum = lnum;
+ }
} else {
shl->attr_cur = shl->attr;
}
diff --git a/src/nvim/math.c b/src/nvim/math.c
index 1ccf4d7806..4ca212413b 100644
--- a/src/nvim/math.c
+++ b/src/nvim/math.c
@@ -78,13 +78,15 @@ int xctz(uint64_t x)
}
/// Count number of set bits in bit field.
-int popcount(uint64_t x)
+unsigned xpopcount(uint64_t x)
{
// Use compiler builtin if possible.
-#if defined(__clang__) || defined(__GNUC__)
- return __builtin_popcountll(x);
+#if defined(__NetBSD__)
+ return popcount64(x);
+#elif defined(__clang__) || defined(__GNUC__)
+ return (unsigned)__builtin_popcountll(x);
#else
- int count = 0;
+ unsigned count = 0;
for (; x != 0; x >>= 1) {
if (x & 1) {
count++;
diff --git a/src/nvim/mbyte.c b/src/nvim/mbyte.c
index a345795bbe..01e720283e 100644
--- a/src/nvim/mbyte.c
+++ b/src/nvim/mbyte.c
@@ -32,6 +32,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <utf8proc.h>
#include <uv.h>
#include <wctype.h>
@@ -43,6 +44,7 @@
#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/getchar.h"
@@ -83,7 +85,6 @@ struct interval {
// uncrustify:off
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mbyte.c.generated.h"
-# include "unicode_tables.generated.h"
#endif
// uncrustify:on
@@ -442,31 +443,10 @@ int mb_get_class_tab(const char *p, const uint64_t *const chartab)
return utf_class_tab(utf_ptr2char(p), chartab);
}
-// Return true if "c" is in "table".
-static bool intable(const struct interval *table, size_t n_items, int c)
- FUNC_ATTR_PURE
+static bool prop_is_emojilike(const utf8proc_property_t *prop)
{
- assert(n_items > 0);
- // first quick check for Latin1 etc. characters
- if (c < table[0].first) {
- return false;
- }
-
- assert(n_items <= SIZE_MAX / 2);
- // binary search in table
- size_t bot = 0;
- size_t top = n_items;
- do {
- size_t mid = (bot + top) >> 1;
- if (table[mid].last < c) {
- bot = mid + 1;
- } else if (table[mid].first > c) {
- top = mid;
- } else {
- return true;
- }
- } while (top > bot);
- return false;
+ return prop->boundclass == UTF8PROC_BOUNDCLASS_EXTENDED_PICTOGRAPHIC
+ || prop->boundclass == UTF8PROC_BOUNDCLASS_REGIONAL_INDICATOR;
}
/// For UTF-8 character "c" return 2 for a double-width character, 1 for others.
@@ -494,13 +474,18 @@ int utf_char2cells(int c)
return n;
}
- if (intable(doublewidth, ARRAY_SIZE(doublewidth), c)) {
+ const utf8proc_property_t *prop = utf8proc_get_property(c);
+
+ if (prop->charwidth == 2) {
return 2;
}
- if (p_emoji && intable(emoji_wide, ARRAY_SIZE(emoji_wide), c)) {
+ if (*p_ambw == 'd' && prop->ambiguous_width) {
return 2;
}
- if (*p_ambw == 'd' && intable(ambiguous, ARRAY_SIZE(ambiguous), c)) {
+
+ // Characters below 1F000 may be considered single width traditionally,
+ // making them double width causes problems.
+ if (p_emoji && c >= 0x1f000 && !prop->ambiguous_width && prop_is_emojilike(prop)) {
return 2;
}
@@ -509,31 +494,43 @@ int utf_char2cells(int c)
/// Return the number of display cells character at "*p" occupies.
/// This doesn't take care of unprintable characters, use ptr2cells() for that.
-int utf_ptr2cells(const char *p)
+int utf_ptr2cells(const char *p_in)
{
+ const uint8_t *p = (const uint8_t *)p_in;
// Need to convert to a character number.
- if ((uint8_t)(*p) >= 0x80) {
- int c = utf_ptr2char(p);
+ if ((*p) >= 0x80) {
+ int len = utf8len_tab[*p];
+ int32_t c = utf_ptr2CharInfo_impl(p, (uintptr_t)len);
// An illegal byte is displayed as <xx>.
- if (utf_ptr2len(p) == 1 || c == NUL) {
+ if (c <= 0) {
return 4;
}
// If the char is ASCII it must be an overlong sequence.
if (c < 0x80) {
return char2cells(c);
}
- return utf_char2cells(c);
+ int cells = utf_char2cells(c);
+ if (cells == 1 && p_emoji
+ && prop_is_emojilike(utf8proc_get_property(c))) {
+ int c2 = utf_ptr2char(p_in + len);
+ if (c2 == 0xFE0F) {
+ return 2; // emoji presentation
+ }
+ }
+ return cells;
}
return 1;
}
/// Convert a UTF-8 byte sequence to a character number.
-/// Doesn't handle ascii! only multibyte and illegal sequences.
+/// Doesn't handle ascii! only multibyte and illegal sequences. ASCII (including NUL)
+/// are treated like illegal sequences.
///
/// @param[in] p String to convert.
/// @param[in] len Length of the character in bytes, 0 or 1 if illegal.
///
-/// @return Unicode codepoint. A negative value when the sequence is illegal.
+/// @return Unicode codepoint. A negative value when the sequence is illegal (or
+/// ASCII, including NUL).
int32_t utf_ptr2CharInfo_impl(uint8_t const *p, uintptr_t const len)
FUNC_ATTR_PURE FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
@@ -601,7 +598,8 @@ int utf_ptr2cells_len(const char *p, int size)
{
// Need to convert to a wide character.
if (size > 0 && (uint8_t)(*p) >= 0x80) {
- if (utf_ptr2len_len(p, size) < utf8len_tab[(uint8_t)(*p)]) {
+ int len = utf_ptr2len_len(p, size);
+ if (len < utf8len_tab[(uint8_t)(*p)]) {
return 1; // truncated
}
int c = utf_ptr2char(p);
@@ -613,7 +611,16 @@ int utf_ptr2cells_len(const char *p, int size)
if (c < 0x80) {
return char2cells(c);
}
- return utf_char2cells(c);
+ int cells = utf_char2cells(c);
+ if (cells == 1 && p_emoji && size > len
+ && prop_is_emojilike(utf8proc_get_property(c))
+ && utf_ptr2len_len(p + len, size - len) == utf8len_tab[(uint8_t)p[len]]) {
+ int c2 = utf_ptr2char(p + len);
+ if (c2 == 0xFE0F) {
+ return 2; // emoji presentation
+ }
+ }
+ return cells;
}
return 1;
}
@@ -646,8 +653,8 @@ size_t mb_string2cells_len(const char *str, size_t size)
size_t clen = 0;
for (const char *p = str; *p != NUL && p < str + size;
- p += utfc_ptr2len_len(p, (int)size + (int)(p - str))) {
- clen += (size_t)utf_ptr2cells(p);
+ p += utfc_ptr2len_len(p, (int)size - (int)(p - str))) {
+ clen += (size_t)utf_ptr2cells_len(p, (int)size - (int)(p - str));
}
return clen;
@@ -791,29 +798,48 @@ int mb_cptr2char_adv(const char **pp)
return c;
}
+/// When "c" is the first char of a string, determine if it needs to be prefixed
+/// by a space byte to be drawn correctly, and not merge with the space left of
+/// the string.
+bool utf_iscomposing_first(int c)
+{
+ return c >= 128 && !utf8proc_grapheme_break(' ', c);
+}
+
/// Check if the character pointed to by "p2" is a composing character when it
-/// comes after "p1". For Arabic sometimes "ab" is replaced with "c", which
-/// behaves like a composing character.
-bool utf_composinglike(const char *p1, const char *p2)
+/// comes after "p1".
+///
+/// We use the definition in UAX#29 as implemented by utf8proc with the following
+/// exceptions:
+///
+/// - ASCII chars always begin a new cluster. This is a long assumed invariant
+/// in the code base and very useful for performance (we can exit early for ASCII
+/// all over the place, branch predictor go brrr in ASCII-only text).
+/// As of Unicode 15.1 this will only break BOUNDCLASS_UREPEND followed by ASCII,
+/// which should be exceedingly rare (these PREPEND chars are expected to be
+/// followed by multibyte chars within the same script family)
+///
+/// - When 'arabicshape' is active, some pairs of arabic letters "ab" is replaced with
+/// "c" taking one single cell, which behaves like a cluster.
+///
+/// @param "state" should be set to GRAPHEME_STATE_INIT before first call
+/// it is allowed to be null, but will then not handle some longer
+/// sequences, like ZWJ based emoji
+bool utf_composinglike(const char *p1, const char *p2, GraphemeState *state)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
- int c2 = utf_ptr2char(p2);
- if (utf_iscomposing(c2)) {
- return true;
- }
- if (!arabic_maycombine(c2)) {
+ if ((uint8_t)(*p2) < 128) {
return false;
}
- return arabic_combine(utf_ptr2char(p1), c2);
-}
-/// Check if the next character is a composing character when it
-/// comes after the first. For Arabic sometimes "ab" is replaced with "c", which
-/// behaves like a composing character.
-/// returns false for negative values
-bool utf_char_composinglike(int32_t const first, int32_t const next)
- FUNC_ATTR_PURE
-{
- return utf_iscomposing(next) || arabic_combine(first, next);
+ int first = utf_ptr2char(p1);
+ int second = utf_ptr2char(p2);
+
+ if (!utf8proc_grapheme_break_stateful(first, second, state)) {
+ return true;
+ }
+
+ return arabic_combine(first, second);
}
/// Get the screen char at the beginning of a string
@@ -832,7 +858,7 @@ schar_T utfc_ptr2schar(const char *p, int *firstc)
{
int c = utf_ptr2char(p);
*firstc = c; // NOT optional, you are gonna need it
- bool first_compose = utf_iscomposing(c);
+ bool first_compose = utf_iscomposing_first(c);
size_t maxlen = MAX_SCHAR_SIZE - 1 - first_compose;
size_t len = (size_t)utfc_ptr2len_len(p, (int)maxlen);
@@ -843,16 +869,13 @@ schar_T utfc_ptr2schar(const char *p, int *firstc)
return schar_from_buf_first(p, len, first_compose);
}
-/// Get the screen char at the beginning of a string with length
+/// Get the screen char from a char with a known length
///
/// Like utfc_ptr2schar but use no more than p[maxlen].
-schar_T utfc_ptr2schar_len(const char *p, int maxlen, int *firstc)
+schar_T utfc_ptrlen2schar(const char *p, int len, int *firstc)
FUNC_ATTR_NONNULL_ALL
{
- assert(maxlen > 0);
-
- size_t len = (size_t)utf_ptr2len_len(p, maxlen);
- if (len > (size_t)maxlen || (len == 1 && (uint8_t)(*p) >= 0x80) || len == 0) {
+ if ((len == 1 && (uint8_t)(*p) >= 0x80) || len == 0) {
// invalid or truncated sequence
*firstc = (uint8_t)(*p);
return 0;
@@ -860,11 +883,13 @@ schar_T utfc_ptr2schar_len(const char *p, int maxlen, int *firstc)
int c = utf_ptr2char(p);
*firstc = c;
- bool first_compose = utf_iscomposing(c);
- maxlen = MIN(maxlen, MAX_SCHAR_SIZE - 1 - first_compose);
- len = (size_t)utfc_ptr2len_len(p, maxlen);
+ bool first_compose = utf_iscomposing_first(c);
+ int maxlen = MAX_SCHAR_SIZE - 1 - first_compose;
+ if (len > maxlen) {
+ len = utfc_ptr2len_len(p, maxlen);
+ }
- return schar_from_buf_first(p, len, first_compose);
+ return schar_from_buf_first(p, (size_t)len, first_compose);
}
/// Caller must ensure there is space for `first_compose`
@@ -962,8 +987,9 @@ int utfc_ptr2len(const char *const p)
// Check for composing characters.
int prevlen = 0;
+ GraphemeState state = GRAPHEME_STATE_INIT;
while (true) {
- if ((uint8_t)p[len] < 0x80 || !utf_composinglike(p + prevlen, p + len)) {
+ if ((uint8_t)p[len] < 0x80 || !utf_composinglike(p + prevlen, p + len, &state)) {
return len;
}
@@ -994,9 +1020,10 @@ int utfc_ptr2len_len(const char *p, int size)
return 1;
}
- // Check for composing characters. We can handle only the first six, but
+ // Check for composing characters. We can only display a limited amount, but
// skip all of them (otherwise the cursor would get stuck).
int prevlen = 0;
+ GraphemeState state = GRAPHEME_STATE_INIT;
while (len < size) {
if ((uint8_t)p[len] < 0x80) {
break;
@@ -1009,7 +1036,7 @@ int utfc_ptr2len_len(const char *p, int size)
break;
}
- if (!utf_composinglike(p + prevlen, p + len)) {
+ if (!utf_composinglike(p + prevlen, p + len, &state)) {
break;
}
@@ -1082,13 +1109,21 @@ int utf_char2bytes(const int c, char *const buf)
}
}
-/// Return true if "c" is a composing UTF-8 character.
-/// This means it will be drawn on top of the preceding character.
+/// Return true if "c" is a legacy composing UTF-8 character.
+///
+/// This is deprecated in favour of utf_composinglike() which uses the modern
+/// stateful algorithm to determine grapheme clusters. Still available
+/// to support some legacy code which hasn't been refactored yet.
+///
+/// To check if a char would combine with a preceeding space, use
+/// utf_iscomposing_first() instead.
+///
/// Based on code from Markus Kuhn.
/// Returns false for negative values.
-bool utf_iscomposing(int c)
+bool utf_iscomposing_legacy(int c)
{
- return intable(combining, ARRAY_SIZE(combining), c);
+ const utf8proc_property_t *prop = utf8proc_get_property(c);
+ return prop->category == UTF8PROC_CATEGORY_MN || prop->category == UTF8PROC_CATEGORY_ME;
}
#ifdef __SSE2__
@@ -1133,6 +1168,33 @@ bool utf_printable(int c)
#else
+// Return true if "c" is in "table".
+static bool intable(const struct interval *table, size_t n_items, int c)
+ FUNC_ATTR_PURE
+{
+ assert(n_items > 0);
+ // first quick check for Latin1 etc. characters
+ if (c < table[0].first) {
+ return false;
+ }
+
+ assert(n_items <= SIZE_MAX / 2);
+ // binary search in table
+ size_t bot = 0;
+ size_t top = n_items;
+ do {
+ size_t mid = (bot + top) >> 1;
+ if (table[mid].last < c) {
+ bot = mid + 1;
+ } else if (table[mid].first > c) {
+ top = mid;
+ } else {
+ return true;
+ }
+ } while (top > bot);
+ return false;
+}
+
// Return true for characters that can be displayed in a normal way.
// Only for characters of 0x100 and above!
bool utf_printable(int c)
@@ -1255,8 +1317,9 @@ int utf_class_tab(const int c, const uint64_t *const chartab)
return 1; // punctuation
}
+ const utf8proc_property_t *prop = utf8proc_get_property(c);
// emoji
- if (intable(emoji_all, ARRAY_SIZE(emoji_all), c)) {
+ if (prop_is_emojilike(prop)) {
return 3;
}
@@ -1276,47 +1339,51 @@ int utf_class_tab(const int c, const uint64_t *const chartab)
return 2;
}
-bool utf_ambiguous_width(int c)
+bool utf_ambiguous_width(const char *p)
{
- return c >= 0x80 && (intable(ambiguous, ARRAY_SIZE(ambiguous), c)
- || intable(emoji_all, ARRAY_SIZE(emoji_all), c));
-}
+ // be quick if there is nothing to print or ASCII-only
+ if (p[0] == NUL || p[1] == NUL) {
+ return false;
+ }
-// Generic conversion function for case operations.
-// Return the converted equivalent of "a", which is a UCS-4 character. Use
-// the given conversion "table". Uses binary search on "table".
-static int utf_convert(int a, const convertStruct *const table, size_t n_items)
-{
- // indices into table
- size_t start = 0;
- size_t end = n_items;
- while (start < end) {
- // need to search further
- size_t mid = (end + start) / 2;
- if (table[mid].rangeEnd < a) {
- start = mid + 1;
- } else {
- end = mid;
+ CharInfo info = utf_ptr2CharInfo(p);
+ if (info.value >= 0x80) {
+ const utf8proc_property_t *prop = utf8proc_get_property(info.value);
+ if (prop->ambiguous_width || prop_is_emojilike(prop)) {
+ return true;
}
}
- if (start < n_items
- && table[start].rangeStart <= a
- && a <= table[start].rangeEnd
- && (a - table[start].rangeStart) % table[start].step == 0) {
- return a + table[start].offset;
- }
- return a;
+
+ // check if second sequence is 0xFE0F VS-16 which can turn things into emoji,
+ // safe with NUL (no second sequence)
+ return memcmp(p + info.len, "\xef\xb8\x8f", 3) == 0;
}
// Return the folded-case equivalent of "a", which is a UCS-4 character. Uses
-// simple case folding.
+// full case folding.
int utf_fold(int a)
{
if (a < 0x80) {
// be fast for ASCII
return a >= 0x41 && a <= 0x5a ? a + 32 : a;
}
- return utf_convert(a, foldCase, ARRAY_SIZE(foldCase));
+
+ // TODO(dundargoc): utf8proc only does full case folding, which breaks some tests. This is a
+ // temporary workaround to circumvent failing tests.
+ //
+ // (0xdf) ß == ss in full casefolding. Using this however breaks the vim spell tests and the error
+ // E763 is thrown. This is due to the test spells relying on the vim spell files.
+ //
+ // (0x130) İ == i̇ in full casefolding.
+ if (a == 0xdf || a == 0x130) {
+ return a;
+ }
+
+ utf8proc_int32_t result[1];
+
+ utf8proc_ssize_t res = utf8proc_decompose_char(a, result, 1, UTF8PROC_CASEFOLD, NULL);
+
+ return (res == 1) ? result[0] : a;
}
// Vim's own character class functions. These exist because many library
@@ -1324,9 +1391,6 @@ int utf_fold(int a)
// invalid values or can't handle latin1 when the locale is C.
// Speed is most important here.
-// Note: UnicodeData.txt does not define U+1E9E as being the corresponding upper
-// case letter for U+00DF (ß), however it is part of the toLower table
-
/// Return the upper-case equivalent of "a", which is a UCS-4 character. Use
/// simple case folding.
int mb_toupper(int a)
@@ -1345,14 +1409,12 @@ int mb_toupper(int a)
return TOUPPER_LOC(a);
}
- // For any other characters use the above mapping table.
- return utf_convert(a, toUpper, ARRAY_SIZE(toUpper));
+ return utf8proc_toupper(a);
}
bool mb_islower(int a)
{
- // German sharp s is lower case but has no upper case equivalent.
- return (mb_toupper(a) != a) || a == 0xdf;
+ return mb_toupper(a) != a;
}
/// Return the lower-case equivalent of "a", which is a UCS-4 character. Use
@@ -1373,8 +1435,7 @@ int mb_tolower(int a)
return TOLOWER_LOC(a);
}
- // For any other characters use the above mapping table.
- return utf_convert(a, toLower, ARRAY_SIZE(toLower));
+ return utf8proc_tolower(a);
}
bool mb_isupper(int a)
@@ -1388,7 +1449,7 @@ bool mb_isalpha(int a)
return mb_islower(a) || mb_isupper(a);
}
-static int utf_strnicmp(const char *s1, const char *s2, size_t n1, size_t n2)
+int utf_strnicmp(const char *s1, const char *s2, size_t n1, size_t n2)
{
int c1, c2;
char buffer[6];
@@ -1545,7 +1606,7 @@ int utf16_to_utf8(const wchar_t *utf16, int utf16len, char **utf8)
return uv_translate_sys_error(GetLastError());
}
- (*utf8)[bufsize] = '\0';
+ (*utf8)[bufsize] = NUL;
return 0;
}
@@ -1673,6 +1734,26 @@ void show_utf8(void)
msg(IObuff, 0);
}
+/// @return true if boundclass bc always starts a new cluster regardless of what's before
+/// false negatives are allowed (perf cost, not correctness)
+static bool always_break(int bc)
+{
+ return (bc == UTF8PROC_BOUNDCLASS_CONTROL);
+}
+
+/// @return true if bc2 always starts a cluster after bc1
+/// false negatives are allowed (perf cost, not correctness)
+static bool always_break_two(int bc1, int bc2)
+{
+ // don't check for UTF8PROC_BOUNDCLASS_CONTROL for bc2 as it either has been checked by
+ // "always_break" on first iteration or when it was bc1 in the previous iteration
+ return ((bc1 != UTF8PROC_BOUNDCLASS_PREPEND && bc2 == UTF8PROC_BOUNDCLASS_OTHER)
+ || (bc1 >= UTF8PROC_BOUNDCLASS_CR && bc1 <= UTF8PROC_BOUNDCLASS_CONTROL)
+ || (bc2 == UTF8PROC_BOUNDCLASS_EXTENDED_PICTOGRAPHIC
+ && (bc1 == UTF8PROC_BOUNDCLASS_OTHER
+ || bc1 == UTF8PROC_BOUNDCLASS_EXTENDED_PICTOGRAPHIC)));
+}
+
/// Return offset from "p" to the start of a character, including composing characters.
/// "base" must be the start of the string, which must be NUL terminated.
/// If "p" points to the NUL at the end of the string return 0.
@@ -1686,50 +1767,111 @@ int utf_head_off(const char *base_in, const char *p_in)
const uint8_t *base = (uint8_t *)base_in;
const uint8_t *p = (uint8_t *)p_in;
- // Skip backwards over trailing bytes: 10xx.xxxx
- // Skip backwards again if on a composing char.
- const uint8_t *q;
- for (q = p;; q--) {
- // Move s to the last byte of this char.
- const uint8_t *s;
- for (s = q; (s[1] & 0xc0) == 0x80; s++) {}
-
- // Move q to the first byte of this char.
- while (q > base && (*q & 0xc0) == 0x80) {
- q--;
- }
- // Check for illegal sequence. Do allow an illegal byte after where we
- // started.
- int len = utf8len_tab[*q];
- if (len != (int)(s - q + 1) && len != (int)(p - q + 1)) {
- return 0;
+ const uint8_t *start = p;
+
+ // move start to the first byte of this codepoint
+ // might stop on a continuation byte if overlong, handled by utf_ptr2CharInfo_impl
+ while (start > base && (*start & 0xc0) == 0x80 && (p - start) < 6) {
+ start--;
+ }
+
+ const uint8_t last_len = utf8len_tab[*start];
+ int32_t cur_code = utf_ptr2CharInfo_impl(start, (uintptr_t)last_len);
+ if (cur_code < 0 || p - start >= last_len) {
+ return 0; // p must be part of an illegal sequence
+ }
+ const uint8_t * const safe_end = start + last_len;
+
+ int cur_bc = utf8proc_get_property(cur_code)->boundclass;
+ if (always_break(cur_bc) || start == base) {
+ return (int)(p - start);
+ }
+
+ // backtrack to find the start of a cluster. we might go too far, checked in the next loop
+ const uint8_t *cur_pos = start;
+ const uint8_t *const p_start = start;
+
+ while (true) {
+ if (start[-1] == NUL) {
+ break;
}
- if (q <= base) {
+ start--;
+ if (*start < 0x80) { // stop on ascii, we are done
break;
}
- int c = utf_ptr2char((char *)q);
- if (utf_iscomposing(c)) {
- continue;
+ while (start > base && (*start & 0xc0) == 0x80 && (cur_pos - start) < 6) {
+ start--;
}
- if (arabic_maycombine(c)) {
- // Advance to get a sneak-peak at the next char
- const uint8_t *j = q;
- j--;
- // Move j to the first byte of this char.
- while (j > base && (*j & 0xc0) == 0x80) {
- j--;
- }
- if (arabic_combine(utf_ptr2char((char *)j), c)) {
- continue;
- }
+ int prev_len = utf8len_tab[*start];
+ int32_t prev_code = utf_ptr2CharInfo_impl(start, (uintptr_t)prev_len);
+ if (prev_code < 0 || prev_len < cur_pos - start) {
+ start = cur_pos; // start at valid sequence after invalid bytes
+ break;
}
- break;
+
+ int prev_bc = utf8proc_get_property(prev_code)->boundclass;
+ if (always_break_two(prev_bc, cur_bc) && !arabic_combine(prev_code, cur_code)) {
+ start = cur_pos; // prev_code cannot be a part of this cluster
+ break;
+ } else if (start == base) {
+ break;
+ }
+ cur_pos = start;
+ cur_bc = prev_bc;
+ cur_code = prev_code;
+ }
+
+ // hot path: we are already on the first codepoint of a sequence
+ if (start == p_start && last_len > p - start) {
+ return (int)(p - start);
}
- return (int)(p - q);
+ const uint8_t *q = start;
+ while (q < p) {
+ // don't need to find end of cluster. once we reached the codepoint of p, we are done
+ int len = utfc_ptr2len_len((const char *)q, (int)(safe_end - q));
+
+ if (q + len > p) {
+ return (int)(p - q);
+ }
+
+ q += len;
+ }
+
+ return 0;
+}
+
+/// Assumes caller already handles ascii. see `utfc_next`
+StrCharInfo utfc_next_impl(StrCharInfo cur)
+{
+ int32_t prev_code = cur.chr.value;
+ uint8_t *next = (uint8_t *)(cur.ptr + cur.chr.len);
+ GraphemeState state = GRAPHEME_STATE_INIT;
+ assert(*next >= 0x80);
+
+ while (true) {
+ uint8_t const next_len = utf8len_tab[*next];
+ int32_t const next_code = utf_ptr2CharInfo_impl(next, (uintptr_t)next_len);
+ if (utf8proc_grapheme_break_stateful(prev_code, next_code, &state)
+ && !arabic_combine(prev_code, next_code)) {
+ return (StrCharInfo){
+ .ptr = (char *)next,
+ .chr = (CharInfo){ .value = next_code, .len = (next_code < 0 ? 1 : next_len) },
+ };
+ }
+
+ prev_code = next_code;
+ next += next_len;
+ if (EXPECT(*next < 0x80U, true)) {
+ return (StrCharInfo){
+ .ptr = (char *)next,
+ .chr = (CharInfo){ .value = *next, .len = 1 },
+ };
+ }
+ }
}
// Whether space is NOT allowed before/after 'c'.
@@ -2688,7 +2830,7 @@ char *string_convert_ext(const vimconv_T *const vcp, char *ptr, size_t *lenp, si
c = 0x100; break; // not in latin9
}
}
- if (!utf_iscomposing(c)) { // skip composing chars
+ if (!utf_iscomposing_legacy(c)) { // skip composing chars
if (c < 0x100) {
*d++ = (uint8_t)c;
} else if (vcp->vc_fail) {
@@ -2776,17 +2918,17 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
emsg(_(e_listreq));
return;
}
+
const list_T *const l = argvars[0].vval.v_list;
- if (tv_list_len(l) == 0) {
+ cw_interval_T *table = NULL;
+ const size_t table_size = (size_t)tv_list_len(l);
+ if (table_size == 0) {
// Clearing the table.
- xfree(cw_table);
- cw_table = NULL;
- cw_table_size = 0;
- return;
+ goto update;
}
// Note: use list_T instead of listitem_T so that TV_LIST_ITEM_NEXT can be used properly below.
- const list_T **ptrs = xmalloc(sizeof(const list_T *) * (size_t)tv_list_len(l));
+ const list_T **ptrs = xmalloc(sizeof(const list_T *) * table_size);
// Check that all entries are a list with three numbers, the range is
// valid and the cell width is valid.
@@ -2838,12 +2980,12 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
});
// Sort the list on the first number.
- qsort((void *)ptrs, (size_t)tv_list_len(l), sizeof(const list_T *), tv_nr_compare);
+ qsort((void *)ptrs, table_size, sizeof(const list_T *), tv_nr_compare);
- cw_interval_T *table = xmalloc(sizeof(cw_interval_T) * (size_t)tv_list_len(l));
+ table = xmalloc(sizeof(cw_interval_T) * table_size);
// Store the items in the new table.
- for (item = 0; item < tv_list_len(l); item++) {
+ for (item = 0; (size_t)item < table_size; item++) {
const list_T *const li_l = ptrs[item];
const listitem_T *lili = tv_list_first(li_l);
const varnumber_T n1 = TV_LIST_ITEM_TV(lili)->vval.v_number;
@@ -2862,10 +3004,12 @@ void f_setcellwidths(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
xfree((void *)ptrs);
+update:
+ ;
cw_interval_T *const cw_table_save = cw_table;
const size_t cw_table_size_save = cw_table_size;
cw_table = table;
- cw_table_size = (size_t)tv_list_len(l);
+ cw_table_size = table_size;
// Check that the new value does not conflict with 'listchars' or
// 'fillchars'.
diff --git a/src/nvim/mbyte.h b/src/nvim/mbyte.h
index ddac040aae..2da051fca2 100644
--- a/src/nvim/mbyte.h
+++ b/src/nvim/mbyte.h
@@ -3,17 +3,21 @@
#include <stdbool.h>
#include <stdint.h>
#include <sys/types.h> // IWYU pragma: keep
+#include <utf8proc.h>
#include <uv.h> // IWYU pragma: keep
#include "nvim/cmdexpand_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
-#include "nvim/func_attr.h"
#include "nvim/macros_defs.h"
#include "nvim/mbyte_defs.h" // IWYU pragma: keep
#include "nvim/types_defs.h" // IWYU pragma: keep
+typedef utf8proc_int32_t GraphemeState;
+#define GRAPHEME_STATE_INIT 0
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "mbyte.h.generated.h"
+# include "mbyte.h.inline.generated.h"
#endif
enum {
@@ -53,18 +57,14 @@ extern const uint8_t utf8len_tab[256];
(p -= utf_head_off((char *)(s), (char *)(p) - 1) + 1)
/// Check whether a given UTF-8 byte is a trailing byte (10xx.xxxx).
-static inline bool utf_is_trail_byte(uint8_t byte)
- REAL_FATTR_CONST REAL_FATTR_ALWAYS_INLINE;
static inline bool utf_is_trail_byte(uint8_t const byte)
+ FUNC_ATTR_CONST FUNC_ATTR_ALWAYS_INLINE
{
// uint8_t is for clang to use smaller cmp
return (uint8_t)(byte & 0xC0U) == 0x80U;
}
-static inline CharInfo utf_ptr2CharInfo(char const *p_in)
- REAL_FATTR_NONNULL_ALL REAL_FATTR_PURE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE;
-
/// Convert a UTF-8 byte sequence to a Unicode code point.
/// Handles ascii, multibyte sequiences and illegal sequences.
///
@@ -73,6 +73,7 @@ static inline CharInfo utf_ptr2CharInfo(char const *p_in)
/// @return information abouth the character. When the sequence is illegal,
/// "value" is negative, "len" is 1.
static inline CharInfo utf_ptr2CharInfo(char const *const p_in)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE
{
uint8_t const *const p = (uint8_t const *)p_in;
uint8_t const first = *p;
@@ -88,43 +89,27 @@ static inline CharInfo utf_ptr2CharInfo(char const *const p_in)
}
}
-static inline StrCharInfo utfc_next(StrCharInfo cur)
- REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE;
-
/// Return information about the next character.
/// Composing and combining characters are considered a part of the current character.
///
/// @param[in] cur Information about the current character in the string.
static inline StrCharInfo utfc_next(StrCharInfo cur)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE
{
- int32_t prev_code = cur.chr.value;
+ // handle ASCII case inline
uint8_t *next = (uint8_t *)(cur.ptr + cur.chr.len);
-
- while (true) {
- if (EXPECT(*next < 0x80U, true)) {
- return (StrCharInfo){
- .ptr = (char *)next,
- .chr = (CharInfo){ .value = *next, .len = 1 },
- };
- }
- uint8_t const next_len = utf8len_tab[*next];
- int32_t const next_code = utf_ptr2CharInfo_impl(next, (uintptr_t)next_len);
- if (!utf_char_composinglike(prev_code, next_code)) {
- return (StrCharInfo){
- .ptr = (char *)next,
- .chr = (CharInfo){ .value = next_code, .len = (next_code < 0 ? 1 : next_len) },
- };
- }
-
- prev_code = next_code;
- next += next_len;
+ if (EXPECT(*next < 0x80U, true)) {
+ return (StrCharInfo){
+ .ptr = (char *)next,
+ .chr = (CharInfo){ .value = *next, .len = 1 },
+ };
}
-}
-static inline StrCharInfo utf_ptr2StrCharInfo(char *ptr)
- REAL_FATTR_NONNULL_ALL REAL_FATTR_ALWAYS_INLINE REAL_FATTR_PURE;
+ return utfc_next_impl(cur);
+}
static inline StrCharInfo utf_ptr2StrCharInfo(char *ptr)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_PURE
{
return (StrCharInfo){ .ptr = ptr, .chr = utf_ptr2CharInfo(ptr) };
}
diff --git a/src/nvim/memfile.c b/src/nvim/memfile.c
index a1713edb66..d032caa3be 100644
--- a/src/nvim/memfile.c
+++ b/src/nvim/memfile.c
@@ -46,6 +46,7 @@
#include "nvim/assert_defs.h"
#include "nvim/buffer_defs.h"
+#include "nvim/errors.h"
#include "nvim/fileio.h"
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
@@ -565,7 +566,8 @@ static int mf_write(memfile_T *mfp, bhdr_T *hp)
bhdr_T *hp2;
unsigned page_count; // number of pages written
- if (mfp->mf_fd < 0) { // there is no file, can't write
+ if (mfp->mf_fd < 0 && !mfp->mf_reopen) {
+ // there is no file and there was no file, can't write
return FAIL;
}
@@ -592,28 +594,48 @@ static int mf_write(memfile_T *mfp, bhdr_T *hp)
// TODO(elmart): Check (page_size * nr) within off_T bounds.
off_T offset = (off_T)(page_size * nr); // offset in the file
- if (vim_lseek(mfp->mf_fd, offset, SEEK_SET) != offset) {
- PERROR(_("E296: Seek error in swap file write"));
- return FAIL;
- }
if (hp2 == NULL) { // freed block, fill with dummy data
page_count = 1;
} else {
page_count = hp2->bh_page_count;
}
unsigned size = page_size * page_count; // number of bytes written
- void *data = (hp2 == NULL) ? hp->bh_data : hp2->bh_data;
- if ((unsigned)write_eintr(mfp->mf_fd, data, size) != size) {
- /// Avoid repeating the error message, this mostly happens when the
- /// disk is full. We give the message again only after a successful
- /// write or when hitting a key. We keep on trying, in case some
- /// space becomes available.
- if (!did_swapwrite_msg) {
- emsg(_("E297: Write error in swap file"));
+
+ for (int attempt = 1; attempt <= 2; attempt++) {
+ if (mfp->mf_fd >= 0) {
+ if (vim_lseek(mfp->mf_fd, offset, SEEK_SET) != offset) {
+ PERROR(_("E296: Seek error in swap file write"));
+ return FAIL;
+ }
+ void *data = (hp2 == NULL) ? hp->bh_data : hp2->bh_data;
+ if ((unsigned)write_eintr(mfp->mf_fd, data, size) == size) {
+ break;
+ }
+ }
+
+ if (attempt == 1) {
+ // If the swap file is on a network drive, and the network
+ // gets disconnected and then re-connected, we can maybe fix it
+ // by closing and then re-opening the file.
+ if (mfp->mf_fd >= 0) {
+ close(mfp->mf_fd);
+ }
+ mfp->mf_fd = os_open(mfp->mf_fname, mfp->mf_flags, S_IREAD | S_IWRITE);
+ mfp->mf_reopen = (mfp->mf_fd < 0);
+ }
+ if (attempt == 2 || mfp->mf_fd < 0) {
+ // Avoid repeating the error message, this mostly happens when the
+ // disk is full. We give the message again only after a successful
+ // write or when hitting a key. We keep on trying, in case some
+ // space becomes available.
+ if (!did_swapwrite_msg) {
+ emsg(_("E297: Write error in swap file"));
+ }
+ did_swapwrite_msg = true;
+ return FAIL;
}
- did_swapwrite_msg = true;
- return FAIL;
}
+
did_swapwrite_msg = false;
if (hp2 != NULL) { // written a non-dummy block
hp2->bh_flags &= ~BH_DIRTY;
@@ -750,7 +772,9 @@ static bool mf_do_open(memfile_T *mfp, char *fname, int flags)
emsg(_("E300: Swap file already exists (symlink attack?)"));
} else {
// try to open the file
- mfp->mf_fd = os_open(mfp->mf_fname, flags | O_NOFOLLOW, S_IREAD | S_IWRITE);
+ flags |= O_NOFOLLOW;
+ mfp->mf_flags = flags;
+ mfp->mf_fd = os_open(mfp->mf_fname, flags, S_IREAD | S_IWRITE);
}
// If the file cannot be opened, use memory only
diff --git a/src/nvim/memfile_defs.h b/src/nvim/memfile_defs.h
index 5caf13c9fb..5f2468c947 100644
--- a/src/nvim/memfile_defs.h
+++ b/src/nvim/memfile_defs.h
@@ -46,6 +46,8 @@ typedef struct {
char *mf_fname; ///< name of the file
char *mf_ffname; ///< idem, full path
int mf_fd; ///< file descriptor
+ int mf_flags; ///< flags used when opening this memfile
+ bool mf_reopen; ///< mf_fd was closed, retry opening
bhdr_T *mf_free_first; ///< first block header in free list
/// The used blocks are kept in mf_hash.
diff --git a/src/nvim/memline.c b/src/nvim/memline.c
index 5acf4f0c37..92e2ef2b55 100644
--- a/src/nvim/memline.c
+++ b/src/nvim/memline.c
@@ -84,7 +84,7 @@
#include "nvim/os/input.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
-#include "nvim/os/process.h"
+#include "nvim/os/proc.h"
#include "nvim/os/time.h"
#include "nvim/os/time_defs.h"
#include "nvim/path.h"
@@ -743,7 +743,7 @@ static void add_b0_fenc(ZeroBlock *b0p, buf_T *buf)
/// @param swap_fname Name of the swapfile. If it's from before a reboot, the result is 0.
///
/// @return PID, or 0 if process is not running or the swapfile is from before a reboot.
-static int swapfile_process_running(const ZeroBlock *b0p, const char *swap_fname)
+static int swapfile_proc_running(const ZeroBlock *b0p, const char *swap_fname)
{
FileInfo st;
double uptime;
@@ -905,12 +905,9 @@ void ml_recover(bool checkext)
msg_end();
goto theend;
}
- off_T size;
- if ((size = vim_lseek(mfp->mf_fd, 0, SEEK_END)) <= 0) {
- mfp->mf_blocknr_max = 0; // no file or empty file
- } else {
- mfp->mf_blocknr_max = size / mfp->mf_page_size;
- }
+ off_T size = vim_lseek(mfp->mf_fd, 0, SEEK_END);
+ // 0 means no file or empty file
+ mfp->mf_blocknr_max = size <= 0 ? 0 : size / mfp->mf_page_size;
mfp->mf_infile_count = mfp->mf_blocknr_max;
// need to reallocate the memory used to store the data
@@ -1217,7 +1214,7 @@ void ml_recover(bool checkext)
msg(_("Recovery completed. Buffer contents equals file contents."), 0);
}
msg_puts(_("\nYou may want to delete the .swp file now."));
- if (swapfile_process_running(b0p, fname_used)) {
+ if (swapfile_proc_running(b0p, fname_used)) {
// Warn there could be an active Vim on the same file, the user may
// want to kill it.
msg_puts(_("\nNote: process STILL RUNNING: "));
@@ -1253,7 +1250,7 @@ theend:
/// with the 'directory' option.
///
/// Used to:
-/// - list the swapfiles for "vim -r"
+/// - list the swapfiles for "nvim -r"
/// - count the number of swapfiles when recovering
/// - list the swapfiles when recovering
/// - list the swapfiles for swapfilelist()
@@ -1325,11 +1322,9 @@ int recover_names(char *fname, bool do_list, list_T *ret_list, int nr, char **fn
} else {
int len = (int)strlen(dir_name);
p = dir_name + len;
- if (after_pathsep(dir_name, p)
- && len > 1
- && p[-1] == p[-2]) {
+ if (after_pathsep(dir_name, p) && len > 1 && p[-1] == p[-2]) {
// Ends with '//', Use Full path for swap name
- tail = make_percent_swname(dir_name, fname_res);
+ tail = make_percent_swname(dir_name, p, fname_res);
} else {
tail = path_tail(fname_res);
tail = concat_fnames(dir_name, tail, true);
@@ -1440,8 +1435,11 @@ int recover_names(char *fname, bool do_list, list_T *ret_list, int nr, char **fn
/// Append the full path to name with path separators made into percent
/// signs, to dir. An unnamed buffer is handled as "" (<currentdir>/"")
-char *make_percent_swname(const char *dir, const char *name)
- FUNC_ATTR_NONNULL_ARG(1)
+/// signs, to "dir". An unnamed buffer is handled as "" (<currentdir>/"")
+/// The last character in "dir" must be an extra slash or backslash, it is
+/// removed.
+char *make_percent_swname(char *dir, char *dir_end, const char *name)
+ FUNC_ATTR_NONNULL_ARG(1, 2)
{
char *d = NULL;
char *f = fix_fname(name != NULL ? name : "");
@@ -1455,6 +1453,8 @@ char *make_percent_swname(const char *dir, const char *name)
*d = '%';
}
}
+
+ dir_end[-1] = NUL; // remove one trailing slash
d = concat_fnames(dir, s, true);
xfree(s);
xfree(f);
@@ -1462,7 +1462,7 @@ char *make_percent_swname(const char *dir, const char *name)
}
// PID of swapfile owner, or zero if not running.
-static int process_running;
+static int proc_running;
/// For Vimscript "swapinfo()".
///
@@ -1488,7 +1488,7 @@ void swapfile_dict(const char *fname, dict_T *d)
tv_dict_add_str_len(d, S_LEN("fname"), b0.b0_fname,
B0_FNAME_SIZE_ORG);
- tv_dict_add_nr(d, S_LEN("pid"), swapfile_process_running(&b0, fname));
+ tv_dict_add_nr(d, S_LEN("pid"), swapfile_proc_running(&b0, fname));
tv_dict_add_nr(d, S_LEN("mtime"), char_to_long(b0.b0_mtime));
tv_dict_add_nr(d, S_LEN("dirty"), b0.b0_dirty ? 1 : 0);
tv_dict_add_nr(d, S_LEN("inode"), char_to_long(b0.b0_ino));
@@ -1572,7 +1572,7 @@ static time_t swapfile_info(char *fname)
if (char_to_long(b0.b0_pid) != 0) {
msg_puts(_("\n process ID: "));
msg_outnum((int)char_to_long(b0.b0_pid));
- if ((process_running = swapfile_process_running(&b0, fname))) {
+ if ((proc_running = swapfile_proc_running(&b0, fname))) {
msg_puts(_(" (STILL RUNNING)"));
}
}
@@ -1640,7 +1640,7 @@ static bool swapfile_unchanged(char *fname)
}
// process must be known and not running.
- if (char_to_long(b0.b0_pid) == 0 || swapfile_process_running(&b0, fname)) {
+ if (char_to_long(b0.b0_pid) == 0 || swapfile_proc_running(&b0, fname)) {
ret = false;
}
@@ -1895,9 +1895,7 @@ errorret:
buf->b_ml.ml_line_lnum = lnum;
return questions;
}
- if (lnum <= 0) { // pretend line 0 is line 1
- lnum = 1;
- }
+ lnum = MAX(lnum, 1); // pretend line 0 is line 1
if (buf->b_ml.ml_mfp == NULL) { // there are no lines
buf->b_ml.ml_line_len = 1;
@@ -2108,12 +2106,8 @@ static int ml_append_int(buf_T *buf, linenr_T lnum, char *line, colnr_T len, boo
if (line_count > db_idx + 1) { // if there are following lines
// Offset is the start of the previous line.
// This will become the character just after the new line.
- int offset;
- if (db_idx < 0) {
- offset = (int)dp->db_txt_end;
- } else {
- offset = ((dp->db_index[db_idx]) & DB_INDEX_MASK);
- }
+ int offset = db_idx < 0 ? (int)dp->db_txt_end
+ : (int)((dp->db_index[db_idx]) & DB_INDEX_MASK);
memmove((char *)dp + dp->db_txt_start,
(char *)dp + dp->db_txt_start + len,
(size_t)offset - (dp->db_txt_start + (size_t)len));
@@ -3192,11 +3186,10 @@ char *makeswapname(char *fname, char *ffname, buf_T *buf, char *dir_name)
int len = (int)strlen(dir_name);
char *s = dir_name + len;
- if (after_pathsep(dir_name, s)
- && len > 1
- && s[-1] == s[-2]) { // Ends with '//', Use Full path
+ if (after_pathsep(dir_name, s) && len > 1 && s[-1] == s[-2]) {
+ // Ends with '//', Use Full path
char *r = NULL;
- s = make_percent_swname(dir_name, fname_res);
+ s = make_percent_swname(dir_name, s, fname_res);
if (s != NULL) {
r = modname(s, ".swp", false);
xfree(s);
@@ -3287,7 +3280,7 @@ static void attention_message(buf_T *buf, char *fname)
" instances of the same\n file when making changes."
" Quit, or continue with caution.\n"));
msg_puts(_("(2) An edit session for this file crashed.\n"));
- msg_puts(_(" If this is the case, use \":recover\" or \"vim -r "));
+ msg_puts(_(" If this is the case, use \":recover\" or \"nvim -r "));
msg_outtrans(buf->b_fname, 0);
msg_puts(_("\"\n to recover the changes (see \":help recovery\").\n"));
msg_puts(_(" If you did this already, delete the swap file \""));
@@ -3406,7 +3399,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
fd = os_open(fname, O_RDONLY, 0);
if (fd >= 0) {
if (read_eintr(fd, &b0, sizeof(b0)) == sizeof(b0)) {
- process_running = swapfile_process_running(&b0, fname);
+ proc_running = swapfile_proc_running(&b0, fname);
// If the swapfile has the same directory as the
// buffer don't compare the directory names, they can
@@ -3466,7 +3459,7 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
choice = SEA_CHOICE_READONLY;
}
- process_running = 0; // Set by attention_message..swapfile_info.
+ proc_running = 0; // Set by attention_message..swapfile_info.
if (choice == SEA_CHOICE_NONE) {
// Show info about the existing swapfile.
attention_message(buf, fname);
@@ -3498,12 +3491,12 @@ static char *findswapname(buf_T *buf, char **dirp, char *old_fname, bool *found_
= do_dialog(VIM_WARNING,
_("VIM - ATTENTION"),
name,
- process_running
+ proc_running
? _("&Open Read-Only\n&Edit anyway\n&Recover\n&Quit\n&Abort")
: _("&Open Read-Only\n&Edit anyway\n&Recover\n&Delete it\n&Quit\n&Abort"),
1, NULL, false);
- if (process_running && dialog_result >= 4) {
+ if (proc_running && dialog_result >= 4) {
// compensate for missing "Delete it" button
dialog_result++;
}
diff --git a/src/nvim/memory.c b/src/nvim/memory.c
index 789535e270..3aa37c047c 100644
--- a/src/nvim/memory.c
+++ b/src/nvim/memory.c
@@ -19,6 +19,7 @@
#include "nvim/context.h"
#include "nvim/decoration_provider.h"
#include "nvim/drawline.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
@@ -198,11 +199,11 @@ void *xmallocz(size_t size)
{
size_t total_size = size + 1;
if (total_size < size) {
- preserve_exit(_("Vim: Data too large to fit into virtual memory space\n"));
+ preserve_exit(_("Nvim: Data too large to fit into virtual memory space\n"));
}
void *ret = xmalloc(total_size);
- ((char *)ret)[size] = '\0';
+ ((char *)ret)[size] = NUL;
return ret;
}
@@ -232,7 +233,7 @@ void *xmemcpyz(void *dst, const void *src, size_t len)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
{
memcpy(dst, src, len);
- ((char *)dst)[len] = '\0';
+ ((char *)dst)[len] = NUL;
return dst;
}
@@ -240,7 +241,7 @@ void *xmemcpyz(void *dst, const void *src, size_t len)
size_t xstrnlen(const char *s, size_t n)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_PURE
{
- const char *end = memchr(s, '\0', n);
+ const char *end = memchr(s, NUL, n);
if (end == NULL) {
return n;
}
@@ -287,7 +288,7 @@ void *xmemscan(const void *addr, char c, size_t size)
void strchrsub(char *str, char c, char x)
FUNC_ATTR_NONNULL_ALL
{
- assert(c != '\0');
+ assert(c != NUL);
while ((str = strchr(str, c))) {
*str++ = x;
}
@@ -387,7 +388,7 @@ char *xstpcpy(char *restrict dst, const char *restrict src)
char *xstpncpy(char *restrict dst, const char *restrict src, size_t maxlen)
FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
- const char *p = memchr(src, '\0', maxlen);
+ const char *p = memchr(src, NUL, maxlen);
if (p) {
size_t srclen = (size_t)(p - src);
memcpy(dst, src, srclen);
@@ -419,7 +420,7 @@ size_t xstrlcpy(char *restrict dst, const char *restrict src, size_t dsize)
if (dsize) {
size_t len = MIN(slen, dsize - 1);
memcpy(dst, src, len);
- dst[len] = '\0';
+ dst[len] = NUL;
}
return slen; // Does not include NUL.
@@ -449,7 +450,7 @@ size_t xstrlcat(char *const dst, const char *const src, const size_t dsize)
if (slen > dsize - dlen - 1) {
memmove(dst + dlen, src, dsize - dlen - 1);
- dst[dsize - 1] = '\0';
+ dst[dsize - 1] = NUL;
} else {
memmove(dst + dlen, src, slen + 1);
}
@@ -509,7 +510,7 @@ char *xstrndup(const char *str, size_t len)
FUNC_ATTR_MALLOC FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
FUNC_ATTR_NONNULL_ALL
{
- char *p = memchr(str, '\0', len);
+ char *p = memchr(str, NUL, len);
return xmemdupz(str, p ? (size_t)(p - str) : len);
}
@@ -882,7 +883,6 @@ void free_all_mem(void)
decor_free_all_mem();
drawline_free_all_mem();
- input_free_all_mem();
if (ui_client_channel_id) {
ui_client_free_all_mem();
diff --git a/src/nvim/memory.h b/src/nvim/memory.h
index 0788670142..a6ff82e39d 100644
--- a/src/nvim/memory.h
+++ b/src/nvim/memory.h
@@ -45,8 +45,6 @@ EXTERN size_t arena_alloc_count INIT( = 0);
((v).capacity = (s), \
(v).items = (void *)arena_alloc(a, sizeof((v).items[0]) * (v).capacity, true))
-#define ARENA_BLOCK_SIZE 4096
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "memory.h.generated.h"
#endif
@@ -72,5 +70,3 @@ EXTERN size_t arena_alloc_count INIT( = 0);
// Like strcpy() but allows overlapped source and destination.
#define STRMOVE(d, s) memmove((d), (s), strlen(s) + 1)
-
-#define STRCAT(d, s) strcat((char *)(d), (char *)(s)) // NOLINT(runtime/printf)
diff --git a/src/nvim/memory_defs.h b/src/nvim/memory_defs.h
index bde0e54f54..df271ceca5 100644
--- a/src/nvim/memory_defs.h
+++ b/src/nvim/memory_defs.h
@@ -11,5 +11,7 @@ typedef struct {
size_t pos, size;
} Arena;
+#define ARENA_BLOCK_SIZE 4096
+
// inits an empty arena.
#define ARENA_EMPTY { .cur_blk = NULL, .pos = 0, .size = 0 }
diff --git a/src/nvim/menu.c b/src/nvim/menu.c
index ab28eeca1c..c33d3cc712 100644
--- a/src/nvim/menu.c
+++ b/src/nvim/menu.c
@@ -13,6 +13,7 @@
#include "nvim/charset.h"
#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -1056,7 +1057,7 @@ char *get_menu_names(expand_T *xp, int idx)
}
// hack on menu separators: use a 'magic' char for the separator
// so that '.' in names gets escaped properly
- STRCAT(tbuffer, "\001");
+ strcat(tbuffer, "\001");
str = tbuffer;
} else {
if (should_advance) {
diff --git a/src/nvim/message.c b/src/nvim/message.c
index 10b90bde29..79e6bc8be7 100644
--- a/src/nvim/message.c
+++ b/src/nvim/message.c
@@ -18,6 +18,7 @@
#include "nvim/channel.h"
#include "nvim/charset.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -445,9 +446,7 @@ void trunc_string(const char *s, char *buf, int room_in, int buflen)
// Last part: End of the string.
half = i = (int)strlen(s);
while (true) {
- do {
- half = half - utf_head_off(s, s + half - 1) - 1;
- } while (half > 0 && utf_iscomposing(utf_ptr2char(s + half)));
+ half = half - utf_head_off(s, s + half - 1) - 1;
n = ptr2cells(s + half);
if (len + n > room || half == 0) {
break;
@@ -1383,11 +1382,7 @@ void msgmore(int n)
return;
}
- if (n > 0) {
- pn = n;
- } else {
- pn = -n;
- }
+ pn = abs(n);
if (pn > p_report) {
if (n > 0) {
@@ -1425,9 +1420,7 @@ void msg_start(void)
{
bool did_return = false;
- if (msg_row < cmdline_row) {
- msg_row = cmdline_row;
- }
+ msg_row = MAX(msg_row, cmdline_row);
if (!msg_silent) {
XFREE_CLEAR(keep_msg); // don't display old message now
@@ -2689,7 +2682,7 @@ static void msg_puts_printf(const char *str, const ptrdiff_t maxlen)
*p++ = '\r';
}
memcpy(p, s, (size_t)len);
- *(p + len) = '\0';
+ *(p + len) = NUL;
if (info_message) {
printf("%s", buf);
} else {
@@ -3381,9 +3374,7 @@ void msg_advance(int col)
}
return;
}
- if (col >= Columns) { // not enough room
- col = Columns - 1;
- }
+ col = MIN(col, Columns - 1); // not enough room
while (msg_col < col) {
msg_putchar(' ');
}
diff --git a/src/nvim/mouse.c b/src/nvim/mouse.c
index f393b0fd0f..884bc88d73 100644
--- a/src/nvim/mouse.c
+++ b/src/nvim/mouse.c
@@ -194,7 +194,11 @@ static void call_click_def_func(StlClickDefinition *click_defs, int col, int whi
? "r"
: (which_button == MOUSE_MIDDLE
? "m"
- : "?")))
+ : (which_button == MOUSE_X1
+ ? "x1"
+ : (which_button == MOUSE_X2
+ ? "x2"
+ : "?")))))
},
},
{
@@ -250,11 +254,17 @@ static int get_fpos_of_mouse(pos_T *mpos)
}
// winpos and height may change in win_enter()!
- if (winrow >= wp->w_height_inner) { // In (or below) status line
+ if (winrow >= wp->w_height_inner + wp->w_status_height) { // Below window
+ if (mouse_grid <= 1 && mouse_row < Rows - p_ch
+ && mouse_row >= Rows - p_ch - global_stl_height()) { // In global status line
+ return IN_STATUS_LINE;
+ }
+ return IN_UNKNOWN;
+ } else if (winrow >= wp->w_height_inner) { // In window status line
return IN_STATUS_LINE;
}
- if (winrow < 0 && winrow + wp->w_winbar_height >= 0) {
+ if (winrow < 0 && winrow + wp->w_winbar_height >= 0) { // In winbar
return MOUSE_WINBAR;
}
@@ -688,6 +698,9 @@ popupexit:
if (in_statuscol && wp->w_p_rl) {
click_col = wp->w_width_inner - click_col - 1;
}
+ if (in_statuscol && click_col >= (int)wp->w_statuscol_click_defs_size) {
+ return false;
+ }
if (click_defs != NULL) {
switch (click_defs[click_col].type) {
@@ -1041,9 +1054,7 @@ void do_mousescroll(cmdarg_T *cap)
// Horizontal scrolling
int step = shift_or_ctrl ? curwin->w_width_inner : (int)p_mousescroll_hor;
colnr_T leftcol = curwin->w_leftcol + (cap->arg == MSCR_RIGHT ? -step : +step);
- if (leftcol < 0) {
- leftcol = 0;
- }
+ leftcol = MAX(leftcol, 0);
do_mousescroll_horiz(leftcol);
}
}
@@ -1610,11 +1621,8 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump)
while (row > 0) {
// Don't include filler lines in "count"
if (win_may_fill(win)) {
- if (lnum == win->w_topline) {
- row -= win->w_topfill;
- } else {
- row -= win_get_fill(win, lnum);
- }
+ row -= lnum == win->w_topline ? win->w_topfill
+ : win_get_fill(win, lnum);
count = plines_win_nofill(win, lnum, false);
} else {
count = plines_win(win, lnum, false);
@@ -1655,9 +1663,7 @@ bool mouse_comp_pos(win_T *win, int *rowp, int *colp, linenr_T *lnump)
if (!retval) {
// Compute the column without wrapping.
int off = win_col_off(win) - win_col_off2(win);
- if (col < off) {
- col = off;
- }
+ col = MAX(col, off);
col += row * (win->w_width_inner - off);
// Add skip column for the topline.
@@ -1672,9 +1678,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) {
- col = 0;
- }
+ col = MAX(col, 0);
*colp = col;
*rowp = row;
diff --git a/src/nvim/move.c b/src/nvim/move.c
index 418ece09ed..6324466dcc 100644
--- a/src/nvim/move.c
+++ b/src/nvim/move.c
@@ -20,6 +20,7 @@
#include "nvim/diff.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/window.h"
#include "nvim/fold.h"
@@ -152,7 +153,6 @@ static void redraw_for_cursorline(win_T *wp)
/// Redraw when w_virtcol changes and
/// - 'cursorcolumn' is set, or
/// - 'cursorlineopt' contains "screenline", or
-/// - "CurSearch" highlight is in use, or
/// - 'concealcursor' is active, or
/// - Visual mode is active.
static void redraw_for_cursorcolumn(win_T *wp)
@@ -172,10 +172,8 @@ static void redraw_for_cursorcolumn(win_T *wp)
return;
}
- if (wp->w_p_cuc
- || (win_hl_attr(wp, HLF_LC) != win_hl_attr(wp, HLF_L) && using_hlsearch())) {
- // When 'cursorcolumn' is set or "CurSearch" is in use
- // need to redraw with UPD_SOME_VALID.
+ if (wp->w_p_cuc) {
+ // When 'cursorcolumn' is set need to redraw with UPD_SOME_VALID.
redraw_later(wp, UPD_SOME_VALID);
} else if (wp->w_p_cul && (wp->w_p_culopt_flags & CULOPT_SCRLINE)) {
// When 'cursorlineopt' contains "screenline" need to redraw with UPD_VALID.
@@ -896,9 +894,7 @@ void curs_columns(win_T *wp, int may_scroll)
new_leftcol = wp->w_leftcol + diff;
}
}
- if (new_leftcol < 0) {
- new_leftcol = 0;
- }
+ new_leftcol = MAX(new_leftcol, 0);
if (new_leftcol != (int)wp->w_leftcol) {
wp->w_leftcol = new_leftcol;
win_check_anchored_floats(wp);
@@ -971,11 +967,8 @@ void curs_columns(win_T *wp, int may_scroll)
if (n > plines - wp->w_height_inner + 1) {
n = plines - wp->w_height_inner + 1;
}
- if (n > 0) {
- wp->w_skipcol = width1 + (n - 1) * width2;
- } else {
- wp->w_skipcol = 0;
- }
+ wp->w_skipcol = n > 0 ? width1 + (n - 1) * width2
+ : 0;
} else if (extra == 1) {
// less than 'scrolloff' lines above, decrease skipcol
assert(so <= INT_MAX);
@@ -992,9 +985,7 @@ void curs_columns(win_T *wp, int may_scroll)
while (endcol > wp->w_virtcol) {
endcol -= width2;
}
- if (endcol > wp->w_skipcol) {
- wp->w_skipcol = endcol;
- }
+ wp->w_skipcol = MAX(wp->w_skipcol, endcol);
}
// adjust w_wrow for the changed w_skipcol
@@ -1131,9 +1122,7 @@ void f_screenpos(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
semsg(_(e_invalid_line_number_nr), pos.lnum);
return;
}
- if (pos.col < 0) {
- pos.col = 0;
- }
+ pos.col = MAX(pos.col, 0);
int row = 0;
int scol = 0;
int ccol = 0;
@@ -1425,9 +1414,7 @@ bool scrolldown(win_T *wp, linenr_T line_count, int byfold)
foldAdjustCursor(wp);
coladvance(wp, wp->w_curswant);
}
- if (wp->w_cursor.lnum < wp->w_topline) {
- wp->w_cursor.lnum = wp->w_topline;
- }
+ wp->w_cursor.lnum = MAX(wp->w_cursor.lnum, wp->w_topline);
return moved;
}
@@ -1508,12 +1495,8 @@ bool scrollup(win_T *wp, linenr_T line_count, bool byfold)
wp->w_botline += line_count; // approximate w_botline
}
- if (wp->w_topline > wp->w_buffer->b_ml.ml_line_count) {
- wp->w_topline = wp->w_buffer->b_ml.ml_line_count;
- }
- if (wp->w_botline > wp->w_buffer->b_ml.ml_line_count + 1) {
- wp->w_botline = wp->w_buffer->b_ml.ml_line_count + 1;
- }
+ wp->w_topline = MIN(wp->w_topline, wp->w_buffer->b_ml.ml_line_count);
+ wp->w_botline = MIN(wp->w_botline, wp->w_buffer->b_ml.ml_line_count + 1);
check_topfill(wp, false);
@@ -1625,9 +1608,7 @@ void check_topfill(win_T *wp, bool down)
wp->w_topfill = 0;
} else {
wp->w_topfill = wp->w_height_inner - n;
- if (wp->w_topfill < 0) {
- wp->w_topfill = 0;
- }
+ wp->w_topfill = MAX(wp->w_topfill, 0);
}
}
}
@@ -1851,15 +1832,11 @@ void scroll_cursor_top(win_T *wp, int min_scroll, int always)
if (new_topline < wp->w_topline || always) {
wp->w_topline = new_topline;
}
- if (wp->w_topline > wp->w_cursor.lnum) {
- wp->w_topline = wp->w_cursor.lnum;
- }
+ wp->w_topline = MIN(wp->w_topline, wp->w_cursor.lnum);
wp->w_topfill = win_get_fill(wp, wp->w_topline);
if (wp->w_topfill > 0 && extra > off) {
wp->w_topfill -= extra - off;
- if (wp->w_topfill < 0) {
- wp->w_topfill = 0;
- }
+ wp->w_topfill = MAX(wp->w_topfill, 0);
}
check_topfill(wp, false);
if (wp->w_topline != old_topline) {
@@ -2278,18 +2255,14 @@ void cursor_correct(win_T *wp)
if (wp->w_topline == 1) {
above_wanted = 0;
int max_off = wp->w_height_inner / 2;
- if (below_wanted > max_off) {
- below_wanted = max_off;
- }
+ below_wanted = MIN(below_wanted, max_off);
}
validate_botline(wp);
if (wp->w_botline == wp->w_buffer->b_ml.ml_line_count + 1
&& mouse_dragging == 0) {
below_wanted = 0;
int max_off = (wp->w_height_inner - 1) / 2;
- if (above_wanted > max_off) {
- above_wanted = max_off;
- }
+ above_wanted = MIN(above_wanted, max_off);
}
// If there are sufficient file-lines above and below the cursor, we can
@@ -2520,9 +2493,11 @@ int pagescroll(Direction dir, int count, bool half)
? MAX(1, (int)p_window - 2) : get_scroll_overlap(dir));
nochange = scroll_with_sms(dir, count, &count);
- // Place cursor at top or bottom of window.
- validate_botline(curwin);
- curwin->w_cursor.lnum = (dir == FORWARD ? curwin->w_topline : curwin->w_botline - 1);
+ if (!nochange) {
+ // Place cursor at top or bottom of window.
+ validate_botline(curwin);
+ curwin->w_cursor.lnum = (dir == FORWARD ? curwin->w_topline : curwin->w_botline - 1);
+ }
}
if (get_scrolloff_value(curwin) > 0) {
diff --git a/src/nvim/msgpack_rpc/channel.c b/src/nvim/msgpack_rpc/channel.c
index 5737a0440f..626312b666 100644
--- a/src/nvim/msgpack_rpc/channel.c
+++ b/src/nvim/msgpack_rpc/channel.c
@@ -1,8 +1,5 @@
#include <assert.h>
#include <inttypes.h>
-#include <msgpack/object.h>
-#include <msgpack/sbuffer.h>
-#include <msgpack/unpack.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
@@ -17,7 +14,7 @@
#include "nvim/event/defs.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
#include "nvim/event/rstream.h"
#include "nvim/event/wstream.h"
#include "nvim/globals.h"
@@ -31,8 +28,6 @@
#include "nvim/msgpack_rpc/packer.h"
#include "nvim/msgpack_rpc/unpacker.h"
#include "nvim/os/input.h"
-#include "nvim/rbuffer.h"
-#include "nvim/rbuffer_defs.h"
#include "nvim/types_defs.h"
#include "nvim/ui.h"
#include "nvim/ui_client.h"
@@ -85,11 +80,11 @@ void rpc_start(Channel *channel)
rpc->unpacker = xcalloc(1, sizeof *rpc->unpacker);
unpacker_init(rpc->unpacker);
rpc->next_request_id = 1;
- rpc->info = (Dictionary)ARRAY_DICT_INIT;
+ rpc->info = (Dict)ARRAY_DICT_INIT;
kv_init(rpc->call_stack);
if (channel->streamtype != kChannelStreamInternal) {
- Stream *out = channel_outstream(channel);
+ RStream *out = channel_outstream(channel);
#ifdef NVIM_LOG_DEBUG
Stream *in = channel_instream(channel);
DLOG("rpc ch %" PRIu64 " in-stream=%p out-stream=%p", channel->id,
@@ -202,10 +197,25 @@ Object rpc_send_call(uint64_t id, const char *method_name, Array args, ArenaMem
return frame.errored ? NIL : frame.result;
}
-static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data, bool eof)
+static size_t receive_msgpack(RStream *stream, const char *rbuf, size_t c, void *data, bool eof)
{
Channel *channel = data;
channel_incref(channel);
+ size_t consumed = 0;
+
+ DLOG("ch %" PRIu64 ": parsing %zu bytes from msgpack Stream: %p",
+ channel->id, c, (void *)stream);
+
+ if (c > 0) {
+ Unpacker *p = channel->rpc.unpacker;
+ p->read_ptr = rbuf;
+ p->read_size = c;
+ parse_msgpack(channel);
+
+ if (!unpacker_closed(p)) {
+ consumed = c - p->read_size;
+ }
+ }
if (eof) {
channel_close(channel->id, kChannelPartRpc, NULL);
@@ -213,25 +223,10 @@ static void receive_msgpack(Stream *stream, RBuffer *rbuf, size_t c, void *data,
snprintf(buf, sizeof(buf), "ch %" PRIu64 " was closed by the client",
channel->id);
chan_close_with_error(channel, buf, LOGLVL_INF);
- goto end;
- }
-
- DLOG("ch %" PRIu64 ": parsing %zu bytes from msgpack Stream: %p",
- channel->id, rbuffer_size(rbuf), (void *)stream);
-
- Unpacker *p = channel->rpc.unpacker;
- size_t size = 0;
- p->read_ptr = rbuffer_read_ptr(rbuf, &size);
- p->read_size = size;
- parse_msgpack(channel);
-
- if (!unpacker_closed(p)) {
- size_t consumed = size - p->read_size;
- rbuffer_consumed_compact(rbuf, consumed);
}
-end:
channel_decref(channel);
+ return consumed;
}
static ChannelCallFrame *find_call_frame(RpcState *rpc, uint32_t request_id)
@@ -505,7 +500,7 @@ void rpc_free(Channel *channel)
xfree(channel->rpc.unpacker);
kv_destroy(channel->rpc.call_stack);
- api_free_dictionary(channel->rpc.info);
+ api_free_dict(channel->rpc.info);
}
static void chan_close_with_error(Channel *channel, char *msg, int loglevel)
@@ -592,16 +587,16 @@ static void packer_buffer_init_channels(Channel **chans, size_t nchans, PackerBu
packer->endptr = packer->startptr + ARENA_BLOCK_SIZE;
packer->packer_flush = channel_flush_callback;
packer->anydata = chans;
- packer->anylen = nchans;
+ packer->anyint = (int64_t)nchans;
}
static void packer_buffer_finish_channels(PackerBuffer *packer)
{
size_t len = (size_t)(packer->ptr - packer->startptr);
if (len > 0) {
- WBuffer *buf = wstream_new_buffer(packer->startptr, len, packer->anylen, free_block);
+ WBuffer *buf = wstream_new_buffer(packer->startptr, len, (size_t)packer->anyint, free_block);
Channel **chans = packer->anydata;
- for (size_t i = 0; i < packer->anylen; i++) {
+ for (int64_t i = 0; i < packer->anyint; i++) {
channel_write(chans[i], buf);
}
} else {
@@ -612,17 +607,17 @@ static void packer_buffer_finish_channels(PackerBuffer *packer)
static void channel_flush_callback(PackerBuffer *packer)
{
packer_buffer_finish_channels(packer);
- packer_buffer_init_channels(packer->anydata, packer->anylen, packer);
+ packer_buffer_init_channels(packer->anydata, (size_t)packer->anyint, packer);
}
-void rpc_set_client_info(uint64_t id, Dictionary info)
+void rpc_set_client_info(uint64_t id, Dict info)
{
Channel *chan = find_rpc_channel(id);
if (!chan) {
abort();
}
- api_free_dictionary(chan->rpc.info);
+ api_free_dict(chan->rpc.info);
chan->rpc.info = info;
// Parse "type" on "info" and set "client_type"
@@ -646,9 +641,9 @@ void rpc_set_client_info(uint64_t id, Dictionary info)
channel_info_changed(chan, false);
}
-Dictionary rpc_client_info(Channel *chan)
+Dict rpc_client_info(Channel *chan)
{
- return copy_dictionary(chan->rpc.info, NULL);
+ return copy_dict(chan->rpc.info, NULL);
}
const char *get_client_info(Channel *chan, const char *key)
@@ -657,7 +652,7 @@ const char *get_client_info(Channel *chan, const char *key)
if (!chan->is_rpc) {
return NULL;
}
- Dictionary info = chan->rpc.info;
+ Dict info = chan->rpc.info;
for (size_t i = 0; i < info.size; i++) {
if (strequal(key, info.items[i].key.data)
&& info.items[i].value.type == kObjectTypeString) {
diff --git a/src/nvim/msgpack_rpc/channel.h b/src/nvim/msgpack_rpc/channel.h
index ff73a2fc53..abf1ce7bf6 100644
--- a/src/nvim/msgpack_rpc/channel.h
+++ b/src/nvim/msgpack_rpc/channel.h
@@ -12,7 +12,7 @@
/// HACK: os/input.c drains this queue immediately before blocking for input.
/// Events on this queue are async-safe, but they need the resolved state
-/// of os_inchar(), so they are processed "just-in-time".
+/// of input_get(), so they are processed "just-in-time".
EXTERN MultiQueue *ch_before_blocking_events INIT( = NULL);
#ifdef INCLUDE_GENERATED_DECLARATIONS
diff --git a/src/nvim/msgpack_rpc/channel_defs.h b/src/nvim/msgpack_rpc/channel_defs.h
index 7dc1374964..871d4e615f 100644
--- a/src/nvim/msgpack_rpc/channel_defs.h
+++ b/src/nvim/msgpack_rpc/channel_defs.h
@@ -1,6 +1,5 @@
#pragma once
-#include <msgpack.h>
#include <stdbool.h>
#include <uv.h>
@@ -41,6 +40,6 @@ typedef struct {
Unpacker *unpacker;
uint32_t next_request_id;
kvec_t(ChannelCallFrame *) call_stack;
- Dictionary info;
+ Dict info;
ClientType client_type;
} RpcState;
diff --git a/src/nvim/msgpack_rpc/packer.c b/src/nvim/msgpack_rpc/packer.c
index cac68f76f0..b739f7ba28 100644
--- a/src/nvim/msgpack_rpc/packer.c
+++ b/src/nvim/msgpack_rpc/packer.c
@@ -8,10 +8,9 @@
# include "msgpack_rpc/packer.c.generated.h"
#endif
-static void check_buffer(PackerBuffer *packer)
+void mpack_check_buffer(PackerBuffer *packer)
{
- ptrdiff_t remaining = packer->endptr - packer->ptr;
- if (remaining < MPACK_ITEM_SIZE) {
+ if (mpack_remaining(packer) < 2 * MPACK_ITEM_SIZE) {
packer->packer_flush(packer);
}
}
@@ -28,15 +27,20 @@ static void mpack_w8(char **b, const char *data)
#endif
}
+void mpack_uint64(char **ptr, uint64_t i)
+{
+ if (i > 0xfffffff) {
+ mpack_w(ptr, 0xcf);
+ mpack_w8(ptr, (char *)&i);
+ } else {
+ mpack_uint(ptr, (uint32_t)i);
+ }
+}
+
void mpack_integer(char **ptr, Integer i)
{
if (i >= 0) {
- if (i > 0xfffffff) {
- mpack_w(ptr, 0xcf);
- mpack_w8(ptr, (char *)&i);
- } else {
- mpack_uint(ptr, (uint32_t)i);
- }
+ mpack_uint64(ptr, (uint64_t)i);
} else {
if (i < -0x80000000LL) {
mpack_w(ptr, 0xd3);
@@ -80,11 +84,35 @@ void mpack_str(String str, PackerBuffer *packer)
abort();
}
+ mpack_raw(str.data, len, packer);
+}
+
+void mpack_bin(String str, PackerBuffer *packer)
+{
+ const size_t len = str.size;
+ if (len < 0xff) {
+ mpack_w(&packer->ptr, 0xc4);
+ mpack_w(&packer->ptr, len);
+ } else if (len < 0xffff) {
+ mpack_w(&packer->ptr, 0xc5);
+ mpack_w2(&packer->ptr, (uint32_t)len);
+ } else if (len < 0xffffffff) {
+ mpack_w(&packer->ptr, 0xc6);
+ mpack_w4(&packer->ptr, (uint32_t)len);
+ } else {
+ abort();
+ }
+
+ mpack_raw(str.data, len, packer);
+}
+
+void mpack_raw(const char *data, size_t len, PackerBuffer *packer)
+{
size_t pos = 0;
while (pos < len) {
ptrdiff_t remaining = packer->endptr - packer->ptr;
size_t to_copy = MIN(len - pos, (size_t)remaining);
- memcpy(packer->ptr, str.data + pos, to_copy);
+ memcpy(packer->ptr, data + pos, to_copy);
packer->ptr += to_copy;
pos += to_copy;
@@ -92,6 +120,28 @@ void mpack_str(String str, PackerBuffer *packer)
packer->packer_flush(packer);
}
}
+ mpack_check_buffer(packer);
+}
+
+void mpack_ext(char *buf, size_t len, int8_t type, PackerBuffer *packer)
+{
+ if (len == 1) {
+ mpack_w(&packer->ptr, 0xd4);
+ } else if (len == 2) {
+ mpack_w(&packer->ptr, 0xd5);
+ } else if (len <= 0xff) {
+ mpack_w(&packer->ptr, 0xc7);
+ } else if (len < 0xffff) {
+ mpack_w(&packer->ptr, 0xc8);
+ mpack_w2(&packer->ptr, (uint32_t)len);
+ } else if (len < 0xffffffff) {
+ mpack_w(&packer->ptr, 0xc9);
+ mpack_w4(&packer->ptr, (uint32_t)len);
+ } else {
+ abort();
+ }
+ mpack_w(&packer->ptr, type);
+ mpack_raw(buf, len, packer);
}
void mpack_handle(ObjectType type, handle_T handle, PackerBuffer *packer)
@@ -113,7 +163,6 @@ void mpack_handle(ObjectType type, handle_T handle, PackerBuffer *packer)
mpack_w(&packer->ptr, 0xc7);
mpack_w(&packer->ptr, packsize);
mpack_w(&packer->ptr, exttype);
- // check_buffer(packer);
memcpy(packer->ptr, buf, (size_t)packsize);
packer->ptr += packsize;
}
@@ -148,7 +197,7 @@ void mpack_object_inner(Object *current, Object *container, size_t container_idx
kvi_init(stack);
while (true) {
- check_buffer(packer);
+ mpack_check_buffer(packer);
switch (current->type) {
case kObjectTypeLuaRef:
// TODO(bfredl): could also be an error. Though kObjectTypeLuaRef
@@ -177,14 +226,14 @@ void mpack_object_inner(Object *current, Object *container, size_t container_idx
case kObjectTypeTabpage:
mpack_handle(current->type, (handle_T)current->data.integer, packer);
break;
- case kObjectTypeDictionary:
+ case kObjectTypeDict:
case kObjectTypeArray: {}
size_t current_size;
if (current->type == kObjectTypeArray) {
current_size = current->data.array.size;
mpack_array(&packer->ptr, (uint32_t)current_size);
} else {
- current_size = current->data.dictionary.size;
+ current_size = current->data.dict.size;
mpack_map(&packer->ptr, (uint32_t)current_size);
}
if (current_size > 0) {
@@ -221,9 +270,9 @@ void mpack_object_inner(Object *current, Object *container, size_t container_idx
container = NULL;
}
} else {
- Dictionary dict = container->data.dictionary;
+ Dict dict = container->data.dict;
KeyValuePair *it = &dict.items[container_idx++];
- check_buffer(packer);
+ mpack_check_buffer(packer);
mpack_str(it->key, packer);
current = &it->value;
if (container_idx >= dict.size) {
@@ -233,3 +282,32 @@ void mpack_object_inner(Object *current, Object *container, size_t container_idx
}
kvi_destroy(stack);
}
+
+PackerBuffer packer_string_buffer(void)
+{
+ const size_t initial_size = 64; // must be larger than SHADA_MPACK_FREE_SPACE
+ char *alloc = xmalloc(initial_size);
+ return (PackerBuffer) {
+ .startptr = alloc,
+ .ptr = alloc,
+ .endptr = alloc + initial_size,
+ .packer_flush = flush_string_buffer,
+ };
+}
+
+static void flush_string_buffer(PackerBuffer *buffer)
+{
+ size_t current_capacity = (size_t)(buffer->endptr - buffer->startptr);
+ size_t new_capacity = 2 * current_capacity;
+ size_t len = (size_t)(buffer->ptr - buffer->startptr);
+
+ buffer->startptr = xrealloc(buffer->startptr, new_capacity);
+ buffer->ptr = buffer->startptr + len;
+ buffer->endptr = buffer->startptr + new_capacity;
+}
+
+/// can only be used with a PackerBuffer from `packer_string_buffer`
+String packer_take_string(PackerBuffer *buffer)
+{
+ return (String){ .data = buffer->startptr, .size = (size_t)(buffer->ptr - buffer->startptr) };
+}
diff --git a/src/nvim/msgpack_rpc/packer.h b/src/nvim/msgpack_rpc/packer.h
index 8117bd09bd..299962bab4 100644
--- a/src/nvim/msgpack_rpc/packer.h
+++ b/src/nvim/msgpack_rpc/packer.h
@@ -71,6 +71,11 @@ static inline void mpack_map(char **buf, uint32_t len)
}
}
+static inline size_t mpack_remaining(PackerBuffer *packer)
+{
+ return (size_t)(packer->endptr - packer->ptr);
+}
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "msgpack_rpc/packer.h.generated.h"
#endif
diff --git a/src/nvim/msgpack_rpc/packer_defs.h b/src/nvim/msgpack_rpc/packer_defs.h
index 420f3dc424..95d86caaab 100644
--- a/src/nvim/msgpack_rpc/packer_defs.h
+++ b/src/nvim/msgpack_rpc/packer_defs.h
@@ -19,6 +19,6 @@ struct packer_buffer_t {
// these are free to be used by packer_flush for any purpose, if want
void *anydata;
- size_t anylen;
+ int64_t anyint;
PackerBufferFlush packer_flush;
};
diff --git a/src/nvim/msgpack_rpc/server.c b/src/nvim/msgpack_rpc/server.c
index 56b03d67d0..462f8397f4 100644
--- a/src/nvim/msgpack_rpc/server.c
+++ b/src/nvim/msgpack_rpc/server.c
@@ -4,18 +4,21 @@
#include <string.h>
#include <uv.h>
+#include "nvim/ascii_defs.h"
#include "nvim/channel.h"
#include "nvim/eval.h"
#include "nvim/event/defs.h"
#include "nvim/event/socket.h"
#include "nvim/garray.h"
#include "nvim/garray_defs.h"
+#include "nvim/globals.h"
#include "nvim/log.h"
#include "nvim/main.h"
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/server.h"
#include "nvim/os/os.h"
#include "nvim/os/stdpaths_defs.h"
+#include "nvim/types_defs.h"
#define MAX_CONNECTIONS 32
#define ENV_LISTEN "NVIM_LISTEN_ADDRESS" // deprecated
@@ -26,37 +29,61 @@ static garray_T watchers = GA_EMPTY_INIT_VALUE;
# include "msgpack_rpc/server.c.generated.h"
#endif
-/// Initializes the module
+/// Initializes resources, handles `--listen`, starts the primary server at v:servername.
+///
+/// @returns true on success, false on fatal error (message stored in IObuff)
bool server_init(const char *listen_addr)
{
+ bool ok = true;
+ bool must_free = false;
+ TriState user_arg = kTrue; // User-provided --listen arg.
ga_init(&watchers, sizeof(SocketWatcher *), 1);
// $NVIM_LISTEN_ADDRESS (deprecated)
- if (!listen_addr && os_env_exists(ENV_LISTEN)) {
+ if ((!listen_addr || listen_addr[0] == '\0') && os_env_exists(ENV_LISTEN)) {
+ user_arg = kFalse; // User-provided env var.
listen_addr = os_getenv(ENV_LISTEN);
}
- int rv = listen_addr ? server_start(listen_addr) : 1;
- if (0 != rv) {
+ if (!listen_addr || listen_addr[0] == '\0') {
+ user_arg = kNone; // Autogenerated server address.
listen_addr = server_address_new(NULL);
- if (!listen_addr) {
- return false;
- }
- rv = server_start(listen_addr);
- xfree((char *)listen_addr);
+ must_free = true;
}
- if (os_env_exists(ENV_LISTEN)) {
- // Unset $NVIM_LISTEN_ADDRESS, it's a liability hereafter.
- os_unsetenv(ENV_LISTEN);
- }
+ int rv = server_start(listen_addr);
- // TODO(justinmk): this is for logging_spec. Can remove this after nvim_log #7062 is merged.
+ // TODO(justinmk): this is for log_spec. Can remove this after nvim_log #7062 is merged.
if (os_env_exists("__NVIM_TEST_LOG")) {
ELOG("test log message");
}
- return rv == 0;
+ if (must_free) {
+ xfree((char *)listen_addr);
+ }
+
+ if (rv == 0 || user_arg == kNone) {
+ // The autogenerated servername can fail if the user has a broken $XDG_RUNTIME_DIR. #30282
+ // But that is not fatal (startup will continue, logged in $NVIM_LOGFILE, empty v:servername).
+ goto end;
+ }
+
+ (void)snprintf(IObuff, IOSIZE,
+ user_arg ==
+ kTrue ? "Failed to --listen: %s: \"%s\""
+ : "Failed $NVIM_LISTEN_ADDRESS: %s: \"%s\"",
+ rv < 0 ? os_strerror(rv) : (rv == 1 ? "empty address" : "?"),
+ listen_addr);
+ ok = false;
+
+end:
+ if (os_env_exists(ENV_LISTEN)) {
+ // Unset $NVIM_LISTEN_ADDRESS, it's a liability hereafter. It is "input only", it must not be
+ // leaked to child jobs or :terminal.
+ os_unsetenv(ENV_LISTEN);
+ }
+
+ return ok;
}
/// Teardown a single server
@@ -87,17 +114,19 @@ void server_teardown(void)
/// - Windows: "\\.\pipe\<name>.<pid>.<counter>"
/// - Other: "/tmp/nvim.user/xxx/<name>.<pid>.<counter>"
char *server_address_new(const char *name)
+ FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
{
static uint32_t count = 0;
char fmt[ADDRESS_MAX_SIZE];
- const char *appname = get_appname();
#ifdef MSWIN
+ (void)get_appname(true);
int r = snprintf(fmt, sizeof(fmt), "\\\\.\\pipe\\%s.%" PRIu64 ".%" PRIu32,
- name ? name : appname, os_get_pid(), count++);
+ name ? name : NameBuff, os_get_pid(), count++);
#else
char *dir = stdpaths_get_xdg_var(kXDGRuntimeDir);
+ (void)get_appname(true);
int r = snprintf(fmt, sizeof(fmt), "%s/%s.%" PRIu64 ".%" PRIu32,
- dir, name ? name : appname, os_get_pid(), count++);
+ dir, name ? name : NameBuff, os_get_pid(), count++);
xfree(dir);
#endif
if ((size_t)r >= sizeof(fmt)) {
@@ -131,7 +160,7 @@ bool server_owns_pipe_address(const char *path)
/// @returns 0: success, 1: validation error, 2: already listening, -errno: failed to bind/listen.
int server_start(const char *addr)
{
- if (addr == NULL || addr[0] == '\0') {
+ if (addr == NULL || addr[0] == NUL) {
WLOG("Empty or NULL address");
return 1;
}
diff --git a/src/nvim/msgpack_rpc/unpacker.c b/src/nvim/msgpack_rpc/unpacker.c
index 28d27e8268..4ddc41e596 100644
--- a/src/nvim/msgpack_rpc/unpacker.c
+++ b/src/nvim/msgpack_rpc/unpacker.c
@@ -11,6 +11,7 @@
#include "nvim/memory.h"
#include "nvim/msgpack_rpc/channel_defs.h"
#include "nvim/msgpack_rpc/unpacker.h"
+#include "nvim/strings.h"
#include "nvim/ui_client.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
@@ -58,7 +59,7 @@ static void api_parse_enter(mpack_parser_t *parser, mpack_node_t *node)
}
case MPACK_TOKEN_MAP: {
Object *obj = parent->data[0].p;
- KeyValuePair *kv = &kv_A(obj->data.dictionary, parent->pos);
+ KeyValuePair *kv = &kv_A(obj->data.dict, parent->pos);
if (!parent->key_visited) {
// TODO(bfredl): when implementing interrupt parse on error,
// stop parsing here when node is not a STR/BIN
@@ -165,10 +166,10 @@ static void api_parse_enter(mpack_parser_t *parser, mpack_node_t *node)
break;
}
case MPACK_TOKEN_MAP: {
- Dictionary dict = KV_INITIAL_VALUE;
+ Dict dict = KV_INITIAL_VALUE;
kv_fixsize_arena(&p->arena, dict, node->tok.length);
kv_size(dict) = node->tok.length;
- *result = DICTIONARY_OBJ(dict);
+ *result = DICT_OBJ(dict);
node->data[0].p = result;
break;
}
@@ -206,6 +207,7 @@ bool unpacker_parse_header(Unpacker *p)
assert(!ERROR_SET(&p->unpack_error));
+ // TODO(bfredl): eliminate p->reader, we can use mpack_rtoken directly
#define NEXT(tok) \
result = mpack_read(&p->reader, &data, &size, &tok); \
if (result) { goto error; }
@@ -522,3 +524,197 @@ bool unpacker_parse_redraw(Unpacker *p)
abort();
}
}
+
+/// Requires a complete string. safe to use e.g. in shada as we have loaded a
+/// complete shada item into a linear buffer.
+///
+/// Data and size are preserved in cause of failure.
+///
+/// @return "data" is NULL only when failure (non-null data and size=0 for
+/// valid empty string)
+String unpack_string(const char **data, size_t *size)
+{
+ const char *data2 = *data;
+ size_t size2 = *size;
+ mpack_token_t tok;
+
+ // TODO(bfredl): this code is hot a f, specialize!
+ int result = mpack_rtoken(&data2, &size2, &tok);
+ if (result || (tok.type != MPACK_TOKEN_STR && tok.type != MPACK_TOKEN_BIN)) {
+ return (String)STRING_INIT;
+ }
+ if (*size < tok.length) {
+ // result = MPACK_EOF;
+ return (String)STRING_INIT;
+ }
+ (*data) = data2 + tok.length;
+ (*size) = size2 - tok.length;
+ return cbuf_as_string((char *)data2, tok.length);
+}
+
+/// @return -1 if not an array or EOF. otherwise size of valid array
+ssize_t unpack_array(const char **data, size_t *size)
+{
+ // TODO(bfredl): this code is hot, specialize!
+ mpack_token_t tok;
+ int result = mpack_rtoken(data, size, &tok);
+ if (result || tok.type != MPACK_TOKEN_ARRAY) {
+ return -1;
+ }
+ return tok.length;
+}
+
+/// does not keep "data" untouched on failure
+bool unpack_integer(const char **data, size_t *size, Integer *res)
+{
+ mpack_token_t tok;
+ int result = mpack_rtoken(data, size, &tok);
+ if (result) {
+ return false;
+ }
+ return unpack_uint_or_sint(tok, res);
+}
+
+bool unpack_uint_or_sint(mpack_token_t tok, Integer *res)
+{
+ if (tok.type == MPACK_TOKEN_UINT) {
+ *res = (Integer)mpack_unpack_uint(tok);
+ return true;
+ } else if (tok.type == MPACK_TOKEN_SINT) {
+ *res = (Integer)mpack_unpack_sint(tok);
+ return true;
+ }
+ return false;
+}
+
+static void parse_nop(mpack_parser_t *parser, mpack_node_t *node)
+{
+}
+
+int unpack_skip(const char **data, size_t *size)
+{
+ mpack_parser_t parser;
+ mpack_parser_init(&parser, 0);
+
+ return mpack_parse(&parser, data, size, parse_nop, parse_nop);
+}
+
+void push_additional_data(AdditionalDataBuilder *ad, const char *data, size_t size)
+{
+ if (kv_size(*ad) == 0) {
+ AdditionalData init = { 0 };
+ kv_concat_len(*ad, &init, sizeof(init));
+ }
+ AdditionalData *a = (AdditionalData *)ad->items;
+ a->nitems++;
+ a->nbytes += (uint32_t)size;
+ kv_concat_len(*ad, data, size);
+}
+
+// currently only used for shada, so not re-entrant like unpacker_parse_redraw
+bool unpack_keydict(void *retval, FieldHashfn hashy, AdditionalDataBuilder *ad, const char **data,
+ size_t *restrict size, char **error)
+{
+ OptKeySet *ks = (OptKeySet *)retval;
+ mpack_token_t tok;
+
+ int result = mpack_rtoken(data, size, &tok);
+ if (result || tok.type != MPACK_TOKEN_MAP) {
+ *error = xstrdup("is not a dict");
+ return false;
+ }
+
+ size_t map_size = tok.length;
+
+ for (size_t i = 0; i < map_size; i++) {
+ const char *item_start = *data;
+ // TODO(bfredl): we could specialize a hot path for FIXSTR here
+ String key = unpack_string(data, size);
+ if (!key.data) {
+ *error = arena_printf(NULL, "has key value which is not a string").data;
+ return false;
+ } else if (key.size == 0) {
+ *error = arena_printf(NULL, "has empty key").data;
+ return false;
+ }
+ KeySetLink *field = hashy(key.data, key.size);
+
+ if (!field) {
+ int status = unpack_skip(data, size);
+ if (status) {
+ return false;
+ }
+
+ if (ad) {
+ push_additional_data(ad, item_start, (size_t)(*data - item_start));
+ }
+ continue;
+ }
+
+ assert(field->opt_index >= 0);
+ uint64_t flag = (1ULL << field->opt_index);
+ if (ks->is_set_ & flag) {
+ *error = xstrdup("duplicate key");
+ return false;
+ }
+ ks->is_set_ |= flag;
+
+ char *mem = ((char *)retval + field->ptr_off);
+ switch (field->type) {
+ case kObjectTypeBoolean:
+ if (*size == 0 || (**data & 0xfe) != 0xc2) {
+ *error = arena_printf(NULL, "has %.*s key value which is not a boolean", (int)key.size,
+ key.data).data;
+ return false;
+ }
+ *(Boolean *)mem = **data & 0x01;
+ (*data)++; (*size)--;
+ break;
+
+ case kObjectTypeInteger:
+ if (!unpack_integer(data, size, (Integer *)mem)) {
+ *error = arena_printf(NULL, "has %.*s key value which is not an integer", (int)key.size,
+ key.data).data;
+ return false;
+ }
+ break;
+
+ case kObjectTypeString: {
+ String val = unpack_string(data, size);
+ if (!val.data) {
+ *error = arena_printf(NULL, "has %.*s key value which is not a binary", (int)key.size,
+ key.data).data;
+ return false;
+ }
+ *(String *)mem = val;
+ break;
+ }
+
+ case kUnpackTypeStringArray: {
+ ssize_t len = unpack_array(data, size);
+ if (len < 0) {
+ *error = arena_printf(NULL, "has %.*s key with non-array value", (int)key.size,
+ key.data).data;
+ return false;
+ }
+ StringArray *a = (StringArray *)mem;
+ kv_ensure_space(*a, (size_t)len);
+ for (size_t j = 0; j < (size_t)len; j++) {
+ String item = unpack_string(data, size);
+ if (!item.data) {
+ *error = arena_printf(NULL, "has %.*s array with non-binary value", (int)key.size,
+ key.data).data;
+ return false;
+ }
+ kv_push(*a, item);
+ }
+ break;
+ }
+
+ default:
+ abort(); // not supported
+ }
+ }
+
+ return true;
+}
diff --git a/src/nvim/msgpack_rpc/unpacker.h b/src/nvim/msgpack_rpc/unpacker.h
index ed55fdd4af..c29462292f 100644
--- a/src/nvim/msgpack_rpc/unpacker.h
+++ b/src/nvim/msgpack_rpc/unpacker.h
@@ -41,6 +41,8 @@ struct Unpacker {
bool has_grid_line_event;
};
+typedef kvec_t(char) AdditionalDataBuilder;
+
// unrecovareble error. unpack_error should be set!
#define unpacker_closed(p) ((p)->state < 0)
diff --git a/src/nvim/normal.c b/src/nvim/normal.c
index 8ba375f29d..be9987cc7f 100644
--- a/src/nvim/normal.c
+++ b/src/nvim/normal.c
@@ -28,12 +28,14 @@
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds2.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_eval.h"
#include "nvim/ex_getln.h"
+#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
#include "nvim/getchar.h"
@@ -349,6 +351,7 @@ static const struct nv_cmd {
{ K_F1, nv_help, NV_NCW, 0 },
{ K_XF1, nv_help, NV_NCW, 0 },
{ K_SELECT, nv_select, 0, 0 },
+ { K_PASTE_START, nv_paste, NV_KEEPREG, 0 },
{ K_EVENT, nv_event, NV_KEEPREG, 0 },
{ K_COMMAND, nv_colon, 0, 0 },
{ K_LUA, nv_colon, 0, 0 },
@@ -835,7 +838,10 @@ static void normal_get_additional_char(NormalState *s)
while ((s->c = vpeekc()) > 0
&& (s->c >= 0x100 || MB_BYTE2LEN(vpeekc()) > 1)) {
s->c = plain_vgetc();
- if (!utf_iscomposing(s->c)) {
+ // TODO(bfredl): only allowing up to two composing chars is cringe af.
+ // Could reuse/abuse schar_T to at least allow us to input anything we are able
+ // to display and use the stateful utf8proc algorithm like utf_composinglike
+ if (!utf_iscomposing_legacy(s->c)) {
vungetc(s->c); // it wasn't, put it back
break;
} else if (s->ca.ncharC1 == 0) {
@@ -1659,7 +1665,6 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text
// When starting on a ']' count it, so that we include the '['.
bn = ptr[col] == ']';
- //
// 2. Back up to start of identifier/text.
//
// Remember class of character under cursor.
@@ -1685,9 +1690,7 @@ size_t find_ident_at_pos(win_T *wp, linenr_T lnum, colnr_T startcol, char **text
// If we don't want just any old text, or we've found an
// identifier, stop searching.
- if (this_class > 2) {
- this_class = 2;
- }
+ this_class = MIN(this_class, 2);
if (!(find_type & FIND_STRING) || this_class == 2) {
break;
}
@@ -1986,7 +1989,7 @@ bool add_to_showcmd(int c)
size_t overflow = old_len + extra_len - limit;
memmove(showcmd_buf, showcmd_buf + overflow, old_len - overflow + 1);
}
- STRCAT(showcmd_buf, p);
+ strcat(showcmd_buf, p);
if (char_avail()) {
return false;
@@ -2011,9 +2014,7 @@ static void del_from_showcmd(int len)
}
int old_len = (int)strlen(showcmd_buf);
- if (len > old_len) {
- len = old_len;
- }
+ len = MIN(len, old_len);
showcmd_buf[old_len - len] = NUL;
if (!char_avail()) {
@@ -2096,17 +2097,23 @@ static void display_showcmd(void)
grid_line_flush();
}
+int get_vtopline(win_T *wp)
+{
+ return plines_m_win_fill(wp, 1, wp->w_topline) - wp->w_topfill;
+}
+
/// When "check" is false, prepare for commands that scroll the window.
/// When "check" is true, take care of scroll-binding after the window has
/// scrolled. Called from normal_cmd() and edit().
void do_check_scrollbind(bool check)
{
static win_T *old_curwin = NULL;
- static linenr_T old_topline = 0;
- static int old_topfill = 0;
+ static linenr_T old_vtopline = 0;
static buf_T *old_buf = NULL;
static colnr_T old_leftcol = 0;
+ int vtopline = get_vtopline(curwin);
+
if (check && curwin->w_p_scb) {
// If a ":syncbind" command was just used, don't scroll, only reset
// the values.
@@ -2119,10 +2126,9 @@ void do_check_scrollbind(bool check)
if ((curwin->w_buffer == old_buf
|| curwin->w_p_diff
)
- && (curwin->w_topline != old_topline
- || curwin->w_topfill != old_topfill
+ && (vtopline != old_vtopline
|| curwin->w_leftcol != old_leftcol)) {
- check_scrollbind(curwin->w_topline - old_topline, curwin->w_leftcol - old_leftcol);
+ check_scrollbind(vtopline - old_vtopline, curwin->w_leftcol - old_leftcol);
}
} else if (vim_strchr(p_sbo, 'j')) { // jump flag set in 'scrollopt'
// When switching between windows, make sure that the relative
@@ -2133,14 +2139,13 @@ void do_check_scrollbind(bool check)
// resync is performed, some of the other 'scrollbind' windows may
// need to jump so that the current window's relative position is
// visible on-screen.
- check_scrollbind(curwin->w_topline - (linenr_T)curwin->w_scbind_pos, 0);
+ check_scrollbind(vtopline - curwin->w_scbind_pos, 0);
}
- curwin->w_scbind_pos = curwin->w_topline;
+ curwin->w_scbind_pos = vtopline;
}
old_curwin = curwin;
- old_topline = curwin->w_topline;
- old_topfill = curwin->w_topfill;
+ old_vtopline = vtopline;
old_buf = curwin->w_buffer;
old_leftcol = curwin->w_leftcol;
}
@@ -2148,20 +2153,18 @@ void do_check_scrollbind(bool check)
/// Synchronize any windows that have "scrollbind" set, based on the
/// number of rows by which the current window has changed
/// (1998-11-02 16:21:01 R. Edward Ralston <eralston@computer.org>)
-void check_scrollbind(linenr_T topline_diff, int leftcol_diff)
+void check_scrollbind(linenr_T vtopline_diff, int leftcol_diff)
{
win_T *old_curwin = curwin;
buf_T *old_curbuf = curbuf;
int old_VIsual_select = VIsual_select;
int old_VIsual_active = VIsual_active;
colnr_T tgt_leftcol = curwin->w_leftcol;
- linenr_T topline;
- linenr_T y;
// check 'scrollopt' string for vertical and horizontal scroll options
- bool want_ver = (vim_strchr(p_sbo, 'v') && topline_diff != 0);
- want_ver |= old_curwin->w_p_diff;
- bool want_hor = (vim_strchr(p_sbo, 'h') && (leftcol_diff || topline_diff != 0));
+ bool want_ver = old_curwin->w_p_diff
+ || (vim_strchr(p_sbo, 'v') && vtopline_diff != 0);
+ bool want_hor = (vim_strchr(p_sbo, 'h') && (leftcol_diff || vtopline_diff != 0));
// loop through the scrollbound windows and scroll accordingly
VIsual_select = VIsual_active = 0;
@@ -2178,16 +2181,19 @@ void check_scrollbind(linenr_T topline_diff, int leftcol_diff)
if (old_curwin->w_p_diff && curwin->w_p_diff) {
diff_set_topline(old_curwin, curwin);
} else {
- curwin->w_scbind_pos += topline_diff;
- topline = (linenr_T)curwin->w_scbind_pos;
- if (topline > curbuf->b_ml.ml_line_count) {
- topline = curbuf->b_ml.ml_line_count;
- }
- if (topline < 1) {
- topline = 1;
- }
+ curwin->w_scbind_pos += vtopline_diff;
+ int curr_vtopline = get_vtopline(curwin);
- y = topline - curwin->w_topline;
+ // Perf: reuse curr_vtopline to reduce the time in plines_m_win_fill().
+ // Equivalent to:
+ // int max_vtopline = plines_m_win_fill(curwin, 1, curbuf->b_ml.ml_line_count);
+ int max_vtopline = curr_vtopline + curwin->w_topfill
+ + plines_m_win_fill(curwin, curwin->w_topline + 1,
+ curbuf->b_ml.ml_line_count);
+
+ int new_vtopline = MAX(MIN((linenr_T)curwin->w_scbind_pos, max_vtopline), 1);
+
+ int y = new_vtopline - curr_vtopline;
if (y > 0) {
scrollup(curwin, y, false);
} else {
@@ -2515,9 +2521,7 @@ bool nv_screengo(oparg_T *oap, int dir, int dist)
} else {
n = width1;
}
- if (curwin->w_curswant >= n) {
- curwin->w_curswant = n - 1;
- }
+ curwin->w_curswant = MIN(curwin->w_curswant, n - 1);
}
while (dist--) {
@@ -2776,11 +2780,7 @@ static void nv_zet(cmdarg_T *cap)
if (cap->count0 == 0) {
// No count given: put cursor at the line below screen
validate_botline(curwin); // make sure w_botline is valid
- if (curwin->w_botline > curbuf->b_ml.ml_line_count) {
- curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
- } else {
- curwin->w_cursor.lnum = curwin->w_botline;
- }
+ curwin->w_cursor.lnum = MIN(curwin->w_botline, curbuf->b_ml.ml_line_count);
}
FALLTHROUGH;
case NL:
@@ -3049,9 +3049,7 @@ static void nv_zet(cmdarg_T *cap)
case 'm':
if (curwin->w_p_fdl > 0) {
curwin->w_p_fdl -= cap->count1;
- if (curwin->w_p_fdl < 0) {
- curwin->w_p_fdl = 0;
- }
+ curwin->w_p_fdl = MAX(curwin->w_p_fdl, 0);
}
old_fdl = -1; // force an update
curwin->w_p_fen = true;
@@ -3069,9 +3067,7 @@ static void nv_zet(cmdarg_T *cap)
curwin->w_p_fdl += cap->count1;
{
int d = getDeepestNesting(curwin);
- if (curwin->w_p_fdl >= d) {
- curwin->w_p_fdl = d;
- }
+ curwin->w_p_fdl = MIN(curwin->w_p_fdl, d);
}
break;
@@ -3662,10 +3658,7 @@ static void nv_scroll(cmdarg_T *cap)
n = lnum - curwin->w_topline;
}
}
- curwin->w_cursor.lnum = curwin->w_topline + n;
- if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
- curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
- }
+ curwin->w_cursor.lnum = MIN(curwin->w_topline + n, curbuf->b_ml.ml_line_count);
}
// Correct for 'so', except when an operator is pending.
@@ -3972,6 +3965,12 @@ static void nv_next(cmdarg_T *cap)
normal_search(cap, 0, NULL, 0, SEARCH_MARK | cap->arg, NULL);
cap->count1 -= 1;
}
+
+ // Redraw the window to refresh the highlighted matches.
+ if (i > 0 && p_hls && !no_hlsearch
+ && win_hl_attr(curwin, HLF_LC) != win_hl_attr(curwin, HLF_L)) {
+ redraw_later(curwin, UPD_SOME_VALID);
+ }
}
/// Search for "pat" in direction "dir" ('/' or '?', 0 for repeat).
@@ -3983,6 +3982,7 @@ static void nv_next(cmdarg_T *cap)
static int normal_search(cmdarg_T *cap, int dir, char *pat, size_t patlen, int opt, int *wrapped)
{
searchit_arg_T sia;
+ pos_T const prev_cursor = curwin->w_cursor;
cap->oap->motion_type = kMTCharWise;
cap->oap->inclusive = false;
@@ -4006,6 +4006,11 @@ static int normal_search(cmdarg_T *cap, int dir, char *pat, size_t patlen, int o
foldOpenCursor();
}
}
+ // Redraw the window to refresh the highlighted matches.
+ if (!equalpos(curwin->w_cursor, prev_cursor) && p_hls && !no_hlsearch
+ && win_hl_attr(curwin, HLF_LC) != win_hl_attr(curwin, HLF_L)) {
+ redraw_later(curwin, UPD_SOME_VALID);
+ }
// "/$" will put the cursor after the end of the line, may need to
// correct that here
@@ -4332,12 +4337,8 @@ static void nv_percent(cmdarg_T *cap)
curwin->w_cursor.lnum = (curbuf->b_ml.ml_line_count *
cap->count0 + 99) / 100;
}
- if (curwin->w_cursor.lnum < 1) {
- curwin->w_cursor.lnum = 1;
- }
- if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
- curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
- }
+ curwin->w_cursor.lnum = MIN(MAX(curwin->w_cursor.lnum, 1), curbuf->b_ml.ml_line_count);
+
beginline(BL_SOL | BL_FIX);
}
} else { // "%" : go to matching paren
@@ -5634,6 +5635,7 @@ static void nv_g_cmd(cmdarg_T *cap)
// "go": goto byte count from start of buffer
case 'o':
+ oap->inclusive = false;
goto_byte(cap->count0);
break;
@@ -6076,11 +6078,7 @@ static void nv_goto(cmdarg_T *cap)
if (cap->count0 != 0) {
lnum = cap->count0;
}
- if (lnum < 1) {
- lnum = 1;
- } else if (lnum > curbuf->b_ml.ml_line_count) {
- lnum = curbuf->b_ml.ml_line_count;
- }
+ lnum = MIN(MAX(lnum, 1), curbuf->b_ml.ml_line_count);
curwin->w_cursor.lnum = lnum;
beginline(BL_SOL | BL_FIX);
if ((fdo_flags & FDO_JUMP) && KeyTyped && cap->oap->op_type == OP_NOP) {
@@ -6410,9 +6408,8 @@ static void nv_join(cmdarg_T *cap)
return;
}
- if (cap->count0 <= 1) {
- cap->count0 = 2; // default for join is two lines!
- }
+ cap->count0 = MAX(cap->count0, 2); // default for join is two lines!
+
if (curwin->w_cursor.lnum + cap->count0 - 1 >
curbuf->b_ml.ml_line_count) {
// can't join when on the last line
@@ -6597,15 +6594,21 @@ static void nv_open(cmdarg_T *cap)
}
}
+/// Handles K_PASTE_START, repeats pasted text.
+static void nv_paste(cmdarg_T *cap)
+{
+ paste_repeat(cap->count1);
+}
+
/// Handle an arbitrary event in normal mode
static void nv_event(cmdarg_T *cap)
{
// Garbage collection should have been executed before blocking for events in
- // the `os_inchar` in `state_enter`, but we also disable it here in case the
- // `os_inchar` branch was not executed (!multiqueue_empty(loop.events), which
+ // the `input_get` in `state_enter`, but we also disable it here in case the
+ // `input_get` branch was not executed (!multiqueue_empty(loop.events), which
// could have `may_garbage_collect` set to true in `normal_check`).
//
- // That is because here we may run code that calls `os_inchar`
+ // That is because here we may run code that calls `input_get`
// later(`f_confirm` or `get_keystroke` for example), but in these cases it is
// not safe to perform garbage collection because there could be unreferenced
// lists or dicts being used.
diff --git a/src/nvim/ops.c b/src/nvim/ops.c
index 6233e446e0..4b8382c971 100644
--- a/src/nvim/ops.c
+++ b/src/nvim/ops.c
@@ -24,6 +24,7 @@
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
@@ -31,6 +32,7 @@
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_getln.h"
#include "nvim/extmark.h"
+#include "nvim/file_search.h"
#include "nvim/fold.h"
#include "nvim/garray.h"
#include "nvim/garray_defs.h"
@@ -262,12 +264,7 @@ void op_shift(oparg_T *oap, bool curs_top, int amount)
foldOpenCursor();
if (oap->line_count > p_report) {
- char *op;
- if (oap->op_type == OP_RSHIFT) {
- op = ">";
- } else {
- op = "<";
- }
+ char *op = oap->op_type == OP_RSHIFT ? ">" : "<";
char *msg_line_single = NGETTEXT("%" PRId64 " line %sed %d time",
"%" PRId64 " line %sed %d times", amount);
@@ -298,8 +295,10 @@ void op_shift(oparg_T *oap, bool curs_top, int amount)
/// @param call_changed_bytes call changed_bytes()
void shift_line(bool left, bool round, int amount, int call_changed_bytes)
{
- const int sw_val = get_sw_value_indent(curbuf);
-
+ int sw_val = get_sw_value_indent(curbuf, left);
+ if (sw_val == 0) {
+ sw_val = 1; // shouldn't happen, just in case
+ }
int count = get_indent(); // get current indent
if (round) { // round off indent
@@ -309,20 +308,14 @@ void shift_line(bool left, bool round, int amount, int call_changed_bytes)
amount--;
}
if (left) {
- i -= amount;
- if (i < 0) {
- i = 0;
- }
+ i = MAX(i - amount, 0);
} else {
i += amount;
}
count = i * sw_val;
} else { // original vi indent
if (left) {
- count -= sw_val * amount;
- if (count < 0) {
- count = 0;
- }
+ count = MAX(count - sw_val * amount, 0);
} else {
count += sw_val * amount;
}
@@ -330,7 +323,7 @@ void shift_line(bool left, bool round, int amount, int call_changed_bytes)
// Set new indent
if (State & VREPLACE_FLAG) {
- change_indent(INDENT_SET, count, false, NUL, call_changed_bytes);
+ change_indent(INDENT_SET, count, false, call_changed_bytes);
} else {
set_indent(count, call_changed_bytes ? SIN_CHANGED : 0);
}
@@ -344,7 +337,7 @@ static void shift_block(oparg_T *oap, int amount)
const int oldstate = State;
char *newp;
const int oldcol = curwin->w_cursor.col;
- const int sw_val = get_sw_value_indent(curbuf);
+ const int sw_val = get_sw_value_indent(curbuf, left);
const int ts_val = (int)curbuf->b_p_ts;
struct block_def bd;
int incr;
@@ -459,9 +452,7 @@ static void shift_block(oparg_T *oap, int amount)
const colnr_T block_space_width = non_white_col - oap->start_vcol;
// We will shift by "total" or "block_space_width", whichever is less.
- const colnr_T shift_amount = block_space_width < total
- ? block_space_width
- : total;
+ const colnr_T shift_amount = MIN(block_space_width, total);
// The column to which we will shift the text.
const colnr_T destination_col = non_white_col - shift_amount;
@@ -575,9 +566,7 @@ static void block_insert(oparg_T *oap, const char *s, size_t slen, bool b_insert
// avoid copying part of a multi-byte character
offset -= utf_head_off(oldp, oldp + offset);
}
- if (spaces < 0) { // can happen when the cursor was moved
- spaces = 0;
- }
+ spaces = MAX(spaces, 0); // can happen when the cursor was moved
assert(count >= 0);
// Make sure the allocated size matches what is actually copied below.
@@ -763,7 +752,7 @@ char *get_expr_line(void)
}
nested++;
- char *rv = eval_to_string(expr_copy, true);
+ char *rv = eval_to_string(expr_copy, true, false);
nested--;
xfree(expr_copy);
return rv;
@@ -906,7 +895,7 @@ static void typval_to_yankreg(yankreg_T* yankreg, typval_T* val)
if (tv_dict_get_tv(dict, "additional_data", &tv) == OK) {
if (tv.v_type == VAR_DICT) {
- yankreg->additional_data = tv.vval.v_dict;
+ yankreg->additional_data = NULL; // tv.vval.v_dict;
}
}
break;
@@ -1125,16 +1114,6 @@ int do_record(int c)
return retval;
}
-static void set_yreg_additional_data(yankreg_T *reg, dict_T *additional_data)
- FUNC_ATTR_NONNULL_ARG(1)
-{
- if (reg->additional_data == additional_data) {
- return;
- }
- tv_dict_unref(reg->additional_data);
- reg->additional_data = additional_data;
-}
-
/// Stuff string "p" into yank register "regname" as a single line (append if
/// uppercase). "p" must have been allocated.
///
@@ -1164,7 +1143,7 @@ static int stuff_yank(int regname, char *p)
*pp = lp;
} else {
free_register(reg);
- set_yreg_additional_data(reg, NULL);
+ reg->additional_data = NULL;
reg->y_array = xmalloc(sizeof(char *));
reg->y_array[0] = p;
reg->y_size = 1;
@@ -1506,9 +1485,9 @@ static dict_T* yankreg_to_dict(yankreg_T* yankreg) {
}
tv_dict_add_str(dict, S_LEN("type"), type);
- if (yankreg->additional_data) {
- tv_dict_add_dict(dict, S_LEN("additional_data"), yankreg->additional_data);
- }
+ // if (yankreg->additional_data) {
+ // tv_dict_add_dict(dict, S_LEN("additional_data"), yankreg->additional_data);
+ // }
list_T *const lines = tv_list_alloc((long)yankreg->y_size);
@@ -2418,18 +2397,6 @@ bool swapchar(int op_type, pos_T *pos)
return false;
}
- // ~ is OP_NOP, g~ is OP_TILDE, gU is OP_UPPER
- if ((op_type == OP_UPPER || op_type == OP_NOP || op_type == OP_TILDE) && c == 0xdf) {
- pos_T sp = curwin->w_cursor;
-
- // Special handling for lowercase German sharp s (ß): convert to uppercase (ẞ).
- curwin->w_cursor = *pos;
- del_char(false);
- ins_char(0x1E9E);
- curwin->w_cursor = sp;
- return true;
- }
-
int nc = c;
if (mb_islower(c)) {
if (op_type == OP_ROT13) {
@@ -2651,9 +2618,7 @@ void op_insert(oparg_T *oap, int count1)
}
}
}
- if (add > len) {
- add = len; // short line, point to the NUL
- }
+ add = MIN(add, len); // short line, point to the NUL
firstline += add;
len -= add;
int ins_len = len - pre_textlen - offset;
@@ -2815,7 +2780,7 @@ void clear_registers(void)
void free_register(yankreg_T *reg)
FUNC_ATTR_NONNULL_ALL
{
- set_yreg_additional_data(reg, NULL);
+ XFREE_CLEAR(reg->additional_data);
if (reg->y_array == NULL) {
return;
}
@@ -2953,7 +2918,7 @@ static void op_yank_reg(oparg_T *oap, bool message, yankreg_T *reg, bool append)
char *pnew = xmalloc(strlen(curr->y_array[curr->y_size - 1])
+ strlen(reg->y_array[0]) + 1);
STRCPY(pnew, curr->y_array[--j]);
- STRCAT(pnew, reg->y_array[0]);
+ strcat(pnew, reg->y_array[0]);
xfree(curr->y_array[j]);
xfree(reg->y_array[0]);
curr->y_array[j++] = pnew;
@@ -3111,13 +3076,13 @@ static void do_autocmd_textyankpost(oparg_T *oap, yankreg_T *reg)
///
/// @param oap Operator arguments.
/// @param reg The yank register used.
-static void do_autocmd_textputpost(int regname, yankreg_T *reg)
+static void do_autocmd_textput(int regname, yankreg_T *reg, enum auto_event evt)
FUNC_ATTR_NONNULL_ALL
{
static bool recursive = false;
int len;
- if (recursive || !has_event(EVENT_TEXTPUTPOST)) {
+ if (recursive || !has_event(evt)) {
// No autocommand was defined, or we yanked from this autocommand.
return;
}
@@ -3142,7 +3107,7 @@ static void do_autocmd_textputpost(int regname, yankreg_T *reg)
(void)tv_dict_add_str(dict, S_LEN("regtype"), buf);
// Name of requested register, or empty string for unnamed operation.
- len = (*utf_char2len)(regname);
+ len = utf_char2len(regname);
buf[len] = 0;
utf_char2bytes(regname, buf);
recursive = true;
@@ -3150,7 +3115,7 @@ static void do_autocmd_textputpost(int regname, yankreg_T *reg)
tv_dict_set_keys_readonly(dict);
textlock++;
- apply_autocmds(EVENT_TEXTPUTPOST, NULL, NULL, false, curbuf);
+ apply_autocmds(evt, NULL, NULL, false, curbuf);
textlock--;
restore_v_event(dict, &save_v_event);
@@ -3188,6 +3153,10 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags)
const pos_T orig_end = curbuf->b_op_end;
unsigned cur_ve_flags = get_ve_flags(curwin);
+ if (reg) {
+ do_autocmd_textput(regname, reg, EVENT_TEXTPUTPRE);
+ }
+
if (flags & PUT_FIXINDENT) {
orig_indent = get_indent();
}
@@ -3400,9 +3369,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags)
if (y_type == kMTBlockWise) {
lnum = curwin->w_cursor.lnum + (linenr_T)y_size + 1;
- if (lnum > curbuf->b_ml.ml_line_count) {
- lnum = curbuf->b_ml.ml_line_count + 1;
- }
+ lnum = MIN(lnum, curbuf->b_ml.ml_line_count + 1);
if (u_save(curwin->w_cursor.lnum - 1, lnum) == FAIL) {
goto end;
}
@@ -3563,9 +3530,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags)
spaces -= win_charsize(cstype, 0, ci.ptr, ci.chr.value, &csarg).width;
ci = utfc_next(ci);
}
- if (spaces < 0) {
- spaces = 0;
- }
+ spaces = MAX(spaces, 0);
}
// Insert the new text.
@@ -3630,10 +3595,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags)
// adjust '] mark
curbuf->b_op_end.lnum = curwin->w_cursor.lnum - 1;
- curbuf->b_op_end.col = bd.textcol + (colnr_T)totlen - 1;
- if (curbuf->b_op_end.col < 0) {
- curbuf->b_op_end.col = 0;
- }
+ curbuf->b_op_end.col = MAX(bd.textcol + (colnr_T)totlen - 1, 0);
curbuf->b_op_end.coladd = 0;
if (flags & PUT_CURSEND) {
curwin->w_cursor = curbuf->b_op_end;
@@ -3641,9 +3603,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags)
// in Insert mode we might be after the NUL, correct for that
colnr_T len = get_cursor_line_len();
- if (curwin->w_cursor.col > len) {
- curwin->w_cursor.col = len;
- }
+ curwin->w_cursor.col = MIN(curwin->w_cursor.col, len);
} else {
curwin->w_cursor.lnum = lnum;
}
@@ -3676,10 +3636,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags)
int first_byte_off = 0;
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;
- }
+ end_lnum = MAX(curbuf->b_visual.vi_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
@@ -3778,7 +3735,7 @@ void do_put(int regname, yankreg_T *reg, int dir, int count, int flags)
totlen = strlen(y_array[y_size - 1]);
char *newp = xmalloc((size_t)ml_get_len(lnum) - (size_t)col + totlen + 1);
STRCPY(newp, y_array[y_size - 1]);
- STRCAT(newp, ptr);
+ strcat(newp, ptr);
// insert second line
ml_append(lnum, newp, 0, false);
new_lnum++;
@@ -3943,7 +3900,7 @@ error:
end:
if (reg) {
- do_autocmd_textputpost(regname, reg);
+ do_autocmd_textput(regname, reg, EVENT_TEXTPUTPOST);
}
if (cmdmod.cmod_flags & CMOD_LOCKMARKS) {
@@ -4622,10 +4579,7 @@ void charwise_block_prep(pos_T start, pos_T end, struct block_def *bdp, linenr_T
if (ce != cs && start.coladd > 0) {
// Part of a tab selected -- but don't double-count it.
bdp->start_char_vcols = ce - cs + 1;
- bdp->startspaces = bdp->start_char_vcols - start.coladd;
- if (bdp->startspaces < 0) {
- bdp->startspaces = 0;
- }
+ bdp->startspaces = MAX(bdp->start_char_vcols - start.coladd, 0);
startcol++;
}
}
@@ -4725,9 +4679,7 @@ void op_addsub(oparg_T *oap, linenr_T Prenum1, bool g_cmd)
}
if (pos.lnum == oap->end.lnum) {
length = ml_get_len(oap->end.lnum);
- if (oap->end.col >= length) {
- oap->end.col = length - 1;
- }
+ oap->end.col = MIN(oap->end.col, length - 1);
length = oap->end.col - pos.col + 1;
}
}
@@ -4783,6 +4735,7 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
int pre; // 'X' or 'x': hex; '0': octal; 'B' or 'b': bin
static bool hexupper = false; // 0xABC
uvarnumber_T n;
+ bool blank_unsigned = false; // blank: treat as unsigned?
bool negative = false;
bool was_positive = true;
bool visual = VIsual_active;
@@ -4793,12 +4746,12 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
pos_T endpos;
colnr_T save_coladd = 0;
- const bool do_hex = vim_strchr(curbuf->b_p_nf, 'x') != NULL; // "heX"
- const bool do_oct = vim_strchr(curbuf->b_p_nf, 'o') != NULL; // "Octal"
- const bool do_bin = vim_strchr(curbuf->b_p_nf, 'b') != NULL; // "Bin"
- const bool do_alpha = vim_strchr(curbuf->b_p_nf, 'p') != NULL; // "alPha"
- // "Unsigned"
- const bool do_unsigned = vim_strchr(curbuf->b_p_nf, 'u') != NULL;
+ const bool do_hex = vim_strchr(curbuf->b_p_nf, 'x') != NULL; // "heX"
+ const bool do_oct = vim_strchr(curbuf->b_p_nf, 'o') != NULL; // "Octal"
+ const bool do_bin = vim_strchr(curbuf->b_p_nf, 'b') != NULL; // "Bin"
+ const bool do_alpha = vim_strchr(curbuf->b_p_nf, 'p') != NULL; // "alPha"
+ const bool do_unsigned = vim_strchr(curbuf->b_p_nf, 'u') != NULL; // "Unsigned"
+ const bool do_blank = vim_strchr(curbuf->b_p_nf, 'k') != NULL; // "blanK"
if (virtual_active(curwin)) {
save_coladd = pos->coladd;
@@ -4895,8 +4848,12 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
if (col > pos->col && ptr[col - 1] == '-'
&& !utf_head_off(ptr, ptr + col - 1)
&& !do_unsigned) {
- negative = true;
- was_positive = false;
+ if (do_blank && col >= 2 && !ascii_iswhite(ptr[col - 2])) {
+ blank_unsigned = true;
+ } else {
+ negative = true;
+ was_positive = false;
+ }
}
}
@@ -4911,21 +4868,13 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
// decrement or increment alphabetic character
if (op_type == OP_NR_SUB) {
if (CHAR_ORD(firstdigit) < Prenum1) {
- if (isupper(firstdigit)) {
- firstdigit = 'A';
- } else {
- firstdigit = 'a';
- }
+ firstdigit = isupper(firstdigit) ? 'A' : 'a';
} else {
firstdigit -= (int)Prenum1;
}
} else {
if (26 - CHAR_ORD(firstdigit) - 1 < Prenum1) {
- if (isupper(firstdigit)) {
- firstdigit = 'Z';
- } else {
- firstdigit = 'z';
- }
+ firstdigit = isupper(firstdigit) ? 'Z' : 'z';
} else {
firstdigit += (int)Prenum1;
}
@@ -4942,9 +4891,13 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
&& !utf_head_off(ptr, ptr + col - 1)
&& !visual
&& !do_unsigned) {
- // negative number
- col--;
- negative = true;
+ if (do_blank && col >= 2 && !ascii_iswhite(ptr[col - 2])) {
+ blank_unsigned = true;
+ } else {
+ // negative number
+ col--;
+ negative = true;
+ }
}
// get the number value (unsigned)
@@ -5001,7 +4954,7 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
}
}
- if (do_unsigned && negative) {
+ if ((do_unsigned || blank_unsigned) && negative) {
if (subtract) {
// sticking at zero.
n = 0;
@@ -5032,11 +4985,7 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
}
while (todel-- > 0) {
if (c < 0x100 && isalpha(c)) {
- if (isupper(c)) {
- hexupper = true;
- } else {
- hexupper = false;
- }
+ hexupper = isupper(c);
}
// del_char() will mark line needing displaying
del_char(false);
@@ -5076,7 +5025,7 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
buf2[i++] = ((n >> --bits) & 0x1) ? '1' : '0';
}
- buf2[i] = '\0';
+ buf2[i] = NUL;
} else if (pre == 0) {
vim_snprintf(buf2, ARRAY_SIZE(buf2), "%" PRIu64, (uint64_t)n);
} else if (pre == '0') {
@@ -5098,7 +5047,7 @@ bool do_addsub(int op_type, pos_T *pos, int length, linenr_T Prenum1)
}
}
*ptr = NUL;
- STRCAT(buf1, buf2);
+ strcat(buf1, buf2);
ins_str(buf1); // insert the new number
endpos = curwin->w_cursor;
if (curwin->w_cursor.col) {
@@ -5516,9 +5465,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str,
for (char **ss = (char **)str; *ss != NULL; ss++, lnum++) {
size_t ss_len = strlen(*ss);
pp[lnum] = xmemdupz(*ss, ss_len);
- if (ss_len > maxlen) {
- maxlen = ss_len;
- }
+ maxlen = MAX(maxlen, ss_len);
}
} else {
size_t line_len;
@@ -5527,9 +5474,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str,
start += line_len + 1, lnum++) {
assert(end - start >= 0);
line_len = (size_t)((char *)xmemscan(start, '\n', (size_t)(end - start)) - start);
- if (line_len > maxlen) {
- maxlen = line_len;
- }
+ maxlen = MAX(maxlen, line_len);
// When appending, copy the previous line and free it after.
size_t extra = append ? strlen(pp[--lnum]) : 0;
@@ -5552,7 +5497,7 @@ static void str_to_reg(yankreg_T *y_ptr, MotionType yank_type, const char *str,
}
y_ptr->y_type = yank_type;
y_ptr->y_size = lnum;
- set_yreg_additional_data(y_ptr, NULL);
+ XFREE_CLEAR(y_ptr->additional_data);
y_ptr->timestamp = os_time();
if (yank_type == kMTBlockWise) {
y_ptr->y_width = (blocklen == -1 ? (colnr_T)maxlen - 1 : blocklen);
@@ -6019,9 +5964,7 @@ static void get_op_vcol(oparg_T *oap, colnr_T redo_VIsual_vcol, bool initial)
if (!redo_VIsual_busy) {
getvvcol(curwin, &(oap->end), &start, NULL, &end);
- if (start < oap->start_vcol) {
- oap->start_vcol = start;
- }
+ oap->start_vcol = MIN(oap->start_vcol, start);
if (end > oap->end_vcol) {
if (initial && *p_sel == 'e'
&& start >= 1
@@ -6040,9 +5983,7 @@ static void get_op_vcol(oparg_T *oap, colnr_T redo_VIsual_vcol, bool initial)
for (curwin->w_cursor.lnum = oap->start.lnum;
curwin->w_cursor.lnum <= oap->end.lnum; curwin->w_cursor.lnum++) {
getvvcol(curwin, &curwin->w_cursor, NULL, NULL, &end);
- if (end > oap->end_vcol) {
- oap->end_vcol = end;
- }
+ oap->end_vcol = MAX(oap->end_vcol, end);
}
} else if (redo_VIsual_busy) {
oap->end_vcol = oap->start_vcol + redo_VIsual_vcol - 1;
@@ -6176,9 +6117,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
// redo_VIsual.rv_line_count and redo_VIsual.rv_vcol.
oap->start = curwin->w_cursor;
curwin->w_cursor.lnum += redo_VIsual.rv_line_count - 1;
- if (curwin->w_cursor.lnum > curbuf->b_ml.ml_line_count) {
- curwin->w_cursor.lnum = curbuf->b_ml.ml_line_count;
- }
+ curwin->w_cursor.lnum = MIN(curwin->w_cursor.lnum, curbuf->b_ml.ml_line_count);
VIsual_mode = redo_VIsual.rv_mode;
if (redo_VIsual.rv_vcol == MAXCOL || VIsual_mode == 'v') {
if (VIsual_mode == 'v') {
@@ -6466,9 +6405,7 @@ void do_pending_operator(cmdarg_T *cap, int old_col, bool gui_yank)
case OP_JOIN_NS:
case OP_JOIN:
- if (oap->line_count < 2) {
- oap->line_count = 2;
- }
+ oap->line_count = MAX(oap->line_count, 2);
if (curwin->w_cursor.lnum + oap->line_count - 1 >
curbuf->b_ml.ml_line_count) {
beep_flush();
@@ -6867,9 +6804,7 @@ void finish_yankreg_from_object(yankreg_T *reg, bool clipboard_adjust)
size_t maxlen = 0;
for (size_t i = 0; i < reg->y_size; i++) {
size_t rowlen = strlen(reg->y_array[i]);
- if (rowlen > maxlen) {
- maxlen = rowlen;
- }
+ maxlen = MAX(maxlen, rowlen);
}
assert(maxlen <= INT_MAX);
reg->y_width = MAX(reg->y_width, (int)maxlen - 1);
@@ -6973,9 +6908,7 @@ static bool get_clipboard(int name, yankreg_T **target, bool quiet)
size_t maxlen = 0;
for (size_t i = 0; i < reg->y_size; i++) {
size_t rowlen = strlen(reg->y_array[i]);
- if (rowlen > maxlen) {
- maxlen = rowlen;
- }
+ maxlen = MAX(maxlen, rowlen);
}
assert(maxlen <= INT_MAX);
reg->y_width = (int)maxlen - 1;
diff --git a/src/nvim/ops.h b/src/nvim/ops.h
index 643d2a2deb..3cca52572f 100644
--- a/src/nvim/ops.h
+++ b/src/nvim/ops.h
@@ -7,7 +7,6 @@
#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#include "nvim/extmark_defs.h" // IWYU pragma: keep
-#include "nvim/func_attr.h"
#include "nvim/macros_defs.h"
#include "nvim/normal_defs.h"
#include "nvim/option_defs.h" // IWYU pragma: keep
@@ -99,6 +98,10 @@ enum {
OP_NR_SUB = 29, ///< "<C-X>" Subtract from the number or alphabetic character
};
+struct yank_registers;
+typedef struct yank_registers yank_registers_T;
+typedef size_t iter_register_T;
+
/// Flags for get_reg_contents().
enum GRegFlags {
kGRegNoExpr = 1, ///< Do not allow expression register.
@@ -113,7 +116,7 @@ typedef struct {
MotionType y_type; ///< Register type
colnr_T y_width; ///< Register width (only valid for y_type == kBlockWise).
Timestamp timestamp; ///< Time when register was last modified.
- dict_T *additional_data; ///< Additional data from ShaDa file.
+ AdditionalData *additional_data; ///< Additional data from ShaDa file.
} yankreg_T;
/// Modes for get_yank_register()
@@ -125,8 +128,10 @@ typedef enum {
/// Returns a reference to a user-defined register.
int get_userreg(int regname);
-static inline int op_reg_index(int regname)
- REAL_FATTR_CONST;
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "ops.h.generated.h"
+# include "ops.h.inline.generated.h"
+#endif
/// Convert register name into register index
///
@@ -134,6 +139,7 @@ static inline int op_reg_index(int regname)
///
/// @return Index in y_regs array or -1 if register name was not recognized.
static inline int op_reg_index(const int regname)
+ FUNC_ATTR_CONST
{
if (ascii_isdigit(regname)) {
return regname - '0';
@@ -152,23 +158,13 @@ static inline int op_reg_index(const int regname)
}
}
-struct yank_registers;
-typedef struct yank_registers yank_registers_T;
-
-typedef size_t iter_register_T;
-
-static inline bool is_literal_register(int regname)
- REAL_FATTR_CONST;
/// @see get_yank_register
/// @return true when register should be inserted literally
/// (selection or clipboard)
static inline bool is_literal_register(const int regname)
+ FUNC_ATTR_CONST
{
return regname == '*' || regname == '+';
}
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "ops.h.generated.h"
-#endif
-
EXTERN LuaRef repeat_luaref INIT( = LUA_NOREF); ///< LuaRef for "."
diff --git a/src/nvim/option.c b/src/nvim/option.c
index 22897d4f7e..ff0c0e2acf 100644
--- a/src/nvim/option.c
+++ b/src/nvim/option.c
@@ -44,6 +44,7 @@
#include "nvim/decoration_provider.h"
#include "nvim/diff.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/vars.h"
@@ -87,6 +88,7 @@
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
#include "nvim/path.h"
+#include "nvim/plines.h"
#include "nvim/popupmenu.h"
#include "nvim/pos_defs.h"
#include "nvim/regexp.h"
@@ -208,39 +210,53 @@ static void set_init_default_backupskip(void)
OptIndex opt_idx = kOptBackupskip;
ga_init(&ga, 1, 100);
- for (size_t n = 0; n < ARRAY_SIZE(names); n++) {
+ for (size_t i = 0; i < ARRAY_SIZE(names); i++) {
bool mustfree = true;
char *p;
+ size_t plen;
#ifdef UNIX
- if (*names[n] == NUL) {
+ if (*names[i] == NUL) {
# ifdef __APPLE__
p = "/private/tmp";
+ plen = STRLEN_LITERAL("/private/tmp");
# else
p = "/tmp";
+ plen = STRLEN_LITERAL("/tmp");
# endif
mustfree = false;
} else
#endif
{
- p = vim_getenv(names[n]);
+ p = vim_getenv(names[i]);
+ plen = 0; // will be calcuated below
}
if (p != NULL && *p != NUL) {
- // First time count the NUL, otherwise count the ','.
- const size_t len = strlen(p) + 3;
- char *item = xmalloc(len);
- xstrlcpy(item, p, len);
- add_pathsep(item);
- xstrlcat(item, "*", len);
- if (find_dup_item(ga.ga_data, item, options[opt_idx].flags)
- == NULL) {
- ga_grow(&ga, (int)len);
- if (!GA_EMPTY(&ga)) {
- STRCAT(ga.ga_data, ",");
+ bool has_trailing_path_sep = false;
+
+ if (plen == 0) {
+ // the value was retrieved from the environment
+ plen = strlen(p);
+ // does the value include a trailing path separator?
+ if (after_pathsep(p, p + plen)) {
+ has_trailing_path_sep = true;
}
- STRCAT(ga.ga_data, p);
- add_pathsep(ga.ga_data);
- STRCAT(ga.ga_data, "*");
- ga.ga_len += (int)len;
+ }
+
+ // item size needs to be large enough to include "/*" and a trailing NUL
+ // note: the value (and therefore plen) may already include a path separator
+ size_t itemsize = plen + (has_trailing_path_sep ? 0 : 1) + 2;
+ char *item = xmalloc(itemsize);
+ // add a preceding comma as a separator after the first item
+ size_t itemseplen = (ga.ga_len == 0) ? 0 : 1;
+
+ size_t itemlen = (size_t)vim_snprintf(item, itemsize, "%s%s*", p,
+ has_trailing_path_sep ? "" : PATHSEPSTR);
+
+ if (find_dup_item(ga.ga_data, item, itemlen, options[opt_idx].flags) == NULL) {
+ ga_grow(&ga, (int)(itemseplen + itemlen + 1));
+ ga.ga_len += vim_snprintf((char *)ga.ga_data + ga.ga_len,
+ itemseplen + itemlen + 1,
+ "%s%s", (itemseplen > 0) ? "," : "", item);
}
xfree(item);
}
@@ -524,7 +540,8 @@ static void set_string_default(OptIndex opt_idx, char *val, bool allocated)
/// For an option value that contains comma separated items, find "newval" in
/// "origval". Return NULL if not found.
-static char *find_dup_item(char *origval, const char *newval, uint32_t flags)
+static char *find_dup_item(char *origval, const char *newval, const size_t newvallen,
+ uint32_t flags)
FUNC_ATTR_NONNULL_ARG(2)
{
if (origval == NULL) {
@@ -533,11 +550,10 @@ static char *find_dup_item(char *origval, const char *newval, uint32_t flags)
int bs = 0;
- const size_t newlen = strlen(newval);
for (char *s = origval; *s != NUL; s++) {
if ((!(flags & P_COMMA) || s == origval || (s[-1] == ',' && !(bs & 1)))
- && strncmp(s, newval, newlen) == 0
- && (!(flags & P_COMMA) || s[newlen] == ',' || s[newlen] == NUL)) {
+ && strncmp(s, newval, newvallen) == 0
+ && (!(flags & P_COMMA) || s[newvallen] == ',' || s[newvallen] == NUL)) {
return s;
}
// Count backslashes. Only a comma with an even number of backslashes
@@ -695,10 +711,10 @@ void set_helplang_default(const char *lang)
}
p_hlg = xmemdupz(lang, lang_len);
// zh_CN becomes "cn", zh_TW becomes "tw".
- if (STRNICMP(p_hlg, "zh_", 3) == 0 && strlen(p_hlg) >= 5) {
+ if (STRNICMP(p_hlg, "zh_", 3) == 0 && lang_len >= 5) {
p_hlg[0] = (char)TOLOWER_ASC(p_hlg[3]);
p_hlg[1] = (char)TOLOWER_ASC(p_hlg[4]);
- } else if (strlen(p_hlg) >= 1 && *p_hlg == 'C') {
+ } else if (lang_len && *p_hlg == 'C') {
// any C like setting, such as C.UTF-8, becomes "en"
p_hlg[0] = 'e';
p_hlg[1] = 'n';
@@ -948,7 +964,7 @@ static char *stropt_get_newval(int nextchar, OptIndex opt_idx, char **argp, void
int len = 0;
if (op == OP_REMOVING || (flags & P_NODUP)) {
len = (int)strlen(newval);
- s = find_dup_item(origval, newval, flags);
+ s = find_dup_item(origval, newval, (size_t)len, flags);
// do not add if already there
if ((op == OP_ADDING || op == OP_PREPENDING) && s != NULL) {
@@ -1200,8 +1216,9 @@ static OptVal get_option_newval(OptIndex opt_idx, int opt_flags, set_prefix_T pr
// Different ways to set a number option:
// & set to default value
// < set to global value
- // <xx> accept special key codes for 'wildchar'
- // c accept any non-digit for 'wildchar'
+ // <xx> accept special key codes for 'wildchar' or 'wildcharm'
+ // ^x accept ctrl key codes for 'wildchar' or 'wildcharm'
+ // c accept any non-digit for 'wildchar' or 'wildcharm'
// [-]0-9 set number
// other error
arg++;
@@ -1223,7 +1240,7 @@ static OptVal get_option_newval(OptIndex opt_idx, int opt_flags, set_prefix_T pr
|| (*arg != NUL && (!arg[1] || ascii_iswhite(arg[1]))
&& !ascii_isdigit(*arg)))) {
newval_num = string_to_key(arg);
- if (newval_num == 0 && (OptInt *)varp != &p_wcm) {
+ if (newval_num == 0) {
*errmsg = e_invarg;
return newval;
}
@@ -1462,11 +1479,10 @@ int do_set(char *arg, int opt_flags)
}
if (errmsg != NULL) {
- xstrlcpy(IObuff, _(errmsg), IOSIZE);
- int i = (int)strlen(IObuff) + 2;
+ int i = vim_snprintf((char *)IObuff, IOSIZE, "%s", _(errmsg)) + 2;
if (i + (arg - startarg) < IOSIZE) {
// append the argument with the error
- xstrlcat(IObuff, ": ", IOSIZE);
+ xstrlcpy(IObuff + i - 2, ": ", (size_t)(IOSIZE - i + 2));
assert(arg >= startarg);
memmove(IObuff + i, startarg, (size_t)(arg - startarg));
IObuff[i + (arg - startarg)] = NUL;
@@ -1509,7 +1525,9 @@ static int find_key_len(const char *arg_arg, size_t len, bool has_lt)
// Don't use get_special_key_code() for t_xx, we don't want it to call
// add_termcap_entry().
if (len >= 4 && arg[0] == 't' && arg[1] == '_') {
- key = TERMCAP2KEY((uint8_t)arg[2], (uint8_t)arg[3]);
+ if (!has_lt || arg[4] == '>') {
+ key = TERMCAP2KEY((uint8_t)arg[2], (uint8_t)arg[3]);
+ }
} else if (has_lt) {
arg--; // put arg at the '<'
int modifiers = 0;
@@ -1523,14 +1541,18 @@ static int find_key_len(const char *arg_arg, size_t len, bool has_lt)
}
/// Convert a key name or string into a key value.
-/// Used for 'wildchar' and 'cedit' options.
+/// Used for 'cedit', 'wildchar' and 'wildcharm' options.
int string_to_key(char *arg)
{
- if (*arg == '<') {
+ if (*arg == '<' && arg[1]) {
return find_key_len(arg + 1, strlen(arg), true);
}
- if (*arg == '^') {
- return CTRL_CHR((uint8_t)arg[1]);
+ if (*arg == '^' && arg[1]) {
+ int key = CTRL_CHR((uint8_t)arg[1]);
+ if (key == 0) { // ^@ is <Nul>
+ key = K_ZERO;
+ }
+ return key;
}
return (uint8_t)(*arg);
}
@@ -2473,7 +2495,7 @@ static const char *did_set_scrollbind(optset_T *args)
return NULL;
}
do_check_scrollbind(false);
- win->w_scbind_pos = win->w_topline;
+ win->w_scbind_pos = get_vtopline(win);
return NULL;
}
@@ -4569,6 +4591,8 @@ void *get_varp_scope_from(vimoption_T *p, int scope, buf_T *buf, win_T *win)
return &(buf->b_p_def);
case PV_INC:
return &(buf->b_p_inc);
+ case PV_COT:
+ return &(buf->b_p_cot);
case PV_DICT:
return &(buf->b_p_dict);
case PV_TSR:
@@ -4652,6 +4676,8 @@ void *get_varp_from(vimoption_T *p, buf_T *buf, win_T *win)
return *buf->b_p_def != NUL ? &(buf->b_p_def) : p->var;
case PV_INC:
return *buf->b_p_inc != NUL ? &(buf->b_p_inc) : p->var;
+ case PV_COT:
+ return *buf->b_p_cot != NUL ? &(buf->b_p_cot) : p->var;
case PV_DICT:
return *buf->b_p_dict != NUL ? &(buf->b_p_dict) : p->var;
case PV_TSR:
@@ -5078,6 +5104,12 @@ void clear_winopt(winopt_T *wop)
void didset_window_options(win_T *wp, bool valid_cursor)
{
+ // Set w_leftcol or w_skipcol to zero.
+ if (wp->w_p_wrap) {
+ wp->w_leftcol = 0;
+ } else {
+ wp->w_skipcol = 0;
+ }
check_colorcolumn(wp);
briopt_check(wp);
fill_culopt_flags(NULL, wp);
@@ -5335,6 +5367,8 @@ void buf_copy_options(buf_T *buf, int flags)
buf->b_p_inc = empty_string_option;
buf->b_p_inex = xstrdup(p_inex);
COPY_OPT_SCTX(buf, BV_INEX);
+ buf->b_p_cot = empty_string_option;
+ buf->b_cot_flags = 0;
buf->b_p_dict = empty_string_option;
buf->b_p_tsr = empty_string_option;
buf->b_p_tsrfu = empty_string_option;
@@ -5427,7 +5461,8 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
xp->xp_pattern = arg;
return;
}
- char *p = arg + strlen(arg) - 1;
+ char *const argend = arg + strlen(arg);
+ char *p = argend - 1;
if (*p == ' ' && *(p - 1) != '\\') {
xp->xp_pattern = p + 1;
return;
@@ -5614,7 +5649,7 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
// Triple-backslashed escaped file names (e.g. 'path') can also be
// delimited by space.
if ((flags & P_EXPAND) || (flags & P_COMMA) || (flags & P_COLON)) {
- for (p = arg + strlen(arg) - 1; p > xp->xp_pattern; p--) {
+ for (p = argend - 1; p > xp->xp_pattern; p--) {
// count number of backslashes before ' ' or ','
if (*p == ' ' || *p == ',' || (*p == ':' && (flags & P_COLON))) {
char *s = p;
@@ -5638,7 +5673,7 @@ void set_context_in_set_cmd(expand_T *xp, char *arg, int opt_flags)
// An option that is a list of single-character flags should always start
// at the end as we don't complete words.
if (flags & P_FLAGLIST) {
- xp->xp_pattern = arg + strlen(arg);
+ xp->xp_pattern = argend;
}
// Some options can either be using file/dir expansions, or custom value
@@ -5948,7 +5983,7 @@ int ExpandSettingSubtract(expand_T *xp, regmatch_T *regmatch, int *numMatches, c
int count = 0;
- (*matches)[count++] = xstrdup(option_val);
+ (*matches)[count++] = xmemdupz(option_val, num_flags);
if (num_flags > 1) {
// If more than one flags, split the flags up and expose each
@@ -6189,10 +6224,10 @@ bool can_bs(int what)
return vim_strchr(p_bs, what) != NULL;
}
-/// Get the local or global value of 'backupcopy'.
+/// Get the local or global value of 'backupcopy' flags.
///
/// @param buf The buffer.
-unsigned get_bkc_value(buf_T *buf)
+unsigned get_bkc_flags(buf_T *buf)
{
return buf->b_bkc_flags ? buf->b_bkc_flags : bkc_flags;
}
@@ -6208,7 +6243,7 @@ char *get_flp_value(buf_T *buf)
return buf->b_p_flp;
}
-/// Get the local or global value of the 'virtualedit' flags.
+/// Get the local or global value of 'virtualedit' flags.
unsigned get_ve_flags(win_T *wp)
{
return (wp->w_ve_flags ? wp->w_ve_flags : ve_flags) & ~(VE_NONE | VE_NONEU);
@@ -6420,30 +6455,29 @@ int get_sidescrolloff_value(win_T *wp)
return (int)(wp->w_p_siso < 0 ? p_siso : wp->w_p_siso);
}
-Dictionary get_vimoption(String name, int scope, buf_T *buf, win_T *win, Arena *arena, Error *err)
+Dict get_vimoption(String name, int scope, buf_T *buf, win_T *win, Arena *arena, Error *err)
{
OptIndex opt_idx = find_option_len(name.data, name.size);
VALIDATE_S(opt_idx != kOptInvalid, "option (not found)", name.data, {
- return (Dictionary)ARRAY_DICT_INIT;
+ return (Dict)ARRAY_DICT_INIT;
});
return vimoption2dict(&options[opt_idx], scope, buf, win, arena);
}
-Dictionary get_all_vimoptions(Arena *arena)
+Dict get_all_vimoptions(Arena *arena)
{
- Dictionary retval = arena_dict(arena, kOptIndexCount);
+ Dict retval = arena_dict(arena, kOptIndexCount);
for (OptIndex opt_idx = 0; opt_idx < kOptIndexCount; opt_idx++) {
- Dictionary opt_dict = vimoption2dict(&options[opt_idx], OPT_GLOBAL, curbuf, curwin, arena);
- PUT_C(retval, options[opt_idx].fullname, DICTIONARY_OBJ(opt_dict));
+ Dict opt_dict = vimoption2dict(&options[opt_idx], OPT_GLOBAL, curbuf, curwin, arena);
+ PUT_C(retval, options[opt_idx].fullname, DICT_OBJ(opt_dict));
}
return retval;
}
-static Dictionary vimoption2dict(vimoption_T *opt, int req_scope, buf_T *buf, win_T *win,
- Arena *arena)
+static Dict vimoption2dict(vimoption_T *opt, int req_scope, buf_T *buf, win_T *win, Arena *arena)
{
- Dictionary dict = arena_dict(arena, 13);
+ Dict dict = arena_dict(arena, 13);
PUT_C(dict, "name", CSTR_AS_OBJ(opt->fullname));
PUT_C(dict, "shortname", CSTR_AS_OBJ(opt->shortname));
diff --git a/src/nvim/option_vars.h b/src/nvim/option_vars.h
index be78a708ad..6b78007253 100644
--- a/src/nvim/option_vars.h
+++ b/src/nvim/option_vars.h
@@ -55,13 +55,13 @@
#define HIGHLIGHT_INIT \
"8:SpecialKey,~:EndOfBuffer,z:TermCursor,Z:TermCursorNC,@:NonText,d:Directory,e:ErrorMsg," \
"i:IncSearch,l:Search,y:CurSearch,m:MoreMsg,M:ModeMsg,n:LineNr,a:LineNrAbove,b:LineNrBelow," \
- "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold" \
- "r:Question,s:StatusLine,S:StatusLineNC,c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg," \
- "W:WildMenu,f:Folded,F:FoldColumn,A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn," \
- "-:Conceal,B:SpellBad,P:SpellCap,R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel," \
- "[:PmenuKind,]:PmenuKindSel,{:PmenuExtra,}:PmenuExtraSel,x:PmenuSbar,X:PmenuThumb," \
- "*:TabLine,#:TabLineSel,_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn," \
- "q:QuickFixLine,g:MsgArea,0:Whitespace,I:NormalNC"
+ "N:CursorLineNr,G:CursorLineSign,O:CursorLineFold,r:Question,s:StatusLine,S:StatusLineNC," \
+ "c:VertSplit,t:Title,v:Visual,V:VisualNOS,w:WarningMsg,W:WildMenu,f:Folded,F:FoldColumn," \
+ "A:DiffAdd,C:DiffChange,D:DiffDelete,T:DiffText,>:SignColumn,-:Conceal,B:SpellBad,P:SpellCap," \
+ "R:SpellRare,L:SpellLocal,+:Pmenu,=:PmenuSel,k:PmenuMatch,<:PmenuMatchSel,[:PmenuKind," \
+ "]:PmenuKindSel,{:PmenuExtra,}:PmenuExtraSel,x:PmenuSbar,X:PmenuThumb,*:TabLine,#:TabLineSel," \
+ "_:TabLineFill,!:CursorColumn,.:CursorLine,o:ColorColumn,q:QuickFixLine,z:StatusLineTerm," \
+ "Z:StatusLineTermNC,g:MsgArea,0:Whitespace,I:NormalNC"
// Default values for 'errorformat'.
// The "%f|%l| %m" one is used for when the contents of the quickfix window is
@@ -429,7 +429,22 @@ EXTERN char *p_cms; ///< 'commentstring'
EXTERN char *p_cpt; ///< 'complete'
EXTERN OptInt p_columns; ///< 'columns'
EXTERN int p_confirm; ///< 'confirm'
+EXTERN char *p_cia; ///< 'completeitemalign'
+EXTERN unsigned cia_flags; ///< order flags of 'completeitemalign'
EXTERN char *p_cot; ///< 'completeopt'
+EXTERN unsigned cot_flags; ///< flags from 'completeopt'
+// Keep in sync with p_cot_values in optionstr.c
+#define COT_MENU 0x001
+#define COT_MENUONE 0x002
+#define COT_ANY_MENU 0x003 // combination of menu flags
+#define COT_LONGEST 0x004 // false: insert full match,
+ // true: insert longest prefix
+#define COT_PREVIEW 0x008
+#define COT_POPUP 0x010
+#define COT_ANY_PREVIEW 0x018 // combination of preview flags
+#define COT_NOINSERT 0x020 // false: select & insert, true: noinsert
+#define COT_NOSELECT 0x040 // false: select & insert, true: noselect
+#define COT_FUZZY 0x080 // true: fuzzy match enabled
#ifdef BACKSLASH_IN_FILENAME
EXTERN char *p_csl; ///< 'completeslash'
#endif
@@ -527,6 +542,7 @@ EXTERN char *p_jop; ///< 'jumpooptions'
EXTERN unsigned jop_flags;
#define JOP_STACK 0x01
#define JOP_VIEW 0x02
+#define JOP_CLEAN 0x04
EXTERN char *p_keymap; ///< 'keymap'
EXTERN char *p_kp; ///< 'keywordprg'
EXTERN char *p_km; ///< 'keymodel'
@@ -680,7 +696,6 @@ EXTERN unsigned tpf_flags; ///< flags from 'termpastefilter'
EXTERN char *p_tfu; ///< 'tagfunc'
EXTERN char *p_spc; ///< 'spellcapcheck'
EXTERN char *p_spf; ///< 'spellfile'
-EXTERN char *p_spk; ///< 'splitkeep'
EXTERN char *p_spl; ///< 'spelllang'
EXTERN char *p_spo; ///< 'spelloptions'
EXTERN unsigned spo_flags;
@@ -697,7 +712,12 @@ EXTERN unsigned swb_flags;
#define SWB_NEWTAB 0x008
#define SWB_VSPLIT 0x010
#define SWB_USELAST 0x020
+EXTERN char *p_spk; ///< 'splitkeep'
EXTERN char *p_syn; ///< 'syntax'
+EXTERN char *p_tcl; ///< 'tabclose'
+EXTERN unsigned tcl_flags; ///< flags from 'tabclose'
+#define TCL_LEFT 0x001
+#define TCL_USELAST 0x002
EXTERN OptInt p_ts; ///< 'tabstop'
EXTERN int p_tbs; ///< 'tagbsearch'
EXTERN char *p_tc; ///< 'tagcase'
diff --git a/src/nvim/options.lua b/src/nvim/options.lua
index 5173240384..932d8f8d0e 100644
--- a/src/nvim/options.lua
+++ b/src/nvim/options.lua
@@ -1,3 +1,5 @@
+-- vim: tw=80
+
--- @class vim.option_meta
--- @field full_name string
--- @field desc? string
@@ -747,7 +749,7 @@ return {
applying 'breakindent', even if the resulting
text should normally be narrower. This prevents
text indented almost to the right window border
- occupying lot of vertical space when broken.
+ occupying lots of vertical space when broken.
(default: 20)
shift:{n} After applying 'breakindent', the wrapped line's
beginning will be shifted by the given number of
@@ -761,9 +763,9 @@ return {
list:{n} Adds an additional indent for lines that match a
numbered or bulleted list (using the
'formatlistpat' setting).
- list:-1 Uses the length of a match with 'formatlistpat'
- for indentation.
(default: 0)
+ list:-1 Uses the width of a match with 'formatlistpat' for
+ indentation.
column:{n} Indent at column {n}. Will overrule the other
sub-options. Note: an additional indent may be
added for the 'showbreak' setting.
@@ -996,9 +998,10 @@ return {
The key used in Command-line Mode to open the command-line window.
Only non-printable keys are allowed.
The key can be specified as a single character, but it is difficult to
- type. The preferred way is to use the <> notation. Examples: >vim
- exe "set cedit=\\<C-Y>"
- exe "set cedit=\\<Esc>"
+ type. The preferred way is to use |key-notation| (e.g. <Up>, <C-F>) or
+ a letter preceded with a caret (e.g. `^F` is CTRL-F). Examples: >vim
+ set cedit=^Y
+ set cedit=<Esc>
< |Nvi| also has this option, but it only uses the first character.
See |cmdwin|.
]=],
@@ -1058,6 +1061,17 @@ return {
v:fname_in name of the input file
v:fname_out name of the output file
Note that v:fname_in and v:fname_out will never be the same.
+
+ The advantage of using a function call without arguments is that it is
+ faster, see |expr-option-function|.
+
+ If the 'charconvert' expression starts with s: or |<SID>|, then it is
+ replaced with the script ID (|local-function|). Example: >vim
+ set charconvert=s:MyConvert()
+ set charconvert=<SID>SomeConvert()
+ < Otherwise the expression is evaluated in the context of the script
+ where the option was set, thus script-local items are available.
+
This option cannot be set from a |modeline| or in the |sandbox|, for
security reasons.
]=],
@@ -1217,11 +1231,10 @@ return {
used. The command-line will cover the last line of the screen when
shown.
- WARNING: `cmdheight=0` is considered experimental. Expect some
- unwanted behaviour. Some 'shortmess' flags and similar
- mechanism might fail to take effect, causing unwanted hit-enter
- prompts. Some informative messages, both from Nvim itself and
- plugins, will not be displayed.
+ WARNING: `cmdheight=0` is EXPERIMENTAL. Expect some unwanted behaviour.
+ Some 'shortmess' flags and similar mechanism might fail to take effect,
+ causing unwanted hit-enter prompts. Some informative messages, both
+ from Nvim itself and plugins, will not be displayed.
]=],
full_name = 'cmdheight',
redraw = { 'all_windows' },
@@ -1310,7 +1323,6 @@ return {
]=],
full_name = 'comments',
list = 'onecomma',
- redraw = { 'curswant' },
scope = { 'buffer' },
short_desc = N_('patterns that can start a comment line'),
tags = { 'E524', 'E525' },
@@ -1324,11 +1336,10 @@ return {
defaults = { if_true = '' },
desc = [=[
A template for a comment. The "%s" in the value is replaced with the
- comment text. For example, C uses "/*%s*/". Used for |commenting| and to
- add markers for folding, see |fold-marker|.
+ comment text, and should be padded with a space when possible.
+ Used for |commenting| and to add markers for folding, see |fold-marker|.
]=],
full_name = 'commentstring',
- redraw = { 'curswant' },
scope = { 'buffer' },
short_desc = N_('template for comments; used for fold marker'),
tags = { 'E537' },
@@ -1417,6 +1428,26 @@ return {
varname = 'p_cfu',
},
{
+ abbreviation = 'cia',
+ cb = 'did_set_completeitemalign',
+ defaults = { if_true = 'abbr,kind,menu' },
+ deny_duplicates = true,
+ desc = [=[
+ A comma-separated list of |complete-items| that controls the alignment
+ and display order of items in the popup menu during Insert mode
+ completion. The supported values are abbr, kind, and menu. These
+ options allow to customize how the completion items are shown in the
+ popup menu. Note: must always contain those three values in any
+ order.
+ ]=],
+ full_name = 'completeitemalign',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('Insert mode completion item align order'),
+ type = 'string',
+ varname = 'p_cia',
+ },
+ {
abbreviation = 'cot',
cb = 'did_set_completeopt',
defaults = { if_true = 'menu,preview' },
@@ -1443,6 +1474,10 @@ return {
completion in the preview window. Only works in
combination with "menu" or "menuone".
+ popup Show extra information about the currently selected
+ completion in a popup window. Only works in combination
+ with "menu" or "menuone". Overrides "preview".
+
noinsert Do not insert any text for a match until the user selects
a match from the menu. Only works in combination with
"menu" or "menuone". No effect if "longest" is present.
@@ -1451,14 +1486,18 @@ return {
select one from the menu. Only works in combination with
"menu" or "menuone".
- popup Show extra information about the currently selected
- completion in a popup window. Only works in combination
- with "menu" or "menuone". Overrides "preview".
+ fuzzy Enable |fuzzy-matching| for completion candidates. This
+ allows for more flexible and intuitive matching, where
+ characters can be skipped and matches can be found even
+ if the exact sequence is not typed. Only makes a
+ difference how completion candidates are reduced from the
+ list of alternatives, but not how the candidates are
+ collected (using different completion types).
]=],
expand_cb = 'expand_set_completeopt',
full_name = 'completeopt',
list = 'onecomma',
- scope = { 'global' },
+ scope = { 'global', 'buffer' },
short_desc = N_('options for Insert mode completion'),
type = 'string',
varname = 'p_cot',
@@ -1956,7 +1995,6 @@ return {
<
]=],
full_name = 'define',
- redraw = { 'curswant' },
scope = { 'global', 'buffer' },
short_desc = N_('pattern to be used to find a macro definition'),
type = 'string',
@@ -2310,9 +2348,12 @@ return {
desc = [=[
When on all Unicode emoji characters are considered to be full width.
This excludes "text emoji" characters, which are normally displayed as
- single width. Unfortunately there is no good specification for this
- and it has been determined on trial-and-error basis. Use the
- |setcellwidths()| function to change the behavior.
+ single width. However, such "text emoji" are treated as full-width
+ emoji if they are followed by the U+FE0F variant selector.
+
+ Unfortunately there is no good specification for this and it has been
+ determined on trial-and-error basis. Use the |setcellwidths()|
+ function to change the behavior.
]=],
full_name = 'emoji',
redraw = { 'all_windows', 'ui_option' },
@@ -2792,14 +2833,14 @@ return {
/* vim: set filetype=idl : */
< |FileType| |filetypes|
When a dot appears in the value then this separates two filetype
- names. Example: >c
+ names, it should therefore not be used for a filetype. Example: >c
/* vim: set filetype=c.doxygen : */
< This will use the "c" filetype first, then the "doxygen" filetype.
This works both for filetype plugins and for syntax files. More than
one dot may appear.
This option is not copied to another buffer, independent of the 's' or
'S' flag in 'cpoptions'.
- Only normal file name characters can be used, `/\*?[|<>` are illegal.
+ Only alphanumeric characters, '-' and '_' can be used.
]=],
full_name = 'filetype',
noglob = true,
@@ -3215,6 +3256,9 @@ return {
< This will invoke the mylang#Format() function in the
autoload/mylang.vim file in 'runtimepath'. |autoload|
+ The advantage of using a function call without arguments is that it is
+ faster, see |expr-option-function|.
+
The expression is also evaluated when 'textwidth' is set and adding
text beyond that limit. This happens under the same conditions as
when internal formatting is used. Make sure the cursor is kept in the
@@ -4149,11 +4193,14 @@ return {
If the expression starts with s: or |<SID>|, then it is replaced with
the script ID (|local-function|). Example: >vim
- setlocal includeexpr=s:MyIncludeExpr(v:fname)
- setlocal includeexpr=<SID>SomeIncludeExpr(v:fname)
+ setlocal includeexpr=s:MyIncludeExpr()
+ setlocal includeexpr=<SID>SomeIncludeExpr()
< Otherwise, the expression is evaluated in the context of the script
where the option was set, thus script-local items are available.
+ It is more efficient if the value is just a function call without
+ arguments, see |expr-option-function|.
+
The expression will be evaluated in the |sandbox| when set from a
modeline, see |sandbox-option|.
This option cannot be set in a modeline when 'modelineexpr' is off.
@@ -4221,7 +4268,7 @@ return {
in Insert mode as specified with the 'indentkeys' option.
When this option is not empty, it overrules the 'cindent' and
'smartindent' indenting. When 'lisp' is set, this option is
- is only used when 'lispoptions' contains "expr:1".
+ only used when 'lispoptions' contains "expr:1".
The expression is evaluated with |v:lnum| set to the line number for
which the indent is to be computed. The cursor is also in this line
when the expression is evaluated (but it may be moved around).
@@ -4233,6 +4280,9 @@ return {
< Otherwise, the expression is evaluated in the context of the script
where the option was set, thus script-local items are available.
+ The advantage of using a function call without arguments is that it is
+ faster, see |expr-option-function|.
+
The expression must return the number of spaces worth of indent. It
can return "-1" to keep the current indent (this means 'autoindent' is
used for the indent).
@@ -4487,7 +4537,7 @@ return {
{
abbreviation = 'jop',
cb = 'did_set_jumpoptions',
- defaults = { if_true = '' },
+ defaults = { if_true = 'clean' },
deny_duplicates = true,
desc = [=[
List of words that change the behavior of the |jumplist|.
@@ -4500,6 +4550,9 @@ return {
view When moving through the jumplist, |changelist|,
|alternate-file| or using |mark-motions| try to
restore the |mark-view| in which the action occurred.
+
+ clean Remove unloaded buffers from the jumplist.
+ EXPERIMENTAL: this flag may change in the future.
]=],
expand_cb = 'expand_set_jumpoptions',
full_name = 'jumpoptions',
@@ -4519,7 +4572,7 @@ return {
Setting this option to a valid keymap name has the side effect of
setting 'iminsert' to one, so that the keymap becomes effective.
'imsearch' is also set to one, unless it was -1
- Only normal file name characters can be used, `/\*?[|<>` are illegal.
+ Only alphanumeric characters, '.', '-' and '_' can be used.
]=],
full_name = 'keymap',
normal_fname_chars = true,
@@ -4612,7 +4665,7 @@ return {
part can be in one of two forms:
1. A list of pairs. Each pair is a "from" character immediately
followed by the "to" character. Examples: "aA", "aAbBcC".
- 2. A list of "from" characters, a semi-colon and a list of "to"
+ 2. A list of "from" characters, a semicolon and a list of "to"
characters. Example: "abc;ABC"
Example: "aA,fgh;FGH,cCdDeE"
Special characters need to be preceded with a backslash. These are
@@ -4725,7 +4778,7 @@ return {
update use |:redraw|.
This may occasionally cause display errors. It is only meant to be set
temporarily when performing an operation where redrawing may cause
- flickering or cause a slow down.
+ flickering or cause a slowdown.
]=],
full_name = 'lazyredraw',
scope = { 'global' },
@@ -4868,6 +4921,9 @@ return {
between tabs and spaces and for trailing blanks. Further changed by
the 'listchars' option.
+ When 'listchars' does not contain "tab" field, tabs are shown as "^I"
+ or "<09>", like how unprintable characters are displayed.
+
The cursor is displayed at the start of the space a Tab character
occupies, not at the end as usual in Normal mode. To get this cursor
position while displaying Tabs with spaces, use: >vim
@@ -5703,6 +5759,20 @@ return {
(without "unsigned" it would become "9-2019").
Using CTRL-X on "0" or CTRL-A on "18446744073709551615"
(2^64 - 1) has no effect, overflow is prevented.
+ blank If included, treat numbers as signed or unsigned based on
+ preceding whitespace. If a number with a leading dash has its
+ dash immediately preceded by a non-whitespace character (i.e.,
+ not a tab or a " "), the negative sign won't be considered as
+ part of the number. For example:
+ Using CTRL-A on "14" in "Carbon-14" results in "Carbon-15"
+ (without "blank" it would become "Carbon-13").
+ Using CTRL-X on "8" in "Carbon -8" results in "Carbon -9"
+ (because -8 is preceded by whitespace. If "unsigned" was
+ set, it would result in "Carbon -7").
+ If this format is included, overflow is prevented as if
+ "unsigned" were set. If both this format and "unsigned" are
+ included, "unsigned" will take precedence.
+
Numbers which simply begin with a digit in the range 1-9 are always
considered decimal. This also happens for numbers that are not
recognized as octal or hex.
@@ -5984,7 +6054,7 @@ return {
set path+=
< To use an environment variable, you probably need to replace the
separator. Here is an example to append $INCL, in which directory
- names are separated with a semi-colon: >vim
+ names are separated with a semicolon: >vim
let &path = &path .. "," .. substitute($INCL, ';', ',', 'g')
< Replace the ';' with a ':' or whatever separator is used. Note that
this doesn't work when $INCL contains a comma or white space.
@@ -6582,6 +6652,9 @@ return {
top are deleted if new lines exceed this limit.
Minimum is 1, maximum is 100000.
Only in |terminal| buffers.
+
+ Note: Lines that are not visible and kept in scrollback are not
+ reflown when the terminal buffer is resized horizontally.
]=],
full_name = 'scrollback',
redraw = { 'current_buffer' },
@@ -7300,7 +7373,7 @@ return {
defaults = { if_true = 'ltToOCF' },
desc = [=[
This option helps to avoid all the |hit-enter| prompts caused by file
- messages, for example with CTRL-G, and to avoid some other messages.
+ messages, for example with CTRL-G, and to avoid some other messages.
It is a list of flags:
flag meaning when present ~
l use "999L, 888B" instead of "999 lines, 888 bytes" *shm-l*
@@ -7317,8 +7390,8 @@ return {
message; also for quickfix message (e.g., ":cn")
s don't give "search hit BOTTOM, continuing at TOP" or *shm-s*
"search hit TOP, continuing at BOTTOM" messages; when using
- the search count do not show "W" after the count message (see
- S below)
+ the search count do not show "W" before the count message
+ (see |shm-S| below)
t truncate file message at the start if it is too long *shm-t*
to fit on the command-line, "<" will appear in the left most
column; ignored in Ex mode
@@ -7340,7 +7413,11 @@ return {
`:silent` was used for the command; note that this also
affects messages from 'autoread' reloading
S do not show search count message when searching, e.g. *shm-S*
- "[1/5]"
+ "[1/5]". When the "S" flag is not present (e.g. search count
+ is shown), the "search hit BOTTOM, continuing at TOP" and
+ "search hit TOP, continuing at BOTTOM" messages are only
+ indicated by a "W" (Mnemonic: Wrapped) letter before the
+ search count statistics.
This gives you the opportunity to avoid that a change between buffers
requires you to hit <Enter>, but still gives as useful a message as
@@ -7889,7 +7966,7 @@ return {
minus two.
timeout:{millisec} Limit the time searching for suggestions to
- {millisec} milli seconds. Applies to the following
+ {millisec} milliseconds. Applies to the following
methods. When omitted the limit is 5000. When
negative there is no limit.
@@ -7909,9 +7986,11 @@ return {
The file is used for all languages.
expr:{expr} Evaluate expression {expr}. Use a function to avoid
- trouble with spaces. |v:val| holds the badly spelled
- word. The expression must evaluate to a List of
- Lists, each with a suggestion and a score.
+ trouble with spaces. Best is to call a function
+ without arguments, see |expr-option-function|.
+ |v:val| holds the badly spelled word. The expression
+ must evaluate to a List of Lists, each with a
+ suggestion and a score.
Example:
[['the', 33], ['that', 44]] ~
Set 'verbose' and use |z=| to see the scores that the
@@ -7997,7 +8076,8 @@ return {
non-blank of the line. When off the cursor is kept in the same column
(if possible). This applies to the commands:
- CTRL-D, CTRL-U, CTRL-B, CTRL-F, "G", "H", "M", "L", "gg"
- - "d", "<<" and ">>" with a linewise operator
+ - "d", "<<", "==" and ">>" with a linewise operator
+ (|operator-resulting-pos|)
- "%" with a count
- buffer changing commands (CTRL-^, :bnext, :bNext, etc.)
- Ex commands that only have a line number, e.g., ":25" or ":+".
@@ -8017,7 +8097,6 @@ return {
cb = 'did_set_statuscolumn',
defaults = { if_true = '' },
desc = [=[
- EXPERIMENTAL
When non-empty, this option determines the content of the area to the
side of a window, normally containing the fold, sign and number columns.
The format of this option is like that of 'statusline'.
@@ -8025,8 +8104,7 @@ return {
Some of the items from the 'statusline' format are different for
'statuscolumn':
- %l line number of currently drawn line
- %r relative line number of currently drawn line
+ %l line number column for currently drawn line
%s sign column for currently drawn line
%C fold column for currently drawn line
@@ -8053,11 +8131,8 @@ return {
handler should be written with this in mind.
Examples: >vim
- " Relative number with bar separator and click handlers:
- set statuscolumn=%@SignCb@%s%=%T%@NumCb@%r│%T
-
- " Right aligned relative cursor line number:
- let &stc='%=%{v:relnum?v:relnum:v:lnum} '
+ " Line number with bar separator and click handlers:
+ set statuscolumn=%@SignCb@%s%=%T%@NumCb@%l│%T
" Line numbers in hexadecimal for non wrapped part of lines:
let &stc='%=%{v:virtnum>0?"":printf("%x",v:lnum)} '
@@ -8468,7 +8543,7 @@ return {
Syntax autocommand event is triggered with the value as argument.
This option is not copied to another buffer, independent of the 's' or
'S' flag in 'cpoptions'.
- Only normal file name characters can be used, `/\*?[|<>` are illegal.
+ Only alphanumeric characters, '.', '-' and '_' can be used.
]=],
full_name = 'syntax',
noglob = true,
@@ -8479,6 +8554,30 @@ return {
varname = 'p_syn',
},
{
+ abbreviation = 'tcl',
+ cb = 'did_set_tabclose',
+ defaults = { if_true = '' },
+ deny_duplicates = true,
+ desc = [=[
+ This option controls the behavior when closing tab pages (e.g., using
+ |:tabclose|). When empty Vim goes to the next (right) tab page.
+
+ Possible values (comma-separated list):
+ left If included, go to the previous tab page instead of
+ the next one.
+ uselast If included, go to the previously used tab page if
+ possible. This option takes precedence over the
+ others.
+ ]=],
+ expand_cb = 'expand_set_tabclose',
+ full_name = 'tabclose',
+ list = 'onecomma',
+ scope = { 'global' },
+ short_desc = N_('which tab page to focus when closing a tab'),
+ type = 'string',
+ varname = 'p_tcl',
+ },
+ {
abbreviation = 'tal',
cb = 'did_set_tabline',
defaults = { if_true = '' },
@@ -8970,7 +9069,7 @@ return {
desc = [=[
When on, the title of the window will be set to the value of
'titlestring' (if it is not empty), or to:
- filename [+=-] (path) - NVIM
+ filename [+=-] (path) - Nvim
Where:
filename the name of the file being edited
- indicates the file cannot be modified, 'ma' off
@@ -8978,11 +9077,11 @@ return {
= indicates the file is read-only
=+ indicates the file is read-only and modified
(path) is the path of the file being edited
- - NVIM the server name |v:servername| or "NVIM"
+ - Nvim the server name |v:servername| or "Nvim"
]=],
full_name = 'title',
scope = { 'global' },
- short_desc = N_('Vim set the title of the window'),
+ short_desc = N_('set the title of the window'),
type = 'boolean',
varname = 'p_title',
},
@@ -9579,7 +9678,12 @@ return {
Some keys will not work, such as CTRL-C, <CR> and Enter.
<Esc> can be used, but hitting it twice in a row will still exit
command-line as a failsafe measure.
- Although 'wc' is a number option, you can set it to a special key: >vim
+ Although 'wc' is a number option, it can be specified as a number, a
+ single character, a |key-notation| (e.g. <Up>, <C-F>) or a letter
+ preceded with a caret (e.g. `^F` is CTRL-F): >vim
+ :set wc=27
+ :set wc=X
+ :set wc=^I
set wc=<Tab>
<
]=],
@@ -9895,7 +9999,6 @@ return {
]=],
full_name = 'winfixbuf',
pv_name = 'p_wfb',
- redraw = { 'current_window' },
scope = { 'window' },
short_desc = N_('pin a window to a specific buffer'),
type = 'boolean',
diff --git a/src/nvim/optionstr.c b/src/nvim/optionstr.c
index 29433ddbb5..c8f19d7ccf 100644
--- a/src/nvim/optionstr.c
+++ b/src/nvim/optionstr.c
@@ -14,6 +14,7 @@
#include "nvim/diff.h"
#include "nvim/digraph.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
#include "nvim/eval/vars.h"
@@ -81,7 +82,7 @@ static char *(p_dip_values[]) = { "filler", "context:", "iblank", "icase",
"closeoff", "hiddenoff", "foldcolumn:", "followwrap", "internal",
"indent-heuristic", "linematch:", "algorithm:", NULL };
static char *(p_dip_algorithm_values[]) = { "myers", "minimal", "patience", "histogram", NULL };
-static char *(p_nf_values[]) = { "bin", "octal", "hex", "alpha", "unsigned", NULL };
+static char *(p_nf_values[]) = { "bin", "octal", "hex", "alpha", "unsigned", "blank", NULL };
static char *(p_ff_values[]) = { FF_UNIX, FF_DOS, FF_MAC, NULL };
static char *(p_cb_values[]) = { "unnamed", "unnamedplus", NULL };
static char *(p_cmp_values[]) = { "internal", "keepascii", NULL };
@@ -97,11 +98,13 @@ static char *(p_ssop_values[]) = { "buffers", "winpos", "resize", "winsize", "lo
"options", "help", "blank", "globals", "slash", "unix", "sesdir",
"curdir", "folds", "cursor", "tabpages", "terminal", "skiprtp",
NULL };
-// Keep in sync with SWB_ flags in option_defs.h
+// Keep in sync with SWB_ flags in option_vars.h
static char *(p_swb_values[]) = { "useopen", "usetab", "split", "newtab", "vsplit", "uselast",
NULL };
static char *(p_spk_values[]) = { "cursor", "screen", "topline", NULL };
static char *(p_tc_values[]) = { "followic", "ignore", "match", "followscs", "smart", NULL };
+// Keep in sync with TCL_ flags in option_vars.h
+static char *(p_tcl_values[]) = { "left", "uselast", NULL };
static char *(p_ve_values[]) = { "block", "insert", "all", "onemore", "none", "NONE", NULL };
// Note: Keep this in sync with check_opt_wim()
static char *(p_wim_values[]) = { "full", "longest", "list", "lastused", NULL };
@@ -121,8 +124,8 @@ static char *(p_bs_values[]) = { "indent", "eol", "start", "nostop", NULL };
static char *(p_fdm_values[]) = { "manual", "expr", "marker", "indent",
"syntax", "diff", NULL };
static char *(p_fcl_values[]) = { "all", NULL };
-static char *(p_cot_values[]) = { "menu", "menuone", "longest", "preview", "noinsert", "noselect",
- "popup", NULL };
+static char *(p_cot_values[]) = { "menu", "menuone", "longest", "preview", "popup",
+ "noinsert", "noselect", "fuzzy", NULL };
#ifdef BACKSLASH_IN_FILENAME
static char *(p_csl_values[]) = { "slash", "backslash", NULL };
#endif
@@ -136,7 +139,7 @@ static char *(p_fdc_values[]) = { "auto", "auto:1", "auto:2", "auto:3", "auto:4"
"5", "6", "7", "8", "9", NULL };
static char *(p_spo_values[]) = { "camel", "noplainbuffer", NULL };
static char *(p_icm_values[]) = { "nosplit", "split", NULL };
-static char *(p_jop_values[]) = { "stack", "view", NULL };
+static char *(p_jop_values[]) = { "stack", "view", "clean", NULL };
static char *(p_tpf_values[]) = { "BS", "HT", "FF", "ESC", "DEL", "C0", "C1", NULL };
static char *(p_rdb_values[]) = { "compositor", "nothrottle", "invalid", "nodelta", "line",
"flush", NULL };
@@ -157,6 +160,7 @@ void didset_string_options(void)
opt_strings_flags(p_cmp, p_cmp_values, &cmp_flags, true);
opt_strings_flags(p_bkc, p_bkc_values, &bkc_flags, true);
opt_strings_flags(p_bo, p_bo_values, &bo_flags, true);
+ opt_strings_flags(p_cot, p_cot_values, &cot_flags, true);
opt_strings_flags(p_ssop, p_ssop_values, &ssop_flags, true);
opt_strings_flags(p_vop, p_ssop_values, &vop_flags, true);
opt_strings_flags(p_fdo, p_fdo_values, &fdo_flags, true);
@@ -167,6 +171,7 @@ void didset_string_options(void)
opt_strings_flags(p_tpf, p_tpf_values, &tpf_flags, true);
opt_strings_flags(p_ve, p_ve_values, &ve_flags, true);
opt_strings_flags(p_swb, p_swb_values, &swb_flags, true);
+ opt_strings_flags(p_tcl, p_tcl_values, &tcl_flags, true);
opt_strings_flags(p_wop, p_wop_values, &wop_flags, true);
opt_strings_flags(p_cb, p_cb_values, &cb_flags, true);
}
@@ -218,6 +223,7 @@ void check_buf_options(buf_T *buf)
check_string_option(&buf->b_p_ft);
check_string_option(&buf->b_p_cinw);
check_string_option(&buf->b_p_cinsd);
+ check_string_option(&buf->b_p_cot);
check_string_option(&buf->b_p_cpt);
check_string_option(&buf->b_p_cfu);
check_string_option(&buf->b_p_ofu);
@@ -300,26 +306,26 @@ int check_signcolumn(win_T *wp)
wp->w_minscwidth = 0;
wp->w_maxscwidth = 1;
}
- return OK;
- }
-
- if (strncmp(val, "auto:", 5) != 0
- || strlen(val) != 8
- || !ascii_isdigit(val[5])
- || val[6] != '-'
- || !ascii_isdigit(val[7])) {
- return FAIL;
- }
-
- // auto:<NUM>-<NUM>
- int min = val[5] - '0';
- int max = val[7] - '0';
- if (min < 1 || max < 2 || min > 8 || min >= max) {
- return FAIL;
+ } else {
+ if (strncmp(val, "auto:", 5) != 0
+ || strlen(val) != 8
+ || !ascii_isdigit(val[5])
+ || val[6] != '-'
+ || !ascii_isdigit(val[7])) {
+ return FAIL;
+ }
+ // auto:<NUM>-<NUM>
+ int min = val[5] - '0';
+ int max = val[7] - '0';
+ if (min < 1 || max < 2 || min > 8 || min >= max) {
+ return FAIL;
+ }
+ wp->w_minscwidth = min;
+ wp->w_maxscwidth = max;
}
- wp->w_minscwidth = min;
- wp->w_maxscwidth = max;
+ int scwidth = wp->w_minscwidth <= 0 ? 0 : MIN(wp->w_maxscwidth, wp->w_scwidth);
+ wp->w_scwidth = MAX(wp->w_minscwidth, scwidth);
return OK;
}
@@ -989,13 +995,71 @@ int expand_set_complete(optexpand_T *args, int *numMatches, char ***matches)
matches);
}
+/// The 'completeitemalign' option is changed.
+const char *did_set_completeitemalign(optset_T *args)
+{
+ char *p = p_cia;
+ unsigned new_cia_flags = 0;
+ bool seen[3] = { false, false, false };
+ int count = 0;
+ char buf[10];
+ while (*p) {
+ copy_option_part(&p, buf, sizeof(buf), ",");
+ if (count >= 3) {
+ return e_invarg;
+ }
+ if (strequal(buf, "abbr")) {
+ if (seen[CPT_ABBR]) {
+ return e_invarg;
+ }
+ new_cia_flags = new_cia_flags * 10 + CPT_ABBR;
+ seen[CPT_ABBR] = true;
+ count++;
+ } else if (strequal(buf, "kind")) {
+ if (seen[CPT_KIND]) {
+ return e_invarg;
+ }
+ new_cia_flags = new_cia_flags * 10 + CPT_KIND;
+ seen[CPT_KIND] = true;
+ count++;
+ } else if (strequal(buf, "menu")) {
+ if (seen[CPT_MENU]) {
+ return e_invarg;
+ }
+ new_cia_flags = new_cia_flags * 10 + CPT_MENU;
+ seen[CPT_MENU] = true;
+ count++;
+ } else {
+ return e_invarg;
+ }
+ }
+ if (new_cia_flags == 0 || count != 3) {
+ return e_invarg;
+ }
+ cia_flags = new_cia_flags;
+ return NULL;
+}
+
/// The 'completeopt' option is changed.
const char *did_set_completeopt(optset_T *args FUNC_ATTR_UNUSED)
{
- if (check_opt_strings(p_cot, p_cot_values, true) != OK) {
+ buf_T *buf = (buf_T *)args->os_buf;
+ char *cot = p_cot;
+ unsigned *flags = &cot_flags;
+
+ if (args->os_flags & OPT_LOCAL) {
+ cot = buf->b_p_cot;
+ flags = &buf->b_cot_flags;
+ }
+
+ if (check_opt_strings(cot, p_cot_values, true) != OK) {
return e_invarg;
}
- completeopt_was_set();
+
+ if (opt_strings_flags(cot, p_cot_values, flags, true) != OK) {
+ return e_invarg;
+ }
+
return NULL;
}
@@ -2019,8 +2083,6 @@ const char *did_set_signcolumn(optset_T *args)
if (check_signcolumn(win) != OK) {
return e_invarg;
}
- int scwidth = win->w_minscwidth <= 0 ? 0 : MIN(win->w_maxscwidth, win->w_scwidth);
- win->w_scwidth = MAX(win->w_minscwidth, scwidth);
// 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') || win->w_minscwidth == SCL_NUM) {
@@ -2191,6 +2253,21 @@ int expand_set_switchbuf(optexpand_T *args, int *numMatches, char ***matches)
matches);
}
+/// The 'tabclose' option is changed.
+const char *did_set_tabclose(optset_T *args FUNC_ATTR_UNUSED)
+{
+ return did_set_opt_flags(p_tcl, p_tcl_values, &tcl_flags, true);
+}
+
+int expand_set_tabclose(optexpand_T *args, int *numMatches, char ***matches)
+{
+ return expand_set_opt_string(args,
+ p_tcl_values,
+ ARRAY_SIZE(p_tcl_values) - 1,
+ numMatches,
+ matches);
+}
+
/// The 'tabline' option is changed.
const char *did_set_tabline(optset_T *args)
{
diff --git a/src/nvim/os/env.c b/src/nvim/os/env.c
index 5a79004c41..ccf6c9554a 100644
--- a/src/nvim/os/env.c
+++ b/src/nvim/os/env.c
@@ -63,13 +63,13 @@ const char *os_getenv(const char *name)
FUNC_ATTR_NONNULL_ALL
{
char *e = NULL;
- if (name[0] == '\0') {
+ if (name[0] == NUL) {
return NULL;
}
int r = 0;
if (map_has(cstr_t, &envmap, name)
&& !!(e = (char *)pmap_get(cstr_t)(&envmap, name))) {
- if (e[0] != '\0') {
+ if (e[0] != NUL) {
// Found non-empty cached env var.
// NOTE: This risks incoherence if an in-process library changes the
// environment without going through our os_setenv() wrapper. If
@@ -85,11 +85,11 @@ const char *os_getenv(const char *name)
if (r == UV_ENOBUFS) {
e = xmalloc(size);
r = uv_os_getenv(name, e, &size);
- if (r != 0 || size == 0 || e[0] == '\0') {
+ if (r != 0 || size == 0 || e[0] == NUL) {
XFREE_CLEAR(e);
goto end;
}
- } else if (r != 0 || size == 0 || buf[0] == '\0') {
+ } else if (r != 0 || size == 0 || buf[0] == NUL) {
e = NULL;
goto end;
} else {
@@ -110,7 +110,7 @@ end:
bool os_env_exists(const char *name)
FUNC_ATTR_NONNULL_ALL
{
- if (name[0] == '\0') {
+ if (name[0] == NUL) {
return false;
}
// Use a tiny buffer because we don't care about the value: if uv_os_getenv()
@@ -134,14 +134,14 @@ bool os_env_exists(const char *name)
int os_setenv(const char *name, const char *value, int overwrite)
FUNC_ATTR_NONNULL_ALL
{
- if (name[0] == '\0') {
+ if (name[0] == NUL) {
return -1;
}
#ifdef MSWIN
if (!overwrite && os_getenv(name) != NULL) {
return 0;
}
- if (value[0] == '\0') {
+ if (value[0] == NUL) {
// Windows (Vim-compat): Empty string undefines the env var.
return os_unsetenv(name);
}
@@ -174,7 +174,7 @@ int os_setenv(const char *name, const char *value, int overwrite)
int os_unsetenv(const char *name)
FUNC_ATTR_NONNULL_ALL
{
- if (name[0] == '\0') {
+ if (name[0] == NUL) {
return -1;
}
pmap_del2(&envmap, name);
@@ -344,7 +344,7 @@ char *os_getenvname_at_index(size_t index)
#endif
}
-/// Get the process ID of the Neovim process.
+/// Get the process ID of the Nvim process.
///
/// @return the process ID.
int64_t os_get_pid(void)
@@ -366,7 +366,7 @@ void os_get_hostname(char *hostname, size_t size)
struct utsname vutsname;
if (uname(&vutsname) < 0) {
- *hostname = '\0';
+ *hostname = NUL;
} else {
xstrlcpy(hostname, vutsname.nodename, size);
}
@@ -374,12 +374,12 @@ void os_get_hostname(char *hostname, size_t size)
wchar_t host_utf16[MAX_COMPUTERNAME_LENGTH + 1];
DWORD host_wsize = sizeof(host_utf16) / sizeof(host_utf16[0]);
if (GetComputerNameW(host_utf16, &host_wsize) == 0) {
- *hostname = '\0';
+ *hostname = NUL;
DWORD err = GetLastError();
semsg("GetComputerNameW failed: %d", err);
return;
}
- host_utf16[host_wsize] = '\0';
+ host_utf16[host_wsize] = NUL;
char *host_utf8;
int conversion_result = utf16_to_utf8(host_utf16, -1, &host_utf8);
@@ -391,27 +391,38 @@ void os_get_hostname(char *hostname, size_t size)
xfree(host_utf8);
#else
emsg("os_get_hostname failed: missing uname()");
- *hostname = '\0';
+ *hostname = NUL;
#endif
}
-/// To get the "real" home directory:
+/// The "real" home directory as determined by `init_homedir`.
+static char *homedir = NULL;
+static char *os_uv_homedir(void);
+
+/// Gets the "real", resolved user home directory as determined by `init_homedir`.
+const char *os_homedir(void)
+{
+ if (!homedir) {
+ emsg("os_homedir failed: homedir not initialized");
+ return NULL;
+ }
+ return homedir;
+}
+
+/// Sets `homedir` to the "real", resolved user home directory, as follows:
/// 1. get value of $HOME
/// 2. if $HOME is not set, try the following
/// For Windows:
/// 1. assemble homedir using HOMEDRIVE and HOMEPATH
-/// 2. try os_homedir()
+/// 2. try os_uv_homedir()
/// 3. resolve a direct reference to another system variable
/// 4. guess C drive
/// For Unix:
-/// 1. try os_homedir()
+/// 1. try os_uv_homedir()
/// 2. go to that directory
/// This also works with mounts and links.
/// Don't do this for Windows, it will change the "current dir" for a drive.
/// 3. fall back to current working directory as a last resort
-static char *homedir = NULL;
-static char *os_homedir(void);
-
void init_homedir(void)
{
// In case we are called a second time.
@@ -440,7 +451,7 @@ void init_homedir(void)
}
}
if (var == NULL) {
- var = os_homedir();
+ var = os_uv_homedir();
}
// Weird but true: $HOME may contain an indirect reference to another
@@ -471,7 +482,7 @@ void init_homedir(void)
#ifdef UNIX
if (var == NULL) {
- var = os_homedir();
+ var = os_uv_homedir();
}
// Get the actual path. This resolves links.
@@ -492,7 +503,7 @@ void init_homedir(void)
static char homedir_buf[MAXPATHL];
-static char *os_homedir(void)
+static char *os_uv_homedir(void)
{
homedir_buf[0] = NUL;
size_t homedir_size = MAXPATHL;
@@ -885,9 +896,9 @@ void vim_get_prefix_from_exepath(char *exe_name)
// but c_grammar.lua does not recognize it (yet).
xstrlcpy(exe_name, get_vim_var_str(VV_PROGPATH), MAXPATHL * sizeof(*exe_name));
char *path_end = path_tail_with_sep(exe_name);
- *path_end = '\0'; // remove the trailing "nvim.exe"
+ *path_end = NUL; // remove the trailing "nvim.exe"
path_end = path_tail(exe_name);
- *path_end = '\0'; // remove the trailing "bin/"
+ *path_end = NUL; // remove the trailing "bin/"
}
/// Vim getenv() wrapper with special handling of $HOME, $VIM, $VIMRUNTIME,
diff --git a/src/nvim/os/fileio.c b/src/nvim/os/fileio.c
index da6fb13768..1981d0dfd4 100644
--- a/src/nvim/os/fileio.c
+++ b/src/nvim/os/fileio.c
@@ -21,8 +21,6 @@
#include "nvim/os/fileio.h"
#include "nvim/os/fs.h"
#include "nvim/os/os_defs.h"
-#include "nvim/rbuffer.h"
-#include "nvim/rbuffer_defs.h"
#include "nvim/types_defs.h"
#ifdef HAVE_SYS_UIO_H
@@ -120,12 +118,10 @@ int file_open_fd(FileDescriptor *const ret_fp, const int fd, const int flags)
assert(!ret_fp->wr || !ret_fp->non_blocking);
ret_fp->fd = fd;
ret_fp->eof = false;
- ret_fp->rv = rbuffer_new(kRWBufferSize);
- ret_fp->_error = 0;
- if (ret_fp->wr) {
- ret_fp->rv->data = ret_fp;
- ret_fp->rv->full_cb = (rbuffer_callback)&file_rb_write_full_cb;
- }
+ ret_fp->buffer = alloc_block();
+ ret_fp->read_pos = ret_fp->buffer;
+ ret_fp->write_pos = ret_fp->buffer;
+ ret_fp->bytes_read = 0;
return 0;
}
@@ -140,6 +136,19 @@ int file_open_stdin(FileDescriptor *fp)
return error;
}
+/// opens buffer for reading
+void file_open_buffer(FileDescriptor *ret_fp, char *data, size_t len)
+{
+ ret_fp->wr = false;
+ ret_fp->non_blocking = false;
+ ret_fp->fd = -1;
+ ret_fp->eof = true;
+ ret_fp->buffer = NULL; // we don't take ownership
+ ret_fp->read_pos = data;
+ ret_fp->write_pos = data + len;
+ ret_fp->bytes_read = 0;
+}
+
/// Close file and free its buffer
///
/// @param[in,out] fp File to close.
@@ -149,32 +158,19 @@ int file_open_stdin(FileDescriptor *fp)
int file_close(FileDescriptor *const fp, const bool do_fsync)
FUNC_ATTR_NONNULL_ALL
{
+ if (fp->fd < 0) {
+ return 0;
+ }
+
const int flush_error = (do_fsync ? file_fsync(fp) : file_flush(fp));
const int close_error = os_close(fp->fd);
- rbuffer_free(fp->rv);
+ free_block(fp->buffer);
if (close_error != 0) {
return close_error;
}
return flush_error;
}
-/// Flush file modifications to disk
-///
-/// @param[in,out] fp File to work with.
-///
-/// @return 0 or error code.
-int file_flush(FileDescriptor *const fp)
- FUNC_ATTR_NONNULL_ALL
-{
- if (!fp->wr) {
- return 0;
- }
- file_rb_write_full_cb(fp->rv, fp);
- const int error = fp->_error;
- fp->_error = 0;
- return error;
-}
-
/// Flush file modifications to disk and run fsync()
///
/// @param[in,out] fp File to work with.
@@ -200,36 +196,29 @@ int file_fsync(FileDescriptor *const fp)
return 0;
}
-/// Buffer used for writing
-///
-/// Like IObuff, but allows file_\* callers not to care about spoiling it.
-static char writebuf[kRWBufferSize];
-
-/// Function run when RBuffer is full when writing to a file
-///
-/// Actually does writing to the file, may also be invoked directly.
+/// Flush file modifications to disk
///
-/// @param[in,out] rv RBuffer instance used.
/// @param[in,out] fp File to work with.
-static void file_rb_write_full_cb(RBuffer *const rv, void *const fp_in)
+///
+/// @return 0 or error code.
+int file_flush(FileDescriptor *fp)
FUNC_ATTR_NONNULL_ALL
{
- FileDescriptor *const fp = fp_in;
- assert(fp->wr);
- assert(rv->data == (void *)fp);
- if (rbuffer_size(rv) == 0) {
- return;
+ if (!fp->wr) {
+ return 0;
}
- const size_t read_bytes = rbuffer_read(rv, writebuf, kRWBufferSize);
- const ptrdiff_t wres = os_write(fp->fd, writebuf, read_bytes,
+
+ ptrdiff_t to_write = fp->write_pos - fp->read_pos;
+ if (to_write == 0) {
+ return 0;
+ }
+ const ptrdiff_t wres = os_write(fp->fd, fp->read_pos, (size_t)to_write,
fp->non_blocking);
- if (wres != (ptrdiff_t)read_bytes) {
- if (wres >= 0) {
- fp->_error = UV_EIO;
- } else {
- fp->_error = (int)wres;
- }
+ fp->read_pos = fp->write_pos = fp->buffer;
+ if (wres != to_write) {
+ return (wres >= 0) ? UV_EIO : (int)wres;
}
+ return 0;
}
/// Read from file
@@ -244,79 +233,98 @@ ptrdiff_t file_read(FileDescriptor *const fp, char *const ret_buf, const size_t
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
assert(!fp->wr);
- char *buf = ret_buf;
- size_t read_remaining = size;
- RBuffer *const rv = fp->rv;
+ size_t from_buffer = MIN((size_t)(fp->write_pos - fp->read_pos), size);
+ memcpy(ret_buf, fp->read_pos, from_buffer);
+
+ char *buf = ret_buf + from_buffer;
+ size_t read_remaining = size - from_buffer;
+ if (!read_remaining) {
+ fp->bytes_read += from_buffer;
+ fp->read_pos += from_buffer;
+ return (ptrdiff_t)from_buffer;
+ }
+
+ // at this point, we have consumed all of an existing buffer. restart from the beginning
+ fp->read_pos = fp->write_pos = fp->buffer;
+
+#ifdef HAVE_READV
bool called_read = false;
while (read_remaining) {
- const size_t rv_size = rbuffer_size(rv);
- if (rv_size > 0) {
- const size_t rsize = rbuffer_read(rv, buf, MIN(rv_size, read_remaining));
- buf += rsize;
- read_remaining -= rsize;
- }
- if (fp->eof
- // Allow only at most one os_read[v] call.
- || (called_read && fp->non_blocking)) {
+ // Allow only at most one os_read[v] call.
+ if (fp->eof || (called_read && fp->non_blocking)) {
break;
}
- if (read_remaining) {
- assert(rbuffer_size(rv) == 0);
- rbuffer_reset(rv);
-#ifdef HAVE_READV
- // If there is readv() syscall, then take an opportunity to populate
- // both target buffer and RBuffer at once, …
- size_t write_count;
- struct iovec iov[] = {
- { .iov_base = buf, .iov_len = read_remaining },
- { .iov_base = rbuffer_write_ptr(rv, &write_count),
- .iov_len = kRWBufferSize },
- };
- assert(write_count == kRWBufferSize);
- const ptrdiff_t r_ret = os_readv(fp->fd, &fp->eof, iov,
- ARRAY_SIZE(iov), fp->non_blocking);
- if (r_ret > 0) {
- if (r_ret > (ptrdiff_t)read_remaining) {
- rbuffer_produced(rv, (size_t)(r_ret - (ptrdiff_t)read_remaining));
- read_remaining = 0;
- } else {
- buf += (size_t)r_ret;
- read_remaining -= (size_t)r_ret;
- }
- } else if (r_ret < 0) {
- return r_ret;
- }
-#else
- if (read_remaining >= kRWBufferSize) {
- // …otherwise leave RBuffer empty and populate only target buffer,
- // because filtering information through rbuffer will be more syscalls.
- const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, buf, read_remaining,
- fp->non_blocking);
- if (r_ret >= 0) {
- read_remaining -= (size_t)r_ret;
- return (ptrdiff_t)(size - read_remaining);
- } else if (r_ret < 0) {
- return r_ret;
- }
+ // If there is readv() syscall, then take an opportunity to populate
+ // both target buffer and RBuffer at once, …
+ struct iovec iov[] = {
+ { .iov_base = buf, .iov_len = read_remaining },
+ { .iov_base = fp->write_pos,
+ .iov_len = ARENA_BLOCK_SIZE },
+ };
+ const ptrdiff_t r_ret = os_readv(fp->fd, &fp->eof, iov,
+ ARRAY_SIZE(iov), fp->non_blocking);
+ if (r_ret > 0) {
+ if (r_ret > (ptrdiff_t)read_remaining) {
+ fp->write_pos += (size_t)(r_ret - (ptrdiff_t)read_remaining);
+ read_remaining = 0;
} else {
- size_t write_count;
- const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof,
- rbuffer_write_ptr(rv, &write_count),
- kRWBufferSize, fp->non_blocking);
- assert(write_count == kRWBufferSize);
- if (r_ret > 0) {
- rbuffer_produced(rv, (size_t)r_ret);
- } else if (r_ret < 0) {
- return r_ret;
- }
+ buf += r_ret;
+ read_remaining -= (size_t)r_ret;
}
-#endif
- called_read = true;
+ } else if (r_ret < 0) {
+ return r_ret;
}
+ called_read = true;
}
+#else
+ if (fp->eof) {
+ // already eof, cannot read more
+ } else if (read_remaining >= ARENA_BLOCK_SIZE) {
+ // …otherwise leave fp->buffer empty and populate only target buffer,
+ // because filtering information through rbuffer will be more syscalls.
+ const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, buf, read_remaining,
+ fp->non_blocking);
+ if (r_ret >= 0) {
+ read_remaining -= (size_t)r_ret;
+ } else if (r_ret < 0) {
+ return r_ret;
+ }
+ } else {
+ const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof,
+ fp->write_pos,
+ ARENA_BLOCK_SIZE, fp->non_blocking);
+ if (r_ret < 0) {
+ return r_ret;
+ } else {
+ fp->write_pos += r_ret;
+ size_t to_copy = MIN((size_t)r_ret, read_remaining);
+ memcpy(buf, fp->read_pos, to_copy);
+ fp->read_pos += to_copy;
+ read_remaining -= to_copy;
+ }
+ }
+#endif
+
+ fp->bytes_read += (size - read_remaining);
return (ptrdiff_t)(size - read_remaining);
}
+/// try to read already buffered data in place
+///
+/// @return NULL if enough data is not available
+/// valid pointer to chunk of "size". pointer becomes invalid in the next "file_read" call!
+char *file_try_read_buffered(FileDescriptor *const fp, const size_t size)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ if ((size_t)(fp->write_pos - fp->read_pos) >= size) {
+ char *ret = fp->read_pos;
+ fp->read_pos += size;
+ fp->bytes_read += size;
+ return ret;
+ }
+ return NULL;
+}
+
/// Write to a file
///
/// @param[in] fd File descriptor to write to.
@@ -328,51 +336,67 @@ ptrdiff_t file_write(FileDescriptor *const fp, const char *const buf, const size
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
{
assert(fp->wr);
- const size_t written = rbuffer_write(fp->rv, buf, size);
- if (fp->_error != 0) {
- const int error = fp->_error;
- fp->_error = 0;
- return error;
- } else if (written != size) {
- return UV_EIO;
+ // includes the trivial case of size==0
+ if (size < file_space(fp)) {
+ memcpy(fp->write_pos, buf, size);
+ fp->write_pos += size;
+ return (ptrdiff_t)size;
}
- return (ptrdiff_t)written;
-}
-/// Buffer used for skipping. Its contents is undefined and should never be
-/// used.
-static char skipbuf[kRWBufferSize];
+ // TODO(bfredl): just as for reading, use iovec to combine fp->buffer with buf
+ int status = file_flush(fp);
+ if (status < 0) {
+ return status;
+ }
+
+ if (size < ARENA_BLOCK_SIZE) {
+ memcpy(fp->write_pos, buf, size);
+ fp->write_pos += size;
+ return (ptrdiff_t)size;
+ }
+
+ const ptrdiff_t wres = os_write(fp->fd, buf, size,
+ fp->non_blocking);
+ return (wres != (ptrdiff_t)size && wres >= 0) ? UV_EIO : wres;
+}
/// Skip some bytes
///
/// This is like `fseek(fp, size, SEEK_CUR)`, but actual implementation simply
-/// reads to a buffer and discards the result.
+/// reads to the buffer and discards the result.
ptrdiff_t file_skip(FileDescriptor *const fp, const size_t size)
FUNC_ATTR_NONNULL_ALL
{
assert(!fp->wr);
- size_t read_bytes = 0;
- do {
- const ptrdiff_t new_read_bytes =
- file_read(fp, skipbuf, MIN(size - read_bytes, sizeof(skipbuf)));
- if (new_read_bytes < 0) {
- return new_read_bytes;
- } else if (new_read_bytes == 0) {
+ size_t from_buffer = MIN((size_t)(fp->write_pos - fp->read_pos), size);
+ size_t skip_remaining = size - from_buffer;
+ if (skip_remaining == 0) {
+ fp->read_pos += from_buffer;
+ fp->bytes_read += from_buffer;
+ return (ptrdiff_t)from_buffer;
+ }
+
+ fp->read_pos = fp->write_pos = fp->buffer;
+ bool called_read = false;
+ while (skip_remaining > 0) {
+ // Allow only at most one os_read[v] call.
+ if (fp->eof || (called_read && fp->non_blocking)) {
break;
}
- read_bytes += (size_t)new_read_bytes;
- } while (read_bytes < size && !file_eof(fp));
-
- return (ptrdiff_t)read_bytes;
-}
+ const ptrdiff_t r_ret = os_read(fp->fd, &fp->eof, fp->buffer, ARENA_BLOCK_SIZE,
+ fp->non_blocking);
+ if (r_ret < 0) {
+ return r_ret;
+ } else if ((size_t)r_ret > skip_remaining) {
+ fp->read_pos = fp->buffer + skip_remaining;
+ fp->write_pos = fp->buffer + r_ret;
+ fp->bytes_read += size;
+ return (ptrdiff_t)size;
+ }
+ skip_remaining -= (size_t)r_ret;
+ called_read = true;
+ }
-/// Print error which occurs when failing to write msgpack data
-///
-/// @param[in] error Error code of the error to print.
-///
-/// @return -1 (error return for msgpack_packer callbacks).
-int msgpack_file_write_error(const int error)
-{
- semsg(_("E5420: Failed to write to file: %s"), os_strerror(error));
- return -1;
+ fp->bytes_read += size - skip_remaining;
+ return (ptrdiff_t)(size - skip_remaining);
}
diff --git a/src/nvim/os/fileio.h b/src/nvim/os/fileio.h
index e8fd2209db..523f9657a4 100644
--- a/src/nvim/os/fileio.h
+++ b/src/nvim/os/fileio.h
@@ -2,6 +2,7 @@
#include <stddef.h> // IWYU pragma: keep
+#include "nvim/memory_defs.h"
#include "nvim/os/fileio_defs.h" // IWYU pragma: keep
/// file_open() flags
@@ -32,6 +33,11 @@ enum {
kRWBufferSize = 1024,
};
+static inline size_t file_space(FileDescriptor *fp)
+{
+ return (size_t)((fp->buffer + ARENA_BLOCK_SIZE) - fp->write_pos);
+}
+
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/fileio.h.generated.h"
#endif
diff --git a/src/nvim/os/fileio_defs.h b/src/nvim/os/fileio_defs.h
index 3dc8c7b22a..47f0629ccf 100644
--- a/src/nvim/os/fileio_defs.h
+++ b/src/nvim/os/fileio_defs.h
@@ -1,22 +1,23 @@
#pragma once
#include <stdbool.h>
-
-#include "nvim/func_attr.h"
-#include "nvim/rbuffer_defs.h"
+#include <stdint.h>
/// Structure used to read from/write to file
typedef struct {
- int fd; ///< File descriptor.
- int _error; ///< Error code for use with RBuffer callbacks or zero.
- RBuffer *rv; ///< Read or write buffer.
+ int fd; ///< File descriptor. Can be -1 if no backing file (file_open_buffer)
+ char *buffer; ///< Read or write buffer. always ARENA_BLOCK_SIZE if allocated
+ char *read_pos; ///< read position in buffer
+ char *write_pos; ///< write position in buffer
bool wr; ///< True if file is in write mode.
bool eof; ///< True if end of file was encountered.
bool non_blocking; ///< True if EAGAIN should not restart syscalls.
+ uint64_t bytes_read; ///< total bytes read so far
} FileDescriptor;
-static inline bool file_eof(const FileDescriptor *fp)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL;
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "os/fileio_defs.h.inline.generated.h"
+#endif
/// Check whether end of file was encountered
///
@@ -25,19 +26,18 @@ static inline bool file_eof(const FileDescriptor *fp)
/// @return true if it was, false if it was not or read operation was never
/// performed.
static inline bool file_eof(const FileDescriptor *const fp)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
- return fp->eof && rbuffer_size(fp->rv) == 0;
+ return fp->eof && fp->read_pos == fp->write_pos;
}
-static inline int file_fd(const FileDescriptor *fp)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_NONNULL_ALL;
-
/// Return the file descriptor associated with the FileDescriptor structure
///
/// @param[in] fp File to check.
///
/// @return File descriptor.
static inline int file_fd(const FileDescriptor *const fp)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
return fp->fd;
}
diff --git a/src/nvim/os/fs.c b/src/nvim/os/fs.c
index 19bdf30311..d0da37b8e7 100644
--- a/src/nvim/os/fs.c
+++ b/src/nvim/os/fs.c
@@ -35,6 +35,7 @@
#include "nvim/api/private/helpers.h"
#include "nvim/ascii_defs.h"
+#include "nvim/errors.h"
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
#include "nvim/log.h"
@@ -301,7 +302,7 @@ static bool is_executable_ext(const char *name, char **abspath)
char *nameext = strrchr(name, '.');
size_t nameext_len = nameext ? strlen(nameext) : 0;
xstrlcpy(os_buf, name, sizeof(os_buf));
- char *buf_end = xstrchrnul(os_buf, '\0');
+ char *buf_end = xstrchrnul(os_buf, NUL);
const char *pathext = os_getenv("PATHEXT");
if (!pathext) {
pathext = ".com;.exe;.bat;.cmd";
@@ -309,7 +310,7 @@ static bool is_executable_ext(const char *name, char **abspath)
const char *ext = pathext;
while (*ext) {
// If $PATHEXT itself contains dot:
- if (ext[0] == '.' && (ext[1] == '\0' || ext[1] == ENV_SEPCHAR)) {
+ if (ext[0] == '.' && (ext[1] == NUL || ext[1] == ENV_SEPCHAR)) {
if (is_executable(name, abspath)) {
return true;
}
@@ -355,10 +356,16 @@ static bool is_executable_in_path(const char *name, char **abspath)
}
#ifdef MSWIN
- // Prepend ".;" to $PATH.
- size_t pathlen = strlen(path_env);
- char *path = memcpy(xmallocz(pathlen + 2), "." ENV_SEPSTR, 2);
- memcpy(path + 2, path_env, pathlen);
+ char *path = NULL;
+ if (!os_env_exists("NoDefaultCurrentDirectoryInExePath")) {
+ // Prepend ".;" to $PATH.
+ size_t pathlen = strlen(path_env);
+ path = xmallocz(pathlen + 2);
+ memcpy(path, "." ENV_SEPSTR, 2);
+ memcpy(path + 2, path_env, pathlen);
+ } else {
+ path = xstrdup(path_env);
+ }
#else
char *path = xstrdup(path_env);
#endif
@@ -435,7 +442,7 @@ FILE *os_fopen(const char *path, const char *flags)
assert(flags != NULL && strlen(flags) > 0 && strlen(flags) <= 2);
int iflags = 0;
// Per table in fopen(3) manpage.
- if (flags[1] == '\0' || flags[1] == 'b') {
+ if (flags[1] == NUL || flags[1] == 'b') {
switch (flags[0]) {
case 'r':
iflags = O_RDONLY;
diff --git a/src/nvim/os/input.c b/src/nvim/os/input.c
index 60b5b48745..7c5293a8b8 100644
--- a/src/nvim/os/input.c
+++ b/src/nvim/os/input.c
@@ -27,22 +27,17 @@
#include "nvim/os/os_defs.h"
#include "nvim/os/time.h"
#include "nvim/profile.h"
-#include "nvim/rbuffer.h"
-#include "nvim/rbuffer_defs.h"
#include "nvim/state.h"
#include "nvim/state_defs.h"
#define READ_BUFFER_SIZE 0xfff
-#define INPUT_BUFFER_SIZE (READ_BUFFER_SIZE * 4)
+#define INPUT_BUFFER_SIZE ((READ_BUFFER_SIZE * 4) + MAX_KEY_CODE_LEN)
-typedef enum {
- kInputNone,
- kInputAvail,
- kInputEof,
-} InbufPollResult;
+static RStream read_stream = { .s.closed = true }; // Input before UI starts.
+static char input_buffer[INPUT_BUFFER_SIZE];
+static char *input_read_pos = input_buffer;
+static char *input_write_pos = input_buffer;
-static Stream read_stream = { .closed = true }; // Input before UI starts.
-static RBuffer *input_buffer = NULL;
static bool input_eof = false;
static bool blocking = false;
static int cursorhold_time = 0; ///< time waiting for CursorHold event
@@ -52,39 +47,27 @@ static int cursorhold_tb_change_cnt = 0; ///< tb_change_cnt when waiting starte
# include "os/input.c.generated.h"
#endif
-void input_init(void)
-{
- input_buffer = rbuffer_new(INPUT_BUFFER_SIZE + MAX_KEY_CODE_LEN);
-}
-
void input_start(void)
{
- if (!read_stream.closed) {
+ if (!read_stream.s.closed) {
return;
}
used_stdin = true;
- rstream_init_fd(&main_loop, &read_stream, STDIN_FILENO, READ_BUFFER_SIZE);
+ rstream_init_fd(&main_loop, &read_stream, STDIN_FILENO);
rstream_start(&read_stream, input_read_cb, NULL);
}
void input_stop(void)
{
- if (read_stream.closed) {
+ if (read_stream.s.closed) {
return;
}
rstream_stop(&read_stream);
- stream_close(&read_stream, NULL, NULL);
+ rstream_may_close(&read_stream);
}
-#ifdef EXITFREE
-void input_free_all_mem(void)
-{
- rbuffer_free(input_buffer);
-}
-#endif
-
static void cursorhold_event(void **argv)
{
event_T event = State & MODE_INSERT ? EVENT_CURSORHOLDI : EVENT_CURSORHOLD;
@@ -95,54 +78,76 @@ static void cursorhold_event(void **argv)
static void create_cursorhold_event(bool events_enabled)
{
// If events are enabled and the queue has any items, this function should not
- // have been called (inbuf_poll would return kInputAvail).
+ // have been called (`inbuf_poll` would return `kTrue`).
// TODO(tarruda): Cursorhold should be implemented as a timer set during the
// `state_check` callback for the states where it can be triggered.
assert(!events_enabled || multiqueue_empty(main_loop.events));
multiqueue_put(main_loop.events, cursorhold_event, NULL);
}
-static void restart_cursorhold_wait(int tb_change_cnt)
+static void reset_cursorhold_wait(int tb_change_cnt)
{
cursorhold_time = 0;
cursorhold_tb_change_cnt = tb_change_cnt;
}
-/// Low level input function
+/// Reads OS input into `buf`, and consumes pending events while waiting (if `ms != 0`).
///
-/// Wait until either the input buffer is non-empty or, if `events` is not NULL
-/// until `events` is non-empty.
-int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events)
+/// - Consumes available input received from the OS.
+/// - Consumes pending events.
+/// - Manages CursorHold events.
+/// - Handles EOF conditions.
+///
+/// Originally based on the Vim `mch_inchar` function.
+///
+/// @param buf Buffer to store consumed input.
+/// @param maxlen Maximum bytes to read into `buf`, or 0 to skip reading.
+/// @param ms Timeout in milliseconds. -1 for indefinite wait, 0 for no wait.
+/// @param tb_change_cnt Used to detect when typeahead changes.
+/// @param events (optional) Events to process.
+/// @return Bytes read into buf, or 0 if no input was read
+int input_get(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *events)
{
// This check is needed so that feeding typeahead from RPC can prevent CursorHold.
if (tb_change_cnt != cursorhold_tb_change_cnt) {
- restart_cursorhold_wait(tb_change_cnt);
+ reset_cursorhold_wait(tb_change_cnt);
}
- if (maxlen && rbuffer_size(input_buffer)) {
- restart_cursorhold_wait(tb_change_cnt);
- return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen);
- }
+#define TRY_READ() \
+ do { \
+ if (maxlen && input_available()) { \
+ reset_cursorhold_wait(tb_change_cnt); \
+ assert(maxlen >= 0); \
+ size_t to_read = MIN((size_t)maxlen, input_available()); \
+ memcpy(buf, input_read_pos, to_read); \
+ input_read_pos += to_read; \
+ /* This is safe because INPUT_BUFFER_SIZE fits in an int. */ \
+ assert(to_read <= INT_MAX); \
+ return (int)to_read; \
+ } \
+ } while (0)
+
+ TRY_READ();
// No risk of a UI flood, so disable CTRL-C "interrupt" behavior if it's mapped.
if ((mapped_ctrl_c | curbuf->b_mapped_ctrl_c) & get_real_state()) {
ctrl_c_interrupts = false;
}
- InbufPollResult result;
+ TriState result; ///< inbuf_poll result.
if (ms >= 0) {
- if ((result = inbuf_poll(ms, events)) == kInputNone) {
+ if ((result = inbuf_poll(ms, events)) == kFalse) {
return 0;
}
} else {
uint64_t wait_start = os_hrtime();
cursorhold_time = MIN(cursorhold_time, (int)p_ut);
- if ((result = inbuf_poll((int)p_ut - cursorhold_time, events)) == kInputNone) {
- if (read_stream.closed && silent_mode) {
+ if ((result = inbuf_poll((int)p_ut - cursorhold_time, events)) == kFalse) {
+ if (read_stream.s.closed && silent_mode) {
// Drained eventloop & initial input; exit silent/batch-mode (-es/-Es).
read_error_exit();
}
- restart_cursorhold_wait(tb_change_cnt);
+ reset_cursorhold_wait(tb_change_cnt);
if (trigger_cursorhold() && !typebuf_changed(tb_change_cnt)) {
create_cursorhold_event(events == main_loop.events);
} else {
@@ -161,29 +166,26 @@ int os_inchar(uint8_t *buf, int maxlen, int ms, int tb_change_cnt, MultiQueue *e
return 0;
}
- if (maxlen && rbuffer_size(input_buffer)) {
- restart_cursorhold_wait(tb_change_cnt);
- // Safe to convert rbuffer_read to int, it will never overflow since we use
- // relatively small buffers.
- return (int)rbuffer_read(input_buffer, (char *)buf, (size_t)maxlen);
- }
+ TRY_READ();
// If there are events, return the keys directly
if (maxlen && pending_events(events)) {
return push_event_key(buf, maxlen);
}
- if (result == kInputEof) {
+ if (result == kNone) {
read_error_exit();
}
return 0;
+
+#undef TRY_READ
}
// Check if a character is available for reading
bool os_char_avail(void)
{
- return inbuf_poll(0, NULL) == kInputAvail;
+ return inbuf_poll(0, NULL) == kTrue;
}
/// Poll for fast events. `got_int` will be set to `true` if CTRL-C was typed.
@@ -247,11 +249,28 @@ bool os_isatty(int fd)
return uv_guess_handle(fd) == UV_TTY;
}
-void input_enqueue_raw(String keys)
+size_t input_available(void)
{
- if (keys.size > 0) {
- rbuffer_write(input_buffer, keys.data, keys.size);
+ return (size_t)(input_write_pos - input_read_pos);
+}
+
+static size_t input_space(void)
+{
+ return (size_t)(input_buffer + INPUT_BUFFER_SIZE - input_write_pos);
+}
+
+void input_enqueue_raw(const char *data, size_t size)
+{
+ if (input_read_pos > input_buffer) {
+ size_t available = input_available();
+ memmove(input_buffer, input_read_pos, available);
+ input_read_pos = input_buffer;
+ input_write_pos = input_buffer + available;
}
+
+ size_t to_write = MIN(size, input_space());
+ memcpy(input_write_pos, data, to_write);
+ input_write_pos += to_write;
}
size_t input_enqueue(String keys)
@@ -259,7 +278,7 @@ size_t input_enqueue(String keys)
const char *ptr = keys.data;
const char *end = ptr + keys.size;
- while (rbuffer_space(input_buffer) >= 19 && ptr < end) {
+ while (input_space() >= 19 && ptr < end) {
// A "<x>" form occupies at least 1 characters, and produces up
// to 19 characters (1 + 5 * 3 for the char and 3 for a modifier).
// In the case of K_SPECIAL (0x80), 3 bytes are escaped and needed,
@@ -272,7 +291,7 @@ size_t input_enqueue(String keys)
if (new_size) {
new_size = handle_mouse_event(&ptr, buf, new_size);
- rbuffer_write(input_buffer, (char *)buf, new_size);
+ input_enqueue_raw((char *)buf, new_size);
continue;
}
@@ -293,11 +312,11 @@ size_t input_enqueue(String keys)
// copy the character, escaping K_SPECIAL
if ((uint8_t)(*ptr) == K_SPECIAL) {
- rbuffer_write(input_buffer, (char *)&(uint8_t){ K_SPECIAL }, 1);
- rbuffer_write(input_buffer, (char *)&(uint8_t){ KS_SPECIAL }, 1);
- rbuffer_write(input_buffer, (char *)&(uint8_t){ KE_FILLER }, 1);
+ input_enqueue_raw((char *)&(uint8_t){ K_SPECIAL }, 1);
+ input_enqueue_raw((char *)&(uint8_t){ KS_SPECIAL }, 1);
+ input_enqueue_raw((char *)&(uint8_t){ KE_FILLER }, 1);
} else {
- rbuffer_write(input_buffer, ptr, 1);
+ input_enqueue_raw(ptr, 1);
}
ptr++;
}
@@ -422,7 +441,7 @@ static unsigned handle_mouse_event(const char **ptr, uint8_t *buf, unsigned bufs
return bufsize;
}
-size_t input_enqueue_mouse(int code, uint8_t modifier, int grid, int row, int col)
+void input_enqueue_mouse(int code, uint8_t modifier, int grid, int row, int col)
{
modifier |= check_multiclick(code, grid, row, col);
uint8_t buf[7];
@@ -442,8 +461,7 @@ size_t input_enqueue_mouse(int code, uint8_t modifier, int grid, int row, int co
mouse_col = col;
size_t written = 3 + (size_t)(p - buf);
- rbuffer_write(input_buffer, (char *)buf, written);
- return written;
+ input_enqueue_raw((char *)buf, written);
}
/// @return true if the main loop is blocked and waiting for input.
@@ -452,15 +470,22 @@ bool input_blocking(void)
return blocking;
}
-// This is a replacement for the old `WaitForChar` function in os_unix.c
-static InbufPollResult inbuf_poll(int ms, MultiQueue *events)
+/// Checks for (but does not read) available input, and consumes `main_loop.events` while waiting.
+///
+/// @param ms Timeout in milliseconds. -1 for indefinite wait, 0 for no wait.
+/// @param events (optional) Queue to check for pending events.
+/// @return TriState:
+/// - kTrue: Input/events available
+/// - kFalse: No input/events
+/// - kNone: EOF reached on the input stream
+static TriState inbuf_poll(int ms, MultiQueue *events)
{
if (os_input_ready(events)) {
- return kInputAvail;
+ return kTrue;
}
if (do_profiling == PROF_YES && ms) {
- prof_inchar_enter();
+ prof_input_start();
}
if ((ms == -1 || ms > 0) && events != main_loop.events && !input_eof) {
@@ -468,38 +493,29 @@ static InbufPollResult inbuf_poll(int ms, MultiQueue *events)
blocking = true;
multiqueue_process_events(ch_before_blocking_events);
}
- DLOG("blocking... events_enabled=%d events_pending=%d", events != NULL,
- events && !multiqueue_empty(events));
- LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms,
- os_input_ready(events) || input_eof);
+ DLOG("blocking... events=%s", !!events ? "true" : "false");
+ LOOP_PROCESS_EVENTS_UNTIL(&main_loop, NULL, ms, os_input_ready(events) || input_eof);
blocking = false;
if (do_profiling == PROF_YES && ms) {
- prof_inchar_exit();
+ prof_input_end();
}
if (os_input_ready(events)) {
- return kInputAvail;
+ return kTrue;
}
- return input_eof ? kInputEof : kInputNone;
-}
-
-bool input_available(void)
-{
- return rbuffer_size(input_buffer) != 0;
+ return input_eof ? kNone : kFalse;
}
-static void input_read_cb(Stream *stream, RBuffer *buf, size_t c, void *data, bool at_eof)
+static size_t input_read_cb(RStream *stream, const char *buf, size_t c, void *data, bool at_eof)
{
if (at_eof) {
input_eof = true;
}
- assert(rbuffer_space(input_buffer) >= rbuffer_size(buf));
- RBUFFER_UNTIL_EMPTY(buf, ptr, len) {
- (void)rbuffer_write(input_buffer, ptr, len);
- rbuffer_consumed(buf, len);
- }
+ assert(input_space() >= c);
+ input_enqueue_raw(buf, c);
+ return c;
}
static void process_ctrl_c(void)
@@ -508,28 +524,29 @@ static void process_ctrl_c(void)
return;
}
- size_t consume_count = 0;
- RBUFFER_EACH_REVERSE(input_buffer, c, i) {
- if ((uint8_t)c == Ctrl_C
- || ((uint8_t)c == 'C' && i >= 3
- && (uint8_t)(*rbuffer_get(input_buffer, i - 3)) == K_SPECIAL
- && (uint8_t)(*rbuffer_get(input_buffer, i - 2)) == KS_MODIFIER
- && (uint8_t)(*rbuffer_get(input_buffer, i - 1)) == MOD_MASK_CTRL)) {
- *rbuffer_get(input_buffer, i) = Ctrl_C;
+ size_t available = input_available();
+ ssize_t i;
+ for (i = (ssize_t)available - 1; i >= 0; i--) { // Reverse-search input for Ctrl_C.
+ uint8_t c = (uint8_t)input_read_pos[i];
+ if (c == Ctrl_C
+ || (c == 'C' && i >= 3
+ && (uint8_t)input_read_pos[i - 3] == K_SPECIAL
+ && (uint8_t)input_read_pos[i - 2] == KS_MODIFIER
+ && (uint8_t)input_read_pos[i - 1] == MOD_MASK_CTRL)) {
+ input_read_pos[i] = Ctrl_C;
got_int = true;
- consume_count = i;
break;
}
}
- if (got_int && consume_count) {
+ if (got_int && i > 0) {
// Remove all unprocessed input (typeahead) before the CTRL-C.
- rbuffer_consumed(input_buffer, consume_count);
+ input_read_pos += i;
}
}
-// Helper function used to push bytes from the 'event' key sequence partially
-// between calls to os_inchar when maxlen < 3
+/// Pushes bytes from the "event" key sequence (KE_EVENT) partially between calls to input_get when
+/// `maxlen < 3`.
static int push_event_key(uint8_t *buf, int maxlen)
{
static const uint8_t key[3] = { K_SPECIAL, KS_EXTRA, KE_EVENT };
@@ -548,7 +565,7 @@ static int push_event_key(uint8_t *buf, int maxlen)
bool os_input_ready(MultiQueue *events)
{
return (typebuf_was_filled // API call filled typeahead
- || rbuffer_size(input_buffer) // Input buffer filled
+ || input_available() // Input buffer filled
|| pending_events(events)); // Events must be processed
}
@@ -559,7 +576,7 @@ static void read_error_exit(void)
if (silent_mode) { // Normal way to exit for "nvim -es".
getout(0);
}
- preserve_exit(_("Vim: Error reading input, exiting...\n"));
+ preserve_exit(_("Nvim: Error reading input, exiting...\n"));
}
static bool pending_events(MultiQueue *events)
diff --git a/src/nvim/os/process.c b/src/nvim/os/proc.c
index e8d38d5b8a..053f5f3ba0 100644
--- a/src/nvim/os/process.c
+++ b/src/nvim/os/proc.c
@@ -37,24 +37,24 @@
#include "nvim/log.h"
#include "nvim/memory.h"
-#include "nvim/os/process.h"
+#include "nvim/os/proc.h"
#ifdef MSWIN
# include "nvim/api/private/helpers.h"
#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "os/process.c.generated.h"
+# include "os/proc.c.generated.h"
#endif
#ifdef MSWIN
-static bool os_proc_tree_kill_rec(HANDLE process, int sig)
+static bool os_proc_tree_kill_rec(HANDLE proc, int sig)
{
- if (process == NULL) {
+ if (proc == NULL) {
return false;
}
PROCESSENTRY32 pe;
- DWORD pid = GetProcessId(process);
+ DWORD pid = GetProcessId(proc);
if (pid != 0) {
HANDLE h = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
@@ -77,7 +77,7 @@ static bool os_proc_tree_kill_rec(HANDLE process, int sig)
}
theend:
- return (bool)TerminateProcess(process, (unsigned)sig);
+ return (bool)TerminateProcess(proc, (unsigned)sig);
}
/// Kills process `pid` and its descendants recursively.
bool os_proc_tree_kill(int pid, int sig)
@@ -230,9 +230,9 @@ int os_proc_children(int ppid, int **proc_list, size_t *proc_count)
///
/// @param pid Process to inspect.
/// @return Map of process properties, empty on error.
-Dictionary os_proc_info(int pid, Arena *arena)
+Dict os_proc_info(int pid, Arena *arena)
{
- Dictionary pinfo = ARRAY_DICT_INIT;
+ Dict pinfo = ARRAY_DICT_INIT;
PROCESSENTRY32 pe;
// Snapshot of all processes. This is used instead of:
diff --git a/src/nvim/os/process.h b/src/nvim/os/proc.h
index 3b116b4bad..1831f21cc3 100644
--- a/src/nvim/os/process.h
+++ b/src/nvim/os/proc.h
@@ -7,5 +7,5 @@
#endif
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "os/process.h.generated.h"
+# include "os/proc.h.generated.h"
#endif
diff --git a/src/nvim/os/pty_conpty_win.c b/src/nvim/os/pty_conpty_win.c
index e7697880af..6402330a52 100644
--- a/src/nvim/os/pty_conpty_win.c
+++ b/src/nvim/os/pty_conpty_win.c
@@ -143,7 +143,7 @@ finished:
return conpty_object;
}
-bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *process_handle, wchar_t *name,
+bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *proc_handle, wchar_t *name,
wchar_t *cmd_line, wchar_t *cwd, wchar_t *env)
{
PROCESS_INFORMATION pi = { 0 };
@@ -159,7 +159,7 @@ bool os_conpty_spawn(conpty_t *conpty_object, HANDLE *process_handle, wchar_t *n
&pi)) {
return false;
}
- *process_handle = pi.hProcess;
+ *proc_handle = pi.hProcess;
return true;
}
diff --git a/src/nvim/os/pty_proc.h b/src/nvim/os/pty_proc.h
new file mode 100644
index 0000000000..d815aae69c
--- /dev/null
+++ b/src/nvim/os/pty_proc.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#ifdef MSWIN
+# include "nvim/os/pty_proc_win.h"
+#else
+# include "nvim/os/pty_proc_unix.h"
+#endif
diff --git a/src/nvim/os/pty_process_unix.c b/src/nvim/os/pty_proc_unix.c
index 4d34e8fac4..3bca065d2d 100644
--- a/src/nvim/os/pty_process_unix.c
+++ b/src/nvim/os/pty_proc_unix.c
@@ -30,20 +30,19 @@
#endif
#include "auto/config.h"
-#include "klib/klist.h"
#include "nvim/eval/typval.h"
#include "nvim/event/defs.h"
#include "nvim/event/loop.h"
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
#include "nvim/log.h"
#include "nvim/os/fs.h"
#include "nvim/os/os_defs.h"
-#include "nvim/os/pty_process.h"
-#include "nvim/os/pty_process_unix.h"
+#include "nvim/os/pty_proc.h"
+#include "nvim/os/pty_proc_unix.h"
#include "nvim/types_defs.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "os/pty_process_unix.c.generated.h"
+# include "os/pty_proc_unix.c.generated.h"
#endif
#if defined(__sun) && !defined(HAVE_FORKPTY)
@@ -158,7 +157,7 @@ static pid_t forkpty(int *amaster, char *name, struct termios *termp, struct win
#endif
/// @returns zero on success, or negative error code
-int pty_process_spawn(PtyProcess *ptyproc)
+int pty_proc_spawn(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
// termios initialized at first use
@@ -168,8 +167,8 @@ int pty_process_spawn(PtyProcess *ptyproc)
}
int status = 0; // zero or negative error code (libuv convention)
- Process *proc = (Process *)ptyproc;
- assert(proc->err.closed);
+ Proc *proc = (Proc *)ptyproc;
+ assert(proc->err.s.closed);
uv_signal_start(&proc->loop->children_watcher, chld_handler, SIGCHLD);
ptyproc->winsize = (struct winsize){ ptyproc->height, ptyproc->width, 0, 0 };
uv_disable_stdio_inheritance();
@@ -208,8 +207,8 @@ int pty_process_spawn(PtyProcess *ptyproc)
&& (status = set_duplicating_descriptor(master, &proc->in.uv.pipe))) {
goto error;
}
- if (!proc->out.closed
- && (status = set_duplicating_descriptor(master, &proc->out.uv.pipe))) {
+ if (!proc->out.s.closed
+ && (status = set_duplicating_descriptor(master, &proc->out.s.uv.pipe))) {
goto error;
}
@@ -224,29 +223,29 @@ error:
return status;
}
-const char *pty_process_tty_name(PtyProcess *ptyproc)
+const char *pty_proc_tty_name(PtyProc *ptyproc)
{
return ptsname(ptyproc->tty_fd);
}
-void pty_process_resize(PtyProcess *ptyproc, uint16_t width, uint16_t height)
+void pty_proc_resize(PtyProc *ptyproc, uint16_t width, uint16_t height)
FUNC_ATTR_NONNULL_ALL
{
ptyproc->winsize = (struct winsize){ height, width, 0, 0 };
ioctl(ptyproc->tty_fd, TIOCSWINSZ, &ptyproc->winsize);
}
-void pty_process_close(PtyProcess *ptyproc)
+void pty_proc_close(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
- pty_process_close_master(ptyproc);
- Process *proc = (Process *)ptyproc;
+ pty_proc_close_master(ptyproc);
+ Proc *proc = (Proc *)ptyproc;
if (proc->internal_close_cb) {
proc->internal_close_cb(proc);
}
}
-void pty_process_close_master(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL
+void pty_proc_close_master(PtyProc *ptyproc) FUNC_ATTR_NONNULL_ALL
{
if (ptyproc->tty_fd >= 0) {
close(ptyproc->tty_fd);
@@ -254,12 +253,12 @@ void pty_process_close_master(PtyProcess *ptyproc) FUNC_ATTR_NONNULL_ALL
}
}
-void pty_process_teardown(Loop *loop)
+void pty_proc_teardown(Loop *loop)
{
uv_signal_stop(&loop->children_watcher);
}
-static void init_child(PtyProcess *ptyproc)
+static void init_child(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
#if defined(HAVE__NSGETENVIRON)
@@ -277,13 +276,13 @@ static void init_child(PtyProcess *ptyproc)
signal(SIGTERM, SIG_DFL);
signal(SIGALRM, SIG_DFL);
- Process *proc = (Process *)ptyproc;
+ Proc *proc = (Proc *)ptyproc;
if (proc->cwd && os_chdir(proc->cwd) != 0) {
ELOG("chdir(%s) failed: %s", proc->cwd, strerror(errno));
return;
}
- const char *prog = process_get_exepath(proc);
+ const char *prog = proc_get_exepath(proc);
assert(proc->env);
environ = tv_dict_to_env(proc->env);
@@ -387,8 +386,8 @@ static void chld_handler(uv_signal_t *handle, int signum)
Loop *loop = handle->loop->data;
- kl_iter(WatcherPtr, loop->children, current) {
- Process *proc = (*current)->data;
+ for (size_t i = 0; i < kv_size(loop->children); i++) {
+ Proc *proc = kv_A(loop->children, i);
do {
pid = waitpid(proc->pid, &stat, WNOHANG);
} while (pid < 0 && errno == EINTR);
@@ -406,10 +405,10 @@ static void chld_handler(uv_signal_t *handle, int signum)
}
}
-PtyProcess pty_process_init(Loop *loop, void *data)
+PtyProc pty_proc_init(Loop *loop, void *data)
{
- PtyProcess rv;
- rv.process = process_init(loop, kProcessTypePty, data);
+ PtyProc rv = { 0 };
+ rv.proc = proc_init(loop, kProcTypePty, data);
rv.width = 80;
rv.height = 24;
rv.tty_fd = -1;
diff --git a/src/nvim/os/pty_process_unix.h b/src/nvim/os/pty_proc_unix.h
index 1a77ae5fd5..47f9af088e 100644
--- a/src/nvim/os/pty_process_unix.h
+++ b/src/nvim/os/pty_proc_unix.h
@@ -1,5 +1,5 @@
#pragma once
-// IWYU pragma: private, include "nvim/os/pty_process.h"
+// IWYU pragma: private, include "nvim/os/pty_proc.h"
#include <stdint.h>
#include <sys/ioctl.h>
@@ -7,12 +7,12 @@
#include "nvim/event/defs.h"
typedef struct {
- Process process;
+ Proc proc;
uint16_t width, height;
struct winsize winsize;
int tty_fd;
-} PtyProcess;
+} PtyProc;
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "os/pty_process_unix.h.generated.h"
+# include "os/pty_proc_unix.h.generated.h"
#endif
diff --git a/src/nvim/os/pty_process_win.c b/src/nvim/os/pty_proc_win.c
index 12831ff05f..5bd6eead51 100644
--- a/src/nvim/os/pty_process_win.c
+++ b/src/nvim/os/pty_proc_win.c
@@ -10,20 +10,20 @@
#include "nvim/memory.h"
#include "nvim/os/os.h"
#include "nvim/os/pty_conpty_win.h"
-#include "nvim/os/pty_process_win.h"
+#include "nvim/os/pty_proc_win.h"
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "os/pty_process_win.c.generated.h"
+# include "os/pty_proc_win.c.generated.h"
#endif
-static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused)
+static void CALLBACK pty_proc_finish1(void *context, BOOLEAN unused)
FUNC_ATTR_NONNULL_ALL
{
- PtyProcess *ptyproc = (PtyProcess *)context;
- Process *proc = (Process *)ptyproc;
+ PtyProc *ptyproc = (PtyProc *)context;
+ Proc *proc = (Proc *)ptyproc;
os_conpty_free(ptyproc->conpty);
- // NB: pty_process_finish1() is called on a separate thread,
+ // NB: pty_proc_finish1() is called on a separate thread,
// but the timer only works properly if it's started by the main thread.
loop_schedule_fast(proc->loop, event_create(start_wait_eof_timer, ptyproc));
}
@@ -31,7 +31,7 @@ static void CALLBACK pty_process_finish1(void *context, BOOLEAN unused)
static void start_wait_eof_timer(void **argv)
FUNC_ATTR_NONNULL_ALL
{
- PtyProcess *ptyproc = (PtyProcess *)argv[0];
+ PtyProc *ptyproc = (PtyProc *)argv[0];
if (ptyproc->finish_wait != NULL) {
uv_timer_start(&ptyproc->wait_eof_timer, wait_eof_timer_cb, 200, 200);
@@ -39,15 +39,15 @@ static void start_wait_eof_timer(void **argv)
}
/// @returns zero on success, or negative error code.
-int pty_process_spawn(PtyProcess *ptyproc)
+int pty_proc_spawn(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
- Process *proc = (Process *)ptyproc;
+ Proc *proc = (Proc *)ptyproc;
int status = 0;
conpty_t *conpty_object = NULL;
char *in_name = NULL;
char *out_name = NULL;
- HANDLE process_handle = NULL;
+ HANDLE proc_handle = NULL;
uv_connect_t *in_req = NULL;
uv_connect_t *out_req = NULL;
wchar_t *cmd_line = NULL;
@@ -55,7 +55,7 @@ int pty_process_spawn(PtyProcess *ptyproc)
wchar_t *env = NULL;
const char *emsg = NULL;
- assert(proc->err.closed);
+ assert(proc->err.s.closed);
if (!os_has_conpty_working() || (conpty_object = os_conpty_init(&in_name,
&out_name, ptyproc->width,
@@ -69,15 +69,15 @@ int pty_process_spawn(PtyProcess *ptyproc)
uv_pipe_connect(in_req,
&proc->in.uv.pipe,
in_name,
- pty_process_connect_cb);
+ pty_proc_connect_cb);
}
- if (!proc->out.closed) {
+ if (!proc->out.s.closed) {
out_req = xmalloc(sizeof(uv_connect_t));
uv_pipe_connect(out_req,
- &proc->out.uv.pipe,
+ &proc->out.s.uv.pipe,
out_name,
- pty_process_connect_cb);
+ pty_proc_connect_cb);
}
if (proc->cwd != NULL) {
@@ -105,7 +105,7 @@ int pty_process_spawn(PtyProcess *ptyproc)
}
if (!os_conpty_spawn(conpty_object,
- &process_handle,
+ &proc_handle,
NULL,
cmd_line,
cwd,
@@ -114,42 +114,42 @@ int pty_process_spawn(PtyProcess *ptyproc)
status = (int)GetLastError();
goto cleanup;
}
- proc->pid = (int)GetProcessId(process_handle);
+ proc->pid = (int)GetProcessId(proc_handle);
uv_timer_init(&proc->loop->uv, &ptyproc->wait_eof_timer);
ptyproc->wait_eof_timer.data = (void *)ptyproc;
if (!RegisterWaitForSingleObject(&ptyproc->finish_wait,
- process_handle,
- pty_process_finish1,
+ proc_handle,
+ pty_proc_finish1,
ptyproc,
INFINITE,
WT_EXECUTEDEFAULT | WT_EXECUTEONLYONCE)) {
abort();
}
- // Wait until pty_process_connect_cb is called.
+ // Wait until pty_proc_connect_cb is called.
while ((in_req != NULL && in_req->handle != NULL)
|| (out_req != NULL && out_req->handle != NULL)) {
uv_run(&proc->loop->uv, UV_RUN_ONCE);
}
ptyproc->conpty = conpty_object;
- ptyproc->process_handle = process_handle;
+ ptyproc->proc_handle = proc_handle;
conpty_object = NULL;
- process_handle = NULL;
+ proc_handle = NULL;
cleanup:
if (status) {
// In the case of an error of MultiByteToWideChar or CreateProcessW.
- ELOG("pty_process_spawn(%s): %s: error code: %d",
+ ELOG("pty_proc_spawn(%s): %s: error code: %d",
proc->argv[0], emsg, status);
status = os_translate_sys_error(status);
}
os_conpty_free(conpty_object);
xfree(in_name);
xfree(out_name);
- if (process_handle != NULL) {
- CloseHandle(process_handle);
+ if (proc_handle != NULL) {
+ CloseHandle(proc_handle);
}
xfree(in_req);
xfree(out_req);
@@ -159,32 +159,32 @@ cleanup:
return status;
}
-const char *pty_process_tty_name(PtyProcess *ptyproc)
+const char *pty_proc_tty_name(PtyProc *ptyproc)
{
return "?";
}
-void pty_process_resize(PtyProcess *ptyproc, uint16_t width, uint16_t height)
+void pty_proc_resize(PtyProc *ptyproc, uint16_t width, uint16_t height)
FUNC_ATTR_NONNULL_ALL
{
os_conpty_set_size(ptyproc->conpty, width, height);
}
-void pty_process_close(PtyProcess *ptyproc)
+void pty_proc_close(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
- Process *proc = (Process *)ptyproc;
+ Proc *proc = (Proc *)ptyproc;
- pty_process_close_master(ptyproc);
+ pty_proc_close_master(ptyproc);
if (ptyproc->finish_wait != NULL) {
UnregisterWaitEx(ptyproc->finish_wait, NULL);
ptyproc->finish_wait = NULL;
uv_close((uv_handle_t *)&ptyproc->wait_eof_timer, NULL);
}
- if (ptyproc->process_handle != NULL) {
- CloseHandle(ptyproc->process_handle);
- ptyproc->process_handle = NULL;
+ if (ptyproc->proc_handle != NULL) {
+ CloseHandle(ptyproc->proc_handle);
+ ptyproc->proc_handle = NULL;
}
if (proc->internal_close_cb) {
@@ -192,17 +192,17 @@ void pty_process_close(PtyProcess *ptyproc)
}
}
-void pty_process_close_master(PtyProcess *ptyproc)
+void pty_proc_close_master(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
}
-void pty_process_teardown(Loop *loop)
+void pty_proc_teardown(Loop *loop)
FUNC_ATTR_NONNULL_ALL
{
}
-static void pty_process_connect_cb(uv_connect_t *req, int status)
+static void pty_proc_connect_cb(uv_connect_t *req, int status)
FUNC_ATTR_NONNULL_ALL
{
assert(status == 0);
@@ -212,23 +212,23 @@ static void pty_process_connect_cb(uv_connect_t *req, int status)
static void wait_eof_timer_cb(uv_timer_t *wait_eof_timer)
FUNC_ATTR_NONNULL_ALL
{
- PtyProcess *ptyproc = wait_eof_timer->data;
- Process *proc = (Process *)ptyproc;
+ PtyProc *ptyproc = wait_eof_timer->data;
+ Proc *proc = (Proc *)ptyproc;
assert(ptyproc->finish_wait != NULL);
- if (proc->out.closed || proc->out.did_eof || !uv_is_readable(proc->out.uvstream)) {
+ if (proc->out.s.closed || proc->out.did_eof || !uv_is_readable(proc->out.s.uvstream)) {
uv_timer_stop(&ptyproc->wait_eof_timer);
- pty_process_finish2(ptyproc);
+ pty_proc_finish2(ptyproc);
}
}
-static void pty_process_finish2(PtyProcess *ptyproc)
+static void pty_proc_finish2(PtyProc *ptyproc)
FUNC_ATTR_NONNULL_ALL
{
- Process *proc = (Process *)ptyproc;
+ Proc *proc = (Proc *)ptyproc;
DWORD exit_code = 0;
- GetExitCodeProcess(ptyproc->process_handle, &exit_code);
+ GetExitCodeProcess(ptyproc->proc_handle, &exit_code);
proc->status = proc->exit_signal ? 128 + proc->exit_signal : (int)exit_code;
proc->internal_exit_cb(proc);
@@ -399,7 +399,7 @@ static int build_env_block(dict_T *denv, wchar_t **env_block)
QUEUE_INSERT_TAIL(&env_q, &env_node->node);
}
- // Additional '\0' after the final entry
+ // Additional NUL after the final entry
env_block_len++;
*env_block = xmalloc(sizeof(**env_block) * env_block_len);
@@ -427,14 +427,14 @@ cleanup:
return rc;
}
-PtyProcess pty_process_init(Loop *loop, void *data)
+PtyProc pty_proc_init(Loop *loop, void *data)
{
- PtyProcess rv;
- rv.process = process_init(loop, kProcessTypePty, data);
+ PtyProc rv;
+ rv.proc = proc_init(loop, kProcTypePty, data);
rv.width = 80;
rv.height = 24;
rv.conpty = NULL;
rv.finish_wait = NULL;
- rv.process_handle = NULL;
+ rv.proc_handle = NULL;
return rv;
}
diff --git a/src/nvim/os/pty_process_win.h b/src/nvim/os/pty_proc_win.h
index 3528f6bfe5..c2fdea506e 100644
--- a/src/nvim/os/pty_process_win.h
+++ b/src/nvim/os/pty_proc_win.h
@@ -1,20 +1,20 @@
#pragma once
-// IWYU pragma: private, include "nvim/os/pty_process.h"
+// IWYU pragma: private, include "nvim/os/pty_proc.h"
#include <uv.h>
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
#include "nvim/lib/queue_defs.h"
#include "nvim/os/pty_conpty_win.h"
typedef struct pty_process {
- Process process;
+ Proc proc;
uint16_t width, height;
conpty_t *conpty;
HANDLE finish_wait;
- HANDLE process_handle;
+ HANDLE proc_handle;
uv_timer_t wait_eof_timer;
-} PtyProcess;
+} PtyProc;
// Structure used by build_cmd_line()
typedef struct arg_node {
@@ -23,5 +23,5 @@ typedef struct arg_node {
} ArgNode;
#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "os/pty_process_win.h.generated.h"
+# include "os/pty_proc_win.h.generated.h"
#endif
diff --git a/src/nvim/os/pty_process.h b/src/nvim/os/pty_process.h
deleted file mode 100644
index 2c7a5f66bd..0000000000
--- a/src/nvim/os/pty_process.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#pragma once
-
-#ifdef MSWIN
-# include "nvim/os/pty_process_win.h"
-#else
-# include "nvim/os/pty_process_unix.h"
-#endif
diff --git a/src/nvim/os/shell.c b/src/nvim/os/shell.c
index 2a10510b0f..efcdee9c8b 100644
--- a/src/nvim/os/shell.c
+++ b/src/nvim/os/shell.c
@@ -10,13 +10,14 @@
#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/event/defs.h"
-#include "nvim/event/libuv_process.h"
+#include "nvim/event/libuv_proc.h"
#include "nvim/event/loop.h"
#include "nvim/event/multiqueue.h"
-#include "nvim/event/process.h"
+#include "nvim/event/proc.h"
#include "nvim/event/rstream.h"
#include "nvim/event/stream.h"
#include "nvim/event/wstream.h"
@@ -39,8 +40,6 @@
#include "nvim/path.h"
#include "nvim/pos_defs.h"
#include "nvim/profile.h"
-#include "nvim/rbuffer.h"
-#include "nvim/rbuffer_defs.h"
#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/tag.h"
@@ -48,17 +47,11 @@
#include "nvim/ui.h"
#include "nvim/vim_defs.h"
-#define DYNAMIC_BUFFER_INIT { NULL, 0, 0 }
#define NS_1_SECOND 1000000000U // 1 second, in nanoseconds
#define OUT_DATA_THRESHOLD 1024 * 10U // 10KB, "a few screenfuls" of data.
#define SHELL_SPECIAL "\t \"&'$;<>()\\|"
-typedef struct {
- char *data;
- size_t cap, len;
-} DynamicBuffer;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "os/shell.c.generated.h"
#endif
@@ -122,7 +115,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
size_t len;
char *p;
char *extra_shell_arg = NULL;
- ShellOpts shellopts = kShellOptExpand | kShellOptSilent;
+ int shellopts = kShellOptExpand | kShellOptSilent;
int j;
char *tempname;
#define STYLE_ECHO 0 // use "echo", the default
@@ -255,11 +248,11 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
} else {
STRCPY(command, "(");
}
- STRCAT(command, pat[0] + 1); // exclude first backtick
+ strcat(command, pat[0] + 1); // exclude first backtick
p = command + strlen(command) - 1;
if (is_fish_shell) {
*p-- = ';';
- STRCAT(command, " end");
+ strcat(command, " end");
} else {
*p-- = ')'; // remove last backtick
}
@@ -270,7 +263,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
ampersand = true;
*p = ' ';
}
- STRCAT(command, ">");
+ strcat(command, ">");
} else {
STRCPY(command, "");
if (shell_style == STYLE_GLOB) {
@@ -278,26 +271,26 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
// otherwise, this may set the positional parameters for the shell,
// e.g. "$*".
if (flags & EW_NOTFOUND) {
- STRCAT(command, "set nonomatch; ");
+ strcat(command, "set nonomatch; ");
} else {
- STRCAT(command, "unset nonomatch; ");
+ strcat(command, "unset nonomatch; ");
}
}
if (shell_style == STYLE_GLOB) {
- STRCAT(command, "glob >");
+ strcat(command, "glob >");
} else if (shell_style == STYLE_PRINT) {
- STRCAT(command, "print -N >");
+ strcat(command, "print -N >");
} else if (shell_style == STYLE_VIMGLOB) {
- STRCAT(command, sh_vimglob_func);
+ strcat(command, sh_vimglob_func);
} else if (shell_style == STYLE_GLOBSTAR) {
- STRCAT(command, sh_globstar_opt);
- STRCAT(command, sh_vimglob_func);
+ strcat(command, sh_globstar_opt);
+ strcat(command, sh_vimglob_func);
} else {
- STRCAT(command, "echo >");
+ strcat(command, "echo >");
}
}
- STRCAT(command, tempname);
+ strcat(command, tempname);
if (shell_style != STYLE_BT) {
for (i = 0; i < num_pat; i++) {
@@ -341,7 +334,7 @@ int os_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, in
}
if (ampersand) {
- STRCAT(command, "&"); // put the '&' after the redirection
+ strcat(command, "&"); // put the '&' after the redirection
}
// Using zsh -G: If a pattern has no matches, it is just deleted from
@@ -647,13 +640,13 @@ char *shell_argv_to_str(char **const argv)
p++;
}
if (n < maxsize) {
- rv[n - 1] = '\0';
+ rv[n - 1] = NUL;
} else {
// Command too long, show ellipsis: "/bin/bash 'foo' 'bar'..."
rv[maxsize - 4] = '.';
rv[maxsize - 3] = '.';
rv[maxsize - 2] = '.';
- rv[maxsize - 1] = '\0';
+ rv[maxsize - 1] = NUL;
}
return rv;
}
@@ -666,9 +659,9 @@ char *shell_argv_to_str(char **const argv)
/// @param extra_args Extra arguments to the shell, or NULL.
///
/// @return shell command exit code
-int os_call_shell(char *cmd, ShellOpts opts, char *extra_args)
+int os_call_shell(char *cmd, int opts, char *extra_args)
{
- DynamicBuffer input = DYNAMIC_BUFFER_INIT;
+ StringBuilder input = KV_INITIAL_VALUE;
char *output = NULL;
char **output_ptr = NULL;
int current_state = State;
@@ -697,9 +690,9 @@ int os_call_shell(char *cmd, ShellOpts opts, char *extra_args)
size_t nread;
int exitcode = do_os_system(shell_build_argv(cmd, extra_args),
- input.data, input.len, output_ptr, &nread,
+ input.items, input.size, output_ptr, &nread,
emsg_silent, forward_output);
- xfree(input.data);
+ kv_destroy(input);
if (output) {
write_output(output, nread, true);
@@ -721,8 +714,10 @@ int os_call_shell(char *cmd, ShellOpts opts, char *extra_args)
/// os_call_shell() wrapper. Handles 'verbose', :profile, and v:shell_error.
/// Invalidates cached tags.
///
+/// @param opts a combination of ShellOpts flags
+///
/// @return shell command exit code
-int call_shell(char *cmd, ShellOpts opts, char *extra_shell_arg)
+int call_shell(char *cmd, int opts, char *extra_shell_arg)
{
int retval;
proftime_T wait_time;
@@ -766,7 +761,7 @@ int call_shell(char *cmd, ShellOpts opts, char *extra_shell_arg)
/// @param ret_len length of the stdout
///
/// @return an allocated string, or NULL for error.
-char *get_cmd_output(char *cmd, char *infile, ShellOpts flags, size_t *ret_len)
+char *get_cmd_output(char *cmd, char *infile, int flags, size_t *ret_len)
{
char *buffer = NULL;
@@ -860,10 +855,10 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
{
out_data_decide_throttle(0); // Initialize throttle decider.
out_data_ring(NULL, 0); // Initialize output ring-buffer.
- bool has_input = (input != NULL && input[0] != '\0');
+ bool has_input = (input != NULL && input[0] != NUL);
// the output buffer
- DynamicBuffer buf = DYNAMIC_BUFFER_INIT;
+ StringBuilder buf = KV_INITIAL_VALUE;
stream_read_cb data_cb = system_data_cb;
if (nread) {
*nread = 0;
@@ -879,12 +874,12 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
char prog[MAXPATHL];
xstrlcpy(prog, argv[0], MAXPATHL);
- LibuvProcess uvproc = libuv_process_init(&main_loop, &buf);
- Process *proc = &uvproc.process;
+ LibuvProc uvproc = libuv_proc_init(&main_loop, &buf);
+ Proc *proc = &uvproc.proc;
MultiQueue *events = multiqueue_new_child(main_loop.events);
proc->events = events;
proc->argv = argv;
- int status = process_spawn(proc, has_input, true, true);
+ int status = proc_spawn(proc, has_input, true, true);
if (status) {
loop_poll_events(&main_loop, 0);
// Failed, probably 'shell' is not executable.
@@ -906,9 +901,9 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
if (has_input) {
wstream_init(&proc->in, 0);
}
- rstream_init(&proc->out, 0);
+ rstream_init(&proc->out);
rstream_start(&proc->out, data_cb, &buf);
- rstream_init(&proc->err, 0);
+ rstream_init(&proc->err);
rstream_start(&proc->err, data_cb, &buf);
// write the input, if any
@@ -917,7 +912,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
if (!wstream_write(&proc->in, input_buffer)) {
// couldn't write, stop the process and tell the user about it
- process_stop(proc);
+ proc_stop(proc);
return -1;
}
// close the input stream after everything is written
@@ -934,7 +929,7 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
msg_no_more = true;
lines_left = -1;
}
- int exitcode = process_wait(proc, -1, NULL);
+ int exitcode = proc_wait(proc, -1, NULL);
if (!got_int && out_data_decide_throttle(0)) {
// Last chunk of output was skipped; display it now.
out_data_ring(NULL, SIZE_MAX);
@@ -951,18 +946,17 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
// prepare the out parameters if requested
if (output) {
- if (buf.len == 0) {
+ assert(nread);
+ if (buf.size == 0) {
// no data received from the process, return NULL
*output = NULL;
- xfree(buf.data);
+ *nread = 0;
+ kv_destroy(buf);
} else {
+ *nread = buf.size;
// NUL-terminate to make the output directly usable as a C string
- buf.data[buf.len] = NUL;
- *output = buf.data;
- }
-
- if (nread) {
- *nread = buf.len;
+ kv_push(buf, NUL);
+ *output = buf.items;
}
}
@@ -972,29 +966,11 @@ static int do_os_system(char **argv, const char *input, size_t len, char **outpu
return exitcode;
}
-/// - ensures at least `desired` bytes in buffer
-///
-/// TODO(aktau): fold with kvec/garray
-static void dynamic_buffer_ensure(DynamicBuffer *buf, size_t desired)
+static size_t system_data_cb(RStream *stream, const char *buf, size_t count, void *data, bool eof)
{
- if (buf->cap >= desired) {
- assert(buf->data);
- return;
- }
-
- buf->cap = desired;
- kv_roundup32(buf->cap);
- buf->data = xrealloc(buf->data, buf->cap);
-}
-
-static void system_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data, bool eof)
-{
- DynamicBuffer *dbuf = data;
-
- size_t nread = buf->size;
- dynamic_buffer_ensure(dbuf, dbuf->len + nread + 1);
- rbuffer_read(buf, dbuf->data + dbuf->len, nread);
- dbuf->len += nread;
+ StringBuilder *dbuf = data;
+ kv_concat_len(*dbuf, buf, count);
+ return count;
}
/// Tracks output received for the current executing shell command, and displays
@@ -1023,7 +999,7 @@ static bool out_data_decide_throttle(size_t size)
static uint64_t started = 0; // Start time of the current throttle.
static size_t received = 0; // Bytes observed since last throttle.
static size_t visit = 0; // "Pulse" count of the current throttle.
- static char pulse_msg[] = { ' ', ' ', ' ', '\0' };
+ static char pulse_msg[] = { ' ', ' ', ' ', NUL };
if (!size) {
bool previous_decision = (visit > 0);
@@ -1077,7 +1053,7 @@ static bool out_data_decide_throttle(size_t size)
///
/// @param output Data to save, or NULL to invoke a special mode.
/// @param size Length of `output`.
-static void out_data_ring(char *output, size_t size)
+static void out_data_ring(const char *output, size_t size)
{
#define MAX_CHUNK_SIZE (OUT_DATA_THRESHOLD / 2)
static char last_skipped[MAX_CHUNK_SIZE]; // Saved output.
@@ -1119,11 +1095,11 @@ static void out_data_ring(char *output, size_t size)
/// @param output Data to append to screen lines.
/// @param count Size of data.
/// @param eof If true, there will be no more data output.
-static void out_data_append_to_screen(char *output, size_t *count, bool eof)
+static void out_data_append_to_screen(const char *output, size_t *count, bool eof)
FUNC_ATTR_NONNULL_ALL
{
- char *p = output;
- char *end = output + *count;
+ const char *p = output;
+ const char *end = output + *count;
while (p < end) {
if (*p == '\n' || *p == '\r' || *p == TAB || *p == BELL) {
msg_putchar_attr((uint8_t)(*p), 0);
@@ -1151,25 +1127,16 @@ end:
ui_flush();
}
-static void out_data_cb(Stream *stream, RBuffer *buf, size_t count, void *data, bool eof)
+static size_t out_data_cb(RStream *stream, const char *ptr, size_t count, void *data, bool eof)
{
- size_t cnt;
- char *ptr = rbuffer_read_ptr(buf, &cnt);
-
- if (ptr != NULL && cnt > 0
- && out_data_decide_throttle(cnt)) { // Skip output above a threshold.
+ if (count > 0 && out_data_decide_throttle(count)) { // Skip output above a threshold.
// Save the skipped output. If it is the final chunk, we display it later.
- out_data_ring(ptr, cnt);
- } else if (ptr != NULL) {
- out_data_append_to_screen(ptr, &cnt, eof);
- }
-
- if (cnt) {
- rbuffer_consumed(buf, cnt);
+ out_data_ring(ptr, count);
+ } else if (count > 0) {
+ out_data_append_to_screen(ptr, &count, eof);
}
- // Move remaining data to start of buffer, so the buffer can never wrap around.
- rbuffer_reset(buf);
+ return count;
}
/// Parses a command string into a sequence of words, taking quotes into
@@ -1233,7 +1200,7 @@ static size_t word_length(const char *str)
/// event loop starts. If we don't (by writing in chunks returned by `ml_get`)
/// the buffer being modified might get modified by reading from the process
/// before we finish writing.
-static void read_input(DynamicBuffer *buf)
+static void read_input(StringBuilder *buf)
{
size_t written = 0;
size_t len = 0;
@@ -1247,14 +1214,11 @@ static void read_input(DynamicBuffer *buf)
} else if (lp[written] == NL) {
// NL -> NUL translation
len = 1;
- dynamic_buffer_ensure(buf, buf->len + len);
- buf->data[buf->len++] = NUL;
+ kv_push(*buf, NUL);
} else {
char *s = vim_strchr(lp + written, NL);
len = s == NULL ? l : (size_t)(s - (lp + written));
- dynamic_buffer_ensure(buf, buf->len + len);
- memcpy(buf->data + buf->len, lp + written, len);
- buf->len += len;
+ kv_concat_len(*buf, lp + written, len);
}
if (len == l) {
@@ -1263,8 +1227,7 @@ static void read_input(DynamicBuffer *buf)
|| (!curbuf->b_p_bin && curbuf->b_p_fixeol)
|| (lnum != curbuf->b_no_eol_lnum
&& (lnum != curbuf->b_ml.ml_line_count || curbuf->b_p_eol))) {
- dynamic_buffer_ensure(buf, buf->len + 1);
- buf->data[buf->len++] = NL;
+ kv_push(*buf, NL);
}
lnum++;
if (lnum > curbuf->b_op_end.lnum) {
@@ -1331,7 +1294,7 @@ static void shell_write_cb(Stream *stream, void *data, int status)
msg_schedule_semsg(_("E5677: Error writing input to shell-command: %s"),
uv_err_name(status));
}
- stream_close(stream, NULL, NULL);
+ stream_may_close(stream, false);
}
/// Applies 'shellxescape' (p_sxe) and 'shellxquote' (p_sxq) to a command.
diff --git a/src/nvim/os/stdpaths.c b/src/nvim/os/stdpaths.c
index e5bdd56fe6..e9a74d197f 100644
--- a/src/nvim/os/stdpaths.c
+++ b/src/nvim/os/stdpaths.c
@@ -63,22 +63,32 @@ static const char *const xdg_defaults[] = {
#endif
};
-/// Get the value of $NVIM_APPNAME or "nvim" if not set.
+/// Gets the value of $NVIM_APPNAME, or "nvim" if not set.
+///
+/// @param namelike Write "name-like" value (no path separators) in `NameBuff`.
///
/// @return $NVIM_APPNAME value
-const char *get_appname(void)
+const char *get_appname(bool namelike)
{
const char *env_val = os_getenv("NVIM_APPNAME");
- if (env_val == NULL || *env_val == '\0') {
+ if (env_val == NULL || *env_val == NUL) {
env_val = "nvim";
}
+
+ if (namelike) {
+ // Appname may be a relative path, replace slashes to make it name-like.
+ xstrlcpy(NameBuff, env_val, sizeof(NameBuff));
+ memchrsub(NameBuff, '/', '-', sizeof(NameBuff));
+ memchrsub(NameBuff, '\\', '-', sizeof(NameBuff));
+ }
+
return env_val;
}
/// Ensure that APPNAME is valid. Must be a name or relative path.
bool appname_is_valid(void)
{
- const char *appname = get_appname();
+ const char *appname = get_appname(false);
if (path_is_absolute(appname)
// TODO(justinmk): on Windows, path_is_absolute says "/" is NOT absolute. Should it?
|| strequal(appname, "/")
@@ -193,7 +203,7 @@ char *get_xdg_home(const XDGVarType idx)
FUNC_ATTR_WARN_UNUSED_RESULT
{
char *dir = stdpaths_get_xdg_var(idx);
- const char *appname = get_appname();
+ const char *appname = get_appname(false);
size_t appname_len = strlen(appname);
assert(appname_len < (IOSIZE - sizeof("-data")));
diff --git a/src/nvim/path.c b/src/nvim/path.c
index d782d1a989..9cce504831 100644
--- a/src/nvim/path.c
+++ b/src/nvim/path.c
@@ -8,23 +8,17 @@
#include "auto/config.h"
#include "nvim/ascii_defs.h"
-#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/eval.h"
-#include "nvim/eval/typval_defs.h"
#include "nvim/ex_docmd.h"
-#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/garray.h"
-#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
#include "nvim/macros_defs.h"
#include "nvim/mbyte.h"
#include "nvim/memory.h"
-#include "nvim/message.h"
#include "nvim/option.h"
-#include "nvim/option_defs.h"
#include "nvim/option_vars.h"
#include "nvim/os/fs.h"
#include "nvim/os/fs_defs.h"
@@ -37,7 +31,6 @@
#include "nvim/regexp_defs.h"
#include "nvim/strings.h"
#include "nvim/vim_defs.h"
-#include "nvim/window.h"
enum {
URL_SLASH = 1, // path_is_url() has found ":/"
@@ -390,14 +383,14 @@ int path_fnamencmp(const char *const fname1, const char *const fname2, size_t le
c2 = utf_ptr2char(p2);
if ((c1 == NUL || c2 == NUL
|| (!((c1 == '/' || c1 == '\\') && (c2 == '\\' || c2 == '/'))))
- && (p_fic ? (c1 != c2 && CH_FOLD(c1) != CH_FOLD(c2)) : c1 != c2)) {
+ && (p_fic ? (c1 != c2 && utf_fold(c1) != utf_fold(c2)) : c1 != c2)) {
break;
}
len -= (size_t)utfc_ptr2len(p1);
p1 += utfc_ptr2len(p1);
p2 += utfc_ptr2len(p2);
}
- return p_fic ? CH_FOLD(c1) - CH_FOLD(c2) : c1 - c2;
+ return p_fic ? utf_fold(c1) - utf_fold(c2) : c1 - c2;
#else
if (p_fic) {
return mb_strnicmp(fname1, fname2, len);
@@ -842,17 +835,18 @@ static bool is_unique(char *maybe_unique, garray_T *gap, int i)
return true; // no match found
}
-// Split the 'path' option into an array of strings in garray_T. Relative
-// paths are expanded to their equivalent fullpath. This includes the "."
-// (relative to current buffer directory) and empty path (relative to current
-// directory) notations.
-//
-// TODO(vim): handle upward search (;) and path limiter (**N) notations by
-// expanding each into their equivalent path(s).
-static void expand_path_option(char *curdir, garray_T *gap)
+/// Split the 'path' option into an array of strings in garray_T. Relative
+/// paths are expanded to their equivalent fullpath. This includes the "."
+/// (relative to current buffer directory) and empty path (relative to current
+/// directory) notations.
+///
+/// @param path_option p_path or p_cdpath
+///
+/// TODO(vim): handle upward search (;) and path limiter (**N) notations by
+/// expanding each into their equivalent path(s).
+static void expand_path_option(char *curdir, char *path_option, garray_T *gap)
FUNC_ATTR_NONNULL_ALL
{
- char *path_option = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path;
char *buf = xmalloc(MAXPATHL);
while (*path_option != NUL) {
@@ -942,7 +936,9 @@ static char *get_path_cutoff(char *fname, garray_T *gap)
/// Sorts, removes duplicates and modifies all the fullpath names in "gap" so
/// that they are unique with respect to each other while conserving the part
/// that matches the pattern. Beware, this is at least O(n^2) wrt "gap->ga_len".
-static void uniquefy_paths(garray_T *gap, char *pattern)
+///
+/// @param path_option p_path or p_cdpath
+static void uniquefy_paths(garray_T *gap, char *pattern, char *path_option)
FUNC_ATTR_NONNULL_ALL
{
char **fnames = gap->ga_data;
@@ -962,8 +958,8 @@ static void uniquefy_paths(garray_T *gap, char *pattern)
char *file_pattern = xmalloc(len + 2);
file_pattern[0] = '*';
file_pattern[1] = NUL;
- STRCAT(file_pattern, pattern);
- char *pat = file_pat_to_reg_pat(file_pattern, NULL, NULL, true);
+ strcat(file_pattern, pattern);
+ char *pat = file_pat_to_reg_pat(file_pattern, NULL, NULL, false);
xfree(file_pattern);
if (pat == NULL) {
return;
@@ -978,7 +974,7 @@ static void uniquefy_paths(garray_T *gap, char *pattern)
char *curdir = xmalloc(MAXPATHL);
os_dirname(curdir, MAXPATHL);
- expand_path_option(curdir, &path_ga);
+ expand_path_option(curdir, path_option, &path_ga);
in_curdir = xcalloc((size_t)gap->ga_len, sizeof(char *));
@@ -1065,7 +1061,7 @@ static void uniquefy_paths(garray_T *gap, char *pattern)
rel_path = xmalloc(strlen(short_name) + strlen(PATHSEPSTR) + 2);
STRCPY(rel_path, ".");
add_pathsep(rel_path);
- STRCAT(rel_path, short_name);
+ strcat(rel_path, short_name);
xfree(fnames[i]);
fnames[i] = rel_path;
@@ -1127,12 +1123,17 @@ static int expand_in_path(garray_T *const gap, char *const pattern, const int fl
FUNC_ATTR_NONNULL_ALL
{
garray_T path_ga;
+ char *path_option = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path;
char *const curdir = xmalloc(MAXPATHL);
os_dirname(curdir, MAXPATHL);
ga_init(&path_ga, (int)sizeof(char *), 1);
- expand_path_option(curdir, &path_ga);
+ if (flags & EW_CDPATH) {
+ expand_path_option(curdir, p_cdpath, &path_ga);
+ } else {
+ expand_path_option(curdir, path_option, &path_ga);
+ }
xfree(curdir);
if (GA_EMPTY(&path_ga)) {
return 0;
@@ -1148,7 +1149,7 @@ static int expand_in_path(garray_T *const gap, char *const pattern, const int fl
if (flags & EW_ADDSLASH) {
glob_flags |= WILD_ADD_SLASH;
}
- globpath(paths, pattern, gap, glob_flags, false);
+ globpath(paths, pattern, gap, glob_flags, !!(flags & EW_CDPATH));
xfree(paths);
return gap->ga_len;
@@ -1229,6 +1230,7 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i
static bool recursive = false;
int add_pat;
bool did_expand_in_path = false;
+ char *path_option = *curbuf->b_p_path == NUL ? p_path : curbuf->b_p_path;
// expand_env() is called to expand things like "~user". If this fails,
// it calls ExpandOne(), which brings us back here. In this case, always
@@ -1302,7 +1304,7 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i
// Otherwise: Add the file name if it exists or when EW_NOTFOUND is
// given.
if (path_has_exp_wildcard(p) || (flags & EW_ICASE)) {
- if ((flags & EW_PATH)
+ if ((flags & (EW_PATH | EW_CDPATH))
&& !path_is_absolute(p)
&& !(p[0] == '.'
&& (vim_ispathsep(p[1])
@@ -1338,8 +1340,8 @@ int gen_expand_wildcards(int num_pat, char **pat, int *num_file, char ***file, i
}
}
- if (did_expand_in_path && !GA_EMPTY(&ga) && (flags & EW_PATH)) {
- uniquefy_paths(&ga, p);
+ if (did_expand_in_path && !GA_EMPTY(&ga) && (flags & (EW_PATH | EW_CDPATH))) {
+ uniquefy_paths(&ga, p, path_option);
}
if (p != pat[i]) {
xfree(p);
@@ -1389,7 +1391,7 @@ static int expand_backtick(garray_T *gap, char *pat, int flags)
char *cmd = xmemdupz(pat + 1, strlen(pat) - 2);
if (*cmd == '=') { // `={expr}`: Expand expression
- buffer = eval_to_string(cmd + 1, true);
+ buffer = eval_to_string(cmd + 1, true, false);
} else {
buffer = get_cmd_output(cmd, NULL, (flags & EW_SILENT) ? kShellOptSilent : 0, NULL);
}
@@ -1678,86 +1680,6 @@ void simplify_filename(char *filename)
} while (*p != NUL);
}
-static char *eval_includeexpr(const char *const ptr, const size_t len)
-{
- const sctx_T save_sctx = current_sctx;
- set_vim_var_string(VV_FNAME, ptr, (ptrdiff_t)len);
- current_sctx = curbuf->b_p_script_ctx[BV_INEX].script_ctx;
-
- char *res = eval_to_string_safe(curbuf->b_p_inex,
- was_set_insecurely(curwin, kOptIncludeexpr, OPT_LOCAL));
-
- set_vim_var_string(VV_FNAME, NULL, 0);
- current_sctx = save_sctx;
- return res;
-}
-
-/// Return the name of the file ptr[len] in 'path'.
-/// Otherwise like file_name_at_cursor().
-///
-/// @param rel_fname file we are searching relative to
-char *find_file_name_in_path(char *ptr, size_t len, int options, long count, char *rel_fname)
-{
- char *file_name;
- char *tofree = NULL;
-
- if (len == 0) {
- return NULL;
- }
-
- if ((options & FNAME_INCL) && *curbuf->b_p_inex != NUL) {
- tofree = eval_includeexpr(ptr, len);
- if (tofree != NULL) {
- ptr = tofree;
- len = strlen(ptr);
- }
- }
-
- if (options & FNAME_EXP) {
- char *file_to_find = NULL;
- char *search_ctx = NULL;
-
- file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
- true, rel_fname, &file_to_find, &search_ctx);
-
- // If the file could not be found in a normal way, try applying
- // 'includeexpr' (unless done already).
- if (file_name == NULL
- && !(options & FNAME_INCL) && *curbuf->b_p_inex != NUL) {
- tofree = eval_includeexpr(ptr, len);
- if (tofree != NULL) {
- ptr = tofree;
- len = strlen(ptr);
- file_name = find_file_in_path(ptr, len, options & ~FNAME_MESS,
- true, rel_fname, &file_to_find, &search_ctx);
- }
- }
- if (file_name == NULL && (options & FNAME_MESS)) {
- char c = ptr[len];
- ptr[len] = NUL;
- semsg(_("E447: Can't find file \"%s\" in path"), ptr);
- ptr[len] = c;
- }
-
- // Repeat finding the file "count" times. This matters when it
- // appears several times in the path.
- while (file_name != NULL && --count > 0) {
- xfree(file_name);
- file_name = find_file_in_path(ptr, len, options, false, rel_fname,
- &file_to_find, &search_ctx);
- }
-
- xfree(file_to_find);
- vim_findfile_cleanup(search_ctx);
- } else {
- file_name = xstrnsave(ptr, len);
- }
-
- xfree(tofree);
-
- return file_name;
-}
-
/// Checks for a Windows drive letter ("C:/") at the start of the path.
///
/// @see https://url.spec.whatwg.org/#start-with-a-windows-drive-letter
@@ -2384,11 +2306,19 @@ static int path_to_absolute(const char *fname, char *buf, size_t len, int force)
p = strrchr(fname, '\\');
}
#endif
+ if (p == NULL && strcmp(fname, "..") == 0) {
+ // Handle ".." without path separators.
+ p = fname + 2;
+ }
if (p != NULL) {
+ if (vim_ispathsep_nocolon(*p) && strcmp(p + 1, "..") == 0) {
+ // For "/path/dir/.." include the "/..".
+ p += 3;
+ }
assert(p >= fname);
memcpy(relative_directory, fname, (size_t)(p - fname + 1));
relative_directory[p - fname + 1] = NUL;
- end_of_path = p + 1;
+ end_of_path = (vim_ispathsep_nocolon(*p) ? p + 1 : p);
} else {
relative_directory[0] = NUL;
}
diff --git a/src/nvim/path.h b/src/nvim/path.h
index a8eb893bb3..26c2bdf14e 100644
--- a/src/nvim/path.h
+++ b/src/nvim/path.h
@@ -25,7 +25,8 @@ enum {
EW_DODOT = 0x4000, ///< also files starting with a dot
EW_EMPTYOK = 0x8000, ///< no matches is not an error
EW_NOTENV = 0x10000, ///< do not expand environment variables
- EW_NOBREAK = 0x20000, ///< do not invoke breakcheck
+ EW_CDPATH = 0x20000, ///< search in 'cdpath' too
+ EW_NOBREAK = 0x40000, ///< do not invoke breakcheck
};
// Note: mostly EW_NOTFOUND and EW_SILENT are mutually exclusive: EW_NOTFOUND
// is used when executing commands and EW_SILENT for interactive expanding.
diff --git a/src/nvim/plines.c b/src/nvim/plines.c
index 5881d34c48..9bf486fb06 100644
--- a/src/nvim/plines.c
+++ b/src/nvim/plines.c
@@ -5,6 +5,7 @@
#include <stdint.h>
#include <string.h>
+#include "nvim/api/extmark.h"
#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
@@ -145,8 +146,8 @@ CharSize charsize_regular(CharsizeArg *csarg, char *const cur, colnr_T const vco
} else if (cur_char < 0) {
size = kInvalidByteCells;
} else {
- size = char2cells(cur_char);
- is_doublewidth = size == 2 && cur_char > 0x80;
+ size = ptr2cells(cur);
+ is_doublewidth = size == 2 && cur_char >= 0x80;
}
if (csarg->virt_row >= 0) {
@@ -157,8 +158,7 @@ CharSize charsize_regular(CharsizeArg *csarg, char *const cur, colnr_T const vco
if (mark.pos.row != csarg->virt_row || mark.pos.col > col) {
break;
} else if (mark.pos.col == col) {
- if (!mt_end(mark) && (mark.flags & MT_FLAG_DECOR_VIRT_TEXT_INLINE)
- && mt_scoped_in_win(mark, wp)) {
+ if (!mt_invalid(mark) && ns_in_win(mark.ns, wp)) {
DecorInline decor = mt_decor(mark);
DecorVirtText *vt = decor.ext ? decor.data.ext.vt : NULL;
while (vt) {
@@ -267,7 +267,7 @@ CharSize charsize_regular(CharsizeArg *csarg, char *const cur, colnr_T const vco
head += (max_head_vcol - (vcol + head_prev + prev_rem)
+ width2 - 1) / width2 * head_mid;
} else if (max_head_vcol < 0) {
- int off = virt_text_cursor_off(csarg, *cur == NUL);
+ int off = mb_added + virt_text_cursor_off(csarg, *cur == NUL);
if (off >= prev_rem) {
if (size > off) {
head += (1 + (off - prev_rem) / width) * head_mid;
@@ -337,8 +337,8 @@ CharSize charsize_regular(CharsizeArg *csarg, char *const cur, colnr_T const vco
///
/// @see charsize_regular
/// @see charsize_fast
-static inline CharSize charsize_fast_impl(win_T *const wp, bool use_tabstop, colnr_T const vcol,
- int32_t const cur_char)
+static inline CharSize charsize_fast_impl(win_T *const wp, const char *cur, bool use_tabstop,
+ colnr_T const vcol, int32_t const cur_char)
FUNC_ATTR_PURE FUNC_ATTR_ALWAYS_INLINE
{
// A tab gets expanded, depending on the current column
@@ -352,7 +352,11 @@ static inline CharSize charsize_fast_impl(win_T *const wp, bool use_tabstop, col
if (cur_char < 0) {
width = kInvalidByteCells;
} else {
- width = char2cells(cur_char);
+ // TODO(bfredl): perf: often cur_char is enough at this point to determine width.
+ // we likely want a specialized version of utf_ptr2StrCharInfo also determining
+ // the ptr2cells width at the same time without any extra decoding. (also applies
+ // to charsize_regular and charsize_nowrap)
+ width = ptr2cells(cur);
}
// If a double-width char doesn't fit at the end of a line, it wraps to the next line,
@@ -371,23 +375,23 @@ static inline CharSize charsize_fast_impl(win_T *const wp, bool use_tabstop, col
/// Can be used if CSType is kCharsizeFast.
///
/// @see charsize_regular
-CharSize charsize_fast(CharsizeArg *csarg, colnr_T const vcol, int32_t const cur_char)
+CharSize charsize_fast(CharsizeArg *csarg, const char *cur, colnr_T vcol, int32_t cur_char)
FUNC_ATTR_PURE
{
- return charsize_fast_impl(csarg->win, csarg->use_tabstop, vcol, cur_char);
+ return charsize_fast_impl(csarg->win, cur, csarg->use_tabstop, vcol, cur_char);
}
/// Get the number of cells taken up on the screen at given virtual column.
///
/// @see win_chartabsize()
-int charsize_nowrap(buf_T *buf, bool use_tabstop, colnr_T vcol, int32_t cur_char)
+int charsize_nowrap(buf_T *buf, const char *cur, bool use_tabstop, colnr_T vcol, int32_t cur_char)
{
if (cur_char == TAB && use_tabstop) {
return tabstop_padding(vcol, buf->b_p_ts, buf->b_p_vts_array);
} else if (cur_char < 0) {
return kInvalidByteCells;
} else {
- return char2cells(cur_char);
+ return ptr2cells(cur);
}
}
@@ -467,7 +471,7 @@ int linesize_fast(CharsizeArg const *const csarg, int vcol_arg, colnr_T const le
StrCharInfo ci = utf_ptr2StrCharInfo(line);
while (ci.ptr - line < len && *ci.ptr != NUL) {
- vcol += charsize_fast_impl(wp, use_tabstop, vcol_arg, ci.chr.value).width;
+ vcol += charsize_fast_impl(wp, ci.ptr, use_tabstop, vcol_arg, ci.chr.value).width;
ci = utfc_next(ci);
if (vcol > MAXCOL) {
vcol_arg = MAXCOL;
@@ -512,7 +516,7 @@ static int virt_text_cursor_off(const CharsizeArg *csarg, bool on_NUL)
void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *end)
{
char *const line = ml_get_buf(wp->w_buffer, pos->lnum); // start of the line
- int const end_col = pos->col;
+ colnr_T const end_col = pos->col;
CharsizeArg csarg;
bool on_NUL = false;
@@ -530,7 +534,7 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en
char_size = (CharSize){ .width = 1 };
break;
}
- char_size = charsize_fast_impl(wp, use_tabstop, vcol, ci.chr.value);
+ char_size = charsize_fast_impl(wp, ci.ptr, use_tabstop, vcol, ci.chr.value);
StrCharInfo const next = utfc_next(ci);
if (next.ptr - line > end_col) {
break;
@@ -556,6 +560,10 @@ void getvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *en
}
}
+ if (*ci.ptr == NUL && end_col < MAXCOL && end_col > ci.ptr - line) {
+ pos->col = (colnr_T)(ci.ptr - line);
+ }
+
int head = char_size.head;
int incr = char_size.width;
@@ -627,7 +635,7 @@ void getvvcol(win_T *wp, pos_T *pos, colnr_T *start, colnr_T *cursor, colnr_T *e
if (pos->col < ml_get_buf_len(wp->w_buffer, pos->lnum)) {
int c = utf_ptr2char(ptr + pos->col);
if ((c != TAB) && vim_isprintc(c)) {
- endadd = (colnr_T)(char2cells(c) - 1);
+ endadd = (colnr_T)(ptr2cells(ptr + pos->col) - 1);
if (coladd > endadd) {
// past end of line
endadd = 0;
@@ -712,7 +720,7 @@ bool win_may_fill(win_T *wp)
/// @return Number of filler lines above lnum
int win_get_fill(win_T *wp, linenr_T lnum)
{
- int virt_lines = decor_virt_lines(wp, lnum, NULL, kNone);
+ int virt_lines = decor_virt_lines(wp, lnum - 1, lnum, NULL, true);
// be quick when there are no filler lines
if (diffopt_filler()) {
@@ -824,7 +832,7 @@ int plines_win_col(win_T *wp, linenr_T lnum, long column)
if (cstype == kCharsizeFast) {
bool const use_tabstop = csarg.use_tabstop;
while (*ci.ptr != NUL && --column >= 0) {
- vcol += charsize_fast_impl(wp, use_tabstop, vcol, ci.chr.value).width;
+ vcol += charsize_fast_impl(wp, ci.ptr, use_tabstop, vcol, ci.chr.value).width;
ci = utfc_next(ci);
}
} else {
@@ -906,6 +914,25 @@ int plines_m_win(win_T *wp, linenr_T first, linenr_T last, int max)
return MIN(max, count);
}
+/// Return number of window lines a physical line range will occupy.
+/// Only considers real and filler lines.
+///
+/// Mainly used for calculating scrolling offsets.
+int plines_m_win_fill(win_T *wp, linenr_T first, linenr_T last)
+{
+ int count = last - first + 1 + decor_virt_lines(wp, first - 1, last, NULL, false);
+
+ if (diffopt_filler()) {
+ for (int lnum = first; lnum <= last; lnum++) {
+ // Note: this also considers folds.
+ int n = diff_check(wp, lnum);
+ count += MAX(n, 0);
+ }
+ }
+
+ return MAX(count, 0);
+}
+
/// Get the number of screen lines a range of text will take in window "wp".
///
/// @param[in] start_lnum Starting line number, 1-based inclusive.
diff --git a/src/nvim/plines.h b/src/nvim/plines.h
index 658206e1be..50310b8ce1 100644
--- a/src/nvim/plines.h
+++ b/src/nvim/plines.h
@@ -3,7 +3,6 @@
#include <stdbool.h>
#include <stdint.h>
-#include "nvim/func_attr.h"
#include "nvim/marktree_defs.h"
#include "nvim/pos_defs.h"
#include "nvim/types_defs.h"
@@ -39,12 +38,9 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "plines.h.generated.h"
+# include "plines.h.inline.generated.h"
#endif
-static inline CharSize win_charsize(CSType cstype, int vcol, char *ptr, int32_t chr,
- CharsizeArg *csarg)
- REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE;
-
/// Get the number of cells taken up on the screen by the given character at vcol.
/// "csarg->cur_text_width_left" and "csarg->cur_text_width_right" are set
/// to the extra size for inline virtual text.
@@ -55,17 +51,15 @@ static inline CharSize win_charsize(CSType cstype, int vcol, char *ptr, int32_t
/// of 'showbreak'/'breakindent' before where cursor should be placed.
static inline CharSize win_charsize(CSType cstype, int vcol, char *ptr, int32_t chr,
CharsizeArg *csarg)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE
{
if (cstype == kCharsizeFast) {
- return charsize_fast(csarg, vcol, chr);
+ return charsize_fast(csarg, ptr, vcol, chr);
} else {
return charsize_regular(csarg, ptr, vcol, chr);
}
}
-static inline int linetabsize_str(char *s)
- REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE;
-
/// Return the number of cells the string "s" will take on the screen,
/// taking into account the size of a tab.
///
@@ -73,13 +67,11 @@ static inline int linetabsize_str(char *s)
///
/// @return Number of cells the string will take on the screen.
static inline int linetabsize_str(char *s)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE
{
return linetabsize_col(0, s);
}
-static inline int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len)
- REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT REAL_FATTR_ALWAYS_INLINE;
-
/// Like linetabsize_str(), but for a given window instead of the current one.
///
/// @param wp
@@ -88,6 +80,7 @@ static inline int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T
///
/// @return Number of cells the string will take on the screen.
static inline int win_linetabsize(win_T *wp, linenr_T lnum, char *line, colnr_T len)
+ FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_ALWAYS_INLINE
{
CharsizeArg csarg;
CSType const cstype = init_charsize_arg(&csarg, wp, lnum, line);
diff --git a/src/nvim/po/CMakeLists.txt b/src/nvim/po/CMakeLists.txt
index 6c2008926a..348ba2881c 100644
--- a/src/nvim/po/CMakeLists.txt
+++ b/src/nvim/po/CMakeLists.txt
@@ -54,13 +54,15 @@ if(HAVE_WORKING_LIBINTL AND GETTEXT_FOUND AND XGETTEXT_PRG AND ICONV_PRG)
add_custom_command(
OUTPUT ${NVIM_POT}
COMMAND $<TARGET_FILE:nvim_bin> -u NONE -i NONE -n --headless --cmd "set cpo+=+"
- -S ${CMAKE_CURRENT_SOURCE_DIR}/tojavascript.vim ${NVIM_POT} ${PROJECT_SOURCE_DIR}/runtime/optwin.vim
+ -c "silent source ${CMAKE_CURRENT_SOURCE_DIR}/tojavascript.vim"
+ ${NVIM_POT} ${PROJECT_SOURCE_DIR}/runtime/optwin.vim
COMMAND ${XGETTEXT_PRG} -o ${NVIM_POT} --default-domain=nvim
--add-comments --keyword=_ --keyword=N_ --keyword=NGETTEXT:1,2
-D ${CMAKE_CURRENT_SOURCE_DIR} -D ${CMAKE_CURRENT_BINARY_DIR}
${NVIM_RELATIVE_SOURCES} optwin.js
COMMAND $<TARGET_FILE:nvim_bin> -u NONE -i NONE -n --headless --cmd "set cpo+=+"
- -S ${CMAKE_CURRENT_SOURCE_DIR}/fixfilenames.vim ${NVIM_POT} ../../../runtime/optwin.vim
+ -c "silent source ${CMAKE_CURRENT_SOURCE_DIR}/fixfilenames.vim"
+ ${NVIM_POT} ${PROJECT_SOURCE_DIR}/runtime/optwin.vim
VERBATIM
DEPENDS ${NVIM_SOURCES} nvim_bin nvim_runtime_deps)
diff --git a/src/nvim/po/af.po b/src/nvim/po/af.po
index 92a1a6ca3c..64acd93f57 100644
--- a/src/nvim/po/af.po
+++ b/src/nvim/po/af.po
@@ -315,7 +315,7 @@ msgstr " Bevelrelvoltooiing (^V^N^P)"
#~ msgstr " Etiketvoltooiing (^]^N^P)"
#, fuzzy
-#~ msgid " Spelling suggestion (s^N^P)"
+#~ msgid " Spelling suggestion (^S^N^P)"
#~ msgstr " Hele-rel voltooiing (^L^N^P)"
msgid " Keyword Local completion (^N^P)"
@@ -3608,8 +3608,8 @@ msgstr ""
#~ "\n"
#~ "(2) 'n Bewerkingsessie van hierdie ler het ineengestort.\n"
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Indien wel, gebruik \":recover\" of \"vim -r"
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Indien wel, gebruik \":recover\" of \"nvim -r"
msgid ""
"\"\n"
diff --git a/src/nvim/po/ca.po b/src/nvim/po/ca.po
index 964fcc9325..44975d9161 100644
--- a/src/nvim/po/ca.po
+++ b/src/nvim/po/ca.po
@@ -393,8 +393,8 @@ msgid " Omni completion (^O^N^P)"
msgstr " Omni-compleci (^O^N^P)"
#: ../edit.c:96
-msgid " Spelling suggestion (s^N^P)"
-msgstr "Suggeriment ortogrfic (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr "Suggeriment ortogrfic (^S^N^P)"
# buscar un nom, en lloc del verb completar. eac
#: ../edit.c:97
@@ -4015,8 +4015,8 @@ msgstr ""
"(2) El Vim s'ha estrellat mentre s'editava aquest fitxer.\n"
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " En aquest cas, useu \":recover\" o b \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " En aquest cas, useu \":recover\" o b \"nvim -r "
#: ../memline.c:3249
msgid ""
diff --git a/src/nvim/po/cs.cp1250.po b/src/nvim/po/cs.cp1250.po
index b939139fb5..43b6c82960 100644
--- a/src/nvim/po/cs.cp1250.po
+++ b/src/nvim/po/cs.cp1250.po
@@ -404,7 +404,7 @@ msgid " Omni completion (^O^N^P)"
msgstr " Doplovn tag (^I/^N/^P)"
#: ../edit.c:96
-msgid " Spelling suggestion (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
msgstr ""
#: ../edit.c:97
@@ -4077,8 +4077,8 @@ msgstr ""
"(2) Editace tohoto souboru byla peruena neekanm ukonenm programu.\n"
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Je-li tomu tak, pak pouijte \":recover\" i \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Je-li tomu tak, pak pouijte \":recover\" i \"nvim -r "
#: ../memline.c:3249
msgid ""
diff --git a/src/nvim/po/cs.po b/src/nvim/po/cs.po
index f98dc2828e..7df69e061f 100644
--- a/src/nvim/po/cs.po
+++ b/src/nvim/po/cs.po
@@ -404,7 +404,7 @@ msgid " Omni completion (^O^N^P)"
msgstr " Doplovn tag (^I/^N/^P)"
#: ../edit.c:96
-msgid " Spelling suggestion (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
msgstr ""
#: ../edit.c:97
@@ -4077,8 +4077,8 @@ msgstr ""
"(2) Editace tohoto souboru byla peruena neekanm ukonenm programu.\n"
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Je-li tomu tak, pak pouijte \":recover\" i \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Je-li tomu tak, pak pouijte \":recover\" i \"nvim -r "
#: ../memline.c:3249
msgid ""
diff --git a/src/nvim/po/da.po b/src/nvim/po/da.po
index ba0301589c..c65477bf13 100644
--- a/src/nvim/po/da.po
+++ b/src/nvim/po/da.po
@@ -403,8 +403,8 @@ msgstr " Fuldførelse af brugerdefineret (^U^N^P)"
msgid " Omni completion (^O^N^P)"
msgstr " Fuldførelse af omni (^O^N^P)"
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Staveforslag (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " Staveforslag (^S^N^P)"
msgid " Keyword Local completion (^N^P)"
msgstr " Fuldførelse af nøgleord local (^N^P)"
@@ -3663,8 +3663,8 @@ msgstr ""
msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) En redigeringssession for filen holdt op med at virke.\n"
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Hvis det er tilfældet, så brug \":recover\" eller \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Hvis det er tilfældet, så brug \":recover\" eller \"nvim -r "
msgid ""
"\"\n"
diff --git a/src/nvim/po/de.po b/src/nvim/po/de.po
index efd52d3efe..0d1195eecf 100644
--- a/src/nvim/po/de.po
+++ b/src/nvim/po/de.po
@@ -397,8 +397,8 @@ msgid " Omni completion (^O^N^P)"
msgstr " Omni-Ergnzung (^O^N^P)"
#: ../edit.c:97
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Vorschlag der Rechtschreibprfung (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " Vorschlag der Rechtschreibprfung (^S^N^P)"
#: ../edit.c:98
msgid " Keyword Local completion (^N^P)"
@@ -3436,9 +3436,9 @@ msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) Eine Sitzung fr diese Datei ist abgestrzt.\n"
#: ../memline.c:3204
-msgid " If this is the case, use \":recover\" or \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
msgstr ""
-" Wenn dies der Fall ist, so verwenden Sie \":recover\" oder \"vim -r "
+" Wenn dies der Fall ist, so verwenden Sie \":recover\" oder \"nvim -r "
#: ../memline.c:3206
msgid ""
diff --git a/src/nvim/po/en_GB.po b/src/nvim/po/en_GB.po
index 1c03b305f9..7b849d4e68 100644
--- a/src/nvim/po/en_GB.po
+++ b/src/nvim/po/en_GB.po
@@ -386,7 +386,7 @@ msgid " Omni completion (^O^N^P)"
msgstr ""
#: ../edit.c:96
-msgid " Spelling suggestion (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
msgstr ""
#: ../edit.c:97
@@ -3847,7 +3847,7 @@ msgid "(2) An edit session for this file crashed.\n"
msgstr ""
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
msgstr ""
#: ../memline.c:3249
diff --git a/src/nvim/po/eo.po b/src/nvim/po/eo.po
index f619c4c408..5cd18ad24b 100644
--- a/src/nvim/po/eo.po
+++ b/src/nvim/po/eo.po
@@ -410,8 +410,8 @@ msgstr " Kompletigo difinita de uzanto (^U^N^P)"
msgid " Omni completion (^O^N^P)"
msgstr " Kompletigo Omni (^O^N^P)"
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Sugesto de literumo (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " Sugesto de literumo (^S^N^P)"
msgid " Keyword Local completion (^N^P)"
msgstr " Kompletigo loka de ŝlosilvorto (^N/^P)"
@@ -3531,8 +3531,8 @@ msgstr ""
msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) Redakta seanco de tiu dosiero kolapsis.\n"
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Se veras, uzu \":recover\" aŭ \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Se veras, uzu \":recover\" aŭ \"nvim -r "
msgid ""
"\"\n"
diff --git a/src/nvim/po/es.po b/src/nvim/po/es.po
index 120a29af15..292ca15264 100644
--- a/src/nvim/po/es.po
+++ b/src/nvim/po/es.po
@@ -387,8 +387,8 @@ msgid " Omni completion (^O^N^P)"
msgstr " Completar con método Omni (^O^N^P)"
#: ../edit.c:96
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Sugerencia de ortografía (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " Sugerencia de ortografía (^S^N^P)"
# Scroll has it's own msgs, in it's place there is the msg for local
# * ctrl_x_mode = 0 (eg continue_status & CONT_LOCAL) -- Acevedo
@@ -4078,8 +4078,8 @@ msgstr ""
"(2) Falló una sesión de edición de este archivo.\n"
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Si es así, use \":recover\" o \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Si es así, use \":recover\" o \"nvim -r "
#: ../memline.c:3249
msgid ""
diff --git a/src/nvim/po/fi.po b/src/nvim/po/fi.po
index ab6acf685f..c9cad6cc69 100644
--- a/src/nvim/po/fi.po
+++ b/src/nvim/po/fi.po
@@ -526,8 +526,8 @@ msgstr " Käyttäjän määrittelemä täydennys (^U^N^P)"
msgid " Omni completion (^O^N^P)"
msgstr " Omnitäydennys (^O^N^P)"
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Oikaisulukuehdotus (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " Oikaisulukuehdotus (^S^N^P)"
msgid " Keyword Local completion (^N^P)"
msgstr " Avainsanan paikallinen täydennys (^N^P)"
@@ -3660,8 +3660,8 @@ msgstr ""
msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) Tiedostonmuokkausistunto on kaatunut.\n"
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Jos näin on, käytä komentoa :recover tai vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Jos näin on, käytä komentoa :recover tai nvim -r "
msgid ""
"\"\n"
diff --git a/src/nvim/po/fr.po b/src/nvim/po/fr.po
index fae6fe2297..92d2ad215f 100644
--- a/src/nvim/po/fr.po
+++ b/src/nvim/po/fr.po
@@ -3241,8 +3241,8 @@ msgstr ""
msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) Une session d'dition de ce fichier a plant.\n"
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Si c'est le cas, utilisez \":recover\" ou \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Si c'est le cas, utilisez \":recover\" ou \"nvim -r "
msgid ""
"\"\n"
diff --git a/src/nvim/po/ga.po b/src/nvim/po/ga.po
index 5107aed75f..7a5893b5f2 100644
--- a/src/nvim/po/ga.po
+++ b/src/nvim/po/ga.po
@@ -389,8 +389,8 @@ msgstr " Comhln saincheaptha (^U^N^P)"
msgid " Omni completion (^O^N^P)"
msgstr " Comhln Omni (^O^N^P)"
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Moladh litrithe (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " Moladh litrithe (^S^N^P)"
msgid " Keyword Local completion (^N^P)"
msgstr " Comhln lognta lorgfhocal (^N^P)"
@@ -3696,8 +3696,8 @@ msgstr ""
msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) Thuairteil seisin eagarthireachta.\n"
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Ms amhlaidh, bain sid as \":recover\" n \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Ms amhlaidh, bain sid as \":recover\" n \"nvim -r "
msgid ""
"\"\n"
diff --git a/src/nvim/po/it.po b/src/nvim/po/it.po
index 2191876724..d02052ec39 100644
--- a/src/nvim/po/it.po
+++ b/src/nvim/po/it.po
@@ -383,8 +383,8 @@ msgid " Omni completion (^O^N^P)"
msgstr " Completamento globale (^O^N^P)"
#: ../edit.c:96
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Suggerimento ortografico (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " Suggerimento ortografico (^S^N^P)"
#: ../edit.c:97
msgid " Keyword Local completion (^N^P)"
@@ -4037,8 +4037,8 @@ msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) Una sessione di edit per questo file finita male.\n"
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Se cos, usa \":recover\" oppure \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Se cos, usa \":recover\" oppure \"nvim -r "
#: ../memline.c:3249
msgid ""
diff --git a/src/nvim/po/ja.euc-jp.po b/src/nvim/po/ja.euc-jp.po
index 87b4fc5ccd..78f927c21d 100644
--- a/src/nvim/po/ja.euc-jp.po
+++ b/src/nvim/po/ja.euc-jp.po
@@ -409,8 +409,8 @@ msgstr " 桼䴰 (^U^N^P)"
msgid " Omni completion (^O^N^P)"
msgstr " 䴰 (^O^N^P)"
-msgid " Spelling suggestion (s^N^P)"
-msgstr " ֤꽤 (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " ֤꽤 (^S^N^P)"
msgid " Keyword Local completion (^N^P)"
msgstr " ɽꥭ䴰 (^N^P)"
@@ -3466,8 +3466,8 @@ msgstr ""
msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) ΥեԽå󤬥å夷.\n"
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " ξˤ \":recover\" \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " ξˤ \":recover\" \"nvim -r "
msgid ""
"\"\n"
diff --git a/src/nvim/po/ja.po b/src/nvim/po/ja.po
index 315c860307..1a493c0336 100644
--- a/src/nvim/po/ja.po
+++ b/src/nvim/po/ja.po
@@ -4247,8 +4247,8 @@ msgstr " ユーザー定義補完 (^U^N^P)"
msgid " Omni completion (^O^N^P)"
msgstr " オムニ補完 (^O^N^P)"
-msgid " Spelling suggestion (s^N^P)"
-msgstr " 綴り修正候補 (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " 綴り修正候補 (^S^N^P)"
msgid " Keyword Local completion (^N^P)"
msgstr " 局所キーワード補完 (^N^P)"
@@ -5092,8 +5092,8 @@ msgstr ""
msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) このファイルの編集セッションがクラッシュした.\n"
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " この場合には \":recover\" か \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " この場合には \":recover\" か \"nvim -r "
msgid ""
"\"\n"
diff --git a/src/nvim/po/ko.UTF-8.po b/src/nvim/po/ko.UTF-8.po
index 8a6b228b18..03dff518f3 100644
--- a/src/nvim/po/ko.UTF-8.po
+++ b/src/nvim/po/ko.UTF-8.po
@@ -381,8 +381,8 @@ msgid " Omni completion (^O^N^P)"
msgstr " Omni 완성 (^O^N^P)"
#: ../edit.c:96
-msgid " Spelling suggestion (s^N^P)"
-msgstr " 단어 제안 (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " 단어 제안 (^S^N^P)"
#: ../edit.c:97
msgid " Keyword Local completion (^N^P)"
@@ -3957,8 +3957,8 @@ msgstr ""
"(2) 파일을 고치다가 죽었었습니다.\n"
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " 만약 그렇다면 \":recover\" 혹은 \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " 만약 그렇다면 \":recover\" 혹은 \"nvim -r "
#: ../memline.c:3249
msgid ""
diff --git a/src/nvim/po/nb.po b/src/nvim/po/nb.po
index 75f4eb28a3..d512789246 100644
--- a/src/nvim/po/nb.po
+++ b/src/nvim/po/nb.po
@@ -401,8 +401,7 @@ msgid " Omni completion (^O^N^P)"
msgstr " Omni-fullfring (^O^N^P)"
#: ../edit.c:96
-#, fuzzy
-msgid " Spelling suggestion (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
msgstr " Staveforslag (^S^N^P)"
#: ../edit.c:97
@@ -3979,8 +3978,8 @@ msgstr ""
"(2) En kt for denne filen krsjet.\n"
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Hvis det er tilfelle, bruk \":recover\" eller \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Hvis det er tilfelle, bruk \":recover\" eller \"nvim -r "
#: ../memline.c:3249
msgid ""
diff --git a/src/nvim/po/nl.po b/src/nvim/po/nl.po
index d07c566e28..f6ce5ddc9f 100644
--- a/src/nvim/po/nl.po
+++ b/src/nvim/po/nl.po
@@ -384,8 +384,8 @@ msgid " Omni completion (^O^N^P)"
msgstr " omni-voltooiing (^O^N^P)"
#: ../edit.c:96
-msgid " Spelling suggestion (s^N^P)"
-msgstr " spellingsuggestie (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " spellingsuggestie (^S^N^P)"
#: ../edit.c:97
msgid " Keyword Local completion (^N^P)"
@@ -3984,8 +3984,8 @@ msgstr ""
"(2) Een sessie waarin dit bestand werd bewerkt is onverhoeds gestopt.\n"
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Als dit het geval is, gebruikt dan \":recover\" of \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Als dit het geval is, gebruikt dan \":recover\" of \"nvim -r "
#: ../memline.c:3249
msgid ""
diff --git a/src/nvim/po/no.po b/src/nvim/po/no.po
index 75f4eb28a3..d512789246 100644
--- a/src/nvim/po/no.po
+++ b/src/nvim/po/no.po
@@ -401,8 +401,7 @@ msgid " Omni completion (^O^N^P)"
msgstr " Omni-fullfring (^O^N^P)"
#: ../edit.c:96
-#, fuzzy
-msgid " Spelling suggestion (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
msgstr " Staveforslag (^S^N^P)"
#: ../edit.c:97
@@ -3979,8 +3978,8 @@ msgstr ""
"(2) En kt for denne filen krsjet.\n"
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Hvis det er tilfelle, bruk \":recover\" eller \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Hvis det er tilfelle, bruk \":recover\" eller \"nvim -r "
#: ../memline.c:3249
msgid ""
diff --git a/src/nvim/po/pl.UTF-8.po b/src/nvim/po/pl.UTF-8.po
index c00130ac8c..b7200a7ba8 100644
--- a/src/nvim/po/pl.UTF-8.po
+++ b/src/nvim/po/pl.UTF-8.po
@@ -380,8 +380,8 @@ msgid " Omni completion (^O^N^P)"
msgstr " Omni uzupełnianie (^O^N^P)"
#: ../edit.c:96
-msgid " Spelling suggestion (s^N^P)"
-msgstr "Propozycja pisowni (^L^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr "Propozycja pisowni (^S^N^P)"
#: ../edit.c:97
msgid " Keyword Local completion (^N^P)"
@@ -3946,8 +3946,8 @@ msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) Sesja edycji dla pliku załamała się.\n"
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Jeśli tak, to użyj \":recover\" lub \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Jeśli tak, to użyj \":recover\" lub \"nvim -r "
#: ../memline.c:3249
msgid ""
diff --git a/src/nvim/po/pt_BR.po b/src/nvim/po/pt_BR.po
index ca5a4508c7..3f2f87991c 100644
--- a/src/nvim/po/pt_BR.po
+++ b/src/nvim/po/pt_BR.po
@@ -625,8 +625,8 @@ msgstr ""
"(2) Ocorreu um travamento numa sesso de edio desse arquivo.\n"
#: ../memline.c:3206
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Se esse for o caso, use \":recover\" ou \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Se esse for o caso, use \":recover\" ou \"nvim -r "
#: ../memline.c:3208
msgid ""
@@ -5610,8 +5610,8 @@ msgid " Omni completion (^O^N^P)"
msgstr " Completao inteligente (^O^N^P)"
#: ../edit.c:97
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Sugesto de ortografia (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " Sugesto de ortografia (^S^N^P)"
#: ../edit.c:98
msgid " Keyword Local completion (^N^P)"
diff --git a/src/nvim/po/ru.po b/src/nvim/po/ru.po
index 160de6c67e..64c18f890c 100644
--- a/src/nvim/po/ru.po
+++ b/src/nvim/po/ru.po
@@ -382,8 +382,8 @@ msgid " Omni completion (^O^N^P)"
msgstr " Omni-дополнение (^O^N^P)"
#: ../edit.c:96
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Предложение исправления правописания (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " Предложение исправления правописания (^S^N^P)"
#: ../edit.c:97
msgid " Keyword Local completion (^N^P)"
@@ -3985,8 +3985,8 @@ msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) Сеанс редактирования этого файла завершён аварийно.\n"
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " В этом случае, используйте команду \":recover\" или \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " В этом случае, используйте команду \":recover\" или \"nvim -r "
#: ../memline.c:3249
msgid ""
diff --git a/src/nvim/po/sk.cp1250.po b/src/nvim/po/sk.cp1250.po
index f0fdc47a1e..9510a357f0 100644
--- a/src/nvim/po/sk.cp1250.po
+++ b/src/nvim/po/sk.cp1250.po
@@ -383,8 +383,7 @@ msgid " Omni completion (^O^N^P)"
msgstr " Doplovanie tagov (^O^N^P)"
#: ../edit.c:96
-#, fuzzy
-msgid " Spelling suggestion (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
msgstr " Doplovanie celch riadkov (^S^N^P)"
#: ../edit.c:97
@@ -3976,8 +3975,8 @@ msgstr ""
"(2) prava tohoto sboru bola preruen neoakvanm ukonenm programu.\n"
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Ak je tomu tak, potom pouite \":recover\" alebo \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Ak je tomu tak, potom pouite \":recover\" alebo \"nvim -r "
#: ../memline.c:3249
msgid ""
diff --git a/src/nvim/po/sk.po b/src/nvim/po/sk.po
index 2d6b4ed901..21791e0061 100644
--- a/src/nvim/po/sk.po
+++ b/src/nvim/po/sk.po
@@ -383,8 +383,7 @@ msgid " Omni completion (^O^N^P)"
msgstr " Doplovanie tagov (^O^N^P)"
#: ../edit.c:96
-#, fuzzy
-msgid " Spelling suggestion (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
msgstr " Doplovanie celch riadkov (^S^N^P)"
#: ../edit.c:97
@@ -3976,8 +3975,8 @@ msgstr ""
"(2) prava tohoto sboru bola preruen neoakvanm ukonenm programu.\n"
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Ak je tomu tak, potom pouite \":recover\" alebo \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Ak je tomu tak, potom pouite \":recover\" alebo \"nvim -r "
#: ../memline.c:3249
msgid ""
diff --git a/src/nvim/po/sr.po b/src/nvim/po/sr.po
index 4a9dcf4a5d..1cbb37eeb1 100644
--- a/src/nvim/po/sr.po
+++ b/src/nvim/po/sr.po
@@ -643,8 +643,8 @@ msgstr " Кориснички дефинисано довршавање (^U^N^P)
msgid " Omni completion (^O^N^P)"
msgstr " Omni довршавање (^O^N^P)"
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Правописни предлог (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " Правописни предлог (^S^N^P)"
msgid " Keyword Local completion (^N^P)"
msgstr " Довршавање локалне кључне речи (^N^P)"
@@ -3002,8 +3002,8 @@ msgstr " Кориснички дефинисано довршавање (^U^N^P)
msgid " Omni completion (^O^N^P)"
msgstr " Omni довршавање (^O^N^P)"
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Правописни предлог (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " Правописни предлог (^S^N^P)"
msgid " Keyword Local completion (^N^P)"
msgstr " Довршавање локалне кључне речи (^N^P)"
@@ -4048,8 +4048,8 @@ msgstr ""
msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) Сесија уређивања ове датотеке се срушила.\n"
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Ако је ово случај, користите \":recover\" или \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Ако је ово случај, користите \":recover\" или \"nvim -r "
msgid ""
"\"\n"
diff --git a/src/nvim/po/sv.po b/src/nvim/po/sv.po
index e46579c067..89a7717746 100644
--- a/src/nvim/po/sv.po
+++ b/src/nvim/po/sv.po
@@ -4335,8 +4335,8 @@ msgid " Omni completion (^O^N^P)"
msgstr " Omnikomplettering (^O^N^P)"
#: ../edit.c:97
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Stavningsfrslag (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " Stavningsfrslag (^S^N^P)"
#: ../edit.c:98
msgid " Keyword Local completion (^N^P)"
@@ -6171,8 +6171,8 @@ msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) En redigeringssession fr den hr filen kraschade.\n"
#: ../memline.c:3200
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Om s r fallet, anvnd \":recover\" eller \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Om s r fallet, anvnd \":recover\" eller \"nvim -r "
#: ../memline.c:3202
msgid ""
diff --git a/src/nvim/po/tr.po b/src/nvim/po/tr.po
index 08b97ec180..5b9a549cb2 100644
--- a/src/nvim/po/tr.po
+++ b/src/nvim/po/tr.po
@@ -3109,8 +3109,8 @@ msgstr " Kullanıcı tanımlı tamamlamalar (^U^N^P)"
msgid " Omni completion (^O^N^P)"
msgstr " Omni tamamlaması (^O^N^P)"
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Yazım önerisi (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " Yazım önerisi (^S^N^P)"
msgid " Keyword Local completion (^N^P)"
msgstr " Dahili anahtar sözcük tamamlaması (^N^P)"
@@ -3947,8 +3947,8 @@ msgstr ""
msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) Bu dosya düzenleme oturumu çöktü.\n"
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Durum buysa \":recover\" veya \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Durum buysa \":recover\" veya \"nvim -r "
msgid ""
"\"\n"
diff --git a/src/nvim/po/uk.po b/src/nvim/po/uk.po
index e62de86b09..6f2f90ad2c 100644
--- a/src/nvim/po/uk.po
+++ b/src/nvim/po/uk.po
@@ -4254,8 +4254,8 @@ msgstr " Користувацьке доповнення (^U^N^P)"
msgid " Omni completion (^O^N^P)"
msgstr " Кмітливе доповнення (^O^N^P)"
-msgid " Spelling suggestion (s^N^P)"
-msgstr " Орфографічна підказка (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " Орфографічна підказка (^S^N^P)"
msgid " Keyword Local completion (^N^P)"
msgstr " Доповнення місцевих ключових слів (^N^P)"
@@ -5108,8 +5108,8 @@ msgstr ""
msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) Сеанс редагування цього файлу зазнав краху.\n"
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " Якщо це справді трапилося, спробуйте «:recover» або «vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " Якщо це справді трапилося, спробуйте «:recover» або «nvim -r "
msgid ""
"\"\n"
diff --git a/src/nvim/po/vi.po b/src/nvim/po/vi.po
index 44772c99ad..3e88ac446c 100644
--- a/src/nvim/po/vi.po
+++ b/src/nvim/po/vi.po
@@ -388,7 +388,7 @@ msgstr " Tự động kết thúc thẻ đánh dấu (^]^N^P)"
#: ../edit.c:96
#, fuzzy
-msgid " Spelling suggestion (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
msgstr " Tự động kết thúc cho cả dòng (^L^N^P)"
#: ../edit.c:97
@@ -4009,9 +4009,9 @@ msgstr ""
"(2) Lần soạn thảo trước của tập tin này gặp sự cố.\n"
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
msgstr ""
-" Trong trường hợp này, hãy sử dụng câu lệnh \":recover\" hoặc \"vim -r "
+" Trong trường hợp này, hãy sử dụng câu lệnh \":recover\" hoặc \"nvim -r "
#: ../memline.c:3249
msgid ""
diff --git a/src/nvim/po/zh_CN.UTF-8.po b/src/nvim/po/zh_CN.UTF-8.po
index af050823d3..e30fc47806 100644
--- a/src/nvim/po/zh_CN.UTF-8.po
+++ b/src/nvim/po/zh_CN.UTF-8.po
@@ -3716,8 +3716,8 @@ msgid " Omni completion (^O^N^P)"
msgstr " 全能补全 (^O^N^P)"
#: ../insexpand.c:113
-msgid " Spelling suggestion (s^N^P)"
-msgstr " 拼写建议 (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
+msgstr " 拼写建议 (^S^N^P)"
#: ../insexpand.c:114
msgid " Keyword Local completion (^N^P)"
@@ -4780,8 +4780,8 @@ msgid "(2) An edit session for this file crashed.\n"
msgstr "(2) 上次编辑此文件时崩溃。\n"
#: ../memline.c:3099
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " 如果是这样,请用 \":recover\" 或 \"vim -r "
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " 如果是这样,请用 \":recover\" 或 \"nvim -r "
#: ../memline.c:3101
msgid ""
diff --git a/src/nvim/po/zh_TW.UTF-8.po b/src/nvim/po/zh_TW.UTF-8.po
index cba95e2af2..cbbc4ad6eb 100644
--- a/src/nvim/po/zh_TW.UTF-8.po
+++ b/src/nvim/po/zh_TW.UTF-8.po
@@ -421,7 +421,7 @@ msgstr " 標籤自動完成 (^]^N^P)"
#: ../edit.c:96
#, fuzzy
-msgid " Spelling suggestion (s^N^P)"
+msgid " Spelling suggestion (^S^N^P)"
msgstr " 整行自動完成 (^L^N^P)"
#: ../edit.c:97
@@ -4017,8 +4017,8 @@ msgstr ""
"(2) 前次編輯此檔時當機\n"
#: ../memline.c:3247
-msgid " If this is the case, use \":recover\" or \"vim -r "
-msgstr " 如果是這樣, 請用 \":recover\" 或 \"vim -r"
+msgid " If this is the case, use \":recover\" or \"nvim -r "
+msgstr " 如果是這樣, 請用 \":recover\" 或 \"nvim -r"
#: ../memline.c:3249
msgid ""
diff --git a/src/nvim/popupmenu.c b/src/nvim/popupmenu.c
index 86f3611ec5..ddcb819054 100644
--- a/src/nvim/popupmenu.c
+++ b/src/nvim/popupmenu.c
@@ -17,13 +17,16 @@
#include "nvim/buffer_updates.h"
#include "nvim/change.h"
#include "nvim/charset.h"
+#include "nvim/cmdexpand.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/extmark.h"
#include "nvim/extmark_defs.h"
+#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
@@ -47,6 +50,7 @@
#include "nvim/plines.h"
#include "nvim/popupmenu.h"
#include "nvim/pos_defs.h"
+#include "nvim/search.h"
#include "nvim/state_defs.h"
#include "nvim/strings.h"
#include "nvim/types_defs.h"
@@ -140,7 +144,7 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
|| (State == MODE_CMDLINE && ui_has(kUIWildmenu));
}
- pum_rl = (curwin->w_p_rl && State != MODE_CMDLINE);
+ pum_rl = State != MODE_CMDLINE && curwin->w_p_rl;
do {
// Mark the pum as visible already here,
@@ -434,6 +438,114 @@ void pum_display(pumitem_T *array, int size, int selected, bool array_changed, i
pum_redraw();
}
+/// Computes attributes of text on the popup menu.
+/// Returns attributes for every cell, or NULL if all attributes are the same.
+static int *pum_compute_text_attrs(char *text, hlf_T hlf, int user_hlattr)
+{
+ if ((hlf != HLF_PSI && hlf != HLF_PNI)
+ || (win_hl_attr(curwin, HLF_PMSI) == win_hl_attr(curwin, HLF_PSI)
+ && win_hl_attr(curwin, HLF_PMNI) == win_hl_attr(curwin, HLF_PNI))) {
+ return NULL;
+ }
+
+ char *leader = State == MODE_CMDLINE ? cmdline_compl_pattern()
+ : ins_compl_leader();
+ if (leader == NULL || *leader == NUL) {
+ return NULL;
+ }
+
+ int *attrs = xmalloc(sizeof(int) * (size_t)vim_strsize(text));
+ bool in_fuzzy = State == MODE_CMDLINE ? cmdline_compl_is_fuzzy()
+ : (get_cot_flags() & COT_FUZZY) != 0;
+ size_t leader_len = strlen(leader);
+
+ garray_T *ga = NULL;
+ bool matched_start = false;
+
+ if (in_fuzzy) {
+ ga = fuzzy_match_str_with_pos(text, leader);
+ } else {
+ matched_start = mb_strnicmp(text, leader, leader_len) == 0;
+ }
+
+ const char *ptr = text;
+ int cell_idx = 0;
+ uint32_t char_pos = 0;
+
+ while (*ptr != NUL) {
+ int new_attr = win_hl_attr(curwin, (int)hlf);
+
+ if (ga != NULL) {
+ // Handle fuzzy matching
+ for (int i = 0; i < ga->ga_len; i++) {
+ if (char_pos == ((uint32_t *)ga->ga_data)[i]) {
+ new_attr = win_hl_attr(curwin, hlf == HLF_PSI ? HLF_PMSI : HLF_PMNI);
+ break;
+ }
+ }
+ } else if (matched_start && ptr < text + leader_len) {
+ new_attr = win_hl_attr(curwin, hlf == HLF_PSI ? HLF_PMSI : HLF_PMNI);
+ }
+
+ if (user_hlattr > 0) {
+ new_attr = hl_combine_attr(new_attr, user_hlattr);
+ }
+
+ int char_cells = utf_ptr2cells(ptr);
+ for (int i = 0; i < char_cells; i++) {
+ attrs[cell_idx + i] = new_attr;
+ }
+ cell_idx += char_cells;
+
+ MB_PTR_ADV(ptr);
+ char_pos++;
+ }
+
+ if (ga != NULL) {
+ ga_clear(ga);
+ xfree(ga);
+ }
+ return attrs;
+}
+
+/// Displays text on the popup menu with specific attributes.
+static void pum_grid_puts_with_attrs(int col, int cells, const char *text, int textlen,
+ const int *attrs)
+{
+ const int col_start = col;
+ const char *ptr = text;
+
+ // Render text with proper attributes
+ while (*ptr != NUL && (textlen < 0 || ptr < text + textlen)) {
+ int char_len = utfc_ptr2len(ptr);
+ int attr = attrs[pum_rl ? (col_start + cells - col - 1) : (col - col_start)];
+ grid_line_puts(col, ptr, char_len, attr);
+ col += utf_ptr2cells(ptr);
+ ptr += char_len;
+ }
+}
+
+static inline void pum_align_order(int *order)
+{
+ bool is_default = cia_flags == 0;
+ order[0] = is_default ? CPT_ABBR : cia_flags / 100;
+ order[1] = is_default ? CPT_KIND : (cia_flags / 10) % 10;
+ order[2] = is_default ? CPT_MENU : cia_flags % 10;
+}
+
+static inline char *pum_get_item(int index, int type)
+{
+ switch (type) {
+ case CPT_ABBR:
+ return pum_array[index].pum_text;
+ case CPT_KIND:
+ return pum_array[index].pum_kind;
+ case CPT_MENU:
+ return pum_array[index].pum_extra;
+ }
+ return NULL;
+}
+
/// Redraw the popup menu, using "pum_first" and "pum_selected".
void pum_redraw(void)
{
@@ -445,11 +557,9 @@ void pum_redraw(void)
int thumb_height = 1;
int n;
-#define HA(hlf) (win_hl_attr(curwin, (hlf)))
- // "word" "kind" "extra text"
- const int attrsNorm[3] = { HA(HLF_PNI), HA(HLF_PNK), HA(HLF_PNX) };
- const int attrsSel[3] = { HA(HLF_PSI), HA(HLF_PSK), HA(HLF_PSX) };
-#undef HA
+ // "word" "kind" "extra text"
+ const hlf_T hlfsNorm[3] = { HLF_PNI, HLF_PNK, HLF_PNX };
+ const hlf_T hlfsSel[3] = { HLF_PSI, HLF_PSK, HLF_PSX };
int grid_width = pum_width;
int col_off = 0;
@@ -516,8 +626,9 @@ void pum_redraw(void)
for (int i = 0; i < pum_height; i++) {
int idx = i + pum_first;
- const int *const attrs = (idx == pum_selected) ? attrsSel : attrsNorm;
- int attr = attrs[0]; // start with "word" highlight
+ const hlf_T *const hlfs = (idx == pum_selected) ? hlfsSel : hlfsNorm;
+ hlf_T hlf = hlfs[0]; // start with "word" highlight
+ int attr = win_hl_attr(curwin, (int)hlf);
grid_line_start(&pum_grid, row);
@@ -531,27 +642,27 @@ void pum_redraw(void)
}
// Display each entry, use two spaces for a Tab.
- // Do this 3 times:
- // 0 - main text
- // 1 - kind
- // 2 - extra info
+ // Do this 3 times and order from p_cia
int grid_col = col_off;
int totwidth = 0;
-
- for (int round = 0; round < 3; round++) {
- attr = attrs[round];
+ int order[3];
+ int items_width_array[3] = { pum_base_width, pum_kind_width, pum_extra_width };
+ pum_align_order(order);
+ int basic_width = items_width_array[order[0]]; // first item width
+ bool last_isabbr = order[2] == CPT_ABBR;
+ for (int j = 0; j < 3; j++) {
+ int item_type = order[j];
+ hlf = hlfs[item_type];
+ attr = win_hl_attr(curwin, (int)hlf);
+ if (pum_array[idx].pum_user_hlattr > 0) {
+ attr = hl_combine_attr(attr, pum_array[idx].pum_user_hlattr);
+ }
+ if (item_type == CPT_KIND && pum_array[idx].pum_user_kind_hlattr > 0) {
+ attr = hl_combine_attr(attr, pum_array[idx].pum_user_kind_hlattr);
+ }
int width = 0;
char *s = NULL;
-
- switch (round) {
- case 0:
- p = pum_array[idx].pum_text; break;
- case 1:
- p = pum_array[idx].pum_kind; break;
- case 2:
- p = pum_array[idx].pum_extra; break;
- }
-
+ p = pum_get_item(idx, item_type);
if (p != NULL) {
for (;; MB_PTR_ADV(p)) {
if (s == NULL) {
@@ -573,36 +684,51 @@ void pum_redraw(void)
*p = saved;
}
+ int user_hlattr = pum_array[idx].pum_user_hlattr;
+ int *attrs = pum_compute_text_attrs(st, hlf, user_hlattr);
+
if (pum_rl) {
char *rt = reverse_text(st);
char *rt_start = rt;
- int size = vim_strsize(rt);
+ int cells = vim_strsize(rt);
- if (size > pum_width) {
+ if (cells > pum_width) {
do {
- size -= utf_ptr2cells(rt);
+ cells -= utf_ptr2cells(rt);
MB_PTR_ADV(rt);
- } while (size > pum_width);
+ } while (cells > pum_width);
- if (size < pum_width) {
+ if (cells < pum_width) {
// Most left character requires 2-cells but only 1 cell
// is available on screen. Put a '<' on the left of the
// pum item
*(--rt) = '<';
- size++;
+ cells++;
}
}
- grid_line_puts(grid_col - size + 1, rt, -1, attr);
+
+ if (attrs == NULL) {
+ grid_line_puts(grid_col - cells + 1, rt, -1, attr);
+ } else {
+ pum_grid_puts_with_attrs(grid_col - cells + 1, cells, rt, -1, attrs);
+ }
+
xfree(rt_start);
xfree(st);
grid_col -= width;
} else {
- // use grid_line_puts() to truncate the text
- grid_line_puts(grid_col, st, -1, attr);
+ if (attrs == NULL) {
+ grid_line_puts(grid_col, st, -1, attr);
+ } else {
+ pum_grid_puts_with_attrs(grid_col, vim_strsize(st), st, -1, attrs);
+ }
+
xfree(st);
grid_col += width;
}
+ xfree(attrs);
+
if (*p != TAB) {
break;
}
@@ -625,31 +751,31 @@ void pum_redraw(void)
}
}
- if (round > 0) {
- n = pum_kind_width + 1;
+ if (j > 0) {
+ n = items_width_array[order[1]] + (last_isabbr ? 0 : 1);
} else {
- n = 1;
+ n = order[j] == CPT_ABBR ? 1 : 0;
}
+ bool next_isempty = false;
+ if (j + 1 < 3) {
+ next_isempty = pum_get_item(idx, order[j + 1]) == NULL;
+ }
// Stop when there is nothing more to display.
- if ((round == 2)
- || ((round == 1)
- && (pum_array[idx].pum_extra == NULL))
- || ((round == 0)
- && (pum_array[idx].pum_kind == NULL)
- && (pum_array[idx].pum_extra == NULL))
- || (pum_base_width + n >= pum_width)) {
+ if ((j == 2)
+ || (next_isempty && (j == 1 || (j == 0 && pum_get_item(idx, order[j + 2]) == NULL)))
+ || (basic_width + n >= pum_width)) {
break;
}
if (pum_rl) {
- grid_line_fill(col_off - pum_base_width - n + 1, grid_col + 1, schar_from_ascii(' '), attr);
- grid_col = col_off - pum_base_width - n + 1;
+ grid_line_fill(col_off - basic_width - n + 1, grid_col + 1, schar_from_ascii(' '), attr);
+ grid_col = col_off - basic_width - n;
} else {
- grid_line_fill(grid_col, col_off + pum_base_width + n, schar_from_ascii(' '), attr);
- grid_col = col_off + pum_base_width + n;
+ grid_line_fill(grid_col, col_off + basic_width + n, schar_from_ascii(' '), attr);
+ grid_col = col_off + basic_width + n;
}
- totwidth = pum_base_width + n;
+ totwidth = basic_width + n;
}
if (pum_rl) {
@@ -699,7 +825,7 @@ static void pum_preview_set_text(buf_T *buf, char *info, linenr_T *lnum, int *ma
}
// delete the empty last line
ml_delete_buf(buf, buf->b_ml.ml_line_count, false);
- if (strstr(p_cot, "popup") != NULL) {
+ if (get_cot_flags() & COT_POPUP) {
extmark_splice(buf, 1, 0, 1, 0, 0, buf->b_ml.ml_line_count, 0, inserted_bytes, kExtmarkNoUndo);
}
}
@@ -794,7 +920,8 @@ static bool pum_set_selected(int n, int repeat)
int prev_selected = pum_selected;
pum_selected = n;
- bool use_float = strstr(p_cot, "popup") != NULL;
+ unsigned cur_cot_flags = get_cot_flags();
+ bool use_float = (cur_cot_flags & COT_POPUP) != 0;
// when new leader add and info window is shown and no selected we still
// need use the first index item to update the info float window position.
bool force_select = use_float && pum_selected < 0 && win_float_find_preview();
@@ -860,7 +987,7 @@ static bool pum_set_selected(int n, int repeat)
if ((pum_array[pum_selected].pum_info != NULL)
&& (Rows > 10)
&& (repeat <= 1)
- && (vim_strchr(p_cot, 'p') != NULL)) {
+ && (cur_cot_flags & COT_ANY_PREVIEW)) {
win_T *curwin_save = curwin;
tabpage_T *curtab_save = curtab;
@@ -1142,12 +1269,14 @@ void pum_set_event_info(dict_T *dict)
static void pum_position_at_mouse(int min_width)
{
int min_row = 0;
+ int min_col = 0;
int max_row = Rows;
int max_col = Columns;
if (mouse_grid > 1) {
win_T *wp = get_win_by_grid_handle(mouse_grid);
if (wp != NULL) {
min_row = -wp->w_winrow;
+ min_col = -wp->w_wincol;
max_row = MAX(Rows - wp->w_winrow, wp->w_grid.rows);
max_col = MAX(Columns - wp->w_wincol, wp->w_grid.cols);
}
@@ -1160,8 +1289,10 @@ static void pum_position_at_mouse(int min_width)
} else {
pum_anchor_grid = mouse_grid;
}
- if (max_row - mouse_row > pum_size) {
- // Enough space below the mouse row.
+
+ if (max_row - mouse_row > pum_size || max_row - mouse_row > mouse_row - min_row) {
+ // Enough space below the mouse row,
+ // or there is more space below the mouse row than above.
pum_above = false;
pum_row = mouse_row + 1;
if (pum_height > max_row - pum_row) {
@@ -1176,16 +1307,29 @@ static void pum_position_at_mouse(int min_width)
pum_row = min_row;
}
}
- if (max_col - mouse_col >= pum_base_width
- || max_col - mouse_col > min_width) {
- // Enough space to show at mouse column.
- pum_col = mouse_col;
+
+ if (pum_rl) {
+ if (mouse_col - min_col + 1 >= pum_base_width
+ || mouse_col - min_col + 1 > min_width) {
+ // Enough space to show at mouse column.
+ pum_col = mouse_col;
+ } else {
+ // Not enough space, left align with window.
+ pum_col = min_col + MIN(pum_base_width, min_width) - 1;
+ }
+ pum_width = pum_col - min_col + 1;
} else {
- // Not enough space, right align with window.
- pum_col = max_col - (pum_base_width > min_width ? min_width : pum_base_width);
+ if (max_col - mouse_col >= pum_base_width
+ || max_col - mouse_col > min_width) {
+ // Enough space to show at mouse column.
+ pum_col = mouse_col;
+ } else {
+ // Not enough space, right align with window.
+ pum_col = max_col - MIN(pum_base_width, min_width);
+ }
+ pum_width = max_col - pum_col;
}
- pum_width = max_col - pum_col;
if (pum_width > pum_base_width + 1) {
pum_width = pum_base_width + 1;
}
@@ -1204,7 +1348,7 @@ static void pum_select_mouse_pos(void)
int idx = mouse_row - pum_row;
- if (idx < 0 || idx >= pum_size) {
+ if (idx < 0 || idx >= pum_height) {
pum_selected = -1;
} else if (*pum_array[idx].pum_text != NUL) {
pum_selected = idx;
@@ -1267,6 +1411,7 @@ void pum_show_popupmenu(vimmenu_T *menu)
pum_compute_size();
pum_scrollbar = 0;
pum_height = pum_size;
+ pum_rl = curwin->w_p_rl;
pum_position_at_mouse(20);
pum_selected = -1;
@@ -1346,7 +1491,9 @@ void pum_make_popup(const char *path_name, int use_mouse_pos)
// Hack: set mouse position at the cursor so that the menu pops up
// around there.
mouse_row = curwin->w_grid.row_offset + curwin->w_wrow;
- mouse_col = curwin->w_grid.col_offset + curwin->w_wcol;
+ mouse_col = curwin->w_grid.col_offset
+ + (curwin->w_p_rl ? curwin->w_width_inner - curwin->w_wcol - 1
+ : curwin->w_wcol);
if (ui_has(kUIMultigrid)) {
mouse_grid = curwin->w_grid.target->handle;
} else if (curwin->w_grid.target != &default_grid) {
diff --git a/src/nvim/popupmenu.h b/src/nvim/popupmenu.h
index 20a342b841..f7eb0240ae 100644
--- a/src/nvim/popupmenu.h
+++ b/src/nvim/popupmenu.h
@@ -10,10 +10,14 @@
/// Used for popup menu items.
typedef struct {
- char *pum_text; // main menu text
- char *pum_kind; // extra kind text (may be truncated)
- char *pum_extra; // extra menu text (may be truncated)
- char *pum_info; // extra info
+ char *pum_text; ///< main menu text
+ char *pum_kind; ///< extra kind text (may be truncated)
+ char *pum_extra; ///< extra menu text (may be truncated)
+ char *pum_info; ///< extra info
+ int pum_score; ///< fuzzy match score
+ int pum_idx; ///< index of item before sorting by score
+ int pum_user_hlattr; ///< highlight attribute to combine with
+ int pum_user_kind_hlattr; ///< highlight attribute for kind
} pumitem_T;
EXTERN ScreenGrid pum_grid INIT( = SCREEN_GRID_INIT);
diff --git a/src/nvim/profile.c b/src/nvim/profile.c
index b88b08d3f0..81207b4e39 100644
--- a/src/nvim/profile.c
+++ b/src/nvim/profile.c
@@ -10,6 +10,7 @@
#include "nvim/charset.h"
#include "nvim/cmdexpand_defs.h"
#include "nvim/debugger.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/userfunc.h"
@@ -382,19 +383,19 @@ void set_context_in_profile_cmd(expand_T *xp, const char *arg)
xp->xp_context = EXPAND_NOTHING;
}
-static proftime_T inchar_time;
+static proftime_T wait_time;
/// Called when starting to wait for the user to type a character.
-void prof_inchar_enter(void)
+void prof_input_start(void)
{
- inchar_time = profile_start();
+ wait_time = profile_start();
}
/// Called when finished waiting for the user to type a character.
-void prof_inchar_exit(void)
+void prof_input_end(void)
{
- inchar_time = profile_end(inchar_time);
- profile_set_wait(profile_add(profile_get_wait(), inchar_time));
+ wait_time = profile_end(wait_time);
+ profile_set_wait(profile_add(profile_get_wait(), wait_time));
}
/// @return true when a function defined in the current script should be
@@ -949,8 +950,8 @@ void time_msg(const char *mesg, const proftime_T *start)
/// Initializes the `time_fd` stream for the --startuptime report.
///
/// @param fname startuptime report file path
-/// @param process_name name of the current Nvim process to write in the report.
-void time_init(const char *fname, const char *process_name)
+/// @param proc_name name of the current Nvim process to write in the report.
+void time_init(const char *fname, const char *proc_name)
{
const size_t bufsize = 8192; // Big enough for the entire --startuptime report.
time_fd = fopen(fname, "a");
@@ -971,7 +972,7 @@ void time_init(const char *fname, const char *process_name)
semsg("time_init: setvbuf failed: %d %s", r, uv_err_name(r));
return;
}
- fprintf(time_fd, "--- Startup times for process: %s ---\n", process_name);
+ fprintf(time_fd, "--- Startup times for process: %s ---\n", proc_name);
}
/// Flushes the startuptimes to disk for the current process
diff --git a/src/nvim/quickfix.c b/src/nvim/quickfix.c
index e022184f2f..ddf2a7247f 100644
--- a/src/nvim/quickfix.c
+++ b/src/nvim/quickfix.c
@@ -20,6 +20,7 @@
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/window.h"
@@ -90,6 +91,7 @@ struct qfline_S {
int qf_end_col; ///< column when the error has range or zero
int qf_nr; ///< error number
char *qf_module; ///< module name for this error
+ char *qf_fname; ///< different filename if there're hard links
char *qf_pattern; ///< search pattern for the error
char *qf_text; ///< description of the error
char qf_viscol; ///< set to true if qf_col and qf_end_col is screen column
@@ -381,11 +383,7 @@ static int qf_init_process_nextline(qf_list_T *qfl, efm_T *fmt_first, qfstate_T
int qf_init(win_T *wp, const char *restrict efile, char *restrict errorformat, int newlist,
const char *restrict qf_title, char *restrict enc)
{
- qf_info_T *qi = &ql_info;
-
- if (wp != NULL) {
- qi = ll_get_or_alloc_list(wp);
- }
+ qf_info_T *qi = wp == NULL ? &ql_info : ll_get_or_alloc_list(wp);
return qf_init_ext(qi, qi->qf_curlist, efile, curbuf, NULL, errorformat,
newlist, 0, 0, qf_title, enc);
@@ -723,7 +721,7 @@ static int qf_get_next_str_line(qfstate_T *state)
state->linelen = len;
}
memcpy(state->linebuf, p_str, state->linelen);
- state->linebuf[state->linelen] = '\0';
+ state->linebuf[state->linelen] = NUL;
// Increment using len in order to discard the rest of the line if it
// exceeds LINE_MAXLEN.
@@ -833,8 +831,7 @@ retry:
break;
}
- state->growbufsiz = (2 * state->growbufsiz < LINE_MAXLEN)
- ? 2 * state->growbufsiz : LINE_MAXLEN;
+ state->growbufsiz = MIN(2 * state->growbufsiz, LINE_MAXLEN);
state->growbuf = xrealloc(state->growbuf, state->growbufsiz);
}
@@ -870,8 +867,7 @@ retry:
xfree(state->growbuf);
state->linebuf = line;
state->growbuf = line;
- state->growbufsiz = state->linelen < LINE_MAXLEN
- ? state->linelen : LINE_MAXLEN;
+ state->growbufsiz = MIN(state->linelen, LINE_MAXLEN);
}
}
}
@@ -1151,14 +1147,10 @@ static int qf_init_ext(qf_info_T *qi, int qf_idx, const char *restrict efile, bu
}
}
- char *efm;
-
// Use the local value of 'errorformat' if it's set.
- if (errorformat == p_efm && tv == NULL && buf && *buf->b_p_efm != NUL) {
- efm = buf->b_p_efm;
- } else {
- efm = errorformat;
- }
+ char *efm = (errorformat == p_efm && tv == NULL && buf && *buf->b_p_efm != NUL)
+ ? buf->b_p_efm
+ : errorformat;
// If the errorformat didn't change between calls, then reuse the previously
// parsed values.
@@ -1490,9 +1482,7 @@ static int qf_parse_fmt_s(regmatch_T *rmp, int midx, qffields_T *fields)
return QF_FAIL;
}
size_t len = (size_t)(rmp->endp[midx] - rmp->startp[midx]);
- if (len > CMDBUFFSIZE - 5) {
- len = CMDBUFFSIZE - 5;
- }
+ len = MIN(len, CMDBUFFSIZE - 5);
STRCPY(fields->pattern, "^\\V");
xstrlcat(fields->pattern, rmp->startp[midx], len + 4);
fields->pattern[len + 3] = '\\';
@@ -1510,9 +1500,7 @@ static int qf_parse_fmt_o(regmatch_T *rmp, int midx, qffields_T *fields)
}
size_t len = (size_t)(rmp->endp[midx] - rmp->startp[midx]);
size_t dsize = strlen(fields->module) + len + 1;
- if (dsize > CMDBUFFSIZE) {
- dsize = CMDBUFFSIZE;
- }
+ dsize = MIN(dsize, CMDBUFFSIZE);
xstrlcat(fields->module, rmp->startp[midx], dsize);
return QF_OK;
}
@@ -1879,11 +1867,13 @@ static int qf_add_entry(qf_list_T *qfl, char *dir, char *fname, char *module, in
char vis_col, char *pattern, int nr, char type, typval_T *user_data,
char valid)
{
+ buf_T *buf;
qfline_T *qfp = xmalloc(sizeof(qfline_T));
+ char *fullname = NULL;
+ char *p = NULL;
if (bufnum != 0) {
- buf_T *buf = buflist_findnr(bufnum);
-
+ buf = buflist_findnr(bufnum);
qfp->qf_fnum = bufnum;
if (buf != NULL) {
buf->b_has_qf_entry |=
@@ -1891,7 +1881,21 @@ static int qf_add_entry(qf_list_T *qfl, char *dir, char *fname, char *module, in
}
} else {
qfp->qf_fnum = qf_get_fnum(qfl, dir, fname);
+ buf = buflist_findnr(qfp->qf_fnum);
+ }
+ if (fname != NULL) {
+ fullname = fix_fname(fname);
+ }
+ qfp->qf_fname = NULL;
+ if (buf != NULL && buf->b_ffname != NULL && fullname != NULL) {
+ if (path_fnamecmp(fullname, buf->b_ffname) != 0) {
+ p = path_try_shorten_fname(fullname);
+ if (p != NULL) {
+ qfp->qf_fname = xstrdup(p);
+ }
+ }
}
+ xfree(fullname);
qfp->qf_text = xstrdup(mesg);
qfp->qf_lnum = lnum;
qfp->qf_end_lnum = end_lnum;
@@ -2526,13 +2530,7 @@ static void win_set_loclist(win_T *wp, qf_info_T *qi)
/// window.
static int jump_to_help_window(qf_info_T *qi, bool newwin, bool *opened_window)
{
- win_T *wp = NULL;
-
- if (cmdmod.cmod_tab != 0 || newwin) {
- wp = NULL;
- } else {
- wp = qf_find_help_win();
- }
+ win_T *wp = (cmdmod.cmod_tab != 0 || newwin) ? NULL : qf_find_help_win();
if (wp != NULL && wp->w_buffer->b_nwindows > 0) {
win_enter(wp, true);
@@ -2881,9 +2879,7 @@ static void qf_jump_goto_line(linenr_T qf_lnum, int qf_col, char qf_viscol, char
// Go to line with error, unless qf_lnum is 0.
linenr_T i = qf_lnum;
if (i > 0) {
- if (i > curbuf->b_ml.ml_line_count) {
- i = curbuf->b_ml.ml_line_count;
- }
+ i = MIN(i, curbuf->b_ml.ml_line_count);
curwin->w_cursor.lnum = i;
}
if (qf_col > 0) {
@@ -2914,8 +2910,7 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf
{
garray_T *const gap = qfga_get();
- // Update the screen before showing the message, unless the screen
- // scrolled up.
+ // Update the screen before showing the message, unless messages scrolled.
if (!msg_scrolled) {
update_topline(curwin);
if (must_redraw) {
@@ -2937,7 +2932,8 @@ static void qf_jump_print_msg(qf_info_T *qi, int qf_index, qfline_T *qf_ptr, buf
linenr_T i = msg_scroll;
if (curbuf == old_curbuf && curwin->w_cursor.lnum == old_lnum) {
msg_scroll = true;
- } else if (!msg_scrolled && shortmess(SHM_OVERALL)) {
+ } else if ((msg_scrolled == 0 || (p_ch == 0 && msg_scrolled == 1))
+ && shortmess(SHM_OVERALL)) {
msg_scroll = false;
}
msg_ext_set_kind("quickfix");
@@ -3166,7 +3162,7 @@ static void qf_list_entry(qfline_T *qfp, int qf_idx, bool cursel)
buf_T *buf;
if (qfp->qf_fnum != 0
&& (buf = buflist_findnr(qfp->qf_fnum)) != NULL) {
- fname = buf->b_fname;
+ fname = qfp->qf_fname == NULL ? buf->b_fname : qfp->qf_fname;
if (qfp->qf_type == 1) { // :helpgrep
fname = path_tail(fname);
}
@@ -3452,6 +3448,7 @@ static void qf_free_items(qf_list_T *qfl)
qfline_T *qfp = qfl->qf_start;
qfline_T *qfpnext = qfp->qf_next;
if (!stop) {
+ xfree(qfp->qf_fname);
xfree(qfp->qf_module);
xfree(qfp->qf_text);
xfree(qfp->qf_pattern);
@@ -3897,13 +3894,8 @@ static bool qf_win_pos_update(qf_info_T *qi, int old_qf_index)
if (win != NULL
&& qf_index <= win->w_buffer->b_ml.ml_line_count
&& old_qf_index != qf_index) {
- if (qf_index > old_qf_index) {
- win->w_redraw_top = old_qf_index;
- win->w_redraw_bot = qf_index;
- } else {
- win->w_redraw_top = qf_index;
- win->w_redraw_bot = old_qf_index;
- }
+ win->w_redraw_top = MIN(old_qf_index, qf_index);
+ win->w_redraw_bot = MAX(old_qf_index, qf_index);
qf_win_goto(win, qf_index);
}
return win != NULL;
@@ -4085,7 +4077,7 @@ static int qf_buf_add_line(qf_list_T *qfl, buf_T *buf, linenr_T lnum, const qfli
}
shorten_buf_fname(errbuf, dirname, false);
}
- ga_concat(gap, errbuf->b_fname);
+ ga_concat(gap, qfp->qf_fname == NULL ? errbuf->b_fname : qfp->qf_fname);
}
}
@@ -4219,11 +4211,7 @@ static void qf_fill_buffer(qf_list_T *qfl, buf_T *buf, qfline_T *old_last, int q
qfp = qfl->qf_start;
lnum = 0;
} else {
- if (old_last->qf_next != NULL) {
- qfp = old_last->qf_next;
- } else {
- qfp = old_last;
- }
+ qfp = old_last->qf_next != NULL ? old_last->qf_next : old_last;
lnum = buf->b_ml.ml_line_count;
}
@@ -4450,15 +4438,11 @@ void ex_make(exarg_T *eap)
incr_quickfix_busy();
- char *errorformat = p_efm;
- bool newlist = true;
+ char *errorformat = (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake)
+ ? p_gefm
+ : p_efm;
- if (eap->cmdidx != CMD_make && eap->cmdidx != CMD_lmake) {
- errorformat = p_gefm;
- }
- if (eap->cmdidx == CMD_grepadd || eap->cmdidx == CMD_lgrepadd) {
- newlist = false;
- }
+ bool newlist = eap->cmdidx != CMD_grepadd && eap->cmdidx != CMD_lgrepadd;
int res = qf_init(wp, fname, errorformat, newlist, qf_cmdtitle(*eap->cmdlinep), enc);
@@ -4530,7 +4514,7 @@ static char *get_mef_name(void)
name = xmalloc(strlen(p_mef) + 30);
STRCPY(name, p_mef);
snprintf(name + (p - p_mef), strlen(name), "%d%d", start, off);
- STRCAT(name, p + 2);
+ strcat(name, p + 2);
// Don't accept a symbolic link, it's a security risk.
FileInfo file_info;
bool file_or_link_found = os_fileinfo_link(name, &file_info);
@@ -5322,9 +5306,7 @@ static bool vgr_match_buflines(qf_list_T *qfl, char *fname, buf_T *buf, char *sp
{
bool found_match = false;
size_t pat_len = strlen(spat);
- if (pat_len > MAX_FUZZY_MATCHES) {
- pat_len = MAX_FUZZY_MATCHES;
- }
+ pat_len = MIN(pat_len, MAX_FUZZY_MATCHES);
for (linenr_T lnum = 1; lnum <= buf->b_ml.ml_line_count && *tomatch > 0; lnum++) {
colnr_T col = 0;
@@ -5466,12 +5448,7 @@ static int vgr_process_args(exarg_T *eap, vgr_args_T *args)
args->regmatch.regprog = NULL;
args->qf_title = xstrdup(qf_cmdtitle(*eap->cmdlinep));
-
- if (eap->addr_count > 0) {
- args->tomatch = eap->line2;
- } else {
- args->tomatch = MAXLNUM;
- }
+ args->tomatch = eap->addr_count > 0 ? eap->line2 : MAXLNUM;
// Get the search pattern: either white-separated or enclosed in //
char *p = skip_vimgrep_pat(eap->arg, &args->spat, &args->flags);
@@ -6701,9 +6678,7 @@ static int qf_setprop_curidx(qf_info_T *qi, qf_list_T *qfl, const dictitem_T *di
if (newidx < 1) { // sanity check
return FAIL;
}
- if (newidx > qfl->qf_count) {
- newidx = qfl->qf_count;
- }
+ newidx = MIN(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) {
@@ -6900,31 +6875,17 @@ static bool mark_quickfix_ctx(qf_info_T *qi, int copyID)
/// "in use". So that garbage collection doesn't free the context.
bool set_ref_in_quickfix(int copyID)
{
- bool abort = mark_quickfix_ctx(&ql_info, copyID);
- if (abort) {
- return abort;
- }
-
- abort = mark_quickfix_user_data(&ql_info, copyID);
- if (abort) {
- return abort;
- }
-
- abort = set_ref_in_callback(&qftf_cb, copyID, NULL, NULL);
- if (abort) {
- return abort;
+ if (mark_quickfix_ctx(&ql_info, copyID)
+ || mark_quickfix_user_data(&ql_info, copyID)
+ || set_ref_in_callback(&qftf_cb, copyID, NULL, NULL)) {
+ return true;
}
FOR_ALL_TAB_WINDOWS(tp, win) {
if (win->w_llist != NULL) {
- abort = mark_quickfix_ctx(win->w_llist, copyID);
- if (abort) {
- return abort;
- }
-
- abort = mark_quickfix_user_data(win->w_llist, copyID);
- if (abort) {
- return abort;
+ if (mark_quickfix_ctx(win->w_llist, copyID)
+ || mark_quickfix_user_data(win->w_llist, copyID)) {
+ return true;
}
}
@@ -6932,14 +6893,14 @@ bool set_ref_in_quickfix(int copyID)
// In a location list window and none of the other windows is
// referring to this location list. Mark the location list
// context as still in use.
- abort = mark_quickfix_ctx(win->w_llist_ref, copyID);
- if (abort) {
- return abort;
+ if (mark_quickfix_ctx(win->w_llist_ref, copyID)
+ || mark_quickfix_user_data(win->w_llist_ref, copyID)) {
+ return true;
}
}
}
- return abort;
+ return false;
}
/// Return the autocmd name for the :cbuffer Ex commands
@@ -7236,7 +7197,7 @@ static void hgr_search_files_in_dir(qf_list_T *qfl, char *dirname, regmatch_T *p
// Find all "*.txt" and "*.??x" files in the "doc" directory.
add_pathsep(dirname);
- STRCAT(dirname, "doc/*.\\(txt\\|??x\\)"); // NOLINT
+ strcat(dirname, "doc/*.\\(txt\\|??x\\)"); // NOLINT
if (gen_expand_wildcards(1, &dirname, &fcount, &fnames, EW_FILE|EW_SILENT) == OK
&& fcount > 0) {
for (int fi = 0; fi < fcount && !got_int; fi++) {
diff --git a/src/nvim/rbuffer.c b/src/nvim/rbuffer.c
deleted file mode 100644
index 493c079d4c..0000000000
--- a/src/nvim/rbuffer.c
+++ /dev/null
@@ -1,230 +0,0 @@
-#include <assert.h>
-#include <stdbool.h>
-#include <stddef.h>
-#include <string.h>
-
-#include "nvim/macros_defs.h"
-#include "nvim/memory.h"
-#include "nvim/rbuffer.h"
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "rbuffer.c.generated.h"
-#endif
-
-/// Creates a new `RBuffer` instance.
-RBuffer *rbuffer_new(size_t capacity)
- FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_RET
-{
- if (!capacity) {
- capacity = 0x10000;
- }
-
- RBuffer *rv = xcalloc(1, sizeof(RBuffer) + capacity);
- rv->full_cb = rv->nonfull_cb = NULL;
- rv->data = NULL;
- rv->size = 0;
- rv->write_ptr = rv->read_ptr = rv->start_ptr;
- rv->end_ptr = rv->start_ptr + capacity;
- rv->temp = NULL;
- return rv;
-}
-
-void rbuffer_free(RBuffer *buf) FUNC_ATTR_NONNULL_ALL
-{
- xfree(buf->temp);
- xfree(buf);
-}
-
-/// Return a pointer to a raw buffer containing the first empty slot available
-/// for writing. The second argument is a pointer to the maximum number of
-/// bytes that could be written.
-///
-/// It is necessary to call this function twice to ensure all empty space was
-/// used. See RBUFFER_UNTIL_FULL for a macro that simplifies this task.
-char *rbuffer_write_ptr(RBuffer *buf, size_t *write_count) FUNC_ATTR_NONNULL_ALL
-{
- if (buf->size == rbuffer_capacity(buf)) {
- *write_count = 0;
- return NULL;
- }
-
- if (buf->write_ptr >= buf->read_ptr) {
- *write_count = (size_t)(buf->end_ptr - buf->write_ptr);
- } else {
- *write_count = (size_t)(buf->read_ptr - buf->write_ptr);
- }
-
- return buf->write_ptr;
-}
-
-// Reset an RBuffer so read_ptr is at the beginning of the memory. If
-// necessary, this moves existing data by allocating temporary memory.
-void rbuffer_reset(RBuffer *buf) FUNC_ATTR_NONNULL_ALL
-{
- size_t temp_size;
- if ((temp_size = rbuffer_size(buf))) {
- if (buf->temp == NULL) {
- buf->temp = xcalloc(1, rbuffer_capacity(buf));
- }
- rbuffer_read(buf, buf->temp, buf->size);
- }
- buf->read_ptr = buf->write_ptr = buf->start_ptr;
- if (temp_size) {
- rbuffer_write(buf, buf->temp, temp_size);
- }
-}
-
-/// Adjust `rbuffer` write pointer to reflect produced data. This is called
-/// automatically by `rbuffer_write`, but when using `rbuffer_write_ptr`
-/// directly, this needs to called after the data was copied to the internal
-/// buffer. The write pointer will be wrapped if required.
-void rbuffer_produced(RBuffer *buf, size_t count) FUNC_ATTR_NONNULL_ALL
-{
- assert(count && count <= rbuffer_space(buf));
-
- buf->write_ptr += count;
- if (buf->write_ptr >= buf->end_ptr) {
- // wrap around
- buf->write_ptr -= rbuffer_capacity(buf);
- }
-
- buf->size += count;
- if (buf->full_cb && !rbuffer_space(buf)) {
- buf->full_cb(buf, buf->data);
- }
-}
-
-/// Return a pointer to a raw buffer containing the first byte available
-/// for reading. The second argument is a pointer to the maximum number of
-/// bytes that could be read.
-///
-/// It is necessary to call this function twice to ensure all available bytes
-/// were read. See RBUFFER_UNTIL_EMPTY for a macro that simplifies this task.
-char *rbuffer_read_ptr(RBuffer *buf, size_t *read_count) FUNC_ATTR_NONNULL_ALL
-{
- if (!buf->size) {
- *read_count = 0;
- return buf->read_ptr;
- }
-
- if (buf->read_ptr < buf->write_ptr) {
- *read_count = (size_t)(buf->write_ptr - buf->read_ptr);
- } else {
- *read_count = (size_t)(buf->end_ptr - buf->read_ptr);
- }
-
- return buf->read_ptr;
-}
-
-/// Adjust `rbuffer` read pointer to reflect consumed data. This is called
-/// automatically by `rbuffer_read`, but when using `rbuffer_read_ptr`
-/// directly, this needs to called after the data was copied from the internal
-/// buffer. The read pointer will be wrapped if required.
-void rbuffer_consumed(RBuffer *buf, size_t count)
- FUNC_ATTR_NONNULL_ALL
-{
- if (count == 0) {
- return;
- }
- assert(count <= buf->size);
-
- buf->read_ptr += count;
- if (buf->read_ptr >= buf->end_ptr) {
- buf->read_ptr -= rbuffer_capacity(buf);
- }
-
- bool was_full = buf->size == rbuffer_capacity(buf);
- buf->size -= count;
- if (buf->nonfull_cb && was_full) {
- buf->nonfull_cb(buf, buf->data);
- }
-}
-
-/// Use instead of rbuffer_consumed to use rbuffer in a linear, non-cyclic fashion.
-///
-/// This is generally useful if we can guarantee to parse all input
-/// except some small incomplete token, like when parsing msgpack.
-void rbuffer_consumed_compact(RBuffer *buf, size_t count)
- FUNC_ATTR_NONNULL_ALL
-{
- assert(buf->read_ptr <= buf->write_ptr);
- rbuffer_consumed(buf, count);
- if (buf->read_ptr > buf->start_ptr) {
- assert((size_t)(buf->write_ptr - buf->read_ptr) == buf->size
- || buf->write_ptr == buf->start_ptr);
- memmove(buf->start_ptr, buf->read_ptr, buf->size);
- buf->read_ptr = buf->start_ptr;
- buf->write_ptr = buf->read_ptr + buf->size;
- }
-}
-
-// Higher level functions for copying from/to RBuffer instances and data
-// pointers
-size_t rbuffer_write(RBuffer *buf, const char *src, size_t src_size)
- FUNC_ATTR_NONNULL_ALL
-{
- size_t size = src_size;
-
- RBUFFER_UNTIL_FULL(buf, wptr, wcnt) {
- size_t copy_count = MIN(src_size, wcnt);
- memcpy(wptr, src, copy_count);
- rbuffer_produced(buf, copy_count);
-
- if (!(src_size -= copy_count)) {
- return size;
- }
-
- src += copy_count;
- }
-
- return size - src_size;
-}
-
-size_t rbuffer_read(RBuffer *buf, char *dst, size_t dst_size)
- FUNC_ATTR_NONNULL_ALL
-{
- size_t size = dst_size;
-
- RBUFFER_UNTIL_EMPTY(buf, rptr, rcnt) {
- size_t copy_count = MIN(dst_size, rcnt);
- memcpy(dst, rptr, copy_count);
- rbuffer_consumed(buf, copy_count);
-
- if (!(dst_size -= copy_count)) {
- return size;
- }
-
- dst += copy_count;
- }
-
- return size - dst_size;
-}
-
-char *rbuffer_get(RBuffer *buf, size_t index)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_NONNULL_RET
-{
- assert(index < buf->size);
- char *rptr = buf->read_ptr + index;
- if (rptr >= buf->end_ptr) {
- rptr -= rbuffer_capacity(buf);
- }
- return rptr;
-}
-
-int rbuffer_cmp(RBuffer *buf, const char *str, size_t count)
- FUNC_ATTR_NONNULL_ALL
-{
- assert(count <= buf->size);
- size_t rcnt;
- rbuffer_read_ptr(buf, &rcnt);
- size_t n = MIN(count, rcnt);
- int rv = memcmp(str, buf->read_ptr, n);
- count -= n;
- size_t remaining = buf->size - rcnt;
-
- if (rv || !count || !remaining) {
- return rv;
- }
-
- return memcmp(str + n, buf->start_ptr, count);
-}
diff --git a/src/nvim/rbuffer.h b/src/nvim/rbuffer.h
deleted file mode 100644
index 942e1f2365..0000000000
--- a/src/nvim/rbuffer.h
+++ /dev/null
@@ -1,71 +0,0 @@
-// Specialized ring buffer. This is basically an array that wraps read/write
-// pointers around the memory region. It should be more efficient than the old
-// RBuffer which required memmove() calls to relocate read/write positions.
-//
-// The main purpose of RBuffer is simplify memory management when reading from
-// uv_stream_t instances:
-//
-// - The event loop writes data to a RBuffer, advancing the write pointer
-// - The main loop reads data, advancing the read pointer
-// - If the buffer becomes full(size == capacity) the rstream is temporarily
-// stopped(automatic backpressure handling)
-//
-// Reference: http://en.wikipedia.org/wiki/Circular_buffer
-#pragma once
-
-#include <stddef.h>
-#include <stdint.h>
-
-#include "nvim/rbuffer_defs.h" // IWYU pragma: keep
-
-// Macros that simplify working with the read/write pointers directly by hiding
-// ring buffer wrap logic. Some examples:
-//
-// - Pass the write pointer to a function(write_data) that incrementally
-// produces data, returning the number of bytes actually written to the
-// ring buffer:
-//
-// RBUFFER_UNTIL_FULL(rbuf, ptr, cnt)
-// rbuffer_produced(rbuf, write_data(state, ptr, cnt));
-//
-// - Pass the read pointer to a function(read_data) that incrementally
-// consumes data, returning the number of bytes actually read from the
-// ring buffer:
-//
-// RBUFFER_UNTIL_EMPTY(rbuf, ptr, cnt)
-// rbuffer_consumed(rbuf, read_data(state, ptr, cnt));
-//
-// Note that the rbuffer_{produced,consumed} calls are necessary or these macros
-// create infinite loops
-#define RBUFFER_UNTIL_EMPTY(buf, rptr, rcnt) \
- for (size_t rcnt = 0, _r = 1; _r; _r = 0) \
- for (char *rptr = rbuffer_read_ptr(buf, &rcnt); \
- buf->size; \
- rptr = rbuffer_read_ptr(buf, &rcnt))
-
-#define RBUFFER_UNTIL_FULL(buf, wptr, wcnt) \
- for (size_t wcnt = 0, _r = 1; _r; _r = 0) \
- for (char *wptr = rbuffer_write_ptr(buf, &wcnt); \
- rbuffer_space(buf); \
- wptr = rbuffer_write_ptr(buf, &wcnt))
-
-// Iteration
-#define RBUFFER_EACH(buf, c, i) \
- for (size_t i = 0; \
- i < buf->size; \
- i = buf->size) \
- for (char c = 0; \
- i < buf->size ? ((int)(c = *rbuffer_get(buf, i))) || 1 : 0; \
- i++)
-
-#define RBUFFER_EACH_REVERSE(buf, c, i) \
- for (size_t i = buf->size; \
- i != SIZE_MAX; \
- i = SIZE_MAX) \
- for (char c = 0; \
- i-- > 0 ? ((int)(c = *rbuffer_get(buf, i))) || 1 : 0; \
- )
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "rbuffer.h.generated.h"
-#endif
diff --git a/src/nvim/rbuffer_defs.h b/src/nvim/rbuffer_defs.h
deleted file mode 100644
index 51dc349846..0000000000
--- a/src/nvim/rbuffer_defs.h
+++ /dev/null
@@ -1,45 +0,0 @@
-#pragma once
-
-#include <stddef.h>
-
-#include "nvim/func_attr.h"
-
-typedef struct rbuffer RBuffer;
-/// Type of function invoked during certain events:
-/// - When the RBuffer switches to the full state
-/// - When the RBuffer switches to the non-full state
-typedef void (*rbuffer_callback)(RBuffer *buf, void *data);
-
-struct rbuffer {
- rbuffer_callback full_cb, nonfull_cb;
- void *data;
- size_t size;
- // helper memory used to by rbuffer_reset if required
- char *temp;
- char *end_ptr, *read_ptr, *write_ptr;
- char start_ptr[];
-};
-
-static inline size_t rbuffer_size(RBuffer *buf)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL;
-
-static inline size_t rbuffer_size(RBuffer *buf)
-{
- return buf->size;
-}
-
-static inline size_t rbuffer_capacity(RBuffer *buf)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL;
-
-static inline size_t rbuffer_capacity(RBuffer *buf)
-{
- return (size_t)(buf->end_ptr - buf->start_ptr);
-}
-
-static inline size_t rbuffer_space(RBuffer *buf)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL;
-
-static inline size_t rbuffer_space(RBuffer *buf)
-{
- return rbuffer_capacity(buf) - buf->size;
-}
diff --git a/src/nvim/regexp.c b/src/nvim/regexp.c
index 5600d6a2f8..c91c112c3c 100644
--- a/src/nvim/regexp.c
+++ b/src/nvim/regexp.c
@@ -18,6 +18,7 @@
#include "nvim/ascii_defs.h"
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
@@ -1731,7 +1732,9 @@ static void mb_decompose(int c, int *c1, int *c2, int *c3)
/// Compare two strings, ignore case if rex.reg_ic set.
/// Return 0 if strings match, non-zero otherwise.
-/// Correct the length "*n" when composing characters are ignored.
+/// Correct the length "*n" when composing characters are ignored
+/// or when both utf codepoints are considered equal because of
+/// case-folding but have different length (e.g. 's' and 'ſ')
static int cstrncmp(char *s1, char *s2, int *n)
{
int result;
@@ -1739,8 +1742,27 @@ static int cstrncmp(char *s1, char *s2, int *n)
if (!rex.reg_ic) {
result = strncmp(s1, s2, (size_t)(*n));
} else {
- assert(*n >= 0);
- result = mb_strnicmp(s1, s2, (size_t)(*n));
+ char *p = s1;
+ int n2 = 0;
+ int n1 = *n;
+ // count the number of characters for byte-length of s1
+ while (n1 > 0 && *p != NUL) {
+ n1 -= utfc_ptr2len(s1);
+ MB_PTR_ADV(p);
+ n2++;
+ }
+ // count the number of bytes to advance the same number of chars for s2
+ p = s2;
+ while (n2-- > 0 && *p != NUL) {
+ MB_PTR_ADV(p);
+ }
+
+ n2 = (int)(p - s2);
+
+ result = utf_strnicmp(s1, s2, (size_t)(*n), (size_t)n2);
+ if (result == 0 && n2 < *n) {
+ *n = n2;
+ }
}
// if it failed and it's utf8 and we want to combineignore:
@@ -1798,29 +1820,34 @@ static inline char *cstrchr(const char *const s, const int c)
return vim_strchr(s, c);
}
- // Use folded case for UTF-8, slow! For ASCII use libc strpbrk which is
- // expected to be highly optimized.
+ int cc, lc;
if (c > 0x80) {
- const int folded_c = utf_fold(c);
- for (const char *p = s; *p != NUL; p += utfc_ptr2len(p)) {
- if (utf_fold(utf_ptr2char(p)) == folded_c) {
- return (char *)p;
- }
- }
- return NULL;
- }
-
- int cc;
- if (ASCII_ISUPPER(c)) {
+ cc = utf_fold(c);
+ lc = cc;
+ } else if (ASCII_ISUPPER(c)) {
cc = TOLOWER_ASC(c);
+ lc = cc;
} else if (ASCII_ISLOWER(c)) {
cc = TOUPPER_ASC(c);
+ lc = c;
} else {
return vim_strchr(s, c);
}
- char tofind[] = { (char)c, (char)cc, NUL };
- return strpbrk(s, tofind);
+ for (const char *p = s; *p != NUL; p += utfc_ptr2len(p)) {
+ const int uc = utf_ptr2char(p);
+ if (c > 0x80 || uc > 0x80) {
+ // Do not match an illegal byte. E.g. 0xff matches 0xc3 0xbf, not 0xff.
+ // Compare with lower case of the character.
+ if ((uc < 0x80 || uc != (uint8_t)(*p)) && utf_fold(uc) == lc) {
+ return (char *)p;
+ }
+ } else if ((uint8_t)(*p) == c || (uint8_t)(*p) == cc) {
+ return (char *)p;
+ }
+ }
+
+ return NULL;
}
////////////////////////////////////////////////////////////////
@@ -2167,7 +2194,7 @@ static int vim_regsub_both(char *source, typval_T *expr, char *dest, int destlen
}
tv_clear(&rettv);
} else {
- eval_result[nested] = eval_to_string(source + 2, true);
+ eval_result[nested] = eval_to_string(source + 2, true, false);
}
nesting--;
@@ -3004,7 +3031,7 @@ static bool use_multibytecode(int c)
{
return utf_char2len(c) > 1
&& (re_multi_type(peekchr()) != NOT_MULTI
- || utf_iscomposing(c));
+ || utf_iscomposing_legacy(c));
}
// Emit (if appropriate) a byte of code
@@ -4299,7 +4326,7 @@ static uint8_t *regatom(int *flagp)
}
// When '.' is followed by a composing char ignore the dot, so that
// the composing char is matched here.
- if (c == Magic('.') && utf_iscomposing(peekchr())) {
+ if (c == Magic('.') && utf_iscomposing_legacy(peekchr())) {
c = getchr();
goto do_multibyte;
}
@@ -4974,9 +5001,10 @@ do_multibyte:
int l;
// Need to get composing character too.
+ GraphemeState state = GRAPHEME_STATE_INIT;
while (true) {
l = utf_ptr2len(regparse);
- if (!utf_composinglike(regparse, regparse + l)) {
+ if (!utf_composinglike(regparse, regparse + l, &state)) {
break;
}
regmbc(utf_ptr2char(regparse));
@@ -6253,15 +6281,20 @@ static bool regmatch(uint8_t *scan, const proftime_T *tm, int *timed_out)
}
break;
- case RE_VCOL:
- if (!re_num_cmp((unsigned)win_linetabsize(rex.reg_win == NULL ? curwin : rex.reg_win,
- rex.reg_firstlnum + rex.lnum,
- (char *)rex.line,
- (colnr_T)(rex.input - rex.line)) + 1,
- scan)) {
+ case RE_VCOL: {
+ win_T *wp = rex.reg_win == NULL ? curwin : rex.reg_win;
+ linenr_T lnum = REG_MULTI ? rex.reg_firstlnum + rex.lnum : 1;
+ if (REG_MULTI && (lnum <= 0 || lnum > wp->w_buffer->b_ml.ml_line_count)) {
+ lnum = 1;
+ }
+ int vcol = win_linetabsize(wp, lnum, (char *)rex.line,
+ (colnr_T)(rex.input - rex.line));
+ if (!re_num_cmp((uint32_t)vcol + 1, scan)) {
status = RA_NOMATCH;
}
break;
+ }
+ break;
case BOW: // \<word; rex.input points to w
if (c == NUL) { // Can't match at end of line
@@ -6537,7 +6570,7 @@ static bool regmatch(uint8_t *scan, const proftime_T *tm, int *timed_out)
// Check for following composing character, unless %C
// follows (skips over all composing chars).
if (status != RA_NOMATCH
- && utf_composinglike((char *)rex.input, (char *)rex.input + len)
+ && utf_composinglike((char *)rex.input, (char *)rex.input + len, NULL)
&& !rex.reg_icombine
&& OP(next) != RE_COMPOSING) {
// raaron: This code makes a composing character get
@@ -6592,14 +6625,14 @@ static bool regmatch(uint8_t *scan, const proftime_T *tm, int *timed_out)
break;
}
const int opndc = utf_ptr2char((char *)opnd);
- if (utf_iscomposing(opndc)) {
+ if (utf_iscomposing_legacy(opndc)) {
// When only a composing char is given match at any
// position where that composing char appears.
status = RA_NOMATCH;
for (i = 0; rex.input[i] != NUL;
i += utf_ptr2len((char *)rex.input + i)) {
const int inpc = utf_ptr2char((char *)rex.input + i);
- if (!utf_iscomposing(inpc)) {
+ if (!utf_iscomposing_legacy(inpc)) {
if (i > 0) {
break;
}
@@ -6611,11 +6644,9 @@ static bool regmatch(uint8_t *scan, const proftime_T *tm, int *timed_out)
}
}
} else {
- for (i = 0; i < len; i++) {
- if (opnd[i] != rex.input[i]) {
- status = RA_NOMATCH;
- break;
- }
+ if (cstrncmp((char *)opnd, (char *)rex.input, &len) != 0) {
+ status = RA_NOMATCH;
+ break;
}
}
rex.input += len;
@@ -6624,7 +6655,7 @@ static bool regmatch(uint8_t *scan, const proftime_T *tm, int *timed_out)
case RE_COMPOSING:
// Skip composing characters.
- while (utf_iscomposing(utf_ptr2char((char *)rex.input))) {
+ while (utf_iscomposing_legacy(utf_ptr2char((char *)rex.input))) {
rex.input += utf_ptr2len((char *)rex.input);
}
break;
@@ -10040,7 +10071,7 @@ static int nfa_regatom(void)
}
// When '.' is followed by a composing char ignore the dot, so that
// the composing char is matched here.
- if (c == Magic('.') && utf_iscomposing(peekchr())) {
+ if (c == Magic('.') && utf_iscomposing_legacy(peekchr())) {
old_regparse = (uint8_t *)regparse;
c = getchr();
goto nfa_do_multibyte;
@@ -10136,7 +10167,7 @@ static int nfa_regatom(void)
case 'e':
EMIT(NFA_ZEND);
rex.nfa_has_zend = true;
- if (!re_mult_next("\\zs")) {
+ if (!re_mult_next("\\ze")) {
return false;
}
break;
@@ -10675,7 +10706,7 @@ collection:
nfa_do_multibyte:
// plen is length of current char with composing chars
if (utf_char2len(c) != (plen = utfc_ptr2len((char *)old_regparse))
- || utf_iscomposing(c)) {
+ || utf_iscomposing_legacy(c)) {
int i = 0;
// A base character plus composing characters, or just one
@@ -11450,7 +11481,7 @@ static void nfa_set_code(int c)
}
if (addnl == true) {
- STRCAT(code, " + NEWLINE ");
+ strcat(code, " + NEWLINE ");
}
}
@@ -11494,7 +11525,7 @@ static void nfa_print_state(FILE *debugf, nfa_state_T *state)
garray_T indent;
ga_init(&indent, 1, 64);
- ga_append(&indent, '\0');
+ ga_append(&indent, NUL);
nfa_print_state2(debugf, state, &indent);
ga_clear(&indent);
}
@@ -13976,19 +14007,25 @@ static int skip_to_start(int c, colnr_T *colp)
static int find_match_text(colnr_T *startcol, int regstart, uint8_t *match_text)
{
colnr_T col = *startcol;
- const int regstart_len = utf_ptr2len((char *)rex.line + col);
+ const int regstart_len = utf_char2len(regstart);
while (true) {
bool match = true;
uint8_t *s1 = match_text;
- uint8_t *s2 = rex.line + col + regstart_len; // skip regstart
+ // skip regstart
+ int regstart_len2 = regstart_len;
+ if (regstart_len2 > 1 && utf_ptr2len((char *)rex.line + col) != regstart_len2) {
+ // because of case-folding of the previously matched text, we may need
+ // to skip fewer bytes than utf_char2len(regstart)
+ regstart_len2 = utf_char2len(utf_fold(regstart));
+ }
+ uint8_t *s2 = rex.line + col + regstart_len2;
while (*s1) {
int c1_len = utf_ptr2len((char *)s1);
int c1 = utf_ptr2char((char *)s1);
int c2_len = utf_ptr2len((char *)s2);
int c2 = utf_ptr2char((char *)s2);
- if ((c1 != c2 && (!rex.reg_ic || utf_fold(c1) != utf_fold(c2)))
- || c1_len != c2_len) {
+ if (c1 != c2 && (!rex.reg_ic || utf_fold(c1) != utf_fold(c2))) {
match = false;
break;
}
@@ -13997,7 +14034,7 @@ static int find_match_text(colnr_T *startcol, int regstart, uint8_t *match_text)
}
if (match
// check that no composing char follows
- && !utf_iscomposing(utf_ptr2char((char *)s2))) {
+ && !utf_iscomposing_legacy(utf_ptr2char((char *)s2))) {
cleanup_subexpr();
if (REG_MULTI) {
rex.reg_startpos[0].lnum = rex.lnum;
@@ -14242,7 +14279,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
// is not really a match.
if (!rex.reg_icombine
&& rex.input != rex.line
- && utf_iscomposing(curc)) {
+ && utf_iscomposing_legacy(curc)) {
break;
}
nfa_match = true;
@@ -14586,7 +14623,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
sta = t->state->out;
len = 0;
- if (utf_iscomposing(sta->c)) {
+ if (utf_iscomposing_legacy(sta->c)) {
// Only match composing character(s), ignore base
// character. Used for ".{composing}" and "{composing}"
// (no preceding character).
@@ -14688,7 +14725,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
int j;
sta = t->state->out->out;
- if (utf_iscomposing(sta->c)) {
+ if (utf_iscomposing_legacy(sta->c)) {
// Only match composing character(s), ignore base
// character. Used for ".{composing}" and "{composing}"
// (no preceding character).
@@ -14800,7 +14837,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
}
case NFA_ANY:
- // Any char except '\0', (end of input) does not match.
+ // Any char except NUL, (end of input) does not match.
if (curc > 0) {
add_state = t->state->out;
add_off = clen;
@@ -14810,7 +14847,7 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
case NFA_ANY_COMPOSING:
// On a composing character skip over it. Otherwise do
// nothing. Always matches.
- if (utf_iscomposing(curc)) {
+ if (utf_iscomposing_legacy(curc)) {
add_off = clen;
} else {
add_here = true;
@@ -15098,9 +15135,13 @@ static int nfa_regmatch(nfa_regprog_T *prog, nfa_state_T *start, regsubs_T *subm
result = col > t->state->val * ts;
}
if (!result) {
- int lts = win_linetabsize(wp, rex.reg_firstlnum + rex.lnum, (char *)rex.line, col);
+ linenr_T lnum = REG_MULTI ? rex.reg_firstlnum + rex.lnum : 1;
+ if (REG_MULTI && (lnum <= 0 || lnum > wp->w_buffer->b_ml.ml_line_count)) {
+ lnum = 1;
+ }
+ int vcol = win_linetabsize(wp, lnum, (char *)rex.line, col);
assert(t->state->val >= 0);
- result = nfa_re_num_cmp((uintmax_t)t->state->val, op, (uintmax_t)lts + 1);
+ result = nfa_re_num_cmp((uintmax_t)t->state->val, op, (uintmax_t)vcol + 1);
}
if (result) {
add_here = true;
@@ -15652,7 +15693,7 @@ static int nfa_regexec_both(uint8_t *line, colnr_T startcol, proftime_T *tm, int
// If match_text is set it contains the full text that must match.
// Nothing else to try. Doesn't handle combining chars well.
- if (prog->match_text != NULL && !rex.reg_icombine) {
+ if (prog->match_text != NULL && *prog->match_text != NUL && !rex.reg_icombine) {
retval = find_match_text(&col, prog->regstart, prog->match_text);
if (REG_MULTI) {
rex.reg_mmatch->rmm_matchcol = col;
diff --git a/src/nvim/runtime.c b/src/nvim/runtime.c
index d913d311db..030bda4fa5 100644
--- a/src/nvim/runtime.c
+++ b/src/nvim/runtime.c
@@ -22,6 +22,7 @@
#include "nvim/charset.h"
#include "nvim/cmdexpand.h"
#include "nvim/debugger.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/userfunc.h"
@@ -408,7 +409,7 @@ int do_in_path(const char *path, const char *prefix, char *name, int flags,
did_one = true;
} else if (buflen + 2 + strlen(prefix) + strlen(name) < MAXPATHL) {
add_pathsep(buf);
- STRCAT(buf, prefix);
+ strcat(buf, prefix);
tail = buf + strlen(buf);
// Loop over all patterns in "name"
@@ -1099,7 +1100,7 @@ static int load_pack_plugin(bool opt, char *fname)
// If runtime/filetype.lua wasn't loaded yet, the scripts will be
// found when it loads.
- if (opt && eval_to_number(cmd) > 0) {
+ if (opt && eval_to_number(cmd, false) > 0) {
do_cmdline_cmd("augroup filetypedetect");
vim_snprintf(pat, len, ftpat, ffname);
gen_expand_wildcards_and_cb(1, &pat, EW_FILE, true, source_callback_vim_lua, NULL);
@@ -1558,7 +1559,7 @@ static inline char *add_env_sep_dirs(char *dest, const char *const val, const ch
return dest;
}
const void *iter = NULL;
- const char *appname = get_appname();
+ const char *appname = get_appname(false);
const size_t appname_len = strlen(appname);
do {
size_t dir_len;
@@ -1625,7 +1626,7 @@ static inline char *add_dir(char *dest, const char *const dir, const size_t dir_
if (!after_pathsep(dest - 1, dest)) {
*dest++ = PATHSEP;
}
- const char *appname = get_appname();
+ const char *appname = get_appname(false);
size_t appname_len = strlen(appname);
assert(appname_len < (IOSIZE - sizeof("-data")));
xmemcpyz(IObuff, appname, appname_len);
@@ -1696,7 +1697,7 @@ char *runtimepath_default(bool clean_arg)
size_t config_len = 0;
size_t vimruntime_len = 0;
size_t libdir_len = 0;
- const char *appname = get_appname();
+ const char *appname = get_appname(false);
size_t appname_len = strlen(appname);
if (data_home != NULL) {
data_len = strlen(data_home);
@@ -2856,7 +2857,7 @@ bool script_autoload(const char *const name, const size_t name_len, const bool r
// Try loading the package from $VIMRUNTIME/autoload/<name>.vim
// Use "ret_sid" to avoid loading the same script again.
int ret_sid;
- if (do_in_runtimepath(scriptname, 0, source_callback, &ret_sid) == OK) {
+ if (do_in_runtimepath(scriptname, DIP_START, source_callback, &ret_sid) == OK) {
ret = true;
}
}
diff --git a/src/nvim/search.c b/src/nvim/search.c
index 746c253708..ff6e135df1 100644
--- a/src/nvim/search.c
+++ b/src/nvim/search.c
@@ -18,14 +18,18 @@
#include "nvim/cmdhist.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
+#include "nvim/eval/typval_defs.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
#include "nvim/ex_getln.h"
+#include "nvim/file_search.h"
#include "nvim/fileio.h"
#include "nvim/fold.h"
+#include "nvim/garray.h"
#include "nvim/getchar.h"
#include "nvim/gettext_defs.h"
#include "nvim/globals.h"
@@ -287,7 +291,7 @@ void restore_search_patterns(void)
static inline void free_spat(SearchPattern *const spat)
{
xfree(spat->pat);
- tv_dict_unref(spat->additional_data);
+ xfree(spat->additional_data);
}
#if defined(EXITFREE)
@@ -944,11 +948,9 @@ int searchit(win_T *win, buf_T *buf, pos_T *pos, pos_T *end_pos, Direction dir,
// This message is also remembered in keep_msg for when the screen
// is redrawn. The keep_msg is cleared whenever another message is
// written.
- if (dir == BACKWARD) { // start second loop at the other end
- lnum = buf->b_ml.ml_line_count;
- } else {
- lnum = 1;
- }
+ lnum = dir == BACKWARD // start second loop at the other end
+ ? buf->b_ml.ml_line_count
+ : 1;
if (!shortmess(SHM_SEARCH)
&& shortmess(SHM_SEARCHCOUNT)
&& (options & SEARCH_MSG)) {
@@ -1050,7 +1052,6 @@ static int first_submatch(regmmatch_T *rp)
int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen, int count,
int options, searchit_arg_T *sia)
{
- pos_T pos; // position of the last match
char *searchstr;
size_t searchstrlen;
int retval; // Return value
@@ -1075,7 +1076,8 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen
// (there is no "if ()" around this because gcc wants them initialized)
SearchOffset old_off = spats[0].off;
- pos = curwin->w_cursor; // start searching at the cursor position
+ pos_T pos = curwin->w_cursor; // Position of the last match.
+ // Start searching at the cursor position.
// Find out the direction of the search.
if (dirc == 0) {
@@ -1085,11 +1087,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen
set_vv_searchforward();
}
if (options & SEARCH_REV) {
- if (dirc == '/') {
- dirc = '?';
- } else {
- dirc = '/';
- }
+ dirc = dirc == '/' ? '?' : '/';
}
// If the cursor is in a closed fold, don't find another match in the same
@@ -1262,7 +1260,7 @@ int do_search(oparg_T *oap, int dirc, int search_delim, char *pat, size_t patlen
// empty for the search_stat feature.
if (!cmd_silent) {
msgbuf[0] = (char)dirc;
- if (utf_iscomposing(utf_ptr2char(p))) {
+ if (utf_iscomposing_first(utf_ptr2char(p))) {
// Use a space to draw the composing char on.
msgbuf[1] = ' ';
memmove(msgbuf + 2, p, plen);
@@ -1561,11 +1559,9 @@ int searchc(cmdarg_T *cap, bool t_cmd)
if (*lastc == NUL && lastc_bytelen <= 1) {
return FAIL;
}
- if (dir) { // repeat in opposite direction
- dir = -lastcdir;
- } else {
- dir = lastcdir;
- }
+ dir = dir // repeat in opposite direction
+ ? -lastcdir
+ : lastcdir;
t_cmd = last_t_cmd;
c = *lastc;
// For multi-byte re-use last lastc_bytes[] and lastc_bytelen.
@@ -1578,11 +1574,7 @@ int searchc(cmdarg_T *cap, bool t_cmd)
}
}
- if (dir == BACKWARD) {
- cap->oap->inclusive = false;
- } else {
- cap->oap->inclusive = true;
- }
+ cap->oap->inclusive = dir != BACKWARD;
char *p = get_cursor_line_ptr();
int col = curwin->w_cursor.col;
@@ -2429,17 +2421,13 @@ int current_search(int count, bool forward)
dec_cursor();
}
- pos_T end_pos; // end position of the pattern match
- pos_T orig_pos; // position of the cursor at beginning
- 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;
+ pos_T pos = curwin->w_cursor; // position after the pattern
+ pos_T orig_pos = curwin->w_cursor; // position of the cursor at beginning
if (VIsual_active) {
// Searching further will extend the match.
if (forward) {
@@ -2456,6 +2444,9 @@ int current_search(int count, bool forward)
return FAIL; // pattern not found
}
+ pos_T end_pos; // end position of the pattern match
+ int result; // result of various function calls
+
// 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.
@@ -3235,9 +3226,8 @@ static int fuzzy_match_item_compare(const void *const s1, const void *const s2)
if (v1 == v2) {
return idx1 == idx2 ? 0 : idx1 > idx2 ? 1 : -1;
- } else {
- return v1 > v2 ? -1 : 1;
}
+ return v1 > v2 ? -1 : 1;
}
/// Fuzzy search the string "str" in a list of "items" and return the matching
@@ -3511,9 +3501,8 @@ static int fuzzy_match_func_compare(const void *const s1, const void *const s2)
}
if (v1 == v2) {
return idx1 == idx2 ? 0 : idx1 > idx2 ? 1 : -1;
- } else {
- return v1 > v2 ? -1 : 1;
}
+ return v1 > v2 ? -1 : 1;
}
/// Sort fuzzy matches of function names by score.
@@ -3541,6 +3530,37 @@ int fuzzy_match_str(char *const str, const char *const pat)
return score;
}
+/// Fuzzy match the position of string "pat" in string "str".
+/// @returns a dynamic array of matching positions. If there is no match, returns NULL.
+garray_T *fuzzy_match_str_with_pos(char *const str, const char *const pat)
+{
+ if (str == NULL || pat == NULL) {
+ return NULL;
+ }
+
+ garray_T *match_positions = xmalloc(sizeof(garray_T));
+ ga_init(match_positions, sizeof(uint32_t), 10);
+
+ unsigned matches[MAX_FUZZY_MATCHES];
+ int score = 0;
+ if (!fuzzy_match(str, pat, false, &score, matches, MAX_FUZZY_MATCHES)
+ || score == 0) {
+ ga_clear(match_positions);
+ xfree(match_positions);
+ return NULL;
+ }
+
+ int j = 0;
+ for (const char *p = pat; *p != NUL; MB_PTR_ADV(p)) {
+ if (!ascii_iswhite(utf_ptr2char(p))) {
+ GA_APPEND(uint32_t, match_positions, matches[j]);
+ j++;
+ }
+ }
+
+ return match_positions;
+}
+
/// Copy a list of fuzzy matches into a string list after sorting the matches by
/// the fuzzy score. Frees the memory allocated for "fuzmatch".
void fuzzymatches_to_strmatches(fuzmatch_str_T *const fuzmatch, char ***const matches,
@@ -3670,13 +3690,8 @@ void find_pattern_in_path(char *ptr, Direction dir, size_t len, bool whole, bool
int old_files = max_path_depth;
int depth = depth_displayed = -1;
- linenr_T lnum = start_lnum;
- if (end_lnum > curbuf->b_ml.ml_line_count) {
- end_lnum = curbuf->b_ml.ml_line_count;
- }
- if (lnum > end_lnum) { // do at least one line
- lnum = end_lnum;
- }
+ end_lnum = MIN(end_lnum, curbuf->b_ml.ml_line_count);
+ linenr_T lnum = MIN(start_lnum, end_lnum); // do at least one line
char *line = get_line_and_copy(lnum, file_line);
while (true) {
@@ -4125,9 +4140,7 @@ exit_matched:
depth--;
curr_fname = (depth == -1) ? curbuf->b_fname
: files[depth].name;
- if (depth < depth_displayed) {
- depth_displayed = depth;
- }
+ depth_displayed = MIN(depth_displayed, depth);
}
if (depth >= 0) { // we could read the line
files[depth].lnum++;
@@ -4288,9 +4301,3 @@ bool search_was_last_used(void)
{
return last_idx == 0;
}
-
-/// @return true if 'hlsearch' highlight is currently in use.
-bool using_hlsearch(void)
-{
- return spats[last_idx].pat != NULL && p_hls && !no_hlsearch;
-}
diff --git a/src/nvim/search.h b/src/nvim/search.h
index 783756b781..1b6c1a6375 100644
--- a/src/nvim/search.h
+++ b/src/nvim/search.h
@@ -84,12 +84,12 @@ typedef struct {
/// Structure containing last search pattern and its attributes.
typedef struct {
char *pat; ///< The pattern (in allocated memory) or NULL.
- size_t patlen; ///< The length of the patten (0 is pat is NULL).
+ size_t patlen; ///< The length of the pattern (0 if pat is NULL).
bool magic; ///< Magicness of the pattern.
bool no_scs; ///< No smartcase for this pattern.
Timestamp timestamp; ///< Time of the last change.
SearchOffset off; ///< Pattern offset.
- dict_T *additional_data; ///< Additional data from ShaDa file.
+ AdditionalData *additional_data; ///< Additional data from ShaDa file.
} SearchPattern;
/// Optional extra arguments for searchit().
diff --git a/src/nvim/sha256.c b/src/nvim/sha256.c
index d49224a987..f892d93236 100644
--- a/src/nvim/sha256.c
+++ b/src/nvim/sha256.c
@@ -15,6 +15,7 @@
#include <stdio.h>
#include <string.h>
+#include "nvim/ascii_defs.h"
#include "nvim/memory.h"
#include "nvim/sha256.h"
@@ -222,18 +223,15 @@ static uint8_t sha256_padding[SHA256_BUFFER_SIZE] = {
void sha256_finish(context_sha256_T *ctx, uint8_t digest[SHA256_SUM_SIZE])
{
- uint32_t last, padn;
- uint32_t high, low;
- uint8_t msglen[8];
-
- high = (ctx->total[0] >> 29) | (ctx->total[1] << 3);
- low = (ctx->total[0] << 3);
+ uint32_t high = (ctx->total[0] >> 29) | (ctx->total[1] << 3);
+ uint32_t low = (ctx->total[0] << 3);
+ uint8_t msglen[8];
PUT_UINT32(high, msglen, 0);
PUT_UINT32(low, msglen, 4);
- last = ctx->total[0] & 0x3F;
- padn = (last < 56) ? (56 - last) : (120 - last);
+ uint32_t last = ctx->total[0] & 0x3F;
+ uint32_t padn = (last < 56) ? (56 - last) : (120 - last);
sha256_update(ctx, sha256_padding, padn);
sha256_update(ctx, msglen, 8);
@@ -262,24 +260,24 @@ void sha256_finish(context_sha256_T *ctx, uint8_t digest[SHA256_SUM_SIZE])
const char *sha256_bytes(const uint8_t *restrict buf, size_t buf_len, const uint8_t *restrict salt,
size_t salt_len)
{
- uint8_t sha256sum[SHA256_SUM_SIZE];
static char hexit[SHA256_BUFFER_SIZE + 1]; // buf size + NULL
- context_sha256_T ctx;
sha256_self_test();
+ context_sha256_T ctx;
sha256_start(&ctx);
sha256_update(&ctx, buf, buf_len);
if (salt != NULL) {
sha256_update(&ctx, salt, salt_len);
}
+ uint8_t sha256sum[SHA256_SUM_SIZE];
sha256_finish(&ctx, sha256sum);
for (size_t j = 0; j < SHA256_SUM_SIZE; j++) {
snprintf(hexit + j * SHA_STEP, SHA_STEP + 1, "%02x", sha256sum[j]);
}
- hexit[sizeof(hexit) - 1] = '\0';
+ hexit[sizeof(hexit) - 1] = NUL;
return hexit;
}
@@ -340,7 +338,7 @@ bool sha256_self_test(void)
if (memcmp(output, sha_self_test_vector[i], SHA256_BUFFER_SIZE) != 0) {
failures = true;
- output[sizeof(output) - 1] = '\0';
+ output[sizeof(output) - 1] = NUL;
// printf("sha256_self_test %d failed %s\n", i, output);
}
diff --git a/src/nvim/shada.c b/src/nvim/shada.c
index 34c36f850f..2b401fa106 100644
--- a/src/nvim/shada.c
+++ b/src/nvim/shada.c
@@ -1,9 +1,5 @@
#include <assert.h>
#include <inttypes.h>
-#include <msgpack/object.h>
-#include <msgpack/pack.h>
-#include <msgpack/sbuffer.h>
-#include <msgpack/unpack.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
@@ -13,12 +9,14 @@
#include <uv.h>
#include "auto/config.h"
+#include "nvim/api/keysets_defs.h"
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/ascii_defs.h"
#include "nvim/buffer.h"
#include "nvim/buffer_defs.h"
#include "nvim/cmdhist.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/decode.h"
#include "nvim/eval/encode.h"
@@ -41,6 +39,8 @@
#include "nvim/mbyte.h"
#include "nvim/memory.h"
#include "nvim/message.h"
+#include "nvim/msgpack_rpc/packer.h"
+#include "nvim/msgpack_rpc/unpacker.h"
#include "nvim/normal_defs.h"
#include "nvim/ops.h"
#include "nvim/option.h"
@@ -69,26 +69,26 @@
# include ENDIAN_INCLUDE_FILE
#endif
-#define SEARCH_KEY_MAGIC "sm"
-#define SEARCH_KEY_SMARTCASE "sc"
-#define SEARCH_KEY_HAS_LINE_OFFSET "sl"
-#define SEARCH_KEY_PLACE_CURSOR_AT_END "se"
-#define SEARCH_KEY_IS_LAST_USED "su"
-#define SEARCH_KEY_IS_SUBSTITUTE_PATTERN "ss"
-#define SEARCH_KEY_HIGHLIGHTED "sh"
-#define SEARCH_KEY_OFFSET "so"
-#define SEARCH_KEY_PAT "sp"
-#define SEARCH_KEY_BACKWARD "sb"
-
-#define REG_KEY_TYPE "rt"
-#define REG_KEY_WIDTH "rw"
-#define REG_KEY_CONTENTS "rc"
-#define REG_KEY_UNNAMED "ru"
-
-#define KEY_LNUM "l"
-#define KEY_COL "c"
-#define KEY_FILE "f"
-#define KEY_NAME_CHAR "n"
+#define SEARCH_KEY_MAGIC sm
+#define SEARCH_KEY_SMARTCASE sc
+#define SEARCH_KEY_HAS_LINE_OFFSET sl
+#define SEARCH_KEY_PLACE_CURSOR_AT_END se
+#define SEARCH_KEY_IS_LAST_USED su
+#define SEARCH_KEY_IS_SUBSTITUTE_PATTERN ss
+#define SEARCH_KEY_HIGHLIGHTED sh
+#define SEARCH_KEY_OFFSET so
+#define SEARCH_KEY_PAT sp
+#define SEARCH_KEY_BACKWARD sb
+
+#define REG_KEY_TYPE rt
+#define REG_KEY_WIDTH rw
+#define REG_KEY_CONTENTS rc
+#define REG_KEY_UNNAMED ru
+
+#define KEY_LNUM l
+#define KEY_COL c
+#define KEY_FILE f
+#define KEY_NAME_CHAR n
// Error messages formerly used by viminfo code:
// E136: viminfo: Too many errors, skipping rest of file
@@ -231,31 +231,17 @@ typedef struct {
ShadaEntryType type;
Timestamp timestamp;
union {
- Dictionary header;
+ Dict header;
struct shada_filemark {
char name;
pos_T mark;
char *fname;
- dict_T *additional_data;
} filemark;
- struct search_pattern {
- bool magic;
- bool smartcase;
- bool has_line_offset;
- bool place_cursor_at_end;
- int64_t offset;
- bool is_last_used;
- bool is_substitute_pattern;
- bool highlighted;
- bool search_backward;
- char *pat;
- dict_T *additional_data;
- } search_pattern;
+ Dict(_shada_search_pat) search_pattern;
struct history_item {
uint8_t histtype;
char *string;
char sep;
- list_T *additional_elements;
} history_item;
struct reg { // yankreg_T
char name;
@@ -264,12 +250,10 @@ typedef struct {
bool is_unnamed;
size_t contents_size;
size_t width;
- dict_T *additional_data;
} reg;
struct global_var {
char *name;
typval_T value;
- list_T *additional_elements;
} global_var;
struct {
uint64_t type;
@@ -278,17 +262,17 @@ typedef struct {
} unknown_item;
struct sub_string {
char *sub;
- list_T *additional_elements;
} sub_string;
struct buffer_list {
size_t size;
struct buffer_list_buffer {
pos_T pos;
char *fname;
- dict_T *additional_data;
+ AdditionalData *additional_data;
} *buffers;
} buffer_list;
} data;
+ AdditionalData *additional_data;
} ShadaEntry;
/// One entry in sized linked list
@@ -356,35 +340,6 @@ typedef struct {
PMap(cstr_t) file_marks; ///< All file marks.
} WriteMergerState;
-typedef struct sd_read_def ShaDaReadDef;
-
-/// Function used to close files defined by ShaDaReadDef
-typedef void (*ShaDaReadCloser)(ShaDaReadDef *const sd_reader)
- REAL_FATTR_NONNULL_ALL;
-
-/// Function used to read ShaDa files
-typedef ptrdiff_t (*ShaDaFileReader)(ShaDaReadDef *const sd_reader,
- void *const dest,
- const size_t size)
- REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT;
-
-/// Function used to skip in ShaDa files
-typedef int (*ShaDaFileSkipper)(ShaDaReadDef *const sd_reader,
- const size_t offset)
- REAL_FATTR_NONNULL_ALL REAL_FATTR_WARN_UNUSED_RESULT;
-
-/// Structure containing necessary pointers for reading ShaDa files
-struct sd_read_def {
- ShaDaFileReader read; ///< Reader function.
- ShaDaReadCloser close; ///< Close function.
- ShaDaFileSkipper skip; ///< Function used to skip some bytes.
- void *cookie; ///< Data describing object read from.
- bool eof; ///< True if reader reached end of file.
- const char *error; ///< Error message in case of error.
- uintmax_t fpos; ///< Current position (amount of bytes read since
- ///< reader structure initialization). May overflow.
-};
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "shada.c.generated.h"
#endif
@@ -393,6 +348,7 @@ struct sd_read_def {
[kSDItem##name] = { \
.timestamp = 0, \
.type = kSDItem##name, \
+ .additional_data = NULL, \
.data = { \
.attr = { __VA_ARGS__ } \
} \
@@ -412,49 +368,41 @@ static const ShadaEntry sd_default_values[] = {
.is_substitute_pattern = false,
.highlighted = false,
.search_backward = false,
- .pat = NULL,
- .additional_data = NULL),
- DEF_SDE(SubString, sub_string, .sub = NULL, .additional_elements = NULL),
+ .pat = STRING_INIT),
+ DEF_SDE(SubString, sub_string, .sub = NULL),
DEF_SDE(HistoryEntry, history_item,
.histtype = HIST_CMD,
.string = NULL,
- .sep = NUL,
- .additional_elements = NULL),
+ .sep = NUL),
DEF_SDE(Register, reg,
.name = NUL,
.type = kMTCharWise,
.contents = NULL,
.contents_size = 0,
.is_unnamed = false,
- .width = 0,
- .additional_data = NULL),
+ .width = 0),
DEF_SDE(Variable, global_var,
.name = NULL,
- .value = { .v_type = VAR_UNKNOWN, .vval = { .v_string = NULL } },
- .additional_elements = NULL),
+ .value = { .v_type = VAR_UNKNOWN, .vval = { .v_string = NULL } }),
DEF_SDE(GlobalMark, filemark,
.name = '"',
.mark = DEFAULT_POS,
- .fname = NULL,
- .additional_data = NULL),
+ .fname = NULL),
DEF_SDE(Jump, filemark,
.name = NUL,
.mark = DEFAULT_POS,
- .fname = NULL,
- .additional_data = NULL),
+ .fname = NULL),
DEF_SDE(BufferList, buffer_list,
.size = 0,
.buffers = NULL),
DEF_SDE(LocalMark, filemark,
.name = '"',
.mark = DEFAULT_POS,
- .fname = NULL,
- .additional_data = NULL),
+ .fname = NULL),
DEF_SDE(Change, filemark,
.name = NUL,
.mark = DEFAULT_POS,
- .fname = NULL,
- .additional_data = NULL),
+ .fname = NULL),
};
#undef DEFAULT_POS
#undef DEF_SDE
@@ -587,70 +535,6 @@ static inline void hmll_dealloc(HMLList *const hmll)
xfree(hmll->entries);
}
-/// Wrapper for reading from file descriptors
-///
-/// @return -1 or number of bytes read.
-static ptrdiff_t read_file(ShaDaReadDef *const sd_reader, void *const dest, const size_t size)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
-{
- const ptrdiff_t ret = file_read(sd_reader->cookie, dest, size);
- sd_reader->eof = file_eof(sd_reader->cookie);
- if (ret < 0) {
- sd_reader->error = os_strerror((int)ret);
- return -1;
- }
- sd_reader->fpos += (size_t)ret;
- return ret;
-}
-
-/// Read one character
-static int read_char(ShaDaReadDef *const sd_reader)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
-{
- uint8_t ret;
- ptrdiff_t read_bytes = sd_reader->read(sd_reader, &ret, 1);
- if (read_bytes != 1) {
- return EOF;
- }
- return (int)ret;
-}
-
-/// Wrapper for closing file descriptors opened for reading
-static void close_sd_reader(ShaDaReadDef *const sd_reader)
- FUNC_ATTR_NONNULL_ALL
-{
- close_file(sd_reader->cookie);
- xfree(sd_reader->cookie);
-}
-
-/// Wrapper for read that reads to IObuff and ignores bytes read
-///
-/// Used for skipping.
-///
-/// @param[in,out] sd_reader File read.
-/// @param[in] offset Amount of bytes to skip.
-///
-/// @return FAIL in case of failure, OK in case of success. May set
-/// sd_reader->eof or sd_reader->error.
-static int sd_reader_skip_read(ShaDaReadDef *const sd_reader, const size_t offset)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
-{
- const ptrdiff_t skip_bytes = file_skip(sd_reader->cookie, offset);
- if (skip_bytes < 0) {
- sd_reader->error = os_strerror((int)skip_bytes);
- return FAIL;
- } else if (skip_bytes != (ptrdiff_t)offset) {
- assert(skip_bytes < (ptrdiff_t)offset);
- sd_reader->eof = file_eof(sd_reader->cookie);
- if (!sd_reader->eof) {
- sd_reader->error = _("too few bytes read");
- }
- return FAIL;
- }
- sd_reader->fpos += (size_t)skip_bytes;
- return OK;
-}
-
/// Wrapper for read that can be used when lseek cannot be used
///
/// E.g. when trying to read from a pipe.
@@ -660,55 +544,30 @@ static int sd_reader_skip_read(ShaDaReadDef *const sd_reader, const size_t offse
///
/// @return kSDReadStatusReadError, kSDReadStatusNotShaDa or
/// kSDReadStatusSuccess.
-static ShaDaReadResult sd_reader_skip(ShaDaReadDef *const sd_reader, const size_t offset)
+static ShaDaReadResult sd_reader_skip(FileDescriptor *const sd_reader, const size_t offset)
FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
{
- if (sd_reader->skip(sd_reader, offset) != OK) {
- if (sd_reader->error != NULL) {
- semsg(_(SERR "System error while skipping in ShaDa file: %s"),
- sd_reader->error);
- return kSDReadStatusReadError;
- } else if (sd_reader->eof) {
+ const ptrdiff_t skip_bytes = file_skip(sd_reader, offset);
+ if (skip_bytes < 0) {
+ semsg(_(SERR "System error while skipping in ShaDa file: %s"),
+ os_strerror((int)skip_bytes));
+ return kSDReadStatusReadError;
+ } else if (skip_bytes != (ptrdiff_t)offset) {
+ assert(skip_bytes < (ptrdiff_t)offset);
+ if (file_eof(sd_reader)) {
semsg(_(RCERR "Error while reading ShaDa file: "
"last entry specified that it occupies %" PRIu64 " bytes, "
"but file ended earlier"),
(uint64_t)offset);
- return kSDReadStatusNotShaDa;
+ } else {
+ semsg(_(SERR "System error while skipping in ShaDa file: %s"),
+ _("too few bytes read"));
}
- abort();
+ return kSDReadStatusNotShaDa;
}
return kSDReadStatusSuccess;
}
-/// Open ShaDa file for reading
-///
-/// @param[in] fname File name to open.
-/// @param[out] sd_reader Location where reader structure will be saved.
-///
-/// @return libuv error in case of error, 0 otherwise.
-static int open_shada_file_for_reading(const char *const fname, ShaDaReadDef *sd_reader)
- FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ALL
-{
- *sd_reader = (ShaDaReadDef) {
- .read = &read_file,
- .close = &close_sd_reader,
- .skip = &sd_reader_skip_read,
- .error = NULL,
- .eof = false,
- .fpos = 0,
- .cookie = xmalloc(sizeof(FileDescriptor)),
- };
- int error = file_open(sd_reader->cookie, fname, kFileReadOnly, 0);
- if (error) {
- XFREE_CLEAR(sd_reader->cookie);
- return error;
- }
-
- assert(strcmp(p_enc, "utf-8") == 0);
-
- return 0;
-}
-
/// Wrapper for closing file descriptors
static void close_file(FileDescriptor *cookie)
{
@@ -719,20 +578,6 @@ static void close_file(FileDescriptor *cookie)
}
}
-/// Msgpack callback for writing to FileDescriptor*
-static int msgpack_sd_writer_write(void *data, const char *buf, size_t len)
-{
- FileDescriptor *const sd_writer = (FileDescriptor *)data;
- const ptrdiff_t ret = file_write(sd_writer, buf, len);
- if (ret < 0) {
- semsg(_(SERR "System error while writing ShaDa file: %s"),
- os_strerror((int)ret));
- return -1;
- }
-
- return 0;
-}
-
/// Check whether writing to shada file was disabled ("-i NONE" or "--clean").
///
/// @return true if it was disabled, false otherwise.
@@ -757,8 +602,8 @@ static int shada_read_file(const char *const file, const int flags)
char *const fname = shada_filename(file);
- ShaDaReadDef sd_reader;
- const int of_ret = open_shada_file_for_reading(fname, &sd_reader);
+ FileDescriptor sd_reader;
+ int of_ret = file_open(&sd_reader, fname, kFileReadOnly, 0);
if (p_verbose > 1) {
verbose_enter();
@@ -782,7 +627,7 @@ static int shada_read_file(const char *const file, const int flags)
xfree(fname);
shada_read(&sd_reader, flags);
- sd_reader.close(&sd_reader);
+ close_file(&sd_reader);
return OK;
}
@@ -815,9 +660,9 @@ static const void *shada_hist_iter(const void *const iter, const uint8_t history
.sep = (char)(history_type == HIST_SEARCH
? hist_he.hisstr[strlen(hist_he.hisstr) + 1]
: 0),
- .additional_elements = hist_he.additional_elements,
}
- }
+ },
+ .additional_data = hist_he.additional_data,
};
}
return ret;
@@ -939,8 +784,7 @@ static inline void hms_to_he_array(const HistoryMergerState *const hms_p,
hist->timestamp = cur_entry->data.timestamp;
hist->hisnum = (int)(hist - hist_array) + 1;
hist->hisstr = cur_entry->data.data.history_item.string;
- hist->additional_elements =
- cur_entry->data.data.history_item.additional_elements;
+ hist->additional_data = cur_entry->data.additional_data;
hist++;
})
*new_hisnum = (int)(hist - hist_array);
@@ -968,7 +812,7 @@ static inline void hms_dealloc(HistoryMergerState *const hms_p)
/// Iterate over global variables
///
-/// @warning No modifications to global variable dictionary must be performed
+/// @warning No modifications to global variable Dict must be performed
/// while iteration is in progress.
///
/// @param[in] iter Iterator. Pass NULL to start iteration.
@@ -1094,7 +938,7 @@ static inline bool marks_equal(const pos_T a, const pos_T b)
///
/// @param[in] sd_reader Structure containing file reader definition.
/// @param[in] flags What to read, see ShaDaReadFileFlags enum.
-static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
+static void shada_read(FileDescriptor *const sd_reader, const int flags)
FUNC_ATTR_NONNULL_ALL
{
list_T *oldfiles_list = get_vim_var_list(VV_OLDFILES);
@@ -1187,9 +1031,9 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
.end = cur_entry.data.search_pattern.place_cursor_at_end,
.off = cur_entry.data.search_pattern.offset,
},
- .pat = cur_entry.data.search_pattern.pat,
- .patlen = strlen(cur_entry.data.search_pattern.pat),
- .additional_data = cur_entry.data.search_pattern.additional_data,
+ .pat = cur_entry.data.search_pattern.pat.data,
+ .patlen = cur_entry.data.search_pattern.pat.size,
+ .additional_data = cur_entry.additional_data,
.timestamp = cur_entry.timestamp,
};
@@ -1217,7 +1061,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
sub_set_replacement((SubReplacementString) {
.sub = cur_entry.data.sub_string.sub,
.timestamp = cur_entry.timestamp,
- .additional_elements = cur_entry.data.sub_string.additional_elements,
+ .additional_data = cur_entry.additional_data,
});
// Without using regtilde and without / &cpo flag previous substitute
// string is close to useless: you can only use it with :& or :~ and
@@ -1255,7 +1099,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
.y_type = cur_entry.data.reg.type,
.y_width = (colnr_T)cur_entry.data.reg.width,
.timestamp = cur_entry.timestamp,
- .additional_data = cur_entry.data.reg.additional_data,
+ .additional_data = cur_entry.additional_data,
}, cur_entry.data.reg.is_unnamed)) {
shada_free_shada_entry(&cur_entry);
}
@@ -1280,7 +1124,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
.fnum = (buf == NULL ? 0 : buf->b_fnum),
.timestamp = cur_entry.timestamp,
.view = INIT_FMARKV,
- .additional_data = cur_entry.data.filemark.additional_data,
+ .additional_data = cur_entry.additional_data,
},
};
if (cur_entry.type == kSDItemGlobalMark) {
@@ -1322,8 +1166,9 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
cur_entry.data.buffer_list.buffers[i].pos, 0, view);
buflist_setfpos(buf, curwin, buf->b_last_cursor.mark.lnum,
buf->b_last_cursor.mark.col, false);
- buf->additional_data =
- cur_entry.data.buffer_list.buffers[i].additional_data;
+
+ xfree(buf->additional_data);
+ buf->additional_data = cur_entry.data.buffer_list.buffers[i].additional_data;
cur_entry.data.buffer_list.buffers[i].additional_data = NULL;
}
}
@@ -1360,7 +1205,7 @@ static void shada_read(ShaDaReadDef *const sd_reader, const int flags)
.fnum = 0,
.timestamp = cur_entry.timestamp,
.view = INIT_FMARKV,
- .additional_data = cur_entry.data.filemark.additional_data,
+ .additional_data = cur_entry.additional_data,
};
if (cur_entry.type == kSDItemLocalMark) {
if (!mark_set_local(cur_entry.data.filemark.name, buf, fm, !force)) {
@@ -1468,19 +1313,30 @@ static char *shada_filename(const char *file)
return xstrdup(file);
}
-#define PACK_STATIC_STR(s) \
- do { \
- msgpack_pack_str(spacker, sizeof(s) - 1); \
- msgpack_pack_str_body(spacker, s, sizeof(s) - 1); \
- } while (0)
-#define PACK_BIN(s) \
- do { \
- const String s_ = (s); \
- msgpack_pack_bin(spacker, s_.size); \
- if (s_.size > 0) { \
- msgpack_pack_bin_body(spacker, s_.data, s_.size); \
- } \
- } while (0)
+#define KEY_NAME_(s) #s
+#define PACK_KEY(s) mpack_str(STATIC_CSTR_AS_STRING(KEY_NAME_(s)), &sbuf);
+#define KEY_NAME(s) KEY_NAME_(s)
+
+#define SHADA_MPACK_FREE_SPACE (4 * MPACK_ITEM_SIZE)
+
+static void shada_check_buffer(PackerBuffer *packer)
+{
+ if (mpack_remaining(packer) < SHADA_MPACK_FREE_SPACE) {
+ packer->packer_flush(packer);
+ }
+}
+
+static uint32_t additional_data_len(AdditionalData *src)
+{
+ return src ? src->nitems : 0;
+}
+
+static void dump_additional_data(AdditionalData *src, PackerBuffer *sbuf)
+{
+ if (src != NULL) {
+ mpack_raw(src->data, src->nbytes, sbuf);
+ }
+}
/// Write single ShaDa entry
///
@@ -1490,145 +1346,93 @@ static char *shada_filename(const char *file)
/// restrictions.
///
/// @return kSDWriteSuccessful, kSDWriteFailed or kSDWriteIgnError.
-static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntry entry,
+static ShaDaWriteResult shada_pack_entry(PackerBuffer *const packer, ShadaEntry entry,
const size_t max_kbyte)
FUNC_ATTR_NONNULL_ALL
{
ShaDaWriteResult ret = kSDWriteFailed;
- msgpack_sbuffer sbuf;
- msgpack_sbuffer_init(&sbuf);
- msgpack_packer *spacker = msgpack_packer_new(&sbuf, &msgpack_sbuffer_write);
-#define DUMP_ADDITIONAL_ELEMENTS(src, what) \
- do { \
- if ((src) != NULL) { \
- TV_LIST_ITER((src), li, { \
- if (encode_vim_to_msgpack(spacker, TV_LIST_ITEM_TV(li), \
- _("additional elements of ShaDa " what)) \
- == FAIL) { \
- goto shada_pack_entry_error; \
- } \
- }); \
- } \
- } while (0)
-#define DUMP_ADDITIONAL_DATA(src, what) \
- do { \
- dict_T *const d = (src); \
- if (d != NULL) { \
- size_t todo = d->dv_hashtab.ht_used; \
- for (const hashitem_T *hi = d->dv_hashtab.ht_array; todo; hi++) { \
- if (!HASHITEM_EMPTY(hi)) { \
- todo--; \
- dictitem_T *const di = TV_DICT_HI2DI(hi); \
- const size_t key_len = strlen(hi->hi_key); \
- msgpack_pack_str(spacker, key_len); \
- msgpack_pack_str_body(spacker, hi->hi_key, key_len); \
- if (encode_vim_to_msgpack(spacker, &di->di_tv, \
- _("additional data of ShaDa " what)) \
- == FAIL) { \
- goto shada_pack_entry_error; \
- } \
- } \
- } \
- } \
- } while (0)
+ PackerBuffer sbuf = packer_string_buffer();
+
#define CHECK_DEFAULT(entry, attr) \
(sd_default_values[(entry).type].data.attr == (entry).data.attr)
#define ONE_IF_NOT_DEFAULT(entry, attr) \
- ((size_t)(!CHECK_DEFAULT(entry, attr)))
+ ((uint32_t)(!CHECK_DEFAULT(entry, attr)))
#define PACK_BOOL(entry, name, attr) \
do { \
if (!CHECK_DEFAULT(entry, search_pattern.attr)) { \
- PACK_STATIC_STR(name); \
- if (sd_default_values[(entry).type].data.search_pattern.attr) { \
- msgpack_pack_false(spacker); \
- } else { \
- msgpack_pack_true(spacker); \
- } \
+ PACK_KEY(name); \
+ mpack_bool(&sbuf.ptr, !sd_default_values[(entry).type].data.search_pattern.attr); \
} \
} while (0)
+
+ shada_check_buffer(&sbuf);
switch (entry.type) {
case kSDItemMissing:
abort();
case kSDItemUnknown:
- if (spacker->callback(spacker->data, entry.data.unknown_item.contents,
- (unsigned)entry.data.unknown_item.size) == -1) {
- goto shada_pack_entry_error;
- }
+ mpack_raw(entry.data.unknown_item.contents, entry.data.unknown_item.size, &sbuf);
break;
case kSDItemHistoryEntry: {
const bool is_hist_search =
entry.data.history_item.histtype == HIST_SEARCH;
- const size_t arr_size = 2 + (size_t)is_hist_search + (size_t)(
- tv_list_len(entry.data.
- history_item.
- additional_elements));
- msgpack_pack_array(spacker, arr_size);
- msgpack_pack_uint8(spacker, entry.data.history_item.histtype);
- PACK_BIN(cstr_as_string(entry.data.history_item.string));
+ uint32_t arr_size = (2 + (uint32_t)is_hist_search
+ + additional_data_len(entry.additional_data));
+ mpack_array(&sbuf.ptr, arr_size);
+ mpack_uint(&sbuf.ptr, entry.data.history_item.histtype);
+ mpack_bin(cstr_as_string(entry.data.history_item.string), &sbuf);
if (is_hist_search) {
- msgpack_pack_uint8(spacker, (uint8_t)entry.data.history_item.sep);
+ mpack_uint(&sbuf.ptr, (uint8_t)entry.data.history_item.sep);
}
- DUMP_ADDITIONAL_ELEMENTS(entry.data.history_item.additional_elements,
- "history entry item");
+ dump_additional_data(entry.additional_data, &sbuf);
break;
}
case kSDItemVariable: {
- if (entry.data.global_var.value.v_type == VAR_BLOB) {
- // Strings and Blobs both pack as msgpack BINs; differentiate them by
- // storing an additional VAR_TYPE_BLOB element alongside Blobs
- list_T *const list = tv_list_alloc(1);
- tv_list_append_number(list, VAR_TYPE_BLOB);
- entry.data.global_var.additional_elements = list;
- }
- const size_t arr_size = 2 + (size_t)(tv_list_len(entry.data.global_var.additional_elements));
- msgpack_pack_array(spacker, arr_size);
+ bool is_blob = (entry.data.global_var.value.v_type == VAR_BLOB);
+ uint32_t arr_size = 2 + (is_blob ? 1 : 0) + additional_data_len(entry.additional_data);
+ mpack_array(&sbuf.ptr, arr_size);
const String varname = cstr_as_string(entry.data.global_var.name);
- PACK_BIN(varname);
+ mpack_bin(varname, &sbuf);
char vardesc[256] = "variable g:";
memcpy(&vardesc[sizeof("variable g:") - 1], varname.data,
varname.size + 1);
- if (encode_vim_to_msgpack(spacker, &entry.data.global_var.value, vardesc)
+ if (encode_vim_to_msgpack(&sbuf, &entry.data.global_var.value, vardesc)
== FAIL) {
ret = kSDWriteIgnError;
semsg(_(WERR "Failed to write variable %s"),
entry.data.global_var.name);
goto shada_pack_entry_error;
}
- DUMP_ADDITIONAL_ELEMENTS(entry.data.global_var.additional_elements,
- "variable item");
+ if (is_blob) {
+ mpack_check_buffer(&sbuf);
+ mpack_integer(&sbuf.ptr, VAR_TYPE_BLOB);
+ }
+ dump_additional_data(entry.additional_data, &sbuf);
break;
}
case kSDItemSubString: {
- const size_t arr_size = 1 + (size_t)(
- tv_list_len(entry.data.sub_string.additional_elements));
- msgpack_pack_array(spacker, arr_size);
- PACK_BIN(cstr_as_string(entry.data.sub_string.sub));
- DUMP_ADDITIONAL_ELEMENTS(entry.data.sub_string.additional_elements,
- "sub string item");
+ uint32_t arr_size = 1 + additional_data_len(entry.additional_data);
+ mpack_array(&sbuf.ptr, arr_size);
+ mpack_bin(cstr_as_string(entry.data.sub_string.sub), &sbuf);
+ dump_additional_data(entry.additional_data, &sbuf);
break;
}
case kSDItemSearchPattern: {
- size_t entry_map_size = (
- 1 // Search pattern is always present
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.magic)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.is_last_used)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.smartcase)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.has_line_offset)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.place_cursor_at_end)
- + ONE_IF_NOT_DEFAULT(entry,
- search_pattern.is_substitute_pattern)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.highlighted)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.offset)
- + ONE_IF_NOT_DEFAULT(entry, search_pattern.search_backward)
- // finally, additional data:
- + (
- entry.data.search_pattern.additional_data
- ? entry.data.search_pattern.additional_data->dv_hashtab.ht_used
- : 0));
- msgpack_pack_map(spacker, entry_map_size);
- PACK_STATIC_STR(SEARCH_KEY_PAT);
- PACK_BIN(cstr_as_string(entry.data.search_pattern.pat));
+ uint32_t entry_map_size = (1 // Search pattern is always present
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.magic)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.is_last_used)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.smartcase)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.has_line_offset)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.place_cursor_at_end)
+ + ONE_IF_NOT_DEFAULT(entry,
+ search_pattern.is_substitute_pattern)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.highlighted)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.offset)
+ + ONE_IF_NOT_DEFAULT(entry, search_pattern.search_backward)
+ + additional_data_len(entry.additional_data));
+ mpack_map(&sbuf.ptr, entry_map_size);
+ PACK_KEY(SEARCH_KEY_PAT);
+ mpack_bin(entry.data.search_pattern.pat, &sbuf);
PACK_BOOL(entry, SEARCH_KEY_MAGIC, magic);
PACK_BOOL(entry, SEARCH_KEY_IS_LAST_USED, is_last_used);
PACK_BOOL(entry, SEARCH_KEY_SMARTCASE, smartcase);
@@ -1638,132 +1442,108 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr
PACK_BOOL(entry, SEARCH_KEY_HIGHLIGHTED, highlighted);
PACK_BOOL(entry, SEARCH_KEY_BACKWARD, search_backward);
if (!CHECK_DEFAULT(entry, search_pattern.offset)) {
- PACK_STATIC_STR(SEARCH_KEY_OFFSET);
- msgpack_pack_int64(spacker, entry.data.search_pattern.offset);
+ PACK_KEY(SEARCH_KEY_OFFSET);
+ mpack_integer(&sbuf.ptr, entry.data.search_pattern.offset);
}
#undef PACK_BOOL
- DUMP_ADDITIONAL_DATA(entry.data.search_pattern.additional_data,
- "search pattern item");
+ dump_additional_data(entry.additional_data, &sbuf);
break;
}
case kSDItemChange:
case kSDItemGlobalMark:
case kSDItemLocalMark:
case kSDItemJump: {
- size_t entry_map_size = (
- 1 // File name
+ size_t entry_map_size = (1 // File name
+ ONE_IF_NOT_DEFAULT(entry, filemark.mark.lnum)
+ ONE_IF_NOT_DEFAULT(entry, filemark.mark.col)
+ ONE_IF_NOT_DEFAULT(entry, filemark.name)
- // Additional entries, if any:
- + (
- entry.data.filemark.additional_data == NULL
- ? 0
- : entry.data.filemark.additional_data->dv_hashtab.ht_used));
- msgpack_pack_map(spacker, entry_map_size);
- PACK_STATIC_STR(KEY_FILE);
- PACK_BIN(cstr_as_string(entry.data.filemark.fname));
+ + additional_data_len(entry.additional_data));
+ mpack_map(&sbuf.ptr, (uint32_t)entry_map_size);
+ PACK_KEY(KEY_FILE);
+ mpack_bin(cstr_as_string(entry.data.filemark.fname), &sbuf);
if (!CHECK_DEFAULT(entry, filemark.mark.lnum)) {
- PACK_STATIC_STR(KEY_LNUM);
- msgpack_pack_long(spacker, entry.data.filemark.mark.lnum);
+ PACK_KEY(KEY_LNUM);
+ mpack_integer(&sbuf.ptr, entry.data.filemark.mark.lnum);
}
if (!CHECK_DEFAULT(entry, filemark.mark.col)) {
- PACK_STATIC_STR(KEY_COL);
- msgpack_pack_long(spacker, entry.data.filemark.mark.col);
+ PACK_KEY(KEY_COL);
+ mpack_integer(&sbuf.ptr, entry.data.filemark.mark.col);
}
assert(entry.type == kSDItemJump || entry.type == kSDItemChange
? CHECK_DEFAULT(entry, filemark.name)
: true);
if (!CHECK_DEFAULT(entry, filemark.name)) {
- PACK_STATIC_STR(KEY_NAME_CHAR);
- msgpack_pack_uint8(spacker, (uint8_t)entry.data.filemark.name);
+ PACK_KEY(KEY_NAME_CHAR);
+ mpack_uint(&sbuf.ptr, (uint8_t)entry.data.filemark.name);
}
- DUMP_ADDITIONAL_DATA(entry.data.filemark.additional_data,
- "mark (change, jump, global or local) item");
+ dump_additional_data(entry.additional_data, &sbuf);
break;
}
case kSDItemRegister: {
- size_t entry_map_size = (2 // Register contents and name
- + ONE_IF_NOT_DEFAULT(entry, reg.type)
- + ONE_IF_NOT_DEFAULT(entry, reg.width)
- + ONE_IF_NOT_DEFAULT(entry, reg.is_unnamed)
- // Additional entries, if any:
- + (entry.data.reg.additional_data == NULL
- ? 0
- : entry.data.reg.additional_data->dv_hashtab.ht_used));
- msgpack_pack_map(spacker, entry_map_size);
- PACK_STATIC_STR(REG_KEY_CONTENTS);
- msgpack_pack_array(spacker, entry.data.reg.contents_size);
+ uint32_t entry_map_size = (2 // Register contents and name
+ + ONE_IF_NOT_DEFAULT(entry, reg.type)
+ + ONE_IF_NOT_DEFAULT(entry, reg.width)
+ + ONE_IF_NOT_DEFAULT(entry, reg.is_unnamed)
+ + additional_data_len(entry.additional_data));
+
+ mpack_map(&sbuf.ptr, entry_map_size);
+ PACK_KEY(REG_KEY_CONTENTS);
+ mpack_array(&sbuf.ptr, (uint32_t)entry.data.reg.contents_size);
for (size_t i = 0; i < entry.data.reg.contents_size; i++) {
- PACK_BIN(cstr_as_string(entry.data.reg.contents[i]));
+ mpack_bin(cstr_as_string(entry.data.reg.contents[i]), &sbuf);
}
- PACK_STATIC_STR(KEY_NAME_CHAR);
- msgpack_pack_char(spacker, entry.data.reg.name);
+ PACK_KEY(KEY_NAME_CHAR);
+ mpack_uint(&sbuf.ptr, (uint8_t)entry.data.reg.name);
if (!CHECK_DEFAULT(entry, reg.type)) {
- PACK_STATIC_STR(REG_KEY_TYPE);
- msgpack_pack_uint8(spacker, (uint8_t)entry.data.reg.type);
+ PACK_KEY(REG_KEY_TYPE);
+ mpack_uint(&sbuf.ptr, (uint8_t)entry.data.reg.type);
}
if (!CHECK_DEFAULT(entry, reg.width)) {
- PACK_STATIC_STR(REG_KEY_WIDTH);
- msgpack_pack_uint64(spacker, (uint64_t)entry.data.reg.width);
+ PACK_KEY(REG_KEY_WIDTH);
+ mpack_uint64(&sbuf.ptr, (uint64_t)entry.data.reg.width);
}
if (!CHECK_DEFAULT(entry, reg.is_unnamed)) {
- PACK_STATIC_STR(REG_KEY_UNNAMED);
- if (entry.data.reg.is_unnamed) {
- msgpack_pack_true(spacker);
- } else {
- msgpack_pack_false(spacker);
- }
+ PACK_KEY(REG_KEY_UNNAMED);
+ mpack_bool(&sbuf.ptr, entry.data.reg.is_unnamed);
}
- DUMP_ADDITIONAL_DATA(entry.data.reg.additional_data, "register item");
+ dump_additional_data(entry.additional_data, &sbuf);
break;
}
case kSDItemBufferList:
- msgpack_pack_array(spacker, entry.data.buffer_list.size);
+ mpack_array(&sbuf.ptr, (uint32_t)entry.data.buffer_list.size);
for (size_t i = 0; i < entry.data.buffer_list.size; i++) {
- size_t entry_map_size = (
- 1 // Buffer name
+ size_t entry_map_size = (1 // Buffer name
+ (size_t)(entry.data.buffer_list.buffers[i].pos.lnum
!= default_pos.lnum)
+ (size_t)(entry.data.buffer_list.buffers[i].pos.col
!= default_pos.col)
- // Additional entries, if any:
- + (
- entry.data.buffer_list.buffers[i].additional_data
- == NULL
- ? 0
- : (entry.data.buffer_list.buffers[i].additional_data
- ->dv_hashtab.ht_used)));
- msgpack_pack_map(spacker, entry_map_size);
- PACK_STATIC_STR(KEY_FILE);
- PACK_BIN(cstr_as_string(entry.data.buffer_list.buffers[i].fname));
+ + additional_data_len(entry.data.buffer_list.buffers[i].
+ additional_data));
+ mpack_map(&sbuf.ptr, (uint32_t)entry_map_size);
+ PACK_KEY(KEY_FILE);
+ mpack_bin(cstr_as_string(entry.data.buffer_list.buffers[i].fname), &sbuf);
if (entry.data.buffer_list.buffers[i].pos.lnum != 1) {
- PACK_STATIC_STR(KEY_LNUM);
- msgpack_pack_uint64(spacker, (uint64_t)entry.data.buffer_list.buffers[i].pos.lnum);
+ PACK_KEY(KEY_LNUM);
+ mpack_uint64(&sbuf.ptr, (uint64_t)entry.data.buffer_list.buffers[i].pos.lnum);
}
if (entry.data.buffer_list.buffers[i].pos.col != 0) {
- PACK_STATIC_STR(KEY_COL);
- msgpack_pack_uint64(spacker, (uint64_t)entry.data.buffer_list.buffers[i].pos.col);
+ PACK_KEY(KEY_COL);
+ mpack_uint64(&sbuf.ptr, (uint64_t)entry.data.buffer_list.buffers[i].pos.col);
}
- DUMP_ADDITIONAL_DATA(entry.data.buffer_list.buffers[i].additional_data,
- "buffer list subitem");
+ dump_additional_data(entry.data.buffer_list.buffers[i].additional_data, &sbuf);
}
break;
case kSDItemHeader:
- msgpack_pack_map(spacker, entry.data.header.size);
+ mpack_map(&sbuf.ptr, (uint32_t)entry.data.header.size);
for (size_t i = 0; i < entry.data.header.size; i++) {
- const String s = entry.data.header.items[i].key;
- msgpack_pack_str(spacker, s.size);
- if (s.size) {
- msgpack_pack_str_body(spacker, s.data, s.size);
- }
+ mpack_str(entry.data.header.items[i].key, &sbuf);
const Object obj = entry.data.header.items[i].value;
switch (obj.type) {
case kObjectTypeString:
- PACK_BIN(obj.data.string);
+ mpack_bin(obj.data.string, &sbuf);
break;
case kObjectTypeInteger:
- msgpack_pack_int64(spacker, (int64_t)obj.data.integer);
+ mpack_integer(&sbuf.ptr, obj.data.integer);
break;
default:
abort();
@@ -1773,33 +1553,28 @@ static ShaDaWriteResult shada_pack_entry(msgpack_packer *const packer, ShadaEntr
}
#undef CHECK_DEFAULT
#undef ONE_IF_NOT_DEFAULT
- if (!max_kbyte || sbuf.size <= max_kbyte * 1024) {
+ String packed = packer_take_string(&sbuf);
+ if (!max_kbyte || packed.size <= max_kbyte * 1024) {
+ shada_check_buffer(packer);
+
if (entry.type == kSDItemUnknown) {
- if (msgpack_pack_uint64(packer, entry.data.unknown_item.type) == -1) {
- goto shada_pack_entry_error;
- }
+ mpack_uint64(&packer->ptr, entry.data.unknown_item.type);
} else {
- if (msgpack_pack_uint64(packer, (uint64_t)entry.type) == -1) {
- goto shada_pack_entry_error;
- }
+ mpack_uint64(&packer->ptr, (uint64_t)entry.type);
}
- if (msgpack_pack_uint64(packer, (uint64_t)entry.timestamp) == -1) {
- goto shada_pack_entry_error;
+ mpack_uint64(&packer->ptr, (uint64_t)entry.timestamp);
+ if (packed.size > 0) {
+ mpack_uint64(&packer->ptr, (uint64_t)packed.size);
+ mpack_raw(packed.data, packed.size, packer);
}
- if (sbuf.size > 0) {
- if ((msgpack_pack_uint64(packer, (uint64_t)sbuf.size) == -1)
- || (packer->callback(packer->data, sbuf.data,
- (unsigned)sbuf.size) == -1)) {
- goto shada_pack_entry_error;
- }
+
+ if (packer->anyint != 0) { // error code
+ goto shada_pack_entry_error;
}
}
- msgpack_packer_free(spacker);
- msgpack_sbuffer_destroy(&sbuf);
- return kSDWriteSuccessful;
+ ret = kSDWriteSuccessful;
shada_pack_entry_error:
- msgpack_packer_free(spacker);
- msgpack_sbuffer_destroy(&sbuf);
+ xfree(sbuf.startptr);
return ret;
}
@@ -1811,7 +1586,7 @@ shada_pack_entry_error:
/// @param[in] entry Entry written.
/// @param[in] max_kbyte Maximum size of an item in KiB. Zero means no
/// restrictions.
-static inline ShaDaWriteResult shada_pack_pfreed_entry(msgpack_packer *const packer,
+static inline ShaDaWriteResult shada_pack_pfreed_entry(PackerBuffer *const packer,
PossiblyFreedShadaEntry entry,
const size_t max_kbyte)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_ALWAYS_INLINE
@@ -1849,76 +1624,29 @@ static int compare_file_marks(const void *a, const void *b)
///
/// @return kSDReadStatusNotShaDa, kSDReadStatusReadError or
/// kSDReadStatusSuccess.
-static inline ShaDaReadResult shada_parse_msgpack(ShaDaReadDef *const sd_reader,
- const size_t length,
- msgpack_unpacked *ret_unpacked,
- char **const ret_buf)
- FUNC_ATTR_WARN_UNUSED_RESULT FUNC_ATTR_NONNULL_ARG(1)
+static ShaDaReadResult shada_check_status(uintmax_t initial_fpos, int status, size_t remaining)
+ FUNC_ATTR_WARN_UNUSED_RESULT
{
- const uintmax_t initial_fpos = sd_reader->fpos;
- char *const buf = xmalloc(length);
-
- const ShaDaReadResult fl_ret = fread_len(sd_reader, buf, length);
- if (fl_ret != kSDReadStatusSuccess) {
- xfree(buf);
- return fl_ret;
- }
- bool did_try_to_free = false;
-shada_parse_msgpack_read_next: {}
- size_t off = 0;
- msgpack_unpacked unpacked;
- msgpack_unpacked_init(&unpacked);
- const msgpack_unpack_return result =
- msgpack_unpack_next(&unpacked, buf, length, &off);
- ShaDaReadResult ret = kSDReadStatusSuccess;
- switch (result) {
- case MSGPACK_UNPACK_SUCCESS:
- if (off < length) {
- goto shada_parse_msgpack_extra_bytes;
- }
- break;
- case MSGPACK_UNPACK_PARSE_ERROR:
- semsg(_(RCERR "Failed to parse ShaDa file due to a msgpack parser error "
- "at position %" PRIu64),
- (uint64_t)initial_fpos);
- ret = kSDReadStatusNotShaDa;
- break;
- case MSGPACK_UNPACK_NOMEM_ERROR:
- if (!did_try_to_free) {
- did_try_to_free = true;
- try_to_free_memory();
- goto shada_parse_msgpack_read_next;
+ switch (status) {
+ case MPACK_OK:
+ if (remaining) {
+ semsg(_(RCERR "Failed to parse ShaDa file: extra bytes in msgpack string "
+ "at position %" PRIu64),
+ (uint64_t)initial_fpos);
+ return kSDReadStatusNotShaDa;
}
- emsg(_(e_outofmem));
- ret = kSDReadStatusReadError;
- break;
- case MSGPACK_UNPACK_CONTINUE:
+ return kSDReadStatusSuccess;
+ case MPACK_EOF:
semsg(_(RCERR "Failed to parse ShaDa file: incomplete msgpack string "
"at position %" PRIu64),
(uint64_t)initial_fpos);
- ret = kSDReadStatusNotShaDa;
- break;
- case MSGPACK_UNPACK_EXTRA_BYTES:
-shada_parse_msgpack_extra_bytes:
- semsg(_(RCERR "Failed to parse ShaDa file: extra bytes in msgpack string "
+ return kSDReadStatusNotShaDa;
+ default:
+ semsg(_(RCERR "Failed to parse ShaDa file due to a msgpack parser error "
"at position %" PRIu64),
(uint64_t)initial_fpos);
- ret = kSDReadStatusNotShaDa;
- break;
- }
- if (ret_buf != NULL && ret == kSDReadStatusSuccess) {
- if (ret_unpacked == NULL) {
- msgpack_unpacked_destroy(&unpacked);
- } else {
- *ret_unpacked = unpacked;
- }
- *ret_buf = buf;
- } else {
- assert(ret_buf == NULL || ret != kSDReadStatusSuccess);
- msgpack_unpacked_destroy(&unpacked);
- xfree(buf);
+ return kSDReadStatusNotShaDa;
}
- return ret;
}
/// Format shada entry for debugging purposes
@@ -1935,25 +1663,16 @@ static const char *shada_format_entry(const ShadaEntry entry)
// ^ Space for `can_free_entry`
#define FORMAT_MARK_ENTRY(entry_name, name_fmt, name_fmt_arg) \
do { \
- typval_T ad_tv = { \
- .v_type = VAR_DICT, \
- .vval.v_dict = entry.data.filemark.additional_data \
- }; \
- size_t ad_len; \
- char *const ad = encode_tv2string(&ad_tv, &ad_len); \
vim_snprintf_add(S_LEN(ret), \
entry_name " {" name_fmt " file=[%zu]\"%.512s\", " \
"pos={l=%" PRIdLINENR ",c=%" PRIdCOLNR ",a=%" PRIdCOLNR "}, " \
- "ad={%p:[%zu]%.64s} }", \
+ "}", \
name_fmt_arg, \
strlen(entry.data.filemark.fname), \
entry.data.filemark.fname, \
entry.data.filemark.mark.lnum, \
entry.data.filemark.mark.col, \
- entry.data.filemark.mark.coladd, \
- (void *)entry.data.filemark.additional_data, \
- ad_len, \
- ad); \
+ entry.data.filemark.mark.coladd); \
} while (0)
switch (entry.type) {
case kSDItemMissing:
@@ -2021,11 +1740,11 @@ static const char *shada_format_pfreed_entry(const PossiblyFreedShadaEntry pfs_e
/// @param[in,out] ret_wms Location where results are saved.
/// @param[out] packer MessagePack packer for entries which are not
/// merged.
-static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_reader,
+static inline ShaDaWriteResult shada_read_when_writing(FileDescriptor *const sd_reader,
const unsigned srni_flags,
const size_t max_kbyte,
WriteMergerState *const wms,
- msgpack_packer *const packer)
+ PackerBuffer *const packer)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
ShaDaWriteResult ret = kSDWriteSuccessful;
@@ -2137,8 +1856,8 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re
}
// Ignore duplicates.
if (wms_entry.timestamp == entry.timestamp
- && (wms_entry.data.filemark.additional_data == NULL
- && entry.data.filemark.additional_data == NULL)
+ && (wms_entry.additional_data == NULL
+ && entry.additional_data == NULL)
&& marks_equal(wms_entry.data.filemark.mark,
entry.data.filemark.mark)
&& strcmp(wms_entry.data.filemark.fname,
@@ -2167,13 +1886,18 @@ static inline ShaDaWriteResult shada_read_when_writing(ShaDaReadDef *const sd_re
shada_free_shada_entry(&entry);
break;
}
- if (wms->global_marks[idx].data.type == kSDItemMissing) {
+
+ // Global or numbered mark.
+ PossiblyFreedShadaEntry *mark
+ = idx < 26 ? &wms->global_marks[idx] : &wms->numbered_marks[idx - 26];
+
+ if (mark->data.type == kSDItemMissing) {
if (namedfm[idx].fmark.timestamp >= entry.timestamp) {
shada_free_shada_entry(&entry);
break;
}
}
- COMPARE_WITH_ENTRY(&wms->global_marks[idx], entry);
+ COMPARE_WITH_ENTRY(mark, entry);
}
break;
case kSDItemChange:
@@ -2366,11 +2090,11 @@ static inline void add_search_pattern(PossiblyFreedShadaEntry *const ret_pse,
.is_substitute_pattern = is_substitute_pattern,
.highlighted = ((is_substitute_pattern ^ search_last_used)
&& search_highlighted),
- .pat = pat.pat,
- .additional_data = pat.additional_data,
+ .pat = cstr_as_string(pat.pat),
.search_backward = (!is_substitute_pattern && pat.off.dir == '?'),
}
- }
+ },
+ .additional_data = pat.additional_data,
}
};
}
@@ -2407,11 +2131,11 @@ static inline void shada_initialize_registers(WriteMergerState *const wms, int m
.contents_size = reg.y_size,
.type = reg.y_type,
.width = (size_t)(reg.y_type == kMTBlockWise ? reg.y_width : 0),
- .additional_data = reg.additional_data,
.name = name,
.is_unnamed = is_unnamed,
}
- }
+ },
+ .additional_data = reg.additional_data,
}
};
} while (reg_iter != ITER_REGISTER_NULL);
@@ -2477,13 +2201,37 @@ static int hist_type2char(const int type)
return NUL;
}
+static PackerBuffer packer_buffer_for_file(FileDescriptor *file)
+{
+ if (file_space(file) < SHADA_MPACK_FREE_SPACE) {
+ file_flush(file);
+ }
+ return (PackerBuffer) {
+ .startptr = file->buffer,
+ .ptr = file->write_pos,
+ .endptr = file->buffer + ARENA_BLOCK_SIZE,
+ .anydata = file,
+ .anyint = 0, // set to nonzero if error
+ .packer_flush = flush_file_buffer,
+ };
+}
+
+static void flush_file_buffer(PackerBuffer *buffer)
+{
+ FileDescriptor *fd = buffer->anydata;
+ fd->write_pos = buffer->ptr;
+ buffer->anyint = file_flush(fd);
+ buffer->ptr = fd->write_pos;
+}
+
/// Write ShaDa file
///
/// @param[in] sd_writer Structure containing file writer definition.
/// @param[in] sd_reader Structure containing file reader definition. If it is
/// not NULL then contents of this file will be merged
/// with current Neovim runtime.
-static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDef *const sd_reader)
+static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer,
+ FileDescriptor *const sd_reader)
FUNC_ATTR_NONNULL_ARG(1)
{
ShaDaWriteResult ret = kSDWriteSuccessful;
@@ -2524,8 +2272,7 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
}
}
- const unsigned srni_flags = (unsigned)(
- kSDReadUndisableableData
+ const unsigned srni_flags = (unsigned)(kSDReadUndisableableData
| kSDReadUnknown
| (dump_history ? kSDReadHistory : 0)
| (dump_registers ? kSDReadRegisters : 0)
@@ -2534,8 +2281,7 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
| (num_marked_files ? kSDReadLocalMarks |
kSDReadChanges : 0));
- msgpack_packer *const packer = msgpack_packer_new(sd_writer,
- &msgpack_sd_writer_write);
+ PackerBuffer packer = packer_buffer_for_file(sd_writer);
// Set b_last_cursor for all the buffers that have a window.
//
@@ -2549,7 +2295,7 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
find_removable_bufs(&removable_bufs);
// Write header
- if (shada_pack_entry(packer, (ShadaEntry) {
+ if (shada_pack_entry(&packer, (ShadaEntry) {
.type = kSDItemHeader,
.timestamp = os_time(),
.data = {
@@ -2578,7 +2324,7 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
// Write buffer list
if (find_shada_parameter('%') != NULL) {
ShadaEntry buflist_entry = shada_get_buflist(&removable_bufs);
- if (shada_pack_entry(packer, buflist_entry, 0) == kSDWriteFailed) {
+ if (shada_pack_entry(&packer, buflist_entry, 0) == kSDWriteFailed) {
xfree(buflist_entry.data.buffer_list.buffers);
ret = kSDWriteFailed;
goto shada_write_exit;
@@ -2628,16 +2374,16 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
typval_T tgttv;
tv_copy(&vartv, &tgttv);
ShaDaWriteResult spe_ret;
- if ((spe_ret = shada_pack_entry(packer, (ShadaEntry) {
+ if ((spe_ret = shada_pack_entry(&packer, (ShadaEntry) {
.type = kSDItemVariable,
.timestamp = cur_timestamp,
.data = {
.global_var = {
.name = (char *)name,
.value = tgttv,
- .additional_elements = NULL,
}
- }
+ },
+ .additional_data = NULL,
}, max_kbyte)) == kSDWriteFailed) {
tv_clear(&vartv);
tv_clear(&tgttv);
@@ -2679,9 +2425,9 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
.data = {
.sub_string = {
.sub = sub.sub,
- .additional_elements = sub.additional_elements,
}
- }
+ },
+ .additional_data = sub.additional_data,
}
};
}
@@ -2721,10 +2467,10 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
.filemark = {
.mark = fm.fmark.mark,
.name = name,
- .additional_data = fm.fmark.additional_data,
.fname = (char *)fname,
}
- }
+ },
+ .additional_data = fm.fmark.additional_data,
},
};
if (ascii_isdigit(name)) {
@@ -2770,9 +2516,9 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
.mark = fm.mark,
.name = name,
.fname = (char *)fname,
- .additional_data = fm.additional_data,
}
- }
+ },
+ .additional_data = fm.additional_data,
}
};
if (fm.timestamp > filemarks->greatest_timestamp) {
@@ -2790,9 +2536,9 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
.filemark = {
.mark = fm.mark,
.fname = (char *)fname,
- .additional_data = fm.additional_data,
}
- }
+ },
+ .additional_data = fm.additional_data,
}
};
if (fm.timestamp > filemarks->greatest_timestamp) {
@@ -2805,7 +2551,7 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
if (sd_reader != NULL) {
const ShaDaWriteResult srww_ret = shada_read_when_writing(sd_reader, srni_flags, max_kbyte, wms,
- packer);
+ &packer);
if (srww_ret != kSDWriteSuccessful) {
ret = srww_ret;
}
@@ -2823,10 +2569,10 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
.filemark = {
.mark = curwin->w_cursor,
.name = '0',
- .additional_data = NULL,
.fname = curbuf->b_ffname,
}
- }
+ },
+ .additional_data = NULL,
},
});
}
@@ -2836,7 +2582,7 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
do { \
for (size_t i_ = 0; i_ < ARRAY_SIZE(wms_array); i_++) { \
if ((wms_array)[i_].data.type != kSDItemMissing) { \
- if (shada_pack_pfreed_entry(packer, (wms_array)[i_], max_kbyte) \
+ if (shada_pack_pfreed_entry(&packer, (wms_array)[i_], max_kbyte) \
== kSDWriteFailed) { \
ret = kSDWriteFailed; \
goto shada_write_exit; \
@@ -2848,7 +2594,7 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
PACK_WMS_ARRAY(wms->numbered_marks);
PACK_WMS_ARRAY(wms->registers);
for (size_t i = 0; i < wms->jumps_size; i++) {
- if (shada_pack_pfreed_entry(packer, wms->jumps[i], max_kbyte)
+ if (shada_pack_pfreed_entry(&packer, wms->jumps[i], max_kbyte)
== kSDWriteFailed) {
ret = kSDWriteFailed;
goto shada_write_exit;
@@ -2857,7 +2603,7 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
#define PACK_WMS_ENTRY(wms_entry) \
do { \
if ((wms_entry).data.type != kSDItemMissing) { \
- if (shada_pack_pfreed_entry(packer, wms_entry, max_kbyte) \
+ if (shada_pack_pfreed_entry(&packer, wms_entry, max_kbyte) \
== kSDWriteFailed) { \
ret = kSDWriteFailed; \
goto shada_write_exit; \
@@ -2883,14 +2629,14 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
for (size_t i = 0; i < file_markss_to_dump; i++) {
PACK_WMS_ARRAY(all_file_markss[i]->marks);
for (size_t j = 0; j < all_file_markss[i]->changes_size; j++) {
- if (shada_pack_pfreed_entry(packer, all_file_markss[i]->changes[j],
+ if (shada_pack_pfreed_entry(&packer, all_file_markss[i]->changes[j],
max_kbyte) == kSDWriteFailed) {
ret = kSDWriteFailed;
goto shada_write_exit;
}
}
for (size_t j = 0; j < all_file_markss[i]->additional_marks_size; j++) {
- if (shada_pack_entry(packer, all_file_markss[i]->additional_marks[j],
+ if (shada_pack_entry(&packer, all_file_markss[i]->additional_marks[j],
0) == kSDWriteFailed) {
shada_free_shada_entry(&all_file_markss[i]->additional_marks[j]);
ret = kSDWriteFailed;
@@ -2908,7 +2654,7 @@ static ShaDaWriteResult shada_write(FileDescriptor *const sd_writer, ShaDaReadDe
if (dump_one_history[i]) {
hms_insert_whole_neovim_history(&wms->hms[i]);
HMS_ITER(&wms->hms[i], cur_entry, {
- if (shada_pack_pfreed_entry(packer, (PossiblyFreedShadaEntry) {
+ if (shada_pack_pfreed_entry(&packer, (PossiblyFreedShadaEntry) {
.data = cur_entry->data,
.can_free_entry = cur_entry->can_free_entry,
}, max_kbyte) == kSDWriteFailed) {
@@ -2934,13 +2680,13 @@ shada_write_exit:
})
map_destroy(cstr_t, &wms->file_marks);
set_destroy(ptr_t, &removable_bufs);
- msgpack_packer_free(packer);
+ packer.packer_flush(&packer);
set_destroy(cstr_t, &wms->dumped_variables);
xfree(wms);
return ret;
}
-#undef PACK_STATIC_STR
+#undef PACK_KEY
/// Write ShaDa file to a given location
///
@@ -2958,12 +2704,13 @@ int shada_write_file(const char *const file, bool nomerge)
char *const fname = shada_filename(file);
char *tempname = NULL;
FileDescriptor sd_writer;
- ShaDaReadDef sd_reader = { .close = NULL };
+ FileDescriptor sd_reader;
bool did_open_writer = false;
+ bool did_open_reader = false;
if (!nomerge) {
int error;
- if ((error = open_shada_file_for_reading(fname, &sd_reader)) != 0) {
+ if ((error = file_open(&sd_reader, fname, kFileReadOnly, 0)) != 0) {
if (error != UV_ENOENT) {
semsg(_(SERR "System error while opening ShaDa file %s for reading "
"to merge before writing it: %s"),
@@ -2973,6 +2720,8 @@ int shada_write_file(const char *const file, bool nomerge)
}
nomerge = true;
goto shada_write_file_nomerge;
+ } else {
+ did_open_reader = true;
}
tempname = modname(fname, ".tmp.a", false);
if (tempname == NULL) {
@@ -3001,8 +2750,9 @@ shada_write_file_open: {}
fname);
xfree(fname);
xfree(tempname);
- assert(sd_reader.close != NULL);
- sd_reader.close(&sd_reader);
+ if (did_open_reader) {
+ close_file(&sd_reader);
+ }
return FAIL;
}
(*wp)++;
@@ -3047,8 +2797,8 @@ shada_write_file_nomerge: {}
if (!did_open_writer) {
xfree(fname);
xfree(tempname);
- if (sd_reader.cookie != NULL) {
- sd_reader.close(&sd_reader);
+ if (did_open_reader) {
+ close_file(&sd_reader);
}
return FAIL;
}
@@ -3062,7 +2812,9 @@ shada_write_file_nomerge: {}
const ShaDaWriteResult sw_ret = shada_write(&sd_writer, (nomerge ? NULL : &sd_reader));
assert(sw_ret != kSDWriteIgnError);
if (!nomerge) {
- sd_reader.close(&sd_reader);
+ if (did_open_reader) {
+ close_file(&sd_reader);
+ }
bool did_remove = false;
if (sw_ret == kSDWriteSuccessful) {
FileInfo old_info;
@@ -3164,47 +2916,42 @@ static void shada_free_shada_entry(ShadaEntry *const entry)
xfree(entry->data.unknown_item.contents);
break;
case kSDItemHeader:
- api_free_dictionary(entry->data.header);
+ api_free_dict(entry->data.header);
break;
case kSDItemChange:
case kSDItemJump:
case kSDItemGlobalMark:
case kSDItemLocalMark:
- tv_dict_unref(entry->data.filemark.additional_data);
xfree(entry->data.filemark.fname);
break;
case kSDItemSearchPattern:
- tv_dict_unref(entry->data.search_pattern.additional_data);
- xfree(entry->data.search_pattern.pat);
+ api_free_string(entry->data.search_pattern.pat);
break;
case kSDItemRegister:
- tv_dict_unref(entry->data.reg.additional_data);
for (size_t i = 0; i < entry->data.reg.contents_size; i++) {
xfree(entry->data.reg.contents[i]);
}
xfree(entry->data.reg.contents);
break;
case kSDItemHistoryEntry:
- tv_list_unref(entry->data.history_item.additional_elements);
xfree(entry->data.history_item.string);
break;
case kSDItemVariable:
- tv_list_unref(entry->data.global_var.additional_elements);
xfree(entry->data.global_var.name);
tv_clear(&entry->data.global_var.value);
break;
case kSDItemSubString:
- tv_list_unref(entry->data.sub_string.additional_elements);
xfree(entry->data.sub_string.sub);
break;
case kSDItemBufferList:
for (size_t i = 0; i < entry->data.buffer_list.size; i++) {
xfree(entry->data.buffer_list.buffers[i].fname);
- tv_dict_unref(entry->data.buffer_list.buffers[i].additional_data);
+ xfree(entry->data.buffer_list.buffers[i].additional_data);
}
xfree(entry->data.buffer_list.buffers);
break;
}
+ XFREE_CLEAR(entry->additional_data);
}
#ifndef HAVE_BE64TOH
@@ -3235,18 +2982,18 @@ static inline uint64_t be64toh(uint64_t big_endian_64_bits)
/// @return kSDReadStatusSuccess if everything was OK, kSDReadStatusNotShaDa if
/// there were not enough bytes to read or kSDReadStatusReadError if
/// there was some error while reading.
-static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader, char *const buffer,
+static ShaDaReadResult fread_len(FileDescriptor *const sd_reader, char *const buffer,
const size_t length)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- const ptrdiff_t read_bytes = sd_reader->read(sd_reader, buffer, length);
+ const ptrdiff_t read_bytes = file_read(sd_reader, buffer, length);
+ if (read_bytes < 0) {
+ semsg(_(SERR "System error while reading ShaDa file: %s"),
+ os_strerror((int)read_bytes));
+ return kSDReadStatusReadError;
+ }
if (read_bytes != (ptrdiff_t)length) {
- if (sd_reader->error != NULL) {
- semsg(_(SERR "System error while reading ShaDa file: %s"),
- sd_reader->error);
- return kSDReadStatusReadError;
- }
semsg(_(RCERR "Error while reading ShaDa file: "
"last entry specified that it occupies %" PRIu64 " bytes, "
"but file ended earlier"),
@@ -3272,26 +3019,32 @@ static ShaDaReadResult fread_len(ShaDaReadDef *const sd_reader, char *const buff
/// @return kSDReadStatusSuccess if reading was successful,
/// kSDReadStatusNotShaDa if there were not enough bytes to read or
/// kSDReadStatusReadError if reading failed for whatever reason.
-static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, const int first_char,
+/// kSDReadStatusFinished if eof and that was allowed
+static ShaDaReadResult msgpack_read_uint64(FileDescriptor *const sd_reader, bool allow_eof,
uint64_t *const result)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
- const uintmax_t fpos = sd_reader->fpos - 1;
-
- if (first_char == EOF) {
- if (sd_reader->error) {
- semsg(_(SERR "System error while reading integer from ShaDa file: %s"),
- sd_reader->error);
- return kSDReadStatusReadError;
- } else if (sd_reader->eof) {
- semsg(_(RCERR "Error while reading ShaDa file: "
- "expected positive integer at position %" PRIu64
- ", but got nothing"),
- (uint64_t)fpos);
- return kSDReadStatusNotShaDa;
+ const uintmax_t fpos = sd_reader->bytes_read;
+
+ uint8_t ret;
+ ptrdiff_t read_bytes = file_read(sd_reader, (char *)&ret, 1);
+
+ if (read_bytes < 0) {
+ semsg(_(SERR "System error while reading integer from ShaDa file: %s"),
+ os_strerror((int)read_bytes));
+ return kSDReadStatusReadError;
+ } else if (read_bytes == 0) {
+ if (allow_eof && file_eof(sd_reader)) {
+ return kSDReadStatusFinished;
}
+ semsg(_(RCERR "Error while reading ShaDa file: "
+ "expected positive integer at position %" PRIu64
+ ", but got nothing"),
+ (uint64_t)fpos);
+ return kSDReadStatusNotShaDa;
}
+ int first_char = (int)ret;
if (~first_char & 0x80) {
// Positive fixnum
*result = (uint64_t)((uint8_t)first_char);
@@ -3332,128 +3085,6 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, const
RERR "Error while reading ShaDa file: " \
entry_name " entry at position %" PRIu64 " " \
error_desc
-#define CHECK_KEY(key, \
- expected) ((key).via.str.size == (sizeof(expected) - 1) \
- && strncmp((key).via.str.ptr, expected, (sizeof(expected) - 1)) == 0)
-#define CLEAR_GA_AND_ERROR_OUT(ga) \
- do { \
- ga_clear(&(ga)); \
- goto shada_read_next_item_error; \
- } while (0)
-#define ID(s) s
-#define BINDUP(b) xmemdupz((b).ptr, (b).size)
-#define TOINT(s) ((int)(s))
-#define TOCHAR(s) ((char)(s))
-#define TOU8(s) ((uint8_t)(s))
-#define TOSIZE(s) ((size_t)(s))
-#define CHECKED_ENTRY(condition, error_desc, entry_name, obj, tgt, attr, \
- proc) \
- do { \
- if (!(condition)) { \
- semsg(_(READERR(entry_name, error_desc)), initial_fpos); \
- CLEAR_GA_AND_ERROR_OUT(ad_ga); \
- } \
- (tgt) = proc((obj).via.attr); \
- } while (0)
-#define CHECK_KEY_IS_STR(un, entry_name) \
- if ((un).data.via.map.ptr[i].key.type != MSGPACK_OBJECT_STR) { \
- semsg(_(READERR(entry_name, "has key which is not a string")), \
- initial_fpos); \
- CLEAR_GA_AND_ERROR_OUT(ad_ga); \
- } else if ((un).data.via.map.ptr[i].key.via.str.size == 0) { \
- semsg(_(READERR(entry_name, "has empty key")), initial_fpos); \
- CLEAR_GA_AND_ERROR_OUT(ad_ga); \
- }
-#define CHECKED_KEY(un, entry_name, name, error_desc, tgt, condition, attr, proc) \
- else if (CHECK_KEY((un).data.via.map.ptr[i].key, name)) \
- { \
- CHECKED_ENTRY(condition, \
- "has " name " key value " error_desc, \
- entry_name, \
- (un).data.via.map.ptr[i].val, \
- tgt, \
- attr, \
- proc); \
- }
-#define TYPED_KEY(un, entry_name, name, type_name, tgt, objtype, attr, proc) \
- CHECKED_KEY(un, entry_name, name, "which is not " type_name, tgt, \
- (un).data.via.map.ptr[i].val.type == MSGPACK_OBJECT_##objtype, \
- attr, proc)
-#define BOOLEAN_KEY(un, entry_name, name, tgt) \
- TYPED_KEY(un, entry_name, name, "a boolean", tgt, BOOLEAN, boolean, ID)
-#define STRING_KEY(un, entry_name, name, tgt) \
- TYPED_KEY(un, entry_name, name, "a binary", tgt, BIN, bin, BINDUP)
-#define CONVERTED_STRING_KEY(un, entry_name, name, tgt) \
- TYPED_KEY(un, entry_name, name, "a binary", tgt, BIN, bin, \
- BIN_CONVERTED)
-#define INT_KEY(un, entry_name, name, tgt, proc) \
- CHECKED_KEY(un, entry_name, name, "which is not an integer", tgt, \
- (((un).data.via.map.ptr[i].val.type \
- == MSGPACK_OBJECT_POSITIVE_INTEGER) \
- || ((un).data.via.map.ptr[i].val.type \
- == MSGPACK_OBJECT_NEGATIVE_INTEGER)), \
- i64, proc)
-#define INTEGER_KEY(un, entry_name, name, tgt) \
- INT_KEY(un, entry_name, name, tgt, TOINT)
-#define ADDITIONAL_KEY(un) \
- else { \
- ga_grow(&ad_ga, 1); \
- memcpy(((char *)ad_ga.ga_data) + ((size_t)ad_ga.ga_len \
- * sizeof(*(un).data.via.map.ptr)), \
- (un).data.via.map.ptr + i, \
- sizeof(*(un).data.via.map.ptr)); \
- ad_ga.ga_len++; \
- }
-#define BIN_CONVERTED(b) (xmemdupz(((b).ptr), ((b).size)))
-#define SET_ADDITIONAL_DATA(tgt, name) \
- do { \
- if (ad_ga.ga_len) { \
- msgpack_object obj = { \
- .type = MSGPACK_OBJECT_MAP, \
- .via = { \
- .map = { \
- .size = (uint32_t)ad_ga.ga_len, \
- .ptr = ad_ga.ga_data, \
- } \
- } \
- }; \
- typval_T adtv; \
- if (msgpack_to_vim(obj, &adtv) == FAIL \
- || adtv.v_type != VAR_DICT) { \
- semsg(_(READERR(name, \
- "cannot be converted to a Vimscript dictionary")), \
- initial_fpos); \
- ga_clear(&ad_ga); \
- tv_clear(&adtv); \
- goto shada_read_next_item_error; \
- } \
- (tgt) = adtv.vval.v_dict; \
- } \
- ga_clear(&ad_ga); \
- } while (0)
-#define SET_ADDITIONAL_ELEMENTS(src, src_maxsize, tgt, name) \
- do { \
- if ((src).size > (size_t)(src_maxsize)) { \
- msgpack_object obj = { \
- .type = MSGPACK_OBJECT_ARRAY, \
- .via = { \
- .array = { \
- .size = ((src).size - (uint32_t)(src_maxsize)), \
- .ptr = (src).ptr + (src_maxsize), \
- } \
- } \
- }; \
- typval_T aetv; \
- if (msgpack_to_vim(obj, &aetv) == FAIL) { \
- semsg(_(READERR(name, "cannot be converted to a Vimscript list")), \
- initial_fpos); \
- tv_clear(&aetv); \
- goto shada_read_next_item_error; \
- } \
- assert(aetv.v_type == VAR_LIST); \
- (tgt) = aetv.vval.v_list; \
- } \
- } while (0)
/// Iterate over shada file contents
///
@@ -3465,8 +3096,9 @@ static ShaDaReadResult msgpack_read_uint64(ShaDaReadDef *const sd_reader, const
/// greater then given.
///
/// @return Any value from ShaDaReadResult enum.
-static ShaDaReadResult shada_read_next_item(ShaDaReadDef *const sd_reader, ShadaEntry *const entry,
- const unsigned flags, const size_t max_kbyte)
+static ShaDaReadResult shada_read_next_item(FileDescriptor *const sd_reader,
+ ShadaEntry *const entry, const unsigned flags,
+ const size_t max_kbyte)
FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
{
ShaDaReadResult ret = kSDReadStatusMalformed;
@@ -3476,29 +3108,30 @@ shada_read_next_item_start:
// somebody calls goto shada_read_next_item_error before anything is set in
// the switch.
CLEAR_POINTER(entry);
- if (sd_reader->eof) {
+ if (file_eof(sd_reader)) {
return kSDReadStatusFinished;
}
+ bool verify_but_ignore = false;
+
// First: manually unpack type, timestamp and length.
// This is needed to avoid both seeking and having to maintain a buffer.
uint64_t type_u64 = (uint64_t)kSDItemMissing;
uint64_t timestamp_u64;
uint64_t length_u64;
- const uint64_t initial_fpos = (uint64_t)sd_reader->fpos;
- const int first_char = read_char(sd_reader);
- if (first_char == EOF && sd_reader->eof) {
- return kSDReadStatusFinished;
- }
+ const uint64_t initial_fpos = sd_reader->bytes_read;
+ AdditionalDataBuilder ad = KV_INITIAL_VALUE;
+ uint32_t read_additional_array_elements = 0;
+ char *error_alloc = NULL;
ShaDaReadResult mru_ret;
- if (((mru_ret = msgpack_read_uint64(sd_reader, first_char, &type_u64))
+ if (((mru_ret = msgpack_read_uint64(sd_reader, true, &type_u64))
!= kSDReadStatusSuccess)
- || ((mru_ret = msgpack_read_uint64(sd_reader, read_char(sd_reader),
+ || ((mru_ret = msgpack_read_uint64(sd_reader, false,
&timestamp_u64))
!= kSDReadStatusSuccess)
- || ((mru_ret = msgpack_read_uint64(sd_reader, read_char(sd_reader),
+ || ((mru_ret = msgpack_read_uint64(sd_reader, false,
&length_u64))
!= kSDReadStatusSuccess)) {
return mru_ret;
@@ -3538,16 +3171,42 @@ shada_read_next_item_start:
// in incomplete MessagePack string.
if (initial_fpos == 0
&& (type_u64 == '\n' || type_u64 > SHADA_LAST_ENTRY)) {
- const ShaDaReadResult spm_ret = shada_parse_msgpack(sd_reader, length,
- NULL, NULL);
- if (spm_ret != kSDReadStatusSuccess) {
- return spm_ret;
- }
+ verify_but_ignore = true;
} else {
const ShaDaReadResult srs_ret = sd_reader_skip(sd_reader, length);
if (srs_ret != kSDReadStatusSuccess) {
return srs_ret;
}
+ goto shada_read_next_item_start;
+ }
+ }
+
+ const uint64_t parse_pos = sd_reader->bytes_read;
+ bool buf_allocated = false;
+ // try to avoid allocation for small items which fits entirely
+ // in the internal buffer of sd_reader
+ char *buf = file_try_read_buffered(sd_reader, length);
+ if (!buf) {
+ buf_allocated = true;
+ buf = xmalloc(length);
+ const ShaDaReadResult fl_ret = fread_len(sd_reader, buf, length);
+ if (fl_ret != kSDReadStatusSuccess) {
+ ret = fl_ret;
+ goto shada_read_next_item_error;
+ }
+ }
+
+ const char *read_ptr = buf;
+ size_t read_size = length;
+
+ if (verify_but_ignore) {
+ int status = unpack_skip(&read_ptr, &read_size);
+ ShaDaReadResult spm_ret = shada_check_status(parse_pos, status, read_size);
+ if (buf_allocated) {
+ xfree(buf);
+ }
+ if (spm_ret != kSDReadStatusSuccess) {
+ return spm_ret;
}
goto shada_read_next_item_start;
}
@@ -3557,397 +3216,308 @@ shada_read_next_item_start:
entry->data.unknown_item.size = length;
entry->data.unknown_item.type = type_u64;
if (initial_fpos == 0) {
- const ShaDaReadResult spm_ret = shada_parse_msgpack(sd_reader, length, NULL,
- &entry->data.unknown_item.contents);
+ int status = unpack_skip(&read_ptr, &read_size);
+ ShaDaReadResult spm_ret = shada_check_status(parse_pos, status, read_size);
if (spm_ret != kSDReadStatusSuccess) {
+ if (buf_allocated) {
+ xfree(buf);
+ }
entry->type = kSDItemMissing;
+ return spm_ret;
}
- return spm_ret;
}
- entry->data.unknown_item.contents = xmalloc(length);
- const ShaDaReadResult fl_ret =
- fread_len(sd_reader, entry->data.unknown_item.contents, length);
- if (fl_ret != kSDReadStatusSuccess) {
- shada_free_shada_entry(entry);
- entry->type = kSDItemMissing;
- }
- return fl_ret;
+ entry->data.unknown_item.contents = buf_allocated ? buf : xmemdup(buf, length);
+ return kSDReadStatusSuccess;
}
- msgpack_unpacked unpacked;
- char *buf = NULL;
-
- const ShaDaReadResult spm_ret = shada_parse_msgpack(sd_reader, length,
- &unpacked, &buf);
- if (spm_ret != kSDReadStatusSuccess) {
- ret = spm_ret;
- goto shada_read_next_item_error;
- }
entry->data = sd_default_values[type_u64].data;
switch ((ShadaEntryType)type_u64) {
case kSDItemHeader:
// TODO(bfredl): header is written to file and provides useful debugging
// info. It is never read by nvim (earlier we parsed it back to a
- // Dictionary, but that value was never used)
+ // Dict, but that value was never used)
break;
case kSDItemSearchPattern: {
- if (unpacked.data.type != MSGPACK_OBJECT_MAP) {
- semsg(_(READERR("search pattern", "is not a dictionary")),
- initial_fpos);
+ Dict(_shada_search_pat) *it = &entry->data.search_pattern;
+ if (!unpack_keydict(it, DictHash(_shada_search_pat), &ad, &read_ptr, &read_size,
+ &error_alloc)) {
+ semsg(_(READERR("search pattern", "%s")), initial_fpos, error_alloc);
+ it->pat = NULL_STRING;
goto shada_read_next_item_error;
}
- garray_T ad_ga;
- ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1);
- for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
- CHECK_KEY_IS_STR(unpacked, "search pattern")
- BOOLEAN_KEY(unpacked, "search pattern", SEARCH_KEY_MAGIC,
- entry->data.search_pattern.magic)
- BOOLEAN_KEY(unpacked, "search pattern", SEARCH_KEY_SMARTCASE,
- entry->data.search_pattern.smartcase)
- BOOLEAN_KEY(unpacked, "search pattern", SEARCH_KEY_HAS_LINE_OFFSET,
- entry->data.search_pattern.has_line_offset)
- BOOLEAN_KEY(unpacked, "search pattern", SEARCH_KEY_PLACE_CURSOR_AT_END,
- entry->data.search_pattern.place_cursor_at_end)
- BOOLEAN_KEY(unpacked, "search pattern", SEARCH_KEY_IS_LAST_USED,
- entry->data.search_pattern.is_last_used)
- BOOLEAN_KEY(unpacked, "search pattern",
- SEARCH_KEY_IS_SUBSTITUTE_PATTERN,
- entry->data.search_pattern.is_substitute_pattern)
- BOOLEAN_KEY(unpacked, "search pattern", SEARCH_KEY_HIGHLIGHTED,
- entry->data.search_pattern.highlighted)
- BOOLEAN_KEY(unpacked, "search pattern", SEARCH_KEY_BACKWARD,
- entry->data.search_pattern.search_backward)
- INTEGER_KEY(unpacked, "search pattern", SEARCH_KEY_OFFSET,
- entry->data.search_pattern.offset)
- CONVERTED_STRING_KEY(unpacked, "search pattern", SEARCH_KEY_PAT,
- entry->data.search_pattern.pat)
- ADDITIONAL_KEY(unpacked)
- }
- if (entry->data.search_pattern.pat == NULL) {
+
+ if (!HAS_KEY(it, _shada_search_pat, sp)) { // SEARCH_KEY_PAT
semsg(_(READERR("search pattern", "has no pattern")), initial_fpos);
- CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ goto shada_read_next_item_error;
}
- SET_ADDITIONAL_DATA(entry->data.search_pattern.additional_data,
- "search pattern");
+ entry->data.search_pattern.pat = copy_string(entry->data.search_pattern.pat, NULL);
+
break;
}
case kSDItemChange:
case kSDItemJump:
case kSDItemGlobalMark:
case kSDItemLocalMark: {
- if (unpacked.data.type != MSGPACK_OBJECT_MAP) {
- semsg(_(READERR("mark", "is not a dictionary")), initial_fpos);
+ Dict(_shada_mark) it = { 0 };
+ if (!unpack_keydict(&it, DictHash(_shada_mark), &ad, &read_ptr, &read_size, &error_alloc)) {
+ semsg(_(READERR("mark", "%s")), initial_fpos, error_alloc);
goto shada_read_next_item_error;
}
- garray_T ad_ga;
- ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1);
- for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
- CHECK_KEY_IS_STR(unpacked, "mark")
- if (CHECK_KEY(unpacked.data.via.map.ptr[i].key, KEY_NAME_CHAR)) {
- if (type_u64 == kSDItemJump || type_u64 == kSDItemChange) {
- semsg(_(READERR("mark", "has n key which is only valid for "
- "local and global mark entries")), initial_fpos);
- CLEAR_GA_AND_ERROR_OUT(ad_ga);
- }
- CHECKED_ENTRY((unpacked.data.via.map.ptr[i].val.type
- == MSGPACK_OBJECT_POSITIVE_INTEGER),
- "has n key value which is not an unsigned integer",
- "mark", unpacked.data.via.map.ptr[i].val,
- entry->data.filemark.name, u64, TOCHAR);
+
+ if (HAS_KEY(&it, _shada_mark, n)) {
+ if (type_u64 == kSDItemJump || type_u64 == kSDItemChange) {
+ semsg(_(READERR("mark", "has n key which is only valid for "
+ "local and global mark entries")), initial_fpos);
+ goto shada_read_next_item_error;
}
- INTEGER_KEY(unpacked, "mark", KEY_LNUM, entry->data.filemark.mark.lnum)
- INTEGER_KEY(unpacked, "mark", KEY_COL, entry->data.filemark.mark.col)
- STRING_KEY(unpacked, "mark", KEY_FILE, entry->data.filemark.fname)
- ADDITIONAL_KEY(unpacked)
+ entry->data.filemark.name = (char)it.n;
+ }
+
+ if (HAS_KEY(&it, _shada_mark, l)) {
+ entry->data.filemark.mark.lnum = (linenr_T)it.l;
+ }
+ if (HAS_KEY(&it, _shada_mark, c)) {
+ entry->data.filemark.mark.col = (colnr_T)it.c;
}
+ if (HAS_KEY(&it, _shada_mark, f)) {
+ entry->data.filemark.fname = xmemdupz(it.f.data, it.f.size);
+ }
+
if (entry->data.filemark.fname == NULL) {
semsg(_(READERR("mark", "is missing file name")), initial_fpos);
- CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ goto shada_read_next_item_error;
}
if (entry->data.filemark.mark.lnum <= 0) {
semsg(_(READERR("mark", "has invalid line number")), initial_fpos);
- CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ goto shada_read_next_item_error;
}
if (entry->data.filemark.mark.col < 0) {
semsg(_(READERR("mark", "has invalid column number")), initial_fpos);
- CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ goto shada_read_next_item_error;
}
- SET_ADDITIONAL_DATA(entry->data.filemark.additional_data, "mark");
break;
}
case kSDItemRegister: {
- if (unpacked.data.type != MSGPACK_OBJECT_MAP) {
- semsg(_(READERR("register", "is not a dictionary")), initial_fpos);
+ Dict(_shada_register) it = { 0 };
+ if (!unpack_keydict(&it, DictHash(_shada_register), &ad, &read_ptr, &read_size, &error_alloc)) {
+ semsg(_(READERR("register", "%s")), initial_fpos, error_alloc);
+ kv_destroy(it.rc);
goto shada_read_next_item_error;
}
- garray_T ad_ga;
- ga_init(&ad_ga, sizeof(*(unpacked.data.via.map.ptr)), 1);
- for (size_t i = 0; i < unpacked.data.via.map.size; i++) {
- CHECK_KEY_IS_STR(unpacked, "register")
- if (CHECK_KEY(unpacked.data.via.map.ptr[i].key,
- REG_KEY_CONTENTS)) {
- if (unpacked.data.via.map.ptr[i].val.type != MSGPACK_OBJECT_ARRAY) {
- semsg(_(READERR("register",
- "has " REG_KEY_CONTENTS
- " key with non-array value")),
- initial_fpos);
- CLEAR_GA_AND_ERROR_OUT(ad_ga);
- }
- if (unpacked.data.via.map.ptr[i].val.via.array.size == 0) {
- semsg(_(READERR("register",
- "has " REG_KEY_CONTENTS " key with empty array")),
- initial_fpos);
- CLEAR_GA_AND_ERROR_OUT(ad_ga);
- }
- const msgpack_object_array arr =
- unpacked.data.via.map.ptr[i].val.via.array;
- for (size_t j = 0; j < arr.size; j++) {
- if (arr.ptr[j].type != MSGPACK_OBJECT_BIN) {
- semsg(_(READERR("register", "has " REG_KEY_CONTENTS " array "
- "with non-binary value")), initial_fpos);
- CLEAR_GA_AND_ERROR_OUT(ad_ga);
- }
- }
- entry->data.reg.contents_size = arr.size;
- entry->data.reg.contents = xmalloc(arr.size * sizeof(char *));
- for (size_t j = 0; j < arr.size; j++) {
- entry->data.reg.contents[j] = BIN_CONVERTED(arr.ptr[j].via.bin);
- }
- }
- BOOLEAN_KEY(unpacked, "register", REG_KEY_UNNAMED,
- entry->data.reg.is_unnamed)
- TYPED_KEY(unpacked, "register", REG_KEY_TYPE, "an unsigned integer",
- entry->data.reg.type, POSITIVE_INTEGER, u64, TOU8)
- TYPED_KEY(unpacked, "register", KEY_NAME_CHAR, "an unsigned integer",
- entry->data.reg.name, POSITIVE_INTEGER, u64, TOCHAR)
- TYPED_KEY(unpacked, "register", REG_KEY_WIDTH, "an unsigned integer",
- entry->data.reg.width, POSITIVE_INTEGER, u64, TOSIZE)
- ADDITIONAL_KEY(unpacked)
- }
- if (entry->data.reg.contents == NULL) {
- semsg(_(READERR("register", "has missing " REG_KEY_CONTENTS " array")),
+ if (it.rc.size == 0) {
+ semsg(_(READERR("register",
+ "has " KEY_NAME(REG_KEY_CONTENTS) " key with missing or empty array")),
initial_fpos);
- CLEAR_GA_AND_ERROR_OUT(ad_ga);
+ goto shada_read_next_item_error;
+ }
+ entry->data.reg.contents_size = it.rc.size;
+ entry->data.reg.contents = xmalloc(it.rc.size * sizeof(char *));
+ for (size_t j = 0; j < it.rc.size; j++) {
+ entry->data.reg.contents[j] = xmemdupz(it.rc.items[j].data, it.rc.items[j].size);
}
- SET_ADDITIONAL_DATA(entry->data.reg.additional_data, "register");
+ kv_destroy(it.rc);
+
+#define REGISTER_VAL(name, loc, type) \
+ if (HAS_KEY(&it, _shada_register, name)) { \
+ loc = (type)it.name; \
+ }
+ REGISTER_VAL(REG_KEY_UNNAMED, entry->data.reg.is_unnamed, bool)
+ REGISTER_VAL(REG_KEY_TYPE, entry->data.reg.type, uint8_t)
+ REGISTER_VAL(KEY_NAME_CHAR, entry->data.reg.name, char)
+ REGISTER_VAL(REG_KEY_WIDTH, entry->data.reg.width, size_t)
break;
}
case kSDItemHistoryEntry: {
- if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
- semsg(_(READERR("history", "is not an array")), initial_fpos);
- goto shada_read_next_item_error;
- }
- if (unpacked.data.via.array.size < 2) {
- semsg(_(READERR("history", "does not have enough elements")),
- initial_fpos);
+ ssize_t len = unpack_array(&read_ptr, &read_size);
+
+ if (len < 2) {
+ semsg(_(READERR("history", "is not an array with enough elements")), initial_fpos);
goto shada_read_next_item_error;
}
- if (unpacked.data.via.array.ptr[0].type
- != MSGPACK_OBJECT_POSITIVE_INTEGER) {
- semsg(_(READERR("history", "has wrong history type type")),
- initial_fpos);
+ Integer hist_type;
+ if (!unpack_integer(&read_ptr, &read_size, &hist_type)) {
+ semsg(_(READERR("history", "has wrong history type type")), initial_fpos);
goto shada_read_next_item_error;
}
- if (unpacked.data.via.array.ptr[1].type
- != MSGPACK_OBJECT_BIN) {
- semsg(_(READERR("history", "has wrong history string type")),
- initial_fpos);
+ const String item = unpack_string(&read_ptr, &read_size);
+ if (!item.data) {
+ semsg(_(READERR("history", "has wrong history string type")), initial_fpos);
goto shada_read_next_item_error;
}
- if (memchr(unpacked.data.via.array.ptr[1].via.bin.ptr, 0,
- unpacked.data.via.array.ptr[1].via.bin.size) != NULL) {
- semsg(_(READERR("history", "contains string with zero byte inside")),
- initial_fpos);
+ if (memchr(item.data, 0, item.size) != NULL) {
+ semsg(_(READERR("history", "contains string with zero byte inside")), initial_fpos);
goto shada_read_next_item_error;
}
- entry->data.history_item.histtype =
- (uint8_t)unpacked.data.via.array.ptr[0].via.u64;
- const bool is_hist_search =
- entry->data.history_item.histtype == HIST_SEARCH;
+ entry->data.history_item.histtype = (uint8_t)hist_type;
+ const bool is_hist_search = entry->data.history_item.histtype == HIST_SEARCH;
if (is_hist_search) {
- if (unpacked.data.via.array.size < 3) {
+ if (len < 3) {
semsg(_(READERR("search history",
"does not have separator character")), initial_fpos);
goto shada_read_next_item_error;
}
- if (unpacked.data.via.array.ptr[2].type
- != MSGPACK_OBJECT_POSITIVE_INTEGER) {
- semsg(_(READERR("search history",
- "has wrong history separator type")), initial_fpos);
+ Integer sep_type;
+ if (!unpack_integer(&read_ptr, &read_size, &sep_type)) {
+ semsg(_(READERR("search history", "has wrong history separator type")), initial_fpos);
goto shada_read_next_item_error;
}
- entry->data.history_item.sep =
- (char)unpacked.data.via.array.ptr[2].via.u64;
+ entry->data.history_item.sep = (char)sep_type;
}
- size_t strsize;
- strsize = (
- unpacked.data.via.array.ptr[1].via.bin.size
- + 1 // Zero byte
- + 1); // Separator character
+ size_t strsize = (item.size
+ + 1 // Zero byte
+ + 1); // Separator character
entry->data.history_item.string = xmalloc(strsize);
- memcpy(entry->data.history_item.string,
- unpacked.data.via.array.ptr[1].via.bin.ptr,
- unpacked.data.via.array.ptr[1].via.bin.size);
+ memcpy(entry->data.history_item.string, item.data, item.size);
entry->data.history_item.string[strsize - 2] = 0;
- entry->data.history_item.string[strsize - 1] =
- entry->data.history_item.sep;
- SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, (2 + is_hist_search),
- entry->data.history_item.additional_elements,
- "history");
+ entry->data.history_item.string[strsize - 1] = entry->data.history_item.sep;
+ read_additional_array_elements = (uint32_t)(len - (2 + is_hist_search));
break;
}
case kSDItemVariable: {
- if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
- semsg(_(READERR("variable", "is not an array")), initial_fpos);
- goto shada_read_next_item_error;
- }
- if (unpacked.data.via.array.size < 2) {
- semsg(_(READERR("variable", "does not have enough elements")),
- initial_fpos);
+ ssize_t len = unpack_array(&read_ptr, &read_size);
+
+ if (len < 2) {
+ semsg(_(READERR("variable", "is not an array with enough elements")), initial_fpos);
goto shada_read_next_item_error;
}
- if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) {
- semsg(_(READERR("variable", "has wrong variable name type")),
- initial_fpos);
+
+ String name = unpack_string(&read_ptr, &read_size);
+
+ if (!name.data) {
+ semsg(_(READERR("variable", "has wrong variable name type")), initial_fpos);
goto shada_read_next_item_error;
}
- entry->data.global_var.name =
- xmemdupz(unpacked.data.via.array.ptr[0].via.bin.ptr,
- unpacked.data.via.array.ptr[0].via.bin.size);
- SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, 2,
- entry->data.global_var.additional_elements,
- "variable");
+ entry->data.global_var.name = xmemdupz(name.data, name.size);
+
+ String binval = unpack_string(&read_ptr, &read_size);
+
bool is_blob = false;
- // A msgpack BIN could be a String or Blob; an additional VAR_TYPE_BLOB
- // element is stored with Blobs which can be used to differentiate them
- if (unpacked.data.via.array.ptr[1].type == MSGPACK_OBJECT_BIN) {
- const listitem_T *type_item
- = tv_list_first(entry->data.global_var.additional_elements);
- if (type_item != NULL) {
- const typval_T *type_tv = TV_LIST_ITEM_TV(type_item);
- if (type_tv->v_type != VAR_NUMBER
- || type_tv->vval.v_number != VAR_TYPE_BLOB) {
+ if (binval.data) {
+ if (len > 2) {
+ // A msgpack BIN could be a String or Blob; an additional VAR_TYPE_BLOB
+ // element is stored with Blobs which can be used to differentiate them
+ Integer type;
+ if (!unpack_integer(&read_ptr, &read_size, &type) || type != VAR_TYPE_BLOB) {
semsg(_(READERR("variable", "has wrong variable type")),
initial_fpos);
goto shada_read_next_item_error;
}
is_blob = true;
}
+ entry->data.global_var.value = decode_string(binval.data, binval.size, is_blob, false);
+ } else {
+ int status = unpack_typval(&read_ptr, &read_size, &entry->data.global_var.value);
+ if (status != MPACK_OK) {
+ semsg(_(READERR("variable", "has value that cannot "
+ "be converted to the Vimscript value")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
}
- if (is_blob) {
- const msgpack_object_bin *const bin
- = &unpacked.data.via.array.ptr[1].via.bin;
- blob_T *const blob = tv_blob_alloc();
- ga_concat_len(&blob->bv_ga, bin->ptr, (size_t)bin->size);
- tv_blob_set_ret(&entry->data.global_var.value, blob);
- } else if (msgpack_to_vim(unpacked.data.via.array.ptr[1],
- &(entry->data.global_var.value)) == FAIL) {
- semsg(_(READERR("variable", "has value that cannot "
- "be converted to the Vimscript value")), initial_fpos);
- goto shada_read_next_item_error;
- }
+ read_additional_array_elements = (uint32_t)(len - 2 - (is_blob ? 1 : 0));
break;
}
- case kSDItemSubString:
- if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
- semsg(_(READERR("sub string", "is not an array")), initial_fpos);
- goto shada_read_next_item_error;
- }
- if (unpacked.data.via.array.size < 1) {
- semsg(_(READERR("sub string", "does not have enough elements")),
- initial_fpos);
+ case kSDItemSubString: {
+ ssize_t len = unpack_array(&read_ptr, &read_size);
+
+ if (len < 1) {
+ semsg(_(READERR("sub string", "is not an array with enough elements")), initial_fpos);
goto shada_read_next_item_error;
}
- if (unpacked.data.via.array.ptr[0].type != MSGPACK_OBJECT_BIN) {
- semsg(_(READERR("sub string", "has wrong sub string type")),
- initial_fpos);
+
+ String sub = unpack_string(&read_ptr, &read_size);
+ if (!sub.data) {
+ semsg(_(READERR("sub string", "has wrong sub string type")), initial_fpos);
goto shada_read_next_item_error;
}
- entry->data.sub_string.sub =
- BIN_CONVERTED(unpacked.data.via.array.ptr[0].via.bin);
- SET_ADDITIONAL_ELEMENTS(unpacked.data.via.array, 1,
- entry->data.sub_string.additional_elements,
- "sub string");
+ entry->data.sub_string.sub = xmemdupz(sub.data, sub.size);
+ read_additional_array_elements = (uint32_t)(len - 1);
break;
- case kSDItemBufferList:
- if (unpacked.data.type != MSGPACK_OBJECT_ARRAY) {
+ }
+ case kSDItemBufferList: {
+ ssize_t len = unpack_array(&read_ptr, &read_size);
+ if (len < 0) {
semsg(_(READERR("buffer list", "is not an array")), initial_fpos);
goto shada_read_next_item_error;
}
- if (unpacked.data.via.array.size == 0) {
+ if (len == 0) {
break;
}
- entry->data.buffer_list.buffers =
- xcalloc(unpacked.data.via.array.size,
- sizeof(*entry->data.buffer_list.buffers));
- for (size_t i = 0; i < unpacked.data.via.array.size; i++) {
+ entry->data.buffer_list.buffers = xcalloc((size_t)len,
+ sizeof(*entry->data.buffer_list.buffers));
+ for (size_t i = 0; i < (size_t)len; i++) {
entry->data.buffer_list.size++;
- msgpack_unpacked unpacked_2 = (msgpack_unpacked) {
- .data = unpacked.data.via.array.ptr[i],
- };
- {
- if (unpacked_2.data.type != MSGPACK_OBJECT_MAP) {
- semsg(_(RERR "Error while reading ShaDa file: "
- "buffer list at position %" PRIu64 " "
- "contains entry that is not a dictionary"),
- initial_fpos);
- goto shada_read_next_item_error;
- }
- entry->data.buffer_list.buffers[i].pos = default_pos;
- garray_T ad_ga;
- ga_init(&ad_ga, sizeof(*(unpacked_2.data.via.map.ptr)), 1);
- {
- // XXX: Temporarily reassign `i` because the macros depend on it.
- const size_t j = i;
- {
- for (i = 0; i < unpacked_2.data.via.map.size; i++) {
- CHECK_KEY_IS_STR(unpacked_2, "buffer list entry")
- INTEGER_KEY(unpacked_2, "buffer list entry", KEY_LNUM,
- entry->data.buffer_list.buffers[j].pos.lnum)
- INTEGER_KEY(unpacked_2, "buffer list entry", KEY_COL,
- entry->data.buffer_list.buffers[j].pos.col)
- STRING_KEY(unpacked_2, "buffer list entry", KEY_FILE,
- entry->data.buffer_list.buffers[j].fname)
- ADDITIONAL_KEY(unpacked_2)
- }
- }
- i = j; // XXX: Restore `i`.
- }
- if (entry->data.buffer_list.buffers[i].pos.lnum <= 0) {
- semsg(_(RERR "Error while reading ShaDa file: "
- "buffer list at position %" PRIu64 " "
- "contains entry with invalid line number"),
- initial_fpos);
- CLEAR_GA_AND_ERROR_OUT(ad_ga);
- }
- if (entry->data.buffer_list.buffers[i].pos.col < 0) {
- semsg(_(RERR "Error while reading ShaDa file: "
- "buffer list at position %" PRIu64 " "
- "contains entry with invalid column number"),
- initial_fpos);
- CLEAR_GA_AND_ERROR_OUT(ad_ga);
- }
- if (entry->data.buffer_list.buffers[i].fname == NULL) {
- semsg(_(RERR "Error while reading ShaDa file: "
- "buffer list at position %" PRIu64 " "
- "contains entry that does not have a file name"),
- initial_fpos);
- CLEAR_GA_AND_ERROR_OUT(ad_ga);
- }
- SET_ADDITIONAL_DATA(entry->data.buffer_list.buffers[i].additional_data,
- "buffer list entry");
+ Dict(_shada_buflist_item) it = { 0 };
+ AdditionalDataBuilder it_ad = KV_INITIAL_VALUE;
+ if (!unpack_keydict(&it, DictHash(_shada_buflist_item), &it_ad, &read_ptr, &read_size,
+ &error_alloc)) {
+ semsg(_(RERR "Error while reading ShaDa file: "
+ "buffer list at position %" PRIu64 " contains entry that %s"),
+ initial_fpos, error_alloc);
+ kv_destroy(it_ad);
+ goto shada_read_next_item_error;
+ }
+ struct buffer_list_buffer *e = &entry->data.buffer_list.buffers[i];
+ e->additional_data = (AdditionalData *)it_ad.items;
+ e->pos = default_pos;
+ if (HAS_KEY(&it, _shada_buflist_item, l)) {
+ e->pos.lnum = (linenr_T)it.l;
+ }
+ if (HAS_KEY(&it, _shada_buflist_item, c)) {
+ e->pos.col = (colnr_T)it.c;
+ }
+ if (HAS_KEY(&it, _shada_buflist_item, f)) {
+ e->fname = xmemdupz(it.f.data, it.f.size);
+ }
+
+ if (e->pos.lnum <= 0) {
+ semsg(_(RERR "Error while reading ShaDa file: "
+ "buffer list at position %" PRIu64 " "
+ "contains entry with invalid line number"),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (e->pos.col < 0) {
+ semsg(_(RERR "Error while reading ShaDa file: "
+ "buffer list at position %" PRIu64 " "
+ "contains entry with invalid column number"),
+ initial_fpos);
+ goto shada_read_next_item_error;
+ }
+ if (e->fname == NULL) {
+ semsg(_(RERR "Error while reading ShaDa file: "
+ "buffer list at position %" PRIu64 " "
+ "contains entry that does not have a file name"),
+ initial_fpos);
+ goto shada_read_next_item_error;
}
}
break;
+ }
case kSDItemMissing:
case kSDItemUnknown:
abort();
}
+
+ for (uint32_t i = 0; i < read_additional_array_elements; i++) {
+ const char *item_start = read_ptr;
+ int status = unpack_skip(&read_ptr, &read_size);
+ if (status) {
+ goto shada_read_next_item_error;
+ }
+
+ push_additional_data(&ad, item_start, (size_t)(read_ptr - item_start));
+ }
+
+ if (read_size) {
+ semsg(_(READERR("item", "additional bytes")), initial_fpos);
+ goto shada_read_next_item_error;
+ }
+
entry->type = (ShadaEntryType)type_u64;
+ entry->additional_data = (AdditionalData *)ad.items;
ret = kSDReadStatusSuccess;
shada_read_next_item_end:
- if (buf != NULL) {
- msgpack_unpacked_destroy(&unpacked);
+ if (buf_allocated) {
xfree(buf);
}
return ret;
@@ -3955,26 +3525,10 @@ shada_read_next_item_error:
entry->type = (ShadaEntryType)type_u64;
shada_free_shada_entry(entry);
entry->type = kSDItemMissing;
+ xfree(error_alloc);
+ kv_destroy(ad);
goto shada_read_next_item_end;
}
-#undef BIN_CONVERTED
-#undef CHECK_KEY
-#undef BOOLEAN_KEY
-#undef CONVERTED_STRING_KEY
-#undef STRING_KEY
-#undef ADDITIONAL_KEY
-#undef ID
-#undef BINDUP
-#undef TOCHAR
-#undef TOINT
-#undef TYPED_KEY
-#undef INT_KEY
-#undef INTEGER_KEY
-#undef TOU8
-#undef TOSIZE
-#undef SET_ADDITIONAL_DATA
-#undef SET_ADDITIONAL_ELEMENTS
-#undef CLEAR_GA_AND_ERROR_OUT
/// Check whether "name" is on removable media (according to 'shada')
///
@@ -4051,9 +3605,9 @@ static inline size_t shada_init_jumps(PossiblyFreedShadaEntry *jumps,
.name = NUL,
.mark = fm.fmark.mark,
.fname = (char *)fname,
- .additional_data = fm.fmark.additional_data,
}
- }
+ },
+ .additional_data = fm.fmark.additional_data,
}
};
} while (jump_iter != NULL);
@@ -4063,13 +3617,12 @@ static inline size_t shada_init_jumps(PossiblyFreedShadaEntry *jumps,
/// Write registers ShaDa entries in given msgpack_sbuffer.
///
/// @param[in] sbuf target msgpack_sbuffer to write to.
-void shada_encode_regs(msgpack_sbuffer *const sbuf)
+String shada_encode_regs(void)
FUNC_ATTR_NONNULL_ALL
{
WriteMergerState *const wms = xcalloc(1, sizeof(*wms));
shada_initialize_registers(wms, -1);
- msgpack_packer packer;
- msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
+ PackerBuffer packer = packer_string_buffer();
for (size_t i = 0; i < ARRAY_SIZE(wms->registers); i++) {
if (wms->registers[i].data.type == kSDItemRegister) {
if (kSDWriteFailed
@@ -4079,52 +3632,53 @@ void shada_encode_regs(msgpack_sbuffer *const sbuf)
}
}
xfree(wms);
+ return packer_take_string(&packer);
}
/// Write jumplist ShaDa entries in given msgpack_sbuffer.
///
/// @param[in] sbuf target msgpack_sbuffer to write to.
-void shada_encode_jumps(msgpack_sbuffer *const sbuf)
+String shada_encode_jumps(void)
FUNC_ATTR_NONNULL_ALL
{
Set(ptr_t) removable_bufs = SET_INIT;
find_removable_bufs(&removable_bufs);
PossiblyFreedShadaEntry jumps[JUMPLISTSIZE];
size_t jumps_size = shada_init_jumps(jumps, &removable_bufs);
- msgpack_packer packer;
- msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
+ PackerBuffer packer = packer_string_buffer();
for (size_t i = 0; i < jumps_size; i++) {
if (kSDWriteFailed == shada_pack_pfreed_entry(&packer, jumps[i], 0)) {
abort();
}
}
+ return packer_take_string(&packer);
}
/// Write buffer list ShaDa entry in given msgpack_sbuffer.
///
/// @param[in] sbuf target msgpack_sbuffer to write to.
-void shada_encode_buflist(msgpack_sbuffer *const sbuf)
+String shada_encode_buflist(void)
FUNC_ATTR_NONNULL_ALL
{
Set(ptr_t) removable_bufs = SET_INIT;
find_removable_bufs(&removable_bufs);
ShadaEntry buflist_entry = shada_get_buflist(&removable_bufs);
- msgpack_packer packer;
- msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
+
+ PackerBuffer packer = packer_string_buffer();
if (kSDWriteFailed == shada_pack_entry(&packer, buflist_entry, 0)) {
abort();
}
xfree(buflist_entry.data.buffer_list.buffers);
+ return packer_take_string(&packer);
}
/// Write global variables ShaDa entries in given msgpack_sbuffer.
///
/// @param[in] sbuf target msgpack_sbuffer to write to.
-void shada_encode_gvars(msgpack_sbuffer *const sbuf)
+String shada_encode_gvars(void)
FUNC_ATTR_NONNULL_ALL
{
- msgpack_packer packer;
- msgpack_packer_init(&packer, sbuf, msgpack_sbuffer_write);
+ PackerBuffer packer = packer_string_buffer();
const void *var_iter = NULL;
const Timestamp cur_timestamp = os_time();
do {
@@ -4145,9 +3699,9 @@ void shada_encode_gvars(msgpack_sbuffer *const sbuf)
.global_var = {
.name = (char *)name,
.value = tgttv,
- .additional_elements = NULL,
}
- }
+ },
+ .additional_data = NULL,
}, 0);
if (kSDWriteFailed == r) {
abort();
@@ -4156,77 +3710,21 @@ void shada_encode_gvars(msgpack_sbuffer *const sbuf)
}
tv_clear(&vartv);
} while (var_iter != NULL);
+ return packer_take_string(&packer);
}
-/// Wrapper for reading from msgpack_sbuffer.
-///
-/// @return number of bytes read.
-static ptrdiff_t read_sbuf(ShaDaReadDef *const sd_reader, void *const dest, const size_t size)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
-{
- msgpack_sbuffer *sbuf = (msgpack_sbuffer *)sd_reader->cookie;
- const uintmax_t bytes_read = MIN(size, sbuf->size - sd_reader->fpos);
- if (bytes_read < size) {
- sd_reader->eof = true;
- }
- memcpy(dest, sbuf->data + sd_reader->fpos, (size_t)bytes_read);
- sd_reader->fpos += bytes_read;
- return (ptrdiff_t)bytes_read;
-}
-
-/// Wrapper for read that ignores bytes read from msgpack_sbuffer.
-///
-/// Used for skipping.
-///
-/// @param[in,out] sd_reader ShaDaReadDef with msgpack_sbuffer.
-/// @param[in] offset Amount of bytes to skip.
-///
-/// @return FAIL in case of failure, OK in case of success. May set
-/// sd_reader->eof.
-static int sd_sbuf_reader_skip_read(ShaDaReadDef *const sd_reader, const size_t offset)
- FUNC_ATTR_NONNULL_ALL FUNC_ATTR_WARN_UNUSED_RESULT
-{
- msgpack_sbuffer *sbuf = (msgpack_sbuffer *)sd_reader->cookie;
- assert(sbuf->size >= sd_reader->fpos);
- const uintmax_t skip_bytes = MIN(offset, sbuf->size - sd_reader->fpos);
- if (skip_bytes < offset) {
- sd_reader->eof = true;
- return FAIL;
- }
- sd_reader->fpos += offset;
- return OK;
-}
-
-/// Prepare ShaDaReadDef with msgpack_sbuffer for reading.
-///
-/// @param[in] sbuf msgpack_sbuffer to read from.
-/// @param[out] sd_reader Location where reader structure will be saved.
-static void open_shada_sbuf_for_reading(const msgpack_sbuffer *const sbuf, ShaDaReadDef *sd_reader)
- FUNC_ATTR_NONNULL_ALL
-{
- *sd_reader = (ShaDaReadDef) {
- .read = &read_sbuf,
- .close = NULL,
- .skip = &sd_sbuf_reader_skip_read,
- .error = NULL,
- .eof = false,
- .fpos = 0,
- .cookie = (void *)sbuf,
- };
-}
-
-/// Read ShaDa from msgpack_sbuffer.
+/// Read ShaDa from String.
///
-/// @param[in] file msgpack_sbuffer to read from.
+/// @param[in] string string to read from.
/// @param[in] flags Flags, see ShaDaReadFileFlags enum.
-void shada_read_sbuf(msgpack_sbuffer *const sbuf, const int flags)
+void shada_read_string(String string, const int flags)
FUNC_ATTR_NONNULL_ALL
{
- assert(sbuf != NULL);
- if (sbuf->data == NULL) {
+ if (string.size == 0) {
return;
}
- ShaDaReadDef sd_reader;
- open_shada_sbuf_for_reading(sbuf, &sd_reader);
+ FileDescriptor sd_reader;
+ file_open_buffer(&sd_reader, string.data, string.size);
shada_read(&sd_reader, flags);
+ close_file(&sd_reader);
}
diff --git a/src/nvim/shada.h b/src/nvim/shada.h
index d7cac24afc..7d736dadc7 100644
--- a/src/nvim/shada.h
+++ b/src/nvim/shada.h
@@ -1,6 +1,6 @@
#pragma once
-#include <msgpack.h> // IWYU pragma: keep
+#include "nvim/api/private/defs.h"
/// Flags for shada_read_file and children
typedef enum {
diff --git a/src/nvim/sign.c b/src/nvim/sign.c
index 4e6672c5dd..b4ba7833e9 100644
--- a/src/nvim/sign.c
+++ b/src/nvim/sign.c
@@ -21,6 +21,7 @@
#include "nvim/decoration_defs.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -125,8 +126,8 @@ static void buf_set_sign(buf_T *buf, uint32_t *id, char *group, int prio, linenr
| (has_hl ? MT_FLAG_DECOR_SIGNHL : 0);
DecorInline decor = { .ext = true, .data.ext = { .vt = NULL, .sh_idx = decor_put_sh(sign) } };
- extmark_set(buf, ns, id, lnum - 1, 0, -1, -1, decor, decor_flags, true,
- false, true, true, false, NULL);
+ extmark_set(buf, ns, id, MIN(buf->b_ml.ml_line_count, lnum) - 1, 0, -1, -1,
+ decor, decor_flags, true, false, true, true, NULL);
}
/// For an existing, placed sign with "id", modify the sign, group or priority.
@@ -246,12 +247,6 @@ static int buf_delete_signs(buf_T *buf, char *group, int id, linenr_T atlnum)
return FAIL;
}
- // When deleting the last sign need to redraw the windows to remove the
- // sign column. Not when curwin is NULL (this means we're exiting).
- if (!buf_meta_total(buf, kMTMetaSignText) && curwin != NULL) {
- changed_line_abv_curs();
- }
-
return OK;
}
@@ -297,8 +292,8 @@ static void sign_list_placed(buf_T *rbuf, char *group)
qsort((void *)&kv_A(signs, 0), kv_size(signs), sizeof(MTKey), sign_row_cmp);
for (size_t i = 0; i < kv_size(signs); i++) {
- namebuf[0] = '\0';
- groupbuf[0] = '\0';
+ namebuf[0] = NUL;
+ groupbuf[0] = NUL;
MTKey mark = kv_A(signs, i);
DecorSignHighlight *sh = decor_find_sign(mt_decor(mark));
@@ -381,7 +376,7 @@ int init_sign_text(sign_T *sp, schar_T *sign_text, char *text)
if (!vim_isprintc(c)) {
break;
}
- int width = utf_char2cells(c);
+ int width = utf_ptr2cells(s);
if (width == 2) {
sign_text[cells + 1] = 0;
}
@@ -406,22 +401,16 @@ int init_sign_text(sign_T *sp, schar_T *sign_text, char *text)
/// Define a new sign or update an existing sign
static int sign_define_by_name(char *name, char *icon, char *text, char *linehl, char *texthl,
- char *culhl, char *numhl)
+ char *culhl, char *numhl, int prio)
{
cstr_t *key;
- sign_T **sp = (sign_T **)pmap_put_ref(cstr_t)(&sign_map, name, &key, NULL);
+ bool new_sign = false;
+ sign_T **sp = (sign_T **)pmap_put_ref(cstr_t)(&sign_map, name, &key, &new_sign);
- if (*sp == NULL) {
+ if (new_sign) {
*key = xstrdup(name);
*sp = xcalloc(1, sizeof(sign_T));
(*sp)->sn_name = (char *)(*key);
- } else {
- // Signs may already exist, a redraw is needed in windows with a non-empty sign list.
- FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
- if (buf_has_signs(wp->w_buffer)) {
- redraw_buf_later(wp->w_buffer, UPD_NOT_VALID);
- }
- }
}
// Set values for a defined sign.
@@ -436,6 +425,8 @@ static int sign_define_by_name(char *name, char *icon, char *text, char *linehl,
return FAIL;
}
+ (*sp)->sn_priority = prio;
+
char *arg[] = { linehl, texthl, culhl, numhl };
int *hl[] = { &(*sp)->sn_line_hl, &(*sp)->sn_text_hl, &(*sp)->sn_cul_hl, &(*sp)->sn_num_hl };
for (int i = 0; i < 4; i++) {
@@ -444,6 +435,28 @@ static int sign_define_by_name(char *name, char *icon, char *text, char *linehl,
}
}
+ // Update already placed signs and redraw if necessary when modifying a sign.
+ if (!new_sign) {
+ bool did_redraw = false;
+ for (size_t i = 0; i < kv_size(decor_items); i++) {
+ DecorSignHighlight *sh = &kv_A(decor_items, i);
+ if (sh->sign_name && strcmp(sh->sign_name, name) == 0) {
+ memcpy(sh->text, (*sp)->sn_text, SIGN_WIDTH * sizeof(schar_T));
+ sh->hl_id = (*sp)->sn_text_hl;
+ sh->line_hl_id = (*sp)->sn_line_hl;
+ sh->number_hl_id = (*sp)->sn_num_hl;
+ sh->cursorline_hl_id = (*sp)->sn_cul_hl;
+ if (!did_redraw) {
+ FOR_ALL_WINDOWS_IN_TAB(wp, curtab) {
+ if (buf_has_signs(wp->w_buffer)) {
+ redraw_buf_later(wp->w_buffer, UPD_NOT_VALID);
+ }
+ }
+ did_redraw = true;
+ }
+ }
+ }
+ }
return OK;
}
@@ -477,6 +490,11 @@ static void sign_list_defined(sign_T *sp)
describe_sign_text(buf, sp->sn_text);
msg_outtrans(buf, 0);
}
+ if (sp->sn_priority > 0) {
+ char lbuf[MSG_BUF_LEN];
+ vim_snprintf(lbuf, MSG_BUF_LEN, " priority=%d", sp->sn_priority);
+ msg_puts(lbuf);
+ }
static char *arg[] = { " linehl=", " texthl=", " culhl=", " numhl=" };
int hl[] = { sp->sn_line_hl, sp->sn_text_hl, sp->sn_cul_hl, sp->sn_num_hl };
for (int i = 0; i < 4; i++) {
@@ -499,22 +517,11 @@ static void sign_list_by_name(char *name)
}
}
-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;
- }
-}
-
/// Place a sign at the specified file location or update a sign.
static int sign_place(uint32_t *id, char *group, char *name, buf_T *buf, linenr_T lnum, int prio)
{
// Check for reserved character '*' in group name
- if (group != NULL && (*group == '*' || *group == '\0')) {
+ if (group != NULL && (*group == '*' || *group == NUL)) {
return FAIL;
}
@@ -524,6 +531,11 @@ static int sign_place(uint32_t *id, char *group, char *name, buf_T *buf, linenr_
return FAIL;
}
+ // Use the default priority value for this sign.
+ if (prio == -1) {
+ prio = (sp->sn_priority != -1) ? sp->sn_priority : SIGN_DEF_PRIO;
+ }
+
if (lnum > 0) {
// ":sign place {id} line={lnum} name={name} file={fname}": place a sign
buf_set_sign(buf, id, group, prio, lnum, sp);
@@ -531,11 +543,7 @@ static int sign_place(uint32_t *id, char *group, char *name, buf_T *buf, linenr_
// ":sign place {id} file={fname}": change sign type and/or priority
lnum = buf_mod_sign(buf, id, group, prio, sp);
}
- if (lnum > 0) {
- // 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 {
+ if (lnum <= 0) {
semsg(_("E885: Not possible to change sign %s"), name);
return FAIL;
}
@@ -562,13 +570,6 @@ static int sign_unplace_inner(buf_T *buf, int id, char *group, linenr_T atlnum)
}
}
- // 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_meta_total(buf, kMTMetaSignText)) {
- may_force_numberwidth_recompute(buf, true);
- }
-
return OK;
}
@@ -629,6 +630,7 @@ static void sign_define_cmd(char *name, char *cmdline)
char *texthl = NULL;
char *culhl = NULL;
char *numhl = NULL;
+ int prio = -1;
// set values for a defined sign.
while (true) {
@@ -649,6 +651,8 @@ static void sign_define_cmd(char *name, char *cmdline)
culhl = arg + 6;
} else if (strncmp(arg, "numhl=", 6) == 0) {
numhl = arg + 6;
+ } else if (strncmp(arg, "priority=", 9) == 0) {
+ prio = atoi(arg + 9);
} else {
semsg(_(e_invarg2), arg);
return;
@@ -659,7 +663,7 @@ static void sign_define_cmd(char *name, char *cmdline)
*cmdline++ = NUL;
}
- sign_define_by_name(name, icon, text, linehl, texthl, culhl, numhl);
+ sign_define_by_name(name, icon, text, linehl, texthl, culhl, numhl, prio);
}
/// ":sign place" command
@@ -676,14 +680,14 @@ static void sign_place_cmd(buf_T *buf, linenr_T lnum, char *name, int id, char *
// :sign place
// :sign place group={group}
// :sign place group=*
- if (lnum >= 0 || name != NULL || (group != NULL && *group == '\0')) {
+ if (lnum >= 0 || name != NULL || (group != NULL && *group == NUL)) {
emsg(_(e_invarg));
} else {
sign_list_placed(buf, group);
}
} else {
// Place a new sign
- if (name == NULL || buf == NULL || (group != NULL && *group == '\0')) {
+ if (name == NULL || buf == NULL || (group != NULL && *group == NUL)) {
emsg(_(e_invarg));
return;
}
@@ -695,7 +699,7 @@ static void sign_place_cmd(buf_T *buf, linenr_T lnum, char *name, int id, char *
/// ":sign unplace" command
static void sign_unplace_cmd(buf_T *buf, linenr_T lnum, const char *name, int id, char *group)
{
- if (lnum >= 0 || name != NULL || (group != NULL && *group == '\0')) {
+ if (lnum >= 0 || name != NULL || (group != NULL && *group == NUL)) {
emsg(_(e_invarg));
return;
}
@@ -722,7 +726,7 @@ static void sign_jump_cmd(buf_T *buf, linenr_T lnum, const char *name, int id, c
return;
}
- if (buf == NULL || (group != NULL && *group == '\0') || lnum >= 0 || name != NULL) {
+ if (buf == NULL || (group != NULL && *group == NUL) || lnum >= 0 || name != NULL) {
// File or buffer is not specified or an empty group is used
// or a line number or a sign name is specified.
emsg(_(e_invarg));
@@ -874,7 +878,7 @@ void ex_sign(exarg_T *eap)
linenr_T lnum = -1;
char *name = NULL;
char *group = NULL;
- int prio = SIGN_DEF_PRIO;
+ int prio = -1;
buf_T *buf = NULL;
// Parse command line arguments
@@ -907,6 +911,9 @@ static dict_T *sign_get_info_dict(sign_T *sp)
describe_sign_text(buf, sp->sn_text);
tv_dict_add_str(d, S_LEN("text"), buf);
}
+ if (sp->sn_priority > 0) {
+ tv_dict_add_nr(d, S_LEN("priority"), sp->sn_priority);
+ }
static char *arg[] = { "linehl", "texthl", "culhl", "numhl" };
int hl[] = { sp->sn_line_hl, sp->sn_text_hl, sp->sn_cul_hl, sp->sn_num_hl };
for (int i = 0; i < 4; i++) {
@@ -1071,7 +1078,8 @@ char *get_sign_name(expand_T *xp, int idx)
case EXP_SUBCMD:
return cmds[idx];
case EXP_DEFINE: {
- char *define_arg[] = { "culhl=", "icon=", "linehl=", "numhl=", "text=", "texthl=", NULL };
+ char *define_arg[] = { "culhl=", "icon=", "linehl=", "numhl=", "text=", "texthl=",
+ "priority=", NULL };
return define_arg[idx];
}
case EXP_PLACE: {
@@ -1227,6 +1235,7 @@ static int sign_define_from_dict(char *name, dict_T *dict)
char *texthl = NULL;
char *culhl = NULL;
char *numhl = NULL;
+ int prio = -1;
if (dict != NULL) {
icon = tv_dict_get_string(dict, "icon", false);
@@ -1235,9 +1244,10 @@ static int sign_define_from_dict(char *name, dict_T *dict)
texthl = tv_dict_get_string(dict, "texthl", false);
culhl = tv_dict_get_string(dict, "culhl", false);
numhl = tv_dict_get_string(dict, "numhl", false);
+ prio = (int)tv_dict_get_number_def(dict, "priority", -1);
}
- return sign_define_by_name(name, icon, text, linehl, texthl, culhl, numhl) - 1;
+ return sign_define_by_name(name, icon, text, linehl, texthl, culhl, numhl, prio) - 1;
}
/// Define multiple signs using attributes from list 'l' and store the return
@@ -1343,7 +1353,7 @@ void f_sign_getplaced(typval_T *argvars, typval_T *rettv, EvalFuncData fptr)
if (group == NULL) {
return;
}
- if (*group == '\0') { // empty string means global group
+ if (*group == NUL) { // empty string means global group
group = NULL;
}
}
@@ -1469,7 +1479,7 @@ static int sign_place_from_dict(typval_T *id_tv, typval_T *group_tv, typval_T *n
}
}
- int prio = SIGN_DEF_PRIO;
+ int prio = -1;
di = tv_dict_find(dict, "priority", -1);
if (di != NULL) {
prio = (int)tv_get_number_chk(&di->di_tv, &notanum);
diff --git a/src/nvim/sign_defs.h b/src/nvim/sign_defs.h
index ad2a2b737d..f6712513b7 100644
--- a/src/nvim/sign_defs.h
+++ b/src/nvim/sign_defs.h
@@ -18,6 +18,7 @@ typedef struct {
int sn_text_hl; // highlight ID for text
int sn_cul_hl; // highlight ID for text on current line when 'cursorline' is set
int sn_num_hl; // highlight ID for line number
+ int sn_priority; // default priority of this sign, -1 means SIGN_DEF_PRIO
} sign_T;
typedef struct {
diff --git a/src/nvim/spell.c b/src/nvim/spell.c
index d7a6adef58..8ec28c7f61 100644
--- a/src/nvim/spell.c
+++ b/src/nvim/spell.c
@@ -72,6 +72,7 @@
#include "nvim/decoration.h"
#include "nvim/decoration_provider.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/ex_cmds.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/ex_docmd.h"
@@ -230,14 +231,6 @@ char *repl_to = NULL;
/// caller can skip over the word.
size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount)
{
- matchinf_T mi; // Most things are put in "mi" so that it can
- // be passed to functions quickly.
- size_t nrlen = 0; // found a number first
- size_t wrongcaplen = 0;
- bool count_word = docount;
- bool use_camel_case = (wp->w_s->b_p_spo_flags & SPO_CAMEL) != 0;
- bool is_camel_case = false;
-
// A word never starts at a space or a control character. Return quickly
// then, skipping over the character.
if ((uint8_t)(*ptr) <= ' ') {
@@ -249,6 +242,13 @@ size_t spell_check(win_T *wp, char *ptr, hlf_T *attrp, int *capcol, bool docount
return 1;
}
+ size_t nrlen = 0; // found a number first
+ size_t wrongcaplen = 0;
+ bool count_word = docount;
+ bool use_camel_case = (wp->w_s->b_p_spo_flags & SPO_CAMEL) != 0;
+ bool is_camel_case = false;
+
+ matchinf_T mi; // Most things are put in "mi" so that it can be passed to functions quickly.
CLEAR_FIELD(mi);
// A number is always OK. Also skip hexadecimal numbers 0xFF99 and
@@ -540,7 +540,6 @@ static void find_word(matchinf_T *mip, int mode)
int endlen[MAXWLEN]; // length at possible word endings
idx_T endidx[MAXWLEN]; // possible word endings
int endidxcnt = 0;
- int c;
// Repeat advancing in the tree until:
// - there is a byte that doesn't match,
@@ -582,7 +581,7 @@ static void find_word(matchinf_T *mip, int mode)
}
// Perform a binary search in the list of accepted bytes.
- c = (uint8_t)ptr[wlen];
+ int c = (uint8_t)ptr[wlen];
if (c == TAB) { // <Tab> is handled like <Space>
c = ' ';
}
@@ -626,9 +625,6 @@ static void find_word(matchinf_T *mip, int mode)
}
}
- char *p;
- bool word_ends;
-
// Verify that one of the possible endings is valid. Try the longest
// first.
while (endidxcnt > 0) {
@@ -639,6 +635,7 @@ static void find_word(matchinf_T *mip, int mode)
if (utf_head_off(ptr, ptr + wlen) > 0) {
continue; // not at first byte of character
}
+ bool word_ends;
if (spell_iswordp(ptr + wlen, mip->mi_win)) {
if (slang->sl_compprog == NULL && !slang->sl_nobreak) {
continue; // next char is a word character
@@ -655,7 +652,7 @@ static void find_word(matchinf_T *mip, int mode)
// Compute byte length in original word, length may change
// when folding case. This can be slow, take a shortcut when the
// case-folded word is equal to the keep-case word.
- p = mip->mi_word;
+ char *p = mip->mi_word;
if (strncmp(ptr, p, (size_t)wlen) != 0) {
for (char *s = ptr; s < ptr + wlen; MB_PTR_ADV(s)) {
MB_PTR_ADV(p);
@@ -691,10 +688,10 @@ static void find_word(matchinf_T *mip, int mode)
// When mode is FIND_PREFIX the word must support the prefix:
// check the prefix ID and the condition. Do that for the list at
// mip->mi_prefarridx that find_prefix() filled.
- c = valid_word_prefix(mip->mi_prefcnt, mip->mi_prefarridx,
- (int)flags,
- mip->mi_word + mip->mi_cprefixlen, slang,
- false);
+ int c = valid_word_prefix(mip->mi_prefcnt, mip->mi_prefarridx,
+ (int)flags,
+ mip->mi_word + mip->mi_cprefixlen, slang,
+ false);
if (c == 0) {
continue;
}
@@ -765,6 +762,7 @@ static void find_word(matchinf_T *mip, int mode)
if (mode == FIND_COMPOUND) {
int capflags;
+ char *p;
// Need to check the caps type of the appended compound
// word.
@@ -851,7 +849,7 @@ static void find_word(matchinf_T *mip, int mode)
// byte length in keep-case word. Length may change when
// folding case. This can be slow, take a shortcut when
// the case-folded word is equal to the keep-case word.
- p = mip->mi_fword;
+ char *p = mip->mi_fword;
if (strncmp(ptr, p, (size_t)wlen) != 0) {
for (char *s = ptr; s < ptr + wlen; MB_PTR_ADV(s)) {
MB_PTR_ADV(p);
@@ -1296,12 +1294,14 @@ static inline bool can_syn_spell(win_T *wp, linenr_T lnum, int col)
/// @return 0 if not found, length of the badly spelled word otherwise.
size_t spell_move_to(win_T *wp, int dir, smt_T behaviour, bool curline, hlf_T *attrp)
{
+ if (no_spell_checking(wp)) {
+ return 0;
+ }
+
pos_T found_pos;
size_t found_len = 0;
hlf_T attr = HLF_COUNT;
- size_t len;
bool has_syntax = syntax_present(wp);
- colnr_T col;
char *buf = NULL;
size_t buflen = 0;
int skip = 0;
@@ -1309,10 +1309,6 @@ size_t spell_move_to(win_T *wp, int dir, smt_T behaviour, bool curline, hlf_T *a
bool found_one = false;
bool wrapped = false;
- if (no_spell_checking(wp)) {
- return 0;
- }
-
size_t ret = 0;
// Start looking for bad word at the start of the line, because we can't
@@ -1342,7 +1338,7 @@ size_t spell_move_to(win_T *wp, int dir, smt_T behaviour, bool curline, hlf_T *a
while (!got_int) {
char *line = ml_get_buf(wp->w_buffer, lnum);
- len = (size_t)ml_get_buf_len(wp->w_buffer, lnum);
+ size_t len = (size_t)ml_get_buf_len(wp->w_buffer, lnum);
if (buflen < len + MAXWLEN + 2) {
xfree(buf);
buflen = len + MAXWLEN + 2;
@@ -1360,7 +1356,7 @@ size_t spell_move_to(win_T *wp, int dir, smt_T behaviour, bool curline, hlf_T *a
capcol = (colnr_T)getwhitecols(line);
} else if (curline && wp == curwin) {
// For spellbadword(): check if first word needs a capital.
- col = (colnr_T)getwhitecols(line);
+ colnr_T col = (colnr_T)getwhitecols(line);
if (check_need_cap(curwin, lnum, col)) {
capcol = col;
}
@@ -1409,7 +1405,7 @@ size_t spell_move_to(win_T *wp, int dir, smt_T behaviour, bool curline, hlf_T *a
|| ((colnr_T)(curline
? p - buf + (ptrdiff_t)len
: p - buf) > wp->w_cursor.col)) {
- col = (colnr_T)(p - buf);
+ colnr_T col = (colnr_T)(p - buf);
bool no_plain_buffer = (wp->w_s->b_p_spo_flags & SPO_NPBUFFER) != 0;
bool can_spell = !no_plain_buffer;
@@ -1816,17 +1812,16 @@ void count_common_word(slang_T *lp, char *word, int len, uint8_t count)
p = buf;
}
- wordcount_T *wc;
hash_T hash = hash_hash(p);
const size_t p_len = strlen(p);
hashitem_T *hi = hash_lookup(&lp->sl_wordcount, p, p_len, hash);
if (HASHITEM_EMPTY(hi)) {
- wc = xmalloc(offsetof(wordcount_T, wc_word) + p_len + 1);
+ wordcount_T *wc = xmalloc(offsetof(wordcount_T, wc_word) + p_len + 1);
memcpy(wc->wc_word, p, p_len + 1);
wc->wc_count = count;
hash_add_item(&lp->sl_wordcount, hi, wc->wc_word, hash);
} else {
- wc = HI2WC(hi);
+ wordcount_T *wc = HI2WC(hi);
wc->wc_count = (uint16_t)(wc->wc_count + count);
if (wc->wc_count < count) { // check for overflow
wc->wc_count = MAXWORDCOUNT;
@@ -1882,14 +1877,14 @@ int init_syl_tab(slang_T *slang)
static int count_syllables(slang_T *slang, const char *word)
FUNC_ATTR_NONNULL_ALL
{
- int cnt = 0;
- bool skip = false;
- int len;
-
if (slang->sl_syllable == NULL) {
return 0;
}
+ int cnt = 0;
+ bool skip = false;
+ int len;
+
for (const char *p = word; *p != NUL; p += len) {
// When running into a space reset counter.
if (*p == ' ') {
@@ -1929,25 +1924,14 @@ static int count_syllables(slang_T *slang, const char *word)
/// @return NULL if it's OK, an untranslated error message otherwise.
char *parse_spelllang(win_T *wp)
{
- garray_T ga;
- char *splp;
- char *region;
char region_cp[3];
- bool filename;
- int region_mask;
- slang_T *slang;
- int c;
char lang[MAXWLEN + 1];
char spf_name[MAXPATHL];
- char *p;
- int round;
- char *spf;
char *use_region = NULL;
bool dont_use_region = false;
bool nobreak = false;
static bool recursive = false;
char *ret_msg = NULL;
- char *spl_copy;
bufref_T bufref;
set_bufref(&bufref, wp->w_buffer);
@@ -1960,20 +1944,21 @@ char *parse_spelllang(win_T *wp)
}
recursive = true;
+ garray_T ga;
ga_init(&ga, sizeof(langp_T), 2);
clear_midword(wp);
// Make a copy of 'spelllang', the SpellFileMissing autocommands may change
// it under our fingers.
- spl_copy = xstrdup(wp->w_s->b_p_spl);
+ char *spl_copy = xstrdup(wp->w_s->b_p_spl);
wp->w_s->b_cjk = 0;
// Loop over comma separated language names.
- for (splp = spl_copy; *splp != NUL;) {
+ for (char *splp = spl_copy; *splp != NUL;) {
// Get one language name.
copy_option_part(&splp, lang, MAXWLEN, ",");
- region = NULL;
+ char *region = NULL;
int len = (int)strlen(lang);
if (!valid_spelllang(lang)) {
@@ -1985,6 +1970,8 @@ char *parse_spelllang(win_T *wp)
continue;
}
+ slang_T *slang;
+ bool filename;
// If the name ends in ".spl" use it as the name of the spell file.
// If there is a region name let "region" point to it and remove it
// from the name.
@@ -1992,7 +1979,7 @@ char *parse_spelllang(win_T *wp)
filename = true;
// Locate a region and remove it from the file name.
- p = vim_strchr(path_tail(lang), '_');
+ char *p = vim_strchr(path_tail(lang), '_');
if (p != NULL && ASCII_ISALPHA(p[1]) && ASCII_ISALPHA(p[2])
&& !ASCII_ISALPHA(p[3])) {
xstrlcpy(region_cp, p + 1, 3);
@@ -2055,10 +2042,10 @@ char *parse_spelllang(win_T *wp)
if (filename
? path_full_compare(lang, slang->sl_fname, false, true) == kEqualFiles
: STRICMP(lang, slang->sl_name) == 0) {
- region_mask = REGION_ALL;
+ int region_mask = REGION_ALL;
if (!filename && region != NULL) {
// find region in sl_regions
- c = find_region(slang->sl_regions, region);
+ int c = find_region(slang->sl_regions, region);
if (c == REGION_ALL) {
if (slang->sl_add) {
if (*slang->sl_regions != NUL) {
@@ -2094,8 +2081,8 @@ char *parse_spelllang(win_T *wp)
// round 1: load first name in 'spellfile'.
// round 2: load second name in 'spellfile.
// etc.
- spf = curwin->w_s->b_p_spf;
- for (round = 0; round == 0 || *spf != NUL; round++) {
+ char *spf = curwin->w_s->b_p_spf;
+ for (int round = 0; round == 0 || *spf != NUL; round++) {
if (round == 0) {
// Internal wordlist, if there is one.
if (int_wordlist == NULL) {
@@ -2105,11 +2092,12 @@ char *parse_spelllang(win_T *wp)
} else {
// One entry in 'spellfile'.
copy_option_part(&spf, spf_name, MAXPATHL - 5, ",");
- STRCAT(spf_name, ".spl");
+ strcat(spf_name, ".spl");
+ int c;
// If it was already found above then skip it.
for (c = 0; c < ga.ga_len; c++) {
- p = LANGP_ENTRY(ga, c)->lp_slang->sl_fname;
+ char *p = LANGP_ENTRY(ga, c)->lp_slang->sl_fname;
if (p != NULL
&& path_full_compare(spf_name, p, false, true) == kEqualFiles) {
break;
@@ -2120,6 +2108,8 @@ char *parse_spelllang(win_T *wp)
}
}
+ slang_T *slang;
+
// Check if it was loaded already.
for (slang = first_lang; slang != NULL; slang = slang->sl_next) {
if (path_full_compare(spf_name, slang->sl_fname, false, true)
@@ -2135,7 +2125,7 @@ char *parse_spelllang(win_T *wp)
STRCPY(lang, "internal wordlist");
} else {
xstrlcpy(lang, path_tail(spf_name), MAXWLEN + 1);
- p = vim_strchr(lang, '.');
+ char *p = vim_strchr(lang, '.');
if (p != NULL) {
*p = NUL; // truncate at ".encoding.add"
}
@@ -2149,10 +2139,10 @@ char *parse_spelllang(win_T *wp)
}
}
if (slang != NULL) {
- region_mask = REGION_ALL;
+ int region_mask = REGION_ALL;
if (use_region != NULL && !dont_use_region) {
// find region in sl_regions
- c = find_region(slang->sl_regions, use_region);
+ int c = find_region(slang->sl_regions, use_region);
if (c != REGION_ALL) {
region_mask = 1 << c;
} else if (*slang->sl_regions != NUL) {
@@ -2687,7 +2677,7 @@ void ex_spellrepall(exarg_T *eap)
char *p = xmalloc((size_t)get_cursor_line_len() + (size_t)addlen + 1);
memmove(p, line, (size_t)curwin->w_cursor.col);
STRCPY(p + curwin->w_cursor.col, repl_to);
- STRCAT(p, line + curwin->w_cursor.col + repl_from_len);
+ strcat(p, line + curwin->w_cursor.col + repl_from_len);
ml_replace(curwin->w_cursor.lnum, p, false);
inserted_bytes(curwin->w_cursor.lnum, curwin->w_cursor.col,
(int)repl_from_len, (int)repl_to_len);
@@ -2902,19 +2892,7 @@ static void spell_soundfold_sofo(slang_T *slang, const char *inword, char *res)
// Multi-byte version of spell_soundfold().
static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
{
- salitem_T *smp = (salitem_T *)slang->sl_sal.ga_data;
int word[MAXWLEN] = { 0 };
- int wres[MAXWLEN] = { 0 };
- int *ws;
- int *pf;
- int j, z;
- int reslen;
- int k = 0;
- int k0;
- int n0;
- int pri;
- int p0 = -333;
- int c0;
bool did_white = false;
// Convert the multi-byte string to a wide-character string.
@@ -2942,17 +2920,24 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
}
word[wordlen] = NUL;
+ salitem_T *smp = (salitem_T *)slang->sl_sal.ga_data;
+ int wres[MAXWLEN] = { 0 };
+ int k = 0;
+ int p0 = -333;
int c;
// This algorithm comes from Aspell phonet.cpp.
// Converted from C++ to C. Added support for multi-byte chars.
// Changed to keep spaces.
- int i = reslen = z = 0;
+ int i = 0;
+ int reslen = 0;
+ int z = 0;
while ((c = word[i]) != NUL) {
// Start with the first rule that has the character in the word.
int n = slang->sl_sal_first[c & 0xff];
int z0 = 0;
if (n >= 0) {
+ int *ws;
// Check all rules for the same index byte.
// If c is 0x300 need extra check for the end of the array, as
// (c & 0xff) is NUL.
@@ -2969,6 +2954,7 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
continue;
}
if (k > 2) {
+ int j;
for (j = 2; j < k; j++) {
if (word[i + j] != ws[j]) {
break;
@@ -2980,6 +2966,7 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
}
}
+ int *pf;
if ((pf = smp[n].sm_oneof_w) != NULL) {
// Check for match with one of the chars in "sm_oneof".
while (*pf != NUL && *pf != word[i + k]) {
@@ -2991,10 +2978,10 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
k++;
}
char *s = smp[n].sm_rules;
- pri = 5; // default priority
+ int pri = 5; // default priority
p0 = (uint8_t)(*s);
- k0 = k;
+ int k0 = k;
while (*s == '-' && k > 1) {
k--;
s++;
@@ -3022,8 +3009,8 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
&& (!spell_iswordp_w(word + i + k0, curwin)))) {
// search for followup rules, if:
// followup and k > 1 and NO '-' in searchstring
- c0 = word[i + k - 1];
- n0 = slang->sl_sal_first[c0 & 0xff];
+ int c0 = word[i + k - 1];
+ int n0 = slang->sl_sal_first[c0 & 0xff];
if (slang->sl_followup && k > 1 && n0 >= 0
&& p0 != '-' && word[i + k] != NUL) {
@@ -3042,6 +3029,7 @@ static void spell_soundfold_wsal(slang_T *slang, const char *inword, char *res)
}
if (k0 > 2) {
pf = word + i + k + 1;
+ int j;
for (j = 2; j < k0; j++) {
if (*pf++ != ws[j]) {
break;
@@ -3262,23 +3250,13 @@ void ex_spelldump(exarg_T *eap)
/// @param dumpflags_arg DUMPFLAG_*
void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
{
- langp_T *lp;
- slang_T *slang;
idx_T arridx[MAXWLEN];
int curi[MAXWLEN];
char word[MAXWLEN];
- int c;
- uint8_t *byts;
- idx_T *idxs;
linenr_T lnum = 0;
- int depth;
- int n;
- int flags;
char *region_names = NULL; // region names being used
bool do_region = true; // dump region names and numbers
- char *p;
int dumpflags = dumpflags_arg;
- int patlen;
// When ignoring case or when the pattern starts with capital pass this on
// to dump_word().
@@ -3286,7 +3264,7 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
if (ic) {
dumpflags |= DUMPFLAG_ICASE;
} else {
- n = captype(pat, NULL);
+ int n = captype(pat, NULL);
if (n == WF_ONECAP) {
dumpflags |= DUMPFLAG_ONECAP;
} else if (n == WF_ALLCAP
@@ -3299,8 +3277,8 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
// Find out if we can support regions: All languages must support the same
// regions or none at all.
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
- p = lp->lp_slang->sl_regions;
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ char *p = lp->lp_slang->sl_regions;
if (p[0] != 0) {
if (region_names == NULL) { // first language with regions
region_names = p;
@@ -3320,8 +3298,8 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
// Loop over all files loaded for the entries in 'spelllang'.
for (int lpi = 0; lpi < curwin->w_s->b_langp.ga_len; lpi++) {
- lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
- slang = lp->lp_slang;
+ langp_T *lp = LANGP_ENTRY(curwin->w_s->b_langp, lpi);
+ slang_T *slang = lp->lp_slang;
if (slang->sl_fbyts == NULL) { // reloading failed
continue;
}
@@ -3331,6 +3309,7 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
ml_append(lnum++, IObuff, 0, false);
}
+ int patlen;
// When matching with a pattern and there are no prefixes only use
// parts of the tree that match "pat".
if (pat != NULL && slang->sl_pbyts == NULL) {
@@ -3342,6 +3321,8 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
// round 1: case-folded tree
// round 2: keep-case tree
for (int round = 1; round <= 2; round++) {
+ uint8_t *byts;
+ idx_T *idxs;
if (round == 1) {
dumpflags &= ~DUMPFLAG_KEEPCASE;
byts = slang->sl_fbyts;
@@ -3354,7 +3335,7 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
if (byts == NULL) {
continue; // array is empty
}
- depth = 0;
+ int depth = 0;
arridx[0] = 0;
curi[0] = 1;
while (depth >= 0 && !got_int
@@ -3366,16 +3347,16 @@ void spell_dump_compl(char *pat, int ic, Direction *dir, int dumpflags_arg)
ins_compl_check_keys(50, false);
} else {
// Do one more byte at this node.
- n = arridx[depth] + curi[depth];
+ int n = arridx[depth] + curi[depth];
curi[depth]++;
- c = byts[n];
+ int c = byts[n];
if (c == 0 || depth >= MAXWLEN - 1) {
// End of word or reached maximum length, deal with the
// word.
// Don't use keep-case words in the fold-case tree,
// they will appear in the keep-case tree.
// Only use the word when the region matches.
- flags = (int)idxs[n];
+ int flags = (int)idxs[n];
if ((round == 2 || (flags & WF_KEEPCAP) == 0)
&& (flags & WF_NEEDCOMP) == 0
&& (do_region
@@ -3463,14 +3444,14 @@ static void dump_word(slang_T *slang, char *word, char *pat, Direction *dir, int
// Add flags and regions after a slash.
if ((flags & (WF_BANNED | WF_RARE | WF_REGION)) || keepcap) {
STRCPY(badword, p);
- STRCAT(badword, "/");
+ strcat(badword, "/");
if (keepcap) {
- STRCAT(badword, "=");
+ strcat(badword, "=");
}
if (flags & WF_BANNED) {
- STRCAT(badword, "!");
+ strcat(badword, "!");
} else if (flags & WF_RARE) {
- STRCAT(badword, "?");
+ strcat(badword, "?");
}
if (flags & WF_REGION) {
for (int i = 0; i < 7; i++) {
diff --git a/src/nvim/spellfile.c b/src/nvim/spellfile.c
index 046a0a56e5..ae8c243486 100644
--- a/src/nvim/spellfile.c
+++ b/src/nvim/spellfile.c
@@ -240,6 +240,7 @@
#include "nvim/buffer_defs.h"
#include "nvim/charset.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/ex_cmds_defs.h"
#include "nvim/fileio.h"
#include "nvim/garray.h"
@@ -2139,11 +2140,11 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
+ strlen(items[1]) + 3, false);
if (spin->si_info != NULL) {
STRCPY(p, spin->si_info);
- STRCAT(p, "\n");
+ strcat(p, "\n");
}
- STRCAT(p, items[0]);
- STRCAT(p, " ");
- STRCAT(p, items[1]);
+ strcat(p, items[0]);
+ strcat(p, " ");
+ strcat(p, items[1]);
spin->si_info = p;
} else if (is_aff_rule(items, itemcnt, "MIDWORD", 2) && midword == NULL) {
midword = getroom_save(spin, items[1]);
@@ -2199,7 +2200,7 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
// "Na" into "Na+", "1234" into "1234+".
p = getroom(spin, strlen(items[1]) + 2, false);
STRCPY(p, items[1]);
- STRCAT(p, "+");
+ strcat(p, "+");
compflags = p;
} else if (is_aff_rule(items, itemcnt, "COMPOUNDRULES", 2)) {
// We don't use the count, but do check that it's a number and
@@ -2220,9 +2221,9 @@ static afffile_T *spell_read_aff(spellinfo_T *spin, char *fname)
p = getroom(spin, (size_t)l, false);
if (compflags != NULL) {
STRCPY(p, compflags);
- STRCAT(p, "/");
+ strcat(p, "/");
}
- STRCAT(p, items[1]);
+ strcat(p, items[1]);
compflags = p;
}
} else if (is_aff_rule(items, itemcnt, "COMPOUNDWORDMAX", 2)
@@ -2843,7 +2844,7 @@ static void process_compflags(spellinfo_T *spin, afffile_T *aff, char *compflags
char *p = getroom(spin, (size_t)len, false);
if (spin->si_compflags != NULL) {
STRCPY(p, spin->si_compflags);
- STRCAT(p, "/");
+ strcat(p, "/");
}
spin->si_compflags = p;
uint8_t *tp = (uint8_t *)p + strlen(p);
@@ -3385,7 +3386,7 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
MB_PTR_ADV(p);
}
}
- STRCAT(newword, p);
+ strcat(newword, p);
} else {
// suffix: chop/add at the end of the word
xstrlcpy(newword, word, MAXWLEN);
@@ -3399,7 +3400,7 @@ static int store_aff_word(spellinfo_T *spin, char *word, char *afflist, afffile_
*p = NUL;
}
if (ae->ae_add != NULL) {
- STRCAT(newword, ae->ae_add);
+ strcat(newword, ae->ae_add);
}
}
@@ -4104,8 +4105,8 @@ static void wordtree_compress(spellinfo_T *spin, wordnode_T *root, const char *n
perc = (tot - n) * 100 / tot;
}
vim_snprintf(IObuff, IOSIZE,
- _("Compressed %s of %d nodes; %d (%ld%%) remaining"),
- name, tot, tot - n, perc);
+ _("Compressed %s: %d of %d nodes; %d (%ld%%) remaining"),
+ name, n, tot, tot - n, perc);
spell_message(spin, IObuff);
}
#ifdef SPELL_PRINTTREE
diff --git a/src/nvim/spellsuggest.c b/src/nvim/spellsuggest.c
index a7de20d14e..b37f01e769 100644
--- a/src/nvim/spellsuggest.c
+++ b/src/nvim/spellsuggest.c
@@ -14,6 +14,7 @@
#include "nvim/change.h"
#include "nvim/charset.h"
#include "nvim/cursor.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -275,7 +276,6 @@ static bool can_be_compound(trystate_T *sp, slang_T *slang, uint8_t *compflags,
static int score_wordcount_adj(slang_T *slang, int score, char *word, bool split)
{
int bonus;
- int newscore;
hashitem_T *hi = hash_find(&slang->sl_wordcount, word);
if (HASHITEM_EMPTY(hi)) {
@@ -290,11 +290,8 @@ static int score_wordcount_adj(slang_T *slang, int score, char *word, bool split
} else {
bonus = SCORE_COMMON3;
}
- if (split) {
- newscore = score - bonus / 2;
- } else {
- newscore = score - bonus;
- }
+ int newscore = split ? score - bonus / 2
+ : score - bonus;
if (newscore < 0) {
return 0;
}
@@ -448,7 +445,6 @@ void spell_suggest(int count)
suginfo_T sug;
suggest_T *stp;
bool mouse_used;
- int limit;
int selected = count;
int badlen = 0;
int msg_scroll_save = msg_scroll;
@@ -480,9 +476,7 @@ void spell_suggest(int count)
badlen++;
end_visual_mode();
// make sure we don't include the NUL at the end of the line
- if (badlen > get_cursor_line_len() - curwin->w_cursor.col) {
- badlen = get_cursor_line_len() - curwin->w_cursor.col;
- }
+ badlen = MIN(badlen, get_cursor_line_len() - curwin->w_cursor.col);
// Find the start of the badly spelled word.
} else if (spell_move_to(curwin, FORWARD, SMT_ALL, true, NULL) == 0
|| curwin->w_cursor.col > prev_cursor.col) {
@@ -518,11 +512,7 @@ void spell_suggest(int count)
// Get the list of suggestions. Limit to 'lines' - 2 or the number in
// 'spellsuggest', whatever is smaller.
- if (sps_limit > Rows - 2) {
- limit = Rows - 2;
- } else {
- limit = sps_limit;
- }
+ int limit = MIN(sps_limit, Rows - 2);
spell_find_suggest(line + curwin->w_cursor.col, badlen, &sug, limit,
true, need_cap, true);
@@ -642,7 +632,7 @@ void spell_suggest(int count)
int c = (int)(sug.su_badptr - line);
memmove(p, line, (size_t)c);
STRCPY(p + c, stp->st_word);
- STRCAT(p, sug.su_badptr + stp->st_orglen);
+ strcat(p, sug.su_badptr + stp->st_orglen);
// For redo we use a change-word command.
ResetRedobuff();
@@ -730,10 +720,7 @@ static void spell_find_suggest(char *badptr, int badlen, suginfo_T *su, int maxc
}
su->su_maxcount = maxcount;
su->su_maxscore = SCORE_MAXINIT;
-
- if (su->su_badlen >= MAXWLEN) {
- su->su_badlen = MAXWLEN - 1; // just in case
- }
+ su->su_badlen = MIN(su->su_badlen, MAXWLEN - 1); // just in case
xmemcpyz(su->su_badword, su->su_badptr, (size_t)su->su_badlen);
spell_casefold(curwin, su->su_badptr, su->su_badlen, su->su_fbadword,
MAXWLEN);
@@ -1144,7 +1131,6 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
idx_T *idxs, *fidxs, *pidxs;
int c, c2, c3;
int n = 0;
- garray_T *gap;
idx_T arridx;
int fl = 0;
int tl;
@@ -1637,7 +1623,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// Append a space to preword when splitting.
if (!try_compound && !fword_ends) {
- STRCAT(preword, " ");
+ strcat(preword, " ");
}
sp->ts_prewordlen = (uint8_t)strlen(preword);
sp->ts_splitoff = sp->ts_twordlen;
@@ -1735,11 +1721,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// Done all bytes at this node, do next state. When still at
// already changed bytes skip the other tricks.
PROF_STORE(sp->ts_state)
- if (sp->ts_fidx >= sp->ts_fidxtry) {
- sp->ts_state = STATE_DEL;
- } else {
- sp->ts_state = STATE_FINAL;
- }
+ sp->ts_state = sp->ts_fidx >= sp->ts_fidxtry ? STATE_DEL
+ : STATE_FINAL;
} else {
arridx += sp->ts_curi++;
c = byts[arridx];
@@ -1809,10 +1792,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// For changing a composing character adjust
// the score from SCORE_SUBST to
// SCORE_SUBCOMP.
- if (utf_iscomposing(utf_ptr2char(tword + sp->ts_twordlen
- - sp->ts_tcharlen))
- && utf_iscomposing(utf_ptr2char(fword
- + sp->ts_fcharstart))) {
+ if (utf_iscomposing_legacy(utf_ptr2char(tword + sp->ts_twordlen - sp->ts_tcharlen))
+ && utf_iscomposing_legacy(utf_ptr2char(fword + sp->ts_fcharstart))) {
sp->ts_score -= SCORE_SUBST - SCORE_SUBCOMP;
} else if (!soundfold
&& slang->sl_has_map
@@ -1828,7 +1809,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
&& sp->ts_twordlen > sp->ts_tcharlen) {
p = tword + sp->ts_twordlen - sp->ts_tcharlen;
c = utf_ptr2char(p);
- if (utf_iscomposing(c)) {
+ if (utf_iscomposing_legacy(c)) {
// Inserting a composing char doesn't
// count that much.
sp->ts_score -= SCORE_INS - SCORE_INSCOMP;
@@ -1893,7 +1874,7 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
c = utf_ptr2char(fword + sp->ts_fidx);
stack[depth].ts_fidx =
(uint8_t)(stack[depth].ts_fidx + utfc_ptr2len(fword + sp->ts_fidx));
- if (utf_iscomposing(c)) {
+ if (utf_iscomposing_legacy(c)) {
stack[depth].ts_score -= SCORE_DEL - SCORE_DELCOMP;
} else if (c == utf_ptr2char(fword + stack[depth].ts_fidx)) {
stack[depth].ts_score -= SCORE_DEL - SCORE_DELDUP;
@@ -2253,11 +2234,8 @@ static void suggest_trie_walk(suginfo_T *su, langp_T *lp, char *fword, bool soun
// valid.
p = fword + sp->ts_fidx;
- if (soundfold) {
- gap = &slang->sl_repsal;
- } else {
- gap = &lp->lp_replang->sl_rep;
- }
+ garray_T *gap = soundfold ? &slang->sl_repsal
+ : &lp->lp_replang->sl_rep;
while (sp->ts_curi < gap->ga_len) {
fromto_T *ftp = (fromto_T *)gap->ga_data + sp->ts_curi++;
if (*ftp->ft_from != *p) {
@@ -3125,11 +3103,9 @@ static void add_suggestion(suginfo_T *su, garray_T *gap, const char *goodword, i
// the best suggestions.
if (gap->ga_len > SUG_MAX_COUNT(su)) {
if (maxsf) {
- su->su_sfmaxscore = cleanup_suggestions(gap,
- su->su_sfmaxscore, SUG_CLEAN_COUNT(su));
+ su->su_sfmaxscore = cleanup_suggestions(gap, su->su_sfmaxscore, SUG_CLEAN_COUNT(su));
} else {
- su->su_maxscore = cleanup_suggestions(gap,
- su->su_maxscore, SUG_CLEAN_COUNT(su));
+ su->su_maxscore = cleanup_suggestions(gap, su->su_maxscore, SUG_CLEAN_COUNT(su));
}
}
}
@@ -3275,8 +3251,6 @@ static int soundalike_score(char *goodstart, char *badstart)
{
char *goodsound = goodstart;
char *badsound = badstart;
- char *pl, *ps;
- char *pl2, *ps2;
int score = 0;
// Adding/inserting "*" at the start (word starts with vowel) shouldn't be
@@ -3317,13 +3291,10 @@ static int soundalike_score(char *goodstart, char *badstart)
return SCORE_MAXMAX;
}
- if (n > 0) {
- pl = goodsound; // goodsound is longest
- ps = badsound;
- } else {
- pl = badsound; // badsound is longest
- ps = goodsound;
- }
+ // n > 0 : goodsound is longest
+ // n <= 0 : badsound is longest
+ char *pl = n > 0 ? goodsound : badsound;
+ char *ps = n > 0 ? badsound : goodsound;
// Skip over the identical part.
while (*pl == *ps && *pl != NUL) {
@@ -3331,6 +3302,8 @@ static int soundalike_score(char *goodstart, char *badstart)
ps++;
}
+ char *pl2, *ps2;
+
switch (n) {
case -2:
case 2:
@@ -3551,19 +3524,13 @@ static int spell_edit_score(slang_T *slang, const char *badword, const char *goo
int pgc = wgoodword[j - 2];
if (bc == pgc && pbc == gc) {
int t = SCORE_SWAP + CNT(i - 2, j - 2);
- if (t < CNT(i, j)) {
- CNT(i, j) = t;
- }
+ CNT(i, j) = MIN(CNT(i, j), t);
}
}
int t = SCORE_DEL + CNT(i - 1, j);
- if (t < CNT(i, j)) {
- CNT(i, j) = t;
- }
+ CNT(i, j) = MIN(CNT(i, j), t);
t = SCORE_INS + CNT(i, j - 1);
- if (t < CNT(i, j)) {
- CNT(i, j) = t;
- }
+ CNT(i, j) = MIN(CNT(i, j), t);
}
}
}
diff --git a/src/nvim/state.c b/src/nvim/state.c
index 993db255de..908f724792 100644
--- a/src/nvim/state.c
+++ b/src/nvim/state.c
@@ -66,18 +66,19 @@ getkey:
// Event was made available after the last multiqueue_process_events call
key = K_EVENT;
} else {
- // Duplicate display updating logic in vgetorpeek()
- if (((State & MODE_INSERT) != 0 || p_lz) && (State & MODE_CMDLINE) == 0
- && must_redraw != 0 && !need_wait_return) {
+ // Ensure the screen is fully updated before blocking for input. Because of the duality of
+ // redraw_later, this can't be done in command-line or when waiting for "Press ENTER".
+ // In many of those cases the redraw is expected AFTER the key press, while normally it should
+ // update the screen immediately.
+ if (must_redraw != 0 && !need_wait_return && (State & MODE_CMDLINE) == 0) {
update_screen();
setcursor(); // put cursor back where it belongs
}
// Flush screen updates before blocking.
ui_flush();
- // Call `os_inchar` directly to block for events or user input without
- // consuming anything from `input_buffer`(os/input.c) or calling the
- // mapping engine.
- os_inchar(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events);
+ // Call `input_get` directly to block for events or user input without consuming anything from
+ // `os/input.c:input_buffer` or calling the mapping engine.
+ input_get(NULL, 0, -1, typebuf.tb_change_cnt, main_loop.events);
// If an event was put into the queue, we send K_EVENT directly.
if (!input_available() && !multiqueue_empty(main_loop.events)) {
key = K_EVENT;
@@ -177,17 +178,8 @@ void get_mode(char *buf)
{
int i = 0;
- if (VIsual_active) {
- if (VIsual_select) {
- buf[i++] = (char)(VIsual_mode + 's' - 'v');
- } else {
- buf[i++] = (char)VIsual_mode;
- if (restart_VIsual_select) {
- buf[i++] = 's';
- }
- }
- } else if (State == MODE_HITRETURN || State == MODE_ASKMORE || State == MODE_SETWSIZE
- || State == MODE_CONFIRM) {
+ if (State == MODE_HITRETURN || State == MODE_ASKMORE
+ || State == MODE_SETWSIZE || State == MODE_CONFIRM) {
buf[i++] = 'r';
if (State == MODE_ASKMORE) {
buf[i++] = 'm';
@@ -222,6 +214,15 @@ void get_mode(char *buf)
}
} else if (State & MODE_TERMINAL) {
buf[i++] = 't';
+ } else if (VIsual_active) {
+ if (VIsual_select) {
+ buf[i++] = (char)(VIsual_mode + 's' - 'v');
+ } else {
+ buf[i++] = (char)VIsual_mode;
+ if (restart_VIsual_select) {
+ buf[i++] = 's';
+ }
+ }
} else {
buf[i++] = 'n';
if (finish_op) {
@@ -233,8 +234,7 @@ void get_mode(char *buf)
if (restart_edit == 'I') {
buf[i++] = 'T';
}
- } else if (restart_edit == 'I' || restart_edit == 'R'
- || restart_edit == 'V') {
+ } else if (restart_edit == 'I' || restart_edit == 'R' || restart_edit == 'V') {
buf[i++] = 'i';
buf[i++] = (char)restart_edit;
}
diff --git a/src/nvim/statusline.c b/src/nvim/statusline.c
index ca7083a9e3..07bb7dd69a 100644
--- a/src/nvim/statusline.c
+++ b/src/nvim/statusline.c
@@ -123,10 +123,7 @@ void win_redr_status(win_T *wp)
// len += (int)strlen(p + len); // dead assignment
}
- int this_ru_col = ru_col - (Columns - stl_width);
- if (this_ru_col < (stl_width + 1) / 2) {
- this_ru_col = (stl_width + 1) / 2;
- }
+ int this_ru_col = MAX(ru_col - (Columns - stl_width), (stl_width + 1) / 2);
if (this_ru_col <= 1) {
p = "<"; // No room for file name!
len = 1;
@@ -374,10 +371,7 @@ static void win_redr_custom(win_T *wp, bool draw_winbar, bool draw_ruler)
stl = p_ruf;
}
}
- col = ru_col - (Columns - maxwidth);
- if (col < (maxwidth + 1) / 2) {
- col = (maxwidth + 1) / 2;
- }
+ col = MAX(ru_col - (Columns - maxwidth), (maxwidth + 1) / 2);
maxwidth -= col;
if (!in_status_line) {
grid = &msg_grid_adj;
@@ -574,15 +568,9 @@ void win_redr_ruler(win_T *wp)
if (wp->w_status_height == 0 && !is_stl_global) { // can't use last char of screen
o++;
}
- int this_ru_col = ru_col - (Columns - width);
- if (this_ru_col < 0) {
- this_ru_col = 0;
- }
// Never use more than half the window/screen width, leave the other half
// for the filename.
- if (this_ru_col < (width + 1) / 2) {
- this_ru_col = (width + 1) / 2;
- }
+ int this_ru_col = MAX(ru_col - (Columns - width), (width + 1) / 2);
if (this_ru_col + o < width) {
// Need at least 3 chars left for get_rel_pos() + NUL.
while (this_ru_col + o < width && RULER_BUF_LEN > i + 4) {
@@ -660,14 +648,14 @@ static void ui_ext_tabline_update(void)
Array tabs = arena_array(&arena, n_tabs);
FOR_ALL_TABS(tp) {
- Dictionary tab_info = arena_dict(&arena, 2);
+ Dict tab_info = arena_dict(&arena, 2);
PUT_C(tab_info, "tab", TABPAGE_OBJ(tp->handle));
win_T *cwp = (tp == curtab) ? curwin : tp->tp_curwin;
get_trans_bufname(cwp->w_buffer);
PUT_C(tab_info, "name", CSTR_TO_ARENA_OBJ(&arena, NameBuff));
- ADD_C(tabs, DICTIONARY_OBJ(tab_info));
+ ADD_C(tabs, DICT_OBJ(tab_info));
}
size_t n_buffers = 0;
@@ -682,13 +670,13 @@ static void ui_ext_tabline_update(void)
continue;
}
- Dictionary buffer_info = arena_dict(&arena, 2);
+ Dict buffer_info = arena_dict(&arena, 2);
PUT_C(buffer_info, "buffer", BUFFER_OBJ(buf->handle));
get_trans_bufname(buf);
PUT_C(buffer_info, "name", CSTR_TO_ARENA_OBJ(&arena, NameBuff));
- ADD_C(buffers, DICTIONARY_OBJ(buffer_info));
+ ADD_C(buffers, DICT_OBJ(buffer_info));
}
ui_call_tabline_update(curtab->handle, tabs, curbuf->handle, buffers);
@@ -726,7 +714,6 @@ void draw_tabline(void)
win_redr_custom(NULL, false, false);
} else {
int tabcount = 0;
- int tabwidth = 0;
int col = 0;
win_T *cwp;
int wincount;
@@ -735,13 +722,7 @@ void draw_tabline(void)
tabcount++;
}
- if (tabcount > 0) {
- tabwidth = (Columns - 1 + tabcount / 2) / tabcount;
- }
-
- if (tabwidth < 6) {
- tabwidth = 6;
- }
+ int tabwidth = MAX(tabcount > 0 ? (Columns - 1 + tabcount / 2) / tabcount : 0, 6);
int attr = attr_nosel;
tabcount = 0;
@@ -810,9 +791,7 @@ void draw_tabline(void)
len -= ptr2cells(p);
MB_PTR_ADV(p);
}
- if (len > Columns - col - 1) {
- len = Columns - col - 1;
- }
+ len = MIN(len, Columns - col - 1);
grid_line_puts(col, p, -1, attr);
col += len;
@@ -831,6 +810,16 @@ void draw_tabline(void)
}
}
+ for (int scol = col; scol < Columns; scol++) {
+ // Use 0 as tabpage number here, so that double-click opens a tabpage
+ // after the last one, and single-click goes to the next tabpage.
+ tab_page_click_defs[scol] = (StlClickDefinition) {
+ .type = kStlClickTabSwitch,
+ .tabnr = 0,
+ .func = NULL,
+ };
+ }
+
char c = use_sep_chars ? '_' : ' ';
grid_line_fill(col, Columns, schar_from_ascii(c), attr_fill);
@@ -975,7 +964,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op
};
set_var(S_LEN("g:statusline_winid"), &tv, false);
- usefmt = eval_to_string_safe(fmt + 2, use_sandbox);
+ usefmt = eval_to_string_safe(fmt + 2, use_sandbox, false);
if (usefmt == NULL) {
usefmt = fmt;
}
@@ -1017,7 +1006,6 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op
int evaldepth = 0;
int curitem = 0;
- int foldsignitem = -1;
bool prevchar_isflag = true;
bool prevchar_isitem = false;
@@ -1194,9 +1182,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op
// If the item was partially or completely truncated, set its
// start to the start of the group
- if (stl_items[idx].start < t) {
- stl_items[idx].start = t;
- }
+ stl_items[idx].start = MAX(stl_items[idx].start, t);
}
// If the group is shorter than the minimum width, add padding characters.
} else if (abs(stl_items[stl_groupitems[groupdepth]].minwid) > group_len) {
@@ -1234,6 +1220,8 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op
}
int minwid = 0;
int maxwid = 9999;
+ int foldsignitem = -1; // Start of fold or sign item
+ bool left_align_num = false; // Number item for should be left-aligned
bool left_align = false;
// Denotes that numbers should be left-padded with zeros
@@ -1268,24 +1256,17 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op
// TABPAGE pairs are used to denote a region that when clicked will
// either switch to or close a tab.
//
- // Ex: tabline=%0Ttab\ zero%X
- // This tabline has a TABPAGENR item with minwid `0`,
+ // Ex: tabline=%1Ttab\ one%X
+ // This tabline has a TABPAGENR item with minwid `1`,
// which is then closed with a TABCLOSENR item.
- // Clicking on this region with mouse enabled will switch to tab 0.
+ // Clicking on this region with mouse enabled will switch to tab 1.
// Setting the minwid to a different value will switch
// to that tab, if it exists
//
// Ex: tabline=%1Xtab\ one%X
// This tabline has a TABCLOSENR item with minwid `1`,
// which is then closed with a TABCLOSENR item.
- // Clicking on this region with mouse enabled will close tab 0.
- // This is determined by the following formula:
- // tab to close = (1 - minwid)
- // This is because for TABPAGENR we use `minwid` = `tab number`.
- // For TABCLOSENR we store the tab number as a negative value.
- // Because 0 is a valid TABPAGENR value, we have to
- // start our numbering at `-1`.
- // So, `-1` corresponds to us wanting to close tab `0`
+ // Clicking on this region with mouse enabled will close tab 1.
//
// Note: These options are only valid when creating a tabline.
if (*fmt_p == STL_TABPAGENR || *fmt_p == STL_TABCLOSENR) {
@@ -1451,7 +1432,7 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op
}
// Note: The result stored in `t` is unused.
- str = eval_to_string_safe(out_p, use_sandbox);
+ str = eval_to_string_safe(out_p, use_sandbox, false);
curwin = save_curwin;
curbuf = save_curbuf;
@@ -1505,12 +1486,20 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op
}
case STL_LINE:
- // Overload %l with v:lnum for 'statuscolumn'
- if (stcp != NULL) {
- if (wp->w_p_nu && !get_vim_var_nr(VV_VIRTNUM)) {
- num = (int)get_vim_var_nr(VV_LNUM);
+ // Overload %l with v:(re)lnum for 'statuscolumn'. Place a sign when 'signcolumn'
+ // is set to "number". Take care of alignment for 'number' + 'relativenumber'.
+ if (stcp != NULL && (wp->w_p_nu || wp->w_p_rnu) && get_vim_var_nr(VV_VIRTNUM) == 0) {
+ if (wp->w_maxscwidth == SCL_NUM && stcp->sattrs[0].text[0]) {
+ goto stcsign;
}
- } else {
+ int relnum = (int)get_vim_var_nr(VV_RELNUM);
+ num = (!wp->w_p_rnu || (wp->w_p_nu && relnum == 0)) ? (int)get_vim_var_nr(VV_LNUM) : relnum;
+ left_align_num = wp->w_p_rnu && wp->w_p_nu && relnum == 0;
+ if (!left_align_num) {
+ stl_items[curitem].type = Separate;
+ stl_items[curitem++].start = out_p;
+ }
+ } else if (stcp == NULL) {
num = (wp->w_buffer->b_ml.ml_flags & ML_EMPTY) ? 0 : wp->w_cursor.lnum;
}
break;
@@ -1609,16 +1598,9 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op
case STL_ROFLAG:
case STL_ROFLAG_ALT:
- // Overload %r with v:relnum for 'statuscolumn'
- if (stcp != NULL) {
- if (wp->w_p_rnu && !get_vim_var_nr(VV_VIRTNUM)) {
- num = (int)get_vim_var_nr(VV_RELNUM);
- }
- } else {
- itemisflag = true;
- if (wp->w_buffer->b_p_ro) {
- str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]");
- }
+ itemisflag = true;
+ if (wp->w_buffer->b_p_ro) {
+ str = (opt == STL_ROFLAG_ALT) ? ",RO" : _("[RO]");
}
break;
@@ -1632,21 +1614,20 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op
case STL_FOLDCOL: // 'C' for 'statuscolumn'
case STL_SIGNCOL: { // 's' for 'statuscolumn'
+stcsign:
if (stcp == NULL) {
break;
}
- bool fold = opt == STL_FOLDCOL;
- int fdc = fold ? compute_foldcolumn(wp, 0) : 0;
- int width = fold ? fdc > 0 : wp->w_scwidth;
+ int fdc = opt == STL_FOLDCOL ? compute_foldcolumn(wp, 0) : 0;
+ int width = opt == STL_FOLDCOL ? fdc > 0 : opt == STL_SIGNCOL ? wp->w_scwidth : 1;
if (width <= 0) {
break;
}
foldsignitem = curitem;
- char *p = NULL;
- if (fold) {
- schar_T fold_buf[10];
+ if (fdc > 0) {
+ schar_T fold_buf[9];
fill_foldcolumn(wp, stcp->foldinfo, (linenr_T)get_vim_var_nr(VV_LNUM),
0, fdc, NULL, fold_buf);
stl_items[curitem].minwid = -((stcp->use_cul ? HLF_CLF : HLF_FC) + 1);
@@ -1654,31 +1635,26 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op
// TODO(bfredl): this is very backwards. we must support schar_T
// being used directly in 'statuscolumn'
for (int i = 0; i < fdc; i++) {
- buflen += schar_get(out_p + buflen, fold_buf[i]);
+ buflen += schar_get(buf_tmp + buflen, fold_buf[i]);
}
- p = out_p;
}
- char buf[SIGN_WIDTH * MAX_SCHAR_SIZE];
- size_t buflen = 0;
- varnumber_T virtnum = get_vim_var_nr(VV_VIRTNUM);
+ size_t signlen = 0;
for (int i = 0; i < width; i++) {
- if (!fold) {
- SignTextAttrs *sattr = virtnum ? NULL : &stcp->sattrs[i];
- p = " ";
- if (sattr && sattr->text[0]) {
- describe_sign_text(buf, sattr->text);
- p = buf;
+ stl_items[curitem].start = out_p + signlen;
+ if (fdc == 0) {
+ if (stcp->sattrs[i].text[0] && get_vim_var_nr(VV_VIRTNUM) == 0) {
+ SignTextAttrs sattrs = stcp->sattrs[i];
+ signlen += describe_sign_text(buf_tmp + signlen, sattrs.text);
+ stl_items[curitem].minwid = -(stcp->sign_cul_id ? stcp->sign_cul_id : sattrs.hl_id);
+ } else {
+ buf_tmp[signlen++] = ' ';
+ buf_tmp[signlen++] = ' ';
+ buf_tmp[signlen] = NUL;
+ stl_items[curitem].minwid = -((stcp->use_cul ? HLF_CLS : HLF_SC) + 1);
}
- stl_items[curitem].minwid = -(sattr && sattr->text[0]
- ? (stcp->sign_cul_id ? stcp->sign_cul_id : sattr->hl_id)
- : (stcp->use_cul ? HLF_CLS : HLF_SC) + 1);
}
- stl_items[curitem].type = Highlight;
- stl_items[curitem].start = out_p + buflen;
- xstrlcpy(buf_tmp + buflen, p, TMPLEN - buflen);
- buflen += strlen(p);
- curitem++;
+ stl_items[curitem++].type = Highlight;
}
str = buf_tmp;
break;
@@ -1851,7 +1827,6 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op
// For a 'statuscolumn' sign or fold item, add an item to reset the highlight group
if (foldsignitem >= 0) {
- foldsignitem = -1;
stl_items[curitem].type = Highlight;
stl_items[curitem].start = out_p;
stl_items[curitem].minwid = 0;
@@ -1956,6 +1931,11 @@ int build_stl_str_hl(win_T *wp, char *out, size_t outlen, char *fmt, OptIndex op
// Item processed, move to the next
curitem++;
+ // For a 'statuscolumn' number item that is left aligned, add a separator item.
+ if (left_align_num) {
+ stl_items[curitem].type = Separate;
+ stl_items[curitem++].start = out_p;
+ }
}
*out_p = NUL;
diff --git a/src/nvim/strings.c b/src/nvim/strings.c
index 16ae35272b..118abbae6d 100644
--- a/src/nvim/strings.c
+++ b/src/nvim/strings.c
@@ -12,6 +12,7 @@
#include "nvim/ascii_defs.h"
#include "nvim/assert_defs.h"
#include "nvim/charset.h"
+#include "nvim/errors.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -268,9 +269,9 @@ char *vim_strsave_shellescape(const char *string, bool do_special, bool do_newli
}
if (do_special && find_cmdline_var(p, &l) >= 0) {
*d++ = '\\'; // insert backslash
- while (--l != SIZE_MAX) { // copy the var
- *d++ = *p++;
- }
+ memcpy(d, p, l); // copy the var
+ d += l;
+ p += l;
continue;
}
if (*p == '\\' && fish_like) {
@@ -332,7 +333,7 @@ void vim_strcpy_up(char *restrict dst, const char *restrict src)
while ((c = (uint8_t)(*src++)) != NUL) {
*dst++ = (char)(uint8_t)(c < 'a' || c > 'z' ? c : c - 0x20);
}
- *dst = '\0';
+ *dst = NUL;
}
// strncpy (NUL-terminated) plus vim_strup.
@@ -343,7 +344,7 @@ void vim_strncpy_up(char *restrict dst, const char *restrict src, size_t n)
while (n-- && (c = (uint8_t)(*src++)) != NUL) {
*dst++ = (char)(uint8_t)(c < 'a' || c > 'z' ? c : c - 0x20);
}
- *dst = '\0';
+ *dst = NUL;
}
// memcpy (does not NUL-terminate) plus vim_strup.
@@ -495,6 +496,20 @@ char *vim_strchr(const char *const string, const int c)
}
}
+// Sized version of strchr that can handle embedded NULs.
+// Adjusts n to the new size.
+char *strnchr(const char *p, size_t *n, int c)
+{
+ while (*n > 0) {
+ if (*p == c) {
+ return (char *)p;
+ }
+ p++;
+ (*n)--;
+ }
+ return NULL;
+}
+
// Sort an array of strings.
static int sort_compare(const void *s1, const void *s2)
@@ -628,12 +643,14 @@ static const void *tv_ptr(const typval_T *const tvs, int *const idxp)
#define OFF(attr) offsetof(union typval_vval_union, attr)
STATIC_ASSERT(OFF(v_string) == OFF(v_list)
&& OFF(v_string) == OFF(v_dict)
+ && OFF(v_string) == OFF(v_blob)
&& OFF(v_string) == OFF(v_partial)
&& sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_list)
&& sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_dict)
+ && sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_blob)
&& sizeof(tvs[0].vval.v_string) == sizeof(tvs[0].vval.v_partial),
- "Strings, dictionaries, lists and partials are expected to be pointers, "
- "so that all three of them can be accessed via v_string");
+ "Strings, Dictionaries, Lists, Blobs and Partials are expected to be pointers, "
+ "so that all of them can be accessed via v_string");
#undef OFF
const int idx = *idxp - 1;
if (tvs[idx].v_type == VAR_UNKNOWN) {
@@ -793,10 +810,10 @@ static int format_typeof(const char *type)
FUNC_ATTR_NONNULL_ALL
{
// allowed values: \0, h, l, L
- char length_modifier = '\0';
+ char length_modifier = NUL;
// current conversion specifier character
- char fmt_spec = '\0';
+ char fmt_spec = NUL;
// parse 'h', 'l', 'll' and 'z' length modifiers
if (*type == 'h' || *type == 'l' || *type == 'z') {
@@ -864,7 +881,7 @@ static int format_typeof(const char *type)
} else if (fmt_spec == 'd') {
// signed
switch (length_modifier) {
- case '\0':
+ case NUL:
case 'h':
// char and short arguments are passed as int.
return TYPE_INT;
@@ -878,7 +895,7 @@ static int format_typeof(const char *type)
} else {
// unsigned
switch (length_modifier) {
- case '\0':
+ case NUL:
case 'h':
return TYPE_UNSIGNEDINT;
case 'l':
@@ -1002,7 +1019,7 @@ static void format_overflow_error(const char *pstart)
enum { MAX_ALLOWED_STRING_WIDTH = 6400, };
-static int get_unsigned_int(const char *pstart, const char **p, unsigned *uj)
+static int get_unsigned_int(const char *pstart, const char **p, unsigned *uj, bool overflow_err)
{
*uj = (unsigned)(**p - '0');
(*p)++;
@@ -1013,8 +1030,12 @@ static int get_unsigned_int(const char *pstart, const char **p, unsigned *uj)
}
if (*uj > MAX_ALLOWED_STRING_WIDTH) {
- format_overflow_error(pstart);
- return FAIL;
+ if (overflow_err) {
+ format_overflow_error(pstart);
+ return FAIL;
+ } else {
+ *uj = MAX_ALLOWED_STRING_WIDTH;
+ }
}
return OK;
@@ -1049,7 +1070,7 @@ static int parse_fmt_types(const char ***ap_types, int *num_posarg, const char *
p += n;
} else {
// allowed values: \0, h, l, L
- char length_modifier = '\0';
+ char length_modifier = NUL;
// variable for positional arg
int pos_arg = -1;
@@ -1075,7 +1096,7 @@ static int parse_fmt_types(const char ***ap_types, int *num_posarg, const char *
// Positional argument
unsigned uj;
- if (get_unsigned_int(pstart, &p, &uj) == FAIL) {
+ if (get_unsigned_int(pstart, &p, &uj, tvs != NULL) == FAIL) {
goto error;
}
@@ -1118,7 +1139,7 @@ static int parse_fmt_types(const char ***ap_types, int *num_posarg, const char *
// Positional argument field width
unsigned uj;
- if (get_unsigned_int(arg + 1, &p, &uj) == FAIL) {
+ if (get_unsigned_int(arg + 1, &p, &uj, tvs != NULL) == FAIL) {
goto error;
}
@@ -1144,7 +1165,7 @@ static int parse_fmt_types(const char ***ap_types, int *num_posarg, const char *
const char *digstart = p;
unsigned uj;
- if (get_unsigned_int(digstart, &p, &uj) == FAIL) {
+ if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL) {
goto error;
}
@@ -1165,7 +1186,7 @@ static int parse_fmt_types(const char ***ap_types, int *num_posarg, const char *
// Parse precision
unsigned uj;
- if (get_unsigned_int(arg + 1, &p, &uj) == FAIL) {
+ if (get_unsigned_int(arg + 1, &p, &uj, tvs != NULL) == FAIL) {
goto error;
}
@@ -1192,7 +1213,7 @@ static int parse_fmt_types(const char ***ap_types, int *num_posarg, const char *
const char *digstart = p;
unsigned uj;
- if (get_unsigned_int(digstart, &p, &uj) == FAIL) {
+ if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL) {
goto error;
}
@@ -1440,7 +1461,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
int space_for_positive = 1;
// allowed values: \0, h, l, 2 (for ll), z, L
- char length_modifier = '\0';
+ char length_modifier = NUL;
// temporary buffer for simple numeric->string conversion
#define TMP_LEN 350 // 1e308 seems reasonable as the maximum printable
@@ -1465,7 +1486,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
size_t zero_padding_insertion_ind = 0;
// current conversion specifier character
- char fmt_spec = '\0';
+ char fmt_spec = NUL;
// buffer for 's' and 'S' specs
char *tofree = NULL;
@@ -1488,7 +1509,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
const char *digstart = p;
unsigned uj;
- if (get_unsigned_int(digstart, &p, &uj) == FAIL) {
+ if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL) {
goto error;
}
@@ -1530,7 +1551,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
// Positional argument field width
unsigned uj;
- if (get_unsigned_int(digstart, &p, &uj) == FAIL) {
+ if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL) {
goto error;
}
@@ -1539,15 +1560,19 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
p++;
}
- const int j = (tvs
- ? (int)tv_nr(tvs, &arg_idx)
- : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
- &arg_cur, fmt),
- va_arg(ap, int)));
+ int j = (tvs
+ ? (int)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int)));
if (j > MAX_ALLOWED_STRING_WIDTH) {
- format_overflow_error(digstart);
- goto error;
+ if (tvs != NULL) {
+ format_overflow_error(digstart);
+ goto error;
+ } else {
+ j = MAX_ALLOWED_STRING_WIDTH;
+ }
}
if (j >= 0) {
@@ -1562,12 +1587,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
const char *digstart = p;
unsigned uj;
- if (get_unsigned_int(digstart, &p, &uj) == FAIL) {
- goto error;
- }
-
- if (uj > MAX_ALLOWED_STRING_WIDTH) {
- format_overflow_error(digstart);
+ if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL) {
goto error;
}
@@ -1585,12 +1605,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
const char *digstart = p;
unsigned uj;
- if (get_unsigned_int(digstart, &p, &uj) == FAIL) {
- goto error;
- }
-
- if (uj > MAX_ALLOWED_STRING_WIDTH) {
- format_overflow_error(digstart);
+ if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL) {
goto error;
}
@@ -1604,7 +1619,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
// positional argument
unsigned uj;
- if (get_unsigned_int(digstart, &p, &uj) == FAIL) {
+ if (get_unsigned_int(digstart, &p, &uj, tvs != NULL) == FAIL) {
goto error;
}
@@ -1613,15 +1628,19 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
p++;
}
- const int j = (tvs
- ? (int)tv_nr(tvs, &arg_idx)
- : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
- &arg_cur, fmt),
- va_arg(ap, int)));
+ int j = (tvs
+ ? (int)tv_nr(tvs, &arg_idx)
+ : (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
+ &arg_cur, fmt),
+ va_arg(ap, int)));
if (j > MAX_ALLOWED_STRING_WIDTH) {
- format_overflow_error(digstart);
- goto error;
+ if (tvs != NULL) {
+ format_overflow_error(digstart);
+ goto error;
+ } else {
+ j = MAX_ALLOWED_STRING_WIDTH;
+ }
}
if (j >= 0) {
@@ -1668,7 +1687,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
case 'o':
case 'x':
case 'X':
- if (tvs && length_modifier == '\0') {
+ if (tvs && length_modifier == NUL) {
length_modifier = 'L';
}
}
@@ -1789,7 +1808,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
} else if (fmt_spec == 'd') {
// signed
switch (length_modifier) {
- case '\0':
+ case NUL:
arg = (tvs
? (int)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
@@ -1835,7 +1854,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
} else {
// unsigned
switch (length_modifier) {
- case '\0':
+ case NUL:
uarg = (tvs
? (unsigned)tv_nr(tvs, &arg_idx)
: (skip_to_arg(ap_types, ap_start, &ap, &arg_idx,
@@ -2222,7 +2241,7 @@ int vim_vsnprintf_typval(char *str, size_t str_m, const char *fmt, va_list ap_st
if (str_m > 0) {
// make sure the string is nul-terminated even at the expense of
// overwriting the last character (shouldn't happen, but just in case)
- str[str_l <= str_m - 1 ? str_l : str_m - 1] = '\0';
+ str[str_l <= str_m - 1 ? str_l : str_m - 1] = NUL;
}
if (tvs != NULL
diff --git a/src/nvim/strings.h b/src/nvim/strings.h
index f80e85afb0..c2d078615d 100644
--- a/src/nvim/strings.h
+++ b/src/nvim/strings.h
@@ -7,29 +7,9 @@
#include "klib/kvec.h"
#include "nvim/api/private/defs.h"
#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
-#include "nvim/func_attr.h"
#include "nvim/os/os_defs.h"
#include "nvim/types_defs.h" // IWYU pragma: keep
-static inline char *strappend(char *dst, const char *src)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL
- REAL_FATTR_NONNULL_RET REAL_FATTR_WARN_UNUSED_RESULT;
-
-/// Append string to string and return pointer to the next byte
-///
-/// Unlike strcat, this one does *not* add NUL byte and returns pointer to the
-/// past of the added string.
-///
-/// @param[out] dst String to append to.
-/// @param[in] src String to append.
-///
-/// @return pointer to the byte just past the appended byte.
-static inline char *strappend(char *const dst, const char *const src)
-{
- const size_t src_len = strlen(src);
- return (char *)memmove(dst, src, src_len) + src_len;
-}
-
typedef kvec_t(char) StringBuilder;
// Return the length of a string literal
@@ -46,8 +26,26 @@ typedef struct {
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "strings.h.generated.h"
+# include "strings.h.inline.generated.h"
#endif
+/// Append string to string and return pointer to the next byte
+///
+/// Unlike strcat, this one does *not* add NUL byte and returns pointer to the
+/// past of the added string.
+///
+/// @param[out] dst String to append to.
+/// @param[in] src String to append.
+///
+/// @return pointer to the byte just past the appended byte.
+static inline char *strappend(char *const dst, const char *const src)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
+ FUNC_ATTR_NONNULL_RET FUNC_ATTR_WARN_UNUSED_RESULT
+{
+ const size_t src_len = strlen(src);
+ return (char *)memmove(dst, src, src_len) + src_len;
+}
+
#ifdef HAVE_STRCASECMP
# define STRICMP(d, s) strcasecmp((char *)(d), (char *)(s))
#else
diff --git a/src/nvim/syntax.c b/src/nvim/syntax.c
index b63d2a729d..9eeed3fbd2 100644
--- a/src/nvim/syntax.c
+++ b/src/nvim/syntax.c
@@ -15,6 +15,7 @@
#include "nvim/charset.h"
#include "nvim/cmdexpand_defs.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval_defs.h"
#include "nvim/eval/vars.h"
@@ -157,11 +158,6 @@ typedef struct {
char *pattern;
} time_entry_T;
-struct name_list {
- int flag;
- char *name;
-};
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "syntax.c.generated.h"
#endif
@@ -2286,7 +2282,7 @@ static void update_si_end(stateitem_T *sip, int startcol, bool force)
// a "oneline" never continues in the next line
sip->si_ends = true;
sip->si_m_endpos.lnum = current_lnum;
- sip->si_m_endpos.col = (colnr_T)strlen(syn_getcurline());
+ sip->si_m_endpos.col = syn_getcurline_len();
} else {
// continues in the next line
sip->si_ends = false;
@@ -2658,6 +2654,12 @@ static char *syn_getcurline(void)
return ml_get_buf(syn_buf, current_lnum);
}
+/// Get length of current line in syntax buffer.
+static colnr_T syn_getcurline_len(void)
+{
+ return ml_get_buf_len(syn_buf, current_lnum);
+}
+
// Call vim_regexec() to find a match with "rmp" in "syn_buf".
// Returns true when there is a match.
static bool syn_regexec(regmmatch_T *rmp, linenr_T lnum, colnr_T col, syn_time_T *st)
@@ -3326,24 +3328,22 @@ static int last_matchgroup;
static void syn_list_one(const int id, const bool syncing, const bool link_only)
{
bool did_header = false;
- static struct name_list namelist1[] = {
- { HL_DISPLAY, "display" },
- { HL_CONTAINED, "contained" },
- { HL_ONELINE, "oneline" },
- { HL_KEEPEND, "keepend" },
- { HL_EXTEND, "extend" },
- { HL_EXCLUDENL, "excludenl" },
- { HL_TRANSP, "transparent" },
- { HL_FOLD, "fold" },
- { HL_CONCEAL, "conceal" },
- { HL_CONCEALENDS, "concealends" },
- { 0, NULL }
+ static keyvalue_T namelist1[] = {
+ KEYVALUE_ENTRY(HL_DISPLAY, "display"),
+ KEYVALUE_ENTRY(HL_CONTAINED, "contained"),
+ KEYVALUE_ENTRY(HL_ONELINE, "oneline"),
+ KEYVALUE_ENTRY(HL_KEEPEND, "keepend"),
+ KEYVALUE_ENTRY(HL_EXTEND, "extend"),
+ KEYVALUE_ENTRY(HL_EXCLUDENL, "excludenl"),
+ KEYVALUE_ENTRY(HL_TRANSP, "transparent"),
+ KEYVALUE_ENTRY(HL_FOLD, "fold"),
+ KEYVALUE_ENTRY(HL_CONCEAL, "conceal"),
+ KEYVALUE_ENTRY(HL_CONCEALENDS, "concealends"),
};
- static struct name_list namelist2[] = {
- { HL_SKIPWHITE, "skipwhite" },
- { HL_SKIPNL, "skipnl" },
- { HL_SKIPEMPTY, "skipempty" },
- { 0, NULL }
+ static keyvalue_T namelist2[] = {
+ KEYVALUE_ENTRY(HL_SKIPWHITE, "skipwhite"),
+ KEYVALUE_ENTRY(HL_SKIPNL, "skipnl"),
+ KEYVALUE_ENTRY(HL_SKIPEMPTY, "skipempty"),
};
const int attr = HL_ATTR(HLF_D); // highlight like directories
@@ -3384,7 +3384,7 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only)
idx--;
msg_putchar(' ');
}
- syn_list_flags(namelist1, spp->sp_flags, attr);
+ syn_list_flags(namelist1, ARRAY_SIZE(namelist1), spp->sp_flags, attr);
if (spp->sp_cont_list != NULL) {
put_id_list("contains", spp->sp_cont_list, attr);
@@ -3396,7 +3396,7 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only)
if (spp->sp_next_list != NULL) {
put_id_list("nextgroup", spp->sp_next_list, attr);
- syn_list_flags(namelist2, spp->sp_flags, attr);
+ syn_list_flags(namelist2, ARRAY_SIZE(namelist2), spp->sp_flags, attr);
}
if (spp->sp_flags & (HL_SYNC_HERE|HL_SYNC_THERE)) {
if (spp->sp_flags & HL_SYNC_HERE) {
@@ -3424,11 +3424,11 @@ static void syn_list_one(const int id, const bool syncing, const bool link_only)
}
}
-static void syn_list_flags(struct name_list *nlist, int flags, int attr)
+static void syn_list_flags(keyvalue_T *nlist, size_t nr_entries, int flags, int attr)
{
- for (int i = 0; nlist[i].flag != 0; i++) {
- if (flags & nlist[i].flag) {
- msg_puts_attr(nlist[i].name, attr);
+ for (size_t i = 0; i < nr_entries; i++) {
+ if (flags & nlist[i].key) {
+ msg_puts_attr(nlist[i].value, attr);
msg_putchar(' ');
}
}
@@ -3700,17 +3700,22 @@ static void clear_keywtab(hashtab_T *ht)
/// @param flags flags for this keyword
/// @param cont_in_list containedin for this keyword
/// @param next_list nextgroup for this keyword
-static void add_keyword(char *const name, const int id, const int flags,
+static void add_keyword(char *const name, size_t namelen, const int id, const int flags,
int16_t *const cont_in_list, int16_t *const next_list,
const int conceal_char)
{
char name_folded[MAXKEYWLEN + 1];
- const char *const name_ic = (curwin->w_s->b_syn_ic)
- ? str_foldcase(name, (int)strlen(name), name_folded,
- sizeof(name_folded))
- : name;
+ const char *name_ic;
+ size_t name_iclen;
+ if (curwin->w_s->b_syn_ic) {
+ name_ic = str_foldcase(name, (int)namelen, name_folded, MAXKEYWLEN + 1);
+ name_iclen = strlen(name_ic);
+ } else {
+ name_ic = name;
+ name_iclen = namelen;
+ }
- keyentry_T *const kp = xmalloc(offsetof(keyentry_T, keyword) + strlen(name_ic) + 1);
+ keyentry_T *const kp = xmalloc(offsetof(keyentry_T, keyword) + name_iclen + 1);
STRCPY(kp->keyword, name_ic);
kp->k_syn.id = (int16_t)id;
kp->k_syn.inc_tag = current_syn_inc_tag;
@@ -4061,12 +4066,16 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing)
syn_incl_toplevel(syn_id, &syn_opt_arg.flags);
// 2: Add an entry for each keyword.
- for (char *kw = keyword_copy; --cnt >= 0; kw += strlen(kw) + 1) {
+ size_t kwlen = 0;
+ for (char *kw = keyword_copy; --cnt >= 0; kw += kwlen + 1) {
for (p = vim_strchr(kw, '[');;) {
- if (p != NULL) {
+ if (p == NULL) {
+ kwlen = strlen(kw);
+ } else {
*p = NUL;
+ kwlen = (size_t)(p - kw);
}
- add_keyword(kw, syn_id, syn_opt_arg.flags,
+ add_keyword(kw, kwlen, syn_id, syn_opt_arg.flags,
syn_opt_arg.cont_in_list,
syn_opt_arg.next_list, conceal_char);
if (p == NULL) {
@@ -4082,6 +4091,7 @@ static void syn_cmd_keyword(exarg_T *eap, int syncing)
goto error;
}
kw = p + 1;
+ kwlen = 1;
break; // skip over the "]"
}
const int l = utfc_ptr2len(p + 1);
@@ -4991,7 +5001,7 @@ static int get_id_list(char **const arg, const int keylen, int16_t **const list,
} else {
// Handle match of regexp with group names.
*name = '^';
- STRCAT(name, "$");
+ strcat(name, "$");
regmatch.regprog = vim_regcomp(name, RE_MAGIC);
if (regmatch.regprog == NULL) {
failed = true;
@@ -5205,7 +5215,6 @@ static struct subcommand subcommands[] = {
{ "spell", syn_cmd_spell },
{ "sync", syn_cmd_sync },
{ "", syn_cmd_list },
- { NULL, NULL }
};
/// ":syntax".
@@ -5224,17 +5233,19 @@ void ex_syntax(exarg_T *eap)
if (eap->skip) { // skip error messages for all subcommands
emsg_skip++;
}
- for (int i = 0;; i++) {
- if (subcommands[i].name == NULL) {
- semsg(_("E410: Invalid :syntax subcommand: %s"), subcmd_name);
- break;
- }
+ size_t i;
+ for (i = 0; i < ARRAY_SIZE(subcommands); i++) {
if (strcmp(subcmd_name, subcommands[i].name) == 0) {
eap->arg = skipwhite(subcmd_end);
(subcommands[i].func)(eap, false);
break;
}
}
+
+ if (i == ARRAY_SIZE(subcommands)) {
+ semsg(_("E410: Invalid :syntax subcommand: %s"), subcmd_name);
+ }
+
xfree(subcmd_name);
if (eap->skip) {
emsg_skip--;
@@ -5365,6 +5376,9 @@ char *get_syntax_name(expand_T *xp, int idx)
{
switch (expand_what) {
case EXP_SUBCMD:
+ if (idx < 0 || idx >= (int)ARRAY_SIZE(subcommands)) {
+ return NULL;
+ }
return subcommands[idx].name;
case EXP_CASE: {
static char *case_args[] = { "match", "ignore", NULL };
@@ -5645,9 +5659,8 @@ static void syntime_report(void)
} else {
len = Columns - 70;
}
- if (len > (int)strlen(p->pattern)) {
- len = (int)strlen(p->pattern);
- }
+ int patlen = (int)strlen(p->pattern);
+ len = MIN(len, patlen);
msg_outtrans_len(p->pattern, len, 0);
msg_puts("\n");
}
diff --git a/src/nvim/tag.c b/src/nvim/tag.c
index e7f483dd3d..7a0c1cd810 100644
--- a/src/nvim/tag.c
+++ b/src/nvim/tag.c
@@ -18,6 +18,7 @@
#include "nvim/cmdexpand_defs.h"
#include "nvim/cursor.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds.h"
@@ -585,11 +586,7 @@ void do_tag(char *tag, int type, int count, int forceit, bool verbose)
|| type == DT_LTAG) {
cur_match = MAXCOL - 1;
}
- if (type == DT_TAG) {
- max_num_matches = MAXCOL;
- } else {
- max_num_matches = cur_match + 1;
- }
+ max_num_matches = type == DT_TAG ? MAXCOL : cur_match + 1;
// when the argument starts with '/', use it as a regexp
if (!no_regexp && *name == '/') {
@@ -599,12 +596,8 @@ void do_tag(char *tag, int type, int count, int forceit, bool verbose)
flags = TAG_NOIC;
}
- if (verbose) {
- flags |= TAG_VERBOSE;
- }
- if (!use_tfu) {
- flags |= TAG_NO_TAGFUNC;
- }
+ flags |= verbose ? TAG_VERBOSE : 0;
+ flags |= !use_tfu ? TAG_NO_TAGFUNC : 0;
if (find_tags(name, &new_num_matches, &new_matches, flags,
max_num_matches, buf_ffname) == OK
@@ -814,10 +807,7 @@ static void print_tag_list(bool new_tag, bool use_tagstack, int num_matches, cha
// Assume that the first match indicates how long the tags can
// be, and align the file names to that.
parse_match(matches[0], &tagp);
- int taglen = (int)(tagp.tagname_end - tagp.tagname + 2);
- if (taglen < 18) {
- taglen = 18;
- }
+ int taglen = MAX((int)(tagp.tagname_end - tagp.tagname + 2), 18);
if (taglen > Columns - 25) {
taglen = MAXCOL;
}
@@ -998,10 +988,7 @@ static int add_llist_tags(char *tag, int num_matches, char **matches)
parse_match(matches[i], &tagp);
// Save the tag name
- int len = (int)(tagp.tagname_end - tagp.tagname);
- if (len > 128) {
- len = 128;
- }
+ int len = MIN((int)(tagp.tagname_end - tagp.tagname), 128);
xmemcpyz(tag_name, tagp.tagname, (size_t)len);
tag_name[len] = NUL;
@@ -1059,13 +1046,10 @@ static int add_llist_tags(char *tag, int num_matches, char **matches)
// Precede the tag pattern with \V to make it very
// nomagic.
- STRCAT(cmd, "\\V");
+ strcat(cmd, "\\V");
len += 2;
- int cmd_len = (int)(cmd_end - cmd_start + 1);
- if (cmd_len > (CMDBUFFSIZE - 5)) {
- cmd_len = CMDBUFFSIZE - 5;
- }
+ int cmd_len = MIN((int)(cmd_end - cmd_start + 1), CMDBUFFSIZE - 5);
snprintf(cmd + len, (size_t)(CMDBUFFSIZE + 1 - len),
"%.*s", cmd_len, cmd_start);
len += cmd_len;
@@ -1819,11 +1803,9 @@ static tagmatch_status_T findtags_parse_line(findtags_state_T *st, tagptrs_T *ta
} else if (st->state == TS_STEP_FORWARD) {
assert(cmplen >= 0);
if (mb_strnicmp(tagpp->tagname, st->orgpat->head, (size_t)cmplen) != 0) {
- if ((off_T)vim_ftell(st->fp) > sinfo_p->match_offset) {
- return TAG_MATCH_STOP; // past last match
- } else {
- return TAG_MATCH_NEXT; // before first match
- }
+ return ((off_T)vim_ftell(st->fp) > sinfo_p->match_offset)
+ ? TAG_MATCH_STOP // past last match
+ : TAG_MATCH_NEXT; // before first match
}
} else {
// skip this match if it can't match
@@ -1846,11 +1828,9 @@ static tagmatch_status_T findtags_parse_line(findtags_state_T *st, tagptrs_T *ta
status = parse_tag_line(st->lbuf, tagpp);
}
- if (status == FAIL) {
- return TAG_MATCH_FAIL;
- }
-
- return TAG_MATCH_SUCCESS;
+ return status == FAIL
+ ? TAG_MATCH_FAIL
+ : TAG_MATCH_SUCCESS;
}
/// Initialize the structure used for tag matching.
@@ -1943,32 +1923,22 @@ static void findtags_add_match(findtags_state_T *st, tagptrs_T *tagpp, findtags_
char *buf_ffname, hash_T *hash)
{
const bool name_only = (st->flags & TAG_NAMES);
- int mtt;
size_t len = 0;
size_t mfp_size = 0;
- bool is_current; // file name matches
- bool is_static; // current tag line is static
char *mfp;
// Decide in which array to store this match.
- is_current = test_for_current(tagpp->fname, tagpp->fname_end,
- st->tag_fname, buf_ffname);
- is_static = test_for_static(tagpp);
+ bool is_current = test_for_current(tagpp->fname, tagpp->fname_end,
+ st->tag_fname, buf_ffname);
+
+ // current tag line is static
+ bool is_static = test_for_static(tagpp);
// Decide in which of the sixteen tables to store this match.
- if (is_static) {
- if (is_current) {
- mtt = MT_ST_CUR;
- } else {
- mtt = MT_ST_OTH;
- }
- } else {
- if (is_current) {
- mtt = MT_GL_CUR;
- } else {
- mtt = MT_GL_OTH;
- }
- }
+ int mtt = is_static
+ ? is_current ? MT_ST_CUR : MT_ST_OTH
+ : is_current ? MT_GL_CUR : MT_GL_OTH;
+
if (st->orgpat->regmatch.rm_ic && !margs->match_no_ic) {
mtt += MT_IC_OFF;
}
@@ -2236,13 +2206,11 @@ static void findtags_in_file(findtags_state_T *st, int flags, char *buf_ffname)
static int findtags_copy_matches(findtags_state_T *st, char ***matchesp)
{
const bool name_only = (st->flags & TAG_NAMES);
- char **matches;
- if (st->match_count > 0) {
- matches = xmalloc((size_t)st->match_count * sizeof(char *));
- } else {
- matches = NULL;
- }
+ char **matches = st->match_count > 0
+ ? xmalloc((size_t)st->match_count * sizeof(char *))
+ : NULL;
+
st->match_count = 0;
for (int mtt = 0; mtt < MT_COUNT; mtt++) {
for (int i = 0; i < st->ga_match[mtt].ga_len; i++) {
@@ -2582,6 +2550,10 @@ int get_tagfname(tagname_T *tnp, int first, char *buf)
// move the filename one char forward and truncate the
// filepath with a NUL
filename = path_tail(buf);
+ if (r_ptr != NULL) {
+ STRMOVE(r_ptr + 1, r_ptr);
+ r_ptr++;
+ }
STRMOVE(filename + 1, filename);
*filename++ = NUL;
@@ -2951,13 +2923,13 @@ static int jumpto_tag(const char *lbuf_arg, int forceit, bool keep_help)
p_ic = false; // don't ignore case now
p_scs = false;
linenr_T save_lnum = curwin->w_cursor.lnum;
- if (tagp.tagline > 0) {
- // start search before line from "line:" field
- curwin->w_cursor.lnum = tagp.tagline - 1;
- } else {
- // start search before first line
- curwin->w_cursor.lnum = 0;
- }
+
+ curwin->w_cursor.lnum = tagp.tagline > 0
+ // start search before line from "line:" field
+ ? tagp.tagline - 1
+ // start search before first line
+ : 0;
+
if (do_search(NULL, pbuf[0], pbuf[0], pbuf + 1, pbuflen - 1, 1,
search_options, NULL)) {
retval = OK;
@@ -3180,10 +3152,8 @@ static int find_extra(char **pp)
return FAIL;
}
-//
-// Free a single entry in a tag stack
-//
-static void tagstack_clear_entry(taggy_T *item)
+/// Free a single entry in a tag stack
+void tagstack_clear_entry(taggy_T *item)
{
XFREE_CLEAR(item->tagname);
XFREE_CLEAR(item->user_data);
@@ -3192,18 +3162,12 @@ static void tagstack_clear_entry(taggy_T *item)
/// @param tagnames expand tag names
int expand_tags(bool tagnames, char *pat, int *num_file, char ***file)
{
- int extra_flag;
size_t name_buf_size = 100;
- tagptrs_T t_p;
int ret;
char *name_buf = xmalloc(name_buf_size);
- if (tagnames) {
- extra_flag = TAG_NAMES;
- } else {
- extra_flag = 0;
- }
+ int extra_flag = tagnames ? TAG_NAMES : 0;
if (pat[0] == '/') {
ret = find_tags(pat + 1, num_file, file,
TAG_REGEXP | extra_flag | TAG_VERBOSE | TAG_NO_TAGFUNC,
@@ -3217,10 +3181,9 @@ int expand_tags(bool tagnames, char *pat, int *num_file, char ***file)
// Reorganize the tags for display and matching as strings of:
// "<tagname>\0<kind>\0<filename>\0"
for (int i = 0; i < *num_file; i++) {
- size_t len;
-
+ tagptrs_T t_p;
parse_match((*file)[i], &t_p);
- len = (size_t)(t_p.tagname_end - t_p.tagname);
+ size_t len = (size_t)(t_p.tagname_end - t_p.tagname);
if (len > name_buf_size - 3) {
name_buf_size = len + 3;
char *buf = xrealloc(name_buf, name_buf_size);
@@ -3249,8 +3212,6 @@ int expand_tags(bool tagnames, char *pat, int *num_file, char ***file)
static int add_tag_field(dict_T *dict, const char *field_name, const char *start, const char *end)
FUNC_ATTR_NONNULL_ARG(1, 2)
{
- int len = 0;
-
// Check that the field name doesn't exist yet.
if (tv_dict_find(dict, field_name, -1) != NULL) {
if (p_verbose > 0) {
@@ -3260,6 +3221,7 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char *start
}
return FAIL;
}
+ int len = 0;
char *buf = xmalloc(MAXPATHL);
if (start != NULL) {
if (end == NULL) {
@@ -3268,10 +3230,7 @@ static int add_tag_field(dict_T *dict, const char *field_name, const char *start
end--;
}
}
- len = (int)(end - start);
- if (len > MAXPATHL - 1) {
- len = MAXPATHL - 1;
- }
+ len = MIN((int)(end - start), MAXPATHL - 1);
xmemcpyz(buf, start, (size_t)len);
}
buf[len] = NUL;
@@ -3450,9 +3409,7 @@ static void tagstack_push_item(win_T *wp, char *tagname, int cur_fnum, int cur_m
tagstack[idx].tagname = tagname;
tagstack[idx].cur_fnum = cur_fnum;
tagstack[idx].cur_match = cur_match;
- if (tagstack[idx].cur_match < 0) {
- tagstack[idx].cur_match = 0;
- }
+ tagstack[idx].cur_match = MAX(tagstack[idx].cur_match, 0);
tagstack[idx].fmark.mark = mark;
tagstack[idx].fmark.fnum = fnum;
tagstack[idx].user_data = user_data;
@@ -3502,12 +3459,8 @@ static void tagstack_push_items(win_T *wp, list_T *l)
static void tagstack_set_curidx(win_T *wp, int curidx)
{
wp->w_tagstackidx = curidx;
- if (wp->w_tagstackidx < 0) { // sanity check
- wp->w_tagstackidx = 0;
- }
- if (wp->w_tagstackidx > wp->w_tagstacklen) {
- wp->w_tagstackidx = wp->w_tagstacklen;
- }
+ // sanity check
+ wp->w_tagstackidx = MIN(MAX(wp->w_tagstackidx, 0), wp->w_tagstacklen);
}
// Set the tag stack entries of the specified window.
@@ -3518,15 +3471,15 @@ static void tagstack_set_curidx(win_T *wp, int curidx)
int set_tagstack(win_T *wp, const dict_T *d, int action)
FUNC_ATTR_NONNULL_ARG(1)
{
- dictitem_T *di;
- list_T *l = NULL;
-
// not allowed to alter the tag stack entries from inside tagfunc
if (tfu_in_use) {
emsg(_(e_cannot_modify_tag_stack_within_tagfunc));
return FAIL;
}
+ dictitem_T *di;
+ list_T *l = NULL;
+
if ((di = tv_dict_find(d, "items", -1)) != NULL) {
if (di->di_tv.v_type != VAR_LIST) {
emsg(_(e_listreq));
diff --git a/src/nvim/tag.h b/src/nvim/tag.h
index 42196b44b7..1c6f41050d 100644
--- a/src/nvim/tag.h
+++ b/src/nvim/tag.h
@@ -1,5 +1,6 @@
#pragma once
+#include "nvim/buffer_defs.h" // IWYU pragma: keep
#include "nvim/eval/typval_defs.h" // IWYU pragma: keep
#include "nvim/ex_cmds_defs.h" // IWYU pragma: keep
#include "nvim/option_defs.h" // IWYU pragma: keep
diff --git a/src/nvim/terminal.c b/src/nvim/terminal.c
index 2b05a8047e..b916660024 100644
--- a/src/nvim/terminal.c
+++ b/src/nvim/terminal.c
@@ -40,8 +40,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
-#include <vterm.h>
-#include <vterm_keycodes.h>
#include "klib/kvec.h"
#include "nvim/api/private/helpers.h"
@@ -94,6 +92,8 @@
#include "nvim/ui.h"
#include "nvim/vim_defs.h"
#include "nvim/window.h"
+#include "vterm/vterm.h"
+#include "vterm/vterm_keycodes.h"
typedef struct {
VimState state;
@@ -112,6 +112,9 @@ typedef struct {
// libvterm. Improves performance when receiving large bursts of data.
#define REFRESH_DELAY 10
+#define TEXTBUF_SIZE 0x1fff
+#define SELECTIONBUF_SIZE 0x0400
+
static TimeWatcher refresh_timer;
static bool refresh_pending = false;
@@ -127,7 +130,7 @@ struct terminal {
// buffer used to:
// - convert VTermScreen cell arrays into utf8 strings
// - receive data from libvterm as a result of key presses.
- char textbuf[0x1fff];
+ char textbuf[TEXTBUF_SIZE];
ScrollbackLine **sb_buffer; // Scrollback storage.
size_t sb_current; // Lines stored in sb_buffer.
@@ -166,6 +169,9 @@ struct terminal {
// When there is a pending TermRequest autocommand, block and store input.
StringBuilder *pending_send;
+ char *selection_buffer; /// libvterm selection buffer
+ StringBuilder selection; /// Growable array containing full selection data
+
size_t refcount; // reference count
};
@@ -179,6 +185,12 @@ static VTermScreenCallbacks vterm_screen_callbacks = {
.sb_popline = term_sb_pop,
};
+static VTermSelectionCallbacks vterm_selection_callbacks = {
+ .set = term_selection_set,
+ // For security reasons we don't support querying the system clipboard from the embedded terminal
+ .query = NULL,
+};
+
static Set(ptr_t) invalidated_terminals = SET_INIT;
static void emit_termrequest(void **argv)
@@ -215,11 +227,67 @@ static void schedule_termrequest(Terminal *term, char *payload, size_t payload_l
term->pending_send);
}
+static int parse_osc8(VTermStringFragment frag, int *attr)
+ FUNC_ATTR_NONNULL_ALL
+{
+ // Parse the URI from the OSC 8 sequence and add the URL to our URL set.
+ // Skip the ID, we don't use it (for now)
+ size_t i = 0;
+ for (; i < frag.len; i++) {
+ if (frag.str[i] == ';') {
+ break;
+ }
+ }
+
+ // Move past the semicolon
+ i++;
+
+ if (i >= frag.len) {
+ // Invalid OSC sequence
+ return 0;
+ }
+
+ // Find the terminator
+ const size_t start = i;
+ for (; i < frag.len; i++) {
+ if (frag.str[i] == '\a' || frag.str[i] == '\x1b') {
+ break;
+ }
+ }
+
+ const size_t len = i - start;
+ if (len == 0) {
+ // Empty OSC 8, no URL
+ *attr = 0;
+ return 1;
+ }
+
+ char *url = xmemdupz(&frag.str[start], len + 1);
+ url[len] = 0;
+ *attr = hl_add_url(0, url);
+ xfree(url);
+
+ return 1;
+}
+
static int on_osc(int command, VTermStringFragment frag, void *user)
+ FUNC_ATTR_NONNULL_ALL
{
- if (frag.str == NULL) {
+ Terminal *term = user;
+
+ if (frag.str == NULL || frag.len == 0) {
return 0;
}
+
+ if (command == 8) {
+ int attr = 0;
+ if (parse_osc8(frag, &attr)) {
+ VTermState *state = vterm_obtain_state(term->vt);
+ VTermValue value = { .number = attr };
+ vterm_state_set_penattr(state, VTERM_ATTR_URI, VTERM_VALUETYPE_INT, &value);
+ }
+ }
+
if (!has_event(EVENT_TERMREQUEST)) {
return 1;
}
@@ -227,7 +295,7 @@ static int on_osc(int command, VTermStringFragment frag, void *user)
StringBuilder request = KV_INITIAL_VALUE;
kv_printf(request, "\x1b]%d;", command);
kv_concat_len(request, frag.str, frag.len);
- schedule_termrequest(user, request.items, request.size);
+ schedule_termrequest(term, request.items, request.size);
return 1;
}
@@ -307,14 +375,18 @@ void terminal_open(Terminal **termpp, buf_T *buf, TerminalOptions opts)
// Set up screen
term->vts = vterm_obtain_screen(term->vt);
vterm_screen_enable_altscreen(term->vts, true);
- // TODO(clason): reenable when https://github.com/neovim/neovim/issues/23762 is fixed
- // vterm_screen_enable_reflow(term->vts, true);
+ vterm_screen_enable_reflow(term->vts, true);
// delete empty lines at the end of the buffer
vterm_screen_set_callbacks(term->vts, &vterm_screen_callbacks, term);
vterm_screen_set_unrecognised_fallbacks(term->vts, &vterm_fallbacks, term);
vterm_screen_set_damage_merge(term->vts, VTERM_DAMAGE_SCROLL);
vterm_screen_reset(term->vts, 1);
vterm_output_set_callback(term->vt, term_output_callback, term);
+
+ term->selection_buffer = xcalloc(SELECTIONBUF_SIZE, 1);
+ vterm_state_set_selection_callbacks(state, &vterm_selection_callbacks, term,
+ term->selection_buffer, SELECTIONBUF_SIZE);
+
// force a initial refresh of the screen to ensure the buffer will always
// have as many lines as screen rows when refresh_scrollback is called
term->invalid_start = 0;
@@ -326,14 +398,6 @@ void terminal_open(Terminal **termpp, buf_T *buf, TerminalOptions opts)
refresh_screen(term, buf);
set_option_value(kOptBuftype, STATIC_CSTR_AS_OPTVAL("terminal"), OPT_LOCAL);
- // Default settings for terminal buffers
- buf->b_p_ma = false; // 'nomodifiable'
- buf->b_p_ul = -1; // 'undolevels'
- buf->b_p_scbk = // 'scrollback' (initialize local from global)
- (p_scbk < 0) ? 10000 : MAX(1, p_scbk);
- buf->b_p_tw = 0; // 'textwidth'
- set_option_value(kOptWrap, BOOLEAN_OPTVAL(false), OPT_LOCAL);
- set_option_value(kOptList, BOOLEAN_OPTVAL(false), OPT_LOCAL);
if (buf->b_ffname != NULL) {
buf_set_term_title(buf, buf->b_ffname, strlen(buf->b_ffname));
}
@@ -684,6 +748,10 @@ static int terminal_execute(VimState *state, int key)
}
break;
+ case K_PASTE_START:
+ paste_repeat(1);
+ break;
+
case K_EVENT:
// We cannot let an event free the terminal yet. It is still needed.
s->term->refcount++;
@@ -718,6 +786,12 @@ static int terminal_execute(VimState *state, int key)
FALLTHROUGH;
default:
+ if (key == Ctrl_C) {
+ // terminal_enter() always sets `mapped_ctrl_c` to avoid `got_int`. 8eeda7169aa4
+ // But `got_int` may be set elsewhere, e.g. by interrupt() or an autocommand,
+ // so ensure that it is cleared.
+ got_int = false;
+ }
if (key == Ctrl_BSL && !s->got_bsl) {
s->got_bsl = true;
break;
@@ -769,6 +843,8 @@ void terminal_destroy(Terminal **termpp)
}
xfree(term->sb_buffer);
xfree(term->title);
+ xfree(term->selection_buffer);
+ kv_destroy(term->selection);
vterm_free(term->vt);
xfree(term);
*termpp = NULL; // coverity[dead-store]
@@ -845,7 +921,7 @@ void terminal_paste(int count, char **y_array, size_t y_size)
}
char *dst = buff;
char *src = y_array[j];
- while (*src != '\0') {
+ while (*src != NUL) {
len = (size_t)utf_ptr2len(src);
int c = utf_ptr2char(src);
if (!is_filter_char(c)) {
@@ -982,6 +1058,10 @@ void terminal_get_line_attributes(Terminal *term, win_T *wp, int linenr, int *te
});
}
+ if (cell.uri > 0) {
+ attr_id = hl_combine_attr(attr_id, cell.uri);
+ }
+
if (term->cursor.visible && term->cursor.row == row
&& term->cursor.col == col) {
attr_id = hl_combine_attr(attr_id,
@@ -1180,10 +1260,7 @@ static int term_sb_pop(int cols, VTermScreenCell *cells, void *data)
memmove(term->sb_buffer, term->sb_buffer + 1,
sizeof(term->sb_buffer[0]) * (term->sb_current));
- size_t cols_to_copy = (size_t)cols;
- if (cols_to_copy > sbrow->cols) {
- cols_to_copy = sbrow->cols;
- }
+ size_t cols_to_copy = MIN((size_t)cols, sbrow->cols);
// copy to vterm state
memcpy(cells, sbrow->cells, sizeof(cells[0]) * cols_to_copy);
@@ -1198,6 +1275,54 @@ static int term_sb_pop(int cols, VTermScreenCell *cells, void *data)
return 1;
}
+static void term_clipboard_set(void **argv)
+{
+ VTermSelectionMask mask = (VTermSelectionMask)(long)argv[0];
+ char *data = argv[1];
+
+ char regname;
+ switch (mask) {
+ case VTERM_SELECTION_CLIPBOARD:
+ regname = '+';
+ break;
+ case VTERM_SELECTION_PRIMARY:
+ regname = '*';
+ break;
+ default:
+ regname = '+';
+ break;
+ }
+
+ list_T *lines = tv_list_alloc(1);
+ tv_list_append_allocated_string(lines, data);
+
+ list_T *args = tv_list_alloc(3);
+ tv_list_append_list(args, lines);
+
+ const char regtype = 'v';
+ tv_list_append_string(args, &regtype, 1);
+
+ tv_list_append_string(args, &regname, 1);
+ eval_call_provider("clipboard", "set", args, true);
+}
+
+static int term_selection_set(VTermSelectionMask mask, VTermStringFragment frag, void *user)
+{
+ Terminal *term = user;
+ if (frag.initial) {
+ kv_size(term->selection) = 0;
+ }
+
+ kv_concat_len(term->selection, frag.str, frag.len);
+
+ if (frag.final) {
+ char *data = xmemdupz(term->selection.items, kv_size(term->selection));
+ multiqueue_put(main_loop.events, term_clipboard_set, (void *)mask, data);
+ }
+
+ return 1;
+}
+
// }}}
// input handling {{{
diff --git a/src/nvim/testing.c b/src/nvim/testing.c
index 343568d71e..adbdd3e611 100644
--- a/src/nvim/testing.c
+++ b/src/nvim/testing.c
@@ -7,6 +7,7 @@
#include <string.h>
#include "nvim/ascii_defs.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/encode.h"
#include "nvim/eval/typval.h"
@@ -129,7 +130,7 @@ static void ga_concat_shorten_esc(garray_T *gap, const char *str)
return;
}
- for (const char *p = str; *p != NUL; p++) {
+ for (const char *p = str; *p != NUL;) {
int same_len = 1;
const char *s = p;
const int c = mb_cptr2char_adv(&s);
@@ -145,9 +146,10 @@ static void ga_concat_shorten_esc(garray_T *gap, const char *str)
vim_snprintf(buf, NUMBUFLEN, "%d", same_len);
ga_concat(gap, buf);
ga_concat(gap, " times]");
- p = s - 1;
+ p = s;
} else {
ga_concat_esc(gap, p, clen);
+ p += clen;
}
}
}
@@ -197,7 +199,7 @@ static void fill_assert_error(garray_T *gap, typval_T *opt_msg_tv, const char *e
if (!HASHITEM_EMPTY(hi)) {
dictitem_T *item2 = tv_dict_find(got_d, hi->hi_key, -1);
if (item2 == NULL
- || !tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &item2->di_tv, false, false)) {
+ || !tv_equal(&TV_DICT_HI2DI(hi)->di_tv, &item2->di_tv, false)) {
// item of exp_d not present in got_d or values differ.
const size_t key_len = strlen(hi->hi_key);
tv_dict_add_tv(exp_tv->vval.v_dict, hi->hi_key, key_len, &TV_DICT_HI2DI(hi)->di_tv);
@@ -270,8 +272,7 @@ static int assert_equal_common(typval_T *argvars, assert_type_T atype)
{
garray_T ga;
- if (tv_equal(&argvars[0], &argvars[1], false, false)
- != (atype == ASSERT_EQUAL)) {
+ if (tv_equal(&argvars[0], &argvars[1], false) != (atype == ASSERT_EQUAL)) {
prepare_assert_error(&ga);
fill_assert_error(&ga, &argvars[2], NULL,
&argvars[0], &argvars[1], atype);
diff --git a/src/nvim/textformat.c b/src/nvim/textformat.c
index 1722bcc968..9095d4e8c9 100644
--- a/src/nvim/textformat.c
+++ b/src/nvim/textformat.c
@@ -47,7 +47,7 @@ static bool did_add_space = false; ///< auto_format() added an extra space
///< under the cursor
#define WHITECHAR(cc) (ascii_iswhite(cc) \
- && !utf_iscomposing(utf_ptr2char((char *)get_cursor_pos_ptr() + 1)))
+ && !utf_iscomposing_first(utf_ptr2char((char *)get_cursor_pos_ptr() + 1)))
/// Return true if format option 'x' is in effect.
/// Take care of no formatting when 'paste' is set.
@@ -348,9 +348,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
inc_cursor();
}
startcol -= curwin->w_cursor.col;
- if (startcol < 0) {
- startcol = 0;
- }
+ startcol = MAX(startcol, 0);
if (State & VREPLACE_FLAG) {
// In MODE_VREPLACE state, we will backspace over the text to be
@@ -402,7 +400,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
}
if (second_indent >= 0) {
if (State & VREPLACE_FLAG) {
- change_indent(INDENT_SET, second_indent, false, NUL, true);
+ change_indent(INDENT_SET, second_indent, false, true);
} else if (leader_len > 0 && second_indent - leader_len > 0) {
int padding = second_indent - leader_len;
@@ -433,9 +431,7 @@ void internal_format(int textwidth, int second_indent, int flags, bool format_on
// may have added or removed indent.
curwin->w_cursor.col += startcol;
colnr_T len = get_cursor_line_len();
- if (curwin->w_cursor.col > len) {
- curwin->w_cursor.col = len;
- }
+ curwin->w_cursor.col = MIN(curwin->w_cursor.col, len);
}
haveto_redraw = true;
@@ -758,14 +754,9 @@ int comp_textwidth(bool ff)
textwidth -= 8;
}
}
- if (textwidth < 0) {
- textwidth = 0;
- }
+ textwidth = MAX(textwidth, 0);
if (ff && textwidth == 0) {
- textwidth = curwin->w_width_inner - 1;
- if (textwidth > 79) {
- textwidth = 79;
- }
+ textwidth = MIN(curwin->w_width_inner - 1, 79);
}
return textwidth;
}
@@ -878,7 +869,7 @@ int fex_format(linenr_T lnum, long count, int c)
if (use_sandbox) {
sandbox++;
}
- int r = (int)eval_to_number(fex);
+ int r = (int)eval_to_number(fex, true);
if (use_sandbox) {
sandbox--;
}
@@ -1105,11 +1096,7 @@ void format_lines(linenr_T line_count, bool avoid_fex)
}
first_par_line = false;
// If the line is getting long, format it next time
- if (get_cursor_line_len() > max_len) {
- force_format = true;
- } else {
- force_format = false;
- }
+ force_format = get_cursor_line_len() > max_len;
}
}
line_breakcheck();
diff --git a/src/nvim/textobject.c b/src/nvim/textobject.c
index 3c81a840ea..45978765bf 100644
--- a/src/nvim/textobject.c
+++ b/src/nvim/textobject.c
@@ -1193,13 +1193,18 @@ again:
pos_T end_pos = curwin->w_cursor;
if (!do_include) {
- // Exclude the start tag.
+ // Exclude the start tag,
+ // but skip over '>' if it appears in quotes
+ bool in_quotes = false;
curwin->w_cursor = start_pos;
while (inc_cursor() >= 0) {
- if (*get_cursor_pos_ptr() == '>') {
+ p = get_cursor_pos_ptr();
+ if (*p == '>' && !in_quotes) {
inc_cursor();
start_pos = curwin->w_cursor;
break;
+ } else if (*p == '"' || *p == '\'') {
+ in_quotes = !in_quotes;
}
}
curwin->w_cursor = end_pos;
@@ -1264,11 +1269,7 @@ int current_par(oparg_T *oap, int count, bool include, int type)
// When visual area is more than one line: extend it.
if (VIsual_active && start_lnum != VIsual.lnum) {
extend:
- if (start_lnum < VIsual.lnum) {
- dir = BACKWARD;
- } else {
- dir = FORWARD;
- }
+ dir = start_lnum < VIsual.lnum ? BACKWARD : FORWARD;
for (int i = count; --i >= 0;) {
if (start_lnum ==
(dir == BACKWARD ? 1 : curbuf->b_ml.ml_line_count)) {
diff --git a/src/nvim/tui/input.c b/src/nvim/tui/input.c
index f1594dfcb9..98dd7b4b45 100644
--- a/src/nvim/tui/input.c
+++ b/src/nvim/tui/input.c
@@ -7,28 +7,29 @@
#include "nvim/api/private/defs.h"
#include "nvim/api/private/helpers.h"
#include "nvim/event/loop.h"
+#include "nvim/event/rstream.h"
#include "nvim/event/stream.h"
#include "nvim/macros_defs.h"
#include "nvim/main.h"
#include "nvim/map_defs.h"
#include "nvim/memory.h"
+#include "nvim/msgpack_rpc/channel.h"
#include "nvim/option_vars.h"
#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
-#include "nvim/rbuffer.h"
#include "nvim/strings.h"
#include "nvim/tui/input.h"
#include "nvim/tui/input_defs.h"
+#include "nvim/tui/termkey/driver-csi.h"
+#include "nvim/tui/termkey/termkey.h"
#include "nvim/tui/tui.h"
#include "nvim/ui_client.h"
+
#ifdef MSWIN
# include "nvim/os/os_win_console.h"
#endif
-#include "nvim/event/rstream.h"
-#include "nvim/msgpack_rpc/channel.h"
#define READ_STREAM_SIZE 0xfff
-#define KEY_BUFFER_SIZE 0xfff
/// Size of libtermkey's internal input buffer. The buffer may grow larger than
/// this when processing very long escape sequences, but will shrink back to
@@ -132,7 +133,6 @@ void tinput_init(TermInput *input, Loop *loop)
input->key_encoding = kKeyEncodingLegacy;
input->ttimeout = (bool)p_ttimeout;
input->ttimeoutlen = p_ttm;
- input->key_buffer = rbuffer_new(KEY_BUFFER_SIZE);
for (size_t i = 0; i < ARRAY_SIZE(kitty_key_map_entry); i++) {
pmap_put(int)(&kitty_key_map, kitty_key_map_entry[i].key, (ptr_t)kitty_key_map_entry[i].name);
@@ -155,7 +155,7 @@ void tinput_init(TermInput *input, Loop *loop)
termkey_set_canonflags(input->tk, curflags | TERMKEY_CANON_DELBS);
// setup input handle
- rstream_init_fd(loop, &input->read_stream, input->in_fd, READ_STREAM_SIZE);
+ rstream_init_fd(loop, &input->read_stream, input->in_fd);
// initialize a timer handle for handling ESC with libtermkey
uv_timer_init(&loop->uv, &input->timer_handle);
@@ -165,9 +165,8 @@ void tinput_init(TermInput *input, Loop *loop)
void tinput_destroy(TermInput *input)
{
map_destroy(int, &kitty_key_map);
- rbuffer_free(input->key_buffer);
uv_close((uv_handle_t *)&input->timer_handle, NULL);
- stream_close(&input->read_stream, NULL, NULL);
+ rstream_may_close(&input->read_stream);
termkey_destroy(input->tk);
}
@@ -191,44 +190,38 @@ static void tinput_done_event(void **argv)
/// Send all pending input in key buffer to Nvim server.
static void tinput_flush(TermInput *input)
{
+ String keys = { .data = input->key_buffer, .size = input->key_buffer_len };
if (input->paste) { // produce exactly one paste event
- const size_t len = rbuffer_size(input->key_buffer);
- String keys = { .data = xmallocz(len), .size = len };
- rbuffer_read(input->key_buffer, keys.data, len);
MAXSIZE_TEMP_ARRAY(args, 3);
ADD_C(args, STRING_OBJ(keys)); // 'data'
ADD_C(args, BOOLEAN_OBJ(true)); // 'crlf'
ADD_C(args, INTEGER_OBJ(input->paste)); // 'phase'
rpc_send_event(ui_client_channel_id, "nvim_paste", args);
- api_free_string(keys);
if (input->paste == 1) {
// Paste phase: "continue"
input->paste = 2;
}
- rbuffer_reset(input->key_buffer);
} else { // enqueue input
- RBUFFER_UNTIL_EMPTY(input->key_buffer, buf, len) {
- const String keys = { .data = buf, .size = len };
+ if (input->key_buffer_len > 0) {
MAXSIZE_TEMP_ARRAY(args, 1);
ADD_C(args, STRING_OBJ(keys));
// NOTE: This is non-blocking and won't check partially processed input,
// but should be fine as all big sends are handled with nvim_paste, not nvim_input
rpc_send_event(ui_client_channel_id, "nvim_input", args);
- rbuffer_consumed(input->key_buffer, len);
- rbuffer_reset(input->key_buffer);
}
}
+ input->key_buffer_len = 0;
}
-static void tinput_enqueue(TermInput *input, char *buf, size_t size)
+static void tinput_enqueue(TermInput *input, const char *buf, size_t size)
{
- if (rbuffer_size(input->key_buffer) >
- rbuffer_capacity(input->key_buffer) - 0xff) {
- // don't ever let the buffer get too full or we risk putting incomplete keys
- // into it
+ if (input->key_buffer_len > KEY_BUFFER_SIZE - size) {
+ // don't ever let the buffer get too full or we risk putting incomplete keys into it
tinput_flush(input);
}
- rbuffer_write(input->key_buffer, buf, size);
+ size_t to_copy = MIN(size, KEY_BUFFER_SIZE - input->key_buffer_len);
+ memcpy(input->key_buffer + input->key_buffer_len, buf, to_copy);
+ input->key_buffer_len += to_copy;
}
/// Handle TERMKEY_KEYMOD_* modifiers, i.e. Shift, Alt and Ctrl.
@@ -271,7 +264,7 @@ static size_t handle_more_modifiers(TermKeyKey *key, char *buf, size_t buflen)
static void handle_kitty_key_protocol(TermInput *input, TermKeyKey *key)
{
- const char *name = pmap_get(int)(&kitty_key_map, (int)key->code.codepoint);
+ const char *name = pmap_get(int)(&kitty_key_map, key->code.codepoint);
if (name) {
char buf[64];
size_t len = 0;
@@ -471,9 +464,11 @@ static void tinput_timer_cb(uv_timer_t *handle)
{
TermInput *input = handle->data;
// If the raw buffer is not empty, process the raw buffer first because it is
- // processing an incomplete bracketed paster sequence.
- if (rbuffer_size(input->read_stream.buffer)) {
- handle_raw_buffer(input, true);
+ // processing an incomplete bracketed paste sequence.
+ size_t size = rstream_available(&input->read_stream);
+ if (size) {
+ size_t consumed = handle_raw_buffer(input, true, input->read_stream.read_pos, size);
+ rstream_consume(&input->read_stream, consumed);
}
tk_getkeys(input, true);
tinput_flush(input);
@@ -487,39 +482,37 @@ static void tinput_timer_cb(uv_timer_t *handle)
///
/// @param input the input stream
/// @return true iff handle_focus_event consumed some input
-static bool handle_focus_event(TermInput *input)
+static size_t handle_focus_event(TermInput *input, const char *ptr, size_t size)
{
- if (rbuffer_size(input->read_stream.buffer) > 2
- && (!rbuffer_cmp(input->read_stream.buffer, "\x1b[I", 3)
- || !rbuffer_cmp(input->read_stream.buffer, "\x1b[O", 3))) {
- bool focus_gained = *rbuffer_get(input->read_stream.buffer, 2) == 'I';
- // Advance past the sequence
- rbuffer_consumed(input->read_stream.buffer, 3);
+ if (size >= 3
+ && (!memcmp(ptr, "\x1b[I", 3)
+ || !memcmp(ptr, "\x1b[O", 3))) {
+ bool focus_gained = ptr[2] == 'I';
MAXSIZE_TEMP_ARRAY(args, 1);
ADD_C(args, BOOLEAN_OBJ(focus_gained));
rpc_send_event(ui_client_channel_id, "nvim_ui_set_focus", args);
- return true;
+ return 3; // Advance past the sequence
}
- return false;
+ return 0;
}
#define START_PASTE "\x1b[200~"
#define END_PASTE "\x1b[201~"
-static HandleState handle_bracketed_paste(TermInput *input)
+static size_t handle_bracketed_paste(TermInput *input, const char *ptr, size_t size,
+ bool *incomplete)
{
- size_t buf_size = rbuffer_size(input->read_stream.buffer);
- if (buf_size > 5
- && (!rbuffer_cmp(input->read_stream.buffer, START_PASTE, 6)
- || !rbuffer_cmp(input->read_stream.buffer, END_PASTE, 6))) {
- bool enable = *rbuffer_get(input->read_stream.buffer, 4) == '0';
+ if (size >= 6
+ && (!memcmp(ptr, START_PASTE, 6)
+ || !memcmp(ptr, END_PASTE, 6))) {
+ bool enable = ptr[4] == '0';
if (input->paste && enable) {
- return kNotApplicable; // Pasting "start paste" code literally.
+ return 0; // Pasting "start paste" code literally.
}
+
// Advance past the sequence
- rbuffer_consumed(input->read_stream.buffer, 6);
if (!!input->paste == enable) {
- return kComplete; // Spurious "disable paste" code.
+ return 6; // Spurious "disable paste" code.
}
if (enable) {
@@ -534,15 +527,15 @@ static HandleState handle_bracketed_paste(TermInput *input)
// Paste phase: "disabled".
input->paste = 0;
}
- return kComplete;
- } else if (buf_size < 6
- && (!rbuffer_cmp(input->read_stream.buffer, START_PASTE, buf_size)
- || !rbuffer_cmp(input->read_stream.buffer,
- END_PASTE, buf_size))) {
+ return 6;
+ } else if (size < 6
+ && (!memcmp(ptr, START_PASTE, size)
+ || !memcmp(ptr, END_PASTE, size))) {
// Wait for further input, as the sequence may be split.
- return kIncomplete;
+ *incomplete = true;
+ return 0;
}
- return kNotApplicable;
+ return 0;
}
/// Handle an OSC or DCS response sequence from the terminal.
@@ -606,10 +599,10 @@ static void handle_unknown_csi(TermInput *input, const TermKeyKey *key)
{
// There is no specified limit on the number of parameters a CSI sequence can
// contain, so just allocate enough space for a large upper bound
- long args[16];
- size_t nargs = 16;
- unsigned long cmd;
- if (termkey_interpret_csi(input->tk, key, args, &nargs, &cmd) != TERMKEY_RES_KEY) {
+ TermKeyCsiParam params[16];
+ size_t nparams = 16;
+ unsigned cmd;
+ if (termkey_interpret_csi(input->tk, key, params, &nparams, &cmd) != TERMKEY_RES_KEY) {
return;
}
@@ -648,25 +641,55 @@ static void handle_unknown_csi(TermInput *input, const TermKeyKey *key)
break;
}
break;
+ case 't':
+ if (nparams == 5) {
+ // We only care about the first 3 parameters, and we ignore subparameters
+ int args[3];
+ for (size_t i = 0; i < ARRAY_SIZE(args); i++) {
+ if (termkey_interpret_csi_param(params[i], &args[i], NULL, NULL) != TERMKEY_RES_KEY) {
+ return;
+ }
+ }
+
+ if (args[0] == 48) {
+ // In-band resize event (DEC private mode 2048)
+ int height_chars = args[1];
+ int width_chars = args[2];
+ tui_set_size(input->tui_data, width_chars, height_chars);
+ ui_client_set_size(width_chars, height_chars);
+ }
+ }
+ break;
default:
break;
}
}
-static void handle_raw_buffer(TermInput *input, bool force)
+static size_t handle_raw_buffer(TermInput *input, bool force, const char *data, size_t size)
{
- HandleState is_paste = kNotApplicable;
+ const char *ptr = data;
do {
- if (!force
- && (handle_focus_event(input)
- || (is_paste = handle_bracketed_paste(input)) != kNotApplicable)) {
- if (is_paste == kIncomplete) {
+ if (!force) {
+ size_t consumed = handle_focus_event(input, ptr, size);
+ if (consumed) {
+ ptr += consumed;
+ size -= consumed;
+ continue;
+ }
+
+ bool incomplete = false;
+ consumed = handle_bracketed_paste(input, ptr, size, &incomplete);
+ if (incomplete) {
+ assert(consumed == 0);
// Wait for the next input, leaving it in the raw buffer due to an
// incomplete sequence.
- return;
+ return (size_t)(ptr - data);
+ } else if (consumed) {
+ ptr += consumed;
+ size -= consumed;
+ continue;
}
- continue;
}
//
@@ -675,55 +698,47 @@ static void handle_raw_buffer(TermInput *input, bool force)
// calls (above) depend on this.
//
size_t count = 0;
- RBUFFER_EACH(input->read_stream.buffer, c, i) {
+ for (size_t i = 0; i < size; i++) {
count = i + 1;
- if (c == '\x1b' && count > 1) {
+ if (ptr[i] == '\x1b' && count > 1) {
count--;
break;
}
}
// Push bytes directly (paste).
if (input->paste) {
- RBUFFER_UNTIL_EMPTY(input->read_stream.buffer, ptr, len) {
- size_t consumed = MIN(count, len);
- assert(consumed <= input->read_stream.buffer->size);
- tinput_enqueue(input, ptr, consumed);
- rbuffer_consumed(input->read_stream.buffer, consumed);
- if (!(count -= consumed)) {
- break;
- }
- }
+ tinput_enqueue(input, ptr, count);
+ ptr += count;
+ size -= count;
continue;
}
+
// Push through libtermkey (translates to "<keycode>" strings, etc.).
- RBUFFER_UNTIL_EMPTY(input->read_stream.buffer, ptr, len) {
- const size_t size = MIN(count, len);
- if (size > termkey_get_buffer_remaining(input->tk)) {
+ {
+ const size_t to_use = MIN(count, size);
+ if (to_use > termkey_get_buffer_remaining(input->tk)) {
// We are processing a very long escape sequence. Increase termkey's
// internal buffer size. We don't handle out of memory situations so
// abort if it fails
- const size_t delta = size - termkey_get_buffer_remaining(input->tk);
+ const size_t delta = to_use - termkey_get_buffer_remaining(input->tk);
const size_t bufsize = termkey_get_buffer_size(input->tk);
if (!termkey_set_buffer_size(input->tk, MAX(bufsize + delta, bufsize * 2))) {
abort();
}
}
- size_t consumed = termkey_push_bytes(input->tk, ptr, size);
+ size_t consumed = termkey_push_bytes(input->tk, ptr, to_use);
// We resize termkey's buffer when it runs out of space, so this should
// never happen
- assert(consumed <= rbuffer_size(input->read_stream.buffer));
- rbuffer_consumed(input->read_stream.buffer, consumed);
+ assert(consumed <= to_use);
+ ptr += consumed;
+ size -= consumed;
// Process the input buffer now for any keys
tk_getkeys(input, false);
-
- if (!(count -= consumed)) {
- break;
- }
}
- } while (rbuffer_size(input->read_stream.buffer));
+ } while (size);
const size_t tk_size = termkey_get_buffer_size(input->tk);
const size_t tk_remaining = termkey_get_buffer_remaining(input->tk);
@@ -735,23 +750,25 @@ static void handle_raw_buffer(TermInput *input, bool force)
abort();
}
}
+
+ return (size_t)(ptr - data);
}
-static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, void *data, bool eof)
+static size_t tinput_read_cb(RStream *stream, const char *buf, size_t count_, void *data, bool eof)
{
TermInput *input = data;
+ size_t consumed = handle_raw_buffer(input, false, buf, count_);
+ tinput_flush(input);
+
if (eof) {
loop_schedule_fast(&main_loop, event_create(tinput_done_event, NULL));
- return;
+ return consumed;
}
- handle_raw_buffer(input, false);
- tinput_flush(input);
-
// An incomplete sequence was found. Leave it in the raw buffer and wait for
// the next input.
- if (rbuffer_size(input->read_stream.buffer)) {
+ if (consumed < count_) {
// If 'ttimeout' is not set, start the timer with a timeout of 0 to process
// the next input.
int64_t ms = input->ttimeout
@@ -759,11 +776,7 @@ static void tinput_read_cb(Stream *stream, RBuffer *buf, size_t count_, void *da
// Stop the current timer if already running
uv_timer_stop(&input->timer_handle);
uv_timer_start(&input->timer_handle, tinput_timer_cb, (uint32_t)ms, 0);
- return;
}
- // Make sure the next input escape sequence fits into the ring buffer without
- // wraparound, else it could be misinterpreted (because rbuffer_read_ptr()
- // exposes the underlying buffer to callers unaware of the wraparound).
- rbuffer_reset(input->read_stream.buffer);
+ return consumed;
}
diff --git a/src/nvim/tui/input.h b/src/nvim/tui/input.h
index bf6d0f2978..4c2baf908e 100644
--- a/src/nvim/tui/input.h
+++ b/src/nvim/tui/input.h
@@ -5,11 +5,10 @@
#include <uv.h>
#include "nvim/event/defs.h"
-#include "nvim/rbuffer_defs.h"
#include "nvim/tui/input_defs.h" // IWYU pragma: keep
+#include "nvim/tui/termkey/termkey_defs.h"
#include "nvim/tui/tui_defs.h"
#include "nvim/types_defs.h"
-#include "termkey/termkey.h"
typedef enum {
kKeyEncodingLegacy, ///< Legacy key encoding
@@ -17,6 +16,7 @@ typedef enum {
kKeyEncodingXterm, ///< Xterm's modifyOtherKeys encoding (XTMODKEYS)
} KeyEncoding;
+#define KEY_BUFFER_SIZE 0x1000
typedef struct {
int in_fd;
// Phases: -1=all 0=disabled 1=first-chunk 2=continue 3=last-chunk
@@ -33,17 +33,12 @@ typedef struct {
TermKey_Terminfo_Getstr_Hook *tk_ti_hook_fn; ///< libtermkey terminfo hook
uv_timer_t timer_handle;
Loop *loop;
- Stream read_stream;
- RBuffer *key_buffer;
+ RStream read_stream;
TUIData *tui_data;
+ char key_buffer[KEY_BUFFER_SIZE];
+ size_t key_buffer_len;
} TermInput;
-typedef enum {
- kIncomplete = -1,
- kNotApplicable = 0,
- kComplete = 1,
-} HandleState;
-
#ifdef INCLUDE_GENERATED_DECLARATIONS
# include "tui/input.h.generated.h"
#endif
diff --git a/src/nvim/tui/terminfo.c b/src/nvim/tui/terminfo.c
index 3cf9650428..657bd6dd10 100644
--- a/src/nvim/tui/terminfo.c
+++ b/src/nvim/tui/terminfo.c
@@ -35,7 +35,7 @@ bool terminfo_is_term_family(const char *term, const char *family)
// The screen terminfo may have a terminal name like screen.xterm. By making
// the dot(.) a valid separator, such terminal names will also be the
// terminal family of the screen.
- && ('\0' == term[flen] || '-' == term[flen] || '.' == term[flen]);
+ && (NUL == term[flen] || '-' == term[flen] || '.' == term[flen]);
}
bool terminfo_is_bsd_console(const char *term)
diff --git a/src/nvim/tui/termkey/README b/src/nvim/tui/termkey/README
new file mode 100644
index 0000000000..fd081025fc
--- /dev/null
+++ b/src/nvim/tui/termkey/README
@@ -0,0 +1 @@
+// Adapted from libtermkey: https://github.com/neovim/libtermkey
diff --git a/src/nvim/tui/termkey/driver-csi.c b/src/nvim/tui/termkey/driver-csi.c
new file mode 100644
index 0000000000..28c7eaccfd
--- /dev/null
+++ b/src/nvim/tui/termkey/driver-csi.c
@@ -0,0 +1,902 @@
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "nvim/memory.h"
+#include "nvim/tui/termkey/driver-csi.h"
+#include "nvim/tui/termkey/termkey-internal.h"
+#include "nvim/tui/termkey/termkey.h"
+#include "nvim/tui/termkey/termkey_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "tui/termkey/driver-csi.c.generated.h"
+#endif
+
+// There are 64 codes 0x40 - 0x7F
+static int keyinfo_initialised = 0;
+static struct keyinfo ss3s[64];
+static char ss3_kpalts[64];
+
+typedef TermKeyResult CsiHandler(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params,
+ int nparams);
+static CsiHandler *csi_handlers[64];
+
+// Handler for CSI/SS3 cmd keys
+
+static struct keyinfo csi_ss3s[64];
+
+static TermKeyResult handle_csi_ss3_full(TermKey *tk, TermKeyKey *key, int cmd,
+ TermKeyCsiParam *params, int nparams)
+{
+ TermKeyResult result = TERMKEY_RES_KEY;
+
+ if (nparams > 1 && params[1].param != NULL) {
+ int arg = 0;
+ result = termkey_interpret_csi_param(params[1], &arg, NULL, NULL);
+ if (result != TERMKEY_RES_KEY) {
+ return result;
+ }
+
+ key->modifiers = arg - 1;
+ } else {
+ key->modifiers = 0;
+ }
+
+ key->type = csi_ss3s[cmd - 0x40].type;
+ key->code.sym = csi_ss3s[cmd - 0x40].sym;
+ key->modifiers &= ~(csi_ss3s[cmd - 0x40].modifier_mask);
+ key->modifiers |= csi_ss3s[cmd - 0x40].modifier_set;
+
+ if (key->code.sym == TERMKEY_SYM_UNKNOWN) {
+ result = TERMKEY_RES_NONE;
+ }
+
+ return result;
+}
+
+static void register_csi_ss3_full(TermKeyType type, TermKeySym sym, int modifier_set,
+ int modifier_mask, unsigned char cmd)
+{
+ if (cmd < 0x40 || cmd >= 0x80) {
+ return;
+ }
+
+ csi_ss3s[cmd - 0x40].type = type;
+ csi_ss3s[cmd - 0x40].sym = sym;
+ csi_ss3s[cmd - 0x40].modifier_set = modifier_set;
+ csi_ss3s[cmd - 0x40].modifier_mask = modifier_mask;
+
+ csi_handlers[cmd - 0x40] = &handle_csi_ss3_full;
+}
+
+static void register_csi_ss3(TermKeyType type, TermKeySym sym, unsigned char cmd)
+{
+ register_csi_ss3_full(type, sym, 0, 0, cmd);
+}
+
+/// Handler for SS3 keys with kpad alternate representations
+static void register_ss3kpalt(TermKeyType type, TermKeySym sym, unsigned char cmd, char kpalt)
+{
+ if (cmd < 0x40 || cmd >= 0x80) {
+ return;
+ }
+
+ ss3s[cmd - 0x40].type = type;
+ ss3s[cmd - 0x40].sym = sym;
+ ss3s[cmd - 0x40].modifier_set = 0;
+ ss3s[cmd - 0x40].modifier_mask = 0;
+ ss3_kpalts[cmd - 0x40] = kpalt;
+}
+
+// Handler for CSI number ~ function keys
+
+#define NCSIFUNCS 35 // This value must be increased if more CSI function keys are added
+static struct keyinfo csifuncs[NCSIFUNCS];
+
+static TermKeyResult handle_csifunc(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params,
+ int nparams)
+{
+ if (nparams == 0) {
+ return TERMKEY_RES_NONE;
+ }
+
+ TermKeyResult result = TERMKEY_RES_KEY;
+ int args[3];
+
+ if (nparams > 1 && params[1].param != NULL) {
+ result = termkey_interpret_csi_param(params[1], &args[1], NULL, NULL);
+ if (result != TERMKEY_RES_KEY) {
+ return result;
+ }
+
+ key->modifiers = args[1] - 1;
+ } else {
+ key->modifiers = 0;
+ }
+
+ key->type = TERMKEY_TYPE_KEYSYM;
+
+ result = termkey_interpret_csi_param(params[0], &args[0], NULL, NULL);
+ if (result != TERMKEY_RES_KEY) {
+ return result;
+ }
+
+ if (args[0] == 27 && nparams > 2 && params[2].param != NULL) {
+ result = termkey_interpret_csi_param(params[2], &args[2], NULL, NULL);
+ if (result != TERMKEY_RES_KEY) {
+ return result;
+ }
+
+ int mod = key->modifiers;
+ (*tk->method.emit_codepoint)(tk, args[2], key);
+ key->modifiers |= mod;
+ } else if (args[0] >= 0 && args[0] < NCSIFUNCS) {
+ key->type = csifuncs[args[0]].type;
+ key->code.sym = csifuncs[args[0]].sym;
+ key->modifiers &= ~(csifuncs[args[0]].modifier_mask);
+ key->modifiers |= csifuncs[args[0]].modifier_set;
+ } else {
+ key->code.sym = TERMKEY_SYM_UNKNOWN;
+ }
+
+ if (key->code.sym == TERMKEY_SYM_UNKNOWN) {
+#ifdef DEBUG
+ fprintf(stderr, "CSI: Unknown function key %ld\n", arg[0]);
+#endif
+ result = TERMKEY_RES_NONE;
+ }
+
+ return result;
+}
+
+static void register_csifunc(TermKeyType type, TermKeySym sym, int number)
+{
+ if (number >= NCSIFUNCS) {
+ return;
+ }
+
+ csifuncs[number].type = type;
+ csifuncs[number].sym = sym;
+ csifuncs[number].modifier_set = 0;
+ csifuncs[number].modifier_mask = 0;
+
+ csi_handlers['~' - 0x40] = &handle_csifunc;
+}
+
+/// Handler for CSI u extended Unicode keys
+static TermKeyResult handle_csi_u(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params,
+ int nparams)
+{
+ switch (cmd) {
+ case 'u': {
+ int args[2];
+ if (nparams > 1 && params[1].param != NULL) {
+ int subparam = 0;
+ size_t nsubparams = 1;
+ if (termkey_interpret_csi_param(params[1], &args[1], &subparam,
+ &nsubparams) != TERMKEY_RES_KEY) {
+ return TERMKEY_RES_ERROR;
+ }
+
+ if (nsubparams > 0 && subparam != 1) {
+ // Not a press event. Ignore for now
+ return TERMKEY_RES_NONE;
+ }
+
+ key->modifiers = args[1] - 1;
+ } else {
+ key->modifiers = 0;
+ }
+
+ if (termkey_interpret_csi_param(params[0], &args[0], NULL, NULL) != TERMKEY_RES_KEY) {
+ return TERMKEY_RES_ERROR;
+ }
+
+ int mod = key->modifiers;
+ key->type = TERMKEY_TYPE_KEYSYM;
+ (*tk->method.emit_codepoint)(tk, args[0], key);
+ key->modifiers |= mod;
+
+ return TERMKEY_RES_KEY;
+ }
+ default:
+ return TERMKEY_RES_NONE;
+ }
+}
+
+/// Handler for CSI M / CSI m mouse events in SGR and rxvt encodings
+/// Note: This does not handle X10 encoding
+static TermKeyResult handle_csi_m(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params,
+ int nparams)
+{
+ int initial = cmd >> 8;
+ cmd &= 0xff;
+
+ switch (cmd) {
+ case 'M':
+ case 'm':
+ break;
+ default:
+ return TERMKEY_RES_NONE;
+ }
+
+ if (nparams < 3) {
+ return TERMKEY_RES_NONE;
+ }
+
+ int args[3];
+ for (size_t i = 0; i < 3; i++) {
+ if (termkey_interpret_csi_param(params[i], &args[i], NULL, NULL) != TERMKEY_RES_KEY) {
+ return TERMKEY_RES_ERROR;
+ }
+ }
+
+ if (!initial) { // rxvt protocol
+ key->type = TERMKEY_TYPE_MOUSE;
+ key->code.mouse[0] = (char)args[0];
+
+ key->modifiers = (key->code.mouse[0] & 0x1c) >> 2;
+ key->code.mouse[0] &= ~0x1c;
+
+ termkey_key_set_linecol(key, args[1], args[2]);
+
+ return TERMKEY_RES_KEY;
+ }
+
+ if (initial == '<') { // SGR protocol
+ key->type = TERMKEY_TYPE_MOUSE;
+ key->code.mouse[0] = (char)args[0];
+
+ key->modifiers = (key->code.mouse[0] & 0x1c) >> 2;
+ key->code.mouse[0] &= ~0x1c;
+
+ termkey_key_set_linecol(key, args[1], args[2]);
+
+ if (cmd == 'm') { // release
+ key->code.mouse[3] |= 0x80;
+ }
+
+ return TERMKEY_RES_KEY;
+ }
+
+ return TERMKEY_RES_NONE;
+}
+
+TermKeyResult termkey_interpret_mouse(TermKey *tk, const TermKeyKey *key, TermKeyMouseEvent *event,
+ int *button, int *line, int *col)
+{
+ if (key->type != TERMKEY_TYPE_MOUSE) {
+ return TERMKEY_RES_NONE;
+ }
+
+ if (button) {
+ *button = 0;
+ }
+
+ termkey_key_get_linecol(key, line, col);
+
+ if (!event) {
+ return TERMKEY_RES_KEY;
+ }
+
+ int btn = 0;
+
+ int code = (unsigned char)key->code.mouse[0];
+
+ int drag = code & 0x20;
+
+ code &= ~0x3c;
+
+ switch (code) {
+ case 0:
+ case 1:
+ case 2:
+ *event = drag ? TERMKEY_MOUSE_DRAG : TERMKEY_MOUSE_PRESS;
+ btn = code + 1;
+ break;
+
+ case 3:
+ *event = TERMKEY_MOUSE_RELEASE;
+ // no button hint
+ break;
+
+ case 64:
+ case 65:
+ case 66:
+ case 67:
+ *event = drag ? TERMKEY_MOUSE_DRAG : TERMKEY_MOUSE_PRESS;
+ btn = code + 4 - 64;
+ break;
+
+ default:
+ *event = TERMKEY_MOUSE_UNKNOWN;
+ }
+
+ if (button) {
+ *button = btn;
+ }
+
+ if (key->code.mouse[3] & 0x80) {
+ *event = TERMKEY_MOUSE_RELEASE;
+ }
+
+ return TERMKEY_RES_KEY;
+}
+
+/// Handler for CSI ? R position reports
+/// A plain CSI R with no arguments is probably actually <F3>
+static TermKeyResult handle_csi_R(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params,
+ int nparams)
+{
+ switch (cmd) {
+ case 'R'|'?' << 8:
+ if (nparams < 2) {
+ return TERMKEY_RES_NONE;
+ }
+
+ int args[2];
+ if (termkey_interpret_csi_param(params[0], &args[0], NULL, NULL) != TERMKEY_RES_KEY) {
+ return TERMKEY_RES_ERROR;
+ }
+
+ if (termkey_interpret_csi_param(params[1], &args[1], NULL, NULL) != TERMKEY_RES_KEY) {
+ return TERMKEY_RES_ERROR;
+ }
+
+ key->type = TERMKEY_TYPE_POSITION;
+ termkey_key_set_linecol(key, args[1], args[0]);
+ return TERMKEY_RES_KEY;
+
+ default:
+ return handle_csi_ss3_full(tk, key, cmd, params, nparams);
+ }
+}
+
+TermKeyResult termkey_interpret_position(TermKey *tk, const TermKeyKey *key, int *line, int *col)
+{
+ if (key->type != TERMKEY_TYPE_POSITION) {
+ return TERMKEY_RES_NONE;
+ }
+
+ termkey_key_get_linecol(key, line, col);
+
+ return TERMKEY_RES_KEY;
+}
+
+/// Handler for CSI $y mode status reports
+static TermKeyResult handle_csi_y(TermKey *tk, TermKeyKey *key, int cmd, TermKeyCsiParam *params,
+ int nparams)
+{
+ switch (cmd) {
+ case 'y'|'$' << 16:
+ case 'y'|'$' << 16 | '?' << 8:
+ if (nparams < 2) {
+ return TERMKEY_RES_NONE;
+ }
+
+ int args[2];
+ if (termkey_interpret_csi_param(params[0], &args[0], NULL, NULL) != TERMKEY_RES_KEY) {
+ return TERMKEY_RES_ERROR;
+ }
+
+ if (termkey_interpret_csi_param(params[1], &args[1], NULL, NULL) != TERMKEY_RES_KEY) {
+ return TERMKEY_RES_ERROR;
+ }
+
+ key->type = TERMKEY_TYPE_MODEREPORT;
+ key->code.mouse[0] = (char)(cmd >> 8);
+ key->code.mouse[1] = (char)(args[0] >> 8);
+ key->code.mouse[2] = (char)(args[0] & 0xff);
+ key->code.mouse[3] = (char)args[1];
+ return TERMKEY_RES_KEY;
+
+ default:
+ return TERMKEY_RES_NONE;
+ }
+}
+
+TermKeyResult termkey_interpret_modereport(TermKey *tk, const TermKeyKey *key, int *initial,
+ int *mode, int *value)
+{
+ if (key->type != TERMKEY_TYPE_MODEREPORT) {
+ return TERMKEY_RES_NONE;
+ }
+
+ if (initial) {
+ *initial = (unsigned char)key->code.mouse[0];
+ }
+
+ if (mode) {
+ *mode = ((uint8_t)key->code.mouse[1] << 8) | (uint8_t)key->code.mouse[2];
+ }
+
+ if (value) {
+ *value = (unsigned char)key->code.mouse[3];
+ }
+
+ return TERMKEY_RES_KEY;
+}
+
+#define CHARAT(i) (tk->buffer[tk->buffstart + (i)])
+
+static TermKeyResult parse_csi(TermKey *tk, size_t introlen, size_t *csi_len,
+ TermKeyCsiParam params[], size_t *nargs, unsigned *commandp)
+{
+ size_t csi_end = introlen;
+
+ while (csi_end < tk->buffcount) {
+ if (CHARAT(csi_end) >= 0x40 && CHARAT(csi_end) < 0x80) {
+ break;
+ }
+ csi_end++;
+ }
+
+ if (csi_end >= tk->buffcount) {
+ return TERMKEY_RES_AGAIN;
+ }
+
+ unsigned char cmd = CHARAT(csi_end);
+ *commandp = cmd;
+
+ char present = 0;
+ int argi = 0;
+
+ size_t p = introlen;
+
+ // See if there is an initial byte
+ if (CHARAT(p) >= '<' && CHARAT(p) <= '?') {
+ *commandp |= (unsigned)(CHARAT(p) << 8);
+ p++;
+ }
+
+ // Now attempt to parse out up number;number;... separated values
+ while (p < csi_end) {
+ unsigned char c = CHARAT(p);
+
+ if (c >= '0' && c < ';') {
+ if (!present) {
+ params[argi].param = &CHARAT(p);
+ present = 1;
+ }
+ } else if (c == ';') {
+ if (!present) {
+ params[argi].param = NULL;
+ params[argi].length = 0;
+ } else {
+ params[argi].length = (size_t)(&CHARAT(p) - params[argi].param);
+ }
+ present = 0;
+ argi++;
+
+ if (argi > 16) {
+ break;
+ }
+ } else if (c >= 0x20 && c <= 0x2f) {
+ *commandp |= (unsigned)(c << 16);
+ break;
+ }
+
+ p++;
+ }
+
+ if (present) {
+ params[argi].length = (size_t)(&CHARAT(p) - params[argi].param);
+ argi++;
+ }
+
+ *nargs = (size_t)argi;
+ *csi_len = csi_end + 1;
+
+ return TERMKEY_RES_KEY;
+}
+
+TermKeyResult termkey_interpret_csi(TermKey *tk, const TermKeyKey *key, TermKeyCsiParam params[],
+ size_t *nparams, unsigned *cmd)
+{
+ size_t dummy;
+
+ if (tk->hightide == 0) {
+ return TERMKEY_RES_NONE;
+ }
+ if (key->type != TERMKEY_TYPE_UNKNOWN_CSI) {
+ return TERMKEY_RES_NONE;
+ }
+
+ return parse_csi(tk, 0, &dummy, params, nparams, cmd);
+}
+
+TermKeyResult termkey_interpret_csi_param(TermKeyCsiParam param, int *paramp, int subparams[],
+ size_t *nsubparams)
+{
+ if (paramp == NULL) {
+ return TERMKEY_RES_ERROR;
+ }
+
+ if (param.param == NULL) {
+ *paramp = -1;
+ if (nsubparams) {
+ *nsubparams = 0;
+ }
+ return TERMKEY_RES_KEY;
+ }
+
+ int arg = 0;
+ size_t i = 0;
+ size_t capacity = nsubparams ? *nsubparams : 0;
+ size_t length = 0;
+ for (; i < param.length && length <= capacity; i++) {
+ unsigned char c = param.param[i];
+ if (c == ':') {
+ if (length == 0) {
+ *paramp = arg;
+ } else {
+ subparams[length - 1] = arg;
+ }
+
+ arg = 0;
+ length++;
+ continue;
+ }
+
+ assert(c >= '0' && c <= '9');
+ arg = (10 * arg) + (c - '0');
+ }
+
+ if (length == 0) {
+ *paramp = arg;
+ } else {
+ subparams[length - 1] = arg;
+ }
+
+ if (nsubparams) {
+ *nsubparams = length;
+ }
+
+ return TERMKEY_RES_KEY;
+}
+
+static int register_keys(void)
+{
+ int i;
+
+ for (i = 0; i < 64; i++) {
+ csi_ss3s[i].sym = TERMKEY_SYM_UNKNOWN;
+ ss3s[i].sym = TERMKEY_SYM_UNKNOWN;
+ ss3_kpalts[i] = 0;
+ }
+
+ for (i = 0; i < NCSIFUNCS; i++) {
+ csifuncs[i].sym = TERMKEY_SYM_UNKNOWN;
+ }
+
+ register_csi_ss3(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_UP, 'A');
+ register_csi_ss3(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_DOWN, 'B');
+ register_csi_ss3(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_RIGHT, 'C');
+ register_csi_ss3(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_LEFT, 'D');
+ register_csi_ss3(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_BEGIN, 'E');
+ register_csi_ss3(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_END, 'F');
+ register_csi_ss3(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_HOME, 'H');
+ register_csi_ss3(TERMKEY_TYPE_FUNCTION, 1, 'P');
+ register_csi_ss3(TERMKEY_TYPE_FUNCTION, 2, 'Q');
+ register_csi_ss3(TERMKEY_TYPE_FUNCTION, 3, 'R');
+ register_csi_ss3(TERMKEY_TYPE_FUNCTION, 4, 'S');
+
+ register_csi_ss3_full(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_TAB, TERMKEY_KEYMOD_SHIFT,
+ TERMKEY_KEYMOD_SHIFT, 'Z');
+
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPENTER, 'M', 0);
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPEQUALS, 'X', '=');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPMULT, 'j', '*');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPPLUS, 'k', '+');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPCOMMA, 'l', ',');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPMINUS, 'm', '-');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPPERIOD, 'n', '.');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KPDIV, 'o', '/');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP0, 'p', '0');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP1, 'q', '1');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP2, 'r', '2');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP3, 's', '3');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP4, 't', '4');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP5, 'u', '5');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP6, 'v', '6');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP7, 'w', '7');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP8, 'x', '8');
+ register_ss3kpalt(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_KP9, 'y', '9');
+
+ register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_FIND, 1);
+ register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_INSERT, 2);
+ register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_DELETE, 3);
+ register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_SELECT, 4);
+ register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEUP, 5);
+ register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEDOWN, 6);
+ register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_HOME, 7);
+ register_csifunc(TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_END, 8);
+
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 1, 11);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 2, 12);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 3, 13);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 4, 14);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 5, 15);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 6, 17);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 7, 18);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 8, 19);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 9, 20);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 10, 21);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 11, 23);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 12, 24);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 13, 25);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 14, 26);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 15, 28);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 16, 29);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 17, 31);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 18, 32);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 19, 33);
+ register_csifunc(TERMKEY_TYPE_FUNCTION, 20, 34);
+
+ csi_handlers['u' - 0x40] = &handle_csi_u;
+
+ csi_handlers['M' - 0x40] = &handle_csi_m;
+ csi_handlers['m' - 0x40] = &handle_csi_m;
+
+ csi_handlers['R' - 0x40] = &handle_csi_R;
+
+ csi_handlers['y' - 0x40] = &handle_csi_y;
+
+ keyinfo_initialised = 1;
+ return 1;
+}
+
+void *new_driver_csi(TermKey *tk, const char *term)
+{
+ if (!keyinfo_initialised) {
+ if (!register_keys()) {
+ return NULL;
+ }
+ }
+
+ TermKeyCsi *csi = xmalloc(sizeof *csi);
+
+ csi->tk = tk;
+ csi->saved_string_id = 0;
+ csi->saved_string = NULL;
+
+ return csi;
+}
+
+void free_driver_csi(void *info)
+{
+ TermKeyCsi *csi = info;
+
+ if (csi->saved_string) {
+ xfree(csi->saved_string);
+ }
+
+ xfree(csi);
+}
+
+static TermKeyResult peekkey_csi_csi(TermKey *tk, TermKeyCsi *csi, size_t introlen, TermKeyKey *key,
+ int force, size_t *nbytep)
+{
+ size_t csi_len;
+ size_t nparams = 16;
+ TermKeyCsiParam params[16];
+ unsigned cmd;
+
+ TermKeyResult ret = parse_csi(tk, introlen, &csi_len, params, &nparams, &cmd);
+
+ if (ret == TERMKEY_RES_AGAIN) {
+ if (!force) {
+ return TERMKEY_RES_AGAIN;
+ }
+
+ (*tk->method.emit_codepoint)(tk, '[', key);
+ key->modifiers |= TERMKEY_KEYMOD_ALT;
+ *nbytep = introlen;
+ return TERMKEY_RES_KEY;
+ }
+
+ if (cmd == 'M' && nparams < 3) { // Mouse in X10 encoding consumes the next 3 bytes also
+ tk->buffstart += csi_len;
+ tk->buffcount -= csi_len;
+
+ TermKeyResult mouse_result = (*tk->method.peekkey_mouse)(tk, key, nbytep);
+
+ tk->buffstart -= csi_len;
+ tk->buffcount += csi_len;
+
+ if (mouse_result == TERMKEY_RES_KEY) {
+ *nbytep += csi_len;
+ }
+
+ return mouse_result;
+ }
+
+ TermKeyResult result = TERMKEY_RES_NONE;
+
+ // We know from the logic above that cmd must be >= 0x40 and < 0x80
+ if (csi_handlers[(cmd & 0xff) - 0x40]) {
+ result = (*csi_handlers[(cmd & 0xff) - 0x40])(tk, key, (int)cmd, params, (int)nparams);
+ }
+
+ if (result == TERMKEY_RES_NONE) {
+#ifdef DEBUG
+ switch (args) {
+ case 0:
+ fprintf(stderr, "CSI: Unknown cmd=%c\n", (char)cmd);
+ break;
+ case 1:
+ fprintf(stderr, "CSI: Unknown arg1=%ld cmd=%c\n", arg[0], (char)cmd);
+ break;
+ case 2:
+ fprintf(stderr, "CSI: Unknown arg1=%ld arg2=%ld cmd=%c\n", arg[0], arg[1], (char)cmd);
+ break;
+ case 3:
+ fprintf(stderr, "CSI: Unknown arg1=%ld arg2=%ld arg3=%ld cmd=%c\n", arg[0], arg[1], arg[2],
+ (char)cmd);
+ break;
+ default:
+ fprintf(stderr, "CSI: Unknown arg1=%ld arg2=%ld arg3=%ld ... args=%d cmd=%c\n", arg[0],
+ arg[1], arg[2], args, (char)cmd);
+ break;
+ }
+#endif
+ key->type = TERMKEY_TYPE_UNKNOWN_CSI;
+ key->code.number = (int)cmd;
+ key->modifiers = 0;
+
+ tk->hightide = csi_len - introlen;
+ *nbytep = introlen; // Do not yet eat the data bytes
+ return TERMKEY_RES_KEY;
+ }
+
+ *nbytep = csi_len;
+ return result;
+}
+
+static TermKeyResult peekkey_ss3(TermKey *tk, TermKeyCsi *csi, size_t introlen, TermKeyKey *key,
+ int force, size_t *nbytep)
+{
+ if (tk->buffcount < introlen + 1) {
+ if (!force) {
+ return TERMKEY_RES_AGAIN;
+ }
+
+ (*tk->method.emit_codepoint)(tk, 'O', key);
+ key->modifiers |= TERMKEY_KEYMOD_ALT;
+ *nbytep = tk->buffcount;
+ return TERMKEY_RES_KEY;
+ }
+
+ unsigned char cmd = CHARAT(introlen);
+
+ if (cmd < 0x40 || cmd >= 0x80) {
+ return TERMKEY_RES_NONE;
+ }
+
+ key->type = csi_ss3s[cmd - 0x40].type;
+ key->code.sym = csi_ss3s[cmd - 0x40].sym;
+ key->modifiers = csi_ss3s[cmd - 0x40].modifier_set;
+
+ if (key->code.sym == TERMKEY_SYM_UNKNOWN) {
+ if (tk->flags & TERMKEY_FLAG_CONVERTKP && ss3_kpalts[cmd - 0x40]) {
+ key->type = TERMKEY_TYPE_UNICODE;
+ key->code.codepoint = (unsigned char)ss3_kpalts[cmd - 0x40];
+ key->modifiers = 0;
+
+ key->utf8[0] = (char)key->code.codepoint;
+ key->utf8[1] = 0;
+ } else {
+ key->type = ss3s[cmd - 0x40].type;
+ key->code.sym = ss3s[cmd - 0x40].sym;
+ key->modifiers = ss3s[cmd - 0x40].modifier_set;
+ }
+ }
+
+ if (key->code.sym == TERMKEY_SYM_UNKNOWN) {
+#ifdef DEBUG
+ fprintf(stderr, "CSI: Unknown SS3 %c (0x%02x)\n", (char)cmd, cmd);
+#endif
+ return TERMKEY_RES_NONE;
+ }
+
+ *nbytep = introlen + 1;
+
+ return TERMKEY_RES_KEY;
+}
+
+static TermKeyResult peekkey_ctrlstring(TermKey *tk, TermKeyCsi *csi, size_t introlen,
+ TermKeyKey *key, int force, size_t *nbytep)
+{
+ size_t str_end = introlen;
+
+ while (str_end < tk->buffcount) {
+ if (CHARAT(str_end) == 0x07) { // BEL
+ break;
+ }
+ if (CHARAT(str_end) == 0x9c) { // ST
+ break;
+ }
+ if (CHARAT(str_end) == 0x1b
+ && (str_end + 1) < tk->buffcount
+ && CHARAT(str_end + 1) == 0x5c) { // ESC-prefixed ST
+ break;
+ }
+
+ str_end++;
+ }
+
+ if (str_end >= tk->buffcount) {
+ return TERMKEY_RES_AGAIN;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "Found a control string: %*s",
+ str_end - introlen, tk->buffer + tk->buffstart + introlen);
+#endif
+
+ *nbytep = str_end + 1;
+ if (CHARAT(str_end) == 0x1b) {
+ (*nbytep)++;
+ }
+
+ if (csi->saved_string) {
+ xfree(csi->saved_string);
+ }
+
+ size_t len = str_end - introlen;
+
+ csi->saved_string_id++;
+ csi->saved_string = xmalloc(len + 1);
+
+ strncpy(csi->saved_string, (char *)tk->buffer + tk->buffstart + introlen, len); // NOLINT(runtime/printf)
+ csi->saved_string[len] = 0;
+
+ key->type = (CHARAT(introlen - 1) & 0x1f) == 0x10
+ ? TERMKEY_TYPE_DCS : TERMKEY_TYPE_OSC;
+ key->code.number = csi->saved_string_id;
+ key->modifiers = 0;
+
+ return TERMKEY_RES_KEY;
+}
+
+TermKeyResult peekkey_csi(TermKey *tk, void *info, TermKeyKey *key, int force, size_t *nbytep)
+{
+ if (tk->buffcount == 0) {
+ return tk->is_closed ? TERMKEY_RES_EOF : TERMKEY_RES_NONE;
+ }
+
+ TermKeyCsi *csi = info;
+
+ switch (CHARAT(0)) {
+ case 0x1b:
+ if (tk->buffcount < 2) {
+ return TERMKEY_RES_NONE;
+ }
+
+ switch (CHARAT(1)) {
+ case 0x4f: // ESC-prefixed SS3
+ return peekkey_ss3(tk, csi, 2, key, force, nbytep);
+
+ case 0x50: // ESC-prefixed DCS
+ case 0x5d: // ESC-prefixed OSC
+ return peekkey_ctrlstring(tk, csi, 2, key, force, nbytep);
+
+ case 0x5b: // ESC-prefixed CSI
+ return peekkey_csi_csi(tk, csi, 2, key, force, nbytep);
+ }
+
+ return TERMKEY_RES_NONE;
+
+ case 0x8f: // SS3
+ return peekkey_ss3(tk, csi, 1, key, force, nbytep);
+
+ case 0x90: // DCS
+ case 0x9d: // OSC
+ return peekkey_ctrlstring(tk, csi, 1, key, force, nbytep);
+
+ case 0x9b: // CSI
+ return peekkey_csi_csi(tk, csi, 1, key, force, nbytep);
+ }
+
+ return TERMKEY_RES_NONE;
+}
diff --git a/src/nvim/tui/termkey/driver-csi.h b/src/nvim/tui/termkey/driver-csi.h
new file mode 100644
index 0000000000..0abd8b5c2e
--- /dev/null
+++ b/src/nvim/tui/termkey/driver-csi.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "nvim/tui/termkey/termkey_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "tui/termkey/driver-csi.h.generated.h"
+#endif
diff --git a/src/nvim/tui/termkey/driver-ti.c b/src/nvim/tui/termkey/driver-ti.c
new file mode 100644
index 0000000000..745ee9902f
--- /dev/null
+++ b/src/nvim/tui/termkey/driver-ti.c
@@ -0,0 +1,595 @@
+#include <ctype.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unibilium.h>
+
+#include "nvim/memory.h"
+#include "nvim/tui/termkey/driver-ti.h"
+#include "nvim/tui/termkey/termkey-internal.h"
+#include "nvim/tui/termkey/termkey.h"
+
+#ifndef _WIN32
+# include <unistd.h>
+#else
+# include <io.h>
+#endif
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "tui/termkey/driver-ti.c.generated.h"
+#endif
+
+#define streq(a, b) (!strcmp(a, b))
+
+#define MAX_FUNCNAME 9
+
+static struct {
+ const char *funcname;
+ TermKeyType type;
+ TermKeySym sym;
+ int mods;
+} funcs[] = {
+ // THIS LIST MUST REMAIN SORTED!
+ { "backspace", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_BACKSPACE, 0 },
+ { "begin", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_BEGIN, 0 },
+ { "beg", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_BEGIN, 0 },
+ { "btab", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_TAB, TERMKEY_KEYMOD_SHIFT },
+ { "cancel", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_CANCEL, 0 },
+ { "clear", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_CLEAR, 0 },
+ { "close", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_CLOSE, 0 },
+ { "command", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_COMMAND, 0 },
+ { "copy", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_COPY, 0 },
+ { "dc", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_DELETE, 0 },
+ { "down", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_DOWN, 0 },
+ { "end", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_END, 0 },
+ { "enter", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_ENTER, 0 },
+ { "exit", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_EXIT, 0 },
+ { "find", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_FIND, 0 },
+ { "help", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_HELP, 0 },
+ { "home", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_HOME, 0 },
+ { "ic", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_INSERT, 0 },
+ { "left", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_LEFT, 0 },
+ { "mark", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_MARK, 0 },
+ { "message", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_MESSAGE, 0 },
+ { "move", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_MOVE, 0 },
+ { "next", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEDOWN, 0 }, // Not quite, but it's the best we can do
+ { "npage", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEDOWN, 0 },
+ { "open", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_OPEN, 0 },
+ { "options", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_OPTIONS, 0 },
+ { "ppage", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEUP, 0 },
+ { "previous", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PAGEUP, 0 }, // Not quite, but it's the best we can do
+ { "print", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_PRINT, 0 },
+ { "redo", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REDO, 0 },
+ { "reference", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REFERENCE, 0 },
+ { "refresh", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REFRESH, 0 },
+ { "replace", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_REPLACE, 0 },
+ { "restart", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_RESTART, 0 },
+ { "resume", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_RESUME, 0 },
+ { "right", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_RIGHT, 0 },
+ { "save", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_SAVE, 0 },
+ { "select", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_SELECT, 0 },
+ { "suspend", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_SUSPEND, 0 },
+ { "undo", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_UNDO, 0 },
+ { "up", TERMKEY_TYPE_KEYSYM, TERMKEY_SYM_UP, 0 },
+ { NULL, 0, 0, 0 },
+};
+
+static enum unibi_string unibi_lookup_str(const char *name)
+{
+ for (enum unibi_string ret = unibi_string_begin_ + 1; ret < unibi_string_end_; ret++) {
+ if (streq(unibi_name_str(ret), name)) {
+ return ret;
+ }
+ }
+
+ return (enum unibi_string)-1;
+}
+
+static const char *unibi_get_str_by_name(const unibi_term *ut, const char *name)
+{
+ enum unibi_string idx = unibi_lookup_str(name);
+ if (idx == (enum unibi_string)-1) {
+ return NULL;
+ }
+
+ return unibi_get_str(ut, idx);
+}
+
+// To be efficient at lookups, we store the byte sequence => keyinfo mapping
+// in a trie. This avoids a slow linear search through a flat list of
+// sequences. Because it is likely most nodes will be very sparse, we optimise
+// vector to store an extent map after the database is loaded.
+
+typedef enum {
+ TYPE_KEY,
+ TYPE_ARR,
+} trie_nodetype;
+
+struct trie_node {
+ trie_nodetype type;
+};
+
+struct trie_node_key {
+ trie_nodetype type;
+ struct keyinfo key;
+};
+
+struct trie_node_arr {
+ trie_nodetype type;
+ unsigned char min, max; // INCLUSIVE endpoints of the extent range
+ struct trie_node *arr[]; // dynamic size at allocation time
+};
+
+static int insert_seq(TermKeyTI *ti, const char *seq, struct trie_node *node);
+
+static struct trie_node *new_node_key(TermKeyType type, TermKeySym sym, int modmask, int modset)
+{
+ struct trie_node_key *n = xmalloc(sizeof(*n));
+
+ n->type = TYPE_KEY;
+
+ n->key.type = type;
+ n->key.sym = sym;
+ n->key.modifier_mask = modmask;
+ n->key.modifier_set = modset;
+
+ return (struct trie_node *)n;
+}
+
+static struct trie_node *new_node_arr(unsigned char min, unsigned char max)
+{
+ struct trie_node_arr *n = xmalloc(sizeof(*n) + (max - min + 1) * sizeof(n->arr[0]));
+
+ n->type = TYPE_ARR;
+ n->min = min; n->max = max;
+
+ int i;
+ for (i = min; i <= max; i++) {
+ n->arr[i - min] = NULL;
+ }
+
+ return (struct trie_node *)n;
+}
+
+static struct trie_node *lookup_next(struct trie_node *n, unsigned char b)
+{
+ switch (n->type) {
+ case TYPE_KEY:
+ fprintf(stderr, "ABORT: lookup_next within a TYPE_KEY node\n");
+ abort();
+ case TYPE_ARR: {
+ struct trie_node_arr *nar = (struct trie_node_arr *)n;
+ if (b < nar->min || b > nar->max) {
+ return NULL;
+ }
+ return nar->arr[b - nar->min];
+ }
+ }
+
+ return NULL; // Never reached but keeps compiler happy
+}
+
+static void free_trie(struct trie_node *n)
+{
+ switch (n->type) {
+ case TYPE_KEY:
+ break;
+ case TYPE_ARR: {
+ struct trie_node_arr *nar = (struct trie_node_arr *)n;
+ int i;
+ for (i = nar->min; i <= nar->max; i++) {
+ if (nar->arr[i - nar->min]) {
+ free_trie(nar->arr[i - nar->min]);
+ }
+ }
+ break;
+ }
+ }
+
+ xfree(n);
+}
+
+static struct trie_node *compress_trie(struct trie_node *n)
+{
+ if (!n) {
+ return NULL;
+ }
+
+ switch (n->type) {
+ case TYPE_KEY:
+ return n;
+ case TYPE_ARR: {
+ struct trie_node_arr *nar = (struct trie_node_arr *)n;
+ unsigned char min, max;
+ // Find the real bounds
+ for (min = 0; !nar->arr[min]; min++) {
+ if (min == 255 && !nar->arr[min]) {
+ xfree(nar);
+ return new_node_arr(1, 0);
+ }
+ }
+
+ for (max = 0xff; !nar->arr[max]; max--) {}
+
+ struct trie_node_arr *new = (struct trie_node_arr *)new_node_arr(min, max);
+ int i;
+ for (i = min; i <= max; i++) {
+ new->arr[i - min] = compress_trie(nar->arr[i]);
+ }
+
+ xfree(nar);
+ return (struct trie_node *)new;
+ }
+ }
+
+ return n;
+}
+
+static bool try_load_terminfo_key(TermKeyTI *ti, const char *name, struct keyinfo *info)
+{
+ const char *value = NULL;
+
+ if (ti->unibi) {
+ value = unibi_get_str_by_name(ti->unibi, name);
+ }
+
+ if (ti->tk->ti_getstr_hook) {
+ value = (ti->tk->ti_getstr_hook)(name, value, ti->tk->ti_getstr_hook_data);
+ }
+
+ if (!value || value == (char *)-1 || !value[0]) {
+ return false;
+ }
+
+ struct trie_node *node = new_node_key(info->type, info->sym, info->modifier_mask,
+ info->modifier_set);
+ insert_seq(ti, value, node);
+
+ return true;
+}
+
+static int load_terminfo(TermKeyTI *ti)
+{
+ int i;
+
+ unibi_term *unibi = ti->unibi;
+
+ ti->root = new_node_arr(0, 0xff);
+ if (!ti->root) {
+ return 0;
+ }
+
+ // First the regular key strings
+ for (i = 0; funcs[i].funcname; i++) {
+ char name[MAX_FUNCNAME + 5 + 1];
+
+ sprintf(name, "key_%s", funcs[i].funcname); // NOLINT(runtime/printf)
+ if (!try_load_terminfo_key(ti, name, &(struct keyinfo){
+ .type = funcs[i].type,
+ .sym = funcs[i].sym,
+ .modifier_mask = funcs[i].mods,
+ .modifier_set = funcs[i].mods,
+ })) {
+ continue;
+ }
+
+ // Maybe it has a shifted version
+ sprintf(name, "key_s%s", funcs[i].funcname); // NOLINT(runtime/printf)
+ try_load_terminfo_key(ti, name, &(struct keyinfo){
+ .type = funcs[i].type,
+ .sym = funcs[i].sym,
+ .modifier_mask = funcs[i].mods | TERMKEY_KEYMOD_SHIFT,
+ .modifier_set = funcs[i].mods | TERMKEY_KEYMOD_SHIFT,
+ });
+ }
+
+ // Now the F<digit> keys
+ for (i = 1; i < 255; i++) {
+ char name[9];
+ sprintf(name, "key_f%d", i); // NOLINT(runtime/printf)
+ if (!try_load_terminfo_key(ti, name, &(struct keyinfo){
+ .type = TERMKEY_TYPE_FUNCTION,
+ .sym = i,
+ .modifier_mask = 0,
+ .modifier_set = 0,
+ })) {
+ break;
+ }
+ }
+
+ // Finally mouse mode
+ {
+ const char *value = NULL;
+
+ if (ti->unibi) {
+ value = unibi_get_str_by_name(ti->unibi, "key_mouse");
+ }
+
+ if (ti->tk->ti_getstr_hook) {
+ value = (ti->tk->ti_getstr_hook)("key_mouse", value, ti->tk->ti_getstr_hook_data);
+ }
+
+ // Some terminfos (e.g. xterm-1006) claim a different key_mouse that won't
+ // give X10 encoding. We'll only accept this if it's exactly "\e[M"
+ if (value && streq(value, "\x1b[M")) {
+ struct trie_node *node = new_node_key(TERMKEY_TYPE_MOUSE, 0, 0, 0);
+ insert_seq(ti, value, node);
+ }
+ }
+
+ // Take copies of these terminfo strings, in case we build multiple termkey
+ // instances for multiple different termtypes, and it's different by the
+ // time we want to use it
+ const char *keypad_xmit = unibi
+ ? unibi_get_str(unibi, unibi_keypad_xmit)
+ : NULL;
+
+ if (keypad_xmit) {
+ ti->start_string = xstrdup(keypad_xmit);
+ } else {
+ ti->start_string = NULL;
+ }
+
+ const char *keypad_local = unibi
+ ? unibi_get_str(unibi, unibi_keypad_local)
+ : NULL;
+
+ if (keypad_local) {
+ ti->stop_string = xstrdup(keypad_local);
+ } else {
+ ti->stop_string = NULL;
+ }
+
+ if (unibi) {
+ unibi_destroy(unibi);
+ }
+
+ ti->unibi = NULL;
+
+ ti->root = compress_trie(ti->root);
+
+ return 1;
+}
+
+void *new_driver_ti(TermKey *tk, const char *term)
+{
+ TermKeyTI *ti = xmalloc(sizeof *ti);
+
+ ti->tk = tk;
+ ti->root = NULL;
+ ti->start_string = NULL;
+ ti->stop_string = NULL;
+
+ ti->unibi = unibi_from_term(term);
+ int saved_errno = errno;
+ if (!ti->unibi && saved_errno != ENOENT) {
+ xfree(ti);
+ return NULL;
+ }
+ // ti->unibi may be NULL if errno == ENOENT. That means the terminal wasn't
+ // known. Lets keep going because if we get getstr hook that might invent
+ // new strings for us
+
+ return ti;
+}
+
+int start_driver_ti(TermKey *tk, void *info)
+{
+ TermKeyTI *ti = info;
+ struct stat statbuf;
+ char *start_string;
+ size_t len;
+
+ if (!ti->root) {
+ load_terminfo(ti);
+ }
+
+ start_string = ti->start_string;
+
+ if (tk->fd == -1 || !start_string) {
+ return 1;
+ }
+
+ // The terminfo database will contain keys in application cursor key mode.
+ // We may need to enable that mode
+
+ // There's no point trying to write() to a pipe
+ if (fstat(tk->fd, &statbuf) == -1) {
+ return 0;
+ }
+
+#ifndef _WIN32
+ if (S_ISFIFO(statbuf.st_mode)) {
+ return 1;
+ }
+#endif
+
+ // Can't call putp or tputs because they suck and don't give us fd control
+ len = strlen(start_string);
+ while (len) {
+ ssize_t result = write(tk->fd, start_string, (unsigned)len);
+ if (result < 0) {
+ return 0;
+ }
+ size_t written = (size_t)result;
+ start_string += written;
+ len -= written;
+ }
+ return 1;
+}
+
+int stop_driver_ti(TermKey *tk, void *info)
+{
+ TermKeyTI *ti = info;
+ struct stat statbuf;
+ char *stop_string = ti->stop_string;
+ size_t len;
+
+ if (tk->fd == -1 || !stop_string) {
+ return 1;
+ }
+
+ // There's no point trying to write() to a pipe
+ if (fstat(tk->fd, &statbuf) == -1) {
+ return 0;
+ }
+
+#ifndef _WIN32
+ if (S_ISFIFO(statbuf.st_mode)) {
+ return 1;
+ }
+#endif
+
+ // The terminfo database will contain keys in application cursor key mode.
+ // We may need to enable that mode
+
+ // Can't call putp or tputs because they suck and don't give us fd control
+ len = strlen(stop_string);
+ while (len) {
+ ssize_t result = write(tk->fd, stop_string, (unsigned)len);
+ if (result < 0) {
+ return 0;
+ }
+ size_t written = (size_t)result;
+ stop_string += written;
+ len -= written;
+ }
+ return 1;
+}
+
+void free_driver_ti(void *info)
+{
+ TermKeyTI *ti = info;
+
+ free_trie(ti->root);
+
+ if (ti->start_string) {
+ xfree(ti->start_string);
+ }
+
+ if (ti->stop_string) {
+ xfree(ti->stop_string);
+ }
+
+ if (ti->unibi) {
+ unibi_destroy(ti->unibi);
+ }
+
+ xfree(ti);
+}
+
+#define CHARAT(i) (tk->buffer[tk->buffstart + (i)])
+
+TermKeyResult peekkey_ti(TermKey *tk, void *info, TermKeyKey *key, int force, size_t *nbytep)
+{
+ TermKeyTI *ti = info;
+
+ if (tk->buffcount == 0) {
+ return tk->is_closed ? TERMKEY_RES_EOF : TERMKEY_RES_NONE;
+ }
+
+ struct trie_node *p = ti->root;
+
+ unsigned pos = 0;
+ while (pos < tk->buffcount) {
+ p = lookup_next(p, CHARAT(pos));
+ if (!p) {
+ break;
+ }
+
+ pos++;
+
+ if (p->type != TYPE_KEY) {
+ continue;
+ }
+
+ struct trie_node_key *nk = (struct trie_node_key *)p;
+ if (nk->key.type == TERMKEY_TYPE_MOUSE) {
+ tk->buffstart += pos;
+ tk->buffcount -= pos;
+
+ TermKeyResult mouse_result = (*tk->method.peekkey_mouse)(tk, key, nbytep);
+
+ tk->buffstart -= pos;
+ tk->buffcount += pos;
+
+ if (mouse_result == TERMKEY_RES_KEY) {
+ *nbytep += pos;
+ }
+
+ return mouse_result;
+ }
+
+ key->type = nk->key.type;
+ key->code.sym = nk->key.sym;
+ key->modifiers = nk->key.modifier_set;
+ *nbytep = pos;
+ return TERMKEY_RES_KEY;
+ }
+
+ // If p is not NULL then we hadn't walked off the end yet, so we have a
+ // partial match
+ if (p && !force) {
+ return TERMKEY_RES_AGAIN;
+ }
+
+ return TERMKEY_RES_NONE;
+}
+
+static int insert_seq(TermKeyTI *ti, const char *seq, struct trie_node *node)
+{
+ int pos = 0;
+ struct trie_node *p = ti->root;
+
+ // Unsigned because we'll be using it as an array subscript
+ unsigned char b;
+
+ while ((b = (unsigned char)seq[pos])) {
+ struct trie_node *next = lookup_next(p, b);
+ if (!next) {
+ break;
+ }
+ p = next;
+ pos++;
+ }
+
+ while ((b = (unsigned char)seq[pos])) {
+ struct trie_node *next;
+ if (seq[pos + 1]) {
+ // Intermediate node
+ next = new_node_arr(0, 0xff);
+ } else {
+ // Final key node
+ next = node;
+ }
+
+ if (!next) {
+ return 0;
+ }
+
+ switch (p->type) {
+ case TYPE_ARR: {
+ struct trie_node_arr *nar = (struct trie_node_arr *)p;
+ if (b < nar->min || b > nar->max) {
+ fprintf(stderr,
+ "ASSERT FAIL: Trie insert at 0x%02x is outside of extent bounds (0x%02x..0x%02x)\n",
+ b, nar->min, nar->max);
+ abort();
+ }
+ nar->arr[b - nar->min] = next;
+ p = next;
+ break;
+ }
+ case TYPE_KEY:
+ fprintf(stderr, "ASSERT FAIL: Tried to insert child node in TYPE_KEY\n");
+ abort();
+ }
+
+ pos++;
+ }
+
+ return 1;
+}
diff --git a/src/nvim/tui/termkey/driver-ti.h b/src/nvim/tui/termkey/driver-ti.h
new file mode 100644
index 0000000000..df9bd72d5b
--- /dev/null
+++ b/src/nvim/tui/termkey/driver-ti.h
@@ -0,0 +1,7 @@
+#pragma once
+
+#include "nvim/tui/termkey/termkey_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "tui/termkey/driver-ti.h.generated.h"
+#endif
diff --git a/src/nvim/tui/termkey/termkey-internal.h b/src/nvim/tui/termkey/termkey-internal.h
new file mode 100644
index 0000000000..107591f950
--- /dev/null
+++ b/src/nvim/tui/termkey/termkey-internal.h
@@ -0,0 +1,109 @@
+#pragma once
+
+#include <stdint.h>
+
+#include "nvim/tui/termkey/termkey_defs.h"
+
+#define HAVE_TERMIOS
+#ifdef _WIN32
+# undef HAVE_TERMIOS
+#endif
+
+#ifdef HAVE_TERMIOS
+# include <termios.h>
+#endif
+
+#ifdef _MSC_VER
+# include <BaseTsd.h>
+typedef SSIZE_T ssize_t;
+#endif
+
+struct TermKeyDriver {
+ const char *name;
+ void *(*new_driver)(TermKey *tk, const char *term);
+ void (*free_driver)(void *info);
+ int (*start_driver)(TermKey *tk, void *info);
+ int (*stop_driver)(TermKey *tk, void *info);
+ TermKeyResult (*peekkey)(TermKey *tk, void *info, TermKeyKey *key, int force, size_t *nbytes);
+};
+
+struct keyinfo {
+ TermKeyType type;
+ TermKeySym sym;
+ int modifier_mask;
+ int modifier_set;
+};
+
+struct TermKeyDriverNode;
+struct TermKeyDriverNode {
+ struct TermKeyDriver *driver;
+ void *info;
+ struct TermKeyDriverNode *next;
+};
+
+struct TermKey {
+ int fd;
+ int flags;
+ int canonflags;
+ unsigned char *buffer;
+ size_t buffstart; // First offset in buffer
+ size_t buffcount; // NUMBER of entires valid in buffer
+ size_t buffsize; // Total malloc'ed size
+ size_t hightide; // Position beyond buffstart at which peekkey() should next start
+ // normally 0, but see also termkey_interpret_csi
+
+#ifdef HAVE_TERMIOS
+ struct termios restore_termios;
+ char restore_termios_valid;
+#endif
+
+ TermKey_Terminfo_Getstr_Hook *ti_getstr_hook;
+ void *ti_getstr_hook_data;
+
+ int waittime; // msec
+
+ char is_closed;
+ char is_started;
+
+ int nkeynames;
+ const char **keynames;
+
+ // There are 32 C0 codes
+ struct keyinfo c0[32];
+
+ struct TermKeyDriverNode *drivers;
+
+ // Now some "protected" methods for the driver to call but which we don't
+ // want exported as real symbols in the library
+ struct {
+ void (*emit_codepoint)(TermKey *tk, int codepoint, TermKeyKey *key);
+ TermKeyResult (*peekkey_simple)(TermKey *tk, TermKeyKey *key, int force, size_t *nbytes);
+ TermKeyResult (*peekkey_mouse)(TermKey *tk, TermKeyKey *key, size_t *nbytes);
+ } method;
+};
+
+static inline void termkey_key_get_linecol(const TermKeyKey *key, int *line, int *col)
+{
+ if (col) {
+ *col = (unsigned char)key->code.mouse[1] | ((unsigned char)key->code.mouse[3] & 0x0f) << 8;
+ }
+
+ if (line) {
+ *line = (unsigned char)key->code.mouse[2] | ((unsigned char)key->code.mouse[3] & 0x70) << 4;
+ }
+}
+
+static inline void termkey_key_set_linecol(TermKeyKey *key, int line, int col)
+{
+ if (line > 0xfff) {
+ line = 0xfff;
+ }
+
+ if (col > 0x7ff) {
+ col = 0x7ff;
+ }
+
+ key->code.mouse[1] = (char)(line & 0x0ff);
+ key->code.mouse[2] = (char)(col & 0x0ff);
+ key->code.mouse[3] = (line & 0xf00) >> 8 | (col & 0x300) >> 4;
+}
diff --git a/src/nvim/tui/termkey/termkey.c b/src/nvim/tui/termkey/termkey.c
new file mode 100644
index 0000000000..e6440118f3
--- /dev/null
+++ b/src/nvim/tui/termkey/termkey.c
@@ -0,0 +1,1315 @@
+#include <ctype.h>
+#include <errno.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <string.h>
+
+#include "nvim/mbyte.h"
+#include "nvim/memory.h"
+#include "nvim/tui/termkey/driver-csi.h"
+#include "nvim/tui/termkey/driver-ti.h"
+#include "nvim/tui/termkey/termkey-internal.h"
+#include "nvim/tui/termkey/termkey.h"
+#include "nvim/tui/termkey/termkey_defs.h"
+
+#ifndef _WIN32
+# include <poll.h>
+# include <strings.h>
+# include <unistd.h>
+#else
+# include <io.h>
+#endif
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "tui/termkey/termkey.c.generated.h"
+#endif
+
+#ifdef _MSC_VER
+# define strcaseeq(a, b) (_stricmp(a, b) == 0)
+#else
+# define strcaseeq(a, b) (strcasecmp(a, b) == 0)
+#endif
+
+struct TermKeyDriver termkey_driver_ti = {
+ .name = "terminfo",
+
+ .new_driver = new_driver_ti,
+ .free_driver = free_driver_ti,
+
+ .start_driver = start_driver_ti,
+ .stop_driver = stop_driver_ti,
+
+ .peekkey = peekkey_ti,
+};
+
+struct TermKeyDriver termkey_driver_csi = {
+ .name = "CSI",
+
+ .new_driver = new_driver_csi,
+ .free_driver = free_driver_csi,
+
+ .peekkey = peekkey_csi,
+};
+
+static struct TermKeyDriver *drivers[] = {
+ &termkey_driver_ti,
+ &termkey_driver_csi,
+ NULL,
+};
+
+static struct {
+ TermKeySym sym;
+ const char *name;
+} keynames[] = {
+ { TERMKEY_SYM_NONE, "NONE" },
+ { TERMKEY_SYM_BACKSPACE, "Backspace" },
+ { TERMKEY_SYM_TAB, "Tab" },
+ { TERMKEY_SYM_ENTER, "Enter" },
+ { TERMKEY_SYM_ESCAPE, "Escape" },
+ { TERMKEY_SYM_SPACE, "Space" },
+ { TERMKEY_SYM_DEL, "DEL" },
+ { TERMKEY_SYM_UP, "Up" },
+ { TERMKEY_SYM_DOWN, "Down" },
+ { TERMKEY_SYM_LEFT, "Left" },
+ { TERMKEY_SYM_RIGHT, "Right" },
+ { TERMKEY_SYM_BEGIN, "Begin" },
+ { TERMKEY_SYM_FIND, "Find" },
+ { TERMKEY_SYM_INSERT, "Insert" },
+ { TERMKEY_SYM_DELETE, "Delete" },
+ { TERMKEY_SYM_SELECT, "Select" },
+ { TERMKEY_SYM_PAGEUP, "PageUp" },
+ { TERMKEY_SYM_PAGEDOWN, "PageDown" },
+ { TERMKEY_SYM_HOME, "Home" },
+ { TERMKEY_SYM_END, "End" },
+ { TERMKEY_SYM_CANCEL, "Cancel" },
+ { TERMKEY_SYM_CLEAR, "Clear" },
+ { TERMKEY_SYM_CLOSE, "Close" },
+ { TERMKEY_SYM_COMMAND, "Command" },
+ { TERMKEY_SYM_COPY, "Copy" },
+ { TERMKEY_SYM_EXIT, "Exit" },
+ { TERMKEY_SYM_HELP, "Help" },
+ { TERMKEY_SYM_MARK, "Mark" },
+ { TERMKEY_SYM_MESSAGE, "Message" },
+ { TERMKEY_SYM_MOVE, "Move" },
+ { TERMKEY_SYM_OPEN, "Open" },
+ { TERMKEY_SYM_OPTIONS, "Options" },
+ { TERMKEY_SYM_PRINT, "Print" },
+ { TERMKEY_SYM_REDO, "Redo" },
+ { TERMKEY_SYM_REFERENCE, "Reference" },
+ { TERMKEY_SYM_REFRESH, "Refresh" },
+ { TERMKEY_SYM_REPLACE, "Replace" },
+ { TERMKEY_SYM_RESTART, "Restart" },
+ { TERMKEY_SYM_RESUME, "Resume" },
+ { TERMKEY_SYM_SAVE, "Save" },
+ { TERMKEY_SYM_SUSPEND, "Suspend" },
+ { TERMKEY_SYM_UNDO, "Undo" },
+ { TERMKEY_SYM_KP0, "KP0" },
+ { TERMKEY_SYM_KP1, "KP1" },
+ { TERMKEY_SYM_KP2, "KP2" },
+ { TERMKEY_SYM_KP3, "KP3" },
+ { TERMKEY_SYM_KP4, "KP4" },
+ { TERMKEY_SYM_KP5, "KP5" },
+ { TERMKEY_SYM_KP6, "KP6" },
+ { TERMKEY_SYM_KP7, "KP7" },
+ { TERMKEY_SYM_KP8, "KP8" },
+ { TERMKEY_SYM_KP9, "KP9" },
+ { TERMKEY_SYM_KPENTER, "KPEnter" },
+ { TERMKEY_SYM_KPPLUS, "KPPlus" },
+ { TERMKEY_SYM_KPMINUS, "KPMinus" },
+ { TERMKEY_SYM_KPMULT, "KPMult" },
+ { TERMKEY_SYM_KPDIV, "KPDiv" },
+ { TERMKEY_SYM_KPCOMMA, "KPComma" },
+ { TERMKEY_SYM_KPPERIOD, "KPPeriod" },
+ { TERMKEY_SYM_KPEQUALS, "KPEquals" },
+ { 0, NULL },
+};
+
+// Mouse event names
+static const char *evnames[] = { "Unknown", "Press", "Drag", "Release" };
+
+#define CHARAT(i) (tk->buffer[tk->buffstart + (i)])
+
+#ifdef DEBUG
+// Some internal debugging functions
+
+static void print_buffer(TermKey *tk)
+{
+ int i;
+ for (i = 0; i < tk->buffcount && i < 20; i++) {
+ fprintf(stderr, "%02x ", CHARAT(i));
+ }
+ if (tk->buffcount > 20) {
+ fprintf(stderr, "...");
+ }
+}
+
+static void print_key(TermKey *tk, TermKeyKey *key)
+{
+ switch (key->type) {
+ case TERMKEY_TYPE_UNICODE:
+ fprintf(stderr, "Unicode codepoint=U+%04lx utf8='%s'", key->code.codepoint, key->utf8);
+ break;
+ case TERMKEY_TYPE_FUNCTION:
+ fprintf(stderr, "Function F%d", key->code.number);
+ break;
+ case TERMKEY_TYPE_KEYSYM:
+ fprintf(stderr, "Keysym sym=%d(%s)", key->code.sym, termkey_get_keyname(tk, key->code.sym));
+ break;
+ case TERMKEY_TYPE_MOUSE: {
+ TermKeyMouseEvent ev;
+ int button, line, col;
+ termkey_interpret_mouse(tk, key, &ev, &button, &line, &col);
+ fprintf(stderr, "Mouse ev=%d button=%d pos=(%d,%d)\n", ev, button, line, col);
+ }
+ break;
+ case TERMKEY_TYPE_POSITION: {
+ int line, col;
+ termkey_interpret_position(tk, key, &line, &col);
+ fprintf(stderr, "Position report pos=(%d,%d)\n", line, col);
+ }
+ break;
+ case TERMKEY_TYPE_MODEREPORT: {
+ int initial, mode, value;
+ termkey_interpret_modereport(tk, key, &initial, &mode, &value);
+ fprintf(stderr, "Mode report mode=%s %d val=%d\n", initial == '?' ? "DEC" : "ANSI", mode,
+ value);
+ }
+ break;
+ case TERMKEY_TYPE_DCS:
+ fprintf(stderr, "Device Control String");
+ break;
+ case TERMKEY_TYPE_OSC:
+ fprintf(stderr, "Operating System Control");
+ break;
+ case TERMKEY_TYPE_UNKNOWN_CSI:
+ fprintf(stderr, "unknown CSI\n");
+ break;
+ }
+
+ int m = key->modifiers;
+ fprintf(stderr, " mod=%s%s%s+%02x",
+ (m & TERMKEY_KEYMOD_CTRL ? "C" : ""),
+ (m & TERMKEY_KEYMOD_ALT ? "A" : ""),
+ (m & TERMKEY_KEYMOD_SHIFT ? "S" : ""),
+ m & ~(TERMKEY_KEYMOD_CTRL|TERMKEY_KEYMOD_ALT|TERMKEY_KEYMOD_SHIFT));
+}
+
+static const char *res2str(TermKeyResult res)
+{
+ static char errorbuffer[256];
+
+ switch (res) {
+ case TERMKEY_RES_KEY:
+ return "TERMKEY_RES_KEY";
+ case TERMKEY_RES_EOF:
+ return "TERMKEY_RES_EOF";
+ case TERMKEY_RES_AGAIN:
+ return "TERMKEY_RES_AGAIN";
+ case TERMKEY_RES_NONE:
+ return "TERMKEY_RES_NONE";
+ case TERMKEY_RES_ERROR:
+ snprintf(errorbuffer, sizeof errorbuffer, "TERMKEY_RES_ERROR(errno=%d)\n", errno);
+ return (const char *)errorbuffer;
+ }
+
+ return "unknown";
+}
+#endif
+
+TermKeyResult termkey_interpret_string(TermKey *tk, const TermKeyKey *key, const char **strp)
+{
+ struct TermKeyDriverNode *p;
+ for (p = tk->drivers; p; p = p->next) {
+ if (p->driver == &termkey_driver_csi) {
+ break;
+ }
+ }
+
+ if (!p) {
+ return TERMKEY_RES_NONE;
+ }
+
+ if (key->type != TERMKEY_TYPE_DCS
+ && key->type != TERMKEY_TYPE_OSC) {
+ return TERMKEY_RES_NONE;
+ }
+
+ TermKeyCsi *csi = p->info;
+
+ if (csi->saved_string_id != key->code.number) {
+ return TERMKEY_RES_NONE;
+ }
+
+ *strp = csi->saved_string;
+
+ return TERMKEY_RES_KEY;
+}
+
+/// Similar to snprintf(str, size, "%s", src) except it turns CamelCase into
+/// space separated values
+static int snprint_cameltospaces(char *str, size_t size, const char *src)
+{
+ int prev_lower = 0;
+ size_t l = 0;
+ while (*src && l < size - 1) {
+ if (isupper(*src) && prev_lower) {
+ if (str) {
+ str[l++] = ' ';
+ }
+ if (l >= size - 1) {
+ break;
+ }
+ }
+ prev_lower = islower(*src);
+ str[l++] = (char)tolower(*src++);
+ }
+ str[l] = 0;
+ // For consistency with snprintf, return the number of bytes that would have
+ // been written, excluding '\0'
+ while (*src) {
+ if (isupper(*src) && prev_lower) {
+ l++;
+ }
+ prev_lower = islower(*src);
+ src++; l++;
+ }
+ return (int)l;
+}
+
+/// Similar to strcmp(str, strcamel, n) except that:
+/// it compares CamelCase in strcamel with space separated values in str;
+/// it takes char**s and updates them
+/// n counts bytes of strcamel, not str
+static int strpncmp_camel(const char **strp, const char **strcamelp, size_t n)
+{
+ const char *str = *strp, *strcamel = *strcamelp;
+ int prev_lower = 0;
+
+ for (; (*str || *strcamel) && n; n--) {
+ char b = (char)tolower(*strcamel);
+ if (isupper(*strcamel) && prev_lower) {
+ if (*str != ' ') {
+ break;
+ }
+ str++;
+ if (*str != b) {
+ break;
+ }
+ } else if (*str != b) {
+ break;
+ }
+
+ prev_lower = islower(*strcamel);
+
+ str++;
+ strcamel++;
+ }
+
+ *strp = str;
+ *strcamelp = strcamel;
+ return *str - *strcamel;
+}
+
+static TermKey *termkey_alloc(void)
+{
+ TermKey *tk = xmalloc(sizeof(TermKey));
+
+ // Default all the object fields but don't allocate anything
+
+ tk->fd = -1;
+ tk->flags = 0;
+ tk->canonflags = 0;
+
+ tk->buffer = NULL;
+ tk->buffstart = 0;
+ tk->buffcount = 0;
+ tk->buffsize = 256; // bytes
+ tk->hightide = 0;
+
+#ifdef HAVE_TERMIOS
+ tk->restore_termios_valid = 0;
+#endif
+
+ tk->ti_getstr_hook = NULL;
+ tk->ti_getstr_hook_data = NULL;
+
+ tk->waittime = 50; // msec
+
+ tk->is_closed = 0;
+ tk->is_started = 0;
+
+ tk->nkeynames = 64;
+ tk->keynames = NULL;
+
+ for (int i = 0; i < 32; i++) {
+ tk->c0[i].sym = TERMKEY_SYM_NONE;
+ }
+
+ tk->drivers = NULL;
+
+ tk->method.emit_codepoint = &emit_codepoint;
+ tk->method.peekkey_simple = &peekkey_simple;
+ tk->method.peekkey_mouse = &peekkey_mouse;
+
+ return tk;
+}
+
+static int termkey_init(TermKey *tk, const char *term)
+{
+ tk->buffer = xmalloc(tk->buffsize);
+ tk->keynames = xmalloc(sizeof(tk->keynames[0]) * (size_t)tk->nkeynames);
+
+ int i;
+ for (i = 0; i < tk->nkeynames; i++) {
+ tk->keynames[i] = NULL;
+ }
+
+ for (i = 0; keynames[i].name; i++) {
+ if (termkey_register_keyname(tk, keynames[i].sym, keynames[i].name) == -1) {
+ goto abort_free_keynames;
+ }
+ }
+
+ register_c0(tk, TERMKEY_SYM_TAB, 0x09, NULL);
+ register_c0(tk, TERMKEY_SYM_ENTER, 0x0d, NULL);
+ register_c0(tk, TERMKEY_SYM_ESCAPE, 0x1b, NULL);
+
+ struct TermKeyDriverNode *tail = NULL;
+
+ for (i = 0; drivers[i]; i++) {
+ void *info = (*drivers[i]->new_driver)(tk, term);
+ if (!info) {
+ continue;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "Loading the %s driver...\n", drivers[i]->name);
+#endif
+
+ struct TermKeyDriverNode *thisdrv = xmalloc(sizeof(*thisdrv));
+ if (!thisdrv) {
+ goto abort_free_drivers;
+ }
+
+ thisdrv->driver = drivers[i];
+ thisdrv->info = info;
+ thisdrv->next = NULL;
+
+ if (!tail) {
+ tk->drivers = thisdrv;
+ } else {
+ tail->next = thisdrv;
+ }
+
+ tail = thisdrv;
+
+#ifdef DEBUG
+ fprintf(stderr, "Loaded %s driver\n", drivers[i]->name);
+#endif
+ }
+
+ if (!tk->drivers) {
+ errno = ENOENT;
+ goto abort_free_keynames;
+ }
+
+ return 1;
+
+abort_free_drivers:
+ for (struct TermKeyDriverNode *p = tk->drivers; p;) {
+ (*p->driver->free_driver)(p->info);
+ struct TermKeyDriverNode *next = p->next;
+ xfree(p);
+ p = next;
+ }
+
+abort_free_keynames:
+ xfree(tk->keynames);
+ xfree(tk->buffer);
+
+ return 0;
+}
+
+TermKey *termkey_new_abstract(const char *term, int flags)
+{
+ TermKey *tk = termkey_alloc();
+ if (!tk) {
+ return NULL;
+ }
+
+ tk->fd = -1;
+
+ termkey_set_flags(tk, flags);
+
+ if (!termkey_init(tk, term)) {
+ xfree(tk);
+ return NULL;
+ }
+
+ if (!(flags & TERMKEY_FLAG_NOSTART) && !termkey_start(tk)) {
+ goto abort;
+ }
+
+ return tk;
+
+abort:
+ xfree(tk);
+ return NULL;
+}
+
+void termkey_free(TermKey *tk)
+{
+ xfree(tk->buffer); tk->buffer = NULL;
+ xfree(tk->keynames); tk->keynames = NULL;
+
+ struct TermKeyDriverNode *p;
+ for (p = tk->drivers; p;) {
+ (*p->driver->free_driver)(p->info);
+ struct TermKeyDriverNode *next = p->next;
+ xfree(p);
+ p = next;
+ }
+
+ xfree(tk);
+}
+
+void termkey_destroy(TermKey *tk)
+{
+ if (tk->is_started) {
+ termkey_stop(tk);
+ }
+
+ termkey_free(tk);
+}
+
+void termkey_hook_terminfo_getstr(TermKey *tk, TermKey_Terminfo_Getstr_Hook *hookfn, void *data)
+{
+ tk->ti_getstr_hook = hookfn;
+ tk->ti_getstr_hook_data = data;
+}
+
+int termkey_start(TermKey *tk)
+{
+ if (tk->is_started) {
+ return 1;
+ }
+
+#ifdef HAVE_TERMIOS
+ if (tk->fd != -1 && !(tk->flags & TERMKEY_FLAG_NOTERMIOS)) {
+ struct termios termios;
+ if (tcgetattr(tk->fd, &termios) == 0) {
+ tk->restore_termios = termios;
+ tk->restore_termios_valid = 1;
+
+ termios.c_iflag &= (tcflag_t) ~(IXON|INLCR|ICRNL);
+ termios.c_lflag &= (tcflag_t) ~(ICANON|ECHO
+# ifdef IEXTEN
+ | IEXTEN
+# endif
+ );
+ termios.c_cc[VMIN] = 1;
+ termios.c_cc[VTIME] = 0;
+
+ if (tk->flags & TERMKEY_FLAG_CTRLC) {
+ // want no signal keys at all, so just disable ISIG
+ termios.c_lflag &= (tcflag_t) ~ISIG;
+ } else {
+ // Disable Ctrl-\==VQUIT and Ctrl-D==VSUSP but leave Ctrl-C as SIGINT
+ termios.c_cc[VQUIT] = _POSIX_VDISABLE;
+ termios.c_cc[VSUSP] = _POSIX_VDISABLE;
+ // Some OSes have Ctrl-Y==VDSUSP
+# ifdef VDSUSP
+ termios.c_cc[VDSUSP] = _POSIX_VDISABLE;
+# endif
+ }
+
+# ifdef DEBUG
+ fprintf(stderr, "Setting termios(3) flags\n");
+# endif
+ tcsetattr(tk->fd, TCSANOW, &termios);
+ }
+ }
+#endif
+
+ struct TermKeyDriverNode *p;
+ for (p = tk->drivers; p; p = p->next) {
+ if (p->driver->start_driver) {
+ if (!(*p->driver->start_driver)(tk, p->info)) {
+ return 0;
+ }
+ }
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "Drivers started; termkey instance %p is ready\n", tk);
+#endif
+
+ tk->is_started = 1;
+ return 1;
+}
+
+int termkey_stop(TermKey *tk)
+{
+ if (!tk->is_started) {
+ return 1;
+ }
+
+ struct TermKeyDriverNode *p;
+ for (p = tk->drivers; p; p = p->next) {
+ if (p->driver->stop_driver) {
+ (*p->driver->stop_driver)(tk, p->info);
+ }
+ }
+
+#ifdef HAVE_TERMIOS
+ if (tk->restore_termios_valid) {
+ tcsetattr(tk->fd, TCSANOW, &tk->restore_termios);
+ }
+#endif
+
+ tk->is_started = 0;
+
+ return 1;
+}
+
+void termkey_set_flags(TermKey *tk, int newflags)
+{
+ tk->flags = newflags;
+
+ if (tk->flags & TERMKEY_FLAG_SPACESYMBOL) {
+ tk->canonflags |= TERMKEY_CANON_SPACESYMBOL;
+ } else {
+ tk->canonflags &= ~TERMKEY_CANON_SPACESYMBOL;
+ }
+}
+
+int termkey_get_canonflags(TermKey *tk)
+{
+ return tk->canonflags;
+}
+
+void termkey_set_canonflags(TermKey *tk, int flags)
+{
+ tk->canonflags = flags;
+
+ if (tk->canonflags & TERMKEY_CANON_SPACESYMBOL) {
+ tk->flags |= TERMKEY_FLAG_SPACESYMBOL;
+ } else {
+ tk->flags &= ~TERMKEY_FLAG_SPACESYMBOL;
+ }
+}
+
+size_t termkey_get_buffer_size(TermKey *tk)
+{
+ return tk->buffsize;
+}
+
+int termkey_set_buffer_size(TermKey *tk, size_t size)
+{
+ unsigned char *buffer = xrealloc(tk->buffer, size);
+
+ tk->buffer = buffer;
+ tk->buffsize = size;
+
+ return 1;
+}
+
+size_t termkey_get_buffer_remaining(TermKey *tk)
+{
+ // Return the total number of free bytes in the buffer, because that's what
+ // is available to the user.
+ return tk->buffsize - tk->buffcount;
+}
+
+static void eat_bytes(TermKey *tk, size_t count)
+{
+ if (count >= tk->buffcount) {
+ tk->buffstart = 0;
+ tk->buffcount = 0;
+ return;
+ }
+
+ tk->buffstart += count;
+ tk->buffcount -= count;
+}
+
+// TODO(dundargoc): we should be able to replace this with utf_char2bytes from mbyte.c
+int fill_utf8(int codepoint, char *str)
+{
+ int nbytes = utf_char2len(codepoint);
+
+ str[nbytes] = 0;
+
+ // This is easier done backwards
+ int b = nbytes;
+ while (b > 1) {
+ b--;
+ str[b] = (char)0x80 | (codepoint & 0x3f);
+ codepoint >>= 6;
+ }
+
+ switch (nbytes) {
+ case 1:
+ str[0] = (codepoint & 0x7f); break;
+ case 2:
+ str[0] = (char)0xc0 | (codepoint & 0x1f); break;
+ case 3:
+ str[0] = (char)0xe0 | (codepoint & 0x0f); break;
+ case 4:
+ str[0] = (char)0xf0 | (codepoint & 0x07); break;
+ case 5:
+ str[0] = (char)0xf8 | (codepoint & 0x03); break;
+ case 6:
+ str[0] = (char)0xfc | (codepoint & 0x01); break;
+ }
+
+ return nbytes;
+}
+
+#define UTF8_INVALID 0xFFFD
+static TermKeyResult parse_utf8(const unsigned char *bytes, size_t len, int *cp, size_t *nbytep)
+{
+ unsigned nbytes;
+
+ unsigned char b0 = bytes[0];
+
+ if (b0 < 0x80) {
+ // Single byte ASCII
+ *cp = b0;
+ *nbytep = 1;
+ return TERMKEY_RES_KEY;
+ } else if (b0 < 0xc0) {
+ // Starts with a continuation byte - that's not right
+ *cp = UTF8_INVALID;
+ *nbytep = 1;
+ return TERMKEY_RES_KEY;
+ } else if (b0 < 0xe0) {
+ nbytes = 2;
+ *cp = b0 & 0x1f;
+ } else if (b0 < 0xf0) {
+ nbytes = 3;
+ *cp = b0 & 0x0f;
+ } else if (b0 < 0xf8) {
+ nbytes = 4;
+ *cp = b0 & 0x07;
+ } else if (b0 < 0xfc) {
+ nbytes = 5;
+ *cp = b0 & 0x03;
+ } else if (b0 < 0xfe) {
+ nbytes = 6;
+ *cp = b0 & 0x01;
+ } else {
+ *cp = UTF8_INVALID;
+ *nbytep = 1;
+ return TERMKEY_RES_KEY;
+ }
+
+ for (unsigned b = 1; b < nbytes; b++) {
+ unsigned char cb;
+
+ if (b >= len) {
+ return TERMKEY_RES_AGAIN;
+ }
+
+ cb = bytes[b];
+ if (cb < 0x80 || cb >= 0xc0) {
+ *cp = UTF8_INVALID;
+ *nbytep = b;
+ return TERMKEY_RES_KEY;
+ }
+
+ *cp <<= 6;
+ *cp |= cb & 0x3f;
+ }
+
+ // Check for overlong sequences
+ if ((int)nbytes > utf_char2len(*cp)) {
+ *cp = UTF8_INVALID;
+ }
+
+ // Check for UTF-16 surrogates or invalid *cps
+ if ((*cp >= 0xD800 && *cp <= 0xDFFF)
+ || *cp == 0xFFFE
+ || *cp == 0xFFFF) {
+ *cp = UTF8_INVALID;
+ }
+
+ *nbytep = nbytes;
+ return TERMKEY_RES_KEY;
+}
+
+static void emit_codepoint(TermKey *tk, int codepoint, TermKeyKey *key)
+{
+ if (codepoint == 0) {
+ // ASCII NUL = Ctrl-Space
+ key->type = TERMKEY_TYPE_KEYSYM;
+ key->code.sym = TERMKEY_SYM_SPACE;
+ key->modifiers = TERMKEY_KEYMOD_CTRL;
+ } else if (codepoint < 0x20) {
+ // C0 range
+ key->code.codepoint = 0;
+ key->modifiers = 0;
+
+ if (!(tk->flags & TERMKEY_FLAG_NOINTERPRET) && tk->c0[codepoint].sym != TERMKEY_SYM_UNKNOWN) {
+ key->code.sym = tk->c0[codepoint].sym;
+ key->modifiers |= tk->c0[codepoint].modifier_set;
+ }
+
+ if (!key->code.sym) {
+ key->type = TERMKEY_TYPE_UNICODE;
+ // Generically modified Unicode ought not report the SHIFT state, or else
+ // we get into complications trying to report Shift-; vs : and so on...
+ // In order to be able to represent Ctrl-Shift-A as CTRL modified
+ // unicode A, we need to call Ctrl-A simply 'a', lowercase
+ if (codepoint + 0x40 >= 'A' && codepoint + 0x40 <= 'Z') {
+ // it's a letter - use lowercase instead
+ key->code.codepoint = codepoint + 0x60;
+ } else {
+ key->code.codepoint = codepoint + 0x40;
+ }
+ key->modifiers = TERMKEY_KEYMOD_CTRL;
+ } else {
+ key->type = TERMKEY_TYPE_KEYSYM;
+ }
+ } else if (codepoint == 0x7f && !(tk->flags & TERMKEY_FLAG_NOINTERPRET)) {
+ // ASCII DEL
+ key->type = TERMKEY_TYPE_KEYSYM;
+ key->code.sym = TERMKEY_SYM_DEL;
+ key->modifiers = 0;
+ } else if (codepoint >= 0x20 && codepoint < 0x80) {
+ // ASCII lowbyte range
+ key->type = TERMKEY_TYPE_UNICODE;
+ key->code.codepoint = codepoint;
+ key->modifiers = 0;
+ } else if (codepoint >= 0x80 && codepoint < 0xa0) {
+ // UTF-8 never starts with a C1 byte. So we can be sure of these
+ key->type = TERMKEY_TYPE_UNICODE;
+ key->code.codepoint = codepoint - 0x40;
+ key->modifiers = TERMKEY_KEYMOD_CTRL|TERMKEY_KEYMOD_ALT;
+ } else {
+ // UTF-8 codepoint
+ key->type = TERMKEY_TYPE_UNICODE;
+ key->code.codepoint = codepoint;
+ key->modifiers = 0;
+ }
+
+ termkey_canonicalise(tk, key);
+
+ if (key->type == TERMKEY_TYPE_UNICODE) {
+ fill_utf8(key->code.codepoint, key->utf8);
+ }
+}
+
+void termkey_canonicalise(TermKey *tk, TermKeyKey *key)
+{
+ int flags = tk->canonflags;
+
+ if (flags & TERMKEY_CANON_SPACESYMBOL) {
+ if (key->type == TERMKEY_TYPE_UNICODE && key->code.codepoint == 0x20) {
+ key->type = TERMKEY_TYPE_KEYSYM;
+ key->code.sym = TERMKEY_SYM_SPACE;
+ }
+ } else {
+ if (key->type == TERMKEY_TYPE_KEYSYM && key->code.sym == TERMKEY_SYM_SPACE) {
+ key->type = TERMKEY_TYPE_UNICODE;
+ key->code.codepoint = 0x20;
+ fill_utf8(key->code.codepoint, key->utf8);
+ }
+ }
+
+ if (flags & TERMKEY_CANON_DELBS) {
+ if (key->type == TERMKEY_TYPE_KEYSYM && key->code.sym == TERMKEY_SYM_DEL) {
+ key->code.sym = TERMKEY_SYM_BACKSPACE;
+ }
+ }
+}
+
+static TermKeyResult peekkey(TermKey *tk, TermKeyKey *key, int force, size_t *nbytep)
+{
+ int again = 0;
+
+ if (!tk->is_started) {
+ errno = EINVAL;
+ return TERMKEY_RES_ERROR;
+ }
+
+#ifdef DEBUG
+ fprintf(stderr, "getkey(force=%d): buffer ", force);
+ print_buffer(tk);
+ fprintf(stderr, "\n");
+#endif
+
+ if (tk->hightide) {
+ tk->buffstart += tk->hightide;
+ tk->buffcount -= tk->hightide;
+ tk->hightide = 0;
+ }
+
+ TermKeyResult ret;
+ struct TermKeyDriverNode *p;
+ for (p = tk->drivers; p; p = p->next) {
+ ret = (p->driver->peekkey)(tk, p->info, key, force, nbytep);
+
+#ifdef DEBUG
+ fprintf(stderr, "Driver %s yields %s\n", p->driver->name, res2str(ret));
+#endif
+
+ switch (ret) {
+ case TERMKEY_RES_KEY:
+#ifdef DEBUG
+ print_key(tk, key); fprintf(stderr, "\n");
+#endif
+ // Slide the data down to stop it running away
+ {
+ size_t halfsize = tk->buffsize / 2;
+
+ if (tk->buffstart > halfsize) {
+ memcpy(tk->buffer, tk->buffer + halfsize, halfsize);
+ tk->buffstart -= halfsize;
+ }
+ }
+ FALLTHROUGH;
+ case TERMKEY_RES_EOF:
+ case TERMKEY_RES_ERROR:
+ return ret;
+
+ case TERMKEY_RES_AGAIN:
+ if (!force) {
+ again = 1;
+ }
+ FALLTHROUGH;
+ case TERMKEY_RES_NONE:
+ break;
+ }
+ }
+
+ if (again) {
+ return TERMKEY_RES_AGAIN;
+ }
+
+ ret = peekkey_simple(tk, key, force, nbytep);
+
+#ifdef DEBUG
+ fprintf(stderr, "getkey_simple(force=%d) yields %s\n", force, res2str(ret));
+ if (ret == TERMKEY_RES_KEY) {
+ print_key(tk, key); fprintf(stderr, "\n");
+ }
+#endif
+
+ return ret;
+}
+
+static TermKeyResult peekkey_simple(TermKey *tk, TermKeyKey *key, int force, size_t *nbytep)
+{
+ if (tk->buffcount == 0) {
+ return tk->is_closed ? TERMKEY_RES_EOF : TERMKEY_RES_NONE;
+ }
+
+ unsigned char b0 = CHARAT(0);
+
+ if (b0 == 0x1b) {
+ // Escape-prefixed value? Might therefore be Alt+key
+ if (tk->buffcount == 1) {
+ // This might be an <Esc> press, or it may want to be part of a longer
+ // sequence
+ if (!force) {
+ return TERMKEY_RES_AGAIN;
+ }
+
+ (*tk->method.emit_codepoint)(tk, b0, key);
+ *nbytep = 1;
+ return TERMKEY_RES_KEY;
+ }
+
+ // Try another key there
+ tk->buffstart++;
+ tk->buffcount--;
+
+ // Run the full driver
+ TermKeyResult metakey_result = peekkey(tk, key, force, nbytep);
+
+ tk->buffstart--;
+ tk->buffcount++;
+
+ switch (metakey_result) {
+ case TERMKEY_RES_KEY:
+ key->modifiers |= TERMKEY_KEYMOD_ALT;
+ (*nbytep)++;
+ break;
+
+ case TERMKEY_RES_NONE:
+ case TERMKEY_RES_EOF:
+ case TERMKEY_RES_AGAIN:
+ case TERMKEY_RES_ERROR:
+ break;
+ }
+
+ return metakey_result;
+ } else if (b0 < 0xa0) {
+ // Single byte C0, G0 or C1 - C1 is never UTF-8 initial byte
+ (*tk->method.emit_codepoint)(tk, b0, key);
+ *nbytep = 1;
+ return TERMKEY_RES_KEY;
+ } else if (tk->flags & TERMKEY_FLAG_UTF8) {
+ // Some UTF-8
+ int codepoint;
+ TermKeyResult res = parse_utf8(tk->buffer + tk->buffstart, tk->buffcount, &codepoint, nbytep);
+
+ if (res == TERMKEY_RES_AGAIN && force) {
+ // There weren't enough bytes for a complete UTF-8 sequence but caller
+ // demands an answer. About the best thing we can do here is eat as many
+ // bytes as we have, and emit a UTF8_INVALID. If the remaining bytes
+ // arrive later, they'll be invalid too.
+ codepoint = UTF8_INVALID;
+ *nbytep = tk->buffcount;
+ res = TERMKEY_RES_KEY;
+ }
+
+ key->type = TERMKEY_TYPE_UNICODE;
+ key->modifiers = 0;
+ (*tk->method.emit_codepoint)(tk, codepoint, key);
+ return res;
+ } else {
+ // Non UTF-8 case - just report the raw byte
+ key->type = TERMKEY_TYPE_UNICODE;
+ key->code.codepoint = b0;
+ key->modifiers = 0;
+
+ key->utf8[0] = (char)key->code.codepoint;
+ key->utf8[1] = 0;
+
+ *nbytep = 1;
+
+ return TERMKEY_RES_KEY;
+ }
+}
+
+static TermKeyResult peekkey_mouse(TermKey *tk, TermKeyKey *key, size_t *nbytep)
+{
+ if (tk->buffcount < 3) {
+ return TERMKEY_RES_AGAIN;
+ }
+
+ key->type = TERMKEY_TYPE_MOUSE;
+ key->code.mouse[0] = (char)CHARAT(0) - 0x20;
+ key->code.mouse[1] = (char)CHARAT(1) - 0x20;
+ key->code.mouse[2] = (char)CHARAT(2) - 0x20;
+ key->code.mouse[3] = 0;
+
+ key->modifiers = (key->code.mouse[0] & 0x1c) >> 2;
+ key->code.mouse[0] &= ~0x1c;
+
+ *nbytep = 3;
+ return TERMKEY_RES_KEY;
+}
+
+TermKeyResult termkey_getkey(TermKey *tk, TermKeyKey *key)
+{
+ size_t nbytes = 0;
+ TermKeyResult ret = peekkey(tk, key, 0, &nbytes);
+
+ if (ret == TERMKEY_RES_KEY) {
+ eat_bytes(tk, nbytes);
+ }
+
+ if (ret == TERMKEY_RES_AGAIN) {
+ // Call peekkey() again in force mode to obtain whatever it can
+ (void)peekkey(tk, key, 1, &nbytes);
+ }
+ // Don't eat it yet though
+
+ return ret;
+}
+
+TermKeyResult termkey_getkey_force(TermKey *tk, TermKeyKey *key)
+{
+ size_t nbytes = 0;
+ TermKeyResult ret = peekkey(tk, key, 1, &nbytes);
+
+ if (ret == TERMKEY_RES_KEY) {
+ eat_bytes(tk, nbytes);
+ }
+
+ return ret;
+}
+
+size_t termkey_push_bytes(TermKey *tk, const char *bytes, size_t len)
+{
+ if (tk->buffstart) {
+ memmove(tk->buffer, tk->buffer + tk->buffstart, tk->buffcount);
+ tk->buffstart = 0;
+ }
+
+ // Not expecting it ever to be greater but doesn't hurt to handle that
+ if (tk->buffcount >= tk->buffsize) {
+ errno = ENOMEM;
+ return (size_t)-1;
+ }
+
+ if (len > tk->buffsize - tk->buffcount) {
+ len = tk->buffsize - tk->buffcount;
+ }
+
+ // memcpy(), not strncpy() in case of null bytes in input
+ memcpy(tk->buffer + tk->buffcount, bytes, len);
+ tk->buffcount += len;
+
+ return len;
+}
+
+TermKeySym termkey_register_keyname(TermKey *tk, TermKeySym sym, const char *name)
+{
+ if (!sym) {
+ sym = tk->nkeynames;
+ }
+
+ if (sym >= tk->nkeynames) {
+ const char **new_keynames = xrealloc(tk->keynames, sizeof(new_keynames[0]) * ((size_t)sym + 1));
+
+ tk->keynames = new_keynames;
+
+ // Fill in the hole
+ for (int i = tk->nkeynames; i < sym; i++) {
+ tk->keynames[i] = NULL;
+ }
+
+ tk->nkeynames = sym + 1;
+ }
+
+ tk->keynames[sym] = name;
+
+ return sym;
+}
+
+const char *termkey_get_keyname(TermKey *tk, TermKeySym sym)
+{
+ if (sym == TERMKEY_SYM_UNKNOWN) {
+ return "UNKNOWN";
+ }
+
+ if (sym < tk->nkeynames) {
+ return tk->keynames[sym];
+ }
+
+ return "UNKNOWN";
+}
+
+static const char *termkey_lookup_keyname_format(TermKey *tk, const char *str, TermKeySym *sym,
+ TermKeyFormat format)
+{
+ // We store an array, so we can't do better than a linear search. Doesn't
+ // matter because user won't be calling this too often
+
+ for (*sym = 0; *sym < tk->nkeynames; (*sym)++) {
+ const char *thiskey = tk->keynames[*sym];
+ if (!thiskey) {
+ continue;
+ }
+ size_t len = strlen(thiskey);
+ if (format & TERMKEY_FORMAT_LOWERSPACE) {
+ const char *thisstr = str;
+ if (strpncmp_camel(&thisstr, &thiskey, len) == 0) {
+ return thisstr;
+ }
+ } else {
+ if (strncmp(str, thiskey, len) == 0) {
+ return (char *)str + len;
+ }
+ }
+ }
+
+ return NULL;
+}
+
+const char *termkey_lookup_keyname(TermKey *tk, const char *str, TermKeySym *sym)
+{
+ return termkey_lookup_keyname_format(tk, str, sym, 0);
+}
+
+static TermKeySym register_c0(TermKey *tk, TermKeySym sym, unsigned char ctrl, const char *name)
+{
+ return register_c0_full(tk, sym, 0, 0, ctrl, name);
+}
+
+static TermKeySym register_c0_full(TermKey *tk, TermKeySym sym, int modifier_set, int modifier_mask,
+ unsigned char ctrl, const char *name)
+{
+ if (ctrl >= 0x20) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (name) {
+ sym = termkey_register_keyname(tk, sym, name);
+ }
+
+ tk->c0[ctrl].sym = sym;
+ tk->c0[ctrl].modifier_set = modifier_set;
+ tk->c0[ctrl].modifier_mask = modifier_mask;
+
+ return sym;
+}
+
+static struct modnames {
+ const char *shift, *alt, *ctrl;
+}
+modnames[] = {
+ { "S", "A", "C" }, // 0
+ { "Shift", "Alt", "Ctrl" }, // LONGMOD
+ { "S", "M", "C" }, // ALTISMETA
+ { "Shift", "Meta", "Ctrl" }, // ALTISMETA+LONGMOD
+ { "s", "a", "c" }, // LOWERMOD
+ { "shift", "alt", "ctrl" }, // LOWERMOD+LONGMOD
+ { "s", "m", "c" }, // LOWERMOD+ALTISMETA
+ { "shift", "meta", "ctrl" }, // LOWERMOD+ALTISMETA+LONGMOD
+};
+
+size_t termkey_strfkey(TermKey *tk, char *buffer, size_t len, TermKeyKey *key, TermKeyFormat format)
+{
+ size_t pos = 0;
+ size_t l = 0;
+
+ struct modnames *mods = &modnames[!!(format & TERMKEY_FORMAT_LONGMOD) +
+ !!(format & TERMKEY_FORMAT_ALTISMETA) * 2 +
+ !!(format & TERMKEY_FORMAT_LOWERMOD) * 4];
+
+ int wrapbracket = (format & TERMKEY_FORMAT_WRAPBRACKET)
+ && (key->type != TERMKEY_TYPE_UNICODE || key->modifiers != 0);
+
+ char sep = (format & TERMKEY_FORMAT_SPACEMOD) ? ' ' : '-';
+
+ if (format & TERMKEY_FORMAT_CARETCTRL
+ && key->type == TERMKEY_TYPE_UNICODE
+ && key->modifiers == TERMKEY_KEYMOD_CTRL) {
+ long codepoint = key->code.codepoint;
+
+ // Handle some of the special cases first
+ if (codepoint >= 'a' && codepoint <= 'z') {
+ l = (size_t)snprintf(buffer + pos, len - pos, wrapbracket ? "<^%c>" : "^%c",
+ (char)codepoint - 0x20);
+ if (l <= 0) {
+ return pos;
+ }
+ pos += l;
+ return pos;
+ } else if ((codepoint >= '@' && codepoint < 'A')
+ || (codepoint > 'Z' && codepoint <= '_')) {
+ l = (size_t)snprintf(buffer + pos, len - pos, wrapbracket ? "<^%c>" : "^%c", (char)codepoint);
+ if (l <= 0) {
+ return pos;
+ }
+ pos += l;
+ return pos;
+ }
+ }
+
+ if (wrapbracket) {
+ l = (size_t)snprintf(buffer + pos, len - pos, "<");
+ if (l <= 0) {
+ return pos;
+ }
+ pos += l;
+ }
+
+ if (key->modifiers & TERMKEY_KEYMOD_ALT) {
+ l = (size_t)snprintf(buffer + pos, len - pos, "%s%c", mods->alt, sep);
+ if (l <= 0) {
+ return pos;
+ }
+ pos += l;
+ }
+
+ if (key->modifiers & TERMKEY_KEYMOD_CTRL) {
+ l = (size_t)snprintf(buffer + pos, len - pos, "%s%c", mods->ctrl, sep);
+ if (l <= 0) {
+ return pos;
+ }
+ pos += l;
+ }
+
+ if (key->modifiers & TERMKEY_KEYMOD_SHIFT) {
+ l = (size_t)snprintf(buffer + pos, len - pos, "%s%c", mods->shift, sep);
+ if (l <= 0) {
+ return pos;
+ }
+ pos += l;
+ }
+
+ switch (key->type) {
+ case TERMKEY_TYPE_UNICODE:
+ if (!key->utf8[0]) { // In case of user-supplied key structures
+ fill_utf8(key->code.codepoint, key->utf8);
+ }
+ l = (size_t)snprintf(buffer + pos, len - pos, "%s", key->utf8);
+ break;
+ case TERMKEY_TYPE_KEYSYM: {
+ const char *name = termkey_get_keyname(tk, key->code.sym);
+ if (format & TERMKEY_FORMAT_LOWERSPACE) {
+ l = (size_t)snprint_cameltospaces(buffer + pos, len - pos, name);
+ } else {
+ l = (size_t)snprintf(buffer + pos, len - pos, "%s", name);
+ }
+ }
+ break;
+ case TERMKEY_TYPE_FUNCTION:
+ l = (size_t)snprintf(buffer + pos, len - pos, "%c%d",
+ (format & TERMKEY_FORMAT_LOWERSPACE ? 'f' : 'F'), key->code.number);
+ break;
+ case TERMKEY_TYPE_MOUSE: {
+ TermKeyMouseEvent ev;
+ int button;
+ int line, col;
+ termkey_interpret_mouse(tk, key, &ev, &button, &line, &col);
+
+ l = (size_t)snprintf(buffer + pos, len - pos, "Mouse%s(%d)",
+ evnames[ev], button);
+
+ if (format & TERMKEY_FORMAT_MOUSE_POS) {
+ if (l <= 0) {
+ return pos;
+ }
+ pos += l;
+
+ l = (size_t)snprintf(buffer + pos, len - pos, " @ (%u,%u)", col, line);
+ }
+ }
+ break;
+ case TERMKEY_TYPE_POSITION:
+ l = (size_t)snprintf(buffer + pos, len - pos, "Position");
+ break;
+ case TERMKEY_TYPE_MODEREPORT: {
+ int initial, mode, value;
+ termkey_interpret_modereport(tk, key, &initial, &mode, &value);
+ if (initial) {
+ l = (size_t)snprintf(buffer + pos, len - pos, "Mode(%c%d=%d)", initial, mode, value);
+ } else {
+ l = (size_t)snprintf(buffer + pos, len - pos, "Mode(%d=%d)", mode, value);
+ }
+ }
+ break;
+ case TERMKEY_TYPE_DCS:
+ l = (size_t)snprintf(buffer + pos, len - pos, "DCS");
+ break;
+ case TERMKEY_TYPE_OSC:
+ l = (size_t)snprintf(buffer + pos, len - pos, "OSC");
+ break;
+ case TERMKEY_TYPE_UNKNOWN_CSI:
+ l = (size_t)snprintf(buffer + pos, len - pos, "CSI %c", key->code.number & 0xff);
+ break;
+ }
+
+ if (l <= 0) {
+ return pos;
+ }
+ pos += l;
+
+ if (wrapbracket) {
+ l = (size_t)snprintf(buffer + pos, len - pos, ">");
+ if (l <= 0) {
+ return pos;
+ }
+ pos += l;
+ }
+
+ return pos;
+}
diff --git a/src/nvim/tui/termkey/termkey.h b/src/nvim/tui/termkey/termkey.h
new file mode 100644
index 0000000000..21ed141346
--- /dev/null
+++ b/src/nvim/tui/termkey/termkey.h
@@ -0,0 +1,10 @@
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include "nvim/tui/termkey/termkey_defs.h"
+
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "tui/termkey/termkey.h.generated.h"
+#endif
diff --git a/src/nvim/tui/termkey/termkey_defs.h b/src/nvim/tui/termkey/termkey_defs.h
new file mode 100644
index 0000000000..7c218ba7c2
--- /dev/null
+++ b/src/nvim/tui/termkey/termkey_defs.h
@@ -0,0 +1,199 @@
+#pragma once
+
+#include <stddef.h>
+#include <stdint.h>
+#include <unibilium.h>
+#include <uv.h>
+
+#include "nvim/event/defs.h"
+#include "nvim/tui/tui_defs.h"
+#include "nvim/types_defs.h"
+
+typedef struct TermKey TermKey;
+
+typedef struct {
+ TermKey *tk;
+ int saved_string_id;
+ char *saved_string;
+} TermKeyCsi;
+
+typedef enum {
+ TERMKEY_RES_NONE,
+ TERMKEY_RES_KEY,
+ TERMKEY_RES_EOF,
+ TERMKEY_RES_AGAIN,
+ TERMKEY_RES_ERROR,
+} TermKeyResult;
+
+typedef enum {
+ TERMKEY_SYM_UNKNOWN = -1,
+ TERMKEY_SYM_NONE = 0,
+
+ // Special names in C0
+ TERMKEY_SYM_BACKSPACE,
+ TERMKEY_SYM_TAB,
+ TERMKEY_SYM_ENTER,
+ TERMKEY_SYM_ESCAPE,
+
+ // Special names in G0
+ TERMKEY_SYM_SPACE,
+ TERMKEY_SYM_DEL,
+
+ // Special keys
+ TERMKEY_SYM_UP,
+ TERMKEY_SYM_DOWN,
+ TERMKEY_SYM_LEFT,
+ TERMKEY_SYM_RIGHT,
+ TERMKEY_SYM_BEGIN,
+ TERMKEY_SYM_FIND,
+ TERMKEY_SYM_INSERT,
+ TERMKEY_SYM_DELETE,
+ TERMKEY_SYM_SELECT,
+ TERMKEY_SYM_PAGEUP,
+ TERMKEY_SYM_PAGEDOWN,
+ TERMKEY_SYM_HOME,
+ TERMKEY_SYM_END,
+
+ // Special keys from terminfo
+ TERMKEY_SYM_CANCEL,
+ TERMKEY_SYM_CLEAR,
+ TERMKEY_SYM_CLOSE,
+ TERMKEY_SYM_COMMAND,
+ TERMKEY_SYM_COPY,
+ TERMKEY_SYM_EXIT,
+ TERMKEY_SYM_HELP,
+ TERMKEY_SYM_MARK,
+ TERMKEY_SYM_MESSAGE,
+ TERMKEY_SYM_MOVE,
+ TERMKEY_SYM_OPEN,
+ TERMKEY_SYM_OPTIONS,
+ TERMKEY_SYM_PRINT,
+ TERMKEY_SYM_REDO,
+ TERMKEY_SYM_REFERENCE,
+ TERMKEY_SYM_REFRESH,
+ TERMKEY_SYM_REPLACE,
+ TERMKEY_SYM_RESTART,
+ TERMKEY_SYM_RESUME,
+ TERMKEY_SYM_SAVE,
+ TERMKEY_SYM_SUSPEND,
+ TERMKEY_SYM_UNDO,
+
+ // Numeric keypad special keys
+ TERMKEY_SYM_KP0,
+ TERMKEY_SYM_KP1,
+ TERMKEY_SYM_KP2,
+ TERMKEY_SYM_KP3,
+ TERMKEY_SYM_KP4,
+ TERMKEY_SYM_KP5,
+ TERMKEY_SYM_KP6,
+ TERMKEY_SYM_KP7,
+ TERMKEY_SYM_KP8,
+ TERMKEY_SYM_KP9,
+ TERMKEY_SYM_KPENTER,
+ TERMKEY_SYM_KPPLUS,
+ TERMKEY_SYM_KPMINUS,
+ TERMKEY_SYM_KPMULT,
+ TERMKEY_SYM_KPDIV,
+ TERMKEY_SYM_KPCOMMA,
+ TERMKEY_SYM_KPPERIOD,
+ TERMKEY_SYM_KPEQUALS,
+
+ // et cetera ad nauseum
+ TERMKEY_N_SYMS,
+} TermKeySym;
+
+typedef enum {
+ TERMKEY_TYPE_UNICODE,
+ TERMKEY_TYPE_FUNCTION,
+ TERMKEY_TYPE_KEYSYM,
+ TERMKEY_TYPE_MOUSE,
+ TERMKEY_TYPE_POSITION,
+ TERMKEY_TYPE_MODEREPORT,
+ TERMKEY_TYPE_DCS,
+ TERMKEY_TYPE_OSC,
+ // add other recognised types here
+
+ TERMKEY_TYPE_UNKNOWN_CSI = -1,
+} TermKeyType;
+
+typedef enum {
+ TERMKEY_MOUSE_UNKNOWN,
+ TERMKEY_MOUSE_PRESS,
+ TERMKEY_MOUSE_DRAG,
+ TERMKEY_MOUSE_RELEASE,
+} TermKeyMouseEvent;
+
+enum {
+ TERMKEY_KEYMOD_SHIFT = 1 << 0,
+ TERMKEY_KEYMOD_ALT = 1 << 1,
+ TERMKEY_KEYMOD_CTRL = 1 << 2,
+};
+
+typedef struct {
+ const unsigned char *param;
+ size_t length;
+} TermKeyCsiParam;
+
+enum {
+ TERMKEY_FLAG_NOINTERPRET = 1 << 0, // Do not interpret C0//DEL codes if possible
+ TERMKEY_FLAG_CONVERTKP = 1 << 1, // Convert KP codes to regular keypresses
+ TERMKEY_FLAG_RAW = 1 << 2, // Input is raw bytes, not UTF-8
+ TERMKEY_FLAG_UTF8 = 1 << 3, // Input is definitely UTF-8
+ TERMKEY_FLAG_NOTERMIOS = 1 << 4, // Do not make initial termios calls on construction
+ TERMKEY_FLAG_SPACESYMBOL = 1 << 5, // Sets TERMKEY_CANON_SPACESYMBOL
+ TERMKEY_FLAG_CTRLC = 1 << 6, // Allow Ctrl-C to be read as normal, disabling SIGINT
+ TERMKEY_FLAG_EINTR = 1 << 7, // Return ERROR on signal (EINTR) rather than retry
+ TERMKEY_FLAG_NOSTART = 1 << 8, // Do not call termkey_start() in constructor
+};
+
+enum {
+ TERMKEY_CANON_SPACESYMBOL = 1 << 0, // Space is symbolic rather than Unicode
+ TERMKEY_CANON_DELBS = 1 << 1, // Del is converted to Backspace
+};
+
+typedef struct {
+ TermKeyType type;
+ union {
+ int codepoint; // TERMKEY_TYPE_UNICODE
+ int number; // TERMKEY_TYPE_FUNCTION
+ TermKeySym sym; // TERMKEY_TYPE_KEYSYM
+ char mouse[4]; // TERMKEY_TYPE_MOUSE
+ // opaque. see termkey_interpret_mouse
+ } code;
+
+ int modifiers;
+
+ // Any Unicode character can be UTF-8 encoded in no more than 6 bytes, plus
+ // terminating NUL
+ char utf8[7];
+} TermKeyKey;
+
+// Mostly-undocumented hooks for doing evil evil things
+typedef const char *TermKey_Terminfo_Getstr_Hook(const char *name, const char *value, void *data);
+
+typedef enum {
+ TERMKEY_FORMAT_LONGMOD = 1 << 0, // Shift-... instead of S-...
+ TERMKEY_FORMAT_CARETCTRL = 1 << 1, // ^X instead of C-X
+ TERMKEY_FORMAT_ALTISMETA = 1 << 2, // Meta- or M- instead of Alt- or A-
+ TERMKEY_FORMAT_WRAPBRACKET = 1 << 3, // Wrap special keys in brackets like <Escape>
+ TERMKEY_FORMAT_SPACEMOD = 1 << 4, // M Foo instead of M-Foo
+ TERMKEY_FORMAT_LOWERMOD = 1 << 5, // meta or m instead of Meta or M
+ TERMKEY_FORMAT_LOWERSPACE = 1 << 6, // page down instead of PageDown
+
+ TERMKEY_FORMAT_MOUSE_POS = 1 << 8, // Include mouse position if relevant; @ col,line
+} TermKeyFormat;
+
+// Some useful combinations
+
+#define TERMKEY_FORMAT_VIM (TermKeyFormat)(TERMKEY_FORMAT_ALTISMETA|TERMKEY_FORMAT_WRAPBRACKET)
+
+typedef struct {
+ TermKey *tk;
+
+ unibi_term *unibi; // only valid until first 'start' call
+
+ struct trie_node *root;
+
+ char *start_string;
+ char *stop_string;
+} TermKeyTI;
diff --git a/src/nvim/tui/tui.c b/src/nvim/tui/tui.c
index 2a9530defb..fa50a8252d 100644
--- a/src/nvim/tui/tui.c
+++ b/src/nvim/tui/tui.c
@@ -109,12 +109,14 @@ struct TUIData {
bool set_cursor_color_as_str;
bool cursor_color_changed;
bool is_starting;
+ bool did_set_grapheme_cluster_mode;
FILE *screenshot;
cursorentry_T cursor_shapes[SHAPE_IDX_COUNT];
HlAttrs clear_attrs;
kvec_t(HlAttrs) attrs;
int print_attr_id;
bool default_attr;
+ bool set_default_colors;
bool can_clear_attr;
ModeShape showing_mode;
Integer verbose;
@@ -166,18 +168,11 @@ void tui_start(TUIData **tui_p, int *width, int *height, char **term, bool *rgb)
tui->seen_error_exit = 0;
tui->loop = &main_loop;
tui->url = -1;
- // Because setting the default colors is delayed until after startup to avoid
- // flickering with the default colorscheme background, any flush that happens
- // during startup in turn would result in clearing invalidated regions with
- // uninitialized attrs(black). Instead initialize clear_attrs with current
- // terminal background so that it is at least not perceived as flickering, even
- // though it may be different from the colorscheme that is set during startup.
- tui->clear_attrs.rgb_bg_color = normal_bg;
- tui->clear_attrs.cterm_bg_color = (int16_t)cterm_normal_bg_color;
kv_init(tui->invalid_regions);
kv_init(tui->urlbuf);
signal_watcher_init(tui->loop, &tui->winch_handle, tui);
+ signal_watcher_start(&tui->winch_handle, sigwinch_cb, SIGWINCH);
// TODO(bfredl): zero hl is empty, send this explicitly?
kv_push(tui->attrs, HLATTRS_INIT);
@@ -212,10 +207,21 @@ static void tui_request_term_mode(TUIData *tui, TermMode mode)
out(tui, buf, (size_t)len);
}
+/// Set (DECSET) or reset (DECRST) a terminal mode.
+static void tui_set_term_mode(TUIData *tui, TermMode mode, bool set)
+ FUNC_ATTR_NONNULL_ALL
+{
+ char buf[12];
+ int len = snprintf(buf, sizeof(buf), "\x1b[?%d%c", (int)mode, set ? 'h' : 'l');
+ assert((len > 0) && (len < (int)sizeof(buf)));
+ out(tui, buf, (size_t)len);
+}
+
/// Handle a mode report (DECRPM) from the terminal.
void tui_handle_term_mode(TUIData *tui, TermMode mode, TermModeState state)
FUNC_ATTR_NONNULL_ALL
{
+ bool is_set = false;
switch (state) {
case kTermModeNotRecognized:
case kTermModePermanentlySet:
@@ -224,6 +230,8 @@ void tui_handle_term_mode(TUIData *tui, TermMode mode, TermModeState state)
// then there is nothing to do
break;
case kTermModeSet:
+ is_set = true;
+ FALLTHROUGH;
case kTermModeReset:
// The terminal supports changing the given mode
switch (mode) {
@@ -231,6 +239,17 @@ void tui_handle_term_mode(TUIData *tui, TermMode mode, TermModeState state)
// Ref: https://gist.github.com/christianparpart/d8a62cc1ab659194337d73e399004036
tui->unibi_ext.sync = (int)unibi_add_ext_str(tui->ut, "Sync",
"\x1b[?2026%?%p1%{1}%-%tl%eh%;");
+ break;
+ case kTermModeResizeEvents:
+ signal_watcher_stop(&tui->winch_handle);
+ tui_set_term_mode(tui, mode, true);
+ break;
+ case kTermModeGraphemeClusters:
+ if (!is_set) {
+ tui_set_term_mode(tui, mode, true);
+ tui->did_set_grapheme_cluster_mode = true;
+ }
+ break;
}
}
}
@@ -424,6 +443,8 @@ static void terminfo_start(TUIData *tui)
// Some terminals (such as Terminal.app) do not support DECRQM, so skip the query.
if (!nsterm) {
tui_request_term_mode(tui, kTermModeSynchronizedOutput);
+ tui_request_term_mode(tui, kTermModeResizeEvents);
+ tui_request_term_mode(tui, kTermModeGraphemeClusters);
}
// Don't use DECRQSS in screen or tmux, as they behave strangely when receiving it.
@@ -482,6 +503,11 @@ static void terminfo_stop(TUIData *tui)
// Reset the key encoding
tui_reset_key_encoding(tui);
+ // Disable resize events
+ tui_set_term_mode(tui, kTermModeResizeEvents, false);
+ if (tui->did_set_grapheme_cluster_mode) {
+ tui_set_term_mode(tui, kTermModeGraphemeClusters, false);
+ }
// May restore old title before exiting alternate screen.
tui_set_title(tui, NULL_STRING);
if (ui_client_exit_status == 0) {
@@ -517,7 +543,6 @@ static void tui_terminal_start(TUIData *tui)
tui->print_attr_id = -1;
terminfo_start(tui);
tui_guess_size(tui);
- signal_watcher_start(&tui->winch_handle, sigwinch_cb, SIGWINCH);
tinput_start(&tui->input);
}
@@ -546,7 +571,6 @@ static void tui_terminal_stop(TUIData *tui)
return;
}
tinput_stop(&tui->input);
- signal_watcher_stop(&tui->winch_handle);
// Position the cursor on the last screen line, below all the text
cursor_goto(tui, tui->height - 1, 0);
terminfo_stop(tui);
@@ -563,6 +587,7 @@ void tui_stop(TUIData *tui)
stream_set_blocking(tui->input.in_fd, true); // normalize stream (#2598)
tinput_destroy(&tui->input);
tui->stopped = true;
+ signal_watcher_stop(&tui->winch_handle);
signal_watcher_close(&tui->winch_handle, NULL);
uv_close((uv_handle_t *)&tui->startup_delay_timer, NULL);
}
@@ -781,7 +806,12 @@ static void update_attrs(TUIData *tui, int attr_id)
if (attrs.url >= 0) {
const char *url = urls.keys[attrs.url];
kv_size(tui->urlbuf) = 0;
- kv_printf(tui->urlbuf, "\x1b]8;;%s\x1b\\", url);
+
+ // Add some fixed offset to the URL ID to deconflict with other
+ // applications which may set their own IDs
+ const uint64_t id = 0xE1EA0000U + (uint32_t)attrs.url;
+
+ kv_printf(tui->urlbuf, "\x1b]8;id=%" PRIu64 ";%s\x1b\\", id, url);
out(tui, tui->urlbuf.items, kv_size(tui->urlbuf));
} else {
out(tui, S_LEN("\x1b]8;;\x1b\\"));
@@ -871,6 +901,7 @@ static void cursor_goto(TUIData *tui, int row, int col)
if (tui->url >= 0) {
out(tui, S_LEN("\x1b]8;;\x1b\\"));
tui->url = -1;
+ tui->print_attr_id = -1;
}
if (0 == row && 0 == col) {
@@ -992,7 +1023,7 @@ static void print_cell_at_pos(TUIData *tui, int row, int col, UCell *cell, bool
char buf[MAX_SCHAR_SIZE];
schar_get(buf, cell->data);
int c = utf_ptr2char(buf);
- bool is_ambiwidth = utf_ambiguous_width(c);
+ bool is_ambiwidth = utf_ambiguous_width(buf);
if (is_doublewidth && (is_ambiwidth || utf_char2cells(c) == 1)) {
// If the server used setcellwidths() to treat a single-width char as double-width,
// it needs to be treated like an ambiguous-width char.
@@ -1016,7 +1047,16 @@ static void clear_region(TUIData *tui, int top, int bot, int left, int right, in
{
UGrid *grid = &tui->grid;
- update_attrs(tui, attr_id);
+ // Setting the default colors is delayed until after startup to avoid flickering
+ // with the default colorscheme background. Consequently, any flush that happens
+ // during startup would result in clearing invalidated regions with zeroed
+ // clear_attrs, perceived as a black flicker. Reset attributes to clear with
+ // current terminal background instead (#28667, #28668).
+ if (tui->set_default_colors) {
+ update_attrs(tui, attr_id);
+ } else {
+ unibi_out(tui, unibi_exit_attribute_mode);
+ }
// Background is set to the default color and the right edge matches the
// screen end, try to use terminal codes for clearing the requested area.
@@ -1159,7 +1199,7 @@ static CursorShape tui_cursor_decode_shape(const char *shape_str)
return shape;
}
-static cursorentry_T decode_cursor_entry(Dictionary args)
+static cursorentry_T decode_cursor_entry(Dict args)
{
cursorentry_T r = shape_table[0];
@@ -1191,8 +1231,8 @@ void tui_mode_info_set(TUIData *tui, bool guicursor_enabled, Array args)
// cursor style entries as defined by `shape_table`.
for (size_t i = 0; i < args.size; i++) {
- assert(args.items[i].type == kObjectTypeDictionary);
- cursorentry_T r = decode_cursor_entry(args.items[i].data.dictionary);
+ assert(args.items[i].type == kObjectTypeDict);
+ cursorentry_T r = decode_cursor_entry(args.items[i].data.dict);
tui->cursor_shapes[i] = r;
}
@@ -1419,6 +1459,7 @@ void tui_default_colors_set(TUIData *tui, Integer rgb_fg, Integer rgb_bg, Intege
tui->clear_attrs.cterm_bg_color = (int16_t)cterm_bg;
tui->print_attr_id = -1;
+ tui->set_default_colors = true;
invalidate(tui, 0, tui->grid.height, 0, tui->grid.width);
}
@@ -1498,7 +1539,7 @@ static void show_verbose_terminfo(TUIData *tui)
ADD_C(args, BOOLEAN_OBJ(true)); // history
MAXSIZE_TEMP_DICT(opts, 1);
PUT_C(opts, "verbose", BOOLEAN_OBJ(true));
- ADD_C(args, DICTIONARY_OBJ(opts));
+ ADD_C(args, DICT_OBJ(opts));
rpc_send_event(ui_client_channel_id, "nvim_echo", args);
xfree(str.data);
}
@@ -1694,6 +1735,14 @@ static void ensure_space_buf_size(TUIData *tui, size_t len)
}
}
+void tui_set_size(TUIData *tui, int width, int height)
+ FUNC_ATTR_NONNULL_ALL
+{
+ tui->width = width;
+ tui->height = height;
+ ensure_space_buf_size(tui, (size_t)tui->width);
+}
+
/// Tries to get the user's wanted dimensions (columns and rows) for the entire
/// application (i.e., the host terminal).
void tui_guess_size(TUIData *tui)
@@ -1701,7 +1750,7 @@ void tui_guess_size(TUIData *tui)
int width = 0;
int height = 0;
- // 1 - try from a system call(ioctl/TIOCGWINSZ on unix)
+ // 1 - try from a system call (ioctl/TIOCGWINSZ on unix)
if (tui->out_isatty
&& !uv_tty_get_winsize(&tui->output_handle.tty, &width, &height)) {
goto end;
@@ -1728,9 +1777,7 @@ void tui_guess_size(TUIData *tui)
height = DFLT_ROWS;
}
- tui->width = width;
- tui->height = height;
- ensure_space_buf_size(tui, (size_t)tui->width);
+ tui_set_size(tui, width, height);
// Redraw on SIGWINCH event if size didn't change. #23411
ui_client_set_size(width, height);
@@ -1852,20 +1899,12 @@ static int unibi_find_ext_bool(unibi_term *ut, const char *name)
return -1;
}
-/// Determine if the terminal supports truecolor or not:
+/// Determine if the terminal supports truecolor or not.
///
-/// 1. If $COLORTERM is "24bit" or "truecolor", return true
-/// 2. Else, check terminfo for Tc, RGB, setrgbf, or setrgbb capabilities. If
-/// found, return true
-/// 3. Else, return false
+/// If terminfo contains Tc, RGB, or both setrgbf and setrgbb capabilities, return true.
static bool term_has_truecolor(TUIData *tui, const char *colorterm)
{
- // Check $COLORTERM
- if (strequal(colorterm, "truecolor") || strequal(colorterm, "24bit")) {
- return true;
- }
-
- // Check for Tc and RGB
+ // Check for Tc or RGB
for (size_t i = 0; i < unibi_count_ext_bool(tui->ut); i++) {
const char *n = unibi_get_ext_bool_name(tui->ut, i);
if (n && (!strcmp(n, "Tc") || !strcmp(n, "RGB"))) {
@@ -2505,7 +2544,7 @@ static const char *tui_get_stty_erase(int fd)
struct termios t;
if (tcgetattr(fd, &t) != -1) {
stty_erase[0] = (char)t.c_cc[VERASE];
- stty_erase[1] = '\0';
+ stty_erase[1] = NUL;
DLOG("stty/termios:erase=%s", stty_erase);
}
#endif
diff --git a/src/nvim/tui/tui_defs.h b/src/nvim/tui/tui_defs.h
index c5149d4829..bd99d6b0ad 100644
--- a/src/nvim/tui/tui_defs.h
+++ b/src/nvim/tui/tui_defs.h
@@ -4,6 +4,8 @@ typedef struct TUIData TUIData;
typedef enum {
kTermModeSynchronizedOutput = 2026,
+ kTermModeGraphemeClusters = 2027,
+ kTermModeResizeEvents = 2048,
} TermMode;
typedef enum {
diff --git a/src/nvim/types_defs.h b/src/nvim/types_defs.h
index 934159b9d9..2dd2b01adf 100644
--- a/src/nvim/types_defs.h
+++ b/src/nvim/types_defs.h
@@ -56,3 +56,9 @@ typedef struct regprog regprog_T;
typedef struct syn_state synstate_T;
typedef struct terminal Terminal;
typedef struct window_S win_T;
+
+typedef struct {
+ uint32_t nitems;
+ uint32_t nbytes;
+ char data[];
+} AdditionalData;
diff --git a/src/nvim/ui.c b/src/nvim/ui.c
index 9bb66b886e..365aa5c74f 100644
--- a/src/nvim/ui.c
+++ b/src/nvim/ui.c
@@ -144,11 +144,15 @@ void ui_free_all_mem(void)
/// Returns true if any `rgb=true` UI is attached.
bool ui_rgb_attached(void)
{
- if (!headless_mode && p_tgc) {
+ if (p_tgc) {
return true;
}
for (size_t i = 0; i < ui_count; i++) {
- if (uis[i]->rgb) {
+ // We do not consider the TUI in this loop because we already checked for 'termguicolors' at the
+ // beginning of this function. In this loop, we are checking to see if any _other_ UIs which
+ // support RGB are attached.
+ bool tui = uis[i]->stdin_tty || uis[i]->stdout_tty;
+ if (!tui && uis[i]->rgb) {
return true;
}
}
@@ -178,9 +182,10 @@ bool ui_override(void)
return false;
}
-bool ui_active(void)
+/// Gets the number of UIs connected to this server.
+size_t ui_active(void)
{
- return ui_count > 0;
+ return ui_count;
}
void ui_refresh(void)
@@ -193,7 +198,7 @@ void ui_refresh(void)
int height = INT_MAX;
bool ext_widgets[kUIExtCount];
bool inclusive = ui_override();
- memset(ext_widgets, ui_active(), ARRAY_SIZE(ext_widgets));
+ memset(ext_widgets, !!ui_active(), ARRAY_SIZE(ext_widgets));
for (size_t i = 0; i < ui_count; i++) {
RemoteUI *ui = uis[i];
@@ -654,7 +659,7 @@ Array ui_array(Arena *arena)
Array all_uis = arena_array(arena, ui_count);
for (size_t i = 0; i < ui_count; i++) {
RemoteUI *ui = uis[i];
- Dictionary info = arena_dict(arena, 10 + kUIExtCount);
+ Dict info = arena_dict(arena, 10 + kUIExtCount);
PUT_C(info, "width", INTEGER_OBJ(ui->width));
PUT_C(info, "height", INTEGER_OBJ(ui->height));
PUT_C(info, "rgb", BOOLEAN_OBJ(ui->rgb));
@@ -677,7 +682,7 @@ Array ui_array(Arena *arena)
}
PUT_C(info, "chan", INTEGER_OBJ((Integer)ui->channel_id));
- ADD_C(all_uis, DICTIONARY_OBJ(info));
+ ADD_C(all_uis, DICT_OBJ(info));
}
return all_uis;
}
diff --git a/src/nvim/ui_client.c b/src/nvim/ui_client.c
index 4f36cae4b2..adaf3eadb8 100644
--- a/src/nvim/ui_client.c
+++ b/src/nvim/ui_client.c
@@ -22,7 +22,9 @@
#include "nvim/memory_defs.h"
#include "nvim/msgpack_rpc/channel.h"
#include "nvim/msgpack_rpc/channel_defs.h"
+#include "nvim/os/os.h"
#include "nvim/os/os_defs.h"
+#include "nvim/profile.h"
#include "nvim/tui/tui.h"
#include "nvim/tui/tui_defs.h"
#include "nvim/ui.h"
@@ -80,12 +82,15 @@ uint64_t ui_client_start_server(int argc, char **argv)
return channel->id;
}
+/// Attaches this client to the UI channel, and sets its client info.
void ui_client_attach(int width, int height, char *term, bool rgb)
{
+ //
+ // nvim_ui_attach
+ //
MAXSIZE_TEMP_ARRAY(args, 3);
ADD_C(args, INTEGER_OBJ(width));
ADD_C(args, INTEGER_OBJ(height));
-
MAXSIZE_TEMP_DICT(opts, 9);
PUT_C(opts, "rgb", BOOLEAN_OBJ(rgb));
PUT_C(opts, "ext_linegrid", BOOLEAN_OBJ(true));
@@ -93,7 +98,6 @@ void ui_client_attach(int width, int height, char *term, bool rgb)
if (term) {
PUT_C(opts, "term_name", CSTR_AS_OBJ(term));
}
-
PUT_C(opts, "term_colors", INTEGER_OBJ(t_colors));
if (!ui_client_is_remote) {
PUT_C(opts, "stdin_tty", BOOLEAN_OBJ(stdin_isatty));
@@ -103,10 +107,44 @@ void ui_client_attach(int width, int height, char *term, bool rgb)
ui_client_forward_stdin = false; // stdin shouldn't be forwarded again #22292
}
}
- ADD_C(args, DICTIONARY_OBJ(opts));
+ ADD_C(args, DICT_OBJ(opts));
rpc_send_event(ui_client_channel_id, "nvim_ui_attach", args);
ui_client_attached = true;
+
+ TIME_MSG("nvim_ui_attach");
+
+ //
+ // nvim_set_client_info
+ //
+ MAXSIZE_TEMP_ARRAY(args2, 5);
+ ADD_C(args2, CSTR_AS_OBJ("nvim-tui")); // name
+ Object m = api_metadata();
+ Dict version = { 0 };
+ assert(m.data.dict.size > 0);
+ for (size_t i = 0; i < m.data.dict.size; i++) {
+ if (strequal(m.data.dict.items[i].key.data, "version")) {
+ version = m.data.dict.items[i].value.data.dict;
+ break;
+ } else if (i + 1 == m.data.dict.size) {
+ abort();
+ }
+ }
+ ADD_C(args2, DICT_OBJ(version)); // version
+ ADD_C(args2, CSTR_AS_OBJ("ui")); // type
+ // We don't send api_metadata.functions as the "methods" because:
+ // 1. it consumes memory.
+ // 2. it is unlikely to be useful, since the peer can just call `nvim_get_api`.
+ // 3. nvim_set_client_info expects a dict instead of an array.
+ ADD_C(args2, ARRAY_OBJ((Array)ARRAY_DICT_INIT)); // methods
+ MAXSIZE_TEMP_DICT(info, 9); // attributes
+ PUT_C(info, "website", CSTR_AS_OBJ("https://neovim.io"));
+ PUT_C(info, "license", CSTR_AS_OBJ("Apache 2"));
+ PUT_C(info, "pid", INTEGER_OBJ(os_get_pid()));
+ ADD_C(args2, DICT_OBJ(info)); // attributes
+ rpc_send_event(ui_client_channel_id, "nvim_set_client_info", args2);
+
+ TIME_MSG("nvim_set_client_info");
}
void ui_client_detach(void)
@@ -126,6 +164,13 @@ void ui_client_run(bool remote_ui)
ui_client_attach(width, height, term, rgb);
+ // TODO(justinmk): this is for log_spec. Can remove this after nvim_log #7062 is merged.
+ if (os_env_exists("__NVIM_TEST_LOG")) {
+ ELOG("test log message");
+ }
+
+ time_finish();
+
// os_exit() will be invoked when the client channel detaches
while (true) {
LOOP_PROCESS_EVENTS(&main_loop, resize_events, -1);
@@ -170,11 +215,11 @@ Object handle_ui_client_redraw(uint64_t channel_id, Array args, Arena *arena, Er
return NIL;
}
-static HlAttrs ui_client_dict2hlattrs(Dictionary d, bool rgb)
+static HlAttrs ui_client_dict2hlattrs(Dict d, bool rgb)
{
Error err = ERROR_INIT;
Dict(highlight) dict = KEYDICT_INIT;
- if (!api_dict_to_keydict(&dict, KeyDict_highlight_get_field, d, &err)) {
+ if (!api_dict_to_keydict(&dict, DictHash(highlight), d, &err)) {
// TODO(bfredl): log "err"
return HLATTRS_INIT;
}
diff --git a/src/nvim/ui_client.h b/src/nvim/ui_client.h
index d3be9882aa..928bd4c0a5 100644
--- a/src/nvim/ui_client.h
+++ b/src/nvim/ui_client.h
@@ -14,7 +14,7 @@ EXTERN size_t grid_line_buf_size INIT( = 0);
EXTERN schar_T *grid_line_buf_char INIT( = NULL);
EXTERN sattr_T *grid_line_buf_attr INIT( = NULL);
-// ID of the ui client channel. If zero, the client is not running.
+// Client-side UI channel. Zero during early startup or if not a (--remote-ui) UI client.
EXTERN uint64_t ui_client_channel_id INIT( = 0);
// exit status from embedded nvim process
diff --git a/src/nvim/undo.c b/src/nvim/undo.c
index ba720c9f6a..15c8e0b283 100644
--- a/src/nvim/undo.c
+++ b/src/nvim/undo.c
@@ -92,6 +92,7 @@
#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval/funcs.h"
#include "nvim/eval/typval.h"
#include "nvim/ex_cmds_defs.h"
@@ -342,8 +343,7 @@ static OptInt get_undolevel(buf_T *buf)
static inline void zero_fmark_additional_data(fmark_T *fmarks)
{
for (size_t i = 0; i < NMARKS; i++) {
- tv_dict_unref(fmarks[i].additional_data);
- fmarks[i].additional_data = NULL;
+ XFREE_CLEAR(fmarks[i].additional_data);
}
}
@@ -1131,17 +1131,11 @@ static void serialize_pos(bufinfo_T *bi, pos_T pos)
static void unserialize_pos(bufinfo_T *bi, pos_T *pos)
{
pos->lnum = undo_read_4c(bi);
- if (pos->lnum < 0) {
- pos->lnum = 0;
- }
+ pos->lnum = MAX(pos->lnum, 0);
pos->col = undo_read_4c(bi);
- if (pos->col < 0) {
- pos->col = 0;
- }
+ pos->col = MAX(pos->col, 0);
pos->coladd = undo_read_4c(bi);
- if (pos->coladd < 0) {
- pos->coladd = 0;
- }
+ pos->coladd = MAX(pos->coladd, 0);
}
/// Serializes "info".
@@ -1208,14 +1202,12 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
// Strip any sticky and executable bits.
perm = perm & 0666;
- int fd;
-
// If the undo file already exists, verify that it actually is an undo
// file, and delete it.
if (os_path_exists(file_name)) {
if (name == NULL || !forceit) {
// Check we can read it and it's an undo file.
- fd = os_open(file_name, O_RDONLY, 0);
+ int fd = os_open(file_name, O_RDONLY, 0);
if (fd < 0) {
if (name != NULL || p_verbose > 0) {
if (name == NULL) {
@@ -1260,7 +1252,7 @@ void u_write_undo(const char *const name, const bool forceit, buf_T *const buf,
goto theend;
}
- fd = os_open(file_name, O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm);
+ int fd = os_open(file_name, O_CREAT|O_WRONLY|O_EXCL|O_NOFOLLOW, perm);
if (fd < 0) {
semsg(_(e_not_open), file_name);
goto theend;
@@ -2000,9 +1992,7 @@ void undo_time(int step, bool sec, bool file, bool absolute)
target = curbuf->b_u_seq_cur + step;
}
if (step < 0) {
- if (target < 0) {
- target = 0;
- }
+ target = MAX(target, 0);
closest = -1;
} else {
if (dosec) {
@@ -2395,9 +2385,7 @@ static void u_undoredo(bool undo, bool do_buf_event)
}
// Set the '[ mark.
- if (top + 1 < curbuf->b_op_start.lnum) {
- curbuf->b_op_start.lnum = top + 1;
- }
+ curbuf->b_op_start.lnum = MIN(curbuf->b_op_start.lnum, top + 1);
// Set the '] mark.
if (newsize == 0 && top + 1 > curbuf->b_op_end.lnum) {
curbuf->b_op_end.lnum = top + 1;
@@ -2418,12 +2406,8 @@ static void u_undoredo(bool undo, bool do_buf_event)
}
// Ensure the '[ and '] marks are within bounds.
- if (curbuf->b_op_start.lnum > curbuf->b_ml.ml_line_count) {
- curbuf->b_op_start.lnum = curbuf->b_ml.ml_line_count;
- }
- if (curbuf->b_op_end.lnum > curbuf->b_ml.ml_line_count) {
- curbuf->b_op_end.lnum = curbuf->b_ml.ml_line_count;
- }
+ curbuf->b_op_start.lnum = MIN(curbuf->b_op_start.lnum, curbuf->b_ml.ml_line_count);
+ curbuf->b_op_end.lnum = MIN(curbuf->b_op_end.lnum, curbuf->b_ml.ml_line_count);
// Adjust Extmarks
if (undo) {
diff --git a/src/nvim/usercmd.c b/src/nvim/usercmd.c
index e3d9dc5f54..d32e0ee319 100644
--- a/src/nvim/usercmd.c
+++ b/src/nvim/usercmd.c
@@ -98,6 +98,7 @@ static const char *command_complete[] = {
[EXPAND_USER_VARS] = "var",
[EXPAND_BREAKPOINT] = "breakpoint",
[EXPAND_SCRIPTNAMES] = "scriptnames",
+ [EXPAND_DIRS_IN_CDPATH] = "dir_in_path",
};
/// List of names of address types. Must be alphabetical for completion.
@@ -414,7 +415,7 @@ char *get_user_cmd_complete(expand_T *xp, int idx)
return NULL;
}
char *cmd_compl = get_command_complete(idx);
- if (cmd_compl == NULL) {
+ if (cmd_compl == NULL || idx == EXPAND_USER_LUA) {
return "";
}
return cmd_compl;
@@ -580,7 +581,7 @@ static void uc_list(char *name, size_t name_len)
IObuff[len++] = ' ';
} while ((int64_t)len < 25 - over);
- IObuff[len] = '\0';
+ IObuff[len] = NUL;
msg_outtrans(IObuff, 0);
if (cmd->uc_luaref != LUA_NOREF) {
@@ -804,9 +805,7 @@ invalid_count:
}
}
- if (*def < 0) {
- *def = 0;
- }
+ *def = MAX(*def, 0);
} else if (STRNICMP(attr, "complete", attrlen) == 0) {
if (val == NULL) {
semsg(_(e_argument_required_for_str), "-complete");
@@ -831,7 +830,7 @@ invalid_count:
}
} else {
char ch = attr[len];
- attr[len] = '\0';
+ attr[len] = NUL;
semsg(_("E181: Invalid attribute: %s"), attr);
attr[len] = ch;
return FAIL;
@@ -1274,9 +1273,9 @@ static size_t add_cmd_modifier(char *buf, char *mod_str, bool *multi_mods)
if (buf != NULL) {
if (*multi_mods) {
- STRCAT(buf, " ");
+ strcat(buf, " ");
}
- STRCAT(buf, mod_str);
+ strcat(buf, mod_str);
}
*multi_mods = true;
@@ -1364,7 +1363,7 @@ size_t uc_mods(char *buf, const cmdmod_T *cmod, bool quote)
if (quote) {
*buf++ = '"';
}
- *buf = '\0';
+ *buf = NUL;
}
// the modifiers that are simple flags
@@ -1744,14 +1743,14 @@ int do_ucmd(exarg_T *eap, bool preview)
/// @param buf Buffer to inspect, or NULL to get global commands.
///
/// @return Map of maps describing commands
-Dictionary commands_array(buf_T *buf, Arena *arena)
+Dict commands_array(buf_T *buf, Arena *arena)
{
garray_T *gap = (buf == NULL) ? &ucmds : &buf->b_ucmds;
- Dictionary rv = arena_dict(arena, (size_t)gap->ga_len);
+ Dict rv = arena_dict(arena, (size_t)gap->ga_len);
for (int i = 0; i < gap->ga_len; i++) {
char arg[2] = { 0, 0 };
- Dictionary d = arena_dict(arena, 14);
+ Dict d = arena_dict(arena, 14);
ucmd_T *cmd = USER_CMD_GA(gap, i);
PUT_C(d, "name", CSTR_AS_OBJ(cmd->uc_name));
@@ -1815,7 +1814,7 @@ Dictionary commands_array(buf_T *buf, Arena *arena)
}
PUT_C(d, "addr", obj);
- PUT_C(rv, cmd->uc_name, DICTIONARY_OBJ(d));
+ PUT_C(rv, cmd->uc_name, DICT_OBJ(d));
}
return rv;
}
diff --git a/src/nvim/version.c b/src/nvim/version.c
index c392362bf4..368bf79cf4 100644
--- a/src/nvim/version.c
+++ b/src/nvim/version.c
@@ -1069,7 +1069,7 @@ static const int included_patches[] = {
1416,
1415,
1414,
- // 1413,
+ 1413,
1412,
1411,
1410,
@@ -1376,7 +1376,7 @@ static const int included_patches[] = {
1109,
1108,
1107,
- // 1106,
+ 1106,
1105,
1104,
1103,
diff --git a/src/nvim/viml/parser/expressions.c b/src/nvim/viml/parser/expressions.c
index 3403fb7926..7fb4c62b35 100644
--- a/src/nvim/viml/parser/expressions.c
+++ b/src/nvim/viml/parser/expressions.c
@@ -1264,21 +1264,12 @@ static bool viml_pexpr_handle_bop(const ParserState *const pstate, ExprASTStack
|| bop_node->type == kExprNodeSubscript)
? kEOpLvlSubscript
: node_lvl(*bop_node));
-#ifndef NDEBUG
- const ExprOpAssociativity bop_node_ass = (
- (bop_node->type == kExprNodeCall
- || bop_node->type == kExprNodeSubscript)
- ? kEOpAssLeft
- : node_ass(*bop_node));
-#endif
do {
ExprASTNode **new_top_node_p = kv_last(*ast_stack);
ExprASTNode *new_top_node = *new_top_node_p;
assert(new_top_node != NULL);
const ExprOpLvl new_top_node_lvl = node_lvl(*new_top_node);
const ExprOpAssociativity new_top_node_ass = node_ass(*new_top_node);
- assert(bop_node_lvl != new_top_node_lvl
- || bop_node_ass == new_top_node_ass);
if (top_node_p != NULL
&& ((bop_node_lvl > new_top_node_lvl
|| (bop_node_lvl == new_top_node_lvl
@@ -3014,8 +3005,7 @@ viml_pexpr_parse_end:
break;
case kExprNodeCurlyBracesIdentifier:
// Until trailing "}" it is impossible to distinguish curly braces
- // identifier and dictionary, so it must not appear in the stack like
- // this.
+ // identifier and Dict, so it must not appear in the stack like this.
abort();
case kExprNodeInteger:
case kExprNodeFloat:
diff --git a/src/nvim/viml/parser/expressions.h b/src/nvim/viml/parser/expressions.h
index ba54c4de07..60c3db8c8f 100644
--- a/src/nvim/viml/parser/expressions.h
+++ b/src/nvim/viml/parser/expressions.h
@@ -216,7 +216,7 @@ typedef enum {
/// kExprNodeCurlyBracesIdentifier.
kExprNodeUnknownFigure,
kExprNodeLambda, ///< Lambda.
- kExprNodeDictLiteral, ///< Dictionary literal.
+ kExprNodeDictLiteral, ///< Dict literal.
kExprNodeCurlyBracesIdentifier, ///< Part of the curly braces name.
kExprNodeComma, ///< Comma “operator”.
kExprNodeColon, ///< Colon “operator”.
diff --git a/src/nvim/viml/parser/parser.h b/src/nvim/viml/parser/parser.h
index 31decdc798..c4ebc1589a 100644
--- a/src/nvim/viml/parser/parser.h
+++ b/src/nvim/viml/parser/parser.h
@@ -5,13 +5,13 @@
#include <stddef.h>
#include "klib/kvec.h"
-#include "nvim/func_attr.h"
#include "nvim/mbyte_defs.h"
#include "nvim/viml/parser/parser_defs.h" // IWYU pragma: keep
-static inline void viml_parser_init(ParserState *ret_pstate, ParserLineGetter get_line,
- void *cookie, ParserHighlight *colors)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ARG(1, 2);
+#ifdef INCLUDE_GENERATED_DECLARATIONS
+# include "viml/parser/parser.h.generated.h"
+# include "viml/parser/parser.h.inline.generated.h"
+#endif
/// Initialize a new parser state instance
///
@@ -22,6 +22,7 @@ static inline void viml_parser_init(ParserState *ret_pstate, ParserLineGetter ge
/// needed.
static inline void viml_parser_init(ParserState *const ret_pstate, const ParserLineGetter get_line,
void *const cookie, ParserHighlight *const colors)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ARG(1, 2)
{
*ret_pstate = (ParserState) {
.reader = {
@@ -37,9 +38,6 @@ static inline void viml_parser_init(ParserState *const ret_pstate, const ParserL
kvi_init(ret_pstate->stack);
}
-static inline void viml_parser_advance(ParserState *pstate, size_t len)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL;
-
/// Advance position by a given number of bytes
///
/// At maximum advances to the next line.
@@ -47,6 +45,7 @@ static inline void viml_parser_advance(ParserState *pstate, size_t len)
/// @param pstate Parser state to advance.
/// @param[in] len Number of bytes to advance.
static inline void viml_parser_advance(ParserState *const pstate, const size_t len)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
{
assert(pstate->pos.line == kv_size(pstate->reader.lines) - 1);
const ParserLine pline = kv_last(pstate->reader.lines);
@@ -58,10 +57,6 @@ static inline void viml_parser_advance(ParserState *const pstate, const size_t l
}
}
-static inline void viml_parser_highlight(ParserState *pstate, ParserPosition start, size_t len,
- const char *group)
- REAL_FATTR_ALWAYS_INLINE REAL_FATTR_NONNULL_ALL;
-
/// Record highlighting of some region of text
///
/// @param pstate Parser state to work with.
@@ -70,6 +65,7 @@ static inline void viml_parser_highlight(ParserState *pstate, ParserPosition sta
/// @param[in] group Highlight group.
static inline void viml_parser_highlight(ParserState *const pstate, const ParserPosition start,
const size_t len, const char *const group)
+ FUNC_ATTR_ALWAYS_INLINE FUNC_ATTR_NONNULL_ALL
{
if (pstate->colors == NULL || len == 0) {
return;
@@ -83,7 +79,3 @@ static inline void viml_parser_highlight(ParserState *const pstate, const Parser
.group = group,
}));
}
-
-#ifdef INCLUDE_GENERATED_DECLARATIONS
-# include "viml/parser/parser.h.generated.h"
-#endif
diff --git a/src/nvim/window.c b/src/nvim/window.c
index 1a6c3f7263..d3280a3478 100644
--- a/src/nvim/window.c
+++ b/src/nvim/window.c
@@ -22,6 +22,7 @@
#include "nvim/diff.h"
#include "nvim/drawscreen.h"
#include "nvim/edit.h"
+#include "nvim/errors.h"
#include "nvim/eval.h"
#include "nvim/eval/typval.h"
#include "nvim/eval/typval_defs.h"
@@ -72,6 +73,7 @@
#include "nvim/statusline.h"
#include "nvim/strings.h"
#include "nvim/syntax.h"
+#include "nvim/tag.h"
#include "nvim/terminal.h"
#include "nvim/types_defs.h"
#include "nvim/ui.h"
@@ -357,13 +359,13 @@ newwindow:
wp = lastwin; // wrap around
}
while (wp != NULL && wp->w_floating
- && !wp->w_config.focusable) {
+ && (wp->w_config.hide || !wp->w_config.focusable)) {
wp = wp->w_prev;
}
} else { // go to next window
wp = curwin->w_next;
while (wp != NULL && wp->w_floating
- && !wp->w_config.focusable) {
+ && (wp->w_config.hide || !wp->w_config.focusable)) {
wp = wp->w_next;
}
if (wp == NULL) {
@@ -796,6 +798,19 @@ int win_fdccol_count(win_T *wp)
return fdc[0] - '0';
}
+/// Merges two window configs, freeing replaced fields if necessary.
+void merge_win_config(WinConfig *dst, const WinConfig src)
+ FUNC_ATTR_NONNULL_ALL
+{
+ if (dst->title_chunks.items != src.title_chunks.items) {
+ clear_virttext(&dst->title_chunks);
+ }
+ if (dst->footer_chunks.items != src.footer_chunks.items) {
+ clear_virttext(&dst->footer_chunks);
+ }
+ *dst = src;
+}
+
void ui_ext_win_position(win_T *wp, bool validate)
{
wp->w_pos_changed = false;
@@ -1111,12 +1126,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_fl
if (new_size == 0) {
new_size = oldwin->w_width / 2;
}
- if (new_size > available - minwidth - 1) {
- new_size = available - minwidth - 1;
- }
- if (new_size < wmw1) {
- new_size = wmw1;
- }
+ new_size = MAX(MIN(new_size, available - minwidth - 1), wmw1);
// if it doesn't fit in the current window, need win_equal()
if (oldwin->w_width - new_size - 1 < p_wmw) {
@@ -1197,12 +1207,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_fl
new_size = oldwin_height / 2;
}
- if (new_size > available - minheight - STATUS_HEIGHT) {
- new_size = available - minheight - STATUS_HEIGHT;
- }
- if (new_size < wmh1) {
- new_size = wmh1;
- }
+ new_size = MAX(MIN(new_size, available - minheight - STATUS_HEIGHT), wmh1);
// if it doesn't fit in the current window, need win_equal()
if (oldwin_height - new_size - STATUS_HEIGHT < p_wmh) {
@@ -1296,7 +1301,7 @@ win_T *win_split_ins(int size, int flags, win_T *new_wp, int dir, frame_T *to_fl
new_frame(wp);
// non-floating window doesn't store float config or have a border.
- wp->w_config = WIN_CONFIG_INIT;
+ merge_win_config(&wp->w_config, WIN_CONFIG_INIT);
CLEAR_FIELD(wp->w_border_adj);
}
@@ -1729,12 +1734,8 @@ int make_windows(int count, bool vertical)
- (p_wh - p_wmh)) / ((int)p_wmh + STATUS_HEIGHT + global_winbar_height());
}
- if (maxcount < 2) {
- maxcount = 2;
- }
- if (count > maxcount) {
- count = maxcount;
- }
+ maxcount = MAX(maxcount, 2);
+ count = MIN(count, maxcount);
// add status line now, otherwise first window will be too big
if (count > 1) {
@@ -2188,9 +2189,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int
if (frame_has_win(fr, next_curwin)) {
room += (int)p_wiw - (int)p_wmw;
next_curwin_size = 0;
- if (new_size < p_wiw) {
- new_size = (int)p_wiw;
- }
+ new_size = MAX(new_size, (int)p_wiw);
} else {
// These windows don't use up room.
totwincount -= (n + (fr->fr_next == NULL ? extra_sep : 0)) / ((int)p_wmw + 1);
@@ -2253,9 +2252,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int
}
if (hnc) { // add next_curwin size
next_curwin_size -= (int)p_wiw - (m - n);
- if (next_curwin_size < 0) {
- next_curwin_size = 0;
- }
+ next_curwin_size = MAX(next_curwin_size, 0);
new_size += next_curwin_size;
room -= new_size - next_curwin_size;
} else {
@@ -2318,9 +2315,7 @@ static void win_equal_rec(win_T *next_curwin, bool current, frame_T *topfr, int
if (frame_has_win(fr, next_curwin)) {
room += (int)p_wh - (int)p_wmh;
next_curwin_size = 0;
- if (new_size < p_wh) {
- new_size = (int)p_wh;
- }
+ new_size = MAX(new_size, (int)p_wh);
} else {
// These windows don't use up room.
totwincount -= get_maximum_wincount(fr, (n + (fr->fr_next == NULL ? extra_sep : 0)));
@@ -2489,7 +2484,7 @@ void close_windows(buf_T *buf, bool keep_curwin)
// When the autocommand window is involved win_close() may need to print an error message.
for (win_T *wp = lastwin; wp != NULL && (is_aucmd_win(lastwin) || !one_window(wp));) {
if (wp->w_buffer == buf && (!keep_curwin || wp != curwin)
- && !(wp->w_closing || wp->w_buffer->b_locked > 0)) {
+ && !(win_locked(wp) || wp->w_buffer->b_locked > 0)) {
if (win_close(wp, false, false) == FAIL) {
// If closing the window fails give up, to avoid looping forever.
break;
@@ -2511,7 +2506,7 @@ void close_windows(buf_T *buf, bool keep_curwin)
// Start from tp_lastwin to close floating windows with the same buffer first.
for (win_T *wp = tp->tp_lastwin; wp != NULL; wp = wp->w_prev) {
if (wp->w_buffer == buf
- && !(wp->w_closing || wp->w_buffer->b_locked > 0)) {
+ && !(win_locked(wp) || wp->w_buffer->b_locked > 0)) {
win_close_othertab(wp, false, tp);
// Start all over, the tab page may be closed and
@@ -2648,10 +2643,10 @@ static void win_close_buffer(win_T *win, int action, bool abort_if_last)
if (win->w_buffer != NULL) {
bufref_T bufref;
set_bufref(&bufref, curbuf);
- win->w_closing = true;
+ win->w_locked = true;
close_buffer(win, win->w_buffer, action, abort_if_last, true);
if (win_valid_any_tab(win)) {
- win->w_closing = false;
+ win->w_locked = false;
}
// Make sure curbuf is valid. It can become invalid if 'bufhidden' is
@@ -2679,7 +2674,7 @@ int win_close(win_T *win, bool free_buf, bool force)
return FAIL;
}
- if (win->w_closing
+ if (win_locked(win)
|| (win->w_buffer != NULL && win->w_buffer->b_locked > 0)) {
return FAIL; // window is already being closed
}
@@ -2754,22 +2749,22 @@ int win_close(win_T *win, bool free_buf, bool force)
if (!win_valid(win)) {
return FAIL;
}
- win->w_closing = true;
+ win->w_locked = true;
apply_autocmds(EVENT_BUFLEAVE, NULL, NULL, false, curbuf);
if (!win_valid(win)) {
return FAIL;
}
- win->w_closing = false;
+ win->w_locked = false;
if (last_window(win)) {
return FAIL;
}
}
- win->w_closing = true;
+ win->w_locked = true;
apply_autocmds(EVENT_WINLEAVE, NULL, NULL, false, curbuf);
if (!win_valid(win)) {
return FAIL;
}
- win->w_closing = false;
+ win->w_locked = false;
if (last_window(win)) {
return FAIL;
}
@@ -2816,9 +2811,6 @@ int win_close(win_T *win, bool free_buf, bool force)
// to split a window to avoid trouble.
split_disallowed++;
- // let terminal buffers know that this window dimensions may be ignored
- win->w_closing = true;
-
bool was_floating = win->w_floating;
if (ui_has(kUIMultigrid)) {
ui_call_win_close(win->w_grid_alloc.handle);
@@ -2872,7 +2864,7 @@ int win_close(win_T *win, bool free_buf, bool force)
break;
}
if (!wp->w_p_pvw && !bt_quickfix(wp->w_buffer)
- && !(wp->w_floating && !wp->w_config.focusable)) {
+ && !(wp->w_floating && (wp->w_config.hide || !wp->w_config.focusable))) {
curwin = wp;
break;
}
@@ -2967,7 +2959,7 @@ void win_close_othertab(win_T *win, int free_buf, tabpage_T *tp)
{
// Get here with win->w_buffer == NULL when win_close() detects the tab page
// changed.
- if (win->w_closing
+ if (win_locked(win)
|| (win->w_buffer != NULL && win->w_buffer->b_locked > 0)) {
return; // window is already being closed
}
@@ -3455,14 +3447,23 @@ static frame_T *win_altframe(win_T *win, tabpage_T *tp)
// Return the tabpage that will be used if the current one is closed.
static tabpage_T *alt_tabpage(void)
{
- // Use the next tab page if possible.
- if (curtab->tp_next != NULL) {
- return curtab->tp_next;
+ // Use the last accessed tab page, if possible.
+ if ((tcl_flags & TCL_USELAST) && valid_tabpage(lastused_tabpage)) {
+ return lastused_tabpage;
}
- // Find the last but one tab page.
+ // Use the next tab page, if possible.
+ bool forward = curtab->tp_next != NULL
+ && ((tcl_flags & TCL_LEFT) == 0 || curtab == first_tabpage);
+
tabpage_T *tp;
- for (tp = first_tabpage; tp->tp_next != curtab; tp = tp->tp_next) {}
+ if (forward) {
+ tp = curtab->tp_next;
+ } else {
+ // Use the previous tab page.
+ for (tp = first_tabpage; tp->tp_next != curtab; tp = tp->tp_next) {}
+ }
+
return tp;
}
@@ -3921,9 +3922,7 @@ static int frame_minwidth(frame_T *topfrp, win_T *next_curwin)
frame_T *frp;
FOR_ALL_FRAMES(frp, topfrp->fr_child) {
int n = frame_minwidth(frp, next_curwin);
- if (n > m) {
- m = n;
- }
+ m = MAX(m, n);
}
} else {
// Add up the minimal widths for all frames in this row.
@@ -4256,9 +4255,7 @@ int make_tabpages(int maxcount)
int count = maxcount;
// Limit to 'tabpagemax' tabs.
- if (count > p_tpm) {
- count = (int)p_tpm;
- }
+ count = MIN(count, (int)p_tpm);
// Don't execute autocommands while creating the tab pages. Must do that
// when putting the buffers in the windows.
@@ -5219,8 +5216,7 @@ void win_free(win_T *wp, tabpage_T *tp)
xfree(wp->w_lines);
for (int i = 0; i < wp->w_tagstacklen; i++) {
- xfree(wp->w_tagstack[i].tagname);
- xfree(wp->w_tagstack[i].user_data);
+ tagstack_clear_entry(&wp->w_tagstack[i]);
}
xfree(wp->w_localdir);
@@ -5418,14 +5414,10 @@ void win_new_screensize(void)
/// This only does the current tab page, others must be done when made active.
void win_new_screen_rows(void)
{
- int h = (int)ROWS_AVAIL;
-
if (firstwin == NULL) { // not initialized yet
return;
}
- if (h < frame_minheight(topframe, NULL)) {
- h = frame_minheight(topframe, NULL);
- }
+ int h = MAX((int)ROWS_AVAIL, frame_minheight(topframe, NULL));
// First try setting the heights of windows with 'winfixheight'. If
// that doesn't result in the right height, forget about that option.
@@ -5922,9 +5914,7 @@ static void frame_setheight(frame_T *curfrp, int height)
// Row of frames: Also need to resize frames left and right of this
// one. First check for the minimal height of these.
int h = frame_minheight(curfrp->fr_parent, NULL);
- if (height < h) {
- height = h;
- }
+ height = MAX(height, h);
frame_setheight(curfrp->fr_parent, height);
} else {
// Column of frames: try to change only frames in this column.
@@ -5959,9 +5949,7 @@ static void frame_setheight(frame_T *curfrp, int height)
win_T *wp = lastwin_nofloating();
room_cmdline = Rows - (int)p_ch - global_stl_height()
- (wp->w_winrow + wp->w_height + wp->w_hsep_height + wp->w_status_height);
- if (room_cmdline < 0) {
- room_cmdline = 0;
- }
+ room_cmdline = MAX(room_cmdline, 0);
}
if (height <= room + room_cmdline) {
@@ -5993,9 +5981,7 @@ static void frame_setheight(frame_T *curfrp, int height)
if (take > 0 && room_cmdline > 0) {
// use lines from cmdline first
- if (take < room_cmdline) {
- room_cmdline = take;
- }
+ room_cmdline = MIN(room_cmdline, take),
take -= room_cmdline;
topframe->fr_height += room_cmdline;
}
@@ -6057,12 +6043,7 @@ void win_setwidth_win(int width, win_T *wp)
// Always keep current window at least one column wide, even when
// 'winminwidth' is zero.
if (wp == curwin) {
- if (width < p_wmw) {
- width = (int)p_wmw;
- }
- if (width == 0) {
- width = 1;
- }
+ width = MAX(MAX(width, (int)p_wmw), 1);
} else if (width < 0) {
width = 0;
}
@@ -6100,9 +6081,7 @@ static void frame_setwidth(frame_T *curfrp, int width)
// Column of frames: Also need to resize frames above and below of
// this one. First check for the minimal width of these.
int w = frame_minwidth(curfrp->fr_parent, NULL);
- if (width < w) {
- width = w;
- }
+ width = MAX(width, w);
frame_setwidth(curfrp->fr_parent, width);
} else {
// Row of frames: try to change only frames in this row.
@@ -6299,9 +6278,7 @@ void win_drag_status_line(win_T *dragwin, int offset)
} else if (!p_ch_was_zero) {
room--;
}
- if (room < 0) {
- room = 0;
- }
+ room = MAX(room, 0);
// sum up the room of frames below of the current one
FOR_ALL_FRAMES(fr, curfr->fr_next) {
room += fr->fr_height - frame_minheight(fr, NULL);
@@ -6309,9 +6286,8 @@ void win_drag_status_line(win_T *dragwin, int offset)
fr = curfr; // put fr at window that grows
}
- if (room < offset) { // Not enough room
- offset = room; // Move as far as we can
- }
+ // If not enough room then move as far as we can
+ offset = MIN(offset, room);
if (offset <= 0) {
return;
}
@@ -6413,10 +6389,8 @@ void win_drag_vsep_line(win_T *dragwin, int offset)
fr = curfr; // put fr at window that grows
}
- // Not enough room
- if (room < offset) {
- offset = room; // Move as far as we can
- }
+ // If not enough room thn move as far as we can
+ offset = MIN(offset, room);
// No room at all, quit.
if (offset <= 0) {
@@ -6587,9 +6561,7 @@ void win_new_height(win_T *wp, int height)
{
// Don't want a negative height. Happens when splitting a tiny window.
// Will equalize heights soon to fix it.
- if (height < 0) {
- height = 0;
- }
+ height = MAX(height, 0);
if (wp->w_height == height) {
return; // nothing to do
}
@@ -6615,9 +6587,8 @@ void scroll_to_fraction(win_T *wp, int prev_height)
// Find a value for w_topline that shows the cursor at the same
// relative position in the window as before (more or less).
linenr_T lnum = wp->w_cursor.lnum;
- if (lnum < 1) { // can happen when starting up
- lnum = 1;
- }
+ // can happen when starting up
+ lnum = MAX(lnum, 1);
wp->w_wrow = (wp->w_fraction * height - 1) / FRACTION_MULT;
int line_size = plines_win_col(wp, lnum, wp->w_cursor.col) - 1;
int sline = wp->w_wrow - line_size;
@@ -6833,9 +6804,7 @@ void command_height(void)
break;
}
int h = frp->fr_height - frame_minheight(frp, NULL);
- if (h > p_ch - old_p_ch) {
- h = (int)p_ch - old_p_ch;
- }
+ h = MIN(h, (int)p_ch - old_p_ch);
old_p_ch += h;
frame_add_height(frp, -h);
frp = frp->fr_prev;
@@ -6853,9 +6822,7 @@ void command_height(void)
return;
}
- if (msg_row < cmdline_row) {
- msg_row = cmdline_row;
- }
+ msg_row = MAX(msg_row, cmdline_row);
redraw_cmdline = true;
}
frame_add_height(frp, (int)(old_p_ch - p_ch));
@@ -6880,142 +6847,6 @@ static void frame_add_height(frame_T *frp, int n)
}
}
-// Get the file name at the cursor.
-// If Visual mode is active, use the selected text if it's in one line.
-// Returns the name in allocated memory, NULL for failure.
-char *grab_file_name(int count, linenr_T *file_lnum)
-{
- int options = FNAME_MESS | FNAME_EXP | FNAME_REL | FNAME_UNESC;
- if (VIsual_active) {
- size_t len;
- char *ptr;
- if (get_visual_text(NULL, &ptr, &len) == FAIL) {
- return NULL;
- }
- // Only recognize ":123" here
- if (file_lnum != NULL && ptr[len] == ':' && isdigit((uint8_t)ptr[len + 1])) {
- char *p = ptr + len + 1;
-
- *file_lnum = getdigits_int32(&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);
-}
-
-// Return the file name under or after the cursor.
-//
-// The 'path' option is searched if the file name is not absolute.
-// The string returned has been alloc'ed and should be freed by the caller.
-// NULL is returned if the file name or file is not found.
-//
-// options:
-// FNAME_MESS give error messages
-// FNAME_EXP expand to path
-// FNAME_HYP check for hypertext link
-// FNAME_INCL apply "includeexpr"
-char *file_name_at_cursor(int options, int count, linenr_T *file_lnum)
-{
- return file_name_in_line(get_cursor_line_ptr(),
- curwin->w_cursor.col, options, count, curbuf->b_ffname,
- file_lnum);
-}
-
-/// @param rel_fname file we are searching relative to
-/// @param file_lnum line number after the file name
-///
-/// @return the name of the file under or after ptr[col]. Otherwise like file_name_at_cursor().
-char *file_name_in_line(char *line, int col, int options, int count, char *rel_fname,
- linenr_T *file_lnum)
-{
- // search forward for what could be the start of a file name
- char *ptr = line + col;
- while (*ptr != NUL && !vim_isfilec((uint8_t)(*ptr))) {
- MB_PTR_ADV(ptr);
- }
- if (*ptr == NUL) { // nothing found
- if (options & FNAME_MESS) {
- emsg(_("E446: No file name under cursor"));
- }
- return NULL;
- }
-
- size_t len;
- bool in_type = true;
- bool is_url = false;
-
- // Search backward for first char of the file name.
- // Go one char back to ":" before "//", or to the drive letter before ":\" (even if ":"
- // is not in 'isfname').
- while (ptr > line) {
- if ((len = (size_t)(utf_head_off(line, ptr - 1))) > 0) {
- ptr -= len + 1;
- } else if (vim_isfilec((uint8_t)ptr[-1]) || ((options & FNAME_HYP) && path_is_url(ptr - 1))) {
- ptr--;
- } else {
- break;
- }
- }
-
- // Search forward for the last char of the file name.
- // Also allow ":/" when ':' is not in 'isfname'.
- len = path_has_drive_letter(ptr) ? 2 : 0;
- while (vim_isfilec((uint8_t)ptr[len]) || (ptr[len] == '\\' && ptr[len + 1] == ' ')
- || ((options & FNAME_HYP) && path_is_url(ptr + len))
- || (is_url && vim_strchr(":?&=", (uint8_t)ptr[len]) != NULL)) {
- // After type:// we also include :, ?, & and = as valid characters, so that
- // http://google.com:8080?q=this&that=ok works.
- if ((ptr[len] >= 'A' && ptr[len] <= 'Z') || (ptr[len] >= 'a' && ptr[len] <= 'z')) {
- if (in_type && path_is_url(ptr + len + 1)) {
- is_url = true;
- }
- } else {
- in_type = false;
- }
-
- if (ptr[len] == '\\' && ptr[len + 1] == ' ') {
- // Skip over the "\" in "\ ".
- len++;
- }
- len += (size_t)(utfc_ptr2len(ptr + len));
- }
-
- // If there is trailing punctuation, remove it.
- // But don't remove "..", could be a directory name.
- if (len > 2 && vim_strchr(".,:;!", (uint8_t)ptr[len - 1]) != NULL
- && ptr[len - 2] != '.') {
- len--;
- }
-
- if (file_lnum != NULL) {
- const char *line_english = " line ";
- const char *line_transl = _(line_msg);
-
- // Get the number after the file name and a separator character.
- // Also accept " line 999" with and without the same translation as
- // used in last_set_msg().
- char *p = ptr + len;
- if (strncmp(p, line_english, strlen(line_english)) == 0) {
- p += strlen(line_english);
- } else if (strncmp(p, line_transl, strlen(line_transl)) == 0) {
- p += strlen(line_transl);
- } else {
- p = skipwhite(p);
- }
- if (*p != NUL) {
- if (!isdigit((uint8_t)(*p))) {
- p++; // skip the separator
- }
- p = skipwhite(p);
- if (isdigit((uint8_t)(*p))) {
- *file_lnum = (linenr_T)getdigits_long(&p, false, 0);
- }
- }
- }
-
- return find_file_name_in_path(ptr, len, options, count, rel_fname);
-}
-
/// Add or remove a status line from window(s), according to the
/// value of 'laststatus'.
///
@@ -7241,9 +7072,7 @@ int min_rows(void)
int total = 0;
FOR_ALL_TABS(tp) {
int n = frame_minheight(tp->tp_topframe, NULL);
- if (total < n) {
- total = n;
- }
+ total = MAX(total, n);
}
total += tabline_height() + global_stl_height();
if (p_ch > 0) {
@@ -7538,13 +7367,7 @@ static int int_cmp(const void *pa, const void *pb)
{
const int a = *(const int *)pa;
const int b = *(const int *)pb;
- if (a > b) {
- return 1;
- }
- if (a < b) {
- return -1;
- }
- return 0;
+ return a == b ? 0 : a < b ? -1 : 1;
}
/// Handle setting 'colorcolumn' or 'textwidth' in window "wp".
@@ -7626,6 +7449,12 @@ int get_last_winid(void)
return last_win_id;
}
+/// Don't let autocommands close the given window
+int win_locked(win_T *wp)
+{
+ return wp->w_locked;
+}
+
void win_get_tabwin(handle_T id, int *tabnr, int *winnr)
{
*tabnr = 0;
diff --git a/src/nvim/window.h b/src/nvim/window.h
index d20b799e20..9618ff1c2a 100644
--- a/src/nvim/window.h
+++ b/src/nvim/window.h
@@ -8,17 +8,6 @@
#include "nvim/option_defs.h" // IWYU pragma: keep
#include "nvim/types_defs.h" // IWYU pragma: keep
-/// Values for file_name_in_line()
-enum {
- FNAME_MESS = 1, ///< give error message
- FNAME_EXP = 2, ///< expand to path
- FNAME_HYP = 4, ///< check for hypertext link
- FNAME_INCL = 8, ///< apply 'includeexpr'
- FNAME_REL = 16, ///< ".." and "./" are relative to the (current)
- ///< file instead of the current directory
- FNAME_UNESC = 32, ///< remove backslashes used for escaping
-};
-
/// arguments for win_split()
enum {
WSP_ROOM = 0x01, ///< require enough room
diff --git a/src/nvim/winfloat.c b/src/nvim/winfloat.c
index e3ca0ff139..fdb65ad614 100644
--- a/src/nvim/winfloat.c
+++ b/src/nvim/winfloat.c
@@ -10,12 +10,15 @@
#include "nvim/ascii_defs.h"
#include "nvim/autocmd.h"
#include "nvim/buffer_defs.h"
+#include "nvim/decoration.h"
#include "nvim/drawscreen.h"
+#include "nvim/errors.h"
#include "nvim/globals.h"
#include "nvim/grid.h"
#include "nvim/grid_defs.h"
#include "nvim/macros_defs.h"
#include "nvim/memory.h"
+#include "nvim/message.h"
#include "nvim/mouse.h"
#include "nvim/move.h"
#include "nvim/option.h"
@@ -56,7 +59,7 @@ win_T *win_new_float(win_T *wp, bool last, WinConfig fconfig, Error *err)
if (!tp) {
return NULL;
}
- tp_last = tp->tp_lastwin;
+ tp_last = tp == curtab ? lastwin : tp->tp_lastwin;
while (tp_last->w_floating && tp_last->w_prev) {
tp_last = tp_last->w_prev;
}
@@ -200,7 +203,7 @@ void win_config_float(win_T *wp, WinConfig fconfig)
wp->w_config.border_hl_ids,
sizeof fconfig.border_hl_ids) != 0);
- wp->w_config = fconfig;
+ merge_win_config(&wp->w_config, fconfig);
bool has_border = wp->w_floating && wp->w_config.border;
for (int i = 0; i < 4; i++) {
@@ -358,6 +361,21 @@ win_T *win_float_find_altwin(const win_T *win, const tabpage_T *tp)
: tp->tp_firstwin;
}
+/// Inline helper function for handling errors and cleanup in win_float_create.
+static inline win_T *handle_error_and_cleanup(win_T *wp, Error *err)
+{
+ if (ERROR_SET(err)) {
+ emsg(err->msg);
+ api_clear_error(err);
+ }
+ if (wp) {
+ win_remove(wp, NULL);
+ win_free(wp, NULL);
+ }
+ unblock_autocmds();
+ return NULL;
+}
+
/// create a floating preview window.
///
/// @param[in] bool enter floating window.
@@ -380,23 +398,25 @@ win_T *win_float_create(bool enter, bool new_buf)
block_autocmds();
win_T *wp = win_new_float(NULL, false, config, &err);
if (!wp) {
- unblock_autocmds();
- return NULL;
+ return handle_error_and_cleanup(wp, &err);
}
if (new_buf) {
Buffer b = nvim_create_buf(false, true, &err);
if (!b) {
- win_remove(wp, NULL);
- win_free(wp, NULL);
- unblock_autocmds();
- return NULL;
+ return handle_error_and_cleanup(wp, &err);
}
buf_T *buf = find_buffer_by_handle(b, &err);
+ if (!buf) {
+ return handle_error_and_cleanup(wp, &err);
+ }
buf->b_p_bl = false; // unlist
set_option_direct_for(kOptBufhidden, STATIC_CSTR_AS_OPTVAL("wipe"), OPT_LOCAL, 0, kOptReqBuf,
buf);
win_set_buf(wp, buf, &err);
+ if (ERROR_SET(&err)) {
+ return handle_error_and_cleanup(wp, &err);
+ }
}
unblock_autocmds();
wp->w_p_diff = false;